The .peek-header class from library.html CSS was not available on the
local-workspace page, causing header buttons to stack vertically.
Added inline display:flex;align-items:center;gap:4px on the header div.
- Same 560px width, same header layout (close | icon+title | open | fav | copy | more)
- Header uses peek-header class from library CSS for identical styling
- Meta info (type, size, date) moved inline into body as subtle header
- Tags moved inline into body below meta
- More menu with Open, Favorite, Copy link, Close actions
- Copy link button uses navigator.clipboard API with toast feedback
- Bottom action buttons removed (now in header + more menu)
- Empty state matches library (file icon + 'Select a file to preview')
Root cause: blocks format content is a direct array [{id,type,content}],
not wrapped in {blocks:[...]}. Old code checked blocks.blocks which was
always undefined, so internal pages showed nothing.
Fixes:
- Handle blocks as raw array or {blocks:[...]} wrapper (both formats)
- Add heading styling (h3-h6 based on heading_1/2/3), code blocks (pre)
- Add proper markdown format handler (renders as pre-wrapped text)
- Add format=file handler showing file metadata + Open file link
- Add unknown format fallback as pre-wrapped text with char limit
- Clean up escaped quotes — no more double-escaped \" in strings
- Better empty state messages (Empty document, No content)
Alpine 3.14.9 x-if and x-show are fundamentally broken for reactive
visibility toggling of the preview panel. Complete rewrite:
- Replace all Alpine directives in preview panel with static HTML + IDs
- selectForPreview(): vanilla JS to populate fields and show panel
- closePreviewPanel(): vanilla JS to hide panel
- _loadPreviewContentVanilla(): async content loader using innerHTML
- Open button wired via onclick to navigate to /pages/{id}
- No Alpine reactivity dependencies - panel just works
- Keep outer x-show on preview-panel div for transition animation
- Wrap all inner content referencing previewItem in <template x-if=previewItem>
to prevent Alpine from evaluating expressions when previewItem is null
- Clean up debug console.logs from earlier debugging
- This combines x-show (reliable visibility toggle) with x-if (null guard)
Root cause: Alpine 3.14.9 x-if on template element doesn't reliably re-render
when a reactive property changes. previewItem was being set (confirmed by logs)
but x-if never re-evaluated.
Fix: replace <template x-if> wrapper with direct x-show on the preview-panel div.
x-show with x-transition is the reliable pattern for this Alpine version.
- Add console.log in selectForPreview and openSidePeek to trace button clicks
- Add console.log in Alpine OPEN button click handler
- Remove redundant x-show from preview-panel (was conflicting with x-if transition)
- This should fix the preview panel not appearing when Open button is clicked
- Add CSS rule .ws-tree-item:hover .hover-only for tree item hover visibility
- Move OPEN button OUT of .actions div, place it right after title span with
margin-left:auto and .hover-only class (matches Library layout exactly)
- Fix renderChildren() static HTML: button now between name span and .actions
- Remove duplicate button from inside .actions div
- Button calls selectForPreview(node) or openSidePeek(id) for side preview panel
- Add openSidePeek(id) helper method to find node by id and open in side preview
- Update renderChildren() static HTML OPEN button: replace full page nav with selectForPreview
- Add OPEN button in Alpine x-for tree template actions div for file items
- Uses existing preview-panel (right side panel) instead of navigating away
- Dropdown Filter dans la toolbar (icône filtre, à côté de Sort)
- Menu avec Folders, Pages, PDF, Images, Code, Text + Clear filter
- doFilter() applique maintenant filterType au tree view via _filterTree()
- Filter chips visibles dans toutes les vues comme indicateur
- L'enhanced table prend le relais quand un filtre est actif dans les vues non-tree
143 tests passent
Alpine ne résolvait pas wsId dans le scope x-for.
Maintenant: href='/local-workspace' (route qui utilise le cookie ws actif).
+ hard refresh nécessaire (Ctrl+Shift+R) pour vider cache ancien JS.
143 tests passent
- List (compact): rangée '..' avec icône folder avant les données
- Details (enhanced): rangée '..' avec toutes les colonnes
- Cards (title grid): carte '..' dans la grille
- Content (content list): élément '..' avec preview 'Parent folder'
- x-show='currentFolder > 0' pour n'afficher que dans un sous-dossier
- 143 tests passent
- createPage() now detects active workspace context (library, local-workspace page, sidebar)
- In a local/Gitea workspace: creates workspace item via /api/local-workspace/items with prompt
- No workspace: creates general Notion page via /board/api/pages (no prompt)
- Added hidden #fd-local-ws-id in local_workspace.html to expose workspace_id
- _confirmCreateFolder() passes workspace_id for Gitea workspaces
- Added _getContext() helper that checks multiple context sources in priority order
- Added _toast() helper for cross-component notifications
- Add global window.FlowDeck namespace with createPage() and showCreateFolderModal()
- createPage() creates a Notion-style page via /board/api/pages (same as sidebar bottom-right button)
- showCreateFolderModal() opens a global themed modal (dark/light) instead of browser prompt()
- All New Page buttons now call window.FlowDeck.createPage()
- All New Folder buttons now call window.FlowDeck.showCreateFolderModal()
- Global New Folder modal uses existing .flowdeck-modal CSS classes for theme consistency
- Updated Ctrl+N keyboard shortcut to use unified createPage()
Les échappements de quotes dans le patch précédent étaient cassés
(\\' au lieu de \'), causant des erreurs de syntaxe JS qui
empêchaient l'initialisation de _wsInitData et donc d'Alpine.
- Library: ajout /api/library/children/{id} pour charger les enfants à la demande
- Library: drag handle (.drag-handle) et checkbox (.lib-checkbox) toujours visibles
- Library: SortableJS init pour drag-and-drop réordonner les rows
- Library: toggleExpand() async avec chargement des enfants depuis l'API
- Local workspace: drag handle 6-dots + checkbox toujours visible dans les tree items
- Local workspace: renderChildren() inclut checkbox + drag handle + inline rename
- Local workspace: clic sur nom de fichier = inline rename (plus navigation directe)
- Local workspace: bouton Open pour ouvrir le fichier (double-clic aussi)
- CSS: .item-checkbox toujours visible (plus opacity:0)
- CSS: .drag-handle avec opacité .45 par défaut, 1.0 au hover
Config:
- Fixed SQLite path parsing on Windows (handle drive letters without prepending /)
- Changed default theme from dark to light
- Updated OAuth test credentials (gitea_oauth_client_id/secret)
Auth:
- First real user (excluding default admin with no password) becomes admin
- Changed user count query to exclude users without password_hash
File viewer:
- Removed separate _render_file_viewer() —
Backend:
- DELETE /api/local-workspace/items/{id} now soft-deletes (sets deleted_at
instead of hard DELETE) — items go to trash, not permanently gone
- New POST /api/local-workspace/items/{id}/restore to undo a delete
- _load_workspace_pages() and _load_children() now filter deleted_at IS NULL
so soft-deleted items disappear from sidebar and tree queries
Frontend:
- undoDelete() now calls the restore API per item (single or bulk)
- bulkDelete() now shows the same undo banner as context menu delete
- All delete paths (ctxMenu, bulk, modal) use the same soft-delete + undo flow
The context menu component (_ctxMenuData) was calling methods on
window._wsInitData (the raw JS object), NOT on Alpine's reactive proxy.
This meant that changes made by ctxMenuDelete/ctxMenuDuplicate (like
updating displayTree) were applied to the raw object but not reflected
in Alpine's reactive proxy that controls DOM rendering.
Fix: _ctxMenuData now uses a _ws() helper that returns window._wsData
(Alpine proxy, set in init()) with fallback to window._wsInitData for
safety. All context menu actions now go through the reactive proxy.
Replaced fragile _removeFromTree() + displayTree spread (which had Alpine
reactivity issues) with a clean _reloadAfterAction() call that reloads the
tree from the API. This is the same approach used by doDelete() and
doRename(), which work reliably. _reloadAfterAction also triggers the
sidebar refresh via window.appState.refreshSidebarTree().
Replaced the fragile event-based sidebar refresh with direct function calls:
- appState.init() now sets window.appState = this
- _reloadAfterAction(), doCreate(), deleteWithUndo() call
window.appState.refreshSidebarTree() directly (no DOM event)
- Sidebar handlers (drop, deleteWorkspacePage, wsCtxAction, newFolder)
call window._wsData._reloadAfterAction() directly (no DOM event)
- Removed event listeners for flowdeck:workspace-changed and
flowdeck:sidebar-refresh from local_workspace.html
This is more reliable than CustomEvent which had timing/scope issues.
deleteWithUndo() (called by ctxMenuDelete) was removing the node from the
local tree but never notifying the sidebar to refresh. Added dispatch of
'flowdeck:sidebar-refresh' event so the sidebar tree updates dynamically
without requiring a full page reload.
- Nouvel événement 'flowdeck:workspace-changed' dispatché depuis les actions
sidebar (deleteWorkspacePage, wsCtxAction rename/delete, newFolderInWorkspace)
- La vue principale local_workspace.html écoute 'flowdeck:workspace-changed'
et appelle _reloadAfterAction() pour rafraîchir son arbre
- _reloadAfterAction() continue à dispatcher 'flowdeck:sidebar-refresh'
pour le rafraîchissement sidebar uniquement (évite la boucle infinie)
- Correction du double confirm dans deleteWorkspacePage (skipConfirm=true
depuis wsCtxAction qui a déjà confirmé)
- New endpoint GET /api/sidebar/workspace-tree returns sidebar tree HTML fragment
- refreshSidebarTree() in appState() fetches and replaces #sidebar-workspace-items
- Custom event 'flowdeck:sidebar-refresh' dispatched after doCreate/doRename/doDelete
- newFolderInWorkspace, deleteWorkspacePage, wsCtxAction('rename') now use
refreshSidebarTree() instead of window.location.reload()
- Sidebar stays in sync without full page refresh
Erreur: block 'page_title' defined twice → TemplateAssertionError
Fix: title_prefix (nouveau bloc) pour <title>, page_title (existant) pour Alpine
10 templates mis à jour avec title_prefix.
Si Alpine échoue à s'initialiser, x-show n'était pas traité
et les modals apparaissaient par défaut. display:none en fallback
garantit qu'ils sont cachés, Alpine les montre via x-show.
- @click.away n'existe pas dans Alpine 3.x (renommé @click.outside)
6 occurrences corrigées dans local_workspace.html
- Ajout Alpine.data('_wsInitData') pour un binding fiable des @click
(le IIFE seul ne garantissait pas une reconnaissance Alpine propre)
1. ctxAddNewTag lit window._ctxMenuData.ctxNewTagColor (pas this.ctxNewTagColor)
→ la couleur sélectionnée dans le menu est maintenant sauvegardée
2. onContextMenu: charge workspaceTags si vide avant de calculer ctxAvailTags
→ les tags existants apparaissent dans le dropdown
3. Settings: CSS modal-overlay/modal-box/.btn-danger ajouté
→ le modal delete n'est plus transparent
4. Rename tag: modal avec input au lieu de prompt()
→ double-clic → modal avec couleur + input + autofocus
5. doFilter: _filterTreeByTag filtre displayTree quand tagFilter est actif
→ le filtrage par tags fonctionne dans toutes les vues
Context menu (_ctxMenuData):
- Remplacé getters par propriétés simples (Alpine ne réagit pas aux getters)
- Sync _wsInitData → _ctxMenuData dans onContextMenu, ctxAddNewTag, ctxRemoveTag
- Les 4 tags existants apparaissent maintenant dans le dropdown
Settings > Tags:
- Double-clic sur un tag → prompt rename (PUT /api/settings/tags/<id>)
- Suppression: modal de confirmation au lieu de confirm() natif
- Affiche le nombre d'items impactés si tag utilisé
_wsInitData (162 keys) causait "Cannot redefine property: $nextTick"
quand réutilisé comme x-data sur .ctx-menu. Alpine ajoute des propriétés
magiques ($nextTick, $el, etc.) et certaines entrent en conflit avec
les propriétés de l'objet massif.
Solution: window._ctxMenuData — objet minimal avec getters/setters
déléguant à _wsInitData. Seulement 20 propriétés, zéro conflit.
+ Restauré les directives Alpine (x-show, x-for, :style) dans le menu
puisque le scope Alpine fonctionne maintenant.
Le .ctx-menu était hors du scope _wsInitData dans le DOM (sibling du wrapper).
Ajouter x-data directement sur l'élément lui donne accès à toutes les variables
Alpine internes (ctxTagExistingOpen, ctxTagAdding, ctxAvailTags, ctxTagColors, etc.)
Les toggles de dropdown utilisent maintenant du DOM natif pour éviter les
problèmes de scope.
Backend (dashboard.py):
- POST /api/local-workspace/items/<id>/tags accepte maintenant 'color'
- INSERT INTO tags (name, color) au lieu de INSERT INTO tags (name)
- Si tag existe déjà, garde sa couleur existante
Frontend (local_workspace.html):
- ctxAddNewTag envoie {name, color} dans le body de la requête
(avant: seul {name} était envoyé, la couleur était ignorée)