diff options
Diffstat (limited to 'src/api/dashboard')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 43 |
1 files changed, 40 insertions, 3 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index db8dd15..3c24a2a 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -140,6 +140,8 @@ .editor input[type=text], .editor textarea { width: 100%; font: inherit; color: var(--fg); background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 2px 4px; } .editor button { font: inherit; } + .editor button.delete { color: #fff; background: var(--bad); border: 1px solid var(--bad); + border-radius: 3px; } .tree details.editing > :not(summary):not(.editor) { display: none; } .tree .leaf[title] { cursor: text; } table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; } @@ -966,6 +968,38 @@ function editor(path, value, done, options = {}) { }; save.addEventListener("click", submit); cancel.addEventListener("click", () => done(false)); + // Editing what's there: it can be deleted too (DELETE /store/<path>), after a second + // click to be sure, not too soon after the first: a double-click doesn't delete. + const remove = options.extra ? null : el("button", { type: "button", class: "delete" }, + "Delete"); + let sure = null, askedAt = 0; + remove?.addEventListener("click", async () => { + if (sure === null) { + remove.textContent = "Delete: sure?"; + askedAt = performance.now(); + sure = setTimeout(() => { sure = null; remove.textContent = "Delete"; }, 3000); + return; + } + if (performance.now() - askedAt < 500) return; + clearTimeout(sure); + remove.disabled = save.disabled = true; + message.className = "note"; + message.textContent = "Deleting…"; + try { + const response = await fetch(storeUrl(path, false), { method: "DELETE" }); + if (!response.ok) { + let why = response.status === 403 ? "not allowed" : ""; + try { why = (await response.json()).error || why; } catch { /* no body */ } + throw new Error("HTTP " + response.status + (why ? ": " + why : "")); + } + done("deleted", response); + } catch (error) { + sure = null; + remove.textContent = "Delete"; + remove.disabled = save.disabled = false; + fail("Not deleted: " + error.message); + } + }); // Enter in the short field saves; in the area, Ctrl or ⌘ and Enter does. const keys = (input, line) => input.addEventListener("keydown", event => { if (event.key === "Escape") { event.preventDefault(); done(false); } @@ -977,7 +1011,8 @@ function editor(path, value, done, options = {}) { if (extra) keys(extra, true); setTimeout(() => (extra && !extra.value ? extra : field).focus()); return el("div", { class: "editor" }, ...(extra ? [extra] : []), field, - el("div", {}, save, " ", cancel, " ", el("label", {}, json, " JSON"), " ", + el("div", {}, save, " ", cancel, " ", ...(remove ? [remove, " "] : []), + el("label", {}, json, " JSON"), " ", el("span", { class: "note" }, "Ctrl+Enter saves, Esc cancels"), " ", message)); } @@ -986,9 +1021,11 @@ const editable = path => path.length > 0 && addressable(path); // Branches whose reading again waited for an edit in them to end, by path (as JSON). const documentDeferred = new Set(); // After an edit: what changed is read again (the node's `document` event follows), -// and what was waiting for the edit; `opened`, what it made, shown open. +// and what was waiting for the edit; `opened`, what it made, shown open. `saved`: +// true, false (cancelled), or "deleted" (then its parent is read again). function edited(path, saved, opened = path) { - if (saved) documentOpen.add(JSON.stringify(opened)); + if (saved === "deleted") path = path.slice(0, -1); + else if (saved) documentOpen.add(JSON.stringify(opened)); release(); const waiting = [...documentDeferred].map(id => JSON.parse(id)); documentDeferred.clear(); |
