@nuucognition/ncm-ui 0.1.1-beta.1 → 0.1.1-beta.2

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
@@ -56,6 +56,52 @@ function tryParseInlineRef(input) {
56
56
  }
57
57
  return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
58
58
  }
59
+ function runLength(input, index, char) {
60
+ let end = index;
61
+ while (end < input.length && input[end] === char) end++;
62
+ return end - index;
63
+ }
64
+ function tryParseEmphasis(input) {
65
+ const char = input[0];
66
+ if (char !== "*" && char !== "_") return null;
67
+ const open = Math.min(3, runLength(input, 0, char));
68
+ const need = open;
69
+ let i = open;
70
+ while (i < input.length) {
71
+ const ch = input[i];
72
+ if (ch === "`") {
73
+ const close = input.indexOf("`", i + 1);
74
+ if (close === -1) break;
75
+ i = close + 1;
76
+ continue;
77
+ }
78
+ if (ch !== char) {
79
+ i++;
80
+ continue;
81
+ }
82
+ const run = runLength(input, i, char);
83
+ const closes = need === 1 ? run % 2 === 1 : run >= need;
84
+ if (!closes) {
85
+ i += run;
86
+ continue;
87
+ }
88
+ const extra = run - need;
89
+ const inner = input.slice(open, i + extra);
90
+ if (inner.length === 0) return null;
91
+ const length = i + run;
92
+ const children = parseInline(inner);
93
+ let token;
94
+ if (need === 3) {
95
+ token = { type: "bold", children: [{ type: "italic", children }] };
96
+ } else if (need === 2) {
97
+ token = { type: "bold", children };
98
+ } else {
99
+ token = { type: "italic", children };
100
+ }
101
+ return { token, length };
102
+ }
103
+ return null;
104
+ }
59
105
  function parseInline(content) {
60
106
  if (!content) return [];
61
107
  const tokens = [];
@@ -127,17 +173,11 @@ function parseInline(content) {
127
173
  remaining = remaining.slice(linkMatch[0].length);
128
174
  continue;
129
175
  }
130
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
131
- if (boldMatch) {
132
- const innerContent = boldMatch[1] ?? boldMatch[2];
133
- if (innerContent !== void 0) {
134
- tokens.push({
135
- type: "bold",
136
- children: parseInline(innerContent)
137
- });
138
- remaining = remaining.slice(boldMatch[0].length);
139
- continue;
140
- }
176
+ const emphasis = tryParseEmphasis(remaining);
177
+ if (emphasis) {
178
+ tokens.push(emphasis.token);
179
+ remaining = remaining.slice(emphasis.length);
180
+ continue;
141
181
  }
142
182
  const strikeMatch = remaining.match(/^~~(.+?)~~/);
143
183
  if (strikeMatch && strikeMatch[1] !== void 0) {
@@ -148,18 +188,6 @@ function parseInline(content) {
148
188
  remaining = remaining.slice(strikeMatch[0].length);
149
189
  continue;
150
190
  }
151
- const italicMatch = remaining.match(/^\*([^*]+)\*|^_([^_]+)_/);
152
- if (italicMatch) {
153
- const innerContent = italicMatch[1] ?? italicMatch[2];
154
- if (innerContent !== void 0) {
155
- tokens.push({
156
- type: "italic",
157
- children: parseInline(innerContent)
158
- });
159
- remaining = remaining.slice(italicMatch[0].length);
160
- continue;
161
- }
162
- }
163
191
  const nextSpecial = findNextSpecialChar(remaining);
164
192
  if (nextSpecial > 0) {
165
193
  tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
@@ -230,48 +258,95 @@ function collectListStrings(items) {
230
258
  }
231
259
  return out;
232
260
  }
233
- function countInlineRefs(tokens, acc) {
261
+ function normalizeReferenceContent(content) {
262
+ return content.replace(/\s+/g, " ").trim().toLowerCase();
263
+ }
264
+ var BIBLIOGRAPHY_ENTRY_RE = /^\s*\[(\d+)\]\s+([\s\S]+?)\s*$/;
265
+ function collectBibliography(blocks) {
266
+ const entries = /* @__PURE__ */ new Map();
267
+ for (const block of blocks) {
268
+ if (block.type !== "paragraph") continue;
269
+ const content = block.content ?? "";
270
+ const match = BIBLIOGRAPHY_ENTRY_RE.exec(content);
271
+ if (!match || !match[1] || !match[2]) continue;
272
+ const key = normalizeReferenceContent(match[2]);
273
+ if (key && !entries.has(key)) entries.set(key, Number(match[1]));
274
+ }
275
+ return entries;
276
+ }
277
+ function walkInlineRefs(tokens, visit) {
234
278
  for (const token of tokens) {
235
279
  if (token.type === "inline_ref") {
236
- acc[token.kind] += 1;
280
+ visit(token);
237
281
  } else if ("children" in token) {
238
- countInlineRefs(token.children, acc);
282
+ walkInlineRefs(token.children, visit);
239
283
  }
240
284
  }
241
285
  }
242
- function countBlock(block) {
243
- const acc = { ref: 0, footnote: 0 };
244
- for (const str of collectInlineStrings(block)) {
245
- if (str) countInlineRefs(parseInline(str), acc);
286
+ function computeReferenceNumbering(blocks) {
287
+ const bibliography = collectBibliography(blocks);
288
+ let maxBibliography = 0;
289
+ for (const n of bibliography.values()) maxBibliography = Math.max(maxBibliography, n);
290
+ const numbering = { ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() };
291
+ let nextRef = maxBibliography;
292
+ let nextFootnote = 0;
293
+ for (const block of blocks) {
294
+ for (const str of collectInlineStrings(block)) {
295
+ if (!str) continue;
296
+ walkInlineRefs(parseInline(str), (token) => {
297
+ const key = normalizeReferenceContent(token.content);
298
+ const map = numbering[token.kind];
299
+ if (map.has(key)) return;
300
+ if (token.kind === "ref") {
301
+ const fromBibliography = bibliography.get(key);
302
+ map.set(key, fromBibliography ?? ++nextRef);
303
+ } else {
304
+ map.set(key, ++nextFootnote);
305
+ }
306
+ });
307
+ }
246
308
  }
247
- return acc;
309
+ return numbering;
248
310
  }
249
311
  function computeReferenceBases(blocks) {
250
312
  const bases = /* @__PURE__ */ new Map();
251
313
  const acc = { ref: 0, footnote: 0 };
252
314
  for (const block of blocks) {
253
315
  bases.set(block.id, { ...acc });
254
- const counts = countBlock(block);
255
- acc.ref += counts.ref;
256
- acc.footnote += counts.footnote;
316
+ for (const str of collectInlineStrings(block)) {
317
+ if (!str) continue;
318
+ walkInlineRefs(parseInline(str), (token) => {
319
+ acc[token.kind] += 1;
320
+ });
321
+ }
257
322
  }
258
323
  return bases;
259
324
  }
260
- var ReferenceBasesContext = createContext(null);
325
+ var ReferenceNumberingContext = createContext(null);
261
326
  var BlockReferenceScopeContext = createContext(null);
262
327
  function NcmReferencesProvider({ blocks, children }) {
263
- const bases = useMemo(() => computeReferenceBases(blocks), [blocks]);
264
- return /* @__PURE__ */ jsx(ReferenceBasesContext.Provider, { value: bases, children });
328
+ const numbering = useMemo(() => computeReferenceNumbering(blocks), [blocks]);
329
+ return /* @__PURE__ */ jsx(ReferenceNumberingContext.Provider, { value: numbering, children });
265
330
  }
266
- function BlockReferenceScope({ blockId, children }) {
267
- const bases = useContext(ReferenceBasesContext);
268
- const base = (blockId ? bases?.get(blockId) : void 0) ?? { ref: 0, footnote: 0 };
331
+ function BlockReferenceScope({ children }) {
332
+ const numbering = useContext(ReferenceNumberingContext);
333
+ const local = useRef({ ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() });
269
334
  const counter = useRef({ ref: 0, footnote: 0 });
270
- counter.current = { ref: 0, footnote: 0 };
271
335
  const value = {
336
+ numberFor(kind, content) {
337
+ const key = normalizeReferenceContent(content);
338
+ const fromDocument = numbering?.[kind].get(key);
339
+ if (fromDocument !== void 0) return fromDocument;
340
+ const map = local.current[kind];
341
+ const existing = map.get(key);
342
+ if (existing !== void 0) return existing;
343
+ counter.current[kind] += 1;
344
+ map.set(key, counter.current[kind]);
345
+ return counter.current[kind];
346
+ },
272
347
  next(kind) {
273
348
  counter.current[kind] += 1;
274
- return base[kind] + counter.current[kind];
349
+ return counter.current[kind];
275
350
  }
276
351
  };
277
352
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
@@ -279,7 +354,7 @@ function BlockReferenceScope({ blockId, children }) {
279
354
  function useBlockReferenceScope() {
280
355
  return useContext(BlockReferenceScopeContext);
281
356
  }
282
- var INERT_SCOPE = { next: () => 0 };
357
+ var INERT_SCOPE = { numberFor: () => 0, next: () => 0 };
283
358
  function InertReferenceScope({ children }) {
284
359
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
285
360
  }
@@ -715,7 +790,7 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
715
790
  );
716
791
  }
717
792
  case "inline_ref": {
718
- const number = refScope?.next(token.kind) ?? 0;
793
+ const number = refScope?.numberFor(token.kind, token.content) ?? 0;
719
794
  return /* @__PURE__ */ jsx(ReferenceMarker, { kind: token.kind, number, children: token.content ? /* @__PURE__ */ jsx(InertReferenceScope, { children: /* @__PURE__ */ jsx(InlineContent, { content: token.content, disableBionic: true, disableBeeline: true }) }) : null });
720
795
  }
721
796
  default:
@@ -787,8 +862,32 @@ function renderBionicText(content, ratio = 0.5) {
787
862
  function renderBeelineText(content) {
788
863
  return renderReadingText(content, { beeline: true });
789
864
  }
865
+ function quoteBlocks(block) {
866
+ try {
867
+ const doc = parseNcm(block.content, {
868
+ preset: "ncm-full",
869
+ allowMissingFrontmatter: true,
870
+ // Attribution off, as the NUU Editor's bridge keeps it: the promoter's
871
+ // trigger is any last quote line starting with `-`, so a nested quote
872
+ // ending in a list item would lose that item to a `— Author` line.
873
+ blockOptions: { extensions: { attribution: false } }
874
+ });
875
+ const blocks = doc.blocks ?? [];
876
+ if (blocks.length === 0 || blocks.some((child) => child.type === "frontmatter")) {
877
+ return null;
878
+ }
879
+ return blocks.map((child, index) => ({ ...child, id: `${block.id}.${index + 1}`, stableId: void 0 }));
880
+ } catch {
881
+ return null;
882
+ }
883
+ }
884
+ function QuotedLines({ content }) {
885
+ const lines = content.split("\n");
886
+ return /* @__PURE__ */ jsx(Fragment, { children: 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)) });
887
+ }
790
888
  function BlockquoteBlock({ block, className }) {
791
- const lines = block.content.split("\n");
889
+ const multiline = block.content.includes("\n");
890
+ const blocks = useMemo(() => multiline ? quoteBlocks(block) : null, [block, multiline]);
792
891
  return /* @__PURE__ */ jsx(
793
892
  "blockquote",
794
893
  {
@@ -798,7 +897,9 @@ function BlockquoteBlock({ block, className }) {
798
897
  "data-block-id": block.id,
799
898
  "data-ncm-blockquote": "",
800
899
  className,
801
- 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))
900
+ children: !multiline ? /* @__PURE__ */ jsx(InlineContent, { content: block.content }) : blocks === null ? /* @__PURE__ */ jsx(QuotedLines, { content: block.content }) : blocks.map(
901
+ (child) => child.type === "paragraph" ? /* @__PURE__ */ jsx(QuotedLines, { content: child.content ?? "" }, blockKey(child)) : /* @__PURE__ */ jsx(NcmBlock, { block: child }, blockKey(child))
902
+ )
802
903
  }
803
904
  );
804
905
  }
@@ -1976,4 +2077,4 @@ function useCustomCodeRenderer(language) {
1976
2077
  return customCodeRenderers[language];
1977
2078
  }
1978
2079
 
1979
- export { AdmonitionBlock, AsideBlock, AttributionBlock, BlockReferenceScope, BlockShell, BlockquoteBlock, ChartBlock, ChildBlocks, CodeBlock, ContainerBlock, CustomCodeBlock, DataviewBlock, DefinitionBlock, EmbeddingBlock, FigureBlock, FootnoteBlock, FrontmatterBlock, HeaderBlock, HorizontalRuleBlock, HtmlBlock, InertReferenceScope, InlineContent, LatexDisplayBlock, LinkBlock, ListBlock, MathBlock, MermaidBlock, NcmBlock, NcmBlockPreview, NcmDocument, NcmProvider, NcmReferencesProvider, PageBreakerBlock, ParagraphBlock, ReferenceMarker, SanitizedHtmlBlock, SpoilerBlock, TableBlock, TaskListBlock, blockDataAttributes, blockKey, cn, computeReferenceBases, defaultRenderers, parseInline, renderBeelineText, renderBionicText, renderReadingText, useBlockReferenceScope, useBlockRenderer, useCustomCodeRenderer, useNcmConfig };
2080
+ export { AdmonitionBlock, AsideBlock, AttributionBlock, BlockReferenceScope, BlockShell, BlockquoteBlock, ChartBlock, ChildBlocks, CodeBlock, ContainerBlock, CustomCodeBlock, DataviewBlock, DefinitionBlock, EmbeddingBlock, FigureBlock, FootnoteBlock, FrontmatterBlock, HeaderBlock, HorizontalRuleBlock, HtmlBlock, InertReferenceScope, InlineContent, LatexDisplayBlock, LinkBlock, ListBlock, MathBlock, MermaidBlock, NcmBlock, NcmBlockPreview, NcmDocument, NcmProvider, NcmReferencesProvider, PageBreakerBlock, ParagraphBlock, ReferenceMarker, SanitizedHtmlBlock, SpoilerBlock, TableBlock, TaskListBlock, blockDataAttributes, blockKey, cn, collectBibliography, computeReferenceBases, computeReferenceNumbering, defaultRenderers, normalizeReferenceContent, parseInline, renderBeelineText, renderBionicText, renderReadingText, useBlockReferenceScope, useBlockRenderer, useCustomCodeRenderer, useNcmConfig };
@@ -1,7 +1,7 @@
1
1
  import { b as InlineToken } from '../inline-DwUYUmk4.js';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { d as ReferenceKind } from '../references-BYW5pnj_.js';
4
+ import { d as ReferenceKind } from '../references-BM-lvGrq.js';
5
5
  import '@nuucognition/ncm';
6
6
 
7
7
  /**
@@ -13,8 +13,7 @@ import '@nuucognition/ncm';
13
13
  * - Images: ![alt](src "title")
14
14
  * - Links: [text](url "title")
15
15
  * - Inline reference: ^[ref: ...] / ^[footnote: ...]
16
- * - Bold: **text** or __text__
17
- * - Italic: *text* or _text_
16
+ * - Emphasis: ***both***, **bold**, *italic* (and the `_` spellings)
18
17
  * - Strikethrough: ~~text~~
19
18
  */
20
19
  declare function parseInline(content: string): InlineToken[];
@@ -44,6 +44,52 @@ function tryParseInlineRef(input) {
44
44
  }
45
45
  return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
46
46
  }
47
+ function runLength(input, index, char) {
48
+ let end = index;
49
+ while (end < input.length && input[end] === char) end++;
50
+ return end - index;
51
+ }
52
+ function tryParseEmphasis(input) {
53
+ const char = input[0];
54
+ if (char !== "*" && char !== "_") return null;
55
+ const open = Math.min(3, runLength(input, 0, char));
56
+ const need = open;
57
+ let i = open;
58
+ while (i < input.length) {
59
+ const ch = input[i];
60
+ if (ch === "`") {
61
+ const close = input.indexOf("`", i + 1);
62
+ if (close === -1) break;
63
+ i = close + 1;
64
+ continue;
65
+ }
66
+ if (ch !== char) {
67
+ i++;
68
+ continue;
69
+ }
70
+ const run = runLength(input, i, char);
71
+ const closes = need === 1 ? run % 2 === 1 : run >= need;
72
+ if (!closes) {
73
+ i += run;
74
+ continue;
75
+ }
76
+ const extra = run - need;
77
+ const inner = input.slice(open, i + extra);
78
+ if (inner.length === 0) return null;
79
+ const length = i + run;
80
+ const children = parseInline(inner);
81
+ let token;
82
+ if (need === 3) {
83
+ token = { type: "bold", children: [{ type: "italic", children }] };
84
+ } else if (need === 2) {
85
+ token = { type: "bold", children };
86
+ } else {
87
+ token = { type: "italic", children };
88
+ }
89
+ return { token, length };
90
+ }
91
+ return null;
92
+ }
47
93
  function parseInline(content) {
48
94
  if (!content) return [];
49
95
  const tokens = [];
@@ -115,17 +161,11 @@ function parseInline(content) {
115
161
  remaining = remaining.slice(linkMatch[0].length);
116
162
  continue;
117
163
  }
118
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
119
- if (boldMatch) {
120
- const innerContent = boldMatch[1] ?? boldMatch[2];
121
- if (innerContent !== void 0) {
122
- tokens.push({
123
- type: "bold",
124
- children: parseInline(innerContent)
125
- });
126
- remaining = remaining.slice(boldMatch[0].length);
127
- continue;
128
- }
164
+ const emphasis = tryParseEmphasis(remaining);
165
+ if (emphasis) {
166
+ tokens.push(emphasis.token);
167
+ remaining = remaining.slice(emphasis.length);
168
+ continue;
129
169
  }
130
170
  const strikeMatch = remaining.match(/^~~(.+?)~~/);
131
171
  if (strikeMatch && strikeMatch[1] !== void 0) {
@@ -136,18 +176,6 @@ function parseInline(content) {
136
176
  remaining = remaining.slice(strikeMatch[0].length);
137
177
  continue;
138
178
  }
139
- const italicMatch = remaining.match(/^\*([^*]+)\*|^_([^_]+)_/);
140
- if (italicMatch) {
141
- const innerContent = italicMatch[1] ?? italicMatch[2];
142
- if (innerContent !== void 0) {
143
- tokens.push({
144
- type: "italic",
145
- children: parseInline(innerContent)
146
- });
147
- remaining = remaining.slice(italicMatch[0].length);
148
- continue;
149
- }
150
- }
151
179
  const nextSpecial = findNextSpecialChar(remaining);
152
180
  if (nextSpecial > 0) {
153
181
  tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
@@ -258,17 +286,30 @@ function ReferenceMarker({ kind, number, children }) {
258
286
  }
259
287
  );
260
288
  }
261
- var ReferenceBasesContext = createContext(null);
289
+ function normalizeReferenceContent(content) {
290
+ return content.replace(/\s+/g, " ").trim().toLowerCase();
291
+ }
292
+ var ReferenceNumberingContext = createContext(null);
262
293
  var BlockReferenceScopeContext = createContext(null);
263
- function BlockReferenceScope({ blockId, children }) {
264
- const bases = useContext(ReferenceBasesContext);
265
- const base = (blockId ? bases?.get(blockId) : void 0) ?? { ref: 0, footnote: 0 };
294
+ function BlockReferenceScope({ children }) {
295
+ const numbering = useContext(ReferenceNumberingContext);
296
+ const local = useRef({ ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() });
266
297
  const counter = useRef({ ref: 0, footnote: 0 });
267
- counter.current = { ref: 0, footnote: 0 };
268
298
  const value = {
299
+ numberFor(kind, content) {
300
+ const key = normalizeReferenceContent(content);
301
+ const fromDocument = numbering?.[kind].get(key);
302
+ if (fromDocument !== void 0) return fromDocument;
303
+ const map = local.current[kind];
304
+ const existing = map.get(key);
305
+ if (existing !== void 0) return existing;
306
+ counter.current[kind] += 1;
307
+ map.set(key, counter.current[kind]);
308
+ return counter.current[kind];
309
+ },
269
310
  next(kind) {
270
311
  counter.current[kind] += 1;
271
- return base[kind] + counter.current[kind];
312
+ return counter.current[kind];
272
313
  }
273
314
  };
274
315
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
@@ -276,7 +317,7 @@ function BlockReferenceScope({ blockId, children }) {
276
317
  function useBlockReferenceScope() {
277
318
  return useContext(BlockReferenceScopeContext);
278
319
  }
279
- var INERT_SCOPE = { next: () => 0 };
320
+ var INERT_SCOPE = { numberFor: () => 0, next: () => 0 };
280
321
  function InertReferenceScope({ children }) {
281
322
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
282
323
  }
@@ -567,7 +608,7 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
567
608
  );
568
609
  }
569
610
  case "inline_ref": {
570
- const number = refScope?.next(token.kind) ?? 0;
611
+ const number = refScope?.numberFor(token.kind, token.content) ?? 0;
571
612
  return /* @__PURE__ */ jsx(ReferenceMarker, { kind: token.kind, number, children: token.content ? /* @__PURE__ */ jsx(InertReferenceScope, { children: /* @__PURE__ */ jsx(InlineContent, { content: token.content, disableBionic: true, disableBeeline: true }) }) : null });
572
613
  }
573
614
  default:
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { createContext, useState, createElement, useMemo, useContext, useCallback, useEffect, useRef, Component, useId } from 'react';
2
+ import { createContext, useMemo, useState, createElement, useContext, useCallback, useEffect, useRef, Component, useId } from 'react';
3
3
  import { clsx } from 'clsx';
4
4
  import { twMerge } from 'tailwind-merge';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
- import { serializeInlineContent, isTypedNestedList } from '@nuucognition/ncm';
6
+ import { serializeInlineContent, isTypedNestedList, parseNcm } from '@nuucognition/ncm';
7
7
  import { extractExtension, embedTypeFromExtension } from '@nuucognition/mesh-core';
8
8
 
9
9
  // src/lib/defaults.ts
@@ -71,6 +71,52 @@ function tryParseInlineRef(input) {
71
71
  }
72
72
  return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
73
73
  }
74
+ function runLength(input, index, char) {
75
+ let end = index;
76
+ while (end < input.length && input[end] === char) end++;
77
+ return end - index;
78
+ }
79
+ function tryParseEmphasis(input) {
80
+ const char = input[0];
81
+ if (char !== "*" && char !== "_") return null;
82
+ const open = Math.min(3, runLength(input, 0, char));
83
+ const need = open;
84
+ let i = open;
85
+ while (i < input.length) {
86
+ const ch = input[i];
87
+ if (ch === "`") {
88
+ const close = input.indexOf("`", i + 1);
89
+ if (close === -1) break;
90
+ i = close + 1;
91
+ continue;
92
+ }
93
+ if (ch !== char) {
94
+ i++;
95
+ continue;
96
+ }
97
+ const run = runLength(input, i, char);
98
+ const closes = need === 1 ? run % 2 === 1 : run >= need;
99
+ if (!closes) {
100
+ i += run;
101
+ continue;
102
+ }
103
+ const extra = run - need;
104
+ const inner = input.slice(open, i + extra);
105
+ if (inner.length === 0) return null;
106
+ const length = i + run;
107
+ const children = parseInline(inner);
108
+ let token;
109
+ if (need === 3) {
110
+ token = { type: "bold", children: [{ type: "italic", children }] };
111
+ } else if (need === 2) {
112
+ token = { type: "bold", children };
113
+ } else {
114
+ token = { type: "italic", children };
115
+ }
116
+ return { token, length };
117
+ }
118
+ return null;
119
+ }
74
120
  function parseInline(content) {
75
121
  if (!content) return [];
76
122
  const tokens = [];
@@ -142,17 +188,11 @@ function parseInline(content) {
142
188
  remaining = remaining.slice(linkMatch[0].length);
143
189
  continue;
144
190
  }
145
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
146
- if (boldMatch) {
147
- const innerContent = boldMatch[1] ?? boldMatch[2];
148
- if (innerContent !== void 0) {
149
- tokens.push({
150
- type: "bold",
151
- children: parseInline(innerContent)
152
- });
153
- remaining = remaining.slice(boldMatch[0].length);
154
- continue;
155
- }
191
+ const emphasis = tryParseEmphasis(remaining);
192
+ if (emphasis) {
193
+ tokens.push(emphasis.token);
194
+ remaining = remaining.slice(emphasis.length);
195
+ continue;
156
196
  }
157
197
  const strikeMatch = remaining.match(/^~~(.+?)~~/);
158
198
  if (strikeMatch && strikeMatch[1] !== void 0) {
@@ -163,18 +203,6 @@ function parseInline(content) {
163
203
  remaining = remaining.slice(strikeMatch[0].length);
164
204
  continue;
165
205
  }
166
- const italicMatch = remaining.match(/^\*([^*]+)\*|^_([^_]+)_/);
167
- if (italicMatch) {
168
- const innerContent = italicMatch[1] ?? italicMatch[2];
169
- if (innerContent !== void 0) {
170
- tokens.push({
171
- type: "italic",
172
- children: parseInline(innerContent)
173
- });
174
- remaining = remaining.slice(italicMatch[0].length);
175
- continue;
176
- }
177
- }
178
206
  const nextSpecial = findNextSpecialChar(remaining);
179
207
  if (nextSpecial > 0) {
180
208
  tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
@@ -209,17 +237,30 @@ function mergeAdjacentText(tokens) {
209
237
  }
210
238
  return result;
211
239
  }
212
- var ReferenceBasesContext = createContext(null);
240
+ function normalizeReferenceContent(content) {
241
+ return content.replace(/\s+/g, " ").trim().toLowerCase();
242
+ }
243
+ var ReferenceNumberingContext = createContext(null);
213
244
  var BlockReferenceScopeContext = createContext(null);
214
- function BlockReferenceScope({ blockId, children }) {
215
- const bases = useContext(ReferenceBasesContext);
216
- const base = (blockId ? bases?.get(blockId) : void 0) ?? { ref: 0, footnote: 0 };
245
+ function BlockReferenceScope({ children }) {
246
+ const numbering = useContext(ReferenceNumberingContext);
247
+ const local = useRef({ ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() });
217
248
  const counter = useRef({ ref: 0, footnote: 0 });
218
- counter.current = { ref: 0, footnote: 0 };
219
249
  const value = {
250
+ numberFor(kind, content) {
251
+ const key = normalizeReferenceContent(content);
252
+ const fromDocument = numbering?.[kind].get(key);
253
+ if (fromDocument !== void 0) return fromDocument;
254
+ const map = local.current[kind];
255
+ const existing = map.get(key);
256
+ if (existing !== void 0) return existing;
257
+ counter.current[kind] += 1;
258
+ map.set(key, counter.current[kind]);
259
+ return counter.current[kind];
260
+ },
220
261
  next(kind) {
221
262
  counter.current[kind] += 1;
222
- return base[kind] + counter.current[kind];
263
+ return counter.current[kind];
223
264
  }
224
265
  };
225
266
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
@@ -227,7 +268,7 @@ function BlockReferenceScope({ blockId, children }) {
227
268
  function useBlockReferenceScope() {
228
269
  return useContext(BlockReferenceScopeContext);
229
270
  }
230
- var INERT_SCOPE = { next: () => 0 };
271
+ var INERT_SCOPE = { numberFor: () => 0, next: () => 0 };
231
272
  function InertReferenceScope({ children }) {
232
273
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
233
274
  }
@@ -751,7 +792,7 @@ function TokenRenderer({ token, disableBionic, disableBeeline }) {
751
792
  );
752
793
  }
753
794
  case "inline_ref": {
754
- const number = refScope?.next(token.kind) ?? 0;
795
+ const number = refScope?.numberFor(token.kind, token.content) ?? 0;
755
796
  return /* @__PURE__ */ jsx(ReferenceMarker, { kind: token.kind, number, children: token.content ? /* @__PURE__ */ jsx(InertReferenceScope, { children: /* @__PURE__ */ jsx(InlineContent, { content: token.content, disableBionic: true, disableBeeline: true }) }) : null });
756
797
  }
757
798
  default:
@@ -817,8 +858,32 @@ function renderReadingText(content, opts) {
817
858
  if (cursor < content.length) pieces.push(content.slice(cursor));
818
859
  return /* @__PURE__ */ jsx(Fragment, { children: pieces });
819
860
  }
861
+ function quoteBlocks(block) {
862
+ try {
863
+ const doc = parseNcm(block.content, {
864
+ preset: "ncm-full",
865
+ allowMissingFrontmatter: true,
866
+ // Attribution off, as the NUU Editor's bridge keeps it: the promoter's
867
+ // trigger is any last quote line starting with `-`, so a nested quote
868
+ // ending in a list item would lose that item to a `— Author` line.
869
+ blockOptions: { extensions: { attribution: false } }
870
+ });
871
+ const blocks = doc.blocks ?? [];
872
+ if (blocks.length === 0 || blocks.some((child) => child.type === "frontmatter")) {
873
+ return null;
874
+ }
875
+ return blocks.map((child, index) => ({ ...child, id: `${block.id}.${index + 1}`, stableId: void 0 }));
876
+ } catch {
877
+ return null;
878
+ }
879
+ }
880
+ function QuotedLines({ content }) {
881
+ const lines = content.split("\n");
882
+ return /* @__PURE__ */ jsx(Fragment, { children: 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)) });
883
+ }
820
884
  function BlockquoteBlock({ block, className }) {
821
- const lines = block.content.split("\n");
885
+ const multiline = block.content.includes("\n");
886
+ const blocks = useMemo(() => multiline ? quoteBlocks(block) : null, [block, multiline]);
822
887
  return /* @__PURE__ */ jsx(
823
888
  "blockquote",
824
889
  {
@@ -828,7 +893,9 @@ function BlockquoteBlock({ block, className }) {
828
893
  "data-block-id": block.id,
829
894
  "data-ncm-blockquote": "",
830
895
  className,
831
- 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))
896
+ children: !multiline ? /* @__PURE__ */ jsx(InlineContent, { content: block.content }) : blocks === null ? /* @__PURE__ */ jsx(QuotedLines, { content: block.content }) : blocks.map(
897
+ (child) => child.type === "paragraph" ? /* @__PURE__ */ jsx(QuotedLines, { content: child.content ?? "" }, blockKey(child)) : /* @__PURE__ */ jsx(NcmBlock, { block: child }, blockKey(child))
898
+ )
832
899
  }
833
900
  );
834
901
  }