@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.
package/dist/cjs/index.js CHANGED
@@ -279,6 +279,10 @@ var elementStyles = import_el_base.css`
279
279
  padding: 0 0.5rem;
280
280
  }
281
281
 
282
+ .toolbar[hidden] {
283
+ display: none;
284
+ }
285
+
282
286
  .tab-btn {
283
287
  padding: 0.5rem 0.875rem;
284
288
  border: none;
@@ -517,6 +521,31 @@ var elementStyles = import_el_base.css`
517
521
  gap: 0.5rem;
518
522
  }
519
523
 
524
+ .status-bar-start,
525
+ .status-bar-end {
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 0.5rem;
529
+ }
530
+
531
+ /* Allow slotted light-DOM children to inherit font and alignment */
532
+ .status-bar ::slotted(*),
533
+ .status-bar-start ::slotted(*),
534
+ .status-bar-end ::slotted(*) {
535
+ font-size: inherit;
536
+ font-family: inherit;
537
+ vertical-align: middle;
538
+ }
539
+
540
+ /* When slot="bottom" is used, the slotted element fills the bar */
541
+ .status-bar > slot[name="bottom"]::slotted(*) {
542
+ display: flex;
543
+ align-items: center;
544
+ justify-content: space-between;
545
+ flex: 1;
546
+ gap: 0.5rem;
547
+ }
548
+
520
549
  .attach-btn {
521
550
  display: inline-flex;
522
551
  align-items: center;
@@ -551,7 +580,6 @@ var elementStyles = import_el_base.css`
551
580
  }
552
581
 
553
582
  .status-bar-count {
554
- margin-left: auto;
555
583
  white-space: nowrap;
556
584
  }
557
585
 
@@ -680,6 +708,52 @@ var elementStyles = import_el_base.css`
680
708
  white-space: nowrap;
681
709
  }
682
710
 
711
+ /* ── Attached files (local form mode) ────────────────────────────── */
712
+
713
+ .upload-attached-row {
714
+ display: flex;
715
+ align-items: center;
716
+ gap: 0.5rem;
717
+ padding: 0.375rem 0.75rem;
718
+ border-top: 1px solid var(--md-border);
719
+ font-size: 0.75rem;
720
+ color: var(--md-text-muted);
721
+ }
722
+
723
+ .upload-attached-size {
724
+ white-space: nowrap;
725
+ color: var(--md-text-muted);
726
+ }
727
+
728
+ .upload-remove-btn {
729
+ display: inline-flex;
730
+ align-items: center;
731
+ justify-content: center;
732
+ width: 1.25rem;
733
+ height: 1.25rem;
734
+ margin-left: auto;
735
+ border: none;
736
+ background: transparent;
737
+ color: var(--md-text-muted);
738
+ font-size: 0.85rem;
739
+ line-height: 1;
740
+ cursor: pointer;
741
+ border-radius: 50%;
742
+ transition:
743
+ color 150ms ease,
744
+ background 150ms ease;
745
+ }
746
+
747
+ .upload-remove-btn:hover {
748
+ color: var(--md-color-error);
749
+ background: var(--md-bg-hover);
750
+ }
751
+
752
+ .upload-remove-btn:focus-visible {
753
+ outline: 2px solid var(--md-accent);
754
+ outline-offset: 1px;
755
+ }
756
+
683
757
  /* ── Resizable editor ──────────────────────────────────────────────── */
684
758
  /* resize attribute mirrors the CSS resize property: vertical | horizontal | both */
