@duskmoon-dev/el-markdown-input 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -270,6 +270,10 @@ var elementStyles = import_el_base.css`
270
270
  padding: 0 0.5rem;
271
271
  }
272
272
 
273
+ .toolbar[hidden] {
274
+ display: none;
275
+ }
276
+
273
277
  .tab-btn {
274
278
  padding: 0.5rem 0.875rem;
275
279
  border: none;
@@ -508,6 +512,31 @@ var elementStyles = import_el_base.css`
508
512
  gap: 0.5rem;
509
513
  }
510
514
 
515
+ .status-bar-start,
516
+ .status-bar-end {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 0.5rem;
520
+ }
521
+
522
+ /* Allow slotted light-DOM children to inherit font and alignment */
523
+ .status-bar ::slotted(*),
524
+ .status-bar-start ::slotted(*),
525
+ .status-bar-end ::slotted(*) {
526
+ font-size: inherit;
527
+ font-family: inherit;
528
+ vertical-align: middle;
529
+ }
530
+
531
+ /* When slot="bottom" is used, the slotted element fills the bar */
532
+ .status-bar > slot[name="bottom"]::slotted(*) {
533
+ display: flex;
534
+ align-items: center;
535
+ justify-content: space-between;
536
+ flex: 1;
537
+ gap: 0.5rem;
538
+ }
539
+
511
540
  .attach-btn {
512
541
  display: inline-flex;
513
542
  align-items: center;
@@ -542,7 +571,6 @@ var elementStyles = import_el_base.css`
542
571
  }
543
572
 
544
573
  .status-bar-count {
545
- margin-left: auto;
546
574
  white-space: nowrap;
547
575
  }
548
576
 
@@ -671,6 +699,52 @@ var elementStyles = import_el_base.css`
671
699
  white-space: nowrap;
672
700
  }
673
701
 
702
+ /* ── Attached files (local form mode) ────────────────────────────── */
703
+
704
+ .upload-attached-row {
705
+ display: flex;
706
+ align-items: center;
707
+ gap: 0.5rem;
708
+ padding: 0.375rem 0.75rem;
709
+ border-top: 1px solid var(--md-border);
710
+ font-size: 0.75rem;
711
+ color: var(--md-text-muted);
712
+ }
713
+
714
+ .upload-attached-size {
715
+ white-space: nowrap;
716
+ color: var(--md-text-muted);
717
+ }
718
+
719
+ .upload-remove-btn {
720
+ display: inline-flex;
721
+ align-items: center;
722
+ justify-content: center;
723
+ width: 1.25rem;
724
+ height: 1.25rem;
725
+ margin-left: auto;
726
+ border: none;
727
+ background: transparent;
728
+ color: var(--md-text-muted);
729
+ font-size: 0.85rem;
730
+ line-height: 1;
731
+ cursor: pointer;
732
+ border-radius: 50%;
733
+ transition:
734
+ color 150ms ease,
735
+ background 150ms ease;
736
+ }
737
+
738
+ .upload-remove-btn:hover {
739
+ color: var(--md-color-error);
740
+ background: var(--md-bg-hover);
741
+ }
742
+
743
+ .upload-remove-btn:focus-visible {
744
+ outline: 2px solid var(--md-accent);
745
+ outline-offset: 1px;
746
+ }
747
+
674
748
  /* ── Resizable editor ──────────────────────────────────────────────── */
675
749
  /* resize attribute mirrors the CSS resize property: vertical | horizontal | both */
