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