@nuucognition/ncm-ui 0.1.0-dev.1 → 0.1.1-beta.0

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.
Files changed (67) hide show
  1. package/README.md +90 -0
  2. package/dist/blocks/admonition/index.d.ts +2 -3
  3. package/dist/blocks/admonition/index.js +63 -12
  4. package/dist/blocks/aside/index.d.ts +2 -3
  5. package/dist/blocks/aside/index.js +63 -12
  6. package/dist/blocks/attribution/index.d.ts +2 -3
  7. package/dist/blocks/blockquote/index.d.ts +5 -4
  8. package/dist/blocks/blockquote/index.js +52 -7
  9. package/dist/blocks/chart/index.d.ts +2 -3
  10. package/dist/blocks/code/index.d.ts +2 -3
  11. package/dist/blocks/code/index.js +2 -2
  12. package/dist/blocks/container/index.d.ts +2 -3
  13. package/dist/blocks/container/index.js +50 -5
  14. package/dist/blocks/custom-code/index.d.ts +2 -3
  15. package/dist/blocks/dataview/index.d.ts +2 -3
  16. package/dist/blocks/definition/index.d.ts +2 -3
  17. package/dist/blocks/definition/index.js +63 -12
  18. package/dist/blocks/embed/index.d.ts +2 -3
  19. package/dist/blocks/embedding/index.d.ts +2 -3
  20. package/dist/blocks/embedding/index.js +50 -5
  21. package/dist/blocks/figure/index.d.ts +2 -3
  22. package/dist/blocks/figure/index.js +50 -5
  23. package/dist/blocks/footnote/index.d.ts +2 -3
  24. package/dist/blocks/footnote/index.js +63 -12
  25. package/dist/blocks/frontmatter/index.d.ts +2 -3
  26. package/dist/blocks/header/index.d.ts +2 -3
  27. package/dist/blocks/header/index.js +50 -5
  28. package/dist/blocks/horizontal-rule/index.d.ts +2 -3
  29. package/dist/blocks/html/index.d.ts +3 -4
  30. package/dist/blocks/index.d.ts +1 -2
  31. package/dist/blocks/index.js +63 -12
  32. package/dist/blocks/latex/index.d.ts +2 -3
  33. package/dist/blocks/link/index.d.ts +2 -3
  34. package/dist/blocks/list/index.d.ts +2 -3
  35. package/dist/blocks/list/index.js +57 -7
  36. package/dist/blocks/math/index.d.ts +2 -3
  37. package/dist/blocks/mermaid/index.d.ts +2 -3
  38. package/dist/blocks/page-breaker/index.d.ts +2 -3
  39. package/dist/blocks/paragraph/index.d.ts +2 -3
  40. package/dist/blocks/paragraph/index.js +50 -5
  41. package/dist/blocks/spoiler/index.d.ts +2 -3
  42. package/dist/blocks/spoiler/index.js +63 -12
  43. package/dist/blocks/table/index.d.ts +2 -3
  44. package/dist/blocks/table/index.js +50 -5
  45. package/dist/blocks/task-list/index.d.ts +2 -3
  46. package/dist/blocks/task-list/index.js +52 -6
  47. package/dist/components/index.d.ts +34 -4
  48. package/dist/components/index.js +101 -14
  49. package/dist/components/ncm-block.d.ts +2 -3
  50. package/dist/components/ncm-block.js +63 -12
  51. package/dist/components/ncm-document.d.ts +4 -4
  52. package/dist/components/ncm-document.js +63 -12
  53. package/dist/context/index.d.ts +2 -3
  54. package/dist/hooks/index.d.ts +1 -2
  55. package/dist/hooks/index.js +63 -12
  56. package/dist/hooks/use-block-renderer.js +63 -12
  57. package/dist/hooks/use-ncm-config.d.ts +1 -2
  58. package/dist/index.d.ts +3 -4
  59. package/dist/index.js +102 -15
  60. package/dist/inline/index.d.ts +4 -4
  61. package/dist/inline/index.js +51 -6
  62. package/dist/lib/defaults.js +63 -12
  63. package/dist/{references-Bs5wJybr.d.ts → references-BYW5pnj_.d.ts} +4 -4
  64. package/dist/styles/nuu.css +304 -42
  65. package/dist/styles/paper.css +62 -1
  66. package/dist/{use-ncm-config-D0doMYTH.d.ts → use-ncm-config-CnntcShj.d.ts} +55 -4
  67. package/package.json +5 -4
