@ctzhian/tiptap 2.11.3 → 2.11.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.
@@ -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
- // 简单的 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
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
- // SSR 和客户端首次渲染时使用 simpleSanitize,确保一致性
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, anchorRef.current]);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctzhian/tiptap",
3
- "version": "2.11.3",
3
+ "version": "2.11.4",
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": {