fix: bouton delete cassé + preview PDF cliquable + icônes distinctes FlowDeck vs upload
FlowDeck CI / test (push) Failing after 8s
FlowDeck CI / docker (push) Has been skipped

1. Delete: ev.target pouvait être un noeud texte (emoji 🗑)
   → Text.closest() n'existe pas → handleTreeAction échouait.
   Fix: fallback sur ev.target.parentElement.closest().

2. Preview PDF: ajout lien 'Open viewer →' vers /pages/{id}
   (le viewer standalone rend le PDF dans une iframe).

3. Icônes distinctes:
   - Pages FlowDeck (content_format='blocks'/'markdown') → 📝
   - Fichiers uploadés → icône basée sur l'extension
   - _fileIcon() accepte contentFormat, propage depuis l'API tree
   - Appliqué dans sidebar (board.py) + page workspace (x-for + renderChildren)
This commit is contained in:
2026-07-11 14:00:57 -04:00
parent fc6e30aebb
commit 8497a36da9
2 changed files with 16 additions and 10 deletions
+8 -5
View File
@@ -140,9 +140,12 @@ def _build_page_tree(conn, parent_id: int | None, ws_key: str, depth: int = 0, m
return items return items
def _file_icon(name: str) -> str: def _file_icon(name: str, content_format: str = "") -> str:
"""Map file extension to emoji icon.""" """Map file extension to emoji icon."""
import re import re
# FlowDeck internal pages (no extension)
if content_format and content_format != 'file':
return '📝'
n = name.lower() n = name.lower()
if re.search(r'\.(png|jpe?g|gif|webp|svg|bmp|ico)$', n): return '🖼️' if re.search(r'\.(png|jpe?g|gif|webp|svg|bmp|ico)$', n): return '🖼️'
if n.endswith('.pdf'): return '📕' if n.endswith('.pdf'): return '📕'
@@ -169,7 +172,7 @@ def _load_workspace_pages(ws_cookie: str) -> list:
ws_id = int(ws_cookie) ws_id = int(ws_cookie)
with get_conn() as conn: with get_conn() as conn:
rows = conn.execute( rows = conn.execute(
"SELECT id, title, parent_section FROM pages WHERE workspace_id=? AND parent_id IS NULL ORDER BY created_at DESC", "SELECT id, title, parent_section, content_format FROM pages WHERE workspace_id=? AND parent_id IS NULL ORDER BY created_at DESC",
(ws_id,), (ws_id,),
).fetchall() ).fetchall()
items = [] items = []
@@ -179,7 +182,7 @@ def _load_workspace_pages(ws_cookie: str) -> list:
items.append({ items.append({
"db_id": r["id"], "name": title, "db_id": r["id"], "name": title,
"id": f"page/{r['id']}", "id": f"page/{r['id']}",
"icon": "📁" if is_folder else _file_icon(title), "icon": "📁" if is_folder else _file_icon(title, r["content_format"]),
"is_folder": is_folder, "is_folder": is_folder,
"children": _load_children(r["id"]), "children": _load_children(r["id"]),
}) })
@@ -192,7 +195,7 @@ def _load_children(parent_id: int) -> list:
"""Recursively load children of a page.""" """Recursively load children of a page."""
with get_conn() as conn: with get_conn() as conn:
rows = conn.execute( rows = conn.execute(
"SELECT id, title, parent_section FROM pages WHERE parent_id=? ORDER BY created_at", "SELECT id, title, parent_section, content_format FROM pages WHERE parent_id=? ORDER BY created_at",
(parent_id,), (parent_id,),
).fetchall() ).fetchall()
children = [] children = []
@@ -202,7 +205,7 @@ def _load_children(parent_id: int) -> list:
children.append({ children.append({
"db_id": r["id"], "name": title, "db_id": r["id"], "name": title,
"id": f"page/{r['id']}", "id": f"page/{r['id']}",
"icon": "📁" if is_folder else _file_icon(title), "icon": "📁" if is_folder else _file_icon(title, r["content_format"]),
"is_folder": is_folder, "is_folder": is_folder,
"children": _load_children(r["id"]), "children": _load_children(r["id"]),
}) })
+8 -5
View File
@@ -207,7 +207,7 @@
<span style="width:16px;flex-shrink:0;"></span> <span style="width:16px;flex-shrink:0;"></span>
</template> </template>
<span class="icon" x-text="_fileIcon(node.name, node.is_folder)"></span> <span class="icon" x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span>
<template x-if="node.is_folder"> <template x-if="node.is_folder">
<span class="name folder" @click.stop="navigateToFolder(node.id)" x-text="node.name"></span> <span class="name folder" @click.stop="navigateToFolder(node.id)" x-text="node.name"></span>
@@ -376,7 +376,7 @@ document.addEventListener('alpine:init', function() {
for (var i=0; i<children.length; i++) { for (var i=0; i<children.length; i++) {
var c = children[i]; var c = children[i];
var hasKids = c.children && c.children.length > 0; var hasKids = c.children && c.children.length > 0;
var icon = this._fileIcon(c.name, c.is_folder); var icon = this._fileIcon(c.name, c.is_folder, c.content_format);
var escName = this.esc(c.name); var escName = this.esc(c.name);
html += '<li><div class="ws-tree-item" style="padding-left:'+(depth*20)+'px" draggable="true"' html += '<li><div class="ws-tree-item" style="padding-left:'+(depth*20)+'px" draggable="true"'
+ ' data-folder-id="'+(c.is_folder?c.id:'')+'"' + ' data-folder-id="'+(c.is_folder?c.id:'')+'"'
@@ -421,7 +421,8 @@ document.addEventListener('alpine:init', function() {
// ── Event delegation for renderChildren action buttons ── // ── Event delegation for renderChildren action buttons ──
handleTreeAction(ev) { handleTreeAction(ev) {
var btn = ev.target.closest('[data-ws-action]'); var btn = (ev.target.closest && ev.target.closest('[data-ws-action]'))
|| (ev.target.parentElement && ev.target.parentElement.closest('[data-ws-action]'));
if (!btn) return; if (!btn) return;
ev.stopPropagation(); ev.stopPropagation();
var action = btn.getAttribute('data-ws-action'); var action = btn.getAttribute('data-ws-action');
@@ -516,8 +517,10 @@ document.addEventListener('alpine:init', function() {
}, },
// ── File type icon ── // ── File type icon ──
_fileIcon(name, isFolder) { _fileIcon(name, isFolder, contentFormat) {
if (isFolder) return '📁'; if (isFolder) return '📁';
// FlowDeck internal pages (no extension, blocks/markdown format)
if (contentFormat && contentFormat !== 'file') return '📝';
var n = (name || '').toLowerCase(); var n = (name || '').toLowerCase();
if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(n)) return '🖼️'; if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(n)) return '🖼️';
if (/\.pdf$/.test(n)) return '📕'; if (/\.pdf$/.test(n)) return '📕';
@@ -598,7 +601,7 @@ document.addEventListener('alpine:init', function() {
if (isImg) { if (isImg) {
this.previewContent = '<img src="'+url+'" style="max-width:380px;max-height:180px;display:block;">'; this.previewContent = '<img src="'+url+'" style="max-width:380px;max-height:180px;display:block;">';
} else if ((node.name||'').toLowerCase().endsWith('.pdf')) { } else if ((node.name||'').toLowerCase().endsWith('.pdf')) {
this.previewContent = '<div style="text-align:center;padding:16px;"><span style="font-size:48px;">📕</span><br><span style="color:var(--text-secondary);font-size:12px;">PDF Document</span></div>'; this.previewContent = '<div style="text-align:center;padding:16px;"><span style="font-size:48px;">📕</span><br><span style="color:var(--text-secondary);font-size:12px;">PDF Document</span><br><a href="/pages/'+node.id+'" style="color:var(--accent);font-size:11px;margin-top:4px;display:inline-block;">Open viewer →</a></div>';
} else { } else {
this.previewContent = 'Loading...'; this.previewContent = 'Loading...';
fetch(url) fetch(url)