676
750
  :host([resize='vertical']) .editor {
@@ -1015,6 +1089,13 @@ var coreMarkdownStyles = import_markdown_body.css.replace(/@layer\s+components\s
1015
1089
  var markdownBodySheet = import_el_base3.css`
1016
1090
  ${coreMarkdownStyles}
1017
1091
  `;
1092
+ function formatFileSize(bytes) {
1093
+ if (bytes < 1024)
1094
+ return `${bytes} B`;
1095
+ if (bytes < 1024 * 1024)
1096
+ return `${(bytes / 1024).toFixed(1)} KB`;
1097
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1098
+ }
1018
1099
 
1019
1100
  class ElDmMarkdownInput extends import_el_base2.BaseElement {
1020
1101
  static formAssociated = true;
@@ -1032,7 +1113,8 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1032
1113
  debounce: { type: Number, reflect: true, default: 300 },
1033
1114
  katexCssUrl: { type: String, reflect: true, attribute: "katex-css-url" },
1034
1115
  mermaidSrc: { type: String, reflect: true, attribute: "mermaid-src" },
1035
- resize: { type: String, reflect: true, default: "none" }
1116
+ resize: { type: String, reflect: true, default: "none" },
1117
+ noPreview: { type: Boolean, reflect: true, attribute: "no-preview" }
1036
1118
  };
1037
1119
  #internals;
1038
1120
  #initialized = false;
@@ -1060,6 +1142,7 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1060
1142
  #lastRenderedSource = null;
1061
1143
  #katexCssInjected = false;
1062
1144
  #uploadIdCounter = 0;
1145
+ #attachedFiles = [];
1063
1146
  constructor() {
1064
1147
  super();
1065
1148
  this.#internals = this.attachInternals();
@@ -1123,15 +1206,28 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1123
1206
  this.#renderPreview(ta.value);
1124
1207
  }
1125
1208
  }
1209
+ const noPreview = !!this.noPreview;
1210
+ const toolbar = this.shadowRoot.querySelector(".toolbar");
1211
+ if (noPreview) {
1212
+ toolbar?.setAttribute("hidden", "");
1213
+ if (this.#activeTab === "preview") {
1214
+ this.#activeTab = "write";
1215
+ this.#writeArea?.removeAttribute("hidden");
1216
+ this.#previewBody?.setAttribute("hidden", "");
1217
+ }
1218
+ } else {
1219
+ toolbar?.removeAttribute("hidden");
1220
+ }
1126
1221
  this.#updateStatusBarNow();
1127
1222
  }
