fix command page buttons

This commit is contained in:
Franz Rolfsvaag 2026-06-17 21:32:24 +02:00
parent 4c1c23bd27
commit 0cf7408225
5 changed files with 113 additions and 86 deletions

View File

@ -125,6 +125,7 @@ This file tracks larger Lumi work that cannot safely be completed in one pass. K
## Done ## Done
- 2026-06-17: Fixed custom command Edit buttons and `/commands` Copy Link / expand buttons with delegated handlers, clipboard fallback, and v0.1.5 patch bump.
- 2026-06-17: Fixed repo-based core updates deleting `data/update-cache/repo` during apply, added a verification guard, and bumped core package version to v0.1.4. - 2026-06-17: Fixed repo-based core updates deleting `data/update-cache/repo` during apply, added a verification guard, and bumped core package version to v0.1.4.
- 2026-06-17: Bumped core package version to v0.1.3. - 2026-06-17: Bumped core package version to v0.1.3.
- 2026-06-17: Completed homepage hero embed pass for Discord widgets, YouTube video playback options, external embed fallback/sandbox controls, admin validation, platform-specific fields, and Test preview behavior. - 2026-06-17: Completed homepage hero embed pass for Discord widgets, YouTube video playback options, external embed fallback/sandbox controls, admin validation, platform-specific fields, and Test preview behavior.

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.1.4", "version": "0.1.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.1.4", "version": "0.1.5",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"better-sqlite3": "^11.5.0", "better-sqlite3": "^11.5.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.1.4", "version": "0.1.5",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {

View File

@ -57,35 +57,36 @@
} }
}); });
const editToggles = Array.from( const selectorEscape = (value) => {
document.querySelectorAll("[data-edit-toggle]") if (window.CSS?.escape) return CSS.escape(value);
); return String(value || "").replace(/["\\]/g, "\\$&");
if (editToggles.length) { };
const editRows = Array.from(document.querySelectorAll("[data-edit-row]"));
const updateToggleStates = () => {
editToggles.forEach((button) => {
const key = button.dataset.editToggle;
const row = editRows.find((item) => item.dataset.editRow === key);
const isOpen = row?.classList.contains("is-open");
button.setAttribute("aria-expanded", isOpen ? "true" : "false");
});
};
editToggles.forEach((button) => { const updateEditToggleStates = () => {
button.addEventListener("click", () => { document.querySelectorAll("[data-edit-toggle]").forEach((button) => {
const key = button.dataset.editToggle; const key = button.dataset.editToggle;
const target = editRows.find((item) => item.dataset.editRow === key); const row = key ? document.querySelector(`[data-edit-row="${selectorEscape(key)}"]`) : null;
const willOpen = target ? !target.classList.contains("is-open") : false; const isOpen = row?.classList.contains("is-open");
editRows.forEach((row) => { button.setAttribute("aria-expanded", isOpen ? "true" : "false");
row.classList.remove("is-open");
});
if (target && willOpen) {
target.classList.add("is-open");
}
updateToggleStates();
});
}); });
} };
document.addEventListener("click", (event) => {
const button = event.target.closest("[data-edit-toggle]");
if (!button) return;
event.preventDefault();
const key = button.dataset.editToggle;
const target = key ? document.querySelector(`[data-edit-row="${selectorEscape(key)}"]`) : null;
const willOpen = target ? !target.classList.contains("is-open") : false;
document.querySelectorAll("[data-edit-row].is-open").forEach((row) => {
row.classList.remove("is-open");
});
if (target && willOpen) {
target.classList.add("is-open");
}
updateEditToggleStates();
});
updateEditToggleStates();
document.querySelectorAll("[data-table]").forEach((table) => { document.querySelectorAll("[data-table]").forEach((table) => {
const tbody = table.tBodies[0]; const tbody = table.tBodies[0];
@ -198,21 +199,23 @@
setGroupExpanded(group, false); setGroupExpanded(group, false);
}); });
tbody.querySelectorAll("[data-command-toggle]").forEach((button) => { tbody.addEventListener("click", (event) => {
button.addEventListener("click", (event) => { const button = event.target.closest("[data-command-toggle]");
event.preventDefault(); if (!button || !tbody.contains(button)) {
const rootRow = button.closest("tr"); return;
if (!rootRow) { }
return; event.preventDefault();
} const rootRow = button.closest("tr");
const key = rootRow.dataset.commandRoot; if (!rootRow) {
const group = key ? commandGroups.get(key) : null; return;
if (!group) { }
return; const key = rootRow.dataset.commandRoot;
} const group = key ? commandGroups.get(key) : null;
const expanded = rootRow.dataset.expanded === "true"; if (!group) {
setGroupExpanded(group, !expanded); return;
}); }
const expanded = rootRow.dataset.expanded === "true";
setGroupExpanded(group, !expanded);
}); });
revealAnchorRow(); revealAnchorRow();
@ -864,50 +867,56 @@
}); });
} }
document.querySelectorAll("[data-copy]").forEach((button) => { const copyText = async (text) => {
button.addEventListener("click", async () => { if (navigator.clipboard?.writeText && window.isSecureContext) {
const text = button.getAttribute("data-copy") || ""; await navigator.clipboard.writeText(text);
if (!text) { return true;
return; }
} const tempInput = document.createElement("textarea");
const label = button.querySelector("[data-copy-label]"); tempInput.value = text;
const originalLabel = label ? label.textContent : ""; tempInput.setAttribute("readonly", "");
tempInput.style.position = "fixed";
tempInput.style.left = "-9999px";
tempInput.style.top = "0";
document.body.appendChild(tempInput);
tempInput.focus();
tempInput.select();
try {
return document.execCommand("copy");
} finally {
tempInput.remove();
}
};
const markCopied = () => { document.addEventListener("click", async (event) => {
button.classList.add("copied"); const button = event.target.closest("[data-copy]");
if (!button) return;
event.preventDefault();
const text = button.getAttribute("data-copy") || "";
if (!text) {
return;
}
const label = button.querySelector("[data-copy-label]");
const originalLabel = label ? label.textContent : "";
const markCopyResult = (copied) => {
button.classList.toggle("copied", copied);
button.classList.toggle("copy-failed", !copied);
if (label) {
label.textContent = copied ? "Copied" : "Copy failed";
}
window.setTimeout(() => {
button.classList.remove("copied", "copy-failed");
if (label) { if (label) {
label.textContent = "Copied"; label.textContent = originalLabel;
} }
window.setTimeout(() => { }, copied ? 1200 : 1800);
button.classList.remove("copied"); };
if (label) {
label.textContent = originalLabel;
}
}, 1200);
};
try { try {
if (navigator.clipboard && navigator.clipboard.writeText) { markCopyResult(await copyText(text));
await navigator.clipboard.writeText(text); } catch {
markCopied(); markCopyResult(false);
return; }
}
} catch {
// Fall back to legacy copy.
}
const tempInput = document.createElement("input");
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand("copy");
markCopied();
} catch {
// Ignore copy errors.
} finally {
tempInput.remove();
}
});
}); });
})(); })();

View File

@ -777,6 +777,23 @@ body {
border-radius: 999px; border-radius: 999px;
} }
.copy-pill.copy-failed {
border-color: var(--danger);
}
.copy-pill.copy-failed::after {
content: "Copy failed";
position: absolute;
top: -10px;
right: -6px;
background: var(--danger);
color: white;
font-size: 0.65rem;
padding: 2px 6px;
border-radius: 999px;
white-space: nowrap;
}
.copy-link { .copy-link {
min-width: 96px; min-width: 96px;
justify-content: center; justify-content: center;