wendang 0.0.1 → 0.1.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.
Files changed (107) hide show
  1. package/LICENSE +3 -0
  2. package/NOTICE.md +7 -0
  3. package/README.md +101 -2
  4. package/dist/cjs/document.js +19 -0
  5. package/dist/cjs/index.js +25 -0
  6. package/dist/cjs/mindmap.js +22 -0
  7. package/dist/cjs/package.json +1 -0
  8. package/dist/cjs/schema.js +11 -0
  9. package/dist/cjs/shared/anchor.js +201 -0
  10. package/dist/cjs/shared/callout.js +64 -0
  11. package/dist/cjs/shared/code-block.js +33 -0
  12. package/dist/cjs/shared/code-languages.js +51 -0
  13. package/dist/cjs/shared/doc-compatibility.js +23 -0
  14. package/dist/cjs/shared/doc-text.js +52 -0
  15. package/dist/cjs/shared/document-colors.js +61 -0
  16. package/dist/cjs/shared/inline-comments.js +179 -0
  17. package/dist/cjs/shared/mind-map-folding.js +14 -0
  18. package/dist/cjs/shared/mind-map-phase2.js +133 -0
  19. package/dist/cjs/shared/mind-map.js +445 -0
  20. package/dist/cjs/shared/mind-topic-text.js +57 -0
  21. package/dist/cjs/shared/node-catalog.js +62 -0
  22. package/dist/cjs/shared/presence.js +141 -0
  23. package/dist/cjs/shared/safe-href.js +36 -0
  24. package/dist/cjs/shared/table-cell-format.js +18 -0
  25. package/dist/cjs/shared/table-layout.js +28 -0
  26. package/dist/cjs/shared/types.js +2 -0
  27. package/dist/cjs/shared/video-embed.js +102 -0
  28. package/dist/cjs/shared/workbook.js +152 -0
  29. package/dist/cjs/spreadsheet.js +17 -0
  30. package/dist/cjs/types.js +17 -0
  31. package/dist/cjs/validation/callout.js +17 -0
  32. package/dist/cjs/validation/doc-node-whitelist.js +71 -0
  33. package/dist/cjs/validation/error.js +17 -0
  34. package/dist/cjs/validation/mind-map.js +129 -0
  35. package/dist/cjs/validation/schema-version.js +5 -0
  36. package/dist/cjs/validation/schema.js +376 -0
  37. package/dist/cjs/validation/video-embed.js +17 -0
  38. package/dist/cjs/validation/workbook.js +17 -0
  39. package/dist/esm/document.js +3 -0
  40. package/dist/esm/index.js +3 -0
  41. package/dist/esm/mindmap.js +4 -0
  42. package/dist/esm/schema.js +3 -0
  43. package/dist/esm/shared/anchor.js +192 -0
  44. package/dist/esm/shared/callout.js +56 -0
  45. package/dist/esm/shared/code-block.js +25 -0
  46. package/dist/esm/shared/code-languages.js +46 -0
  47. package/dist/esm/shared/doc-compatibility.js +20 -0
  48. package/dist/esm/shared/doc-text.js +48 -0
  49. package/dist/esm/shared/document-colors.js +56 -0
  50. package/dist/esm/shared/inline-comments.js +173 -0
  51. package/dist/esm/shared/mind-map-folding.js +10 -0
  52. package/dist/esm/shared/mind-map-phase2.js +121 -0
  53. package/dist/esm/shared/mind-map.js +427 -0
  54. package/dist/esm/shared/mind-topic-text.js +49 -0
  55. package/dist/esm/shared/node-catalog.js +57 -0
  56. package/dist/esm/shared/presence.js +130 -0
  57. package/dist/esm/shared/safe-href.js +32 -0
  58. package/dist/esm/shared/table-cell-format.js +14 -0
  59. package/dist/esm/shared/table-layout.js +23 -0
  60. package/dist/esm/shared/types.js +1 -0
  61. package/dist/esm/shared/video-embed.js +96 -0
  62. package/dist/esm/shared/workbook.js +144 -0
  63. package/dist/esm/spreadsheet.js +1 -0
  64. package/dist/esm/types.js +1 -0
  65. package/dist/esm/validation/callout.js +1 -0
  66. package/dist/esm/validation/doc-node-whitelist.js +68 -0
  67. package/dist/esm/validation/error.js +13 -0
  68. package/dist/esm/validation/mind-map.js +125 -0
  69. package/dist/esm/validation/schema-version.js +2 -0
  70. package/dist/esm/validation/schema.js +372 -0
  71. package/dist/esm/validation/video-embed.js +1 -0
  72. package/dist/esm/validation/workbook.js +1 -0
  73. package/dist/types/document.d.ts +4 -0
  74. package/dist/types/index.d.ts +4 -0
  75. package/dist/types/mindmap.d.ts +4 -0
  76. package/dist/types/schema.d.ts +4 -0
  77. package/dist/types/shared/anchor.d.ts +87 -0
  78. package/dist/types/shared/callout.d.ts +27 -0
  79. package/dist/types/shared/code-block.d.ts +26 -0
  80. package/dist/types/shared/code-languages.d.ts +26 -0
  81. package/dist/types/shared/doc-compatibility.d.ts +1 -0
  82. package/dist/types/shared/doc-text.d.ts +21 -0
  83. package/dist/types/shared/document-colors.d.ts +106 -0
  84. package/dist/types/shared/inline-comments.d.ts +71 -0
  85. package/dist/types/shared/mind-map-folding.d.ts +5 -0
  86. package/dist/types/shared/mind-map-phase2.d.ts +19 -0
  87. package/dist/types/shared/mind-map.d.ts +134 -0
  88. package/dist/types/shared/mind-topic-text.d.ts +18 -0
  89. package/dist/types/shared/node-catalog.d.ts +30 -0
  90. package/dist/types/shared/presence.d.ts +69 -0
  91. package/dist/types/shared/safe-href.d.ts +3 -0
  92. package/dist/types/shared/table-cell-format.d.ts +24 -0
  93. package/dist/types/shared/table-layout.d.ts +7 -0
  94. package/dist/types/shared/types.d.ts +44 -0
  95. package/dist/types/shared/video-embed.d.ts +11 -0
  96. package/dist/types/shared/workbook.d.ts +56 -0
  97. package/dist/types/spreadsheet.d.ts +1 -0
  98. package/dist/types/types.d.ts +3 -0
  99. package/dist/types/validation/callout.d.ts +1 -0
  100. package/dist/types/validation/doc-node-whitelist.d.ts +21 -0
  101. package/dist/types/validation/error.d.ts +7 -0
  102. package/dist/types/validation/mind-map.d.ts +22 -0
  103. package/dist/types/validation/schema-version.d.ts +2 -0
  104. package/dist/types/validation/schema.d.ts +49 -0
  105. package/dist/types/validation/video-embed.d.ts +1 -0
  106. package/dist/types/validation/workbook.d.ts +1 -0
  107. package/package.json +87 -5
