djm-ui 0.67.4 → 0.72.0
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/CHANGELOG.md +103 -0
- package/README.md +38 -2
- package/dist/djm-ui.cjs +1032 -347
- package/dist/djm-ui.cjs.map +4 -4
- package/dist/djm-ui.css +752 -17
- package/dist/djm-ui.esm.js +1105 -420
- package/dist/djm-ui.esm.js.map +4 -4
- package/dist/djm-ui.esm.min.js +373 -227
- package/dist/djm-ui.min.cjs +373 -227
- package/dist/djm-ui.min.css +2 -2
- package/dist/djm-ui.umd.js +944 -259
- package/dist/djm-ui.umd.min.js +373 -227
- package/dist/mcp/manifest.json +12195 -0
- package/dist/mcp/server.js +90 -0
- package/package.json +12 -4
- package/src/components/icon/index.js +13 -1
- package/src/components/rich-editor/index.js +724 -61
- package/src/index.js +1 -1
- package/src/locale/en-US.js +33 -1
- package/src/locale/zh-CN.js +33 -1
- package/src/styles/base.css +2 -3
- package/src/styles/components/button.css +2 -1
- package/src/styles/components/console.css +5 -0
- package/src/styles/components/content.css +517 -0
- package/src/styles/components/form.css +5 -0
- package/src/styles/components/layout.css +326 -0
- package/src/styles/components/media-picker.css +3 -3
- package/src/styles/components/menu.css +2 -1
- package/src/styles/components/org-picker.css +1 -1
- package/src/styles/components/responsive.css +86 -0
- package/src/styles/components/rich-editor.css +206 -7
- package/src/styles/components/skin.css +54 -0
- package/src/styles/components/template.css +5 -0
- package/src/styles/index.css +1 -0
- package/src/styles/theme-dark.css +2 -0
- package/src/styles/tokens.css +8 -0
- package/src/theme/index.js +40 -0
- package/src/utils/sanitize-html.js +44 -6
- package/types/index.d.ts +8 -0
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
/* ---------------- 富文本编辑器 ---------------- */
|
|
4
4
|
|
|
5
5
|
.djm-rich-editor {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
6
8
|
border: 1px solid var(--djm-border-strong);
|
|
7
9
|
border-radius: 4px;
|
|
8
10
|
overflow: hidden;
|
|
@@ -14,11 +16,26 @@
|
|
|
14
16
|
box-shadow: var(--djm-ring);
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/* 全屏:铺满视口,内容区自己滚动 */
|
|
20
|
+
.djm-rich-editor.djm-is-fullscreen {
|
|
21
|
+
position: fixed;
|
|
22
|
+
inset: 0;
|
|
23
|
+
z-index: 1200;
|
|
24
|
+
border: 0;
|
|
25
|
+
border-radius: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.djm-rich-editor.djm-is-fullscreen .djm-rich-editor-content,
|
|
29
|
+
.djm-rich-editor.djm-is-fullscreen .djm-rich-editor-source {
|
|
30
|
+
flex: 1;
|
|
31
|
+
max-height: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
.djm-rich-editor-toolbar {
|
|
18
35
|
display: flex;
|
|
19
36
|
align-items: center;
|
|
20
37
|
flex-wrap: wrap;
|
|
21
|
-
gap:
|
|
38
|
+
gap: 2px;
|
|
22
39
|
padding: 5px 6px;
|
|
23
40
|
border-bottom: 1px solid var(--djm-border);
|
|
24
41
|
background: var(--djm-surface-2);
|
|
@@ -26,9 +43,9 @@
|
|
|
26
43
|
|
|
27
44
|
.djm-rich-editor-group {
|
|
28
45
|
display: flex;
|
|
29
|
-
gap:
|
|
30
|
-
padding-right:
|
|
31
|
-
margin-right:
|
|
46
|
+
gap: 1px;
|
|
47
|
+
padding-right: 4px;
|
|
48
|
+
margin-right: 2px;
|
|
32
49
|
border-right: 1px solid var(--djm-border);
|
|
33
50
|
}
|
|
34
51
|
|
|
@@ -36,10 +53,92 @@
|
|
|
36
53
|
border-right: 0;
|
|
37
54
|
}
|
|
38
55
|
|
|
56
|
+
/* 段落格式 / 字号下拉 */
|
|
57
|
+
.djm-rich-editor-select {
|
|
58
|
+
height: 26px;
|
|
59
|
+
padding: 0 2px;
|
|
60
|
+
border: 1px solid var(--djm-border);
|
|
61
|
+
border-radius: 3px;
|
|
62
|
+
background: var(--djm-surface);
|
|
63
|
+
color: var(--djm-text-2);
|
|
64
|
+
font: inherit;
|
|
65
|
+
font-size: 11.5px;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.djm-rich-editor-select-size {
|
|
70
|
+
width: 64px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.djm-rich-editor-select:focus-visible {
|
|
74
|
+
outline: 2px solid var(--djm-brand-500);
|
|
75
|
+
outline-offset: -1px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.djm-rich-editor-select:disabled {
|
|
79
|
+
opacity: 0.5;
|
|
80
|
+
cursor: not-allowed;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* 颜色按钮:字母底部带当前色,背景色按钮直接把颜色铺在字母上 */
|
|
84
|
+
.djm-rich-editor-color {
|
|
85
|
+
position: relative;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.djm-rich-editor-letter {
|
|
89
|
+
display: inline-block;
|
|
90
|
+
padding-bottom: 1px;
|
|
91
|
+
border-bottom: 2px solid var(--djm-text);
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
line-height: 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.djm-rich-editor-letter-bg {
|
|
98
|
+
padding: 0 3px;
|
|
99
|
+
border-bottom: 0;
|
|
100
|
+
border-radius: 2px;
|
|
101
|
+
color: var(--djm-text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.djm-rich-editor-panel {
|
|
105
|
+
position: absolute;
|
|
106
|
+
top: calc(100% + 4px);
|
|
107
|
+
left: 0;
|
|
108
|
+
z-index: 5;
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: repeat(4, 20px);
|
|
111
|
+
gap: 4px;
|
|
112
|
+
padding: 6px;
|
|
113
|
+
border: 1px solid var(--djm-border);
|
|
114
|
+
border-radius: 6px;
|
|
115
|
+
background: var(--djm-surface);
|
|
116
|
+
box-shadow: var(--djm-shadow-md);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.djm-rich-editor-swatch {
|
|
120
|
+
width: 20px;
|
|
121
|
+
height: 20px;
|
|
122
|
+
padding: 0;
|
|
123
|
+
border: 1px solid var(--djm-border);
|
|
124
|
+
border-radius: 4px;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.djm-rich-editor-swatch:hover {
|
|
129
|
+
border-color: var(--djm-brand-500);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* 素材库按钮:媒体选择器默认是块级 span,塞进工具栏里要收成行内 */
|
|
133
|
+
.djm-rich-editor-picker {
|
|
134
|
+
display: inline-flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
}
|
|
137
|
+
|
|
39
138
|
.djm-rich-editor-btn {
|
|
40
|
-
min-width:
|
|
139
|
+
min-width: 24px;
|
|
41
140
|
height: 26px;
|
|
42
|
-
padding: 0
|
|
141
|
+
padding: 0 4px;
|
|
43
142
|
display: inline-flex;
|
|
44
143
|
align-items: center;
|
|
45
144
|
justify-content: center;
|
|
@@ -122,11 +221,93 @@
|
|
|
122
221
|
color: var(--djm-text-3);
|
|
123
222
|
}
|
|
124
223
|
|
|
125
|
-
.djm-rich-editor-content
|
|
224
|
+
.djm-rich-editor-content h1,
|
|
225
|
+
.djm-rich-editor-content h2,
|
|
226
|
+
.djm-rich-editor-content h3 {
|
|
126
227
|
margin: 0 0 8px;
|
|
228
|
+
color: var(--djm-text);
|
|
229
|
+
line-height: 1.5;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.djm-rich-editor-content h1 {
|
|
233
|
+
font-size: 20px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.djm-rich-editor-content h2 {
|
|
127
237
|
font-size: 16px;
|
|
128
238
|
}
|
|
129
239
|
|
|
240
|
+
.djm-rich-editor-content h3 {
|
|
241
|
+
font-size: 14px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.djm-rich-editor-content blockquote {
|
|
245
|
+
margin: 0 0 8px;
|
|
246
|
+
padding: 4px 10px;
|
|
247
|
+
border-left: 3px solid var(--djm-brand-500);
|
|
248
|
+
background: var(--djm-surface-2);
|
|
249
|
+
color: var(--djm-text-2);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.djm-rich-editor-content pre {
|
|
253
|
+
margin: 0 0 8px;
|
|
254
|
+
padding: 10px 12px;
|
|
255
|
+
overflow: auto;
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
background: var(--djm-surface-3);
|
|
258
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
259
|
+
font-size: 12px;
|
|
260
|
+
line-height: 1.7;
|
|
261
|
+
white-space: pre-wrap;
|
|
262
|
+
word-break: break-word;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.djm-rich-editor-content code {
|
|
266
|
+
padding: 1px 4px;
|
|
267
|
+
border-radius: 3px;
|
|
268
|
+
background: var(--djm-surface-3);
|
|
269
|
+
color: var(--djm-brand-700);
|
|
270
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
271
|
+
font-size: 12px;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.djm-rich-editor-content pre code {
|
|
275
|
+
padding: 0;
|
|
276
|
+
background: none;
|
|
277
|
+
color: inherit;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.djm-rich-editor-content hr {
|
|
281
|
+
margin: 12px 0;
|
|
282
|
+
border: 0;
|
|
283
|
+
border-top: 1px solid var(--djm-border);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* 图片:单图、多图统一限宽,避免撑破内容区 */
|
|
287
|
+
.djm-rich-editor-content img {
|
|
288
|
+
max-width: 100%;
|
|
289
|
+
height: auto;
|
|
290
|
+
border-radius: 4px;
|
|
291
|
+
vertical-align: bottom;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* 上传插入的图片之间留一点间距,多图并排时不粘在一起 */
|
|
295
|
+
.djm-rich-editor-img {
|
|
296
|
+
margin: 2px 4px 2px 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* 上传中的占位块,上传完成后被图片替换 */
|
|
300
|
+
.djm-rich-editor-uploading {
|
|
301
|
+
display: inline-flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
padding: 4px 10px;
|
|
304
|
+
border: 1px dashed var(--djm-border-strong);
|
|
305
|
+
border-radius: 4px;
|
|
306
|
+
background: var(--djm-surface-2);
|
|
307
|
+
color: var(--djm-text-3);
|
|
308
|
+
font-size: 12px;
|
|
309
|
+
}
|
|
310
|
+
|
|
130
311
|
.djm-rich-editor-content p {
|
|
131
312
|
margin: 0 0 8px;
|
|
132
313
|
}
|
|
@@ -154,6 +335,24 @@
|
|
|
154
335
|
line-height: 1.7;
|
|
155
336
|
}
|
|
156
337
|
|
|
338
|
+
/* 底部状态:上传提示与字数 */
|
|
339
|
+
.djm-rich-editor-foot {
|
|
340
|
+
display: flex;
|
|
341
|
+
align-items: center;
|
|
342
|
+
justify-content: flex-end;
|
|
343
|
+
gap: 12px;
|
|
344
|
+
padding: 4px 12px;
|
|
345
|
+
border-top: 1px solid var(--djm-border);
|
|
346
|
+
background: var(--djm-surface-2);
|
|
347
|
+
color: var(--djm-text-3);
|
|
348
|
+
font-size: 11px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* 文件选择框只作为上传入口,不显示 */
|
|
352
|
+
.djm-rich-editor-file {
|
|
353
|
+
display: none;
|
|
354
|
+
}
|
|
355
|
+
|
|
157
356
|
.djm-editor-bar {
|
|
158
357
|
display: flex;
|
|
159
358
|
align-items: center;
|
|
@@ -18,6 +18,60 @@
|
|
|
18
18
|
color: var(--djm-text-3);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/* 预设方案:一排小卡片,配色条直接预览效果 */
|
|
22
|
+
.djm-skin-presets {
|
|
23
|
+
display: grid;
|
|
24
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
25
|
+
gap: 6px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.djm-skin-preset {
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
gap: 5px;
|
|
32
|
+
padding: 5px;
|
|
33
|
+
border: 1px solid var(--djm-border);
|
|
34
|
+
border-radius: var(--djm-radius);
|
|
35
|
+
background: var(--djm-surface);
|
|
36
|
+
color: var(--djm-text-2);
|
|
37
|
+
font-size: 11.5px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.djm-skin-preset:hover {
|
|
42
|
+
border-color: var(--djm-border-strong);
|
|
43
|
+
color: var(--djm-text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.djm-skin-preset.djm-is-active {
|
|
47
|
+
border-color: var(--djm-brand-500);
|
|
48
|
+
box-shadow: var(--djm-ring);
|
|
49
|
+
color: var(--djm-brand-700);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.djm-skin-swatch {
|
|
53
|
+
height: 22px;
|
|
54
|
+
border: 1px solid var(--djm-border);
|
|
55
|
+
border-radius: 3px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.djm-skin-preset-label {
|
|
59
|
+
text-align: center;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.djm-skin-hint {
|
|
63
|
+
margin: 0;
|
|
64
|
+
font-size: 11.5px;
|
|
65
|
+
color: var(--djm-text-3);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.djm-skin-preset:focus-visible,
|
|
69
|
+
.djm-skin-seg-btn:focus-visible,
|
|
70
|
+
.djm-skin-color:focus-visible {
|
|
71
|
+
outline: 2px solid var(--djm-brand-500);
|
|
72
|
+
outline-offset: 1px;
|
|
73
|
+
}
|
|
74
|
+
|
|
21
75
|
/* 分段按钮:用两个按钮表达互斥选择,避免额外角色语义 */
|
|
22
76
|
.djm-skin-seg {
|
|
23
77
|
display: flex;
|
|
@@ -194,6 +194,11 @@
|
|
|
194
194
|
color: #fff;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/* 暗色下品牌色阶整体提亮,白字压不住,单独换深蓝渐变 */
|
|
198
|
+
[data-djm-theme="dark"] .djm-login-brand {
|
|
199
|
+
background: linear-gradient(150deg, #0d2236, #123c5f 62%, #17527f);
|
|
200
|
+
}
|
|
201
|
+
|
|
197
202
|
.djm-login-brand-inner {
|
|
198
203
|
max-width: 380px;
|
|
199
204
|
display: flex;
|
package/src/styles/index.css
CHANGED
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
@import './components/query-filter.css';
|
|
76
76
|
@import './components/filter-tags.css';
|
|
77
77
|
@import './components/media-picker.css';
|
|
78
|
+
@import './components/content.css';
|
|
78
79
|
@import './components/kanban.css';
|
|
79
80
|
@import './components/attachment-list.css';
|
|
80
81
|
@import './components/notification.css';
|
package/src/styles/tokens.css
CHANGED
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
:root {
|
|
16
16
|
color-scheme: light;
|
|
17
17
|
|
|
18
|
+
/* 字体族:宿主页面可通过覆盖这两个变量统一换字体 */
|
|
19
|
+
--djm-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
|
20
|
+
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
21
|
+
--djm-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
22
|
+
|
|
18
23
|
--djm-brand-50: #eef6fe;
|
|
19
24
|
--djm-brand-100: #dcebfb;
|
|
20
25
|
--djm-brand-200: #b9d7f6;
|
|
@@ -66,6 +71,9 @@
|
|
|
66
71
|
/* 彩色底上的文字(两主题均为白色) */
|
|
67
72
|
--djm-text-inverse: #fff;
|
|
68
73
|
|
|
74
|
+
/* 文字提示的深底:暗色主题改用略亮的灰,避免和页面背景糊在一起 */
|
|
75
|
+
--djm-tooltip-bg: #1f2a37;
|
|
76
|
+
|
|
69
77
|
/* 盖在图片等媒体内容上的蒙层,不随主题变化 */
|
|
70
78
|
--djm-media-mask: rgba(9, 20, 33, 0.55);
|
|
71
79
|
|
package/src/theme/index.js
CHANGED
|
@@ -139,6 +139,46 @@ export function setPrimaryColor (hex) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/* 相对亮度与对比度:侧栏选中态按色相自动找合适的明度 */
|
|
143
|
+
function luminance (hex) {
|
|
144
|
+
const text = String(hex).replace('#', '')
|
|
145
|
+
const rgb = [0, 2, 4].map((i) => parseInt(text.slice(i, i + 2), 16) / 255)
|
|
146
|
+
const [r, g, b] = rgb.map((v) => (v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4))
|
|
147
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function contrastRatio (a, b) {
|
|
151
|
+
const l1 = luminance(a)
|
|
152
|
+
const l2 = luminance(b)
|
|
153
|
+
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 侧栏单独配色时用:按主色生成一套色阶
|
|
158
|
+
* 侧栏可能和页面主题反向(浅色页 + 深色侧栏),所以由调用方指定明暗
|
|
159
|
+
*/
|
|
160
|
+
export function sidebarShades (hex, dark) {
|
|
161
|
+
const shades = buildShades(hex || '#1a6fc4', dark)
|
|
162
|
+
if (dark) {
|
|
163
|
+
const { h, s } = hexToHsl(hex || '#1a6fc4')
|
|
164
|
+
const sat = Math.min(Math.max(s, 20), 92)
|
|
165
|
+
const bgSat = Math.min(78, sat * 0.85)
|
|
166
|
+
// 选中文字固定在亮档,背景从 36% 明度往下压,直到对比度过 4.5
|
|
167
|
+
// 这样青碧、橙、墨绿不会再选出一团看不清的深色
|
|
168
|
+
const text = hslToHex(h, Math.min(90, sat), 86)
|
|
169
|
+
let bgL = 36
|
|
170
|
+
let bg = hslToHex(h, bgSat, bgL)
|
|
171
|
+
while (bgL > 18 && contrastRatio(bg, text) < 4.6) {
|
|
172
|
+
bgL -= 2
|
|
173
|
+
bg = hslToHex(h, bgSat, bgL)
|
|
174
|
+
}
|
|
175
|
+
shades['--djm-sider-active'] = bg
|
|
176
|
+
shades['--djm-sider-active-hover'] = hslToHex(h, bgSat, Math.max(14, bgL - 4))
|
|
177
|
+
shades['--djm-sider-active-text'] = text
|
|
178
|
+
}
|
|
179
|
+
return shades
|
|
180
|
+
}
|
|
181
|
+
|
|
142
182
|
export function resetPrimaryColor () {
|
|
143
183
|
setPrimaryColor('')
|
|
144
184
|
}
|
|
@@ -17,7 +17,13 @@ const TAGS = new Set([
|
|
|
17
17
|
])
|
|
18
18
|
|
|
19
19
|
// 通用属性
|
|
20
|
-
const GLOBAL_ATTRS = new Set(['class', 'title'])
|
|
20
|
+
const GLOBAL_ATTRS = new Set(['class', 'title', 'style'])
|
|
21
|
+
|
|
22
|
+
// 富文本用到的内联样式白名单:只放行排版类属性,避免靠样式做定位或引外部资源
|
|
23
|
+
const STYLE_PROPS = new Set([
|
|
24
|
+
'color', 'background-color', 'font-size', 'font-weight', 'font-style',
|
|
25
|
+
'text-decoration', 'text-align', 'text-indent', 'line-height'
|
|
26
|
+
])
|
|
21
27
|
|
|
22
28
|
// 各标签额外允许的属性
|
|
23
29
|
const TAG_ATTRS = {
|
|
@@ -28,13 +34,39 @@ const TAG_ATTRS = {
|
|
|
28
34
|
td: new Set(['colspan', 'rowspan'])
|
|
29
35
|
}
|
|
30
36
|
|
|
37
|
+
// 逐条过滤样式声明,丢掉白名单之外的属性与可疑取值
|
|
38
|
+
function cleanStyle (value) {
|
|
39
|
+
return String(value || '')
|
|
40
|
+
.split(';')
|
|
41
|
+
.map((item) => {
|
|
42
|
+
const index = item.indexOf(':')
|
|
43
|
+
if (index === -1) return ''
|
|
44
|
+
const name = item.slice(0, index).trim().toLowerCase()
|
|
45
|
+
const val = item.slice(index + 1).trim()
|
|
46
|
+
if (!STYLE_PROPS.has(name)) return ''
|
|
47
|
+
if (/url\s*\(|expression\s*\(|javascript:/i.test(val)) return ''
|
|
48
|
+
return name + ': ' + val
|
|
49
|
+
})
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join('; ')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 允许的协议:相对地址没有协议,直接放行
|
|
55
|
+
const SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel'])
|
|
56
|
+
|
|
31
57
|
function safeUrl (value, tag) {
|
|
32
58
|
// 浏览器解析 URL 时会忽略换行、制表符等控制字符,先去掉再判断协议
|
|
33
|
-
const url = String(value || '').replace(/[\u0000-\u0020]+/g, '')
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return true
|
|
59
|
+
const url = String(value || '').replace(/[\u0000-\u0020]+/g, '')
|
|
60
|
+
const scheme = url.match(/^([a-z][a-z0-9+.-]*):/i)
|
|
61
|
+
if (!scheme) return true
|
|
62
|
+
const name = scheme[1].toLowerCase()
|
|
63
|
+
if (SAFE_SCHEMES.has(name)) return true
|
|
64
|
+
// 图片额外允许 blob 与 data:image;data:text/html 这类一律拒绝
|
|
65
|
+
if (tag === 'img') {
|
|
66
|
+
if (name === 'blob') return true
|
|
67
|
+
if (name === 'data' && /^data:image\//i.test(url)) return true
|
|
68
|
+
}
|
|
69
|
+
return false
|
|
38
70
|
}
|
|
39
71
|
|
|
40
72
|
function cleanNode (node) {
|
|
@@ -63,6 +95,12 @@ function cleanNode (node) {
|
|
|
63
95
|
child.removeAttribute(attr.name)
|
|
64
96
|
return
|
|
65
97
|
}
|
|
98
|
+
if (name === 'style') {
|
|
99
|
+
const style = cleanStyle(attr.value)
|
|
100
|
+
if (style) child.setAttribute('style', style)
|
|
101
|
+
else child.removeAttribute('style')
|
|
102
|
+
return
|
|
103
|
+
}
|
|
66
104
|
if ((name === 'href' || name === 'src') && !safeUrl(attr.value, tag)) {
|
|
67
105
|
child.removeAttribute(attr.name)
|
|
68
106
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1117,6 +1117,7 @@ export const DjmTreeSelect: DefineComponent<{
|
|
|
1117
1117
|
|
|
1118
1118
|
export type DjmRichEditorEmits = {
|
|
1119
1119
|
'update:modelValue': (html: string) => void
|
|
1120
|
+
'upload-error': (payload: { file: File, message: string }) => void
|
|
1120
1121
|
}
|
|
1121
1122
|
|
|
1122
1123
|
export const DjmRichEditor: DefineComponent<{
|
|
@@ -1125,6 +1126,13 @@ export const DjmRichEditor: DefineComponent<{
|
|
|
1125
1126
|
minHeight?: number
|
|
1126
1127
|
sanitize?: boolean
|
|
1127
1128
|
disabled?: boolean
|
|
1129
|
+
toolbar?: 'simple' | 'basic' | 'full' | Array<'history' | 'format' | 'inline' | 'color' | 'align' | 'list' | 'insert' | 'view'>
|
|
1130
|
+
uploadImage?: ((file: File) => Promise<string | { url: string }>) | null
|
|
1131
|
+
media?: Array<string | { id?: string | number, url: string, name?: string }>
|
|
1132
|
+
accept?: string
|
|
1133
|
+
maxImageSize?: number
|
|
1134
|
+
multiple?: boolean
|
|
1135
|
+
showCount?: boolean
|
|
1128
1136
|
}, {}, {}, {}, {}, {}, {}, DjmRichEditorEmits>
|
|
1129
1137
|
|
|
1130
1138
|
/* ---------------- 业务组件 ---------------- */
|