@ctzhian/tiptap 0.5.6 → 1.0.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.
@@ -0,0 +1,550 @@
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
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
6
+ 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
+ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
8
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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
+ 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
+ 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
+ import { generateJSON } from '@tiptap/html';
13
+ import DiffMatchPatch from 'diff-match-patch';
14
+
15
+ // 创建diff-match-patch实例
16
+ var dmp = new DiffMatchPatch();
17
+
18
+ // 类型定义
19
+
20
+ /**
21
+ * 将HTML转换为ProseMirror文档结构
22
+ * @param {string} html - HTML字符串
23
+ * @param {Array} extensions - Tiptap扩展数组
24
+ * @returns {Object} ProseMirror文档对象
25
+ */
26
+ export function parseHtmlToDoc(html, extensions) {
27
+ return generateJSON(html, extensions);
28
+ }
29
+ function haveSameMarks(a, b) {
30
+ var arrA = a || [];
31
+ var arrB = b || [];
32
+ 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));
37
+ var _iterator = _createForOfIteratorHelper(arrB),
38
+ _step;
39
+ try {
40
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
41
+ var m = _step.value;
42
+ if (!setA.has(serialize(m))) return false;
43
+ }
44
+ } catch (err) {
45
+ _iterator.e(err);
46
+ } finally {
47
+ _iterator.f();
48
+ }
49
+ return true;
50
+ }
51
+
52
+ /**
53
+ * 对比两个ProseMirror文档节点
54
+ * @param {Object} nodeA - 旧文档节点
55
+ * @param {Object} nodeB - 新文档节点
56
+ * @param {Array} path - 当前节点路径
57
+ * @returns {Array} 差异数组
58
+ */
59
+ function compareNodes(nodeA, nodeB) {
60
+ var path = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
61
+ var diffs = [];
62
+
63
+ // 如果节点类型不同,标记整个节点为变更
64
+ if ((nodeA === null || nodeA === void 0 ? void 0 : nodeA.type) !== (nodeB === null || nodeB === void 0 ? void 0 : nodeB.type)) {
65
+ if (nodeA) {
66
+ diffs.push({
67
+ type: 'delete',
68
+ path: _toConsumableArray(path),
69
+ node: nodeA
70
+ });
71
+ }
72
+ if (nodeB) {
73
+ diffs.push({
74
+ type: 'insert',
75
+ path: _toConsumableArray(path),
76
+ node: nodeB
77
+ });
78
+ }
79
+ return diffs;
80
+ }
81
+
82
+ // 如果是文本节点,进行文本级别的diff
83
+ if ((nodeA === null || nodeA === void 0 ? void 0 : nodeA.type) === 'text' && (nodeB === null || nodeB === void 0 ? void 0 : nodeB.type) === 'text') {
84
+ if (nodeA.text !== nodeB.text) {
85
+ // 计算文本差异
86
+ var textDiffs = dmp.diff_main(nodeA.text || '', nodeB.text || '');
87
+ dmp.diff_cleanupSemantic(textDiffs);
88
+
89
+ // 检查是否有文本差异
90
+ // if (textDiffs.length > 1) {
91
+ // console.log('发现文本差异:', { oldText: nodeA.text, newText: nodeB.text, path });
92
+ // }
93
+
94
+ var textOffset = 0; // offset in the NEW text node
95
+
96
+ textDiffs.forEach(function (_ref) {
97
+ var _ref2 = _slicedToArray(_ref, 2),
98
+ operation = _ref2[0],
99
+ text = _ref2[1];
100
+ if (operation === -1) {
101
+ // Delete
102
+ // The widget should be placed at the current position in the new text.
103
+ var diffItem = {
104
+ type: 'delete',
105
+ path: _toConsumableArray(path),
106
+ textDiff: {
107
+ offset: textOffset,
108
+ length: text.length,
109
+ text: text,
110
+ operation: operation
111
+ }
112
+ };
113
+ diffs.push(diffItem);
114
+ // DO NOT advance textOffset
115
+ } else if (operation === 1) {
116
+ // Insert
117
+ var _diffItem = {
118
+ type: 'insert',
119
+ path: _toConsumableArray(path),
120
+ textDiff: {
121
+ offset: textOffset,
122
+ length: text.length,
123
+ text: text,
124
+ operation: operation
125
+ }
126
+ };
127
+ diffs.push(_diffItem);
128
+ textOffset += text.length; // DO advance textOffset
129
+ } else {
130
+ // Equal
131
+ textOffset += text.length; // DO advance textOffset
132
+ }
133
+ });
134
+ }
135
+ // 文本相等或不相等时都可检查 marks 差异(粗粒度:节点级)
136
+ if (!haveSameMarks(nodeA.marks, nodeB.marks)) {
137
+ diffs.push({
138
+ type: 'modify',
139
+ path: _toConsumableArray(path),
140
+ attrChange: {
141
+ key: 'marks',
142
+ oldValue: nodeA.marks || [],
143
+ newValue: nodeB.marks || []
144
+ }
145
+ });
146
+ }
147
+ return diffs;
148
+ }
149
+
150
+ // 优先:段落/标题等内联容器执行字符级 diff 和 marks 区间对比
151
+ if (nodeA && nodeB && isInlineContainer(nodeA) && isInlineContainer(nodeB)) {
152
+ // 文本与 marks 的字符级 diff
153
+ diffs.push.apply(diffs, _toConsumableArray(compareInlineContainer(nodeA, nodeB, path)));
154
+ // 非文本内联节点(如行内数学、行内公式等)的结构化对比
155
+ diffs.push.apply(diffs, _toConsumableArray(compareInlineContainerChildren(nodeA, nodeB, path)));
156
+ return diffs;
157
+ }
158
+
159
+ // 对比节点属性
160
+ var attrsA = (nodeA === null || nodeA === void 0 ? void 0 : nodeA.attrs) || {};
161
+ var attrsB = (nodeB === null || nodeB === void 0 ? void 0 : nodeB.attrs) || {};
162
+ var allAttrKeys = new Set([].concat(_toConsumableArray(Object.keys(attrsA)), _toConsumableArray(Object.keys(attrsB))));
163
+ for (var _i = 0, _Array$from = Array.from(allAttrKeys); _i < _Array$from.length; _i++) {
164
+ var key = _Array$from[_i];
165
+ if (attrsA[key] !== attrsB[key]) {
166
+ diffs.push({
167
+ type: 'modify',
168
+ path: _toConsumableArray(path),
169
+ attrChange: {
170
+ key: key,
171
+ oldValue: attrsA[key],
172
+ newValue: attrsB[key]
173
+ }
174
+ });
175
+ }
176
+ }
177
+
178
+ // 使用 LCS 对齐子节点,减少错配
179
+ var contentA = (nodeA === null || nodeA === void 0 ? void 0 : nodeA.content) || [];
180
+ var contentB = (nodeB === null || nodeB === void 0 ? void 0 : nodeB.content) || [];
181
+ var pairs = lcsAlign(contentA, contentB, nodesEqualForAlign);
182
+ var ai = 0;
183
+ var bi = 0;
184
+ for (var _i2 = 0, _pairs = pairs; _i2 < _pairs.length; _i2++) {
185
+ var _pairs$_i = _slicedToArray(_pairs[_i2], 2),
186
+ i = _pairs$_i[0],
187
+ j = _pairs$_i[1];
188
+ // 先处理 A 中未匹配(删除),以 B 的当前位置作为锚点
189
+ while (ai < i) {
190
+ var delNode = contentA[ai];
191
+ diffs.push({
192
+ type: 'delete',
193
+ path: [].concat(_toConsumableArray(path), [bi]),
194
+ node: delNode
195
+ });
196
+ ai++;
197
+ }
198
+ // 再处理 B 中未匹配(插入)
199
+ while (bi < j) {
200
+ var insNode = contentB[bi];
201
+ diffs.push({
202
+ type: 'insert',
203
+ path: [].concat(_toConsumableArray(path), [bi]),
204
+ node: insNode
205
+ });
206
+ bi++;
207
+ }
208
+ // 匹配上的成对递归,路径以新文档索引为准
209
+ var childA = contentA[i];
210
+ var childB = contentB[j];
211
+ diffs.push.apply(diffs, _toConsumableArray(compareNodes(childA, childB, [].concat(_toConsumableArray(path), [j]))));
212
+ ai = i + 1;
213
+ bi = j + 1;
214
+ }
215
+ // 处理尾部剩余未匹配项
216
+ while (ai < contentA.length) {
217
+ var _delNode = contentA[ai];
218
+ diffs.push({
219
+ type: 'delete',
220
+ path: [].concat(_toConsumableArray(path), [bi]),
221
+ node: _delNode
222
+ });
223
+ ai++;
224
+ }
225
+ while (bi < contentB.length) {
226
+ var _insNode = contentB[bi];
227
+ diffs.push({
228
+ type: 'insert',
229
+ path: [].concat(_toConsumableArray(path), [bi]),
230
+ node: _insNode
231
+ });
232
+ bi++;
233
+ }
234
+ return diffs;
235
+ }
236
+ function isInlineContainer(node) {
237
+ return node.type === 'paragraph' || node.type === 'heading';
238
+ }
239
+ function extractInlineTextAndMarks(node) {
240
+ var resultChars = [];
241
+ var marksMap = [];
242
+ var content = node.content || [];
243
+ var _iterator2 = _createForOfIteratorHelper(content),
244
+ _step2;
245
+ try {
246
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
247
+ var child = _step2.value;
248
+ if (child.type === 'text') {
249
+ var t = child.text || '';
250
+ for (var i = 0; i < t.length; i++) {
251
+ resultChars.push(t[i]);
252
+ marksMap.push((child.marks || []).slice());
253
+ }
254
+ }
255
+ }
256
+ } catch (err) {
257
+ _iterator2.e(err);
258
+ } finally {
259
+ _iterator2.f();
260
+ }
261
+ return {
262
+ text: resultChars.join(''),
263
+ marksMap: marksMap
264
+ };
265
+ }
266
+ function compareInlineContainer(nodeA, nodeB, path) {
267
+ var diffs = [];
268
+ var _extractInlineTextAnd = extractInlineTextAndMarks(nodeA),
269
+ oldText = _extractInlineTextAnd.text,
270
+ oldMarks = _extractInlineTextAnd.marksMap;
271
+ var _extractInlineTextAnd2 = extractInlineTextAndMarks(nodeB),
272
+ newText = _extractInlineTextAnd2.text,
273
+ newMarks = _extractInlineTextAnd2.marksMap;
274
+ var blocks = dmp.diff_main(oldText, newText);
275
+ dmp.diff_cleanupSemantic(blocks);
276
+ var oldOffset = 0;
277
+ var newOffset = 0;
278
+ for (var _i3 = 0, _arr = blocks; _i3 < _arr.length; _i3++) {
279
+ var _arr$_i = _slicedToArray(_arr[_i3], 2),
280
+ operation = _arr$_i[0],
281
+ text = _arr$_i[1];
282
+ var len = text.length;
283
+ if (operation === -1) {
284
+ // 删除:在新文本当前位置放置删除widget
285
+ diffs.push({
286
+ type: 'delete',
287
+ path: _toConsumableArray(path),
288
+ textDiff: {
289
+ offset: newOffset,
290
+ length: len,
291
+ text: text,
292
+ operation: operation
293
+ }
294
+ });
295
+ oldOffset += len;
296
+ } else if (operation === 1) {
297
+ // 插入:直接在新文本当前位置标注
298
+ diffs.push({
299
+ type: 'insert',
300
+ path: _toConsumableArray(path),
301
+ textDiff: {
302
+ offset: newOffset,
303
+ length: len,
304
+ text: text,
305
+ operation: operation
306
+ }
307
+ });
308
+ newOffset += len;
309
+ } else {
310
+ // 相等:检测 marks 差异并生成区间级 modify
311
+ var runStart = null;
312
+ for (var i = 0; i < len; i++) {
313
+ var oldIdx = oldOffset + i;
314
+ var newIdx = newOffset + i;
315
+ var same = haveSameMarks(oldMarks[oldIdx] || [], newMarks[newIdx] || []);
316
+ if (!same) {
317
+ if (runStart === null) runStart = i;
318
+ } else if (runStart !== null) {
319
+ diffs.push({
320
+ type: 'modify',
321
+ path: _toConsumableArray(path),
322
+ attrChange: {
323
+ key: 'marks',
324
+ oldValue: oldMarks.slice(oldOffset + runStart, oldOffset + i),
325
+ newValue: newMarks.slice(newOffset + runStart, newOffset + i),
326
+ fromOffset: newOffset + runStart,
327
+ toOffset: newOffset + i
328
+ }
329
+ });
330
+ runStart = null;
331
+ }
332
+ }
333
+ if (runStart !== null) {
334
+ var _i4 = len;
335
+ diffs.push({
336
+ type: 'modify',
337
+ path: _toConsumableArray(path),
338
+ attrChange: {
339
+ key: 'marks',
340
+ oldValue: oldMarks.slice(oldOffset + runStart, oldOffset + _i4),
341
+ newValue: newMarks.slice(newOffset + runStart, newOffset + _i4),
342
+ fromOffset: newOffset + runStart,
343
+ toOffset: newOffset + _i4
344
+ }
345
+ });
346
+ }
347
+ oldOffset += len;
348
+ newOffset += len;
349
+ }
350
+ }
351
+ return diffs;
352
+ }
353
+ function nodesEqualForAlign(a, b) {
354
+ if (!a || !b) return false;
355
+ if (a.type !== b.type) return false;
356
+ // 对部分结构节点使用关键属性辅助匹配
357
+ if (a.type === 'heading') {
358
+ var _a$attrs$level, _a$attrs, _b$attrs$level, _b$attrs;
359
+ 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
+ }
361
+ 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;
365
+ return langA === langB;
366
+ }
367
+ if (a.type === 'table_cell' || a.type === 'tableHeader' || a.type === 'table_header') {
368
+ var _a$attrs$colspan, _a$attrs4, _b$attrs$colspan, _b$attrs4, _a$attrs$rowspan, _a$attrs5, _b$attrs$rowspan, _b$attrs5;
369
+ 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
+ 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
+ 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
+ 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;
374
+ }
375
+ // 默认仅按类型
376
+ return true;
377
+ }
378
+ function compareInlineContainerChildren(nodeA, nodeB, path) {
379
+ var diffs = [];
380
+ var aList = (nodeA.content || []).map(function (n, idx) {
381
+ return {
382
+ n: n,
383
+ idx: idx
384
+ };
385
+ }).filter(function (x) {
386
+ return x.n.type !== 'text';
387
+ });
388
+ var bList = (nodeB.content || []).map(function (n, idx) {
389
+ return {
390
+ n: n,
391
+ idx: idx
392
+ };
393
+ }).filter(function (x) {
394
+ return x.n.type !== 'text';
395
+ });
396
+ var equals = function equals(x, y) {
397
+ if (!x || !y) return false;
398
+ // 仅按类型对齐,属性差异作为 modify 处理
399
+ return x.n.type === y.n.type;
400
+ };
401
+ var pairs = lcsAlign(aList, bList, equals);
402
+ var ai = 0,
403
+ bi = 0;
404
+ for (var _i5 = 0, _pairs2 = pairs; _i5 < _pairs2.length; _i5++) {
405
+ var _pairs2$_i = _slicedToArray(_pairs2[_i5], 2),
406
+ i = _pairs2$_i[0],
407
+ j = _pairs2$_i[1];
408
+ // 删除:使用新文档当前位置作为锚点路径
409
+ while (ai < i) {
410
+ var del = aList[ai];
411
+ var anchorIdx = bi < bList.length ? bList[bi].idx : nodeB.content ? nodeB.content.length : 0;
412
+ diffs.push({
413
+ type: 'delete',
414
+ path: [].concat(_toConsumableArray(path), [anchorIdx]),
415
+ node: del.n
416
+ });
417
+ ai++;
418
+ }
419
+ // 插入:直接使用新文档该节点的实际索引
420
+ while (bi < j) {
421
+ var ins = bList[bi];
422
+ diffs.push({
423
+ type: 'insert',
424
+ path: [].concat(_toConsumableArray(path), [ins.idx]),
425
+ node: ins.n
426
+ });
427
+ bi++;
428
+ }
429
+ // modify:同类型节点进行属性对比,路径指向新文档该内联节点索引
430
+ var aItem = aList[i];
431
+ var bItem = bList[j];
432
+ if (aItem && bItem) {
433
+ var oldAttrs = aItem.n.attrs || {};
434
+ var newAttrs = bItem.n.attrs || {};
435
+ if (JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs)) {
436
+ diffs.push({
437
+ type: 'modify',
438
+ path: [].concat(_toConsumableArray(path), [bItem.idx]),
439
+ attrChange: {
440
+ key: 'attrs',
441
+ oldValue: oldAttrs,
442
+ newValue: newAttrs
443
+ }
444
+ });
445
+ }
446
+ }
447
+ ai = i + 1;
448
+ bi = j + 1;
449
+ }
450
+
451
+ // 尾部删除
452
+ while (ai < aList.length) {
453
+ var _del = aList[ai++];
454
+ var _anchorIdx = bi < bList.length ? bList[bi].idx : nodeB.content ? nodeB.content.length : 0;
455
+ diffs.push({
456
+ type: 'delete',
457
+ path: [].concat(_toConsumableArray(path), [_anchorIdx]),
458
+ node: _del.n
459
+ });
460
+ }
461
+ // 尾部插入
462
+ while (bi < bList.length) {
463
+ var _ins = bList[bi++];
464
+ diffs.push({
465
+ type: 'insert',
466
+ path: [].concat(_toConsumableArray(path), [_ins.idx]),
467
+ node: _ins.n
468
+ });
469
+ }
470
+ return diffs;
471
+ }
472
+ function lcsAlign(a, b, equals) {
473
+ var n = a.length;
474
+ var m = b.length;
475
+ var dp = Array.from({
476
+ length: n + 1
477
+ }, function () {
478
+ return Array(m + 1).fill(0);
479
+ });
480
+ for (var _i6 = n - 1; _i6 >= 0; _i6--) {
481
+ for (var _j = m - 1; _j >= 0; _j--) {
482
+ dp[_i6][_j] = equals(a[_i6], b[_j]) ? 1 + dp[_i6 + 1][_j + 1] : Math.max(dp[_i6 + 1][_j], dp[_i6][_j + 1]);
483
+ }
484
+ }
485
+ var pairs = [];
486
+ var i = 0,
487
+ j = 0;
488
+ while (i < n && j < m) {
489
+ if (equals(a[i], b[j])) {
490
+ pairs.push([i, j]);
491
+ i++;
492
+ j++;
493
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
494
+ i++;
495
+ } else {
496
+ j++;
497
+ }
498
+ }
499
+ return pairs;
500
+ }
501
+
502
+ /**
503
+ * 对比两个HTML文档并生成结构化差异
504
+ * @param {string} oldHtml - 旧HTML
505
+ * @param {string} newHtml - 新HTML
506
+ * @param {Array} extensions - Tiptap扩展数组
507
+ * @returns {Object} 包含差异信息的对象
508
+ */
509
+ export function compareDocuments(oldHtml, newHtml, extensions) {
510
+ var docA = parseHtmlToDoc(oldHtml, extensions);
511
+ var docB = parseHtmlToDoc(newHtml, extensions);
512
+ var diffs = compareNodes(docA, docB);
513
+ return {
514
+ oldDoc: docA,
515
+ newDoc: docB,
516
+ diffs: diffs,
517
+ hasChanges: diffs.length > 0
518
+ };
519
+ }
520
+
521
+ /**
522
+ * 将路径转换为ProseMirror位置
523
+ * @param {Array} path - 节点路径
524
+ * @param {Object} doc - ProseMirror文档
525
+ * @returns {number} 文档位置
526
+ */
527
+ export function pathToPos(path, doc) {
528
+ var pos = 0;
529
+ var current = doc;
530
+ for (var i = 0; i < path.length; i++) {
531
+ var _current$childCount;
532
+ var index = path[i];
533
+ var contentStartOffset = current.type.name === 'doc' ? 0 : 1;
534
+ var resolvedPos = pos + contentStartOffset;
535
+ var childCount = (_current$childCount = current.childCount) !== null && _current$childCount !== void 0 ? _current$childCount : 0;
536
+ var effectiveIndex = Math.min(index, childCount);
537
+ for (var j = 0; j < effectiveIndex; j++) {
538
+ var child = current.child(j);
539
+ resolvedPos += child.nodeSize;
540
+ }
541
+ pos = resolvedPos;
542
+ if (index < childCount) {
543
+ current = current.child(index);
544
+ } else {
545
+ // Path points past the end in this doc (likely a deletion). Stop descending
546
+ break;
547
+ }
548
+ }
549
+ return pos;
550
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctzhian/tiptap",
3
- "version": "0.5.6",
3
+ "version": "1.0.0",
4
4
  "description": "基于 Tiptap 二次开发的编辑器组件",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -63,6 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@commitlint/cli": "^17.1.2",
65
65
  "@commitlint/config-conventional": "^17.1.0",
66
+ "@types/diff-match-patch": "^1.0.36",
66
67
  "@types/react": "^18.0.0",
67
68
  "@types/react-dom": "^18.0.0",
68
69
  "@umijs/lint": "^4.0.0",
@@ -109,6 +110,7 @@
109
110
  "@tiptap/starter-kit": "^3.4.2",
110
111
  "@tiptap/static-renderer": "^3.4.2",
111
112
  "@tiptap/suggestion": "^3.4.2",
113
+ "diff-match-patch": "^1.0.5",
112
114
  "highlight.js": "^11.11.1",
113
115
  "jszip": "^3.10.1",
114
116
  "katex": "^0.16.22",