tiptap-markdown-react 0.6.2 → 0.7.0
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/README.md +48 -1
- package/dist/commentAnchor/CommentMark.d.ts +29 -0
- package/dist/commentAnchor/CommentPopover.d.ts +18 -0
- package/dist/commentAnchor/commentAnchor.test.d.ts +1 -0
- package/dist/commentAnchor/commentAnchorController.d.ts +15 -0
- package/dist/commentAnchor/commentAnchorPlugin.d.ts +26 -0
- package/dist/commentAnchor/commentMapper.d.ts +40 -0
- package/dist/commentAnchor/commentTypes.d.ts +33 -0
- package/dist/components/CodeBlockView.d.ts +1 -1
- package/dist/components/MarkdownWysiwygEditor.d.ts +27 -0
- package/dist/extractToc-AUH9u6o-.js +515 -0
- package/dist/extractToc-AUH9u6o-.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +518 -129
- package/dist/index.js.map +1 -1
- package/dist/labels.d.ts +10 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +6 -2
- package/dist/style.css +191 -81
- package/package.json +1 -1
- package/dist/extractToc-CSawxIx-.js +0 -260
- package/dist/extractToc-CSawxIx-.js.map +0 -1
package/README.md
CHANGED
|
@@ -114,6 +114,46 @@ export default async function Page() {
|
|
|
114
114
|
}
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
### 4. Comment anchoring (edit session only)
|
|
118
|
+
|
|
119
|
+
Review / annotation workflows: pass decoded comment segments to the editor; the
|
|
120
|
+
library maps them onto text ranges as `commentAnchor` marks, renders a block-left
|
|
121
|
+
gutter, and reports clicks back to you. **Marks are session-only** — `getMarkdown()`
|
|
122
|
+
strips them, undo history ignores them, and the read-only `MarkdownPreview` never
|
|
123
|
+
renders them.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
const comments: CommentRef[] = [
|
|
127
|
+
{
|
|
128
|
+
commentId: 'c1',
|
|
129
|
+
author: 'agent',
|
|
130
|
+
segments: [{ exact: '寒武纪成立于2016年' }],
|
|
131
|
+
body: 'Add a source for this claim.',
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
<MarkdownWysiwygEditor
|
|
136
|
+
ref={ref}
|
|
137
|
+
initialMarkdown={md}
|
|
138
|
+
comments={comments}
|
|
139
|
+
activeCommentId={activeId}
|
|
140
|
+
onActiveCommentChange={setActiveId}
|
|
141
|
+
onCommentClick={({ commentIds, anchorEl }) => openPopover(commentIds[0], anchorEl)}
|
|
142
|
+
/>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- **Ref methods**: `focusComment(id)` (active + scroll + cursor to range start),
|
|
146
|
+
`nextComment('next' | 'prev')`, `getCommentIds()`.
|
|
147
|
+
- **Overlap**: multiple comments on the same text merge into one mark with
|
|
148
|
+
`data-comment-ids="1 2"`; clicks report all ids.
|
|
149
|
+
- **Popover**: `CommentPopover` (Radix) anchors to the clicked mark; content is
|
|
150
|
+
host-owned (`renderComment`-style children).
|
|
151
|
+
- **Guards**: paste / drop strips marks via `transformPasted`; anchor application
|
|
152
|
+
is excluded from undo history.
|
|
153
|
+
- **Segments**: `CommentSegment[]` (`{ blockHash?, prefix?, exact, suffix? }`).
|
|
154
|
+
Capture them from a document with the same text model (see `commentMapper` /
|
|
155
|
+
`blockTextHash`) so re-anchoring is exact.
|
|
156
|
+
|
|
117
157
|
## Theming
|
|
118
158
|
|
|
119
159
|
Override any of these CSS variables on an ancestor (e.g. `:root` or the editor container):
|
|
@@ -130,6 +170,11 @@ Override any of these CSS variables on an ancestor (e.g. `:root` or the editor c
|
|
|
130
170
|
| `--tmr-min-height` | `420px` | Editor min height |
|
|
131
171
|
| `--tmr-code-bg` | `#f4f4f4` | Inline code background |
|
|
132
172
|
| `--tmr-table-header-bg` | `#f7f7f7` | Table header background |
|
|
173
|
+
| `--tmr-comment-bg` | `rgba(255, 213, 79, 0.32)` | Comment mark background |
|
|
174
|
+
| `--tmr-comment-bg-active` | `rgba(255, 183, 0, 0.55)` | Active comment mark background |
|
|
175
|
+
| `--tmr-comment-ring` | `rgba(219, 171, 10, 0.55)` | Active mark / block outline ring |
|
|
176
|
+
| `--tmr-comment-gutter-bg` | `#f4b400` | Gutter bubble background |
|
|
177
|
+
| `--tmr-popover-bg` | `#fff` | CommentPopover background |
|
|
133
178
|
|
|
134
179
|
## API
|
|
135
180
|
|
|
@@ -138,6 +183,7 @@ Override any of these CSS variables on an ancestor (e.g. `:root` or the editor c
|
|
|
138
183
|
| Prop | Type | Description |
|
|
139
184
|
| --- | --- | --- |
|
|
140
185
|
| `initialMarkdown` | `string` | Initial content (markdown). |
|
|
186
|
+
| `editable` | `boolean` | `false` renders read-only with the **same** extensions/NodeViews/styles — interactive controls (code-block language dropdown, delete) collapse. Comments are ignored when `false`. Default `true`. |
|
|
141
187
|
| `placeholder` | `string` | Placeholder for the empty document. |
|
|
142
188
|
| `onEditorReady` | `(editor: Editor \| null) => void` | Get the Tiptap instance (for the toolbar). |
|
|
143
189
|
| `onTocChange` | `(items: TocItem[]) => void` | Fires when headings change. |
|
|
@@ -147,7 +193,8 @@ Override any of these CSS variables on an ancestor (e.g. `:root` or the editor c
|
|
|
147
193
|
| `codeBlockLabels` | `Partial<CodeBlockLabels>` | Localize the code block UI. |
|
|
148
194
|
| `className` | `string` | Class on the scroll container. |
|
|
149
195
|
|
|
150
|
-
Ref handle (`MarkdownWysiwygEditorHandle`): `getMarkdown()`, `getHTML()`, `getJSON()`, `getEditor()
|
|
196
|
+
Ref handle (`MarkdownWysiwygEditorHandle`): `getMarkdown()`, `getHTML()`, `getJSON()`, `getEditor()`,
|
|
197
|
+
`focusComment(id)`, `nextComment(dir?)`, `getCommentIds()`.
|
|
151
198
|
|
|
152
199
|
#### Markdown in: paste, drop, import
|
|
153
200
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Mark } from '@tiptap/core';
|
|
2
|
+
/**
|
|
3
|
+
* 评论锚定 mark —— 仅存在于编辑会话内,导出 markdown 时剥离。
|
|
4
|
+
*
|
|
5
|
+
* - attrs.commentIds:string[]。ProseMirror 同一文本节点不能挂两个同名
|
|
6
|
+
* mark,重叠评论由 commentMapper 合并成「区间 + commentIds[]」后一次性铺入。
|
|
7
|
+
* - renderMarkdown 返回空串 → getMarkdown() 输出纯文本,评论数据不落库。
|
|
8
|
+
* - 粘贴/拖拽拦截与 gutter / active 装饰见 commentAnchorPlugin(客户端)。
|
|
9
|
+
* 本模块保持纯 schema,server 入口可安全导入。
|
|
10
|
+
*/
|
|
11
|
+
declare module '@tiptap/core' {
|
|
12
|
+
interface Commands<ReturnType> {
|
|
13
|
+
commentAnchor: {
|
|
14
|
+
/** 按互不相交的区间 + commentIds 数组铺评论 mark(区间由 mergeCommentIntervals 产出)。 */
|
|
15
|
+
applyCommentAnchors: (intervals: CommentInterval[]) => ReturnType;
|
|
16
|
+
/** 清空全文档评论 mark(comments 刷新 / 退出编辑态前调用)。 */
|
|
17
|
+
clearCommentAnchors: () => ReturnType;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export interface CommentInterval {
|
|
22
|
+
from: number;
|
|
23
|
+
to: number;
|
|
24
|
+
commentIds: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface CommentMarkOptions {
|
|
27
|
+
HTMLAttributes: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export declare const CommentMark: Mark<CommentMarkOptions, any>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface CommentPopoverProps {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange?: (open: boolean) => void;
|
|
5
|
+
/** 被点击的 mark 元素(from onCommentClick 的 anchorEl)。 */
|
|
6
|
+
anchorEl: HTMLElement | null;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
side?: 'top' | 'bottom' | 'left' | 'right';
|
|
9
|
+
align?: 'start' | 'center' | 'end';
|
|
10
|
+
sideOffset?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 评论浮层(Radix)。库只负责锚定与外观,内容由宿主通过 children 提供
|
|
14
|
+
* (典型:评论正文 + Resolve/Dismiss/Reopen 操作)。
|
|
15
|
+
* 锚点是点击 mark 的动态元素:渲染一个 fixed 1x1 span 挂在 anchorEl 的
|
|
16
|
+
* getBoundingClientRect 位置,Radix 以它为锚。
|
|
17
|
+
*/
|
|
18
|
+
export declare function CommentPopover({ open, onOpenChange, anchorEl, children, side, align, sideOffset, }: CommentPopoverProps): import("react").JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Editor } from '@tiptap/core';
|
|
2
|
+
import { CommentRange } from './commentMapper';
|
|
3
|
+
import { CommentRef } from './commentTypes';
|
|
4
|
+
/** 先清后铺,幂等;无评论时清空全部 mark。 */
|
|
5
|
+
export declare function applyCommentAnchorsToEditor(editor: Editor, comments: CommentRef[]): void;
|
|
6
|
+
/** 文档顺序去重的 commentId 列表(gutter / 导航顺序)。 */
|
|
7
|
+
export declare function collectCommentIds(editor: Editor): string[];
|
|
8
|
+
export declare function commentRangesById(editor: Editor, commentId: string): CommentRange[];
|
|
9
|
+
/**
|
|
10
|
+
* 聚焦某条评论:设置 active(mark/block 高亮)+ 滚动到第一个 mark + 光标落到
|
|
11
|
+
* 区间起点。找不到(outdated)时清空 active 并返回 false。
|
|
12
|
+
*/
|
|
13
|
+
export declare function focusComment(editor: Editor, commentId: string): boolean;
|
|
14
|
+
/** 按文档顺序在评论间循环跳转(dir: next / prev)。 */
|
|
15
|
+
export declare function nextComment(editor: Editor, dir?: 'next' | 'prev'): string | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Slice, MarkType } from '@tiptap/pm/model';
|
|
2
|
+
import { Extension } from '@tiptap/core';
|
|
3
|
+
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
4
|
+
export declare const COMMENT_ACTIVE_META = "tmr:comment:active";
|
|
5
|
+
export declare const COMMENT_CLICK_META = "tmr:comment:click";
|
|
6
|
+
export declare const COMMENT_GUTTER_META = "tmr:comment:gutter";
|
|
7
|
+
export interface CommentGutterPayload {
|
|
8
|
+
commentIds: string[];
|
|
9
|
+
pos: number;
|
|
10
|
+
}
|
|
11
|
+
export interface CommentAnchorPluginOptions {
|
|
12
|
+
/** 是否在 block 左缘渲染评论 gutter 气泡,默认 true。 */
|
|
13
|
+
showGutter?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface CommentAnchorPluginState {
|
|
16
|
+
activeCommentId: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare const commentAnchorPluginKey: PluginKey<CommentAnchorPluginState>;
|
|
19
|
+
/** 从 Slice 中剔除 commentAnchor mark(粘贴/拖拽守卫)。 */
|
|
20
|
+
export declare function stripCommentAnchorMarks(slice: Slice, markType: MarkType): Slice;
|
|
21
|
+
export declare function commentAnchorPlugin(options?: CommentAnchorPluginOptions): Plugin<CommentAnchorPluginState>;
|
|
22
|
+
/**
|
|
23
|
+
* Tiptap Extension 包装:把评论锚定 Plugin 挂进 useEditor / Editor 的 extensions。
|
|
24
|
+
* 只读态(MarkdownPreview)不要注入本扩展 —— 评论是编辑会话专属。
|
|
25
|
+
*/
|
|
26
|
+
export declare function commentAnchorExtension(options?: CommentAnchorPluginOptions): Extension<any, any>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Node } from '@tiptap/pm/model';
|
|
2
|
+
import { CommentInterval } from './CommentMark';
|
|
3
|
+
/**
|
|
4
|
+
* SPIKE 原型:把评论的 ref_content segments 映射到 ProseMirror doc 上的文本区间。
|
|
5
|
+
*
|
|
6
|
+
* 与旧 DOM textContent 匹配的区别:
|
|
7
|
+
* - 工作在文档模型上(无表格 wrapper 空白 / text node 碎片问题);
|
|
8
|
+
* - 按顶层 block 作用域搜索,避免跨 block 误命中;
|
|
9
|
+
* - fallback 链与既有 anchorCommentsInDom 一致:hash → context → exact → prefix → outdated。
|
|
10
|
+
*/
|
|
11
|
+
export interface CommentSegment {
|
|
12
|
+
/** block 文本哈希(可选快路径;哈希算法见 blockTextHash,需与捕获端一致)。 */
|
|
13
|
+
blockHash?: string;
|
|
14
|
+
prefix?: string;
|
|
15
|
+
exact: string;
|
|
16
|
+
suffix?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CommentAnchorInput {
|
|
19
|
+
commentId: string;
|
|
20
|
+
segments: CommentSegment[];
|
|
21
|
+
}
|
|
22
|
+
export type CommentAnchorStatus = 'matched' | 'ambiguous' | 'partial' | 'outdated';
|
|
23
|
+
export interface CommentRange {
|
|
24
|
+
from: number;
|
|
25
|
+
to: number;
|
|
26
|
+
}
|
|
27
|
+
export interface CommentAnchorResult {
|
|
28
|
+
commentId: string;
|
|
29
|
+
ranges: CommentRange[];
|
|
30
|
+
status: CommentAnchorStatus;
|
|
31
|
+
}
|
|
32
|
+
/** djb2 哈希(捕获端与消费端必须用同一个实现;invret 接入时对齐)。 */
|
|
33
|
+
export declare function blockTextHash(text: string): string;
|
|
34
|
+
/** 主入口:评论列表 → 每条的 doc 区间 + 状态。 */
|
|
35
|
+
export declare function mapCommentAnchors(doc: Node, comments: CommentAnchorInput[]): CommentAnchorResult[];
|
|
36
|
+
/** 把每条评论的区间合并成互不相交的 CommentInterval(重叠区间合并 commentIds)。 */
|
|
37
|
+
export declare function mergeCommentIntervals(entries: Array<{
|
|
38
|
+
commentId: string;
|
|
39
|
+
ranges: CommentRange[];
|
|
40
|
+
}>): CommentInterval[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { CommentSegment } from './commentMapper';
|
|
3
|
+
/** 宿主评论(已解码为 segments;ref_content 解码由宿主完成)。 */
|
|
4
|
+
export interface CommentRef {
|
|
5
|
+
commentId: string;
|
|
6
|
+
/** 评论作者类型:agent / user(宿主用于配色等)。 */
|
|
7
|
+
author?: 'agent' | 'user';
|
|
8
|
+
/** 决议状态:open / resolved / dismissed。 */
|
|
9
|
+
state?: 'open' | 'resolved' | 'dismissed';
|
|
10
|
+
/** 与正文对齐的锚定片段(编辑器装载时映射为 mark 区间)。 */
|
|
11
|
+
segments: CommentSegment[];
|
|
12
|
+
/** 评论正文(宿主展示用,库不消费)。 */
|
|
13
|
+
body?: string;
|
|
14
|
+
user?: {
|
|
15
|
+
name?: string;
|
|
16
|
+
avatar?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** CommentPopover 内容插槽上下文。 */
|
|
20
|
+
export interface RenderCommentContext {
|
|
21
|
+
comment: CommentRef;
|
|
22
|
+
/** 锚定的评论 id(多条重叠时取第一条)。 */
|
|
23
|
+
commentId: string;
|
|
24
|
+
}
|
|
25
|
+
export type RenderComment = (ctx: RenderCommentContext) => ReactNode;
|
|
26
|
+
/** 编辑器内点击评论 mark / gutter 时上报给宿主的载荷。 */
|
|
27
|
+
export interface CommentClickPayload {
|
|
28
|
+
commentIds: string[];
|
|
29
|
+
/** mark 元素(gutter 点击为 null)。 */
|
|
30
|
+
anchorEl: HTMLElement | null;
|
|
31
|
+
/** 点击位置的 doc 坐标。 */
|
|
32
|
+
pos: number;
|
|
33
|
+
}
|
|
@@ -3,4 +3,4 @@ import { NodeViewProps } from '@tiptap/react';
|
|
|
3
3
|
* 代码块自定义 NodeView:右上角语言选择器(Radix DropdownMenu,支持方向键导航)+ 删除。
|
|
4
4
|
* 文案(Auto-detect / 删除标题)从扩展 options.codeBlockLabels 读取,缺省用英文默认。
|
|
5
5
|
*/
|
|
6
|
-
export declare function CodeBlockView({ node, updateAttributes, deleteNode, extension, }: NodeViewProps): import("react").JSX.Element;
|
|
6
|
+
export declare function CodeBlockView({ node, updateAttributes, deleteNode, extension, editor, }: NodeViewProps): import("react").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AnyExtension, Editor } from '@tiptap/react';
|
|
2
2
|
import { SourceRef } from '../citationUtils';
|
|
3
3
|
import { RenderCitation } from '../citationTypes';
|
|
4
|
+
import { CommentClickPayload, CommentRef } from '../commentAnchor/commentTypes';
|
|
4
5
|
import { CodeBlockLabels } from '../labels';
|
|
5
6
|
import { TocItem } from '../toc/extractToc';
|
|
6
7
|
export interface MarkdownWysiwygEditorHandle {
|
|
@@ -12,10 +13,22 @@ export interface MarkdownWysiwygEditorHandle {
|
|
|
12
13
|
getJSON: () => Record<string, unknown>;
|
|
13
14
|
/** 底层 Tiptap Editor 实例(可能为 null,未就绪时)。 */
|
|
14
15
|
getEditor: () => Editor | null;
|
|
16
|
+
/** 聚焦评论:设 active + 滚动 + 光标落到区间起点。找不到返回 false。 */
|
|
17
|
+
focusComment: (commentId: string) => boolean;
|
|
18
|
+
/** 按文档顺序跳转评论(next / prev),返回目标 commentId。 */
|
|
19
|
+
nextComment: (dir?: 'next' | 'prev') => string | null;
|
|
20
|
+
/** 当前 doc 内已锚定的去重 commentId 列表(文档顺序)。 */
|
|
21
|
+
getCommentIds: () => string[];
|
|
15
22
|
}
|
|
16
23
|
export interface MarkdownWysiwygEditorProps {
|
|
17
24
|
/** 初始 markdown 内容。 */
|
|
18
25
|
initialMarkdown?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 是否可编辑,默认 true。false = 只读渲染,与编辑态同源同路径:
|
|
28
|
+
* 同一套扩展 / NodeView / 样式,交互件(代码块语言选择、删除等)自动收起。
|
|
29
|
+
* 只读态不应用评论锚定(comments 被忽略),与 MarkdownPreview 语义一致。
|
|
30
|
+
*/
|
|
31
|
+
editable?: boolean;
|
|
19
32
|
/**
|
|
20
33
|
* 初始脚注来源:按 index 对齐 `[^n]`,写入 citationRef 的 url/title。
|
|
21
34
|
* 仅影响初始 content;后续插入请用 `insertMarkdown(editor, md, sources)`。
|
|
@@ -47,6 +60,20 @@ export interface MarkdownWysiwygEditorProps {
|
|
|
47
60
|
codeBlockLabels?: Partial<CodeBlockLabels>;
|
|
48
61
|
/** 附加到滚动容器的 class。 */
|
|
49
62
|
className?: string;
|
|
63
|
+
/**
|
|
64
|
+
* 编辑态评论锚定:评论列表(含已解码 segments)。装载时映射为 commentAnchor
|
|
65
|
+
* mark 并高亮;markdown 导出自动剥离,只读态(MarkdownPreview)不渲染评论。
|
|
66
|
+
* 注意:数组引用变化会触发重新铺 mark,宿主应 memo 该数组。
|
|
67
|
+
*/
|
|
68
|
+
comments?: CommentRef[];
|
|
69
|
+
/** 受控 active 评论 id(sidebar 联动)。null = 无 active。 */
|
|
70
|
+
activeCommentId?: string | null;
|
|
71
|
+
/** 编辑器内点击 mark / gutter 时回调。 */
|
|
72
|
+
onCommentClick?: (payload: CommentClickPayload) => void;
|
|
73
|
+
/** active 变化回调(点击 mark / gutter / nextComment 时)。 */
|
|
74
|
+
onActiveCommentChange?: (commentId: string | null) => void;
|
|
75
|
+
/** 是否在 block 左缘渲染评论 gutter 气泡,默认 true。 */
|
|
76
|
+
showCommentGutter?: boolean;
|
|
50
77
|
}
|
|
51
78
|
/**
|
|
52
79
|
* 所见即所得 Markdown 编辑器(基于 Tiptap v3 + 官方 @tiptap/markdown)。
|