@cancia/toolbar 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cancia.js CHANGED
@@ -388,9 +388,9 @@ async function reorderList(listName, ids) {
388
388
  const { apiUrl, site } = state.config;
389
389
  const res = await fetch(
390
390
  `${apiUrl}/api/cancia/lists/${encodeURIComponent(listName)}/reorder?site=${encodeURIComponent(site)}${routeParam()}`,
391
- { method: "POST", headers: headers(), body: JSON.stringify({ ids }) }
391
+ { method: "POST", headers: headers(), body: JSON.stringify({ ids, knownCount: ids.length }) }
392
392
  );
393
- if (!res.ok) throw new Error(`Cancia: failed to reorder list (${res.status})`);
393
+ if (!res.ok) throw await toApiError(res, "Could not save the new order. Check your connection and try again.");
394
394
  }
395
395
  async function flushEdits(edits, save) {
396
396
  const results = await Promise.allSettled(edits.map((edit) => save(edit)));
@@ -2740,6 +2740,99 @@ function renderField(field, initial, depth = 0) {
2740
2740
  getValue2 = () => sel.value === "" ? void 0 : sel.value;
2741
2741
  break;
2742
2742
  }
2743
+ case "file": {
2744
+ let currentUrl = typeof initial === "string" ? initial : "";
2745
+ const box = document.createElement("div");
2746
+ box.style.cssText = `
2747
+ display: flex; flex-direction: column; gap: 8px;
2748
+ background: ${v("surface-raised")};
2749
+ border: 1px dashed ${v("border-strong")};
2750
+ border-radius: ${v("radius")};
2751
+ padding: 10px;
2752
+ `;
2753
+ const nameRow = document.createElement("div");
2754
+ nameRow.style.cssText = `display:flex; align-items:center; gap:8px; min-width:0;`;
2755
+ const nameLink = document.createElement("a");
2756
+ nameLink.target = "_blank";
2757
+ nameLink.rel = "noopener";
2758
+ nameLink.style.cssText = `
2759
+ font-size: ${v("text-sm")}; color: ${v("fg")}; text-decoration: underline;
2760
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
2761
+ `;
2762
+ const renderName = (url) => {
2763
+ if (!url) {
2764
+ nameLink.removeAttribute("href");
2765
+ nameLink.textContent = "No file attached";
2766
+ nameLink.style.color = v("fg-faint");
2767
+ nameLink.style.textDecoration = "none";
2768
+ return;
2769
+ }
2770
+ let label2 = url;
2771
+ try {
2772
+ label2 = decodeURIComponent(new URL(url, location.href).pathname.split("/").pop() || url);
2773
+ } catch {
2774
+ }
2775
+ nameLink.href = url;
2776
+ nameLink.textContent = label2;
2777
+ nameLink.style.color = v("fg");
2778
+ nameLink.style.textDecoration = "underline";
2779
+ };
2780
+ renderName(currentUrl);
2781
+ nameRow.appendChild(nameLink);
2782
+ box.appendChild(nameRow);
2783
+ const btnRow = document.createElement("div");
2784
+ btnRow.style.cssText = `display:flex; gap:8px; align-items:center;`;
2785
+ const fileInput = document.createElement("input");
2786
+ fileInput.type = "file";
2787
+ fileInput.accept = "application/pdf";
2788
+ fileInput.style.display = "none";
2789
+ const uploadBtn = document.createElement("button");
2790
+ uploadBtn.type = "button";
2791
+ uploadBtn.textContent = "Upload\u2026";
2792
+ uploadBtn.style.cssText = `${button("ghost")} font-size: ${v("text-xs")};`;
2793
+ attachHover(uploadBtn, { bg: v("surface-active") });
2794
+ uploadBtn.addEventListener("click", () => fileInput.click());
2795
+ const clearBtn = document.createElement("button");
2796
+ clearBtn.type = "button";
2797
+ clearBtn.textContent = "Clear";
2798
+ clearBtn.style.cssText = `${button("ghost")} font-size: ${v("text-xs")};`;
2799
+ attachHover(clearBtn, { bg: v("surface-active") });
2800
+ clearBtn.addEventListener("click", () => {
2801
+ currentUrl = "";
2802
+ renderName("");
2803
+ progressEl.textContent = "";
2804
+ });
2805
+ const progressEl = document.createElement("span");
2806
+ progressEl.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")};`;
2807
+ fileInput.addEventListener("change", async () => {
2808
+ const f = fileInput.files?.[0];
2809
+ if (!f) return;
2810
+ uploadBtn.disabled = true;
2811
+ try {
2812
+ const url = await uploadImage(f, (pct) => {
2813
+ progressEl.textContent = `Uploading\u2026 ${pct}%`;
2814
+ });
2815
+ currentUrl = url;
2816
+ renderName(url);
2817
+ progressEl.textContent = "Uploaded";
2818
+ } catch (err) {
2819
+ const msg = err instanceof Error && err.message.includes("(413)") ? "That file is too large." : "Upload failed \u2014 try again.";
2820
+ progressEl.textContent = msg;
2821
+ progressEl.style.color = v("danger");
2822
+ } finally {
2823
+ uploadBtn.disabled = false;
2824
+ fileInput.value = "";
2825
+ }
2826
+ });
2827
+ btnRow.appendChild(uploadBtn);
2828
+ btnRow.appendChild(clearBtn);
2829
+ btnRow.appendChild(progressEl);
2830
+ box.appendChild(btnRow);
2831
+ box.appendChild(fileInput);
2832
+ wrapper.appendChild(box);
2833
+ getValue2 = () => currentUrl === "" ? void 0 : currentUrl;
2834
+ break;
2835
+ }
2743
2836
  case "image": {
2744
2837
  const initialUrl = typeof initial === "string" ? initial : "";
2745
2838
  let currentUrl = initialUrl;