@inshapardaz/likhari-converters 0.1.18 → 0.1.19-dev.184

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.
@@ -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;AAoTD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAMvF"}
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;AAkCjD,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;AAoVD,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAMvF"}
@@ -1,3 +1,4 @@
1
+ import { layoutOrDefault, scaleOrDefault } from '../shared/poetry.js';
1
2
  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
3
  const ALIGNMENTS = new Set(['left', 'right', 'center', 'justify', 'start', 'end']);
3
4
  const TAG_FORMATS = {
@@ -251,6 +252,31 @@ function convertLayout(container) {
251
252
  const templateColumns = container.style.gridTemplateColumns || `repeat(${Math.max(items.length, 2)}, 1fr)`;
252
253
  return elementBase('layout-container', items.length > 0 ? items : [elementBase('layout-item', [paragraph()])], { templateColumns });
253
254
  }
255
+ function convertPoetry(el) {
256
+ const layout = layoutOrDefault(el.getAttribute('data-likhari-poetry-layout'));
257
+ const settings = {
258
+ layout,
259
+ spacing: scaleOrDefault(el.getAttribute('data-likhari-poetry-spacing')),
260
+ gutter: scaleOrDefault(el.getAttribute('data-likhari-poetry-gutter')),
261
+ stagger: scaleOrDefault(el.getAttribute('data-likhari-poetry-stagger')),
262
+ ...(Number(el.getAttribute('data-likhari-poetry-width')) > 0 ? { width: Number(el.getAttribute('data-likhari-poetry-width')) } : {}),
263
+ };
264
+ if (layout === 'two-column') {
265
+ // One LayoutContainerNode row per couplet — grab all of them, not just
266
+ // the first, or every couplet past the first silently disappears.
267
+ const containerEls = Array.from(el.children).filter((c) => c.hasAttribute('data-likhari-layout-container'));
268
+ const containers = containerEls.length > 0
269
+ ? containerEls.map((c) => convertLayout(c))
270
+ : [
271
+ elementBase('layout-container', [elementBase('layout-item', [paragraph()]), elementBase('layout-item', [paragraph()])], {
272
+ templateColumns: 'repeat(2, 1fr)',
273
+ }),
274
+ ];
275
+ return elementBase('poetry-couplet', containers, settings);
276
+ }
277
+ const children = flowBlocks(el);
278
+ return elementBase('poetry-couplet', children.length > 0 ? children : [paragraph(), paragraph()], settings);
279
+ }
254
280
  function convertFootnoteList(el) {
255
281
  const items = [];
256
282
  Array.from(el.children).forEach((child) => {
@@ -266,6 +292,8 @@ function convertFootnoteList(el) {
266
292
  Array.from(clone.querySelectorAll('a'))
267
293
  .filter((a) => a.getAttribute('href') === `#fnref-${footnoteId}`)
268
294
  .forEach((a) => a.remove());
295
+ // Likewise the "[n]" marker our serializer writes in front of the note.
296
+ clone.querySelectorAll('[data-likhari-footnote-number]').forEach((n) => n.remove());
269
297
  const children = flowBlocks(clone);
270
298
  items.push(elementBase('footnote-item', children.length > 0 ? children : [paragraph()], { footnoteId }));
271
299
  });
@@ -341,6 +369,8 @@ function convertBlock(el) {
341
369
  return [{ type: 'page-break', version: 1 }];
342
370
  if (el.hasAttribute('data-likhari-layout-container'))
343
371
  return [convertLayout(el)];
372
+ if (el.hasAttribute('data-likhari-poetry-layout'))
373
+ return [convertPoetry(el)];
344
374
  return flowBlocks(el);
345
375
  case 'figcaption':
346
376
  return [];
@@ -4,5 +4,7 @@ import { type SNode } from '../shared/serialized.js';
4
4
  export declare function escapeHtml(value: string): string;
5
5
  export declare function escapeAttr(value: string): string;
6
6
  export declare function nodeToHtml(node: SNode, ctx?: ConverterContext): string;
7
+ /** Lets another converter (Markdown's HTML-table fallback) number footnotes the same way. */
8
+ export declare function setFootnoteOrder(blocks: SNode[]): void;
7
9
  export declare function serializeHtml(state: SerializedEditorState, ctx?: ConverterContext): string;
8
10
  //# sourceMappingURL=serialize.d.ts.map
@@ -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,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"}
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;AAIjD,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;AAiOD,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CA+CtE;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAEtD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAI1F"}
@@ -1,3 +1,4 @@
1
+ import { POETRY_CENTERED_WIDTH, POETRY_COUPLET_GAP, POETRY_GUTTER_PX, POETRY_RULE, POETRY_STAGGER_WIDTH, layoutOrDefault, scaleOrDefault, } from '../shared/poetry.js';
1
2
  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
3
  export function escapeHtml(value) {
3
4
  return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -69,6 +70,13 @@ function listItemToHtml(item, listType, ctx) {
69
70
  const checked = listType === 'check' ? ` data-checked="${item.checked ? 'true' : 'false'}"` : '';
70
71
  return `<li${blockAttrs(item, checked)}>${inlineChildren(item, ctx)}</li>`;
71
72
  }
73
+ /** Table and footnote styling matching editor.css, written inline so exported HTML keeps it. */
74
+ const TABLE_BORDER = '#cfcac0';
75
+ const TABLE_HEADER_BG = '#f1efe9';
76
+ const TABLE_STYLE = 'border-collapse: collapse; margin: 1em 0; width: 100%';
77
+ const CELL_STYLE = `border: 1px solid ${TABLE_BORDER}; padding: 6px 10px; min-width: 72px; vertical-align: top`;
78
+ const HEADER_CELL_STYLE = `background: ${TABLE_HEADER_BG}; font-weight: 600; text-align: start`;
79
+ const FOOTNOTE_LIST_STYLE = `list-style: none; margin: 2em 0 0 0; padding: 0.75em 0 0 1.5em; border-top: 1px solid ${TABLE_BORDER}; font-size: 0.9em`;
72
80
  function tableToHtml(node, ctx) {
73
81
  const rows = (node.children ?? [])
74
82
  .map((row) => {
@@ -79,13 +87,14 @@ function tableToHtml(node, ctx) {
79
87
  const span = (Number(cell.colSpan) > 1 ? ` colspan="${Number(cell.colSpan)}"` : '') +
80
88
  (Number(cell.rowSpan) > 1 ? ` rowspan="${Number(cell.rowSpan)}"` : '');
81
89
  const content = (cell.children ?? []).map((child) => nodeToHtml(child, ctx)).join('');
82
- return `<${tag}${span}>${content}</${tag}>`;
90
+ const style = header ? `${CELL_STYLE}; ${HEADER_CELL_STYLE}` : CELL_STYLE;
91
+ return `<${tag}${span} style="${escapeAttr(style)}">${content}</${tag}>`;
83
92
  })
84
93
  .join('');
85
94
  return `<tr>${cells}</tr>`;
86
95
  })
87
96
  .join('');
88
- return `<table><tbody>${rows}</tbody></table>`;
97
+ return `<table style="${escapeAttr(TABLE_STYLE)}"><tbody>${rows}</tbody></table>`;
89
98
  }
90
99
  /** Set once per serializeHtml() call and read by the footnote cases below —
91
100
  * avoids threading a numbering map through every recursive nodeToHtml call
@@ -102,10 +111,11 @@ function footnoteListToHtml(node, ctx) {
102
111
  .map((item) => {
103
112
  const id = String(item.footnoteId ?? '');
104
113
  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>`;
114
+ const number = `<span data-likhari-footnote-number style="font-weight: 600">[${footnoteOrder.get(id) ?? '?'}] </span>`;
115
+ return `<li id="fn-${escapeAttr(id)}" data-likhari-footnote-item="${escapeAttr(id)}" style="margin: 0.3em 0">${number}${body} <a href="#fnref-${escapeAttr(id)}">↩</a></li>`;
106
116
  })
107
117
  .join('');
108
- return `<ol data-likhari-footnote-list>${items}</ol>`;
118
+ return `<ol data-likhari-footnote-list style="${escapeAttr(FOOTNOTE_LIST_STYLE)}">${items}</ol>`;
109
119
  }
110
120
  function layoutToHtml(node, ctx) {
111
121
  const items = node.children ?? [];
@@ -115,6 +125,92 @@ function layoutToHtml(node, ctx) {
115
125
  .join('');
116
126
  return `<div data-likhari-layout-container style="display: grid; grid-template-columns: ${escapeAttr(templateColumns)}">${columns}</div>`;
117
127
  }
128
+ function firstDirection(nodes) {
129
+ for (const node of nodes) {
130
+ if (node.direction === 'rtl' || node.direction === 'ltr')
131
+ return node.direction;
132
+ const inner = firstDirection(node.children ?? []);
133
+ if (inner)
134
+ return inner;
135
+ }
136
+ return 'ltr';
137
+ }
138
+ /** One misra line; styles are inline so the exported page keeps the editor's look. */
139
+ function poetryLineToHtml(line, extraStyle, ctx) {
140
+ const dir = line.direction === 'rtl' || line.direction === 'ltr' ? ` dir="${line.direction}"` : '';
141
+ const style = ['margin: 0', extraStyle].filter(Boolean).join('; ');
142
+ return `<p${dir} style="${escapeAttr(style)}">${inlineChildren(line, ctx)}</p>`;
143
+ }
144
+ function poetryToHtml(node, ctx) {
145
+ const layout = layoutOrDefault(node.layout);
146
+ const spacing = scaleOrDefault(node.spacing);
147
+ const gutter = scaleOrDefault(node.gutter);
148
+ const stagger = scaleOrDefault(node.stagger);
149
+ const width = typeof node.width === 'number' && node.width > 0 ? node.width : undefined;
150
+ const children = node.children ?? [];
151
+ const dir = firstDirection(children);
152
+ const gap = POETRY_COUPLET_GAP[spacing];
153
+ const separator = `border-top: ${POETRY_RULE}; margin-top: ${gap}; padding-top: calc(${gap} * 0.6)`;
154
+ let body;
155
+ if (layout === 'two-column') {
156
+ body = children
157
+ .map((row, r) => {
158
+ const items = row.children ?? [];
159
+ const centered = items.length === 1;
160
+ const templateColumns = typeof row.templateColumns === 'string' ? row.templateColumns : `repeat(${Math.max(items.length, 1)}, 1fr)`;
161
+ const rowStyle = [
162
+ 'display: grid',
163
+ 'gap: 0',
164
+ `grid-template-columns: ${templateColumns}`,
165
+ 'margin: 0',
166
+ ...(r > 0 ? [separator] : []),
167
+ ...(centered ? [`width: ${POETRY_CENTERED_WIDTH}`, 'margin-inline: auto'] : []),
168
+ ].join('; ');
169
+ const px = POETRY_GUTTER_PX[gutter];
170
+ const cells = items
171
+ .map((item, i) => {
172
+ const cellStyle = centered
173
+ ? 'padding-inline: 0'
174
+ : i === 0
175
+ ? `padding-inline-end: ${px}px`
176
+ : `border-inline-start: ${POETRY_RULE}; padding-inline-start: ${px}px`;
177
+ const lines = (item.children ?? []).map((line) => poetryLineToHtml(line, '', ctx)).join('');
178
+ return `<div data-likhari-layout-item style="${escapeAttr(cellStyle)}">${lines}</div>`;
179
+ })
180
+ .join('');
181
+ return `<div data-likhari-layout-container style="${escapeAttr(rowStyle)}">${cells}</div>`;
182
+ })
183
+ .join('');
184
+ }
185
+ else {
186
+ body = children
187
+ .map((line, i) => {
188
+ const couplet = Math.floor(i / 2);
189
+ const parts = [];
190
+ if (i % 2 === 0 && couplet > 0)
191
+ parts.push(separator);
192
+ if (layout === 'staggered') {
193
+ parts.push(`width: ${POETRY_STAGGER_WIDTH[stagger]}`);
194
+ parts.push(couplet % 2 === 0 ? 'margin-inline-end: auto' : 'margin-inline-start: auto');
195
+ }
196
+ return poetryLineToHtml(line, parts.join('; '), ctx);
197
+ })
198
+ .join('');
199
+ }
200
+ const rtl = dir === 'rtl';
201
+ const blockStyle = [
202
+ 'box-sizing: border-box',
203
+ 'margin: 0 auto 1.4em auto',
204
+ `max-width: ${width !== undefined ? `${width}px` : '70%'}`,
205
+ 'padding: 10px 16px',
206
+ 'text-align: justify',
207
+ 'text-align-last: justify',
208
+ `font-size: ${rtl ? '20px' : '15px'}`,
209
+ `line-height: ${rtl ? '2.1' : '1.6'}`,
210
+ ].join('; ');
211
+ const widthAttr = width !== undefined ? ` data-likhari-poetry-width="${width}"` : '';
212
+ return `<div data-likhari-poetry-layout="${layout}" data-likhari-poetry-spacing="${spacing}" data-likhari-poetry-gutter="${gutter}" data-likhari-poetry-stagger="${stagger}"${widthAttr} dir="${dir}" style="${escapeAttr(blockStyle)}">${body}</div>`;
213
+ }
118
214
  function imageToHtml(node, ctx) {
119
215
  const rawSrc = String(node.src ?? '');
120
216
  const src = ctx?.resolveImageUrl ? ctx.resolveImageUrl(rawSrc) : rawSrc;
@@ -167,13 +263,19 @@ export function nodeToHtml(node, ctx) {
167
263
  return footnoteReferenceToHtml(node);
168
264
  case 'footnote-list':
169
265
  return footnoteListToHtml(node, ctx);
266
+ case 'poetry-couplet':
267
+ return poetryToHtml(node, ctx);
170
268
  default:
171
269
  // Unknown node (e.g. a future feature): keep its content, drop the wrapper.
172
270
  return inlineChildren(node, ctx);
173
271
  }
174
272
  }
273
+ /** Lets another converter (Markdown's HTML-table fallback) number footnotes the same way. */
274
+ export function setFootnoteOrder(blocks) {
275
+ footnoteOrder = collectFootnoteOrder(blocks);
276
+ }
175
277
  export function serializeHtml(state, ctx) {
176
278
  const blocks = rootChildren(state);
177
- footnoteOrder = collectFootnoteOrder(blocks);
279
+ setFootnoteOrder(blocks);
178
280
  return blocks.map((node) => nodeToHtml(node, ctx)).join('\n');
179
281
  }
@@ -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;AAkTjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAO3F"}
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;AAiUjD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,qBAAqB,CAO3F"}
@@ -2,8 +2,9 @@ import { unified } from 'unified';
2
2
  import remarkParse from 'remark-parse';
3
3
  import remarkGfm from 'remark-gfm';
4
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';
5
+ import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, elementBase, makeState, paragraph, rootChildren, textNode, } from '../shared/serialized.js';
6
+ import { parseHtml, safeImageSrc, safeUrl } from '../html/parse.js';
7
+ import { layoutOrDefault, scaleOrDefault } from '../shared/poetry.js';
7
8
  const processor = unified().use(remarkParse).use(remarkGfm).use(remarkDirective);
8
9
  const ALIGNMENTS = new Set(['left', 'right', 'center', 'justify', 'start', 'end']);
9
10
  const DIRECTIVE_FORMATS = {
@@ -225,6 +226,9 @@ function blocks(nodes, extra = {}) {
225
226
  case 'table':
226
227
  out.push(tableBlock(node));
227
228
  break;
229
+ case 'html':
230
+ out.push(...rootChildren(parseHtml(node.value ?? '')).filter((n) => n.type === 'table'));
231
+ break;
228
232
  case 'containerDirective':
229
233
  if (node.name === 'columns') {
230
234
  const columns = (node.children ?? [])
@@ -237,6 +241,18 @@ function blocks(nodes, extra = {}) {
237
241
  out.push(elementBase('layout-container', columns, { templateColumns: `repeat(${columns.length}, 1fr)` }));
238
242
  }
239
243
  }
244
+ else if (node.name === 'poetry') {
245
+ const attrs = node.attributes ?? {};
246
+ const settings = {
247
+ layout: layoutOrDefault(attrs.layout),
248
+ spacing: scaleOrDefault(attrs.spacing),
249
+ gutter: scaleOrDefault(attrs.gutter),
250
+ stagger: scaleOrDefault(attrs.stagger),
251
+ ...(Number(attrs.width) > 0 ? { width: Number(attrs.width) } : {}),
252
+ };
253
+ const children = blocks(node.children ?? []);
254
+ out.push(elementBase('poetry-couplet', children.length > 0 ? children : [paragraph(), paragraph()], settings));
255
+ }
240
256
  else {
241
257
  // Unknown containers (including a stray "column" outside "columns")
242
258
  // are transparent: their content is kept.
@@ -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;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"}
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;AA2C7D,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;AAuJD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,EAAE,GAAG,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAM9F"}
@@ -4,6 +4,8 @@ import remarkGfm from 'remark-gfm';
4
4
  import remarkDirective from 'remark-directive';
5
5
  import { FORMAT_BOLD, FORMAT_CODE, FORMAT_HIGHLIGHT, FORMAT_ITALIC, FORMAT_STRIKETHROUGH, FORMAT_SUBSCRIPT, FORMAT_SUPERSCRIPT, FORMAT_UNDERLINE, rootChildren, } from '../shared/serialized.js';
6
6
  import { directive } from './mdast.js';
7
+ import { layoutOrDefault, scaleOrDefault } from '../shared/poetry.js';
8
+ import { nodeToHtml, setFootnoteOrder } from '../html/serialize.js';
7
9
  const processor = unified()
8
10
  .use(remarkGfm)
9
11
  .use(remarkDirective)
@@ -246,7 +248,29 @@ function layoutToMd(node, ctx) {
246
248
  const columns = items.map((item) => directive('containerDirective', 'column', {}, (item.children ?? []).flatMap((child) => blockToMd(child, ctx))));
247
249
  return directive('containerDirective', 'columns', { count: String(items.length) }, columns);
248
250
  }
251
+ function poetryToMd(node, ctx) {
252
+ const attrs = { layout: layoutOrDefault(node.layout) };
253
+ for (const key of ['spacing', 'gutter', 'stagger']) {
254
+ const value = scaleOrDefault(node[key]);
255
+ if (value !== 'normal')
256
+ attrs[key] = value;
257
+ }
258
+ if (typeof node.width === 'number' && node.width > 0)
259
+ attrs.width = String(node.width);
260
+ const inner = (node.children ?? []).flatMap((child) => blockToMd(child, ctx));
261
+ return directive('containerDirective', 'poetry', attrs, inner);
262
+ }
263
+ /** Markdown tables cannot express merged cells or multi-block cells, so those
264
+ * are written as an HTML table block, read back by the HTML importer. */
265
+ function needsHtmlTable(node) {
266
+ return (node.children ?? []).some((row) => (row.children ?? []).some((cell) => Number(cell.colSpan ?? 1) > 1 ||
267
+ Number(cell.rowSpan ?? 1) > 1 ||
268
+ (cell.children ?? []).length !== 1 ||
269
+ cell.children[0].type !== 'paragraph'));
270
+ }
249
271
  function tableToMd(node, ctx) {
272
+ if (needsHtmlTable(node))
273
+ return { type: 'html', value: nodeToHtml(node, ctx) };
250
274
  const rows = (node.children ?? []).map((row) => ({
251
275
  type: 'tableRow',
252
276
  children: (row.children ?? []).map((cell) => {
@@ -289,6 +313,8 @@ function blockToMd(node, ctx) {
289
313
  return [tableToMd(node, ctx)];
290
314
  case 'layout-container':
291
315
  return [layoutToMd(node, ctx)];
316
+ case 'poetry-couplet':
317
+ return [poetryToMd(node, ctx)];
292
318
  case 'footnote-list':
293
319
  return (node.children ?? []).map((item) => ({
294
320
  type: 'footnoteDefinition',
@@ -302,7 +328,9 @@ function blockToMd(node, ctx) {
302
328
  }
303
329
  }
304
330
  export function serializeMarkdown(state, ctx) {
305
- const tree = { type: 'root', children: rootChildren(state).flatMap((n) => blockToMd(n, ctx)) };
331
+ const blocks = rootChildren(state);
332
+ setFootnoteOrder(blocks);
333
+ const tree = { type: 'root', children: blocks.flatMap((n) => blockToMd(n, ctx)) };
306
334
  // remark-stringify consumes mdast; our MdNode is a structural subset of it.
307
335
  return String(processor.stringify(tree)).replace(/\n+$/, '\n').replace(/^\n+$/, '');
308
336
  }
@@ -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;AA6EhD,eAAO,MAAM,kBAAkB,EAAE,eAsDhC,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;AA4FhD,eAAO,MAAM,kBAAkB,EAAE,eAsDhC,CAAC"}
@@ -27,6 +27,18 @@ function nodeToText(node, order, listItemIndex = 0) {
27
27
  const id = String(node.footnoteId ?? '');
28
28
  return `[${order.get(id) ?? '?'}]`;
29
29
  }
30
+ // Linearized per the fidelity matrix ("Poetry layout variants ⚠️ linearized"):
31
+ // one misra per line, regardless of single/two-column layout and however
32
+ // many couplets the block holds — in two-column layout each couplet is
33
+ // its own layout-container row, so each contributes its two item lines
34
+ // in turn rather than only the block's first couplet being read.
35
+ if (node.type === 'poetry-couplet') {
36
+ const children = withChildren.children ?? [];
37
+ const lines = children.flatMap((child) => child.type === 'layout-container'
38
+ ? (child.children ?? []).map((item) => nodeToText(item, order))
39
+ : [nodeToText(child, order)]);
40
+ return lines.join('\n');
41
+ }
30
42
  if (node.type === 'list' && withChildren.children) {
31
43
  const listType = node.listType ?? 'bullet';
32
44
  return withChildren.children
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Poetry block settings shared by the HTML and Markdown converters. The CSS
3
+ * values mirror the editor's own (packages/react/src/theme/editor.css), so an
4
+ * exported page looks the same outside the editor.
5
+ */
6
+ export declare const POETRY_LAYOUTS: readonly ["single", "two-column", "staggered"];
7
+ export type PoetryLayout = (typeof POETRY_LAYOUTS)[number];
8
+ export declare const POETRY_SCALE_NAMES: readonly ["compact", "normal", "relaxed", "loose"];
9
+ export type PoetryScaleName = (typeof POETRY_SCALE_NAMES)[number];
10
+ export declare const POETRY_COUPLET_GAP: Record<PoetryScaleName, string>;
11
+ export declare const POETRY_GUTTER_PX: Record<PoetryScaleName, number>;
12
+ export declare const POETRY_STAGGER_WIDTH: Record<PoetryScaleName, string>;
13
+ export declare const POETRY_CENTERED_WIDTH = "60%";
14
+ export declare const POETRY_RULE = "1px dashed #8a8880";
15
+ export declare function scaleOrDefault(value: unknown): PoetryScaleName;
16
+ export declare function layoutOrDefault(value: unknown): PoetryLayout;
17
+ //# sourceMappingURL=poetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poetry.d.ts","sourceRoot":"","sources":["../../src/shared/poetry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,cAAc,gDAAiD,CAAC;AAC7E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,eAAO,MAAM,kBAAkB,oDAAqD,CAAC;AACrF,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAK9D,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAK5D,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAKhE,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAC3C,eAAO,MAAM,WAAW,uBAAuB,CAAC;AAEhD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAE9D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAE5D"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Poetry block settings shared by the HTML and Markdown converters. The CSS
3
+ * values mirror the editor's own (packages/react/src/theme/editor.css), so an
4
+ * exported page looks the same outside the editor.
5
+ */
6
+ export const POETRY_LAYOUTS = ['single', 'two-column', 'staggered'];
7
+ export const POETRY_SCALE_NAMES = ['compact', 'normal', 'relaxed', 'loose'];
8
+ export const POETRY_COUPLET_GAP = {
9
+ compact: '0.4em',
10
+ normal: '1em',
11
+ relaxed: '1.8em',
12
+ loose: '2.8em',
13
+ };
14
+ export const POETRY_GUTTER_PX = {
15
+ compact: 8,
16
+ normal: 20,
17
+ relaxed: 32,
18
+ loose: 48,
19
+ };
20
+ export const POETRY_STAGGER_WIDTH = {
21
+ compact: '50%',
22
+ normal: '65%',
23
+ relaxed: '75%',
24
+ loose: '85%',
25
+ };
26
+ export const POETRY_CENTERED_WIDTH = '60%';
27
+ export const POETRY_RULE = '1px dashed #8a8880';
28
+ export function scaleOrDefault(value) {
29
+ return POETRY_SCALE_NAMES.find((name) => name === value) ?? 'normal';
30
+ }
31
+ export function layoutOrDefault(value) {
32
+ return POETRY_LAYOUTS.find((name) => name === value) ?? 'single';
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-converters",
3
- "version": "0.1.18",
3
+ "version": "0.1.19-dev.184",
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": {