@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.
@@ -254,6 +254,10 @@ var elementStyles = css`
254
254
  padding: 0 0.5rem;
255
255
  }
256
256
 
257
+ .toolbar[hidden] {
258
+ display: none;
259
+ }
260
+
257
261
  .tab-btn {
258
262
  padding: 0.5rem 0.875rem;
259
263
  border: none;
@@ -492,6 +496,31 @@ var elementStyles = css`
492
496
  gap: 0.5rem;
493
497
  }
494
498
 
499
+ .status-bar-start,
500
+ .status-bar-end {
501
+ display: flex;
502
+ align-items: center;
503
+ gap: 0.5rem;
504
+ }
505
+
506
+ /* Allow slotted light-DOM children to inherit font and alignment */
507
+ .status-bar ::slotted(*),
508
+ .status-bar-start ::slotted(*),
509
+ .status-bar-end ::slotted(*) {
510
+ font-size: inherit;
511
+ font-family: inherit;
512
+ vertical-align: middle;
513
+ }
514
+
515
+ /* When slot="bottom" is used, the slotted element fills the bar */
516
+ .status-bar > slot[name="bottom"]::slotted(*) {
517
+ display: flex;
518
+ align-items: center;
519
+ justify-content: space-between;
520
+ flex: 1;
521
+ gap: 0.5rem;
522
+ }
523
+
495
524
  .attach-btn {
496
525
  display: inline-flex;
497
526
  align-items: center;
@@ -526,7 +555,6 @@ var elementStyles = css`
526
555
  }
527
556
 
528
557
  .status-bar-count {
529
- margin-left: auto;
530
558
  white-space: nowrap;
531
559
  }
532
560
 
@@ -655,6 +683,52 @@ var elementStyles = css`
655
683
  white-space: nowrap;
656
684
  }
657
685
 
686
+ /* ── Attached files (local form mode) ────────────────────────────── */
687
+
688
+ .upload-attached-row {
689
+ display: flex;
690
+ align-items: center;
691
+ gap: 0.5rem;
692
+ padding: 0.375rem 0.75rem;
693
+ border-top: 1px solid var(--md-border);
694
+ font-size: 0.75rem;
695
+ color: var(--md-text-muted);
696
+ }
697
+
698
+ .upload-attached-size {
699
+ white-space: nowrap;
700
+ color: var(--md-text-muted);
701
+ }
702
+
703
+ .upload-remove-btn {
704
+ display: inline-flex;
705
+ align-items: center;
706
+ justify-content: center;
707
+ width: 1.25rem;
708
+ height: 1.25rem;
709
+ margin-left: auto;
710
+ border: none;
711
+ background: transparent;
712
+ color: var(--md-text-muted);
713
+ font-size: 0.85rem;
714
+ line-height: 1;
715
+ cursor: pointer;
716
+ border-radius: 50%;
717
+ transition:
718
+ color 150ms ease,
719
+ background 150ms ease;
720
+ }
721
+
722
+ .upload-remove-btn:hover {
723
+ color: var(--md-color-error);
724
+ background: var(--md-bg-hover);
725
+ }
726
+
727
+ .upload-remove-btn:focus-visible {
728
+ outline: 2px solid var(--md-accent);
729
+ outline-offset: 1px;
730
+ }
731
+
658
732
  /* ── Resizable editor ──────────────────────────────────────────────── */
659
733
  /* resize attribute mirrors the CSS resize property: vertical | horizontal | both */
