Files

101 lines
3.0 KiB
JavaScript

// URL prefill UI; output is always built with DOM nodes so remote text never becomes markup.
const csrf = () =>
document.cookie
.split("; ")
.find((v) => v.startsWith("rp_csrf="))
?.split("=")[1] || "";
function message(target, text, error = false) {
const p = document.createElement("p");
p.textContent = text;
if (error) p.style.color = "#a8371a";
target.replaceChildren(p);
}
export function initPrefillPanel({ state, renderFormFromPlan, recompute }) {
const urlInput = document.getElementById("prefill-url"),
overwriteBox = document.getElementById("prefill-overwrite"),
goBtn = document.getElementById("prefill-go"),
undoBtn = document.getElementById("prefill-undo"),
resultEl = document.getElementById("prefill-result");
let snapshot = null;
goBtn.addEventListener("click", async () => {
const url = urlInput.value.trim();
if (!url) return;
goBtn.disabled = true;
message(resultEl, "Fetching…");
try {
const res = await fetch("/api/prefill", {
method: "POST",
headers: { "content-type": "application/json", "x-csrf-token": csrf() },
body: JSON.stringify({ url }),
});
const body = await res.json();
if (!body.ok) {
message(resultEl, body.error ?? body.code, true);
return;
}
snapshot = structuredClone(state.plan);
let applied = 0;
for (const [id, value] of Object.entries(body.fields ?? {})) {
if (!overwriteBox.checked && (state.plan.fields[id] ?? "") !== "")
continue;
state.plan.fields[id] = value;
applied++;
}
renderFormFromPlan();
markPrefilled(Object.keys(body.fields ?? {}), body.provenance ?? {});
recompute();
undoBtn.disabled = false;
const p = document.createElement("p"),
link = document.createElement("a");
link.href = body.source.finalUrl;
link.target = "_blank";
link.rel = "noopener";
link.textContent = body.source.finalUrl;
p.append(
`Applied ${applied} field${applied === 1 ? "" : "s"} from `,
link,
".",
);
const children = [p];
if (body.warnings?.length) {
const ul = document.createElement("ul");
ul.className = "warnings";
for (const warning of body.warnings) {
const li = document.createElement("li");
li.textContent = warning;
ul.append(li);
}
children.push(ul);
}
resultEl.replaceChildren(...children);
} catch (err) {
message(resultEl, err.message, true);
} finally {
goBtn.disabled = false;
}
});
undoBtn.addEventListener("click", () => {
if (!snapshot) return;
state.plan = snapshot;
snapshot = null;
undoBtn.disabled = true;
renderFormFromPlan();
for (const el of document.querySelectorAll(".prefilled")) {
el.classList.remove("prefilled");
el.removeAttribute("title");
}
recompute();
message(resultEl, "Prefill undone.");
});
function markPrefilled(ids, provenance) {
for (const id of ids) {
const el = document.querySelector(`[name="${CSS.escape(id)}"]`);
if (el) {
el.classList.add("prefilled");
if (provenance[id]) el.title = `from: ${provenance[id]}`;
}
}
}
}