vestrycms 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +383 -2
- package/dist/config/fields.d.ts +30 -0
- package/dist/config/fields.js +75 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.js +18 -0
- package/dist/config/types.d.ts +412 -0
- package/dist/config/types.js +10 -0
- package/dist/core/api.d.ts +67 -0
- package/dist/core/api.js +543 -0
- package/dist/core/auth.d.ts +1597 -0
- package/dist/core/auth.js +264 -0
- package/dist/core/cache.d.ts +27 -0
- package/dist/core/cache.js +52 -0
- package/dist/core/entries.d.ts +19 -0
- package/dist/core/entries.js +127 -0
- package/dist/core/index-types.d.ts +20 -0
- package/dist/core/index-types.js +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +13 -0
- package/dist/core/mail.d.ts +28 -0
- package/dist/core/mail.js +62 -0
- package/dist/core/paths.d.ts +35 -0
- package/dist/core/paths.js +148 -0
- package/dist/core/roles.d.ts +24 -0
- package/dist/core/roles.js +15 -0
- package/dist/core/service.d.ts +102 -0
- package/dist/core/service.js +478 -0
- package/dist/core/source.d.ts +82 -0
- package/dist/core/source.js +75 -0
- package/dist/core/sources/github-app.d.ts +26 -0
- package/dist/core/sources/github-app.js +161 -0
- package/dist/core/sources/github.d.ts +40 -0
- package/dist/core/sources/github.js +278 -0
- package/dist/core/sources/local.d.ts +16 -0
- package/dist/core/sources/local.js +98 -0
- package/dist/core/values.d.ts +22 -0
- package/dist/core/values.js +123 -0
- package/dist/env.d.ts +19 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +167 -0
- package/dist/integration/build-index.d.ts +8 -0
- package/dist/integration/build-index.js +67 -0
- package/dist/integration/client-deps.d.ts +10 -0
- package/dist/integration/client-deps.js +43 -0
- package/dist/integration/dev-middleware.d.ts +6 -0
- package/dist/integration/dev-middleware.js +48 -0
- package/dist/integration/formatter.d.ts +7 -0
- package/dist/integration/formatter.js +56 -0
- package/dist/mdx/expressions.d.ts +14 -0
- package/dist/mdx/expressions.js +141 -0
- package/dist/mdx/index.d.ts +4 -0
- package/dist/mdx/index.js +4 -0
- package/dist/mdx/parse.d.ts +6 -0
- package/dist/mdx/parse.js +401 -0
- package/dist/mdx/serialize.d.ts +9 -0
- package/dist/mdx/serialize.js +485 -0
- package/dist/mdx/types.d.ts +42 -0
- package/dist/mdx/types.js +6 -0
- package/dist/pages/admin.astro +27 -0
- package/dist/pages/api.d.ts +3 -0
- package/dist/pages/api.js +31 -0
- package/dist/ui/App.d.ts +4 -0
- package/dist/ui/App.js +70 -0
- package/dist/ui/api.d.ts +83 -0
- package/dist/ui/api.js +97 -0
- package/dist/ui/auth-client.d.ts +27 -0
- package/dist/ui/auth-client.js +29 -0
- package/dist/ui/components/AssetPicker.d.ts +27 -0
- package/dist/ui/components/AssetPicker.js +97 -0
- package/dist/ui/components/Icon.d.ts +11 -0
- package/dist/ui/components/Icon.js +46 -0
- package/dist/ui/components/Layout.d.ts +15 -0
- package/dist/ui/components/Layout.js +87 -0
- package/dist/ui/components/editor/EditorContext.d.ts +15 -0
- package/dist/ui/components/editor/EditorContext.js +9 -0
- package/dist/ui/components/editor/NodeViews.d.ts +4 -0
- package/dist/ui/components/editor/NodeViews.js +127 -0
- package/dist/ui/components/editor/RichTextEditor.d.ts +24 -0
- package/dist/ui/components/editor/RichTextEditor.js +230 -0
- package/dist/ui/components/editor/extensions.d.ts +30 -0
- package/dist/ui/components/editor/extensions.js +248 -0
- package/dist/ui/components/editor/schema.d.ts +12 -0
- package/dist/ui/components/editor/schema.js +37 -0
- package/dist/ui/components/fields/CodeInput.d.ts +10 -0
- package/dist/ui/components/fields/CodeInput.js +8 -0
- package/dist/ui/components/fields/CodeMirrorInput.d.ts +2 -0
- package/dist/ui/components/fields/CodeMirrorInput.js +44 -0
- package/dist/ui/components/fields/FieldRenderer.d.ts +41 -0
- package/dist/ui/components/fields/FieldRenderer.js +299 -0
- package/dist/ui/components/fields/ImageTools.d.ts +31 -0
- package/dist/ui/components/fields/ImageTools.js +296 -0
- package/dist/ui/components/fields/MarkdownField.d.ts +7 -0
- package/dist/ui/components/fields/MarkdownField.js +16 -0
- package/dist/ui/components/fields/TreeInput.d.ts +13 -0
- package/dist/ui/components/fields/TreeInput.js +221 -0
- package/dist/ui/components/ui.d.ts +63 -0
- package/dist/ui/components/ui.js +113 -0
- package/dist/ui/context.d.ts +17 -0
- package/dist/ui/context.js +13 -0
- package/dist/ui/lib/format.d.ts +5 -0
- package/dist/ui/lib/format.js +11 -0
- package/dist/ui/lib/utils.d.ts +24 -0
- package/dist/ui/lib/utils.js +255 -0
- package/dist/ui/main.d.ts +1 -0
- package/dist/ui/main.js +11 -0
- package/dist/ui/pages/Assets.d.ts +1 -0
- package/dist/ui/pages/Assets.js +69 -0
- package/dist/ui/pages/CollectionList.d.ts +6 -0
- package/dist/ui/pages/CollectionList.js +115 -0
- package/dist/ui/pages/Dashboard.d.ts +1 -0
- package/dist/ui/pages/Dashboard.js +57 -0
- package/dist/ui/pages/EntryEditor.d.ts +3 -0
- package/dist/ui/pages/EntryEditor.js +313 -0
- package/dist/ui/pages/Login.d.ts +6 -0
- package/dist/ui/pages/Login.js +88 -0
- package/dist/ui/pages/Users.d.ts +1 -0
- package/dist/ui/pages/Users.js +102 -0
- package/dist/ui/styles.css +2554 -0
- package/package.json +123 -3
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isRawExpression } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Convert a parsed JSX attribute expression to a plain JSON value when it is
|
|
4
|
+
* made only of literals; otherwise return a RawExpression holding the source.
|
|
5
|
+
*/
|
|
6
|
+
export function estreeToValue(node, source) {
|
|
7
|
+
if (!node)
|
|
8
|
+
return { $raw: source };
|
|
9
|
+
try {
|
|
10
|
+
return literal(node);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { $raw: source };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class NotLiteral extends Error {
|
|
17
|
+
}
|
|
18
|
+
function literal(node) {
|
|
19
|
+
switch (node.type) {
|
|
20
|
+
case "Program": {
|
|
21
|
+
const body = node.body;
|
|
22
|
+
if (body.length !== 1 || body[0].type !== "ExpressionStatement")
|
|
23
|
+
throw new NotLiteral();
|
|
24
|
+
return literal(body[0].expression);
|
|
25
|
+
}
|
|
26
|
+
case "ExpressionStatement":
|
|
27
|
+
return literal(node.expression);
|
|
28
|
+
case "Literal": {
|
|
29
|
+
const v = node.value;
|
|
30
|
+
if (v instanceof RegExp)
|
|
31
|
+
throw new NotLiteral();
|
|
32
|
+
return v ?? null;
|
|
33
|
+
}
|
|
34
|
+
case "TemplateLiteral": {
|
|
35
|
+
const t = node;
|
|
36
|
+
if (t.expressions.length)
|
|
37
|
+
throw new NotLiteral();
|
|
38
|
+
return t.quasis.map((q) => q.value.cooked ?? q.value.raw).join("");
|
|
39
|
+
}
|
|
40
|
+
case "UnaryExpression": {
|
|
41
|
+
const u = node;
|
|
42
|
+
const arg = literal(u.argument);
|
|
43
|
+
if (u.operator === "-" && typeof arg === "number")
|
|
44
|
+
return -arg;
|
|
45
|
+
if (u.operator === "+" && typeof arg === "number")
|
|
46
|
+
return arg;
|
|
47
|
+
if (u.operator === "!")
|
|
48
|
+
return !arg;
|
|
49
|
+
throw new NotLiteral();
|
|
50
|
+
}
|
|
51
|
+
case "Identifier": {
|
|
52
|
+
const name = node.name;
|
|
53
|
+
if (name === "undefined")
|
|
54
|
+
return undefined;
|
|
55
|
+
throw new NotLiteral();
|
|
56
|
+
}
|
|
57
|
+
case "ArrayExpression":
|
|
58
|
+
return node.elements.map((el) => {
|
|
59
|
+
if (!el || el.type === "SpreadElement")
|
|
60
|
+
throw new NotLiteral();
|
|
61
|
+
return literal(el);
|
|
62
|
+
});
|
|
63
|
+
case "ObjectExpression": {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const p of node.properties) {
|
|
66
|
+
if (p.type !== "Property" ||
|
|
67
|
+
p.kind !== "init" ||
|
|
68
|
+
p.method ||
|
|
69
|
+
p.computed)
|
|
70
|
+
throw new NotLiteral();
|
|
71
|
+
let key;
|
|
72
|
+
if (p.key.type === "Identifier")
|
|
73
|
+
key = p.key.name;
|
|
74
|
+
else if (p.key.type === "Literal")
|
|
75
|
+
key = String(p.key.value);
|
|
76
|
+
else
|
|
77
|
+
throw new NotLiteral();
|
|
78
|
+
out[key] = literal(p.value);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
throw new NotLiteral();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
87
|
+
const PRINT_WIDTH = 80;
|
|
88
|
+
/**
|
|
89
|
+
* Serialize a JSON value (or RawExpression) to JS source for a JSX attribute,
|
|
90
|
+
* in the shape Prettier produces: multi-line objects and arrays with trailing
|
|
91
|
+
* commas, bare identifier keys, double-quoted strings.
|
|
92
|
+
*/
|
|
93
|
+
export function valueToSource(value, indent = "") {
|
|
94
|
+
const inner = `${indent} `;
|
|
95
|
+
if (isRawExpression(value))
|
|
96
|
+
return value.$raw.trim();
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
return "undefined";
|
|
99
|
+
if (value === null)
|
|
100
|
+
return "null";
|
|
101
|
+
if (typeof value === "string")
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
104
|
+
return String(value);
|
|
105
|
+
if (value instanceof Date)
|
|
106
|
+
return JSON.stringify(value.toISOString());
|
|
107
|
+
if (Array.isArray(value)) {
|
|
108
|
+
if (!value.length)
|
|
109
|
+
return "[]";
|
|
110
|
+
const simple = value.every((v) => isScalar(v));
|
|
111
|
+
const items = value.map((v) => valueToSource(v, inner));
|
|
112
|
+
if (simple) {
|
|
113
|
+
const oneLine = `[${items.join(", ")}]`;
|
|
114
|
+
if (oneLine.length + indent.length <= PRINT_WIDTH - 8)
|
|
115
|
+
return oneLine;
|
|
116
|
+
}
|
|
117
|
+
return `[\n${items.map((i) => `${inner}${i},`).join("\n")}\n${indent}]`;
|
|
118
|
+
}
|
|
119
|
+
if (typeof value === "object") {
|
|
120
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined);
|
|
121
|
+
if (!entries.length)
|
|
122
|
+
return "{}";
|
|
123
|
+
const keyOf = (k) => (IDENT.test(k) ? k : JSON.stringify(k));
|
|
124
|
+
// Like Prettier: a flat object that fits on one line stays on one line.
|
|
125
|
+
if (entries.every(([, v]) => isScalar(v))) {
|
|
126
|
+
const oneLine = `{ ${entries.map(([k, v]) => `${keyOf(k)}: ${valueToSource(v)}`).join(", ")} }`;
|
|
127
|
+
if (oneLine.length + indent.length <= PRINT_WIDTH - 2)
|
|
128
|
+
return oneLine;
|
|
129
|
+
}
|
|
130
|
+
const lines = entries.map(([k, v]) => `${inner}${keyOf(k)}: ${valueToSource(v, inner)},`);
|
|
131
|
+
return `{\n${lines.join("\n")}\n${indent}}`;
|
|
132
|
+
}
|
|
133
|
+
return JSON.stringify(value) ?? "null";
|
|
134
|
+
}
|
|
135
|
+
function isScalar(v) {
|
|
136
|
+
return v === null || ["string", "number", "boolean"].includes(typeof v);
|
|
137
|
+
}
|
|
138
|
+
/** Whether a value can be written as a plain string attribute (`a="b"`) rather than an expression. */
|
|
139
|
+
export function isPlainString(value) {
|
|
140
|
+
return typeof value === "string" && !value.includes("\n");
|
|
141
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ParsedDocument } from "./types.js";
|
|
2
|
+
export interface ParseOptions {
|
|
3
|
+
/** `md` disables JSX/ESM parsing (raw HTML is preserved as html nodes). */
|
|
4
|
+
format?: "md" | "mdx";
|
|
5
|
+
}
|
|
6
|
+
export declare function parseMdx(source: string, options?: ParseOptions): ParsedDocument;
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
3
|
+
import { mdxFromMarkdown } from "mdast-util-mdx";
|
|
4
|
+
import { gfm } from "micromark-extension-gfm";
|
|
5
|
+
import { mdxjs } from "micromark-extension-mdxjs";
|
|
6
|
+
import { estreeToValue } from "./expressions.js";
|
|
7
|
+
const SUPERSCRIPT_TAGS = new Set(["sup"]);
|
|
8
|
+
const SUBSCRIPT_TAGS = new Set(["sub"]);
|
|
9
|
+
export function parseMdx(source, options = {}) {
|
|
10
|
+
const isMdx = options.format !== "md";
|
|
11
|
+
const tree = fromMarkdown(source, {
|
|
12
|
+
extensions: isMdx ? [mdxjs(), gfm()] : [gfm()],
|
|
13
|
+
mdastExtensions: isMdx
|
|
14
|
+
? [mdxFromMarkdown(), gfmFromMarkdown()]
|
|
15
|
+
: [gfmFromMarkdown()],
|
|
16
|
+
});
|
|
17
|
+
const ctx = { imports: {}, definitions: new Map() };
|
|
18
|
+
collectDefinitions(tree, ctx);
|
|
19
|
+
const content = blocks(tree.children, ctx);
|
|
20
|
+
return {
|
|
21
|
+
doc: {
|
|
22
|
+
type: "doc",
|
|
23
|
+
content: content.length ? content : [{ type: "paragraph" }],
|
|
24
|
+
},
|
|
25
|
+
imports: ctx.imports,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function collectDefinitions(tree, ctx) {
|
|
29
|
+
const visit = (node) => {
|
|
30
|
+
if (node.type === "definition") {
|
|
31
|
+
const d = node;
|
|
32
|
+
ctx.definitions.set(d.identifier.toLowerCase(), d);
|
|
33
|
+
}
|
|
34
|
+
const children = node.children;
|
|
35
|
+
if (children)
|
|
36
|
+
children.forEach(visit);
|
|
37
|
+
};
|
|
38
|
+
visit(tree);
|
|
39
|
+
}
|
|
40
|
+
function blocks(nodes, ctx) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const node of nodes) {
|
|
43
|
+
const converted = block(node, ctx);
|
|
44
|
+
if (converted)
|
|
45
|
+
out.push(...(Array.isArray(converted) ? converted : [converted]));
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function block(node, ctx) {
|
|
50
|
+
switch (node.type) {
|
|
51
|
+
case "paragraph":
|
|
52
|
+
return {
|
|
53
|
+
type: "paragraph",
|
|
54
|
+
content: inlines(node.children, ctx),
|
|
55
|
+
};
|
|
56
|
+
case "heading":
|
|
57
|
+
return {
|
|
58
|
+
type: "heading",
|
|
59
|
+
attrs: { level: node.depth },
|
|
60
|
+
content: inlines(node.children, ctx),
|
|
61
|
+
};
|
|
62
|
+
case "thematicBreak":
|
|
63
|
+
return { type: "horizontalRule" };
|
|
64
|
+
case "blockquote":
|
|
65
|
+
return {
|
|
66
|
+
type: "blockquote",
|
|
67
|
+
content: ensureBlocks(blocks(node.children, ctx)),
|
|
68
|
+
};
|
|
69
|
+
case "list": {
|
|
70
|
+
const items = node.children.map((item) => listItem(item, ctx));
|
|
71
|
+
const spread = Boolean(node.spread);
|
|
72
|
+
return node.ordered
|
|
73
|
+
? {
|
|
74
|
+
type: "orderedList",
|
|
75
|
+
attrs: { start: node.start ?? 1, spread },
|
|
76
|
+
content: items,
|
|
77
|
+
}
|
|
78
|
+
: { type: "bulletList", attrs: { spread }, content: items };
|
|
79
|
+
}
|
|
80
|
+
case "code":
|
|
81
|
+
return {
|
|
82
|
+
type: "codeBlock",
|
|
83
|
+
attrs: { language: node.lang ?? null, meta: node.meta ?? null },
|
|
84
|
+
content: node.value ? [{ type: "text", text: node.value }] : [],
|
|
85
|
+
};
|
|
86
|
+
case "html":
|
|
87
|
+
return { type: "rawHtml", attrs: { value: node.value } };
|
|
88
|
+
case "table":
|
|
89
|
+
return table(node, ctx);
|
|
90
|
+
case "definition":
|
|
91
|
+
return null;
|
|
92
|
+
case "footnoteDefinition":
|
|
93
|
+
return {
|
|
94
|
+
type: "footnoteDefinition",
|
|
95
|
+
attrs: footnoteAttrs(node),
|
|
96
|
+
content: ensureBlocks(blocks(node.children, ctx)),
|
|
97
|
+
};
|
|
98
|
+
case "mdxJsxFlowElement":
|
|
99
|
+
return jsxElement(node, ctx, false);
|
|
100
|
+
case "mdxFlowExpression":
|
|
101
|
+
return { type: "mdxExpression", attrs: { value: node.value } };
|
|
102
|
+
case "mdxjsEsm":
|
|
103
|
+
return esm(node, ctx);
|
|
104
|
+
case "yaml":
|
|
105
|
+
return null;
|
|
106
|
+
default: {
|
|
107
|
+
// Inline content at block level (shouldn't happen) – wrap in a paragraph.
|
|
108
|
+
const inline = inlineNode(node, ctx, []);
|
|
109
|
+
return inline.length ? { type: "paragraph", content: inline } : null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function ensureBlocks(content) {
|
|
114
|
+
return content.length ? content : [{ type: "paragraph" }];
|
|
115
|
+
}
|
|
116
|
+
function footnoteAttrs(node) {
|
|
117
|
+
return { identifier: node.identifier, label: node.label ?? node.identifier };
|
|
118
|
+
}
|
|
119
|
+
function listItem(item, ctx) {
|
|
120
|
+
const content = ensureBlocks(blocks(item.children, ctx));
|
|
121
|
+
return {
|
|
122
|
+
type: "listItem",
|
|
123
|
+
attrs: item.checked === null || item.checked === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: { checked: item.checked },
|
|
126
|
+
content,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function table(node, ctx) {
|
|
130
|
+
const rows = node.children.map((row, rowIdx) => ({
|
|
131
|
+
type: "tableRow",
|
|
132
|
+
content: row.children.map((cell, colIdx) => ({
|
|
133
|
+
type: rowIdx === 0 ? "tableHeader" : "tableCell",
|
|
134
|
+
attrs: { align: node.align?.[colIdx] ?? null },
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: "paragraph",
|
|
138
|
+
content: inlines(cell.children, ctx),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
})),
|
|
142
|
+
}));
|
|
143
|
+
return { type: "table", content: rows };
|
|
144
|
+
}
|
|
145
|
+
function esm(node, ctx) {
|
|
146
|
+
const program = node.data?.estree;
|
|
147
|
+
// Estree offsets count from the start of the file, not of this block.
|
|
148
|
+
const base = node.position?.start.offset;
|
|
149
|
+
if (!program || typeof base !== "number")
|
|
150
|
+
return { type: "mdxEsm", attrs: { value: node.value } };
|
|
151
|
+
// Cut recognized imports out of the block's source; everything else,
|
|
152
|
+
// comments and blank lines included, stays as written.
|
|
153
|
+
let rest = "";
|
|
154
|
+
let pos = 0;
|
|
155
|
+
for (const stmt of program.body) {
|
|
156
|
+
const { start, end } = stmt;
|
|
157
|
+
if (typeof start !== "number" || typeof end !== "number")
|
|
158
|
+
continue;
|
|
159
|
+
if (!recordImport(stmt, ctx))
|
|
160
|
+
continue;
|
|
161
|
+
rest += node.value.slice(pos, start - base);
|
|
162
|
+
pos = end - base;
|
|
163
|
+
// Drop the rest of the line when only whitespace follows the import.
|
|
164
|
+
const eol = /^[ \t]*(\r?\n|$)/.exec(node.value.slice(pos));
|
|
165
|
+
if (eol)
|
|
166
|
+
pos += eol[0].length;
|
|
167
|
+
}
|
|
168
|
+
rest = (rest + node.value.slice(pos)).trim();
|
|
169
|
+
return rest ? { type: "mdxEsm", attrs: { value: rest } } : null;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Record an import statement's bindings if all of them can be re-emitted from
|
|
173
|
+
* ImportSpecs; the caller then drops the statement. Otherwise it is kept
|
|
174
|
+
* verbatim.
|
|
175
|
+
*/
|
|
176
|
+
function recordImport(stmt, ctx) {
|
|
177
|
+
if (stmt.type !== "ImportDeclaration" ||
|
|
178
|
+
typeof stmt.source.value !== "string")
|
|
179
|
+
return false;
|
|
180
|
+
const from = stmt.source.value;
|
|
181
|
+
const specs = [];
|
|
182
|
+
for (const spec of stmt.specifiers) {
|
|
183
|
+
if (spec.type === "ImportDefaultSpecifier") {
|
|
184
|
+
specs.push({ name: spec.local.name, from });
|
|
185
|
+
}
|
|
186
|
+
else if (spec.type === "ImportSpecifier") {
|
|
187
|
+
const imported = spec.imported.type === "Identifier"
|
|
188
|
+
? spec.imported.name
|
|
189
|
+
: String(spec.imported.value);
|
|
190
|
+
specs.push({
|
|
191
|
+
name: spec.local.name,
|
|
192
|
+
from,
|
|
193
|
+
named: true,
|
|
194
|
+
...(imported === spec.local.name ? {} : { imported }),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!specs.length)
|
|
202
|
+
return false;
|
|
203
|
+
for (const spec of specs)
|
|
204
|
+
ctx.imports[spec.name] = spec;
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
function attrsToProps(attributes) {
|
|
208
|
+
const props = {};
|
|
209
|
+
const spreads = [];
|
|
210
|
+
for (const attr of attributes) {
|
|
211
|
+
if (attr.type === "mdxJsxExpressionAttribute") {
|
|
212
|
+
spreads.push(attr.value);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (attr.value === null || attr.value === undefined) {
|
|
216
|
+
props[attr.name] = true;
|
|
217
|
+
}
|
|
218
|
+
else if (typeof attr.value === "string") {
|
|
219
|
+
props[attr.name] = attr.value;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const estree = attr.value.data?.estree;
|
|
223
|
+
props[attr.name] = estreeToValue(estree, attr.value.value);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { props, spreads };
|
|
227
|
+
}
|
|
228
|
+
function jsxElement(node, ctx, inline) {
|
|
229
|
+
const name = node.name ?? "";
|
|
230
|
+
const { props, spreads } = attrsToProps(node.attributes);
|
|
231
|
+
const children = node.children;
|
|
232
|
+
if (inline) {
|
|
233
|
+
if (SUPERSCRIPT_TAGS.has(name) || SUBSCRIPT_TAGS.has(name)) {
|
|
234
|
+
const mark = {
|
|
235
|
+
type: SUPERSCRIPT_TAGS.has(name) ? "superscript" : "subscript",
|
|
236
|
+
};
|
|
237
|
+
return inlines(children, ctx, [mark]);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
type: "mdxInlineComponent",
|
|
241
|
+
attrs: { name, props, spreads, hasChildren: children.length > 0 },
|
|
242
|
+
content: inlines(children, ctx),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Block element. Children may be flow content or phrasing content (MDX
|
|
246
|
+
// allows `<Foo>text</Foo>`); normalise to blocks.
|
|
247
|
+
const hasFlowChildren = children.some((c) => !isPhrasing(c));
|
|
248
|
+
const content = hasFlowChildren
|
|
249
|
+
? blocks(children, ctx)
|
|
250
|
+
: children.length
|
|
251
|
+
? [{ type: "paragraph", content: inlines(children, ctx) }]
|
|
252
|
+
: [];
|
|
253
|
+
return {
|
|
254
|
+
type: "mdxComponent",
|
|
255
|
+
attrs: { name, props, spreads, hasChildren: content.length > 0 },
|
|
256
|
+
content,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const PHRASING = new Set([
|
|
260
|
+
"text",
|
|
261
|
+
"emphasis",
|
|
262
|
+
"strong",
|
|
263
|
+
"delete",
|
|
264
|
+
"inlineCode",
|
|
265
|
+
"link",
|
|
266
|
+
"linkReference",
|
|
267
|
+
"image",
|
|
268
|
+
"imageReference",
|
|
269
|
+
"break",
|
|
270
|
+
"html",
|
|
271
|
+
"footnoteReference",
|
|
272
|
+
"mdxJsxTextElement",
|
|
273
|
+
"mdxTextExpression",
|
|
274
|
+
]);
|
|
275
|
+
function isPhrasing(node) {
|
|
276
|
+
return PHRASING.has(node.type);
|
|
277
|
+
}
|
|
278
|
+
function inlines(nodes, ctx, marks = []) {
|
|
279
|
+
const out = [];
|
|
280
|
+
for (const node of nodes)
|
|
281
|
+
out.push(...inlineNode(node, ctx, marks));
|
|
282
|
+
return mergeText(out);
|
|
283
|
+
}
|
|
284
|
+
function withMarks(node, marks) {
|
|
285
|
+
return marks.length
|
|
286
|
+
? { ...node, marks: [...(node.marks ?? []), ...marks] }
|
|
287
|
+
: node;
|
|
288
|
+
}
|
|
289
|
+
function inlineNode(node, ctx, marks) {
|
|
290
|
+
switch (node.type) {
|
|
291
|
+
case "text":
|
|
292
|
+
return node.value
|
|
293
|
+
? [withMarks({ type: "text", text: node.value }, marks)]
|
|
294
|
+
: [];
|
|
295
|
+
case "emphasis":
|
|
296
|
+
return inlines(node.children, ctx, [
|
|
297
|
+
...marks,
|
|
298
|
+
{ type: "italic" },
|
|
299
|
+
]);
|
|
300
|
+
case "strong":
|
|
301
|
+
return inlines(node.children, ctx, [
|
|
302
|
+
...marks,
|
|
303
|
+
{ type: "bold" },
|
|
304
|
+
]);
|
|
305
|
+
case "delete":
|
|
306
|
+
return inlines(node.children, ctx, [
|
|
307
|
+
...marks,
|
|
308
|
+
{ type: "strike" },
|
|
309
|
+
]);
|
|
310
|
+
case "inlineCode":
|
|
311
|
+
return [
|
|
312
|
+
withMarks({ type: "text", text: node.value }, [
|
|
313
|
+
...marks,
|
|
314
|
+
{ type: "code" },
|
|
315
|
+
]),
|
|
316
|
+
];
|
|
317
|
+
case "link":
|
|
318
|
+
return inlines(node.children, ctx, [
|
|
319
|
+
...marks,
|
|
320
|
+
{ type: "link", attrs: { href: node.url, title: node.title ?? null } },
|
|
321
|
+
]);
|
|
322
|
+
case "linkReference": {
|
|
323
|
+
const def = ctx.definitions.get(node.identifier.toLowerCase());
|
|
324
|
+
if (!def)
|
|
325
|
+
return inlines(node.children, ctx, marks);
|
|
326
|
+
return inlines(node.children, ctx, [
|
|
327
|
+
...marks,
|
|
328
|
+
{ type: "link", attrs: { href: def.url, title: def.title ?? null } },
|
|
329
|
+
]);
|
|
330
|
+
}
|
|
331
|
+
case "image":
|
|
332
|
+
return [
|
|
333
|
+
withMarks({
|
|
334
|
+
type: "image",
|
|
335
|
+
attrs: {
|
|
336
|
+
src: node.url,
|
|
337
|
+
alt: node.alt ?? null,
|
|
338
|
+
title: node.title ?? null,
|
|
339
|
+
},
|
|
340
|
+
}, marks),
|
|
341
|
+
];
|
|
342
|
+
case "imageReference": {
|
|
343
|
+
const def = ctx.definitions.get(node.identifier.toLowerCase());
|
|
344
|
+
return def
|
|
345
|
+
? [
|
|
346
|
+
withMarks({
|
|
347
|
+
type: "image",
|
|
348
|
+
attrs: {
|
|
349
|
+
src: def.url,
|
|
350
|
+
alt: node.alt ?? null,
|
|
351
|
+
title: def.title ?? null,
|
|
352
|
+
},
|
|
353
|
+
}, marks),
|
|
354
|
+
]
|
|
355
|
+
: [];
|
|
356
|
+
}
|
|
357
|
+
case "break":
|
|
358
|
+
return [{ type: "hardBreak" }];
|
|
359
|
+
case "html":
|
|
360
|
+
return [{ type: "rawInlineHtml", attrs: { value: node.value } }];
|
|
361
|
+
case "footnoteReference":
|
|
362
|
+
return [
|
|
363
|
+
withMarks({ type: "footnoteReference", attrs: footnoteAttrs(node) }, marks),
|
|
364
|
+
];
|
|
365
|
+
case "mdxJsxTextElement": {
|
|
366
|
+
const converted = jsxElement(node, ctx, true);
|
|
367
|
+
const arr = Array.isArray(converted) ? converted : [converted];
|
|
368
|
+
return arr.map((n) => (n.type === "text" ? withMarks(n, marks) : n));
|
|
369
|
+
}
|
|
370
|
+
case "mdxTextExpression":
|
|
371
|
+
return [{ type: "mdxInlineExpression", attrs: { value: node.value } }];
|
|
372
|
+
case "mdxJsxFlowElement": {
|
|
373
|
+
const converted = jsxElement(node, ctx, true);
|
|
374
|
+
return Array.isArray(converted) ? converted : [converted];
|
|
375
|
+
}
|
|
376
|
+
default: {
|
|
377
|
+
// Block content inside inline context: flatten to text.
|
|
378
|
+
const children = node.children;
|
|
379
|
+
return children ? inlines(children, ctx, marks) : [];
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function mergeText(nodes) {
|
|
384
|
+
const out = [];
|
|
385
|
+
for (const n of nodes) {
|
|
386
|
+
const prev = out[out.length - 1];
|
|
387
|
+
if (prev &&
|
|
388
|
+
prev.type === "text" &&
|
|
389
|
+
n.type === "text" &&
|
|
390
|
+
sameMarks(prev.marks, n.marks)) {
|
|
391
|
+
prev.text = (prev.text ?? "") + (n.text ?? "");
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
out.push({ ...n });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
function sameMarks(a, b) {
|
|
400
|
+
return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
|
|
401
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ComponentRegistry, ImportSpec, PMNode } from "./types.js";
|
|
2
|
+
export interface SerializeOptions {
|
|
3
|
+
format?: "md" | "mdx";
|
|
4
|
+
/** Known components; used to emit imports for the ones the document uses. */
|
|
5
|
+
components?: ComponentRegistry;
|
|
6
|
+
/** Imports that were present in the source (used for components not in the registry). */
|
|
7
|
+
existingImports?: Record<string, ImportSpec>;
|
|
8
|
+
}
|
|
9
|
+
export declare function serializeMdx(doc: PMNode, options?: SerializeOptions): string;
|