1128
1223
  render() {
1129
1224
  const ph = this.placeholder ?? "Write markdown…";
1130
1225
  const disabled = !!this.disabled;
1131
1226
  const readonly = !!this.readonly;
1227
+ const noPreview = !!this.noPreview;
1132
1228
  return `
1133
1229
  <div class="editor">
1134
- <div class="toolbar" role="tablist" aria-label="Editor mode">
1230
+ <div class="toolbar" role="tablist" aria-label="Editor mode" ${noPreview ? "hidden" : ""}>
1135
1231
  <button
1136
1232
  class="tab-btn"
1137
1233
  id="tab-write"
@@ -1179,10 +1275,20 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1179
1275
  ></div>
1180
1276
 
1181
1277
  <div class="status-bar">
1182
- <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1183
- &#128206; Attach files
1184
- </button>
1185
- <span class="status-bar-count" aria-live="polite"></span>
1278
+ <slot name="bottom">
1279
+ <div class="status-bar-start">
1280
+ <slot name="bottom-start">
1281
+ <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1282
+ &#128206; Attach files
1283
+ </button>
1284
+ </slot>
1285
+ </div>
1286
+ <div class="status-bar-end">
1287
+ <slot name="bottom-end">
1288
+ <span class="status-bar-count" aria-live="polite"></span>
1289
+ </slot>
1290
+ </div>
1291
+ </slot>
1186
1292
  <input
1187
1293
  type="file"
1188
1294
  class="file-input"
@@ -1233,7 +1339,7 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1233
1339
  if (e.defaultPrevented)
1234
1340
  return;
1235
1341
  }
1236
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P") {
1342
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P" && !this.noPreview) {
1237
1343
  e.preventDefault();
1238
1344
  this.#switchTab(this.#activeTab === "write" ? "preview" : "write");
1239
1345
  return;
@@ -1358,6 +1464,8 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1358
1464
  #switchTab(tab) {
1359
1465
  if (tab === this.#activeTab)
1360
1466
  return;
1467
+ if (tab === "preview" && this.noPreview)
1468
+ return;
1361
1469
  this.#activeTab = tab;
1362
1470
  const writeBtns = this.shadowRoot.querySelectorAll(".tab-btn");
1363
1471
  writeBtns.forEach((btn) => {
@@ -1467,15 +1575,28 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1467
1575
  }, ms);
1468
1576
  }
1469
1577
  #syncFormValue() {
1470
- this.#internals?.setFormValue(this.#textarea?.value ?? "");
1578
+ const text = this.#textarea?.value ?? "";
1579
+ if (this.#attachedFiles.length === 0) {
1580
+ this.#internals?.setFormValue(text);
1581
+ return;
1582
+ }
1583
+ const name = this.name || "markdown";
1584
+ const fd = new FormData;
1585
+ fd.append(name, text);
1586
+ for (const f of this.#attachedFiles) {
1587
+ fd.append(`${name}_files`, f, f.name);
1588
+ }
1589
+ this.#internals?.setFormValue(fd);
1471
1590
  }
1472
1591
  #startUpload(file) {
1473
1592
  this.emit("upload-start", { file });
1474
1593
  const id = `upload-${++this.#uploadIdCounter}`;
1475
1594
  const uploadUrl = this.uploadUrl;
1476
1595
  if (!uploadUrl) {
1477
- this.emit("upload-error", { file, error: "no upload-url set" });
1478
- this.#showUploadError(file, "no upload-url set");
1596
+ this.#attachedFiles.push(file);
1597
+ this.#addAttachedRow(file, this.#attachedFiles.length - 1, id);
1598
+ this.#syncFormValue();
1599
+ this.emit("upload-done", { file, url: "", markdown: "" });
1479
1600
  return;
1480
1601
  }
1481
1602
  this.#addProgressRow(id, file.name);
@@ -1530,6 +1651,22 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1530
1651
  this.#uploadList.appendChild(row);
1531
1652
  setTimeout(() => row.remove(), 4000);
1532
1653
  }
1654
+ #addAttachedRow(file, index, id) {
1655
+ if (!this.#uploadList)
1656
+ return;
1657
+ const row = document.createElement("div");
1658
+ row.className = "upload-attached-row";
1659
+ row.id = id;
1660
+ row.innerHTML = `
1661
+ <span class="upload-filename">${escapeHtmlStr(file.name)}</span>
1662
+ <span class="upload-attached-size">${formatFileSize(file.size)}</span>
1663
+ <button type="button" class="upload-remove-btn" data-attach-index="${index}" aria-label="Remove ${escapeHtmlStr(file.name)}">&#215;</button>
1664
+ `;
1665
+ row.querySelector(".upload-remove-btn").addEventListener("click", () => {
1666
+ this.removeFile(index);
1667
+ });
1668
+ this.#uploadList.appendChild(row);
1669
+ }
1533
1670
  #handleAutocompleteInput() {
1534
1671
  const ta = this.#textarea;
1535
1672
  if (!ta)
@@ -1735,6 +1872,25 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1735
1872
  this.#acSelectedIndex = list.length > 0 ? 0 : -1;
1736
1873
  this.#updateDropdown();
1737
1874
  }
1875
+ getFiles() {
1876
+ return [...this.#attachedFiles];
1877
+ }
1878
+ removeFile(index) {
1879
+ if (index < 0 || index >= this.#attachedFiles.length)
1880
+ return;
1881
+ this.#attachedFiles.splice(index, 1);
1882
+ this.#rebuildAttachedRows();
1883
+ this.#syncFormValue();
1884
+ }
1885
+ #rebuildAttachedRows() {
1886
+ if (!this.#uploadList)
1887
+ return;
1888
+ this.#uploadList.querySelectorAll(".upload-attached-row").forEach((r) => r.remove());
1889
+ this.#attachedFiles.forEach((file, i) => {
1890
+ const id = `upload-${++this.#uploadIdCounter}`;
1891
+ this.#addAttachedRow(file, i, id);
1892
+ });
1893
+ }
1738
1894
  }
1739
1895
  function escapeHtmlStr(s) {
1740
1896
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1745,5 +1901,5 @@ if (!customElements.get("el-dm-markdown-input")) {
1745
1901
  customElements.define("el-dm-markdown-input", ElDmMarkdownInput);
1746
1902
  }
1747
1903
 
1748
- //# debugId=A41B9FAB725BC28A64756E2164756E21
1904
+ //# debugId=5E28FBCFFD0322DF64756E2164756E21
1749
1905
  //# sourceMappingURL=register.js.map