4.8 KiB · text History 14ab219
(() => {
  const isTypingTarget = (el) => {
    if (!el) return false;
    const tag = el.tagName;
    if (tag === "TEXTAREA") return true;
    if (tag === "INPUT") {
      const t = el.type;
      return t !== "file" && t !== "checkbox" && t !== "radio" && t !== "button" && t !== "submit";
    }
    return !!el.isContentEditable;
  };

  const extFor = (type) => {
    if (!type) return "bin";
    const slash = type.indexOf("/");
    return slash >= 0 ? type.slice(slash + 1).split("+")[0] : "bin";
  };

  const pastedName = (blob) => {
    if (blob.name && blob.name !== "image.png") return blob;
    return new File([blob], `pasted-${Date.now()}.${extFor(blob.type)}`, { type: blob.type });
  };

  // Toast — created on demand, auto-removes after a few seconds.
  const showToast = (msg, opts = {}) => {
    let host = document.getElementById("dms-toast-host");
    if (!host) {
      host = document.createElement("div");
      host.id = "dms-toast-host";
      host.className = "toast toast-end z-50";
      document.body.appendChild(host);
    }
    const alert = document.createElement("div");
    const kind = opts.kind || "info";
    alert.className = `alert alert-${kind} shadow-lg`;
    if (opts.href) {
      const a = document.createElement("a");
      a.href = opts.href;
      a.className = "underline";
      a.textContent = msg;
      alert.appendChild(a);
    } else {
      const span = document.createElement("span");
      span.textContent = msg;
      alert.appendChild(span);
    }
    host.appendChild(alert);
    const ttl = opts.ttl ?? 4000;
    if (ttl > 0) setTimeout(() => alert.remove(), ttl);
    return alert;
  };

  // Path 1: a quick-upload form is on this page — attach the pasted file to it.
  const attachToQuickForm = (file) => {
    const fileInput = document.getElementById("quick-upload-file");
    const preview = document.getElementById("quick-upload-preview");
    if (!fileInput) return false;
    const dt = new DataTransfer();
    dt.items.add(file);
    fileInput.files = dt.files;
    if (preview) {
      const img = preview.querySelector("img");
      const name = preview.querySelector("[data-name]");
      if (img) {
        if (img.src) URL.revokeObjectURL(img.src);
        img.src = URL.createObjectURL(file);
      }
      if (name) name.textContent = `${file.name} · ${(file.size / 1024).toFixed(1)} KB`;
      preview.classList.remove("hidden");
      preview.classList.add("flex");
    }
    fileInput.scrollIntoView({ behavior: "smooth", block: "center" });
    return true;
  };

  // Path 2: anywhere else on the site — POST it directly and surface the result.
  const uploadDirect = async (file) => {
    const fd = new FormData();
    fd.append("file", file, file.name);
    const status = showToast(`Uploading ${file.name}…`, { kind: "info", ttl: 0 });
    try {
      const res = await fetch("/uploads", { method: "POST", body: fd, credentials: "same-origin" });
      status.remove();
      if (res.redirected && res.url.includes("/login")) {
        showToast("Sign in to upload.", { kind: "warning" });
        return;
      }
      if (!res.ok) {
        showToast(`Upload failed (${res.status}).`, { kind: "error" });
        return;
      }
      const href = res.redirected ? res.url : "/uploads";
      showToast(`Uploaded ${file.name} — view`, { kind: "success", href });
    } catch (e) {
      status.remove();
      showToast(`Upload error: ${e.message || e}`, { kind: "error" });
    }
  };

  document.addEventListener("paste", (e) => {
    if (!document.body.dataset.authed) return;
    if (isTypingTarget(e.target)) return;
    const items = e.clipboardData && e.clipboardData.items;
    if (!items) return;
    for (const it of items) {
      if (it.kind !== "file") continue;
      const blob = it.getAsFile();
      if (!blob || !blob.type.startsWith("image/")) continue;
      const file = pastedName(blob);
      e.preventDefault();
      if (!attachToQuickForm(file)) uploadDirect(file);
      return;
    }
  });

  // When the quick-upload form is on the page, also preview manual file picks.
  const quickFile = document.getElementById("quick-upload-file");
  const quickPreview = document.getElementById("quick-upload-preview");
  if (quickFile && quickPreview) {
    const img = quickPreview.querySelector("img");
    const name = quickPreview.querySelector("[data-name]");
    quickFile.addEventListener("change", () => {
      const f = quickFile.files && quickFile.files[0];
      if (!f || !f.type.startsWith("image/")) {
        quickPreview.classList.add("hidden");
        quickPreview.classList.remove("flex");
        return;
      }
      if (img) {
        if (img.src) URL.revokeObjectURL(img.src);
        img.src = URL.createObjectURL(f);
      }
      if (name) name.textContent = `${f.name} · ${(f.size / 1024).toFixed(1)} KB`;
      quickPreview.classList.remove("hidden");
      quickPreview.classList.add("flex");
    });
  }
})();