@ctzhian/tiptap 2.11.3 → 2.11.5
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.
|
@@ -8,35 +8,30 @@ import { EditLineIcon } from "../../../component/Icons";
|
|
|
8
8
|
import { Box, Stack, Tooltip } from "@mui/material";
|
|
9
9
|
import { MarkViewContent } from "@tiptap/react";
|
|
10
10
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import sanitizeHtml from 'sanitize-html';
|
|
11
12
|
import EditPopover from "./EditPopover";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// DOMPurify 配置(客户端使用)
|
|
35
|
-
var DOMPURIFY_CONFIG = {
|
|
36
|
-
// 允许基本的格式化标签
|
|
37
|
-
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'b', 'i', 'u', 's', 'strike', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'span', 'div', 'blockquote', 'code', 'pre'],
|
|
38
|
-
ALLOWED_ATTR: ['href', 'title', 'target', 'style', 'class'],
|
|
39
|
-
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
|
13
|
+
var SANITIZE_CONFIG = {
|
|
14
|
+
allowedTags: ['p', 'br', 'strong', 'em', 'b', 'i', 'u', 's', 'strike', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'span', 'div', 'blockquote', 'code', 'pre'],
|
|
15
|
+
allowedAttributes: {
|
|
16
|
+
a: ['href', 'title', 'target'],
|
|
17
|
+
span: ['style', 'class'],
|
|
18
|
+
div: ['style', 'class'],
|
|
19
|
+
p: ['style', 'class'],
|
|
20
|
+
'*': ['class']
|
|
21
|
+
},
|
|
22
|
+
allowedStyles: {
|
|
23
|
+
'*': {
|
|
24
|
+
color: [/^#[0-9A-Fa-f]{3,6}$/, /^rgb/, /^rgba/],
|
|
25
|
+
'text-align': [/^left$/, /^right$/, /^center$/, /^justify$/],
|
|
26
|
+
'font-size': [/^\d+(?:px|em|rem|%)$/],
|
|
27
|
+
'font-weight': [/^\d+$/, /^normal$/, /^bold$/]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
disallowedTagsMode: 'discard',
|
|
31
|
+
allowedSchemes: ['http', 'https', 'mailto'],
|
|
32
|
+
allowedSchemesByTag: {
|
|
33
|
+
a: ['http', 'https', 'mailto']
|
|
34
|
+
}
|
|
40
35
|
};
|
|
41
36
|
var TooltipView = function TooltipView(_ref) {
|
|
42
37
|
var mark = _ref.mark,
|
|
@@ -55,72 +50,10 @@ var TooltipView = function TooltipView(_ref) {
|
|
|
55
50
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
56
51
|
isMobile = _useState6[0],
|
|
57
52
|
setIsMobile = _useState6[1];
|
|
58
|
-
var _useState7 = useState(false),
|
|
59
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
60
|
-
isHydrated = _useState8[0],
|
|
61
|
-
setIsHydrated = _useState8[1];
|
|
62
53
|
var anchorRef = useRef(null);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
var initialSanitized = useMemo(function () {
|
|
66
|
-
return simpleSanitize(rawTooltip);
|
|
54
|
+
var tooltip = useMemo(function () {
|
|
55
|
+
return sanitizeHtml(rawTooltip, SANITIZE_CONFIG).trim();
|
|
67
56
|
}, [rawTooltip]);
|
|
68
|
-
var _useState9 = useState(initialSanitized),
|
|
69
|
-
_useState10 = _slicedToArray(_useState9, 2),
|
|
70
|
-
tooltip = _useState10[0],
|
|
71
|
-
setTooltip = _useState10[1];
|
|
72
|
-
|
|
73
|
-
// 检测是否在客户端环境(hydration 后)
|
|
74
|
-
useEffect(function () {
|
|
75
|
-
setIsHydrated(true);
|
|
76
|
-
}, []);
|
|
77
|
-
|
|
78
|
-
// 延迟导入 DOMPurify,避免在 SSR 环境下加载 jsdom
|
|
79
|
-
// 只在客户端环境下动态导入,确保 SSR 时不会触发 jsdom 初始化
|
|
80
|
-
useEffect(function () {
|
|
81
|
-
// SSR 环境下直接返回,使用 simpleSanitize 的结果
|
|
82
|
-
if (typeof window === 'undefined' || !isHydrated) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 如果内容为空,不需要 sanitize
|
|
87
|
-
if (!rawTooltip.trim()) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 客户端环境下异步导入 isomorphic-dompurify
|
|
92
|
-
// 使用动态 import() 确保在 SSR 构建时不会被打包进去
|
|
93
|
-
var cancelled = false;
|
|
94
|
-
import('isomorphic-dompurify').then(function (DOMPurify) {
|
|
95
|
-
// 检查组件是否已卸载
|
|
96
|
-
if (cancelled) return;
|
|
97
|
-
|
|
98
|
-
// 使用 DOMPurify 进行完整的 HTML sanitize
|
|
99
|
-
var sanitized = DOMPurify.default.sanitize(rawTooltip, DOMPURIFY_CONFIG).trim();
|
|
100
|
-
|
|
101
|
-
// 更新 tooltip(使用函数式更新避免依赖 tooltip 状态)
|
|
102
|
-
setTooltip(function (prevTooltip) {
|
|
103
|
-
// 只在内容发生变化时更新,避免不必要的重渲染
|
|
104
|
-
return sanitized !== prevTooltip ? sanitized : prevTooltip;
|
|
105
|
-
});
|
|
106
|
-
}).catch(function (error) {
|
|
107
|
-
// 如果导入失败(例如在某些 SSR 环境下),使用简单清理
|
|
108
|
-
if (!cancelled) {
|
|
109
|
-
console.warn('Failed to load DOMPurify, using simple sanitize:', error);
|
|
110
|
-
// 使用函数式更新
|
|
111
|
-
setTooltip(function (prevTooltip) {
|
|
112
|
-
var fallbackSanitized = simpleSanitize(rawTooltip);
|
|
113
|
-
return fallbackSanitized !== prevTooltip ? fallbackSanitized : prevTooltip;
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// 清理函数:组件卸载时取消异步操作
|
|
119
|
-
return function () {
|
|
120
|
-
cancelled = true;
|
|
121
|
-
};
|
|
122
|
-
}, [rawTooltip, isHydrated]); // 依赖 rawTooltip 和 isHydrated
|
|
123
|
-
|
|
124
57
|
var handleEditClick = function handleEditClick(e) {
|
|
125
58
|
e.stopPropagation();
|
|
126
59
|
setOpen(true);
|
|
@@ -156,7 +89,7 @@ var TooltipView = function TooltipView(_ref) {
|
|
|
156
89
|
if (isEditable && tooltip === '' && !isSelectionEmpty && anchorRef.current) {
|
|
157
90
|
setOpen(true);
|
|
158
91
|
}
|
|
159
|
-
}, [tooltip, isEditable
|
|
92
|
+
}, [tooltip, isEditable]);
|
|
160
93
|
var isMobileReadonly = !isEditable && isMobile;
|
|
161
94
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Tooltip, {
|
|
162
95
|
arrow: true,
|
|
@@ -175,9 +175,19 @@ export var InlineLinkExtension = Node.create({
|
|
|
175
175
|
return [{
|
|
176
176
|
tag: 'a',
|
|
177
177
|
getAttrs: function getAttrs(dom) {
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
var
|
|
178
|
+
var element = dom;
|
|
179
|
+
var href = element.getAttribute('href');
|
|
180
|
+
var dataType = element.getAttribute('type');
|
|
181
|
+
var download = element.getAttribute('download');
|
|
182
|
+
|
|
183
|
+
// 如果 <a> 标签内部包含图片,则视为"图片链接",不解析为 inlineLink,
|
|
184
|
+
// 让内部的 <img> 节点按普通图片逻辑处理,避免丢失图片和文本。
|
|
185
|
+
var hasImageChild = Array.from(element.childNodes).some(function (node) {
|
|
186
|
+
return node.nodeType === 1 && node.tagName.toLowerCase() === 'img';
|
|
187
|
+
});
|
|
188
|
+
if (hasImageChild) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
181
191
|
|
|
182
192
|
// 如果存在 download 属性,认为是附件,不解析为链接
|
|
183
193
|
if (download !== null) {
|
|
@@ -200,12 +210,12 @@ export var InlineLinkExtension = Node.create({
|
|
|
200
210
|
}
|
|
201
211
|
return {
|
|
202
212
|
href: href,
|
|
203
|
-
target:
|
|
204
|
-
class:
|
|
205
|
-
rel:
|
|
206
|
-
title:
|
|
207
|
-
type:
|
|
208
|
-
download:
|
|
213
|
+
target: element.getAttribute('target') || _this.options.HTMLAttributes.target,
|
|
214
|
+
class: element.getAttribute('class') || _this.options.HTMLAttributes.class,
|
|
215
|
+
rel: element.getAttribute('rel'),
|
|
216
|
+
title: element.textContent || element.getAttribute('title'),
|
|
217
|
+
type: element.getAttribute('type') || 'icon',
|
|
218
|
+
download: element.getAttribute('download')
|
|
209
219
|
};
|
|
210
220
|
}
|
|
211
221
|
}];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctzhian/tiptap",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.5",
|
|
4
4
|
"description": "基于 Tiptap 二次开发的编辑器组件",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"@types/diff-match-patch": "^1.0.36",
|
|
67
67
|
"@types/react": "^18.0.0",
|
|
68
68
|
"@types/react-dom": "^18.0.0",
|
|
69
|
+
"@types/sanitize-html": "^2.16.0",
|
|
69
70
|
"@umijs/lint": "^4.0.0",
|
|
70
71
|
"dumi": "^2.4.21",
|
|
71
72
|
"eslint": "^8.23.0",
|
|
@@ -118,7 +119,6 @@
|
|
|
118
119
|
"core-js": "^3.46.0",
|
|
119
120
|
"diff-match-patch": "^1.0.5",
|
|
120
121
|
"highlight.js": "^11.11.1",
|
|
121
|
-
"isomorphic-dompurify": "^2.34.0",
|
|
122
122
|
"jszip": "^3.10.1",
|
|
123
123
|
"katex": "^0.16.22",
|
|
124
124
|
"linkifyjs": "^4.3.2",
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
"react-colorful": "^5.6.1",
|
|
129
129
|
"react-image-crop": "^11.0.10",
|
|
130
130
|
"react-photo-view": "^1.2.7",
|
|
131
|
+
"sanitize-html": "^2.17.0",
|
|
131
132
|
"uuid": "^11.1.0"
|
|
132
133
|
},
|
|
133
134
|
"scripts": {
|