aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/dashboard')
-rw-r--r--src/api/dashboard/dashboard.html43
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();