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.
Files changed (40) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +38 -2
  3. package/dist/djm-ui.cjs +1051 -356
  4. package/dist/djm-ui.cjs.map +4 -4
  5. package/dist/djm-ui.css +752 -17
  6. package/dist/djm-ui.esm.js +1127 -432
  7. package/dist/djm-ui.esm.js.map +4 -4
  8. package/dist/djm-ui.esm.min.js +375 -229
  9. package/dist/djm-ui.min.cjs +375 -229
  10. package/dist/djm-ui.min.css +2 -2
  11. package/dist/djm-ui.umd.js +967 -272
  12. package/dist/djm-ui.umd.min.js +375 -229
  13. package/dist/mcp/manifest.json +12195 -0
  14. package/dist/mcp/server.js +90 -0
  15. package/package.json +12 -4
  16. package/src/components/icon/index.js +13 -1
  17. package/src/components/input/index.js +17 -6
  18. package/src/components/rich-editor/index.js +724 -61
  19. package/src/index.js +1 -1
  20. package/src/locale/en-US.js +33 -1
  21. package/src/locale/zh-CN.js +33 -1
  22. package/src/styles/base.css +2 -3
  23. package/src/styles/components/button.css +2 -1
  24. package/src/styles/components/console.css +5 -0
  25. package/src/styles/components/content.css +517 -0
  26. package/src/styles/components/form.css +5 -0
  27. package/src/styles/components/layout.css +327 -1
  28. package/src/styles/components/media-picker.css +3 -3
  29. package/src/styles/components/menu.css +2 -1
  30. package/src/styles/components/org-picker.css +1 -1
  31. package/src/styles/components/responsive.css +86 -0
  32. package/src/styles/components/rich-editor.css +206 -7
  33. package/src/styles/components/skin.css +54 -0
  34. package/src/styles/components/template.css +5 -0
  35. package/src/styles/index.css +1 -0
  36. package/src/styles/theme-dark.css +2 -0
  37. package/src/styles/tokens.css +8 -0
  38. package/src/theme/index.js +40 -0
  39. package/src/utils/sanitize-html.js +44 -6
  40. package/types/index.d.ts +8 -0