@@ -0,0 +1,26 @@
1
+ /** Presentation metadata only; code, whitespace and the document schema remain unchanged. */
2
+ export declare const CODE_THEMES: readonly [{
3
+ readonly value: "auto";
4
+ readonly label: "跟随页面";
5
+ }, {
6
+ readonly value: "paper";
7
+ readonly label: "纸白";
8
+ }, {
9
+ readonly value: "soft";
10
+ readonly label: "柔和";
11
+ }, {
12
+ readonly value: "night";
13
+ readonly label: "深色";
14
+ }];
15
+ export type CodeTheme = typeof CODE_THEMES[number]['value'];
16
+ export interface CodeBlockDisplay {
17
+ collapsed: boolean;
18
+ lineNumbers: boolean;
19
+ limited: boolean;
20
+ theme: CodeTheme;
21
+ }
22
+ export declare function codeBlockDisplay(): CodeBlockDisplay;
23
+ export declare function rememberCodeTheme(_theme: CodeTheme): void;
24
+ export declare function codeLineCount(text: string): number;
25
+ export declare function codeLineNumbers(text: string): string;
26
+ export declare function codeBlockClass(display: CodeBlockDisplay): string[];
@@ -0,0 +1,26 @@
1
+ /**
2
+ * 代码块语言目录(**纯数据,无编辑器依赖**)。
3
+ *
4
+ * 为什么放 shared:编辑端(语言选择器)与只读端(语言标签)都要用;
5
+ * 若放在 editor/ 下,只读端 import 它就会把编辑器模块拉进阅读路径,
6
+ * 违反读写分离(与 doc-schema-version.ts 的取舍同理)。
7
+ *
8
+ * `value` 即写入正文 `codeBlock.language` 的值,也是 Markdown 围栏的语言标记
9
+ * (```` ```js ````),因此导出/导入可无损往返(服务端序列化器与解析器均已支持)。
10
+ * `label` 仅供界面展示。
11
+ */
12
+ export interface CodeLanguage {
13
+ value: string;
14
+ label: string;
15
+ }
16
+ /** 空字符串表示「纯文本 / 不指定」。 */
17
+ export declare const CODE_LANGUAGES: CodeLanguage[];
18
+ /**
19
+ * 取语言的展示标签。
20
+ *
21
+ * 未知语言(如用户从 Markdown 导入的少见围栏标记)直接显示原值——
22
+ * 不隐藏、不改写,避免"我明明标了语言却看不到"。
23
+ */
24
+ export declare function codeLanguageLabel(value: string | null | undefined): string;
25
+ /** 是否为目录内已知语言(用于选择器回显:未知值时追加一个临时选项)。 */
26
+ export declare function isKnownCodeLanguage(value: string): boolean;
@@ -0,0 +1 @@
1
+ export declare function supportsDocument(raw: unknown, version?: number): boolean;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 从 ProseMirror 文档提取用于锚点定位的块级纯文本。
3
+ *
4
+ * 与 anchor.ts 配合:锚点用「在扁平文本中的字符偏移」表达,
5
+ * 这里负责把文档结构摊平,并保留「块 → 字符区间」的映射,
6
+ * 以便把定位结果映射回具体的块(前端据此在对应块上加高亮)。
7
+ *
8
+ * 与后端 DocSchemaService.extractPlainText 保持一致的遍历顺序(先文本、后子节点),
9
+ * 否则同一段内容在提交锚点与解析锚点时会得到不同的字符偏移。
10
+ */
11
+ import type { ProseMirrorNode } from './types.js';
12
+ export interface BlockText {
13
+ /** 块在文档顶层 content 中的索引 */
14
+ index: number;
15
+ /** 该块的纯文本 */
16
+ text: string;
17
+ }
18
+ /** 提取顶层块(含嵌套块)的纯文本。列表会递归取出每一项。 */
19
+ export declare function extractBlockTexts(doc: ProseMirrorNode | null | undefined): BlockText[];
20
+ /** 取节点的全部文本(含后代)。 */
21
+ export declare function plainTextOf(node: ProseMirrorNode): string;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 文档文字与高亮的受限调色板。存储值保持原始 hex,显示色通过主题变量切换;
3
+ * 阅读路径复用此模块,不引入 Tiptap。历史色值均保留,复制时 data-color 可往返。
4
+ */
5
+ export declare const TEXT_COLORS: readonly [{
6
+ readonly name: "默认";
7
+ readonly value: null;
8
+ readonly token: null;
9
+ }, {
10
+ readonly name: "灰";
11
+ readonly value: "#868e96";
12
+ readonly token: "gray";
13
+ }, {
14
+ readonly name: "红";
15
+ readonly value: "#d92c2c";
16
+ readonly token: "red";
17
+ }, {
18
+ readonly name: "橙";
19
+ readonly value: "#e07b1a";
20
+ readonly token: "orange";
21
+ }, {
22
+ readonly name: "黄褐";
23
+ readonly value: "#9a7b12";
24
+ readonly token: "yellow";
25
+ }, {
26
+ readonly name: "绿";
27
+ readonly value: "#2f9e44";
28
+ readonly token: "green";
29
+ }, {
30
+ readonly name: "蓝";
31
+ readonly value: "#1971c2";
32
+ readonly token: "blue";
33
+ }, {
34
+ readonly name: "紫";
35
+ readonly value: "#7048e8";
36
+ readonly token: "purple";
37
+ }];
38
+ export declare const HIGHLIGHT_COLORS: readonly [{
39
+ readonly name: "无";
40
+ readonly value: null;
41
+ readonly token: null;
42
+ }, {
43
+ readonly name: "浅灰";
44
+ readonly value: "#f1f3f5";
45
+ readonly token: "gray-soft";
46
+ }, {
47
+ readonly name: "浅红";
48
+ readonly value: "#ffe3e3";
49
+ readonly token: "red";
50
+ }, {
51
+ readonly name: "浅橙";
52
+ readonly value: "#fff0db";
53
+ readonly token: "orange";
54
+ }, {
55
+ readonly name: "浅黄";
56
+ readonly value: "#fff3bf";
57
+ readonly token: "yellow";
58
+ }, {
59
+ readonly name: "浅绿";
60
+ readonly value: "#d3f9d8";
61
+ readonly token: "green";
62
+ }, {
63
+ readonly name: "浅蓝";
64
+ readonly value: "#d0ebff";
65
+ readonly token: "blue";
66
+ }, {
67
+ readonly name: "浅紫";
68
+ readonly value: "#e5dbff";
69
+ readonly token: "purple";
70
+ }, {
71
+ readonly name: "灰";
72
+ readonly value: "#dee2e6";
73
+ readonly token: "gray";
74
+ }, {
75
+ readonly name: "深灰";
76
+ readonly value: "#ced4da";
77
+ readonly token: "gray-strong";
78
+ }, {
79
+ readonly name: "红";
80
+ readonly value: "#ffc9c9";
81
+ readonly token: "red-strong";
82
+ }, {
83
+ readonly name: "橙";
84
+ readonly value: "#ffd8a8";
85
+ readonly token: "orange-strong";
86
+ }, {
87
+ readonly name: "黄";
88
+ readonly value: "#ffec99";
89
+ readonly token: "yellow-strong";
90
+ }, {
91
+ readonly name: "绿";
92
+ readonly value: "#b2f2bb";
93
+ readonly token: "green-strong";
94
+ }, {
95
+ readonly name: "蓝";
96
+ readonly value: "#a5d8ff";
97
+ readonly token: "blue-strong";
98
+ }, {
99
+ readonly name: "紫";
100
+ readonly value: "#d0bfff";
101
+ readonly token: "purple-strong";
102
+ }];
103
+ export declare function paletteColor(value: string, palette: readonly {
104
+ value: string | null;
105
+ }[]): string | null;
106
+ export declare function documentColorAttributes(kind: 'text' | 'highlight', value: unknown): Record<string, string>;
@@ -0,0 +1,71 @@
1
+ import type { CommentItem, ProseMirrorNode } from './types.js';
2
+ /**
3
+ * 把行内评论锚点解析为**文本节点内的字符区间**,供渲染器加高亮。
4
+ *
5
+ * ## 为什么不用「块索引」
6
+ *
7
+ * 直觉做法是记录「第几个段落 + 段内偏移」。但文档结构是嵌套的
8
+ * (列表套列表、引用里含段落、表格单元格…),"第几块"这种扁平索引
9
+ * 很难与渲染器的实际遍历对应,映射错一位高亮就会错位。
10
+ *
11
+ * ## 采用方案:文本节点序号 + 节点内偏移
12
+ *
13
+ * 把文档里所有**文本节点**按深度优先顺序编号(第 0 个、第 1 个…),
14
+ * 锚点记录「第几个文本节点 + 节点内起止」。
15
+ *
16
+ * 关键在于:锚点解析与渲染器**使用完全相同的遍历顺序**
17
+ * (都是深度优先、先文本后子节点),因此序号天然一致,不需要维护额外映射。
18
+ * 这个顺序与后端 `DocSchemaService.extractPlainText` 也保持一致。
19
+ *
20
+ * ## 三种状态必须区分显示
21
+ * - `exact`:原文仍在,正常高亮;
22
+ * - `shifted`:原文被改动,仅靠上下文降级定位,零宽落点并提示;
23
+ * - 失联:无法定位,**不给正文加高亮**,只在评论区列出。
24
+ * 宁可显示「有评论已失联」,也不要把评论挂到无关文字上。
25
+ */
26
+ export type InlineMarkState = 'exact' | 'shifted';
27
+ export interface InlineMark {
28
+ commentId: string;
29
+ /** 文本节点序号(深度优先顺序,从 0 开始) */
30
+ textIndex: number;
31
+ /** 该文本节点内的字符区间(含头不含尾);shifted 时 from === to */
32
+ from: number;
33
+ to: number;
34
+ state: InlineMarkState;
35
+ /** 该评论是否有回复(供高亮配色区分) */
36
+ hasReplies: boolean;
37
+ }
38
+ export interface ResolvedInlineComments {
39
+ marks: InlineMark[];
40
+ /** 无法定位的评论(内容已被删除或改动过大) */
41
+ orphanCommentIds: string[];
42
+ /** 按文本节点序号分组,渲染时 O(1) 查询 */
43
+ byTextIndex: Map<number, InlineMark[]>;
44
+ }
45
+ /**
46
+ * 把文档摊平为「每个文本节点一段」,并给出每段在扁平文本中的偏移。
47
+ *
48
+ * **遍历顺序必须与 DocRenderer 一致**(深度优先、先 text 后 content),
49
+ * 否则文本节点序号会错位。二者的一致性由测试覆盖。
50
+ */
51
+ export interface TextRun {
52
+ /** 文本节点序号 */
53
+ index: number;
54
+ text: string;
55
+ /** 在扁平文本中的起始偏移 */
56
+ flatStart: number;
57
+ /** 在扁平文本中的结束偏移(不含) */
58
+ flatEnd: number;
59
+ }
60
+ export declare function buildTextRuns(doc: ProseMirrorNode | null | undefined): TextRun[];
61
+ /** 文档的扁平文本(与 buildTextRuns 的偏移体系一致)。 */
62
+ export declare function buildFlatText(doc: ProseMirrorNode | null | undefined): string;
63
+ /** 仅有锚点的评论才参与定位(页级评论不属于行内)。 */
64
+ export declare function resolveInlineComments(doc: ProseMirrorNode | null | undefined, comments: CommentItem[]): ResolvedInlineComments;
65
+ /** 响应式封装。 */
66
+ export interface TextPiece {
67
+ text: string;
68
+ /** 覆盖该片段的标记(可能多个:不同评论指向重叠文字) */
69
+ marks: InlineMark[];
70
+ }
71
+ export declare function splitTextByMarks(text: string, marks: InlineMark[]): TextPiece[];
@@ -0,0 +1,5 @@
1
+ import { type MindMapModel } from './mind-map.js';
2
+ /** Root/leaf/stale entries have no local fold affordance and must not hide a whole map. */
3
+ export declare function normalizeMindFolds(model: MindMapModel, ids: Iterable<string>): Set<string>;
4
+ /** Keep every first-level branch visible, collapsing only those with descendants. */
5
+ export declare function firstLevelMindFolds(model: MindMapModel): Set<string>;
@@ -0,0 +1,19 @@
1
+ import { type MindMapJson, type MindMapModel } from './mind-map.js';
2
+ export declare const MIND_ICONS: string[];
3
+ export declare const MIND_COLORS: string[];
4
+ export declare const MIND_BACKGROUNDS: string[];
5
+ export declare function topicTitle(node: MindMapJson): string;
6
+ export declare function topicField(node: MindMapJson, field: 'description' | 'note'): string;
7
+ export declare function branchModel(model: MindMapModel, id: string): MindMapModel;
8
+ export declare function ancestorIds(model: MindMapModel, id: string): string[];
9
+ export interface MindSearchHit {
10
+ id: string;
11
+ field: 'title' | 'description' | 'note';
12
+ from: number;
13
+ to: number;
14
+ }
15
+ export declare function searchMindMap(model: MindMapModel, query: string, details?: boolean): MindSearchHit[];
16
+ export declare function parseMindMapImport(raw: string): MindMapModel;
17
+ export declare function mindMapTemplate(kind: 'project' | 'meeting' | 'knowledge'): MindMapModel;
18
+ export declare function importBranch(model: MindMapModel): MindMapJson[];
19
+ export declare function parseMindBranch(raw: string): MindMapJson[];
@@ -0,0 +1,134 @@
1
+ /** Engine-independent document format. Topic text remains ordinary ProseMirror text. */
2
+ export declare const MIND_MAP_VERSION = 3;
3
+ export declare const MIND_MAP_MAX_TOPICS = 500;
4
+ export declare const MIND_MAP_MAX_TEXT = 2000;
5
+ export declare const MIND_MAP_LAYOUTS: readonly ["right", "left", "both", "down"];
6
+ export declare const MIND_MAP_THEMES: readonly ["blue", "teal", "violet", "amber"];
7
+ export type MindMapLayout = typeof MIND_MAP_LAYOUTS[number];
8
+ export type MindMapTheme = typeof MIND_MAP_THEMES[number];
9
+ export interface MindMark {
10
+ type: string;
11
+ attrs?: Record<string, unknown>;
12
+ }
13
+ export interface MindRun {
14
+ text: string;
15
+ marks?: MindMark[];
16
+ }
17
+ export interface MindRelation {
18
+ id: string;
19
+ source: string;
20
+ target: string;
21
+ kind: 'related' | 'depends' | 'reference';
22
+ label: string;
23
+ }
24
+ export interface MindTopic {
25
+ id: string;
26
+ parentId: string;
27
+ order: number;
28
+ text: string;
29
+ runs?: MindRun[];
30
+ description?: string;
31
+ note?: string;
32
+ shape?: 'round' | 'pill' | 'text';
33
+ color?: string;
34
+ background?: string;
35
+ fontSize?: number;
36
+ align?: 'left' | 'center' | 'right';
37
+ icon?: string;
38
+ done?: boolean;
39
+ attachmentId?: string;
40
+ side?: -1 | 1;
41
+ }
42
+ export interface MindMapModel {
43
+ id: string;
44
+ rootId: string;
45
+ layout: MindMapLayout;
46
+ theme: MindMapTheme;
47
+ height: number;
48
+ topics: MindTopic[];
49
+ relations?: MindRelation[];
50
+ branchStyle?: 'curve' | 'straight' | 'step';
51
+ }
52
+ export interface MindMapViewState {
53
+ openId: string | null;
54
+ maps: Record<string, {
55
+ selectedId: string;
56
+ selectedIds?: string[];
57
+ showGrid?: boolean;
58
+ showQuickControls?: boolean;
59
+ panMode?: boolean;
60
+ collapsed: string[];
61
+ viewport: {
62
+ x: number;
63
+ y: number;
64
+ zoom: number;
65
+ };
66
+ }>;
67
+ }
68
+ export interface MindMapJson {
69
+ type?: string;
70
+ attrs?: Record<string, unknown>;
71
+ text?: string;
72
+ marks?: MindMark[];
73
+ content?: MindMapJson[];
74
+ }
75
+ export interface PlacedTopic extends MindTopic {
76
+ x: number;
77
+ y: number;
78
+ width: number;
79
+ height: number;
80
+ lines: string[];
81
+ side: -1 | 1;
82
+ }
83
+ export interface MindMapDrawing {
84
+ nodes: PlacedTopic[];
85
+ edges: Array<{
86
+ id: string;
87
+ source: string;
88
+ target: string;
89
+ path: string;
90
+ }>;
91
+ bounds: {
92
+ x: number;
93
+ y: number;
94
+ width: number;
95
+ height: number;
96
+ };
97
+ }
98
+ export declare const mindMapId: () => `${string}-${string}-${string}-${string}-${string}`;
99
+ export declare function createMindMap(layout?: MindMapLayout): MindMapJson;
100
+ export declare function readMindMap(node: MindMapJson | null | undefined): MindMapModel | null;
101
+ /** Missing parents and concurrent cycles keep every surviving topic, deterministically. */
102
+ export declare function mindMapTree(model: MindMapModel): MindTopic[];
103
+ export declare function mindMapDescendants(model: MindMapModel, id: string): Set<string>;
104
+ export declare function canMoveMindTopic(model: MindMapModel, id: string, parentId: string): boolean;
105
+ /** Only first-level topics own a side; all descendants inherit their branch direction. */
106
+ export declare function mindMapBranchSides(model: MindMapModel): Map<string, -1 | 1>;
107
+ /** Shared by the live drop preview and the single document transaction on release. */
108
+ export declare function projectMindSideMove(model: MindMapModel, id: string, side: -1 | 1, beforeId?: string): MindMapModel | null;
109
+ export declare function textDifference(before: string, after: string): {
110
+ from: number;
111
+ to: number;
112
+ text: string;
113
+ };
114
+ export declare function layoutMindMap(model: MindMapModel, collapsed?: ReadonlySet<string>): MindMapDrawing;
115
+ export declare const MIND_MAP_PALETTE: Record<MindMapTheme, string>;
116
+ export declare function mindMapSvg(model: MindMapModel, options?: {
117
+ transparent?: boolean;
118
+ images?: Record<string, string>;
119
+ }): string;
120
+ export declare function mindMapOutline(model: MindMapModel): Array<MindTopic & {
121
+ depth: number;
122
+ }>;
123
+ export declare function mindMapMarkdown(model: MindMapModel): string;
124
+ export declare function mindRelationPaths(model: MindMapModel, drawing: MindMapDrawing): {
125
+ path: string;
126
+ x: number;
127
+ y: number;
128
+ id: string;
129
+ source: string;
130
+ target: string;
131
+ kind: "related" | "depends" | "reference";
132
+ label: string;
133
+ }[];
134
+ export declare function mindMapToJson(model: MindMapModel, fresh?: boolean): MindMapJson;
@@ -0,0 +1,18 @@
1
+ export declare const MIND_TOPIC_LINE_HEIGHT = 22;
2
+ export declare const mindTopicLineHeight: (fontSize?: number) => number;
3
+ export declare const MIND_TOPIC_PADDING = 34;
4
+ export declare const MIND_TOPIC_FONT = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif";
5
+ export type MindTextMeasure = (text: string) => number;
6
+ declare let factory: ((fontSize: number, weight: number) => MindTextMeasure | null) | undefined;
7
+ export declare function setMindTextMeasureFactory(value: typeof factory): void;
8
+ /** Real browser font metrics; non-DOM exports/tests keep a conservative text-only fallback. */
9
+ export declare function mindTextMeasure(fontSize?: number, weight?: number): MindTextMeasure;
10
+ /** Keep explicit empty lines and emoji clusters; presentation wrapping never edits the source. */
11
+ export declare function wrapMindText(text: string, width: number, measure: MindTextMeasure): string[];
12
+ export declare function mindTopicTextSize(text: string, fontSize?: number, root?: boolean, description?: string, bold?: boolean, minimumWidth?: number): {
13
+ width: number;
14
+ lines: string[];
15
+ descriptionLines: string[];
16
+ titleHeight: number;
17
+ };
18
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 节点目录:编辑器的 nodeView 与只读渲染规则**共用的单一真源**
3
+ * (对齐《Wiki好用技术方案》1.1)。
4
+ *
5
+ * 这里只放纯数据(无 Vue、无 Tiptap 依赖),因此阅读端可以只引它 + Renderer,
6
+ * 而完全不加载编辑器代码;编辑器端再基于同一份目录生成扩展。
7
+ *
8
+ * 新增节点时必须同时更新:
9
+ * 1) 本目录 2) 服务端 doc-schema.service.ts 白名单 3) 提升 schema 版本
10
+ * 否则旧客户端会因「遇到不支持节点」而被服务端拒写(这是预期行为,避免静默丢内容)。
11
+ */
12
+ export type NodeKind = 'block' | 'inline' | 'text' | 'atom';
13
+ export interface NodeSpecMeta {
14
+ type: string;
15
+ kind: NodeKind;
16
+ /** 能否出现在只读正文的顶层容器 */
17
+ topLevel?: boolean;
18
+ /** 是否为原子节点(无 content,如分隔线、图片) */
19
+ atom?: boolean;
20
+ /** 供 UI 展示与 slash 菜单使用 */
21
+ label: string;
22
+ /** 是否仅编辑态可用(如占位) */
23
+ editorOnly?: boolean;
24
+ }
25
+ export declare const NODE_CATALOG: NodeSpecMeta[];
26
+ export declare function nodeMeta(type: string | undefined): NodeSpecMeta | undefined;
27
+ /** 只读渲染遇到未知节点时使用的降级提示(不静默丢弃,向用户明示)。 */
28
+ export declare const UNKNOWN_NODE_LABEL = "\u6B64\u5185\u5BB9\u7531\u66F4\u9AD8\u7248\u672C\u7684\u7F16\u8F91\u5668\u521B\u5EFA\uFF0C\u5F53\u524D\u7248\u672C\u65E0\u6CD5\u663E\u793A";
29
+ /** 标题级别 → HTML 标签 */
30
+ export declare function headingTag(level: unknown): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 协同在线状态(presence)的纯逻辑(可单测,不依赖 DOM / Yjs / 网络)。
3
+ *
4
+ * ## 为什么单独抽出来
5
+ *
6
+ * 在线状态有两个容易做错、且出错后**不报错、只是显示不对**的地方:
7
+ * 1) 颜色分配:同一用户在不同客户端必须得到**相同颜色**(否则"那个蓝色光标是谁"
8
+ * 无法对应),而颜色若只按"连接顺序"分,B 客户端上同一个人的颜色就会变。
9
+ * 因此颜色必须由**用户身份**稳定推导,不能依赖顺序或随机。
10
+ * 2) 对比度:名字气泡上的文字要与背景色对比可读;简单地对 6 个色相取固定亮度,
11
+ * 在浅色模式下会有几个色相偏亮、白字看不清。
12
+ * 这些都能用纯函数锁定,因此放在这里。
13
+ */
14
+ /** 在线用户条目(用于头像/名字展示)。 */
15
+ export interface PresenceUser {
16
+ userId: string;
17
+ displayName: string;
18
+ /** 稳定的身份色(hex) */
19
+ color: string;
20
+ }
21
+ /**
22
+ * 稳定的用户色板。
23
+ *
24
+ * 选取标准不是"好看",而是**所有颜色配白字都达到 WCAG AA(≥4.5:1)**——
25
+ * 这样光标/名字气泡永远是"彩色底 + 白字",视觉一致,也不会出现浅底白字看不清。
26
+ * (初版用了偏亮的绿 `#1a9d5a`(3.49:1)与橙 `#d97706`(3.19:1),配白字或深字
27
+ * 都不到 AA;这里换成更深的一档,由单测锁定对比度。)
28
+ */
29
+ export declare const PRESENCE_PALETTE: readonly ["#2f6feb", "#127a45", "#b45309", "#c026d3", "#dc2626", "#0e7490", "#7c3aed", "#9a4a08"];
30
+ /**
31
+ * 由用户 id 稳定映射到调色板颜色。
32
+ *
33
+ * 用 FNV-1a 哈希而非 simple sum:简单的字符求和会让 `user1`/`user2`
34
+ * 这类相邻 id 落到同一颜色,而实际用户 id 常是顺序生成的。
35
+ */
36
+ export declare function colorForUser(userId: string): string;
37
+ /** FNV-1a 32 位哈希 → 取模。 */
38
+ export declare function hashIndex(input: string, buckets: number): number;
39
+ /** 名字首字(用于头像占位)。中文取首字,英文取首字母大写。 */
40
+ export declare function initialOf(displayName: string): string;
41
+ /**
42
+ * 汇总在线用户:去重(同一用户多标签页只显示一次)、排除自己、稳定排序。
43
+ *
44
+ * 稳定性很重要:若按 Map 遍历顺序输出,光标/头像列表会随消息到达顺序抖动。
45
+ * 这里按 displayName 排序,保证界面上顺序稳定。
46
+ */
47
+ export declare function collectPresence(states: Array<{
48
+ userId: string;
49
+ displayName: string;
50
+ }>, selfUserId: string | null): PresenceUser[];
51
+ /**
52
+ * 相对亮度(WCAG),用于决定光标/气泡上该用白字还是深字。
53
+ * 返回 0(黑)~1(白)。
54
+ */
55
+ export declare function relativeLuminance(hex: string): number;
56
+ /** 前景深色的基准(与 :root 的 --color-text 接近)。 */
57
+ export declare const DARK_TEXT = "#1f2937";
58
+ /** WCAG 对比度。 */
59
+ export declare function contrastRatio(a: string, b: string): number;
60
+ /**
61
+ * 该背景色上应使用的前景色。
62
+ *
63
+ * 实现为「比较白字与深字各自的对比度,取更高的那个」,而不是用一个亮度阈值判断——
64
+ * 阈值是脆弱的:初版用 0.179 作分界,`#2f6feb`(亮度 0.180)被判为用深字,
65
+ * 但实际白字对比度 4.57 优于深字 3.21,选错了。(由单测发现。)
66
+ */
67
+ export declare function readableTextColor(hex: string): '#ffffff' | typeof DARK_TEXT;
68
+ /** 解析 #rgb / #rrggbb;非法返回 null。 */
69
+ export declare function parseHex(hex: string): [number, number, number] | null;
@@ -0,0 +1,3 @@
1
+ export declare function isSafeHref(raw: unknown): boolean;
2
+ /** 渲染用:安全则原样返回,否则返回 '#'。 */
3
+ export declare function safeHref(raw: unknown): string;
@@ -0,0 +1,24 @@
1
+ /** 固定单元格底色,不接受任意 CSS;读写端共用,服务端同步白名单。 */
2
+ export declare const CELL_BACKGROUNDS: readonly [{
3
+ readonly name: "默认";
4
+ readonly value: null;
5
+ }, {
6
+ readonly name: "灰";
7
+ readonly value: "#f1f3f5";
8
+ }, {
9
+ readonly name: "黄";
10
+ readonly value: "#fff3bf";
11
+ }, {
12
+ readonly name: "绿";
13
+ readonly value: "#d3f9d8";
14
+ }, {
15
+ readonly name: "蓝";
16
+ readonly value: "#d0ebff";
17
+ }, {
18
+ readonly name: "紫";
19
+ readonly value: "#e5dbff";
20
+ }, {
21
+ readonly name: "红";
22
+ readonly value: "#ffe3e3";
23
+ }];
24
+ export declare function cellBackground(value: unknown): string | null;
@@ -0,0 +1,7 @@
1
+ import type { ProseMirrorNode } from './types.js';
2
+ /** 自动列宽保留可读空间;窄屏在表格内部滚动,不把长文本压成单字列。 */
3
+ export declare const TABLE_CELL_MIN_WIDTH = 120;
4
+ /** 首行合并单元格的 colwidth 数组包含多个视觉列,必须逐列展开。 */
5
+ export declare function tableColumnWidths(table: ProseMirrorNode): number[];
6
+ /** 未设置宽度的列与编辑器 cellMinWidth 共用同一默认值;保留历史显式列宽。 */
7
+ export declare function tableMinWidth(widths: number[]): number | undefined;
@@ -0,0 +1,44 @@
1
+ export interface ProseMirrorNode {
2
+ type?: string;
3
+ attrs?: Record<string, unknown>;
4
+ content?: ProseMirrorNode[];
5
+ marks?: Array<{
6
+ type: string;
7
+ attrs?: Record<string, unknown>;
8
+ }>;
9
+ text?: string;
10
+ }
11
+ export interface ProseMirrorDoc {
12
+ type: 'doc';
13
+ content?: ProseMirrorNode[];
14
+ }
15
+ export interface CommentAnchor {
16
+ quote: string;
17
+ prefix: string | null;
18
+ suffix: string | null;
19
+ start: number | null;
20
+ /** 锚点创建时的已发布版本号 */
21
+ seq: number | null;
22
+ }
23
+ export interface CommentItem {
24
+ id: string;
25
+ pageId: string;
26
+ parentId: string | null;
27
+ body: string;
28
+ /** 行内评论的锚点;页级评论为 null */
29
+ anchor?: CommentAnchor | null;
30
+ authorId: string | null;
31
+ authorName: string;
32
+ deleted: boolean;
33
+ canDelete: boolean;
34
+ canEdit?: boolean;
35
+ /** 讨论是否已解决(仅顶层评论有意义) */
36
+ resolved: boolean;
37
+ resolvedByName: string | null;
38
+ resolvedAt: string | null;
39
+ /** 当前用户是否可标记/取消解决 */
40
+ canResolve: boolean;
41
+ createdAt: string;
42
+ updatedAt: string;
43
+ replies: CommentItem[];
44
+ }
@@ -0,0 +1,11 @@
1
+ /** 纯数据协议;服务端同名文件必须保持一致,由契约测试检查。 */
2
+ export interface VideoEmbedData {
3
+ provider: 'youtube' | 'bilibili';
4
+ videoId: string;
5
+ page: number;
6
+ startSeconds: number;
7
+ }
8
+ export declare function normalizeVideoEmbed(raw: unknown): VideoEmbedData | null;
9
+ export declare function parseVideoUrl(value: string): VideoEmbedData | null;
10
+ export declare function videoSourceUrl(data: VideoEmbedData): string;
11
+ export declare function videoPlayerUrl(data: VideoEmbedData): string;