@andesine/converters 0.1.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/dist/anchors.d.ts +18 -0
- package/dist/anchors.js +36 -0
- package/dist/anchors.js.map +1 -0
- package/dist/chunks/adapter-CeNgvE5W.js +177 -0
- package/dist/chunks/adapter-CeNgvE5W.js.map +1 -0
- package/dist/chunks/core-DC64ddB7.js +372 -0
- package/dist/chunks/core-DC64ddB7.js.map +1 -0
- package/dist/chunks/decode-B-xzTJFV.js +102 -0
- package/dist/chunks/decode-B-xzTJFV.js.map +1 -0
- package/dist/chunks/decode-BLP2Olia.d.ts +6 -0
- package/dist/chunks/stringify-DdPaV2y4.js +71 -0
- package/dist/chunks/stringify-DdPaV2y4.js.map +1 -0
- package/dist/chunks/types-SDFWRP3z.d.ts +185 -0
- package/dist/html.d.ts +49 -0
- package/dist/html.js +252 -0
- package/dist/html.js.map +1 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/markdoc.d.ts +49 -0
- package/dist/markdoc.js +310 -0
- package/dist/markdoc.js.map +1 -0
- package/dist/markdown.d.ts +48 -0
- package/dist/markdown.js +49 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mdx.d.ts +50 -0
- package/dist/mdx.js +110 -0
- package/dist/mdx.js.map +1 -0
- package/dist/text.d.ts +36 -0
- package/dist/text.js +69 -0
- package/dist/text.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { getHeadingAnchors } from "../anchors.js";
|
|
2
|
+
import { parseDocument, stringify } from "yaml";
|
|
3
|
+
//#region src/utils.ts
|
|
4
|
+
/** A conversion failure, optionally located at a child-index path in the source tree. */
|
|
5
|
+
var ConversionError = class extends Error {
|
|
6
|
+
path;
|
|
7
|
+
/**
|
|
8
|
+
* @param message - Description of the unsupported content or invalid mapping.
|
|
9
|
+
* @param path - Child indexes appended to the message; defaults to the document root.
|
|
10
|
+
*/
|
|
11
|
+
constructor(message, path = []) {
|
|
12
|
+
super(`${message}${path.length ? ` at content[${path.join("][")}]` : ""}`);
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.name = "ConversionError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const lookup = (record, name) => {
|
|
18
|
+
return record && Object.hasOwn(record, name) ? record[name] : void 0;
|
|
19
|
+
};
|
|
20
|
+
const array = (value) => value === null ? [] : Array.isArray(value) ? value : [value];
|
|
21
|
+
/**
|
|
22
|
+
* Concatenate a node's descendant text without changing the source.
|
|
23
|
+
* @param node - ProseMirror node to read.
|
|
24
|
+
* @returns Raw text with no added block separators, markup, or hard-break replacement.
|
|
25
|
+
*/
|
|
26
|
+
const textContent = (node) => node.text ?? (node.content || []).map(textContent).join("");
|
|
27
|
+
/**
|
|
28
|
+
* Create text content suitable for a node handler.
|
|
29
|
+
* @param text - Literal text to include.
|
|
30
|
+
* @returns One text node, or an empty array for an empty string.
|
|
31
|
+
*/
|
|
32
|
+
const textNode = (text) => text ? [{
|
|
33
|
+
type: "text",
|
|
34
|
+
text
|
|
35
|
+
}] : [];
|
|
36
|
+
/**
|
|
37
|
+
* Append a mark to all descendant text nodes without modifying the input nodes.
|
|
38
|
+
* @param nodes - Content to mark. Existing marks are retained, including duplicates.
|
|
39
|
+
* @param mark - Mark to append to each text node.
|
|
40
|
+
* @returns Mapped nodes; unchanged leaf nodes and existing marks may retain their references.
|
|
41
|
+
*/
|
|
42
|
+
const withMark = (nodes, mark) => nodes.map((node) => {
|
|
43
|
+
if (node.type === "text") return {
|
|
44
|
+
...node,
|
|
45
|
+
marks: [...node.marks || [], mark]
|
|
46
|
+
};
|
|
47
|
+
return node.content ? {
|
|
48
|
+
...node,
|
|
49
|
+
content: withMark(node.content, mark)
|
|
50
|
+
} : node;
|
|
51
|
+
});
|
|
52
|
+
const isJSONValue = (value) => {
|
|
53
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
54
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
55
|
+
if (Array.isArray(value)) return value.every(isJSONValue);
|
|
56
|
+
if (typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype) return false;
|
|
57
|
+
return Object.values(value).every(isJSONValue);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Create an Andesine element node with static JSON properties and a source representation.
|
|
61
|
+
* @param name - Element name, starting with a letter and containing letters, digits, _, ., or -.
|
|
62
|
+
* @param props - Static JSON properties with valid attribute names; executable expressions are rejected.
|
|
63
|
+
* @param content - Child nodes. The supplied content array is retained.
|
|
64
|
+
* @param selfClosing - Whether the source uses a self-closing tag; defaults to true for empty content.
|
|
65
|
+
* @returns A ProseMirror element node for use in import handlers.
|
|
66
|
+
* @throws ConversionError for invalid names, property keys, or non-JSON property values.
|
|
67
|
+
*/
|
|
68
|
+
const element = (name, props, content, selfClosing = !content.length) => {
|
|
69
|
+
if (!/^[A-Za-z][\w.-]*$/.test(name) || !isJSONValue(props)) throw new ConversionError("Elements require a valid name and static JSON properties");
|
|
70
|
+
return {
|
|
71
|
+
type: "element",
|
|
72
|
+
attrs: {
|
|
73
|
+
name,
|
|
74
|
+
props,
|
|
75
|
+
selfClosing,
|
|
76
|
+
source: `<${name}${Object.entries(props).map(([key, value]) => {
|
|
77
|
+
if (!/^[A-Za-z][\w-]*$/.test(key)) throw new ConversionError(`Invalid element property: ${key}`);
|
|
78
|
+
return typeof value === "string" ? ` ${key}=${JSON.stringify(value)}` : ` ${key}={${JSON.stringify(value)}}`;
|
|
79
|
+
}).join("")}${selfClosing ? " /" : ""}>`
|
|
80
|
+
},
|
|
81
|
+
...content.length ? { content } : {}
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/frontmatter.ts
|
|
86
|
+
const readFrontmatter = (source) => {
|
|
87
|
+
const match = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(source);
|
|
88
|
+
if (!match) return {
|
|
89
|
+
source,
|
|
90
|
+
frontmatter: {}
|
|
91
|
+
};
|
|
92
|
+
const document = parseDocument(match[1], {
|
|
93
|
+
schema: "core",
|
|
94
|
+
uniqueKeys: true
|
|
95
|
+
});
|
|
96
|
+
if (document.errors.length) throw new ConversionError(`Invalid frontmatter: ${document.errors[0].message}`);
|
|
97
|
+
const value = document.toJS({ maxAliasCount: 100 });
|
|
98
|
+
if (!value || Array.isArray(value) || typeof value !== "object" || !isJSONValue(value)) throw new ConversionError("Frontmatter must contain an object with static JSON values");
|
|
99
|
+
return {
|
|
100
|
+
source: source.slice(match[0].length),
|
|
101
|
+
frontmatter: value
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const writeFrontmatter = (frontmatter, source) => {
|
|
105
|
+
return Object.keys(frontmatter).length ? `---\n${stringify(frontmatter)}---\n\n${source}` : source;
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/core.ts
|
|
109
|
+
/**
|
|
110
|
+
* Build an async export-only converter using the shared content mappings.
|
|
111
|
+
*
|
|
112
|
+
* @param adapter - Root builder, serializer, and export node/mark handlers. No import stubs are needed.
|
|
113
|
+
* @param defaults - Reusable options. Per-call handler maps and named fragment mappings merge by key.
|
|
114
|
+
* @returns Only encode() and toAST(), both async. Handlers and image callbacks may be async.
|
|
115
|
+
* Import methods are absent at runtime and in TypeScript. No content is saved.
|
|
116
|
+
* Unsupported nodes and marks are handled by the adapter, usually by throwing ConversionError.
|
|
117
|
+
* Defaults render titles as H1s, unwrap fragments, and omit properties. Removed boundaries and
|
|
118
|
+
* omitted data cannot be recovered on import; choose explicit mappings when preservation matters.
|
|
119
|
+
*/
|
|
120
|
+
const createExportConverter = (adapter, defaults = {}) => {
|
|
121
|
+
const toAST = async (document, options = {}) => {
|
|
122
|
+
const settings = mergeOptions(defaults, options);
|
|
123
|
+
const anchors = new Map(getHeadingAnchors(document).map((heading) => [heading.path.join("."), heading.anchor]));
|
|
124
|
+
const frontmatter = Object.create(null);
|
|
125
|
+
const encode = async (node, path) => {
|
|
126
|
+
const marks = [...node.marks || []].sort((first, second) => Number(first.type === "code") - Number(second.type === "code"));
|
|
127
|
+
const context = {
|
|
128
|
+
anchor: node.type === "heading" ? anchors.get(path.join(".")) : void 0,
|
|
129
|
+
options: settings,
|
|
130
|
+
path,
|
|
131
|
+
encode: (child) => encode(child, path),
|
|
132
|
+
children: async (parent) => {
|
|
133
|
+
const children = [];
|
|
134
|
+
for (const [index, child] of (parent.content || []).entries()) children.push(...await encode(child, [...path, index]));
|
|
135
|
+
return children;
|
|
136
|
+
},
|
|
137
|
+
imageURL: async (image) => {
|
|
138
|
+
const url = await settings.imageURL?.(image);
|
|
139
|
+
if (!url) throw new ConversionError("An imageURL callback must resolve the image asset", path);
|
|
140
|
+
return url;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const handlers = settings.encode;
|
|
144
|
+
const elementName = node.type === "element" ? String(node.attrs?.name || "") : void 0;
|
|
145
|
+
const handler = elementName ? lookup(handlers?.elements, elementName) ?? lookup(handlers?.elements, "*") ?? lookup(handlers?.nodes, "element") ?? lookup(handlers?.nodes, "*") : lookup(handlers?.nodes, node.type) ?? lookup(handlers?.nodes, "*");
|
|
146
|
+
let result;
|
|
147
|
+
if (handler) result = array(await handler(node, context));
|
|
148
|
+
else if (node.type === "doc") result = await context.children(node);
|
|
149
|
+
else if (node.type === "title") {
|
|
150
|
+
const title = settings.title ?? "heading";
|
|
151
|
+
if (title === "omit") return [];
|
|
152
|
+
if (typeof title === "object") {
|
|
153
|
+
setMetadata(frontmatter, title.key || "title", textContent(node));
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
result = array(await adapter.encode({
|
|
157
|
+
...node,
|
|
158
|
+
type: "heading",
|
|
159
|
+
attrs: { level: 1 }
|
|
160
|
+
}, context));
|
|
161
|
+
} else if (node.type === "property") {
|
|
162
|
+
if (settings.properties !== void 0 && settings.properties !== "omit") {
|
|
163
|
+
const name = String(node.attrs?.label || "Property");
|
|
164
|
+
const value = node.attrs?.value ?? "";
|
|
165
|
+
const normalized = node.attrs?.type === "number" ? value === "" ? null : Number(value) : value;
|
|
166
|
+
setMetadata(frontmatter, name, normalized);
|
|
167
|
+
}
|
|
168
|
+
return [];
|
|
169
|
+
} else if (node.type === "fragment") {
|
|
170
|
+
const name = String(node.attrs?.name || "Content");
|
|
171
|
+
const mapping = lookup(settings.fragments?.named, name) ?? settings.fragments?.default ?? "unwrap";
|
|
172
|
+
if (mapping === "omit") return [];
|
|
173
|
+
if (mapping === "unwrap") return context.children(node);
|
|
174
|
+
result = await context.encode({
|
|
175
|
+
type: "element",
|
|
176
|
+
attrs: {
|
|
177
|
+
name: mapping.name,
|
|
178
|
+
props: { name },
|
|
179
|
+
selfClosing: false
|
|
180
|
+
},
|
|
181
|
+
content: node.content
|
|
182
|
+
});
|
|
183
|
+
} else result = array(await adapter.encode(node, context));
|
|
184
|
+
if (!result.length) return result;
|
|
185
|
+
for (const mark of marks.reverse()) {
|
|
186
|
+
const handler = lookup(handlers?.marks, mark.type) ?? lookup(handlers?.marks, "*");
|
|
187
|
+
result = array(await (handler ? handler(mark, result, context) : adapter.mark(mark, result, context)));
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
};
|
|
191
|
+
const children = await encode(document, []);
|
|
192
|
+
if (!adapter.frontmatter && Object.keys(frontmatter).length) throw new ConversionError(`${adapter.name} does not support frontmatter`);
|
|
193
|
+
return {
|
|
194
|
+
tree: adapter.root(children),
|
|
195
|
+
frontmatter
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
toAST,
|
|
200
|
+
async encode(document, options) {
|
|
201
|
+
const result = await toAST(document, options);
|
|
202
|
+
return writeFrontmatter(result.frontmatter, await adapter.stringify(result.tree));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Build an async import-only converter using the shared content mappings.
|
|
208
|
+
*
|
|
209
|
+
* @param adapter - Parser, decode handler, and node-type lookup. No export stubs are needed.
|
|
210
|
+
* @param defaults - Reusable import mappings and handlers, merged by key with per-call overrides.
|
|
211
|
+
* @returns Only decode() and fromAST(), both async. Parsers, handlers, and image callbacks may
|
|
212
|
+
* be async. Export methods are absent at runtime and in TypeScript. No content is saved.
|
|
213
|
+
* Imports create an entry with a title by default; document: "fragment" retains headings as body
|
|
214
|
+
* content. Unsupported input is handled by the adapter; parser and handler errors pass through.
|
|
215
|
+
*/
|
|
216
|
+
const createImportConverter = (adapter, defaults = {}) => {
|
|
217
|
+
const fromAST = async (input, options = {}) => {
|
|
218
|
+
const settings = mergeOptions(defaults, options);
|
|
219
|
+
const decode = async (node, path) => {
|
|
220
|
+
const context = {
|
|
221
|
+
options: settings,
|
|
222
|
+
path,
|
|
223
|
+
decode: (child) => decode(child, path),
|
|
224
|
+
children: async (children) => {
|
|
225
|
+
const output = [];
|
|
226
|
+
for (const [index, child] of children.entries()) output.push(...await decode(child, [...path, index]));
|
|
227
|
+
return output;
|
|
228
|
+
},
|
|
229
|
+
image: async (image) => {
|
|
230
|
+
const assetID = await settings.imageAssetID?.(image);
|
|
231
|
+
if (!assetID) throw new ConversionError("An imageAssetID callback must resolve the image URL", path);
|
|
232
|
+
return {
|
|
233
|
+
type: "image",
|
|
234
|
+
attrs: {
|
|
235
|
+
assetID,
|
|
236
|
+
alt: image.alt,
|
|
237
|
+
caption: image.title || ""
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
const handlers = settings.decode;
|
|
243
|
+
const type = adapter.type(node);
|
|
244
|
+
const name = adapter.elementName?.(node);
|
|
245
|
+
const mark = adapter.markName?.(node);
|
|
246
|
+
const handler = (name ? lookup(handlers?.elements, name) ?? lookup(handlers?.elements, "*") : void 0) ?? (mark ? lookup(handlers?.marks, mark) ?? lookup(handlers?.marks, "*") : void 0) ?? lookup(handlers?.nodes, type) ?? lookup(handlers?.nodes, "*");
|
|
247
|
+
return array(await (handler ? handler(node, context) : adapter.decode(node, context))).flatMap((content) => {
|
|
248
|
+
if (content.type !== "element") return [content];
|
|
249
|
+
const props = content.attrs?.props;
|
|
250
|
+
const fragmentName = typeof props?.name === "string" ? props.name : void 0;
|
|
251
|
+
const mapping = fragmentName ? lookup(settings.fragments?.named, fragmentName) ?? settings.fragments?.default : void 0;
|
|
252
|
+
if (mapping && typeof mapping === "object" && mapping.name === content.attrs?.name) return [{
|
|
253
|
+
type: "fragment",
|
|
254
|
+
attrs: { name: fragmentName },
|
|
255
|
+
content: content.content || [{ type: "paragraph" }]
|
|
256
|
+
}];
|
|
257
|
+
return [content];
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
const content = await decode(input.tree, []);
|
|
261
|
+
const title = settings.title ?? "heading";
|
|
262
|
+
const titleKey = typeof title === "object" ? title.key || "title" : void 0;
|
|
263
|
+
const properties = [];
|
|
264
|
+
let titleText = "";
|
|
265
|
+
if (titleKey && input.frontmatter[titleKey] !== void 0) titleText = String(input.frontmatter[titleKey]);
|
|
266
|
+
if (title === "heading" && settings.document !== "fragment" && content[0]?.type === "heading" && content[0].attrs?.level === 1) titleText = textContent(content.shift());
|
|
267
|
+
if (settings.properties && settings.properties !== "omit") for (const [label, value] of Object.entries(input.frontmatter)) {
|
|
268
|
+
if (label === titleKey) continue;
|
|
269
|
+
const type = typeof value === "number" ? "number" : typeof value === "boolean" ? "checkbox" : Array.isArray(value) && value.every((item) => typeof item === "string") ? "multi-select" : "text";
|
|
270
|
+
if (type === "text" && value !== null && typeof value !== "string") throw new ConversionError(`Property ${label} cannot represent this frontmatter value`);
|
|
271
|
+
properties.push({
|
|
272
|
+
type: "property",
|
|
273
|
+
attrs: {
|
|
274
|
+
label,
|
|
275
|
+
type,
|
|
276
|
+
value: value ?? ""
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const body = settings.targetFragment ? [{
|
|
281
|
+
type: "fragment",
|
|
282
|
+
attrs: { name: settings.targetFragment },
|
|
283
|
+
content: content.length ? content : [{ type: "paragraph" }]
|
|
284
|
+
}] : content;
|
|
285
|
+
return {
|
|
286
|
+
type: "doc",
|
|
287
|
+
content: [
|
|
288
|
+
...settings.document === "fragment" ? [] : [{
|
|
289
|
+
type: "title",
|
|
290
|
+
content: textNode(titleText)
|
|
291
|
+
}],
|
|
292
|
+
...properties,
|
|
293
|
+
...body.length ? body : [{ type: "paragraph" }]
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
return {
|
|
298
|
+
fromAST,
|
|
299
|
+
async decode(source, options) {
|
|
300
|
+
const input = adapter.frontmatter ? readFrontmatter(source) : {
|
|
301
|
+
source,
|
|
302
|
+
frontmatter: {}
|
|
303
|
+
};
|
|
304
|
+
return fromAST({
|
|
305
|
+
tree: await adapter.parse(input.source),
|
|
306
|
+
frontmatter: input.frontmatter
|
|
307
|
+
}, options);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* Build an async converter for a custom format that supports both import and export.
|
|
313
|
+
*
|
|
314
|
+
* @param adapter - Full adapter with parser, serializer, and mappings for both directions.
|
|
315
|
+
* @param defaults - Shared defaults and direction-specific handlers. Per-call maps merge by key.
|
|
316
|
+
* @returns encode(), toAST(), decode(), and fromAST(), composed from the one-way factories.
|
|
317
|
+
* Each method accepts options for its own direction. No content is saved or uploaded.
|
|
318
|
+
* Defaults render titles as H1s, unwrap fragments, and omit properties. Removed boundaries and
|
|
319
|
+
* omitted data cannot be recovered on import; choose explicit mappings when preservation matters.
|
|
320
|
+
*/
|
|
321
|
+
const createConverter = (adapter, defaults = {}) => ({
|
|
322
|
+
...createExportConverter(adapter, defaults),
|
|
323
|
+
...createImportConverter(adapter, defaults)
|
|
324
|
+
});
|
|
325
|
+
const setMetadata = (metadata, key, value) => {
|
|
326
|
+
if (Object.hasOwn(metadata, key)) throw new ConversionError(`Duplicate frontmatter field: ${key}`);
|
|
327
|
+
metadata[key] = value;
|
|
328
|
+
};
|
|
329
|
+
const mergeOptions = (defaults, options) => ({
|
|
330
|
+
...defaults,
|
|
331
|
+
...options,
|
|
332
|
+
fragments: {
|
|
333
|
+
...defaults.fragments,
|
|
334
|
+
...options.fragments,
|
|
335
|
+
named: {
|
|
336
|
+
...defaults.fragments?.named,
|
|
337
|
+
...options.fragments?.named
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
encode: {
|
|
341
|
+
nodes: {
|
|
342
|
+
...defaults.encode?.nodes,
|
|
343
|
+
...options.encode?.nodes
|
|
344
|
+
},
|
|
345
|
+
marks: {
|
|
346
|
+
...defaults.encode?.marks,
|
|
347
|
+
...options.encode?.marks
|
|
348
|
+
},
|
|
349
|
+
elements: {
|
|
350
|
+
...defaults.encode?.elements,
|
|
351
|
+
...options.encode?.elements
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
decode: {
|
|
355
|
+
nodes: {
|
|
356
|
+
...defaults.decode?.nodes,
|
|
357
|
+
...options.decode?.nodes
|
|
358
|
+
},
|
|
359
|
+
marks: {
|
|
360
|
+
...defaults.decode?.marks,
|
|
361
|
+
...options.decode?.marks
|
|
362
|
+
},
|
|
363
|
+
elements: {
|
|
364
|
+
...defaults.decode?.elements,
|
|
365
|
+
...options.decode?.elements
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
//#endregion
|
|
370
|
+
export { element as a, textContent as c, ConversionError as i, textNode as l, createExportConverter as n, isJSONValue as o, createImportConverter as r, lookup as s, createConverter as t, withMark as u };
|
|
371
|
+
|
|
372
|
+
//# sourceMappingURL=core-DC64ddB7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-DC64ddB7.js","names":[],"sources":["../../src/utils.ts","../../src/frontmatter.ts","../../src/core.ts"],"sourcesContent":["import type { ContentMark, ContentNode, HandlerResult } from \"./types\";\n\n/** A conversion failure, optionally located at a child-index path in the source tree. */\nclass ConversionError extends Error {\n /**\n * @param message - Description of the unsupported content or invalid mapping.\n * @param path - Child indexes appended to the message; defaults to the document root.\n */\n constructor(\n message: string,\n readonly path: readonly number[] = []\n ) {\n super(`${message}${path.length ? ` at content[${path.join(\"][\")}]` : \"\"}`);\n this.name = \"ConversionError\";\n }\n}\n\nconst lookup = <T>(record: Record<string, T> | undefined, name: string): T | undefined => {\n return record && Object.hasOwn(record, name) ? record[name] : undefined;\n};\nconst array = <T>(value: HandlerResult<T>): T[] =>\n value === null ? [] : Array.isArray(value) ? value : [value];\n/**\n * Concatenate a node's descendant text without changing the source.\n * @param node - ProseMirror node to read.\n * @returns Raw text with no added block separators, markup, or hard-break replacement.\n */\nconst textContent = (node: ContentNode): string =>\n node.text ?? (node.content || []).map(textContent).join(\"\");\n/**\n * Create text content suitable for a node handler.\n * @param text - Literal text to include.\n * @returns One text node, or an empty array for an empty string.\n */\nconst textNode = (text: string): ContentNode[] => (text ? [{ type: \"text\", text }] : []);\n/**\n * Append a mark to all descendant text nodes without modifying the input nodes.\n * @param nodes - Content to mark. Existing marks are retained, including duplicates.\n * @param mark - Mark to append to each text node.\n * @returns Mapped nodes; unchanged leaf nodes and existing marks may retain their references.\n */\nconst withMark = (nodes: ContentNode[], mark: ContentMark): ContentNode[] =>\n nodes.map((node) => {\n if (node.type === \"text\") return { ...node, marks: [...(node.marks || []), mark] };\n\n return node.content ? { ...node, content: withMark(node.content, mark) } : node;\n });\nconst isJSONValue = (value: unknown): boolean => {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (Array.isArray(value)) return value.every(isJSONValue);\n if (typeof value !== \"object\" || Object.getPrototypeOf(value) !== Object.prototype) return false;\n\n return Object.values(value).every(isJSONValue);\n};\n/**\n * Create an Andesine element node with static JSON properties and a source representation.\n * @param name - Element name, starting with a letter and containing letters, digits, _, ., or -.\n * @param props - Static JSON properties with valid attribute names; executable expressions are rejected.\n * @param content - Child nodes. The supplied content array is retained.\n * @param selfClosing - Whether the source uses a self-closing tag; defaults to true for empty content.\n * @returns A ProseMirror element node for use in import handlers.\n * @throws ConversionError for invalid names, property keys, or non-JSON property values.\n */\nconst element = (\n name: string,\n props: Record<string, unknown>,\n content: ContentNode[],\n selfClosing = !content.length\n): ContentNode => {\n if (!/^[A-Za-z][\\w.-]*$/.test(name) || !isJSONValue(props)) {\n throw new ConversionError(\"Elements require a valid name and static JSON properties\");\n }\n const attributes = Object.entries(props)\n .map(([key, value]) => {\n if (!/^[A-Za-z][\\w-]*$/.test(key))\n throw new ConversionError(`Invalid element property: ${key}`);\n\n return typeof value === \"string\"\n ? ` ${key}=${JSON.stringify(value)}`\n : ` ${key}={${JSON.stringify(value)}}`;\n })\n .join(\"\");\n\n return {\n type: \"element\",\n attrs: { name, props, selfClosing, source: `<${name}${attributes}${selfClosing ? \" /\" : \"\"}>` },\n ...(content.length ? { content } : {})\n };\n};\n\nexport { ConversionError, lookup, array, element, isJSONValue, textContent, textNode, withMark };\n","import { parseDocument, stringify } from \"yaml\";\nimport { ConversionError, isJSONValue } from \"./utils\";\n\nconst readFrontmatter = (\n source: string\n): { source: string; frontmatter: Record<string, unknown> } => {\n const match = /^(?:\\uFEFF)?---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/.exec(source);\n\n if (!match) return { source, frontmatter: {} };\n\n const document = parseDocument(match[1], { schema: \"core\", uniqueKeys: true });\n\n if (document.errors.length)\n throw new ConversionError(`Invalid frontmatter: ${document.errors[0].message}`);\n\n const value: unknown = document.toJS({ maxAliasCount: 100 });\n\n if (!value || Array.isArray(value) || typeof value !== \"object\" || !isJSONValue(value)) {\n throw new ConversionError(\"Frontmatter must contain an object with static JSON values\");\n }\n return { source: source.slice(match[0].length), frontmatter: value as Record<string, unknown> };\n};\nconst writeFrontmatter = (frontmatter: Record<string, unknown>, source: string): string => {\n return Object.keys(frontmatter).length\n ? `---\\n${stringify(frontmatter)}---\\n\\n${source}`\n : source;\n};\n\nexport { readFrontmatter, writeFrontmatter };\n","import { getHeadingAnchors } from \"./anchors\";\nimport { readFrontmatter, writeFrontmatter } from \"./frontmatter\";\nimport { array, ConversionError, lookup, textContent, textNode } from \"./utils\";\nimport type {\n ContentNode,\n ConvertedAST,\n Converter,\n ConverterOptions,\n DecodeContext,\n EncodeContext,\n ExportConverter,\n ExportConverterOptions,\n ExportFormatAdapter,\n ImportConverter,\n ImportConverterOptions,\n ImportFormatAdapter,\n FormatAdapter\n} from \"./types\";\n\n/**\n * Build an async export-only converter using the shared content mappings.\n *\n * @param adapter - Root builder, serializer, and export node/mark handlers. No import stubs are needed.\n * @param defaults - Reusable options. Per-call handler maps and named fragment mappings merge by key.\n * @returns Only encode() and toAST(), both async. Handlers and image callbacks may be async.\n * Import methods are absent at runtime and in TypeScript. No content is saved.\n * Unsupported nodes and marks are handled by the adapter, usually by throwing ConversionError.\n * Defaults render titles as H1s, unwrap fragments, and omit properties. Removed boundaries and\n * omitted data cannot be recovered on import; choose explicit mappings when preservation matters.\n */\nconst createExportConverter = <T>(\n adapter: ExportFormatAdapter<T>,\n defaults: ExportConverterOptions<T> = {}\n): ExportConverter<T> => {\n const toAST = async (\n document: ContentNode,\n options: ExportConverterOptions<T> = {}\n ): Promise<ConvertedAST<T>> => {\n const settings = mergeOptions(defaults, options);\n const anchors = new Map(\n getHeadingAnchors(document).map((heading) => [heading.path.join(\".\"), heading.anchor])\n );\n const frontmatter: Record<string, unknown> = Object.create(null);\n const encode = async (node: ContentNode, path: readonly number[]): Promise<T[]> => {\n // Code handlers consume plain text, so code must be the innermost mark.\n const marks = [...(node.marks || [])].sort(\n (first, second) => Number(first.type === \"code\") - Number(second.type === \"code\")\n );\n const context: EncodeContext<T> = {\n anchor: node.type === \"heading\" ? anchors.get(path.join(\".\")) : undefined,\n options: settings,\n path,\n encode: (child) => encode(child, path),\n children: async (parent) => {\n const children: T[] = [];\n\n for (const [index, child] of (parent.content || []).entries())\n children.push(...(await encode(child, [...path, index])));\n return children;\n },\n imageURL: async (image) => {\n const url = await settings.imageURL?.(image);\n\n if (!url)\n throw new ConversionError(\"An imageURL callback must resolve the image asset\", path);\n return url;\n }\n };\n const handlers = settings.encode;\n const elementName = node.type === \"element\" ? String(node.attrs?.name || \"\") : undefined;\n const handler = elementName\n ? (lookup(handlers?.elements, elementName) ??\n lookup(handlers?.elements, \"*\") ??\n lookup(handlers?.nodes, \"element\") ??\n lookup(handlers?.nodes, \"*\"))\n : (lookup(handlers?.nodes, node.type) ?? lookup(handlers?.nodes, \"*\"));\n let result: T[];\n\n if (handler) {\n result = array(await handler(node, context));\n } else if (node.type === \"doc\") {\n result = await context.children(node);\n } else if (node.type === \"title\") {\n const title = settings.title ?? \"heading\";\n\n if (title === \"omit\") return [];\n if (typeof title === \"object\") {\n setMetadata(frontmatter, title.key || \"title\", textContent(node));\n return [];\n }\n result = array(\n await adapter.encode({ ...node, type: \"heading\", attrs: { level: 1 } }, context)\n );\n } else if (node.type === \"property\") {\n if (settings.properties !== undefined && settings.properties !== \"omit\") {\n const name = String(node.attrs?.label || \"Property\");\n const value = node.attrs?.value ?? \"\";\n const normalized =\n node.attrs?.type === \"number\" ? (value === \"\" ? null : Number(value)) : value;\n\n setMetadata(frontmatter, name, normalized);\n }\n return [];\n } else if (node.type === \"fragment\") {\n const name = String(node.attrs?.name || \"Content\");\n const mapping =\n lookup(settings.fragments?.named, name) ?? settings.fragments?.default ?? \"unwrap\";\n\n if (mapping === \"omit\") return [];\n if (mapping === \"unwrap\") return context.children(node);\n\n result = await context.encode({\n type: \"element\",\n attrs: { name: mapping.name, props: { name }, selfClosing: false },\n content: node.content\n });\n } else {\n result = array(await adapter.encode(node, context));\n }\n if (!result.length) return result;\n\n for (const mark of marks.reverse()) {\n const handler = lookup(handlers?.marks, mark.type) ?? lookup(handlers?.marks, \"*\");\n\n result = array(\n await (handler ? handler(mark, result, context) : adapter.mark(mark, result, context))\n );\n }\n return result;\n };\n const children = await encode(document, []);\n\n if (!adapter.frontmatter && Object.keys(frontmatter).length)\n throw new ConversionError(`${adapter.name} does not support frontmatter`);\n\n return { tree: adapter.root(children), frontmatter };\n };\n\n return {\n toAST,\n async encode(document: ContentNode, options?: ExportConverterOptions<T>): Promise<string> {\n const result = await toAST(document, options);\n\n return writeFrontmatter(result.frontmatter, await adapter.stringify(result.tree));\n }\n };\n};\n\n/**\n * Build an async import-only converter using the shared content mappings.\n *\n * @param adapter - Parser, decode handler, and node-type lookup. No export stubs are needed.\n * @param defaults - Reusable import mappings and handlers, merged by key with per-call overrides.\n * @returns Only decode() and fromAST(), both async. Parsers, handlers, and image callbacks may\n * be async. Export methods are absent at runtime and in TypeScript. No content is saved.\n * Imports create an entry with a title by default; document: \"fragment\" retains headings as body\n * content. Unsupported input is handled by the adapter; parser and handler errors pass through.\n */\nconst createImportConverter = <T>(\n adapter: ImportFormatAdapter<T>,\n defaults: ImportConverterOptions<T> = {}\n): ImportConverter<T> => {\n const fromAST = async (\n input: ConvertedAST<T>,\n options: ImportConverterOptions<T> = {}\n ): Promise<ContentNode> => {\n const settings = mergeOptions(defaults, options);\n const decode = async (node: T, path: readonly number[]): Promise<ContentNode[]> => {\n const context: DecodeContext<T> = {\n options: settings,\n path,\n decode: (child) => decode(child, path),\n children: async (children) => {\n const output: ContentNode[] = [];\n\n for (const [index, child] of children.entries())\n output.push(...(await decode(child, [...path, index])));\n return output;\n },\n image: async (image) => {\n const assetID = await settings.imageAssetID?.(image);\n\n if (!assetID)\n throw new ConversionError(\"An imageAssetID callback must resolve the image URL\", path);\n return { type: \"image\", attrs: { assetID, alt: image.alt, caption: image.title || \"\" } };\n }\n };\n const handlers = settings.decode;\n const type = adapter.type(node);\n const name = adapter.elementName?.(node);\n const mark = adapter.markName?.(node);\n const handler =\n (name\n ? (lookup(handlers?.elements, name) ?? lookup(handlers?.elements, \"*\"))\n : undefined) ??\n (mark ? (lookup(handlers?.marks, mark) ?? lookup(handlers?.marks, \"*\")) : undefined) ??\n lookup(handlers?.nodes, type) ??\n lookup(handlers?.nodes, \"*\");\n const result = array(\n await (handler ? handler(node, context) : adapter.decode(node, context))\n );\n\n return result.flatMap((content) => {\n if (content.type !== \"element\") return [content];\n\n const props = content.attrs?.props as Record<string, unknown> | undefined;\n const fragmentName = typeof props?.name === \"string\" ? props.name : undefined;\n const mapping = fragmentName\n ? (lookup(settings.fragments?.named, fragmentName) ?? settings.fragments?.default)\n : undefined;\n\n if (mapping && typeof mapping === \"object\" && mapping.name === content.attrs?.name) {\n return [\n {\n type: \"fragment\",\n attrs: { name: fragmentName },\n content: content.content || [{ type: \"paragraph\" }]\n }\n ];\n }\n return [content];\n });\n };\n const content = await decode(input.tree, []);\n const title = settings.title ?? \"heading\";\n const titleKey = typeof title === \"object\" ? title.key || \"title\" : undefined;\n const properties: ContentNode[] = [];\n let titleText = \"\";\n\n if (titleKey && input.frontmatter[titleKey] !== undefined)\n titleText = String(input.frontmatter[titleKey]);\n if (\n title === \"heading\" &&\n settings.document !== \"fragment\" &&\n content[0]?.type === \"heading\" &&\n content[0].attrs?.level === 1\n ) {\n titleText = textContent(content.shift()!);\n }\n if (settings.properties && settings.properties !== \"omit\") {\n for (const [label, value] of Object.entries(input.frontmatter)) {\n if (label === titleKey) continue;\n\n const type =\n typeof value === \"number\"\n ? \"number\"\n : typeof value === \"boolean\"\n ? \"checkbox\"\n : Array.isArray(value) && value.every((item) => typeof item === \"string\")\n ? \"multi-select\"\n : \"text\";\n\n if (type === \"text\" && value !== null && typeof value !== \"string\")\n throw new ConversionError(`Property ${label} cannot represent this frontmatter value`);\n\n properties.push({ type: \"property\", attrs: { label, type, value: value ?? \"\" } });\n }\n }\n const body: ContentNode[] = settings.targetFragment\n ? [\n {\n type: \"fragment\",\n attrs: { name: settings.targetFragment },\n content: content.length ? content : [{ type: \"paragraph\" }]\n }\n ]\n : content;\n\n return {\n type: \"doc\",\n content: [\n ...(settings.document === \"fragment\"\n ? []\n : [{ type: \"title\", content: textNode(titleText) }]),\n ...properties,\n ...(body.length ? body : [{ type: \"paragraph\" }])\n ]\n };\n };\n\n return {\n fromAST,\n async decode(source: string, options?: ImportConverterOptions<T>): Promise<ContentNode> {\n const input = adapter.frontmatter ? readFrontmatter(source) : { source, frontmatter: {} };\n\n return fromAST(\n { tree: await adapter.parse(input.source), frontmatter: input.frontmatter },\n options\n );\n }\n };\n};\n\n/**\n * Build an async converter for a custom format that supports both import and export.\n *\n * @param adapter - Full adapter with parser, serializer, and mappings for both directions.\n * @param defaults - Shared defaults and direction-specific handlers. Per-call maps merge by key.\n * @returns encode(), toAST(), decode(), and fromAST(), composed from the one-way factories.\n * Each method accepts options for its own direction. No content is saved or uploaded.\n * Defaults render titles as H1s, unwrap fragments, and omit properties. Removed boundaries and\n * omitted data cannot be recovered on import; choose explicit mappings when preservation matters.\n */\nconst createConverter = <T>(\n adapter: FormatAdapter<T>,\n defaults: ConverterOptions<T> = {}\n): Converter<T> => ({\n ...createExportConverter(adapter, defaults),\n ...createImportConverter(adapter, defaults)\n});\nconst setMetadata = (metadata: Record<string, unknown>, key: string, value: unknown): void => {\n if (Object.hasOwn(metadata, key))\n throw new ConversionError(`Duplicate frontmatter field: ${key}`);\n\n metadata[key] = value;\n};\nconst mergeOptions = <T>(\n defaults: ConverterOptions<T>,\n options: ConverterOptions<T>\n): ConverterOptions<T> => ({\n ...defaults,\n ...options,\n fragments: {\n ...defaults.fragments,\n ...options.fragments,\n named: { ...defaults.fragments?.named, ...options.fragments?.named }\n },\n encode: {\n nodes: { ...defaults.encode?.nodes, ...options.encode?.nodes },\n marks: { ...defaults.encode?.marks, ...options.encode?.marks },\n elements: { ...defaults.encode?.elements, ...options.encode?.elements }\n },\n decode: {\n nodes: { ...defaults.decode?.nodes, ...options.decode?.nodes },\n marks: { ...defaults.decode?.marks, ...options.decode?.marks },\n elements: { ...defaults.decode?.elements, ...options.decode?.elements }\n }\n});\n\nexport { createConverter, createExportConverter, createImportConverter };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { i as ConversionError, l as textNode, u as withMark } from "./core-DC64ddB7.js";
|
|
2
|
+
//#region src/markdown/decode.ts
|
|
3
|
+
const paragraphContent = (children) => {
|
|
4
|
+
const blocks = [];
|
|
5
|
+
let inline = [];
|
|
6
|
+
for (const child of children) if (["text", "hardBreak"].includes(child.type)) inline.push(child);
|
|
7
|
+
else {
|
|
8
|
+
if (inline.length) blocks.push({
|
|
9
|
+
type: "paragraph",
|
|
10
|
+
content: inline
|
|
11
|
+
});
|
|
12
|
+
inline = [];
|
|
13
|
+
blocks.push(child);
|
|
14
|
+
}
|
|
15
|
+
if (inline.length || !blocks.length) blocks.push({
|
|
16
|
+
type: "paragraph",
|
|
17
|
+
content: inline
|
|
18
|
+
});
|
|
19
|
+
return blocks;
|
|
20
|
+
};
|
|
21
|
+
const decodeMarkdown = async (node, context) => {
|
|
22
|
+
const children = "children" in node ? await context.children(node.children) : [];
|
|
23
|
+
switch (node.type) {
|
|
24
|
+
case "root": return children;
|
|
25
|
+
case "text": return textNode(node.value);
|
|
26
|
+
case "paragraph": return paragraphContent(children);
|
|
27
|
+
case "heading": return {
|
|
28
|
+
type: "heading",
|
|
29
|
+
attrs: { level: node.depth },
|
|
30
|
+
content: children
|
|
31
|
+
};
|
|
32
|
+
case "blockquote": return {
|
|
33
|
+
type: "blockquote",
|
|
34
|
+
content: children
|
|
35
|
+
};
|
|
36
|
+
case "list": {
|
|
37
|
+
const tasks = children.some((child) => child.type === "taskItem");
|
|
38
|
+
if (node.ordered && tasks) throw new ConversionError("Ordered task lists require a custom handler", context.path);
|
|
39
|
+
return {
|
|
40
|
+
type: node.ordered ? "orderedList" : tasks ? "taskList" : "bulletList",
|
|
41
|
+
...node.ordered ? { attrs: { start: node.start || 1 } } : {},
|
|
42
|
+
content: tasks ? children.map((child) => ({
|
|
43
|
+
...child,
|
|
44
|
+
type: "taskItem",
|
|
45
|
+
attrs: {
|
|
46
|
+
checked: false,
|
|
47
|
+
...child.attrs
|
|
48
|
+
}
|
|
49
|
+
})) : children
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
case "listItem": return {
|
|
53
|
+
type: typeof node.checked === "boolean" ? "taskItem" : "listItem",
|
|
54
|
+
...typeof node.checked === "boolean" ? { attrs: { checked: node.checked } } : {},
|
|
55
|
+
content: children
|
|
56
|
+
};
|
|
57
|
+
case "break": return { type: "hardBreak" };
|
|
58
|
+
case "thematicBreak": return { type: "horizontalRule" };
|
|
59
|
+
case "code": return {
|
|
60
|
+
type: "codeBlock",
|
|
61
|
+
attrs: { language: node.lang || null },
|
|
62
|
+
content: textNode(node.value)
|
|
63
|
+
};
|
|
64
|
+
case "inlineCode": return withMark(textNode(node.value), { type: "code" });
|
|
65
|
+
case "strong": return withMark(children, { type: "bold" });
|
|
66
|
+
case "emphasis": return withMark(children, { type: "italic" });
|
|
67
|
+
case "delete": return withMark(children, { type: "strike" });
|
|
68
|
+
case "link": return withMark(children, {
|
|
69
|
+
type: "link",
|
|
70
|
+
attrs: { href: node.url }
|
|
71
|
+
});
|
|
72
|
+
case "image": return context.image({
|
|
73
|
+
url: node.url,
|
|
74
|
+
alt: node.alt || "",
|
|
75
|
+
title: node.title || void 0
|
|
76
|
+
});
|
|
77
|
+
case "table": return {
|
|
78
|
+
type: "table",
|
|
79
|
+
content: children.map((row, index) => index ? row : {
|
|
80
|
+
...row,
|
|
81
|
+
content: row.content?.map((cell) => ({
|
|
82
|
+
...cell,
|
|
83
|
+
type: "tableHeader"
|
|
84
|
+
}))
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
case "tableRow": return {
|
|
88
|
+
type: "tableRow",
|
|
89
|
+
content: children
|
|
90
|
+
};
|
|
91
|
+
case "tableCell": return {
|
|
92
|
+
type: "tableCell",
|
|
93
|
+
content: paragraphContent(children)
|
|
94
|
+
};
|
|
95
|
+
case "definition": return null;
|
|
96
|
+
default: throw new ConversionError(`Unsupported Markdown syntax: ${node.type}`, context.path);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
export { paragraphContent as n, decodeMarkdown as t };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=decode-B-xzTJFV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-B-xzTJFV.js","names":[],"sources":["../../src/markdown/decode.ts"],"sourcesContent":["import type { Root, RootContent } from \"mdast\";\nimport { ConversionError, textNode, withMark } from \"../utils\";\nimport type { ContentNode, DecodeContext } from \"../types\";\n\ntype MarkdownNode = Root | RootContent;\n\nconst paragraphContent = (children: ContentNode[]): ContentNode[] => {\n const blocks: ContentNode[] = [];\n let inline: ContentNode[] = [];\n\n for (const child of children) {\n if ([\"text\", \"hardBreak\"].includes(child.type)) {\n inline.push(child);\n } else {\n if (inline.length) blocks.push({ type: \"paragraph\", content: inline });\n inline = [];\n blocks.push(child);\n }\n }\n if (inline.length || !blocks.length) blocks.push({ type: \"paragraph\", content: inline });\n\n return blocks;\n};\nconst decodeMarkdown = async (\n node: MarkdownNode,\n context: DecodeContext<MarkdownNode>\n): Promise<ContentNode | ContentNode[] | null> => {\n const children = \"children\" in node ? await context.children(node.children) : [];\n\n switch (node.type) {\n case \"root\":\n return children;\n case \"text\":\n return textNode(node.value);\n case \"paragraph\":\n return paragraphContent(children);\n case \"heading\":\n return { type: \"heading\", attrs: { level: node.depth }, content: children };\n case \"blockquote\":\n return { type: \"blockquote\", content: children };\n case \"list\": {\n const tasks = children.some((child) => child.type === \"taskItem\");\n\n if (node.ordered && tasks)\n throw new ConversionError(\"Ordered task lists require a custom handler\", context.path);\n\n return {\n type: node.ordered ? \"orderedList\" : tasks ? \"taskList\" : \"bulletList\",\n ...(node.ordered ? { attrs: { start: node.start || 1 } } : {}),\n content: tasks\n ? children.map((child) => ({\n ...child,\n type: \"taskItem\",\n attrs: { checked: false, ...child.attrs }\n }))\n : children\n };\n }\n case \"listItem\":\n return {\n type: typeof node.checked === \"boolean\" ? \"taskItem\" : \"listItem\",\n ...(typeof node.checked === \"boolean\" ? { attrs: { checked: node.checked } } : {}),\n content: children\n };\n case \"break\":\n return { type: \"hardBreak\" };\n case \"thematicBreak\":\n return { type: \"horizontalRule\" };\n case \"code\":\n return {\n type: \"codeBlock\",\n attrs: { language: node.lang || null },\n content: textNode(node.value)\n };\n case \"inlineCode\":\n return withMark(textNode(node.value), { type: \"code\" });\n case \"strong\":\n return withMark(children, { type: \"bold\" });\n case \"emphasis\":\n return withMark(children, { type: \"italic\" });\n case \"delete\":\n return withMark(children, { type: \"strike\" });\n case \"link\":\n return withMark(children, { type: \"link\", attrs: { href: node.url } });\n case \"image\":\n return context.image({ url: node.url, alt: node.alt || \"\", title: node.title || undefined });\n case \"table\":\n return {\n type: \"table\",\n content: children.map((row, index) =>\n index\n ? row\n : { ...row, content: row.content?.map((cell) => ({ ...cell, type: \"tableHeader\" })) }\n )\n };\n case \"tableRow\":\n return { type: \"tableRow\", content: children };\n case \"tableCell\":\n return { type: \"tableCell\", content: paragraphContent(children) };\n case \"definition\":\n return null;\n default:\n throw new ConversionError(`Unsupported Markdown syntax: ${node.type}`, context.path);\n }\n};\n\nexport { decodeMarkdown, paragraphContent };\nexport type { MarkdownNode };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
//#region src/markdown/normalize.ts
|
|
2
|
+
const isFormatting = (node) => {
|
|
3
|
+
return [
|
|
4
|
+
"strong",
|
|
5
|
+
"emphasis",
|
|
6
|
+
"delete",
|
|
7
|
+
"link"
|
|
8
|
+
].includes(node.type);
|
|
9
|
+
};
|
|
10
|
+
const sameFormatting = (first, second) => {
|
|
11
|
+
if (first.type !== second.type || JSON.stringify(first.data) !== JSON.stringify(second.data)) return false;
|
|
12
|
+
return first.type !== "link" || second.type === "link" && first.url === second.url && first.title === second.title;
|
|
13
|
+
};
|
|
14
|
+
const normalizeMarkdown = (node) => {
|
|
15
|
+
if (!("children" in node)) return node;
|
|
16
|
+
const children = [];
|
|
17
|
+
for (const child of node.children) {
|
|
18
|
+
const previous = children[children.length - 1];
|
|
19
|
+
if (previous && isFormatting(previous) && isFormatting(child) && sameFormatting(previous, child)) previous.children.push(...child.children);
|
|
20
|
+
else children.push(isFormatting(child) ? {
|
|
21
|
+
...child,
|
|
22
|
+
children: [...child.children]
|
|
23
|
+
} : child);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
...node,
|
|
27
|
+
children: children.map(normalizeMarkdown)
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/markdown/stringify.ts
|
|
32
|
+
const encodeCharacterReference = (character) => {
|
|
33
|
+
return `&#x${character.codePointAt(0).toString(16)};`;
|
|
34
|
+
};
|
|
35
|
+
const needsBoundaryEncoding = (outside, inside) => {
|
|
36
|
+
return Boolean(outside) && !/[\s\p{P}\p{S}]/u.test(outside) && /[\p{P}\p{S}]/u.test(inside);
|
|
37
|
+
};
|
|
38
|
+
const stringifyStrikethrough = (node, _parent, state, info) => {
|
|
39
|
+
const tracker = state.createTracker(info);
|
|
40
|
+
const exit = state.enter("strikethrough");
|
|
41
|
+
const before = tracker.move("~~");
|
|
42
|
+
const content = state.containerPhrasing(node, {
|
|
43
|
+
...tracker.current(),
|
|
44
|
+
before,
|
|
45
|
+
after: "~"
|
|
46
|
+
}).replace(/^\s|\s$/gu, encodeCharacterReference);
|
|
47
|
+
exit();
|
|
48
|
+
state.attentionEncodeSurroundingInfo = {
|
|
49
|
+
before: needsBoundaryEncoding(info.before.slice(-1), content.slice(0, 1)),
|
|
50
|
+
after: needsBoundaryEncoding(info.after.slice(0, 1), content.slice(-1))
|
|
51
|
+
};
|
|
52
|
+
return `~~${content}~~`;
|
|
53
|
+
};
|
|
54
|
+
stringifyStrikethrough.peek = () => "~";
|
|
55
|
+
const markdownStringifyOptions = {
|
|
56
|
+
bullet: "-",
|
|
57
|
+
emphasis: "_",
|
|
58
|
+
fences: true,
|
|
59
|
+
handlers: {
|
|
60
|
+
delete: stringifyStrikethrough,
|
|
61
|
+
text(node, _parent, state, info) {
|
|
62
|
+
const value = state.safe(node.value, info);
|
|
63
|
+
if (state.stack.includes("autolink")) return value;
|
|
64
|
+
return value.replace(/^[\u{10000}-\u{10FFFF}]|[\u{10000}-\u{10FFFF}]$/gu, encodeCharacterReference);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
export { normalizeMarkdown as n, markdownStringifyOptions as t };
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=stringify-DdPaV2y4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stringify-DdPaV2y4.js","names":[],"sources":["../../src/markdown/normalize.ts","../../src/markdown/stringify.ts"],"sourcesContent":["import type { Root, RootContent } from \"mdast\";\n\ntype MarkdownNode = Root | RootContent;\ntype FormattingNode = Extract<RootContent, { type: \"strong\" | \"emphasis\" | \"delete\" | \"link\" }>;\n\nconst isFormatting = (node: MarkdownNode): node is FormattingNode => {\n return [\"strong\", \"emphasis\", \"delete\", \"link\"].includes(node.type);\n};\nconst sameFormatting = (first: FormattingNode, second: FormattingNode): boolean => {\n if (first.type !== second.type || JSON.stringify(first.data) !== JSON.stringify(second.data)) {\n return false;\n }\n\n return (\n first.type !== \"link\" ||\n (second.type === \"link\" && first.url === second.url && first.title === second.title)\n );\n};\n// Separate wrappers can produce ambiguous runs such as **first****second**.\n// Join matching neighbors recursively, without changing caller-owned AST nodes.\nconst normalizeMarkdown = <T extends MarkdownNode>(node: T): T => {\n if (!(\"children\" in node)) return node;\n\n const children: MarkdownNode[] = [];\n\n for (const child of node.children) {\n const previous = children[children.length - 1];\n\n if (\n previous &&\n isFormatting(previous) &&\n isFormatting(child) &&\n sameFormatting(previous, child)\n ) {\n previous.children.push(...child.children);\n } else {\n children.push(isFormatting(child) ? { ...child, children: [...child.children] } : child);\n }\n }\n\n return { ...node, children: children.map(normalizeMarkdown) } as T;\n};\n\nexport { normalizeMarkdown };\n","import type { Delete } from \"mdast\";\nimport type { Options } from \"remark-stringify\";\n\ntype StringifyHandler = NonNullable<NonNullable<Options[\"handlers\"]>[\"delete\"]> & {\n peek?: () => string;\n};\n\nconst encodeCharacterReference = (character: string): string => {\n return `&#x${character.codePointAt(0)!.toString(16)};`;\n};\nconst needsBoundaryEncoding = (outside: string, inside: string): boolean => {\n return Boolean(outside) && !/[\\s\\p{P}\\p{S}]/u.test(outside) && /[\\p{P}\\p{S}]/u.test(inside);\n};\nconst stringifyStrikethrough: StringifyHandler = (node: Delete, _parent, state, info) => {\n const tracker = state.createTracker(info);\n const exit = state.enter(\"strikethrough\" as Parameters<typeof state.enter>[0]);\n const before = tracker.move(\"~~\");\n // As with bold and italic, encode edge whitespace so delimiters can open and close.\n const content = state\n .containerPhrasing(node, {\n ...tracker.current(),\n before,\n after: \"~\"\n })\n .replace(/^\\s|\\s$/gu, encodeCharacterReference);\n\n exit();\n\n // Nested marks start/end with punctuation. Encode adjoining letters so the\n // tilde runs still open and close when formatting changes within a word.\n state.attentionEncodeSurroundingInfo = {\n before: needsBoundaryEncoding(info.before.slice(-1), content.slice(0, 1)),\n after: needsBoundaryEncoding(info.after.slice(0, 1), content.slice(-1))\n };\n\n return `~~${content}~~`;\n};\n\nstringifyStrikethrough.peek = () => \"~\";\n\nconst markdownStringifyHandlers: NonNullable<Options[\"handlers\"]> = {\n delete: stringifyStrikethrough,\n text(node, _parent, state, info) {\n const value = state.safe(node.value, info);\n\n // Autolinks treat character references as literal parts of the URL.\n if (state.stack.includes(\"autolink\")) return value;\n\n // The serializer's boundary escaping uses UTF-16 code units. Encode whole\n // supplementary characters at text edges before it can split a surrogate pair.\n return value.replace(\n /^[\\u{10000}-\\u{10FFFF}]|[\\u{10000}-\\u{10FFFF}]$/gu,\n encodeCharacterReference\n );\n }\n};\nconst markdownStringifyOptions: Options = {\n bullet: \"-\",\n emphasis: \"_\",\n fences: true,\n handlers: markdownStringifyHandlers\n};\n\nexport { markdownStringifyOptions };\n"],"mappings":";AAKA,MAAM,gBAAgB,SAA+C;CACnE,OAAO;EAAC;EAAU;EAAY;EAAU;CAAM,CAAC,CAAC,SAAS,KAAK,IAAI;AACpE;AACA,MAAM,kBAAkB,OAAuB,WAAoC;CACjF,IAAI,MAAM,SAAS,OAAO,QAAQ,KAAK,UAAU,MAAM,IAAI,MAAM,KAAK,UAAU,OAAO,IAAI,GACzF,OAAO;CAGT,OACE,MAAM,SAAS,UACd,OAAO,SAAS,UAAU,MAAM,QAAQ,OAAO,OAAO,MAAM,UAAU,OAAO;AAElF;AAGA,MAAM,qBAA6C,SAAe;CAChE,IAAI,EAAE,cAAc,OAAO,OAAO;CAElC,MAAM,WAA2B,CAAC;CAElC,KAAK,MAAM,SAAS,KAAK,UAAU;EACjC,MAAM,WAAW,SAAS,SAAS,SAAS;EAE5C,IACE,YACA,aAAa,QAAQ,KACrB,aAAa,KAAK,KAClB,eAAe,UAAU,KAAK,GAE9B,SAAS,SAAS,KAAK,GAAG,MAAM,QAAQ;OAExC,SAAS,KAAK,aAAa,KAAK,IAAI;GAAE,GAAG;GAAO,UAAU,CAAC,GAAG,MAAM,QAAQ;EAAE,IAAI,KAAK;CAE3F;CAEA,OAAO;EAAE,GAAG;EAAM,UAAU,SAAS,IAAI,iBAAiB;CAAE;AAC9D;;;AClCA,MAAM,4BAA4B,cAA8B;CAC9D,OAAO,MAAM,UAAU,YAAY,CAAC,CAAC,CAAE,SAAS,EAAE,EAAE;AACtD;AACA,MAAM,yBAAyB,SAAiB,WAA4B;CAC1E,OAAO,QAAQ,OAAO,KAAK,CAAC,kBAAkB,KAAK,OAAO,KAAK,gBAAgB,KAAK,MAAM;AAC5F;AACA,MAAM,0BAA4C,MAAc,SAAS,OAAO,SAAS;CACvF,MAAM,UAAU,MAAM,cAAc,IAAI;CACxC,MAAM,OAAO,MAAM,MAAM,eAAoD;CAC7E,MAAM,SAAS,QAAQ,KAAK,IAAI;CAEhC,MAAM,UAAU,MACb,kBAAkB,MAAM;EACvB,GAAG,QAAQ,QAAQ;EACnB;EACA,OAAO;CACT,CAAC,CAAC,CACD,QAAQ,aAAa,wBAAwB;CAEhD,KAAK;CAIL,MAAM,iCAAiC;EACrC,QAAQ,sBAAsB,KAAK,OAAO,MAAM,EAAE,GAAG,QAAQ,MAAM,GAAG,CAAC,CAAC;EACxE,OAAO,sBAAsB,KAAK,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,MAAM,EAAE,CAAC;CACxE;CAEA,OAAO,KAAK,QAAQ;AACtB;AAEA,uBAAuB,aAAa;AAkBpC,MAAM,2BAAoC;CACxC,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,UAAU;EAnBV,QAAQ;EACR,KAAK,MAAM,SAAS,OAAO,MAAM;GAC/B,MAAM,QAAQ,MAAM,KAAK,KAAK,OAAO,IAAI;GAGzC,IAAI,MAAM,MAAM,SAAS,UAAU,GAAG,OAAO;GAI7C,OAAO,MAAM,QACX,qDACA,wBACF;EACF;CAMkC;AACpC"}
|