Add column editing in dashboard edit mode
Per-column header bar with size dropdown (small/full), left/right
move arrows, and delete. New "+ Add column" button at the bottom of
the page-columns container. Each action POSTs to a column endpoint
and reloads.
Backend endpoints:
- POST /edit/api/pages/{slug}/columns — add (form: size)
- POST /edit/api/pages/{slug}/columns/{col}/delete
- POST /edit/api/pages/{slug}/columns/{col}/move?dir=left|right
- POST /edit/api/pages/{slug}/columns/{col}/size — set size
All go through configEditor's yaml.Node tree so comments and other
config survive.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4b8a34a2ae
commit
8ba6dcb704
@@ -477,3 +477,83 @@ textarea.edit-input {
|
||||
.edit-input-invalid {
|
||||
border-color: var(--color-negative);
|
||||
}
|
||||
|
||||
/* ---------- Column controls ---------- */
|
||||
|
||||
.edit-column-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.35rem 0.5rem;
|
||||
background: var(--color-popover-background);
|
||||
border: 1px solid var(--color-popover-border);
|
||||
border-radius: 4px;
|
||||
font-size: var(--font-size-h5);
|
||||
}
|
||||
|
||||
.edit-column-label {
|
||||
color: var(--color-text-subdue);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.edit-column-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.edit-column-size {
|
||||
background: var(--color-widget-background);
|
||||
border: 1px solid var(--color-widget-content-border);
|
||||
border-radius: 3px;
|
||||
color: var(--color-text-base);
|
||||
font: inherit;
|
||||
font-size: var(--font-size-h6);
|
||||
padding: 0.15rem 0.3rem;
|
||||
}
|
||||
|
||||
.edit-column-move,
|
||||
.edit-column-delete {
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-popover-border);
|
||||
color: var(--color-text-subdue);
|
||||
border-radius: 3px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: var(--font-size-h6);
|
||||
}
|
||||
|
||||
.edit-column-move:hover,
|
||||
.edit-column-delete:hover {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.edit-column-move:disabled,
|
||||
.edit-column-delete:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.edit-column-delete:hover:not(:disabled) {
|
||||
color: var(--color-negative);
|
||||
border-color: var(--color-negative);
|
||||
}
|
||||
|
||||
.edit-add-column {
|
||||
display: block;
|
||||
margin: var(--widget-gap) auto 0;
|
||||
padding: 0.6rem 1.5rem;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-text-subdue);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text-subdue);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: var(--font-size-h4);
|
||||
}
|
||||
|
||||
.edit-add-column:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,90 @@ function removeColumnAddButtons() {
|
||||
document.querySelectorAll(".edit-add-widget").forEach((b) => b.remove());
|
||||
}
|
||||
|
||||
function inferColumnSize(col) {
|
||||
const m = String(col.className || "").match(/page-column-(\S+)/);
|
||||
return m ? m[1] : "full";
|
||||
}
|
||||
|
||||
function addColumnHeaders() {
|
||||
const cols = document.querySelectorAll(".page-column");
|
||||
cols.forEach((col, colIdx) => {
|
||||
if (col.querySelector(":scope > .edit-column-header")) return;
|
||||
const size = inferColumnSize(col);
|
||||
const header = document.createElement("div");
|
||||
header.className = "edit-column-header";
|
||||
header.dataset.col = String(colIdx);
|
||||
header.innerHTML = `
|
||||
<span class="edit-column-label">Column ${colIdx + 1}</span>
|
||||
<select class="edit-column-size" title="Column size">
|
||||
<option value="small" ${size === "small" ? "selected" : ""}>small</option>
|
||||
<option value="full" ${size === "full" ? "selected" : ""}>full</option>
|
||||
</select>
|
||||
<div class="edit-column-spacer"></div>
|
||||
<button type="button" class="edit-column-move" data-dir="up" title="Move left" ${colIdx === 0 ? "disabled" : ""}>←</button>
|
||||
<button type="button" class="edit-column-move" data-dir="down" title="Move right" ${colIdx === cols.length - 1 ? "disabled" : ""}>→</button>
|
||||
<button type="button" class="edit-column-delete" title="Delete column" ${cols.length === 1 ? "disabled" : ""}>✕</button>
|
||||
`;
|
||||
col.insertBefore(header, col.firstChild);
|
||||
});
|
||||
|
||||
// Add a "+ Add column" button in the page-columns container.
|
||||
const container = document.querySelector(".page-columns");
|
||||
if (container && !container.parentElement.querySelector(":scope > .edit-add-column")) {
|
||||
const btn = document.createElement("button");
|
||||
btn.type = "button";
|
||||
btn.className = "edit-add-column";
|
||||
btn.textContent = "+ Add column";
|
||||
container.parentElement.insertBefore(btn, container.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
function removeColumnHeaders() {
|
||||
document.querySelectorAll(".edit-column-header").forEach((el) => el.remove());
|
||||
document.querySelectorAll(".edit-add-column").forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
async function postColumnAction(path, body) {
|
||||
const { slug, baseURL } = pageInfo();
|
||||
const init = { method: "POST" };
|
||||
if (body instanceof URLSearchParams) {
|
||||
init.headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
||||
init.body = body.toString();
|
||||
}
|
||||
setStatus("Saving…", "saving");
|
||||
const r = await fetch(
|
||||
baseURL + "/edit/api/pages/" + encodeURIComponent(slug) + path,
|
||||
init,
|
||||
);
|
||||
if (!r.ok) {
|
||||
setStatus("Failed: " + (await r.text()), "error");
|
||||
return false;
|
||||
}
|
||||
location.reload();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function addColumn(size) {
|
||||
const body = new URLSearchParams();
|
||||
body.set("size", size || "full");
|
||||
return postColumnAction("/columns", body);
|
||||
}
|
||||
|
||||
async function deleteColumn(col) {
|
||||
if (!confirm("Delete this column and everything in it?")) return;
|
||||
return postColumnAction(`/columns/${col}/delete`, null);
|
||||
}
|
||||
|
||||
async function moveColumn(col, dir) {
|
||||
return postColumnAction(`/columns/${col}/move?dir=${dir}`, null);
|
||||
}
|
||||
|
||||
async function setColumnSize(col, size) {
|
||||
const body = new URLSearchParams();
|
||||
body.set("size", size);
|
||||
return postColumnAction(`/columns/${col}/size`, body);
|
||||
}
|
||||
|
||||
function setStatus(text, kind) {
|
||||
let el = document.getElementById("edit-mode-status");
|
||||
if (!el) {
|
||||
@@ -776,6 +860,7 @@ function enterEditMode() {
|
||||
document.querySelectorAll("#edit-mode-toggle").forEach((b) => b.classList.add("active"));
|
||||
indexWidgets();
|
||||
addHandles();
|
||||
addColumnHeaders();
|
||||
addColumnAddButtons();
|
||||
setStatus("Edit mode — drag widgets to rearrange");
|
||||
|
||||
@@ -806,6 +891,7 @@ function exitEditMode() {
|
||||
state.sortables.forEach((s) => s.destroy());
|
||||
state.sortables = [];
|
||||
removeHandles();
|
||||
removeColumnHeaders();
|
||||
removeColumnAddButtons();
|
||||
document.getElementById("edit-mode-status")?.remove();
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
@@ -864,6 +950,36 @@ document.addEventListener("click", (e) => {
|
||||
if (addBtn) {
|
||||
e.preventDefault();
|
||||
showAddPicker(parseInt(addBtn.dataset.col, 10));
|
||||
return;
|
||||
}
|
||||
const addColBtn = e.target.closest(".edit-add-column");
|
||||
if (addColBtn) {
|
||||
e.preventDefault();
|
||||
const size = prompt("Column size: 'small' or 'full'", "full");
|
||||
if (size && (size === "small" || size === "full")) addColumn(size);
|
||||
return;
|
||||
}
|
||||
const moveColBtn = e.target.closest(".edit-column-move");
|
||||
if (moveColBtn) {
|
||||
e.preventDefault();
|
||||
const header = moveColBtn.closest(".edit-column-header");
|
||||
moveColumn(parseInt(header.dataset.col, 10), moveColBtn.dataset.dir);
|
||||
return;
|
||||
}
|
||||
const delColBtn = e.target.closest(".edit-column-delete");
|
||||
if (delColBtn) {
|
||||
e.preventDefault();
|
||||
const header = delColBtn.closest(".edit-column-header");
|
||||
deleteColumn(parseInt(header.dataset.col, 10));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("change", (e) => {
|
||||
const sizeSel = e.target.closest(".edit-column-size");
|
||||
if (sizeSel) {
|
||||
const header = sizeSel.closest(".edit-column-header");
|
||||
setColumnSize(parseInt(header.dataset.col, 10), sizeSel.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user