685
759
  :host([resize='vertical']) .editor {
@@ -1024,6 +1098,13 @@ var coreMarkdownStyles = import_markdown_body.css.replace(/@layer\s+components\s
1024
1098
  var markdownBodySheet = import_el_base3.css`
1025
1099
  ${coreMarkdownStyles}
1026
1100
  `;
1101
+ function formatFileSize(bytes) {
1102
+ if (bytes < 1024)
1103
+ return `${bytes} B`;
1104
+ if (bytes < 1024 * 1024)
1105
+ return `${(bytes / 1024).toFixed(1)} KB`;
1106
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1107
+ }
1027
1108
 
1028
1109
  class ElDmMarkdownInput extends import_el_base2.BaseElement {
1029
1110
  static formAssociated = true;
@@ -1041,7 +1122,8 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1041
1122
  debounce: { type: Number, reflect: true, default: 300 },
1042
1123
  katexCssUrl: { type: String, reflect: true, attribute: "katex-css-url" },
1043
1124
  mermaidSrc: { type: String, reflect: true, attribute: "mermaid-src" },
1044
- resize: { type: String, reflect: true, default: "none" }
1125
+ resize: { type: String, reflect: true, default: "none" },
1126
+ noPreview: { type: Boolean, reflect: true, attribute: "no-preview" }
1045
1127
  };
1046
1128
  #internals;
1047
1129
  #initialized = false;
@@ -1069,6 +1151,7 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1069
1151
  #lastRenderedSource = null;
1070
1152
  #katexCssInjected = false;
1071
1153
  #uploadIdCounter = 0;
1154
+ #attachedFiles = [];
1072
1155
  constructor() {
1073
1156
  super();
1074
1157
  this.#internals = this.attachInternals();
@@ -1132,15 +1215,28 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1132
1215
  this.#renderPreview(ta.value);
1133
1216
  }
1134
1217
  }
1218
+ const noPreview = !!this.noPreview;
1219
+ const toolbar = this.shadowRoot.querySelector(".toolbar");
1220
+ if (noPreview) {
1221
+ toolbar?.setAttribute("hidden", "");
1222
+ if (this.#activeTab === "preview") {
1223
+ this.#activeTab = "write";
1224
+ this.#writeArea?.removeAttribute("hidden");
1225
+ this.#previewBody?.setAttribute("hidden", "");
1226
+ }
1227
+ } else {
1228
+ toolbar?.removeAttribute("hidden");
1229
+ }
1135
1230
  this.#updateStatusBarNow();
1136
1231
  }
