@ctzhian/tiptap 2.1.15 → 2.1.17
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/dist/EditorMarkdown/demo.js +1 -1
- package/dist/extension/extension/StructuredDiff.d.ts +1 -7
- package/dist/extension/extension/StructuredDiff.js +4 -34
- package/dist/extension/index.js +1 -20
- package/dist/util/structuredDiff.d.ts +19 -0
- package/dist/util/structuredDiff.js +275 -239
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ import { Box } from '@mui/material';
|
|
|
13
13
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
14
14
|
import "../index.css";
|
|
15
15
|
var Reader = function Reader() {
|
|
16
|
-
var _useState = useState("本文用以记录 Gitlab 相关问题及解答\n\n
|
|
16
|
+
var _useState = useState("本文用以记录 Gitlab 相关问题及解答\n\n## 我在使用 `git@git.in.chaitin.net:ns/repo` 时可以访问仓库,但是 `https://git.in.chaitin.net/ns/repo` 无法访问\n\n问题通常出现 git clone 与其他依赖 git 进行的操作,如 `go get` 等,具体报错可能为 `Permission Denied`.\n\n在使用 curl 借助 `CI_JOB_TOKEN` 获取其他仓库 cicd job 产物或者 generic package 时,其具体报错为 `404 Not Found` 的问题.\n\n原因是在使用 `git@git.in.chaitin.net` 时,使用的是 ssh 协议,此时默认使用的是 `~/.ssh` 下的私钥进行认证你在 gitlab 中添加的 `SSH Keys`。而使用 `https://git.in.chaitin.net/ns/repo` 时,使用的是 http 协议,此时默认使用的是 `~/.netrc` 认证你在 gitlab 中申请的 `Access tokens`。部分软件如 `go get` 默认使用的便是 http 来拉取代码,因此需要额外配置。\n\n这里推荐两种解决方案:\n\n1. 配置 `~/.netrc` 文件(推荐)\n\n 在 ~/.netrc 文件中添加以下配置:\n\n ```text\n machine git.in.chaitin.net login git password <your access token>\n ```\n\n 在 gitlab cicd job 中遇到拉取其他仓库时遇到权限问题可以使用本配置,比如可以在 job 的 `before_script` 阶段添加以下命令:\n\n ```shell\n echo \"machine git.in.chaitin.net login git password $CI_JOB_TOKEN\" >> ~/.netrc\n ```\n\n 注意: 其中 `$CI_JOB_TOKEN` 是 job 运行期间生成的 token,其权限与 trigger 用户相同,详见 [CI Job Token](https://docs.gitlab.com/ci/jobs/ci_job_token/)。其 Token 权限由 `pipeline 触发者`权限以及`目标仓库权限`共同决定。因此被拉取的目标仓库也需要保证其 `Setting -> CI/CD -> Job token permissions` 配置正确,允许源仓库的 CI/CD 能够访问到该仓库,否则依然会出现如 `404 Not Found`, `403 Forbidden` 的权限问题。(如果你是 pipeline 触发者,可以手动在浏览器通过 gitlab api 访问目标仓库的 generic package 或者 job artifact 如果可以正常下载,说明是 `Job token permissions` 问题)\n\n2. 配置 `git url replace`\n\n 在 ~/.gitconfig 文件中添加以下配置:\n\n ```ini\n [url \"git@git.in.chaitin.net:\"]\n insteadOf = https://git.in.chaitin.net/\n ```\n\n 这个配置可以将所有 `https://git.in.chaitin.net/` 请求替换为 `git@git.in.chaitin.net:` 使用 ssh 来拉取代码仓库\n\n## 我在 clone gitlab 仓库时没问题,但是 lfs 遇到了 EOF 问题\n\nEOF 在大多数情况下由代理导致,如果代理不允许客户端访问某个网站,行为可能是提早结束连接,导致 `EOF` 错误。如\n\n> 在 ubunu 虚拟机里无法拉取 safeline-aliyun 项目里的 lfs 文件怎么处理?\n\n```bash\nroot@OPS-5108:~/proj/safeline-aliyun# git lfs fetch --all\nfetch: 34 object(s) found, done.\nfetch: Fetching all references...\nbatch response: Post https://git.in.chaitin.net/patronus/safeline-aliyun.git/info/lfs/objects/batch: EOF\nerror: failed to fetch some objects from 'https://git.in.chaitin.net/patronus/safeline-aliyun.git/info/lfs'\n```\n\n可以看到返回了 EOF 问题,经查代理未配置在 git config 中。从 `env` 中可以看到用户配置了 *_proxy 但其中发现 `no_proxy` 配置不正确:\n\n```shell\nroot@OPS-5108:~/proj/safeline-aliyun# env | grep pro\nno_proxy=localhost, 127.0.0.1, ::1\nPWD=/root/proj/safeline-aliyun\nftp_proxy=http://proxy.in.chaitin.net:8123\nhttps_proxy=http://proxy.in.chaitin.net:8123\nproxy=http://proxy.in.chaitin.net:8123\nhttp_proxy=http://proxy.in.chaitin.net:8123\nOLDPWD=/root/proj\n```\n\n需要按照 [代理配置](https://info.chaitin.net/colab-editor/page/%E7%A0%94%E5%8F%91%E4%BD%93%E7%B3%BB%2F%E7%A0%94%E5%8F%91%E6%94%AF%E6%8C%81%2F%E4%BB%A3%E7%90%86%E6%89%AB%E7%9B%B2) 进行正确配置后,重新执行操作,发现 EOF 问题被解决。\n\n## 我在访问公司内部服务时报错 `x509: certificate signed by unknown authority`\n\n公司部分内部服务采用自签名证书,导致客户端在访问这些服务时会遇到 `x509: certificate signed by unknown authority` 错误。为了确保客户端能够信任这些自签名证书,你需要配置信任自签名证书即 CA。\n\n以 debian 系系统举例:\n\n```shell\ncurl -o /usr/local/share/ca-certificates/chaitin_ca.crt -L https://chaitin-ops-public.cn-beijing.oss.aliyuncs.com/Chaitin_Ltd_Root_CA.pem\nupdate-ca-certificates\n```\n\n如果无法访问外网,可以尝试 `http://s3-ephemeral.in.chaitin.net/dev/Chaitin_Ltd_Root_CA.pem`\n\n其他系统 CA 证书安装请参考 [安装CA证书](https://info.chaitin.net/colab-editor/page/IT%E5%8A%9E%E5%85%AC/%E5%8A%9E%E5%85%AC%E7%BD%91%E7%BB%9C/windows%E7%94%A8%E6%88%B7/%E5%A4%87%E9%80%89%E6%96%B9%E6%A1%88%EF%BC%88Windows%EF%BC%89/%E8%AF%81%E4%B9%A6%E5%AE%89%E8%A3%85%28windows%29/%E9%95%BF%E4%BA%AD%E6%A0%B9%E8%AF%81%E4%B9%A6%E5%AE%89%E8%A3%85%28windows%29)\n"),
|
|
17
17
|
_useState2 = _slicedToArray(_useState, 2),
|
|
18
18
|
mdContent = _useState2[0],
|
|
19
19
|
setMdContent = _useState2[1];
|
|
@@ -9,13 +9,7 @@ declare module '@tiptap/core' {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
export interface StructuredDiffOptions {
|
|
14
|
-
getExtensionConfig?: () => Omit<GetExtensionsProps, 'editable'> & {
|
|
15
|
-
editable: false;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export declare const StructuredDiffExtension: Extension<StructuredDiffOptions, any>;
|
|
12
|
+
export declare const StructuredDiffExtension: Extension<any, any>;
|
|
19
13
|
export { diffPluginKey };
|
|
20
14
|
export declare function getDiffState(editor: Editor): {
|
|
21
15
|
isActive: any;
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
-
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
-
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
-
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
5
|
-
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."); }
|
|
6
|
-
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); }
|
|
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 _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; }
|
|
10
2
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
11
3
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
12
4
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
@@ -17,7 +9,6 @@ import { Extension } from '@tiptap/core';
|
|
|
17
9
|
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
18
10
|
import { createDecorationsFromDiffs, createEmptyDecorationSet } from "../../util/decorations";
|
|
19
11
|
import { compareDocuments } from "../../util/structuredDiff";
|
|
20
|
-
import { getExtensions } from "../index";
|
|
21
12
|
var diffPluginKey = new PluginKey('structuredDiff');
|
|
22
13
|
var DiffPluginState = /*#__PURE__*/function () {
|
|
23
14
|
function DiffPluginState(decorations) {
|
|
@@ -53,40 +44,20 @@ var DiffPluginState = /*#__PURE__*/function () {
|
|
|
53
44
|
}
|
|
54
45
|
}], [{
|
|
55
46
|
key: "init",
|
|
56
|
-
value: function init(
|
|
47
|
+
value: function init(config, state) {
|
|
57
48
|
return new DiffPluginState(createEmptyDecorationSet(state.doc), [], false);
|
|
58
49
|
}
|
|
59
50
|
}]);
|
|
60
51
|
return DiffPluginState;
|
|
61
52
|
}();
|
|
62
|
-
function getExtensionsForParsing(editor, getExtensionConfig) {
|
|
63
|
-
if (getExtensionConfig) {
|
|
64
|
-
var config = getExtensionConfig();
|
|
65
|
-
if (config) {
|
|
66
|
-
var exclude = [].concat(_toConsumableArray(config.exclude || []), ['structuredDiff']);
|
|
67
|
-
return getExtensions(_objectSpread(_objectSpread({}, config), {}, {
|
|
68
|
-
exclude: exclude,
|
|
69
|
-
editable: false
|
|
70
|
-
}));
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return editor.extensionManager.extensions.filter(function (ext) {
|
|
74
|
-
return ext.name !== 'structuredDiff';
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
53
|
export var StructuredDiffExtension = Extension.create({
|
|
78
54
|
name: 'structuredDiff',
|
|
79
|
-
addOptions: function addOptions() {
|
|
80
|
-
return {
|
|
81
|
-
getExtensionConfig: undefined
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
55
|
addProseMirrorPlugins: function addProseMirrorPlugins() {
|
|
85
56
|
return [new Plugin({
|
|
86
57
|
key: diffPluginKey,
|
|
87
58
|
state: {
|
|
88
59
|
init: DiffPluginState.init,
|
|
89
|
-
apply: DiffPluginState.prototype.apply
|
|
60
|
+
apply: DiffPluginState.prototype.apply
|
|
90
61
|
},
|
|
91
62
|
props: {
|
|
92
63
|
decorations: function decorations(state) {
|
|
@@ -97,7 +68,6 @@ export var StructuredDiffExtension = Extension.create({
|
|
|
97
68
|
})];
|
|
98
69
|
},
|
|
99
70
|
addCommands: function addCommands() {
|
|
100
|
-
var _this = this;
|
|
101
71
|
return {
|
|
102
72
|
showStructuredDiff: function showStructuredDiff(oldHtml, newHtml) {
|
|
103
73
|
return function (_ref) {
|
|
@@ -106,8 +76,8 @@ export var StructuredDiffExtension = Extension.create({
|
|
|
106
76
|
dispatch = _ref.dispatch,
|
|
107
77
|
editor = _ref.editor;
|
|
108
78
|
try {
|
|
109
|
-
var
|
|
110
|
-
var comparison = compareDocuments(oldHtml, newHtml,
|
|
79
|
+
var extensions = editor.extensionManager.extensions;
|
|
80
|
+
var comparison = compareDocuments(oldHtml, newHtml, extensions);
|
|
111
81
|
if (!comparison.hasChanges) {
|
|
112
82
|
return false;
|
|
113
83
|
}
|
package/dist/extension/index.js
CHANGED
|
@@ -150,26 +150,7 @@ export var getExtensions = function getExtensions(_ref) {
|
|
|
150
150
|
} else {
|
|
151
151
|
// 只读模式
|
|
152
152
|
if (!(exclude !== null && exclude !== void 0 && exclude.includes('structuredDiff'))) {
|
|
153
|
-
defaultExtensions.push(StructuredDiffExtension
|
|
154
|
-
getExtensionConfig: function getExtensionConfig() {
|
|
155
|
-
return {
|
|
156
|
-
contentType: contentType,
|
|
157
|
-
limit: limit,
|
|
158
|
-
exclude: [].concat(_toConsumableArray(exclude || []), ['structuredDiff']),
|
|
159
|
-
extensions: extensionsProps,
|
|
160
|
-
youtube: youtube,
|
|
161
|
-
editable: false,
|
|
162
|
-
mentionItems: mentionItems,
|
|
163
|
-
onMentionFilter: onMentionFilter,
|
|
164
|
-
onUpload: onUpload,
|
|
165
|
-
onError: onError,
|
|
166
|
-
onTocUpdate: onTocUpdate,
|
|
167
|
-
onAiWritingGetSuggestion: onAiWritingGetSuggestion,
|
|
168
|
-
onValidateUrl: onValidateUrl,
|
|
169
|
-
placeholder: _placeholder
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}));
|
|
153
|
+
defaultExtensions.push(StructuredDiffExtension);
|
|
173
154
|
}
|
|
174
155
|
}
|
|
175
156
|
if (!(exclude !== null && exclude !== void 0 && exclude.includes('mention')) && (mentionItems && mentionItems.length > 0 || onMentionFilter)) {
|
|
@@ -35,6 +35,25 @@ export interface ProseMirrorNode {
|
|
|
35
35
|
attrs?: Record<string, any>;
|
|
36
36
|
}>;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* 将HTML转换为ProseMirror文档结构
|
|
40
|
+
* @param {string} html - HTML字符串
|
|
41
|
+
* @param {Array} extensions - Tiptap扩展数组
|
|
42
|
+
* @returns {Object} ProseMirror文档对象
|
|
43
|
+
*/
|
|
38
44
|
export declare function parseHtmlToDoc(html: string, extensions: Extensions): any;
|
|
45
|
+
/**
|
|
46
|
+
* 对比两个HTML文档并生成结构化差异
|
|
47
|
+
* @param {string} oldHtml - 旧HTML
|
|
48
|
+
* @param {string} newHtml - 新HTML
|
|
49
|
+
* @param {Array} extensions - Tiptap扩展数组
|
|
50
|
+
* @returns {Object} 包含差异信息的对象
|
|
51
|
+
*/
|
|
39
52
|
export declare function compareDocuments(oldHtml: string, newHtml: string, extensions: Extensions): DocumentComparison;
|
|
53
|
+
/**
|
|
54
|
+
* 将路径转换为ProseMirror位置
|
|
55
|
+
* @param {Array} path - 节点路径
|
|
56
|
+
* @param {Object} doc - ProseMirror文档
|
|
57
|
+
* @returns {number} 文档位置
|
|
58
|
+
*/
|
|
40
59
|
export declare function pathToPos(path: number[], doc: any): number;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
|
|
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."); }
|
|
3
|
-
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
4
|
-
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
5
1
|
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
6
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."); }
|
|
7
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; } }
|
|
8
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
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
12
|
import { generateJSON } from '@tiptap/html';
|
|
13
13
|
import DiffMatchPatch from 'diff-match-patch';
|
|
14
|
+
|
|
15
|
+
// 创建diff-match-patch实例
|
|
14
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
|
+
*/
|
|
15
26
|
export function parseHtmlToDoc(html, extensions) {
|
|
16
|
-
|
|
17
|
-
if (!html || typeof html !== 'string') {
|
|
18
|
-
throw new Error('HTML 内容无效');
|
|
19
|
-
}
|
|
20
|
-
if (!extensions || extensions.length === 0) {
|
|
21
|
-
throw new Error('扩展数组不能为空');
|
|
22
|
-
}
|
|
23
|
-
return generateJSON(html, extensions);
|
|
24
|
-
} catch (error) {
|
|
25
|
-
console.error('解析 HTML 到文档结构时出错:', error);
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
27
|
+
return generateJSON(html, extensions);
|
|
28
28
|
}
|
|
29
29
|
function haveSameMarks(a, b) {
|
|
30
30
|
var arrA = a || [];
|
|
@@ -48,6 +48,191 @@ function haveSameMarks(a, b) {
|
|
|
48
48
|
}
|
|
49
49
|
return true;
|
|
50
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
|
+
}
|
|
51
236
|
function isInlineContainer(node) {
|
|
52
237
|
return node.type === 'paragraph' || node.type === 'heading';
|
|
53
238
|
}
|
|
@@ -90,12 +275,13 @@ function compareInlineContainer(nodeA, nodeB, path) {
|
|
|
90
275
|
dmp.diff_cleanupSemantic(blocks);
|
|
91
276
|
var oldOffset = 0;
|
|
92
277
|
var newOffset = 0;
|
|
93
|
-
for (var
|
|
94
|
-
var _arr$_i = _slicedToArray(_arr[
|
|
278
|
+
for (var _i3 = 0, _arr = blocks; _i3 < _arr.length; _i3++) {
|
|
279
|
+
var _arr$_i = _slicedToArray(_arr[_i3], 2),
|
|
95
280
|
operation = _arr$_i[0],
|
|
96
281
|
text = _arr$_i[1];
|
|
97
282
|
var len = text.length;
|
|
98
283
|
if (operation === -1) {
|
|
284
|
+
// 删除:在新文本当前位置放置删除widget
|
|
99
285
|
diffs.push({
|
|
100
286
|
type: 'delete',
|
|
101
287
|
path: _toConsumableArray(path),
|
|
@@ -108,6 +294,7 @@ function compareInlineContainer(nodeA, nodeB, path) {
|
|
|
108
294
|
});
|
|
109
295
|
oldOffset += len;
|
|
110
296
|
} else if (operation === 1) {
|
|
297
|
+
// 插入:直接在新文本当前位置标注
|
|
111
298
|
diffs.push({
|
|
112
299
|
type: 'insert',
|
|
113
300
|
path: _toConsumableArray(path),
|
|
@@ -120,6 +307,7 @@ function compareInlineContainer(nodeA, nodeB, path) {
|
|
|
120
307
|
});
|
|
121
308
|
newOffset += len;
|
|
122
309
|
} else {
|
|
310
|
+
// 相等:检测 marks 差异并生成区间级 modify
|
|
123
311
|
var runStart = null;
|
|
124
312
|
for (var i = 0; i < len; i++) {
|
|
125
313
|
var oldIdx = oldOffset + i;
|
|
@@ -143,16 +331,16 @@ function compareInlineContainer(nodeA, nodeB, path) {
|
|
|
143
331
|
}
|
|
144
332
|
}
|
|
145
333
|
if (runStart !== null) {
|
|
146
|
-
var
|
|
334
|
+
var _i4 = len;
|
|
147
335
|
diffs.push({
|
|
148
336
|
type: 'modify',
|
|
149
337
|
path: _toConsumableArray(path),
|
|
150
338
|
attrChange: {
|
|
151
339
|
key: 'marks',
|
|
152
|
-
oldValue: oldMarks.slice(oldOffset + runStart, oldOffset +
|
|
153
|
-
newValue: newMarks.slice(newOffset + runStart, newOffset +
|
|
340
|
+
oldValue: oldMarks.slice(oldOffset + runStart, oldOffset + _i4),
|
|
341
|
+
newValue: newMarks.slice(newOffset + runStart, newOffset + _i4),
|
|
154
342
|
fromOffset: newOffset + runStart,
|
|
155
|
-
toOffset: newOffset +
|
|
343
|
+
toOffset: newOffset + _i4
|
|
156
344
|
}
|
|
157
345
|
});
|
|
158
346
|
}
|
|
@@ -162,6 +350,31 @@ function compareInlineContainer(nodeA, nodeB, path) {
|
|
|
162
350
|
}
|
|
163
351
|
return diffs;
|
|
164
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
|
+
}
|
|
165
378
|
function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
166
379
|
var diffs = [];
|
|
167
380
|
var aList = (nodeA.content || []).map(function (n, idx) {
|
|
@@ -182,15 +395,17 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
182
395
|
});
|
|
183
396
|
var equals = function equals(x, y) {
|
|
184
397
|
if (!x || !y) return false;
|
|
398
|
+
// 仅按类型对齐,属性差异作为 modify 处理
|
|
185
399
|
return x.n.type === y.n.type;
|
|
186
400
|
};
|
|
187
401
|
var pairs = lcsAlign(aList, bList, equals);
|
|
188
402
|
var ai = 0,
|
|
189
403
|
bi = 0;
|
|
190
|
-
for (var
|
|
191
|
-
var
|
|
192
|
-
i =
|
|
193
|
-
j =
|
|
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
|
+
// 删除:使用新文档当前位置作为锚点路径
|
|
194
409
|
while (ai < i) {
|
|
195
410
|
var del = aList[ai];
|
|
196
411
|
var anchorIdx = bi < bList.length ? bList[bi].idx : nodeB.content ? nodeB.content.length : 0;
|
|
@@ -201,6 +416,7 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
201
416
|
});
|
|
202
417
|
ai++;
|
|
203
418
|
}
|
|
419
|
+
// 插入:直接使用新文档该节点的实际索引
|
|
204
420
|
while (bi < j) {
|
|
205
421
|
var ins = bList[bi];
|
|
206
422
|
diffs.push({
|
|
@@ -210,6 +426,7 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
210
426
|
});
|
|
211
427
|
bi++;
|
|
212
428
|
}
|
|
429
|
+
// modify:同类型节点进行属性对比,路径指向新文档该内联节点索引
|
|
213
430
|
var aItem = aList[i];
|
|
214
431
|
var bItem = bList[j];
|
|
215
432
|
if (aItem && bItem) {
|
|
@@ -230,6 +447,8 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
230
447
|
ai = i + 1;
|
|
231
448
|
bi = j + 1;
|
|
232
449
|
}
|
|
450
|
+
|
|
451
|
+
// 尾部删除
|
|
233
452
|
while (ai < aList.length) {
|
|
234
453
|
var _del = aList[ai++];
|
|
235
454
|
var _anchorIdx = bi < bList.length ? bList[bi].idx : nodeB.content ? nodeB.content.length : 0;
|
|
@@ -239,6 +458,7 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
239
458
|
node: _del.n
|
|
240
459
|
});
|
|
241
460
|
}
|
|
461
|
+
// 尾部插入
|
|
242
462
|
while (bi < bList.length) {
|
|
243
463
|
var _ins = bList[bi++];
|
|
244
464
|
diffs.push({
|
|
@@ -249,29 +469,6 @@ function compareInlineContainerChildren(nodeA, nodeB, path) {
|
|
|
249
469
|
}
|
|
250
470
|
return diffs;
|
|
251
471
|
}
|
|
252
|
-
function nodesEqualForAlign(a, b) {
|
|
253
|
-
if (!a || !b) return false;
|
|
254
|
-
if (a.type !== b.type) return false;
|
|
255
|
-
if (a.type === 'heading') {
|
|
256
|
-
var _a$attrs$level, _a$attrs, _b$attrs$level, _b$attrs;
|
|
257
|
-
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);
|
|
258
|
-
}
|
|
259
|
-
if (a.type === 'codeBlock' || a.type === 'code_block') {
|
|
260
|
-
var _ref, _a$attrs$language, _a$attrs2, _a$attrs3, _ref2, _b$attrs$language, _b$attrs2, _b$attrs3;
|
|
261
|
-
var langA = (_ref = (_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 && _ref !== void 0 ? _ref : null;
|
|
262
|
-
var langB = (_ref2 = (_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 && _ref2 !== void 0 ? _ref2 : null;
|
|
263
|
-
return langA === langB;
|
|
264
|
-
}
|
|
265
|
-
if (a.type === 'table_cell' || a.type === 'tableHeader' || a.type === 'table_header') {
|
|
266
|
-
var _a$attrs$colspan, _a$attrs4, _b$attrs$colspan, _b$attrs4, _a$attrs$rowspan, _a$attrs5, _b$attrs$rowspan, _b$attrs5;
|
|
267
|
-
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;
|
|
268
|
-
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;
|
|
269
|
-
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;
|
|
270
|
-
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;
|
|
271
|
-
return colspanA === colspanB && rowspanA === rowspanB;
|
|
272
|
-
}
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
472
|
function lcsAlign(a, b, equals) {
|
|
276
473
|
var n = a.length;
|
|
277
474
|
var m = b.length;
|
|
@@ -280,9 +477,9 @@ function lcsAlign(a, b, equals) {
|
|
|
280
477
|
}, function () {
|
|
281
478
|
return Array(m + 1).fill(0);
|
|
282
479
|
});
|
|
283
|
-
for (var
|
|
480
|
+
for (var _i6 = n - 1; _i6 >= 0; _i6--) {
|
|
284
481
|
for (var _j = m - 1; _j >= 0; _j--) {
|
|
285
|
-
dp[
|
|
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]);
|
|
286
483
|
}
|
|
287
484
|
}
|
|
288
485
|
var pairs = [];
|
|
@@ -301,212 +498,51 @@ function lcsAlign(a, b, equals) {
|
|
|
301
498
|
}
|
|
302
499
|
return pairs;
|
|
303
500
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
node: nodeA
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
if (nodeB) {
|
|
316
|
-
diffs.push({
|
|
317
|
-
type: 'insert',
|
|
318
|
-
path: _toConsumableArray(path),
|
|
319
|
-
node: nodeB
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
return diffs;
|
|
323
|
-
}
|
|
324
|
-
if ((nodeA === null || nodeA === void 0 ? void 0 : nodeA.type) === 'text' && (nodeB === null || nodeB === void 0 ? void 0 : nodeB.type) === 'text') {
|
|
325
|
-
if (nodeA.text !== nodeB.text) {
|
|
326
|
-
var textDiffs = dmp.diff_main(nodeA.text || '', nodeB.text || '');
|
|
327
|
-
dmp.diff_cleanupSemantic(textDiffs);
|
|
328
|
-
var textOffset = 0;
|
|
329
|
-
textDiffs.forEach(function (_ref3) {
|
|
330
|
-
var _ref4 = _slicedToArray(_ref3, 2),
|
|
331
|
-
operation = _ref4[0],
|
|
332
|
-
text = _ref4[1];
|
|
333
|
-
if (operation === -1) {
|
|
334
|
-
var diffItem = {
|
|
335
|
-
type: 'delete',
|
|
336
|
-
path: _toConsumableArray(path),
|
|
337
|
-
textDiff: {
|
|
338
|
-
offset: textOffset,
|
|
339
|
-
length: text.length,
|
|
340
|
-
text: text,
|
|
341
|
-
operation: operation
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
diffs.push(diffItem);
|
|
345
|
-
} else if (operation === 1) {
|
|
346
|
-
var _diffItem = {
|
|
347
|
-
type: 'insert',
|
|
348
|
-
path: _toConsumableArray(path),
|
|
349
|
-
textDiff: {
|
|
350
|
-
offset: textOffset,
|
|
351
|
-
length: text.length,
|
|
352
|
-
text: text,
|
|
353
|
-
operation: operation
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
diffs.push(_diffItem);
|
|
357
|
-
textOffset += text.length;
|
|
358
|
-
} else {
|
|
359
|
-
textOffset += text.length;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
if (!haveSameMarks(nodeA.marks, nodeB.marks)) {
|
|
364
|
-
diffs.push({
|
|
365
|
-
type: 'modify',
|
|
366
|
-
path: _toConsumableArray(path),
|
|
367
|
-
attrChange: {
|
|
368
|
-
key: 'marks',
|
|
369
|
-
oldValue: nodeA.marks || [],
|
|
370
|
-
newValue: nodeB.marks || []
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
return diffs;
|
|
375
|
-
}
|
|
376
|
-
if (nodeA && nodeB && isInlineContainer(nodeA) && isInlineContainer(nodeB)) {
|
|
377
|
-
diffs.push.apply(diffs, _toConsumableArray(compareInlineContainer(nodeA, nodeB, path)));
|
|
378
|
-
diffs.push.apply(diffs, _toConsumableArray(compareInlineContainerChildren(nodeA, nodeB, path)));
|
|
379
|
-
return diffs;
|
|
380
|
-
}
|
|
381
|
-
var attrsA = (nodeA === null || nodeA === void 0 ? void 0 : nodeA.attrs) || {};
|
|
382
|
-
var attrsB = (nodeB === null || nodeB === void 0 ? void 0 : nodeB.attrs) || {};
|
|
383
|
-
var allAttrKeys = new Set([].concat(_toConsumableArray(Object.keys(attrsA)), _toConsumableArray(Object.keys(attrsB))));
|
|
384
|
-
for (var _i5 = 0, _Array$from = Array.from(allAttrKeys); _i5 < _Array$from.length; _i5++) {
|
|
385
|
-
var key = _Array$from[_i5];
|
|
386
|
-
if (attrsA[key] !== attrsB[key]) {
|
|
387
|
-
diffs.push({
|
|
388
|
-
type: 'modify',
|
|
389
|
-
path: _toConsumableArray(path),
|
|
390
|
-
attrChange: {
|
|
391
|
-
key: key,
|
|
392
|
-
oldValue: attrsA[key],
|
|
393
|
-
newValue: attrsB[key]
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
var contentA = (nodeA === null || nodeA === void 0 ? void 0 : nodeA.content) || [];
|
|
399
|
-
var contentB = (nodeB === null || nodeB === void 0 ? void 0 : nodeB.content) || [];
|
|
400
|
-
var pairs = lcsAlign(contentA, contentB, nodesEqualForAlign);
|
|
401
|
-
var ai = 0;
|
|
402
|
-
var bi = 0;
|
|
403
|
-
var _iterator3 = _createForOfIteratorHelper(pairs),
|
|
404
|
-
_step3;
|
|
405
|
-
try {
|
|
406
|
-
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
407
|
-
var _step3$value = _slicedToArray(_step3.value, 2),
|
|
408
|
-
i = _step3$value[0],
|
|
409
|
-
j = _step3$value[1];
|
|
410
|
-
while (ai < i) {
|
|
411
|
-
var _delNode = contentA[ai];
|
|
412
|
-
diffs.push({
|
|
413
|
-
type: 'delete',
|
|
414
|
-
path: [].concat(_toConsumableArray(path), [bi]),
|
|
415
|
-
node: _delNode
|
|
416
|
-
});
|
|
417
|
-
ai++;
|
|
418
|
-
}
|
|
419
|
-
while (bi < j) {
|
|
420
|
-
var _insNode = contentB[bi];
|
|
421
|
-
diffs.push({
|
|
422
|
-
type: 'insert',
|
|
423
|
-
path: [].concat(_toConsumableArray(path), [bi]),
|
|
424
|
-
node: _insNode
|
|
425
|
-
});
|
|
426
|
-
bi++;
|
|
427
|
-
}
|
|
428
|
-
var childA = contentA[i];
|
|
429
|
-
var childB = contentB[j];
|
|
430
|
-
diffs.push.apply(diffs, _toConsumableArray(compareNodes(childA, childB, [].concat(_toConsumableArray(path), [j]))));
|
|
431
|
-
ai = i + 1;
|
|
432
|
-
bi = j + 1;
|
|
433
|
-
}
|
|
434
|
-
} catch (err) {
|
|
435
|
-
_iterator3.e(err);
|
|
436
|
-
} finally {
|
|
437
|
-
_iterator3.f();
|
|
438
|
-
}
|
|
439
|
-
while (ai < contentA.length) {
|
|
440
|
-
var delNode = contentA[ai];
|
|
441
|
-
diffs.push({
|
|
442
|
-
type: 'delete',
|
|
443
|
-
path: [].concat(_toConsumableArray(path), [bi]),
|
|
444
|
-
node: delNode
|
|
445
|
-
});
|
|
446
|
-
ai++;
|
|
447
|
-
}
|
|
448
|
-
while (bi < contentB.length) {
|
|
449
|
-
var insNode = contentB[bi];
|
|
450
|
-
diffs.push({
|
|
451
|
-
type: 'insert',
|
|
452
|
-
path: [].concat(_toConsumableArray(path), [bi]),
|
|
453
|
-
node: insNode
|
|
454
|
-
});
|
|
455
|
-
bi++;
|
|
456
|
-
}
|
|
457
|
-
return diffs;
|
|
458
|
-
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* 对比两个HTML文档并生成结构化差异
|
|
504
|
+
* @param {string} oldHtml - 旧HTML
|
|
505
|
+
* @param {string} newHtml - 新HTML
|
|
506
|
+
* @param {Array} extensions - Tiptap扩展数组
|
|
507
|
+
* @returns {Object} 包含差异信息的对象
|
|
508
|
+
*/
|
|
459
509
|
export function compareDocuments(oldHtml, newHtml, extensions) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
};
|
|
470
|
-
} catch (error) {
|
|
471
|
-
console.error('对比文档时出错:', error);
|
|
472
|
-
return {
|
|
473
|
-
oldDoc: null,
|
|
474
|
-
newDoc: null,
|
|
475
|
-
diffs: [],
|
|
476
|
-
hasChanges: false
|
|
477
|
-
};
|
|
478
|
-
}
|
|
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
|
+
};
|
|
479
519
|
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 将路径转换为ProseMirror位置
|
|
523
|
+
* @param {Array} path - 节点路径
|
|
524
|
+
* @param {Object} doc - ProseMirror文档
|
|
525
|
+
* @returns {number} 文档位置
|
|
526
|
+
*/
|
|
480
527
|
export function pathToPos(path, doc) {
|
|
481
|
-
if (!path || path.length === 0) {
|
|
482
|
-
return 0;
|
|
483
|
-
}
|
|
484
|
-
if (!doc) {
|
|
485
|
-
throw new Error('文档节点不能为空');
|
|
486
|
-
}
|
|
487
528
|
var pos = 0;
|
|
488
529
|
var current = doc;
|
|
489
530
|
for (var i = 0; i < path.length; i++) {
|
|
490
|
-
var _current$
|
|
531
|
+
var _current$childCount;
|
|
491
532
|
var index = path[i];
|
|
492
|
-
var contentStartOffset =
|
|
533
|
+
var contentStartOffset = current.type.name === 'doc' ? 0 : 1;
|
|
493
534
|
var resolvedPos = pos + contentStartOffset;
|
|
494
535
|
var childCount = (_current$childCount = current.childCount) !== null && _current$childCount !== void 0 ? _current$childCount : 0;
|
|
495
536
|
var effectiveIndex = Math.min(index, childCount);
|
|
496
537
|
for (var j = 0; j < effectiveIndex; j++) {
|
|
497
538
|
var child = current.child(j);
|
|
498
|
-
if (!child) {
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
539
|
resolvedPos += child.nodeSize;
|
|
502
540
|
}
|
|
503
541
|
pos = resolvedPos;
|
|
504
542
|
if (index < childCount) {
|
|
505
543
|
current = current.child(index);
|
|
506
|
-
if (!current) {
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
544
|
} else {
|
|
545
|
+
// Path points past the end in this doc (likely a deletion). Stop descending
|
|
510
546
|
break;
|
|
511
547
|
}
|
|
512
548
|
}
|