tiptap-markdown-react 0.6.2 → 0.6.3

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.
@@ -188,8 +188,8 @@ const baseExtensions = [
188
188
  TaskList,
189
189
  TaskItem.configure({ nested: true })
190
190
  ];
191
- const editorScroll = "_editorScroll_9hn0j_4";
192
- const editorContent = "_editorContent_9hn0j_8";
191
+ const editorScroll = "_editorScroll_q3j4s_4";
192
+ const editorContent = "_editorContent_q3j4s_8";
193
193
  const styles = {
194
194
  editorScroll,
195
195
  editorContent
@@ -257,4 +257,4 @@ export {
257
257
  pureCodeBlock as p,
258
258
  styles as s
259
259
  };
260
- //# sourceMappingURL=extractToc-CSawxIx-.js.map
260
+ //# sourceMappingURL=extractToc-DknI9ylT.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractToc-CSawxIx-.js","sources":["../src/citationUtils.ts","../src/CitationRef.ts","../src/extensions.ts","../src/toc/tocSlug.ts","../src/ReportContent.tsx","../src/toc/extractToc.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core';\n\nexport interface SourceRef {\n /** 与 markdown `[^n]` 中的 n 对齐(字符串,如 `\"1\"`)。 */\n index: string;\n url?: string;\n title?: string;\n /** 原文片段等;库本身不渲染 Popover,留给消费方。 */\n excerpt?: string;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * 将 markdown 中能匹配到 `sources` 的 `[^n]` 换成带 data-* 的\n * `<span class=\"citation-ref\">`(**不是** `<sup>`,避免被 Superscript 吃掉、\n * 也避免视觉上标)。未匹配的 `[^n]` 原样保留,留给 CitationRef tokenizer。\n *\n * 浏览器端 @tiptap/markdown 可解析内联 HTML;server 端无 DOMParser 时请改用\n * {@link applyCitationSources}(先 parse 再补 attrs)。\n */\nexport function enrichMarkdownCitations(\n markdown: string,\n sources: SourceRef[],\n): string {\n if (!sources.length) return markdown;\n\n return markdown.replace(/\\[\\^(\\d+)\\]/g, (match, num: string) => {\n const source = sources.find((s) => s.index === num);\n if (!source) return match;\n\n const attrs = [`class=\"citation-ref\"`, `data-index=\"${num}\"`];\n if (source.url) attrs.push(`data-url=\"${escapeAttr(source.url)}\"`);\n if (source.title) attrs.push(`data-title=\"${escapeAttr(source.title)}\"`);\n\n if (source.url) {\n return `<span ${attrs.join(' ')}><a href=\"${escapeAttr(source.url)}\" title=\"${escapeAttr(source.title || '')}\" target=\"_blank\" rel=\"noopener\">${num}</a></span>`;\n }\n return `<span ${attrs.join(' ')}>${num}</span>`;\n });\n}\n\nfunction patchCitationNode(\n node: JSONContent,\n byIndex: Map<string, SourceRef>,\n): JSONContent {\n if (node.type === 'citationRef') {\n const index = String(node.attrs?.index ?? '');\n const source = byIndex.get(index);\n if (!source) return node;\n return {\n ...node,\n attrs: {\n ...node.attrs,\n index,\n url: source.url ?? node.attrs?.url ?? null,\n title: source.title ?? node.attrs?.title ?? null,\n },\n };\n }\n\n if (!node.content?.length) return node;\n return {\n ...node,\n content: node.content.map((child) => patchCitationNode(child, byIndex)),\n };\n}\n\n/**\n * 在已 parse 的 Tiptap JSON 上,按 `sources` 给 `citationRef` 节点补 url/title。\n * SSR / 无 DOMParser 环境的首选路径。\n */\nexport function applyCitationSources(\n doc: JSONContent,\n sources: SourceRef[],\n): JSONContent {\n if (!sources.length) return doc;\n const byIndex = new Map(sources.map((s) => [s.index, s]));\n return patchCitationNode(doc, byIndex);\n}\n","import { mergeAttributes, Node } from '@tiptap/core';\n\nexport interface CitationRefOptions {\n HTMLAttributes: Record<string, unknown>;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n citationRef: {\n insertCitationRef: (attrs: {\n index: string;\n url?: string | null;\n title?: string | null;\n }) => ReturnType;\n };\n }\n}\n\n/**\n * 脚注/引文引用——inline atom 节点。\n *\n * - 输入:`[^n]`(markdownTokenizer)或 `<span class=\"citation-ref\">`(parseHTML)\n * - 输出 HTML:居中圆形 pill(CSS `.citation-ref`),**不是**上下标\n * - 输出 markdown:`[^n]`\n *\n * Popover /「原文片段」由消费方通过 {@link createCitationRef} 的 `renderCitation`\n * 插槽完成;本节点只负责标记与默认圆标样式。\n */\nexport const CitationRef = Node.create<CitationRefOptions>({\n name: 'citationRef',\n\n group: 'inline',\n\n inline: true,\n\n atom: true,\n\n selectable: true,\n\n // 高于普通链接,确保 `[^1]` 不被拆成残缺 link\n priority: 1000,\n\n addOptions() {\n return { HTMLAttributes: {} };\n },\n\n addAttributes() {\n return {\n index: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-index'),\n renderHTML: (attributes) => {\n if (!attributes.index) return {};\n return { 'data-index': attributes.index };\n },\n },\n url: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-url'),\n renderHTML: (attributes) => {\n if (!attributes.url) return {};\n return { 'data-url': attributes.url };\n },\n },\n title: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-title'),\n renderHTML: (attributes) => {\n if (!attributes.title) return {};\n return { 'data-title': attributes.title };\n },\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span.citation-ref' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const index = String(node.attrs.index ?? '');\n const url = node.attrs.url as string | null;\n const title = (node.attrs.title as string | null) || '';\n const attrs = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n class: 'citation-ref',\n });\n\n if (url) {\n return [\n 'span',\n attrs,\n [\n 'a',\n {\n href: url,\n title,\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n index,\n ],\n ];\n }\n\n return ['span', attrs, index];\n },\n\n renderMarkdown(node) {\n return `[^${node?.attrs?.index ?? ''}]`;\n },\n\n parseMarkdown: (token, helpers) => {\n return helpers.createNode('citationRef', {\n index: String(token.index ?? ''),\n url: token.url ?? null,\n title: token.title ?? null,\n });\n },\n\n markdownTokenizer: {\n name: 'citationRef',\n level: 'inline' as const,\n start(src: string) {\n const match = /\\[\\^\\d+\\]/.exec(src);\n return match?.index ?? -1;\n },\n tokenize(src) {\n const match = /^\\[\\^(\\d+)\\]/.exec(src);\n if (!match) return undefined;\n return {\n type: 'citationRef',\n raw: match[0],\n index: match[1],\n };\n },\n },\n\n addCommands() {\n return {\n insertCitationRef:\n (attrs) =>\n ({ commands }) =>\n commands.insertContent({\n type: this.name,\n attrs: {\n index: attrs.index,\n url: attrs.url ?? null,\n title: attrs.title ?? null,\n },\n }),\n };\n },\n});\n","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 / CitationRef「会被客户端增强」,**不放进 base**——\n * 否则编辑器追加增强版会与 base 的纯版同名冲突(Tiptap 报 \"Duplicate extension names\")。\n * 改为:base 不含这三类,由调用方按需注入:\n * - 编辑器 / 预览注入「增强版」(React NodeView)\n * - server 注入「纯版」(pureCodeBlock / pureImage / CitationRef)\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 / CitationRef**(见上方边界说明)。\n * 编辑器:`[...baseExtensions, 增强CodeBlock, 增强Image, createCitationRef(), Markdown]`\n * server/预览:`[...baseExtensions, pureCodeBlock, pureImage, CitationRef, …]`\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({\n table: {\n resizable: true,\n // SSR/renderHTML 默认不包 wrapper;阅读页无 NodeView 时宽表会被挤扁。\n // 与编辑器 TableView 的 `.tableWrapper` 对齐,静态 HTML 也可 overflow-x。\n renderWrapper: true,\n },\n }),\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":"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"}
1
+ {"version":3,"file":"extractToc-DknI9ylT.js","sources":["../src/citationUtils.ts","../src/CitationRef.ts","../src/extensions.ts","../src/toc/tocSlug.ts","../src/ReportContent.tsx","../src/toc/extractToc.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core';\n\nexport interface SourceRef {\n /** 与 markdown `[^n]` 中的 n 对齐(字符串,如 `\"1\"`)。 */\n index: string;\n url?: string;\n title?: string;\n /** 原文片段等;库本身不渲染 Popover,留给消费方。 */\n excerpt?: string;\n}\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * 将 markdown 中能匹配到 `sources` 的 `[^n]` 换成带 data-* 的\n * `<span class=\"citation-ref\">`(**不是** `<sup>`,避免被 Superscript 吃掉、\n * 也避免视觉上标)。未匹配的 `[^n]` 原样保留,留给 CitationRef tokenizer。\n *\n * 浏览器端 @tiptap/markdown 可解析内联 HTML;server 端无 DOMParser 时请改用\n * {@link applyCitationSources}(先 parse 再补 attrs)。\n */\nexport function enrichMarkdownCitations(\n markdown: string,\n sources: SourceRef[],\n): string {\n if (!sources.length) return markdown;\n\n return markdown.replace(/\\[\\^(\\d+)\\]/g, (match, num: string) => {\n const source = sources.find((s) => s.index === num);\n if (!source) return match;\n\n const attrs = [`class=\"citation-ref\"`, `data-index=\"${num}\"`];\n if (source.url) attrs.push(`data-url=\"${escapeAttr(source.url)}\"`);\n if (source.title) attrs.push(`data-title=\"${escapeAttr(source.title)}\"`);\n\n if (source.url) {\n return `<span ${attrs.join(' ')}><a href=\"${escapeAttr(source.url)}\" title=\"${escapeAttr(source.title || '')}\" target=\"_blank\" rel=\"noopener\">${num}</a></span>`;\n }\n return `<span ${attrs.join(' ')}>${num}</span>`;\n });\n}\n\nfunction patchCitationNode(\n node: JSONContent,\n byIndex: Map<string, SourceRef>,\n): JSONContent {\n if (node.type === 'citationRef') {\n const index = String(node.attrs?.index ?? '');\n const source = byIndex.get(index);\n if (!source) return node;\n return {\n ...node,\n attrs: {\n ...node.attrs,\n index,\n url: source.url ?? node.attrs?.url ?? null,\n title: source.title ?? node.attrs?.title ?? null,\n },\n };\n }\n\n if (!node.content?.length) return node;\n return {\n ...node,\n content: node.content.map((child) => patchCitationNode(child, byIndex)),\n };\n}\n\n/**\n * 在已 parse 的 Tiptap JSON 上,按 `sources` 给 `citationRef` 节点补 url/title。\n * SSR / 无 DOMParser 环境的首选路径。\n */\nexport function applyCitationSources(\n doc: JSONContent,\n sources: SourceRef[],\n): JSONContent {\n if (!sources.length) return doc;\n const byIndex = new Map(sources.map((s) => [s.index, s]));\n return patchCitationNode(doc, byIndex);\n}\n","import { mergeAttributes, Node } from '@tiptap/core';\n\nexport interface CitationRefOptions {\n HTMLAttributes: Record<string, unknown>;\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n citationRef: {\n insertCitationRef: (attrs: {\n index: string;\n url?: string | null;\n title?: string | null;\n }) => ReturnType;\n };\n }\n}\n\n/**\n * 脚注/引文引用——inline atom 节点。\n *\n * - 输入:`[^n]`(markdownTokenizer)或 `<span class=\"citation-ref\">`(parseHTML)\n * - 输出 HTML:居中圆形 pill(CSS `.citation-ref`),**不是**上下标\n * - 输出 markdown:`[^n]`\n *\n * Popover /「原文片段」由消费方通过 {@link createCitationRef} 的 `renderCitation`\n * 插槽完成;本节点只负责标记与默认圆标样式。\n */\nexport const CitationRef = Node.create<CitationRefOptions>({\n name: 'citationRef',\n\n group: 'inline',\n\n inline: true,\n\n atom: true,\n\n selectable: true,\n\n // 高于普通链接,确保 `[^1]` 不被拆成残缺 link\n priority: 1000,\n\n addOptions() {\n return { HTMLAttributes: {} };\n },\n\n addAttributes() {\n return {\n index: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-index'),\n renderHTML: (attributes) => {\n if (!attributes.index) return {};\n return { 'data-index': attributes.index };\n },\n },\n url: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-url'),\n renderHTML: (attributes) => {\n if (!attributes.url) return {};\n return { 'data-url': attributes.url };\n },\n },\n title: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-title'),\n renderHTML: (attributes) => {\n if (!attributes.title) return {};\n return { 'data-title': attributes.title };\n },\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span.citation-ref' }];\n },\n\n renderHTML({ node, HTMLAttributes }) {\n const index = String(node.attrs.index ?? '');\n const url = node.attrs.url as string | null;\n const title = (node.attrs.title as string | null) || '';\n const attrs = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {\n class: 'citation-ref',\n });\n\n if (url) {\n return [\n 'span',\n attrs,\n [\n 'a',\n {\n href: url,\n title,\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n index,\n ],\n ];\n }\n\n return ['span', attrs, index];\n },\n\n renderMarkdown(node) {\n return `[^${node?.attrs?.index ?? ''}]`;\n },\n\n parseMarkdown: (token, helpers) => {\n return helpers.createNode('citationRef', {\n index: String(token.index ?? ''),\n url: token.url ?? null,\n title: token.title ?? null,\n });\n },\n\n markdownTokenizer: {\n name: 'citationRef',\n level: 'inline' as const,\n start(src: string) {\n const match = /\\[\\^\\d+\\]/.exec(src);\n return match?.index ?? -1;\n },\n tokenize(src) {\n const match = /^\\[\\^(\\d+)\\]/.exec(src);\n if (!match) return undefined;\n return {\n type: 'citationRef',\n raw: match[0],\n index: match[1],\n };\n },\n },\n\n addCommands() {\n return {\n insertCitationRef:\n (attrs) =>\n ({ commands }) =>\n commands.insertContent({\n type: this.name,\n attrs: {\n index: attrs.index,\n url: attrs.url ?? null,\n title: attrs.title ?? null,\n },\n }),\n };\n },\n});\n","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 / CitationRef「会被客户端增强」,**不放进 base**——\n * 否则编辑器追加增强版会与 base 的纯版同名冲突(Tiptap 报 \"Duplicate extension names\")。\n * 改为:base 不含这三类,由调用方按需注入:\n * - 编辑器 / 预览注入「增强版」(React NodeView)\n * - server 注入「纯版」(pureCodeBlock / pureImage / CitationRef)\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 / CitationRef**(见上方边界说明)。\n * 编辑器:`[...baseExtensions, 增强CodeBlock, 增强Image, createCitationRef(), Markdown]`\n * server/预览:`[...baseExtensions, pureCodeBlock, pureImage, CitationRef, …]`\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({\n table: {\n resizable: true,\n // SSR/renderHTML 默认不包 wrapper;阅读页无 NodeView 时宽表会被挤扁。\n // 与编辑器 TableView 的 `.tableWrapper` 对齐,静态 HTML 也可 overflow-x。\n renderWrapper: true,\n },\n }),\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":"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"}
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import Image from "@tiptap/extension-image";
6
6
  import { TableOfContents } from "@tiptap/extension-table-of-contents";
7
7
  import { NodeViewWrapper, ReactNodeViewRenderer, NodeViewContent, useEditor, EditorContent, useEditorState } from "@tiptap/react";
8
8
  import { forwardRef, useEffect, useImperativeHandle, useMemo, useState, useRef, useCallback, useContext, createContext } from "react";
9
- import { C as CitationRef, e as enrichMarkdownCitations, s as styles$4, b as baseExtensions, l as lowlight, m as makeTocGetId, p as pureCodeBlock, a as pureImage, R as ReportContent } from "./extractToc-CSawxIx-.js";
10
- import { c, d } from "./extractToc-CSawxIx-.js";
9
+ import { C as CitationRef, e as enrichMarkdownCitations, s as styles$4, b as baseExtensions, l as lowlight, m as makeTocGetId, p as pureCodeBlock, a as pureImage, R as ReportContent } from "./extractToc-DknI9ylT.js";
10
+ import { c, d } from "./extractToc-DknI9ylT.js";
11
11
  import { Extension } from "@tiptap/core";
12
12
  import { FileHandlePlugin } from "@tiptap/extension-file-handler";
13
13
  import { PluginKey, Plugin } from "@tiptap/pm/state";
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
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-CSawxIx-.js";
2
- import { R, e, l } from "./extractToc-CSawxIx-.js";
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-DknI9ylT.js";
2
+ import { R, e, l } from "./extractToc-DknI9ylT.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";
package/dist/style.css CHANGED
@@ -1,11 +1,11 @@
1
1
  /* 规范正文样式(编辑器 / 只读预览 / SSR 阅读三处共用同一个类,保证所见即所得、
2
2
  发布后一致)。配色/字体全部走可被消费方覆盖的 CSS 变量(--tmr-*,带默认值)。 */
3
3
 
4
- ._editorScroll_9hn0j_4 {
4
+ ._editorScroll_q3j4s_4 {
5
5
  margin-top: 24px;
6
6
  }
7
7
 
8
- ._editorContent_9hn0j_8 {
8
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 p.is-editor-empty:first-child::before {
31
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h1 {
39
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h2 {
47
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h3 {
55
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h4 {
63
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h5 {
70
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 h6 {
77
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 p {
85
+ ._editorContent_q3j4s_8 p {
86
86
  margin: 0.7em 0;
87
87
  }
88
88
 
89
- ._editorContent_9hn0j_8 ul,
90
- ._editorContent_9hn0j_8 ol {
89
+ ._editorContent_q3j4s_8 ul,
90
+ ._editorContent_q3j4s_8 ol {
91
91
  padding-left: 1.5em;
92
92
  margin: 0.7em 0;
93
93
  }
94
94
 
95
- ._editorContent_9hn0j_8 ul {
95
+ ._editorContent_q3j4s_8 ul {
96
96
  list-style: disc;
97
97
  }
98
98
 
99
- ._editorContent_9hn0j_8 ol {
99
+ ._editorContent_q3j4s_8 ol {
100
100
  list-style: decimal;
101
101
  }
102
102
 
103
- ._editorContent_9hn0j_8 blockquote {
103
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 a {
111
+ ._editorContent_q3j4s_8 a {
112
112
  color: var(--tmr-accent, #ff6719);
113
113
  text-decoration: underline;
114
114
  }
115
115
 
116
116
  /* 仅行内代码加灰底;代码块内的 <code>(在 pre 内)由 NodeView 自管,排除掉 */
117
- ._editorContent_9hn0j_8 code:not(pre code) {
117
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 hr {
132
+ ._editorContent_q3j4s_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
- ._editorContent_9hn0j_8 img {
138
+ ._editorContent_q3j4s_8 img {
139
139
  max-width: 100%;
140
140
  height: auto;
141
141
  border-radius: 6px;
@@ -144,53 +144,67 @@
144
144
  }
145
145
 
146
146
  /* 选中的图片高亮 */
147
- ._editorContent_9hn0j_8 img.ProseMirror-selectednode {
147
+ ._editorContent_q3j4s_8 img.ProseMirror-selectednode {
148
148
  outline: 2px solid var(--tmr-accent, #ff6719);
149
149
  }
150
150
 
151
- /* 表格:细边框、表头浅灰;外包 .tableWrapper 横滑(编辑器 NodeView + SSR renderWrapper) */
152
- ._editorContent_9hn0j_8 .tableWrapper {
151
+ /* 表格:细边框、表头浅灰;外包 .tableWrapper(编辑器 NodeView + SSR renderWrapper)
152
+ * 桌面:width:100% + 允许换行,整表落在阅读列内(与改前一致)
153
+ * 窄屏:max-content + nowrap,避免列被挤扁,wrapper 内横滑 */
154
+ ._editorContent_q3j4s_8 .tableWrapper {
153
155
  max-width: 100%;
154
156
  overflow-x: auto;
155
157
  -webkit-overflow-scrolling: touch;
156
158
  margin: 1em 0;
157
159
  }
158
160
 
159
- ._editorContent_9hn0j_8 table {
161
+ ._editorContent_q3j4s_8 table {
160
162
  border-collapse: collapse;
161
- /* 勿用 fixed+width:100%:窄屏会把多列均分挤扁、文字狂换行,且撑不出横滑 */
162
163
  table-layout: auto;
163
- width: max-content;
164
- min-width: 100%;
165
- max-width: none;
164
+ width: 100%;
166
165
  margin: 0;
167
166
  overflow: visible;
168
167
  font-size: 0.92em;
169
168
  }
170
169
 
171
170
  /* 无 wrapper 的遗留 HTML(旧缓存)仍给一点外边距 */
172
- ._editorContent_9hn0j_8 :not(.tableWrapper) > table {
171
+ ._editorContent_q3j4s_8 :not(.tableWrapper) > table {
173
172
  margin: 1em 0;
174
173
  }
175
174
 
176
- ._editorContent_9hn0j_8 table td,
177
- ._editorContent_9hn0j_8 table th {
175
+ ._editorContent_q3j4s_8 table td,
176
+ ._editorContent_q3j4s_8 table th {
178
177
  border: 1px solid var(--tmr-border, #e6e6e6);
179
178
  padding: 8px 12px;
180
179
  vertical-align: top;
181
180
  text-align: left;
182
181
  position: relative;
183
182
  min-width: 60px;
184
- white-space: nowrap;
183
+ white-space: normal;
184
+ word-break: break-word;
185
185
  }
186
186
 
187
- ._editorContent_9hn0j_8 table th {
187
+ ._editorContent_q3j4s_8 table th {
188
188
  background: var(--tmr-table-header-bg, #f7f7f7);
189
189
  font-weight: 600;
190
190
  }
191
191
 
192
+ @media (max-width: 640px) {
193
+ ._editorContent_q3j4s_8 table {
194
+ width: max-content;
195
+ min-width: 100%;
196
+ max-width: none;
197
+ }
198
+
199
+ ._editorContent_q3j4s_8 table td,
200
+ ._editorContent_q3j4s_8 table th {
201
+ white-space: nowrap;
202
+ word-break: normal;
203
+ }
204
+ }
205
+
192
206
  /* 选中的单元格高亮 */
193
- ._editorContent_9hn0j_8 table .selectedCell::after {
207
+ ._editorContent_q3j4s_8 table .selectedCell::after {
194
208
  content: '';
195
209
  position: absolute;
196
210
  inset: 0;
@@ -199,7 +213,7 @@
199
213
  }
200
214
 
201
215
  /* 列宽拖拽手柄 */
202
- ._editorContent_9hn0j_8 table .column-resize-handle {
216
+ ._editorContent_q3j4s_8 table .column-resize-handle {
203
217
  position: absolute;
204
218
  right: -2px;
205
219
  top: 0;
@@ -210,34 +224,34 @@
210
224
  }
211
225
 
212
226
  /* 任务清单 */
213
- ._editorContent_9hn0j_8 ul[data-type='taskList'] {
227
+ ._editorContent_q3j4s_8 ul[data-type='taskList'] {
214
228
  list-style: none;
215
229
  padding-left: 0.2em;
216
230
  }
217
231
 
218
- ._editorContent_9hn0j_8 ul[data-type='taskList'] li {
232
+ ._editorContent_q3j4s_8 ul[data-type='taskList'] li {
219
233
  display: flex;
220
234
  align-items: flex-start;
221
235
  gap: 0.5em;
222
236
  margin: 0.3em 0;
223
237
  }
224
238
 
225
- ._editorContent_9hn0j_8 ul[data-type='taskList'] li > label {
239
+ ._editorContent_q3j4s_8 ul[data-type='taskList'] li > label {
226
240
  margin-top: 0.35em;
227
241
  user-select: none;
228
242
  }
229
243
 
230
- ._editorContent_9hn0j_8 ul[data-type='taskList'] li > div {
244
+ ._editorContent_q3j4s_8 ul[data-type='taskList'] li > div {
231
245
  flex: 1;
232
246
  }
233
247
 
234
- ._editorContent_9hn0j_8 ul[data-type='taskList'] input[type='checkbox'] {
248
+ ._editorContent_q3j4s_8 ul[data-type='taskList'] input[type='checkbox'] {
235
249
  accent-color: var(--tmr-accent, #ff6719);
236
250
  cursor: pointer;
237
251
  }
238
252
 
239
253
  /* 脚注/引文圆标:与正文字垂直居中;配色对齐工具栏 active(浅橙底 + accent 字) */
240
- ._editorContent_9hn0j_8 span.citation-ref {
254
+ ._editorContent_q3j4s_8 span.citation-ref {
241
255
  display: inline-flex;
242
256
  align-items: center;
243
257
  justify-content: center;
@@ -274,7 +288,7 @@
274
288
  border-color 0.15s ease;
275
289
  }
276
290
 
277
- ._editorContent_9hn0j_8 span.citation-ref a {
291
+ ._editorContent_q3j4s_8 span.citation-ref a {
278
292
  color: inherit;
279
293
  text-decoration: none;
280
294
  display: flex;
@@ -286,9 +300,9 @@
286
300
  pointer-events: none;
287
301
  }
288
302
 
289
- ._editorContent_9hn0j_8 span.citation-ref:hover,
290
- ._editorContent_9hn0j_8 span.citation-ref:focus-within,
291
- ._editorContent_9hn0j_8 span.citation-ref[data-tmr-citation-active] {
303
+ ._editorContent_q3j4s_8 span.citation-ref:hover,
304
+ ._editorContent_q3j4s_8 span.citation-ref:focus-within,
305
+ ._editorContent_q3j4s_8 span.citation-ref[data-tmr-citation-active] {
292
306
  background: var(--tmr-citation-bg-hover, var(--tmr-accent, #ff6719));
293
307
  border-color: var(--tmr-citation-bg-hover, var(--tmr-accent, #ff6719));
294
308
  color: var(--tmr-citation-fg-hover, #ffffff);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiptap-markdown-react",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
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",