@inshapardaz/likhari-converters 0.1.11-dev.105 → 0.1.11
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/dist/html/index.d.ts +3 -3
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +9 -7
- package/dist/markdown/index.d.ts +4 -3
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +10 -7
- package/package.json +2 -7
- package/dist/html/parse.d.ts +0 -8
- package/dist/html/parse.d.ts.map +0 -1
- package/dist/html/parse.js +0 -324
- package/dist/html/serialize.d.ts +0 -8
- package/dist/html/serialize.d.ts.map +0 -1
- package/dist/html/serialize.js +0 -143
- package/dist/markdown/mdast.d.ts +0 -22
- package/dist/markdown/mdast.d.ts.map +0 -1
- package/dist/markdown/mdast.js +0 -3
- package/dist/markdown/parse.d.ts +0 -4
- package/dist/markdown/parse.d.ts.map +0 -1
- package/dist/markdown/parse.js +0 -283
- package/dist/markdown/serialize.d.ts +0 -9
- package/dist/markdown/serialize.d.ts.map +0 -1
- package/dist/markdown/serialize.js +0 -289
- package/dist/shared/serialized.d.ts +0 -31
- package/dist/shared/serialized.d.ts.map +0 -1
- package/dist/shared/serialized.js +0 -32
package/dist/html/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { FormatConverter } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* HTML export
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Placeholder — HTML import/export is Phase 2 work (lexical-editor-spec.md
|
|
4
|
+
* §13). Registered now so FormatRegistry's shape is stable and callers get a
|
|
5
|
+
* clear error instead of a missing format.
|
|
6
6
|
*/
|
|
7
7
|
export declare const htmlConverter: FormatConverter;
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/html/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/html/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/html/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,eAQ3B,CAAC"}
|
package/dist/html/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { serializeHtml } from './serialize.js';
|
|
2
|
-
import { parseHtml } from './parse.js';
|
|
3
1
|
/**
|
|
4
|
-
* HTML export
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Placeholder — HTML import/export is Phase 2 work (lexical-editor-spec.md
|
|
3
|
+
* §13). Registered now so FormatRegistry's shape is stable and callers get a
|
|
4
|
+
* clear error instead of a missing format.
|
|
7
5
|
*/
|
|
8
6
|
export const htmlConverter = {
|
|
9
7
|
id: 'html',
|
|
10
|
-
serialize
|
|
11
|
-
|
|
8
|
+
serialize() {
|
|
9
|
+
throw new Error('@inshapardaz/likhari-converters: HTML export is not implemented yet (Phase 2, see docs/lexical-editor-spec.md §13).');
|
|
10
|
+
},
|
|
11
|
+
parse() {
|
|
12
|
+
throw new Error('@inshapardaz/likhari-converters: HTML import is not implemented yet (Phase 2, see docs/lexical-editor-spec.md §13).');
|
|
13
|
+
},
|
|
12
14
|
};
|
package/dist/markdown/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { FormatConverter } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Placeholder — the extended Markdown dialect (lexical-editor-spec.md §2.1)
|
|
4
|
+
* is Phase 2 work (§13) and needs its own spec doc + sign-off before the
|
|
5
|
+
* converter is built against it. Registered now so FormatRegistry's shape
|
|
6
|
+
* is stable and callers get a clear error instead of a missing format.
|
|
6
7
|
*/
|
|
7
8
|
export declare const markdownConverter: FormatConverter;
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdown/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdown/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,eAY/B,CAAC"}
|
package/dist/markdown/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { serializeMarkdown } from './serialize.js';
|
|
2
|
-
import { parseMarkdown } from './parse.js';
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Placeholder — the extended Markdown dialect (lexical-editor-spec.md §2.1)
|
|
3
|
+
* is Phase 2 work (§13) and needs its own spec doc + sign-off before the
|
|
4
|
+
* converter is built against it. Registered now so FormatRegistry's shape
|
|
5
|
+
* is stable and callers get a clear error instead of a missing format.
|
|
7
6
|
*/
|
|
8
7
|
export const markdownConverter = {
|
|
9
8
|
id: 'markdown',
|
|
10
|
-
serialize
|
|
11
|
-
|
|
9
|
+
serialize() {
|
|
10
|
+
throw new Error('@inshapardaz/likhari-converters: the extended Markdown dialect is not implemented yet (Phase 2, see docs/lexical-editor-spec.md §2.1 and §13).');
|
|
11
|
+
},
|
|
12
|
+
parse() {
|
|
13
|
+
throw new Error('@inshapardaz/likhari-converters: the extended Markdown dialect is not implemented yet (Phase 2, see docs/lexical-editor-spec.md §2.1 and §13).');
|
|
14
|
+
},
|
|
12
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inshapardaz/likhari-converters",
|
|
3
|
-
"version": "0.1.11
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Format transformers (Markdown/HTML/plain-text/Lexical JSON) against the canonical Lexical EditorState, isolated from UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,11 +30,6 @@
|
|
|
30
30
|
"prepublishOnly": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"lexical": "^0.20.0"
|
|
34
|
-
"remark-directive": "^4.0.0",
|
|
35
|
-
"remark-gfm": "^4.0.1",
|
|
36
|
-
"remark-parse": "^11.0.0",
|
|
37
|
-
"remark-stringify": "^11.0.0",
|
|
38
|
-
"unified": "^11.0.5"
|
|
33
|
+
"lexical": "^0.20.0"
|
|
39
34
|
}
|
|
40
35
|
}
|
package/dist/html/parse.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { SerializedEditorState } from 'lexical';
|
|
2
|
-
import type { ConverterContext } from '../types.js';
|
|
3
|
-
/** Link hrefs with an executable scheme are dropped on import. */
|
|
4
|
-
export declare function safeUrl(url: string): string | null;
|
|
5
|
-
/** Image sources: only http(s), relative and base64 image data URIs. */
|
|
6
|
-
export declare function safeImageSrc(src: string): string | null;
|
|
7
|
-
export declare function parseHtml(input: string, _ctx?: ConverterContext): SerializedEditorState;
|
|
8
|
-
//# sourceMappingURL=parse.d.ts.map
|
package/dist/html/parse.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/html/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAiCjD,kEAAkE;AAClE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIlD;AAED,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKvD;AAiRD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAMvF"}
|
package/dist/html/parse.js
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, INDENT_PX, elementBase, makeState, paragraph, textNode, } from '../shared/serialized.js';
|
|
2
|
-
const ALIGNMENTS = new Set(['left', 'right', 'center', 'justify', 'start', 'end']);
|
|
3
|
-
const TAG_FORMATS = {
|
|
4
|
-
b: FORMAT_BOLD, strong: FORMAT_BOLD,
|
|
5
|
-
i: FORMAT_ITALIC, em: FORMAT_ITALIC,
|
|
6
|
-
u: FORMAT_UNDERLINE, ins: FORMAT_UNDERLINE,
|
|
7
|
-
s: FORMAT_STRIKETHROUGH, strike: FORMAT_STRIKETHROUGH, del: FORMAT_STRIKETHROUGH,
|
|
8
|
-
code: FORMAT_CODE, sub: FORMAT_SUBSCRIPT, sup: FORMAT_SUPERSCRIPT, mark: FORMAT_HIGHLIGHT,
|
|
9
|
-
};
|
|
10
|
-
/** Dropped with their content: never useful in a document. */
|
|
11
|
-
const SKIP_TAGS = new Set(['script', 'style', 'template', 'head', 'title', 'meta', 'link', 'noscript', 'iframe', 'object', 'embed']);
|
|
12
|
-
/** Transparent containers whose children are processed as if in place. */
|
|
13
|
-
const BLOCK_CONTAINERS = new Set(['div', 'section', 'article', 'main', 'header', 'footer', 'aside', 'nav', 'body', 'html', 'figure', 'tbody', 'thead', 'tfoot', 'details', 'summary', 'span-block']);
|
|
14
|
-
const BLOCK_TAGS = new Set([
|
|
15
|
-
'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th',
|
|
16
|
-
'hr', 'pre', 'img', 'figcaption', 'dl', 'dt', 'dd', ...BLOCK_CONTAINERS,
|
|
17
|
-
]);
|
|
18
|
-
/** Link hrefs with an executable scheme are dropped on import. */
|
|
19
|
-
export function safeUrl(url) {
|
|
20
|
-
const trimmed = url.trim();
|
|
21
|
-
if (/^(javascript|vbscript|data:text\/html)/i.test(trimmed.replace(/[\u0000- ]/g, '')))
|
|
22
|
-
return null;
|
|
23
|
-
return trimmed;
|
|
24
|
-
}
|
|
25
|
-
/** Image sources: only http(s), relative and base64 image data URIs. */
|
|
26
|
-
export function safeImageSrc(src) {
|
|
27
|
-
const trimmed = src.trim();
|
|
28
|
-
if (!trimmed)
|
|
29
|
-
return null;
|
|
30
|
-
if (/^data:/i.test(trimmed))
|
|
31
|
-
return /^data:image\/(png|jpe?g|gif|webp|svg\+xml|avif|bmp);/i.test(trimmed) ? trimmed : null;
|
|
32
|
-
return safeUrl(trimmed);
|
|
33
|
-
}
|
|
34
|
-
function blockExtras(el) {
|
|
35
|
-
const extra = {};
|
|
36
|
-
const dir = el.getAttribute('dir');
|
|
37
|
-
if (dir === 'rtl' || dir === 'ltr')
|
|
38
|
-
extra.direction = dir;
|
|
39
|
-
const align = el.style?.textAlign;
|
|
40
|
-
if (align && ALIGNMENTS.has(align))
|
|
41
|
-
extra.format = align;
|
|
42
|
-
const padding = el.style?.paddingInlineStart || el.style?.paddingLeft || el.style?.paddingRight;
|
|
43
|
-
const px = padding && /^([\d.]+)px$/.exec(padding);
|
|
44
|
-
if (px)
|
|
45
|
-
extra.indent = Math.min(Math.round(Number(px[1]) / INDENT_PX), 10);
|
|
46
|
-
return extra;
|
|
47
|
-
}
|
|
48
|
-
function inlineStyleOf(el) {
|
|
49
|
-
// Only carry presentation the editor itself produces (font, size, color).
|
|
50
|
-
const keep = [];
|
|
51
|
-
for (const prop of ['font-family', 'font-size', 'color', 'background-color']) {
|
|
52
|
-
const value = el.style?.getPropertyValue(prop);
|
|
53
|
-
if (value)
|
|
54
|
-
keep.push(`${prop}: ${value}`);
|
|
55
|
-
}
|
|
56
|
-
return keep.join('; ');
|
|
57
|
-
}
|
|
58
|
-
function formatsFromElement(el, base) {
|
|
59
|
-
let format = base.format | (TAG_FORMATS[el.tagName.toLowerCase()] ?? 0);
|
|
60
|
-
const weight = el.style?.fontWeight;
|
|
61
|
-
if (weight === 'bold' || Number(weight) >= 600)
|
|
62
|
-
format |= FORMAT_BOLD;
|
|
63
|
-
if (el.style?.fontStyle === 'italic')
|
|
64
|
-
format |= FORMAT_ITALIC;
|
|
65
|
-
const deco = el.style?.textDecoration || el.style?.getPropertyValue('text-decoration-line') || '';
|
|
66
|
-
if (deco.includes('underline'))
|
|
67
|
-
format |= FORMAT_UNDERLINE;
|
|
68
|
-
if (deco.includes('line-through'))
|
|
69
|
-
format |= FORMAT_STRIKETHROUGH;
|
|
70
|
-
const own = inlineStyleOf(el);
|
|
71
|
-
return { format, style: [base.style, own].filter(Boolean).join('; ') };
|
|
72
|
-
}
|
|
73
|
-
/** Collapse HTML whitespace like a browser does inside flowing text. */
|
|
74
|
-
function collapse(text) {
|
|
75
|
-
return text.replace(/[ \t\r\n\f]+/g, ' ');
|
|
76
|
-
}
|
|
77
|
-
function collectInline(node, state, out, preserveWs) {
|
|
78
|
-
if (node.nodeType === 3) {
|
|
79
|
-
const raw = node.nodeValue ?? '';
|
|
80
|
-
const text = preserveWs ? raw : collapse(raw);
|
|
81
|
-
if (text)
|
|
82
|
-
out.push(textNode(text, state.format, state.style));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (node.nodeType !== 1)
|
|
86
|
-
return;
|
|
87
|
-
const el = node;
|
|
88
|
-
const tag = el.tagName.toLowerCase();
|
|
89
|
-
if (SKIP_TAGS.has(tag))
|
|
90
|
-
return;
|
|
91
|
-
if (tag === 'br') {
|
|
92
|
-
out.push({ type: 'linebreak', version: 1 });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (tag === 'a') {
|
|
96
|
-
const url = safeUrl(el.getAttribute('href') ?? '');
|
|
97
|
-
const children = [];
|
|
98
|
-
el.childNodes.forEach((child) => collectInline(child, formatsFromElement(el, state), children, preserveWs));
|
|
99
|
-
if (url === null || url === '') {
|
|
100
|
-
out.push(...children);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
out.push(elementBase('link', children, {
|
|
104
|
-
url,
|
|
105
|
-
rel: el.getAttribute('rel'),
|
|
106
|
-
target: el.getAttribute('target'),
|
|
107
|
-
title: el.getAttribute('title'),
|
|
108
|
-
}));
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const next = formatsFromElement(el, state);
|
|
112
|
-
el.childNodes.forEach((child) => collectInline(child, next, out, preserveWs));
|
|
113
|
-
}
|
|
114
|
-
/** Trim the leading/trailing space a block's source whitespace left behind. */
|
|
115
|
-
function trimInline(nodes) {
|
|
116
|
-
const first = nodes[0];
|
|
117
|
-
if (first && first.type === 'text')
|
|
118
|
-
first.text = String(first.text).replace(/^ +/, '');
|
|
119
|
-
const last = nodes[nodes.length - 1];
|
|
120
|
-
if (last && last.type === 'text')
|
|
121
|
-
last.text = String(last.text).replace(/ +$/, '');
|
|
122
|
-
return nodes.filter((n) => n.type !== 'text' || n.text !== '');
|
|
123
|
-
}
|
|
124
|
-
function inlineChildrenOf(el, preserveWs = false) {
|
|
125
|
-
const out = [];
|
|
126
|
-
el.childNodes.forEach((child) => collectInline(child, { format: 0, style: '' }, out, preserveWs));
|
|
127
|
-
return trimInline(out);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Children of a "flow" container (body, div, td, li...): runs of inline
|
|
131
|
-
* content become paragraphs, block children become blocks.
|
|
132
|
-
*/
|
|
133
|
-
function flowBlocks(el) {
|
|
134
|
-
const blocks = [];
|
|
135
|
-
let run = [];
|
|
136
|
-
const flush = () => {
|
|
137
|
-
if (run.length === 0)
|
|
138
|
-
return;
|
|
139
|
-
const out = [];
|
|
140
|
-
for (const n of run)
|
|
141
|
-
collectInline(n, { format: 0, style: '' }, out, false);
|
|
142
|
-
const children = trimInline(out);
|
|
143
|
-
if (children.length > 0)
|
|
144
|
-
blocks.push(paragraph(children));
|
|
145
|
-
run = [];
|
|
146
|
-
};
|
|
147
|
-
el.childNodes.forEach((child) => {
|
|
148
|
-
if (child.nodeType === 1) {
|
|
149
|
-
const tag = child.tagName.toLowerCase();
|
|
150
|
-
if (SKIP_TAGS.has(tag))
|
|
151
|
-
return;
|
|
152
|
-
if (BLOCK_TAGS.has(tag)) {
|
|
153
|
-
flush();
|
|
154
|
-
blocks.push(...convertBlock(child));
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
run.push(child);
|
|
159
|
-
});
|
|
160
|
-
flush();
|
|
161
|
-
return blocks;
|
|
162
|
-
}
|
|
163
|
-
function convertList(el) {
|
|
164
|
-
const tag = el.tagName.toLowerCase();
|
|
165
|
-
const isCheck = el.getAttribute('data-list-type') === 'check';
|
|
166
|
-
const listType = isCheck ? 'check' : tag === 'ol' ? 'number' : 'bullet';
|
|
167
|
-
const start = tag === 'ol' ? Number(el.getAttribute('start')) || 1 : 1;
|
|
168
|
-
const items = [];
|
|
169
|
-
el.childNodes.forEach((child) => {
|
|
170
|
-
if (child.nodeType !== 1)
|
|
171
|
-
return;
|
|
172
|
-
const li = child;
|
|
173
|
-
if (li.tagName.toLowerCase() !== 'li')
|
|
174
|
-
return;
|
|
175
|
-
items.push(...convertListItem(li, isCheck));
|
|
176
|
-
});
|
|
177
|
-
return elementBase('list', items, { listType, start, tag: tag === 'ol' ? 'ol' : 'ul', ...blockExtras(el) });
|
|
178
|
-
}
|
|
179
|
-
/** One <li> may yield several listitems (inline content, then nested lists). */
|
|
180
|
-
function convertListItem(li, isCheck) {
|
|
181
|
-
const items = [];
|
|
182
|
-
let run = [];
|
|
183
|
-
const checked = isCheck ? li.getAttribute('data-checked') === 'true' : undefined;
|
|
184
|
-
const makeItem = (children) => elementBase('listitem', children, { value: 1, ...(isCheck ? { checked: !!checked } : {}), ...blockExtras(li) });
|
|
185
|
-
const flush = () => {
|
|
186
|
-
const out = [];
|
|
187
|
-
for (const n of run)
|
|
188
|
-
collectInline(n, { format: 0, style: '' }, out, false);
|
|
189
|
-
const children = trimInline(out);
|
|
190
|
-
if (children.length > 0)
|
|
191
|
-
items.push(makeItem(children));
|
|
192
|
-
run = [];
|
|
193
|
-
};
|
|
194
|
-
li.childNodes.forEach((child) => {
|
|
195
|
-
if (child.nodeType === 1) {
|
|
196
|
-
const tag = child.tagName.toLowerCase();
|
|
197
|
-
if (tag === 'ul' || tag === 'ol') {
|
|
198
|
-
flush();
|
|
199
|
-
items.push(makeItem([convertList(child)]));
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (tag === 'p') {
|
|
203
|
-
// <li><p>text</p></li>: treat the paragraph as the item's own text.
|
|
204
|
-
run.push(...Array.from(child.childNodes));
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
run.push(child);
|
|
209
|
-
});
|
|
210
|
-
flush();
|
|
211
|
-
return items;
|
|
212
|
-
}
|
|
213
|
-
function convertTable(el) {
|
|
214
|
-
const rows = [];
|
|
215
|
-
el.querySelectorAll('tr').forEach((tr) => {
|
|
216
|
-
// Skip rows of tables nested inside a cell.
|
|
217
|
-
if (tr.closest('table') !== el)
|
|
218
|
-
return;
|
|
219
|
-
const cells = [];
|
|
220
|
-
Array.from(tr.children).forEach((cell) => {
|
|
221
|
-
const tag = cell.tagName.toLowerCase();
|
|
222
|
-
if (tag !== 'td' && tag !== 'th')
|
|
223
|
-
return;
|
|
224
|
-
let children = flowBlocks(cell);
|
|
225
|
-
if (children.length === 0)
|
|
226
|
-
children = [paragraph()];
|
|
227
|
-
cells.push(elementBase('tablecell', children, {
|
|
228
|
-
headerState: tag === 'th' ? 1 : 0,
|
|
229
|
-
colSpan: Number(cell.getAttribute('colspan')) || 1,
|
|
230
|
-
rowSpan: Number(cell.getAttribute('rowspan')) || 1,
|
|
231
|
-
backgroundColor: null,
|
|
232
|
-
width: null,
|
|
233
|
-
}));
|
|
234
|
-
});
|
|
235
|
-
if (cells.length > 0)
|
|
236
|
-
rows.push(elementBase('tablerow', cells, { height: null }));
|
|
237
|
-
});
|
|
238
|
-
return elementBase('table', rows);
|
|
239
|
-
}
|
|
240
|
-
function convertImage(img, caption) {
|
|
241
|
-
const src = safeImageSrc(img.getAttribute('src') ?? '');
|
|
242
|
-
if (!src)
|
|
243
|
-
return null;
|
|
244
|
-
const dim = (name) => {
|
|
245
|
-
const n = Number(img.getAttribute(name));
|
|
246
|
-
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
247
|
-
};
|
|
248
|
-
const declared = img.getAttribute('data-link-type');
|
|
249
|
-
return {
|
|
250
|
-
type: 'image',
|
|
251
|
-
version: 1,
|
|
252
|
-
src,
|
|
253
|
-
altText: img.getAttribute('alt') ?? '',
|
|
254
|
-
caption,
|
|
255
|
-
linkType: declared === 'embedded' || /^data:/i.test(src) ? 'embedded' : 'linked',
|
|
256
|
-
width: dim('width'),
|
|
257
|
-
height: dim('height'),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
function convertBlock(el) {
|
|
261
|
-
const tag = el.tagName.toLowerCase();
|
|
262
|
-
switch (tag) {
|
|
263
|
-
case 'p':
|
|
264
|
-
return [paragraph(inlineChildrenOf(el), blockExtras(el))];
|
|
265
|
-
case 'h1':
|
|
266
|
-
case 'h2':
|
|
267
|
-
case 'h3':
|
|
268
|
-
case 'h4':
|
|
269
|
-
case 'h5':
|
|
270
|
-
case 'h6':
|
|
271
|
-
return [elementBase('heading', inlineChildrenOf(el), { tag, ...blockExtras(el) })];
|
|
272
|
-
case 'blockquote': {
|
|
273
|
-
const hasBlocks = Array.from(el.children).some((c) => BLOCK_TAGS.has(c.tagName.toLowerCase()));
|
|
274
|
-
if (!hasBlocks)
|
|
275
|
-
return [elementBase('quote', inlineChildrenOf(el), blockExtras(el))];
|
|
276
|
-
// Quote holds inline content only: flatten block children, one quote each.
|
|
277
|
-
return flowBlocks(el).map((block) => (block.type === 'paragraph' ? elementBase('quote', block.children ?? [], blockExtras(el)) : block));
|
|
278
|
-
}
|
|
279
|
-
case 'pre': {
|
|
280
|
-
const text = (el.textContent ?? '').replace(/\n$/, '');
|
|
281
|
-
return [paragraph(text ? [textNode(text, FORMAT_CODE)] : [])];
|
|
282
|
-
}
|
|
283
|
-
case 'ul':
|
|
284
|
-
case 'ol':
|
|
285
|
-
return [convertList(el)];
|
|
286
|
-
case 'li':
|
|
287
|
-
return convertListItem(el, false).length > 0 ? [elementBase('list', convertListItem(el, false), { listType: 'bullet', start: 1, tag: 'ul' })] : [];
|
|
288
|
-
case 'table':
|
|
289
|
-
return [convertTable(el)];
|
|
290
|
-
case 'hr':
|
|
291
|
-
return [{ type: 'horizontalrule', version: 1 }];
|
|
292
|
-
case 'img': {
|
|
293
|
-
const image = convertImage(el, null);
|
|
294
|
-
return image ? [image] : [];
|
|
295
|
-
}
|
|
296
|
-
case 'figure': {
|
|
297
|
-
const img = el.querySelector('img');
|
|
298
|
-
const figcaption = el.querySelector('figcaption');
|
|
299
|
-
if (!img)
|
|
300
|
-
return flowBlocks(el);
|
|
301
|
-
const caption = figcaption ? (figcaption.textContent ?? '').trim() || null : null;
|
|
302
|
-
const image = convertImage(img, caption);
|
|
303
|
-
return image ? [image] : [];
|
|
304
|
-
}
|
|
305
|
-
case 'div':
|
|
306
|
-
if (el.hasAttribute('data-likhari-page-break'))
|
|
307
|
-
return [{ type: 'page-break', version: 1 }];
|
|
308
|
-
return flowBlocks(el);
|
|
309
|
-
case 'figcaption':
|
|
310
|
-
return [];
|
|
311
|
-
case 'dt':
|
|
312
|
-
case 'dd':
|
|
313
|
-
return [paragraph(inlineChildrenOf(el), blockExtras(el))];
|
|
314
|
-
default:
|
|
315
|
-
return flowBlocks(el);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
export function parseHtml(input, _ctx) {
|
|
319
|
-
if (typeof DOMParser === 'undefined') {
|
|
320
|
-
throw new Error('@inshapardaz/likhari-converters: HTML import needs a DOM (DOMParser); run it in a browser or jsdom.');
|
|
321
|
-
}
|
|
322
|
-
const doc = new DOMParser().parseFromString(input, 'text/html');
|
|
323
|
-
return makeState(flowBlocks(doc.body));
|
|
324
|
-
}
|
package/dist/html/serialize.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { SerializedEditorState } from 'lexical';
|
|
2
|
-
import type { ConverterContext } from '../types.js';
|
|
3
|
-
import { type SNode } from '../shared/serialized.js';
|
|
4
|
-
export declare function escapeHtml(value: string): string;
|
|
5
|
-
export declare function escapeAttr(value: string): string;
|
|
6
|
-
export declare function nodeToHtml(node: SNode, ctx?: ConverterContext): string;
|
|
7
|
-
export declare function serializeHtml(state: SerializedEditorState, ctx?: ConverterContext): string;
|
|
8
|
-
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/html/serialize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAE4E,KAAK,KAAK,EAC5F,MAAM,sBAAsB,CAAC;AAE9B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAgGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAuCtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAE1F"}
|
package/dist/html/serialize.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, INDENT_PX, rootChildren, } from '../shared/serialized.js';
|
|
2
|
-
export function escapeHtml(value) {
|
|
3
|
-
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
4
|
-
}
|
|
5
|
-
export function escapeAttr(value) {
|
|
6
|
-
return escapeHtml(value).replace(/"/g, '"');
|
|
7
|
-
}
|
|
8
|
-
/** Wrapping order matters for round-trips: outermost first. */
|
|
9
|
-
const FORMAT_TAGS = [
|
|
10
|
-
[FORMAT_CODE, 'code'],
|
|
11
|
-
[FORMAT_BOLD, 'strong'],
|
|
12
|
-
[FORMAT_ITALIC, 'em'],
|
|
13
|
-
[FORMAT_UNDERLINE, 'u'],
|
|
14
|
-
[FORMAT_STRIKETHROUGH, 's'],
|
|
15
|
-
[FORMAT_SUBSCRIPT, 'sub'],
|
|
16
|
-
[FORMAT_SUPERSCRIPT, 'sup'],
|
|
17
|
-
[FORMAT_HIGHLIGHT, 'mark'],
|
|
18
|
-
];
|
|
19
|
-
const ALIGNMENTS = new Set(['left', 'right', 'center', 'justify', 'start', 'end']);
|
|
20
|
-
function textToHtml(node) {
|
|
21
|
-
const text = String(node.text ?? '');
|
|
22
|
-
let out = escapeHtml(text).replace(/\t/g, '	');
|
|
23
|
-
const format = Number(node.format ?? 0);
|
|
24
|
-
const style = String(node.style ?? '').trim();
|
|
25
|
-
if (style)
|
|
26
|
-
out = `<span style="${escapeAttr(style)}">${out}</span>`;
|
|
27
|
-
for (const [bit, tag] of FORMAT_TAGS) {
|
|
28
|
-
if (format & bit)
|
|
29
|
-
out = `<${tag}>${out}</${tag}>`;
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
function inlineChildren(node, ctx) {
|
|
34
|
-
return (node.children ?? []).map((child) => nodeToHtml(child, ctx)).join('');
|
|
35
|
-
}
|
|
36
|
-
/** `dir`, `style` (alignment, indent) and any extra attributes for a block. */
|
|
37
|
-
function blockAttrs(node, extra = '') {
|
|
38
|
-
let attrs = '';
|
|
39
|
-
if (node.direction === 'rtl' || node.direction === 'ltr')
|
|
40
|
-
attrs += ` dir="${node.direction}"`;
|
|
41
|
-
const styles = [];
|
|
42
|
-
const format = String(node.format ?? '');
|
|
43
|
-
if (ALIGNMENTS.has(format))
|
|
44
|
-
styles.push(`text-align: ${format}`);
|
|
45
|
-
const indent = Number(node.indent ?? 0);
|
|
46
|
-
if (indent > 0)
|
|
47
|
-
styles.push(`padding-inline-start: ${indent * INDENT_PX}px`);
|
|
48
|
-
if (styles.length > 0)
|
|
49
|
-
attrs += ` style="${styles.join('; ')}"`;
|
|
50
|
-
return attrs + extra;
|
|
51
|
-
}
|
|
52
|
-
function listToHtml(node, ctx) {
|
|
53
|
-
const listType = String(node.listType ?? 'bullet');
|
|
54
|
-
const tag = listType === 'number' ? 'ol' : 'ul';
|
|
55
|
-
let extra = '';
|
|
56
|
-
if (tag === 'ol' && Number(node.start) > 1)
|
|
57
|
-
extra += ` start="${Number(node.start)}"`;
|
|
58
|
-
if (listType === 'check')
|
|
59
|
-
extra += ' data-list-type="check"';
|
|
60
|
-
const items = (node.children ?? []).map((item) => listItemToHtml(item, listType, ctx)).join('');
|
|
61
|
-
return `<${tag}${blockAttrs(node, extra)}>${items}</${tag}>`;
|
|
62
|
-
}
|
|
63
|
-
function listItemToHtml(item, listType, ctx) {
|
|
64
|
-
const children = item.children ?? [];
|
|
65
|
-
// Lexical nests a sublist as a listitem whose only child is the list.
|
|
66
|
-
if (children.length === 1 && children[0].type === 'list') {
|
|
67
|
-
return `<li style="list-style-type: none">${listToHtml(children[0], ctx)}</li>`;
|
|
68
|
-
}
|
|
69
|
-
const checked = listType === 'check' ? ` data-checked="${item.checked ? 'true' : 'false'}"` : '';
|
|
70
|
-
return `<li${blockAttrs(item, checked)}>${inlineChildren(item, ctx)}</li>`;
|
|
71
|
-
}
|
|
72
|
-
function tableToHtml(node, ctx) {
|
|
73
|
-
const rows = (node.children ?? [])
|
|
74
|
-
.map((row) => {
|
|
75
|
-
const cells = (row.children ?? [])
|
|
76
|
-
.map((cell) => {
|
|
77
|
-
const header = Number(cell.headerState ?? 0) !== 0;
|
|
78
|
-
const tag = header ? 'th' : 'td';
|
|
79
|
-
const span = (Number(cell.colSpan) > 1 ? ` colspan="${Number(cell.colSpan)}"` : '') +
|
|
80
|
-
(Number(cell.rowSpan) > 1 ? ` rowspan="${Number(cell.rowSpan)}"` : '');
|
|
81
|
-
const content = (cell.children ?? []).map((child) => nodeToHtml(child, ctx)).join('');
|
|
82
|
-
return `<${tag}${span}>${content}</${tag}>`;
|
|
83
|
-
})
|
|
84
|
-
.join('');
|
|
85
|
-
return `<tr>${cells}</tr>`;
|
|
86
|
-
})
|
|
87
|
-
.join('');
|
|
88
|
-
return `<table><tbody>${rows}</tbody></table>`;
|
|
89
|
-
}
|
|
90
|
-
function imageToHtml(node, ctx) {
|
|
91
|
-
const rawSrc = String(node.src ?? '');
|
|
92
|
-
const src = ctx?.resolveImageUrl ? ctx.resolveImageUrl(rawSrc) : rawSrc;
|
|
93
|
-
let img = `<img src="${escapeAttr(src)}" alt="${escapeAttr(String(node.altText ?? ''))}"`;
|
|
94
|
-
if (node.width)
|
|
95
|
-
img += ` width="${Number(node.width)}"`;
|
|
96
|
-
if (node.height)
|
|
97
|
-
img += ` height="${Number(node.height)}"`;
|
|
98
|
-
img += ` data-link-type="${node.linkType === 'embedded' ? 'embedded' : 'linked'}">`;
|
|
99
|
-
const caption = typeof node.caption === 'string' && node.caption !== '' ? `<figcaption>${escapeHtml(node.caption)}</figcaption>` : '';
|
|
100
|
-
return `<figure>${img}${caption}</figure>`;
|
|
101
|
-
}
|
|
102
|
-
export function nodeToHtml(node, ctx) {
|
|
103
|
-
switch (node.type) {
|
|
104
|
-
case 'text':
|
|
105
|
-
return textToHtml(node);
|
|
106
|
-
case 'linebreak':
|
|
107
|
-
return '<br>';
|
|
108
|
-
case 'tab':
|
|
109
|
-
return '	';
|
|
110
|
-
case 'paragraph':
|
|
111
|
-
return `<p${blockAttrs(node)}>${inlineChildren(node, ctx)}</p>`;
|
|
112
|
-
case 'heading': {
|
|
113
|
-
const tag = /^h[1-6]$/.test(String(node.tag)) ? String(node.tag) : 'h2';
|
|
114
|
-
return `<${tag}${blockAttrs(node)}>${inlineChildren(node, ctx)}</${tag}>`;
|
|
115
|
-
}
|
|
116
|
-
case 'quote':
|
|
117
|
-
return `<blockquote${blockAttrs(node)}>${inlineChildren(node, ctx)}</blockquote>`;
|
|
118
|
-
case 'list':
|
|
119
|
-
return listToHtml(node, ctx);
|
|
120
|
-
case 'link':
|
|
121
|
-
case 'autolink': {
|
|
122
|
-
const attrs = ` href="${escapeAttr(String(node.url ?? ''))}"` +
|
|
123
|
-
(node.rel ? ` rel="${escapeAttr(String(node.rel))}"` : '') +
|
|
124
|
-
(node.target ? ` target="${escapeAttr(String(node.target))}"` : '') +
|
|
125
|
-
(node.title ? ` title="${escapeAttr(String(node.title))}"` : '');
|
|
126
|
-
return `<a${attrs}>${inlineChildren(node, ctx)}</a>`;
|
|
127
|
-
}
|
|
128
|
-
case 'horizontalrule':
|
|
129
|
-
return '<hr>';
|
|
130
|
-
case 'page-break':
|
|
131
|
-
return '<div data-likhari-page-break style="page-break-after: always"></div>';
|
|
132
|
-
case 'image':
|
|
133
|
-
return imageToHtml(node, ctx);
|
|
134
|
-
case 'table':
|
|
135
|
-
return tableToHtml(node, ctx);
|
|
136
|
-
default:
|
|
137
|
-
// Unknown node (e.g. a future feature): keep its content, drop the wrapper.
|
|
138
|
-
return inlineChildren(node, ctx);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
export function serializeHtml(state, ctx) {
|
|
142
|
-
return rootChildren(state).map((node) => nodeToHtml(node, ctx)).join('\n');
|
|
143
|
-
}
|
package/dist/markdown/mdast.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/** Minimal structural view of an mdast node: the dialect's tree is composed of
|
|
2
|
-
* several plugin-defined node types, so a loose type is more honest than a union. */
|
|
3
|
-
export interface MdNode {
|
|
4
|
-
type: string;
|
|
5
|
-
children?: MdNode[];
|
|
6
|
-
value?: string;
|
|
7
|
-
depth?: number;
|
|
8
|
-
ordered?: boolean;
|
|
9
|
-
start?: number | null;
|
|
10
|
-
spread?: boolean;
|
|
11
|
-
checked?: boolean | null;
|
|
12
|
-
url?: string;
|
|
13
|
-
title?: string | null;
|
|
14
|
-
alt?: string | null;
|
|
15
|
-
lang?: string | null;
|
|
16
|
-
name?: string;
|
|
17
|
-
attributes?: Record<string, string | null | undefined> | null;
|
|
18
|
-
align?: Array<'left' | 'right' | 'center' | null> | null;
|
|
19
|
-
}
|
|
20
|
-
export type Attrs = Record<string, string>;
|
|
21
|
-
export declare function directive(type: 'textDirective' | 'leafDirective' | 'containerDirective', name: string, attributes: Attrs, children?: MdNode[]): MdNode;
|
|
22
|
-
//# sourceMappingURL=mdast.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mdast.d.ts","sourceRoot":"","sources":["../../src/markdown/mdast.ts"],"names":[],"mappings":"AAAA;qFACqF;AACrF,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;IAC9D,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE3C,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe,GAAG,oBAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,GAAE,MAAM,EAAO,GAAG,MAAM,CAE1J"}
|
package/dist/markdown/mdast.js
DELETED
package/dist/markdown/parse.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/markdown/parse.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAoRjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAI3F"}
|
package/dist/markdown/parse.js
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { unified } from 'unified';
|
|
2
|
-
import remarkParse from 'remark-parse';
|
|
3
|
-
import remarkGfm from 'remark-gfm';
|
|
4
|
-
import remarkDirective from 'remark-directive';
|
|
5
|
-
import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, elementBase, makeState, paragraph, textNode, } from '../shared/serialized.js';
|
|
6
|
-
import { safeImageSrc, safeUrl } from '../html/parse.js';
|
|
7
|
-
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkDirective);
|
|
8
|
-
const ALIGNMENTS = new Set(['left', 'right', 'center', 'justify', 'start', 'end']);
|
|
9
|
-
const DIRECTIVE_FORMATS = {
|
|
10
|
-
u: FORMAT_UNDERLINE,
|
|
11
|
-
sup: FORMAT_SUPERSCRIPT,
|
|
12
|
-
sub: FORMAT_SUBSCRIPT,
|
|
13
|
-
mark: FORMAT_HIGHLIGHT,
|
|
14
|
-
};
|
|
15
|
-
/** Inverse of the serializer's `styleToAttrs`. */
|
|
16
|
-
function attrsToStyle(attrs) {
|
|
17
|
-
const parts = [];
|
|
18
|
-
const map = [['font', 'font-family'], ['size', 'font-size'], ['color', 'color'], ['bg', 'background-color']];
|
|
19
|
-
for (const [attr, prop] of map) {
|
|
20
|
-
const value = attrs[attr];
|
|
21
|
-
// A value that could break out of the declaration is dropped.
|
|
22
|
-
if (value && !/[;{}<>]/.test(value))
|
|
23
|
-
parts.push(`${prop}: ${value}`);
|
|
24
|
-
}
|
|
25
|
-
if (attrs.style && !/[{}<>]/.test(attrs.style))
|
|
26
|
-
parts.push(attrs.style);
|
|
27
|
-
return parts.join('; ');
|
|
28
|
-
}
|
|
29
|
-
function inline(nodes, state, out) {
|
|
30
|
-
for (const node of nodes) {
|
|
31
|
-
switch (node.type) {
|
|
32
|
-
case 'text':
|
|
33
|
-
if (node.value)
|
|
34
|
-
out.push(textNode(node.value, state.format, state.style));
|
|
35
|
-
break;
|
|
36
|
-
case 'strong':
|
|
37
|
-
inline(node.children ?? [], { ...state, format: state.format | FORMAT_BOLD }, out);
|
|
38
|
-
break;
|
|
39
|
-
case 'emphasis':
|
|
40
|
-
inline(node.children ?? [], { ...state, format: state.format | FORMAT_ITALIC }, out);
|
|
41
|
-
break;
|
|
42
|
-
case 'delete':
|
|
43
|
-
inline(node.children ?? [], { ...state, format: state.format | FORMAT_STRIKETHROUGH }, out);
|
|
44
|
-
break;
|
|
45
|
-
case 'inlineCode':
|
|
46
|
-
if (node.value)
|
|
47
|
-
out.push(textNode(node.value, state.format | FORMAT_CODE, state.style));
|
|
48
|
-
break;
|
|
49
|
-
case 'break':
|
|
50
|
-
out.push({ type: 'linebreak', version: 1 });
|
|
51
|
-
break;
|
|
52
|
-
case 'html':
|
|
53
|
-
// A literal <br> (how the serializer writes a line break inside a table cell).
|
|
54
|
-
if (/^<br\s*\/?>$/i.test((node.value ?? '').trim()))
|
|
55
|
-
out.push({ type: 'linebreak', version: 1 });
|
|
56
|
-
break;
|
|
57
|
-
case 'link': {
|
|
58
|
-
const url = safeUrl(node.url ?? '');
|
|
59
|
-
const children = [];
|
|
60
|
-
inline(node.children ?? [], state, children);
|
|
61
|
-
if (!url)
|
|
62
|
-
out.push(...children);
|
|
63
|
-
else
|
|
64
|
-
out.push(elementBase('link', children, { url, rel: null, target: null, title: node.title ?? null }));
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
case 'textDirective': {
|
|
68
|
-
const name = node.name ?? '';
|
|
69
|
-
if (name === 'span') {
|
|
70
|
-
const style = attrsToStyle(node.attributes ?? {});
|
|
71
|
-
inline(node.children ?? [], { ...state, style: [state.style, style].filter(Boolean).join('; ') }, out);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
inline(node.children ?? [], { ...state, format: state.format | (DIRECTIVE_FORMATS[name] ?? 0) }, out);
|
|
75
|
-
}
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
default:
|
|
79
|
-
// Images inside a text run are lifted out by the caller; references
|
|
80
|
-
// and unknown inline nodes keep their text.
|
|
81
|
-
if (node.children)
|
|
82
|
-
inline(node.children, state, out);
|
|
83
|
-
else if (node.value)
|
|
84
|
-
out.push(textNode(node.value, state.format, state.style));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function inlineOf(nodes) {
|
|
89
|
-
const out = [];
|
|
90
|
-
inline(nodes, { format: 0, style: '' }, out);
|
|
91
|
-
// Merge neighbours that ended up with identical formatting (e.g. around a restored ":30").
|
|
92
|
-
return out.reduce((acc, n) => {
|
|
93
|
-
const prev = acc[acc.length - 1];
|
|
94
|
-
if (prev && prev.type === 'text' && n.type === 'text' && prev.format === n.format && prev.style === n.style)
|
|
95
|
-
prev.text = String(prev.text) + String(n.text);
|
|
96
|
-
else
|
|
97
|
-
acc.push(n);
|
|
98
|
-
return acc;
|
|
99
|
-
}, []);
|
|
100
|
-
}
|
|
101
|
-
function imageBlock(node) {
|
|
102
|
-
const src = safeImageSrc(node.url ?? '');
|
|
103
|
-
if (!src)
|
|
104
|
-
return null;
|
|
105
|
-
return {
|
|
106
|
-
type: 'image', version: 1, src, altText: node.alt ?? '', caption: null,
|
|
107
|
-
linkType: /^data:/i.test(src) ? 'embedded' : 'linked', width: null, height: null,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/** A paragraph's images become block images; text between them stays paragraphs. */
|
|
111
|
-
function paragraphBlocks(node, extra) {
|
|
112
|
-
const blocks = [];
|
|
113
|
-
let run = [];
|
|
114
|
-
const flush = () => {
|
|
115
|
-
const children = inlineOf(run);
|
|
116
|
-
if (children.length > 0)
|
|
117
|
-
blocks.push(paragraph(children, extra));
|
|
118
|
-
run = [];
|
|
119
|
-
};
|
|
120
|
-
for (const child of node.children ?? []) {
|
|
121
|
-
if (child.type === 'image') {
|
|
122
|
-
flush();
|
|
123
|
-
const image = imageBlock(child);
|
|
124
|
-
if (image)
|
|
125
|
-
blocks.push(image);
|
|
126
|
-
}
|
|
127
|
-
else
|
|
128
|
-
run.push(child);
|
|
129
|
-
}
|
|
130
|
-
flush();
|
|
131
|
-
return blocks;
|
|
132
|
-
}
|
|
133
|
-
function listBlock(node, extra) {
|
|
134
|
-
const isCheck = (node.children ?? []).some((item) => typeof item.checked === 'boolean');
|
|
135
|
-
const listType = isCheck ? 'check' : node.ordered ? 'number' : 'bullet';
|
|
136
|
-
const items = [];
|
|
137
|
-
for (const item of node.children ?? []) {
|
|
138
|
-
const textParts = [];
|
|
139
|
-
const flush = () => {
|
|
140
|
-
if (textParts.length === 0)
|
|
141
|
-
return;
|
|
142
|
-
const children = [];
|
|
143
|
-
textParts.forEach((p, i) => {
|
|
144
|
-
if (i > 0)
|
|
145
|
-
children.push({ type: 'linebreak', version: 1 });
|
|
146
|
-
children.push(...inlineOf(p.children ?? []));
|
|
147
|
-
});
|
|
148
|
-
items.push(elementBase('listitem', children, { value: 1, ...(isCheck ? { checked: item.checked === true } : {}) }));
|
|
149
|
-
textParts.length = 0;
|
|
150
|
-
};
|
|
151
|
-
for (const child of item.children ?? []) {
|
|
152
|
-
if (child.type === 'list') {
|
|
153
|
-
flush();
|
|
154
|
-
items.push(elementBase('listitem', [listBlock(child, {})], { value: 1 }));
|
|
155
|
-
}
|
|
156
|
-
else if (child.type === 'paragraph')
|
|
157
|
-
textParts.push(child);
|
|
158
|
-
else if (child.children)
|
|
159
|
-
textParts.push({ type: 'paragraph', children: child.children });
|
|
160
|
-
}
|
|
161
|
-
flush();
|
|
162
|
-
}
|
|
163
|
-
return elementBase('list', items, { listType, start: node.ordered ? node.start ?? 1 : 1, tag: node.ordered ? 'ol' : 'ul', ...extra });
|
|
164
|
-
}
|
|
165
|
-
function tableBlock(node) {
|
|
166
|
-
const rows = (node.children ?? []).map((row, rowIndex) => elementBase('tablerow', (row.children ?? []).map((cell) => elementBase('tablecell', [paragraph(inlineOf(cell.children ?? []))], {
|
|
167
|
-
headerState: rowIndex === 0 ? 1 : 0, colSpan: 1, rowSpan: 1, backgroundColor: null, width: null,
|
|
168
|
-
})), { height: null }));
|
|
169
|
-
return elementBase('table', rows);
|
|
170
|
-
}
|
|
171
|
-
function blockExtras(attrs) {
|
|
172
|
-
const extra = {};
|
|
173
|
-
if (attrs.align && ALIGNMENTS.has(attrs.align))
|
|
174
|
-
extra.format = attrs.align;
|
|
175
|
-
if (attrs.dir === 'rtl' || attrs.dir === 'ltr')
|
|
176
|
-
extra.direction = attrs.dir;
|
|
177
|
-
const indent = Number(attrs.indent);
|
|
178
|
-
if (Number.isInteger(indent) && indent > 0)
|
|
179
|
-
extra.indent = Math.min(indent, 10);
|
|
180
|
-
return extra;
|
|
181
|
-
}
|
|
182
|
-
function dim(value) {
|
|
183
|
-
const n = Number(value);
|
|
184
|
-
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
185
|
-
}
|
|
186
|
-
function blocks(nodes, extra = {}) {
|
|
187
|
-
const out = [];
|
|
188
|
-
for (const node of nodes) {
|
|
189
|
-
switch (node.type) {
|
|
190
|
-
case 'paragraph':
|
|
191
|
-
out.push(...paragraphBlocks(node, extra));
|
|
192
|
-
break;
|
|
193
|
-
case 'heading':
|
|
194
|
-
out.push(elementBase('heading', inlineOf(node.children ?? []), { tag: `h${Math.min(6, Math.max(1, node.depth ?? 2))}`, ...extra }));
|
|
195
|
-
break;
|
|
196
|
-
case 'blockquote':
|
|
197
|
-
for (const child of node.children ?? []) {
|
|
198
|
-
if (child.type === 'paragraph')
|
|
199
|
-
out.push(elementBase('quote', inlineOf(child.children ?? []), extra));
|
|
200
|
-
else
|
|
201
|
-
out.push(...blocks([child]));
|
|
202
|
-
}
|
|
203
|
-
break;
|
|
204
|
-
case 'list':
|
|
205
|
-
out.push(listBlock(node, extra));
|
|
206
|
-
break;
|
|
207
|
-
case 'code': {
|
|
208
|
-
const lines = (node.value ?? '').split('\n');
|
|
209
|
-
const children = [];
|
|
210
|
-
lines.forEach((line, i) => {
|
|
211
|
-
if (i > 0)
|
|
212
|
-
children.push({ type: 'linebreak', version: 1 });
|
|
213
|
-
if (line)
|
|
214
|
-
children.push(textNode(line, FORMAT_CODE));
|
|
215
|
-
});
|
|
216
|
-
out.push(paragraph(children, extra));
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
case 'thematicBreak':
|
|
220
|
-
out.push({ type: 'horizontalrule', version: 1 });
|
|
221
|
-
break;
|
|
222
|
-
case 'table':
|
|
223
|
-
out.push(tableBlock(node));
|
|
224
|
-
break;
|
|
225
|
-
case 'containerDirective':
|
|
226
|
-
// Unknown containers are transparent: their content is kept.
|
|
227
|
-
out.push(...blocks(node.children ?? [], node.name === 'para' ? blockExtras(node.attributes ?? {}) : extra));
|
|
228
|
-
break;
|
|
229
|
-
case 'leafDirective': {
|
|
230
|
-
if (node.name === 'pagebreak')
|
|
231
|
-
out.push({ type: 'page-break', version: 1 });
|
|
232
|
-
else if (node.name === 'figure') {
|
|
233
|
-
const attrs = node.attributes ?? {};
|
|
234
|
-
const src = safeImageSrc(attrs.src ?? '');
|
|
235
|
-
if (src) {
|
|
236
|
-
const caption = inlineOf(node.children ?? []).map((n) => String(n.text ?? '')).join('');
|
|
237
|
-
out.push({
|
|
238
|
-
type: 'image', version: 1, src, altText: attrs.alt ?? '', caption: caption || null,
|
|
239
|
-
linkType: /^data:/i.test(src) ? 'embedded' : 'linked', width: dim(attrs.width), height: dim(attrs.height),
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case 'html':
|
|
246
|
-
case 'definition':
|
|
247
|
-
case 'yaml':
|
|
248
|
-
break; // raw HTML, link definitions and front matter carry no editable content
|
|
249
|
-
default:
|
|
250
|
-
if (node.children)
|
|
251
|
-
out.push(...blocks(node.children, extra));
|
|
252
|
-
else if (node.value)
|
|
253
|
-
out.push(paragraph([textNode(node.value)], extra));
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return out;
|
|
257
|
-
}
|
|
258
|
-
const KNOWN_TEXT_DIRECTIVES = new Set(['span', ...Object.keys(DIRECTIVE_FORMATS)]);
|
|
259
|
-
/**
|
|
260
|
-
* `remark-directive` reads any `:word` as a directive ("10:30", "a:b"), which
|
|
261
|
-
* would silently eat ordinary prose. Directives the dialect doesn't define
|
|
262
|
-
* are turned back into the literal source text.
|
|
263
|
-
*/
|
|
264
|
-
function restoreUnknownDirectives(node, source) {
|
|
265
|
-
if (!node.children)
|
|
266
|
-
return;
|
|
267
|
-
node.children = node.children.map((child) => {
|
|
268
|
-
const c = child;
|
|
269
|
-
const unknownText = c.type === 'textDirective' && !KNOWN_TEXT_DIRECTIVES.has(c.name ?? '');
|
|
270
|
-
const unknownLeaf = c.type === 'leafDirective' && c.name !== 'pagebreak' && c.name !== 'figure';
|
|
271
|
-
if ((unknownText || unknownLeaf) && c.position?.start.offset !== undefined && c.position.end.offset !== undefined) {
|
|
272
|
-
const raw = source.slice(c.position.start.offset, c.position.end.offset);
|
|
273
|
-
return unknownLeaf ? { type: 'paragraph', children: [{ type: 'text', value: raw }] } : { type: 'text', value: raw };
|
|
274
|
-
}
|
|
275
|
-
restoreUnknownDirectives(c, source);
|
|
276
|
-
return c;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
export function parseMarkdown(input, _ctx) {
|
|
280
|
-
const tree = processor.parse(input);
|
|
281
|
-
restoreUnknownDirectives(tree, input);
|
|
282
|
-
return makeState(blocks(tree.children ?? []));
|
|
283
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { SerializedEditorState } from 'lexical';
|
|
2
|
-
import type { ConverterContext } from '../types.js';
|
|
3
|
-
import { type Attrs } from './mdast.js';
|
|
4
|
-
/** Known presentation properties become readable attributes; anything else is kept verbatim in `style`. */
|
|
5
|
-
export declare function styleToAttrs(style: string): Attrs;
|
|
6
|
-
/** First strong character decides the direction a reader would infer without a hint. */
|
|
7
|
-
export declare function autoDirection(text: string): 'ltr' | 'rtl' | null;
|
|
8
|
-
export declare function serializeMarkdown(state: SerializedEditorState, ctx?: ConverterContext): string;
|
|
9
|
-
//# sourceMappingURL=serialize.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/markdown/serialize.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAKjD,OAAO,EAAa,KAAK,KAAK,EAAe,MAAM,SAAS,CAAC;AAyC7D,2GAA2G;AAC3G,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAUjD;AAiHD,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,CAMhE;AA4GD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI9F"}
|
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { unified } from 'unified';
|
|
2
|
-
import remarkStringify from 'remark-stringify';
|
|
3
|
-
import remarkGfm from 'remark-gfm';
|
|
4
|
-
import remarkDirective from 'remark-directive';
|
|
5
|
-
import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, rootChildren, } from '../shared/serialized.js';
|
|
6
|
-
import { directive } from './mdast.js';
|
|
7
|
-
const processor = unified()
|
|
8
|
-
.use(remarkGfm)
|
|
9
|
-
.use(remarkDirective)
|
|
10
|
-
.use(remarkStringify, { bullet: '-', emphasis: '*', strong: '*', rule: '-', fences: true, listItemIndent: 'one', incrementListMarker: true });
|
|
11
|
-
const FORMAT_WRAPPERS = [
|
|
12
|
-
[FORMAT_BOLD, { key: 'strong', wrap: (c) => ({ type: 'strong', children: c }) }],
|
|
13
|
-
[FORMAT_ITALIC, { key: 'emphasis', wrap: (c) => ({ type: 'emphasis', children: c }) }],
|
|
14
|
-
[FORMAT_STRIKETHROUGH, { key: 'delete', wrap: (c) => ({ type: 'delete', children: c }) }],
|
|
15
|
-
[FORMAT_UNDERLINE, { key: 'u', wrap: (c) => directive('textDirective', 'u', {}, c) }],
|
|
16
|
-
[FORMAT_SUPERSCRIPT, { key: 'sup', wrap: (c) => directive('textDirective', 'sup', {}, c) }],
|
|
17
|
-
[FORMAT_SUBSCRIPT, { key: 'sub', wrap: (c) => directive('textDirective', 'sub', {}, c) }],
|
|
18
|
-
[FORMAT_HIGHLIGHT, { key: 'mark', wrap: (c) => directive('textDirective', 'mark', {}, c) }],
|
|
19
|
-
];
|
|
20
|
-
const STYLE_ATTRS = [
|
|
21
|
-
['font-family', 'font'],
|
|
22
|
-
['font-size', 'size'],
|
|
23
|
-
['color', 'color'],
|
|
24
|
-
['background-color', 'bg'],
|
|
25
|
-
];
|
|
26
|
-
function parseStyle(style) {
|
|
27
|
-
return style
|
|
28
|
-
.split(';')
|
|
29
|
-
.map((part) => part.trim())
|
|
30
|
-
.filter(Boolean)
|
|
31
|
-
.map((part) => {
|
|
32
|
-
const i = part.indexOf(':');
|
|
33
|
-
return [part.slice(0, i).trim().toLowerCase(), part.slice(i + 1).trim()];
|
|
34
|
-
})
|
|
35
|
-
.filter(([k, v]) => k && v);
|
|
36
|
-
}
|
|
37
|
-
/** Known presentation properties become readable attributes; anything else is kept verbatim in `style`. */
|
|
38
|
-
export function styleToAttrs(style) {
|
|
39
|
-
const attrs = {};
|
|
40
|
-
const rest = [];
|
|
41
|
-
for (const [prop, value] of parseStyle(style)) {
|
|
42
|
-
const mapped = STYLE_ATTRS.find(([p]) => p === prop);
|
|
43
|
-
if (mapped)
|
|
44
|
-
attrs[mapped[1]] = value;
|
|
45
|
-
else
|
|
46
|
-
rest.push(`${prop}: ${value}`);
|
|
47
|
-
}
|
|
48
|
-
if (rest.length > 0)
|
|
49
|
-
attrs.style = rest.join('; ');
|
|
50
|
-
return attrs;
|
|
51
|
-
}
|
|
52
|
-
function runFor(node) {
|
|
53
|
-
const format = Number(node.format ?? 0);
|
|
54
|
-
const text = String(node.text ?? '');
|
|
55
|
-
const wrappers = [];
|
|
56
|
-
const attrs = styleToAttrs(String(node.style ?? ''));
|
|
57
|
-
if (Object.keys(attrs).length > 0) {
|
|
58
|
-
wrappers.push({ key: `span:${JSON.stringify(attrs)}`, wrap: (c) => directive('textDirective', 'span', attrs, c) });
|
|
59
|
-
}
|
|
60
|
-
for (const [bit, wrapper] of FORMAT_WRAPPERS)
|
|
61
|
-
if (format & bit)
|
|
62
|
-
wrappers.push(wrapper);
|
|
63
|
-
const leaf = format & FORMAT_CODE && text.trim() !== '' ? { type: 'inlineCode', value: text } : { type: 'text', value: text };
|
|
64
|
-
return { wrappers, leaf };
|
|
65
|
-
}
|
|
66
|
-
const WRAPPER_TYPES = new Set(['strong', 'emphasis', 'delete', 'textDirective']);
|
|
67
|
-
/**
|
|
68
|
-
* Markdown emphasis must hug its text (`**bold** x`, never `** bold**`), so
|
|
69
|
-
* whitespace at a wrapper's edges is moved outside it. A wrapper holding only
|
|
70
|
-
* whitespace dissolves into plain text.
|
|
71
|
-
*/
|
|
72
|
-
function hoistWhitespace(nodes) {
|
|
73
|
-
const out = [];
|
|
74
|
-
for (const node of nodes) {
|
|
75
|
-
if (!WRAPPER_TYPES.has(node.type) || !node.children) {
|
|
76
|
-
out.push(node);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const children = hoistWhitespace(node.children);
|
|
80
|
-
let lead = '';
|
|
81
|
-
let trail = '';
|
|
82
|
-
const first = children[0];
|
|
83
|
-
if (first?.type === 'text') {
|
|
84
|
-
lead = /^\s*/.exec(first.value ?? '')[0];
|
|
85
|
-
first.value = (first.value ?? '').slice(lead.length);
|
|
86
|
-
}
|
|
87
|
-
const last = children[children.length - 1];
|
|
88
|
-
if (last?.type === 'text') {
|
|
89
|
-
trail = /\s*$/.exec(last.value ?? '')[0];
|
|
90
|
-
last.value = (last.value ?? '').slice(0, (last.value ?? '').length - trail.length);
|
|
91
|
-
}
|
|
92
|
-
const kept = children.filter((c) => c.type !== 'text' || c.value !== '');
|
|
93
|
-
if (lead)
|
|
94
|
-
out.push({ type: 'text', value: lead });
|
|
95
|
-
if (kept.length > 0)
|
|
96
|
-
out.push({ ...node, children: kept });
|
|
97
|
-
if (trail)
|
|
98
|
-
out.push({ type: 'text', value: trail });
|
|
99
|
-
}
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
/** Nests consecutive runs sharing outer wrappers: `**a *b***`, not `**a****b**`. */
|
|
103
|
-
function buildInline(runs, depth = 0) {
|
|
104
|
-
const out = [];
|
|
105
|
-
let i = 0;
|
|
106
|
-
while (i < runs.length) {
|
|
107
|
-
const wrapper = runs[i].wrappers[depth];
|
|
108
|
-
if (!wrapper) {
|
|
109
|
-
out.push(runs[i].leaf);
|
|
110
|
-
i += 1;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
let j = i;
|
|
114
|
-
while (j < runs.length && runs[j].wrappers[depth]?.key === wrapper.key)
|
|
115
|
-
j += 1;
|
|
116
|
-
out.push(wrapper.wrap(buildInline(runs.slice(i, j), depth + 1)));
|
|
117
|
-
i = j;
|
|
118
|
-
}
|
|
119
|
-
return out;
|
|
120
|
-
}
|
|
121
|
-
function mergeText(nodes) {
|
|
122
|
-
const out = [];
|
|
123
|
-
for (const node of nodes) {
|
|
124
|
-
const prev = out[out.length - 1];
|
|
125
|
-
if (node.type === 'text' && prev?.type === 'text')
|
|
126
|
-
prev.value = (prev.value ?? '') + (node.value ?? '');
|
|
127
|
-
else
|
|
128
|
-
out.push(node);
|
|
129
|
-
}
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
function inlineNodes(children, ctx) {
|
|
133
|
-
const out = [];
|
|
134
|
-
let pending = [];
|
|
135
|
-
const flush = () => {
|
|
136
|
-
if (pending.length > 0)
|
|
137
|
-
out.push(...mergeText(hoistWhitespace(buildInline(pending))));
|
|
138
|
-
pending = [];
|
|
139
|
-
};
|
|
140
|
-
for (const child of children) {
|
|
141
|
-
if (child.type === 'text') {
|
|
142
|
-
pending.push(runFor(child));
|
|
143
|
-
}
|
|
144
|
-
else if (child.type === 'tab') {
|
|
145
|
-
pending.push({ wrappers: [], leaf: { type: 'text', value: '\t' } });
|
|
146
|
-
}
|
|
147
|
-
else if (child.type === 'linebreak') {
|
|
148
|
-
flush();
|
|
149
|
-
out.push({ type: 'break' });
|
|
150
|
-
}
|
|
151
|
-
else if (child.type === 'link' || child.type === 'autolink') {
|
|
152
|
-
flush();
|
|
153
|
-
out.push({ type: 'link', url: String(child.url ?? ''), title: child.title ? String(child.title) : null, children: inlineNodes(child.children ?? [], ctx) });
|
|
154
|
-
}
|
|
155
|
-
else if (child.children) {
|
|
156
|
-
flush();
|
|
157
|
-
out.push(...inlineNodes(child.children, ctx));
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
flush();
|
|
161
|
-
return out;
|
|
162
|
-
}
|
|
163
|
-
// ---------------------------------------------------------------- blocks
|
|
164
|
-
/** First strong character decides the direction a reader would infer without a hint. */
|
|
165
|
-
export function autoDirection(text) {
|
|
166
|
-
for (const ch of text) {
|
|
167
|
-
if (/[-ࣿיִ-﷿ﹰ-]/.test(ch))
|
|
168
|
-
return 'rtl';
|
|
169
|
-
if (/[A-Za-zÀ-ɏ]/.test(ch))
|
|
170
|
-
return 'ltr';
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
function plainText(node) {
|
|
175
|
-
if (typeof node.text === 'string')
|
|
176
|
-
return node.text;
|
|
177
|
-
return (node.children ?? []).map(plainText).join('');
|
|
178
|
-
}
|
|
179
|
-
/** `:::para{align=… dir=… indent=…}` — only emitted for what the reader could not infer. */
|
|
180
|
-
function withBlockAttrs(node, block) {
|
|
181
|
-
const attrs = {};
|
|
182
|
-
const format = String(node.format ?? '');
|
|
183
|
-
if (format)
|
|
184
|
-
attrs.align = format;
|
|
185
|
-
const indent = Number(node.indent ?? 0);
|
|
186
|
-
if (indent > 0)
|
|
187
|
-
attrs.indent = String(indent);
|
|
188
|
-
const dir = node.direction === 'rtl' || node.direction === 'ltr' ? node.direction : null;
|
|
189
|
-
if (dir && dir !== (autoDirection(plainText(node)) ?? dir))
|
|
190
|
-
attrs.dir = dir;
|
|
191
|
-
if (Object.keys(attrs).length === 0)
|
|
192
|
-
return block;
|
|
193
|
-
return directive('containerDirective', 'para', attrs, [block]);
|
|
194
|
-
}
|
|
195
|
-
function paragraphOf(children, ctx) {
|
|
196
|
-
return { type: 'paragraph', children: inlineNodes(children, ctx) };
|
|
197
|
-
}
|
|
198
|
-
function listToMd(node, ctx) {
|
|
199
|
-
const listType = String(node.listType ?? 'bullet');
|
|
200
|
-
const items = [];
|
|
201
|
-
for (const item of node.children ?? []) {
|
|
202
|
-
const kids = item.children ?? [];
|
|
203
|
-
const nested = kids.length === 1 && kids[0].type === 'list';
|
|
204
|
-
if (nested) {
|
|
205
|
-
const sub = listToMd(kids[0], ctx);
|
|
206
|
-
const prev = items[items.length - 1];
|
|
207
|
-
if (prev)
|
|
208
|
-
prev.children.push(sub);
|
|
209
|
-
else
|
|
210
|
-
items.push({ type: 'listItem', spread: false, checked: null, children: [sub] });
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
items.push({
|
|
214
|
-
type: 'listItem',
|
|
215
|
-
spread: false,
|
|
216
|
-
checked: listType === 'check' ? !!item.checked : null,
|
|
217
|
-
children: [paragraphOf(kids, ctx)],
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return { type: 'list', ordered: listType === 'number', start: listType === 'number' ? Number(node.start ?? 1) : null, spread: false, children: items };
|
|
221
|
-
}
|
|
222
|
-
function imageToMd(node, ctx) {
|
|
223
|
-
const raw = String(node.src ?? '');
|
|
224
|
-
const url = ctx?.resolveImageUrl ? ctx.resolveImageUrl(raw) : raw;
|
|
225
|
-
const alt = String(node.altText ?? '');
|
|
226
|
-
const caption = typeof node.caption === 'string' && node.caption !== '' ? node.caption : null;
|
|
227
|
-
if (caption === null && !node.width && !node.height) {
|
|
228
|
-
return { type: 'paragraph', children: [{ type: 'image', url, alt, title: null }] };
|
|
229
|
-
}
|
|
230
|
-
const attrs = { src: url };
|
|
231
|
-
if (alt)
|
|
232
|
-
attrs.alt = alt;
|
|
233
|
-
if (node.width)
|
|
234
|
-
attrs.width = String(node.width);
|
|
235
|
-
if (node.height)
|
|
236
|
-
attrs.height = String(node.height);
|
|
237
|
-
return directive('leafDirective', 'figure', attrs, caption ? [{ type: 'text', value: caption }] : []);
|
|
238
|
-
}
|
|
239
|
-
function tableToMd(node, ctx) {
|
|
240
|
-
const rows = (node.children ?? []).map((row) => ({
|
|
241
|
-
type: 'tableRow',
|
|
242
|
-
children: (row.children ?? []).map((cell) => {
|
|
243
|
-
// A cell's blocks are flattened into one inline run separated by line breaks.
|
|
244
|
-
const parts = [];
|
|
245
|
-
(cell.children ?? []).forEach((block, i) => {
|
|
246
|
-
if (i > 0)
|
|
247
|
-
parts.push({ type: 'html', value: '<br>' });
|
|
248
|
-
parts.push(...inlineNodes(block.children ?? [], ctx));
|
|
249
|
-
});
|
|
250
|
-
return { type: 'tableCell', children: parts };
|
|
251
|
-
}),
|
|
252
|
-
}));
|
|
253
|
-
const columns = Math.max(0, ...rows.map((r) => r.children.length));
|
|
254
|
-
return { type: 'table', align: Array.from({ length: columns }, () => null), children: rows };
|
|
255
|
-
}
|
|
256
|
-
function blockToMd(node, ctx) {
|
|
257
|
-
switch (node.type) {
|
|
258
|
-
case 'paragraph': {
|
|
259
|
-
const children = node.children ?? [];
|
|
260
|
-
if (children.length === 0)
|
|
261
|
-
return [];
|
|
262
|
-
return [withBlockAttrs(node, paragraphOf(children, ctx))];
|
|
263
|
-
}
|
|
264
|
-
case 'heading': {
|
|
265
|
-
const depth = Math.min(6, Math.max(1, Number(String(node.tag).slice(1)) || 2));
|
|
266
|
-
return [withBlockAttrs(node, { type: 'heading', depth, children: inlineNodes(node.children ?? [], ctx) })];
|
|
267
|
-
}
|
|
268
|
-
case 'quote':
|
|
269
|
-
return [withBlockAttrs(node, { type: 'blockquote', children: [paragraphOf(node.children ?? [], ctx)] })];
|
|
270
|
-
case 'list':
|
|
271
|
-
return [withBlockAttrs(node, listToMd(node, ctx))];
|
|
272
|
-
case 'horizontalrule':
|
|
273
|
-
return [{ type: 'thematicBreak' }];
|
|
274
|
-
case 'page-break':
|
|
275
|
-
return [directive('leafDirective', 'pagebreak', {})];
|
|
276
|
-
case 'image':
|
|
277
|
-
return [imageToMd(node, ctx)];
|
|
278
|
-
case 'table':
|
|
279
|
-
return [tableToMd(node, ctx)];
|
|
280
|
-
default:
|
|
281
|
-
// Fallback rule (dialect spec §6): unknown blocks keep their text as paragraphs.
|
|
282
|
-
return (node.children ?? []).flatMap((child) => blockToMd(child, ctx));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
export function serializeMarkdown(state, ctx) {
|
|
286
|
-
const tree = { type: 'root', children: rootChildren(state).flatMap((n) => blockToMd(n, ctx)) };
|
|
287
|
-
// remark-stringify consumes mdast; our MdNode is a structural subset of it.
|
|
288
|
-
return String(processor.stringify(tree)).replace(/\n+$/, '\n').replace(/^\n+$/, '');
|
|
289
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { SerializedEditorState } from 'lexical';
|
|
2
|
-
/**
|
|
3
|
-
* Loose structural view of Lexical's serialized nodes. Converters work on
|
|
4
|
-
* plain JSON so they stay free of the editor, the DOM-bound node classes and
|
|
5
|
-
* React (architecture doc §1).
|
|
6
|
-
*/
|
|
7
|
-
export interface SNode {
|
|
8
|
-
type: string;
|
|
9
|
-
version?: number;
|
|
10
|
-
children?: SNode[];
|
|
11
|
-
[key: string]: unknown;
|
|
12
|
-
}
|
|
13
|
-
export declare const FORMAT_BOLD = 1;
|
|
14
|
-
export declare const FORMAT_ITALIC = 2;
|
|
15
|
-
export declare const FORMAT_STRIKETHROUGH = 4;
|
|
16
|
-
export declare const FORMAT_UNDERLINE = 8;
|
|
17
|
-
export declare const FORMAT_CODE = 16;
|
|
18
|
-
export declare const FORMAT_SUBSCRIPT = 32;
|
|
19
|
-
export declare const FORMAT_SUPERSCRIPT = 64;
|
|
20
|
-
export declare const FORMAT_HIGHLIGHT = 128;
|
|
21
|
-
/** Pixels Lexical's own DOM export uses per indent level. */
|
|
22
|
-
export declare const INDENT_PX = 40;
|
|
23
|
-
export type Direction = 'ltr' | 'rtl' | null;
|
|
24
|
-
export declare function rootChildren(state: SerializedEditorState): SNode[];
|
|
25
|
-
export declare function textNode(text: string, format?: number, style?: string): SNode;
|
|
26
|
-
export declare function elementBase(type: string, children: SNode[], extra?: Record<string, unknown>): SNode;
|
|
27
|
-
export declare function paragraph(children?: SNode[], extra?: Record<string, unknown>): SNode;
|
|
28
|
-
export declare function makeState(blocks: SNode[]): SerializedEditorState;
|
|
29
|
-
/** Inline node types: everything else is a block. */
|
|
30
|
-
export declare function isInlineNode(node: SNode): boolean;
|
|
31
|
-
//# sourceMappingURL=serialized.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialized.d.ts","sourceRoot":"","sources":["../../src/shared/serialized.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,kBAAkB,KAAK,CAAC;AACrC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,6DAA6D;AAC7D,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;AAE7C,wBAAgB,YAAY,CAAC,KAAK,EAAE,qBAAqB,GAAG,KAAK,EAAE,CAElE;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,EAAE,KAAK,SAAK,GAAG,KAAK,CAEpE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,KAAK,CAEvG;AAED,wBAAgB,SAAS,CAAC,QAAQ,GAAE,KAAK,EAAO,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,KAAK,CAE5F;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,qBAAqB,CAKhE;AAED,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,OAAO,CAEjD"}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export const FORMAT_BOLD = 1;
|
|
2
|
-
export const FORMAT_ITALIC = 2;
|
|
3
|
-
export const FORMAT_STRIKETHROUGH = 4;
|
|
4
|
-
export const FORMAT_UNDERLINE = 8;
|
|
5
|
-
export const FORMAT_CODE = 16;
|
|
6
|
-
export const FORMAT_SUBSCRIPT = 32;
|
|
7
|
-
export const FORMAT_SUPERSCRIPT = 64;
|
|
8
|
-
export const FORMAT_HIGHLIGHT = 128;
|
|
9
|
-
/** Pixels Lexical's own DOM export uses per indent level. */
|
|
10
|
-
export const INDENT_PX = 40;
|
|
11
|
-
export function rootChildren(state) {
|
|
12
|
-
return (state.root.children ?? []);
|
|
13
|
-
}
|
|
14
|
-
export function textNode(text, format = 0, style = '') {
|
|
15
|
-
return { type: 'text', text, detail: 0, format, mode: 'normal', style, version: 1 };
|
|
16
|
-
}
|
|
17
|
-
export function elementBase(type, children, extra = {}) {
|
|
18
|
-
return { type, children, direction: null, format: '', indent: 0, version: 1, ...extra };
|
|
19
|
-
}
|
|
20
|
-
export function paragraph(children = [], extra = {}) {
|
|
21
|
-
return elementBase('paragraph', children, { textFormat: 0, textStyle: '', ...extra });
|
|
22
|
-
}
|
|
23
|
-
export function makeState(blocks) {
|
|
24
|
-
const children = blocks.length > 0 ? blocks : [paragraph()];
|
|
25
|
-
return {
|
|
26
|
-
root: { type: 'root', children, direction: null, format: '', indent: 0, version: 1 },
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/** Inline node types: everything else is a block. */
|
|
30
|
-
export function isInlineNode(node) {
|
|
31
|
-
return node.type === 'text' || node.type === 'linebreak' || node.type === 'tab' || node.type === 'link' || node.type === 'autolink';
|
|
32
|
-
}
|