diff --git a/AGENTS.md b/AGENTS.md index bea3bb7..c8fb8c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,8 +100,17 @@ not for the public pages. See `docs/design-principles.md` for the design. the banner pen — everything saves immediately as you edit (brand/title debounced, slug on commit since it renames the path), tree rows navigate in place without transitions when focused, and the front page is a - root-only row whose empty slug is editable like any other (empty child - lists become drop zones while dragging). The two pens swap the docked + root-only row whose empty slug is editable like any other. Every + non-empty list (and the root) ends with a non-draggable ➕ footer row + (vuedraggable `#footer` slot): clicking it starts a new pending page at + that level, and while dragging it is the list's "end of list" drop + target. Dropping ON the lower part of a row moves the page under that + row (the child list's container invisibly overlaps its own row's bottom + via negative margin — Sortable inserts it as the first child natively), + while a row's exposed top edge inserts a sibling before it. Row + indentation is structural (each nested list margin-indents itself), so a + dragged row previews its whole subtree at the target list's depth. The + two pens swap the docked panel for the other editor; clicking the open editor's own pen closes it. Normally dynamic-imported onto the content page by pagerite.js when a 🖊️ edit link is clicked (the link carries `data-editor-src`/`data-editor-css`/`data-editor-mode`); the `/_/admin` diff --git a/docs/design-principles.md b/docs/design-principles.md index 1fe147d..e21c202 100644 --- a/docs/design-principles.md +++ b/docs/design-principles.md @@ -167,14 +167,18 @@ evolves. insert `![alt](/_/f/hash.ext)` at the cursor. - The **structure panel** (vue-draggable tree of the whole site, in site mode) covers page management: reorder any menu level, drag across - sections (empty child lists appear as drop zones while dragging), add, - delete (two clicks: the button arms, then deletes — no dialogs). - Every node is a real label — content-less category rows offer a - ➕ to give them a landing page. + sections, add, delete (two clicks: the button arms, then deletes — no + dialogs). Every node is a real label — content-less category rows offer + a ➕ to give them a landing page. Deleting a category removes only its landing page (the label and its - subpages stay). The ➕ in the panel header starts a new page as a - local-only tree row that can be dragged into place before its title and - slug are filled in; it is persisted only on commit. Rows are always + subpages stay). Every non-empty list ends with a ➕ row that starts a + new page as a local-only tree row at that level; the row can be dragged + into place before its title and slug are filled in and is persisted only + on commit. While dragging, these ➕ rows double as "end of this list" + drop targets; dropping ON the lower part of a row makes the page that + row's first child (even a leaf's, creating a sublist), while a row's + exposed top edge inserts a sibling before it. A dragged row's + indentation previews the target list's depth. Rows are always editable: titles save while typing, slug edits commit on blur/Enter since they rename the path (moving the whole subtree). The front page is the root row with an empty slug — renaming it away leaves no front diff --git a/frontend/src/SiteEditor.vue b/frontend/src/SiteEditor.vue index 81e1e69..d69cf75 100644 --- a/frontend/src/SiteEditor.vue +++ b/frontend/src/SiteEditor.vue @@ -157,12 +157,13 @@ function followMove(oldPath, newPath) { } // --- New page flow ------------------------------------------------------- -// The ➕ in the pages header adds a *pending* row to the tree: a local-only -// item that can be dragged into place before anything is filled in. It is -// persisted only on commit (✓/Enter), at wherever it currently sits. +// The ➕ row at the end of any list adds a *pending* row there: a +// local-only item that can be dragged into place before anything is +// filled in. It is persisted only on commit (✓/Enter), at wherever it +// currently sits. const pending = ref(null) -function newPage() { +function newPage(list) { if (pending.value) return // one at a time pending.value = { slug: '', @@ -174,7 +175,7 @@ function newPage() { children: [], pending: true, } - tree.value.push(pending.value) + list.push(pending.value) } // Where does the pending row currently sit? -> {parentPath, list, index}. @@ -431,6 +432,7 @@ provide('structureHandlers', { addContent, commitPending, discardPending, + newPage, }) // --- Banner editing ------------------------------------------------------ @@ -644,12 +646,6 @@ onUnmounted(() => {
-
@@ -774,21 +770,4 @@ onUnmounted(() => { overflow-y: auto; min-height: 0; } - -/* Icon buttons (➕) keep the emoji's own color, no button chrome. */ -.structure .add { - align-self: flex-start; /* don't stretch to the block's full width */ - margin-top: 0.3rem; - margin-left: 1.2em; /* align with the row titles, past the drag handle */ - padding: 0.1rem 0.3rem; - background: none; - border: none; - font-size: 1rem; - cursor: pointer; - opacity: 0.7; -} - -.structure .add:hover { - opacity: 1; -} diff --git a/frontend/src/StructureTree.vue b/frontend/src/StructureTree.vue index de24fd3..936957e 100644 --- a/frontend/src/StructureTree.vue +++ b/frontend/src/StructureTree.vue @@ -7,9 +7,15 @@ // the slug commits on blur/Enter since it renames the path, moving the // whole subtree. Nodes without content are category labels that redirect // to their first child; the ➕ on their row gives them a landing page. -// The ➕ in the panel header adds a *pending* row: a local-only item that -// can be dragged into place before its title/slug are filled in, and is -// persisted to the server only on commit (✓/Enter, Esc discards). +// Every non-empty list (and the root list) ends with a ➕ footer row: +// clicking it adds a *pending* row (a local-only item persisted to the +// server only on commit, ✓/Enter, Esc discards) at the end of that list, +// and while dragging it doubles as the list's "end of list" drop target. +// Leaf pages have no ➕ row of their own, but every row's child list is a +// drop target: its (invisible) container box reaches up over the bottom of +// its row, so dropping ON a row makes it a child (first position), while +// dropping on the row's top edge inserts a sibling before it. See the +// `.node > .treelist` style. // The front page is the root row with an empty slug: renaming it away // leaves no front page, and giving another top-level row the empty slug // makes it the front page. Delete is a two-step inline button (no dialog). @@ -59,8 +65,8 @@ function onMove(evt) { return true } -// While dragging, reveal empty child lists as drop zones (style.css) so a -// page can be moved under a childless page. +// While dragging, the child-list overlap strips become hit-testable (see +// styles). Purely functional — nothing is shown or resized. function onStart() { document.body.classList.add('tree-dragging') } @@ -85,12 +91,12 @@ function onEnd() { > diff --git a/frontend/src/assets/style.css b/frontend/src/assets/style.css index ce03454..55e85d7 100644 --- a/frontend/src/assets/style.css +++ b/frontend/src/assets/style.css @@ -191,14 +191,6 @@ body.editing #sidebar { } } -/* Site structure tree: while dragging, empty child lists appear as drop - zones so a page can be moved under a childless page. */ -body.tree-dragging .treelist:empty { - min-height: 1.2rem; - outline: 1px dashed var(--line); - border-radius: 4px; -} - /* The banner's own pen: opens the site editor (banner + structure). Qualified with `button` to beat the later .edit-link rule's left offset (both classes apply to the same element). */ @@ -211,15 +203,10 @@ button.banner-edit-link { font: inherit; border: none; cursor: pointer; - opacity: 0.55; background: none; padding: 0; } -.banner-edit-link:hover { - opacity: 1; -} - #sidebar { grid-column: 1; /* Pinned to the page's left edge (not the article's) and kept in view