@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.
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { createContext, useContext, createElement, useState, useEffect, useMemo, useCallback, useRef, Component, useId } from 'react';
2
+ import { createContext, useContext, createElement, useMemo, useState, useEffect, useCallback, useRef, Component, useId } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
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/context/ncm-context.tsx
@@ -75,6 +75,52 @@ function tryParseInlineRef(input) {
75
75
  }
76
76
  return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
77
77
  }
78
+ function runLength(input, index, char) {
79
+ let end = index;
80
+ while (end < input.length && input[end] === char) end++;
81
+ return end - index;
82
+ }
83
+ function tryParseEmphasis(input) {
84
+ const char = input[0];
85
+ if (char !== "*" && char !== "_") return null;
86
+ const open = Math.min(3, runLength(input, 0, char));
87
+ const need = open;
88
+ let i = open;
89
+ while (i < input.length) {
90
+ const ch = input[i];
91
+ if (ch === "`") {
92
+ const close = input.indexOf("`", i + 1);
93
+ if (close === -1) break;
94
+ i = close + 1;
95
+ continue;
96
+ }
97
+ if (ch !== char) {
98
+ i++;
99
+ continue;
100
+ }
101
+ const run = runLength(input, i, char);
102
+ const closes = need === 1 ? run % 2 === 1 : run >= need;
103
+ if (!closes) {
104
+ i += run;
105
+ continue;
106
+ }
107
+ const extra = run - need;
108
+ const inner = input.slice(open, i + extra);
109
+ if (inner.length === 0) return null;
110
+ const length = i + run;
111
+ const children = parseInline(inner);
112
+ let token;
113
+ if (need === 3) {
114
+ token = { type: "bold", children: [{ type: "italic", children }] };
115
+ } else if (need === 2) {
116
+ token = { type: "bold", children };
117
+ } else {
118
+ token = { type: "italic", children };
119
+ }
120
+ return { token, length };
121
+ }
122
+ return null;
123
+ }
78
124
  function parseInline(content) {
79
125
  if (!content) return [];
80
126
  const tokens = [];
@@ -146,17 +192,11 @@ function parseInline(content) {
146
192
  remaining = remaining.slice(linkMatch[0].length);
147
193
  continue;
148
194
  }
149
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
150
- if (boldMatch) {
151
- const innerContent = boldMatch[1] ?? boldMatch[2];
152
- if (innerContent !== void 0) {
153
- tokens.push({
154
- type: "bold",
155
- children: parseInline(innerContent)
156
- });
157
- remaining = remaining.slice(boldMatch[0].length);
158
- continue;
159
- }
195
+ const emphasis = tryParseEmphasis(remaining);
196
+ if (emphasis) {
197
+ tokens.push(emphasis.token);
198
+ remaining = remaining.slice(emphasis.length);
199
+ continue;
160
200
  }
161
201
  const strikeMatch = remaining.match(/^~~(.+?)~~/);
162
202
  if (strikeMatch && strikeMatch[1] !== void 0) {
@@ -167,18 +207,6 @@ function parseInline(content) {
167
207
  remaining = remaining.slice(strikeMatch[0].length);
168
208
  continue;
169
209
  }
170
- const italicMatch = remaining.match(/^\*([^*]+)\*|^_([^_]+)_/);
171
- if (italicMatch) {
172
- const innerContent = italicMatch[1] ?? italicMatch[2];
173
- if (innerContent !== void 0) {
174
- tokens.push({
175
- type: "italic",
176
- children: parseInline(innerContent)
177
- });
178
- remaining = remaining.slice(italicMatch[0].length);
179
- continue;
180
- }
181
- }
182
210
  const nextSpecial = findNextSpecialChar(remaining);
183
211
  if (nextSpecial > 0) {
184
212
  tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
@@ -213,17 +241,30 @@ function mergeAdjacentText(tokens) {
213
241
  }
214
242
  return result;
215
243
  }
216
- var ReferenceBasesContext = createContext(null);
244
+ function normalizeReferenceContent(content) {
245
+ return content.replace(/\s+/g, " ").trim().toLowerCase();
246
+ }
247
+ var ReferenceNumberingContext = createContext(null);
217
248
  var BlockReferenceScopeContext = createContext(null);
218
- function BlockReferenceScope({ blockId, children }) {
219
- const bases = useContext(ReferenceBasesContext);
220
- const base = (blockId ? bases?.get(blockId) : void 0) ?? { ref: 0, footnote: 0 };
249
+ function BlockReferenceScope({ children }) {
250
+ const numbering = useContext(ReferenceNumberingContext);
251
+ const local = useRef({ ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() });
221
252
  const counter = useRef({ ref: 0, footnote: 0 });
222
- counter.current = { ref: 0, footnote: 0 };
223
253
  const value = {
254
+ numberFor(kind, content) {
255
+ const key = normalizeReferenceContent(content);
256
+ const fromDocument = numbering?.[kind].get(key);
257
+ if (fromDocument !== void 0) return fromDocument;
258
+ const map = local.current[kind];
259
+ const existing = map.get(key);
260
+ if (existing !== void 0) return existing;
261
+ counter.current[kind] += 1;
262
+ map.set(key, counter.current[kind]);
263
+ return counter.current[kind];
264
+ },
224
265
  next(kind) {
225
266
  counter.current[kind] += 1;
226
- return base[kind] + counter.current[kind];
267
+ return counter.current[kind];
227
268
  }
228
269
  };
229
270
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
@@ -231,7 +272,7 @@ function BlockReferenceScope({ blockId, children }) {
231
272
  function useBlockReferenceScope() {
232
273
  return useContext(BlockReferenceScopeContext);
233
274
  }
234
- var INERT_SCOPE = { next: () => 0 };
275
+ var INERT_SCOPE = { numberFor: () => 0, next: () => 0 };
235
276
  function InertReferenceScope({ children }) {
236
277
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
237
278
  }
@@ -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
  }
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { createContext, useContext, createElement, useState, useEffect, useMemo, useCallback, useRef, Component, useId } from 'react';
2
+ import { createContext, useContext, createElement, useMemo, useState, useEffect, useCallback, useRef, Component, useId } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
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/context/ncm-context.tsx
@@ -75,6 +75,52 @@ function tryParseInlineRef(input) {
75
75
  }
76
76
  return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
77
77
  }
78
+ function runLength(input, index, char) {
79
+ let end = index;
80
+ while (end < input.length && input[end] === char) end++;
81
+ return end - index;
82
+ }
83
+ function tryParseEmphasis(input) {
84
+ const char = input[0];
85
+ if (char !== "*" && char !== "_") return null;
86
+ const open = Math.min(3, runLength(input, 0, char));
87
+ const need = open;
88
+ let i = open;
89
+ while (i < input.length) {
90
+ const ch = input[i];
91
+ if (ch === "`") {
92
+ const close = input.indexOf("`", i + 1);
93
+ if (close === -1) break;
94
+ i = close + 1;
95
+ continue;
96
+ }
97
+ if (ch !== char) {
98
+ i++;
99
+ continue;
100
+ }
101
+ const run = runLength(input, i, char);
102
+ const closes = need === 1 ? run % 2 === 1 : run >= need;
103
+ if (!closes) {
104
+ i += run;
105
+ continue;
106
+ }
107
+ const extra = run - need;
108
+ const inner = input.slice(open, i + extra);
109
+ if (inner.length === 0) return null;
110
+ const length = i + run;
111
+ const children = parseInline(inner);
112
+ let token;
113
+ if (need === 3) {
114
+ token = { type: "bold", children: [{ type: "italic", children }] };
115
+ } else if (need === 2) {
116
+ token = { type: "bold", children };
117
+ } else {
118
+ token = { type: "italic", children };
119
+ }
120
+ return { token, length };
121
+ }
122
+ return null;
123
+ }
78
124
  function parseInline(content) {
79
125
  if (!content) return [];
80
126
  const tokens = [];
@@ -146,17 +192,11 @@ function parseInline(content) {
146
192
  remaining = remaining.slice(linkMatch[0].length);
147
193
  continue;
148
194
  }
149
- const boldMatch = remaining.match(/^\*\*(.+?)\*\*|^__(.+?)__/);
150
- if (boldMatch) {
151
- const innerContent = boldMatch[1] ?? boldMatch[2];
152
- if (innerContent !== void 0) {
153
- tokens.push({
154
- type: "bold",
155
- children: parseInline(innerContent)
156
- });
157
- remaining = remaining.slice(boldMatch[0].length);
158
- continue;
159
- }
195
+ const emphasis = tryParseEmphasis(remaining);
196
+ if (emphasis) {
197
+ tokens.push(emphasis.token);
198
+ remaining = remaining.slice(emphasis.length);
199
+ continue;
160
200
  }
161
201
  const strikeMatch = remaining.match(/^~~(.+?)~~/);
162
202
  if (strikeMatch && strikeMatch[1] !== void 0) {
@@ -167,18 +207,6 @@ function parseInline(content) {
167
207
  remaining = remaining.slice(strikeMatch[0].length);
168
208
  continue;
169
209
  }
170
- const italicMatch = remaining.match(/^\*([^*]+)\*|^_([^_]+)_/);
171
- if (italicMatch) {
172
- const innerContent = italicMatch[1] ?? italicMatch[2];
173
- if (innerContent !== void 0) {
174
- tokens.push({
175
- type: "italic",
176
- children: parseInline(innerContent)
177
- });
178
- remaining = remaining.slice(italicMatch[0].length);
179
- continue;
180
- }
181
- }
182
210
  const nextSpecial = findNextSpecialChar(remaining);
183
211
  if (nextSpecial > 0) {
184
212
  tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
@@ -213,17 +241,30 @@ function mergeAdjacentText(tokens) {
213
241
  }
214
242
  return result;
215
243
  }
216
- var ReferenceBasesContext = createContext(null);
244
+ function normalizeReferenceContent(content) {
245
+ return content.replace(/\s+/g, " ").trim().toLowerCase();
246
+ }
247
+ var ReferenceNumberingContext = createContext(null);
217
248
  var BlockReferenceScopeContext = createContext(null);
218
- function BlockReferenceScope({ blockId, children }) {
219
- const bases = useContext(ReferenceBasesContext);
220
- const base = (blockId ? bases?.get(blockId) : void 0) ?? { ref: 0, footnote: 0 };
249
+ function BlockReferenceScope({ children }) {
250
+ const numbering = useContext(ReferenceNumberingContext);
251
+ const local = useRef({ ref: /* @__PURE__ */ new Map(), footnote: /* @__PURE__ */ new Map() });
221
252
  const counter = useRef({ ref: 0, footnote: 0 });
222
- counter.current = { ref: 0, footnote: 0 };
223
253
  const value = {
254
+ numberFor(kind, content) {
255
+ const key = normalizeReferenceContent(content);
256
+ const fromDocument = numbering?.[kind].get(key);
257
+ if (fromDocument !== void 0) return fromDocument;
258
+ const map = local.current[kind];
259
+ const existing = map.get(key);
260
+ if (existing !== void 0) return existing;
261
+ counter.current[kind] += 1;
262
+ map.set(key, counter.current[kind]);
263
+ return counter.current[kind];
264
+ },
224
265
  next(kind) {
225
266
  counter.current[kind] += 1;
226
- return base[kind] + counter.current[kind];
267
+ return counter.current[kind];
227
268
  }
228
269
  };
229
270
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
@@ -231,7 +272,7 @@ function BlockReferenceScope({ blockId, children }) {
231
272
  function useBlockReferenceScope() {
232
273
  return useContext(BlockReferenceScopeContext);
233
274
  }
234
- var INERT_SCOPE = { next: () => 0 };
275
+ var INERT_SCOPE = { numberFor: () => 0, next: () => 0 };
235
276
  function InertReferenceScope({ children }) {
236
277
  return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
237
278
  }
@@ -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
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { C as CodeConfig, E as EmbeddingsConfig, I as ImageErrorProps, a as InlineConfig, N as NcmConfig, b as NcmProvider, c as NcmProviderProps, u as useNcmConfig } from './use-ncm-config-CnntcShj.js';
2
- export { B as BlockReferenceScope, a as BlockReferenceScopeProps, b as BlockReferenceScopeValue, I as InertReferenceScope, N as NcmReferencesProvider, c as NcmReferencesProviderProps, R as ReferenceCounts, d as ReferenceKind, e as computeReferenceBases, u as useBlockReferenceScope } from './references-BYW5pnj_.js';
2
+ export { B as BlockReferenceScope, a as BlockReferenceScopeProps, b as BlockReferenceScopeValue, I as InertReferenceScope, N as NcmReferencesProvider, c as NcmReferencesProviderProps, R as ReferenceCounts, d as ReferenceKind, e as ReferenceNumbering, f as collectBibliography, g as computeReferenceBases, h as computeReferenceNumbering, n as normalizeReferenceContent, u as useBlockReferenceScope } from './references-BM-lvGrq.js';
3
3
  export { B as BlockRenderer, a as BlockRendererProps, b as BlockRendererRegistry, C as CompleteBlockRendererRegistry, E as EmbeddingRendererProps, c as EmbeddingRendererRegistry, K as KeyedBlockType } from './renderers-C5f41FCy.js';
4
4
  export { B as BoldToken, C as CodeToken, I as ImageToken, a as InlineRefToken, b as InlineToken, c as ItalicToken, L as LatexToken, d as LinkToken, S as StrikethroughToken, T as TextToken } from './inline-DwUYUmk4.js';
5
5
  export { BlockShell, BlockShellProps, ChildBlocks, ChildBlocksProps, NcmBlockPreview, NcmBlockPreviewProps, blockDataAttributes, blockKey } from './components/index.js';