djm-ui 0.67.3 → 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 +108 -0
- package/README.md +38 -2
- package/dist/djm-ui.cjs +1051 -356
- package/dist/djm-ui.cjs.map +4 -4
- package/dist/djm-ui.css +752 -17
- package/dist/djm-ui.esm.js +1127 -432
- package/dist/djm-ui.esm.js.map +4 -4
- package/dist/djm-ui.esm.min.js +375 -229
- package/dist/djm-ui.min.cjs +375 -229
- package/dist/djm-ui.min.css +2 -2
- package/dist/djm-ui.umd.js +967 -272
- package/dist/djm-ui.umd.min.js +375 -229
- 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/input/index.js +17 -6
- 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 +327 -1
- 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
package/dist/djm-ui.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! DJM UI v0.
|
|
1
|
+
/*! DJM UI v0.72.0 · MIT · 2026-10-26 */
|
|
2
2
|
"use strict";
|
|
3
3
|
var DjmUI = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1119,7 +1119,19 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1119
1119
|
activity: "M22 12h-4l-3 9L9 3l-3 9H2",
|
|
1120
1120
|
database: "M12 8c4.42 0 8-1.34 8-3s-3.58-3-8-3-8 1.34-8 3 3.58 3 8 3zM4 5v14c0 1.66 3.58 3 8 3s8-1.34 8-3V5M4 12c0 1.66 3.58 3 8 3s8-1.34 8-3",
|
|
1121
1121
|
sun: "M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42",
|
|
1122
|
-
moon: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"
|
|
1122
|
+
moon: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z",
|
|
1123
|
+
undo: "M3 10h11a5 5 0 1 1 0 10H8M3 10l4-4M3 10l4 4",
|
|
1124
|
+
redo: "M21 10H10a5 5 0 1 0 0 10h6M21 10l-4-4M21 10l-4 4",
|
|
1125
|
+
"align-left": "M4 6h16M4 12h10M4 18h13",
|
|
1126
|
+
"align-center": "M4 6h16M7 12h10M6 18h12",
|
|
1127
|
+
"align-right": "M4 6h16M10 12h10M7 18h13",
|
|
1128
|
+
"align-justify": "M4 6h16M4 12h16M4 18h16",
|
|
1129
|
+
list: "M9 6h11M9 12h11M9 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01",
|
|
1130
|
+
"list-ordered": "M10 6h10M10 12h10M10 18h10M4 6h1v4M4 12h2M4 16h2v2H4",
|
|
1131
|
+
outdent: "M4 6h16M10 12h10M10 18h10M8 9l-3 3 3 3",
|
|
1132
|
+
indent: "M4 6h16M10 12h10M10 18h10M5 9l3 3-3 3",
|
|
1133
|
+
unlink: "M10 13a5 5 0 0 0 7.5.5M14 11a5 5 0 0 0-7.5-.5M2 2l20 20",
|
|
1134
|
+
images: "M8 3h12v12M4 7h12v12H4zM7 15l3-3 3 3"
|
|
1123
1135
|
};
|
|
1124
1136
|
var icon_default = {
|
|
1125
1137
|
name: "DjmIcon",
|
|
@@ -1249,19 +1261,29 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1249
1261
|
this.focus();
|
|
1250
1262
|
},
|
|
1251
1263
|
focus() {
|
|
1264
|
+
this.clearStaleValue();
|
|
1252
1265
|
if (this.$refs.input) this.$refs.input.focus();
|
|
1253
1266
|
},
|
|
1267
|
+
onFocus(event) {
|
|
1268
|
+
this.clearStaleValue();
|
|
1269
|
+
this.$emit("focus", event);
|
|
1270
|
+
},
|
|
1254
1271
|
blur() {
|
|
1255
1272
|
if (this.$refs.input) this.$refs.input.blur();
|
|
1256
1273
|
}
|
|
1257
1274
|
},
|
|
1258
1275
|
mounted() {
|
|
1259
|
-
this.
|
|
1260
|
-
|
|
1276
|
+
this.staleTimers = [null, 200, 600, 1200, 2400].map((delay) => {
|
|
1277
|
+
if (delay === null) {
|
|
1278
|
+
this.clearStaleValue();
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
return setTimeout(() => this.clearStaleValue(), delay);
|
|
1282
|
+
});
|
|
1261
1283
|
window.addEventListener("pageshow", this.clearStaleValue);
|
|
1262
1284
|
},
|
|
1263
1285
|
beforeUnmount() {
|
|
1264
|
-
|
|
1286
|
+
this.staleTimers.forEach((timer) => clearTimeout(timer));
|
|
1265
1287
|
window.removeEventListener("pageshow", this.clearStaleValue);
|
|
1266
1288
|
},
|
|
1267
1289
|
template: `
|
|
@@ -1281,7 +1303,7 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1281
1303
|
@input="onInput"
|
|
1282
1304
|
@change="$emit('change', $event)"
|
|
1283
1305
|
@keyup.enter="$emit('enter', $event)"
|
|
1284
|
-
@focus="
|
|
1306
|
+
@focus="onFocus"
|
|
1285
1307
|
@blur="$emit('blur', $event)"
|
|
1286
1308
|
/>
|
|
1287
1309
|
<button
|
|
@@ -1311,7 +1333,7 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1311
1333
|
@input="onInput"
|
|
1312
1334
|
@change="$emit('change', $event)"
|
|
1313
1335
|
@keyup.enter="$emit('enter', $event)"
|
|
1314
|
-
@focus="
|
|
1336
|
+
@focus="onFocus"
|
|
1315
1337
|
@blur="$emit('blur', $event)"
|
|
1316
1338
|
/>
|
|
1317
1339
|
`
|
|
@@ -1448,7 +1470,6 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1448
1470
|
"richEditor.italic": "\u659C\u4F53",
|
|
1449
1471
|
"richEditor.underline": "\u4E0B\u5212\u7EBF",
|
|
1450
1472
|
"richEditor.strike": "\u5220\u9664\u7EBF",
|
|
1451
|
-
"richEditor.heading": "\u4E8C\u7EA7\u6807\u9898",
|
|
1452
1473
|
"richEditor.paragraph": "\u6B63\u6587",
|
|
1453
1474
|
"richEditor.bulletList": "\u65E0\u5E8F\u5217\u8868",
|
|
1454
1475
|
"richEditor.numberList": "\u6709\u5E8F\u5217\u8868",
|
|
@@ -1459,6 +1480,39 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1459
1480
|
"richEditor.redo": "\u91CD\u505A",
|
|
1460
1481
|
"richEditor.source": "\u6E90\u7801",
|
|
1461
1482
|
"richEditor.visual": "\u53EF\u89C6\u5316",
|
|
1483
|
+
"richEditor.toolbar": "\u7F16\u8F91\u5DE5\u5177\u680F",
|
|
1484
|
+
"richEditor.block": "\u6BB5\u843D\u683C\u5F0F",
|
|
1485
|
+
"richEditor.heading1": "\u6807\u9898 1",
|
|
1486
|
+
"richEditor.heading2": "\u6807\u9898 2",
|
|
1487
|
+
"richEditor.heading3": "\u6807\u9898 3",
|
|
1488
|
+
"richEditor.quote": "\u5F15\u7528",
|
|
1489
|
+
"richEditor.codeBlock": "\u4EE3\u7801\u5757",
|
|
1490
|
+
"richEditor.fontSize": "\u5B57\u53F7",
|
|
1491
|
+
"richEditor.defaultSize": "\u9ED8\u8BA4",
|
|
1492
|
+
"richEditor.inlineCode": "\u884C\u5185\u4EE3\u7801",
|
|
1493
|
+
"richEditor.textColor": "\u6587\u5B57\u989C\u8272",
|
|
1494
|
+
"richEditor.bgColor": "\u80CC\u666F\u8272",
|
|
1495
|
+
"richEditor.alignLeft": "\u5DE6\u5BF9\u9F50",
|
|
1496
|
+
"richEditor.alignCenter": "\u5C45\u4E2D",
|
|
1497
|
+
"richEditor.alignRight": "\u53F3\u5BF9\u9F50",
|
|
1498
|
+
"richEditor.alignJustify": "\u4E24\u7AEF\u5BF9\u9F50",
|
|
1499
|
+
"richEditor.outdent": "\u51CF\u5C11\u7F29\u8FDB",
|
|
1500
|
+
"richEditor.indent": "\u589E\u52A0\u7F29\u8FDB",
|
|
1501
|
+
"richEditor.unlink": "\u53D6\u6D88\u94FE\u63A5",
|
|
1502
|
+
"richEditor.image": "\u63D2\u5165\u56FE\u7247",
|
|
1503
|
+
"richEditor.singleImage": "\u63D2\u5165\u5355\u56FE",
|
|
1504
|
+
"richEditor.multiImage": "\u63D2\u5165\u591A\u56FE",
|
|
1505
|
+
"richEditor.mediaLibrary": "\u4ECE\u7D20\u6750\u5E93\u9009\u62E9",
|
|
1506
|
+
"richEditor.hr": "\u5206\u5272\u7EBF",
|
|
1507
|
+
"richEditor.fullscreen": "\u5168\u5C4F",
|
|
1508
|
+
"richEditor.exitFullscreen": "\u9000\u51FA\u5168\u5C4F",
|
|
1509
|
+
"richEditor.wordCount": "\u5171 {count} \u5B57",
|
|
1510
|
+
"richEditor.uploading": "\u6B63\u5728\u4E0A\u4F20 {count} \u5F20\u56FE\u7247\u2026",
|
|
1511
|
+
"richEditor.uploadFailed": "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25",
|
|
1512
|
+
"richEditor.imageTypeError": "\u53EA\u80FD\u4E0A\u4F20\u56FE\u7247\u6587\u4EF6",
|
|
1513
|
+
"richEditor.imageSizeError": "\u56FE\u7247\u4E0D\u80FD\u8D85\u8FC7 {size}MB",
|
|
1514
|
+
"richEditor.imageUrlInvalid": "\u56FE\u7247\u5730\u5740\u4E0D\u5408\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u63D2\u5165",
|
|
1515
|
+
"richEditor.linkInvalid": "\u94FE\u63A5\u53EA\u652F\u6301 http(s)\u3001mailto\u3001tel \u6216\u7AD9\u5185\u76F8\u5BF9\u5730\u5740",
|
|
1462
1516
|
"approval.start": "\u53D1\u8D77",
|
|
1463
1517
|
"approval.cc": "\u6284\u9001",
|
|
1464
1518
|
"approval.approved": "\u5DF2\u901A\u8FC7",
|
|
@@ -1667,7 +1721,6 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1667
1721
|
"richEditor.italic": "Italic",
|
|
1668
1722
|
"richEditor.underline": "Underline",
|
|
1669
1723
|
"richEditor.strike": "Strikethrough",
|
|
1670
|
-
"richEditor.heading": "Heading 2",
|
|
1671
1724
|
"richEditor.paragraph": "Paragraph",
|
|
1672
1725
|
"richEditor.bulletList": "Bullet list",
|
|
1673
1726
|
"richEditor.numberList": "Numbered list",
|
|
@@ -1678,6 +1731,39 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
1678
1731
|
"richEditor.redo": "Redo",
|
|
1679
1732
|
"richEditor.source": "Source",
|
|
1680
1733
|
"richEditor.visual": "Visual",
|
|
1734
|
+
"richEditor.toolbar": "Formatting toolbar",
|
|
1735
|
+
"richEditor.block": "Paragraph style",
|
|
1736
|
+
"richEditor.heading1": "Heading 1",
|
|
1737
|
+
"richEditor.heading2": "Heading 2",
|
|
1738
|
+
"richEditor.heading3": "Heading 3",
|
|
1739
|
+
"richEditor.quote": "Quote",
|
|
1740
|
+
"richEditor.codeBlock": "Code block",
|
|
1741
|
+
"richEditor.fontSize": "Font size",
|
|
1742
|
+
"richEditor.defaultSize": "Default",
|
|
1743
|
+
"richEditor.inlineCode": "Inline code",
|
|
1744
|
+
"richEditor.textColor": "Text color",
|
|
1745
|
+
"richEditor.bgColor": "Background color",
|
|
1746
|
+
"richEditor.alignLeft": "Align left",
|
|
1747
|
+
"richEditor.alignCenter": "Align center",
|
|
1748
|
+
"richEditor.alignRight": "Align right",
|
|
1749
|
+
"richEditor.alignJustify": "Justify",
|
|
1750
|
+
"richEditor.outdent": "Decrease indent",
|
|
1751
|
+
"richEditor.indent": "Increase indent",
|
|
1752
|
+
"richEditor.unlink": "Remove link",
|
|
1753
|
+
"richEditor.image": "Insert image",
|
|
1754
|
+
"richEditor.singleImage": "Insert single image",
|
|
1755
|
+
"richEditor.multiImage": "Insert multiple images",
|
|
1756
|
+
"richEditor.mediaLibrary": "Choose from media library",
|
|
1757
|
+
"richEditor.hr": "Divider",
|
|
1758
|
+
"richEditor.fullscreen": "Fullscreen",
|
|
1759
|
+
"richEditor.exitFullscreen": "Exit fullscreen",
|
|
1760
|
+
"richEditor.wordCount": "{count} characters",
|
|
1761
|
+
"richEditor.uploading": "Uploading {count} image(s)\u2026",
|
|
1762
|
+
"richEditor.uploadFailed": "Image upload failed",
|
|
1763
|
+
"richEditor.imageTypeError": "Only image files are allowed",
|
|
1764
|
+
"richEditor.imageSizeError": "Image must be smaller than {size}MB",
|
|
1765
|
+
"richEditor.imageUrlInvalid": "Invalid image URL, insertion rejected",
|
|
1766
|
+
"richEditor.linkInvalid": "Only http(s), mailto, tel or relative URLs are allowed",
|
|
1681
1767
|
"approval.start": "Started",
|
|
1682
1768
|
"approval.cc": "CC",
|
|
1683
1769
|
"approval.approved": "Approved",
|
|
@@ -9460,6 +9546,197 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9460
9546
|
`
|
|
9461
9547
|
};
|
|
9462
9548
|
|
|
9549
|
+
// src/components/media-picker/index.js
|
|
9550
|
+
var media_picker_default = {
|
|
9551
|
+
name: "DjmMediaPicker",
|
|
9552
|
+
components: { DjmModal: modal_default, DjmPagination: pagination_default, DjmIcon: icon_default },
|
|
9553
|
+
props: {
|
|
9554
|
+
modelValue: { type: [Array, String, Number], default: "" },
|
|
9555
|
+
items: { type: Array, default: () => [] },
|
|
9556
|
+
multiple: { type: Boolean, default: false },
|
|
9557
|
+
// 最多选几个,0 表示不限
|
|
9558
|
+
limit: { type: Number, default: 0 },
|
|
9559
|
+
// image 只显示图片,file 只显示非图片,空串不过滤
|
|
9560
|
+
accept: { type: String, default: "" },
|
|
9561
|
+
title: { type: String, default: "" },
|
|
9562
|
+
triggerText: { type: String, default: "" },
|
|
9563
|
+
pageSize: { type: Number, default: 12 },
|
|
9564
|
+
disabled: { type: Boolean, default: false }
|
|
9565
|
+
},
|
|
9566
|
+
emits: ["update:modelValue", "change"],
|
|
9567
|
+
setup(props, { emit }) {
|
|
9568
|
+
const open = ref(false);
|
|
9569
|
+
const keyword = ref("");
|
|
9570
|
+
const page = ref(1);
|
|
9571
|
+
const temp = ref([]);
|
|
9572
|
+
const selectedIds = computed(() => {
|
|
9573
|
+
if (props.multiple) return Array.isArray(props.modelValue) ? props.modelValue : [];
|
|
9574
|
+
if (props.modelValue === "" || props.modelValue === null || props.modelValue === void 0) return [];
|
|
9575
|
+
return [props.modelValue];
|
|
9576
|
+
});
|
|
9577
|
+
function isImage(item) {
|
|
9578
|
+
if (item.type) return item.type === "image";
|
|
9579
|
+
return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(item.url || "");
|
|
9580
|
+
}
|
|
9581
|
+
const filtered = computed(() => {
|
|
9582
|
+
const key = keyword.value.trim().toLowerCase();
|
|
9583
|
+
return props.items.filter((item) => {
|
|
9584
|
+
if (props.accept === "image" && !isImage(item)) return false;
|
|
9585
|
+
if (props.accept === "file" && isImage(item)) return false;
|
|
9586
|
+
if (key && !String(item.name || "").toLowerCase().includes(key)) return false;
|
|
9587
|
+
return true;
|
|
9588
|
+
});
|
|
9589
|
+
});
|
|
9590
|
+
const paged = computed(() => {
|
|
9591
|
+
const start = (page.value - 1) * props.pageSize;
|
|
9592
|
+
return filtered.value.slice(start, start + props.pageSize);
|
|
9593
|
+
});
|
|
9594
|
+
watch(keyword, () => {
|
|
9595
|
+
page.value = 1;
|
|
9596
|
+
});
|
|
9597
|
+
watch(
|
|
9598
|
+
() => filtered.value.length,
|
|
9599
|
+
() => {
|
|
9600
|
+
const max = Math.max(1, Math.ceil(filtered.value.length / props.pageSize));
|
|
9601
|
+
if (page.value > max) page.value = max;
|
|
9602
|
+
}
|
|
9603
|
+
);
|
|
9604
|
+
function isChecked(id) {
|
|
9605
|
+
return temp.value.includes(id);
|
|
9606
|
+
}
|
|
9607
|
+
function atLimit() {
|
|
9608
|
+
return props.limit > 0 && temp.value.length >= props.limit;
|
|
9609
|
+
}
|
|
9610
|
+
function toggle(item) {
|
|
9611
|
+
if (props.multiple) {
|
|
9612
|
+
if (isChecked(item.id)) {
|
|
9613
|
+
temp.value = temp.value.filter((id) => id !== item.id);
|
|
9614
|
+
return;
|
|
9615
|
+
}
|
|
9616
|
+
if (atLimit()) return;
|
|
9617
|
+
temp.value = [...temp.value, item.id];
|
|
9618
|
+
return;
|
|
9619
|
+
}
|
|
9620
|
+
temp.value = [item.id];
|
|
9621
|
+
}
|
|
9622
|
+
function openPanel() {
|
|
9623
|
+
if (props.disabled) return;
|
|
9624
|
+
temp.value = [...selectedIds.value];
|
|
9625
|
+
keyword.value = "";
|
|
9626
|
+
page.value = 1;
|
|
9627
|
+
open.value = true;
|
|
9628
|
+
}
|
|
9629
|
+
function commit() {
|
|
9630
|
+
const next = props.multiple ? [...temp.value] : temp.value[0] !== void 0 ? temp.value[0] : "";
|
|
9631
|
+
emit("update:modelValue", next);
|
|
9632
|
+
emit("change", next);
|
|
9633
|
+
open.value = false;
|
|
9634
|
+
}
|
|
9635
|
+
function cancel() {
|
|
9636
|
+
temp.value = [...selectedIds.value];
|
|
9637
|
+
open.value = false;
|
|
9638
|
+
}
|
|
9639
|
+
return {
|
|
9640
|
+
open,
|
|
9641
|
+
keyword,
|
|
9642
|
+
page,
|
|
9643
|
+
temp,
|
|
9644
|
+
selectedIds,
|
|
9645
|
+
filtered,
|
|
9646
|
+
paged,
|
|
9647
|
+
isImage,
|
|
9648
|
+
isChecked,
|
|
9649
|
+
atLimit,
|
|
9650
|
+
toggle,
|
|
9651
|
+
openPanel,
|
|
9652
|
+
commit,
|
|
9653
|
+
cancel,
|
|
9654
|
+
t
|
|
9655
|
+
};
|
|
9656
|
+
},
|
|
9657
|
+
template: `
|
|
9658
|
+
<span class="djm-media-picker">
|
|
9659
|
+
<slot name="trigger" :open="openPanel" :count="selectedIds.length">
|
|
9660
|
+
<button
|
|
9661
|
+
class="djm-btn"
|
|
9662
|
+
type="button"
|
|
9663
|
+
:disabled="disabled"
|
|
9664
|
+
@click="openPanel"
|
|
9665
|
+
>{{ triggerText || t('mediaPicker.select') }}<span v-if="selectedIds.length" class="djm-media-picker-count">{{ selectedIds.length }}</span></button>
|
|
9666
|
+
</slot>
|
|
9667
|
+
|
|
9668
|
+
<DjmModal
|
|
9669
|
+
:model-value="open"
|
|
9670
|
+
:title="title || t('mediaPicker.title')"
|
|
9671
|
+
width="760px"
|
|
9672
|
+
@update:model-value="cancel"
|
|
9673
|
+
>
|
|
9674
|
+
<div class="djm-media-toolbar">
|
|
9675
|
+
<label class="djm-media-search">
|
|
9676
|
+
<DjmIcon name="search" :size="13" />
|
|
9677
|
+
<input
|
|
9678
|
+
v-model="keyword"
|
|
9679
|
+
class="djm-media-search-input"
|
|
9680
|
+
type="text"
|
|
9681
|
+
:placeholder="t('mediaPicker.search')"
|
|
9682
|
+
:aria-label="t('mediaPicker.search')"
|
|
9683
|
+
/>
|
|
9684
|
+
</label>
|
|
9685
|
+
<span class="djm-media-toolbar-slot">
|
|
9686
|
+
<slot name="toolbar"></slot>
|
|
9687
|
+
</span>
|
|
9688
|
+
</div>
|
|
9689
|
+
|
|
9690
|
+
<div class="djm-media-grid" role="listbox" :aria-multiselectable="multiple ? 'true' : 'false'">
|
|
9691
|
+
<div
|
|
9692
|
+
v-for="item in paged"
|
|
9693
|
+
:key="item.id"
|
|
9694
|
+
class="djm-media-item"
|
|
9695
|
+
:class="{
|
|
9696
|
+
'djm-is-checked': isChecked(item.id),
|
|
9697
|
+
'djm-is-disabled': multiple && atLimit() && !isChecked(item.id)
|
|
9698
|
+
}"
|
|
9699
|
+
role="option"
|
|
9700
|
+
tabindex="0"
|
|
9701
|
+
:aria-selected="String(isChecked(item.id))"
|
|
9702
|
+
@click="toggle(item)"
|
|
9703
|
+
@keydown.enter.prevent="toggle(item)"
|
|
9704
|
+
@keydown.space.prevent="toggle(item)"
|
|
9705
|
+
>
|
|
9706
|
+
<div class="djm-media-thumb">
|
|
9707
|
+
<img v-if="isImage(item)" :src="item.url" :alt="item.name" loading="lazy" />
|
|
9708
|
+
<DjmIcon v-else name="folder" :size="26" />
|
|
9709
|
+
<span v-if="isChecked(item.id)" class="djm-media-check">
|
|
9710
|
+
<DjmIcon name="check" :size="11" />
|
|
9711
|
+
</span>
|
|
9712
|
+
</div>
|
|
9713
|
+
<div class="djm-media-name" :title="item.name">{{ item.name }}</div>
|
|
9714
|
+
</div>
|
|
9715
|
+
<div v-if="!paged.length" class="djm-media-empty">{{ t('mediaPicker.empty') }}</div>
|
|
9716
|
+
</div>
|
|
9717
|
+
|
|
9718
|
+
<div class="djm-media-foot">
|
|
9719
|
+
<span class="djm-media-total">
|
|
9720
|
+
{{ t('mediaPicker.total', { total: filtered.length }) }}
|
|
9721
|
+
<em v-if="multiple && limit > 0">{{ t('mediaPicker.limit', { limit }) }}</em>
|
|
9722
|
+
</span>
|
|
9723
|
+
<DjmPagination
|
|
9724
|
+
v-if="filtered.length > pageSize"
|
|
9725
|
+
v-model:page="page"
|
|
9726
|
+
:page-size="pageSize"
|
|
9727
|
+
:total="filtered.length"
|
|
9728
|
+
/>
|
|
9729
|
+
</div>
|
|
9730
|
+
|
|
9731
|
+
<template #footer>
|
|
9732
|
+
<button class="djm-btn" type="button" @click="cancel">{{ t('common.cancel') }}</button>
|
|
9733
|
+
<button class="djm-btn djm-btn-primary" type="button" @click="commit">{{ t('common.confirm') }}</button>
|
|
9734
|
+
</template>
|
|
9735
|
+
</DjmModal>
|
|
9736
|
+
</span>
|
|
9737
|
+
`
|
|
9738
|
+
};
|
|
9739
|
+
|
|
9463
9740
|
// src/utils/sanitize-html.js
|
|
9464
9741
|
var TAGS = /* @__PURE__ */ new Set([
|
|
9465
9742
|
"p",
|
|
@@ -9498,7 +9775,18 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9498
9775
|
"th",
|
|
9499
9776
|
"td"
|
|
9500
9777
|
]);
|
|
9501
|
-
var GLOBAL_ATTRS = /* @__PURE__ */ new Set(["class", "title"]);
|
|
9778
|
+
var GLOBAL_ATTRS = /* @__PURE__ */ new Set(["class", "title", "style"]);
|
|
9779
|
+
var STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
9780
|
+
"color",
|
|
9781
|
+
"background-color",
|
|
9782
|
+
"font-size",
|
|
9783
|
+
"font-weight",
|
|
9784
|
+
"font-style",
|
|
9785
|
+
"text-decoration",
|
|
9786
|
+
"text-align",
|
|
9787
|
+
"text-indent",
|
|
9788
|
+
"line-height"
|
|
9789
|
+
]);
|
|
9502
9790
|
var TAG_ATTRS = {
|
|
9503
9791
|
a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
|
|
9504
9792
|
img: /* @__PURE__ */ new Set(["src", "alt", "width", "height"]),
|
|
@@ -9506,11 +9794,29 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9506
9794
|
th: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope"]),
|
|
9507
9795
|
td: /* @__PURE__ */ new Set(["colspan", "rowspan"])
|
|
9508
9796
|
};
|
|
9797
|
+
function cleanStyle(value) {
|
|
9798
|
+
return String(value || "").split(";").map((item) => {
|
|
9799
|
+
const index = item.indexOf(":");
|
|
9800
|
+
if (index === -1) return "";
|
|
9801
|
+
const name = item.slice(0, index).trim().toLowerCase();
|
|
9802
|
+
const val = item.slice(index + 1).trim();
|
|
9803
|
+
if (!STYLE_PROPS.has(name)) return "";
|
|
9804
|
+
if (/url\s*\(|expression\s*\(|javascript:/i.test(val)) return "";
|
|
9805
|
+
return name + ": " + val;
|
|
9806
|
+
}).filter(Boolean).join("; ");
|
|
9807
|
+
}
|
|
9808
|
+
var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
9509
9809
|
function safeUrl(value, tag) {
|
|
9510
|
-
const url = String(value || "").replace(/[\u0000-\u0020]+/g, "")
|
|
9511
|
-
|
|
9512
|
-
if (
|
|
9513
|
-
|
|
9810
|
+
const url = String(value || "").replace(/[\u0000-\u0020]+/g, "");
|
|
9811
|
+
const scheme = url.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
9812
|
+
if (!scheme) return true;
|
|
9813
|
+
const name = scheme[1].toLowerCase();
|
|
9814
|
+
if (SAFE_SCHEMES.has(name)) return true;
|
|
9815
|
+
if (tag === "img") {
|
|
9816
|
+
if (name === "blob") return true;
|
|
9817
|
+
if (name === "data" && /^data:image\//i.test(url)) return true;
|
|
9818
|
+
}
|
|
9819
|
+
return false;
|
|
9514
9820
|
}
|
|
9515
9821
|
function cleanNode(node) {
|
|
9516
9822
|
const children = [...node.childNodes];
|
|
@@ -9533,6 +9839,12 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9533
9839
|
child.removeAttribute(attr.name);
|
|
9534
9840
|
return;
|
|
9535
9841
|
}
|
|
9842
|
+
if (name === "style") {
|
|
9843
|
+
const style = cleanStyle(attr.value);
|
|
9844
|
+
if (style) child.setAttribute("style", style);
|
|
9845
|
+
else child.removeAttribute("style");
|
|
9846
|
+
return;
|
|
9847
|
+
}
|
|
9536
9848
|
if ((name === "href" || name === "src") && !safeUrl(attr.value, tag)) {
|
|
9537
9849
|
child.removeAttribute(attr.name);
|
|
9538
9850
|
}
|
|
@@ -9553,78 +9865,474 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9553
9865
|
}
|
|
9554
9866
|
|
|
9555
9867
|
// src/components/rich-editor/index.js
|
|
9868
|
+
var BLOCKS = [
|
|
9869
|
+
{ value: "p", label: "paragraph" },
|
|
9870
|
+
{ value: "h1", label: "heading1" },
|
|
9871
|
+
{ value: "h2", label: "heading2" },
|
|
9872
|
+
{ value: "h3", label: "heading3" },
|
|
9873
|
+
{ value: "blockquote", label: "quote" },
|
|
9874
|
+
{ value: "pre", label: "codeBlock" }
|
|
9875
|
+
];
|
|
9876
|
+
var SIZES = [
|
|
9877
|
+
{ value: "", label: "defaultSize" },
|
|
9878
|
+
{ value: "12px", label: "12px" },
|
|
9879
|
+
{ value: "14px", label: "14px" },
|
|
9880
|
+
{ value: "16px", label: "16px" },
|
|
9881
|
+
{ value: "18px", label: "18px" },
|
|
9882
|
+
{ value: "24px", label: "24px" }
|
|
9883
|
+
];
|
|
9884
|
+
var COLORS = ["#1f2d3d", "#5a6b7d", "#2b86d9", "#2fa36b", "#d97706", "#dc2626", "#7c3aed", "#ffffff"];
|
|
9885
|
+
var TOOLBAR_PRESETS = {
|
|
9886
|
+
simple: ["history", "inline", "list", "insert"],
|
|
9887
|
+
basic: ["history", "format", "inline", "align", "list", "insert", "view"],
|
|
9888
|
+
full: ["history", "format", "inline", "color", "align", "list", "insert", "view"]
|
|
9889
|
+
};
|
|
9890
|
+
function isSafeImageUrl(url) {
|
|
9891
|
+
const value = String(url || "").replace(/[\u0000-\u0020]+/g, "");
|
|
9892
|
+
if (!value) return false;
|
|
9893
|
+
const scheme = value.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
9894
|
+
if (!scheme) return true;
|
|
9895
|
+
const name = scheme[1].toLowerCase();
|
|
9896
|
+
if (name === "http" || name === "https" || name === "blob") return true;
|
|
9897
|
+
return name === "data" && /^data:image\//i.test(value);
|
|
9898
|
+
}
|
|
9899
|
+
function isSafeLink(url) {
|
|
9900
|
+
const value = String(url || "").trim();
|
|
9901
|
+
if (!value) return false;
|
|
9902
|
+
const scheme = value.replace(/[\u0000-\u0020]+/g, "").match(/^([a-z][a-z0-9+.-]*):/i);
|
|
9903
|
+
if (!scheme) return true;
|
|
9904
|
+
return ["http", "https", "mailto", "tel"].indexOf(scheme[1].toLowerCase()) > -1;
|
|
9905
|
+
}
|
|
9556
9906
|
var rich_editor_default = {
|
|
9557
9907
|
name: "DjmRichEditor",
|
|
9558
|
-
components: { Icon: icon_default },
|
|
9908
|
+
components: { Icon: icon_default, MediaPicker: media_picker_default },
|
|
9559
9909
|
props: {
|
|
9560
9910
|
modelValue: { type: String, default: "" },
|
|
9561
9911
|
placeholder: { type: String, default: "" },
|
|
9562
9912
|
minHeight: { type: Number, default: 200 },
|
|
9563
|
-
//
|
|
9564
|
-
sanitize: { type: Boolean, default:
|
|
9565
|
-
disabled: { type: Boolean, default: false }
|
|
9913
|
+
// 默认开启:对外输出的 HTML 走白名单清洗,内容里不会带出脚本
|
|
9914
|
+
sanitize: { type: Boolean, default: true },
|
|
9915
|
+
disabled: { type: Boolean, default: false },
|
|
9916
|
+
// 工具条:simple / basic / full,或传分组数组(history / format / inline / color / align / list / insert / view)
|
|
9917
|
+
toolbar: { type: [String, Array], default: "full" },
|
|
9918
|
+
// 自定义上传:接收 File,返回图片地址(或 { url }),不传则转成 dataURL 本地预览
|
|
9919
|
+
uploadImage: { type: Function, default: null },
|
|
9920
|
+
// 已上传图片,提供后工具栏出现「从素材库选择」:字符串或 { url, name }
|
|
9921
|
+
media: { type: Array, default: () => [] },
|
|
9922
|
+
accept: { type: String, default: "image/*" },
|
|
9923
|
+
// 单张图片大小上限(MB)
|
|
9924
|
+
maxImageSize: { type: Number, default: 5 },
|
|
9925
|
+
// 是否允许一次选多张
|
|
9926
|
+
multiple: { type: Boolean, default: true },
|
|
9927
|
+
showCount: { type: Boolean, default: true }
|
|
9566
9928
|
},
|
|
9567
|
-
emits: ["update:modelValue"],
|
|
9929
|
+
emits: ["update:modelValue", "upload-error"],
|
|
9568
9930
|
setup(props, { emit }) {
|
|
9569
9931
|
const editor = ref(null);
|
|
9932
|
+
const fileInput = ref(null);
|
|
9570
9933
|
const sourceMode = ref(false);
|
|
9571
9934
|
const source = ref(props.modelValue);
|
|
9572
|
-
const
|
|
9935
|
+
const fullscreen = ref(false);
|
|
9936
|
+
const panel = ref("");
|
|
9937
|
+
const uploadCount = ref(0);
|
|
9938
|
+
const textColor = ref(COLORS[0]);
|
|
9939
|
+
const bgColor = ref("#fdf3c4");
|
|
9940
|
+
const fontSize = ref("");
|
|
9941
|
+
const count2 = ref(0);
|
|
9942
|
+
const formats = ref({
|
|
9943
|
+
bold: false,
|
|
9944
|
+
italic: false,
|
|
9945
|
+
underline: false,
|
|
9946
|
+
strike: false,
|
|
9947
|
+
code: false,
|
|
9948
|
+
block: "p",
|
|
9949
|
+
align: "",
|
|
9950
|
+
list: ""
|
|
9951
|
+
});
|
|
9952
|
+
let savedRange = null;
|
|
9953
|
+
const ready = computed(() => !props.disabled && !sourceMode.value);
|
|
9954
|
+
const toolbarGroups = computed(() => {
|
|
9955
|
+
const list = Array.isArray(props.toolbar) ? props.toolbar : TOOLBAR_PRESETS[props.toolbar] || TOOLBAR_PRESETS.full;
|
|
9956
|
+
return new Set(list);
|
|
9957
|
+
});
|
|
9958
|
+
function has(group) {
|
|
9959
|
+
return toolbarGroups.value.has(group);
|
|
9960
|
+
}
|
|
9961
|
+
const mediaLibrary = computed(
|
|
9962
|
+
() => (props.media || []).map((item, index) => {
|
|
9963
|
+
if (typeof item === "string") {
|
|
9964
|
+
return { id: "media-" + index, url: item, name: "", type: "image" };
|
|
9965
|
+
}
|
|
9966
|
+
return {
|
|
9967
|
+
id: item && item.id !== void 0 ? String(item.id) : "media-" + index,
|
|
9968
|
+
url: item && (item.url || item.src) || "",
|
|
9969
|
+
name: item && (item.name || item.alt) || "",
|
|
9970
|
+
type: "image"
|
|
9971
|
+
};
|
|
9972
|
+
}).filter((item) => !!item.url)
|
|
9973
|
+
);
|
|
9573
9974
|
function clean(html) {
|
|
9574
9975
|
return props.sanitize ? sanitizeHtml(html) : html;
|
|
9575
9976
|
}
|
|
9977
|
+
function plainText(html) {
|
|
9978
|
+
return String(html || "").replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&[a-z]+;/gi, " ");
|
|
9979
|
+
}
|
|
9576
9980
|
function syncFromDom() {
|
|
9577
9981
|
if (!editor.value) return;
|
|
9578
|
-
|
|
9982
|
+
const html = clean(editor.value.innerHTML);
|
|
9983
|
+
count2.value = plainText(html).trim().length;
|
|
9984
|
+
emit("update:modelValue", html);
|
|
9579
9985
|
}
|
|
9580
|
-
function
|
|
9581
|
-
const
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9986
|
+
function currentRange() {
|
|
9987
|
+
const selection = window.getSelection();
|
|
9988
|
+
if (!selection || !selection.rangeCount || !editor.value) return null;
|
|
9989
|
+
const range = selection.getRangeAt(0);
|
|
9990
|
+
return editor.value.contains(range.commonAncestorContainer) ? range : null;
|
|
9991
|
+
}
|
|
9992
|
+
function rememberRange() {
|
|
9993
|
+
const range = currentRange();
|
|
9994
|
+
savedRange = range ? range.cloneRange() : null;
|
|
9995
|
+
}
|
|
9996
|
+
function focusAt(range) {
|
|
9997
|
+
if (!editor.value) return;
|
|
9998
|
+
editor.value.focus();
|
|
9999
|
+
const selection = window.getSelection();
|
|
10000
|
+
selection.removeAllRanges();
|
|
10001
|
+
if (range) {
|
|
10002
|
+
selection.addRange(range);
|
|
10003
|
+
return;
|
|
10004
|
+
}
|
|
10005
|
+
const end = document.createRange();
|
|
10006
|
+
end.selectNodeContents(editor.value);
|
|
10007
|
+
end.collapse(false);
|
|
10008
|
+
selection.addRange(end);
|
|
10009
|
+
}
|
|
10010
|
+
function insertNodeAt(range, node) {
|
|
10011
|
+
if (!editor.value) return;
|
|
10012
|
+
focusAt(range);
|
|
10013
|
+
const selection = window.getSelection();
|
|
10014
|
+
if (selection.rangeCount) selection.getRangeAt(0).insertNode(node);
|
|
10015
|
+
else editor.value.appendChild(node);
|
|
9586
10016
|
syncFromDom();
|
|
10017
|
+
syncFormats();
|
|
10018
|
+
}
|
|
10019
|
+
function ensureRange() {
|
|
10020
|
+
const range = currentRange();
|
|
10021
|
+
if (range) return range;
|
|
10022
|
+
if (!savedRange) return null;
|
|
10023
|
+
focusAt(savedRange);
|
|
10024
|
+
return currentRange();
|
|
9587
10025
|
}
|
|
9588
10026
|
function exec(command, value = null) {
|
|
9589
|
-
if (
|
|
10027
|
+
if (!ready.value) return;
|
|
10028
|
+
if (typeof document.execCommand !== "function") return;
|
|
10029
|
+
ensureRange();
|
|
10030
|
+
document.execCommand("styleWithCSS", false, true);
|
|
9590
10031
|
document.execCommand(command, false, value);
|
|
9591
10032
|
if (editor.value) editor.value.focus();
|
|
9592
10033
|
syncFromDom();
|
|
9593
10034
|
syncFormats();
|
|
9594
10035
|
}
|
|
9595
|
-
function
|
|
9596
|
-
|
|
10036
|
+
function setBlock(value) {
|
|
10037
|
+
panel.value = "";
|
|
10038
|
+
exec("formatBlock", "<" + value + ">");
|
|
10039
|
+
}
|
|
10040
|
+
function setFontSize(size) {
|
|
10041
|
+
panel.value = "";
|
|
10042
|
+
if (!ready.value || !editor.value) return;
|
|
10043
|
+
fontSize.value = size;
|
|
10044
|
+
const range = ensureRange();
|
|
10045
|
+
if (!range || range.collapsed) return;
|
|
10046
|
+
if (!size) {
|
|
10047
|
+
const walker = document.createTreeWalker(editor.value, NodeFilter.SHOW_ELEMENT);
|
|
10048
|
+
const targets = [];
|
|
10049
|
+
while (walker.nextNode()) {
|
|
10050
|
+
const el = walker.currentNode;
|
|
10051
|
+
if (el.style && el.style.fontSize && range.intersectsNode(el)) targets.push(el);
|
|
10052
|
+
}
|
|
10053
|
+
targets.forEach((el) => {
|
|
10054
|
+
el.style.fontSize = "";
|
|
10055
|
+
});
|
|
10056
|
+
syncFromDom();
|
|
10057
|
+
return;
|
|
10058
|
+
}
|
|
10059
|
+
const span = document.createElement("span");
|
|
10060
|
+
span.style.fontSize = size;
|
|
10061
|
+
span.appendChild(range.extractContents());
|
|
10062
|
+
range.insertNode(span);
|
|
10063
|
+
const selection = window.getSelection();
|
|
10064
|
+
const after = document.createRange();
|
|
10065
|
+
after.setStartAfter(span);
|
|
10066
|
+
after.collapse(true);
|
|
10067
|
+
selection.removeAllRanges();
|
|
10068
|
+
selection.addRange(after);
|
|
10069
|
+
syncFromDom();
|
|
10070
|
+
syncFormats();
|
|
9597
10071
|
}
|
|
9598
|
-
function
|
|
9599
|
-
|
|
9600
|
-
if (
|
|
10072
|
+
function applyColor(kind, color) {
|
|
10073
|
+
panel.value = "";
|
|
10074
|
+
if (kind === "text") textColor.value = color;
|
|
10075
|
+
else bgColor.value = color;
|
|
10076
|
+
if (!ready.value) return;
|
|
10077
|
+
if (typeof document.execCommand !== "function") return;
|
|
10078
|
+
document.execCommand("styleWithCSS", false, true);
|
|
10079
|
+
const command = kind === "text" ? "foreColor" : "hiliteColor";
|
|
10080
|
+
if (!document.execCommand(command, false, color) && kind === "bg") {
|
|
10081
|
+
document.execCommand("backColor", false, color);
|
|
10082
|
+
}
|
|
10083
|
+
if (editor.value) editor.value.focus();
|
|
10084
|
+
syncFromDom();
|
|
10085
|
+
syncFormats();
|
|
9601
10086
|
}
|
|
9602
|
-
function
|
|
9603
|
-
if (
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
10087
|
+
function togglePanel(name) {
|
|
10088
|
+
if (!ready.value) return;
|
|
10089
|
+
panel.value = panel.value === name ? "" : name;
|
|
10090
|
+
}
|
|
10091
|
+
function toggleInlineCode() {
|
|
10092
|
+
if (!ready.value || !editor.value) return;
|
|
10093
|
+
const range = currentRange();
|
|
10094
|
+
if (!range) return;
|
|
10095
|
+
const anchor = range.commonAncestorContainer;
|
|
10096
|
+
const node = anchor.nodeType === 1 ? anchor : anchor.parentElement;
|
|
10097
|
+
const code = node && node.closest ? node.closest("code") : null;
|
|
10098
|
+
if (code && editor.value.contains(code)) {
|
|
10099
|
+
const parent = code.parentNode;
|
|
10100
|
+
while (code.firstChild) parent.insertBefore(code.firstChild, code);
|
|
10101
|
+
parent.removeChild(code);
|
|
10102
|
+
syncFromDom();
|
|
10103
|
+
return;
|
|
10104
|
+
}
|
|
10105
|
+
if (range.collapsed) return;
|
|
10106
|
+
const wrapper = document.createElement("code");
|
|
10107
|
+
wrapper.appendChild(range.extractContents());
|
|
10108
|
+
range.insertNode(wrapper);
|
|
10109
|
+
const selection = window.getSelection();
|
|
10110
|
+
const after = document.createRange();
|
|
10111
|
+
after.setStartAfter(wrapper);
|
|
10112
|
+
after.collapse(true);
|
|
10113
|
+
selection.removeAllRanges();
|
|
10114
|
+
selection.addRange(after);
|
|
10115
|
+
syncFromDom();
|
|
10116
|
+
syncFormats();
|
|
10117
|
+
}
|
|
10118
|
+
function addLink() {
|
|
10119
|
+
if (!ready.value) return;
|
|
10120
|
+
const url = window.prompt(t("richEditor.linkPrompt"), "https://");
|
|
10121
|
+
if (!url) return;
|
|
10122
|
+
if (!isSafeLink(url)) {
|
|
10123
|
+
window.alert(t("richEditor.linkInvalid"));
|
|
10124
|
+
return;
|
|
10125
|
+
}
|
|
10126
|
+
const range = currentRange();
|
|
10127
|
+
if (range && range.collapsed) {
|
|
10128
|
+
const link = document.createElement("a");
|
|
10129
|
+
link.href = url;
|
|
10130
|
+
link.target = "_blank";
|
|
10131
|
+
link.rel = "noopener noreferrer";
|
|
10132
|
+
link.textContent = url;
|
|
10133
|
+
insertNodeAt(range, link);
|
|
10134
|
+
return;
|
|
10135
|
+
}
|
|
10136
|
+
exec("createLink", url);
|
|
10137
|
+
if (editor.value) {
|
|
10138
|
+
editor.value.querySelectorAll("a[href]").forEach((link) => {
|
|
10139
|
+
if (!link.getAttribute("target")) link.setAttribute("target", "_blank");
|
|
10140
|
+
link.setAttribute("rel", "noopener noreferrer");
|
|
10141
|
+
});
|
|
10142
|
+
}
|
|
10143
|
+
syncFromDom();
|
|
10144
|
+
}
|
|
10145
|
+
function readAsDataUrl(file) {
|
|
10146
|
+
return new Promise((resolve, reject) => {
|
|
10147
|
+
const reader = new FileReader();
|
|
10148
|
+
reader.onload = () => resolve(reader.result);
|
|
10149
|
+
reader.onerror = () => reject(reader.error || new Error("read failed"));
|
|
10150
|
+
reader.readAsDataURL(file);
|
|
10151
|
+
});
|
|
10152
|
+
}
|
|
10153
|
+
async function uploadOne(file) {
|
|
10154
|
+
if (props.uploadImage) {
|
|
10155
|
+
const result = await props.uploadImage(file);
|
|
10156
|
+
const url = typeof result === "string" ? result : result && result.url;
|
|
10157
|
+
if (!url) throw new Error(t("richEditor.imageUrlInvalid"));
|
|
10158
|
+
if (!isSafeImageUrl(url)) throw new Error(t("richEditor.imageUrlInvalid"));
|
|
10159
|
+
return String(url).trim();
|
|
10160
|
+
}
|
|
10161
|
+
return readAsDataUrl(file);
|
|
10162
|
+
}
|
|
10163
|
+
function isImage(file) {
|
|
10164
|
+
return file && /^image\//.test(file.type);
|
|
10165
|
+
}
|
|
10166
|
+
function pickImage(allowMultiple) {
|
|
10167
|
+
if (!ready.value || !fileInput.value) return;
|
|
10168
|
+
rememberRange();
|
|
10169
|
+
fileInput.value.multiple = !!allowMultiple && props.multiple;
|
|
10170
|
+
fileInput.value.click();
|
|
10171
|
+
}
|
|
10172
|
+
function insertMedia(ids) {
|
|
10173
|
+
if (!ready.value) return;
|
|
10174
|
+
const picked = (Array.isArray(ids) ? ids : [ids]).map((id) => mediaLibrary.value.find((item) => item.id === String(id))).filter(Boolean);
|
|
10175
|
+
const safe = picked.filter((item) => {
|
|
10176
|
+
if (isSafeImageUrl(item.url)) return true;
|
|
10177
|
+
emit("upload-error", { file: null, message: t("richEditor.imageUrlInvalid") });
|
|
10178
|
+
return false;
|
|
10179
|
+
});
|
|
10180
|
+
if (!safe.length) return;
|
|
10181
|
+
focusAt(savedRange);
|
|
10182
|
+
const fragment = document.createDocumentFragment();
|
|
10183
|
+
safe.forEach((item) => {
|
|
10184
|
+
const img = document.createElement("img");
|
|
10185
|
+
img.src = item.url;
|
|
10186
|
+
img.alt = item.name || "image";
|
|
10187
|
+
img.className = "djm-rich-editor-img";
|
|
10188
|
+
fragment.appendChild(img);
|
|
10189
|
+
});
|
|
10190
|
+
const selection = window.getSelection();
|
|
10191
|
+
if (selection.rangeCount) selection.getRangeAt(0).insertNode(fragment);
|
|
10192
|
+
else editor.value.appendChild(fragment);
|
|
10193
|
+
syncFromDom();
|
|
10194
|
+
syncFormats();
|
|
10195
|
+
}
|
|
10196
|
+
function insertImages(files) {
|
|
10197
|
+
if (!files.length || !editor.value) return;
|
|
10198
|
+
uploadCount.value = files.length;
|
|
10199
|
+
const holder = document.createElement("span");
|
|
10200
|
+
holder.className = "djm-rich-editor-uploading";
|
|
10201
|
+
holder.textContent = t("richEditor.uploading", { count: files.length });
|
|
10202
|
+
const anchor = savedRange && editor.value.contains(savedRange.commonAncestorContainer) ? savedRange : null;
|
|
10203
|
+
focusAt(anchor);
|
|
10204
|
+
const selection = window.getSelection();
|
|
10205
|
+
if (selection.rangeCount) selection.getRangeAt(0).insertNode(holder);
|
|
10206
|
+
else editor.value.appendChild(holder);
|
|
10207
|
+
Promise.all(files.map(async (file) => {
|
|
10208
|
+
if (!isImage(file)) return { file, error: t("richEditor.imageTypeError") };
|
|
10209
|
+
if (props.maxImageSize > 0 && file.size > props.maxImageSize * 1024 * 1024) {
|
|
10210
|
+
return { file, error: t("richEditor.imageSizeError", { size: props.maxImageSize }) };
|
|
10211
|
+
}
|
|
10212
|
+
try {
|
|
10213
|
+
return { file, url: await uploadOne(file) };
|
|
10214
|
+
} catch (error) {
|
|
10215
|
+
return { file, error: error && error.message || t("richEditor.uploadFailed") };
|
|
10216
|
+
}
|
|
10217
|
+
})).then((results) => {
|
|
10218
|
+
uploadCount.value = 0;
|
|
10219
|
+
const done = results.filter((item) => item.url);
|
|
10220
|
+
const failed = results.filter((item) => item.error);
|
|
10221
|
+
const alive = editor.value && editor.value.contains(holder);
|
|
10222
|
+
if (done.length && editor.value) {
|
|
10223
|
+
done.forEach((item) => {
|
|
10224
|
+
const img = document.createElement("img");
|
|
10225
|
+
img.src = item.url;
|
|
10226
|
+
img.alt = item.file.name || "image";
|
|
10227
|
+
img.className = "djm-rich-editor-img";
|
|
10228
|
+
if (alive) holder.parentNode.insertBefore(img, holder);
|
|
10229
|
+
else editor.value.appendChild(img);
|
|
10230
|
+
});
|
|
10231
|
+
}
|
|
10232
|
+
if (alive) holder.remove();
|
|
10233
|
+
failed.forEach((item) => emit("upload-error", { file: item.file, message: item.error }));
|
|
10234
|
+
syncFromDom();
|
|
10235
|
+
});
|
|
10236
|
+
}
|
|
10237
|
+
function onFilesChange(event) {
|
|
10238
|
+
const files = Array.from(event.target.files || []);
|
|
10239
|
+
event.target.value = "";
|
|
10240
|
+
insertImages(files);
|
|
10241
|
+
}
|
|
10242
|
+
function onPaste(event) {
|
|
10243
|
+
const data = event.clipboardData;
|
|
10244
|
+
if (!data) return;
|
|
10245
|
+
const images = Array.from(data.files || []).filter(isImage);
|
|
10246
|
+
if (images.length) {
|
|
10247
|
+
event.preventDefault();
|
|
10248
|
+
insertImages(images);
|
|
10249
|
+
return;
|
|
10250
|
+
}
|
|
10251
|
+
const html = data.getData("text/html");
|
|
10252
|
+
if (!html) return;
|
|
10253
|
+
event.preventDefault();
|
|
10254
|
+
document.execCommand("insertHTML", false, sanitizeHtml(html));
|
|
10255
|
+
syncFromDom();
|
|
10256
|
+
}
|
|
10257
|
+
function onDragOver(event) {
|
|
10258
|
+
if (!ready.value) return;
|
|
10259
|
+
if (event.dataTransfer && Array.from(event.dataTransfer.types || []).includes("Files")) {
|
|
10260
|
+
event.preventDefault();
|
|
10261
|
+
}
|
|
10262
|
+
}
|
|
10263
|
+
function onDrop(event) {
|
|
10264
|
+
if (!ready.value) return;
|
|
10265
|
+
const files = Array.from(event.dataTransfer && event.dataTransfer.files || []).filter(isImage);
|
|
10266
|
+
if (!files.length) return;
|
|
10267
|
+
event.preventDefault();
|
|
10268
|
+
rememberRange();
|
|
10269
|
+
insertImages(files);
|
|
10270
|
+
}
|
|
10271
|
+
function closestTag(node, tag) {
|
|
10272
|
+
const el = node && (node.nodeType === 1 ? node : node.parentElement);
|
|
10273
|
+
return el && el.closest ? el.closest(tag) : null;
|
|
10274
|
+
}
|
|
10275
|
+
function detectBlock() {
|
|
10276
|
+
const selection = window.getSelection();
|
|
10277
|
+
if (!selection || !selection.anchorNode) return "p";
|
|
10278
|
+
let node = selection.anchorNode;
|
|
10279
|
+
if (node.nodeType !== 1) node = node.parentElement;
|
|
10280
|
+
while (node && node !== editor.value) {
|
|
10281
|
+
const tag = node.tagName.toLowerCase();
|
|
10282
|
+
if (["p", "h1", "h2", "h3", "blockquote", "pre"].includes(tag)) return tag;
|
|
10283
|
+
node = node.parentElement;
|
|
10284
|
+
}
|
|
10285
|
+
return "p";
|
|
10286
|
+
}
|
|
10287
|
+
function syncFormats() {
|
|
10288
|
+
if (props.disabled || sourceMode.value || !editor.value) return;
|
|
10289
|
+
if (!editor.value.contains(document.activeElement)) return;
|
|
10290
|
+
const selection = window.getSelection();
|
|
10291
|
+
formats.value = {
|
|
10292
|
+
bold: document.queryCommandState("bold"),
|
|
10293
|
+
italic: document.queryCommandState("italic"),
|
|
10294
|
+
underline: document.queryCommandState("underline"),
|
|
10295
|
+
strike: document.queryCommandState("strikeThrough"),
|
|
10296
|
+
code: !!closestTag(selection.anchorNode, "code"),
|
|
10297
|
+
block: detectBlock(),
|
|
10298
|
+
align: document.queryCommandState("justifyCenter") ? "center" : document.queryCommandState("justifyRight") ? "right" : document.queryCommandState("justifyFull") ? "justify" : "left",
|
|
10299
|
+
list: document.queryCommandState("insertOrderedList") ? "ol" : document.queryCommandState("insertUnorderedList") ? "ul" : ""
|
|
10300
|
+
};
|
|
9611
10301
|
}
|
|
9612
10302
|
function onSourceInput() {
|
|
10303
|
+
count2.value = plainText(source.value).trim().length;
|
|
9613
10304
|
emit("update:modelValue", clean(source.value));
|
|
9614
10305
|
}
|
|
9615
10306
|
function toggleSource() {
|
|
9616
10307
|
if (props.disabled) return;
|
|
10308
|
+
panel.value = "";
|
|
9617
10309
|
if (sourceMode.value) {
|
|
9618
10310
|
emit("update:modelValue", clean(source.value));
|
|
9619
10311
|
sourceMode.value = false;
|
|
9620
10312
|
requestAnimationFrame(() => {
|
|
9621
10313
|
if (editor.value) editor.value.innerHTML = clean(source.value);
|
|
10314
|
+
syncFromDom();
|
|
9622
10315
|
});
|
|
9623
10316
|
} else {
|
|
9624
10317
|
source.value = editor.value ? editor.value.innerHTML : props.modelValue;
|
|
9625
10318
|
sourceMode.value = true;
|
|
9626
10319
|
}
|
|
9627
10320
|
}
|
|
10321
|
+
function toggleFullscreen() {
|
|
10322
|
+
if (props.disabled) return;
|
|
10323
|
+
fullscreen.value = !fullscreen.value;
|
|
10324
|
+
panel.value = "";
|
|
10325
|
+
}
|
|
10326
|
+
function onDocumentClick(event) {
|
|
10327
|
+
if (!panel.value) return;
|
|
10328
|
+
if (!(event.target.closest && event.target.closest(".djm-rich-editor-color"))) panel.value = "";
|
|
10329
|
+
}
|
|
10330
|
+
function onKeydown(event) {
|
|
10331
|
+
if (event.key === "Escape" && fullscreen.value) {
|
|
10332
|
+
fullscreen.value = false;
|
|
10333
|
+
panel.value = "";
|
|
10334
|
+
}
|
|
10335
|
+
}
|
|
9628
10336
|
watch(
|
|
9629
10337
|
() => props.modelValue,
|
|
9630
10338
|
(value) => {
|
|
@@ -9634,40 +10342,117 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9634
10342
|
}
|
|
9635
10343
|
const next = clean(value);
|
|
9636
10344
|
if (editor.value && editor.value.innerHTML !== next) editor.value.innerHTML = next;
|
|
10345
|
+
count2.value = plainText(next).trim().length;
|
|
9637
10346
|
}
|
|
9638
10347
|
);
|
|
9639
10348
|
onMounted(() => {
|
|
9640
10349
|
if (editor.value) editor.value.innerHTML = clean(props.modelValue);
|
|
10350
|
+
count2.value = plainText(props.modelValue).trim().length;
|
|
9641
10351
|
document.addEventListener("selectionchange", syncFormats);
|
|
10352
|
+
document.addEventListener("click", onDocumentClick);
|
|
10353
|
+
document.addEventListener("keydown", onKeydown);
|
|
10354
|
+
});
|
|
10355
|
+
onBeforeUnmount(() => {
|
|
10356
|
+
document.removeEventListener("selectionchange", syncFormats);
|
|
10357
|
+
document.removeEventListener("click", onDocumentClick);
|
|
10358
|
+
document.removeEventListener("keydown", onKeydown);
|
|
9642
10359
|
});
|
|
9643
|
-
onBeforeUnmount(() => document.removeEventListener("selectionchange", syncFormats));
|
|
9644
10360
|
return {
|
|
9645
10361
|
editor,
|
|
10362
|
+
fileInput,
|
|
9646
10363
|
sourceMode,
|
|
9647
10364
|
source,
|
|
10365
|
+
fullscreen,
|
|
10366
|
+
panel,
|
|
10367
|
+
uploadCount,
|
|
10368
|
+
textColor,
|
|
10369
|
+
bgColor,
|
|
10370
|
+
fontSize,
|
|
10371
|
+
count: count2,
|
|
9648
10372
|
formats,
|
|
10373
|
+
blocks: BLOCKS,
|
|
10374
|
+
sizes: SIZES,
|
|
10375
|
+
colors: COLORS,
|
|
10376
|
+
mediaLibrary,
|
|
10377
|
+
has,
|
|
9649
10378
|
exec,
|
|
9650
|
-
|
|
10379
|
+
setBlock,
|
|
10380
|
+
setFontSize,
|
|
10381
|
+
applyColor,
|
|
10382
|
+
togglePanel,
|
|
10383
|
+
toggleInlineCode,
|
|
9651
10384
|
addLink,
|
|
9652
|
-
|
|
10385
|
+
pickImage,
|
|
10386
|
+
insertMedia,
|
|
10387
|
+
onFilesChange,
|
|
9653
10388
|
onPaste,
|
|
10389
|
+
onDragOver,
|
|
10390
|
+
onDrop,
|
|
10391
|
+
rememberRange,
|
|
10392
|
+
onSourceInput,
|
|
9654
10393
|
toggleSource,
|
|
10394
|
+
toggleFullscreen,
|
|
9655
10395
|
syncFromDom,
|
|
9656
10396
|
syncFormats,
|
|
9657
10397
|
t
|
|
9658
10398
|
};
|
|
9659
10399
|
},
|
|
9660
10400
|
template: `
|
|
9661
|
-
<div
|
|
9662
|
-
|
|
9663
|
-
|
|
10401
|
+
<div
|
|
10402
|
+
class="djm-rich-editor"
|
|
10403
|
+
:class="{ 'djm-is-disabled': disabled, 'djm-is-fullscreen': fullscreen }"
|
|
10404
|
+
>
|
|
10405
|
+
<div class="djm-rich-editor-toolbar" role="toolbar" :aria-label="t('richEditor.toolbar')" @mousedown="rememberRange">
|
|
10406
|
+
<div class="djm-rich-editor-group" v-if="has('history')">
|
|
10407
|
+
<button
|
|
10408
|
+
class="djm-rich-editor-btn"
|
|
10409
|
+
type="button"
|
|
10410
|
+
:title="t('richEditor.undo')"
|
|
10411
|
+
:aria-label="t('richEditor.undo')"
|
|
10412
|
+
:disabled="disabled || sourceMode"
|
|
10413
|
+
@mousedown.prevent
|
|
10414
|
+
@click="exec('undo')"
|
|
10415
|
+
><Icon name="undo" :size="13" /></button>
|
|
10416
|
+
<button
|
|
10417
|
+
class="djm-rich-editor-btn"
|
|
10418
|
+
type="button"
|
|
10419
|
+
:title="t('richEditor.redo')"
|
|
10420
|
+
:aria-label="t('richEditor.redo')"
|
|
10421
|
+
:disabled="disabled || sourceMode"
|
|
10422
|
+
@mousedown.prevent
|
|
10423
|
+
@click="exec('redo')"
|
|
10424
|
+
><Icon name="redo" :size="13" /></button>
|
|
10425
|
+
</div>
|
|
10426
|
+
|
|
10427
|
+
<div class="djm-rich-editor-group" v-if="has('format')">
|
|
10428
|
+
<select
|
|
10429
|
+
class="djm-rich-editor-select"
|
|
10430
|
+
:value="formats.block"
|
|
10431
|
+
:aria-label="t('richEditor.block')"
|
|
10432
|
+
:disabled="disabled || sourceMode"
|
|
10433
|
+
@change="setBlock($event.target.value)"
|
|
10434
|
+
>
|
|
10435
|
+
<option v-for="item in blocks" :key="item.value" :value="item.value">{{ t('richEditor.' + item.label) }}</option>
|
|
10436
|
+
</select>
|
|
10437
|
+
<select
|
|
10438
|
+
class="djm-rich-editor-select djm-rich-editor-select-size"
|
|
10439
|
+
:value="fontSize"
|
|
10440
|
+
:aria-label="t('richEditor.fontSize')"
|
|
10441
|
+
:disabled="disabled || sourceMode"
|
|
10442
|
+
@change="setFontSize($event.target.value)"
|
|
10443
|
+
>
|
|
10444
|
+
<option v-for="item in sizes" :key="item.value" :value="item.value">{{ t('richEditor.' + item.label) }}</option>
|
|
10445
|
+
</select>
|
|
10446
|
+
</div>
|
|
10447
|
+
|
|
10448
|
+
<div class="djm-rich-editor-group" v-if="has('inline')">
|
|
9664
10449
|
<button
|
|
9665
10450
|
class="djm-rich-editor-btn djm-rich-editor-btn-bold"
|
|
9666
10451
|
type="button"
|
|
9667
10452
|
:title="t('richEditor.bold')"
|
|
9668
10453
|
:aria-label="t('richEditor.bold')"
|
|
9669
10454
|
:aria-pressed="String(formats.bold)"
|
|
9670
|
-
:disabled="disabled"
|
|
10455
|
+
:disabled="disabled || sourceMode"
|
|
9671
10456
|
@mousedown.prevent
|
|
9672
10457
|
@click="exec('bold')"
|
|
9673
10458
|
>B</button>
|
|
@@ -9677,7 +10462,7 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9677
10462
|
:title="t('richEditor.italic')"
|
|
9678
10463
|
:aria-label="t('richEditor.italic')"
|
|
9679
10464
|
:aria-pressed="String(formats.italic)"
|
|
9680
|
-
:disabled="disabled"
|
|
10465
|
+
:disabled="disabled || sourceMode"
|
|
9681
10466
|
@mousedown.prevent
|
|
9682
10467
|
@click="exec('italic')"
|
|
9683
10468
|
>I</button>
|
|
@@ -9687,7 +10472,7 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9687
10472
|
:title="t('richEditor.underline')"
|
|
9688
10473
|
:aria-label="t('richEditor.underline')"
|
|
9689
10474
|
:aria-pressed="String(formats.underline)"
|
|
9690
|
-
:disabled="disabled"
|
|
10475
|
+
:disabled="disabled || sourceMode"
|
|
9691
10476
|
@mousedown.prevent
|
|
9692
10477
|
@click="exec('underline')"
|
|
9693
10478
|
>U</button>
|
|
@@ -9697,46 +10482,130 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9697
10482
|
:title="t('richEditor.strike')"
|
|
9698
10483
|
:aria-label="t('richEditor.strike')"
|
|
9699
10484
|
:aria-pressed="String(formats.strike)"
|
|
9700
|
-
:disabled="disabled"
|
|
10485
|
+
:disabled="disabled || sourceMode"
|
|
9701
10486
|
@mousedown.prevent
|
|
9702
10487
|
@click="exec('strikeThrough')"
|
|
9703
10488
|
>S</button>
|
|
10489
|
+
<button
|
|
10490
|
+
class="djm-rich-editor-btn"
|
|
10491
|
+
type="button"
|
|
10492
|
+
:title="t('richEditor.inlineCode')"
|
|
10493
|
+
:aria-label="t('richEditor.inlineCode')"
|
|
10494
|
+
:aria-pressed="String(formats.code)"
|
|
10495
|
+
:disabled="disabled || sourceMode"
|
|
10496
|
+
@mousedown.prevent
|
|
10497
|
+
@click="toggleInlineCode"
|
|
10498
|
+
><Icon name="code" :size="13" /></button>
|
|
9704
10499
|
</div>
|
|
9705
10500
|
|
|
9706
|
-
<div class="djm-rich-editor-group">
|
|
9707
|
-
<button
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
10501
|
+
<div class="djm-rich-editor-group djm-rich-editor-color" v-if="has('color')">
|
|
10502
|
+
<button
|
|
10503
|
+
class="djm-rich-editor-btn"
|
|
10504
|
+
type="button"
|
|
10505
|
+
:title="t('richEditor.textColor')"
|
|
10506
|
+
:aria-label="t('richEditor.textColor')"
|
|
10507
|
+
:aria-expanded="String(panel === 'text')"
|
|
10508
|
+
:disabled="disabled || sourceMode"
|
|
10509
|
+
@mousedown.prevent
|
|
10510
|
+
@click.stop="togglePanel('text')"
|
|
10511
|
+
>
|
|
10512
|
+
<span class="djm-rich-editor-letter" :style="{ borderBottomColor: textColor }">A</span>
|
|
9717
10513
|
</button>
|
|
9718
|
-
<button
|
|
9719
|
-
|
|
10514
|
+
<button
|
|
10515
|
+
class="djm-rich-editor-btn"
|
|
10516
|
+
type="button"
|
|
10517
|
+
:title="t('richEditor.bgColor')"
|
|
10518
|
+
:aria-label="t('richEditor.bgColor')"
|
|
10519
|
+
:aria-expanded="String(panel === 'bg')"
|
|
10520
|
+
:disabled="disabled || sourceMode"
|
|
10521
|
+
@mousedown.prevent
|
|
10522
|
+
@click.stop="togglePanel('bg')"
|
|
10523
|
+
>
|
|
10524
|
+
<span class="djm-rich-editor-letter djm-rich-editor-letter-bg" :style="{ background: bgColor }">A</span>
|
|
9720
10525
|
</button>
|
|
10526
|
+
<div v-if="panel === 'text' || panel === 'bg'" class="djm-rich-editor-panel" role="group" :aria-label="t('richEditor.textColor')">
|
|
10527
|
+
<button
|
|
10528
|
+
v-for="color in colors"
|
|
10529
|
+
:key="color"
|
|
10530
|
+
class="djm-rich-editor-swatch"
|
|
10531
|
+
type="button"
|
|
10532
|
+
:title="color"
|
|
10533
|
+
:aria-label="color"
|
|
10534
|
+
:style="{ background: color }"
|
|
10535
|
+
@mousedown.prevent
|
|
10536
|
+
@click="applyColor(panel, color)"
|
|
10537
|
+
></button>
|
|
10538
|
+
</div>
|
|
9721
10539
|
</div>
|
|
9722
10540
|
|
|
9723
|
-
<div class="djm-rich-editor-group">
|
|
9724
|
-
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.
|
|
9725
|
-
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.
|
|
9726
|
-
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.
|
|
10541
|
+
<div class="djm-rich-editor-group" v-if="has('align')">
|
|
10542
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.alignLeft')" :aria-label="t('richEditor.alignLeft')" :aria-pressed="String(formats.align === 'left')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('justifyLeft')"><Icon name="align-left" :size="13" /></button>
|
|
10543
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.alignCenter')" :aria-label="t('richEditor.alignCenter')" :aria-pressed="String(formats.align === 'center')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('justifyCenter')"><Icon name="align-center" :size="13" /></button>
|
|
10544
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.alignRight')" :aria-label="t('richEditor.alignRight')" :aria-pressed="String(formats.align === 'right')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('justifyRight')"><Icon name="align-right" :size="13" /></button>
|
|
10545
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.alignJustify')" :aria-label="t('richEditor.alignJustify')" :aria-pressed="String(formats.align === 'justify')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('justifyFull')"><Icon name="align-justify" :size="13" /></button>
|
|
9727
10546
|
</div>
|
|
9728
10547
|
|
|
9729
|
-
<div class="djm-rich-editor-
|
|
9730
|
-
|
|
9731
|
-
class="djm-rich-editor-btn"
|
|
9732
|
-
|
|
9733
|
-
type="button"
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
<
|
|
9739
|
-
|
|
10548
|
+
<div class="djm-rich-editor-group" v-if="has('list')">
|
|
10549
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.bulletList')" :aria-label="t('richEditor.bulletList')" :aria-pressed="String(formats.list === 'ul')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('insertUnorderedList')"><Icon name="list" :size="13" /></button>
|
|
10550
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.numberList')" :aria-label="t('richEditor.numberList')" :aria-pressed="String(formats.list === 'ol')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('insertOrderedList')"><Icon name="list-ordered" :size="13" /></button>
|
|
10551
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.outdent')" :aria-label="t('richEditor.outdent')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('outdent')"><Icon name="outdent" :size="13" /></button>
|
|
10552
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.indent')" :aria-label="t('richEditor.indent')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('indent')"><Icon name="indent" :size="13" /></button>
|
|
10553
|
+
</div>
|
|
10554
|
+
|
|
10555
|
+
<div class="djm-rich-editor-group" v-if="has('insert')">
|
|
10556
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.link')" :aria-label="t('richEditor.link')" :disabled="disabled || sourceMode" @mousedown.prevent @click="addLink"><Icon name="link" :size="13" /></button>
|
|
10557
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.unlink')" :aria-label="t('richEditor.unlink')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('unlink')"><Icon name="unlink" :size="13" /></button>
|
|
10558
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.singleImage')" :aria-label="t('richEditor.singleImage')" :disabled="disabled || sourceMode" @mousedown.prevent @click="pickImage(false)"><Icon name="image" :size="13" /></button>
|
|
10559
|
+
<button v-if="multiple" class="djm-rich-editor-btn" type="button" :title="t('richEditor.multiImage')" :aria-label="t('richEditor.multiImage')" :disabled="disabled || sourceMode" @mousedown.prevent @click="pickImage(true)"><Icon name="images" :size="13" /></button>
|
|
10560
|
+
<MediaPicker
|
|
10561
|
+
v-if="mediaLibrary.length"
|
|
10562
|
+
class="djm-rich-editor-picker"
|
|
10563
|
+
multiple
|
|
10564
|
+
accept="image"
|
|
10565
|
+
:items="mediaLibrary"
|
|
10566
|
+
:title="t('richEditor.mediaLibrary')"
|
|
10567
|
+
:disabled="disabled || sourceMode"
|
|
10568
|
+
@change="insertMedia"
|
|
10569
|
+
>
|
|
10570
|
+
<template #trigger="{ open }">
|
|
10571
|
+
<button
|
|
10572
|
+
class="djm-rich-editor-btn"
|
|
10573
|
+
type="button"
|
|
10574
|
+
:title="t('richEditor.mediaLibrary')"
|
|
10575
|
+
:aria-label="t('richEditor.mediaLibrary')"
|
|
10576
|
+
:disabled="disabled || sourceMode"
|
|
10577
|
+
@mousedown.prevent="rememberRange"
|
|
10578
|
+
@click="open"
|
|
10579
|
+
><Icon name="grid" :size="13" /></button>
|
|
10580
|
+
</template>
|
|
10581
|
+
</MediaPicker>
|
|
10582
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.hr')" :aria-label="t('richEditor.hr')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('insertHorizontalRule')">\u2014</button>
|
|
10583
|
+
<button class="djm-rich-editor-btn" type="button" :title="t('richEditor.clearFormat')" :aria-label="t('richEditor.clearFormat')" :disabled="disabled || sourceMode" @mousedown.prevent @click="exec('removeFormat')"><span class="djm-rich-editor-glyph">T\xD7</span></button>
|
|
10584
|
+
</div>
|
|
10585
|
+
|
|
10586
|
+
<div class="djm-rich-editor-spacer" v-if="has('view')"></div>
|
|
10587
|
+
<div class="djm-rich-editor-group djm-rich-editor-group-end" v-if="has('view')">
|
|
10588
|
+
<button
|
|
10589
|
+
class="djm-rich-editor-btn"
|
|
10590
|
+
:class="{ 'djm-is-active': sourceMode }"
|
|
10591
|
+
type="button"
|
|
10592
|
+
:title="sourceMode ? t('richEditor.visual') : t('richEditor.source')"
|
|
10593
|
+
:aria-label="sourceMode ? t('richEditor.visual') : t('richEditor.source')"
|
|
10594
|
+
:aria-pressed="String(sourceMode)"
|
|
10595
|
+
:disabled="disabled"
|
|
10596
|
+
@click="toggleSource"
|
|
10597
|
+
><Icon name="code" :size="13" /></button>
|
|
10598
|
+
<button
|
|
10599
|
+
class="djm-rich-editor-btn"
|
|
10600
|
+
:class="{ 'djm-is-active': fullscreen }"
|
|
10601
|
+
type="button"
|
|
10602
|
+
:title="fullscreen ? t('richEditor.exitFullscreen') : t('richEditor.fullscreen')"
|
|
10603
|
+
:aria-label="fullscreen ? t('richEditor.exitFullscreen') : t('richEditor.fullscreen')"
|
|
10604
|
+
:aria-pressed="String(fullscreen)"
|
|
10605
|
+
:disabled="disabled"
|
|
10606
|
+
@click="toggleFullscreen"
|
|
10607
|
+
><Icon :name="fullscreen ? 'minimize' : 'maximize'" :size="13" /></button>
|
|
10608
|
+
</div>
|
|
9740
10609
|
</div>
|
|
9741
10610
|
|
|
9742
10611
|
<div
|
|
@@ -9752,6 +10621,8 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9752
10621
|
:style="{ minHeight: minHeight + 'px' }"
|
|
9753
10622
|
@input="syncFromDom"
|
|
9754
10623
|
@paste="onPaste"
|
|
10624
|
+
@dragover="onDragOver"
|
|
10625
|
+
@drop="onDrop"
|
|
9755
10626
|
></div>
|
|
9756
10627
|
|
|
9757
10628
|
<textarea
|
|
@@ -9764,6 +10635,21 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
9764
10635
|
spellcheck="false"
|
|
9765
10636
|
@input="onSourceInput"
|
|
9766
10637
|
></textarea>
|
|
10638
|
+
|
|
10639
|
+
<div v-if="showCount" class="djm-rich-editor-foot">
|
|
10640
|
+
<span v-if="uploadCount" class="djm-rich-editor-foot-item">{{ t('richEditor.uploading', { count: uploadCount }) }}</span>
|
|
10641
|
+
<span class="djm-rich-editor-foot-item">{{ t('richEditor.wordCount', { count }) }}</span>
|
|
10642
|
+
</div>
|
|
10643
|
+
|
|
10644
|
+
<input
|
|
10645
|
+
ref="fileInput"
|
|
10646
|
+
class="djm-rich-editor-file"
|
|
10647
|
+
type="file"
|
|
10648
|
+
:accept="accept"
|
|
10649
|
+
tabindex="-1"
|
|
10650
|
+
aria-hidden="true"
|
|
10651
|
+
@change="onFilesChange"
|
|
10652
|
+
/>
|
|
9767
10653
|
</div>
|
|
9768
10654
|
`
|
|
9769
10655
|
};
|
|
@@ -11827,197 +12713,6 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
11827
12713
|
`
|
|
11828
12714
|
};
|
|
11829
12715
|
|
|
11830
|
-
// src/components/media-picker/index.js
|
|
11831
|
-
var media_picker_default = {
|
|
11832
|
-
name: "DjmMediaPicker",
|
|
11833
|
-
components: { DjmModal: modal_default, DjmPagination: pagination_default, DjmIcon: icon_default },
|
|
11834
|
-
props: {
|
|
11835
|
-
modelValue: { type: [Array, String, Number], default: "" },
|
|
11836
|
-
items: { type: Array, default: () => [] },
|
|
11837
|
-
multiple: { type: Boolean, default: false },
|
|
11838
|
-
// 最多选几个,0 表示不限
|
|
11839
|
-
limit: { type: Number, default: 0 },
|
|
11840
|
-
// image 只显示图片,file 只显示非图片,空串不过滤
|
|
11841
|
-
accept: { type: String, default: "" },
|
|
11842
|
-
title: { type: String, default: "" },
|
|
11843
|
-
triggerText: { type: String, default: "" },
|
|
11844
|
-
pageSize: { type: Number, default: 12 },
|
|
11845
|
-
disabled: { type: Boolean, default: false }
|
|
11846
|
-
},
|
|
11847
|
-
emits: ["update:modelValue", "change"],
|
|
11848
|
-
setup(props, { emit }) {
|
|
11849
|
-
const open = ref(false);
|
|
11850
|
-
const keyword = ref("");
|
|
11851
|
-
const page = ref(1);
|
|
11852
|
-
const temp = ref([]);
|
|
11853
|
-
const selectedIds = computed(() => {
|
|
11854
|
-
if (props.multiple) return Array.isArray(props.modelValue) ? props.modelValue : [];
|
|
11855
|
-
if (props.modelValue === "" || props.modelValue === null || props.modelValue === void 0) return [];
|
|
11856
|
-
return [props.modelValue];
|
|
11857
|
-
});
|
|
11858
|
-
function isImage(item) {
|
|
11859
|
-
if (item.type) return item.type === "image";
|
|
11860
|
-
return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(item.url || "");
|
|
11861
|
-
}
|
|
11862
|
-
const filtered = computed(() => {
|
|
11863
|
-
const key = keyword.value.trim().toLowerCase();
|
|
11864
|
-
return props.items.filter((item) => {
|
|
11865
|
-
if (props.accept === "image" && !isImage(item)) return false;
|
|
11866
|
-
if (props.accept === "file" && isImage(item)) return false;
|
|
11867
|
-
if (key && !String(item.name || "").toLowerCase().includes(key)) return false;
|
|
11868
|
-
return true;
|
|
11869
|
-
});
|
|
11870
|
-
});
|
|
11871
|
-
const paged = computed(() => {
|
|
11872
|
-
const start = (page.value - 1) * props.pageSize;
|
|
11873
|
-
return filtered.value.slice(start, start + props.pageSize);
|
|
11874
|
-
});
|
|
11875
|
-
watch(keyword, () => {
|
|
11876
|
-
page.value = 1;
|
|
11877
|
-
});
|
|
11878
|
-
watch(
|
|
11879
|
-
() => filtered.value.length,
|
|
11880
|
-
() => {
|
|
11881
|
-
const max = Math.max(1, Math.ceil(filtered.value.length / props.pageSize));
|
|
11882
|
-
if (page.value > max) page.value = max;
|
|
11883
|
-
}
|
|
11884
|
-
);
|
|
11885
|
-
function isChecked(id) {
|
|
11886
|
-
return temp.value.includes(id);
|
|
11887
|
-
}
|
|
11888
|
-
function atLimit() {
|
|
11889
|
-
return props.limit > 0 && temp.value.length >= props.limit;
|
|
11890
|
-
}
|
|
11891
|
-
function toggle(item) {
|
|
11892
|
-
if (props.multiple) {
|
|
11893
|
-
if (isChecked(item.id)) {
|
|
11894
|
-
temp.value = temp.value.filter((id) => id !== item.id);
|
|
11895
|
-
return;
|
|
11896
|
-
}
|
|
11897
|
-
if (atLimit()) return;
|
|
11898
|
-
temp.value = [...temp.value, item.id];
|
|
11899
|
-
return;
|
|
11900
|
-
}
|
|
11901
|
-
temp.value = [item.id];
|
|
11902
|
-
}
|
|
11903
|
-
function openPanel() {
|
|
11904
|
-
if (props.disabled) return;
|
|
11905
|
-
temp.value = [...selectedIds.value];
|
|
11906
|
-
keyword.value = "";
|
|
11907
|
-
page.value = 1;
|
|
11908
|
-
open.value = true;
|
|
11909
|
-
}
|
|
11910
|
-
function commit() {
|
|
11911
|
-
const next = props.multiple ? [...temp.value] : temp.value[0] !== void 0 ? temp.value[0] : "";
|
|
11912
|
-
emit("update:modelValue", next);
|
|
11913
|
-
emit("change", next);
|
|
11914
|
-
open.value = false;
|
|
11915
|
-
}
|
|
11916
|
-
function cancel() {
|
|
11917
|
-
temp.value = [...selectedIds.value];
|
|
11918
|
-
open.value = false;
|
|
11919
|
-
}
|
|
11920
|
-
return {
|
|
11921
|
-
open,
|
|
11922
|
-
keyword,
|
|
11923
|
-
page,
|
|
11924
|
-
temp,
|
|
11925
|
-
selectedIds,
|
|
11926
|
-
filtered,
|
|
11927
|
-
paged,
|
|
11928
|
-
isImage,
|
|
11929
|
-
isChecked,
|
|
11930
|
-
atLimit,
|
|
11931
|
-
toggle,
|
|
11932
|
-
openPanel,
|
|
11933
|
-
commit,
|
|
11934
|
-
cancel,
|
|
11935
|
-
t
|
|
11936
|
-
};
|
|
11937
|
-
},
|
|
11938
|
-
template: `
|
|
11939
|
-
<span class="djm-media-picker">
|
|
11940
|
-
<slot name="trigger" :open="openPanel" :count="selectedIds.length">
|
|
11941
|
-
<button
|
|
11942
|
-
class="djm-btn"
|
|
11943
|
-
type="button"
|
|
11944
|
-
:disabled="disabled"
|
|
11945
|
-
@click="openPanel"
|
|
11946
|
-
>{{ triggerText || t('mediaPicker.select') }}<span v-if="selectedIds.length" class="djm-media-picker-count">{{ selectedIds.length }}</span></button>
|
|
11947
|
-
</slot>
|
|
11948
|
-
|
|
11949
|
-
<DjmModal
|
|
11950
|
-
:model-value="open"
|
|
11951
|
-
:title="title || t('mediaPicker.title')"
|
|
11952
|
-
width="760px"
|
|
11953
|
-
@update:model-value="cancel"
|
|
11954
|
-
>
|
|
11955
|
-
<div class="djm-media-toolbar">
|
|
11956
|
-
<label class="djm-media-search">
|
|
11957
|
-
<DjmIcon name="search" :size="13" />
|
|
11958
|
-
<input
|
|
11959
|
-
v-model="keyword"
|
|
11960
|
-
class="djm-media-search-input"
|
|
11961
|
-
type="text"
|
|
11962
|
-
:placeholder="t('mediaPicker.search')"
|
|
11963
|
-
:aria-label="t('mediaPicker.search')"
|
|
11964
|
-
/>
|
|
11965
|
-
</label>
|
|
11966
|
-
<span class="djm-media-toolbar-slot">
|
|
11967
|
-
<slot name="toolbar"></slot>
|
|
11968
|
-
</span>
|
|
11969
|
-
</div>
|
|
11970
|
-
|
|
11971
|
-
<div class="djm-media-grid" role="listbox" :aria-multiselectable="multiple ? 'true' : 'false'">
|
|
11972
|
-
<div
|
|
11973
|
-
v-for="item in paged"
|
|
11974
|
-
:key="item.id"
|
|
11975
|
-
class="djm-media-item"
|
|
11976
|
-
:class="{
|
|
11977
|
-
'djm-is-checked': isChecked(item.id),
|
|
11978
|
-
'djm-is-disabled': multiple && atLimit() && !isChecked(item.id)
|
|
11979
|
-
}"
|
|
11980
|
-
role="option"
|
|
11981
|
-
tabindex="0"
|
|
11982
|
-
:aria-selected="String(isChecked(item.id))"
|
|
11983
|
-
@click="toggle(item)"
|
|
11984
|
-
@keydown.enter.prevent="toggle(item)"
|
|
11985
|
-
@keydown.space.prevent="toggle(item)"
|
|
11986
|
-
>
|
|
11987
|
-
<div class="djm-media-thumb">
|
|
11988
|
-
<img v-if="isImage(item)" :src="item.url" :alt="item.name" loading="lazy" />
|
|
11989
|
-
<DjmIcon v-else name="folder" :size="26" />
|
|
11990
|
-
<span v-if="isChecked(item.id)" class="djm-media-check">
|
|
11991
|
-
<DjmIcon name="check" :size="11" />
|
|
11992
|
-
</span>
|
|
11993
|
-
</div>
|
|
11994
|
-
<div class="djm-media-name" :title="item.name">{{ item.name }}</div>
|
|
11995
|
-
</div>
|
|
11996
|
-
<div v-if="!paged.length" class="djm-media-empty">{{ t('mediaPicker.empty') }}</div>
|
|
11997
|
-
</div>
|
|
11998
|
-
|
|
11999
|
-
<div class="djm-media-foot">
|
|
12000
|
-
<span class="djm-media-total">
|
|
12001
|
-
{{ t('mediaPicker.total', { total: filtered.length }) }}
|
|
12002
|
-
<em v-if="multiple && limit > 0">{{ t('mediaPicker.limit', { limit }) }}</em>
|
|
12003
|
-
</span>
|
|
12004
|
-
<DjmPagination
|
|
12005
|
-
v-if="filtered.length > pageSize"
|
|
12006
|
-
v-model:page="page"
|
|
12007
|
-
:page-size="pageSize"
|
|
12008
|
-
:total="filtered.length"
|
|
12009
|
-
/>
|
|
12010
|
-
</div>
|
|
12011
|
-
|
|
12012
|
-
<template #footer>
|
|
12013
|
-
<button class="djm-btn" type="button" @click="cancel">{{ t('common.cancel') }}</button>
|
|
12014
|
-
<button class="djm-btn djm-btn-primary" type="button" @click="commit">{{ t('common.confirm') }}</button>
|
|
12015
|
-
</template>
|
|
12016
|
-
</DjmModal>
|
|
12017
|
-
</span>
|
|
12018
|
-
`
|
|
12019
|
-
};
|
|
12020
|
-
|
|
12021
12716
|
// src/components/kanban/index.js
|
|
12022
12717
|
function cloneColumns(list) {
|
|
12023
12718
|
return (list || []).map((col) => ({ ...col, cards: (col.cards || []).slice() }));
|
|
@@ -15336,7 +16031,7 @@ Minimum version required to store current data is: ` + i + `.
|
|
|
15336
16031
|
org_chart_default,
|
|
15337
16032
|
page_tabs_default
|
|
15338
16033
|
];
|
|
15339
|
-
var version = "0.
|
|
16034
|
+
var version = "0.72.0";
|
|
15340
16035
|
function install(app, options = {}) {
|
|
15341
16036
|
if (options.locale) setLocale(options.locale);
|
|
15342
16037
|
components.forEach((component) => app.component(component.name, component));
|