Skip to content

Commit

Permalink
Social Image: render definition docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hojberg committed Jun 26, 2024
1 parent 1e08f05 commit 88d398f
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 72 deletions.
54 changes: 51 additions & 3 deletions netlify/edge-functions/common/definition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
type SyntaxSegment = { annotation?: { tag: string }; segment: string };
type DocElement = { annotation?: { tag: string }; segment: string };
type SyntaxSegment = {
annotation?: { tag: string };
segment: string;
};

type DocSpecial =
| { tag: "Source"; contents: Array<SyntaxSegment> }
| { tag: "Link"; contents: Array<SyntaxSegment> }
| { tag: "Example"; contents: Array<SyntaxSegment> }
| { tag: "ExampleBlock"; contents: Array<SyntaxSegment> }
| { tag: "Signature"; contents: Array<Array<SyntaxSegment>> }
| { tag: "SignatureInline"; contents: Array<SyntaxSegment> }
| { tag: "Eval"; contents: [Array<SyntaxSegment>, Array<SyntaxSegment>] }
| {
tag: "EvalInline";
contents: [Array<SyntaxSegment>, Array<SyntaxSegment>];
}
| { tag: "Embed"; contents: Array<SyntaxSegment> }
| { tag: "EmbedInline"; contents: Array<SyntaxSegment> };

type DocElement =
| { tag: "Word"; contents: string }
| { tag: "NamedLink"; contents: [DocElement, DocElement] }
| { tag: "Paragraph"; contents: Array<DocElement> }
| { tag: "Special"; contents: DocSpecial }
| { tag: "Span"; contents: Array<DocElement> }
| { tag: "UntitledSection"; contents: Array<DocElement> }
| { tag: "Section"; contents: [DocElement, Array<DocElement>] }
| { tag: "BulletedList"; contents: Array<DocElement> }
| { tag: "NumberedList"; contents: [Number, Array<DocElement>] }
| { tag: "Code"; contents: DocElement }
| { tag: "CodeBlock"; contents: [string, DocElement] }
| { tag: "Group"; contents: DocElement }
| { tag: "Join"; contents: Array<DocElement> }
| { tag: "Column"; contents: Array<DocElement> }
| { tag: "Image"; contents: [DocElement, DocElement, DocElement?] }
| { tag: "Folded"; contents: [DocElement, DocElement] }
| { tag: "Callout"; contents: [DocElement | null, DocElement] }
| { tag: "Aside"; contents: DocElement }
| { tag: "Tooltip"; contents: [DocElement, DocElement] }
| { tag: "SectionBreak"; contents: [] }
| { tag: "Blankline"; contents: [] }
| { tag: "Anchor"; contents: [string, DocElement] }
| { tag: "Style"; contents: [string, DocElement] }
| { tag: "Blockqoute"; contents: DocElement }
| { tag: "Italic"; contents: DocElement }
| { tag: "Bold"; contents: DocElement }
| { tag: "Strikethrough"; contents: DocElement }
| { tag: "Table"; contents: Array<Array<DocElement>> };

