tiptap-markdown-react 0.3.1 → 0.4.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/dist/CitationRef.d.ts +26 -0
- package/dist/CitationRef.test.d.ts +1 -0
- package/dist/citationDom.d.ts +11 -0
- package/dist/citationDom.test.d.ts +1 -0
- package/dist/citationTypes.d.ts +31 -0
- package/dist/citationUtils.d.ts +23 -0
- package/dist/citationUtils.test.d.ts +1 -0
- package/dist/components/CitationInteractive.d.ts +51 -0
- package/dist/components/CitationRefView.d.ts +6 -0
- package/dist/components/MarkdownPreview.d.ts +13 -1
- package/dist/components/MarkdownWysiwygEditor.d.ts +12 -0
- package/dist/components/ReportContentInteractive.d.ts +15 -0
- package/dist/createCitationRef.d.ts +12 -0
- package/dist/extensions.d.ts +8 -8
- package/dist/extractToc-DW6HOiGt.js +253 -0
- package/dist/extractToc-DW6HOiGt.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +328 -96
- package/dist/index.js.map +1 -1
- package/dist/insertMarkdown.d.ts +6 -2
- package/dist/renderReportHtml.d.ts +11 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +23 -7
- package/dist/server.js.map +1 -1
- package/dist/style.css +87 -32
- package/package.json +6 -2
- package/dist/extractToc-C7e1DuVc.js +0 -97
- package/dist/extractToc-C7e1DuVc.js.map +0 -1
package/dist/server.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* 硬依赖(可在 Server Component / ISR 内直接 import)。
|
|
4
4
|
*/
|
|
5
5
|
export { baseExtensions, lowlight, pureCodeBlock, pureImage, } from './extensions';
|
|
6
|
+
export { CitationRef } from './CitationRef';
|
|
7
|
+
export { applyCitationSources, enrichMarkdownCitations, type SourceRef, } from './citationUtils';
|
|
6
8
|
export { ReportContent, type ReportContentProps } from './ReportContent';
|
|
7
|
-
export { renderReportHtml, type RenderedReport } from './renderReportHtml';
|
|
9
|
+
export { renderReportHtml, type RenderedReport, type RenderReportHtmlOptions, } from './renderReportHtml';
|
|
8
10
|
export { extractToc, type TocItem } from './toc/extractToc';
|
|
9
11
|
export { makeTocGetId } from './toc/tocSlug';
|
package/dist/server.js
CHANGED
|
@@ -1,35 +1,51 @@
|
|
|
1
|
-
import { b as baseExtensions, p as pureCodeBlock, a as pureImage, m as makeTocGetId,
|
|
2
|
-
import { R, l } from "./extractToc-
|
|
1
|
+
import { c as applyCitationSources, b as baseExtensions, p as pureCodeBlock, a as pureImage, C as CitationRef, m as makeTocGetId, d as extractToc } from "./extractToc-DW6HOiGt.js";
|
|
2
|
+
import { R, e, l } from "./extractToc-DW6HOiGt.js";
|
|
3
3
|
import { TableOfContents, generateTocIds } from "@tiptap/extension-table-of-contents";
|
|
4
4
|
import { MarkdownManager } from "@tiptap/markdown";
|
|
5
5
|
import { renderToHTMLString } from "@tiptap/static-renderer";
|
|
6
6
|
const sharedManager = new MarkdownManager({
|
|
7
|
-
extensions: [
|
|
7
|
+
extensions: [
|
|
8
|
+
...baseExtensions,
|
|
9
|
+
pureCodeBlock,
|
|
10
|
+
pureImage,
|
|
11
|
+
CitationRef,
|
|
12
|
+
TableOfContents
|
|
13
|
+
]
|
|
8
14
|
});
|
|
9
|
-
function renderReportHtml(markdown,
|
|
15
|
+
function renderReportHtml(markdown, lockedTitlesOrOptions = []) {
|
|
10
16
|
if (!(markdown == null ? void 0 : markdown.trim())) return { html: "", toc: [] };
|
|
11
|
-
const
|
|
17
|
+
const options = Array.isArray(lockedTitlesOrOptions) ? { lockedTitles: lockedTitlesOrOptions } : lockedTitlesOrOptions;
|
|
18
|
+
const lockedTitles = options.lockedTitles ?? [];
|
|
19
|
+
const sources = options.sources ?? [];
|
|
20
|
+
const json = applyCitationSources(
|
|
21
|
+
sharedManager.parse(markdown),
|
|
22
|
+
sources
|
|
23
|
+
);
|
|
12
24
|
const extensions = [
|
|
13
25
|
...baseExtensions,
|
|
14
26
|
pureCodeBlock,
|
|
15
27
|
pureImage,
|
|
28
|
+
CitationRef,
|
|
16
29
|
TableOfContents.configure({ getId: makeTocGetId() })
|
|
17
30
|
];
|
|
18
31
|
try {
|
|
19
32
|
const jsonWithIds = generateTocIds(json, extensions);
|
|
20
33
|
const html = renderToHTMLString({ extensions, content: jsonWithIds });
|
|
21
34
|
return { html, toc: extractToc(jsonWithIds, lockedTitles) };
|
|
22
|
-
} catch (
|
|
35
|
+
} catch (e2) {
|
|
23
36
|
console.error(
|
|
24
37
|
"[renderReportHtml] Tiptap pipeline failed, falling back to empty content:",
|
|
25
|
-
|
|
38
|
+
e2.message
|
|
26
39
|
);
|
|
27
40
|
return { html: "", toc: [] };
|
|
28
41
|
}
|
|
29
42
|
}
|
|
30
43
|
export {
|
|
44
|
+
CitationRef,
|
|
31
45
|
R as ReportContent,
|
|
46
|
+
applyCitationSources,
|
|
32
47
|
baseExtensions,
|
|
48
|
+
e as enrichMarkdownCitations,
|
|
33
49
|
extractToc,
|
|
34
50
|
l as lowlight,
|
|
35
51
|
makeTocGetId,
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/renderReportHtml.ts"],"sourcesContent":["import {\n generateTocIds,\n TableOfContents,\n} from '@tiptap/extension-table-of-contents';\nimport { MarkdownManager } from '@tiptap/markdown';\nimport { renderToHTMLString } from '@tiptap/static-renderer';\nimport { baseExtensions, pureCodeBlock, pureImage } from './extensions';\nimport { extractToc, type TocItem } from './toc/extractToc';\nimport { makeTocGetId } from './toc/tocSlug';\n\n/**\n * server 端把 markdown 渲成 HTML 字符串 + 目录(纯函数,无 editor / 无 window)。\n *\n * 走官方管线 `MarkdownManager.parse → generateTocIds → renderToHTMLString`,可在\n * Server Component / ISR 里直接跑,正文 HTML + 锚点 id 进静态产物,兑现 SEO / 首屏。\n *\n * 扩展集与编辑器同源(base + 纯版 CodeBlock/Image)+ TableOfContents。TOC 扩展在 v3\n * 会扩展 Heading schema 使 id 渲染进 HTML;`generateTocIds` 是官方纯函数(无 editor /\n * 无 window,专为「renderToHTMLString 前在 server 注入锚点」设计)。\n * 锚点用 slug + 去重(makeTocGetId),跨 SSR / ISR 稳定、可分享。\n *\n * 已知边界:官方包解析内联 HTML 依赖 window.DOMParser,server 端无 window 会把\n * 内联 HTML(如 <u>)转义为字面文本。\n */\nexport interface RenderedReport {\n html: string;\n toc: TocItem[];\n}\n\n// [perf] 模块级复用单个 MarkdownManager。每次 `new MarkdownManager` 会让底层 marked\n// 全局累积扩展,parse 逐次变慢。parse 只用 schema、与 TOC 的 getId 无关,可跨调用\n// 安全复用;getId 相关的锚点注入放在 generateTocIds 阶段、用每篇新建的 extensions 处理。\nconst sharedManager = new MarkdownManager({\n extensions: [...baseExtensions
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../src/renderReportHtml.ts"],"sourcesContent":["import {\n generateTocIds,\n TableOfContents,\n} from '@tiptap/extension-table-of-contents';\nimport type { JSONContent } from '@tiptap/core';\nimport { MarkdownManager } from '@tiptap/markdown';\nimport { renderToHTMLString } from '@tiptap/static-renderer';\nimport {\n applyCitationSources,\n type SourceRef,\n} from './citationUtils';\nimport { CitationRef } from './CitationRef';\nimport { baseExtensions, pureCodeBlock, pureImage } from './extensions';\nimport { extractToc, type TocItem } from './toc/extractToc';\nimport { makeTocGetId } from './toc/tocSlug';\n\n/**\n * server 端把 markdown 渲成 HTML 字符串 + 目录(纯函数,无 editor / 无 window)。\n *\n * 走官方管线 `MarkdownManager.parse → generateTocIds → renderToHTMLString`,可在\n * Server Component / ISR 里直接跑,正文 HTML + 锚点 id 进静态产物,兑现 SEO / 首屏。\n *\n * 扩展集与编辑器同源(base + 纯版 CodeBlock/Image)+ TableOfContents。TOC 扩展在 v3\n * 会扩展 Heading schema 使 id 渲染进 HTML;`generateTocIds` 是官方纯函数(无 editor /\n * 无 window,专为「renderToHTMLString 前在 server 注入锚点」设计)。\n * 锚点用 slug + 去重(makeTocGetId),跨 SSR / ISR 稳定、可分享。\n *\n * `[^n]` 由 CitationRef tokenizer 解析;`sources` 经 {@link applyCitationSources}\n * 补 url/title(不依赖 DOMParser,避免 server 内联 HTML 失效)。\n *\n * 已知边界:官方包解析内联 HTML 依赖 window.DOMParser,server 端无 window 会把\n * 内联 HTML(如 <u>)转义为字面文本。\n */\nexport interface RenderedReport {\n html: string;\n toc: TocItem[];\n}\n\nexport interface RenderReportHtmlOptions {\n /** 被付费墙锁住的章节标题(TOC 标 locked)。 */\n lockedTitles?: string[];\n /** 脚注来源,按 index 对齐 `[^n]`。 */\n sources?: SourceRef[];\n}\n\n// [perf] 模块级复用单个 MarkdownManager。每次 `new MarkdownManager` 会让底层 marked\n// 全局累积扩展,parse 逐次变慢。parse 只用 schema、与 TOC 的 getId 无关,可跨调用\n// 安全复用;getId 相关的锚点注入放在 generateTocIds 阶段、用每篇新建的 extensions 处理。\nconst sharedManager = new MarkdownManager({\n extensions: [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents,\n ],\n});\n\nexport function renderReportHtml(\n markdown: string,\n lockedTitlesOrOptions: string[] | RenderReportHtmlOptions = [],\n): RenderedReport {\n if (!markdown?.trim()) return { html: '', toc: [] };\n\n const options: RenderReportHtmlOptions = Array.isArray(lockedTitlesOrOptions)\n ? { lockedTitles: lockedTitlesOrOptions }\n : lockedTitlesOrOptions;\n const lockedTitles = options.lockedTitles ?? [];\n const sources = options.sources ?? [];\n\n const json = applyCitationSources(\n sharedManager.parse(markdown) as JSONContent,\n sources,\n );\n\n // getId 是有状态去重闭包,每篇必须重置,否则锚点 id 跨调用串号。这份 extensions\n // 只喂 generateTocIds / renderToHTMLString——schema 与 sharedManager 一致(仅 getId\n // 配置不同,不影响 schema),故对 sharedManager.parse 出的 json 兼容。\n const extensions = [\n ...baseExtensions,\n pureCodeBlock,\n pureImage,\n CitationRef,\n TableOfContents.configure({ getId: makeTocGetId() }),\n ];\n try {\n // 给 heading 注入 id / data-toc-id(server 端,renderToHTMLString 前)。\n const jsonWithIds = generateTocIds(json, extensions);\n const html = renderToHTMLString({ extensions, content: jsonWithIds });\n return { html, toc: extractToc(jsonWithIds, lockedTitles) };\n } catch (e) {\n // 某些 markdown 经 parse 产出的 JSON 可能在 heading 内嵌套 block 级子节点,\n // 导致 generateTocIds 的 setNodeMarkup 校验失败。单篇异常不应拖垮整个构建,\n // 降级返回空内容。\n console.error(\n '[renderReportHtml] Tiptap pipeline failed, falling back to empty content:',\n (e as Error).message,\n );\n return { html: '', toc: [] };\n }\n}\n"],"names":["e"],"mappings":";;;;;AAgDA,MAAM,gBAAgB,IAAI,gBAAgB;AAAA,EACxC,YAAY;AAAA,IACV,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ,CAAC;AAEM,SAAS,iBACd,UACA,wBAA4D,IAC5C;AAChB,MAAI,EAAC,qCAAU,QAAQ,QAAO,EAAE,MAAM,IAAI,KAAK,GAAC;AAEhD,QAAM,UAAmC,MAAM,QAAQ,qBAAqB,IACxE,EAAE,cAAc,0BAChB;AACJ,QAAM,eAAe,QAAQ,gBAAgB,CAAA;AAC7C,QAAM,UAAU,QAAQ,WAAW,CAAA;AAEnC,QAAM,OAAO;AAAA,IACX,cAAc,MAAM,QAAQ;AAAA,IAC5B;AAAA,EAAA;AAMF,QAAM,aAAa;AAAA,IACjB,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,UAAU,EAAE,OAAO,aAAA,GAAgB;AAAA,EAAA;AAErD,MAAI;AAEF,UAAM,cAAc,eAAe,MAAM,UAAU;AACnD,UAAM,OAAO,mBAAmB,EAAE,YAAY,SAAS,aAAa;AACpE,WAAO,EAAE,MAAM,KAAK,WAAW,aAAa,YAAY,EAAA;AAAA,EAC1D,SAASA,IAAG;AAIV,YAAQ;AAAA,MACN;AAAA,MACCA,GAAY;AAAA,IAAA;AAEf,WAAO,EAAE,MAAM,IAAI,KAAK,CAAA,EAAC;AAAA,EAC3B;AACF;"}
|
package/dist/style.css
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* 规范正文样式(编辑器 / 只读预览 / SSR 阅读三处共用同一个类,保证所见即所得、
|
|
2
2
|
发布后一致)。配色/字体全部走可被消费方覆盖的 CSS 变量(--tmr-*,带默认值)。 */
|
|
3
3
|
|
|
4
|
-
.
|
|
4
|
+
._editorScroll_siipj_4 {
|
|
5
5
|
margin-top: 24px;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
.
|
|
8
|
+
._editorContent_siipj_8 {
|
|
9
9
|
outline: none;
|
|
10
10
|
padding: 0;
|
|
11
11
|
font-family: var(
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/* placeholder:空文档第一段显示占位符 */
|
|
31
|
-
.
|
|
31
|
+
._editorContent_siipj_8 p.is-editor-empty:first-child::before {
|
|
32
32
|
content: attr(data-placeholder);
|
|
33
33
|
float: left;
|
|
34
34
|
height: 0;
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
pointer-events: none;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
.
|
|
39
|
+
._editorContent_siipj_8 h1 {
|
|
40
40
|
font-family: inherit;
|
|
41
41
|
font-size: 1.55em;
|
|
42
42
|
font-weight: 700;
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
line-height: 1.3;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
.
|
|
47
|
+
._editorContent_siipj_8 h2 {
|
|
48
48
|
font-family: inherit;
|
|
49
49
|
font-size: 1.3em;
|
|
50
50
|
font-weight: 700;
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
line-height: 1.3;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
.
|
|
55
|
+
._editorContent_siipj_8 h3 {
|
|
56
56
|
font-family: inherit;
|
|
57
57
|
font-size: 1.12em;
|
|
58
58
|
font-weight: 600;
|
|
@@ -60,21 +60,21 @@
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/* h4-h6:延续 h1-h3 尺寸梯度递减;字号差变小后靠字重保持标题感,h6 字色稍弱。 */
|
|
63
|
-
.
|
|
63
|
+
._editorContent_siipj_8 h4 {
|
|
64
64
|
font-family: inherit;
|
|
65
65
|
font-size: 1.05em;
|
|
66
66
|
font-weight: 600;
|
|
67
67
|
margin: 0.7em 0 0.3em;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
.
|
|
70
|
+
._editorContent_siipj_8 h5 {
|
|
71
71
|
font-family: inherit;
|
|
72
72
|
font-size: 0.95em;
|
|
73
73
|
font-weight: 600;
|
|
74
74
|
margin: 0.7em 0 0.3em;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
.
|
|
77
|
+
._editorContent_siipj_8 h6 {
|
|
78
78
|
font-family: inherit;
|
|
79
79
|
font-size: 0.88em;
|
|
80
80
|
font-weight: 600;
|
|
@@ -82,25 +82,25 @@
|
|
|
82
82
|
color: var(--tmr-muted, #777777);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
.
|
|
85
|
+
._editorContent_siipj_8 p {
|
|
86
86
|
margin: 0.7em 0;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
.
|
|
90
|
-
.
|
|
89
|
+
._editorContent_siipj_8 ul,
|
|
90
|
+
._editorContent_siipj_8 ol {
|
|
91
91
|
padding-left: 1.5em;
|
|
92
92
|
margin: 0.7em 0;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
.
|
|
95
|
+
._editorContent_siipj_8 ul {
|
|
96
96
|
list-style: disc;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
.
|
|
99
|
+
._editorContent_siipj_8 ol {
|
|
100
100
|
list-style: decimal;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
.
|
|
103
|
+
._editorContent_siipj_8 blockquote {
|
|
104
104
|
border-left: 3px solid var(--tmr-accent, #ff6719);
|
|
105
105
|
padding-left: 1em;
|
|
106
106
|
margin: 0.9em 0;
|
|
@@ -108,13 +108,13 @@
|
|
|
108
108
|
font-style: italic;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
.
|
|
111
|
+
._editorContent_siipj_8 a {
|
|
112
112
|
color: var(--tmr-accent, #ff6719);
|
|
113
113
|
text-decoration: underline;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/* 仅行内代码加灰底;代码块内的 <code>(在 pre 内)由 NodeView 自管,排除掉 */
|
|
117
|
-
.
|
|
117
|
+
._editorContent_siipj_8 code:not(pre code) {
|
|
118
118
|
background: var(--tmr-code-bg, #f4f4f4);
|
|
119
119
|
padding: 0.15em 0.4em;
|
|
120
120
|
border-radius: 4px;
|
|
@@ -129,13 +129,13 @@
|
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
.
|
|
132
|
+
._editorContent_siipj_8 hr {
|
|
133
133
|
border: none;
|
|
134
134
|
border-top: 1px solid var(--tmr-border, #e6e6e6);
|
|
135
135
|
margin: 1.5em 0;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
.
|
|
138
|
+
._editorContent_siipj_8 img {
|
|
139
139
|
max-width: 100%;
|
|
140
140
|
height: auto;
|
|
141
141
|
border-radius: 6px;
|
|
@@ -144,12 +144,12 @@
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/* 选中的图片高亮 */
|
|
147
|
-
.
|
|
147
|
+
._editorContent_siipj_8 img.ProseMirror-selectednode {
|
|
148
148
|
outline: 2px solid var(--tmr-accent, #ff6719);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/* 表格:细边框、表头浅灰、可拖拽列宽 */
|
|
152
|
-
.
|
|
152
|
+
._editorContent_siipj_8 table {
|
|
153
153
|
border-collapse: collapse;
|
|
154
154
|
table-layout: fixed;
|
|
155
155
|
width: 100%;
|
|
@@ -158,8 +158,8 @@
|
|
|
158
158
|
font-size: 0.92em;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
.
|
|
162
|
-
.
|
|
161
|
+
._editorContent_siipj_8 table td,
|
|
162
|
+
._editorContent_siipj_8 table th {
|
|
163
163
|
border: 1px solid var(--tmr-border, #e6e6e6);
|
|
164
164
|
padding: 8px 12px;
|
|
165
165
|
vertical-align: top;
|
|
@@ -168,13 +168,13 @@
|
|
|
168
168
|
min-width: 60px;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
.
|
|
171
|
+
._editorContent_siipj_8 table th {
|
|
172
172
|
background: var(--tmr-table-header-bg, #f7f7f7);
|
|
173
173
|
font-weight: 600;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/* 选中的单元格高亮 */
|
|
177
|
-
.
|
|
177
|
+
._editorContent_siipj_8 table .selectedCell::after {
|
|
178
178
|
content: '';
|
|
179
179
|
position: absolute;
|
|
180
180
|
inset: 0;
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
/* 列宽拖拽手柄 */
|
|
186
|
-
.
|
|
186
|
+
._editorContent_siipj_8 table .column-resize-handle {
|
|
187
187
|
position: absolute;
|
|
188
188
|
right: -2px;
|
|
189
189
|
top: 0;
|
|
@@ -193,36 +193,91 @@
|
|
|
193
193
|
cursor: col-resize;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
.
|
|
196
|
+
._editorContent_siipj_8 .tableWrapper {
|
|
197
197
|
overflow-x: auto;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/* 任务清单 */
|
|
201
|
-
.
|
|
201
|
+
._editorContent_siipj_8 ul[data-type='taskList'] {
|
|
202
202
|
list-style: none;
|
|
203
203
|
padding-left: 0.2em;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
.
|
|
206
|
+
._editorContent_siipj_8 ul[data-type='taskList'] li {
|
|
207
207
|
display: flex;
|
|
208
208
|
align-items: flex-start;
|
|
209
209
|
gap: 0.5em;
|
|
210
210
|
margin: 0.3em 0;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
.
|
|
213
|
+
._editorContent_siipj_8 ul[data-type='taskList'] li > label {
|
|
214
214
|
margin-top: 0.35em;
|
|
215
215
|
user-select: none;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
.
|
|
218
|
+
._editorContent_siipj_8 ul[data-type='taskList'] li > div {
|
|
219
219
|
flex: 1;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
.
|
|
222
|
+
._editorContent_siipj_8 ul[data-type='taskList'] input[type='checkbox'] {
|
|
223
223
|
accent-color: var(--tmr-accent, #ff6719);
|
|
224
224
|
cursor: pointer;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/* 脚注/引文圆标:与正文字垂直居中;配色对齐工具栏 active(浅橙底 + accent 字) */
|
|
228
|
+
._editorContent_siipj_8 span.citation-ref {
|
|
229
|
+
display: inline-flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
justify-content: center;
|
|
232
|
+
vertical-align: middle;
|
|
233
|
+
box-sizing: border-box;
|
|
234
|
+
min-width: 1.4em;
|
|
235
|
+
height: 1.4em;
|
|
236
|
+
padding: 0 0.28em;
|
|
237
|
+
margin: 0 0.14em;
|
|
238
|
+
border-radius: 999px;
|
|
239
|
+
border: 1px solid var(--tmr-citation-border, #ffd4b8);
|
|
240
|
+
background: var(--tmr-citation-bg, #fff3ec);
|
|
241
|
+
color: var(--tmr-citation-fg, var(--tmr-accent, #ff6719));
|
|
242
|
+
font-size: 0.68em;
|
|
243
|
+
font-weight: 600;
|
|
244
|
+
font-style: normal;
|
|
245
|
+
font-family:
|
|
246
|
+
system-ui,
|
|
247
|
+
-apple-system,
|
|
248
|
+
BlinkMacSystemFont,
|
|
249
|
+
'Segoe UI',
|
|
250
|
+
Roboto,
|
|
251
|
+
Helvetica,
|
|
252
|
+
Arial,
|
|
253
|
+
sans-serif;
|
|
254
|
+
letter-spacing: -0.02em;
|
|
255
|
+
line-height: 1;
|
|
256
|
+
text-decoration: none;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
user-select: none;
|
|
259
|
+
transition:
|
|
260
|
+
background 0.15s ease,
|
|
261
|
+
color 0.15s ease,
|
|
262
|
+
border-color 0.15s ease;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
._editorContent_siipj_8 span.citation-ref a {
|
|
266
|
+
color: inherit;
|
|
267
|
+
text-decoration: none;
|
|
268
|
+
display: flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
justify-content: center;
|
|
271
|
+
width: 100%;
|
|
272
|
+
height: 100%;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
._editorContent_siipj_8 span.citation-ref:hover,
|
|
276
|
+
._editorContent_siipj_8 span.citation-ref:focus-within {
|
|
277
|
+
background: var(--tmr-citation-bg-hover, var(--tmr-accent, #ff6719));
|
|
278
|
+
border-color: var(--tmr-citation-bg-hover, var(--tmr-accent, #ff6719));
|
|
279
|
+
color: var(--tmr-citation-fg-hover, #ffffff);
|
|
280
|
+
}
|
|
226
281
|
/* 代码块 NodeView:白底、细边框、顶部 header 放语言选择(Radix 下拉)+ 删除。 */
|
|
227
282
|
._wrapper_2dv2g_2 {
|
|
228
283
|
position: relative;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiptap-markdown-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A batteries-included, self-styled Markdown WYSIWYG editor and reader suite built on Tiptap v3. Markdown in/out, SSR/static rendering, table of contents, themeable via CSS variables, zero Ant Design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"build": "vite build",
|
|
49
49
|
"dev": "vite build --watch",
|
|
50
50
|
"typecheck": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
51
53
|
"prepublishOnly": "npm run build"
|
|
52
54
|
},
|
|
53
55
|
"publishConfig": {
|
|
@@ -88,7 +90,9 @@
|
|
|
88
90
|
"rollup-preserve-directives": "^1.1.3",
|
|
89
91
|
"typescript": "^5.9.3",
|
|
90
92
|
"vite": "^5.4.21",
|
|
91
|
-
"vite-plugin-dts": "^4.5.4"
|
|
93
|
+
"vite-plugin-dts": "^4.5.4",
|
|
94
|
+
"vitest": "^3.2.4",
|
|
95
|
+
"happy-dom": "^18.0.1"
|
|
92
96
|
},
|
|
93
97
|
"overrides": {
|
|
94
98
|
"@tiptap/core": "3.27.1"
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
|
2
|
-
import Highlight from "@tiptap/extension-highlight";
|
|
3
|
-
import Image from "@tiptap/extension-image";
|
|
4
|
-
import Subscript from "@tiptap/extension-subscript";
|
|
5
|
-
import Superscript from "@tiptap/extension-superscript";
|
|
6
|
-
import { TableKit } from "@tiptap/extension-table";
|
|
7
|
-
import TaskItem from "@tiptap/extension-task-item";
|
|
8
|
-
import TaskList from "@tiptap/extension-task-list";
|
|
9
|
-
import { TextStyle, Color, FontSize } from "@tiptap/extension-text-style";
|
|
10
|
-
import StarterKit from "@tiptap/starter-kit";
|
|
11
|
-
import { createLowlight, common } from "lowlight";
|
|
12
|
-
import { jsx } from "react/jsx-runtime";
|
|
13
|
-
const lowlight = createLowlight(common);
|
|
14
|
-
const pureCodeBlock = CodeBlockLowlight.configure({ lowlight });
|
|
15
|
-
const pureImage = Image.configure({ inline: false });
|
|
16
|
-
const baseExtensions = [
|
|
17
|
-
// 禁用 StarterKit 内置 codeBlock,统一改用带 lowlight 的代码块(由调用方注入)
|
|
18
|
-
StarterKit.configure({ codeBlock: false }),
|
|
19
|
-
TextStyle,
|
|
20
|
-
Color.configure({ types: [TextStyle.name] }),
|
|
21
|
-
// 正文字号:官方 FontSize(v3 起并入 text-style 包),以内联样式挂在 TextStyle
|
|
22
|
-
// 的 <span> 上(如 style="font-size: 16px"),与 Color 同基座,三端共用。
|
|
23
|
-
FontSize.configure({ types: [TextStyle.name] }),
|
|
24
|
-
Highlight.configure({ multicolor: true }),
|
|
25
|
-
Superscript,
|
|
26
|
-
Subscript,
|
|
27
|
-
TableKit.configure({ table: { resizable: true } }),
|
|
28
|
-
TaskList,
|
|
29
|
-
TaskItem.configure({ nested: true })
|
|
30
|
-
];
|
|
31
|
-
const editorScroll = "_editorScroll_fahhq_4";
|
|
32
|
-
const editorContent = "_editorContent_fahhq_8";
|
|
33
|
-
const styles = {
|
|
34
|
-
editorScroll,
|
|
35
|
-
editorContent
|
|
36
|
-
};
|
|
37
|
-
function baseSlug(text) {
|
|
38
|
-
return text.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^\w一-龥-]/g, "").replace(/^-+|-+$/g, "") || "section";
|
|
39
|
-
}
|
|
40
|
-
function makeTocGetId() {
|
|
41
|
-
const seen = /* @__PURE__ */ new Map();
|
|
42
|
-
return (content) => {
|
|
43
|
-
const base = baseSlug(content);
|
|
44
|
-
const n = (seen.get(base) ?? 0) + 1;
|
|
45
|
-
seen.set(base, n);
|
|
46
|
-
return n === 1 ? base : `${base}-${n}`;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function ReportContent({ html, className, ...rest }) {
|
|
50
|
-
return /* @__PURE__ */ jsx(
|
|
51
|
-
"div",
|
|
52
|
-
{
|
|
53
|
-
...rest,
|
|
54
|
-
className: className ? `${styles.editorContent} ${className}` : styles.editorContent,
|
|
55
|
-
dangerouslySetInnerHTML: { __html: html }
|
|
56
|
-
}
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
const MAX_LEVEL = 6;
|
|
60
|
-
function headingText(node) {
|
|
61
|
-
if (!node.content) return "";
|
|
62
|
-
return node.content.map((c) => c.type === "text" ? c.text ?? "" : "").join("").trim();
|
|
63
|
-
}
|
|
64
|
-
function extractToc(doc, lockedTitles = []) {
|
|
65
|
-
const locked = new Set(lockedTitles.map((t) => t.trim()));
|
|
66
|
-
const items = [];
|
|
67
|
-
const walk = (node) => {
|
|
68
|
-
var _a, _b, _c, _d;
|
|
69
|
-
const level = (_a = node.attrs) == null ? void 0 : _a.level;
|
|
70
|
-
if (node.type === "heading" && typeof level === "number" && level <= MAX_LEVEL) {
|
|
71
|
-
const text = headingText(node);
|
|
72
|
-
const id = ((_b = node.attrs) == null ? void 0 : _b["data-toc-id"]) ?? ((_c = node.attrs) == null ? void 0 : _c.id);
|
|
73
|
-
if (text && id) {
|
|
74
|
-
items.push({
|
|
75
|
-
id,
|
|
76
|
-
level,
|
|
77
|
-
text,
|
|
78
|
-
locked: locked.has(text) || void 0
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
(_d = node.content) == null ? void 0 : _d.forEach(walk);
|
|
83
|
-
};
|
|
84
|
-
walk(doc);
|
|
85
|
-
return items;
|
|
86
|
-
}
|
|
87
|
-
export {
|
|
88
|
-
ReportContent as R,
|
|
89
|
-
pureImage as a,
|
|
90
|
-
baseExtensions as b,
|
|
91
|
-
extractToc as e,
|
|
92
|
-
lowlight as l,
|
|
93
|
-
makeTocGetId as m,
|
|
94
|
-
pureCodeBlock as p,
|
|
95
|
-
styles as s
|
|
96
|
-
};
|
|
97
|
-
//# sourceMappingURL=extractToc-C7e1DuVc.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extractToc-C7e1DuVc.js","sources":["../src/extensions.ts","../src/toc/tocSlug.ts","../src/ReportContent.tsx","../src/toc/extractToc.ts"],"sourcesContent":["import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';\nimport Highlight from '@tiptap/extension-highlight';\nimport Image from '@tiptap/extension-image';\nimport Subscript from '@tiptap/extension-subscript';\nimport Superscript from '@tiptap/extension-superscript';\nimport { TableKit } from '@tiptap/extension-table';\nimport TaskItem from '@tiptap/extension-task-item';\nimport TaskList from '@tiptap/extension-task-list';\nimport { Color, FontSize, TextStyle } from '@tiptap/extension-text-style';\n// AnyExtension 从 @tiptap/react 取(它 re-export 自 core),避免新增 @tiptap/core 直接依赖\nimport type { AnyExtension } from '@tiptap/react';\nimport StarterKit from '@tiptap/starter-kit';\nimport { common, createLowlight } from 'lowlight';\n\n/**\n * 编辑器的「内容 schema 级」扩展集——纯节点/标记定义,不含任何 React NodeView\n * 或键盘交互绑定。三处共用:\n * 1. 编辑器(MarkdownWysiwygEditor):base + 交互增强版 CodeBlock/Image\n * 2. 只读预览(MarkdownPreview)\n * 3. server 端 markdown→HTML 渲染(renderReportHtml)\n *\n * 分层动机:交互增强依赖 @tiptap/react / 浏览器,会污染 server bundle(RSC)。\n * base 保持纯 schema,server import 它只引入节点定义,无 React 代码。\n *\n * 关键边界:CodeBlock 与 Image「会被编辑器增强」,**不放进 base**——否则编辑器\n * 追加增强版会与 base 的纯版同名冲突(Tiptap 报 \"Duplicate extension names\")。\n * 改为:base 不含这两个节点,由调用方按需注入:\n * - 编辑器注入「增强版」(React 视图 / 删除快捷键)\n * - server / 预览注入「纯版」(pureCodeBlock / pureImage)\n */\n\n// lowlight 实例:代码块语法高亮。导出供编辑器增强版与 server 纯版共用同一实例。\nexport const lowlight = createLowlight(common);\n\n/** 纯版代码块(无 React 视图):server / 预览用,产出带高亮 class 的静态结构。 */\nexport const pureCodeBlock = CodeBlockLowlight.configure({ lowlight });\n\n/**\n * 纯版块级图片(无删除快捷键):server / 预览用。\n * inline:false(官方默认)——研报里图片均为大图独占一行,块级图片可直接作为 doc\n * 顶层子节点,避免「inline image 裸挂 doc 顶层」被 generateTocIds 严格校验判非法。\n */\nexport const pureImage = Image.configure({ inline: false });\n\n/**\n * 三处共用的纯 schema 扩展——**不含 CodeBlock / Image**(见上方边界说明)。\n * 编辑器:`[...baseExtensions, 增强CodeBlock, 增强Image, Markdown]`\n * server/预览:`[...baseExtensions, pureCodeBlock, pureImage, Markdown]`\n */\nexport const baseExtensions: AnyExtension[] = [\n // 禁用 StarterKit 内置 codeBlock,统一改用带 lowlight 的代码块(由调用方注入)\n StarterKit.configure({ codeBlock: false }),\n TextStyle,\n Color.configure({ types: [TextStyle.name] }),\n // 正文字号:官方 FontSize(v3 起并入 text-style 包),以内联样式挂在 TextStyle\n // 的 <span> 上(如 style=\"font-size: 16px\"),与 Color 同基座,三端共用。\n FontSize.configure({ types: [TextStyle.name] }),\n Highlight.configure({ multicolor: true }),\n Superscript,\n Subscript,\n TableKit.configure({ table: { resizable: true } }),\n TaskList,\n TaskItem.configure({ nested: true }),\n];\n","/**\n * 目录锚点 id 生成——阅读页(SSR)与编辑页(editor)共用的**单一真源**,\n * 保证「创作预览的锚点 = 发布后阅读页的锚点」。\n *\n * 用 slug 而非官方默认 UUID:slug 跨 SSR / ISR revalidate 稳定(UUID 每次都变、\n * 分享的 #hash 会失效),且 `#introduction` 比 `#d4590f81-…` 可读、对 SEO 友好。\n *\n * slug 会重名(两个「细节」都 slug 成 `xi-jie`),故用闭包计数器去重:\n * 同名追加 -2 / -3(已实测:details / details-2 / details-3,HTML id 全唯一)。\n */\n\n/** 单个标题文本 → 基础 slug(保留中文,去标点,空白转连字符)。 */\nfunction baseSlug(text: string): string {\n return (\n text\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, '-')\n // 保留字母/数字/下划线/中文/连字符,其余剔除\n .replace(/[^\\w一-龥-]/g, '')\n .replace(/^-+|-+$/g, '') || 'section'\n );\n}\n\n/**\n * 造一个带去重的 `getId`。每次生成新实例(闭包各自计数),传给\n * `TableOfContents.configure({ getId })` 或 `generateTocIds` 的扩展配置。\n */\nexport function makeTocGetId(): (content: string) => string {\n const seen = new Map<string, number>();\n return (content: string) => {\n const base = baseSlug(content);\n const n = (seen.get(base) ?? 0) + 1;\n seen.set(base, n);\n return n === 1 ? base : `${base}-${n}`;\n };\n}\n","import type { HTMLAttributes } from 'react';\nimport styles from './styles/content.module.css';\n\nexport interface ReportContentProps extends HTMLAttributes<HTMLDivElement> {\n /** `renderReportHtml(...).html` 产出的正文 HTML 字符串。 */\n html: string;\n}\n\n/**\n * 只读静态正文渲染(RSC / SSR 安全,无 client 代码、无编辑器实例)。\n *\n * 把 `renderReportHtml` 产出的 HTML 注入容器,并套上与编辑器 / 只读预览**同一个\n * 规范正文类**,保证「编辑所见 = 预览所见 = 发布后读者所见」。\n * 用于阅读页首屏直出(SEO 可见),无需 JS 即可显示。\n */\nexport function ReportContent({ html, className, ...rest }: ReportContentProps) {\n return (\n <div\n {...rest}\n className={\n className ? `${styles.editorContent} ${className}` : styles.editorContent\n }\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n","/**\n * 最小 Tiptap JSON 节点结构(只取本模块用到的字段)。\n * 不从 `@tiptap/core` 引 `JSONContent`——core 是嵌套依赖、非直接依赖,\n * 直接 import 类型解析不到(与仓库既有做法一致,见 extensions.ts)。\n */\ninterface TocDocNode {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown> | null;\n content?: TocDocNode[];\n}\n\n/** 目录项。 */\nexport interface TocItem {\n /** 锚点 id(= heading 的 data-toc-id / id,由 generateTocIds 注入)。 */\n id: string;\n /** 标题层级(1-6)。 */\n level: number;\n /** 标题纯文本。 */\n text: string;\n /** 付费墙锁住的章节(置灰不可点)。 */\n locked?: boolean;\n}\n\n/** 目录纳入的最大层级(h1-h6,全层级入目录)。 */\nconst MAX_LEVEL = 6;\n\n/** 取 heading 节点的纯文本(拼接其 text 子节点)。 */\nfunction headingText(node: TocDocNode): string {\n if (!node.content) return '';\n return node.content\n .map((c) => (c.type === 'text' ? c.text ?? '' : ''))\n .join('')\n .trim();\n}\n\n/**\n * 从已注入锚点 id 的 Tiptap JSON doc 提取 h1-h6 目录。\n *\n * 必须在 `generateTocIds` 之后调用(heading 的 attrs 里才有 `data-toc-id`)。\n * `lockedTitles`:被截断(锁住)章节标题,命中则标 `locked`——\n * 目录列全部章节,锁住项置灰不可点(付费墙钩子)。用 Set 便于去重命中。\n */\nexport function extractToc(\n doc: TocDocNode,\n lockedTitles: string[] = [],\n): TocItem[] {\n const locked = new Set(lockedTitles.map((t) => t.trim()));\n const items: TocItem[] = [];\n\n const walk = (node: TocDocNode) => {\n const level = node.attrs?.level;\n if (\n node.type === 'heading' &&\n typeof level === 'number' &&\n level <= MAX_LEVEL\n ) {\n const text = headingText(node);\n const id = (node.attrs?.['data-toc-id'] ?? node.attrs?.id) as\n | string\n | undefined;\n // 有文本 + 有锚点 id 才入目录(空标题 / 未注入 id 的跳过)。\n if (text && id) {\n items.push({\n id,\n level,\n text,\n locked: locked.has(text) || undefined,\n });\n }\n }\n node.content?.forEach(walk);\n };\n\n walk(doc);\n return items;\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAgCO,MAAM,WAAW,eAAe,MAAM;AAGtC,MAAM,gBAAgB,kBAAkB,UAAU,EAAE,UAAU;AAO9D,MAAM,YAAY,MAAM,UAAU,EAAE,QAAQ,OAAO;AAOnD,MAAM,iBAAiC;AAAA;AAAA,EAE5C,WAAW,UAAU,EAAE,WAAW,OAAO;AAAA,EACzC;AAAA,EACA,MAAM,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,GAAG;AAAA;AAAA;AAAA,EAG3C,SAAS,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,GAAG;AAAA,EAC9C,UAAU,UAAU,EAAE,YAAY,MAAM;AAAA,EACxC;AAAA,EACA;AAAA,EACA,SAAS,UAAU,EAAE,OAAO,EAAE,WAAW,KAAA,GAAQ;AAAA,EACjD;AAAA,EACA,SAAS,UAAU,EAAE,QAAQ,MAAM;AACrC;;;;;;;ACnDA,SAAS,SAAS,MAAsB;AACtC,SACE,KACG,YAAA,EACA,KAAA,EACA,QAAQ,QAAQ,GAAG,EAEnB,QAAQ,cAAc,EAAE,EACxB,QAAQ,YAAY,EAAE,KAAK;AAElC;AAMO,SAAS,eAA4C;AAC1D,QAAM,2BAAW,IAAA;AACjB,SAAO,CAAC,YAAoB;AAC1B,UAAM,OAAO,SAAS,OAAO;AAC7B,UAAM,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;AAClC,SAAK,IAAI,MAAM,CAAC;AAChB,WAAO,MAAM,IAAI,OAAO,GAAG,IAAI,IAAI,CAAC;AAAA,EACtC;AACF;ACrBO,SAAS,cAAc,EAAE,MAAM,WAAW,GAAG,QAA4B;AAC9E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,WACE,YAAY,GAAG,OAAO,aAAa,IAAI,SAAS,KAAK,OAAO;AAAA,MAE9D,yBAAyB,EAAE,QAAQ,KAAA;AAAA,IAAK;AAAA,EAAA;AAG9C;ACAA,MAAM,YAAY;AAGlB,SAAS,YAAY,MAA0B;AAC7C,MAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,SAAO,KAAK,QACT,IAAI,CAAC,MAAO,EAAE,SAAS,SAAS,EAAE,QAAQ,KAAK,EAAG,EAClD,KAAK,EAAE,EACP,KAAA;AACL;AASO,SAAS,WACd,KACA,eAAyB,IACd;AACX,QAAM,SAAS,IAAI,IAAI,aAAa,IAAI,CAAC,MAAM,EAAE,KAAA,CAAM,CAAC;AACxD,QAAM,QAAmB,CAAA;AAEzB,QAAM,OAAO,CAAC,SAAqB;;AACjC,UAAM,SAAQ,UAAK,UAAL,mBAAY;AAC1B,QACE,KAAK,SAAS,aACd,OAAO,UAAU,YACjB,SAAS,WACT;AACA,YAAM,OAAO,YAAY,IAAI;AAC7B,YAAM,OAAM,UAAK,UAAL,mBAAa,qBAAkB,UAAK,UAAL,mBAAY;AAIvD,UAAI,QAAQ,IAAI;AACd,cAAM,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,QAAQ,OAAO,IAAI,IAAI,KAAK;AAAA,QAAA,CAC7B;AAAA,MACH;AAAA,IACF;AACA,eAAK,YAAL,mBAAc,QAAQ;AAAA,EACxB;AAEA,OAAK,GAAG;AACR,SAAO;AACT;"}
|