feat: Notion-style block editor v1 + Move to Trash + fixes
- Block editor: 13 block types with contentEditable + vanilla JS event delegation
- Slash menu /: 14 commands, 100% vanilla JS (zero Alpine conflicts)
- Keyboard: Enter=new block, Shift+Enter=soft break, arrows navigate blocks
- Drag & drop: SortableJS with 6-dot handles on block-wrapper
- Move to Trash: DELETE /board/api/pages/{id} + instant sidebar cleanup
- Placeholders: 'New page' + 'Type / for commands' in gray (CSS :empty::before)
- Redirect after delete stays in project context via workspaceKey
- Fixes: title auto-save, cursor positioning, x-for keys, Sortable guard
This commit is contained in:
+26
-4
@@ -389,6 +389,7 @@
|
||||
// ── Drag & drop tree ──
|
||||
treeSortables: [],
|
||||
initTreeSortable() {
|
||||
if (typeof Sortable === 'undefined') return;
|
||||
// Initialize SortableJS on every .sidebar-items list
|
||||
const self = this;
|
||||
document.querySelectorAll('.sidebar-items').forEach(function(el) {
|
||||
@@ -475,8 +476,29 @@
|
||||
alert(`Moved to ${section}`);
|
||||
break;
|
||||
case 'delete':
|
||||
if (!confirm(`Delete "${pageName}"?`)) break;
|
||||
alert('Delete not yet implemented (pages go to Trash in DB)');
|
||||
if (!confirm(`Move "${pageName}" to trash?`)) break;
|
||||
const delNum = pageId.replace('page/', '');
|
||||
fetch(`/board/api/pages/${delNum}`, {
|
||||
method: 'DELETE', headers: { 'X-CSRF-Token': csrf }
|
||||
})
|
||||
.then(r => { if (!r.ok) throw new Error('Delete failed'); return r.json(); })
|
||||
.then(data => {
|
||||
// Remove from ALL sidebar sections (Recents, Private, etc.)
|
||||
const items = document.querySelectorAll(`.sidebar-item[data-page-id="${pageId}"]`);
|
||||
items.forEach(item => {
|
||||
const next = item.nextElementSibling;
|
||||
if (next && next.classList.contains('tree-children-wrapper')) {
|
||||
next.remove();
|
||||
}
|
||||
item.remove();
|
||||
});
|
||||
// Redirect if viewing deleted page — stay in project context
|
||||
if (window.location.pathname.includes(`/pages/${delNum}`)) {
|
||||
const wk = this.workspaceKey;
|
||||
window.location.href = wk && wk.includes('/') ? `/board/${wk}` : '/';
|
||||
}
|
||||
})
|
||||
.catch(e => alert('Delete failed: ' + e.message));
|
||||
break;
|
||||
case 'openTab':
|
||||
window.open('/' + pageId, '_blank');
|
||||
@@ -501,7 +523,7 @@
|
||||
},
|
||||
newPage(section) {
|
||||
const project = this.workspaceKey || '';
|
||||
fetch(`/board/api/pages?title=New%20page§ion=${encodeURIComponent(section)}&project=${encodeURIComponent(project)}`, {
|
||||
fetch(`/board/api/pages?section=${encodeURIComponent(section)}&project=${encodeURIComponent(project)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': this.getCsrfToken() }
|
||||
})
|
||||
@@ -512,7 +534,7 @@
|
||||
newSubPage(parentId) {
|
||||
const project = this.workspaceKey || '';
|
||||
const pid = parentId.replace('page/', '');
|
||||
fetch(`/board/api/pages?title=New%20sub-page§ion=Private&project=${encodeURIComponent(project)}&parent_id=${pid}`, {
|
||||
fetch(`/board/api/pages?section=Private&project=${encodeURIComponent(project)}&parent_id=${pid}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': this.getCsrfToken() }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user