type DefinitionSyntax = {
contents: Array<SyntaxSegment>;
Expand All @@ -11,7 +58,7 @@ type APITerm = {
defnTermTag: string;
signature: Array<SyntaxSegment>;
termDefinition: DefinitionSyntax;
termDocs: Array<DocElement>;
termDocs: Array<[string, string, DocElement]>;
};

type APIType = {
Expand All @@ -33,5 +80,6 @@ export {
APITerm,
APIType,
APIDefinitions,
DocSpecial,
DocElement,
};
249 changes: 249 additions & 0 deletions netlify/edge-functions/social-image-helpers/docs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
Due to this bug: https://github.com/vercel/satori/issues/484
We just render the doc mostly on a single line and, hide the overflow with a fade.
*/
import React from "https://esm.sh/[email protected]";
import { DocSpecial, DocElement } from "../common/definition.ts";
import * as Sizing from "../common/sizing.ts";
import Colors from "../common/colors.ts";
import { intersperse } from "../common/utils.ts";
import { InlineSyntax, Syntax } from "./syntax.tsx";
import * as Fonts from "../common/fonts.ts";

function Docs(props: { docRoot: DocElement }) {
function renderSectionContent(e: DocElement) {
switch (e.tag) {
case "Span":
return <p style={STYLES.docBlock}>{go(e)}</p>;
case "Paragraph":
return <p style={STYLES.docBlock}>{go(e)}</p>;
default:
return go(e);
}
}

function go(e: DocElement) {
switch (e.tag) {
case "Special":
const special = e.contents;
switch (special.tag) {
case "Source":
return (
<span style={STYLES.docCode}>
<Syntax syntax={special.contents} />
</span>
);
case "Link":
return (
<span style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
<InlineSyntax syntax={special.contents} />
</span>
);
case "Example":
return (
<span style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
<InlineSyntax syntax={special.contents} />
</span>
);
case "ExampleBlock":
return (
<span style={STYLES.docCode}>
<Syntax syntax={special.contents} />
</span>
);
case "EmbedInline":
return (
<span style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
<InlineSyntax syntax={special.contents} />
</span>
);
case "Embed":
return (
<span style={STYLES.docCode}>
<Syntax syntax={special.contents} />
</span>
);
case "SignatureInline":
return (
<span style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
<InlineSyntax syntax={special.contents} />
</span>
);
case "Signature":
return (
<span style={STYLES.docCode}>
{special.contents.map((s) => (
<Syntax syntax={s} />
))}
</span>
);
case "Eval":
return (
<span style={{ ...STYLES.docCode }}>
<Syntax syntax={special.contents[0]} />
<Syntax syntax={special.contents[1]} />
</span>
);
case "EvalInline":
return (
<span style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
<InlineSyntax syntax={special.contents[0]} />
<InlineSyntax syntax={special.contents[1]} />
</span>
);

default:
return <></>;
}
case "NamedLink":
return <a style={STYLES.docLink}>{go(e.contents[0])}</a>;
case "Word":
return <span>{e.contents}</span>;
case "Paragraph":
return (
<span style={STYLES.docInline}>
{intersperse(
e.contents.map(go),
<span style={STYLES.docSpace}> </span>
)}
</span>
);
case "Span":
return (
<span style={STYLES.docInline}>
{intersperse(
e.contents.map(go),
<span style={STYLES.docSpace}> </span>
)}
</span>
);
case "UntitledSection":
return (
<section style={STYLES.docBlock}>
{e.contents.map(renderSectionContent)}
</section>
);
case "Section":
const [title, sectionContent] = e.contents as [
DocElement,
Array<DocElement>
];

return (
<section style={STYLES.docBlock}>
<h1 style={STYLES.docBlock}>{go(title)}</h1>
<div style={STYLES.docBlock}>
{sectionContent.map(renderSectionContent)}
</div>
</section>
);
case "BulletedList":
return (
<ul style={STYLES.docList}>
{e.contents.map((e) => (
<li style={STYLES.docListItem}>
<span style={STYLES.docListItemBullet}></span> {go(e)}
</li>
))}
</ul>
);
case "NumberedList":
const [start, els] = e.contents;
return (
<ol start={start} style={STYLES.docList}>
{els.map((e, i) => (
<li style={STYLES.docListItem}>
<span style={STYLES.docListItemBullet}>{start + i}</span>{" "}
{go(e)}
</li>
))}
</ol>
);
case "Code":
return (
<code style={{ ...STYLES.docCode, ...STYLES.docCodeInline }}>
{go(e.contents)}
</code>
);
case "Join":
return <span style={STYLES.docInline}>{e.contents}</span>;
case "Group":
return <span style={STYLES.docInline}>{go(e.contents)}</span>;
default:
return <></>;
}
}

const docs = go(props.docRoot);

return (
<section style={STYLES.docs}>
{docs}
<span style={STYLES.docsFade}></span>
</section>
);
}

const STYLES = {
docs: {
display: "flex",
paddingTop: Sizing.toPx(2),
height: Sizing.toPx(5),
maxHeight: Sizing.toPx(5),
overflow: "hidden",
borderTop: `2px solid ${Colors.gray.lighten40}`,
fontSize: Sizing.toPx(2.25),
gap: 0,
position: "relative",
fontWeight: Fonts.Weights.semiBold,
border: "1px solid blue",
},
docsFade: {
position: "absolute",
top: -10,
right: `-${Sizing.toPx(2.5)}px`,
bottom: -10,
width: 400,
height: Sizing.toPx(6),
background: `linear-gradient(to right, rgba(255, 255, 255, 0), ${Colors.gray.lighten100}, ${Colors.gray.lighten100})`,
},
docBlock: {
display: "flex",
flexWrap: "wrap",
gap: 0,
marginTop: 0,
width: "100%",
},
docLink: {
color: Colors.blue1,
},
docList: {
display: "flex",
flexWrap: "wrap",
gap: Sizing.toPx(0.5),
marginTop: 0,
marginLeft: Sizing.toPx(1),
width: "100%",
},
docListItem: {
display: "flex",
flexWrap: "wrap",
gap: 0,
marginTop: 0,
width: "100%",
listStyleType: "disc",
},
docListItemBullet: {
marginRight: Sizing.toPx(1),
},
docInline: {},
docSpace: {
width: "8ch",
},
docCode: {
fontFamily: "monospace",
},
docCodeInline: {},
};

export default Docs;
Loading

0 comments on commit 88d398f

Please sign in to comment.