shelving 1.201.0 → 1.203.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extract/DirectoryExtractor.d.ts +29 -0
- package/extract/DirectoryExtractor.js +48 -0
- package/extract/Extractor.d.ts +11 -0
- package/extract/Extractor.js +6 -0
- package/extract/FileExtractor.d.ts +17 -0
- package/extract/FileExtractor.js +25 -0
- package/extract/MarkdownExtractor.d.ts +13 -0
- package/extract/MarkdownExtractor.js +32 -0
- package/extract/TypescriptExtractor.d.ts +11 -0
- package/extract/TypescriptExtractor.js +244 -0
- package/extract/index.d.ts +5 -0
- package/extract/index.js +5 -0
- package/markup/render.d.ts +4 -4
- package/markup/render.js +4 -4
- package/markup/rule/unordered.d.ts +2 -2
- package/markup/util/rule.d.ts +4 -4
- package/package.json +8 -6
- package/ui/app/App.d.ts +2 -2
- package/ui/app/App.js +4 -6
- package/ui/app/App.tsx +6 -8
- package/ui/block/Video.js +1 -1
- package/ui/block/Video.tsx +1 -1
- package/ui/form/ButtonInput.d.ts +1 -1
- package/ui/form/CheckboxInput.d.ts +1 -1
- package/ui/form/DateInput.d.ts +1 -1
- package/ui/form/FileInput.d.ts +1 -1
- package/ui/form/Input.d.ts +1 -1
- package/ui/form/NumberInput.d.ts +1 -1
- package/ui/form/Popover.d.ts +1 -1
- package/ui/form/RadioInput.d.ts +1 -1
- package/ui/form/TextInput.d.ts +1 -1
- package/ui/index.d.ts +1 -0
- package/ui/index.js +1 -0
- package/ui/index.ts +1 -0
- package/ui/layout/SidebarLayout.d.ts +17 -0
- package/ui/layout/SidebarLayout.js +15 -0
- package/ui/layout/SidebarLayout.module.css +47 -0
- package/ui/layout/SidebarLayout.tsx +36 -0
- package/ui/layout/index.d.ts +1 -0
- package/ui/layout/index.js +1 -0
- package/ui/layout/index.tsx +1 -0
- package/ui/misc/Mapper.d.ts +32 -0
- package/ui/misc/Mapper.js +46 -0
- package/ui/misc/Mapper.tsx +71 -0
- package/ui/misc/Meta.d.ts +2 -2
- package/ui/misc/Meta.tsx +2 -2
- package/ui/misc/index.d.ts +1 -0
- package/ui/misc/index.js +1 -0
- package/ui/misc/index.tsx +1 -0
- package/ui/page/HTML.d.ts +10 -0
- package/ui/page/HTML.js +11 -0
- package/ui/page/HTML.tsx +20 -0
- package/ui/page/Head.d.ts +0 -5
- package/ui/page/Head.js +4 -2
- package/ui/page/Head.tsx +3 -8
- package/ui/page/Page.d.ts +4 -3
- package/ui/page/Page.js +2 -1
- package/ui/page/Page.tsx +4 -3
- package/ui/page/index.d.ts +1 -0
- package/ui/page/index.js +1 -0
- package/ui/page/index.ts +1 -0
- package/ui/router/Router.d.ts +13 -11
- package/ui/router/Router.js +13 -3
- package/ui/router/Router.tsx +19 -14
- package/ui/router/RouterStore.d.ts +1 -1
- package/ui/router/RouterStore.js +2 -2
- package/ui/router/RouterStore.tsx +2 -2
- package/ui/tree/TreeApp.d.ts +24 -0
- package/ui/tree/TreeApp.js +22 -0
- package/ui/tree/TreeApp.tsx +64 -0
- package/ui/tree/TreeCards.d.ts +9 -0
- package/ui/tree/TreeCards.js +8 -0
- package/ui/tree/TreeCards.module.css +31 -0
- package/ui/tree/TreeCards.tsx +20 -0
- package/ui/tree/TreeMenu.d.ts +9 -0
- package/ui/tree/TreeMenu.js +8 -0
- package/ui/tree/TreeMenu.module.css +29 -0
- package/ui/tree/TreeMenu.tsx +22 -0
- package/ui/tree/TreePage.d.ts +15 -0
- package/ui/tree/TreePage.js +17 -0
- package/ui/tree/TreePage.tsx +24 -0
- package/ui/tree/index.d.ts +4 -0
- package/ui/tree/index.js +4 -0
- package/ui/tree/index.ts +4 -0
- package/ui/util/meta.d.ts +8 -8
- package/ui/util/meta.js +5 -5
- package/ui/util/meta.ts +11 -11
- package/util/data.d.ts +11 -1
- package/util/data.js +9 -2
- package/util/element.d.ts +218 -0
- package/util/element.js +136 -0
- package/util/file.d.ts +10 -0
- package/util/file.js +15 -0
- package/util/index.d.ts +1 -1
- package/util/index.js +1 -1
- package/util/iterate.d.ts +1 -1
- package/util/jsx.d.ts +0 -32
- package/util/jsx.js +0 -41
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Element } from "../util/element.js";
|
|
2
|
+
import { Extractor } from "./Extractor.js";
|
|
3
|
+
import type { FileExtractor } from "./FileExtractor.js";
|
|
4
|
+
/** Options for a directory extractor. */
|
|
5
|
+
export interface DirectoryExtractorOptions {
|
|
6
|
+
/** Filenames to treat as the directory's index file (e.g. `["README.md", "INDEX.md"]`). */
|
|
7
|
+
readonly index: readonly string[];
|
|
8
|
+
/** Extractor to use for child files. */
|
|
9
|
+
readonly extractor: FileExtractor;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Extractor that processes a directory's files into a directory element.
|
|
13
|
+
* - Finds an index file and absorbs its content as the directory's own content.
|
|
14
|
+
* - Delegates remaining files to the child file extractor.
|
|
15
|
+
* - Errors if two child elements resolve to the same slug.
|
|
16
|
+
* - Preserves file system ordering.
|
|
17
|
+
*/
|
|
18
|
+
export declare class DirectoryExtractor extends Extractor<{
|
|
19
|
+
name: string;
|
|
20
|
+
files: File[];
|
|
21
|
+
}> {
|
|
22
|
+
private readonly _index;
|
|
23
|
+
private readonly _extractor;
|
|
24
|
+
constructor({ index, extractor }: DirectoryExtractorOptions);
|
|
25
|
+
extract({ name, files }: {
|
|
26
|
+
name: string;
|
|
27
|
+
files: File[];
|
|
28
|
+
}): Promise<Element>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { requireSlug } from "../util/string.js";
|
|
2
|
+
import { Extractor } from "./Extractor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extractor that processes a directory's files into a directory element.
|
|
5
|
+
* - Finds an index file and absorbs its content as the directory's own content.
|
|
6
|
+
* - Delegates remaining files to the child file extractor.
|
|
7
|
+
* - Errors if two child elements resolve to the same slug.
|
|
8
|
+
* - Preserves file system ordering.
|
|
9
|
+
*/
|
|
10
|
+
export class DirectoryExtractor extends Extractor {
|
|
11
|
+
_index;
|
|
12
|
+
_extractor;
|
|
13
|
+
constructor({ index, extractor }) {
|
|
14
|
+
super();
|
|
15
|
+
this._index = index;
|
|
16
|
+
this._extractor = extractor;
|
|
17
|
+
}
|
|
18
|
+
async extract({ name, files }) {
|
|
19
|
+
let indexElement;
|
|
20
|
+
const children = [];
|
|
21
|
+
const keys = new Set();
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
if (!indexElement && this._index.includes(file.name)) {
|
|
24
|
+
indexElement = await this._extractor.extract(file);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const element = await this._extractor.extract(file);
|
|
28
|
+
const { key } = element;
|
|
29
|
+
if (key) {
|
|
30
|
+
if (keys.has(key))
|
|
31
|
+
throw new Error(`Duplicate key "${key}" in directory "${name}"`);
|
|
32
|
+
keys.add(key);
|
|
33
|
+
}
|
|
34
|
+
children.push(element);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
type: "tree-directory",
|
|
39
|
+
key: requireSlug(name),
|
|
40
|
+
props: {
|
|
41
|
+
title: indexElement?.props.title ?? name,
|
|
42
|
+
description: indexElement?.props.description,
|
|
43
|
+
content: indexElement?.props.content,
|
|
44
|
+
children,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Element } from "../util/element.js";
|
|
2
|
+
/**
|
|
3
|
+
* Base class for an extractor that converts input into an element.
|
|
4
|
+
* - Extractors are composable: outer extractors delegate to inner extractors.
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class Extractor<I> {
|
|
7
|
+
/** Extract an element from the given input. */
|
|
8
|
+
abstract extract(input: I): Element | Promise<Element>;
|
|
9
|
+
}
|
|
10
|
+
/** Extractor that converts string content into an element. */
|
|
11
|
+
export type ContentExtractor = Extractor<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Element } from "../util/element.js";
|
|
2
|
+
import { type ContentExtractor, Extractor } from "./Extractor.js";
|
|
3
|
+
/** Options for a file extractor. */
|
|
4
|
+
export interface FileExtractorOptions {
|
|
5
|
+
/** Map of file extension (e.g. `".md"`, `".ts"`) to content extractor. */
|
|
6
|
+
readonly extractors: Readonly<Record<string, ContentExtractor>>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Extractor that dispatches to a content extractor based on file extension.
|
|
10
|
+
* - Reads the file content and passes it to the matched extractor.
|
|
11
|
+
* - Sets the element `key` to the slugified filename (without extension).
|
|
12
|
+
*/
|
|
13
|
+
export declare class FileExtractor extends Extractor<File> {
|
|
14
|
+
private readonly _extractors;
|
|
15
|
+
constructor({ extractors }: FileExtractorOptions);
|
|
16
|
+
extract(file: File): Promise<Element>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { splitFileExtension } from "../util/file.js";
|
|
2
|
+
import { requireSlug } from "../util/string.js";
|
|
3
|
+
import { Extractor } from "./Extractor.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extractor that dispatches to a content extractor based on file extension.
|
|
6
|
+
* - Reads the file content and passes it to the matched extractor.
|
|
7
|
+
* - Sets the element `key` to the slugified filename (without extension).
|
|
8
|
+
*/
|
|
9
|
+
export class FileExtractor extends Extractor {
|
|
10
|
+
_extractors;
|
|
11
|
+
constructor({ extractors }) {
|
|
12
|
+
super();
|
|
13
|
+
this._extractors = extractors;
|
|
14
|
+
}
|
|
15
|
+
async extract(file) {
|
|
16
|
+
const [base, ext] = splitFileExtension(file.name);
|
|
17
|
+
const key = requireSlug(base ?? file.name);
|
|
18
|
+
const extractor = ext ? this._extractors[ext] : undefined;
|
|
19
|
+
if (!extractor)
|
|
20
|
+
return { type: "tree-file", key, props: {} };
|
|
21
|
+
const content = await file.text();
|
|
22
|
+
const element = await extractor.extract(content);
|
|
23
|
+
return { ...element, key };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MarkupOptions } from "../markup/util/options.js";
|
|
2
|
+
import type { Element } from "../util/element.js";
|
|
3
|
+
import { type ContentExtractor, Extractor } from "./Extractor.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extractor that converts a Markdown string into a file element.
|
|
6
|
+
* - Parses the markdown using the markup module.
|
|
7
|
+
* - Extracts the first `<h1>` heading as the element title.
|
|
8
|
+
*/
|
|
9
|
+
export declare class MarkdownExtractor extends Extractor<string> implements ContentExtractor {
|
|
10
|
+
private readonly _options;
|
|
11
|
+
constructor(options?: MarkupOptions);
|
|
12
|
+
extract(content: string): Element;
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { renderMarkup } from "../markup/render.js";
|
|
2
|
+
import { MARKUP_RULES } from "../markup/rule/index.js";
|
|
3
|
+
import { getElements, getElementText } from "../util/element.js";
|
|
4
|
+
import { Extractor } from "./Extractor.js";
|
|
5
|
+
/**
|
|
6
|
+
* Extractor that converts a Markdown string into a file element.
|
|
7
|
+
* - Parses the markdown using the markup module.
|
|
8
|
+
* - Extracts the first `<h1>` heading as the element title.
|
|
9
|
+
*/
|
|
10
|
+
export class MarkdownExtractor extends Extractor {
|
|
11
|
+
_options;
|
|
12
|
+
constructor(options = { rules: MARKUP_RULES }) {
|
|
13
|
+
super();
|
|
14
|
+
this._options = options;
|
|
15
|
+
}
|
|
16
|
+
extract(content) {
|
|
17
|
+
const elements = renderMarkup(content, this._options);
|
|
18
|
+
const title = _getFirstHeadingText(elements);
|
|
19
|
+
return {
|
|
20
|
+
type: "tree-file",
|
|
21
|
+
key: null,
|
|
22
|
+
props: { title, content: elements },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Find the first `h1` element and extract its text content. */
|
|
27
|
+
function _getFirstHeadingText(elements) {
|
|
28
|
+
for (const el of getElements(elements)) {
|
|
29
|
+
if (el.type === "h1")
|
|
30
|
+
return getElementText(el.props.children) || undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Element } from "../util/element.js";
|
|
2
|
+
import { type ContentExtractor, Extractor } from "./Extractor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extractor that converts a TypeScript source string into a file element.
|
|
5
|
+
* - Uses the TypeScript compiler API to parse the AST.
|
|
6
|
+
* - Extracts exported, public, non-`_`-prefixed declarations with their JSDoc and type signatures.
|
|
7
|
+
* - Top-of-file JSDoc comment becomes the file's description.
|
|
8
|
+
*/
|
|
9
|
+
export declare class TypescriptExtractor extends Extractor<string> implements ContentExtractor {
|
|
10
|
+
extract(content: string): Element;
|
|
11
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { Extractor } from "./Extractor.js";
|
|
3
|
+
/**
|
|
4
|
+
* Extractor that converts a TypeScript source string into a file element.
|
|
5
|
+
* - Uses the TypeScript compiler API to parse the AST.
|
|
6
|
+
* - Extracts exported, public, non-`_`-prefixed declarations with their JSDoc and type signatures.
|
|
7
|
+
* - Top-of-file JSDoc comment becomes the file's description.
|
|
8
|
+
*/
|
|
9
|
+
export class TypescriptExtractor extends Extractor {
|
|
10
|
+
extract(content) {
|
|
11
|
+
const source = ts.createSourceFile("source.ts", content, ts.ScriptTarget.Latest, true);
|
|
12
|
+
const children = [];
|
|
13
|
+
const description = _getFileDocComment(source);
|
|
14
|
+
for (const statement of source.statements) {
|
|
15
|
+
const element = _extractStatement(statement, source);
|
|
16
|
+
if (element)
|
|
17
|
+
children.push(element);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
type: "tree-file",
|
|
21
|
+
key: null,
|
|
22
|
+
props: { description, children },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Get the leading JSDoc comment of the file (before the first statement). */
|
|
27
|
+
function _getFileDocComment(source) {
|
|
28
|
+
const { statements } = source;
|
|
29
|
+
if (!statements.length)
|
|
30
|
+
return;
|
|
31
|
+
const first = statements[0];
|
|
32
|
+
if (!first)
|
|
33
|
+
return;
|
|
34
|
+
const ranges = ts.getLeadingCommentRanges(source.text, first.pos);
|
|
35
|
+
if (!ranges?.length)
|
|
36
|
+
return;
|
|
37
|
+
const range = ranges[0];
|
|
38
|
+
if (!range || range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
39
|
+
return;
|
|
40
|
+
const text = source.text.slice(range.pos, range.end);
|
|
41
|
+
return _parseJSDocComment(text);
|
|
42
|
+
}
|
|
43
|
+
/** Extract an element from a top-level statement, or return undefined if it should be skipped. */
|
|
44
|
+
function _extractStatement(statement, source) {
|
|
45
|
+
// Skip non-exported statements.
|
|
46
|
+
if (!_isExported(statement))
|
|
47
|
+
return;
|
|
48
|
+
// Skip statements without a name.
|
|
49
|
+
const name = _getStatementName(statement);
|
|
50
|
+
if (!name)
|
|
51
|
+
return;
|
|
52
|
+
// Skip private/internal names.
|
|
53
|
+
if (name.startsWith("_"))
|
|
54
|
+
return;
|
|
55
|
+
const jsDoc = _getJSDoc(statement, source);
|
|
56
|
+
const kind = _getElementType(statement);
|
|
57
|
+
if (!kind)
|
|
58
|
+
return;
|
|
59
|
+
const signature = _getSignature(statement, source);
|
|
60
|
+
const params = _getParams(statement, source);
|
|
61
|
+
const returns = _getReturnType(statement, source);
|
|
62
|
+
const examples = jsDoc?.examples;
|
|
63
|
+
const children = _getClassMembers(statement, source);
|
|
64
|
+
const props = {
|
|
65
|
+
title: name,
|
|
66
|
+
description: jsDoc?.description,
|
|
67
|
+
signature,
|
|
68
|
+
};
|
|
69
|
+
if (params?.length)
|
|
70
|
+
props.params = params;
|
|
71
|
+
if (returns)
|
|
72
|
+
props.returns = returns;
|
|
73
|
+
if (examples?.length)
|
|
74
|
+
props.examples = examples;
|
|
75
|
+
if (children?.length)
|
|
76
|
+
props.children = children;
|
|
77
|
+
return { type: kind, key: null, props };
|
|
78
|
+
}
|
|
79
|
+
/** Check if a statement has an `export` modifier. */
|
|
80
|
+
function _isExported(statement) {
|
|
81
|
+
const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;
|
|
82
|
+
return !!modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
83
|
+
}
|
|
84
|
+
/** Get the declared name of a statement. */
|
|
85
|
+
function _getStatementName(statement) {
|
|
86
|
+
if (ts.isFunctionDeclaration(statement) ||
|
|
87
|
+
ts.isClassDeclaration(statement) ||
|
|
88
|
+
ts.isInterfaceDeclaration(statement) ||
|
|
89
|
+
ts.isTypeAliasDeclaration(statement) ||
|
|
90
|
+
ts.isEnumDeclaration(statement)) {
|
|
91
|
+
return statement.name?.text;
|
|
92
|
+
}
|
|
93
|
+
if (ts.isVariableStatement(statement)) {
|
|
94
|
+
const declaration = statement.declarationList.declarations[0];
|
|
95
|
+
if (declaration && ts.isIdentifier(declaration.name))
|
|
96
|
+
return declaration.name.text;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Map a statement to its tree element type. */
|
|
100
|
+
function _getElementType(statement) {
|
|
101
|
+
if (ts.isFunctionDeclaration(statement))
|
|
102
|
+
return "tree-function";
|
|
103
|
+
if (ts.isClassDeclaration(statement))
|
|
104
|
+
return "tree-class";
|
|
105
|
+
if (ts.isInterfaceDeclaration(statement))
|
|
106
|
+
return "tree-interface";
|
|
107
|
+
if (ts.isTypeAliasDeclaration(statement))
|
|
108
|
+
return "tree-type";
|
|
109
|
+
if (ts.isVariableStatement(statement))
|
|
110
|
+
return "tree-constant";
|
|
111
|
+
}
|
|
112
|
+
/** Get the text signature of a statement. */
|
|
113
|
+
function _getSignature(statement, source) {
|
|
114
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
115
|
+
const params = statement.parameters.map(p => p.getText(source)).join(", ");
|
|
116
|
+
const ret = statement.type ? statement.type.getText(source) : "void";
|
|
117
|
+
return `(${params}) => ${ret}`;
|
|
118
|
+
}
|
|
119
|
+
if (ts.isTypeAliasDeclaration(statement)) {
|
|
120
|
+
return statement.type.getText(source);
|
|
121
|
+
}
|
|
122
|
+
if (ts.isVariableStatement(statement)) {
|
|
123
|
+
const declaration = statement.declarationList.declarations[0];
|
|
124
|
+
if (declaration?.type)
|
|
125
|
+
return declaration.type.getText(source);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Extract parameters from a function or method declaration. */
|
|
129
|
+
function _getParams(statement, source) {
|
|
130
|
+
if (!ts.isFunctionDeclaration(statement))
|
|
131
|
+
return;
|
|
132
|
+
const jsDocParams = _getJSDoc(statement, source)?.params;
|
|
133
|
+
const params = statement.parameters.map(p => {
|
|
134
|
+
const name = p.name.getText(source);
|
|
135
|
+
const type = p.type?.getText(source);
|
|
136
|
+
const optional = !!p.questionToken || !!p.initializer;
|
|
137
|
+
const description = jsDocParams?.find(d => d.name === name)?.description;
|
|
138
|
+
return { name, type, description, optional };
|
|
139
|
+
});
|
|
140
|
+
return params.length ? params : undefined;
|
|
141
|
+
}
|
|
142
|
+
/** Get the return type of a function declaration. */
|
|
143
|
+
function _getReturnType(statement, source) {
|
|
144
|
+
if (ts.isFunctionDeclaration(statement) && statement.type)
|
|
145
|
+
return statement.type.getText(source);
|
|
146
|
+
}
|
|
147
|
+
/** Extract class or interface members as child elements. */
|
|
148
|
+
function _getClassMembers(statement, source) {
|
|
149
|
+
if (!ts.isClassDeclaration(statement) && !ts.isInterfaceDeclaration(statement))
|
|
150
|
+
return;
|
|
151
|
+
const members = [];
|
|
152
|
+
for (const member of statement.members) {
|
|
153
|
+
// Skip private, protected, and _-prefixed members.
|
|
154
|
+
const name = member.name && ts.isIdentifier(member.name) ? member.name.text : undefined;
|
|
155
|
+
if (!name || name.startsWith("_"))
|
|
156
|
+
continue;
|
|
157
|
+
if (ts.canHaveModifiers(member)) {
|
|
158
|
+
const modifiers = ts.getModifiers(member);
|
|
159
|
+
if (modifiers?.some(m => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword))
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const jsDoc = _getJSDoc(member, source);
|
|
163
|
+
const props = {
|
|
164
|
+
title: name,
|
|
165
|
+
description: jsDoc?.description,
|
|
166
|
+
};
|
|
167
|
+
if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
|
|
168
|
+
const params = member.parameters.map(p => p.getText(source)).join(", ");
|
|
169
|
+
const ret = member.type ? member.type.getText(source) : "void";
|
|
170
|
+
props.signature = `(${params}) => ${ret}`;
|
|
171
|
+
members.push({ type: "tree-method", key: null, props });
|
|
172
|
+
}
|
|
173
|
+
else if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
|
|
174
|
+
if (member.type)
|
|
175
|
+
props.signature = member.type.getText(source);
|
|
176
|
+
members.push({ type: "tree-property", key: null, props });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return members.length ? members : undefined;
|
|
180
|
+
}
|
|
181
|
+
/** Extract JSDoc from a node. */
|
|
182
|
+
function _getJSDoc(node, source) {
|
|
183
|
+
const ranges = ts.getLeadingCommentRanges(source.text, node.pos);
|
|
184
|
+
if (!ranges?.length)
|
|
185
|
+
return;
|
|
186
|
+
// Find the last JSDoc-style comment (/** ... */).
|
|
187
|
+
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
188
|
+
const range = ranges[i];
|
|
189
|
+
if (!range || range.kind !== ts.SyntaxKind.MultiLineCommentTrivia)
|
|
190
|
+
continue;
|
|
191
|
+
const text = source.text.slice(range.pos, range.end);
|
|
192
|
+
if (!text.startsWith("/**"))
|
|
193
|
+
continue;
|
|
194
|
+
const description = _parseJSDocComment(text);
|
|
195
|
+
const params = _parseJSDocParams(text);
|
|
196
|
+
const examples = _parseJSDocExamples(text);
|
|
197
|
+
return {
|
|
198
|
+
description: description || undefined,
|
|
199
|
+
params: params.length ? params : undefined,
|
|
200
|
+
examples: examples.length ? examples : undefined,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Parse a JSDoc comment block into its description text. */
|
|
205
|
+
function _parseJSDocComment(text) {
|
|
206
|
+
const lines = text
|
|
207
|
+
.replace(/^\/\*\*\s*/, "")
|
|
208
|
+
.replace(/\s*\*\/$/, "")
|
|
209
|
+
.split("\n")
|
|
210
|
+
.map(l => l.replace(/^\s*\*\s?/, ""));
|
|
211
|
+
// Collect lines until we hit a @tag.
|
|
212
|
+
const description = [];
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
if (line.startsWith("@"))
|
|
215
|
+
break;
|
|
216
|
+
description.push(line);
|
|
217
|
+
}
|
|
218
|
+
const result = description.join("\n").trim();
|
|
219
|
+
return result || undefined;
|
|
220
|
+
}
|
|
221
|
+
/** Parse `@param` tags from a JSDoc comment. */
|
|
222
|
+
function _parseJSDocParams(text) {
|
|
223
|
+
const results = [];
|
|
224
|
+
const regexp = /@param\s+(?:\{[^}]*\}\s+)?(\w+)\s+(.*)/g;
|
|
225
|
+
let match;
|
|
226
|
+
while ((match = regexp.exec(text))) {
|
|
227
|
+
const name = match[1];
|
|
228
|
+
const description = match[2];
|
|
229
|
+
if (name && description)
|
|
230
|
+
results.push({ name, description: description.trim() });
|
|
231
|
+
}
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
234
|
+
/** Parse `@example` tags from a JSDoc comment. */
|
|
235
|
+
function _parseJSDocExamples(text) {
|
|
236
|
+
const results = [];
|
|
237
|
+
const regexp = /@example\s+(.*)/g;
|
|
238
|
+
let match;
|
|
239
|
+
while ((match = regexp.exec(text))) {
|
|
240
|
+
if (match[1])
|
|
241
|
+
results.push(match[1].trim());
|
|
242
|
+
}
|
|
243
|
+
return results;
|
|
244
|
+
}
|
package/extract/index.js
ADDED
package/markup/render.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Elements } from "../util/element.js";
|
|
2
2
|
import type { MarkupOptions } from "./util/options.js";
|
|
3
3
|
/**
|
|
4
|
-
* Parse a text string as Markdownish syntax and render it as
|
|
4
|
+
* Parse a text string as Markdownish syntax and render it as elements.
|
|
5
5
|
* - Syntax is not defined by this code, but by the rules supplied to it.
|
|
6
6
|
*
|
|
7
7
|
* @param input The string content possibly containing markup syntax, e.g. "This is a *bold* string.
|
|
8
8
|
* @param options An options object for the render.
|
|
9
9
|
* @param context The context to render in (defaults to `"block"`).
|
|
10
10
|
*
|
|
11
|
-
* @returns
|
|
11
|
+
* @returns Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
12
12
|
*/
|
|
13
|
-
export declare function renderMarkup(input: string, options: MarkupOptions, context?: string):
|
|
13
|
+
export declare function renderMarkup(input: string, options: MarkupOptions, context?: string): Elements;
|
package/markup/render.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Parse a text string as Markdownish syntax and render it as
|
|
2
|
+
* Parse a text string as Markdownish syntax and render it as elements.
|
|
3
3
|
* - Syntax is not defined by this code, but by the rules supplied to it.
|
|
4
4
|
*
|
|
5
5
|
* @param input The string content possibly containing markup syntax, e.g. "This is a *bold* string.
|
|
6
6
|
* @param options An options object for the render.
|
|
7
7
|
* @param context The context to render in (defaults to `"block"`).
|
|
8
8
|
*
|
|
9
|
-
* @returns
|
|
9
|
+
* @returns Elements, i.e. either a complete `Element`, `null`, `undefined`, `string`, or an array of zero or more of those.
|
|
10
10
|
*/
|
|
11
11
|
export function renderMarkup(input, options, context = "block") {
|
|
12
12
|
const arr = Array.from(_parseString(input, options, context));
|
|
13
13
|
return !arr.length ? null : arr.length === 1 ? arr[0] : arr;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Parse a string to its corresponding
|
|
17
|
-
* - This code is heavily inspired by `simple-markdown`, but intends to be even simpler (and faster) by always producing
|
|
16
|
+
* Parse a string to its corresponding elements in a given context.
|
|
17
|
+
* - This code is heavily inspired by `simple-markdown`, but intends to be even simpler (and faster) by always producing elements.
|
|
18
18
|
*/
|
|
19
19
|
function* _parseString(
|
|
20
20
|
/** The input string. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Element } from "../../util/element.js";
|
|
2
2
|
import type { MarkupOptions } from "../util/options.js";
|
|
3
3
|
export declare const UNORDERED_REGEXP: import("../../index.js").NamedRegExp<{
|
|
4
4
|
list?: string;
|
|
@@ -13,4 +13,4 @@ export declare const UNORDERED_REGEXP: import("../../index.js").NamedRegExp<{
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const UNORDERED_RULE: import("../util/rule.js").MarkupRule;
|
|
15
15
|
/** Parse a markdown list into a set of items elements. */
|
|
16
|
-
export declare function _getItems(list: string, options: MarkupOptions): Iterable<
|
|
16
|
+
export declare function _getItems(list: string, options: MarkupOptions): Iterable<Element>;
|
package/markup/util/rule.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Element } from "../../util/element.js";
|
|
2
2
|
import type { NamedRegExp, NamedRegExpExecArray } from "../../util/regexp.js";
|
|
3
3
|
import type { MarkupOptions } from "./options.js";
|
|
4
4
|
export type MarkupContexts = [string, ...string[]];
|
|
5
5
|
export interface MarkupRule {
|
|
6
6
|
/** Regular expression used for matching the rule. */
|
|
7
7
|
regexp: RegExp;
|
|
8
|
-
/** Use the matched data to render
|
|
9
|
-
render(match: RegExpExecArray, options: MarkupOptions, key: string):
|
|
8
|
+
/** Use the matched data to render an element. */
|
|
9
|
+
render(match: RegExpExecArray, options: MarkupOptions, key: string): Element;
|
|
10
10
|
/** One or more contexts this rule should render in. */
|
|
11
11
|
contexts: MarkupContexts;
|
|
12
12
|
/** Priority for this rule (higher priority rules override lower priority rules). */
|
|
@@ -14,4 +14,4 @@ export interface MarkupRule {
|
|
|
14
14
|
}
|
|
15
15
|
export type MarkupRules = readonly MarkupRule[];
|
|
16
16
|
/** Helper to make it easier to create typed `MarkupRule` instances using `NamedRegExp` regular expressions. */
|
|
17
|
-
export declare function getMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) =>
|
|
17
|
+
export declare function getMarkupRule<T extends NamedRegExp | RegExp>(regexp: T, render: T extends NamedRegExp<infer X> ? (match: NamedRegExpExecArray<X>, options: MarkupOptions, key: string) => Element : (match: RegExpExecArray, options: MarkupOptions, key: string) => Element, contexts: MarkupContexts, priority?: number): MarkupRule;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shelving",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.203.0",
|
|
4
4
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,16 +9,17 @@
|
|
|
9
9
|
"main": "./index.js",
|
|
10
10
|
"module": "./index.js",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@biomejs/biome": "^2.4.
|
|
12
|
+
"@biomejs/biome": "^2.4.15",
|
|
13
13
|
"@google-cloud/firestore": "^8.5.0",
|
|
14
14
|
"@heroicons/react": "^2.2.0",
|
|
15
15
|
"@types/bun": "^1.3.13",
|
|
16
16
|
"@types/react": "^19.2.14",
|
|
17
17
|
"@types/react-dom": "^19.2.3",
|
|
18
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
19
|
-
"firebase": "^12.
|
|
20
|
-
"react": "canary",
|
|
21
|
-
"react-dom": "canary"
|
|
18
|
+
"@typescript/native-preview": "^7.0.0-dev.20260509.2",
|
|
19
|
+
"firebase": "^12.13.0",
|
|
20
|
+
"react": "^19.3.0-canary-d5736f09-20260507",
|
|
21
|
+
"react-dom": "canary",
|
|
22
|
+
"typescript": "^5.9.3"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"@google-cloud/firestore": ">=7.0.0",
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"test:unit": "bun test --concurrent --only-failures",
|
|
70
71
|
"fix": "bun run --sequential fix:*",
|
|
71
72
|
"fix:0:lint": "biome check --write .",
|
|
73
|
+
"docs": "bun ./scripts/docs.tsx",
|
|
72
74
|
"build": "bun run --sequential build:*",
|
|
73
75
|
"build:0:setup": "rm -rf ./dist && mkdir -p ./dist",
|
|
74
76
|
"build:1:copy": "cp package.json dist/package.json && cp LICENSE.md dist/LICENSE.md && cp README.md dist/README.md && cp .npmignore dist/.npmignore && cp -r modules/ui dist/ui",
|
package/ui/app/App.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
export interface AppProps extends
|
|
2
|
+
import type { PossibleMeta } from "../util/meta.js";
|
|
3
|
+
export interface AppProps extends PossibleMeta {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
}
|
|
6
6
|
/**
|
package/ui/app/App.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { Meta } from "../misc/Meta.js";
|
|
4
4
|
import APP_CSS from "./App.module.css";
|
|
5
|
-
const
|
|
5
|
+
const APP_CLASS = APP_CSS.app;
|
|
6
6
|
/**
|
|
7
7
|
* Root component for an application.
|
|
8
8
|
* - Adds the theme CSS class (which sets CSS token variables on `:root`) to `document.body` on mount and removes it on unmount.
|
|
@@ -10,12 +10,10 @@ const _appClass = APP_CSS.app;
|
|
|
10
10
|
*/
|
|
11
11
|
export function App({ children, ...metadata }) {
|
|
12
12
|
useEffect(() => {
|
|
13
|
-
if (!
|
|
13
|
+
if (!APP_CLASS)
|
|
14
14
|
return;
|
|
15
|
-
document.body.classList.add(
|
|
16
|
-
return () =>
|
|
17
|
-
document.body.classList.remove(_appClass);
|
|
18
|
-
};
|
|
15
|
+
document.body.classList.add(APP_CLASS);
|
|
16
|
+
return () => document.body.classList.remove(APP_CLASS);
|
|
19
17
|
}, []);
|
|
20
18
|
return _jsx(Meta, { ...metadata, children: children });
|
|
21
19
|
}
|
package/ui/app/App.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ReactElement, type ReactNode, useEffect } from "react";
|
|
2
2
|
import { Meta } from "../misc/Meta.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { PossibleMeta } from "../util/meta.js";
|
|
4
4
|
import APP_CSS from "./App.module.css";
|
|
5
5
|
|
|
6
|
-
export interface AppProps extends
|
|
6
|
+
export interface AppProps extends PossibleMeta {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const APP_CLASS = APP_CSS.app;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Root component for an application.
|
|
@@ -16,11 +16,9 @@ const _appClass = APP_CSS.app;
|
|
|
16
16
|
*/
|
|
17
17
|
export function App({ children, ...metadata }: AppProps): ReactElement {
|
|
18
18
|
useEffect(() => {
|
|
19
|
-
if (!
|
|
20
|
-
document.body.classList.add(
|
|
21
|
-
return () =>
|
|
22
|
-
document.body.classList.remove(_appClass);
|
|
23
|
-
};
|
|
19
|
+
if (!APP_CLASS) return;
|
|
20
|
+
document.body.classList.add(APP_CLASS);
|
|
21
|
+
return () => document.body.classList.remove(APP_CLASS);
|
|
24
22
|
}, []);
|
|
25
23
|
return <Meta {...metadata}>{children}</Meta>;
|
|
26
24
|
}
|
package/ui/block/Video.js
CHANGED
|
@@ -22,7 +22,7 @@ export function VideoButton({ children, title, onClick, disabled, ...variants })
|
|
|
22
22
|
}
|
|
23
23
|
/** Button to make a video element go fullscreen. */
|
|
24
24
|
export function FullscreenVideoButton() {
|
|
25
|
-
const [isFull, setFull] = useState(!!document.fullscreenElement);
|
|
25
|
+
const [isFull, setFull] = useState(() => typeof document !== "undefined" && !!document.fullscreenElement);
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
const onChange = () => setFull(!!document.fullscreenElement);
|
|
28
28
|
document.addEventListener("fullscreenchange", onChange);
|