;
149 | base: URL;
150 | currentSymbol?: string;
151 | },
152 | ) {
153 | const items = take(children, true);
154 |
155 | const [categories, uncategorized] = categorize(items);
156 |
157 | const entries = [];
158 | for (
159 | const [name, symbols] of Object.entries(categories).sort(([a], [b]) =>
160 | a.localeCompare(b)
161 | )
162 | ) {
163 | entries.push(
164 |
169 | {symbols}
170 | ,
171 | );
172 | }
173 |
174 | const uncategorizedActive = !!uncategorized.find(({ name }) =>
175 | name === currentSymbol
176 | );
177 | for (const symbol of uncategorized) {
178 | entries.push(
179 |
185 | {symbol}
186 | ,
187 | );
188 | }
189 |
190 | if (entries.length === 0) {
191 | return null;
192 | }
193 | return (
194 |
195 | {entries}
196 |
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/doc/markdown.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | all,
5 | comrak,
6 | createLowlight,
7 | htmlEntities,
8 | toHtml,
9 | tw,
10 | } from "../deps.ts";
11 | import { services } from "../services.ts";
12 | import { style, type StyleKey } from "../styles.ts";
13 | import { assert, type Child, splitMarkdownTitle, take } from "./utils.ts";
14 |
15 | const CODE_BLOCK_RE =
16 | /([^<]+)<\/code><\/pre>/m;
17 |
18 | /** Matches `{@link ...}`, `{@linkcode ...}, and `{@linkplain ...}` structures
19 | * in JSDoc */
20 | const JSDOC_LINK_RE = /\{\s*@link(code|plain)?\s+([^}]+)}/m;
21 |
22 | const MARKDOWN_OPTIONS: comrak.ComrakOptions = {
23 | extension: {
24 | autolink: true,
25 | descriptionLists: true,
26 | strikethrough: true,
27 | superscript: true,
28 | table: true,
29 | tagfilter: true,
30 | },
31 | };
32 |
33 | let lowlight: ReturnType;
34 |
35 | function syntaxHighlight(html: string): string {
36 | let match;
37 | while ((match = CODE_BLOCK_RE.exec(html))) {
38 | let [text, lang, code] = match;
39 | lang = lang.split(",")[0];
40 | let codeHTML;
41 | if (lowlight.registered(lang)) {
42 | const tree = lowlight.highlight(
43 | lang,
44 | htmlEntities.decode(code),
45 | {
46 | prefix: "code-",
47 | },
48 | );
49 | codeHTML = toHtml(tree);
50 | } else {
51 | codeHTML = code;
52 | }
53 | assert(match.index != null);
54 | html = `${html.slice(0, match.index)}${codeHTML}
${
55 | html.slice(match.index + text.length)
56 | }`;
57 | }
58 | return html;
59 | }
60 |
61 | /** Determines if the value looks like a relative or absolute path, or is
62 | * a URI with a protocol. */
63 | function isLink(link: string): boolean {
64 | return /^\.{0,2}\//.test(link) || /^[A-Za-z]+:\S/.test(link);
65 | }
66 |
67 | function parseLinks(markdown: string, url: URL, namespace?: string): string {
68 | let match;
69 | while ((match = JSDOC_LINK_RE.exec(markdown))) {
70 | const [text, modifier, value] = match;
71 | let link = value;
72 | let title;
73 | const indexOfSpace = value.indexOf(" ");
74 | const indexOfPipe = value.indexOf("|");
75 | if (indexOfPipe >= 0) {
76 | link = value.slice(0, indexOfPipe);
77 | title = value.slice(indexOfPipe + 1).trim();
78 | } else if (indexOfSpace >= 0) {
79 | link = value.slice(0, indexOfSpace);
80 | title = value.slice(indexOfSpace + 1).trim();
81 | }
82 | const href = services.lookupHref(url, namespace, link);
83 | if (href) {
84 | if (!title) {
85 | title = link;
86 | }
87 | link = href;
88 | }
89 | let replacement;
90 | if (isLink(link)) {
91 | if (title) {
92 | replacement = modifier === "code"
93 | ? `[\`${title}\`](${link})`
94 | : `[${title}](${link})`;
95 | } else {
96 | replacement = modifier === "code"
97 | ? `[\`${link}\`](${link})`
98 | : `[${link}](${link})`;
99 | }
100 | } else {
101 | replacement = modifier === "code"
102 | ? `\`${link}\`${title ? ` | ${title}` : ""}`
103 | : `${link}${title ? ` | ${title}` : ""}`;
104 | }
105 | markdown = `${markdown.slice(0, match.index)}${replacement}${
106 | markdown.slice(match.index + text.length)
107 | }`;
108 | }
109 | return markdown;
110 | }
111 |
112 | export function mdToHtml(markdown: string): string {
113 | if (!lowlight) {
114 | lowlight = createLowlight(all);
115 | }
116 | return syntaxHighlight(comrak.markdownToHTML(markdown, MARKDOWN_OPTIONS));
117 | }
118 |
119 | export interface Context {
120 | url: URL;
121 | namespace?: string;
122 | replacers?: [string, string][];
123 | typeParams?: string[];
124 | }
125 |
126 | export function Markdown(
127 | { children, summary, context }: {
128 | children: Child;
129 | summary?: boolean;
130 | context: Context;
131 | },
132 | ) {
133 | let md = take(children);
134 | if (!md) {
135 | return null;
136 | }
137 | if (context.replacers) {
138 | for (const [pattern, replacement] of context.replacers) {
139 | md = md.replaceAll(pattern, replacement);
140 | }
141 | }
142 | let mdStyle: StyleKey = "markdown";
143 | let additionalStyle = services.markdownStyle;
144 | if (summary) {
145 | mdStyle = "markdownSummary";
146 | additionalStyle = services.markdownSummaryStyle;
147 | [md] = splitMarkdownTitle(md);
148 | }
149 |
150 | return (
151 |
159 | );
160 | }
161 |
162 | export function JsDoc(
163 | { children, context }: {
164 | children: Child<{ doc?: string } | undefined>;
165 | context: Context;
166 | },
167 | ) {
168 | const jsDoc = take(children);
169 | if (!jsDoc) {
170 | return null;
171 | }
172 | return {jsDoc.doc} ;
173 | }
174 |
--------------------------------------------------------------------------------
/doc/module_doc.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type ComponentChildren, type DocNode } from "../deps.ts";
4 | import { Examples, SectionTitle, tagVariants } from "./doc_common.tsx";
5 | import * as Icons from "../icons.tsx";
6 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
7 | import { services } from "../services.ts";
8 | import { style } from "../styles.ts";
9 | import { Usage } from "./usage.tsx";
10 | import * as SymbolKind from "./symbol_kind.tsx";
11 | import {
12 | asCollection,
13 | byName,
14 | type Child,
15 | DocNodeCollection,
16 | DocNodeTupleArray,
17 | isAbstract,
18 | isDeprecated,
19 | maybe,
20 | take,
21 | } from "./utils.ts";
22 |
23 | function Entry(
24 | { children, icon, context }: {
25 | children: Child<[label: string, node: Node]>;
26 | icon: ComponentChildren;
27 | context: Context;
28 | },
29 | ) {
30 | const [label, node] = take(children, true);
31 | const name = context.namespace ? `${context.namespace}.${label}` : label;
32 | const href = services.resolveHref(context.url, name);
33 |
34 | return (
35 |
36 |
37 |
38 | {icon}
39 |
{name}
40 | {maybe(isAbstract(node), tagVariants.abstract())}
41 | {maybe(isDeprecated(node), tagVariants.deprecated())}
42 |
43 |
44 |
45 |
46 | {node.jsDoc?.doc}
47 |
48 |
49 |
50 | );
51 | }
52 |
53 | function Section(
54 | { children, title, icon, context }: {
55 | children: Child>;
56 | title: string;
57 | icon: ComponentChildren;
58 | context: Context;
59 | },
60 | ) {
61 | const tuples = take(children, true, true);
62 | const displayed = new Set();
63 | const items = tuples.sort(byName).map(([label, node]) => {
64 | if (displayed.has(label)) {
65 | return null;
66 | }
67 | displayed.add(label);
68 | return (
69 |
70 | {[label, node]}
71 |
72 | );
73 | });
74 | return (
75 |
79 | );
80 | }
81 |
82 | export function DocTypeSections(
83 | { children, context }: {
84 | children: Child;
85 | context: Context;
86 | },
87 | ) {
88 | const collection = take(children);
89 | return (
90 | <>
91 | {collection.namespace && (
92 | }
95 | context={context}
96 | >
97 | {collection.namespace}
98 |
99 | )}
100 | {collection.class && (
101 | }
104 | context={context}
105 | >
106 | {collection.class}
107 |
108 | )}
109 | {collection.enum && (
110 | }
113 | context={context}
114 | >
115 | {collection.enum}
116 |
117 | )}
118 | {collection.variable && (
119 | }
122 | context={context}
123 | >
124 | {collection.variable}
125 |
126 | )}
127 | {collection.function && (
128 | }
131 | context={context}
132 | >
133 | {collection.function}
134 |
135 | )}
136 | {collection.interface && (
137 | }
140 | context={context}
141 | >
142 | {collection.interface}
143 |
144 | )}
145 | {collection.typeAlias && (
146 | }
149 | context={context}
150 | >
151 | {collection.typeAlias}
152 |
153 | )}
154 | >
155 | );
156 | }
157 |
158 | export function ModuleDoc(
159 | { children, sourceUrl, ...context }: {
160 | children: Child;
161 | sourceUrl: string;
162 | } & Pick,
163 | ) {
164 | const docNodes = take(children, true);
165 | const isEmpty = docNodes.length === 0;
166 | const hasExports = docNodes.some(({ declarationKind, kind }) =>
167 | kind !== "moduleDoc" && declarationKind === "export"
168 | );
169 | const collection = asCollection(docNodes);
170 | const jsDoc = collection.moduleDoc?.[0][1].jsDoc;
171 | const deprecated = isDeprecated({ jsDoc });
172 |
173 | return (
174 |
175 |
185 |
186 |
187 |
188 | {deprecated && (
189 |
190 |
191 |
192 |
193 | Deprecated
194 |
195 |
196 |
197 | {deprecated.doc &&
198 | (
199 |
200 | {deprecated.doc}
201 |
202 | )}
203 |
204 | )}
205 |
206 | {isEmpty || hasExports ?
: undefined}
207 |
208 |
{jsDoc}
209 |
{jsDoc}
210 |
211 | {isEmpty
212 | ? (
213 |
214 | The documentation for this module is currently unavailable.
215 |
216 | )
217 | : hasExports
218 | ? (
219 |
220 | {collection}
221 |
222 | )
223 | : (
224 |
225 | This module does not provide any exports.
226 |
227 | )}
228 |
229 |
230 |
231 | );
232 | }
233 |
--------------------------------------------------------------------------------
/doc/module_index.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type Context, Markdown } from "./markdown.tsx";
4 | import { services } from "../services.ts";
5 | import { style } from "../styles.ts";
6 | import { type Child, take } from "./utils.ts";
7 | import * as Icons from "../icons.tsx";
8 |
9 | export interface IndexItem {
10 | kind: "dir" | "module" | "file";
11 | path: string;
12 | size: number;
13 | ignored: boolean;
14 | doc?: string;
15 | }
16 |
17 | function Folder({ children, parent, context }: {
18 | children: Child;
19 | parent: string;
20 | context: Context;
21 | }) {
22 | const item = take(children);
23 | const url = new URL(context.url);
24 | url.pathname += item.path;
25 | const href = services.resolveHref(url);
26 | const label = item.path.slice(parent === "/" ? 1 : parent.length + 1);
27 | return (
28 |
29 |
30 |
31 | {label}
32 |
33 |
34 |
35 | {item.doc}
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | function Module({ children, parent, context }: {
43 | children: Child;
44 | parent: string;
45 | context: Context;
46 | }) {
47 | const item = take(children);
48 | const url = new URL(context.url);
49 | url.pathname += item.path;
50 | const href = services.resolveHref(url);
51 | const label = item.path.slice(parent === "/" ? 1 : parent.length + 1);
52 | return (
53 |
54 |
55 |
56 | {label}
57 |
58 |
59 |
60 | {item.doc}
61 |
62 |
63 |
64 | );
65 | }
66 |
67 | const order = ["dir", "module", "file"] as const;
68 |
69 | export function ModuleIndex(
70 | {
71 | children,
72 | path = "/",
73 | skipMods = false,
74 | sourceUrl,
75 | ...context
76 | }: {
77 | children: Child;
78 | skipMods?: boolean;
79 | path?: string;
80 | sourceUrl: string;
81 | } & Pick,
82 | ) {
83 | const items = take(children, true);
84 | items.sort((a, b) =>
85 | (order.indexOf(a.kind) - order.indexOf(b.kind)) ||
86 | a.path.localeCompare(b.path)
87 | );
88 | const entries = [];
89 | for (const item of items) {
90 | if (item.ignored) {
91 | continue;
92 | }
93 | if (item.kind === "dir") {
94 | entries.push(
95 |
96 | {item}
97 | ,
98 | );
99 | } else if (item.kind === "module" && !skipMods) {
100 | entries.push(
101 |
102 | {item}
103 | ,
104 | );
105 | }
106 | }
107 |
108 | if (entries.length === 0) {
109 | return null;
110 | }
111 | return (
112 |
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/doc/module_index_panel.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeKind, type JsDoc, tw } from "../deps.ts";
4 | import { byKindValue, getIndex } from "./doc.ts";
5 | import { services } from "../services.ts";
6 | import { style } from "../styles.ts";
7 | import { type Child, take } from "./utils.ts";
8 | import * as Icons from "../icons.tsx";
9 | import { docNodeKindMap } from "./symbol_kind.tsx";
10 |
11 | interface DocPageDirItem {
12 | kind: "dir";
13 | path: string;
14 | }
15 |
16 | export interface SymbolItem {
17 | name: string;
18 | kind: DocNodeKind;
19 | category?: string;
20 | jsDoc?: JsDoc | null;
21 | }
22 |
23 | interface DocPageModuleItem {
24 | kind: "module";
25 | path: string;
26 | items: SymbolItem[];
27 | }
28 |
29 | export type DocPageNavItem = DocPageModuleItem | DocPageDirItem;
30 |
31 | export function splitItems(
32 | _rootPath: string,
33 | items: DocPageNavItem[],
34 | ): [folders: DocPageDirItem[], modules: DocPageModuleItem[]] {
35 | const folders: DocPageDirItem[] = [];
36 | const modules: DocPageModuleItem[] = [];
37 | for (const item of items) {
38 | if (item.kind === "dir") {
39 | folders.push(item);
40 | } else {
41 | modules.push(item);
42 | }
43 | }
44 | return [folders, modules];
45 | }
46 |
47 | function Folder({ children, base, parent }: {
48 | children: Child;
49 | base: URL;
50 | parent: string;
51 | }) {
52 | const folderName = take(children);
53 | const url = new URL(base);
54 | url.pathname += folderName;
55 | const href = services.resolveHref(url);
56 | const label = folderName.slice(parent === "/" ? 1 : parent.length + 1);
57 | return (
58 |
59 |
60 | {label}
61 |
62 | );
63 | }
64 |
65 | function Module(
66 | { children, base, parent, current, currentSymbol, isIndex }: {
67 | children: Child;
68 | base: URL;
69 | parent: string;
70 | current?: string;
71 | currentSymbol?: string;
72 | isIndex?: boolean;
73 | },
74 | ) {
75 | const { path, items } = take(children);
76 | const url = new URL(base);
77 | url.pathname += path;
78 | const href = services.resolveHref(url);
79 | const label = path.slice(parent === "/" ? 1 : parent.length + 1);
80 | const active = current ? current == path : isIndex;
81 |
82 | const symbols: Record = {};
83 | for (
84 | const symbolItem of items.filter((symbol) =>
85 | symbol.kind !== "import" && symbol.kind !== "moduleDoc"
86 | ).sort((a, b) =>
87 | byKindValue(a.kind, b.kind) || a.name.localeCompare(b.name)
88 | )
89 | ) {
90 | if (Object.hasOwn(symbols, symbolItem.name)) {
91 | symbols[symbolItem.name].push(symbolItem.kind);
92 | } else {
93 | symbols[symbolItem.name] = [symbolItem.kind];
94 | }
95 | }
96 |
97 | return (
98 |
99 |
106 |
110 |
111 | {label}
112 | {isIndex && (
113 |
114 | {" "}(default module)
115 |
116 | )}
117 |
118 |
119 |
120 | {Object.entries(symbols).map(([name, kinds]) => (
121 |
129 |
130 |
131 | {kinds.map((kind) => docNodeKindMap[kind]())}
132 |
133 | {name}
134 |
135 |
136 | ))}
137 |
138 | );
139 | }
140 |
141 | export function ModuleIndexPanel(
142 | { children, path = "/", base, current, currentSymbol }: {
143 | children: Child;
144 | base: URL;
145 | path: string;
146 | current?: string;
147 | currentSymbol?: string;
148 | },
149 | ) {
150 | const items = take(children, true);
151 | const [folders, modules] = splitItems(path, items);
152 | const entries = folders.sort().map((folder) => (
153 |
154 | {folder.path}
155 |
156 | ));
157 |
158 | const moduleIndex = getIndex(modules.map((module) => module.path));
159 | if (moduleIndex) {
160 | if (current === path) {
161 | current = moduleIndex;
162 | }
163 | entries.push(
164 |
171 | {modules.find((module) => module.path === moduleIndex)!}
172 | ,
173 | );
174 | }
175 | modules.sort();
176 | for (const module of modules) {
177 | if (module.path !== moduleIndex) {
178 | entries.push(
179 |
185 | {module}
186 | ,
187 | );
188 | }
189 | }
190 | if (entries.length === 0) {
191 | return null;
192 | }
193 | return (
194 |
195 | {entries}
196 |
197 | );
198 | }
199 |
--------------------------------------------------------------------------------
/doc/namespaces.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeNamespace } from "../deps.ts";
4 | import { type Context } from "./markdown.tsx";
5 | import { DocTypeSections } from "./module_doc.tsx";
6 | import { style } from "../styles.ts";
7 | import { asCollection, Child, take } from "./utils.ts";
8 | import { Examples } from "./doc_common.tsx";
9 |
10 | export function DocBlockNamespace(
11 | { children, context }: {
12 | children: Child;
13 | context: Context;
14 | },
15 | ) {
16 | const def = take(children);
17 | const collection = asCollection(def.namespaceDef.elements);
18 | return (
19 |
20 | {def.jsDoc}
21 |
22 |
23 | {collection}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/doc/params.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type ComponentChildren, type ParamDef } from "../deps.ts";
4 | import { style } from "../styles.ts";
5 | import { TypeDef } from "./types.tsx";
6 | import { type Child, take } from "./utils.ts";
7 | import { Context } from "./markdown.tsx";
8 |
9 | function Param(
10 | { children, i, context }: {
11 | children: Child;
12 | i: number;
13 | context: Context;
14 | },
15 | ) {
16 | const param = take(children);
17 | const name = paramName(param, i);
18 | const type = param.kind === "assign" ? param.left.tsType : param.tsType;
19 |
20 | return (
21 |
22 | {name}
23 | {((("optional" in param) && param.optional) || param.kind === "assign") &&
24 | "?"}
25 | {type && (
26 |
27 | :{" "}
28 |
29 | {type}
30 |
31 |
32 | )}
33 |
34 | );
35 | }
36 |
37 | export function Params(
38 | { children, context }: {
39 | children: Child;
40 | context: Context;
41 | },
42 | ) {
43 | const params = take(children, true);
44 | if (!params.length) {
45 | return null;
46 | }
47 | if (params.length < 3) {
48 | const items = [];
49 | for (let i = 0; i < params.length; i++) {
50 | items.push(
51 |
52 | {params[i]}
53 | ,
54 | );
55 | if (i < params.length - 1) {
56 | items.push({", "} );
57 | }
58 | }
59 | return {items} ;
60 | } else {
61 | return (
62 |
63 | {params.map((param, i) => (
64 |
65 |
66 | {param}
67 | ,
68 |
69 | ))}
70 |
71 | );
72 | }
73 | }
74 |
75 | export function paramName(param: ParamDef, i: number): ComponentChildren {
76 | switch (param.kind) {
77 | case "array":
78 | case "object":
79 | return unnamed {i} ;
80 | case "assign":
81 | return paramName(param.left, i);
82 | case "identifier":
83 | return param.name;
84 | case "rest":
85 | return ...{paramName(param.arg, i)} ;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/doc/symbol_doc.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | type DocNodeClass,
6 | type DocNodeFunction,
7 | type DocNodeInterface,
8 | type DocNodeTypeAlias,
9 | type JsDocTagDoc,
10 | type JsDocTagTags,
11 | } from "../deps.ts";
12 | import { byKind } from "./doc.ts";
13 | import { DocBlock } from "./doc_block.tsx";
14 | import { Tag, tagVariants } from "./doc_common.tsx";
15 | import * as Icons from "../icons.tsx";
16 | import { services } from "../services.ts";
17 | import { style } from "../styles.ts";
18 | import { Usage } from "./usage.tsx";
19 | import {
20 | type Child,
21 | isAbstract,
22 | isDeprecated,
23 | processProperty,
24 | take,
25 | } from "./utils.ts";
26 | import { DocTitle } from "./doc_title.tsx";
27 | import { type Context, JsDoc, Markdown } from "./markdown.tsx";
28 |
29 | function isTypeOnly(
30 | docNodes: DocNode[],
31 | ): docNodes is (DocNodeInterface | DocNodeTypeAlias)[] {
32 | return docNodes.every(({ kind }) =>
33 | kind === "interface" || kind === "typeAlias"
34 | );
35 | }
36 |
37 | export function SymbolDoc(
38 | { children, name, library = false, property, ...context }: {
39 | children: Child;
40 | name: string;
41 | library?: boolean;
42 | property?: string;
43 | } & Pick,
44 | ) {
45 | const docNodes = [...take(children, true)];
46 | docNodes.sort(byKind);
47 | let splitNodes: Record = {};
48 | let isReExport = false;
49 | for (const docNode of docNodes) {
50 | if (docNode.kind === "import") {
51 | isReExport = true;
52 | continue;
53 | }
54 | if (!(docNode.kind in splitNodes)) {
55 | splitNodes[docNode.kind] = [];
56 | }
57 | splitNodes[docNode.kind].push(docNode);
58 | }
59 |
60 | let propertyName: string | undefined;
61 | if (property && ("class" in splitNodes)) {
62 | // TODO(@crowlKats): type parameters declared in the class are not available
63 | // in the drilled down method
64 | const [propName, isPrototype] = processProperty(property);
65 |
66 | const classNode = (splitNodes["class"] as DocNodeClass[])[0];
67 | const functionNodes: DocNodeFunction[] = classNode.classDef.methods.filter((
68 | def,
69 | ) => def.name === propName && (isPrototype === !def.isStatic)).map(
70 | (def) => {
71 | return {
72 | declarationKind: classNode.declarationKind,
73 | functionDef: def.functionDef,
74 | jsDoc: def.jsDoc,
75 | kind: "function",
76 | location: def.location,
77 | name: def.name,
78 | };
79 | },
80 | );
81 |
82 | if (functionNodes.length !== 0) {
83 | splitNodes = { function: functionNodes };
84 | propertyName = property;
85 | }
86 | }
87 |
88 | const showUsage = !(context.url.href.endsWith(".d.ts") || library);
89 |
90 | return (
91 |
92 | {Object.values(splitNodes).map((nodes) => (
93 |
100 | {nodes}
101 |
102 | ))}
103 |
104 | );
105 | }
106 |
107 | function Symbol(
108 | { children, showUsage, property, name, isReExport, context }: {
109 | children: Child;
110 | showUsage: boolean;
111 | property?: string;
112 | name: string;
113 | isReExport: boolean;
114 | context: Context;
115 | },
116 | ) {
117 | const docNodes = take(children, true);
118 | const jsDoc = docNodes.map(({ jsDoc }) => jsDoc).find((jsDoc) => !!jsDoc);
119 | const isFunction = docNodes[0].kind === "function";
120 |
121 | const tags = [];
122 |
123 | if (isReExport) {
124 | tags.push(tagVariants.reExportLg());
125 | }
126 |
127 | const jsDocTags: string[] = docNodes.flatMap(({ jsDoc }) =>
128 | (jsDoc?.tags?.filter(({ kind }) => kind === "tags") as
129 | | JsDocTagTags[]
130 | | undefined)?.flatMap(({ tags }) => tags) ?? []
131 | );
132 |
133 | const permTags = jsDocTags.filter((tag, i) =>
134 | tag.startsWith("allow-") && jsDocTags.indexOf(tag) === i
135 | );
136 | if (permTags.length !== 0) {
137 | tags.push(
138 |
139 |
140 | {permTags.map((tag, i) => (
141 | <>
142 | {i !== 0 &&
}
143 | {tag}
144 | >
145 | ))}
146 |
147 | ,
148 | );
149 | }
150 |
151 | if (jsDocTags.includes("unstable")) {
152 | tags.push(tagVariants.unstableLg());
153 | }
154 |
155 | if (isAbstract(docNodes[0])) {
156 | tags.push(tagVariants.abstractLg());
157 | }
158 |
159 | let deprecated: JsDocTagDoc | undefined;
160 | if (docNodes.every(isDeprecated)) {
161 | deprecated = isDeprecated(docNodes[0]);
162 | if (deprecated) {
163 | tags.push(tagVariants.deprecatedLg());
164 | }
165 | }
166 |
167 | const lastSymbolIndex = name.lastIndexOf(".");
168 | context.namespace = lastSymbolIndex !== -1
169 | ? name.slice(0, lastSymbolIndex)
170 | : undefined;
171 |
172 | return (
173 |
174 |
197 |
198 | {deprecated?.doc && (
199 |
200 |
201 |
202 |
203 | Deprecated
204 |
205 |
206 |
207 |
208 | {deprecated.doc}
209 |
210 |
211 | )}
212 |
213 |
214 | {showUsage && (
215 |
220 | )}
221 | {!isFunction && {jsDoc} }
222 |
223 |
224 |
225 | {docNodes}
226 |
227 |
228 | );
229 | }
230 |
--------------------------------------------------------------------------------
/doc/symbol_kind.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { style } from "../styles.ts";
4 |
5 | export const docNodeKindColors = {
6 | "namespace": ["#D25646", "#D256461A"],
7 | "class": ["#20B44B", "#2FA8501A"],
8 | "enum": ["#22ABB0", "#22ABB01A"],
9 | "variable": ["#7E57C0", "#7E57C01A"],
10 | "function": ["#056CF0", "#026BEB1A"],
11 | "interface": ["#D2A064", "#D4A0681A"],
12 | "typeAlias": ["#A4478C", "#A4478C1A"],
13 | "moduleDoc": ["", ""],
14 | "import": ["", ""],
15 | } as const;
16 |
17 | export const docNodeKindMap = {
18 | "namespace": Namespace,
19 | "class": Class,
20 | "enum": Enum,
21 | "variable": Variable,
22 | "function": Function,
23 | "interface": Interface,
24 | "typeAlias": TypeAlias,
25 | "moduleDoc": () => null,
26 | "import": () => null,
27 | } as const;
28 |
29 | export function Namespace() {
30 | const [text, bg] = docNodeKindColors["namespace"];
31 | return (
32 |
33 | N
34 |
35 | );
36 | }
37 |
38 | export function Class() {
39 | const [text, bg] = docNodeKindColors["class"];
40 | return (
41 |
45 | c
46 |
47 | );
48 | }
49 |
50 | export function Enum() {
51 | const [text, bg] = docNodeKindColors["enum"];
52 | return (
53 |
57 | E
58 |
59 | );
60 | }
61 |
62 | export function Variable() {
63 | const [text, bg] = docNodeKindColors["variable"];
64 | return (
65 |
69 | v
70 |
71 | );
72 | }
73 |
74 | export function Function() {
75 | const [text, bg] = docNodeKindColors["function"];
76 | return (
77 |
81 | f
82 |
83 | );
84 | }
85 |
86 | export function Interface() {
87 | const [text, bg] = docNodeKindColors["interface"];
88 | return (
89 |
93 | I
94 |
95 | );
96 | }
97 |
98 | export function TypeAlias() {
99 | const [text, bg] = docNodeKindColors["typeAlias"];
100 | return (
101 |
105 | T
106 |
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/doc/type_aliases.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeTypeAlias } from "../deps.ts";
4 | import { DocEntry, Examples, nameToId, tagVariants } from "./doc_common.tsx";
5 | import { type Context } from "./markdown.tsx";
6 | import { style } from "../styles.ts";
7 | import { TypeDef, TypeParamsDoc } from "./types.tsx";
8 | import { type Child, isDeprecated, take } from "./utils.ts";
9 |
10 | export function DocBlockTypeAlias(
11 | { children, context }: {
12 | children: Child;
13 | context: Context;
14 | },
15 | ) {
16 | const def = take(children);
17 | const id = nameToId("typeAlias", def.name);
18 | context.typeParams = def.typeAliasDef.typeParams.map(({ name }) => name);
19 | const tags = [];
20 | if (isDeprecated(def)) {
21 | tags.push(tagVariants.deprecated());
22 | }
23 | return (
24 |
25 | {def.jsDoc}
26 |
27 |
28 | {def.typeAliasDef.typeParams}
29 |
30 |
31 |
38 | :{" "}
39 |
40 | {def.typeAliasDef.tsType}
41 |
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/doc/types.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | htmlEntities,
6 | type JsDocTagNamed,
7 | type LiteralCallSignatureDef,
8 | type LiteralIndexSignatureDef,
9 | type LiteralMethodDef,
10 | type LiteralPropertyDef,
11 | type Location,
12 | type TruePlusMinus,
13 | type TsTypeDef,
14 | type TsTypeIntersectionDef,
15 | type TsTypeMappedDef,
16 | type TsTypeParamDef,
17 | type TsTypeTupleDef,
18 | type TsTypeUnionDef,
19 | } from "../deps.ts";
20 | import { Params } from "./params.tsx";
21 | import { services } from "../services.ts";
22 | import { style } from "../styles.ts";
23 | import { type Child, maybe, take } from "./utils.ts";
24 | import { Context } from "./markdown.tsx";
25 | import { DocEntry, nameToId, Section, tagVariants } from "./doc_common.tsx";
26 |
27 | function LiteralIndexSignatures(
28 | { children, context }: {
29 | children: Child;
30 | context: Context;
31 | },
32 | ) {
33 | const signatures = take(children, true);
34 | if (!signatures.length) {
35 | return null;
36 | }
37 | const items = signatures.map(({ params, readonly, tsType }) => (
38 | <>
39 | {maybe(
40 | readonly,
41 |
42 | readonly{" "}
43 | ,
44 | )}[
45 | {params}
46 | ]{tsType &&
47 | (
48 | <>
49 | :{" "}
50 |
51 | {tsType}
52 |
53 | >
54 | )};{" "}
55 | >
56 | ));
57 |
58 | return <>{items}>;
59 | }
60 |
61 | function LiteralCallSignatures({ children, context }: {
62 | children: Child;
63 | context: Context;
64 | }) {
65 | const signatures = take(children, true);
66 | if (!signatures.length) {
67 | return null;
68 | }
69 | const items = signatures.map(({ typeParams, params, tsType }) => (
70 | <>
71 |
72 | {typeParams}
73 | (
74 | {params}
75 | ){tsType && (
76 | <>
77 | :{" "}
78 |
79 | {tsType}
80 |
81 | >
82 | )};{" "}
83 | >
84 | ));
85 | return <>{items}>;
86 | }
87 |
88 | function LiteralProperties(
89 | { children, context }: {
90 | children: Child;
91 | context: Context;
92 | },
93 | ) {
94 | const properties = take(children, true);
95 | if (!properties.length) {
96 | return null;
97 | }
98 | const items = properties.map(
99 | ({ name, readonly, computed, optional, tsType }) => (
100 | <>
101 | {maybe(
102 | readonly,
103 |
104 | readonly{" "}
105 | ,
106 | )}
107 | {maybe(computed, `[${name}]`, name)}
108 | {maybe(optional, "?")}
109 | {tsType && (
110 | <>
111 | :{" "}
112 |
113 | {tsType}
114 |
115 | >
116 | )}
117 | {"; "}
118 | >
119 | ),
120 | );
121 | return <>{items}>;
122 | }
123 |
124 | function LiteralMethods({ children, context }: {
125 | children: Child;
126 | context: Context;
127 | }) {
128 | const methods = take(children, true);
129 | if (!methods.length) {
130 | return null;
131 | }
132 | const items = methods.map(
133 | (
134 | {
135 | name,
136 | kind,
137 | optional,
138 | computed,
139 | returnType,
140 | typeParams,
141 | params,
142 | },
143 | ) => (
144 | <>
145 | {kind === "getter"
146 | ? get{" "}
147 | : kind === "setter"
148 | ? set{" "}
149 | : undefined}
150 | {name === "new"
151 | ? {name}{" "}
152 | : computed
153 | ? `[${name}]`
154 | : name}
155 | {maybe(optional, "?")}
156 |
157 | {typeParams}
158 | (
159 | {params}
160 | ){returnType && (
161 | <>
162 | :{" "}
163 |
164 | {returnType}
165 |
166 | >
167 | )}
168 | {"; "}
169 | >
170 | ),
171 | );
172 | return <>{items}>;
173 | }
174 |
175 | function MappedOptional(
176 | { children }: { children: Child },
177 | ) {
178 | const optional = take(children);
179 | switch (optional) {
180 | case true:
181 | return <>?>;
182 | case "+":
183 | return <>+?>;
184 | case "-":
185 | return <>-?>;
186 | default:
187 | return null;
188 | }
189 | }
190 |
191 | function MappedReadOnly(
192 | { children }: {
193 | children: Child;
194 | },
195 | ) {
196 | const readonly = take(children);
197 | switch (readonly) {
198 | case true:
199 | return readonly{" "} ;
200 | case "+":
201 | return +readonly{" "} ;
202 | case "-":
203 | return -readonly{" "} ;
204 | default:
205 | return null;
206 | }
207 | }
208 |
209 | export function TypeArguments(
210 | { children, context }: {
211 | children: Child;
212 | context: Context;
213 | },
214 | ) {
215 | const args = take(children, true);
216 | if (!args || !args.length || !args[0]) {
217 | return null;
218 | }
219 | const items = [];
220 | for (let i = 0; i < args.length; i++) {
221 | items.push(
222 |
223 | {args[i]}
224 | ,
225 | );
226 | if (i < args.length - 1) {
227 | items.push(<>{", "}>);
228 | }
229 | }
230 | return <><{items}>>;
231 | }
232 |
233 | export function TypeDef({ children, context }: {
234 | children: Child;
235 | context: Context;
236 | }) {
237 | const def = take(children);
238 | switch (def.kind) {
239 | case "array":
240 | return (
241 | <>
242 |
243 | {def.array}
244 | []
245 | >
246 | );
247 | case "conditional": {
248 | const {
249 | conditionalType: { checkType, extendsType, trueType, falseType },
250 | } = def;
251 | return (
252 | <>
253 |
254 | {checkType}
255 | {" "}
256 | extends {" "}
257 |
258 | {extendsType}
259 | {" "}
260 | ?{" "}
261 |
262 | {trueType}
263 | {" "}
264 | :{" "}
265 |
266 | {falseType}
267 |
268 | >
269 | );
270 | }
271 | case "fnOrConstructor": {
272 | const { fnOrConstructor } = def;
273 | return (
274 | <>
275 | {maybe(fnOrConstructor.constructor, new{" "} )}
276 |
279 | {fnOrConstructor.typeParams}
280 | (
281 | {fnOrConstructor.params}
282 | ) =>{" "}
283 |
284 | {fnOrConstructor.tsType}
285 |
286 | >
287 | );
288 | }
289 | case "importType": {
290 | const { importType } = def;
291 | return (
292 | <>
293 | import ("{importType.specifier}"){importType.qualifier &&
294 | .{importType.qualifier} }
295 |
296 | {importType.typeParams}
297 |
298 | >
299 | );
300 | }
301 | case "indexedAccess": {
302 | const { indexedAccess: { objType, indexType } } = def;
303 | return (
304 | <>
305 |
306 | {objType}
307 | [
308 | {indexType}
309 | ]
310 | >
311 | );
312 | }
313 | case "infer": {
314 | const { infer: { typeParam } } = def;
315 | return (
316 | <>
317 | infer{" "}
318 |
319 | {typeParam}
320 |
321 | >
322 | );
323 | }
324 | case "intersection":
325 | return (
326 |
327 | {def}
328 |
329 | );
330 | case "keyword": {
331 | return {def.keyword} ;
332 | }
333 | case "literal": {
334 | const { literal: { kind }, repr } = def;
335 | let item;
336 | switch (kind) {
337 | case "bigInt":
338 | case "boolean":
339 | case "number":
340 | item = {repr} ;
341 | break;
342 | case "string":
343 | item = {JSON.stringify(repr)} ;
344 | break;
345 | case "template":
346 | // TODO(@kitsonk) do this properly and escape properly
347 | item = `{repr}` ;
348 | break;
349 | }
350 | return <>{item}>;
351 | }
352 | case "mapped":
353 | return (
354 |
355 | {def}
356 |
357 | );
358 | case "optional": {
359 | const { optional } = def;
360 | return (
361 |
362 | {optional}
363 |
364 | );
365 | }
366 | case "parenthesized": {
367 | const { parenthesized } = def;
368 | return (
369 | <>
370 | (
371 | {parenthesized}
372 | )
373 | >
374 | );
375 | }
376 | case "rest": {
377 | const { rest } = def;
378 | return (
379 | <>
380 | ...
381 | {rest}
382 |
383 | >
384 | );
385 | }
386 | case "this": {
387 | return this ;
388 | }
389 | case "tuple": {
390 | return (
391 |
392 | {def}
393 |
394 | );
395 | }
396 | case "typeLiteral": {
397 | const {
398 | typeLiteral: { indexSignatures, callSignatures, properties, methods },
399 | } = def;
400 | return (
401 | <>
402 | {"{ "}
403 |
404 | {indexSignatures}
405 |
406 |
407 | {callSignatures}
408 |
409 |
410 | {properties}
411 |
412 |
413 | {methods}
414 |
415 | {" }"}
416 | >
417 | );
418 | }
419 | case "typeOperator": {
420 | const { typeOperator: { operator, tsType } } = def;
421 | return (
422 | <>
423 | {operator} {" "}
424 |
425 | {tsType}
426 |
427 | >
428 | );
429 | }
430 | case "typePredicate": {
431 | const {
432 | typePredicate: { asserts, param: { type: paramType, name }, type },
433 | } = def;
434 | return (
435 | <>
436 | {maybe(asserts, asserts{" "} )}
437 | {maybe(paramType === "this", this , name)}
438 | {type && (
439 | <>
440 | {" is "}
441 |
442 | {type}
443 |
444 | >
445 | )}
446 | >
447 | );
448 | }
449 | case "typeQuery": {
450 | const { typeQuery } = def;
451 | return <>{typeQuery}>;
452 | }
453 | case "typeRef": {
454 | const { typeRef } = def;
455 |
456 | let href;
457 | if (context.typeParams?.includes(typeRef.typeName)) {
458 | const url = new URL(context.url);
459 | url.hash = nameToId("type_param", typeRef.typeName);
460 | href = url.href;
461 | } else {
462 | href = services.lookupHref(
463 | context.url,
464 | context.namespace,
465 | typeRef.typeName,
466 | );
467 | }
468 | return (
469 | <>
470 | {href
471 | ? {typeRef.typeName}
472 | : {typeRef.typeName} }
473 |
474 | {typeRef.typeParams}
475 |
476 | >
477 | );
478 | }
479 | case "union":
480 | return (
481 |
482 | {def}
483 |
484 | );
485 | default:
486 | return (
487 | <>
488 | {htmlEntities.encode((def as TsTypeDef).repr)}
489 | >
490 | );
491 | }
492 | }
493 |
494 | function TypeDefIntersection(
495 | { children, context }: {
496 | children: Child;
497 | context: Context;
498 | },
499 | ) {
500 | const { intersection } = take(children);
501 | const lastIndex = intersection.length - 1;
502 | if (intersection.length <= 3) {
503 | const items = [];
504 | for (let i = 0; i < intersection.length; i++) {
505 | items.push(
506 |
507 | {intersection[i]}
508 | ,
509 | );
510 | if (i < lastIndex) {
511 | items.push({" & "} );
512 | }
513 | }
514 | return <>{items}>;
515 | }
516 | const items = intersection.map((def) => (
517 |
518 | {" & "}
519 |
520 | {def}
521 |
522 |
523 | ));
524 | return {items}
;
525 | }
526 |
527 | function TypeDefMapped(
528 | { children, context }: {
529 | children: Child;
530 | context: Context;
531 | },
532 | ) {
533 | const {
534 | mappedType: { readonly, typeParam, nameType, optional, tsType },
535 | } = take(children);
536 | return (
537 | <>
538 | {readonly} [
542 | {typeParam}
543 |
544 | {nameType && (
545 | <>
546 |
547 | in keyof{" "}
548 |
549 |
550 | {nameType}
551 |
552 | >
553 | )}]{optional}
554 | {tsType && (
555 | <>
556 | :{" "}
557 |
558 | {tsType}
559 |
560 | >
561 | )}
562 | >
563 | );
564 | }
565 |
566 | function TypeDefTuple(
567 | { children, context }: {
568 | children: Child;
569 | context: Context;
570 | },
571 | ) {
572 | const { tuple } = take(children);
573 | if (tuple.length <= 3) {
574 | const items = [];
575 | for (let i = 0; i < tuple.length; i++) {
576 | items.push(
577 |
578 | {tuple[i]}
579 | ,
580 | );
581 | if (i < tuple.length - 1) {
582 | items.push(", ");
583 | }
584 | }
585 | return [{items}] ;
586 | }
587 | const items = tuple.map((def) => (
588 |
589 |
590 | {def}
591 | ,{" "}
592 |
593 | ));
594 | return [{items}]
;
595 | }
596 |
597 | function TypeDefUnion(
598 | { children, context }: {
599 | children: Child;
600 | context: Context;
601 | },
602 | ) {
603 | const { union } = take(children);
604 | const lastIndex = union.length - 1;
605 | if (union.length <= 3) {
606 | const items = [];
607 | for (let i = 0; i < union.length; i++) {
608 | items.push(
609 |
610 | {union[i]}
611 | ,
612 | );
613 | if (i < lastIndex) {
614 | items.push({" | "} );
615 | }
616 | }
617 | return {items} ;
618 | }
619 | const items = union.map((def) => (
620 |
621 | {" | "}
622 |
623 | {def}
624 |
625 |
626 | ));
627 | return {items}
;
628 | }
629 |
630 | function TypeParamSummary(
631 | { children, constraintKind = "extends", context }: {
632 | children: Child;
633 | constraintKind?: string;
634 | context: Context;
635 | },
636 | ) {
637 | const { name, constraint, default: def } = take(children);
638 | return (
639 | <>
640 | {name}
641 | {constraint && (
642 | <>
643 | {` ${constraintKind} `}
644 |
645 | {constraint}
646 |
647 | >
648 | )}
649 | {def && (
650 | <>
651 | {` = `}
652 |
653 | {def}
654 |
655 | >
656 | )}
657 | >
658 | );
659 | }
660 |
661 | export function DocTypeParamsSummary(
662 | { children, context }: {
663 | children: Child;
664 | context: Context;
665 | },
666 | ) {
667 | const typeParams = take(children, true);
668 | if (typeParams.length === 0) {
669 | return null;
670 | }
671 |
672 | return (
673 |
674 | {"<"}
675 | {typeParams.map((typeParam, i) => (
676 | <>
677 |
678 | {typeParam.name}
679 | {typeParam.constraint && (
680 |
681 | {" extends "}
682 |
683 | {typeParam.constraint}
684 |
685 |
686 | )}
687 | {typeParam.default && (
688 |
689 | {" = "}
690 |
691 | {typeParam.default}
692 |
693 |
694 | )}
695 |
696 | {i !== (typeParams.length - 1) && ,{" "} }
697 | >
698 | ))}
699 | {">"}
700 |
701 | );
702 | }
703 |
704 | export function TypeParam(
705 | { children, id, location, doc, context }: {
706 | children: Child;
707 | id: string;
708 | location: Location;
709 | doc?: JsDocTagNamed;
710 | context: Context;
711 | },
712 | ) {
713 | const def = take(children);
714 |
715 | const tags = [];
716 | if (def.default) {
717 | tags.push(tagVariants.optional());
718 | }
719 |
720 | return (
721 |
729 | {def.constraint && (
730 |
731 | {" extends "}
732 |
733 | {def.constraint}
734 |
735 |
736 | )}
737 | {def.default && (
738 |
739 | {" = "}
740 |
741 | {def.default}
742 |
743 |
744 | )}
745 |
746 | );
747 | }
748 |
749 | export function TypeParamsDoc(
750 | { children, base, context }: {
751 | children: Child;
752 | base: DocNode;
753 | context: Context;
754 | },
755 | ) {
756 | const defs = take(children, true);
757 |
758 | const typeParamDocs: JsDocTagNamed[] =
759 | (base.jsDoc?.tags?.filter(({ kind }) => kind === "template") as
760 | | JsDocTagNamed[]
761 | | undefined) ??
762 | [];
763 |
764 | const items = defs.map((typeParam) => {
765 | const id = nameToId("type_param", typeParam.name);
766 |
767 | return (
768 | name === typeParam.name)}
772 | context={context}
773 | >
774 | {typeParam}
775 |
776 | );
777 | });
778 |
779 | return ;
780 | }
781 |
--------------------------------------------------------------------------------
/doc/usage.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { style } from "../styles.ts";
4 | import { camelize, parseURL } from "./utils.ts";
5 | import * as Icons from "../icons.tsx";
6 | import { Markdown } from "./markdown.tsx";
7 |
8 | interface ParsedUsage {
9 | /** The symbol that the item should be imported as. If `usageSymbol` and
10 | * `localVar` is defined, then the item is a named import, otherwise it is
11 | * a namespace import. */
12 | importSymbol: string;
13 | /** The undecorated code of the generated import statement to import and use
14 | * the item. */
15 | importStatement: string;
16 | /** The final specifier that should be used to import from. */
17 | importSpecifier: string;
18 | /** The local variable that the `usageSymbol` should be destructured out of.
19 | */
20 | localVar?: string;
21 | /** The symbol that should be destructured from the `localVar` which will be
22 | * bound to the item's value. */
23 | usageSymbol?: string;
24 | }
25 |
26 | /** Given the URL and optional item and is type flag, provide back a parsed
27 | * version of the usage of an item for rendering. */
28 | export function parseUsage(
29 | url: URL,
30 | item?: string,
31 | isType?: boolean,
32 | clearSearch = true,
33 | ): string {
34 | const parsed = parseURL(url);
35 | const target = new URL(url);
36 | if (clearSearch) {
37 | target.search = "";
38 | }
39 | const itemParts = item?.split(".");
40 | // when the imported symbol is a namespace import, we try to guess at an
41 | // intelligent camelized name for the import based on the package name. If
42 | // it is a named import, we simply import the symbol itself.
43 | const importSymbol = itemParts
44 | ? itemParts[0]
45 | : camelize(parsed?.package ?? "mod");
46 | // when using a symbol from an imported namespace exported from a module, we
47 | // need to create the local symbol, which we identify here.
48 | const usageSymbol = itemParts && itemParts.length > 1
49 | ? itemParts.pop()
50 | : undefined;
51 | // if it is namespaces within namespaces, we simply re-join them together
52 | // instead of trying to figure out some sort of nested restructuring
53 | const localVar = itemParts?.join(".");
54 | // we create an import statement which is used to populate the copy paste
55 | // snippet of code.
56 | let importStatement = item
57 | ? `import { ${
58 | isType ? "type " : ""
59 | }${importSymbol} } from "${target.href}";`
60 | : `import * as ${importSymbol} from "${target.href}";`;
61 | // if we are using a symbol off a imported namespace, we need to destructure
62 | // it to a local variable.
63 | if (usageSymbol) {
64 | importStatement += `\nconst { ${usageSymbol} } = ${localVar};`;
65 | }
66 | return importStatement;
67 | }
68 |
69 | export function Usage(
70 | { url, name, isType }: { url: URL; name?: string; isType?: boolean },
71 | ) {
72 | const importStatement = parseUsage(url, name, isType, true);
73 | const onClick =
74 | // deno-lint-ignore no-explicit-any
75 | `navigator?.clipboard?.writeText('${importStatement}');` as any;
76 |
77 | return (
78 |
79 |
80 | {`\`\`\`typescript\n${importStatement}\n\`\`\``}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/doc/utils.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | type DocNode,
5 | type DocNodeClass,
6 | type DocNodeEnum,
7 | type DocNodeFunction,
8 | type DocNodeImport,
9 | type DocNodeInterface,
10 | type DocNodeModuleDoc,
11 | type DocNodeNamespace,
12 | type DocNodeTypeAlias,
13 | type DocNodeVariable,
14 | type JsDoc,
15 | type JsDocTagDoc,
16 | } from "../deps.ts";
17 |
18 | /** Some JSX libraries (notably nano-jsx) have strange handling of the
19 | * child element and don't have good typings when creating a functional
20 | * component. This type and the function {@linkcode take} abstract this
21 | * away. */
22 | export type Child = T | [T];
23 |
24 | export interface DocNodeCollection {
25 | moduleDoc?: DocNodeTupleArray;
26 | import?: DocNodeTupleArray;
27 | namespace?: DocNodeTupleArray;
28 | class?: DocNodeTupleArray;
29 | enum?: DocNodeTupleArray;
30 | variable?: DocNodeTupleArray;
31 | function?: DocNodeTupleArray;
32 | interface?: DocNodeTupleArray;
33 | typeAlias?: DocNodeTupleArray;
34 | }
35 |
36 | export type DocNodeTupleArray = [label: string, node: N][];
37 |
38 | interface ParsedURL {
39 | registry: string;
40 | org?: string;
41 | package?: string;
42 | version?: string;
43 | module?: string;
44 | }
45 |
46 | function appendCollection(
47 | collection: DocNodeCollection,
48 | nodes: DocNode[],
49 | path?: string,
50 | includePrivate = false,
51 | ) {
52 | for (const node of nodes) {
53 | if (includePrivate || node.declarationKind !== "private") {
54 | if (node.kind === "namespace" && !node.namespaceDef.elements.length) {
55 | continue;
56 | }
57 | if (node.kind === "moduleDoc" && path) {
58 | continue;
59 | }
60 | const docNodes: DocNodeTupleArray = collection[node.kind] ??
61 | (collection[node.kind] = []);
62 | const label = path ? `${path}.${node.name}` : node.name;
63 | docNodes.push([label, node]);
64 | if (node.kind === "namespace") {
65 | appendCollection(
66 | collection,
67 | node.namespaceDef.elements,
68 | label,
69 | includePrivate,
70 | );
71 | }
72 | }
73 | }
74 | }
75 |
76 | export function asCollection(
77 | nodes: DocNode[],
78 | path?: string,
79 | includePrivate = false,
80 | ): DocNodeCollection {
81 | const collection: DocNodeCollection = {};
82 | appendCollection(collection, nodes, path, includePrivate);
83 | return collection;
84 | }
85 |
86 | export function assert(
87 | cond: unknown,
88 | message = "Assertion error",
89 | ): asserts cond {
90 | if (!cond) {
91 | throw new Error(message);
92 | }
93 | }
94 |
95 | export function byName(
96 | a: [label: string, node: Node],
97 | b: [label: string, node: Node],
98 | ) {
99 | return a[0].localeCompare(b[0]);
100 | }
101 |
102 | /** Convert a string into a camelCased string. */
103 | export function camelize(str: string): string {
104 | return str.split(/[\s_\-]+/).map((word, index) =>
105 | index === 0
106 | ? word.toLowerCase()
107 | : `${word.charAt(0).toUpperCase()}${word.slice(1).toLowerCase()}`
108 | ).join("");
109 | }
110 |
111 | /** Convert a camelCased string into a normal string. */
112 | export function decamelize(str: string): string {
113 | return str.split("").map((char) =>
114 | /[A-Z]/.test(char) ? ` ${char.toLowerCase()}` : char
115 | ).join("");
116 | }
117 |
118 | export function isAbstract(node: DocNode) {
119 | if (node.kind === "class") {
120 | return node.classDef.isAbstract;
121 | }
122 | return false;
123 | }
124 |
125 | export function isDeprecated(
126 | node?: { jsDoc?: JsDoc },
127 | ): JsDocTagDoc | undefined {
128 | if (node && node.jsDoc && node.jsDoc.tags) {
129 | return node.jsDoc.tags.find(({ kind }) => kind === "deprecated") as
130 | | JsDocTagDoc
131 | | undefined;
132 | }
133 | }
134 |
135 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
136 | * which defaults to `undefined`. */
137 | export function maybe(cond: unknown, isTrue: T): T | null;
138 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
139 | * which defaults to `undefined`. */
140 | export function maybe(cond: unknown, isTrue: T, isFalse: F): T | F;
141 | /** If the condition is true, return the `isTrue` value, other return `isFalse`
142 | * which defaults to `undefined`. */
143 | export function maybe(
144 | cond: unknown,
145 | isTrue: T,
146 | isFalse?: F,
147 | ): T | F | null {
148 | return cond ? isTrue : isFalse ?? null;
149 | }
150 |
151 | /** Patterns of "registries" which will be parsed to be displayed in a more
152 | * human readable way. */
153 | const patterns = {
154 | "deno.land/x": [
155 | new URLPattern(
156 | "https://deno.land/x/:pkg([^@/]+){@}?:ver?/:mod*",
157 | ),
158 | ],
159 | "deno.land/std": [new URLPattern("https://deno.land/std{@}?:ver?/:mod*")],
160 | "nest.land": [new URLPattern("https://x.nest.land/:pkg([^@/]+)@:ver/:mod*")],
161 | "crux.land": [new URLPattern("https://crux.land/:pkg([^@/]+)@:ver")],
162 | "github.com": [
163 | new URLPattern(
164 | "https://raw.githubusercontent.com/:org/:pkg/:ver/:mod*",
165 | ),
166 | // https://github.com/denoland/deno_std/raw/main/http/mod.ts
167 | new URLPattern(
168 | "https://github.com/:org/:pkg/raw/:ver/:mod*",
169 | ),
170 | ],
171 | "gist.github.com": [
172 | new URLPattern(
173 | "https://gist.githubusercontent.com/:org/:pkg/raw/:ver/:mod*",
174 | ),
175 | ],
176 | "esm.sh": [
177 | new URLPattern(
178 | "http{s}?://esm.sh/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
179 | ),
180 | // https://cdn.esm.sh/v58/firebase@9.4.1/database/dist/database/index.d.ts
181 | new URLPattern(
182 | "http{s}?://cdn.esm.sh/:regver*/:org(@[^/]+)?/:pkg([^@/]+)@:ver/:mod*",
183 | ),
184 | ],
185 | "skypack.dev": [
186 | new URLPattern({
187 | protocol: "https",
188 | hostname: "cdn.skypack.dev",
189 | pathname: "/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
190 | search: "*",
191 | }),
192 | // https://cdn.skypack.dev/-/@firebase/firestore@v3.4.3-A3UEhS17OZ2Vgra7HCZF/dist=es2019,mode=types/dist/index.d.ts
193 | new URLPattern(
194 | "https://cdn.skypack.dev/-/:org(@[^/]+)?/:pkg([^@/]+)@:ver([^-]+):hash/:path*",
195 | ),
196 | ],
197 | "unpkg.com": [
198 | new URLPattern(
199 | "https://unpkg.com/:org(@[^/]+)?/:pkg([^@/]+){@}?:ver?/:mod?",
200 | ),
201 | ],
202 | };
203 |
204 | /** Take a string URL and attempt to pattern match it against a known registry
205 | * and returned the parsed structure. */
206 | export function parseURL(url: URL): ParsedURL | undefined {
207 | for (const [registry, pattern] of Object.entries(patterns)) {
208 | for (const pat of pattern) {
209 | const match = pat.exec(url);
210 | if (match) {
211 | let { pathname: { groups: { regver, org, pkg, ver, mod } } } = match;
212 | if (registry === "gist.github.com") {
213 | pkg = pkg?.substring(0, 7);
214 | ver = ver?.substring(0, 7);
215 | }
216 | return {
217 | registry: regver ? `${registry} @ ${regver}` : registry,
218 | org: org || undefined,
219 | package: pkg || undefined,
220 | version: ver || undefined,
221 | module: mod || undefined,
222 | };
223 | }
224 | }
225 | }
226 | }
227 |
228 | /** A utility function that inspects a value, and if the value is an array,
229 | * returns the first element of the array, otherwise returns the value. This is
230 | * used to deal with the ambiguity around children properties with nano_jsx. */
231 | export function take(
232 | value: Child,
233 | itemIsArray = false,
234 | isArrayOfArrays = false,
235 | ): T {
236 | if (itemIsArray) {
237 | if (isArrayOfArrays) {
238 | return Array.isArray(value) && Array.isArray(value[0]) &&
239 | Array.isArray(value[0][0])
240 | ? value[0]
241 | : value as T;
242 | } else {
243 | return Array.isArray(value) && Array.isArray(value[0])
244 | ? value[0]
245 | : value as T;
246 | }
247 | } else {
248 | return Array.isArray(value) ? value[0] : value;
249 | }
250 | }
251 |
252 | /**
253 | * Splits a markdown file by its first line or first codeblock, depending on
254 | * which is first.
255 | *
256 | * @param markdown
257 | */
258 | export function splitMarkdownTitle(
259 | markdown: string,
260 | ): [summary: string, body: string] {
261 | let newlineIndex = markdown.indexOf("\n\n");
262 | let codeblockIndex = markdown.indexOf("```");
263 | if (newlineIndex == -1) {
264 | newlineIndex = Infinity;
265 | }
266 | if (codeblockIndex == -1) {
267 | codeblockIndex = Infinity;
268 | }
269 | const splitIndex = Math.min(newlineIndex, codeblockIndex);
270 | const summary = markdown.slice(0, splitIndex).trim();
271 | const body = markdown.slice(splitIndex).trim();
272 | return [summary, body];
273 | }
274 |
275 | /**
276 | * Get the property from a property string.
277 | * @return the property and whether it is part of the prototype or static.
278 | */
279 | export function processProperty(
280 | property: string,
281 | ): [property: string, isPrototype: boolean] {
282 | const isPrototype = property.startsWith("prototype.");
283 | const propName = isPrototype ? property.slice(10) : property;
284 | return [propName, isPrototype];
285 | }
286 |
--------------------------------------------------------------------------------
/doc/variables.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { type DocNodeVariable } from "../deps.ts";
4 | import { style } from "../styles.ts";
5 | import { TypeDef } from "./types.tsx";
6 | import { type Child, take } from "./utils.ts";
7 | import { Context } from "./markdown.tsx";
8 | import { DocEntry, Examples, nameToId, Section } from "./doc_common.tsx";
9 |
10 | export function DocBlockVariable(
11 | { children, context }: {
12 | children: Child;
13 | context: Context;
14 | },
15 | ) {
16 | const def = take(children);
17 | const id = nameToId("variable", def.name);
18 |
19 | if (!def.variableDef.tsType) {
20 | return null;
21 | }
22 |
23 | return (
24 |
25 | {def.jsDoc}
26 |
27 |
28 | {[
29 |
30 |
31 | {def.variableDef.tsType}
32 |
33 | ,
34 | ]}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/doc_test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { assertEquals } from "./deps_test.ts";
4 | import { type DocNode } from "./deps.ts";
5 | import { byKind } from "./doc/doc.ts";
6 | import { processProperty, splitMarkdownTitle } from "./doc/utils.ts";
7 |
8 | Deno.test({
9 | name: "doc - sort by kind",
10 | fn() {
11 | const fixtures: DocNode[] = [
12 | {
13 | name: "namespace",
14 | kind: "namespace",
15 | namespaceDef: { elements: [] },
16 | location: { filename: "", line: 0, col: 0 },
17 | declarationKind: "export",
18 | },
19 | {
20 | name: "fn",
21 | kind: "function",
22 | functionDef: {
23 | params: [],
24 | isAsync: false,
25 | isGenerator: false,
26 | typeParams: [],
27 | },
28 | location: { filename: "", line: 0, col: 0 },
29 | declarationKind: "export",
30 | },
31 | {
32 | name: "A",
33 | kind: "interface",
34 | interfaceDef: {
35 | extends: [],
36 | methods: [],
37 | properties: [],
38 | callSignatures: [],
39 | indexSignatures: [],
40 | typeParams: [],
41 | },
42 | location: { filename: "", line: 0, col: 0 },
43 | declarationKind: "export",
44 | },
45 | ];
46 | fixtures.sort(byKind);
47 | assertEquals(fixtures.map(({ kind }) => kind), [
48 | "namespace",
49 | "interface",
50 | "function",
51 | ]);
52 | },
53 | });
54 |
55 | Deno.test("splitMarkdownTitle - simple", () => {
56 | const markdown = `some text
57 |
58 | \`\`\`
59 | // comment
60 | \`\`\`
61 | `;
62 | const [summary, body] = splitMarkdownTitle(markdown);
63 | assertEquals(summary, "some text");
64 | assertEquals(
65 | body,
66 | `\`\`\`
67 | // comment
68 | \`\`\``,
69 | );
70 | });
71 |
72 | Deno.test("splitMarkdownTitle - markdown only", () => {
73 | const markdown = `\`\`\`
74 | // comment
75 | \`\`\`
76 | `;
77 | const [summary, body] = splitMarkdownTitle(markdown);
78 | assertEquals(summary, "");
79 | assertEquals(
80 | body,
81 | `\`\`\`
82 | // comment
83 | \`\`\``,
84 | );
85 | });
86 |
87 | Deno.test("splitMarkdownTitle - summary only", () => {
88 | const markdown = "some text";
89 | const [summary, body] = splitMarkdownTitle(markdown);
90 | assertEquals(summary, "some text");
91 | assertEquals(body, "");
92 | });
93 |
94 | Deno.test("splitMarkdownTitle - paragraphs only", () => {
95 | const markdown = `some text
96 |
97 | hello world`;
98 | const [summary, body] = splitMarkdownTitle(markdown);
99 | assertEquals(summary, "some text");
100 | assertEquals(body, "hello world");
101 | });
102 |
103 | Deno.test("splitMarkdownTitle - tight", () => {
104 | const markdown = `some text
105 | \`\`\`
106 | // comment
107 | \`\`\`
108 | `;
109 | const [summary, body] = splitMarkdownTitle(markdown);
110 | assertEquals(summary, "some text");
111 | assertEquals(
112 | body,
113 | `\`\`\`
114 | // comment
115 | \`\`\``,
116 | );
117 | });
118 |
119 | Deno.test("processProperty - prototype", () => {
120 | const [property, isPrototype] = processProperty("prototype.foo");
121 | assertEquals(property, "foo");
122 | assertEquals(isPrototype, true);
123 | });
124 |
125 | Deno.test("processProperty - static", () => {
126 | const [property, isPrototype] = processProperty("foo");
127 | assertEquals(property, "foo");
128 | assertEquals(isPrototype, false);
129 | });
130 |
131 | Deno.test("processProperty - prototype compute", () => {
132 | const [property, isPrototype] = processProperty(
133 | "prototype.[Symbol.iterator]",
134 | );
135 | assertEquals(property, "[Symbol.iterator]");
136 | assertEquals(isPrototype, true);
137 | });
138 |
139 | Deno.test("processProperty - static compute", () => {
140 | const [property, isPrototype] = processProperty("[Symbol.iterator]");
141 | assertEquals(property, "[Symbol.iterator]");
142 | assertEquals(isPrototype, false);
143 | });
144 |
--------------------------------------------------------------------------------
/icons.tsx:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | export function Dir(props: { class?: string }) {
4 | return (
5 |
13 |
17 |
18 | );
19 | }
20 |
21 | export function Source(props: { class?: string }) {
22 | return (
23 |
31 |
38 |
45 |
46 | );
47 | }
48 |
49 | export function Index() {
50 | return (
51 |
58 |
64 |
65 | );
66 | }
67 |
68 | export function TriangleRight(
69 | props: {
70 | class?: string;
71 | tabindex?: number;
72 | onKeyDown?: string;
73 | "aria-label"?: string;
74 | },
75 | ) {
76 | return (
77 | // @ts-ignore onKeyDown does support strings
78 |
86 |
87 |
88 | );
89 | }
90 |
91 | export function Copy() {
92 | return (
93 |
100 |
104 |
105 | );
106 | }
107 |
108 | export function GitHub(props: { class?: string }) {
109 | return (
110 |
115 |
120 |
121 | );
122 | }
123 |
124 | export function Discord(props: { class?: string }) {
125 | return (
126 |
131 |
132 |
133 | );
134 | }
135 |
136 | export function Twitter(props: { class?: string }) {
137 | return (
138 |
143 |
144 |
145 | );
146 | }
147 |
148 | export function Plus() {
149 | return (
150 |
157 |
166 |
175 |
176 | );
177 | }
178 |
179 | export function Minus(props: { class?: string }) {
180 | return (
181 |
189 |
198 |
199 | );
200 | }
201 |
202 | export function TrashCan(props: { class?: string }) {
203 | return (
204 |
212 |
218 |
219 | );
220 | }
221 |
222 | export function ExclamationMark(props: { class?: string }) {
223 | return (
224 |
232 |
240 |
249 |
250 | );
251 | }
252 |
253 | export function Logo(props: { class?: string }) { // Size not normalized
254 | return (
255 |
262 |
263 |
267 |
271 |
275 |
279 |
283 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 | );
295 | }
296 |
297 | export function Deno(props: { class?: string }) { // Size not normalized
298 | return (
299 |
306 |
307 |
311 |
315 |
319 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 | );
331 | }
332 |
333 | export function Link(props: { class?: string }) {
334 | return (
335 |
343 |
344 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 | );
358 | }
359 |
360 | export function LinkLine(props: { class?: string }) {
361 | return (
362 |
370 |
377 |
384 |
385 | );
386 | }
387 |
388 | export function YouTube(props: { class?: string }) {
389 | return (
390 |
399 |
403 |
404 | );
405 | }
406 |
407 | export function Mastodon(props: { class?: string }) {
408 | return (
409 |
417 |
423 |
424 | );
425 | }
426 |
427 | export function Menu(props: { class?: string }) { // Size not normalized
428 | return (
429 |
437 |
445 |
453 |
461 |
462 | );
463 | }
464 |
465 | export function Cross(props: { class?: string }) { // Size not normalized
466 | return (
467 |
473 |
479 |
480 | );
481 | }
482 |
--------------------------------------------------------------------------------
/services.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import {
4 | comrak,
5 | type Configuration as TwConfiguration,
6 | type CSSRules,
7 | type Directive,
8 | setup as twSetup,
9 | } from "./deps.ts";
10 | import { mdToHtml } from "./doc/markdown.tsx";
11 | import { comrakStyles } from "./styles.ts";
12 |
13 | export interface Configuration {
14 | /** Called when the doc components are trying to resolve a symbol. The
15 | * current url is provided as a string, an optional namespace and the symbol
16 | * name attempting to be resolved.
17 | *
18 | * If provided the namespace, any nested namespaces will be separated by a
19 | * `.`.
20 | *
21 | * Implementors should search the scope of the current module and namespace
22 | * ascending to global scopes to resolve the href. If the symbol cannot be
23 | * found, the function should return `undefined`. */
24 | lookupHref?: (
25 | url: URL,
26 | namespace: string | undefined,
27 | symbol: string,
28 | ) => string | undefined;
29 | /** Called when the doc components are trying to generate a link to a path,
30 | * module or symbol within a module. The URL to the path or module will be
31 | * provided, and the symbol will be provided. If the symbol contains `.`,
32 | * the symbol is located within a namespace in the file.
33 | *
34 | * Implementors should return a string which will be used as the `href` value
35 | * for a link. */
36 | resolveHref?: (
37 | url: URL,
38 | symbol?: string,
39 | namespace?: string,
40 | property?: string,
41 | ) => string;
42 | /** Called when doc components are trying to generate a link to a source file.
43 | *
44 | * Implementors should return a string which which will be used as the `href`
45 | * value for a link to the source code view of a file. If no source file can
46 | * be resolved, `undefined` should be returned. */
47 | resolveSourceHref?: (url: string, line?: number) => string;
48 | /** Called when markdown needs to be rendered. */
49 | markdownToHTML?: (markdown: string) => string;
50 | /** If provided, the twind {@linkcode twSetup setup} will be performed. */
51 | tw?: TwConfiguration;
52 | /** Class to give to markdown blocks */
53 | markdownStyle?: string | Directive;
54 | /** Class to give to markdown summary blocks */
55 | markdownSummaryStyle?: string | Directive;
56 | }
57 |
58 | const runtimeConfig: Required<
59 | Pick<
60 | Configuration,
61 | | "resolveHref"
62 | | "lookupHref"
63 | | "resolveSourceHref"
64 | | "markdownToHTML"
65 | | "markdownStyle"
66 | | "markdownSummaryStyle"
67 | >
68 | > = {
69 | resolveHref(current, symbol, _namespace, property) {
70 | return symbol
71 | ? (property
72 | ? `/${current}/~/${symbol}/~/${property}`
73 | : `/${current}/~/${symbol}`)
74 | : `/${current}`;
75 | },
76 | lookupHref(current, namespace, symbol) {
77 | return namespace
78 | ? `/${current}/~/${namespace}.${symbol}`
79 | : `/${current}/~/${symbol}`;
80 | },
81 | resolveSourceHref(url, line) {
82 | return line ? `${url}#L${line}` : url;
83 | },
84 | markdownToHTML: mdToHtml,
85 | markdownStyle: comrakStyles,
86 | markdownSummaryStyle: "",
87 | };
88 |
89 | /** Setup the services used by the doc components. */
90 | export async function setup(config: Configuration) {
91 | const { tw, ...other } = config;
92 | Object.assign(runtimeConfig, other);
93 | if (tw) {
94 | twSetup(tw);
95 | }
96 | if (!other.markdownToHTML) {
97 | await comrak.init();
98 | }
99 | }
100 |
101 | export const services = {
102 | /** Return a link to the provided URL and optional symbol. */
103 | get resolveHref(): (
104 | url: URL,
105 | symbol?: string,
106 | namespace?: string,
107 | property?: string,
108 | ) => string {
109 | return runtimeConfig.resolveHref;
110 | },
111 |
112 | /** Attempt to find a link to a specific symbol from the current URL and
113 | * optionally namespace. */
114 | get lookupHref(): (
115 | url: URL,
116 | namespace: string | undefined,
117 | symbol: string,
118 | ) => string | undefined {
119 | return runtimeConfig.lookupHref;
120 | },
121 |
122 | get resolveSourceHref(): (url: string, line?: number) => string | undefined {
123 | return runtimeConfig.resolveSourceHref;
124 | },
125 |
126 | /** Render Markdown to HTML */
127 | get markdownToHTML(): (markdown: string) => string {
128 | return runtimeConfig.markdownToHTML;
129 | },
130 |
131 | /** Class to give to markdown blocks */
132 | get markdownStyle(): string | Directive {
133 | return runtimeConfig.markdownStyle;
134 | },
135 | /** Class to give to markdown summary blocks */
136 | get markdownSummaryStyle(): string | Directive {
137 | return runtimeConfig.markdownSummaryStyle;
138 | },
139 | };
140 |
--------------------------------------------------------------------------------
/styles.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021-2023 the Deno authors. All rights reserved. MIT license.
2 |
3 | import { apply, css, type Directive, tw } from "./deps.ts";
4 |
5 | export const comrakStyles = css({
6 | // code
7 | ":not(pre) > code": apply`font-mono text-sm py-1 px-1.5 rounded bg-gray-100`,
8 | pre:
9 | apply`font-mono text-sm p-2.5 rounded-lg text-black bg-gray-100 overflow-x-auto`,
10 |
11 | // general
12 | a: apply`link`,
13 | h1: apply`text-xl md:text-2xl lg:text-3xl`,
14 | h2: apply`text-lg md:text-xl lg:text-2xl`,
15 | h3: apply`font-bold md:text-lg md:font-normal lg:text-xl lg:font-normal`,
16 | h4: apply`font-semibold md:font-bold lg:text-lg lg:font-normal`,
17 | h5: apply`font-italic md:font-semibold lg:font-bold`,
18 | h6: apply`md:font-italic lg:font-semibold`,
19 | hr: apply`m-2 border-gray-500`,
20 | ol: apply`list-decimal lg:list-inside`,
21 | p: apply`my-1 text-left`,
22 | table: apply`table-auto`,
23 | td: apply`p-2 border border-solid border-gray-500`,
24 | th: apply`font-bold text-center`,
25 | ul: apply`lg:list-disc lg:list-inside`,
26 |
27 | // syntax highlighting
28 | ".code-comment": apply`text-gray-500`,
29 | ".code-function": apply`text-green-700`,
30 | ".code-literal": apply`text-cyan-600`,
31 | ".code-keyword, .code-operator, .code-variable.code-language":
32 | apply`text-purple-800`,
33 | ".code-number, .code-doctag": apply`text-indigo-600`,
34 | ".code-regexp": apply`text-red-700`,
35 | ".code-string": apply`text-yellow-500`,
36 | ".code-type, .code-built_in": apply`text-cyan-600 italic`,
37 | });
38 |
39 | const styles = {
40 | anchor:
41 | apply`float-left leading-none hidden group-hover:block text-gray-600 -ml-[18px] pr-[4px]`,
42 | copyButton: apply`rounded border border-gray-300 p-1.5 hover:bg-gray-100`,
43 | details: css({
44 | "& > summary": apply`list-none`,
45 | "& > summary::-webkit-details-marker": apply`hidden`,
46 | "&[open] svg": apply`rotate-90`,
47 | }),
48 | docBlockItems: apply`space-y-7`,
49 | docEntry: apply`flex justify-between`,
50 | docEntryChildren: apply`break-words flex items-center gap-2`,
51 | docItem: apply`group relative`,
52 | indent: apply`ml-4`,
53 | main: apply`space-y-7 md:col-span-3`,
54 | markdown: apply`flex flex-col space-y-4 text-justify`,
55 | markdownSummary: apply`inline text-gray-600 ${
56 | css({
57 | "p": apply`inline-block`,
58 | })
59 | }`,
60 | moduleDoc: apply`space-y-6`,
61 | moduleDocHeader: apply`flex justify-between mb-8`,
62 | moduleIndex: apply`rounded-lg w-full border border-gray-300`,
63 | moduleIndexHeader: apply`flex justify-between items-center py-3.5 pr-5`,
64 | moduleIndexHeaderTitle: apply`ml-5 font-semibold text-lg flex items-center`,
65 | moduleIndexHeaderTitleSpan: apply`ml-2 leading-none`,
66 | moduleIndexTable: apply`block lg:table w-full`,
67 | moduleIndexRow: apply`block lg:table-row odd:bg-gray-50`,
68 | moduleIndexLinkCell:
69 | apply`block lg:table-cell pl-5 pr-3 py-2.5 font-semibold`,
70 | moduleIndexLinkCellIcon: apply`inline my-1.5 mr-3`,
71 | moduleIndexDocCell:
72 | apply`block lg:table-cell lg:pl-0 lg:pt-2.5 lg:mt-0 pl-11 pr-[1.375rem] pb-2.5 -mt-2 text-gray-500`,
73 | moduleIndexPanel: apply`lg:w-72 flex-shrink-0`,
74 | moduleIndexPanelActive: apply`bg-gray-100 font-bold`,
75 | moduleIndexPanelEntry:
76 | apply`flex items-center gap-2 py-2 px-3 rounded-lg w-full leading-6 hover:text-gray-500 hover:bg-gray-50 children:last-child:truncate children:last-child:flex-shrink-1`,
77 | moduleIndexPanelModuleIndex: apply`text-gray-500 font-light`,
78 | moduleIndexPanelSymbol:
79 | apply`flex items-center justify-between gap-1 py-1.5 pl-2.5 pr-3 rounded-lg w-full leading-6 hover:text-gray-500 hover:bg-gray-50 children:first-child:flex children:first-child:items-center children:first-child:gap-2 children:first-child:min-w-0 children:first-child:children:last-child:truncate`,
80 | section: apply`text-sm leading-6 font-semibold text-gray-400 py-1`,
81 | symbolDoc: apply`space-y-12 md:col-span-3`,
82 | symbolDocHeader: apply`flex justify-between items-start`,
83 | symbolKind:
84 | apply`rounded-full w-6 h-6 inline-flex items-center justify-center font-medium text-xs leading-none flex-shrink-0 select-none`,
85 | sourceLink:
86 | apply`pl-2 break-words text-gray-600 hover:text-gray-800 hover:underline`,
87 | symbolListCellSymbol:
88 | apply`block lg:table-cell py-1 pr-3 font-bold children:space-x-2 children:min-w-[13rem] children:flex children:items-center`,
89 | symbolListCellDoc: apply`block lg:table-cell py-1 text-sm text-gray-500`,
90 | symbolListRow: apply`block lg:table-row`,
91 | symbolListTable: apply`block lg:table`,
92 | symbolKindDisplay:
93 | apply`w-11 flex-none flex children:not-first-child:-ml-[7px]`,
94 | tag:
95 | apply`inline-flex items-center gap-0.5 children:flex-none rounded-full font-medium text-sm leading-none`,
96 | } as const;
97 |
98 | export type StyleKey = keyof typeof styles;
99 |
100 | export function style(name: StyleKey): string;
101 | // deno-lint-ignore no-explicit-any
102 | export function style(name: StyleKey, raw: boolean): string | Directive;
103 | // deno-lint-ignore no-explicit-any
104 | export function style(name: StyleKey, raw: true): Directive;
105 | export function style(
106 | name: StyleKey,
107 | raw = false,
108 | // deno-lint-ignore no-explicit-any
109 | ): string | Directive {
110 | if (raw) {
111 | return styles[name];
112 | }
113 | return tw`${styles[name]}`;
114 | }
115 |
--------------------------------------------------------------------------------
/twind.config.ts:
--------------------------------------------------------------------------------
1 | import { apply, type Plugin, type ThemeConfiguration } from "twind";
2 | import * as twColors from "twind/colors";
3 | import { css } from "twind/css";
4 |
5 | export const theme: ThemeConfiguration = {
6 | colors: {
7 | transparent: "transparent",
8 | current: "currentColor",
9 | ...twColors,
10 | },
11 | fontFamily: {
12 | mono: [
13 | "Menlo",
14 | "Monaco",
15 | '"Lucida Console"',
16 | "Consolas",
17 | '"Liberation Mono"',
18 | '"Courier New"',
19 | "monospace",
20 | ],
21 | },
22 | extend: {
23 | spacing: {
24 | 4.5: "1.125rem",
25 | 18: "4.5rem",
26 | 72: "18rem",
27 | },
28 | backgroundSize: {
29 | "4": "1rem",
30 | },
31 | },
32 | };
33 |
34 | export const plugins: Record = {
35 | link:
36 | apply`text-blue-600 transition duration-75 ease-in-out hover:text-blue-400`,
37 | "section-x-inset": (parts) =>
38 | parts[0] === "none"
39 | ? apply`max-w-none mx-0 px-0`
40 | : apply`max-w-screen-${parts[0]} mx-auto px-6 md:px-8 lg:px-10 xl:px-14`,
41 | "divide-incl-y": (parts) =>
42 | css({
43 | "& > *": {
44 | "&:first-child": {
45 | "border-top-width": (parts[0] ?? 1) + "px",
46 | },
47 | "border-top-width": "0px",
48 | "border-bottom-width": (parts[0] ?? 1) + "px",
49 | },
50 | }),
51 | "icon-button": apply`border border-gray-300 rounded p-2 hover:bg-gray-100`,
52 | };
53 |
--------------------------------------------------------------------------------