@ctzhian/tiptap 3.0.2 → 3.0.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.
package/README.md CHANGED
@@ -117,6 +117,10 @@ const { editor } = useTiptap({
117
117
  以只读方式展示两段 HTML 的结构化差异。
118
118
 
119
119
  - Props:`{ oldHtml: string; newHtml: string }`
120
+ - 可选增强:支持 `diffOptions`,例如通过 `ignoreAttrs` 忽略节点或 mark 上的指定属性差异,或通过 `engine: 'enhanced'` 为特殊 inline block 和重排场景启用更细粒度的 diff;未传时默认行为保持不变。
121
+ - 兼容性:现有接入方式无需调整,`v3.x` 内默认仍保持零配置升级。
122
+ - 当前定位:只读结构化 diff 展示,不等同于修订模式或 track changes。
123
+ - 本轮优化:自动清理“无差异时残留高亮”,并增强图片、附件、公式、音视频等删除节点的预览文案。
120
124
 
121
125
  ```tsx
122
126
  <EditorDiff oldHtml={'<p>old</p>'} newHtml={'<p><strong>new</strong></p>'} />
@@ -1,9 +1,11 @@
1
1
  import React from "react";
2
+ import type { StructuredDiffOptions } from "../util/structuredDiff";
2
3
  import 'core-js/actual/array/find-last';
3
4
  interface EditorDiffProps {
4
5
  oldHtml: string;
5
6
  newHtml: string;
6
7
  baseUrl?: string;
8
+ diffOptions?: Partial<StructuredDiffOptions>;
7
9
  }
8
- declare const EditorDiff: ({ oldHtml, newHtml, baseUrl }: EditorDiffProps) => React.JSX.Element;
10
+ declare const EditorDiff: ({ oldHtml, newHtml, baseUrl, diffOptions }: EditorDiffProps) => React.JSX.Element;
9
11
  export default EditorDiff;
@@ -9,11 +9,13 @@ import useTiptap from "../hook";
9
9
  var EditorDiff = function EditorDiff(_ref) {
10
10
  var oldHtml = _ref.oldHtml,
11
11
  newHtml = _ref.newHtml,
12
- baseUrl = _ref.baseUrl;
12
+ baseUrl = _ref.baseUrl,
13
+ diffOptions = _ref.diffOptions;
13
14
  var editorRef = useTiptap({
14
15
  editable: false,
15
16
  content: newHtml,
16
17
  baseUrl: baseUrl,
18
+ structuredDiffOptions: diffOptions,
17
19
  exclude: ['youtube', 'mention']
18
20
  });
19
21
  useEffect(function () {
@@ -7,6 +7,6 @@ export interface UploadProgressAttributes {
7
7
  tempId: string;
8
8
  }
9
9
  export declare const getFileIcon: (fileType: string) => React.JSX.Element;
10
- export declare const getFileTypeText: (fileType: string) => "视频" | "音频" | "图片" | "文件";
10
+ export declare const getFileTypeText: (fileType: string) => "图片" | "视频" | "音频" | "文件";
11
11
  declare const UploadProgressView: React.FC<NodeViewProps>;
12
12
  export default UploadProgressView;
@@ -1,5 +1,7 @@
1
- import { Editor, Extension } from '@tiptap/core';
1
+ import { Extension } from '@tiptap/core';
2
+ import type { Editor } from '@tiptap/core';
2
3
  import { PluginKey } from '@tiptap/pm/state';
4
+ import type { StructuredDiffOptions } from '../../util/structuredDiff';
3
5
  declare const diffPluginKey: PluginKey<any>;
4
6
  declare module '@tiptap/core' {
5
7
  interface Commands<ReturnType> {
@@ -9,7 +11,7 @@ declare module '@tiptap/core' {
9
11
  };
10
12
  }
11
13
  }
12
- export declare const StructuredDiffExtension: Extension<any, any>;
14
+ export declare const StructuredDiffExtension: Extension<StructuredDiffOptions, any>;
13
15
  export { diffPluginKey };
14
16
  export declare function getDiffState(editor: Editor): {
15
17
  isActive: any;
@@ -10,6 +10,11 @@ import { Plugin, PluginKey } from '@tiptap/pm/state';
10
10
  import { createDecorationsFromDiffs, createEmptyDecorationSet } from "../../util/decorations";
11
11
  import { compareDocuments } from "../../util/structuredDiff";
12
12
  var diffPluginKey = new PluginKey('structuredDiff');
13
+ function createHideDiffTransaction(tr) {
14
+ return tr.setMeta(diffPluginKey, {
15
+ type: 'hideDiff'
16
+ });
17
+ }
13
18
  var DiffPluginState = /*#__PURE__*/function () {
14
19
  function DiffPluginState(decorations) {
15
20
  var diffs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
@@ -52,6 +57,13 @@ var DiffPluginState = /*#__PURE__*/function () {
52
57
  }();
53
58
  export var StructuredDiffExtension = Extension.create({
54
59
  name: 'structuredDiff',
60
+ addOptions: function addOptions() {
61
+ return {
62
+ engine: 'legacy',
63
+ ignoreAttrs: [],
64
+ nodePreviewSerializer: undefined
65
+ };
66
+ },
55
67
  addProseMirrorPlugins: function addProseMirrorPlugins() {
56
68
  return [new Plugin({
57
69
  key: diffPluginKey,
@@ -68,6 +80,7 @@ export var StructuredDiffExtension = Extension.create({
68
80
  })];
69
81
  },
70
82
  addCommands: function addCommands() {
83
+ var diffOptions = this.options;
71
84
  return {
72
85
  showStructuredDiff: function showStructuredDiff(oldHtml, newHtml) {
73
86
  return function (_ref) {
@@ -77,11 +90,14 @@ export var StructuredDiffExtension = Extension.create({
77
90
  editor = _ref.editor;
78
91
  try {
79
92
  var extensions = editor.extensionManager.extensions;
80
- var comparison = compareDocuments(oldHtml, newHtml, extensions);
93
+ var comparison = compareDocuments(oldHtml, newHtml, extensions, diffOptions);
81
94
  if (!comparison.hasChanges) {
95
+ if (dispatch) {
96
+ dispatch(createHideDiffTransaction(tr));
97
+ }
82
98
  return false;
83
99
  }
84
- var decorations = createDecorationsFromDiffs(comparison.diffs, state.doc);
100
+ var decorations = createDecorationsFromDiffs(comparison.diffs, state.doc, diffOptions);
85
101
  var newTr = tr.setMeta(diffPluginKey, {
86
102
  type: 'showDiff',
87
103
  decorations: decorations,
@@ -100,12 +116,9 @@ export var StructuredDiffExtension = Extension.create({
100
116
  hideStructuredDiff: function hideStructuredDiff() {
101
117
  return function (_ref2) {
102
118
  var tr = _ref2.tr,
103
- state = _ref2.state,
104
119
  dispatch = _ref2.dispatch;
105
120
  try {
106
- var newTr = tr.setMeta(diffPluginKey, {
107
- type: 'hideDiff'
108
- });
121
+ var newTr = createHideDiffTransaction(tr);
109
122
  if (dispatch) dispatch(newTr);
110
123
  return true;
111
124
  } catch (error) {
@@ -1,2 +1,2 @@
1
1
  import { GetExtensionsProps } from '../type';
2
- export declare const getExtensions: ({ limit, exclude, extensions: extensionsProps, editable, mentionItems, baseUrl, onMentionFilter, onUpload, onUploadImgUrl, onError, onTocUpdate, onAiWritingGetSuggestion, onValidateUrl, placeholder, mermaidOptions, youtubeOptions, tableOfContentsOptions, }: GetExtensionsProps) => any;
2
+ export declare const getExtensions: ({ limit, exclude, extensions: extensionsProps, editable, mentionItems, baseUrl, onMentionFilter, onUpload, onUploadImgUrl, onError, onTocUpdate, onAiWritingGetSuggestion, onValidateUrl, placeholder, mermaidOptions, youtubeOptions, tableOfContentsOptions, structuredDiffOptions, }: GetExtensionsProps) => any;
@@ -34,7 +34,8 @@ export var getExtensions = function getExtensions(_ref) {
34
34
  _placeholder = _ref.placeholder,
35
35
  mermaidOptions = _ref.mermaidOptions,
36
36
  youtubeOptions = _ref.youtubeOptions,
37
- tableOfContentsOptions = _ref.tableOfContentsOptions;
37
+ tableOfContentsOptions = _ref.tableOfContentsOptions,
38
+ structuredDiffOptions = _ref.structuredDiffOptions;
38
39
  var defaultExtensions = [ImeComposition, StarterKit.configure({
39
40
  link: false,
40
41
  code: false,
@@ -170,7 +171,7 @@ export var getExtensions = function getExtensions(_ref) {
170
171
  } else {
171
172
  // 只读模式
172
173
  if (!(exclude !== null && exclude !== void 0 && exclude.includes('structuredDiff'))) {
173
- defaultExtensions.push(StructuredDiffExtension);
174
+ defaultExtensions.push(StructuredDiffExtension.configure(structuredDiffOptions || {}));
174
175
  }
175
176
  }
176
177
  if (!(exclude !== null && exclude !== void 0 && exclude.includes('mention')) && (mentionItems && mentionItems.length > 0 || onMentionFilter)) {
@@ -5,7 +5,9 @@ import { TableOfContentsOptions } from '@tiptap/extension-table-of-contents';
5
5
  import { YoutubeOptions } from '@tiptap/extension-youtube';
6
6
  import { UseEditorOptions } from '@tiptap/react';
7
7
  import { MermaidConfig } from 'mermaid';
8
+ import type { StructuredDiffOptions } from '../util/structuredDiff';
8
9
  export type { Editor } from '@tiptap/react';
10
+ export type { StructuredDiffEngine, StructuredDiffOptions } from '../util/structuredDiff';
9
11
  export interface MenuItem {
10
12
  label?: React.ReactNode;
11
13
  customLabel?: React.ReactNode;
@@ -118,6 +120,7 @@ export type NodeOrMetaOrSuggestionOrExtensionOptions = {
118
120
  mermaidOptions?: Partial<MermaidConfig>;
119
121
  youtubeOptions?: Partial<YoutubeOptions>;
120
122
  tableOfContentsOptions?: Partial<TableOfContentsOptions>;
123
+ structuredDiffOptions?: Partial<StructuredDiffOptions>;
121
124
  };
122
125
  export type BaseExtensionOptions = {
123
126
  /**
@@ -1,13 +1,14 @@
1
1
  import { Node as PMNode } from '@tiptap/pm/model';
2
2
  import { DecorationSet } from '@tiptap/pm/view';
3
- import { DiffItem, ProseMirrorNode } from './structuredDiff';
3
+ import type { DiffItem, ProseMirrorNode, StructuredDiffOptions } from './structuredDiff';
4
+ export declare function getDeletedNodePreviewText(node: ProseMirrorNode, options?: StructuredDiffOptions): string;
4
5
  /**
5
6
  * 根据差异数组创建装饰集合
6
7
  * @param {Array} diffs - 差异数组
7
8
  * @param {Object} doc - ProseMirror文档
8
9
  * @returns {DecorationSet} 装饰集合
9
10
  */
10
- export declare function createDecorationsFromDiffs(diffs: DiffItem[], doc: PMNode): DecorationSet;
11
+ export declare function createDecorationsFromDiffs(diffs: DiffItem[], doc: PMNode, options?: StructuredDiffOptions): DecorationSet;
11
12
  /**
12
13
  * 创建空的装饰集合
13
14
  * @param {Object} doc - ProseMirror文档
@@ -1,9 +1,10 @@
1
1
  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
2
2
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
3
3
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
4
+ /* eslint-disable @typescript-eslint/no-use-before-define */
5
+
4
6
  import { Decoration, DecorationSet } from '@tiptap/pm/view';
5
7
  import { pathToPos } from "./structuredDiff";
6
-
7
8
  /**
8
9
  * 创建插入内容的装饰
9
10
  * @param {number} from - 开始位置
@@ -36,13 +37,13 @@ function createModifyDecoration(from, to) {
36
37
  * @param {Object} deletedNode - 被删除的节点
37
38
  * @returns {Decoration} widget装饰对象
38
39
  */
39
- function createDeleteWidget(pos, deletedNode) {
40
+ function createDeleteWidget(pos, deletedNode, options) {
40
41
  var widget = document.createElement('span');
41
42
  widget.className = 'diff-delete-widget';
42
43
  widget.style.cssText = "\n background-color: rgba(220, 53, 69, 0.1);\n color: #dc3545;\n text-decoration: line-through;\n padding: 2px 4px;\n border-radius: 3px;\n margin: 0 1px;\n border: 1px dashed #dc3545;\n font-style: italic;\n ";
43
44
 
44
45
  // 提取删除内容的文本
45
- var deletedText = extractTextFromNode(deletedNode);
46
+ var deletedText = getDeletedNodePreviewText(deletedNode, options);
46
47
  widget.textContent = deletedText || '[已删除]';
47
48
 
48
49
  // 添加工具提示
@@ -51,6 +52,43 @@ function createDeleteWidget(pos, deletedNode) {
51
52
  side: 1
52
53
  });
53
54
  }
55
+ function normalizePreviewText(value) {
56
+ if (typeof value === 'string') {
57
+ return value.replace(/\s+/g, ' ').trim();
58
+ }
59
+ if (typeof value === 'number') {
60
+ return String(value);
61
+ }
62
+ return '';
63
+ }
64
+ function truncatePreviewText(text) {
65
+ var maxLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 60;
66
+ if (text.length <= maxLength) {
67
+ return text;
68
+ }
69
+ return "".concat(text.slice(0, maxLength - 1), "...");
70
+ }
71
+ function getFileNameFromUrl(url) {
72
+ var normalizedUrl = normalizePreviewText(url);
73
+ if (!normalizedUrl) {
74
+ return '';
75
+ }
76
+ try {
77
+ var parsedUrl = new URL(normalizedUrl, 'https://placeholder.local');
78
+ var segments = parsedUrl.pathname.split('/').filter(Boolean);
79
+ return decodeURIComponent(segments[segments.length - 1] || '');
80
+ } catch (_unused) {
81
+ var _segments = normalizedUrl.split('/').filter(Boolean);
82
+ return _segments[_segments.length - 1] || normalizedUrl;
83
+ }
84
+ }
85
+ function formatDeletedNodeLabel(typeLabel, detail) {
86
+ var normalizedDetail = normalizePreviewText(detail);
87
+ if (!normalizedDetail) {
88
+ return "[\u5DF2\u5220\u9664".concat(typeLabel, "]");
89
+ }
90
+ return "[\u5DF2\u5220\u9664".concat(typeLabel, ": ").concat(truncatePreviewText(normalizedDetail), "]");
91
+ }
54
92
 
55
93
  /**
56
94
  * 从节点中提取文本内容
@@ -69,6 +107,59 @@ function extractTextFromNode(node) {
69
107
  }
70
108
  return '';
71
109
  }
110
+ export function getDeletedNodePreviewText(node, options) {
111
+ var _options$nodePreviewS;
112
+ if (!node) {
113
+ return '';
114
+ }
115
+ var customPreview = normalizePreviewText(options === null || options === void 0 || (_options$nodePreviewS = options.nodePreviewSerializer) === null || _options$nodePreviewS === void 0 ? void 0 : _options$nodePreviewS.call(options, node));
116
+ if (customPreview) {
117
+ return truncatePreviewText(customPreview);
118
+ }
119
+ var textContent = truncatePreviewText(normalizePreviewText(extractTextFromNode(node)));
120
+ if (textContent) {
121
+ return textContent;
122
+ }
123
+ var attrs = node.attrs || {};
124
+ var nodeType = node.type || 'content';
125
+ var namedDetail = function namedDetail(value, fallback) {
126
+ var detail = normalizePreviewText(value) || normalizePreviewText(fallback);
127
+ return detail || '';
128
+ };
129
+ switch (nodeType) {
130
+ case 'image':
131
+ return formatDeletedNodeLabel('图片', namedDetail(attrs.alt, attrs.src && getFileNameFromUrl(attrs.src)));
132
+ case 'inlineAttachment':
133
+ case 'blockAttachment':
134
+ return formatDeletedNodeLabel('附件', namedDetail(attrs.title, attrs.url && getFileNameFromUrl(attrs.url)));
135
+ case 'video':
136
+ return formatDeletedNodeLabel('视频', namedDetail(attrs.title, attrs.src && getFileNameFromUrl(attrs.src)));
137
+ case 'audio':
138
+ return formatDeletedNodeLabel('音频', namedDetail(attrs.title, attrs.src && getFileNameFromUrl(attrs.src)));
139
+ case 'iframe':
140
+ return formatDeletedNodeLabel('嵌入内容', namedDetail(attrs.title, attrs.src));
141
+ case 'inlineMath':
142
+ return formatDeletedNodeLabel('行内公式', attrs.latex);
143
+ case 'blockMath':
144
+ return formatDeletedNodeLabel('公式', attrs.latex);
145
+ case 'inlineLink':
146
+ case 'blockLink':
147
+ return formatDeletedNodeLabel('链接', namedDetail(attrs.title, attrs.href));
148
+ case 'details':
149
+ return formatDeletedNodeLabel('折叠块');
150
+ case 'detailsSummary':
151
+ return formatDeletedNodeLabel('折叠标题');
152
+ case 'detailsContent':
153
+ return formatDeletedNodeLabel('折叠内容');
154
+ case 'table':
155
+ return formatDeletedNodeLabel('表格');
156
+ case 'codeBlock':
157
+ case 'code_block':
158
+ return formatDeletedNodeLabel('代码块', attrs.language || attrs.lang);
159
+ default:
160
+ return formatDeletedNodeLabel('内容');
161
+ }
162
+ }
72
163
 
73
164
  /**
74
165
  * 根据差异数组创建装饰集合
@@ -76,7 +167,7 @@ function extractTextFromNode(node) {
76
167
  * @param {Object} doc - ProseMirror文档
77
168
  * @returns {DecorationSet} 装饰集合
78
169
  */
79
- export function createDecorationsFromDiffs(diffs, doc) {
170
+ export function createDecorationsFromDiffs(diffs, doc, options) {
80
171
  var decorations = [];
81
172
  diffs.forEach(function (diff) {
82
173
  try {
@@ -126,10 +217,10 @@ export function createDecorationsFromDiffs(diffs, doc) {
126
217
  decorations.push(createDeleteWidget(widgetPos, {
127
218
  type: 'text',
128
219
  text: diff.textDiff.text
129
- }));
220
+ }, options));
130
221
  } else if (diff.node) {
131
222
  // 节点级别的删除 - 使用widget显示
132
- decorations.push(createDeleteWidget(pos, diff.node));
223
+ decorations.push(createDeleteWidget(pos, diff.node, options));
133
224
  }
134
225
  break;
135
226
  case 'modify':
@@ -172,50 +263,23 @@ export function createDecorationsFromDiffs(diffs, doc) {
172
263
  });
173
264
  return DecorationSet.create(doc, decorations);
174
265
  }
175
-
176
- /**
177
- * 获取指定路径节点的大小
178
- * @param {Array} path - 节点路径
179
- * @param {Object} doc - ProseMirror文档
180
- * @returns {number} 节点大小
181
- */
182
- function getNodeSizeAtPath(path, doc) {
183
- var currentNode = doc;
266
+ function getPMNodeAtPath(path, doc) {
267
+ var current = doc;
184
268
  var _iterator = _createForOfIteratorHelper(path),
185
269
  _step;
186
270
  try {
187
271
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
188
- var index = _step.value;
189
- if (currentNode.content && currentNode.content[index]) {
190
- currentNode = currentNode.content[index];
191
- } else {
192
- return 1; // 默认大小
193
- }
194
- }
195
- } catch (err) {
196
- _iterator.e(err);
197
- } finally {
198
- _iterator.f();
199
- }
200
- return getNodeSize(currentNode);
201
- }
202
- function getPMNodeAtPath(path, doc) {
203
- var current = doc;
204
- var _iterator2 = _createForOfIteratorHelper(path),
205
- _step2;
206
- try {
207
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
208
272
  var _childCount;
209
- var index = _step2.value;
273
+ var index = _step.value;
210
274
  if (!current) return null;
211
275
  var childCount = (_childCount = current.childCount) !== null && _childCount !== void 0 ? _childCount : 0;
212
276
  if (index >= childCount) return null;
213
277
  current = current.child(index);
214
278
  }
215
279
  } catch (err) {
216
- _iterator2.e(err);
280
+ _iterator.e(err);
217
281
  } finally {
218
- _iterator2.f();
282
+ _iterator.f();
219
283
  }
220
284
  return current;
221
285
  }
@@ -285,17 +349,17 @@ export function getNodeSize(node) {
285
349
  }
286
350
  var size = 2; // 开始和结束标记
287
351
  if (node.content && Array.isArray(node.content)) {
288
- var _iterator3 = _createForOfIteratorHelper(node.content),
289
- _step3;
352
+ var _iterator2 = _createForOfIteratorHelper(node.content),
353
+ _step2;
290
354
  try {
291
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
292
- var child = _step3.value;
355
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
356
+ var child = _step2.value;
293
357
  size += getNodeSize(child);
294
358
  }
295
359
  } catch (err) {
296
- _iterator3.e(err);
360
+ _iterator2.e(err);
297
361
  } finally {
298
- _iterator3.f();
362
+ _iterator2.f();
299
363
  }
300
364
  }
301
365
  return size;
@@ -1,4 +1,4 @@
1
- import { Extensions } from '@tiptap/core';
1
+ import type { Extensions } from '@tiptap/core';
2
2
  export interface TextDiff {
3
3
  offset: number;
4
4
  length: number;
@@ -35,6 +35,12 @@ export interface ProseMirrorNode {
35
35
  attrs?: Record<string, any>;
36
36
  }>;
37
37
  }
38
+ export type StructuredDiffEngine = 'legacy' | 'enhanced';
39
+ export interface StructuredDiffOptions {
40
+ engine?: StructuredDiffEngine;
41
+ ignoreAttrs?: string[];
42
+ nodePreviewSerializer?: (node: ProseMirrorNode) => string | null | undefined;
43
+ }
38
44
  /**
39
45
  * 将HTML转换为ProseMirror文档结构
40
46
  * @param {string} html - HTML字符串
@@ -49,7 +55,7 @@ export declare function parseHtmlToDoc(html: string, extensions: Extensions): an
49
55
  * @param {Array} extensions - Tiptap扩展数组
50
56
  * @returns {Object} 包含差异信息的对象
51
57
  */
52
- export declare function compareDocuments(oldHtml: string, newHtml: string, extensions: Extensions): DocumentComparison;
58
+ export declare function compareDocuments(oldHtml: string, newHtml: string, extensions: Extensions, options?: StructuredDiffOptions): DocumentComparison;
53
59
  /**
54
60
  * 将路径转换为ProseMirror位置
55
61
  * @param {Array} path - 节点路径
@@ -1,14 +1,16 @@
1
- function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
- function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
- function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
4
- function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
5
1
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
6
2
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
7
3
  function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
8
4
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
5
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
6
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
7
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
8
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
9
9
  function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
10
10
  function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
11
11
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
12
+ /* eslint-disable @typescript-eslint/no-use-before-define */
13
+
12
14
  import { generateJSON } from '@tiptap/html';
13
15
  import DiffMatchPatch from 'diff-match-patch';
14
16
 
@@ -17,6 +19,10 @@ var dmp = new DiffMatchPatch();
17
19
 
18
20
  // 类型定义
19
21
 
22
+ var LEGACY_INLINE_CONTAINER_TYPES = new Set(['paragraph', 'heading']);
23
+ var NON_INLINE_LEAF_CONTAINER_TYPES = new Set(['doc', 'table', 'tableRow', 'table_row', 'tableCell', 'table_cell', 'tableHeader', 'table_header', 'orderedList', 'ordered_list', 'bulletList', 'bullet_list', 'taskList', 'task_list', 'taskItem', 'task_item', 'listItem', 'list_item', 'blockquote', 'details', 'detailsContent', 'details_content', 'codeBlock', 'code_block', 'alert', 'horizontalRule', 'horizontal_rule', 'flow', 'flipGrid', 'flip_grid', 'flipGridColumn', 'flip_grid_column', 'yamlFrontmatter', 'yaml_frontmatter']);
24
+ var BLOCKISH_CHILD_NODE_TYPES = new Set(['image', 'video', 'audio', 'iframe', 'flow', 'blockAttachment', 'blockLink', 'blockMath', 'table', 'horizontalRule', 'horizontal_rule', 'codeBlock', 'code_block', 'details', 'detailsContent', 'details_content', 'orderedList', 'ordered_list', 'bulletList', 'bullet_list', 'taskList', 'task_list', 'taskItem', 'task_item', 'listItem', 'list_item', 'blockquote', 'alert', 'flipGrid', 'flip_grid', 'flipGridColumn', 'flip_grid_column', 'yamlFrontmatter', 'yaml_frontmatter']);
25
+
20
26
  /**
21
27
  * 将HTML转换为ProseMirror文档结构
22
28
  * @param {string} html - HTML字符串
@@ -26,20 +32,25 @@ var dmp = new DiffMatchPatch();
26
32
  export function parseHtmlToDoc(html, extensions) {
27
33
  return generateJSON(html, extensions);
28
34
  }
29
- function haveSameMarks(a, b) {
35
+ function serializeMark(mark, options) {
36
+ return JSON.stringify({
37
+ type: mark.type,
38
+ attrs: getFilteredAttrs(mark.attrs, options)
39
+ });
40
+ }
41
+ function haveSameMarks(a, b, options) {
30
42
  var arrA = a || [];
31
43
  var arrB = b || [];
32
44
  if (arrA.length !== arrB.length) return false;
33
- var serialize = function serialize(m) {
34
- return "".concat(m.type, ":").concat(JSON.stringify(m.attrs || {}));
35
- };
36
- var setA = new Set(arrA.map(serialize));
45
+ var setA = new Set(arrA.map(function (mark) {
46
+ return serializeMark(mark, options);
47
+ }));
37
48
  var _iterator = _createForOfIteratorHelper(arrB),
38
49
  _step;
39
50
  try {
40
51
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
41
52
  var m = _step.value;
42
- if (!setA.has(serialize(m))) return false;
53
+ if (!setA.has(serializeMark(m, options))) return false;
43
54
  }
44
55
  } catch (err) {
45
56
  _iterator.e(err);
@@ -56,8 +67,88 @@ function haveSameMarks(a, b) {
56
67
  * @param {Array} path - 当前节点路径
57
68
  * @returns {Array} 差异数组
58
69
  */
70
+ function isIgnoredAttr(key, options) {
71
+ var _options$ignoreAttrs;
72
+ return !!(options !== null && options !== void 0 && (_options$ignoreAttrs = options.ignoreAttrs) !== null && _options$ignoreAttrs !== void 0 && _options$ignoreAttrs.includes(key));
73
+ }
74
+ function getFilteredAttrs(attrs, options) {
75
+ var _options$ignoreAttrs2;
76
+ var source = attrs || {};
77
+ if (!(options !== null && options !== void 0 && (_options$ignoreAttrs2 = options.ignoreAttrs) !== null && _options$ignoreAttrs2 !== void 0 && _options$ignoreAttrs2.length)) {
78
+ return source;
79
+ }
80
+ return Object.fromEntries(Object.entries(source).filter(function (_ref) {
81
+ var _ref2 = _slicedToArray(_ref, 1),
82
+ key = _ref2[0];
83
+ return !isIgnoredAttr(key, options);
84
+ }));
85
+ }
86
+ function areAttrsEqual(attrsA, attrsB, options) {
87
+ return JSON.stringify(getFilteredAttrs(attrsA, options)) === JSON.stringify(getFilteredAttrs(attrsB, options));
88
+ }
89
+ function getStructuredDiffEngine(options) {
90
+ var _options$engine;
91
+ return (_options$engine = options === null || options === void 0 ? void 0 : options.engine) !== null && _options$engine !== void 0 ? _options$engine : 'legacy';
92
+ }
93
+ function getNodeSignatureForAlign(node, options) {
94
+ if (!node) {
95
+ return '';
96
+ }
97
+ if (node.type === 'text') {
98
+ return JSON.stringify({
99
+ type: node.type,
100
+ text: node.text || '',
101
+ marks: (node.marks || []).map(function (mark) {
102
+ return serializeMark(mark, options);
103
+ })
104
+ });
105
+ }
106
+ return JSON.stringify({
107
+ type: node.type,
108
+ attrs: getFilteredAttrs(node.attrs, options),
109
+ content: (node.content || []).map(function (child) {
110
+ return getNodeSignatureForAlign(child, options);
111
+ })
112
+ });
113
+ }
114
+ function createAlignPredicate(a, b, getNode, options) {
115
+ if (getStructuredDiffEngine(options) !== 'enhanced') {
116
+ return function (x, y) {
117
+ return nodesEqualForAlign(getNode(x), getNode(y), options);
118
+ };
119
+ }
120
+ var countSignatures = function countSignatures(items) {
121
+ var counts = new Map();
122
+ items.forEach(function (item) {
123
+ var signature = getNodeSignatureForAlign(getNode(item), options);
124
+ if (!signature) {
125
+ return;
126
+ }
127
+ counts.set(signature, (counts.get(signature) || 0) + 1);
128
+ });
129
+ return counts;
130
+ };
131
+ var countsA = countSignatures(a);
132
+ var countsB = countSignatures(b);
133
+ var uniqueSharedSignatures = new Set(Array.from(countsA.keys()).filter(function (signature) {
134
+ return countsA.get(signature) === 1 && countsB.get(signature) === 1;
135
+ }));
136
+ return function (x, y) {
137
+ var nodeX = getNode(x);
138
+ var nodeY = getNode(y);
139
+ var signatureX = getNodeSignatureForAlign(nodeX, options);
140
+ var signatureY = getNodeSignatureForAlign(nodeY, options);
141
+ var isXUnique = uniqueSharedSignatures.has(signatureX);
142
+ var isYUnique = uniqueSharedSignatures.has(signatureY);
143
+ if (isXUnique || isYUnique) {
144
+ return isXUnique && isYUnique && signatureX === signatureY;
145
+ }
146
+ return nodesEqualForAlign(nodeX, nodeY, options);
147
+ };
148
+ }
59
149
  function compareNodes(nodeA, nodeB) {
60
150
  var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
151
+ var options = arguments.length > 3 ? arguments[3] : undefined;
61
152
  var diffs = [];
62
153
 
63
154
  // 如果节点类型不同,标记整个节点为变更
@@ -93,10 +184,10 @@ function compareNodes(nodeA, nodeB) {
93
184
 
94
185
  var textOffset = 0; // offset in the NEW text node
95
186
 
96
- textDiffs.forEach(function (_ref) {
97
- var _ref2 = _slicedToArray(_ref, 2),
98
- operation = _ref2[0],
99
- text = _ref2[1];
187
+ textDiffs.forEach(function (_ref3) {
188
+ var _ref4 = _slicedToArray(_ref3, 2),
189
+ operation = _ref4[0],
190
+ text = _ref4[1];
100
191
  if (operation === -1) {
101
192
  // Delete
102
193
  // The widget should be placed at the current position in the new text.
@@ -133,7 +224,7 @@ function compareNodes(nodeA, nodeB) {
133
224
  });
134
225
  }
135
226
  // 文本相等或不相等时都可检查 marks 差异(粗粒度:节点级)
136
- if (!haveSameMarks(nodeA.marks, nodeB.marks)) {
227
+ if (!haveSameMarks(nodeA.marks, nodeB.marks, options)) {
137
228
  diffs.push({
138
229
  type: 'modify',
139
230
  path: _toConsumableArray(path),
@@ -148,11 +239,11 @@ function compareNodes(nodeA, nodeB) {
148
239
  }
149
240
 
150
241
  // 优先:段落/标题等内联容器执行字符级 diff 和 marks 区间对比
151
- if (nodeA && nodeB && isInlineContainer(nodeA) && isInlineContainer(nodeB)) {
242
+ if (nodeA && nodeB && isInlineContainer(nodeA, options) && isInlineContainer(nodeB, options)) {
152
243
  // 文本与 marks 的字符级 diff
153
- diffs.push.apply(diffs, _toConsumableArray(compareInlineContainer(nodeA, nodeB, path)));
244
+ diffs.push.apply(diffs, _toConsumableArray(compareInlineContainer(nodeA, nodeB, path, options)));
154
245
  // 非文本内联节点(如行内数学、行内公式等)的结构化对比
155
- diffs.push.apply(diffs, _toConsumableArray(compareInlineContainerChildren(nodeA, nodeB, path)));
246
+ diffs.push.apply(diffs, _toConsumableArray(compareInlineContainerChildren(nodeA, nodeB, path, options)));
156
247
  return diffs;
157
248
  }
158
249
 
@@ -162,6 +253,9 @@ function compareNodes(nodeA, nodeB) {
162
253
  var allAttrKeys = new Set([].concat(_toConsumableArray(Object.keys(attrsA)), _toConsumableArray(Object.keys(attrsB))));
163
254
  for (var _i = 0, _Array$from = Array.from(allAttrKeys); _i < _Array$from.length; _i++) {
164
255
  var key = _Array$from[_i];
256
+ if (isIgnoredAttr(key, options)) {
257
+ continue;
258
+ }
165
259
  if (attrsA[key] !== attrsB[key]) {
166
260
  diffs.push({
167
261
  type: 'modify',
@@ -178,7 +272,9 @@ function compareNodes(nodeA, nodeB) {
178
272
  // 使用 LCS 对齐子节点,减少错配
179
273
  var contentA = (nodeA === null || nodeA === void 0 ? void 0 : nodeA.content) || [];
180
274
  var contentB = (nodeB === null || nodeB === void 0 ? void 0 : nodeB.content) || [];
181
- var pairs = lcsAlign(contentA, contentB, nodesEqualForAlign);
275
+ var pairs = lcsAlign(contentA, contentB, createAlignPredicate(contentA, contentB, function (node) {
276
+ return node;
277
+ }, options));
182
278
  var ai = 0;
183
279
  var bi = 0;
184
280
  for (var _i2 = 0, _pairs = pairs; _i2 < _pairs.length; _i2++) {
@@ -208,7 +304,7 @@ function compareNodes(nodeA, nodeB) {
208
304
  // 匹配上的成对递归,路径以新文档索引为准
209
305
  var childA = contentA[i];
210
306
  var childB = contentB[j];
211
- diffs.push.apply(diffs, _toConsumableArray(compareNodes(childA, childB, [].concat(_toConsumableArray(path), [j]))));
307
+ diffs.push.apply(diffs, _toConsumableArray(compareNodes(childA, childB, [].concat(_toConsumableArray(path), [j]), options)));
212
308
  ai = i + 1;
213
309
  bi = j + 1;
214
310
  }
@@ -233,8 +329,31 @@ function compareNodes(nodeA, nodeB) {
233
329
  }
234
330
  return diffs;
235
331
  }
236
- function isInlineContainer(node) {
237
- return node.type === 'paragraph' || node.type === 'heading';
332
+ function isInlineLikeChildNode(node) {
333
+ var _node$content;
334
+ if (node.type === 'text') {
335
+ return true;
336
+ }
337
+ if (BLOCKISH_CHILD_NODE_TYPES.has(node.type)) {
338
+ return false;
339
+ }
340
+ return !((_node$content = node.content) !== null && _node$content !== void 0 && _node$content.length);
341
+ }
342
+ function isInlineLeafContainer(node) {
343
+ if (NON_INLINE_LEAF_CONTAINER_TYPES.has(node.type)) {
344
+ return false;
345
+ }
346
+ var content = node.content || [];
347
+ if (content.length === 0) {
348
+ return false;
349
+ }
350
+ return content.every(isInlineLikeChildNode);
351
+ }
352
+ function isInlineContainer(node, options) {
353
+ if (getStructuredDiffEngine(options) === 'legacy') {
354
+ return LEGACY_INLINE_CONTAINER_TYPES.has(node.type);
355
+ }
356
+ return isInlineLeafContainer(node);
238
357
  }
239
358
  function extractInlineTextAndMarks(node) {
240
359
  var resultChars = [];
@@ -263,7 +382,7 @@ function extractInlineTextAndMarks(node) {
263
382
  marksMap: marksMap
264
383
  };
265
384
  }
266
- function compareInlineContainer(nodeA, nodeB, path) {
385
+ function compareInlineContainer(nodeA, nodeB, path, options) {
267
386
  var diffs = [];
268
387
  var _extractInlineTextAnd = extractInlineTextAndMarks(nodeA),
269
388
  oldText = _extractInlineTextAnd.text,
@@ -312,7 +431,7 @@ function compareInlineContainer(nodeA, nodeB, path) {
312
431
  for (var i = 0; i < len; i++) {
313
432
  var oldIdx = oldOffset + i;
314
433
  var newIdx = newOffset + i;
315
- var same = haveSameMarks(oldMarks[oldIdx] || [], newMarks[newIdx] || []);
434
+ var same = haveSameMarks(oldMarks[oldIdx] || [], newMarks[newIdx] || [], options);
316
435
  if (!same) {
317
436
  if (runStart === null) runStart = i;
318
437
  } else if (runStart !== null) {
@@ -350,32 +469,40 @@ function compareInlineContainer(nodeA, nodeB, path) {
350
469
  }
351
470
  return diffs;
352
471
  }
353
- function nodesEqualForAlign(a, b) {
472
+ function nodesEqualForAlign(a, b, options) {
354
473
  if (!a || !b) return false;
355
474
  if (a.type !== b.type) return false;
356
475
  // 对部分结构节点使用关键属性辅助匹配
357
476
  if (a.type === 'heading') {
358
477
  var _a$attrs$level, _a$attrs, _b$attrs$level, _b$attrs;
478
+ if (isIgnoredAttr('level', options)) {
479
+ return true;
480
+ }
359
481
  return ((_a$attrs$level = (_a$attrs = a.attrs) === null || _a$attrs === void 0 ? void 0 : _a$attrs.level) !== null && _a$attrs$level !== void 0 ? _a$attrs$level : null) === ((_b$attrs$level = (_b$attrs = b.attrs) === null || _b$attrs === void 0 ? void 0 : _b$attrs.level) !== null && _b$attrs$level !== void 0 ? _b$attrs$level : null);
360
482
  }
361
483
  if (a.type === 'codeBlock' || a.type === 'code_block') {
362
- var _ref3, _a$attrs$language, _a$attrs2, _a$attrs3, _ref4, _b$attrs$language, _b$attrs2, _b$attrs3;
363
- var langA = (_ref3 = (_a$attrs$language = (_a$attrs2 = a.attrs) === null || _a$attrs2 === void 0 ? void 0 : _a$attrs2.language) !== null && _a$attrs$language !== void 0 ? _a$attrs$language : (_a$attrs3 = a.attrs) === null || _a$attrs3 === void 0 ? void 0 : _a$attrs3.lang) !== null && _ref3 !== void 0 ? _ref3 : null;
364
- var langB = (_ref4 = (_b$attrs$language = (_b$attrs2 = b.attrs) === null || _b$attrs2 === void 0 ? void 0 : _b$attrs2.language) !== null && _b$attrs$language !== void 0 ? _b$attrs$language : (_b$attrs3 = b.attrs) === null || _b$attrs3 === void 0 ? void 0 : _b$attrs3.lang) !== null && _ref4 !== void 0 ? _ref4 : null;
484
+ var _ref5, _a$attrs$language, _a$attrs2, _a$attrs3, _ref6, _b$attrs$language, _b$attrs2, _b$attrs3;
485
+ if (isIgnoredAttr('language', options) || isIgnoredAttr('lang', options)) {
486
+ return true;
487
+ }
488
+ var langA = (_ref5 = (_a$attrs$language = (_a$attrs2 = a.attrs) === null || _a$attrs2 === void 0 ? void 0 : _a$attrs2.language) !== null && _a$attrs$language !== void 0 ? _a$attrs$language : (_a$attrs3 = a.attrs) === null || _a$attrs3 === void 0 ? void 0 : _a$attrs3.lang) !== null && _ref5 !== void 0 ? _ref5 : null;
489
+ var langB = (_ref6 = (_b$attrs$language = (_b$attrs2 = b.attrs) === null || _b$attrs2 === void 0 ? void 0 : _b$attrs2.language) !== null && _b$attrs$language !== void 0 ? _b$attrs$language : (_b$attrs3 = b.attrs) === null || _b$attrs3 === void 0 ? void 0 : _b$attrs3.lang) !== null && _ref6 !== void 0 ? _ref6 : null;
365
490
  return langA === langB;
366
491
  }
367
492
  if (a.type === 'table_cell' || a.type === 'tableHeader' || a.type === 'table_header') {
368
493
  var _a$attrs$colspan, _a$attrs4, _b$attrs$colspan, _b$attrs4, _a$attrs$rowspan, _a$attrs5, _b$attrs$rowspan, _b$attrs5;
494
+ var compareColspan = !isIgnoredAttr('colspan', options);
495
+ var compareRowspan = !isIgnoredAttr('rowspan', options);
369
496
  var colspanA = (_a$attrs$colspan = (_a$attrs4 = a.attrs) === null || _a$attrs4 === void 0 ? void 0 : _a$attrs4.colspan) !== null && _a$attrs$colspan !== void 0 ? _a$attrs$colspan : 1;
370
497
  var colspanB = (_b$attrs$colspan = (_b$attrs4 = b.attrs) === null || _b$attrs4 === void 0 ? void 0 : _b$attrs4.colspan) !== null && _b$attrs$colspan !== void 0 ? _b$attrs$colspan : 1;
371
498
  var rowspanA = (_a$attrs$rowspan = (_a$attrs5 = a.attrs) === null || _a$attrs5 === void 0 ? void 0 : _a$attrs5.rowspan) !== null && _a$attrs$rowspan !== void 0 ? _a$attrs$rowspan : 1;
372
499
  var rowspanB = (_b$attrs$rowspan = (_b$attrs5 = b.attrs) === null || _b$attrs5 === void 0 ? void 0 : _b$attrs5.rowspan) !== null && _b$attrs$rowspan !== void 0 ? _b$attrs$rowspan : 1;
373
- return colspanA === colspanB && rowspanA === rowspanB;
500
+ return (!compareColspan || colspanA === colspanB) && (!compareRowspan || rowspanA === rowspanB);
374
501
  }
375
502
  // 默认仅按类型
376
503
  return true;
377
504
  }
378
- function compareInlineContainerChildren(nodeA, nodeB, path) {
505
+ function compareInlineContainerChildren(nodeA, nodeB, path, options) {
379
506
  var diffs = [];
380
507
  var aList = (nodeA.content || []).map(function (n, idx) {
381
508
  return {
@@ -398,7 +525,9 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
398
525
  // 仅按类型对齐,属性差异作为 modify 处理
399
526
  return x.n.type === y.n.type;
400
527
  };
401
- var pairs = lcsAlign(aList, bList, equals);
528
+ var pairs = lcsAlign(aList, bList, getStructuredDiffEngine(options) === 'enhanced' ? createAlignPredicate(aList, bList, function (item) {
529
+ return item.n;
530
+ }, options) : equals);
402
531
  var ai = 0,
403
532
  bi = 0;
404
533
  for (var _i5 = 0, _pairs2 = pairs; _i5 < _pairs2.length; _i5++) {
@@ -432,14 +561,14 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
432
561
  if (aItem && bItem) {
433
562
  var oldAttrs = aItem.n.attrs || {};
434
563
  var newAttrs = bItem.n.attrs || {};
435
- if (JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs)) {
564
+ if (!areAttrsEqual(oldAttrs, newAttrs, options)) {
436
565
  diffs.push({
437
566
  type: 'modify',
438
567
  path: [].concat(_toConsumableArray(path), [bItem.idx]),
439
568
  attrChange: {
440
569
  key: 'attrs',
441
- oldValue: oldAttrs,
442
- newValue: newAttrs
570
+ oldValue: getFilteredAttrs(oldAttrs, options),
571
+ newValue: getFilteredAttrs(newAttrs, options)
443
572
  }
444
573
  });
445
574
  }
@@ -506,10 +635,10 @@ function lcsAlign(a, b, equals) {
506
635
  * @param {Array} extensions - Tiptap扩展数组
507
636
  * @returns {Object} 包含差异信息的对象
508
637
  */
509
- export function compareDocuments(oldHtml, newHtml, extensions) {
638
+ export function compareDocuments(oldHtml, newHtml, extensions, options) {
510
639
  var docA = parseHtmlToDoc(oldHtml, extensions);
511
640
  var docB = parseHtmlToDoc(newHtml, extensions);
512
- var diffs = compareNodes(docA, docB);
641
+ var diffs = compareNodes(docA, docB, [], options);
513
642
  return {
514
643
  oldDoc: docA,
515
644
  newDoc: docB,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctzhian/tiptap",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "基于 Tiptap 二次开发的编辑器组件",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -139,6 +139,7 @@
139
139
  "docs:build": "dumi build",
140
140
  "docs:preview": "dumi preview",
141
141
  "doctor": "father doctor",
142
+ "test:smoke": "pnpm build && node --loader ./test/esm-loader.mjs --test test/*.test.mjs",
142
143
  "lint": "npm run lint:es && npm run lint:css",
143
144
  "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"",
144
145
  "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"",