660
734
  :host([resize='vertical']) .editor {
@@ -999,6 +1073,13 @@ var coreMarkdownStyles = markdownBodyCSS.replace(/@layer\s+components\s*\{/, "")
999
1073
  var markdownBodySheet = css2`
1000
1074
  ${coreMarkdownStyles}
1001
1075
  `;
1076
+ function formatFileSize(bytes) {
1077
+ if (bytes < 1024)
1078
+ return `${bytes} B`;
1079
+ if (bytes < 1024 * 1024)
1080
+ return `${(bytes / 1024).toFixed(1)} KB`;
1081
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1082
+ }
1002
1083
 
1003
1084
  class ElDmMarkdownInput extends BaseElement {
1004
1085
  static formAssociated = true;
@@ -1016,7 +1097,8 @@ class ElDmMarkdownInput extends BaseElement {
1016
1097
  debounce: { type: Number, reflect: true, default: 300 },
1017
1098
  katexCssUrl: { type: String, reflect: true, attribute: "katex-css-url" },
1018
1099
  mermaidSrc: { type: String, reflect: true, attribute: "mermaid-src" },
1019
- resize: { type: String, reflect: true, default: "none" }
1100
+ resize: { type: String, reflect: true, default: "none" },
1101
+ noPreview: { type: Boolean, reflect: true, attribute: "no-preview" }
1020
1102
  };
1021
1103
  #internals;
1022
1104
  #initialized = false;
@@ -1044,6 +1126,7 @@ class ElDmMarkdownInput extends BaseElement {
1044
1126
  #lastRenderedSource = null;
1045
1127
  #katexCssInjected = false;
1046
1128
  #uploadIdCounter = 0;
1129
+ #attachedFiles = [];
1047
1130
  constructor() {
1048
1131
  super();
1049
1132
  this.#internals = this.attachInternals();
@@ -1107,15 +1190,28 @@ class ElDmMarkdownInput extends BaseElement {
1107
1190
  this.#renderPreview(ta.value);
1108
1191
  }
1109
1192
  }
1193
+ const noPreview = !!this.noPreview;
1194
+ const toolbar = this.shadowRoot.querySelector(".toolbar");
1195
+ if (noPreview) {
1196
+ toolbar?.setAttribute("hidden", "");
1197
+ if (this.#activeTab === "preview") {
1198
+ this.#activeTab = "write";
1199
+ this.#writeArea?.removeAttribute("hidden");
1200
+ this.#previewBody?.setAttribute("hidden", "");
1201
+ }
1202
+ } else {
1203
+ toolbar?.removeAttribute("hidden");
1204
+ }
1110
1205
  this.#updateStatusBarNow();
1111
1206
  }
1112
1207
  render() {
1113
1208
  const ph = this.placeholder ?? "Write markdown…";
1114
1209
  const disabled = !!this.disabled;
1115
1210
  const readonly = !!this.readonly;
1211
+ const noPreview = !!this.noPreview;
1116
1212
  return `
1117
1213
  <div class="editor">
1118
- <div class="toolbar" role="tablist" aria-label="Editor mode">
1214
+ <div class="toolbar" role="tablist" aria-label="Editor mode" ${noPreview ? "hidden" : ""}>
1119
1215
  <button
1120
1216
  class="tab-btn"
1121
1217
  id="tab-write"
@@ -1163,10 +1259,20 @@ class ElDmMarkdownInput extends BaseElement {
1163
1259
  ></div>
1164
1260
 
1165
1261
  <div class="status-bar">
1166
- <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1167
- &#128206; Attach files
1168
- </button>
1169
- <span class="status-bar-count" aria-live="polite"></span>
1262
+ <slot name="bottom">
1263
+ <div class="status-bar-start">
1264
+ <slot name="bottom-start">
1265
+ <button class="attach-btn" type="button" aria-label="Attach files" ${disabled || readonly ? "disabled" : ""}>
1266
+ &#128206; Attach files
1267
+ </button>
1268
+ </slot>
1269
+ </div>
1270
+ <div class="status-bar-end">
1271
+ <slot name="bottom-end">
1272
+ <span class="status-bar-count" aria-live="polite"></span>
1273
+ </slot>
1274
+ </div>
1275
+ </slot>
1170
1276
  <input
1171
1277
  type="file"
1172
1278
  class="file-input"
@@ -1217,7 +1323,7 @@ class ElDmMarkdownInput extends BaseElement {
1217
1323
  if (e.defaultPrevented)
1218
1324
  return;
1219
1325
  }
1220
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P") {
1326
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "P" && !this.noPreview) {
1221
1327
  e.preventDefault();
1222
1328
  this.#switchTab(this.#activeTab === "write" ? "preview" : "write");
1223
1329
  return;
@@ -1342,6 +1448,8 @@ class ElDmMarkdownInput extends BaseElement {
1342
1448
  #switchTab(tab) {
1343
1449
  if (tab === this.#activeTab)
1344
1450
  return;
1451
+ if (tab === "preview" && this.noPreview)
1452
+ return;
1345
1453
  this.#activeTab = tab;
1346
1454
  const writeBtns = this.shadowRoot.querySelectorAll(".tab-btn");
1347
1455
  writeBtns.forEach((btn) => {
@@ -1451,15 +1559,28 @@ class ElDmMarkdownInput extends BaseElement {
1451
1559
  }, ms);
1452
1560
  }
1453
1561
  #syncFormValue() {
1454
- this.#internals?.setFormValue(this.#textarea?.value ?? "");
1562
+ const text = this.#textarea?.value ?? "";
1563
+ if (this.#attachedFiles.length === 0) {
1564
+ this.#internals?.setFormValue(text);
1565
+ return;
1566
+ }
1567
+ const name = this.name || "markdown";
1568
+ const fd = new FormData;
1569
+ fd.append(name, text);
1570
+ for (const f of this.#attachedFiles) {
1571
+ fd.append(`${name}_files`, f, f.name);
1572
+ }
1573
+ this.#internals?.setFormValue(fd);
1455
1574
  }
1456
1575
  #startUpload(file) {
1457
1576
  this.emit("upload-start", { file });
1458
1577
  const id = `upload-${++this.#uploadIdCounter}`;
1459
1578
  const uploadUrl = this.uploadUrl;
1460
1579
  if (!uploadUrl) {
1461
- this.emit("upload-error", { file, error: "no upload-url set" });
1462
- this.#showUploadError(file, "no upload-url set");
1580
+ this.#attachedFiles.push(file);
1581
+ this.#addAttachedRow(file, this.#attachedFiles.length - 1, id);
1582
+ this.#syncFormValue();
1583
+ this.emit("upload-done", { file, url: "", markdown: "" });
1463
1584
  return;
1464
1585
  }
1465
1586
  this.#addProgressRow(id, file.name);
@@ -1514,6 +1635,22 @@ class ElDmMarkdownInput extends BaseElement {
1514
1635
  this.#uploadList.appendChild(row);
1515
1636
  setTimeout(() => row.remove(), 4000);
1516
1637
  }
1638
+ #addAttachedRow(file, index, id) {
1639
+ if (!this.#uploadList)
1640
+ return;
1641
+ const row = document.createElement("div");
1642
+ row.className = "upload-attached-row";
1643
+ row.id = id;
1644
+ row.innerHTML = `
1645
+ <span class="upload-filename">${escapeHtmlStr(file.name)}</span>
1646
+ <span class="upload-attached-size">${formatFileSize(file.size)}</span>
1647
+ <button type="button" class="upload-remove-btn" data-attach-index="${index}" aria-label="Remove ${escapeHtmlStr(file.name)}">&#215;</button>
1648
+ `;
1649
+ row.querySelector(".upload-remove-btn").addEventListener("click", () => {
1650
+ this.removeFile(index);
1651
+ });
1652
+ this.#uploadList.appendChild(row);
1653
+ }
1517
1654
  #handleAutocompleteInput() {
1518
1655
  const ta = this.#textarea;
1519
1656
  if (!ta)
@@ -1719,6 +1856,25 @@ class ElDmMarkdownInput extends BaseElement {
1719
1856
  this.#acSelectedIndex = list.length > 0 ? 0 : -1;
1720
1857
  this.#updateDropdown();
1721
1858
  }
1859
+ getFiles() {
1860
+ return [...this.#attachedFiles];
1861
+ }
1862
+ removeFile(index) {
1863
+ if (index < 0 || index >= this.#attachedFiles.length)
1864
+ return;
1865
+ this.#attachedFiles.splice(index, 1);
1866
+ this.#rebuildAttachedRows();
1867
+ this.#syncFormValue();
1868
+ }
1869
+ #rebuildAttachedRows() {
1870
+ if (!this.#uploadList)
1871
+ return;
1872
+ this.#uploadList.querySelectorAll(".upload-attached-row").forEach((r) => r.remove());
1873
+ this.#attachedFiles.forEach((file, i) => {
1874
+ const id = `upload-${++this.#uploadIdCounter}`;
1875
+ this.#addAttachedRow(file, i, id);
1876
+ });
1877
+ }
1722
1878
  }
1723
1879
  function escapeHtmlStr(s) {
1724
1880
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1729,5 +1885,5 @@ if (!customElements.get("el-dm-markdown-input")) {
1729
1885
  customElements.define("el-dm-markdown-input", ElDmMarkdownInput);
1730
1886
  }
1731
1887
 
1732
- //# debugId=ABC98EC77764587964756E2164756E21
1888
+ //# debugId=D5D4120E3A9113D764756E2164756E21
1733
1889
  //# sourceMappingURL=register.js.map