Skip to content

Commit 7184f89

Browse files
authored
Merge pull request #22 from unisoncomputing/social-image-definition
wip
2 parents 23686cd + ddd0c95 commit 7184f89

File tree

11 files changed

+5115
-37
lines changed

11 files changed

+5115
-37
lines changed

deno.lock

+4,618
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
type SyntaxSegment = { annotation?: { tag: string }; segment: string };
2+
type DocElement = { annotation?: { tag: string }; segment: string };
3+
4+
type DefinitionSyntax = {
5+
contents: Array<SyntaxSegment>;
6+
tag: "UserObject" | "BuiltinObject";
7+
};
8+
9+
type APITerm = {
10+
bestTermName: string;
11+
defnTermTag: string;
12+
signature: Array<SyntaxSegment>;
13+
termDefinition: DefinitionSyntax;
14+
termDocs: Array<DocElement>;
15+
};
16+
17+
type APIType = {
18+
bestTypeName: string;
19+
defnTypeTag: string;
20+
typeDefinition: DefinitionSyntax;
21+
typeDocs: Array<DocElement>;
22+
};
23+
24+
type APIDefinitions = {
25+
missingDefinitions: Array<string>;
26+
termDefinitions: Array<{ [key: string]: APITerm }>;
27+
typeDefinitions: Array<{ [key: string]: APIType }>;
28+
};
29+
30+
export {
31+
SyntaxSegment,
32+
DefinitionSyntax,
33+
APITerm,
34+
APIType,
35+
APIDefinitions,
36+
DocElement,
37+
};

netlify/edge-functions/common/share-api.ts

