@ctzhian/tiptap 3.0.2 → 3.0.4
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 +4 -0
- package/dist/EditorDiff/index.d.ts +3 -1
- package/dist/EditorDiff/index.js +3 -1
- package/dist/EditorMarkdown/demo.js +1 -1
- package/dist/extension/component/UploadProgress/index.d.ts +1 -1
- package/dist/extension/extension/StructuredDiff.d.ts +4 -2
- package/dist/extension/extension/StructuredDiff.js +19 -6
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +3 -2
- package/dist/extension/node/FileHandler.d.ts +1 -1
- package/dist/extension/node/Link/index.js +3 -2
- package/dist/type/index.d.ts +3 -0
- package/dist/util/decorations.d.ts +3 -2
- package/dist/util/decorations.js +108 -44
- package/dist/util/structuredDiff.d.ts +8 -2
- package/dist/util/structuredDiff.js +165 -36
- package/package.json +2 -1
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;
|
package/dist/EditorDiff/index.js
CHANGED
|
@@ -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 () {
|
|
@@ -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(":wolf:\n\n本文用以记录 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"),
|
|
16
|
+
var _useState = useState(":wolf:\n\n[]()\n\n本文用以记录 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];
|
|
@@ -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 {
|
|
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<
|
|
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
|
|
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;
|
package/dist/extension/index.js
CHANGED
|
@@ -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)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { UploadFunction } from "../../type";
|
|
2
2
|
export declare const FileHandlerExtension: (props: {
|
|
3
3
|
onUpload?: UploadFunction;
|
|
4
|
-
}) => import("@tiptap/core").Extension<Omit<import("@tiptap/extension-file-handler").FileHandlePluginOptions, "
|
|
4
|
+
}) => import("@tiptap/core").Extension<Omit<import("@tiptap/extension-file-handler").FileHandlePluginOptions, "editor" | "key">, any>;
|
|
@@ -381,12 +381,13 @@ export var InlineLinkExtension = Node.create({
|
|
|
381
381
|
},
|
|
382
382
|
markdownTokenName: 'link',
|
|
383
383
|
parseMarkdown: function parseMarkdown(token, helpers) {
|
|
384
|
+
var text = token.text || '';
|
|
384
385
|
return helpers.createNode('inlineLink', {
|
|
385
386
|
href: token.href || '',
|
|
386
|
-
title:
|
|
387
|
+
title: text,
|
|
387
388
|
type: 'icon',
|
|
388
389
|
target: '_blank'
|
|
389
|
-
}, [helpers.createTextNode(
|
|
390
|
+
}, text ? [helpers.createTextNode(text)] : []);
|
|
390
391
|
},
|
|
391
392
|
renderMarkdown: function renderMarkdown(node) {
|
|
392
393
|
var _node$attrs, _node$attrs2, _node$attrs3;
|
package/dist/type/index.d.ts
CHANGED
|
@@ -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文档
|
package/dist/util/decorations.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
280
|
+
_iterator.e(err);
|
|
217
281
|
} finally {
|
|
218
|
-
|
|
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
|
|
289
|
-
|
|
352
|
+
var _iterator2 = _createForOfIteratorHelper(node.content),
|
|
353
|
+
_step2;
|
|
290
354
|
try {
|
|
291
|
-
for (
|
|
292
|
-
var child =
|
|
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
|
-
|
|
360
|
+
_iterator2.e(err);
|
|
297
361
|
} finally {
|
|
298
|
-
|
|
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
|
|
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
|
|
34
|
-
return
|
|
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(
|
|
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 (
|
|
97
|
-
var
|
|
98
|
-
operation =
|
|
99
|
-
text =
|
|
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,
|
|
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
|
|
237
|
-
|
|
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
|
|
363
|
-
|
|
364
|
-
|
|
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,
|
|
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 (
|
|
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.
|
|
3
|
+
"version": "3.0.4",
|
|
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}\"",
|