1137
1232
  render() {
1138
1233
  const ph = this.placeholder ?? "Write markdown…";
1139
1234
  const disabled = !!this.disabled;
1140
1235
  const readonly = !!this.readonly;
1236
+ const noPreview = !!this.noPreview;
1141
1237
  return `
1142
1238
  <div class="editor">
1143
- <div class="toolbar" role="tablist" aria-label="Editor mode">
1239
+ <div class="toolbar" role="tablist" aria-label="Editor mode" ${noPreview ? "hidden" : ""}>
1144
1240
  <button
1145
1241
  class="tab-btn"
1146
1242
  id="tab-write"
@@ -1188,10 +1284,20 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1188
1284
  ></div>
1189
1285
 
1190
1286
  <div class="status-bar">
1191
- <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1192
- &#128206; Attach files
1193
- </button>
1194
- <span class="status-bar-count" aria-live="polite"></span>
1287
+ <slot name="bottom">
1288
+ <div class="status-bar-start">
1289
+ <slot name="bottom-start">
1290
+ <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1291
+ &#128206; Attach files
1292
+ </button>
1293
+ </slot>
1294
+ </div>
1295
+ <div class="status-bar-end">
1296
+ <slot name="bottom-end">
1297
+ <span class="status-bar-count" aria-live="polite"></span>
1298
+ </slot>
1299
+ </div>
1300
+ </slot>
1195
1301
  <input
1196
1302
  type="file"
1197
1303
  class="file-input"
@@ -1242,7 +1348,7 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1242
1348
  if (e.defaultPrevented)
1243
1349
  return;
1244
1350
  }
1245
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P") {
1351
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P" && !this.noPreview) {
1246
1352
  e.preventDefault();
1247
1353
  this.#switchTab(this.#activeTab === "write" ? "preview" : "write");
1248
1354
  return;
@@ -1367,6 +1473,8 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1367
1473
  #switchTab(tab) {
1368
1474
  if (tab === this.#activeTab)
1369
1475
  return;
1476
+ if (tab === "preview" && this.noPreview)
1477
+ return;
1370
1478
  this.#activeTab = tab;
1371
1479
  const writeBtns = this.shadowRoot.querySelectorAll(".tab-btn");
1372
1480
  writeBtns.forEach((btn) => {
@@ -1476,15 +1584,28 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1476
1584
  }, ms);
1477
1585
  }
1478
1586
  #syncFormValue() {
1479
- this.#internals?.setFormValue(this.#textarea?.value ?? "");
1587
+ const text = this.#textarea?.value ?? "";
1588
+ if (this.#attachedFiles.length === 0) {
1589
+ this.#internals?.setFormValue(text);
1590
+ return;
1591
+ }
1592
+ const name = this.name || "markdown";
1593
+ const fd = new FormData;
1594
+ fd.append(name, text);
1595
+ for (const f of this.#attachedFiles) {
1596
+ fd.append(`${name}_files`, f, f.name);
1597
+ }
1598
+ this.#internals?.setFormValue(fd);
1480
1599
  }
1481
1600
  #startUpload(file) {
1482
1601
  this.emit("upload-start", { file });
1483
1602
  const id = `upload-${++this.#uploadIdCounter}`;
1484
1603
  const uploadUrl = this.uploadUrl;
1485
1604
  if (!uploadUrl) {
1486
- this.emit("upload-error", { file, error: "no upload-url set" });
1487
- this.#showUploadError(file, "no upload-url set");
1605
+ this.#attachedFiles.push(file);
1606
+ this.#addAttachedRow(file, this.#attachedFiles.length - 1, id);
1607
+ this.#syncFormValue();
1608
+ this.emit("upload-done", { file, url: "", markdown: "" });
1488
1609
  return;
1489
1610
  }
1490
1611
  this.#addProgressRow(id, file.name);
@@ -1539,6 +1660,22 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1539
1660
  this.#uploadList.appendChild(row);
1540
1661
  setTimeout(() => row.remove(), 4000);
1541
1662
  }
1663
+ #addAttachedRow(file, index, id) {
1664
+ if (!this.#uploadList)
1665
+ return;
1666
+ const row = document.createElement("div");
1667
+ row.className = "upload-attached-row";
1668
+ row.id = id;
1669
+ row.innerHTML = `
1670
+ <span class="upload-filename">${escapeHtmlStr(file.name)}</span>
1671
+ <span class="upload-attached-size">${formatFileSize(file.size)}</span>
1672
+ <button type="button" class="upload-remove-btn" data-attach-index="${index}" aria-label="Remove ${escapeHtmlStr(file.name)}">&#215;</button>
1673
+ `;
1674
+ row.querySelector(".upload-remove-btn").addEventListener("click", () => {
1675
+ this.removeFile(index);
1676
+ });
1677
+ this.#uploadList.appendChild(row);
1678
+ }
1542
1679
  #handleAutocompleteInput() {
1543
1680
  const ta = this.#textarea;
1544
1681
  if (!ta)
@@ -1744,6 +1881,25 @@ class ElDmMarkdownInput extends import_el_base2.BaseElement {
1744
1881
  this.#acSelectedIndex = list.length > 0 ? 0 : -1;
1745
1882
  this.#updateDropdown();
1746
1883
  }
1884
+ getFiles() {
1885
+ return [...this.#attachedFiles];
1886
+ }
1887
+ removeFile(index) {
1888
+ if (index < 0 || index >= this.#attachedFiles.length)
1889
+ return;
1890
+ this.#attachedFiles.splice(index, 1);
1891
+ this.#rebuildAttachedRows();
1892
+ this.#syncFormValue();
1893
+ }
1894
+ #rebuildAttachedRows() {
1895
+ if (!this.#uploadList)
1896
+ return;
1897
+ this.#uploadList.querySelectorAll(".upload-attached-row").forEach((r) => r.remove());
1898
+ this.#attachedFiles.forEach((file, i) => {
1899
+ const id = `upload-${++this.#uploadIdCounter}`;
1900
+ this.#addAttachedRow(file, i, id);
1901
+ });
1902
+ }
1747
1903
  }
1748
1904
  function escapeHtmlStr(s) {
1749
1905
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1774,5 +1930,5 @@ var MarkdownInputHook = {
1774
1930
  }
1775
1931
  };
1776
1932
 
1777
- //# debugId=5C9887BADB0DFDB864756E2164756E21
1933
+ //# debugId=FC4F4846FEEC843664756E2164756E21
1778
1934
  //# sourceMappingURL=index.js.map