@@ -130,7 +130,7 @@ function CodeBlock({ block, className }) {
130
130
  "data-ncm-language": block.language,
131
131
  className: cn("relative", className),
132
132
  children: [
133
- codeConfig.copyButton !== false && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
133
+ codeConfig.copyButton === true && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
134
134
  /* @__PURE__ */ jsx(CustomRenderer, { code: block.content, language: block.language }),
135
135
  languageLabel
136
136
  ]
@@ -149,7 +149,7 @@ function CodeBlock({ block, className }) {
149
149
  "data-ncm-language": block.language,
150
150
  className: cn("relative", className),
151
151
  children: [
152
- codeConfig?.copyButton !== false && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
152
+ codeConfig?.copyButton === true && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
153
153
  /* @__PURE__ */ jsx("pre", { "data-ncm-code-pre": "", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
154
154
  codeConfig?.lineNumbers && /* @__PURE__ */ jsx(LineNumbers, { count: lineCount }),
155
155
  /* @__PURE__ */ jsx("code", { "data-ncm-code": "", className: "flex-1", children: /* @__PURE__ */ jsx(DefaultShikiRenderer, { code: block.content, language: block.language }) })
@@ -1,7 +1,6 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { ContainerBlock as ContainerBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type ContainerBlockProps = BlockRendererProps<ContainerBlock$1>;
7
6
  /**
@@ -12,6 +11,6 @@ type ContainerBlockProps = BlockRendererProps<ContainerBlock$1>;
12
11
  * (admonition, figure, tabs…) replace this by promoting upstream or overriding
13
12
  * the `container` renderer key.
14
13
  */
15
- declare function ContainerBlock({ block, className }: ContainerBlockProps): react_jsx_runtime.JSX.Element;
14
+ declare function ContainerBlock({ block, className }: ContainerBlockProps): react.JSX.Element;
16
15
 
17
16
  export { ContainerBlock, type ContainerBlockProps };
@@ -473,9 +473,10 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
473
473
  case "image": {
474
474
  const src = config.inline?.resolveAssetPath?.(token.src) ?? token.src;
475
475
  return /* @__PURE__ */ jsx(
476
- "img",
476
+ EmbedImage,
477
477
  {
478
- "data-ncm-token": "image",
478
+ attrs: { "data-ncm-token": "image" },
479
+ target: token.src,
479
480
  src,
480
481
  alt: token.alt,
481
482
  title: token.title
@@ -509,10 +510,13 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
509
510
  }
510
511
  if (embedType === "image") {
511
512
  return /* @__PURE__ */ jsx(
512
- "img",
513
+ EmbedImage,
513
514
  {
514
- "data-ncm-token": "wiki_embed",
515
- "data-ncm-wikiembed-type": embedType,
515
+ attrs: {
516
+ "data-ncm-token": "wiki_embed",
517
+ "data-ncm-wikiembed-type": embedType
518
+ },
519
+ target: token.target,
516
520
  src: url,
517
521
  alt: token.display ?? token.target,
518
522
  className: config.embeddings?.imageClassName
@@ -543,6 +547,22 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
543
547
  }
544
548
  );
545
549
  }
550
+ const onEmbedClick = config.embeddings?.onEmbedClick;
551
+ if (onEmbedClick) {
552
+ const handleClick = (e) => {
553
+ onEmbedClick(token.target, result, e);
554
+ };
555
+ return /* @__PURE__ */ jsx(
556
+ "button",
557
+ {
558
+ type: "button",
559
+ "data-ncm-token": "wiki_embed",
560
+ "data-ncm-wikiembed-type": embedType,
561
+ onClick: handleClick,
562
+ children: token.display ?? token.target
563
+ }
564
+ );
565
+ }
546
566
  return /* @__PURE__ */ jsx(
547
567
  "a",
548
568
  {
@@ -561,6 +581,31 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
561
581
  return null;
562
582
  }
563
583
  }
584
+ function EmbedImage({ attrs, target, src, alt, title, className }) {
585
+ const config = useNcmConfig();
586
+ const Fallback = config.embeddings?.renderImageError;
587
+ const [failedSrc, setFailedSrc] = useState(null);
588
+ useEffect(() => {
589
+ setFailedSrc(null);
590
+ }, [src]);
591
+ if (!Fallback) {
592
+ return /* @__PURE__ */ jsx("img", { ...attrs, src, alt, title, className });
593
+ }
594
+ if (failedSrc === src) {
595
+ return /* @__PURE__ */ jsx(Fallback, { target, src, alt, retry: () => setFailedSrc(null) });
596
+ }
597
+ return /* @__PURE__ */ jsx(
598
+ "img",
599
+ {
600
+ ...attrs,
601
+ src,
602
+ alt,
603
+ title,
604
+ className,
605
+ onError: () => setFailedSrc(src)
606
+ }
607
+ );
608
+ }
564
609
  function renderReadingText(content, opts) {
565
610
  if (!content) return /* @__PURE__ */ jsx(Fragment, { children: content });
566
611
  const { bionic = false, beeline = false } = opts;
@@ -1,7 +1,6 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { CustomCodeBlock as CustomCodeBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type CustomCodeBlockProps = BlockRendererProps<CustomCodeBlock$1>;
7
6
  /**
@@ -10,6 +9,6 @@ type CustomCodeBlockProps = BlockRendererProps<CustomCodeBlock$1>;
10
9
  * Fallback when no custom renderer is registered for a language.
11
10
  * Custom renderers register via NcmProvider config.renderers.custom_code[language].
12
11
  */
13
- declare function CustomCodeBlock({ block, className }: CustomCodeBlockProps): react_jsx_runtime.JSX.Element;
12
+ declare function CustomCodeBlock({ block, className }: CustomCodeBlockProps): react.JSX.Element;
14
13
 
15
14
  export { CustomCodeBlock, type CustomCodeBlockProps };
@@ -1,7 +1,6 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { DataviewBlock as DataviewBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type DataviewBlockProps = BlockRendererProps<DataviewBlock$1>;
7
6
  /**
@@ -9,6 +8,6 @@ type DataviewBlockProps = BlockRendererProps<DataviewBlock$1>;
9
8
  * engine is bundled here). `data-ncm-query-type` exposes the detected query
10
9
  * kind; override the `dataview` renderer key to execute and render results.
11
10
  */
12
- declare function DataviewBlock({ block, className }: DataviewBlockProps): react_jsx_runtime.JSX.Element;
11
+ declare function DataviewBlock({ block, className }: DataviewBlockProps): react.JSX.Element;
13
12
 
14
13
  export { DataviewBlock, type DataviewBlockProps };
@@ -1,13 +1,12 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { DefinitionBlock as DefinitionBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type DefinitionBlockProps = BlockRendererProps<DefinitionBlock$1>;
7
6
  /**
8
7
  * Definition-list renderer — a term and its definition blocks. The term is
9
8
  * inline content; the definition renders through `NcmBlock`.
10
9
  */
11
- declare function DefinitionBlock({ block, className }: DefinitionBlockProps): react_jsx_runtime.JSX.Element;
10
+ declare function DefinitionBlock({ block, className }: DefinitionBlockProps): react.JSX.Element;
12
11
 
13
12
  export { DefinitionBlock, type DefinitionBlockProps };
@@ -470,9 +470,10 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
470
470
  case "image": {
471
471
  const src = config.inline?.resolveAssetPath?.(token.src) ?? token.src;
472
472
  return /* @__PURE__ */ jsx(
473
- "img",
473
+ EmbedImage,
474
474
  {
475
- "data-ncm-token": "image",
475
+ attrs: { "data-ncm-token": "image" },
476
+ target: token.src,
476
477
  src,
477
478
  alt: token.alt,
478
479
  title: token.title
@@ -506,10 +507,13 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
506
507
  }
507
508
  if (embedType === "image") {
508
509
  return /* @__PURE__ */ jsx(
509
- "img",
510
+ EmbedImage,
510
511
  {
511
- "data-ncm-token": "wiki_embed",
512
- "data-ncm-wikiembed-type": embedType,
512
+ attrs: {
513
+ "data-ncm-token": "wiki_embed",
514
+ "data-ncm-wikiembed-type": embedType
515
+ },
516
+ target: token.target,
513
517
  src: url,
514
518
  alt: token.display ?? token.target,
515
519
  className: config.embeddings?.imageClassName
@@ -540,6 +544,22 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
540
544
  }
541
545
  );
542
546
  }
547
+ const onEmbedClick = config.embeddings?.onEmbedClick;
548
+ if (onEmbedClick) {
549
+ const handleClick = (e) => {
550
+ onEmbedClick(token.target, result, e);
551
+ };
552
+ return /* @__PURE__ */ jsx(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ "data-ncm-token": "wiki_embed",
557
+ "data-ncm-wikiembed-type": embedType,
558
+ onClick: handleClick,
559
+ children: token.display ?? token.target
560
+ }
561
+ );
562
+ }
543
563
  return /* @__PURE__ */ jsx(
544
564
  "a",
545
565
  {
@@ -558,6 +578,31 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
558
578
  return null;
559
579
  }
560
580
  }
581
+ function EmbedImage({ attrs, target, src, alt, title, className }) {
582
+ const config = useNcmConfig();
583
+ const Fallback = config.embeddings?.renderImageError;
584
+ const [failedSrc, setFailedSrc] = useState(null);
585
+ useEffect(() => {
586
+ setFailedSrc(null);
587
+ }, [src]);
588
+ if (!Fallback) {
589
+ return /* @__PURE__ */ jsx("img", { ...attrs, src, alt, title, className });
590
+ }
591
+ if (failedSrc === src) {
592
+ return /* @__PURE__ */ jsx(Fallback, { target, src, alt, retry: () => setFailedSrc(null) });
593
+ }
594
+ return /* @__PURE__ */ jsx(
595
+ "img",
596
+ {
597
+ ...attrs,
598
+ src,
599
+ alt,
600
+ title,
601
+ className,
602
+ onError: () => setFailedSrc(src)
603
+ }
604
+ );
605
+ }
561
606
  function renderReadingText(content, opts) {
562
607
  if (!content) return /* @__PURE__ */ jsx(Fragment, { children: content });
563
608
  const { bionic = false, beeline = false } = opts;
@@ -666,7 +711,7 @@ function AttributionBlock({ block, className }) {
666
711
  );
667
712
  }
668
713
  function BlockquoteBlock({ block, className }) {
669
- const lines = block.content.split("\n").filter((line) => line.trim() !== "");
714
+ const lines = block.content.split("\n");
670
715
  return /* @__PURE__ */ jsx(
671
716
  "blockquote",
672
717
  {
@@ -676,7 +721,7 @@ function BlockquoteBlock({ block, className }) {
676
721
  "data-block-id": block.id,
677
722
  "data-ncm-blockquote": "",
678
723
  className,
679
- children: lines.length <= 1 ? /* @__PURE__ */ jsx(InlineContent, { content: block.content }) : lines.map((line, index) => /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(InlineContent, { content: line }) }, index))
724
+ children: lines.length <= 1 ? /* @__PURE__ */ jsx(InlineContent, { content: block.content }) : lines.map((line, index) => /* @__PURE__ */ jsx("p", { children: line.trim().length > 0 ? /* @__PURE__ */ jsx(InlineContent, { content: line }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xA0" }) }, index))
680
725
  }
681
726
  );
682
727
  }
@@ -816,7 +861,7 @@ function CodeBlock({ block, className }) {
816
861
  "data-ncm-language": block.language,
817
862
  className: cn("relative", className),
818
863
  children: [
819
- codeConfig.copyButton !== false && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
864
+ codeConfig.copyButton === true && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
820
865
  /* @__PURE__ */ jsx(CustomRenderer, { code: block.content, language: block.language }),
821
866
  languageLabel
822
867
  ]
@@ -835,7 +880,7 @@ function CodeBlock({ block, className }) {
835
880
  "data-ncm-language": block.language,
836
881
  className: cn("relative", className),
837
882
  children: [
838
- codeConfig?.copyButton !== false && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
883
+ codeConfig?.copyButton === true && /* @__PURE__ */ jsx(CopyButton, { content: block.content }),
839
884
  /* @__PURE__ */ jsx("pre", { "data-ncm-code-pre": "", children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
840
885
  codeConfig?.lineNumbers && /* @__PURE__ */ jsx(LineNumbers, { count: lineCount }),
841
886
  /* @__PURE__ */ jsx("code", { "data-ncm-code": "", className: "flex-1", children: /* @__PURE__ */ jsx(DefaultShikiRenderer, { code: block.content, language: block.language }) })
@@ -1272,10 +1317,12 @@ function LinkBlock({ block, className }) {
1272
1317
  function ListBlock({ block, className }) {
1273
1318
  const isOrdered = block.type === "list_ordered";
1274
1319
  const Tag = isOrdered ? "ol" : "ul";
1320
+ const start = isOrdered && typeof block.marker === "number" && block.marker !== 1 ? block.marker : void 0;
1275
1321
  return /* @__PURE__ */ jsx(
1276
1322
  Tag,
1277
1323
  {
1278
1324
  role: "list",
1325
+ start,
1279
1326
  "data-ncm-block": block.id,
1280
1327
  "data-ncm-stable": block.stableId,
1281
1328
  "data-ncm-type": block.type,
@@ -1287,6 +1334,9 @@ function ListBlock({ block, className }) {
1287
1334
  }
1288
1335
  );
1289
1336
  }
1337
+ function ItemContent({ content }) {
1338
+ return content.length > 0 ? /* @__PURE__ */ jsx(InlineContent, { content }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xA0" });
1339
+ }
1290
1340
  function nestedItemIdKey(id) {
1291
1341
  if (!id) {
1292
1342
  return void 0;
@@ -1303,7 +1353,7 @@ function ListItems({ items, itemIds, ordered }) {
1303
1353
  return /* @__PURE__ */ jsx(Fragment, { children: items.map((item, index) => {
1304
1354
  const key = nestedItemIdKey(itemIds?.[index]) ?? `item-${index}`;
1305
1355
  if (typeof item === "string") {
1306
- return /* @__PURE__ */ jsx("li", { "data-ncm-list-item": "", children: /* @__PURE__ */ jsx(InlineContent, { content: item }) }, key);
1356
+ return /* @__PURE__ */ jsx("li", { "data-ncm-list-item": "", children: /* @__PURE__ */ jsx(ItemContent, { content: item }) }, key);
1307
1357
  }
1308
1358
  if (isTypedNestedList(item)) {
1309
1359
  return /* @__PURE__ */ jsx(NestedTypedList, { list: item }, key);
@@ -1313,7 +1363,7 @@ function ListItems({ items, itemIds, ordered }) {
1313
1363
  const content = typeof first === "string" ? first : "";
1314
1364
  const nestedContent = processNestedItems(rest, ordered);
1315
1365
  return /* @__PURE__ */ jsxs("li", { "data-ncm-list-item": "", children: [
1316
- /* @__PURE__ */ jsx(InlineContent, { content }),
1366
+ /* @__PURE__ */ jsx(ItemContent, { content }),
1317
1367
  nestedContent
1318
1368
  ] }, key);
1319
1369
  }
@@ -1472,6 +1522,7 @@ function TableBlock({ block, className }) {
1472
1522
  function TaskItems({ items }) {
1473
1523
  return /* @__PURE__ */ jsx(Fragment, { children: items.map((item) => {
1474
1524
  const status = item.checked ? "Completed" : "Incomplete";
1525
+ const text = serializeInlineContent(item.content);
1475
1526
  return /* @__PURE__ */ jsxs(
1476
1527
  "li",
1477
1528
  {
@@ -1498,7 +1549,7 @@ function TaskItems({ items }) {
1498
1549
  ":",
1499
1550
  " "
1500
1551
  ] }),
1501
- /* @__PURE__ */ jsx(InlineContent, { content: serializeInlineContent(item.content) })
1552
+ text.length > 0 ? /* @__PURE__ */ jsx(InlineContent, { content: text }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xA0" })
1502
1553
  ] })
1503
1554
  ] }),
1504
1555
  item.children ? /* @__PURE__ */ jsx("ul", { role: "list", "data-ncm-list-nested": "", className: "ml-6 list-none", children: /* @__PURE__ */ jsx(TaskItems, { items: item.children.items }) }) : null
@@ -1,12 +1,11 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { EmbedBlock as EmbedBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type EmbedBlockProps = BlockRendererProps<EmbedBlock$1>;
7
6
  /**
8
7
  * Renderer for standalone normalized embed blocks.
9
8
  */
10
- declare function EmbedBlock({ block, className }: EmbedBlockProps): react_jsx_runtime.JSX.Element;
9
+ declare function EmbedBlock({ block, className }: EmbedBlockProps): react.JSX.Element;
11
10
 
12
11
  export { EmbedBlock, type EmbedBlockProps };
@@ -1,13 +1,12 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { EmbeddingBlock as EmbeddingBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type EmbeddingBlockProps = BlockRendererProps<EmbeddingBlock$1>;
7
6
  /**
8
7
  * Embedding block renderer that dispatches by embed_type
9
8
  * (image, video, audio, pdf, ncm_embed, iframe, generic_file).
10
9
  */
11
- declare function EmbeddingBlock({ block, className }: EmbeddingBlockProps): react_jsx_runtime.JSX.Element;
10
+ declare function EmbeddingBlock({ block, className }: EmbeddingBlockProps): react.JSX.Element;
12
11
 
13
12
  export { EmbeddingBlock, type EmbeddingBlockProps };
@@ -470,9 +470,10 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
470
470
  case "image": {
471
471
  const src = config.inline?.resolveAssetPath?.(token.src) ?? token.src;
472
472
  return /* @__PURE__ */ jsx(
473
- "img",
473
+ EmbedImage,
474
474
  {
475
- "data-ncm-token": "image",
475
+ attrs: { "data-ncm-token": "image" },
476
+ target: token.src,
476
477
  src,
477
478
  alt: token.alt,
478
479
  title: token.title
@@ -506,10 +507,13 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
506
507
  }
507
508
  if (embedType === "image") {
508
509
  return /* @__PURE__ */ jsx(
509
- "img",
510
+ EmbedImage,
510
511
  {
511
- "data-ncm-token": "wiki_embed",
512
- "data-ncm-wikiembed-type": embedType,
512
+ attrs: {
513
+ "data-ncm-token": "wiki_embed",
514
+ "data-ncm-wikiembed-type": embedType
515
+ },
516
+ target: token.target,
513
517
  src: url,
514
518
  alt: token.display ?? token.target,
515
519
  className: config.embeddings?.imageClassName
@@ -540,6 +544,22 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
540
544
  }
541
545
  );
542
546
  }
547
+ const onEmbedClick = config.embeddings?.onEmbedClick;
548
+ if (onEmbedClick) {
549
+ const handleClick = (e) => {
550
+ onEmbedClick(token.target, result, e);
551
+ };
552
+ return /* @__PURE__ */ jsx(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ "data-ncm-token": "wiki_embed",
557
+ "data-ncm-wikiembed-type": embedType,
558
+ onClick: handleClick,
559
+ children: token.display ?? token.target
560
+ }
561
+ );
562
+ }
543
563
  return /* @__PURE__ */ jsx(
544
564
  "a",
545
565
  {
@@ -558,6 +578,31 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
558
578
  return null;
559
579
  }
560
580
  }
581
+ function EmbedImage({ attrs, target, src, alt, title, className }) {
582
+ const config = useNcmConfig();
583
+ const Fallback = config.embeddings?.renderImageError;
584
+ const [failedSrc, setFailedSrc] = useState(null);
585
+ useEffect(() => {
586
+ setFailedSrc(null);
587
+ }, [src]);
588
+ if (!Fallback) {
589
+ return /* @__PURE__ */ jsx("img", { ...attrs, src, alt, title, className });
590
+ }
591
+ if (failedSrc === src) {
592
+ return /* @__PURE__ */ jsx(Fallback, { target, src, alt, retry: () => setFailedSrc(null) });
593
+ }
594
+ return /* @__PURE__ */ jsx(
595
+ "img",
596
+ {
597
+ ...attrs,
598
+ src,
599
+ alt,
600
+ title,
601
+ className,
602
+ onError: () => setFailedSrc(src)
603
+ }
604
+ );
605
+ }
561
606
  function renderReadingText(content, opts) {
562
607
  if (!content) return /* @__PURE__ */ jsx(Fragment, { children: content });
563
608
  const { bionic = false, beeline = false } = opts;
@@ -1,13 +1,12 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { FigureBlock as FigureBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type FigureBlockProps = BlockRendererProps<FigureBlock$1>;
7
6
  /**
8
7
  * Figure renderer — an embed plus caption, promoted from `embed + paragraph`
9
8
  * (no blank line). Images render inline; other embed types render as a link.
10
9
  */
11
- declare function FigureBlock({ block, className }: FigureBlockProps): react_jsx_runtime.JSX.Element;
10
+ declare function FigureBlock({ block, className }: FigureBlockProps): react.JSX.Element;
12
11
 
13
12
  export { FigureBlock, type FigureBlockProps };
@@ -470,9 +470,10 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
470
470
  case "image": {
471
471
  const src = config.inline?.resolveAssetPath?.(token.src) ?? token.src;
472
472
  return /* @__PURE__ */ jsx(
473
- "img",
473
+ EmbedImage,
474
474
  {
475
- "data-ncm-token": "image",
475
+ attrs: { "data-ncm-token": "image" },
476
+ target: token.src,
476
477
  src,
477
478
  alt: token.alt,
478
479
  title: token.title
@@ -506,10 +507,13 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
506
507
  }
507
508
  if (embedType === "image") {
508
509
  return /* @__PURE__ */ jsx(
509
- "img",
510
+ EmbedImage,
510
511
  {
511
- "data-ncm-token": "wiki_embed",
512
- "data-ncm-wikiembed-type": embedType,
512
+ attrs: {
513
+ "data-ncm-token": "wiki_embed",
514
+ "data-ncm-wikiembed-type": embedType
515
+ },
516
+ target: token.target,
513
517
  src: url,
514
518
  alt: token.display ?? token.target,
515
519
  className: config.embeddings?.imageClassName
@@ -540,6 +544,22 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
540
544
  }
541
545
  );
542
546
  }
547
+ const onEmbedClick = config.embeddings?.onEmbedClick;
548
+ if (onEmbedClick) {
549
+ const handleClick = (e) => {
550
+ onEmbedClick(token.target, result, e);
551
+ };
552
+ return /* @__PURE__ */ jsx(
553
+ "button",
554
+ {
555
+ type: "button",
556
+ "data-ncm-token": "wiki_embed",
557
+ "data-ncm-wikiembed-type": embedType,
558
+ onClick: handleClick,
559
+ children: token.display ?? token.target
560
+ }
561
+ );
562
+ }
543
563
  return /* @__PURE__ */ jsx(
544
564
  "a",
545
565
  {
@@ -558,6 +578,31 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
558
578
  return null;
559
579
  }
560
580
  }
581
+ function EmbedImage({ attrs, target, src, alt, title, className }) {
582
+ const config = useNcmConfig();
583
+ const Fallback = config.embeddings?.renderImageError;
584
+ const [failedSrc, setFailedSrc] = useState(null);
585
+ useEffect(() => {
586
+ setFailedSrc(null);
587
+ }, [src]);
588
+ if (!Fallback) {
589
+ return /* @__PURE__ */ jsx("img", { ...attrs, src, alt, title, className });
590
+ }
591
+ if (failedSrc === src) {
592
+ return /* @__PURE__ */ jsx(Fallback, { target, src, alt, retry: () => setFailedSrc(null) });
593
+ }
594
+ return /* @__PURE__ */ jsx(
595
+ "img",
596
+ {
597
+ ...attrs,
598
+ src,
599
+ alt,
600
+ title,
601
+ className,
602
+ onError: () => setFailedSrc(src)
603
+ }
604
+ );
605
+ }
561
606
  function renderReadingText(content, opts) {
562
607
  if (!content) return /* @__PURE__ */ jsx(Fragment, { children: content });
563
608
  const { bionic = false, beeline = false } = opts;
@@ -1,13 +1,12 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
  import { FootnoteBlock as FootnoteBlock$1 } from '@nuucognition/ncm';
3
3
  import { a as BlockRendererProps } from '../../renderers-C5f41FCy.js';
4
- import 'react';
5
4
 
6
5
  type FootnoteBlockProps = BlockRendererProps<FootnoteBlock$1>;
7
6
  /**
8
7
  * Footnote-definition renderer. Anchored by its `fn-{label}` stable id so inline
9
8
  * footnote references can link to it. Content renders through `NcmBlock`.
10
9
  */
11
- declare function FootnoteBlock({ block, className }: FootnoteBlockProps): react_jsx_runtime.JSX.Element;
10
+ declare function FootnoteBlock({ block, className }: FootnoteBlockProps): react.JSX.Element;
12
11
 
13
12
  export { FootnoteBlock, type FootnoteBlockProps };