@ctzhian/tiptap 2.11.2 → 2.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,13 +7,41 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
7
7
|
import { EditLineIcon } from "../../../component/Icons";
|
|
8
8
|
import { Box, Stack, Tooltip } from "@mui/material";
|
|
9
9
|
import { MarkViewContent } from "@tiptap/react";
|
|
10
|
-
import
|
|
11
|
-
import React, { useEffect, useRef, useState } from "react";
|
|
10
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
12
11
|
import EditPopover from "./EditPopover";
|
|
12
|
+
|
|
13
|
+
// 简单的 HTML 清理函数,用于 SSR 环境(不依赖 jsdom)
|
|
14
|
+
// 确保 SSR 和客户端首次渲染一致,避免 hydration mismatch
|
|
15
|
+
var simpleSanitize = function simpleSanitize(html) {
|
|
16
|
+
if (!html) return '';
|
|
17
|
+
return html
|
|
18
|
+
// 移除 script 标签
|
|
19
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
20
|
+
// 移除 style 标签(防止 CSS 注入)
|
|
21
|
+
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
|
|
22
|
+
// 移除 iframe 标签
|
|
23
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
24
|
+
// 移除 object、embed、link 等危险标签
|
|
25
|
+
.replace(/<(object|embed|link|meta|base)\b[^<]*(?:(?!<\/\1>)<[^<]*)*<\/\1>/gi, '')
|
|
26
|
+
// 移除所有事件处理器(onclick, onerror 等)
|
|
27
|
+
.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '')
|
|
28
|
+
// 移除 javascript: 协议
|
|
29
|
+
.replace(/javascript:/gi, '')
|
|
30
|
+
// 移除 data: URL(防止 base64 注入)
|
|
31
|
+
.replace(/data:\s*[^;]*;base64[^"'\s]*/gi, '').trim();
|
|
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
|
|
40
|
+
};
|
|
13
41
|
var TooltipView = function TooltipView(_ref) {
|
|
14
42
|
var mark = _ref.mark,
|
|
15
43
|
editor = _ref.editor;
|
|
16
|
-
var
|
|
44
|
+
var rawTooltip = mark.attrs.tooltip || '';
|
|
17
45
|
var isEditable = editor.isEditable;
|
|
18
46
|
var _useState = useState(false),
|
|
19
47
|
_useState2 = _slicedToArray(_useState, 2),
|
|
@@ -27,7 +55,72 @@ var TooltipView = function TooltipView(_ref) {
|
|
|
27
55
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
28
56
|
isMobile = _useState6[0],
|
|
29
57
|
setIsMobile = _useState6[1];
|
|
58
|
+
var _useState7 = useState(false),
|
|
59
|
+
_useState8 = _slicedToArray(_useState7, 2),
|
|
60
|
+
isHydrated = _useState8[0],
|
|
61
|
+
setIsHydrated = _useState8[1];
|
|
30
62
|
var anchorRef = useRef(null);
|
|
63
|
+
|
|
64
|
+
// SSR 和客户端首次渲染时使用 simpleSanitize,确保一致性
|
|
65
|
+
var initialSanitized = useMemo(function () {
|
|
66
|
+
return simpleSanitize(rawTooltip);
|
|
67
|
+
}, [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
|
+
|
|
31
124
|
var handleEditClick = function handleEditClick(e) {
|
|
32
125
|
e.stopPropagation();
|
|
33
126
|
setOpen(true);
|