feat: Notion-style Library page — complete redesign
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

Complete rewrite of /library page to match Notion's Library design:

1. Table view with columns: Page name (with icon), Created by (avatar),
   Source, Last edited time, Last visited time
2. Hover effects showing drag handle (6 dots), checkbox, OPEN button
3. Side peek panel: opens on right, shows page content preview,
   close button, favorite toggle, copy link, more menu
4. Multi-selection: checkbox per row, select-all header, 'X selected' bar
   with Delete and '...' menu
5. ... menu: Remove from Recents, Copy links to all, Move to, Move to Trash
6. Inline rename: double-click title → edit field
7. 6 tabs: Recents, Favorites, Shared, Published, Private, Workspace
8. Tree expand/collapse for nested pages (has_children support)
9. + Add new row at bottom
10. Search bar toggleable

API additions:
- library.py: enhanced _build_item with icon, source_label, author
- dashboard.py: new /api/pages/{id}/content, /rename, /trash endpoints
- All 133 tests pass
This commit is contained in:
2026-07-18 10:57:10 -04:00
parent 08c823d758
commit 28a41a30da
3 changed files with 747 additions and 344 deletions
+48
View File
@@ -1844,3 +1844,51 @@ def _render_blocks_public(blocks: list) -> str:
else:
html_parts.append(f'<p style="margin:4px 0;line-height:1.7;">{c}</p>')
return "\n".join(html_parts)
# ═══════════ Library page actions API ═══════════
@router.get("/api/pages/{page_id:int}/content")
async def api_page_content(page_id: int):
"""Get page content for side peek preview."""
with get_conn() as conn:
row = conn.execute(
"SELECT title, content, content_format FROM pages WHERE id=? AND deleted_at IS NULL",
(page_id,),
).fetchone()
if not row:
return JSONResponse({"error": "Not found"}, status_code=404)
return {
"title": row["title"],
"content": row["content"],
"format": row["content_format"] or "blocks",
}
@router.put("/api/pages/{page_id:int}/rename")
async def api_rename_page(page_id: int, request: Request):
"""Inline rename a page title."""
import json as _json
body = await request.json()
title = (body.get("title") or "").strip()
if not title:
return JSONResponse({"error": "Title required"}, status_code=400)
with get_conn() as conn:
conn.execute(
"UPDATE pages SET title=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND deleted_at IS NULL",
(title, page_id),
)
conn.commit()
return {"status": "ok", "title": title}
@router.post("/api/pages/{page_id:int}/trash")
async def api_trash_page(page_id: int):
"""Soft-delete a page (move to trash)."""
with get_conn() as conn:
conn.execute(
"UPDATE pages SET parent_section='Trash', deleted_at=CURRENT_TIMESTAMP WHERE id=?",
(page_id,),
)
conn.commit()
return {"status": "ok"}