@willwang-io/react-djot 0.1.4 → 0.1.5

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { parse } from '@djot/djot';
2
- import { Fragment, createElement } from 'react';
2
+ import { Fragment, createElement, isValidElement, cloneElement } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
5
5
  // src/Djot.tsx
@@ -18,14 +18,86 @@ function pickComponent(components, primary, alias) {
18
18
  }
19
19
  return components[primary] ?? (alias ? components[alias] : void 0);
20
20
  }
21
- function renderChildren(children, components) {
21
+ function renderChildren(children, components, footnoteState) {
22
22
  return children.map(
23
23
  (child, index) => renderNode(child, {
24
24
  components,
25
+ footnoteState,
25
26
  key: index
26
27
  })
27
28
  );
28
29
  }
30
+ function collectFootnoteReferences(nodes, indexByLabel, order) {
31
+ for (const node of nodes) {
32
+ if (node.tag === "footnote_reference") {
33
+ const label = node.text;
34
+ if (!indexByLabel.has(label)) {
35
+ const index = order.length + 1;
36
+ indexByLabel.set(label, index);
37
+ order.push(label);
38
+ }
39
+ continue;
40
+ }
41
+ if (isParentNode(node)) {
42
+ collectFootnoteReferences(node.children, indexByLabel, order);
43
+ }
44
+ }
45
+ }
46
+ function createFootnoteState(node) {
47
+ const indexByLabel = /* @__PURE__ */ new Map();
48
+ const order = [];
49
+ collectFootnoteReferences(node.children, indexByLabel, order);
50
+ return {
51
+ firstRefIdByLabel: /* @__PURE__ */ new Map(),
52
+ indexByLabel,
53
+ order,
54
+ refCountByLabel: /* @__PURE__ */ new Map()
55
+ };
56
+ }
57
+ function ensureFootnoteIndex(label, footnoteState) {
58
+ const existing = footnoteState.indexByLabel.get(label);
59
+ if (existing) {
60
+ return existing;
61
+ }
62
+ const index = footnoteState.order.length + 1;
63
+ footnoteState.indexByLabel.set(label, index);
64
+ footnoteState.order.push(label);
65
+ return index;
66
+ }
67
+ function appendBacklink(nodes, backlink) {
68
+ if (nodes.length === 0) {
69
+ return [backlink];
70
+ }
71
+ const next = nodes.slice();
72
+ const lastIndex = next.length - 1;
73
+ const last = next[lastIndex];
74
+ if (isValidElement(last)) {
75
+ next[lastIndex] = cloneElement(last, void 0, last.props.children, backlink);
76
+ return next;
77
+ }
78
+ next.push(backlink);
79
+ return next;
80
+ }
81
+ function toSmartPunctuation(type, fallback) {
82
+ switch (type) {
83
+ case "left_double_quote":
84
+ return "\u201C";
85
+ case "right_double_quote":
86
+ return "\u201D";
87
+ case "left_single_quote":
88
+ return "\u2018";
89
+ case "right_single_quote":
90
+ return "\u2019";
91
+ case "em_dash":
92
+ return "\u2014";
93
+ case "en_dash":
94
+ return "\u2013";
95
+ case "ellipses":
96
+ return "\u2026";
97
+ default:
98
+ return fallback;
99
+ }
100
+ }
29
101
  function toAltText(nodes) {
30
102
  let output = "";
31
103
  for (const node of nodes) {
@@ -33,9 +105,20 @@ function toAltText(nodes) {
33
105
  case "str":
34
106
  case "code":
35
107
  case "verbatim":
108
+ case "inline_math":
109
+ case "display_math":
36
110
  case "code_block":
37
111
  output += node.text;
38
112
  break;
113
+ case "smart_punctuation":
114
+ output += toSmartPunctuation(node.type, node.text);
115
+ break;
116
+ case "double_quoted":
117
+ output += `\u201C${toAltText(node.children)}\u201D`;
118
+ break;
119
+ case "single_quoted":
120
+ output += `\u2018${toAltText(node.children)}\u2019`;
121
+ break;
39
122
  case "soft_break":
40
123
  case "softbreak":
41
124
  output += " ";
@@ -71,6 +154,32 @@ function withKey(props, key) {
71
154
  key
72
155
  };
73
156
  }
157
+ function toDomPropsFromAttributes(attributes) {
158
+ if (!attributes) {
159
+ return {};
160
+ }
161
+ const props = {};
162
+ for (const [name, value] of Object.entries(attributes)) {
163
+ if (name === "class") {
164
+ props.className = value;
165
+ } else {
166
+ props[name] = value;
167
+ }
168
+ }
169
+ return props;
170
+ }
171
+ function textAlignForCell(align) {
172
+ if (align === "left") {
173
+ return "left";
174
+ }
175
+ if (align === "right") {
176
+ return "right";
177
+ }
178
+ if (align === "center") {
179
+ return "center";
180
+ }
181
+ return void 0;
182
+ }
74
183
  function renderWithOverride(override, fallback, domProps, customProps, key, children) {
75
184
  const Component = override ?? fallback;
76
185
  const props = typeof Component === "string" ? withKey(domProps, key) : withKey(
@@ -83,18 +192,21 @@ function renderWithOverride(override, fallback, domProps, customProps, key, chil
83
192
  return createElement(Component, props, children);
84
193
  }
85
194
  function renderDoc(node, components, key) {
86
- const children = renderChildren(node.children, components);
195
+ const footnoteState = createFootnoteState(node);
196
+ const children = renderChildren(node.children, components, footnoteState);
197
+ const endnotes = renderEndnotes(node, components, footnoteState);
198
+ const allChildren = endnotes ? [...children, endnotes] : children;
87
199
  const Component = pickComponent(components, "doc");
88
200
  if (Component) {
89
201
  if (typeof Component === "string") {
90
- return createElement(Component, withKey({}, key), children);
202
+ return createElement(Component, withKey({}, key), allChildren);
91
203
  }
92
- return createElement(Component, withKey({ node }, key), children);
204
+ return createElement(Component, withKey({ node }, key), allChildren);
93
205
  }
94
- return createElement(Fragment, withKey({}, key), children);
206
+ return createElement(Fragment, withKey({}, key), allChildren);
95
207
  }
96
- function renderSection(node, components, key) {
97
- const children = renderChildren(node.children, components);
208
+ function renderSection(node, components, footnoteState, key) {
209
+ const children = renderChildren(node.children, components, footnoteState);
98
210
  const Component = pickComponent(components, "section");
99
211
  if (Component) {
100
212
  if (typeof Component === "string") {
@@ -113,9 +225,86 @@ function renderSection(node, components, key) {
113
225
  }
114
226
  return createElement(Fragment, withKey({}, key), children);
115
227
  }
116
- function renderHeading(node, components, key) {
228
+ function renderDiv(node, components, footnoteState, key) {
229
+ const children = renderChildren(node.children, components, footnoteState);
230
+ return renderWithOverride(
231
+ pickComponent(components, "div"),
232
+ "div",
233
+ toDomPropsFromAttributes(node.attributes),
234
+ {
235
+ node
236
+ },
237
+ key,
238
+ children
239
+ );
240
+ }
241
+ function renderTable(node, components, footnoteState, key) {
242
+ const children = renderChildren(node.children, components, footnoteState);
243
+ return renderWithOverride(
244
+ pickComponent(components, "table"),
245
+ "table",
246
+ toDomPropsFromAttributes(node.attributes),
247
+ {
248
+ node
249
+ },
250
+ key,
251
+ children
252
+ );
253
+ }
254
+ function renderCaption(node, components, footnoteState, key) {
255
+ const children = renderChildren(node.children, components, footnoteState);
256
+ const Component = pickComponent(components, "caption");
257
+ if (!Component && children.length === 0) {
258
+ return null;
259
+ }
260
+ return renderWithOverride(
261
+ Component,
262
+ "caption",
263
+ toDomPropsFromAttributes(node.attributes),
264
+ {
265
+ node
266
+ },
267
+ key,
268
+ children
269
+ );
270
+ }
271
+ function renderRow(node, components, footnoteState, key) {
272
+ const children = renderChildren(node.children, components, footnoteState);
273
+ return renderWithOverride(
274
+ pickComponent(components, "row"),
275
+ "tr",
276
+ toDomPropsFromAttributes(node.attributes),
277
+ {
278
+ head: node.head,
279
+ node
280
+ },
281
+ key,
282
+ children
283
+ );
284
+ }
285
+ function renderCell(node, components, footnoteState, key) {
286
+ const children = renderChildren(node.children, components, footnoteState);
287
+ const textAlign = textAlignForCell(node.align);
288
+ const domProps = {
289
+ ...toDomPropsFromAttributes(node.attributes),
290
+ style: textAlign ? { textAlign } : void 0
291
+ };
292
+ return renderWithOverride(
293
+ pickComponent(components, "cell"),
294
+ node.head ? "th" : "td",
295
+ domProps,
296
+ {
297
+ align: node.align,
298
+ head: node.head,
299
+ node
300
+ },
301
+ key,
302
+ children
303
+ );
304
+ }
305
+ function renderHeading(node, components, footnoteState, key) {
117
306
  const level = clampHeadingLevel(node.level);
118
- const children = renderChildren(node.children, components);
307
+ const children = renderChildren(node.children, components, footnoteState);
119
308
  return renderWithOverride(
120
309
  pickComponent(components, "heading"),
121
310
  `h${level}`,
@@ -128,6 +317,230 @@ function renderHeading(node, components, key) {
128
317
  children
129
318
  );
130
319
  }
320
+ function renderMark(node, components, footnoteState, key, primary, alias) {
321
+ const children = renderChildren(node.children, components, footnoteState);
322
+ return renderWithOverride(
323
+ pickComponent(components, primary, alias),
324
+ "mark",
325
+ {},
326
+ {
327
+ node
328
+ },
329
+ key,
330
+ children
331
+ );
332
+ }
333
+ function renderSuperscript(node, components, footnoteState, key, primary, alias) {
334
+ const children = renderChildren(node.children, components, footnoteState);
335
+ return renderWithOverride(
336
+ pickComponent(components, primary, alias),
337
+ "sup",
338
+ {},
339
+ {
340
+ node
341
+ },
342
+ key,
343
+ children
344
+ );
345
+ }
346
+ function renderSubscript(node, components, footnoteState, key) {
347
+ const children = renderChildren(node.children, components, footnoteState);
348
+ return renderWithOverride(
349
+ pickComponent(components, "subscript"),
350
+ "sub",
351
+ {},
352
+ {
353
+ node
354
+ },
355
+ key,
356
+ children
357
+ );
358
+ }
359
+ function renderInsert(node, components, footnoteState, key) {
360
+ const children = renderChildren(node.children, components, footnoteState);
361
+ return renderWithOverride(
362
+ pickComponent(components, "insert"),
363
+ "ins",
364
+ {},
365
+ {
366
+ node
367
+ },
368
+ key,
369
+ children
370
+ );
371
+ }
372
+ function renderDelete(node, components, footnoteState, key) {
373
+ const children = renderChildren(node.children, components, footnoteState);
374
+ return renderWithOverride(
375
+ pickComponent(components, "delete"),
376
+ "del",
377
+ {},
378
+ {
379
+ node
380
+ },
381
+ key,
382
+ children
383
+ );
384
+ }
385
+ function renderFootnoteReference(node, components, footnoteState, key) {
386
+ const label = node.text;
387
+ const index = footnoteState ? ensureFootnoteIndex(label, footnoteState) : 1;
388
+ const refCount = (footnoteState?.refCountByLabel.get(label) ?? 0) + 1;
389
+ if (footnoteState) {
390
+ footnoteState.refCountByLabel.set(label, refCount);
391
+ }
392
+ const id = refCount === 1 ? `fnref${index}` : `fnref${index}-${refCount}`;
393
+ if (footnoteState && !footnoteState.firstRefIdByLabel.has(label)) {
394
+ footnoteState.firstRefIdByLabel.set(label, id);
395
+ }
396
+ const href = `#fn${index}`;
397
+ const children = createElement("sup", null, index);
398
+ return renderWithOverride(
399
+ pickComponent(components, "footnote_reference"),
400
+ "a",
401
+ {
402
+ href,
403
+ id,
404
+ role: "doc-noteref"
405
+ },
406
+ {
407
+ index,
408
+ label,
409
+ node
410
+ },
411
+ key,
412
+ children
413
+ );
414
+ }
415
+ function renderEndnotes(node, components, footnoteState) {
416
+ if (footnoteState.order.length === 0) {
417
+ return null;
418
+ }
419
+ const items = footnoteState.order.map((label, itemIndex) => {
420
+ const index = itemIndex + 1;
421
+ const footnoteNode = node.footnotes?.[label] ?? {
422
+ children: [],
423
+ label,
424
+ tag: "footnote"
425
+ };
426
+ const footnoteChildren = renderChildren(footnoteNode.children, components, footnoteState);
427
+ const backlink = createElement(
428
+ "a",
429
+ {
430
+ href: `#${footnoteState.firstRefIdByLabel.get(label) ?? `fnref${index}`}`,
431
+ role: "doc-backlink"
432
+ },
433
+ "\u21A9\uFE0E"
434
+ );
435
+ const content = appendBacklink(footnoteChildren, backlink);
436
+ return renderWithOverride(
437
+ pickComponent(components, "footnote"),
438
+ "li",
439
+ {
440
+ id: `fn${index}`,
441
+ key: label
442
+ },
443
+ {
444
+ index,
445
+ label,
446
+ node: footnoteNode
447
+ },
448
+ label,
449
+ content
450
+ );
451
+ });
452
+ const ol = createElement("ol", null, items);
453
+ const sectionChildren = [createElement("hr", { key: "hr" }), createElement(Fragment, { key: "ol" }, ol)];
454
+ return renderWithOverride(
455
+ pickComponent(components, "endnotes"),
456
+ "section",
457
+ {
458
+ role: "doc-endnotes"
459
+ },
460
+ {
461
+ node,
462
+ order: footnoteState.order
463
+ },
464
+ "endnotes",
465
+ sectionChildren
466
+ );
467
+ }
468
+ function renderQuoted(node, components, footnoteState, key, primary, alias, openQuote, closeQuote) {
469
+ const children = renderChildren(node.children, components, footnoteState);
470
+ const Component = pickComponent(components, primary, alias);
471
+ if (!Component) {
472
+ return createElement(Fragment, withKey({}, key), openQuote, children, closeQuote);
473
+ }
474
+ if (typeof Component === "string") {
475
+ return createElement(Component, withKey({}, key), openQuote, children, closeQuote);
476
+ }
477
+ return createElement(
478
+ Component,
479
+ withKey(
480
+ {
481
+ node
482
+ },
483
+ key
484
+ ),
485
+ openQuote,
486
+ children,
487
+ closeQuote
488
+ );
489
+ }
490
+ function renderSmartPunctuation(node, components, key) {
491
+ const value = toSmartPunctuation(node.type, node.text);
492
+ const Component = pickComponent(components, "smart_punctuation");
493
+ if (!Component) {
494
+ return value;
495
+ }
496
+ if (typeof Component === "string") {
497
+ return createElement(Component, withKey({}, key), value);
498
+ }
499
+ return createElement(
500
+ Component,
501
+ withKey(
502
+ {
503
+ kind: node.type,
504
+ node,
505
+ value
506
+ },
507
+ key
508
+ ),
509
+ value
510
+ );
511
+ }
512
+ function renderInlineMath(node, components, key) {
513
+ const value = node.text;
514
+ return renderWithOverride(
515
+ pickComponent(components, "inline_math"),
516
+ "span",
517
+ {
518
+ className: "math inline"
519
+ },
520
+ {
521
+ node,
522
+ value
523
+ },
524
+ key,
525
+ `\\(${value}\\)`
526
+ );
527
+ }
528
+ function renderDisplayMath(node, components, key) {
529
+ const value = node.text;
530
+ return renderWithOverride(
531
+ pickComponent(components, "display_math"),
532
+ "span",
533
+ {
534
+ className: "math display"
535
+ },
536
+ {
537
+ node,
538
+ value
539
+ },
540
+ key,
541
+ `\\[${value}\\]`
542
+ );
543
+ }
131
544
  function renderCode(node, components, key, primary, alias) {
132
545
  const value = node.text;
133
546
  return renderWithOverride(
@@ -165,8 +578,8 @@ function renderCodeBlock(node, components, key) {
165
578
  fallbackChildren
166
579
  );
167
580
  }
168
- function renderLink(node, components, key) {
169
- const children = renderChildren(node.children, components);
581
+ function renderLink(node, components, footnoteState, key) {
582
+ const children = renderChildren(node.children, components, footnoteState);
170
583
  const href = node.destination;
171
584
  return renderWithOverride(
172
585
  pickComponent(components, "link"),
@@ -198,8 +611,8 @@ function renderImage(node, components, key) {
198
611
  key
199
612
  );
200
613
  }
201
- function renderOrderedList(node, components, key) {
202
- const children = renderChildren(node.children, components);
614
+ function renderOrderedList(node, components, footnoteState, key) {
615
+ const children = renderChildren(node.children, components, footnoteState);
203
616
  return renderWithOverride(
204
617
  pickComponent(components, "ordered_list"),
205
618
  "ol",
@@ -214,8 +627,8 @@ function renderOrderedList(node, components, key) {
214
627
  children
215
628
  );
216
629
  }
217
- function renderBlockQuote(node, components, key, primary, alias) {
218
- const children = renderChildren(node.children, components);
630
+ function renderBlockQuote(node, components, footnoteState, key, primary, alias) {
631
+ const children = renderChildren(node.children, components, footnoteState);
219
632
  return renderWithOverride(
220
633
  pickComponent(components, primary, alias),
221
634
  "blockquote",
@@ -279,13 +692,23 @@ function renderHardBreak(node, components, key) {
279
692
  );
280
693
  }
281
694
  function renderNode(node, options = {}) {
282
- const { components, key } = options;
283
- const children = isParentNode(node) ? renderChildren(node.children, components) : void 0;
695
+ const { components, footnoteState, key } = options;
696
+ const children = isParentNode(node) ? renderChildren(node.children, components, footnoteState) : void 0;
284
697
  switch (node.tag) {
285
698
  case "doc":
286
699
  return renderDoc(node, components, key);
287
700
  case "section":
288
- return renderSection(node, components, key);
701
+ return renderSection(node, components, footnoteState, key);
702
+ case "div":
703
+ return renderDiv(node, components, footnoteState, key);
704
+ case "table":
705
+ return renderTable(node, components, footnoteState, key);
706
+ case "caption":
707
+ return renderCaption(node, components, footnoteState, key);
708
+ case "row":
709
+ return renderRow(node, components, footnoteState, key);
710
+ case "cell":
711
+ return renderCell(node, components, footnoteState, key);
289
712
  case "para":
290
713
  return renderWithOverride(
291
714
  pickComponent(components, "para"),
@@ -298,7 +721,7 @@ function renderNode(node, options = {}) {
298
721
  children
299
722
  );
300
723
  case "heading":
301
- return renderHeading(node, components, key);
724
+ return renderHeading(node, components, footnoteState, key);
302
725
  case "emph":
303
726
  return renderWithOverride(
304
727
  pickComponent(components, "emph"),
@@ -321,6 +744,63 @@ function renderNode(node, options = {}) {
321
744
  key,
322
745
  children
323
746
  );
747
+ case "mark":
748
+ return renderMark(node, components, footnoteState, key, "mark", "highlighted");
749
+ case "highlighted":
750
+ return renderMark(node, components, footnoteState, key, "highlighted", "mark");
751
+ case "superscript":
752
+ return renderSuperscript(node, components, footnoteState, key, "superscript", "supe");
753
+ case "supe":
754
+ return renderSuperscript(node, components, footnoteState, key, "supe", "superscript");
755
+ case "subscript":
756
+ return renderSubscript(node, components, footnoteState, key);
757
+ case "insert":
758
+ return renderInsert(node, components, footnoteState, key);
759
+ case "delete":
760
+ return renderDelete(node, components, footnoteState, key);
761
+ case "footnote_reference":
762
+ return renderFootnoteReference(node, components, footnoteState, key);
763
+ case "footnote":
764
+ return renderWithOverride(
765
+ pickComponent(components, "footnote"),
766
+ "li",
767
+ {},
768
+ {
769
+ index: 0,
770
+ label: node.label,
771
+ node
772
+ },
773
+ key,
774
+ children
775
+ );
776
+ case "double_quoted":
777
+ return renderQuoted(
778
+ node,
779
+ components,
780
+ footnoteState,
781
+ key,
782
+ "double_quoted",
783
+ "single_quoted",
784
+ "\u201C",
785
+ "\u201D"
786
+ );
787
+ case "single_quoted":
788
+ return renderQuoted(
789
+ node,
790
+ components,
791
+ footnoteState,
792
+ key,
793
+ "single_quoted",
794
+ "double_quoted",
795
+ "\u2018",
796
+ "\u2019"
797
+ );
798
+ case "smart_punctuation":
799
+ return renderSmartPunctuation(node, components, key);
800
+ case "inline_math":
801
+ return renderInlineMath(node, components, key);
802
+ case "display_math":
803
+ return renderDisplayMath(node, components, key);
324
804
  case "code":
325
805
  return renderCode(node, components, key, "code", "verbatim");
326
806
  case "verbatim":
@@ -328,7 +808,7 @@ function renderNode(node, options = {}) {
328
808
  case "code_block":
329
809
  return renderCodeBlock(node, components, key);
330
810
  case "link":
331
- return renderLink(node, components, key);
811
+ return renderLink(node, components, footnoteState, key);
332
812
  case "image":
333
813
  return renderImage(node, components, key);
334
814
  case "bullet_list":
@@ -343,7 +823,7 @@ function renderNode(node, options = {}) {
343
823
  children
344
824
  );
345
825
  case "ordered_list":
346
- return renderOrderedList(node, components, key);
826
+ return renderOrderedList(node, components, footnoteState, key);
347
827
  case "list_item":
348
828
  return renderWithOverride(
349
829
  pickComponent(components, "list_item"),
@@ -356,9 +836,9 @@ function renderNode(node, options = {}) {
356
836
  children
357
837
  );
358
838
  case "blockquote":
359
- return renderBlockQuote(node, components, key, "blockquote", "block_quote");
839
+ return renderBlockQuote(node, components, footnoteState, key, "blockquote", "block_quote");
360
840
  case "block_quote":
361
- return renderBlockQuote(node, components, key, "block_quote", "blockquote");
841
+ return renderBlockQuote(node, components, footnoteState, key, "block_quote", "blockquote");
362
842
  case "thematic_break":
363
843
  return renderWithOverride(
364
844
  pickComponent(components, "thematic_break"),