package/dist/djm-ui.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! DJM UI v0.67.3 · MIT · 2026-10-26 */
1
+ /*! DJM UI v0.72.0 · MIT · 2026-10-26 */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1119,7 +1119,19 @@ var PATHS = {
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 @@ var input_default = {
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.clearStaleValue();
1260
- this.staleTimer = setTimeout(() => this.clearStaleValue(), 300);
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
- clearTimeout(this.staleTimer);
1286
+ this.staleTimers.forEach((timer) => clearTimeout(timer));
1265
1287
  window.removeEventListener("pageshow", this.clearStaleValue);
1266
1288
  },
1267
1289
  template: `
@@ -1281,7 +1303,7 @@ var input_default = {
1281
1303
  @input="onInput"
1282
1304
  @change="$emit('change', $event)"
1283
1305
  @keyup.enter="$emit('enter', $event)"
1284
- @focus="$emit('focus', $event)"
1306
+ @focus="onFocus"
1285
1307
  @blur="$emit('blur', $event)"
1286
1308
  />
1287
1309
  <button
@@ -1311,7 +1333,7 @@ var input_default = {
1311
1333
  @input="onInput"
1312
1334
  @change="$emit('change', $event)"
1313
1335
  @keyup.enter="$emit('enter', $event)"
1314
- @focus="$emit('focus', $event)"
1336
+ @focus="onFocus"
1315
1337
  @blur="$emit('blur', $event)"
1316
1338
  />
1317
1339
  `
@@ -1436,7 +1458,6 @@ var zh_CN_default = {
1436
1458
  "richEditor.italic": "\u659C\u4F53",
1437
1459
  "richEditor.underline": "\u4E0B\u5212\u7EBF",
1438
1460
  "richEditor.strike": "\u5220\u9664\u7EBF",
1439
- "richEditor.heading": "\u4E8C\u7EA7\u6807\u9898",
1440
1461
  "richEditor.paragraph": "\u6B63\u6587",
1441
1462
  "richEditor.bulletList": "\u65E0\u5E8F\u5217\u8868",
1442
1463
  "richEditor.numberList": "\u6709\u5E8F\u5217\u8868",
@@ -1447,6 +1468,39 @@ var zh_CN_default = {
1447
1468
  "richEditor.redo": "\u91CD\u505A",
1448
1469
  "richEditor.source": "\u6E90\u7801",
1449
1470
  "richEditor.visual": "\u53EF\u89C6\u5316",
1471
+ "richEditor.toolbar": "\u7F16\u8F91\u5DE5\u5177\u680F",
1472
+ "richEditor.block": "\u6BB5\u843D\u683C\u5F0F",
1473
+ "richEditor.heading1": "\u6807\u9898 1",
1474
+ "richEditor.heading2": "\u6807\u9898 2",
1475
+ "richEditor.heading3": "\u6807\u9898 3",
1476
+ "richEditor.quote": "\u5F15\u7528",
1477
+ "richEditor.codeBlock": "\u4EE3\u7801\u5757",
1478
+ "richEditor.fontSize": "\u5B57\u53F7",
1479
+ "richEditor.defaultSize": "\u9ED8\u8BA4",
1480
+ "richEditor.inlineCode": "\u884C\u5185\u4EE3\u7801",
1481
+ "richEditor.textColor": "\u6587\u5B57\u989C\u8272",
1482
+ "richEditor.bgColor": "\u80CC\u666F\u8272",
1483
+ "richEditor.alignLeft": "\u5DE6\u5BF9\u9F50",
1484
+ "richEditor.alignCenter": "\u5C45\u4E2D",
1485
+ "richEditor.alignRight": "\u53F3\u5BF9\u9F50",
1486
+ "richEditor.alignJustify": "\u4E24\u7AEF\u5BF9\u9F50",
1487
+ "richEditor.outdent": "\u51CF\u5C11\u7F29\u8FDB",
1488
+ "richEditor.indent": "\u589E\u52A0\u7F29\u8FDB",
1489
+ "richEditor.unlink": "\u53D6\u6D88\u94FE\u63A5",
1490
+ "richEditor.image": "\u63D2\u5165\u56FE\u7247",
1491
+ "richEditor.singleImage": "\u63D2\u5165\u5355\u56FE",
1492
+ "richEditor.multiImage": "\u63D2\u5165\u591A\u56FE",
1493
+ "richEditor.mediaLibrary": "\u4ECE\u7D20\u6750\u5E93\u9009\u62E9",
1494
+ "richEditor.hr": "\u5206\u5272\u7EBF",
1495
+ "richEditor.fullscreen": "\u5168\u5C4F",
1496
+ "richEditor.exitFullscreen": "\u9000\u51FA\u5168\u5C4F",
1497
+ "richEditor.wordCount": "\u5171 {count} \u5B57",
1498
+ "richEditor.uploading": "\u6B63\u5728\u4E0A\u4F20 {count} \u5F20\u56FE\u7247\u2026",
1499
+ "richEditor.uploadFailed": "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25",
1500
+ "richEditor.imageTypeError": "\u53EA\u80FD\u4E0A\u4F20\u56FE\u7247\u6587\u4EF6",
1501
+ "richEditor.imageSizeError": "\u56FE\u7247\u4E0D\u80FD\u8D85\u8FC7 {size}MB",
1502
+ "richEditor.imageUrlInvalid": "\u56FE\u7247\u5730\u5740\u4E0D\u5408\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u63D2\u5165",
1503
+ "richEditor.linkInvalid": "\u94FE\u63A5\u53EA\u652F\u6301 http(s)\u3001mailto\u3001tel \u6216\u7AD9\u5185\u76F8\u5BF9\u5730\u5740",
1450
1504
  "approval.start": "\u53D1\u8D77",
1451
1505
  "approval.cc": "\u6284\u9001",
1452
1506
  "approval.approved": "\u5DF2\u901A\u8FC7",
@@ -1655,7 +1709,6 @@ var en_US_default = {
1655
1709
  "richEditor.italic": "Italic",
1656
1710
  "richEditor.underline": "Underline",
1657
1711
  "richEditor.strike": "Strikethrough",
1658
- "richEditor.heading": "Heading 2",
1659
1712
  "richEditor.paragraph": "Paragraph",
1660
1713
  "richEditor.bulletList": "Bullet list",
1661
1714
  "richEditor.numberList": "Numbered list",
@@ -1666,6 +1719,39 @@ var en_US_default = {
1666
1719
  "richEditor.redo": "Redo",
1667
1720
  "richEditor.source": "Source",
1668
1721
  "richEditor.visual": "Visual",
1722
+ "richEditor.toolbar": "Formatting toolbar",
1723
+ "richEditor.block": "Paragraph style",
1724
+ "richEditor.heading1": "Heading 1",
1725
+ "richEditor.heading2": "Heading 2",
1726
+ "richEditor.heading3": "Heading 3",
1727
+ "richEditor.quote": "Quote",
1728
+ "richEditor.codeBlock": "Code block",
1729
+ "richEditor.fontSize": "Font size",
1730
+ "richEditor.defaultSize": "Default",
1731
+ "richEditor.inlineCode": "Inline code",
1732
+ "richEditor.textColor": "Text color",
1733
+ "richEditor.bgColor": "Background color",
1734
+ "richEditor.alignLeft": "Align left",
1735
+ "richEditor.alignCenter": "Align center",
1736
+ "richEditor.alignRight": "Align right",
1737
+ "richEditor.alignJustify": "Justify",
1738
+ "richEditor.outdent": "Decrease indent",
1739
+ "richEditor.indent": "Increase indent",
1740
+ "richEditor.unlink": "Remove link",
1741
+ "richEditor.image": "Insert image",
1742
+ "richEditor.singleImage": "Insert single image",
1743
+ "richEditor.multiImage": "Insert multiple images",
1744
+ "richEditor.mediaLibrary": "Choose from media library",
1745
+ "richEditor.hr": "Divider",
1746
+ "richEditor.fullscreen": "Fullscreen",
1747
+ "richEditor.exitFullscreen": "Exit fullscreen",
1748
+ "richEditor.wordCount": "{count} characters",
1749
+ "richEditor.uploading": "Uploading {count} image(s)\u2026",
1750
+ "richEditor.uploadFailed": "Image upload failed",
1751
+ "richEditor.imageTypeError": "Only image files are allowed",
1752
+ "richEditor.imageSizeError": "Image must be smaller than {size}MB",
1753
+ "richEditor.imageUrlInvalid": "Invalid image URL, insertion rejected",
1754
+ "richEditor.linkInvalid": "Only http(s), mailto, tel or relative URLs are allowed",
1669
1755
  "approval.start": "Started",
1670
1756
  "approval.cc": "CC",
1671
1757
  "approval.approved": "Approved",
@@ -9484,7 +9570,199 @@ var tree_select_default = {
9484
9570
  };
9485
9571
 
9486
9572
  // src/components/rich-editor/index.js
9573
+ var import_vue33 = require("vue");
9574
+
9575
+ // src/components/media-picker/index.js
9487
9576
  var import_vue32 = require("vue");
9577
+ var media_picker_default = {
9578
+ name: "DjmMediaPicker",
9579
+ components: { DjmModal: modal_default, DjmPagination: pagination_default, DjmIcon: icon_default },
9580
+ props: {
9581
+ modelValue: { type: [Array, String, Number], default: "" },
9582
+ items: { type: Array, default: () => [] },
9583
+ multiple: { type: Boolean, default: false },
9584
+ // 最多选几个,0 表示不限
9585
+ limit: { type: Number, default: 0 },
9586
+ // image 只显示图片,file 只显示非图片,空串不过滤
9587
+ accept: { type: String, default: "" },
9588
+ title: { type: String, default: "" },
9589
+ triggerText: { type: String, default: "" },
9590
+ pageSize: { type: Number, default: 12 },
9591
+ disabled: { type: Boolean, default: false }
9592
+ },
9593
+ emits: ["update:modelValue", "change"],
9594
+ setup(props, { emit }) {
9595
+ const open = (0, import_vue32.ref)(false);
9596
+ const keyword = (0, import_vue32.ref)("");
9597
+ const page = (0, import_vue32.ref)(1);
9598
+ const temp = (0, import_vue32.ref)([]);
9599
+ const selectedIds = (0, import_vue32.computed)(() => {
9600
+ if (props.multiple) return Array.isArray(props.modelValue) ? props.modelValue : [];
9601
+ if (props.modelValue === "" || props.modelValue === null || props.modelValue === void 0) return [];
9602
+ return [props.modelValue];
9603
+ });
9604
+ function isImage(item) {
9605
+ if (item.type) return item.type === "image";
9606
+ return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(item.url || "");
9607
+ }
9608
+ const filtered = (0, import_vue32.computed)(() => {
9609
+ const key = keyword.value.trim().toLowerCase();
9610
+ return props.items.filter((item) => {
9611
+ if (props.accept === "image" && !isImage(item)) return false;
9612
+ if (props.accept === "file" && isImage(item)) return false;
9613
+ if (key && !String(item.name || "").toLowerCase().includes(key)) return false;
9614
+ return true;
9615
+ });
9616
+ });
9617
+ const paged = (0, import_vue32.computed)(() => {
9618
+ const start = (page.value - 1) * props.pageSize;
9619
+ return filtered.value.slice(start, start + props.pageSize);
9620
+ });
9621
+ (0, import_vue32.watch)(keyword, () => {
9622
+ page.value = 1;
9623
+ });
9624
+ (0, import_vue32.watch)(
9625
+ () => filtered.value.length,
9626
+ () => {
9627
+ const max = Math.max(1, Math.ceil(filtered.value.length / props.pageSize));
9628
+ if (page.value > max) page.value = max;
9629
+ }
9630
+ );
9631
+ function isChecked(id) {
9632
+ return temp.value.includes(id);
9633
+ }
9634
+ function atLimit() {
9635
+ return props.limit > 0 && temp.value.length >= props.limit;
9636
+ }
9637
+ function toggle(item) {
9638
+ if (props.multiple) {
9639
+ if (isChecked(item.id)) {
9640
+ temp.value = temp.value.filter((id) => id !== item.id);
9641
+ return;
9642
+ }
9643
+ if (atLimit()) return;
9644
+ temp.value = [...temp.value, item.id];
9645
+ return;
9646
+ }
9647
+ temp.value = [item.id];
9648
+ }
9649
+ function openPanel() {
9650
+ if (props.disabled) return;
9651
+ temp.value = [...selectedIds.value];
9652
+ keyword.value = "";
9653
+ page.value = 1;
9654
+ open.value = true;
9655
+ }
9656
+ function commit() {
9657
+ const next = props.multiple ? [...temp.value] : temp.value[0] !== void 0 ? temp.value[0] : "";
9658
+ emit("update:modelValue", next);
9659
+ emit("change", next);
9660
+ open.value = false;
9661
+ }
9662
+ function cancel() {
9663
+ temp.value = [...selectedIds.value];
9664
+ open.value = false;
9665
+ }
9666
+ return {
9667
+ open,
9668
+ keyword,
9669
+ page,
9670
+ temp,
9671
+ selectedIds,
9672
+ filtered,
9673
+ paged,
9674
+ isImage,
9675
+ isChecked,
9676
+ atLimit,
9677
+ toggle,
9678
+ openPanel,
9679
+ commit,
9680
+ cancel,
9681
+ t
9682
+ };
9683
+ },
9684
+ template: `
9685
+ <span class="djm-media-picker">
9686
+ <slot name="trigger" :open="openPanel" :count="selectedIds.length">
9687
+ <button
9688
+ class="djm-btn"
9689
+ type="button"
9690
+ :disabled="disabled"
9691
+ @click="openPanel"
9692
+ >{{ triggerText || t('mediaPicker.select') }}<span v-if="selectedIds.length" class="djm-media-picker-count">{{ selectedIds.length }}</span></button>
9693
+ </slot>
9694
+
9695
+ <DjmModal
9696
+ :model-value="open"
9697
+ :title="title || t('mediaPicker.title')"
9698
+ width="760px"
9699
+ @update:model-value="cancel"
9700
+ >
9701
+ <div class="djm-media-toolbar">
9702
+ <label class="djm-media-search">
9703
+ <DjmIcon name="search" :size="13" />
9704
+ <input
9705
+ v-model="keyword"
9706
+ class="djm-media-search-input"
9707
+ type="text"
9708
+ :placeholder="t('mediaPicker.search')"
9709
+ :aria-label="t('mediaPicker.search')"
9710
+ />
9711
+ </label>
9712
+ <span class="djm-media-toolbar-slot">
9713
+ <slot name="toolbar"></slot>
9714
+ </span>
9715
+ </div>
9716
+
9717
+ <div class="djm-media-grid" role="listbox" :aria-multiselectable="multiple ? 'true' : 'false'">
9718
+ <div
9719
+ v-for="item in paged"
9720
+ :key="item.id"
9721
+ class="djm-media-item"
9722
+ :class="{
9723
+ 'djm-is-checked': isChecked(item.id),
9724
+ 'djm-is-disabled': multiple && atLimit() && !isChecked(item.id)
9725
+ }"
9726
+ role="option"
9727
+ tabindex="0"
9728
+ :aria-selected="String(isChecked(item.id))"
9729
+ @click="toggle(item)"
9730
+ @keydown.enter.prevent="toggle(item)"
9731
+ @keydown.space.prevent="toggle(item)"
9732
+ >
9733
+ <div class="djm-media-thumb">
9734
+ <img v-if="isImage(item)" :src="item.url" :alt="item.name" loading="lazy" />
9735
+ <DjmIcon v-else name="folder" :size="26" />
9736
+ <span v-if="isChecked(item.id)" class="djm-media-check">
9737
+ <DjmIcon name="check" :size="11" />
9738
+ </span>
9739
+ </div>
9740
+ <div class="djm-media-name" :title="item.name">{{ item.name }}</div>
9741
+ </div>
9742
+ <div v-if="!paged.length" class="djm-media-empty">{{ t('mediaPicker.empty') }}</div>
9743
+ </div>
9744
+
9745
+ <div class="djm-media-foot">
9746
+ <span class="djm-media-total">
9747
+ {{ t('mediaPicker.total', { total: filtered.length }) }}
9748
+ <em v-if="multiple && limit > 0">{{ t('mediaPicker.limit', { limit }) }}</em>
9749
+ </span>
9750
+ <DjmPagination
9751
+ v-if="filtered.length > pageSize"
9752
+ v-model:page="page"
9753
+ :page-size="pageSize"
9754
+ :total="filtered.length"
9755
+ />
9756
+ </div>
9757
+
9758
+ <template #footer>
9759
+ <button class="djm-btn" type="button" @click="cancel">{{ t('common.cancel') }}</button>
9760
+ <button class="djm-btn djm-btn-primary" type="button" @click="commit">{{ t('common.confirm') }}</button>
9761
+ </template>
9762
+ </DjmModal>
9763
+ </span>
9764
+ `
9765
+ };
9488
9766
 
9489
9767
  // src/utils/sanitize-html.js
9490
9768
  var TAGS = /* @__PURE__ */ new Set([
@@ -9524,7 +9802,18 @@ var TAGS = /* @__PURE__ */ new Set([
9524
9802
  "th",
9525
9803
  "td"
9526
9804
  ]);
9527
- var GLOBAL_ATTRS = /* @__PURE__ */ new Set(["class", "title"]);
9805
+ var GLOBAL_ATTRS = /* @__PURE__ */ new Set(["class", "title", "style"]);
9806
+ var STYLE_PROPS = /* @__PURE__ */ new Set([
9807
+ "color",
9808
+ "background-color",
9809
+ "font-size",
9810
+ "font-weight",
9811
+ "font-style",
9812
+ "text-decoration",
9813
+ "text-align",
9814
+ "text-indent",
9815
+ "line-height"
9816
+ ]);
9528
9817
  var TAG_ATTRS = {
9529
9818
  a: /* @__PURE__ */ new Set(["href", "target", "rel"]),
9530
9819
  img: /* @__PURE__ */ new Set(["src", "alt", "width", "height"]),
@@ -9532,11 +9821,29 @@ var TAG_ATTRS = {
9532
9821
  th: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope"]),
9533
9822
  td: /* @__PURE__ */ new Set(["colspan", "rowspan"])
9534
9823
  };
9824
+ function cleanStyle(value) {
9825
+ return String(value || "").split(";").map((item) => {
9826
+ const index = item.indexOf(":");
9827
+ if (index === -1) return "";
9828
+ const name = item.slice(0, index).trim().toLowerCase();
9829
+ const val = item.slice(index + 1).trim();
9830
+ if (!STYLE_PROPS.has(name)) return "";
9831
+ if (/url\s*\(|expression\s*\(|javascript:/i.test(val)) return "";
9832
+ return name + ": " + val;
9833
+ }).filter(Boolean).join("; ");
9834
+ }
9835
+ var SAFE_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
9535
9836
  function safeUrl(value, tag) {
9536
- const url = String(value || "").replace(/[\u0000-\u0020]+/g, "").toLowerCase();
9537
- if (url.startsWith("javascript:") || url.startsWith("vbscript:")) return false;
9538
- if (url.startsWith("data:")) return tag === "img" && url.startsWith("data:image/");
9539
- return true;
9837
+ const url = String(value || "").replace(/[\u0000-\u0020]+/g, "");
9838
+ const scheme = url.match(/^([a-z][a-z0-9+.-]*):/i);
9839
+ if (!scheme) return true;
9840
+ const name = scheme[1].toLowerCase();
9841
+ if (SAFE_SCHEMES.has(name)) return true;
9842
+ if (tag === "img") {
9843
+ if (name === "blob") return true;
9844
+ if (name === "data" && /^data:image\//i.test(url)) return true;
9845
+ }
9846
+ return false;
9540
9847
  }
9541
9848
  function cleanNode(node) {
9542
9849
  const children = [...node.childNodes];
@@ -9559,6 +9866,12 @@ function cleanNode(node) {
9559
9866
  child.removeAttribute(attr.name);
9560
9867
  return;
9561
9868
  }
9869
+ if (name === "style") {
9870
+ const style = cleanStyle(attr.value);
9871
+ if (style) child.setAttribute("style", style);
9872
+ else child.removeAttribute("style");
9873
+ return;
9874
+ }
9562
9875
  if ((name === "href" || name === "src") && !safeUrl(attr.value, tag)) {
9563
9876
  child.removeAttribute(attr.name);
9564
9877
  }
@@ -9579,79 +9892,475 @@ function sanitizeHtml(html) {
9579
9892
  }
9580
9893
 
9581
9894
  // src/components/rich-editor/index.js
9895
+ var BLOCKS = [
9896
+ { value: "p", label: "paragraph" },
9897
+ { value: "h1", label: "heading1" },
9898
+ { value: "h2", label: "heading2" },
9899
+ { value: "h3", label: "heading3" },
9900
+ { value: "blockquote", label: "quote" },
9901
+ { value: "pre", label: "codeBlock" }
9902
+ ];
9903
+ var SIZES = [
9904
+ { value: "", label: "defaultSize" },
9905
+ { value: "12px", label: "12px" },
9906
+ { value: "14px", label: "14px" },
9907
+ { value: "16px", label: "16px" },
9908
+ { value: "18px", label: "18px" },
9909
+ { value: "24px", label: "24px" }
9910
+ ];
9911
+ var COLORS = ["#1f2d3d", "#5a6b7d", "#2b86d9", "#2fa36b", "#d97706", "#dc2626", "#7c3aed", "#ffffff"];
9912
+ var TOOLBAR_PRESETS = {
9913
+ simple: ["history", "inline", "list", "insert"],
9914
+ basic: ["history", "format", "inline", "align", "list", "insert", "view"],
9915
+ full: ["history", "format", "inline", "color", "align", "list", "insert", "view"]
9916
+ };
9917
+ function isSafeImageUrl(url) {
9918
+ const value = String(url || "").replace(/[\u0000-\u0020]+/g, "");
9919
+ if (!value) return false;
9920
+ const scheme = value.match(/^([a-z][a-z0-9+.-]*):/i);
9921
+ if (!scheme) return true;
9922
+ const name = scheme[1].toLowerCase();
9923
+ if (name === "http" || name === "https" || name === "blob") return true;
9924
+ return name === "data" && /^data:image\//i.test(value);
9925
+ }
9926
+ function isSafeLink(url) {
9927
+ const value = String(url || "").trim();
9928
+ if (!value) return false;
9929
+ const scheme = value.replace(/[\u0000-\u0020]+/g, "").match(/^([a-z][a-z0-9+.-]*):/i);
9930
+ if (!scheme) return true;
9931
+ return ["http", "https", "mailto", "tel"].indexOf(scheme[1].toLowerCase()) > -1;
9932
+ }
9582
9933
  var rich_editor_default = {
9583
9934
  name: "DjmRichEditor",
9584
- components: { Icon: icon_default },
9935
+ components: { Icon: icon_default, MediaPicker: media_picker_default },
9585
9936
  props: {
9586
9937
  modelValue: { type: String, default: "" },
9587
9938
  placeholder: { type: String, default: "" },
9588
9939
  minHeight: { type: Number, default: 200 },
9589
- // 开启后对外输出的 HTML 会走白名单清洗
9590
- sanitize: { type: Boolean, default: false },
9591
- disabled: { type: Boolean, default: false }
9940
+ // 默认开启:对外输出的 HTML 走白名单清洗,内容里不会带出脚本
9941
+ sanitize: { type: Boolean, default: true },
9942
+ disabled: { type: Boolean, default: false },
9943
+ // 工具条:simple / basic / full,或传分组数组(history / format / inline / color / align / list / insert / view)
9944
+ toolbar: { type: [String, Array], default: "full" },
9945
+ // 自定义上传:接收 File,返回图片地址(或 { url }),不传则转成 dataURL 本地预览
9946
+ uploadImage: { type: Function, default: null },
9947
+ // 已上传图片,提供后工具栏出现「从素材库选择」:字符串或 { url, name }
9948
+ media: { type: Array, default: () => [] },
9949
+ accept: { type: String, default: "image/*" },
9950
+ // 单张图片大小上限(MB)
9951
+ maxImageSize: { type: Number, default: 5 },
9952
+ // 是否允许一次选多张
9953
+ multiple: { type: Boolean, default: true },
9954
+ showCount: { type: Boolean, default: true }
9592
9955
  },
9593
- emits: ["update:modelValue"],
9956
+ emits: ["update:modelValue", "upload-error"],
9594
9957
  setup(props, { emit }) {
9595
- const editor = (0, import_vue32.ref)(null);
9596
- const sourceMode = (0, import_vue32.ref)(false);
9597
- const source = (0, import_vue32.ref)(props.modelValue);
9598
- const formats = (0, import_vue32.ref)({ bold: false, italic: false, underline: false, strike: false });
9958
+ const editor = (0, import_vue33.ref)(null);
9959
+ const fileInput = (0, import_vue33.ref)(null);
9960
+ const sourceMode = (0, import_vue33.ref)(false);
9961
+ const source = (0, import_vue33.ref)(props.modelValue);
9962
+ const fullscreen = (0, import_vue33.ref)(false);
9963
+ const panel = (0, import_vue33.ref)("");
9964
+ const uploadCount = (0, import_vue33.ref)(0);
9965
+ const textColor = (0, import_vue33.ref)(COLORS[0]);
9966
+ const bgColor = (0, import_vue33.ref)("#fdf3c4");
9967
+ const fontSize = (0, import_vue33.ref)("");
9968
+ const count2 = (0, import_vue33.ref)(0);
9969
+ const formats = (0, import_vue33.ref)({
9970
+ bold: false,
9971
+ italic: false,
9972
+ underline: false,
9973
+ strike: false,
9974
+ code: false,
9975
+ block: "p",
9976
+ align: "",
9977
+ list: ""
9978
+ });
9979
+ let savedRange = null;
9980
+ const ready = (0, import_vue33.computed)(() => !props.disabled && !sourceMode.value);
9981
+ const toolbarGroups = (0, import_vue33.computed)(() => {
9982
+ const list = Array.isArray(props.toolbar) ? props.toolbar : TOOLBAR_PRESETS[props.toolbar] || TOOLBAR_PRESETS.full;
9983
+ return new Set(list);
9984
+ });
9985
+ function has(group) {
9986
+ return toolbarGroups.value.has(group);
9987
+ }
9988
+ const mediaLibrary = (0, import_vue33.computed)(
9989
+ () => (props.media || []).map((item, index) => {
9990
+ if (typeof item === "string") {
9991
+ return { id: "media-" + index, url: item, name: "", type: "image" };
9992
+ }
9993
+ return {
9994
+ id: item && item.id !== void 0 ? String(item.id) : "media-" + index,
9995
+ url: item && (item.url || item.src) || "",
9996
+ name: item && (item.name || item.alt) || "",
9997
+ type: "image"
9998
+ };
9999
+ }).filter((item) => !!item.url)
10000
+ );
9599
10001
  function clean(html) {
9600
10002
  return props.sanitize ? sanitizeHtml(html) : html;
9601
10003
  }
10004
+ function plainText(html) {
10005
+ return String(html || "").replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&[a-z]+;/gi, " ");
10006
+ }
9602
10007
  function syncFromDom() {
9603
10008
  if (!editor.value) return;
9604
- emit("update:modelValue", clean(editor.value.innerHTML));
10009
+ const html = clean(editor.value.innerHTML);
10010
+ count2.value = plainText(html).trim().length;
10011
+ emit("update:modelValue", html);
9605
10012
  }
9606
- function onPaste(event) {
9607
- const data = event.clipboardData;
9608
- const html = data && data.getData("text/html");
9609
- if (!html) return;
9610
- event.preventDefault();
9611
- document.execCommand("insertHTML", false, sanitizeHtml(html));
10013
+ function currentRange() {
10014
+ const selection = window.getSelection();
10015
+ if (!selection || !selection.rangeCount || !editor.value) return null;
10016
+ const range = selection.getRangeAt(0);
10017
+ return editor.value.contains(range.commonAncestorContainer) ? range : null;
10018
+ }
10019
+ function rememberRange() {
10020
+ const range = currentRange();
10021
+ savedRange = range ? range.cloneRange() : null;
10022
+ }
10023
+ function focusAt(range) {
10024
+ if (!editor.value) return;
10025
+ editor.value.focus();
10026
+ const selection = window.getSelection();
10027
+ selection.removeAllRanges();
10028
+ if (range) {
10029
+ selection.addRange(range);
10030
+ return;
10031
+ }
10032
+ const end = document.createRange();
10033
+ end.selectNodeContents(editor.value);
10034
+ end.collapse(false);
10035
+ selection.addRange(end);
10036
+ }
10037
+ function insertNodeAt(range, node) {
10038
+ if (!editor.value) return;
10039
+ focusAt(range);
10040
+ const selection = window.getSelection();
10041
+ if (selection.rangeCount) selection.getRangeAt(0).insertNode(node);
10042
+ else editor.value.appendChild(node);
9612
10043
  syncFromDom();
10044
+ syncFormats();
10045
+ }
10046
+ function ensureRange() {
10047
+ const range = currentRange();
10048
+ if (range) return range;
10049
+ if (!savedRange) return null;
10050
+ focusAt(savedRange);
10051
+ return currentRange();
9613
10052
  }
9614
10053
  function exec(command, value = null) {
9615
- if (props.disabled || sourceMode.value) return;
10054
+ if (!ready.value) return;
10055
+ if (typeof document.execCommand !== "function") return;
10056
+ ensureRange();
10057
+ document.execCommand("styleWithCSS", false, true);
9616
10058
  document.execCommand(command, false, value);
9617
10059
  if (editor.value) editor.value.focus();
9618
10060
  syncFromDom();
9619
10061
  syncFormats();
9620
10062
  }
9621
- function formatBlock(tag) {
9622
- exec("formatBlock", tag);
10063
+ function setBlock(value) {
10064
+ panel.value = "";
10065
+ exec("formatBlock", "<" + value + ">");
10066
+ }
10067
+ function setFontSize(size) {
10068
+ panel.value = "";
10069
+ if (!ready.value || !editor.value) return;
10070
+ fontSize.value = size;
10071
+ const range = ensureRange();
10072
+ if (!range || range.collapsed) return;
10073
+ if (!size) {
10074
+ const walker = document.createTreeWalker(editor.value, NodeFilter.SHOW_ELEMENT);
10075
+ const targets = [];
10076
+ while (walker.nextNode()) {
10077
+ const el = walker.currentNode;
10078
+ if (el.style && el.style.fontSize && range.intersectsNode(el)) targets.push(el);
10079
+ }
10080
+ targets.forEach((el) => {
10081
+ el.style.fontSize = "";
10082
+ });
10083
+ syncFromDom();
10084
+ return;
10085
+ }
10086
+ const span = document.createElement("span");
10087
+ span.style.fontSize = size;
10088
+ span.appendChild(range.extractContents());
10089
+ range.insertNode(span);
10090
+ const selection = window.getSelection();
10091
+ const after = document.createRange();
10092
+ after.setStartAfter(span);
10093
+ after.collapse(true);
10094
+ selection.removeAllRanges();
10095
+ selection.addRange(after);
10096
+ syncFromDom();
10097
+ syncFormats();
10098
+ }
10099
+ function applyColor(kind, color) {
10100
+ panel.value = "";
10101
+ if (kind === "text") textColor.value = color;
10102
+ else bgColor.value = color;
10103
+ if (!ready.value) return;
10104
+ if (typeof document.execCommand !== "function") return;
10105
+ document.execCommand("styleWithCSS", false, true);
10106
+ const command = kind === "text" ? "foreColor" : "hiliteColor";
10107
+ if (!document.execCommand(command, false, color) && kind === "bg") {
10108
+ document.execCommand("backColor", false, color);
10109
+ }
10110
+ if (editor.value) editor.value.focus();
10111
+ syncFromDom();
10112
+ syncFormats();
10113
+ }
10114
+ function togglePanel(name) {
10115
+ if (!ready.value) return;
10116
+ panel.value = panel.value === name ? "" : name;
10117
+ }
10118
+ function toggleInlineCode() {
10119
+ if (!ready.value || !editor.value) return;
10120
+ const range = currentRange();
10121
+ if (!range) return;
10122
+ const anchor = range.commonAncestorContainer;
10123
+ const node = anchor.nodeType === 1 ? anchor : anchor.parentElement;
10124
+ const code = node && node.closest ? node.closest("code") : null;
10125
+ if (code && editor.value.contains(code)) {
10126
+ const parent = code.parentNode;
10127
+ while (code.firstChild) parent.insertBefore(code.firstChild, code);
10128
+ parent.removeChild(code);
10129
+ syncFromDom();
10130
+ return;
10131
+ }
10132
+ if (range.collapsed) return;
10133
+ const wrapper = document.createElement("code");
10134
+ wrapper.appendChild(range.extractContents());
10135
+ range.insertNode(wrapper);
10136
+ const selection = window.getSelection();
10137
+ const after = document.createRange();
10138
+ after.setStartAfter(wrapper);
10139
+ after.collapse(true);
10140
+ selection.removeAllRanges();
10141
+ selection.addRange(after);
10142
+ syncFromDom();
10143
+ syncFormats();
9623
10144
  }
9624
10145
  function addLink() {
10146
+ if (!ready.value) return;
9625
10147
  const url = window.prompt(t("richEditor.linkPrompt"), "https://");
9626
- if (url) exec("createLink", url);
10148
+ if (!url) return;
10149
+ if (!isSafeLink(url)) {
10150
+ window.alert(t("richEditor.linkInvalid"));
10151
+ return;
10152
+ }
10153
+ const range = currentRange();
10154
+ if (range && range.collapsed) {
10155
+ const link = document.createElement("a");
10156
+ link.href = url;
10157
+ link.target = "_blank";
10158
+ link.rel = "noopener noreferrer";
10159
+ link.textContent = url;
10160
+ insertNodeAt(range, link);
10161
+ return;
10162
+ }
10163
+ exec("createLink", url);
10164
+ if (editor.value) {
10165
+ editor.value.querySelectorAll("a[href]").forEach((link) => {
10166
+ if (!link.getAttribute("target")) link.setAttribute("target", "_blank");
10167
+ link.setAttribute("rel", "noopener noreferrer");
10168
+ });
10169
+ }
10170
+ syncFromDom();
9627
10171
  }
9628
- function syncFormats() {
9629
- if (props.disabled || sourceMode.value) return;
9630
- if (!editor.value || !editor.value.contains(document.activeElement)) return;
9631
- formats.value = {
9632
- bold: document.queryCommandState("bold"),
9633
- italic: document.queryCommandState("italic"),
9634
- underline: document.queryCommandState("underline"),
9635
- strike: document.queryCommandState("strikeThrough")
9636
- };
10172
+ function readAsDataUrl(file) {
10173
+ return new Promise((resolve, reject) => {
10174
+ const reader = new FileReader();
10175
+ reader.onload = () => resolve(reader.result);
10176
+ reader.onerror = () => reject(reader.error || new Error("read failed"));
10177
+ reader.readAsDataURL(file);
10178
+ });
10179
+ }
10180
+ async function uploadOne(file) {
10181
+ if (props.uploadImage) {
10182
+ const result = await props.uploadImage(file);
10183
+ const url = typeof result === "string" ? result : result && result.url;
10184
+ if (!url) throw new Error(t("richEditor.imageUrlInvalid"));
10185
+ if (!isSafeImageUrl(url)) throw new Error(t("richEditor.imageUrlInvalid"));
10186
+ return String(url).trim();
10187
+ }
10188
+ return readAsDataUrl(file);
10189
+ }
10190
+ function isImage(file) {
10191
+ return file && /^image\//.test(file.type);
10192
+ }
10193
+ function pickImage(allowMultiple) {
10194
+ if (!ready.value || !fileInput.value) return;
10195
+ rememberRange();
10196
+ fileInput.value.multiple = !!allowMultiple && props.multiple;
10197
+ fileInput.value.click();
10198
+ }
10199
+ function insertMedia(ids) {
10200
+ if (!ready.value) return;
10201
+ const picked = (Array.isArray(ids) ? ids : [ids]).map((id) => mediaLibrary.value.find((item) => item.id === String(id))).filter(Boolean);
10202
+ const safe = picked.filter((item) => {
10203
+ if (isSafeImageUrl(item.url)) return true;
10204
+ emit("upload-error", { file: null, message: t("richEditor.imageUrlInvalid") });
10205
+ return false;
10206
+ });
10207
+ if (!safe.length) return;
10208
+ focusAt(savedRange);
10209
+ const fragment = document.createDocumentFragment();
10210
+ safe.forEach((item) => {
10211
+ const img = document.createElement("img");
10212
+ img.src = item.url;
10213
+ img.alt = item.name || "image";
10214
+ img.className = "djm-rich-editor-img";
10215
+ fragment.appendChild(img);
10216
+ });
10217
+ const selection = window.getSelection();
10218
+ if (selection.rangeCount) selection.getRangeAt(0).insertNode(fragment);
10219
+ else editor.value.appendChild(fragment);
10220
+ syncFromDom();
10221
+ syncFormats();
10222
+ }
10223
+ function insertImages(files) {
10224
+ if (!files.length || !editor.value) return;
10225
+ uploadCount.value = files.length;
10226
+ const holder = document.createElement("span");
10227
+ holder.className = "djm-rich-editor-uploading";
10228
+ holder.textContent = t("richEditor.uploading", { count: files.length });
10229
+ const anchor = savedRange && editor.value.contains(savedRange.commonAncestorContainer) ? savedRange : null;
10230
+ focusAt(anchor);
10231
+ const selection = window.getSelection();
10232
+ if (selection.rangeCount) selection.getRangeAt(0).insertNode(holder);
10233
+ else editor.value.appendChild(holder);
10234
+ Promise.all(files.map(async (file) => {
10235
+ if (!isImage(file)) return { file, error: t("richEditor.imageTypeError") };
10236
+ if (props.maxImageSize > 0 && file.size > props.maxImageSize * 1024 * 1024) {
10237
+ return { file, error: t("richEditor.imageSizeError", { size: props.maxImageSize }) };
10238
+ }
10239
+ try {
10240
+ return { file, url: await uploadOne(file) };
10241
+ } catch (error) {
10242
+ return { file, error: error && error.message || t("richEditor.uploadFailed") };
10243
+ }
10244
+ })).then((results) => {
10245
+ uploadCount.value = 0;
10246
+ const done = results.filter((item) => item.url);
10247
+ const failed = results.filter((item) => item.error);
10248
+ const alive = editor.value && editor.value.contains(holder);
10249
+ if (done.length && editor.value) {
10250
+ done.forEach((item) => {
10251
+ const img = document.createElement("img");
10252
+ img.src = item.url;
10253
+ img.alt = item.file.name || "image";
10254
+ img.className = "djm-rich-editor-img";
10255
+ if (alive) holder.parentNode.insertBefore(img, holder);
10256
+ else editor.value.appendChild(img);
10257
+ });
10258
+ }
10259
+ if (alive) holder.remove();
10260
+ failed.forEach((item) => emit("upload-error", { file: item.file, message: item.error }));
10261
+ syncFromDom();
10262
+ });
10263
+ }
10264
+ function onFilesChange(event) {
10265
+ const files = Array.from(event.target.files || []);
10266
+ event.target.value = "";
10267
+ insertImages(files);
10268
+ }
10269
+ function onPaste(event) {
10270
+ const data = event.clipboardData;
10271
+ if (!data) return;
10272
+ const images = Array.from(data.files || []).filter(isImage);
10273
+ if (images.length) {
10274
+ event.preventDefault();
10275
+ insertImages(images);
10276
+ return;
10277
+ }
10278
+ const html = data.getData("text/html");
10279
+ if (!html) return;
10280
+ event.preventDefault();
10281
+ document.execCommand("insertHTML", false, sanitizeHtml(html));
10282
+ syncFromDom();
10283
+ }
10284
+ function onDragOver(event) {
10285
+ if (!ready.value) return;
10286
+ if (event.dataTransfer && Array.from(event.dataTransfer.types || []).includes("Files")) {
10287
+ event.preventDefault();
10288
+ }
10289
+ }
10290
+ function onDrop(event) {
10291
+ if (!ready.value) return;
10292
+ const files = Array.from(event.dataTransfer && event.dataTransfer.files || []).filter(isImage);
10293
+ if (!files.length) return;
10294
+ event.preventDefault();
10295
+ rememberRange();
10296
+ insertImages(files);
10297
+ }
10298
+ function closestTag(node, tag) {
10299
+ const el = node && (node.nodeType === 1 ? node : node.parentElement);
10300
+ return el && el.closest ? el.closest(tag) : null;
10301
+ }
10302
+ function detectBlock() {
10303
+ const selection = window.getSelection();
10304
+ if (!selection || !selection.anchorNode) return "p";
10305
+ let node = selection.anchorNode;
10306
+ if (node.nodeType !== 1) node = node.parentElement;
10307
+ while (node && node !== editor.value) {
10308
+ const tag = node.tagName.toLowerCase();
10309
+ if (["p", "h1", "h2", "h3", "blockquote", "pre"].includes(tag)) return tag;
10310
+ node = node.parentElement;
10311
+ }
10312
+ return "p";
10313
+ }
10314
+ function syncFormats() {
10315
+ if (props.disabled || sourceMode.value || !editor.value) return;
10316
+ if (!editor.value.contains(document.activeElement)) return;
10317
+ const selection = window.getSelection();
10318
+ formats.value = {
10319
+ bold: document.queryCommandState("bold"),
10320
+ italic: document.queryCommandState("italic"),
10321
+ underline: document.queryCommandState("underline"),
10322
+ strike: document.queryCommandState("strikeThrough"),
10323
+ code: !!closestTag(selection.anchorNode, "code"),
10324
+ block: detectBlock(),
10325
+ align: document.queryCommandState("justifyCenter") ? "center" : document.queryCommandState("justifyRight") ? "right" : document.queryCommandState("justifyFull") ? "justify" : "left",
10326
+ list: document.queryCommandState("insertOrderedList") ? "ol" : document.queryCommandState("insertUnorderedList") ? "ul" : ""
10327
+ };
9637
10328
  }
9638
10329
  function onSourceInput() {
10330
+ count2.value = plainText(source.value).trim().length;
9639
10331
  emit("update:modelValue", clean(source.value));
9640
10332
  }
9641
10333
  function toggleSource() {
9642
10334
  if (props.disabled) return;
10335
+ panel.value = "";
9643
10336
  if (sourceMode.value) {
9644
10337
  emit("update:modelValue", clean(source.value));
9645
10338
  sourceMode.value = false;
9646
10339
  requestAnimationFrame(() => {
9647
10340
  if (editor.value) editor.value.innerHTML = clean(source.value);
10341
+ syncFromDom();
9648
10342
  });
9649
10343
  } else {
9650
10344
  source.value = editor.value ? editor.value.innerHTML : props.modelValue;
9651
10345
  sourceMode.value = true;
9652
10346
  }
9653
10347
  }
9654
- (0, import_vue32.watch)(
10348
+ function toggleFullscreen() {
10349
+ if (props.disabled) return;
10350
+ fullscreen.value = !fullscreen.value;
10351
+ panel.value = "";
10352
+ }
10353
+ function onDocumentClick(event) {
10354
+ if (!panel.value) return;
10355
+ if (!(event.target.closest && event.target.closest(".djm-rich-editor-color"))) panel.value = "";
10356
+ }
10357
+ function onKeydown(event) {
10358
+ if (event.key === "Escape" && fullscreen.value) {
10359
+ fullscreen.value = false;
10360
+ panel.value = "";
10361
+ }
10362
+ }
10363
+ (0, import_vue33.watch)(
9655
10364
  () => props.modelValue,
9656
10365
  (value) => {
9657
10366
  if (sourceMode.value) {
@@ -9660,40 +10369,117 @@ var rich_editor_default = {
9660
10369
  }
9661
10370
  const next = clean(value);
9662
10371
  if (editor.value && editor.value.innerHTML !== next) editor.value.innerHTML = next;
10372
+ count2.value = plainText(next).trim().length;
9663
10373
  }
9664
10374
  );
9665
- (0, import_vue32.onMounted)(() => {
10375
+ (0, import_vue33.onMounted)(() => {
9666
10376
  if (editor.value) editor.value.innerHTML = clean(props.modelValue);
10377
+ count2.value = plainText(props.modelValue).trim().length;
9667
10378
  document.addEventListener("selectionchange", syncFormats);
10379
+ document.addEventListener("click", onDocumentClick);
10380
+ document.addEventListener("keydown", onKeydown);
10381
+ });
10382
+ (0, import_vue33.onBeforeUnmount)(() => {
10383
+ document.removeEventListener("selectionchange", syncFormats);
10384
+ document.removeEventListener("click", onDocumentClick);
10385
+ document.removeEventListener("keydown", onKeydown);
9668
10386
  });
9669
- (0, import_vue32.onBeforeUnmount)(() => document.removeEventListener("selectionchange", syncFormats));
9670
10387
  return {
9671
10388
  editor,
10389
+ fileInput,
9672
10390
  sourceMode,
9673
10391
  source,
10392
+ fullscreen,
10393
+ panel,
10394
+ uploadCount,
10395
+ textColor,
10396
+ bgColor,
10397
+ fontSize,
10398
+ count: count2,
9674
10399
  formats,
10400
+ blocks: BLOCKS,
10401
+ sizes: SIZES,
10402
+ colors: COLORS,
10403
+ mediaLibrary,
10404
+ has,
9675
10405
  exec,
9676
- formatBlock,
10406
+ setBlock,
10407
+ setFontSize,
10408
+ applyColor,
10409
+ togglePanel,
10410
+ toggleInlineCode,
9677
10411
  addLink,
9678
- onSourceInput,
10412
+ pickImage,
10413
+ insertMedia,
10414
+ onFilesChange,
9679
10415
  onPaste,
10416
+ onDragOver,
10417
+ onDrop,
10418
+ rememberRange,
10419
+ onSourceInput,
9680
10420
  toggleSource,
10421
+ toggleFullscreen,
9681
10422
  syncFromDom,
9682
10423
  syncFormats,
9683
10424
  t
9684
10425
  };
9685
10426
  },
9686
10427
  template: `
9687
- <div class="djm-rich-editor" :class="{ 'djm-is-disabled': disabled }">
9688
- <div class="djm-rich-editor-toolbar">
9689
- <div class="djm-rich-editor-group">
10428
+ <div
10429
+ class="djm-rich-editor"
10430
+ :class="{ 'djm-is-disabled': disabled, 'djm-is-fullscreen': fullscreen }"
10431
+ >
10432
+ <div class="djm-rich-editor-toolbar" role="toolbar" :aria-label="t('richEditor.toolbar')" @mousedown="rememberRange">
10433
+ <div class="djm-rich-editor-group" v-if="has('history')">
10434
+ <button
10435
+ class="djm-rich-editor-btn"
10436
+ type="button"
10437
+ :title="t('richEditor.undo')"
10438
+ :aria-label="t('richEditor.undo')"
10439
+ :disabled="disabled || sourceMode"
10440
+ @mousedown.prevent
10441
+ @click="exec('undo')"
10442
+ ><Icon name="undo" :size="13" /></button>
10443
+ <button
10444
+ class="djm-rich-editor-btn"
10445
+ type="button"
10446
+ :title="t('richEditor.redo')"
10447
+ :aria-label="t('richEditor.redo')"
10448
+ :disabled="disabled || sourceMode"
10449
+ @mousedown.prevent
10450
+ @click="exec('redo')"
10451
+ ><Icon name="redo" :size="13" /></button>
10452
+ </div>
10453
+
10454
+ <div class="djm-rich-editor-group" v-if="has('format')">
10455
+ <select
10456
+ class="djm-rich-editor-select"
10457
+ :value="formats.block"
10458
+ :aria-label="t('richEditor.block')"
10459
+ :disabled="disabled || sourceMode"
10460
+ @change="setBlock($event.target.value)"
10461
+ >
10462
+ <option v-for="item in blocks" :key="item.value" :value="item.value">{{ t('richEditor.' + item.label) }}</option>
10463
+ </select>
10464
+ <select
10465
+ class="djm-rich-editor-select djm-rich-editor-select-size"
10466
+ :value="fontSize"
10467
+ :aria-label="t('richEditor.fontSize')"
10468
+ :disabled="disabled || sourceMode"
10469
+ @change="setFontSize($event.target.value)"
10470
+ >
10471
+ <option v-for="item in sizes" :key="item.value" :value="item.value">{{ t('richEditor.' + item.label) }}</option>
10472
+ </select>
10473
+ </div>
10474
+
10475
+ <div class="djm-rich-editor-group" v-if="has('inline')">
9690
10476
  <button
9691
10477
  class="djm-rich-editor-btn djm-rich-editor-btn-bold"
9692
10478
  type="button"
9693
10479
  :title="t('richEditor.bold')"
9694
10480
  :aria-label="t('richEditor.bold')"
9695
10481
  :aria-pressed="String(formats.bold)"
9696
- :disabled="disabled"
10482
+ :disabled="disabled || sourceMode"
9697
10483
  @mousedown.prevent
9698
10484
  @click="exec('bold')"
9699
10485
  >B</button>
@@ -9703,7 +10489,7 @@ var rich_editor_default = {
9703
10489
  :title="t('richEditor.italic')"
9704
10490
  :aria-label="t('richEditor.italic')"
9705
10491
  :aria-pressed="String(formats.italic)"
9706
- :disabled="disabled"
10492
+ :disabled="disabled || sourceMode"
9707
10493
  @mousedown.prevent
9708
10494
  @click="exec('italic')"
9709
10495
  >I</button>
@@ -9713,7 +10499,7 @@ var rich_editor_default = {
9713
10499
  :title="t('richEditor.underline')"
9714
10500
  :aria-label="t('richEditor.underline')"
9715
10501
  :aria-pressed="String(formats.underline)"
9716
- :disabled="disabled"
10502
+ :disabled="disabled || sourceMode"
9717
10503
  @mousedown.prevent
9718
10504
  @click="exec('underline')"
9719
10505
  >U</button>
@@ -9723,46 +10509,130 @@ var rich_editor_default = {
9723
10509
  :title="t('richEditor.strike')"
9724
10510
  :aria-label="t('richEditor.strike')"
9725
10511
  :aria-pressed="String(formats.strike)"
9726
- :disabled="disabled"
10512
+ :disabled="disabled || sourceMode"
9727
10513
  @mousedown.prevent
9728
10514
  @click="exec('strikeThrough')"
9729
10515
  >S</button>
10516
+ <button
10517
+ class="djm-rich-editor-btn"
10518
+ type="button"
10519
+ :title="t('richEditor.inlineCode')"
10520
+ :aria-label="t('richEditor.inlineCode')"
10521
+ :aria-pressed="String(formats.code)"
10522
+ :disabled="disabled || sourceMode"
10523
+ @mousedown.prevent
10524
+ @click="toggleInlineCode"
10525
+ ><Icon name="code" :size="13" /></button>
9730
10526
  </div>
9731
10527
 
9732
- <div class="djm-rich-editor-group">
9733
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.heading')" :disabled="disabled" @mousedown.prevent @click="formatBlock('h2')">H2</button>
9734
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.paragraph')" :disabled="disabled" @mousedown.prevent @click="formatBlock('p')">{{ t('richEditor.paragraph') }}</button>
9735
- </div>
9736
-
9737
- <div class="djm-rich-editor-group">
9738
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.bulletList')" :disabled="disabled" @mousedown.prevent @click="exec('insertUnorderedList')">
9739
- <span class="djm-rich-editor-glyph">\u2022 \u2261</span>
9740
- </button>
9741
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.numberList')" :disabled="disabled" @mousedown.prevent @click="exec('insertOrderedList')">
9742
- <span class="djm-rich-editor-glyph">1. \u2261</span>
10528
+ <div class="djm-rich-editor-group djm-rich-editor-color" v-if="has('color')">
10529
+ <button
10530
+ class="djm-rich-editor-btn"
10531
+ type="button"
10532
+ :title="t('richEditor.textColor')"
10533
+ :aria-label="t('richEditor.textColor')"
10534
+ :aria-expanded="String(panel === 'text')"
10535
+ :disabled="disabled || sourceMode"
10536
+ @mousedown.prevent
10537
+ @click.stop="togglePanel('text')"
10538
+ >
10539
+ <span class="djm-rich-editor-letter" :style="{ borderBottomColor: textColor }">A</span>
9743
10540
  </button>
9744
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.link')" :aria-label="t('richEditor.link')" :disabled="disabled" @mousedown.prevent @click="addLink">
9745
- <Icon name="link" :size="13" />
10541
+ <button
10542
+ class="djm-rich-editor-btn"
10543
+ type="button"
10544
+ :title="t('richEditor.bgColor')"
10545
+ :aria-label="t('richEditor.bgColor')"
10546
+ :aria-expanded="String(panel === 'bg')"
10547
+ :disabled="disabled || sourceMode"
10548
+ @mousedown.prevent
10549
+ @click.stop="togglePanel('bg')"
10550
+ >
10551
+ <span class="djm-rich-editor-letter djm-rich-editor-letter-bg" :style="{ background: bgColor }">A</span>
9746
10552
  </button>
10553
+ <div v-if="panel === 'text' || panel === 'bg'" class="djm-rich-editor-panel" role="group" :aria-label="t('richEditor.textColor')">
10554
+ <button
10555
+ v-for="color in colors"
10556
+ :key="color"
10557
+ class="djm-rich-editor-swatch"
10558
+ type="button"
10559
+ :title="color"
10560
+ :aria-label="color"
10561
+ :style="{ background: color }"
10562
+ @mousedown.prevent
10563
+ @click="applyColor(panel, color)"
10564
+ ></button>
10565
+ </div>
9747
10566
  </div>
9748
10567
 
9749
- <div class="djm-rich-editor-group">
9750
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.clearFormat')" :disabled="disabled" @mousedown.prevent @click="exec('removeFormat')">{{ t('richEditor.clearFormat') }}</button>
9751
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.undo')" :aria-label="t('richEditor.undo')" :disabled="disabled" @mousedown.prevent @click="exec('undo')">\u21B6</button>
9752
- <button class="djm-rich-editor-btn" type="button" :title="t('richEditor.redo')" :aria-label="t('richEditor.redo')" :disabled="disabled" @mousedown.prevent @click="exec('redo')">\u21B7</button>
10568
+ <div class="djm-rich-editor-group" v-if="has('align')">
10569
+ <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>
10570
+ <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>
10571
+ <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>
10572
+ <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>
9753
10573
  </div>
9754
10574
 
9755
- <div class="djm-rich-editor-spacer"></div>
9756
- <button
9757
- class="djm-rich-editor-btn"
9758
- :class="{ 'djm-is-active': sourceMode }"
9759
- type="button"
9760
- :aria-pressed="String(sourceMode)"
9761
- :disabled="disabled"
9762
- @click="toggleSource"
9763
- >
9764
- <Icon name="code" :size="13" />{{ sourceMode ? t('richEditor.visual') : t('richEditor.source') }}
9765
- </button>
10575
+ <div class="djm-rich-editor-group" v-if="has('list')">
10576
+ <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>
10577
+ <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>
10578
+ <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>
10579
+ <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>
10580
+ </div>
10581
+
10582
+ <div class="djm-rich-editor-group" v-if="has('insert')">
10583
+ <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>
10584
+ <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>
10585
+ <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>
10586
+ <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>
10587
+ <MediaPicker
10588
+ v-if="mediaLibrary.length"
10589
+ class="djm-rich-editor-picker"
10590
+ multiple
10591
+ accept="image"
10592
+ :items="mediaLibrary"
10593
+ :title="t('richEditor.mediaLibrary')"
10594
+ :disabled="disabled || sourceMode"
10595
+ @change="insertMedia"
10596
+ >
10597
+ <template #trigger="{ open }">
10598
+ <button
10599
+ class="djm-rich-editor-btn"
10600
+ type="button"
10601
+ :title="t('richEditor.mediaLibrary')"
10602
+ :aria-label="t('richEditor.mediaLibrary')"
10603
+ :disabled="disabled || sourceMode"
10604
+ @mousedown.prevent="rememberRange"
10605
+ @click="open"
10606
+ ><Icon name="grid" :size="13" /></button>
10607
+ </template>
10608
+ </MediaPicker>
10609
+ <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>
10610
+ <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>
10611
+ </div>
10612
+
10613
+ <div class="djm-rich-editor-spacer" v-if="has('view')"></div>
10614
+ <div class="djm-rich-editor-group djm-rich-editor-group-end" v-if="has('view')">
10615
+ <button
10616
+ class="djm-rich-editor-btn"
10617
+ :class="{ 'djm-is-active': sourceMode }"
10618
+ type="button"
10619
+ :title="sourceMode ? t('richEditor.visual') : t('richEditor.source')"
10620
+ :aria-label="sourceMode ? t('richEditor.visual') : t('richEditor.source')"
10621
+ :aria-pressed="String(sourceMode)"
10622
+ :disabled="disabled"
10623
+ @click="toggleSource"
10624
+ ><Icon name="code" :size="13" /></button>
10625
+ <button
10626
+ class="djm-rich-editor-btn"
10627
+ :class="{ 'djm-is-active': fullscreen }"
10628
+ type="button"
10629
+ :title="fullscreen ? t('richEditor.exitFullscreen') : t('richEditor.fullscreen')"
10630
+ :aria-label="fullscreen ? t('richEditor.exitFullscreen') : t('richEditor.fullscreen')"
10631
+ :aria-pressed="String(fullscreen)"
10632
+ :disabled="disabled"
10633
+ @click="toggleFullscreen"
10634
+ ><Icon :name="fullscreen ? 'minimize' : 'maximize'" :size="13" /></button>
10635
+ </div>
9766
10636
  </div>
9767
10637
 
9768
10638
  <div
@@ -9778,6 +10648,8 @@ var rich_editor_default = {
9778
10648
  :style="{ minHeight: minHeight + 'px' }"
9779
10649
  @input="syncFromDom"
9780
10650
  @paste="onPaste"
10651
+ @dragover="onDragOver"
10652
+ @drop="onDrop"
9781
10653
  ></div>
9782
10654
 
9783
10655
  <textarea
@@ -9790,12 +10662,27 @@ var rich_editor_default = {
9790
10662
  spellcheck="false"
9791
10663
  @input="onSourceInput"
9792
10664
  ></textarea>
10665
+
10666
+ <div v-if="showCount" class="djm-rich-editor-foot">
10667
+ <span v-if="uploadCount" class="djm-rich-editor-foot-item">{{ t('richEditor.uploading', { count: uploadCount }) }}</span>
10668
+ <span class="djm-rich-editor-foot-item">{{ t('richEditor.wordCount', { count }) }}</span>
10669
+ </div>
10670
+
10671
+ <input
10672
+ ref="fileInput"
10673
+ class="djm-rich-editor-file"
10674
+ type="file"
10675
+ :accept="accept"
10676
+ tabindex="-1"
10677
+ aria-hidden="true"
10678
+ @change="onFilesChange"
10679
+ />
9793
10680
  </div>
9794
10681
  `
9795
10682
  };
9796
10683
 
9797
10684
  // src/components/floating-window/index.js
9798
- var import_vue33 = require("vue");
10685
+ var import_vue34 = require("vue");
9799
10686
  function clamp(value, min, max) {
9800
10687
  return Math.min(Math.max(value, min), Math.max(min, max));
9801
10688
  }
@@ -9813,12 +10700,12 @@ var floating_window_default = {
9813
10700
  },
9814
10701
  emits: ["close", "focus"],
9815
10702
  setup(props, { emit }) {
9816
- const root = (0, import_vue33.ref)(null);
9817
- const pos = (0, import_vue33.ref)({ x: props.initialX, y: props.initialY });
9818
- const size = (0, import_vue33.ref)({ w: props.width, h: props.height });
9819
- const minimized = (0, import_vue33.ref)(false);
9820
- const maximized = (0, import_vue33.ref)(false);
9821
- const saved = (0, import_vue33.ref)(null);
10703
+ const root = (0, import_vue34.ref)(null);
10704
+ const pos = (0, import_vue34.ref)({ x: props.initialX, y: props.initialY });
10705
+ const size = (0, import_vue34.ref)({ w: props.width, h: props.height });
10706
+ const minimized = (0, import_vue34.ref)(false);
10707
+ const maximized = (0, import_vue34.ref)(false);
10708
+ const saved = (0, import_vue34.ref)(null);
9822
10709
  let drag = null;
9823
10710
  let resize = null;
9824
10711
  function stage() {
@@ -9906,7 +10793,7 @@ var floating_window_default = {
9906
10793
  }
9907
10794
  emit("focus");
9908
10795
  }
9909
- (0, import_vue33.onBeforeUnmount)(() => {
10796
+ (0, import_vue34.onBeforeUnmount)(() => {
9910
10797
  window.removeEventListener("pointermove", onDrag);
9911
10798
  window.removeEventListener("pointerup", endDrag);
9912
10799
  window.removeEventListener("pointermove", onResize);
@@ -9954,7 +10841,7 @@ var floating_window_default = {
9954
10841
  };
9955
10842
 
9956
10843
  // src/components/upload/index.js
9957
- var import_vue34 = require("vue");
10844
+ var import_vue35 = require("vue");
9958
10845
  var uidSeed6 = 0;
9959
10846
  function extension(name) {
9960
10847
  const index = name.lastIndexOf(".");
@@ -10004,11 +10891,11 @@ var upload_default = {
10004
10891
  },
10005
10892
  emits: ["update:modelValue", "change", "success", "error", "remove", "exceed"],
10006
10893
  setup(props, { emit }) {
10007
- const files = (0, import_vue34.ref)([]);
10008
- const dragActive = (0, import_vue34.ref)(false);
10009
- const inputRef = (0, import_vue34.ref)(null);
10894
+ const files = (0, import_vue35.ref)([]);
10895
+ const dragActive = (0, import_vue35.ref)(false);
10896
+ const inputRef = (0, import_vue35.ref)(null);
10010
10897
  const tasks = /* @__PURE__ */ new Map();
10011
- (0, import_vue34.watch)(
10898
+ (0, import_vue35.watch)(
10012
10899
  () => props.modelValue,
10013
10900
  (list) => {
10014
10901
  const next = list || [];
@@ -10102,7 +10989,7 @@ var upload_default = {
10102
10989
  if (pass === false) continue;
10103
10990
  }
10104
10991
  uidSeed6 += 1;
10105
- const item = (0, import_vue34.reactive)({
10992
+ const item = (0, import_vue35.reactive)({
10106
10993
  uid: uidSeed6,
10107
10994
  name: raw.name,
10108
10995
  size: raw.size,
@@ -10162,7 +11049,7 @@ var upload_default = {
10162
11049
  files.value = [];
10163
11050
  emitList();
10164
11051
  }
10165
- (0, import_vue34.onBeforeUnmount)(() => {
11052
+ (0, import_vue35.onBeforeUnmount)(() => {
10166
11053
  files.value.forEach((item) => {
10167
11054
  if (item.url) URL.revokeObjectURL(item.url);
10168
11055
  });
@@ -10310,7 +11197,7 @@ var upload_default = {
10310
11197
  };
10311
11198
 
10312
11199
  // src/components/tree/index.js
10313
- var import_vue35 = require("vue");
11200
+ var import_vue36 = require("vue");
10314
11201
  var tree_default = {
10315
11202
  name: "DjmTree",
10316
11203
  components: { Icon: icon_default },
@@ -10342,17 +11229,17 @@ var tree_default = {
10342
11229
  },
10343
11230
  emits: ["update:checked", "update:expanded", "node-click", "check", "node-drop", "load-error"],
10344
11231
  setup(props, { emit }) {
10345
- const lazyChildren = (0, import_vue35.ref)({});
10346
- const loadingKeys = (0, import_vue35.ref)([]);
10347
- const innerExpanded = (0, import_vue35.ref)([...props.expanded]);
10348
- const innerChecked = (0, import_vue35.ref)([...props.checked]);
10349
- (0, import_vue35.watch)(
11232
+ const lazyChildren = (0, import_vue36.ref)({});
11233
+ const loadingKeys = (0, import_vue36.ref)([]);
11234
+ const innerExpanded = (0, import_vue36.ref)([...props.expanded]);
11235
+ const innerChecked = (0, import_vue36.ref)([...props.checked]);
11236
+ (0, import_vue36.watch)(
10350
11237
  () => props.expanded,
10351
11238
  (value) => {
10352
11239
  if (Array.isArray(value)) innerExpanded.value = value;
10353
11240
  }
10354
11241
  );
10355
- (0, import_vue35.watch)(
11242
+ (0, import_vue36.watch)(
10356
11243
  () => props.checked,
10357
11244
  (value) => {
10358
11245
  if (Array.isArray(value)) innerChecked.value = value;
@@ -10390,7 +11277,7 @@ var tree_default = {
10390
11277
  });
10391
11278
  return out;
10392
11279
  }
10393
- (0, import_vue35.watch)(
11280
+ (0, import_vue36.watch)(
10394
11281
  () => props.data,
10395
11282
  () => {
10396
11283
  if (props.defaultExpandAll) innerExpanded.value = collectExpandable(props.data);
@@ -10407,7 +11294,7 @@ var tree_default = {
10407
11294
  const children = childrenOf(node);
10408
11295
  return Boolean(children && children.some((child) => matchSubtree(child)));
10409
11296
  }
10410
- const rows = (0, import_vue35.computed)(() => {
11297
+ const rows = (0, import_vue36.computed)(() => {
10411
11298
  const result = [];
10412
11299
  const walk = (nodes, level) => {
10413
11300
  nodes.forEach((node) => {
@@ -10478,8 +11365,8 @@ var tree_default = {
10478
11365
  function isActive(node) {
10479
11366
  return props.activeKey !== "" && node[props.nodeKey] === props.activeKey;
10480
11367
  }
10481
- const rootEl = (0, import_vue35.ref)(null);
10482
- const focusIndex = (0, import_vue35.ref)(-1);
11368
+ const rootEl = (0, import_vue36.ref)(null);
11369
+ const focusIndex = (0, import_vue36.ref)(-1);
10483
11370
  function tabIndexOf(index) {
10484
11371
  return (focusIndex.value === -1 ? 0 : focusIndex.value) === index ? 0 : -1;
10485
11372
  }
@@ -10536,9 +11423,9 @@ var tree_default = {
10536
11423
  function rowClassOf(node, level) {
10537
11424
  return typeof props.rowClass === "function" ? props.rowClass(node, level) : props.rowClass;
10538
11425
  }
10539
- const dragKey = (0, import_vue35.ref)("");
10540
- const dropKey = (0, import_vue35.ref)("");
10541
- const dropPosition = (0, import_vue35.ref)("");
11426
+ const dragKey = (0, import_vue36.ref)("");
11427
+ const dropKey = (0, import_vue36.ref)("");
11428
+ const dropPosition = (0, import_vue36.ref)("");
10542
11429
  function findNodeByKey(key, nodes = props.data) {
10543
11430
  for (const node of nodes) {
10544
11431
  if (node[props.nodeKey] === key) return node;
@@ -10650,7 +11537,7 @@ var tree_default = {
10650
11537
  onDrop,
10651
11538
  onDragEnd,
10652
11539
  labelOf,
10653
- emptyLabel: (0, import_vue35.computed)(() => props.emptyText || t("tree.empty")),
11540
+ emptyLabel: (0, import_vue36.computed)(() => props.emptyText || t("tree.empty")),
10654
11541
  t
10655
11542
  };
10656
11543
  },
@@ -10719,7 +11606,7 @@ var tree_default = {
10719
11606
  };
10720
11607
 
10721
11608
  // src/components/editable-table/index.js
10722
- var import_vue36 = require("vue");
11609
+ var import_vue37 = require("vue");
10723
11610
  var ACTIONS_KEY = "__editable_actions";
10724
11611
  function isEmpty(value) {
10725
11612
  if (value === "" || value === null || value === void 0) return true;
@@ -10742,13 +11629,13 @@ var editable_table_default = {
10742
11629
  },
10743
11630
  emits: ["edit", "save", "cancel"],
10744
11631
  setup(props, { emit }) {
10745
- const slots = (0, import_vue36.useSlots)();
10746
- const editingKey = (0, import_vue36.ref)("");
10747
- const draft = (0, import_vue36.reactive)({});
10748
- const errors = (0, import_vue36.reactive)({});
10749
- const tableRef = (0, import_vue36.ref)(null);
10750
- const editableColumns = (0, import_vue36.computed)(() => props.columns.filter((column) => column.editor));
10751
- const renderColumns = (0, import_vue36.computed)(() => {
11632
+ const slots = (0, import_vue37.useSlots)();
11633
+ const editingKey = (0, import_vue37.ref)("");
11634
+ const draft = (0, import_vue37.reactive)({});
11635
+ const errors = (0, import_vue37.reactive)({});
11636
+ const tableRef = (0, import_vue37.ref)(null);
11637
+ const editableColumns = (0, import_vue37.computed)(() => props.columns.filter((column) => column.editor));
11638
+ const renderColumns = (0, import_vue37.computed)(() => {
10752
11639
  if (!props.showActions) return props.columns;
10753
11640
  return props.columns.concat({
10754
11641
  key: ACTIONS_KEY,
@@ -10760,7 +11647,7 @@ var editable_table_default = {
10760
11647
  exportable: false
10761
11648
  });
10762
11649
  });
10763
- const forwardedSlotNames = (0, import_vue36.computed)(() => {
11650
+ const forwardedSlotNames = (0, import_vue37.computed)(() => {
10764
11651
  const taken = new Set(editableColumns.value.map((column) => "cell-" + column.key));
10765
11652
  taken.add("cell-" + ACTIONS_KEY);
10766
11653
  return Object.keys(slots).filter((name) => !taken.has(name));
@@ -10835,7 +11722,7 @@ var editable_table_default = {
10835
11722
  return false;
10836
11723
  }
10837
11724
  if (!validate()) {
10838
- (0, import_vue36.nextTick)(() => {
11725
+ (0, import_vue37.nextTick)(() => {
10839
11726
  const el = tableRef.value && tableRef.value.$el ? tableRef.value.$el.querySelector(".djm-cell-error") : null;
10840
11727
  if (el) el.scrollIntoView({ block: "nearest" });
10841
11728
  });
@@ -10855,7 +11742,7 @@ var editable_table_default = {
10855
11742
  clearErrors();
10856
11743
  return true;
10857
11744
  }
10858
- (0, import_vue36.watch)(
11745
+ (0, import_vue37.watch)(
10859
11746
  () => props.rows,
10860
11747
  () => {
10861
11748
  if (editingKey.value && !findRow(editingKey.value)) {
@@ -10991,8 +11878,8 @@ var descriptions_default = {
10991
11878
  };
10992
11879
 
10993
11880
  // src/components/message/index.js
10994
- var import_vue37 = require("vue");
10995
- var djmMessages = (0, import_vue37.reactive)([]);
11881
+ var import_vue38 = require("vue");
11882
+ var djmMessages = (0, import_vue38.reactive)([]);
10996
11883
  var seq = 0;
10997
11884
  function close(id) {
10998
11885
  const index = djmMessages.findIndex((item) => item.id === id);
@@ -11037,7 +11924,7 @@ var message_default = {
11037
11924
  };
11038
11925
 
11039
11926
  // src/components/org-picker/index.js
11040
- var import_vue38 = require("vue");
11927
+ var import_vue39 = require("vue");
11041
11928
  var org_picker_default = {
11042
11929
  name: "DjmOrgPicker",
11043
11930
  components: { DjmIcon: icon_default },
@@ -11056,13 +11943,13 @@ var org_picker_default = {
11056
11943
  },
11057
11944
  emits: ["update:modelValue", "change", "visible-change"],
11058
11945
  setup(props, { emit }) {
11059
- const open = (0, import_vue38.ref)(false);
11060
- const root = (0, import_vue38.ref)(null);
11061
- const searchRef = (0, import_vue38.ref)(null);
11062
- const keyword = (0, import_vue38.ref)("");
11063
- const activeDept = (0, import_vue38.ref)("");
11064
- const temp = (0, import_vue38.ref)([]);
11065
- const expandedIds = (0, import_vue38.ref)(/* @__PURE__ */ new Set());
11946
+ const open = (0, import_vue39.ref)(false);
11947
+ const root = (0, import_vue39.ref)(null);
11948
+ const searchRef = (0, import_vue39.ref)(null);
11949
+ const keyword = (0, import_vue39.ref)("");
11950
+ const activeDept = (0, import_vue39.ref)("");
11951
+ const temp = (0, import_vue39.ref)([]);
11952
+ const expandedIds = (0, import_vue39.ref)(/* @__PURE__ */ new Set());
11066
11953
  let detachPanel = null;
11067
11954
  function collectDeptIds(nodes, out = []) {
11068
11955
  nodes.forEach((node) => {
@@ -11092,22 +11979,22 @@ var org_picker_default = {
11092
11979
  walk([root2]);
11093
11980
  return ids;
11094
11981
  }
11095
- const staffMap = (0, import_vue38.computed)(() => {
11982
+ const staffMap = (0, import_vue39.computed)(() => {
11096
11983
  const map = /* @__PURE__ */ new Map();
11097
11984
  props.staff.forEach((item) => map.set(item.id, item));
11098
11985
  return map;
11099
11986
  });
11100
- const appliedStaff = (0, import_vue38.computed)(
11987
+ const appliedStaff = (0, import_vue39.computed)(
11101
11988
  () => props.modelValue.map((id) => staffMap.value.get(id)).filter(Boolean)
11102
11989
  );
11103
- const visibleTags = (0, import_vue38.computed)(() => {
11990
+ const visibleTags = (0, import_vue39.computed)(() => {
11104
11991
  if (props.maxTagCount <= 0) return appliedStaff.value;
11105
11992
  return appliedStaff.value.slice(0, props.maxTagCount);
11106
11993
  });
11107
- const restTagCount = (0, import_vue38.computed)(
11994
+ const restTagCount = (0, import_vue39.computed)(
11108
11995
  () => Math.max(0, appliedStaff.value.length - visibleTags.value.length)
11109
11996
  );
11110
- const visibleStaff = (0, import_vue38.computed)(() => {
11997
+ const visibleStaff = (0, import_vue39.computed)(() => {
11111
11998
  const key = keyword.value.trim().toLowerCase();
11112
11999
  if (key) {
11113
12000
  return props.staff.filter(
@@ -11118,7 +12005,7 @@ var org_picker_default = {
11118
12005
  const scope = deptScope(activeDept.value);
11119
12006
  return props.staff.filter((item) => scope.has(item.deptId));
11120
12007
  });
11121
- const deptRows = (0, import_vue38.computed)(() => {
12008
+ const deptRows = (0, import_vue39.computed)(() => {
11122
12009
  const rows = [];
11123
12010
  const walk = (nodes, depth) => {
11124
12011
  nodes.forEach((node) => {
@@ -11130,11 +12017,11 @@ var org_picker_default = {
11130
12017
  walk(props.departments, 0);
11131
12018
  return rows;
11132
12019
  });
11133
- const selectedCount = (0, import_vue38.computed)(() => temp.value.length);
11134
- const placeholderText = (0, import_vue38.computed)(() => props.placeholder || t("orgPicker.placeholder"));
11135
- const emptyLabel = (0, import_vue38.computed)(() => props.emptyText || t("orgPicker.empty"));
11136
- const panel = (0, import_vue38.ref)(null);
11137
- const panelStyle = (0, import_vue38.ref)({ ...PANEL_HIDDEN_STYLE });
12020
+ const selectedCount = (0, import_vue39.computed)(() => temp.value.length);
12021
+ const placeholderText = (0, import_vue39.computed)(() => props.placeholder || t("orgPicker.placeholder"));
12022
+ const emptyLabel = (0, import_vue39.computed)(() => props.emptyText || t("orgPicker.empty"));
12023
+ const panel = (0, import_vue39.ref)(null);
12024
+ const panelStyle = (0, import_vue39.ref)({ ...PANEL_HIDDEN_STYLE });
11138
12025
  const PANEL_HEIGHT = 342;
11139
12026
  function updatePosition() {
11140
12027
  if (!open.value || !root.value) return;
@@ -11151,7 +12038,7 @@ var org_picker_default = {
11151
12038
  expandedIds.value = new Set(collectDeptIds(props.departments));
11152
12039
  }
11153
12040
  expandAll();
11154
- (0, import_vue38.watch)(() => props.departments, () => {
12041
+ (0, import_vue39.watch)(() => props.departments, () => {
11155
12042
  expandedIds.value = /* @__PURE__ */ new Set([...expandedIds.value, ...collectDeptIds(props.departments)]);
11156
12043
  });
11157
12044
  function initialOf(item) {
@@ -11240,7 +12127,7 @@ var org_picker_default = {
11240
12127
  function clearTemp() {
11241
12128
  temp.value = [];
11242
12129
  }
11243
- (0, import_vue38.onBeforeUnmount)(() => {
12130
+ (0, import_vue39.onBeforeUnmount)(() => {
11244
12131
  if (detachPanel) {
11245
12132
  detachPanel();
11246
12133
  detachPanel = null;
@@ -11426,7 +12313,7 @@ var org_picker_default = {
11426
12313
  };
11427
12314
 
11428
12315
  // src/components/schema-form/index.js
11429
- var import_vue39 = require("vue");
12316
+ var import_vue40 = require("vue");
11430
12317
  var CONTROL_MAP = {
11431
12318
  input: input_default,
11432
12319
  number: input_number_default,
@@ -11462,26 +12349,26 @@ var schema_form_default = {
11462
12349
  },
11463
12350
  emits: ["update:modelValue", "change"],
11464
12351
  setup(props, { emit }) {
11465
- const innerForm = (0, import_vue39.ref)(null);
11466
- const model = (0, import_vue39.reactive)({ ...props.modelValue });
11467
- (0, import_vue39.watch)(() => props.fields, (fields) => {
12352
+ const innerForm = (0, import_vue40.ref)(null);
12353
+ const model = (0, import_vue40.reactive)({ ...props.modelValue });
12354
+ (0, import_vue40.watch)(() => props.fields, (fields) => {
11468
12355
  fields.forEach((field) => {
11469
12356
  if (model[field.key] === void 0 && field.default !== void 0) {
11470
12357
  model[field.key] = typeof field.default === "function" ? field.default() : field.default;
11471
12358
  }
11472
12359
  });
11473
12360
  }, { immediate: true, deep: true });
11474
- (0, import_vue39.watch)(() => props.modelValue, (value) => {
12361
+ (0, import_vue40.watch)(() => props.modelValue, (value) => {
11475
12362
  if (!value) return;
11476
12363
  Object.keys(value).forEach((key) => {
11477
12364
  if (model[key] !== value[key]) model[key] = value[key];
11478
12365
  });
11479
12366
  }, { deep: true });
11480
- (0, import_vue39.watch)(model, () => {
12367
+ (0, import_vue40.watch)(model, () => {
11481
12368
  emit("update:modelValue", { ...model });
11482
12369
  emit("change", { ...model });
11483
12370
  }, { deep: true });
11484
- const formRules = (0, import_vue39.computed)(() => {
12371
+ const formRules = (0, import_vue40.computed)(() => {
11485
12372
  const merged = { ...props.rules };
11486
12373
  props.fields.forEach((field) => {
11487
12374
  if (!field.required && !field.rules) return;
@@ -11494,7 +12381,7 @@ var schema_form_default = {
11494
12381
  });
11495
12382
  return merged;
11496
12383
  });
11497
- const visibleFields = (0, import_vue39.computed)(
12384
+ const visibleFields = (0, import_vue40.computed)(
11498
12385
  () => props.fields.filter((field) => {
11499
12386
  if (typeof field.visibleWhen !== "function") return true;
11500
12387
  return Boolean(field.visibleWhen(model));
@@ -11688,7 +12575,7 @@ var approval_flow_default = {
11688
12575
  };
11689
12576
 
11690
12577
  // src/components/query-filter/index.js
11691
- var import_vue40 = require("vue");
12578
+ var import_vue41 = require("vue");
11692
12579
  var CONTROL_MAP2 = {
11693
12580
  input: input_default,
11694
12581
  number: input_number_default,
@@ -11713,26 +12600,26 @@ var query_filter_default = {
11713
12600
  },
11714
12601
  emits: ["update:modelValue", "search", "reset"],
11715
12602
  setup(props, { emit }) {
11716
- const expanded = (0, import_vue40.ref)(false);
11717
- const model = (0, import_vue40.reactive)({ ...props.modelValue });
11718
- (0, import_vue40.watch)(() => props.fields, (fields) => {
12603
+ const expanded = (0, import_vue41.ref)(false);
12604
+ const model = (0, import_vue41.reactive)({ ...props.modelValue });
12605
+ (0, import_vue41.watch)(() => props.fields, (fields) => {
11719
12606
  fields.forEach((field) => {
11720
12607
  if (model[field.key] === void 0 && field.default !== void 0) {
11721
12608
  model[field.key] = typeof field.default === "function" ? field.default() : field.default;
11722
12609
  }
11723
12610
  });
11724
12611
  }, { immediate: true, deep: true });
11725
- (0, import_vue40.watch)(() => props.modelValue, (value) => {
12612
+ (0, import_vue41.watch)(() => props.modelValue, (value) => {
11726
12613
  if (!value) return;
11727
12614
  Object.keys(value).forEach((key) => {
11728
12615
  if (model[key] !== value[key]) model[key] = value[key];
11729
12616
  });
11730
12617
  }, { deep: true });
11731
- (0, import_vue40.watch)(model, () => {
12618
+ (0, import_vue41.watch)(model, () => {
11732
12619
  emit("update:modelValue", { ...model });
11733
12620
  }, { deep: true });
11734
- const collapsible = (0, import_vue40.computed)(() => props.fields.length > props.visibleCount);
11735
- const visibleFields = (0, import_vue40.computed)(() => {
12621
+ const collapsible = (0, import_vue41.computed)(() => props.fields.length > props.visibleCount);
12622
+ const visibleFields = (0, import_vue41.computed)(() => {
11736
12623
  if (!collapsible.value || expanded.value) return props.fields;
11737
12624
  return props.fields.slice(0, props.visibleCount);
11738
12625
  });
@@ -11861,198 +12748,6 @@ var filter_tags_default = {
11861
12748
  `
11862
12749
  };
11863
12750
 
11864
- // src/components/media-picker/index.js
11865
- var import_vue41 = require("vue");
11866
- var media_picker_default = {
11867
- name: "DjmMediaPicker",
11868
- components: { DjmModal: modal_default, DjmPagination: pagination_default, DjmIcon: icon_default },
11869
- props: {
11870
- modelValue: { type: [Array, String, Number], default: "" },
11871
- items: { type: Array, default: () => [] },
11872
- multiple: { type: Boolean, default: false },
11873
- // 最多选几个,0 表示不限
11874
- limit: { type: Number, default: 0 },
11875
- // image 只显示图片,file 只显示非图片,空串不过滤
11876
- accept: { type: String, default: "" },
11877
- title: { type: String, default: "" },
11878
- triggerText: { type: String, default: "" },
11879
- pageSize: { type: Number, default: 12 },
11880
- disabled: { type: Boolean, default: false }
11881
- },
11882
- emits: ["update:modelValue", "change"],
11883
- setup(props, { emit }) {
11884
- const open = (0, import_vue41.ref)(false);
11885
- const keyword = (0, import_vue41.ref)("");
11886
- const page = (0, import_vue41.ref)(1);
11887
- const temp = (0, import_vue41.ref)([]);
11888
- const selectedIds = (0, import_vue41.computed)(() => {
11889
- if (props.multiple) return Array.isArray(props.modelValue) ? props.modelValue : [];
11890
- if (props.modelValue === "" || props.modelValue === null || props.modelValue === void 0) return [];
11891
- return [props.modelValue];
11892
- });
11893
- function isImage(item) {
11894
- if (item.type) return item.type === "image";
11895
- return /\.(png|jpe?g|gif|webp|svg|avif)$/i.test(item.url || "");
11896
- }
11897
- const filtered = (0, import_vue41.computed)(() => {
11898
- const key = keyword.value.trim().toLowerCase();
11899
- return props.items.filter((item) => {
11900
- if (props.accept === "image" && !isImage(item)) return false;
11901
- if (props.accept === "file" && isImage(item)) return false;
11902
- if (key && !String(item.name || "").toLowerCase().includes(key)) return false;
11903
- return true;
11904
- });
11905
- });
11906
- const paged = (0, import_vue41.computed)(() => {
11907
- const start = (page.value - 1) * props.pageSize;
11908
- return filtered.value.slice(start, start + props.pageSize);
11909
- });
11910
- (0, import_vue41.watch)(keyword, () => {
11911
- page.value = 1;
11912
- });
11913
- (0, import_vue41.watch)(
11914
- () => filtered.value.length,
11915
- () => {
11916
- const max = Math.max(1, Math.ceil(filtered.value.length / props.pageSize));
11917
- if (page.value > max) page.value = max;
11918
- }
11919
- );
11920
- function isChecked(id) {
11921
- return temp.value.includes(id);
11922
- }
11923
- function atLimit() {
11924
- return props.limit > 0 && temp.value.length >= props.limit;
11925
- }
11926
- function toggle(item) {
11927
- if (props.multiple) {
11928
- if (isChecked(item.id)) {
11929
- temp.value = temp.value.filter((id) => id !== item.id);
11930
- return;
11931
- }
11932
- if (atLimit()) return;
11933
- temp.value = [...temp.value, item.id];
11934
- return;
11935
- }
11936
- temp.value = [item.id];
11937
- }
11938
- function openPanel() {
11939
- if (props.disabled) return;
11940
- temp.value = [...selectedIds.value];
11941
- keyword.value = "";
11942
- page.value = 1;
11943
- open.value = true;
11944
- }
11945
- function commit() {
11946
- const next = props.multiple ? [...temp.value] : temp.value[0] !== void 0 ? temp.value[0] : "";
11947
- emit("update:modelValue", next);
11948
- emit("change", next);
11949
- open.value = false;
11950
- }
11951
- function cancel() {
11952
- temp.value = [...selectedIds.value];
11953
- open.value = false;
11954
- }
11955
- return {
11956
- open,
11957
- keyword,
11958
- page,
11959
- temp,
11960
- selectedIds,
11961
- filtered,
11962
- paged,
11963
- isImage,
11964
- isChecked,
11965
- atLimit,
11966
- toggle,
11967
- openPanel,
11968
- commit,
11969
- cancel,
11970
- t
11971
- };
11972
- },
11973
- template: `
11974
- <span class="djm-media-picker">
11975
- <slot name="trigger" :open="openPanel" :count="selectedIds.length">
11976
- <button
11977
- class="djm-btn"
11978
- type="button"
11979
- :disabled="disabled"
11980
- @click="openPanel"
11981
- >{{ triggerText || t('mediaPicker.select') }}<span v-if="selectedIds.length" class="djm-media-picker-count">{{ selectedIds.length }}</span></button>
11982
- </slot>
11983
-
11984
- <DjmModal
11985
- :model-value="open"
11986
- :title="title || t('mediaPicker.title')"
11987
- width="760px"
11988
- @update:model-value="cancel"
11989
- >
11990
- <div class="djm-media-toolbar">
11991
- <label class="djm-media-search">
11992
- <DjmIcon name="search" :size="13" />
11993
- <input
11994
- v-model="keyword"
11995
- class="djm-media-search-input"
11996
- type="text"
11997
- :placeholder="t('mediaPicker.search')"
11998
- :aria-label="t('mediaPicker.search')"
11999
- />
12000
- </label>
12001
- <span class="djm-media-toolbar-slot">
12002
- <slot name="toolbar"></slot>
12003
- </span>
12004
- </div>
12005
-
12006
- <div class="djm-media-grid" role="listbox" :aria-multiselectable="multiple ? 'true' : 'false'">
12007
- <div
12008
- v-for="item in paged"
12009
- :key="item.id"
12010
- class="djm-media-item"
12011
- :class="{
12012
- 'djm-is-checked': isChecked(item.id),
12013
- 'djm-is-disabled': multiple && atLimit() && !isChecked(item.id)
12014
- }"
12015
- role="option"
12016
- tabindex="0"
12017
- :aria-selected="String(isChecked(item.id))"
12018
- @click="toggle(item)"
12019
- @keydown.enter.prevent="toggle(item)"
12020
- @keydown.space.prevent="toggle(item)"
12021
- >
12022
- <div class="djm-media-thumb">
12023
- <img v-if="isImage(item)" :src="item.url" :alt="item.name" loading="lazy" />
12024
- <DjmIcon v-else name="folder" :size="26" />
12025
- <span v-if="isChecked(item.id)" class="djm-media-check">
12026
- <DjmIcon name="check" :size="11" />
12027
- </span>
12028
- </div>
12029
- <div class="djm-media-name" :title="item.name">{{ item.name }}</div>
12030
- </div>
12031
- <div v-if="!paged.length" class="djm-media-empty">{{ t('mediaPicker.empty') }}</div>
12032
- </div>
12033
-
12034
- <div class="djm-media-foot">
12035
- <span class="djm-media-total">
12036
- {{ t('mediaPicker.total', { total: filtered.length }) }}
12037
- <em v-if="multiple && limit > 0">{{ t('mediaPicker.limit', { limit }) }}</em>
12038
- </span>
12039
- <DjmPagination
12040
- v-if="filtered.length > pageSize"
12041
- v-model:page="page"
12042
- :page-size="pageSize"
12043
- :total="filtered.length"
12044
- />
12045
- </div>
12046
-
12047
- <template #footer>
12048
- <button class="djm-btn" type="button" @click="cancel">{{ t('common.cancel') }}</button>
12049
- <button class="djm-btn djm-btn-primary" type="button" @click="commit">{{ t('common.confirm') }}</button>
12050
- </template>
12051
- </DjmModal>
12052
- </span>
12053
- `
12054
- };
12055
-
12056
12751
  // src/components/kanban/index.js
12057
12752
  var import_vue42 = require("vue");
12058
12753
  function cloneColumns(list) {
@@ -15393,7 +16088,7 @@ var components = [
15393
16088
  org_chart_default,
15394
16089
  page_tabs_default
15395
16090
  ];
15396
- var version = "0.67.3";
16091
+ var version = "0.72.0";
15397
16092
  function install(app, options = {}) {
15398
16093
  if (options.locale) setLocale(options.locale);
15399
16094
  components.forEach((component) => app.component(component.name, component));