+44-16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { APIDefinition } from "./definition.ts";
2+
13
type APIProject = {
24
owner: { handle: string };
35
slug: string;
@@ -55,6 +57,12 @@ function apiHandle(handle: string): string {
5557
const ShareAPI = {
5658
baseURL: "https://api.unison-lang.org",
5759

60+
projectBaseUrl: (handle: string, projectSlug: string, path?: string) => {
61+
return `${ShareAPI.baseURL}/users/${apiHandle(
62+
handle
63+
)}/projects/${projectSlug}${path || ""}`;
64+
},
65+
5866
getUser: async (handle: string): Promise<APIUser> => {
5967
return fetch(`${ShareAPI.baseURL}/users/${apiHandle(handle)}`).then(
6068
(resp) => {
@@ -71,9 +79,7 @@ const ShareAPI = {
7179
handle: string,
7280
projectSlug: string
7381
): Promise<APIProject> => {
74-
return fetch(
75-
`${ShareAPI.baseURL}/users/${apiHandle(handle)}/projects/${projectSlug}`
76-
).then((resp) => {
82+
return fetch(ShareAPI.projectBaseUrl(handle, projectSlug)).then((resp) => {
7783
if (!resp.ok) {
7884
throw new Error(resp.statusText);
7985
}
@@ -88,9 +94,11 @@ const ShareAPI = {
8894
contribRef: number
8995
): Promise<APIContribution> => {
9096
return fetch(
91-
`${ShareAPI.baseURL}/users/${apiHandle(
92-
handle
93-
)}/projects/${projectSlug}/contributions/${contribRef}`
97+
ShareAPI.projectBaseUrl(
98+
handle,
99+
projectSlug,
100+
`/contributions/${contribRef}`
101+
)
94102
).then((resp) => {
95103
if (!resp.ok) {
96104
throw new Error(resp.statusText);
@@ -106,9 +114,7 @@ const ShareAPI = {
106114
ticketRef: number
107115
): Promise<APITicket> => {
108116
return fetch(
109-
`${ShareAPI.baseURL}/users/${apiHandle(
110-
handle
111-
)}/projects/${projectSlug}/tickets/${ticketRef}`
117+
ShareAPI.projectBaseUrl(handle, projectSlug, `/tickets/${ticketRef}`)
112118
).then((resp) => {
113119
if (!resp.ok) {
114120
throw new Error(resp.statusText);
@@ -124,9 +130,7 @@ const ShareAPI = {
124130
version: string
125131
): Promise<APIRelease> => {
126132
return fetch(
127-
`${ShareAPI.baseURL}/users/${apiHandle(
128-
handle
129-
)}/projects/${projectSlug}/releases/${version}`
133+
ShareAPI.projectBaseUrl(handle, projectSlug, `/releases/${version}`)
130134
).then((resp) => {
131135
if (!resp.ok) {
132136
throw new Error(resp.statusText);
@@ -135,11 +139,35 @@ const ShareAPI = {
135139
return resp.json() as Promise<APIRelease>;
136140
});
137141
},
142+
138143
getDefinition: async (
139-
_handle: string,
140-
_projectSlug: string,
141-
_fqn: Array<string>
142-
) => {},
144+
handle: string,
145+
projectSlug: string,
146+
branchRef: string,
147+
fqn: Array<string>
148+
): Promise<APIDefinitions> => {
149+
function mkUrl(branchPart: string): string {
150+
return ShareAPI.projectBaseUrl(
151+
handle,
152+
projectSlug,
153+
`/${branchPart}/definitions/by-name/${fqn.join(".")}`
154+
);
155+
}
156+
157+
let url = mkUrl(`branches/${branchRef.replace("/", "%2F")}`);
158+
if (branchRef.startsWith("releases")) {
159+
url = mkUrl(branchRef);
160+
}
161+
162+
return fetch(url).then((resp) => {
163+
if (!resp.ok) {
164+
throw new Error(resp.statusText);
165+
}
166+
167+
return resp.json() as Promise<APIDefinitions>;
168+
});
169+
},
143170
};
144171

145172
export default ShareAPI;
173+
export { ShareAPI };

netlify/edge-functions/common/share-route.ts

+53-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class Route extends SumType<{
44
UserOverview: [string];
55
ProjectOverview: [string, string];
66
ProjectCode: [string, string, string | undefined];
7+
ProjectDefinition: [string, string, string, string, Array<string>];
78
ProjectTickets: [string, string];
89
ProjectTicket: [string, string, number];
910
ProjectContributions: [string, string];
@@ -30,6 +31,23 @@ function ProjectCode(
3031
return new Route("ProjectCode", handle, projectSlug, branchRef);
3132
}
3233

34+
function ProjectDefinition(
35+
handle: string,
36+
projectSlug: string,
37+
branchRef: string,
38+
definitionType: string,
39+
fqn: Array<string>
40+
): Route {
41+
return new Route(
42+
"ProjectDefinition",
43+
handle,
44+
projectSlug,
45+
branchRef,
46+
definitionType,
47+
fqn
48+
);
49+
}
50+
3351
function ProjectTickets(handle: string, projectSlug: string): Route {
3452
return new Route("ProjectTickets", handle, projectSlug);
3553
}
@@ -79,11 +97,13 @@ function parse(rawUrl: string): Route {
7997
return fromPathname(url.pathname);
8098
}
8199

100+
function isContributorBranchOrRelease(s: string): boolean {
101+
return s.startsWith("@") || s.startsWith("releases");
102+
}
103+
82104
function fromPathname(rawPath: string): Route {
83105
const parts = rawPath.split("/").filter((s) => s.length);
84106

85-
console.log("route parts", parts);
86-
87107
const [handle, projectSlug, ...rest] = parts;
88108

89109
if (handle && handle.startsWith("@") && !projectSlug) {
@@ -92,17 +112,41 @@ function fromPathname(rawPath: string): Route {
92112
const [projectPage] = rest;
93113

94114
if (projectPage === "code") {
95-
const [_, branchPart1, branchPart2] = rest;
115+
const [
116+
_,
117+
branchPart1,
118+
branchPart2,
119+
namespaceHashOrDefinitionType,
120+
definitionTypeOrNameSegment,
121+
...nameSegments
122+
] = rest;
96123

97124
let branchRef = branchPart1;
98-
if (
99-
branchPart1 &&
100-
(branchPart1.startsWith("@") || branchPart1.startsWith("releases"))
101-
) {
125+
let fqn = nameSegments || [];
126+
let definitionType = definitionTypeOrNameSegment;
127+
if (branchPart1 && isContributorBranchOrRelease(branchPart1)) {
102128
branchRef = `${branchRef}/${branchPart2}`;
129+
} else if (branchPart2) {
130+
// if the branchPart1 contributor branch a release, then branch2 is a namespaceHash,
131+
// which means the namespaceHashOrDefinitionType is a definition type, and definitionTypeOrNameSegment
132+
// is the first part of the FQN
133+
fqn = [definitionTypeOrNameSegment, ...nameSegments];
134+
definitionType = namespaceHashOrDefinitionType;
103135
}
104136

105-
return ProjectCode(handle, projectSlug, branchRef);
137+
// Gotta have an FQN to be a definition Route
138+
if (fqn.length && definitionType) {
139+
return ProjectDefinition(
140+
handle,
141+
projectSlug,
142+
branchRef,
143+
// remove the trailing 's' from the definition type
144+
definitionType.substring(0, definitionType.length - 1),
145+
fqn
146+
);
147+
} else {
148+
return ProjectCode(handle, projectSlug, branchRef);
149+
}
106150
} else if (projectPage === "tickets") {
107151
const [_, ticketRef] = rest;
108152

@@ -141,6 +185,7 @@ export {
141185
UserOverview,
142186
ProjectOverview,
143187
ProjectCode,
188+
ProjectDefinition,
144189
ProjectContributions,
145190
ProjectContribution,
146191
ProjectTickets,

netlify/edge-functions/common/utils.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ function userHandle(handle?: string): string {
2929
}
3030

3131
function hash(h: string): string {
32-
return h.replace("#", "").slice(0, 8);
32+
// builtins starts with 2 ## and we shouldn't shorten them
33+
if (h.startsWith("##")) {
34+
return h.replaceAll("#", "");
35+
} else {
36+
return h.replaceAll("#", "").slice(0, 8);
37+
}
3338
}
3439

3540
export { truncate, titleize, intersperse, userHandle, hash };

netlify/edge-functions/replace-social-content.ts

+43
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,49 @@ async function getContent(rawUrl: string): Promise<SocialContent> {
7373
};
7474
},
7575

76+
async ProjectDefinition(
77+
handle,
78+
projectSlug,
79+
branchRef,
80+
definitionType,
81+
ref
82+
) {
83+
const project = await ShareAPI.getProject(handle, projectSlug);
84+
const definitions = await ShareAPI.getDefinition(
85+
handle,
86+
projectSlug,
87+
branchRef,
88+
ref
89+
);
90+
91+
if (!project) return DefaultSocialContent;
92+
if (!definitions) return DefaultSocialContent;
93+
94+
let definition = null;
95+
if (definitionType === "term") {
96+
const [hash] = Object.keys(definitions.termDefinitions);
97+
const raw = definitions.termDefinitions[hash];
98+
if (hash) {
99+
definition = { name: raw.bestTermName };
100+
}
101+
} else {
102+
const [hash] = Object.keys(definitions.typeDefinitions);
103+
const raw = definitions.typeDefinitions[hash];
104+
if (hash) {
105+
definition = { name: raw.bestTypeName };
106+
}
107+
}
108+
109+
if (!definition) return DefaultSocialContent;
110+
const title = definition.name;
111+
112+
return {
113+
title: `${title} · ${handle}/${projectSlug}/${branchRef} | Unison Share`,
114+
description: project.summary,
115+
imageUrl,
116+
};
117+
},
118+
76119
async ProjectTickets(handle, projectSlug) {
77120
const project = await ShareAPI.getProject(handle, projectSlug);
78121

0 commit comments

Comments
 (0)