fix: bouton delete cassé + preview PDF cliquable + icônes distinctes FlowDeck vs upload
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:
@@ -140,9 +140,12 @@ def _build_page_tree(conn, parent_id: int | None, ws_key: str, depth: int = 0, m
|
||||
return items
|
||||
|
||||
|
||||
def _file_icon(name: str) -> str:
|
||||
def _file_icon(name: str, content_format: str = "") -> str:
|
||||
"""Map file extension to emoji icon."""
|
||||
import re
|
||||
# FlowDeck internal pages (no extension)
|
||||
if content_format and content_format != 'file':
|
||||
return '📝'
|
||||
n = name.lower()
|
||||
if re.search(r'\.(png|jpe?g|gif|webp|svg|bmp|ico)$', n): return '🖼️'
|
||||
if n.endswith('.pdf'): return '📕'
|
||||
@@ -169,7 +172,7 @@ def _load_workspace_pages(ws_cookie: str) -> list:
|
||||
ws_id = int(ws_cookie)
|
||||
with get_conn() as conn:
|
||||
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,),
|
||||
).fetchall()
|
||||
items = []
|
||||
@@ -179,7 +182,7 @@ def _load_workspace_pages(ws_cookie: str) -> list:
|
||||
items.append({
|
||||
"db_id": r["id"], "name": title,
|
||||
"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,
|
||||
"children": _load_children(r["id"]),
|
||||
})
|
||||
@@ -192,7 +195,7 @@ def _load_children(parent_id: int) -> list:
|
||||
"""Recursively load children of a page."""
|
||||
with get_conn() as conn:
|
||||
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,),
|
||||
).fetchall()
|
||||
children = []
|
||||
@@ -202,7 +205,7 @@ def _load_children(parent_id: int) -> list:
|
||||
children.append({
|
||||
"db_id": r["id"], "name": title,
|
||||
"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,
|
||||
"children": _load_children(r["id"]),
|
||||
})
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
<span style="width:16px;flex-shrink:0;"></span>
|
||||
</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">
|
||||
<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++) {
|
||||
var c = children[i];
|
||||
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);
|
||||
html += '<li><div class="ws-tree-item" style="padding-left:'+(depth*20)+'px" draggable="true"'
|
||||
+ ' data-folder-id="'+(c.is_folder?c.id:'')+'"'
|
||||
@@ -421,7 +421,8 @@ document.addEventListener('alpine:init', function() {
|
||||
|
||||
// ── Event delegation for renderChildren action buttons ──
|
||||
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;
|
||||
ev.stopPropagation();
|
||||
var action = btn.getAttribute('data-ws-action');
|
||||
@@ -516,8 +517,10 @@ document.addEventListener('alpine:init', function() {
|
||||
},
|
||||
|
||||
// ── File type icon ──
|
||||
_fileIcon(name, isFolder) {
|
||||
_fileIcon(name, isFolder, contentFormat) {
|
||||
if (isFolder) return '📁';
|
||||
// FlowDeck internal pages (no extension, blocks/markdown format)
|
||||
if (contentFormat && contentFormat !== 'file') return '📝';
|
||||
var n = (name || '').toLowerCase();
|
||||
if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(n)) return '🖼️';
|
||||
if (/\.pdf$/.test(n)) return '📕';
|
||||
@@ -598,7 +601,7 @@ document.addEventListener('alpine:init', function() {
|
||||
if (isImg) {
|
||||
this.previewContent = '<img src="'+url+'" style="max-width:380px;max-height:180px;display:block;">';
|
||||
} 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 {
|
||||
this.previewContent = 'Loading...';
|
||||
fetch(url)
|
||||
|
||||
Reference in New Issue
Block a user