@inshapardaz/likhari-converters 0.1.14 → 0.1.15-dev.118
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/parse.d.ts.map +1 -1
- package/dist/html/parse.js +37 -1
- package/dist/html/serialize.d.ts.map +1 -1
- package/dist/html/serialize.js +38 -2
- package/dist/markdown/mdast.d.ts +5 -0
- package/dist/markdown/mdast.d.ts.map +1 -1
- package/dist/markdown/parse.d.ts.map +1 -1
- package/dist/markdown/parse.js +35 -3
- package/dist/markdown/serialize.d.ts.map +1 -1
- package/dist/markdown/serialize.js +19 -0
- package/dist/plain-text/index.d.ts.map +1 -1
- package/dist/plain-text/index.js +29 -6
- package/dist/shared/serialized.d.ts +5 -0
- package/dist/shared/serialized.d.ts.map +1 -1
- package/dist/shared/serialized.js +15 -0
- package/package.json +1 -1
package/dist/html/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAoTD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAMvF"}
|
package/dist/html/parse.js
CHANGED
|
@@ -92,6 +92,10 @@ function collectInline(node, state, out, preserveWs) {
|
|
|
92
92
|
out.push({ type: 'linebreak', version: 1 });
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
+
if (tag === 'sup' && el.hasAttribute('data-likhari-footnote-ref')) {
|
|
96
|
+
out.push({ type: 'footnote-reference', version: 1, footnoteId: el.getAttribute('data-likhari-footnote-ref') ?? '' });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
95
99
|
if (tag === 'a') {
|
|
96
100
|
const url = safeUrl(el.getAttribute('href') ?? '');
|
|
97
101
|
const children = [];
|
|
@@ -237,6 +241,36 @@ function convertTable(el) {
|
|
|
237
241
|
});
|
|
238
242
|
return elementBase('table', rows);
|
|
239
243
|
}
|
|
244
|
+
function convertLayout(container) {
|
|
245
|
+
const items = Array.from(container.children)
|
|
246
|
+
.filter((child) => child.hasAttribute('data-likhari-layout-item'))
|
|
247
|
+
.map((item) => {
|
|
248
|
+
const children = flowBlocks(item);
|
|
249
|
+
return elementBase('layout-item', children.length > 0 ? children : [paragraph()]);
|
|
250
|
+
});
|
|
251
|
+
const templateColumns = container.style.gridTemplateColumns || `repeat(${Math.max(items.length, 2)}, 1fr)`;
|
|
252
|
+
return elementBase('layout-container', items.length > 0 ? items : [elementBase('layout-item', [paragraph()])], { templateColumns });
|
|
253
|
+
}
|
|
254
|
+
function convertFootnoteList(el) {
|
|
255
|
+
const items = [];
|
|
256
|
+
Array.from(el.children).forEach((child) => {
|
|
257
|
+
if (child.tagName.toLowerCase() !== 'li')
|
|
258
|
+
return;
|
|
259
|
+
const li = child;
|
|
260
|
+
const footnoteId = li.getAttribute('data-likhari-footnote-item') ?? (li.getAttribute('id') ?? '').replace(/^fn-/, '');
|
|
261
|
+
if (!footnoteId)
|
|
262
|
+
return;
|
|
263
|
+
// Drop the "↩" backlink our own serializer appends — it's a generated
|
|
264
|
+
// affordance, not part of the note's content.
|
|
265
|
+
const clone = li.cloneNode(true);
|
|
266
|
+
Array.from(clone.querySelectorAll('a'))
|
|
267
|
+
.filter((a) => a.getAttribute('href') === `#fnref-${footnoteId}`)
|
|
268
|
+
.forEach((a) => a.remove());
|
|
269
|
+
const children = flowBlocks(clone);
|
|
270
|
+
items.push(elementBase('footnote-item', children.length > 0 ? children : [paragraph()], { footnoteId }));
|
|
271
|
+
});
|
|
272
|
+
return elementBase('footnote-list', items);
|
|
273
|
+
}
|
|
240
274
|
function convertImage(img, caption) {
|
|
241
275
|
const src = safeImageSrc(img.getAttribute('src') ?? '');
|
|
242
276
|
if (!src)
|
|
@@ -282,7 +316,7 @@ function convertBlock(el) {
|
|
|
282
316
|
}
|
|
283
317
|
case 'ul':
|
|
284
318
|
case 'ol':
|
|
285
|
-
return [convertList(el)];
|
|
319
|
+
return el.hasAttribute('data-likhari-footnote-list') ? [convertFootnoteList(el)] : [convertList(el)];
|
|
286
320
|
case 'li':
|
|
287
321
|
return convertListItem(el, false).length > 0 ? [elementBase('list', convertListItem(el, false), { listType: 'bullet', start: 1, tag: 'ul' })] : [];
|
|
288
322
|
case 'table':
|
|
@@ -305,6 +339,8 @@ function convertBlock(el) {
|
|
|
305
339
|
case 'div':
|
|
306
340
|
if (el.hasAttribute('data-likhari-page-break'))
|
|
307
341
|
return [{ type: 'page-break', version: 1 }];
|
|
342
|
+
if (el.hasAttribute('data-likhari-layout-container'))
|
|
343
|
+
return [convertLayout(el)];
|
|
308
344
|
return flowBlocks(el);
|
|
309
345
|
case 'figcaption':
|
|
310
346
|
return [];
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,EAEkG,KAAK,KAAK,EAClH,MAAM,sBAAsB,CAAC;AAE9B,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAgID,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CA6CtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI1F"}
|
package/dist/html/serialize.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
1
|
+
import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, INDENT_PX, collectFootnoteOrder, rootChildren, } from '../shared/serialized.js';
|
|
2
2
|
export function escapeHtml(value) {
|
|
3
3
|
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
4
4
|
}
|
|
@@ -87,6 +87,34 @@ function tableToHtml(node, ctx) {
|
|
|
87
87
|
.join('');
|
|
88
88
|
return `<table><tbody>${rows}</tbody></table>`;
|
|
89
89
|
}
|
|
90
|
+
/** Set once per serializeHtml() call and read by the footnote cases below —
|
|
91
|
+
* avoids threading a numbering map through every recursive nodeToHtml call
|
|
92
|
+
* for what is otherwise a leaf concern of two node types. Safe because
|
|
93
|
+
* serialization is synchronous and this module is never re-entered mid-call. */
|
|
94
|
+
let footnoteOrder = new Map();
|
|
95
|
+
function footnoteReferenceToHtml(node) {
|
|
96
|
+
const id = String(node.footnoteId ?? '');
|
|
97
|
+
const n = footnoteOrder.get(id) ?? '?';
|
|
98
|
+
return `<sup data-likhari-footnote-ref="${escapeAttr(id)}"><a href="#fn-${escapeAttr(id)}" id="fnref-${escapeAttr(id)}">${n}</a></sup>`;
|
|
99
|
+
}
|
|
100
|
+
function footnoteListToHtml(node, ctx) {
|
|
101
|
+
const items = (node.children ?? [])
|
|
102
|
+
.map((item) => {
|
|
103
|
+
const id = String(item.footnoteId ?? '');
|
|
104
|
+
const body = (item.children ?? []).map((child) => nodeToHtml(child, ctx)).join('');
|
|
105
|
+
return `<li id="fn-${escapeAttr(id)}" data-likhari-footnote-item="${escapeAttr(id)}">${body} <a href="#fnref-${escapeAttr(id)}">↩</a></li>`;
|
|
106
|
+
})
|
|
107
|
+
.join('');
|
|
108
|
+
return `<ol data-likhari-footnote-list>${items}</ol>`;
|
|
109
|
+
}
|
|
110
|
+
function layoutToHtml(node, ctx) {
|
|
111
|
+
const items = node.children ?? [];
|
|
112
|
+
const templateColumns = typeof node.templateColumns === 'string' ? node.templateColumns : `repeat(${items.length}, 1fr)`;
|
|
113
|
+
const columns = items
|
|
114
|
+
.map((item) => `<div data-likhari-layout-item>${(item.children ?? []).map((child) => nodeToHtml(child, ctx)).join('')}</div>`)
|
|
115
|
+
.join('');
|
|
116
|
+
return `<div data-likhari-layout-container style="display: grid; grid-template-columns: ${escapeAttr(templateColumns)}">${columns}</div>`;
|
|
117
|
+
}
|
|
90
118
|
function imageToHtml(node, ctx) {
|
|
91
119
|
const rawSrc = String(node.src ?? '');
|
|
92
120
|
const src = ctx?.resolveImageUrl ? ctx.resolveImageUrl(rawSrc) : rawSrc;
|
|
@@ -133,11 +161,19 @@ export function nodeToHtml(node, ctx) {
|
|
|
133
161
|
return imageToHtml(node, ctx);
|
|
134
162
|
case 'table':
|
|
135
163
|
return tableToHtml(node, ctx);
|
|
164
|
+
case 'layout-container':
|
|
165
|
+
return layoutToHtml(node, ctx);
|
|
166
|
+
case 'footnote-reference':
|
|
167
|
+
return footnoteReferenceToHtml(node);
|
|
168
|
+
case 'footnote-list':
|
|
169
|
+
return footnoteListToHtml(node, ctx);
|
|
136
170
|
default:
|
|
137
171
|
// Unknown node (e.g. a future feature): keep its content, drop the wrapper.
|
|
138
172
|
return inlineChildren(node, ctx);
|
|
139
173
|
}
|
|
140
174
|
}
|
|
141
175
|
export function serializeHtml(state, ctx) {
|
|
142
|
-
|
|
176
|
+
const blocks = rootChildren(state);
|
|
177
|
+
footnoteOrder = collectFootnoteOrder(blocks);
|
|
178
|
+
return blocks.map((node) => nodeToHtml(node, ctx)).join('\n');
|
|
143
179
|
}
|
package/dist/markdown/mdast.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface MdNode {
|
|
|
16
16
|
name?: string;
|
|
17
17
|
attributes?: Record<string, string | null | undefined> | null;
|
|
18
18
|
align?: Array<'left' | 'right' | 'center' | null> | null;
|
|
19
|
+
/** GFM footnotes (remark-gfm): `identifier` is normalized/lowercased by
|
|
20
|
+
* remark itself, `label` keeps the original casing. We only ever use
|
|
21
|
+
* plain numerals for both, so the distinction doesn't matter here. */
|
|
22
|
+
identifier?: string;
|
|
23
|
+
label?: string;
|
|
19
24
|
}
|
|
20
25
|
export type Attrs = Record<string, string>;
|
|
21
26
|
export declare function directive(type: 'textDirective' | 'leafDirective' | 'containerDirective', name: string, attributes: Attrs, children?: MdNode[]): MdNode;
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;IACzD;;0EAEsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;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"}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAkTjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAO3F"}
|
package/dist/markdown/parse.js
CHANGED
|
@@ -64,6 +64,9 @@ function inline(nodes, state, out) {
|
|
|
64
64
|
out.push(elementBase('link', children, { url, rel: null, target: null, title: node.title ?? null }));
|
|
65
65
|
break;
|
|
66
66
|
}
|
|
67
|
+
case 'footnoteReference':
|
|
68
|
+
out.push({ type: 'footnote-reference', version: 1, footnoteId: node.identifier ?? '' });
|
|
69
|
+
break;
|
|
67
70
|
case 'textDirective': {
|
|
68
71
|
const name = node.name ?? '';
|
|
69
72
|
if (name === 'span') {
|
|
@@ -223,8 +226,22 @@ function blocks(nodes, extra = {}) {
|
|
|
223
226
|
out.push(tableBlock(node));
|
|
224
227
|
break;
|
|
225
228
|
case 'containerDirective':
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
if (node.name === 'columns') {
|
|
230
|
+
const columns = (node.children ?? [])
|
|
231
|
+
.filter((c) => c.type === 'containerDirective' && c.name === 'column')
|
|
232
|
+
.map((c) => {
|
|
233
|
+
const children = blocks(c.children ?? []);
|
|
234
|
+
return elementBase('layout-item', children.length > 0 ? children : [paragraph()]);
|
|
235
|
+
});
|
|
236
|
+
if (columns.length > 0) {
|
|
237
|
+
out.push(elementBase('layout-container', columns, { templateColumns: `repeat(${columns.length}, 1fr)` }));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Unknown containers (including a stray "column" outside "columns")
|
|
242
|
+
// are transparent: their content is kept.
|
|
243
|
+
out.push(...blocks(node.children ?? [], node.name === 'para' ? blockExtras(node.attributes ?? {}) : extra));
|
|
244
|
+
}
|
|
228
245
|
break;
|
|
229
246
|
case 'leafDirective': {
|
|
230
247
|
if (node.name === 'pagebreak')
|
|
@@ -276,8 +293,23 @@ function restoreUnknownDirectives(node, source) {
|
|
|
276
293
|
return c;
|
|
277
294
|
});
|
|
278
295
|
}
|
|
296
|
+
/** Footnote definitions are always top-level in CommonMark/GFM (never nested
|
|
297
|
+
* inside a list/quote/etc.), wherever in the source they appear — so unlike
|
|
298
|
+
* everything else `blocks()` handles, they're pulled out before the normal
|
|
299
|
+
* recursive walk and collected into the one footnote-list our editor model
|
|
300
|
+
* expects, instead of staying scattered where the source happened to put them. */
|
|
301
|
+
function extractFootnoteList(topLevel) {
|
|
302
|
+
const defs = topLevel.filter((node) => node.type === 'footnoteDefinition');
|
|
303
|
+
if (defs.length === 0)
|
|
304
|
+
return null;
|
|
305
|
+
const items = defs.map((def) => elementBase('footnote-item', blocks(def.children ?? []), { footnoteId: def.identifier ?? '' }));
|
|
306
|
+
return elementBase('footnote-list', items);
|
|
307
|
+
}
|
|
279
308
|
export function parseMarkdown(input, _ctx) {
|
|
280
309
|
const tree = processor.parse(input);
|
|
281
310
|
restoreUnknownDirectives(tree, input);
|
|
282
|
-
|
|
311
|
+
const topLevel = tree.children ?? [];
|
|
312
|
+
const footnoteList = extractFootnoteList(topLevel);
|
|
313
|
+
const body = blocks(topLevel.filter((node) => node.type !== 'footnoteDefinition'));
|
|
314
|
+
return makeState(footnoteList ? [...body, footnoteList] : body);
|
|
283
315
|
}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAqHD,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,CAMhE;AA2HD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI9F"}
|
|
@@ -152,6 +152,11 @@ function inlineNodes(children, ctx) {
|
|
|
152
152
|
flush();
|
|
153
153
|
out.push({ type: 'link', url: String(child.url ?? ''), title: child.title ? String(child.title) : null, children: inlineNodes(child.children ?? [], ctx) });
|
|
154
154
|
}
|
|
155
|
+
else if (child.type === 'footnote-reference') {
|
|
156
|
+
flush();
|
|
157
|
+
const id = String(child.footnoteId ?? '');
|
|
158
|
+
out.push({ type: 'footnoteReference', identifier: id, label: id });
|
|
159
|
+
}
|
|
155
160
|
else if (child.children) {
|
|
156
161
|
flush();
|
|
157
162
|
out.push(...inlineNodes(child.children, ctx));
|
|
@@ -236,6 +241,11 @@ function imageToMd(node, ctx) {
|
|
|
236
241
|
attrs.height = String(node.height);
|
|
237
242
|
return directive('leafDirective', 'figure', attrs, caption ? [{ type: 'text', value: caption }] : []);
|
|
238
243
|
}
|
|
244
|
+
function layoutToMd(node, ctx) {
|
|
245
|
+
const items = node.children ?? [];
|
|
246
|
+
const columns = items.map((item) => directive('containerDirective', 'column', {}, (item.children ?? []).flatMap((child) => blockToMd(child, ctx))));
|
|
247
|
+
return directive('containerDirective', 'columns', { count: String(items.length) }, columns);
|
|
248
|
+
}
|
|
239
249
|
function tableToMd(node, ctx) {
|
|
240
250
|
const rows = (node.children ?? []).map((row) => ({
|
|
241
251
|
type: 'tableRow',
|
|
@@ -277,6 +287,15 @@ function blockToMd(node, ctx) {
|
|
|
277
287
|
return [imageToMd(node, ctx)];
|
|
278
288
|
case 'table':
|
|
279
289
|
return [tableToMd(node, ctx)];
|
|
290
|
+
case 'layout-container':
|
|
291
|
+
return [layoutToMd(node, ctx)];
|
|
292
|
+
case 'footnote-list':
|
|
293
|
+
return (node.children ?? []).map((item) => ({
|
|
294
|
+
type: 'footnoteDefinition',
|
|
295
|
+
identifier: String(item.footnoteId ?? ''),
|
|
296
|
+
label: String(item.footnoteId ?? ''),
|
|
297
|
+
children: (item.children ?? []).flatMap((child) => blockToMd(child, ctx)),
|
|
298
|
+
}));
|
|
280
299
|
default:
|
|
281
300
|
// Fallback rule (dialect spec §6): unknown blocks keep their text as paragraphs.
|
|
282
301
|
return (node.children ?? []).flatMap((child) => blockToMd(child, ctx));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plain-text/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plain-text/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA6EhD,eAAO,MAAM,kBAAkB,EAAE,eAsDhC,CAAC"}
|
package/dist/plain-text/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectFootnoteOrder } from '../shared/serialized.js';
|
|
1
2
|
const LIST_ITEM_PREFIX = {
|
|
2
3
|
bullet: () => '- ',
|
|
3
4
|
number: (index) => `${index + 1}. `,
|
|
@@ -9,7 +10,7 @@ const LIST_ITEM_PREFIX = {
|
|
|
9
10
|
* all formatting — this is a documented, exhaustive mapping per node type,
|
|
10
11
|
* not an ad hoc `.textContent` walk.
|
|
11
12
|
*/
|
|
12
|
-
function nodeToText(node, listItemIndex = 0) {
|
|
13
|
+
function nodeToText(node, order, listItemIndex = 0) {
|
|
13
14
|
const withChildren = node;
|
|
14
15
|
if (node.type === 'text' || node.type === 'linebreak') {
|
|
15
16
|
return node.text ?? (node.type === 'linebreak' ? '\n' : '');
|
|
@@ -20,28 +21,50 @@ function nodeToText(node, listItemIndex = 0) {
|
|
|
20
21
|
const image = node;
|
|
21
22
|
return image.altText || image.caption || '';
|
|
22
23
|
}
|
|
24
|
+
// Inline marker only — its matching note is in the trailing list (below),
|
|
25
|
+
// never inline, per the fidelity matrix's "inline bracketed number".
|
|
26
|
+
if (node.type === 'footnote-reference') {
|
|
27
|
+
const id = String(node.footnoteId ?? '');
|
|
28
|
+
return `[${order.get(id) ?? '?'}]`;
|
|
29
|
+
}
|
|
23
30
|
if (node.type === 'list' && withChildren.children) {
|
|
24
31
|
const listType = node.listType ?? 'bullet';
|
|
25
32
|
return withChildren.children
|
|
26
33
|
.map((child, i) => {
|
|
27
34
|
const prefix = LIST_ITEM_PREFIX[listType]?.(i) ?? '- ';
|
|
28
|
-
return prefix + nodeToText(child, i);
|
|
35
|
+
return prefix + nodeToText(child, order, i);
|
|
29
36
|
})
|
|
30
37
|
.join('\n');
|
|
31
38
|
}
|
|
32
39
|
if (withChildren.children) {
|
|
33
|
-
return withChildren.children.map((child) => nodeToText(child, listItemIndex)).join('');
|
|
40
|
+
return withChildren.children.map((child) => nodeToText(child, order, listItemIndex)).join('');
|
|
34
41
|
}
|
|
35
42
|
return '';
|
|
36
43
|
}
|
|
37
|
-
function blockToText(node) {
|
|
38
|
-
return nodeToText(node);
|
|
44
|
+
function blockToText(node, order) {
|
|
45
|
+
return nodeToText(node, order);
|
|
46
|
+
}
|
|
47
|
+
/** `[n] note text` per footnote, in display-number order — the "trailing
|
|
48
|
+
* list" half of the fidelity matrix's plain-text footnote entry. */
|
|
49
|
+
function footnoteListToText(node, order) {
|
|
50
|
+
const items = node.children ?? [];
|
|
51
|
+
return items
|
|
52
|
+
.map((item) => {
|
|
53
|
+
const id = String(item.footnoteId ?? '');
|
|
54
|
+
return `[${order.get(id) ?? '?'}] ${nodeToText(item, order)}`;
|
|
55
|
+
})
|
|
56
|
+
.join('\n');
|
|
39
57
|
}
|
|
40
58
|
export const plainTextConverter = {
|
|
41
59
|
id: 'plain-text',
|
|
42
60
|
serialize(editorState) {
|
|
43
61
|
const root = editorState.root;
|
|
44
|
-
const
|
|
62
|
+
const children = root.children ?? [];
|
|
63
|
+
const order = collectFootnoteOrder(children);
|
|
64
|
+
const footnoteLists = children.filter((n) => n.type === 'footnote-list');
|
|
65
|
+
const blocks = children.filter((n) => n.type !== 'footnote-list').map((n) => blockToText(n, order));
|
|
66
|
+
if (footnoteLists.length > 0)
|
|
67
|
+
blocks.push(footnoteLists.map((n) => footnoteListToText(n, order)).join('\n'));
|
|
45
68
|
return blocks.join('\n\n');
|
|
46
69
|
},
|
|
47
70
|
parse(input) {
|
|
@@ -28,4 +28,9 @@ export declare function paragraph(children?: SNode[], extra?: Record<string, unk
|
|
|
28
28
|
export declare function makeState(blocks: SNode[]): SerializedEditorState;
|
|
29
29
|
/** Inline node types: everything else is a block. */
|
|
30
30
|
export declare function isInlineNode(node: SNode): boolean;
|
|
31
|
+
/** Footnote reference numbers (lexical-editor-spec.md §4.10) are never
|
|
32
|
+
* stored — they're each `footnote-reference` node's position, in document
|
|
33
|
+
* order, among all such nodes. Walks the whole tree once so every converter
|
|
34
|
+
* numbers references and their matching footnote-list entries identically. */
|
|
35
|
+
export declare function collectFootnoteOrder(blocks: SNode[]): Map<string, number>;
|
|
31
36
|
//# sourceMappingURL=serialized.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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;AAED;;;8EAG8E;AAC9E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAUzE"}
|
|
@@ -30,3 +30,18 @@ export function makeState(blocks) {
|
|
|
30
30
|
export function isInlineNode(node) {
|
|
31
31
|
return node.type === 'text' || node.type === 'linebreak' || node.type === 'tab' || node.type === 'link' || node.type === 'autolink';
|
|
32
32
|
}
|
|
33
|
+
/** Footnote reference numbers (lexical-editor-spec.md §4.10) are never
|
|
34
|
+
* stored — they're each `footnote-reference` node's position, in document
|
|
35
|
+
* order, among all such nodes. Walks the whole tree once so every converter
|
|
36
|
+
* numbers references and their matching footnote-list entries identically. */
|
|
37
|
+
export function collectFootnoteOrder(blocks) {
|
|
38
|
+
const order = new Map();
|
|
39
|
+
const visit = (node) => {
|
|
40
|
+
if (node.type === 'footnote-reference' && typeof node.footnoteId === 'string' && !order.has(node.footnoteId)) {
|
|
41
|
+
order.set(node.footnoteId, order.size + 1);
|
|
42
|
+
}
|
|
43
|
+
(node.children ?? []).forEach(visit);
|
|
44
|
+
};
|
|
45
|
+
blocks.forEach(visit);
|
|
46
|
+
return order;
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inshapardaz/likhari-converters",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15-dev.118",
|
|
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": {
|