@nuucognition/ncm-ui 0.1.0-dev.1
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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/blocks/admonition/index.d.ts +13 -0
- package/dist/blocks/admonition/index.js +1713 -0
- package/dist/blocks/aside/index.d.ts +13 -0
- package/dist/blocks/aside/index.js +1713 -0
- package/dist/blocks/attribution/index.d.ts +13 -0
- package/dist/blocks/attribution/index.js +32 -0
- package/dist/blocks/blockquote/index.d.ts +15 -0
- package/dist/blocks/blockquote/index.js +609 -0
- package/dist/blocks/chart/index.d.ts +14 -0
- package/dist/blocks/chart/index.js +29 -0
- package/dist/blocks/code/index.d.ts +15 -0
- package/dist/blocks/code/index.js +163 -0
- package/dist/blocks/container/index.d.ts +17 -0
- package/dist/blocks/container/index.js +633 -0
- package/dist/blocks/custom-code/index.d.ts +15 -0
- package/dist/blocks/custom-code/index.js +31 -0
- package/dist/blocks/dataview/index.d.ts +14 -0
- package/dist/blocks/dataview/index.js +29 -0
- package/dist/blocks/definition/index.d.ts +13 -0
- package/dist/blocks/definition/index.js +1713 -0
- package/dist/blocks/embed/index.d.ts +12 -0
- package/dist/blocks/embed/index.js +46 -0
- package/dist/blocks/embedding/index.d.ts +13 -0
- package/dist/blocks/embedding/index.js +763 -0
- package/dist/blocks/figure/index.d.ts +13 -0
- package/dist/blocks/figure/index.js +656 -0
- package/dist/blocks/footnote/index.d.ts +13 -0
- package/dist/blocks/footnote/index.js +1713 -0
- package/dist/blocks/frontmatter/index.d.ts +16 -0
- package/dist/blocks/frontmatter/index.js +38 -0
- package/dist/blocks/header/index.d.ts +9 -0
- package/dist/blocks/header/index.js +610 -0
- package/dist/blocks/horizontal-rule/index.d.ts +9 -0
- package/dist/blocks/horizontal-rule/index.js +19 -0
- package/dist/blocks/html/index.d.ts +31 -0
- package/dist/blocks/html/index.js +52 -0
- package/dist/blocks/index.d.ts +32 -0
- package/dist/blocks/index.js +1736 -0
- package/dist/blocks/latex/index.d.ts +9 -0
- package/dist/blocks/latex/index.js +107 -0
- package/dist/blocks/link/index.d.ts +14 -0
- package/dist/blocks/link/index.js +27 -0
- package/dist/blocks/list/index.d.ts +9 -0
- package/dist/blocks/list/index.js +675 -0
- package/dist/blocks/math/index.d.ts +13 -0
- package/dist/blocks/math/index.js +88 -0
- package/dist/blocks/mermaid/index.d.ts +14 -0
- package/dist/blocks/mermaid/index.js +29 -0
- package/dist/blocks/page-breaker/index.d.ts +16 -0
- package/dist/blocks/page-breaker/index.js +21 -0
- package/dist/blocks/paragraph/index.d.ts +9 -0
- package/dist/blocks/paragraph/index.js +608 -0
- package/dist/blocks/spoiler/index.d.ts +13 -0
- package/dist/blocks/spoiler/index.js +1713 -0
- package/dist/blocks/table/index.d.ts +9 -0
- package/dist/blocks/table/index.js +619 -0
- package/dist/blocks/task-list/index.d.ts +13 -0
- package/dist/blocks/task-list/index.js +670 -0
- package/dist/components/index.d.ts +599 -0
- package/dist/components/index.js +1840 -0
- package/dist/components/ncm-block.d.ts +28 -0
- package/dist/components/ncm-block.js +1712 -0
- package/dist/components/ncm-document.d.ts +48 -0
- package/dist/components/ncm-document.js +1839 -0
- package/dist/context/index.d.ts +7 -0
- package/dist/context/index.js +284 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +1731 -0
- package/dist/hooks/use-block-renderer.d.ts +28 -0
- package/dist/hooks/use-block-renderer.js +1731 -0
- package/dist/hooks/use-ncm-config.d.ts +6 -0
- package/dist/hooks/use-ncm-config.js +11 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +1887 -0
- package/dist/inline/index.d.ts +108 -0
- package/dist/inline/index.js +598 -0
- package/dist/inline-DwUYUmk4.d.ts +75 -0
- package/dist/lib/defaults.d.ts +24 -0
- package/dist/lib/defaults.js +1713 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/katex.d.ts +3 -0
- package/dist/lib/katex.js +45 -0
- package/dist/lib/utils.d.ts +8 -0
- package/dist/lib/utils.js +10 -0
- package/dist/references-Bs5wJybr.d.ts +63 -0
- package/dist/renderers-C5f41FCy.d.ts +99 -0
- package/dist/styles/fonts/OpenDyslexic3-Bold.woff +0 -0
- package/dist/styles/fonts/OpenDyslexic3-Bold.woff2 +0 -0
- package/dist/styles/fonts/OpenDyslexic3-Regular.woff +0 -0
- package/dist/styles/fonts/OpenDyslexic3-Regular.woff2 +0 -0
- package/dist/styles/nuu.css +364 -0
- package/dist/styles/paper.css +332 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.js +2 -0
- package/dist/use-ncm-config-D0doMYTH.d.ts +114 -0
- package/package.json +99 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { b as InlineToken } from '../inline-DwUYUmk4.js';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { d as ReferenceKind } from '../references-Bs5wJybr.js';
|
|
5
|
+
import '@nuucognition/ncm';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse inline markdown content into tokens
|
|
9
|
+
*
|
|
10
|
+
* Supported patterns (in priority order):
|
|
11
|
+
* - Inline code: `code`
|
|
12
|
+
* - Inline LaTeX: $math$
|
|
13
|
+
* - Images: 
|
|
14
|
+
* - Links: [text](url "title")
|
|
15
|
+
* - Inline reference: ^[ref: ...] / ^[footnote: ...]
|
|
16
|
+
* - Bold: **text** or __text__
|
|
17
|
+
* - Italic: *text* or _text_
|
|
18
|
+
* - Strikethrough: ~~text~~
|
|
19
|
+
*/
|
|
20
|
+
declare function parseInline(content: string): InlineToken[];
|
|
21
|
+
|
|
22
|
+
interface InlineContentProps {
|
|
23
|
+
content: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Suppress bionic-reading transformation for this subtree even if the
|
|
27
|
+
* `NcmConfig.reading.bionic` flag is on. Used by block renderers (e.g.
|
|
28
|
+
* `HeaderBlock`) that should keep their type rendering pristine.
|
|
29
|
+
*/
|
|
30
|
+
disableBionic?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Suppress beeline word-wrapping for this subtree even if the
|
|
33
|
+
* `NcmConfig.reading.beeline` flag is on. Used by `HeaderBlock` so
|
|
34
|
+
* headings keep their type weight unaffected by the gradient pass.
|
|
35
|
+
*/
|
|
36
|
+
disableBeeline?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Render inline markdown content with full parsing support.
|
|
40
|
+
*
|
|
41
|
+
* Emits semantic HTML and `data-ncm-token` attributes; visual styling is owned
|
|
42
|
+
* by the parent document's stylesheet (nuu.css or paper.css).
|
|
43
|
+
*/
|
|
44
|
+
declare function InlineContent({ content, className, disableBionic, disableBeeline, }: InlineContentProps): react_jsx_runtime.JSX.Element;
|
|
45
|
+
interface RenderReadingTextOptions {
|
|
46
|
+
bionic?: boolean;
|
|
47
|
+
bionicRatio?: number;
|
|
48
|
+
beeline?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Walk plain text and emit per-word wrappers for the configured reading aids.
|
|
52
|
+
*
|
|
53
|
+
* The same Unicode word regex is shared with the Surf reader's word
|
|
54
|
+
* selection so the markup, the selection range engine, and the per-word
|
|
55
|
+
* gradient pass all see the same word boundaries.
|
|
56
|
+
*
|
|
57
|
+
* - `bionic`: split each word into a head (`<span data-ncm-bionic-bold>`)
|
|
58
|
+
* and a tail to give the eye an anchor near the start of the word.
|
|
59
|
+
* - `beeline`: mark the whole word with `data-ncm-beeline-word` so the
|
|
60
|
+
* host can paint a per-line color gradient after layout. NCM does not
|
|
61
|
+
* assign colors itself — the host owns the palette and line grouping.
|
|
62
|
+
*
|
|
63
|
+
* Both can be active at once: the outer span carries both data attributes
|
|
64
|
+
* and the bionic head/tail split lives inside.
|
|
65
|
+
*/
|
|
66
|
+
declare function renderReadingText(content: string, opts: RenderReadingTextOptions): React.ReactNode;
|
|
67
|
+
/**
|
|
68
|
+
* Render plain text with "bionic reading" first-half-of-word bolding.
|
|
69
|
+
* Backwards-compatible wrapper around `renderReadingText({ bionic: true })`.
|
|
70
|
+
*/
|
|
71
|
+
declare function renderBionicText(content: string, ratio?: number): React.ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* Render plain text with BeeLine word-wrapping. Each word is marked with
|
|
74
|
+
* `data-ncm-beeline-word` so a host-side colorize pass can assign per-line
|
|
75
|
+
* gradient colors after layout. No bionic head/tail split is applied here
|
|
76
|
+
* — when both reading aids are active, route through `renderReadingText`
|
|
77
|
+
* directly so the single output carries both data attributes.
|
|
78
|
+
*/
|
|
79
|
+
declare function renderBeelineText(content: string): React.ReactNode;
|
|
80
|
+
|
|
81
|
+
interface ReferenceMarkerProps {
|
|
82
|
+
kind: ReferenceKind;
|
|
83
|
+
/** Document-order number for this kind (refs and footnotes count separately). */
|
|
84
|
+
number: number;
|
|
85
|
+
/**
|
|
86
|
+
* Rendered popover content (the reference's inline body). When omitted, or
|
|
87
|
+
* when `NcmConfig.inline.referencePopover` is `false`, no popover is emitted.
|
|
88
|
+
*/
|
|
89
|
+
children?: ReactNode;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Inline reference marker.
|
|
93
|
+
*
|
|
94
|
+
* - `ref` → baseline bracketed number `[1]`, `[2]`, … (citation style)
|
|
95
|
+
* - `footnote` → superscript number `¹`, `²`, … (footnote style)
|
|
96
|
+
*
|
|
97
|
+
* The marker carries an accessible label and, when a popover is enabled,
|
|
98
|
+
* describes itself via the hidden popover content. Visual styling (including
|
|
99
|
+
* the hover/focus reveal of the popover) is owned by the document stylesheet
|
|
100
|
+
* (`nuu.css` / `paper.css`) via the `data-ncm-*` hooks below.
|
|
101
|
+
*
|
|
102
|
+
* On reveal the popover is nudged horizontally (and flipped above when needed)
|
|
103
|
+
* so it never spills outside the reading pane — measured lazily, only for the
|
|
104
|
+
* marker actually being hovered/focused, so the common case stays JS-free.
|
|
105
|
+
*/
|
|
106
|
+
declare function ReferenceMarker({ kind, number, children }: ReferenceMarkerProps): react_jsx_runtime.JSX.Element;
|
|
107
|
+
|
|
108
|
+
export { InlineContent, type InlineContentProps, ReferenceMarker, type ReferenceMarkerProps, type RenderReadingTextOptions, parseInline, renderBeelineText, renderBionicText, renderReadingText };
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useId, useRef, useCallback, useMemo, useContext, useState, useEffect } from 'react';
|
|
3
|
+
import { extractExtension, embedTypeFromExtension } from '@nuucognition/mesh-core';
|
|
4
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import '@nuucognition/ncm';
|
|
6
|
+
|
|
7
|
+
// src/inline/parser.ts
|
|
8
|
+
function splitOnce(input, delimiter) {
|
|
9
|
+
const idx = input.indexOf(delimiter);
|
|
10
|
+
if (idx === -1) {
|
|
11
|
+
return [input, null];
|
|
12
|
+
}
|
|
13
|
+
return [input.slice(0, idx), input.slice(idx + delimiter.length)];
|
|
14
|
+
}
|
|
15
|
+
function parseWikiTarget(rawInside) {
|
|
16
|
+
const inside = rawInside.trim();
|
|
17
|
+
const [targetPlusAnchor, displayRaw] = splitOnce(inside, "|");
|
|
18
|
+
const [targetRaw, anchorRaw] = splitOnce(targetPlusAnchor, "#");
|
|
19
|
+
const target = targetRaw.trim();
|
|
20
|
+
const display = displayRaw !== null ? displayRaw.trim() || null : null;
|
|
21
|
+
const anchor = anchorRaw !== null ? anchorRaw.trim() || null : null;
|
|
22
|
+
return { target, display, anchor };
|
|
23
|
+
}
|
|
24
|
+
var INLINE_REF_KIND_RE = /^\s*(ref|footnote)\s*:\s*([\s\S]*)$/i;
|
|
25
|
+
function tryParseInlineRef(input) {
|
|
26
|
+
let depth = 1;
|
|
27
|
+
let i = 2;
|
|
28
|
+
for (; i < input.length; i++) {
|
|
29
|
+
const ch = input[i];
|
|
30
|
+
if (ch === "[") {
|
|
31
|
+
depth++;
|
|
32
|
+
} else if (ch === "]") {
|
|
33
|
+
depth--;
|
|
34
|
+
if (depth === 0) break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (depth !== 0) return null;
|
|
38
|
+
const inner = input.slice(2, i);
|
|
39
|
+
const length = i + 1;
|
|
40
|
+
const match = INLINE_REF_KIND_RE.exec(inner);
|
|
41
|
+
if (match && match[1]) {
|
|
42
|
+
const kind = match[1].toLowerCase() === "ref" ? "ref" : "footnote";
|
|
43
|
+
return { token: { type: "inline_ref", kind, content: (match[2] ?? "").trim() }, length };
|
|
44
|
+
}
|
|
45
|
+
return { token: { type: "inline_ref", kind: "footnote", content: inner.trim() }, length };
|
|
46
|
+
}
|
|
47
|
+
function parseInline(content) {
|
|
48
|
+
if (!content) return [];
|
|
49
|
+
const tokens = [];
|
|
50
|
+
let remaining = content;
|
|
51
|
+
while (remaining.length > 0) {
|
|
52
|
+
const codeMatch = remaining.match(/^`([^`]+)`/);
|
|
53
|
+
if (codeMatch && codeMatch[1] !== void 0) {
|
|
54
|
+
tokens.push({ type: "code", content: codeMatch[1] });
|
|
55
|
+
remaining = remaining.slice(codeMatch[0].length);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const latexMatch = remaining.match(/^\$(?!\s)([^$\n]+?)(?<!\s)\$/);
|
|
59
|
+
if (latexMatch && latexMatch[1] !== void 0) {
|
|
60
|
+
tokens.push({ type: "latex", content: latexMatch[1] });
|
|
61
|
+
remaining = remaining.slice(latexMatch[0].length);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (remaining.startsWith("^[")) {
|
|
65
|
+
const inlineRef = tryParseInlineRef(remaining);
|
|
66
|
+
if (inlineRef) {
|
|
67
|
+
tokens.push(inlineRef.token);
|
|
68
|
+
remaining = remaining.slice(inlineRef.length);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const wikiEmbedMatch = remaining.match(/^!\[\[([^\]]+)\]\]/);
|
|
73
|
+
if (wikiEmbedMatch && wikiEmbedMatch[1] !== void 0) {
|
|
74
|
+
const parsed = parseWikiTarget(wikiEmbedMatch[1]);
|
|
75
|
+
tokens.push({
|
|
76
|
+
type: "wiki_embed",
|
|
77
|
+
target: parsed.target,
|
|
78
|
+
display: parsed.display,
|
|
79
|
+
anchor: parsed.anchor
|
|
80
|
+
});
|
|
81
|
+
remaining = remaining.slice(wikiEmbedMatch[0].length);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const wikiLinkMatch = remaining.match(/^\[\[([^\]]+)\]\]/);
|
|
85
|
+
if (wikiLinkMatch && wikiLinkMatch[1] !== void 0) {
|
|
86
|
+
const parsed = parseWikiTarget(wikiLinkMatch[1]);
|
|
87
|
+
tokens.push({
|
|
88
|
+
type: "wiki_link",
|
|
89
|
+
target: parsed.target,
|
|
90
|
+
display: parsed.display,
|
|
91
|
+
anchor: parsed.anchor
|
|
92
|
+
});
|
|
93
|
+
remaining = remaining.slice(wikiLinkMatch[0].length);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const imageMatch = remaining.match(/^!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
|
|
97
|
+
if (imageMatch && imageMatch[1] !== void 0 && imageMatch[2] !== void 0) {
|
|
98
|
+
tokens.push({
|
|
99
|
+
type: "image",
|
|
100
|
+
alt: imageMatch[1],
|
|
101
|
+
src: imageMatch[2],
|
|
102
|
+
title: imageMatch[3]
|
|
103
|
+
});
|
|
104
|
+
remaining = remaining.slice(imageMatch[0].length);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const linkMatch = remaining.match(/^\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/);
|
|
108
|
+
if (linkMatch && linkMatch[1] !== void 0 && linkMatch[2] !== void 0) {
|
|
109
|
+
tokens.push({
|
|
110
|
+
type: "link",
|
|
111
|
+
href: linkMatch[2],
|
|
112
|
+
title: linkMatch[3],
|
|
113
|
+
children: parseInline(linkMatch[1])
|
|
114
|
+
});
|
|
115
|
+
remaining = remaining.slice(linkMatch[0].length);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
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
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const strikeMatch = remaining.match(/^~~(.+?)~~/);
|
|
131
|
+
if (strikeMatch && strikeMatch[1] !== void 0) {
|
|
132
|
+
tokens.push({
|
|
133
|
+
type: "strikethrough",
|
|
134
|
+
children: parseInline(strikeMatch[1])
|
|
135
|
+
});
|
|
136
|
+
remaining = remaining.slice(strikeMatch[0].length);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
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
|
+
const nextSpecial = findNextSpecialChar(remaining);
|
|
152
|
+
if (nextSpecial > 0) {
|
|
153
|
+
tokens.push({ type: "text", content: remaining.slice(0, nextSpecial) });
|
|
154
|
+
remaining = remaining.slice(nextSpecial);
|
|
155
|
+
} else {
|
|
156
|
+
tokens.push({ type: "text", content: remaining });
|
|
157
|
+
remaining = "";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return mergeAdjacentText(tokens);
|
|
161
|
+
}
|
|
162
|
+
function findNextSpecialChar(content) {
|
|
163
|
+
const specialChars = ["`", "$", "!", "[", "*", "_", "~", "^"];
|
|
164
|
+
let minIndex = -1;
|
|
165
|
+
for (const char of specialChars) {
|
|
166
|
+
const index = content.indexOf(char);
|
|
167
|
+
if (index > 0 && (minIndex === -1 || index < minIndex)) {
|
|
168
|
+
minIndex = index;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return minIndex;
|
|
172
|
+
}
|
|
173
|
+
function mergeAdjacentText(tokens) {
|
|
174
|
+
const result = [];
|
|
175
|
+
for (const token of tokens) {
|
|
176
|
+
const last = result[result.length - 1];
|
|
177
|
+
if (token.type === "text" && last?.type === "text") {
|
|
178
|
+
last.content += token.content;
|
|
179
|
+
} else {
|
|
180
|
+
result.push(token);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
var NcmContext = createContext(null);
|
|
186
|
+
function useNcmConfig() {
|
|
187
|
+
return useContext(NcmContext) ?? {};
|
|
188
|
+
}
|
|
189
|
+
function clipBounds(el) {
|
|
190
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
191
|
+
const s = getComputedStyle(node);
|
|
192
|
+
if (/(auto|scroll|hidden)/.test(s.overflowX) || /(auto|scroll|hidden)/.test(s.overflowY)) {
|
|
193
|
+
const r = node.getBoundingClientRect();
|
|
194
|
+
return { left: r.left, right: r.right, top: r.top, bottom: r.bottom };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight };
|
|
198
|
+
}
|
|
199
|
+
function ReferenceMarker({ kind, number, children }) {
|
|
200
|
+
const config = useNcmConfig();
|
|
201
|
+
const popoverEnabled = config.inline?.referencePopover !== false && children != null;
|
|
202
|
+
const popoverId = useId();
|
|
203
|
+
const isRef = kind === "ref";
|
|
204
|
+
const popoverRef = useRef(null);
|
|
205
|
+
const positionPopover = useCallback(() => {
|
|
206
|
+
const el = popoverRef.current;
|
|
207
|
+
if (!el) return;
|
|
208
|
+
el.style.transform = "";
|
|
209
|
+
el.style.top = "";
|
|
210
|
+
el.style.bottom = "";
|
|
211
|
+
const margin = 8;
|
|
212
|
+
const bounds = clipBounds(el);
|
|
213
|
+
const rect = el.getBoundingClientRect();
|
|
214
|
+
if (rect.bottom > bounds.bottom - margin && rect.height < rect.top - bounds.top) {
|
|
215
|
+
el.style.top = "auto";
|
|
216
|
+
el.style.bottom = "1.6em";
|
|
217
|
+
}
|
|
218
|
+
let dx = 0;
|
|
219
|
+
if (rect.right > bounds.right - margin) dx = bounds.right - margin - rect.right;
|
|
220
|
+
if (rect.left + dx < bounds.left + margin) dx = bounds.left + margin - rect.left;
|
|
221
|
+
if (dx) el.style.transform = `translateX(${Math.round(dx)}px)`;
|
|
222
|
+
}, []);
|
|
223
|
+
const describedBy = popoverEnabled ? popoverId : void 0;
|
|
224
|
+
const label = `${isRef ? "Reference" : "Footnote"} ${number}`;
|
|
225
|
+
const marker = isRef ? /* @__PURE__ */ jsx(
|
|
226
|
+
"span",
|
|
227
|
+
{
|
|
228
|
+
"data-ncm-ref-marker": "",
|
|
229
|
+
tabIndex: 0,
|
|
230
|
+
role: "note",
|
|
231
|
+
"aria-label": label,
|
|
232
|
+
"aria-describedby": describedBy,
|
|
233
|
+
children: `[${number}]`
|
|
234
|
+
}
|
|
235
|
+
) : /* @__PURE__ */ jsx(
|
|
236
|
+
"sup",
|
|
237
|
+
{
|
|
238
|
+
"data-ncm-ref-marker": "",
|
|
239
|
+
tabIndex: 0,
|
|
240
|
+
role: "note",
|
|
241
|
+
"aria-label": label,
|
|
242
|
+
"aria-describedby": describedBy,
|
|
243
|
+
children: number
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
return /* @__PURE__ */ jsxs(
|
|
247
|
+
"span",
|
|
248
|
+
{
|
|
249
|
+
"data-ncm-token": isRef ? "reference" : "footnote",
|
|
250
|
+
"data-ncm-ref-kind": kind,
|
|
251
|
+
"data-ncm-ref-number": number,
|
|
252
|
+
onMouseEnter: popoverEnabled ? positionPopover : void 0,
|
|
253
|
+
onFocus: popoverEnabled ? positionPopover : void 0,
|
|
254
|
+
children: [
|
|
255
|
+
marker,
|
|
256
|
+
popoverEnabled ? /* @__PURE__ */ jsx("span", { id: popoverId, ref: popoverRef, role: "tooltip", "data-ncm-ref-popover": "", children }) : null
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
var ReferenceBasesContext = createContext(null);
|
|
262
|
+
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 };
|
|
266
|
+
const counter = useRef({ ref: 0, footnote: 0 });
|
|
267
|
+
counter.current = { ref: 0, footnote: 0 };
|
|
268
|
+
const value = {
|
|
269
|
+
next(kind) {
|
|
270
|
+
counter.current[kind] += 1;
|
|
271
|
+
return base[kind] + counter.current[kind];
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value, children });
|
|
275
|
+
}
|
|
276
|
+
function useBlockReferenceScope() {
|
|
277
|
+
return useContext(BlockReferenceScopeContext);
|
|
278
|
+
}
|
|
279
|
+
var INERT_SCOPE = { next: () => 0 };
|
|
280
|
+
function InertReferenceScope({ children }) {
|
|
281
|
+
return /* @__PURE__ */ jsx(BlockReferenceScopeContext.Provider, { value: INERT_SCOPE, children });
|
|
282
|
+
}
|
|
283
|
+
var katexPromise = null;
|
|
284
|
+
function loadKatex() {
|
|
285
|
+
if (!katexPromise) {
|
|
286
|
+
katexPromise = Promise.all([
|
|
287
|
+
import('katex'),
|
|
288
|
+
import('katex/dist/katex.min.css')
|
|
289
|
+
]).then(([module]) => module.default).catch(() => null);
|
|
290
|
+
}
|
|
291
|
+
return katexPromise;
|
|
292
|
+
}
|
|
293
|
+
function useKatexHtml(content, displayMode, enabled = true) {
|
|
294
|
+
const [katexModule, setKatexModule] = useState(null);
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
if (!enabled) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
let active = true;
|
|
300
|
+
loadKatex().then((loaded) => {
|
|
301
|
+
if (active) {
|
|
302
|
+
setKatexModule(loaded);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
return () => {
|
|
306
|
+
active = false;
|
|
307
|
+
};
|
|
308
|
+
}, [enabled]);
|
|
309
|
+
return useMemo(() => {
|
|
310
|
+
if (!enabled || !katexModule) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
return katexModule.renderToString(content, {
|
|
315
|
+
throwOnError: false,
|
|
316
|
+
displayMode
|
|
317
|
+
});
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}, [content, displayMode, enabled, katexModule]);
|
|
322
|
+
}
|
|
323
|
+
function InlineContent({
|
|
324
|
+
content,
|
|
325
|
+
className,
|
|
326
|
+
disableBionic,
|
|
327
|
+
disableBeeline
|
|
328
|
+
}) {
|
|
329
|
+
const tokens = useMemo(() => parseInline(content), [content]);
|
|
330
|
+
const hasScope = useBlockReferenceScope() != null;
|
|
331
|
+
const rendered = tokens.map((token, index) => /* @__PURE__ */ jsx(
|
|
332
|
+
TokenRenderer,
|
|
333
|
+
{
|
|
334
|
+
token,
|
|
335
|
+
disableBionic,
|
|
336
|
+
disableBeeline
|
|
337
|
+
},
|
|
338
|
+
index
|
|
339
|
+
));
|
|
340
|
+
const body = className ? /* @__PURE__ */ jsx("span", { "data-ncm-inline": "", className, children: rendered }) : /* @__PURE__ */ jsx(Fragment, { children: rendered });
|
|
341
|
+
return hasScope ? body : /* @__PURE__ */ jsx(BlockReferenceScope, { children: body });
|
|
342
|
+
}
|
|
343
|
+
function TokenRenderer({ token, disableBionic, disableBeeline }) {
|
|
344
|
+
const config = useNcmConfig();
|
|
345
|
+
const refScope = useBlockReferenceScope();
|
|
346
|
+
const bionicOn = !disableBionic && Boolean(config.reading?.bionic);
|
|
347
|
+
const beelineOn = !disableBeeline && Boolean(config.reading?.beeline);
|
|
348
|
+
const latexContent = token.type === "latex" ? token.content : "";
|
|
349
|
+
const latexHtml = useKatexHtml(latexContent, false, token.type === "latex");
|
|
350
|
+
switch (token.type) {
|
|
351
|
+
case "text":
|
|
352
|
+
if (bionicOn || beelineOn) {
|
|
353
|
+
return renderReadingText(token.content, {
|
|
354
|
+
bionic: bionicOn,
|
|
355
|
+
bionicRatio: config.reading?.bionicRatio,
|
|
356
|
+
beeline: beelineOn
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return /* @__PURE__ */ jsx(Fragment, { children: token.content });
|
|
360
|
+
case "bold":
|
|
361
|
+
return /* @__PURE__ */ jsx("strong", { "data-ncm-token": "bold", children: token.children.map((child, i) => /* @__PURE__ */ jsx(
|
|
362
|
+
TokenRenderer,
|
|
363
|
+
{
|
|
364
|
+
token: child,
|
|
365
|
+
disableBionic,
|
|
366
|
+
disableBeeline
|
|
367
|
+
},
|
|
368
|
+
i
|
|
369
|
+
)) });
|
|
370
|
+
case "italic":
|
|
371
|
+
return /* @__PURE__ */ jsx("em", { "data-ncm-token": "italic", children: token.children.map((child, i) => /* @__PURE__ */ jsx(
|
|
372
|
+
TokenRenderer,
|
|
373
|
+
{
|
|
374
|
+
token: child,
|
|
375
|
+
disableBionic,
|
|
376
|
+
disableBeeline
|
|
377
|
+
},
|
|
378
|
+
i
|
|
379
|
+
)) });
|
|
380
|
+
case "code":
|
|
381
|
+
return /* @__PURE__ */ jsx("code", { "data-ncm-token": "code", children: token.content });
|
|
382
|
+
case "link": {
|
|
383
|
+
const handleClick = (e) => {
|
|
384
|
+
config.inline?.onLinkClick?.(token.href, e);
|
|
385
|
+
};
|
|
386
|
+
return /* @__PURE__ */ jsx(
|
|
387
|
+
"a",
|
|
388
|
+
{
|
|
389
|
+
"data-ncm-token": "link",
|
|
390
|
+
href: token.href,
|
|
391
|
+
title: token.title,
|
|
392
|
+
onClick: handleClick,
|
|
393
|
+
children: token.children.map((child, i) => /* @__PURE__ */ jsx(
|
|
394
|
+
TokenRenderer,
|
|
395
|
+
{
|
|
396
|
+
token: child,
|
|
397
|
+
disableBionic,
|
|
398
|
+
disableBeeline
|
|
399
|
+
},
|
|
400
|
+
i
|
|
401
|
+
))
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
case "wiki_link": {
|
|
406
|
+
const result = config.mesh?.resolveLink(token.target) ?? {
|
|
407
|
+
status: "missing",
|
|
408
|
+
target: token.target,
|
|
409
|
+
resolved: null,
|
|
410
|
+
candidates: []
|
|
411
|
+
};
|
|
412
|
+
const href = config.inline?.resolveWikiLinkHref?.(token.target, result) ?? null;
|
|
413
|
+
const label = token.display ?? token.target;
|
|
414
|
+
if (href) {
|
|
415
|
+
const handleClick = (e) => {
|
|
416
|
+
config.inline?.onWikiLinkClick?.(token.target, result, e);
|
|
417
|
+
};
|
|
418
|
+
return /* @__PURE__ */ jsx(
|
|
419
|
+
"a",
|
|
420
|
+
{
|
|
421
|
+
"data-ncm-token": "wiki_link",
|
|
422
|
+
"data-ncm-wikilink-status": result.status,
|
|
423
|
+
href,
|
|
424
|
+
onClick: handleClick,
|
|
425
|
+
children: label
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (config.inline?.onWikiLinkClick) {
|
|
430
|
+
const handleClick = (e) => {
|
|
431
|
+
config.inline?.onWikiLinkClick?.(token.target, result, e);
|
|
432
|
+
};
|
|
433
|
+
return /* @__PURE__ */ jsx(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
type: "button",
|
|
437
|
+
"data-ncm-token": "wiki_link",
|
|
438
|
+
"data-ncm-wikilink-status": result.status,
|
|
439
|
+
onClick: handleClick,
|
|
440
|
+
children: label
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
return /* @__PURE__ */ jsx("span", { "data-ncm-token": "wiki_link", "data-ncm-wikilink-status": result.status, children: label });
|
|
445
|
+
}
|
|
446
|
+
case "latex": {
|
|
447
|
+
const CustomRenderer = config.inline?.customLatexRenderer;
|
|
448
|
+
if (CustomRenderer) {
|
|
449
|
+
return /* @__PURE__ */ jsx(CustomRenderer, { content: token.content, display: false });
|
|
450
|
+
}
|
|
451
|
+
if (latexHtml) {
|
|
452
|
+
return /* @__PURE__ */ jsx("span", { "data-ncm-token": "latex", dangerouslySetInnerHTML: { __html: latexHtml } });
|
|
453
|
+
}
|
|
454
|
+
return /* @__PURE__ */ jsx("code", { "data-ncm-token": "latex", children: token.content });
|
|
455
|
+
}
|
|
456
|
+
case "strikethrough":
|
|
457
|
+
return /* @__PURE__ */ jsx("del", { "data-ncm-token": "strikethrough", children: token.children.map((child, i) => /* @__PURE__ */ jsx(
|
|
458
|
+
TokenRenderer,
|
|
459
|
+
{
|
|
460
|
+
token: child,
|
|
461
|
+
disableBionic,
|
|
462
|
+
disableBeeline
|
|
463
|
+
},
|
|
464
|
+
i
|
|
465
|
+
)) });
|
|
466
|
+
case "image": {
|
|
467
|
+
const src = config.inline?.resolveAssetPath?.(token.src) ?? token.src;
|
|
468
|
+
return /* @__PURE__ */ jsx(
|
|
469
|
+
"img",
|
|
470
|
+
{
|
|
471
|
+
"data-ncm-token": "image",
|
|
472
|
+
src,
|
|
473
|
+
alt: token.alt,
|
|
474
|
+
title: token.title
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
case "wiki_embed": {
|
|
479
|
+
const extension = extractExtension(token.target);
|
|
480
|
+
const embedType = embedTypeFromExtension(extension);
|
|
481
|
+
const result = config.mesh?.resolveEmbed(token.target) ?? null;
|
|
482
|
+
const url = config.embeddings?.resolveUrl?.(token.target, embedType) ?? token.target;
|
|
483
|
+
if (result?.status === "missing") {
|
|
484
|
+
return /* @__PURE__ */ jsx(
|
|
485
|
+
"span",
|
|
486
|
+
{
|
|
487
|
+
"data-ncm-token": "wiki_embed",
|
|
488
|
+
"data-ncm-wikiembed-status": "missing",
|
|
489
|
+
children: `![[${token.target}]]`
|
|
490
|
+
}
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
if (result?.status === "ambiguous") {
|
|
494
|
+
return /* @__PURE__ */ jsx(
|
|
495
|
+
"span",
|
|
496
|
+
{
|
|
497
|
+
"data-ncm-token": "wiki_embed",
|
|
498
|
+
"data-ncm-wikiembed-status": "ambiguous",
|
|
499
|
+
children: `![[${token.target}]]`
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
if (embedType === "image") {
|
|
504
|
+
return /* @__PURE__ */ jsx(
|
|
505
|
+
"img",
|
|
506
|
+
{
|
|
507
|
+
"data-ncm-token": "wiki_embed",
|
|
508
|
+
"data-ncm-wikiembed-type": embedType,
|
|
509
|
+
src: url,
|
|
510
|
+
alt: token.display ?? token.target,
|
|
511
|
+
className: config.embeddings?.imageClassName
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (embedType === "video") {
|
|
516
|
+
return /* @__PURE__ */ jsx(
|
|
517
|
+
"video",
|
|
518
|
+
{
|
|
519
|
+
"data-ncm-token": "wiki_embed",
|
|
520
|
+
"data-ncm-wikiembed-type": embedType,
|
|
521
|
+
src: url,
|
|
522
|
+
controls: true,
|
|
523
|
+
className: config.embeddings?.videoClassName
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
if (embedType === "audio") {
|
|
528
|
+
return /* @__PURE__ */ jsx(
|
|
529
|
+
"audio",
|
|
530
|
+
{
|
|
531
|
+
"data-ncm-token": "wiki_embed",
|
|
532
|
+
"data-ncm-wikiembed-type": embedType,
|
|
533
|
+
src: url,
|
|
534
|
+
controls: true,
|
|
535
|
+
className: config.embeddings?.audioClassName
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return /* @__PURE__ */ jsx(
|
|
540
|
+
"a",
|
|
541
|
+
{
|
|
542
|
+
"data-ncm-token": "wiki_embed",
|
|
543
|
+
"data-ncm-wikiembed-type": embedType,
|
|
544
|
+
href: url,
|
|
545
|
+
children: token.display ?? token.target
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
case "inline_ref": {
|
|
550
|
+
const number = refScope?.next(token.kind) ?? 0;
|
|
551
|
+
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 });
|
|
552
|
+
}
|
|
553
|
+
default:
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function renderReadingText(content, opts) {
|
|
558
|
+
if (!content) return /* @__PURE__ */ jsx(Fragment, { children: content });
|
|
559
|
+
const { bionic = false, beeline = false } = opts;
|
|
560
|
+
if (!bionic && !beeline) return /* @__PURE__ */ jsx(Fragment, { children: content });
|
|
561
|
+
const ratio = Math.min(1, Math.max(0.05, opts.bionicRatio ?? 0.5));
|
|
562
|
+
const pieces = [];
|
|
563
|
+
const regex = /[\p{L}\p{N}][\p{L}\p{N}\p{M}'’-]*/gu;
|
|
564
|
+
let match;
|
|
565
|
+
let key = 0;
|
|
566
|
+
let cursor = 0;
|
|
567
|
+
while ((match = regex.exec(content)) !== null) {
|
|
568
|
+
if (match.index > cursor) pieces.push(content.slice(cursor, match.index));
|
|
569
|
+
const segment = match[0];
|
|
570
|
+
const wrapperProps = {};
|
|
571
|
+
if (bionic) wrapperProps["data-ncm-bionic"] = "";
|
|
572
|
+
if (beeline) wrapperProps["data-ncm-beeline-word"] = "";
|
|
573
|
+
if (bionic) {
|
|
574
|
+
const headLen = Math.max(1, Math.ceil(segment.length * ratio));
|
|
575
|
+
pieces.push(
|
|
576
|
+
/* @__PURE__ */ jsxs("span", { ...wrapperProps, children: [
|
|
577
|
+
/* @__PURE__ */ jsx("span", { "data-ncm-bionic-bold": "", style: { fontWeight: 600 }, children: segment.slice(0, headLen) }),
|
|
578
|
+
segment.slice(headLen)
|
|
579
|
+
] }, key++)
|
|
580
|
+
);
|
|
581
|
+
} else {
|
|
582
|
+
pieces.push(
|
|
583
|
+
/* @__PURE__ */ jsx("span", { ...wrapperProps, children: segment }, key++)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
cursor = match.index + segment.length;
|
|
587
|
+
}
|
|
588
|
+
if (cursor < content.length) pieces.push(content.slice(cursor));
|
|
589
|
+
return /* @__PURE__ */ jsx(Fragment, { children: pieces });
|
|
590
|
+
}
|
|
591
|
+
function renderBionicText(content, ratio = 0.5) {
|
|
592
|
+
return renderReadingText(content, { bionic: true, bionicRatio: ratio });
|
|
593
|
+
}
|
|
594
|
+
function renderBeelineText(content) {
|
|
595
|
+
return renderReadingText(content, { beeline: true });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export { InlineContent, ReferenceMarker, parseInline, renderBeelineText, renderBionicText, renderReadingText };
|