feat: outline SVG icons + version + workspaces in user menu
FlowDeck CI / test (push) Failing after 12s
FlowDeck CI / docker (push) Has been skipped

- _icons.html: 25 outline SVG icons (Feather/Lucide style, stroke=currentColor)
- base.html: toutes les icônes emoji remplacées par {{ fd_icon() }}
  - Sidebar sections, empty states, user menu, footer links, context menu
  - JS getSvgIcon() helper pour rendu dynamique
- User menu: ajout version (FlowDeck v4.0.3) + liste workspaces locaux
  - Entre Switch workspace et Log out, avec ✓ sur le workspace actif
- dashboard.py: _sidebar_data enrichi (app_version, local_workspaces)
- CSS: .page-icon-svg, .um-version, .um-section-label, .um-item svg
- Dark + light theme compatible (stroke:currentColor)
- 143 tests passent
This commit is contained in:
2026-07-20 20:18:50 -04:00
parent 39b485622d
commit 5372f4f22f
4 changed files with 158 additions and 32 deletions
+60 -32
View File
@@ -140,6 +140,8 @@
{% endif %}
{% endmacro %}
{% from '_icons.html' import fd_icon %}
<!-- ═══════════ SIDEBAR Notion ═══════════ -->
<aside class="sidebar"
:class="{ collapsed: sidebarCollapsed, 'mobile-open': mobileSidebarOpen }"
@@ -172,10 +174,24 @@
</div>
</div>
<div class="um-sep"></div>
<a href="/settings" class="um-item" @click="workspaceMenuOpen=false">⚙️ Settings</a>
<a href="/workspaces" class="um-item" @click="workspaceMenuOpen=false">🔄 Switch workspace</a>
<a href="/settings" class="um-item" @click="workspaceMenuOpen=false">{{ fd_icon("settings", 16) }} Settings</a>
<a href="/workspaces" class="um-item" @click="workspaceMenuOpen=false">{{ fd_icon("refresh", 16) }} Switch workspace</a>
{% if local_workspaces %}
<div class="um-sep"></div>
<a href="/auth/logout" class="um-item um-danger" @click="workspaceMenuOpen=false">🚪 Log out</a>
<div class="um-section-label">Workspaces</div>
{% for ws in local_workspaces %}
<a href="/local-workspace?ws={{ ws.id }}" class="um-item" @click="workspaceMenuOpen=false"
style="padding-left:24px;font-size:13px;">
<span class="um-ws-dot" style="width:8px;height:8px;border-radius:50%;background:var(--accent);display:inline-block;margin-right:8px;flex-shrink:0;"></span>{{ ws.name }}
{% if active_ws_name == ws.name %}
<span style="margin-left:auto;color:var(--accent);">✓</span>
{% endif %}
</a>
{% endfor %}
{% endif %}
<div class="um-sep"></div>
<div class="um-version">FlowDeck v{{ app_version }}</div>
<a href="/auth/logout" class="um-item um-danger" @click="workspaceMenuOpen=false">{{ fd_icon("log-out", 16) }} Log out</a>
</div>
</div>
@@ -203,15 +219,15 @@
<div class="sidebar-section-title">
<span class="chevron" :class="{ open: sectionsOpen.workspace }" x-text="sectionsOpen.workspace ? '▼' : '▶'"></span>
{% if has_active_workspace %}
<span>{% if gitea_workspace %}📁 {{ active_ws_name }}{% elif workspace_key and '/' in workspace_key %}🔗{% else %}📁{% endif %} {{ active_ws_name if not gitea_workspace else '' }}</span>
<span><span style="vertical-align:sub;margin-right:4px;">{{ fd_icon("folder",16) }}</span>{{ active_ws_name if active_ws_name else "" }}{% if gitea_workspace or (workspace_key and "/" in workspace_key) %} {{ fd_icon("link",12) }}{% endif %}</span>
{% else %}
<span>📁 No workspace open</span>
<span><span style="vertical-align:sub;margin-right:4px;">{{ fd_icon("folder",16) }}</span>No workspace open</span>
{% endif %}
</div>
{% if has_active_workspace %}
<div class="sidebar-section-actions">
<button class="section-action-btn" title="New Page" @click.stop="window.FlowDeck.createPage()">📄</button>
<button class="section-action-btn" title="New Folder" @click.stop="window.FlowDeck.showCreateFolderModal()">📁</button>
<button class="section-action-btn" title="New Page" @click.stop="window.FlowDeck.createPage()">{{ fd_icon("file",16) }}</button>
<button class="section-action-btn" title="New Folder" @click.stop="window.FlowDeck.showCreateFolderModal()">{{ fd_icon("folder",16) }}</button>
</div>
{% endif %}
</div>
@@ -222,13 +238,13 @@
{% if workspace_pages %}
{{ render_workspace_tree(workspace_pages) }}
{% else %}
<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No pages yet</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("file",16) }}</span><span class="page-name text-dim">No pages yet</span></li>
{% endif %}
{% else %}
<li class="sidebar-item empty-hint"><span class="page-icon">📁</span><span class="page-name text-dim">Open a workspace to see your files</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("folder",16) }}</span><span class="page-name text-dim">Open a workspace to see your files</span></li>
{% endif %}
<li class="sidebar-item" style="margin-top:4px;border-top:1px solid var(--border);padding-top:8px;" onclick="window.location='/workspaces'">
<span class="page-icon">⚙</span><span class="page-name text-dim">Manage Workspaces</span>
<span class="page-icon page-icon-svg">{{ fd_icon("settings",16) }}</span><span class="page-name text-dim">Manage Workspaces</span>
</li>
</ul>
</div>
@@ -243,7 +259,7 @@
<span>🔗 Repository (Gitea)</span>
</div>
<div class="sidebar-section-actions">
<button class="section-action-btn" title="Refresh" @click.stop="if(window._gwData)window._gwData.refreshTree()">🔄</button>
<button class="section-action-btn" title="Refresh" @click.stop="if(window._gwData)window._gwData.refreshTree()">{{ fd_icon("refresh",16) }}</button>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.gitea" x-transition>
@@ -268,7 +284,7 @@
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.meetings" x-transition>
<ul class="sidebar-items"><li class="sidebar-item empty-hint"><span class="page-icon">📅</span><span class="page-name text-dim">No meetings</span></li></ul>
<ul class="sidebar-items"><li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("calendar",16) }}</span><span class="page-name text-dim">No meetings</span></li></ul>
</div>
</div>
@@ -280,7 +296,7 @@
<span>Recents</span>
</div>
<div class="sidebar-section-actions">
<a href="/library?tab=Recents" class="library-link-btn" title="View in Library" @click.stop>📚</a>
<a href="/library?tab=Recents" class="library-link-btn" title="View in Library" @click.stop>{{ fd_icon("book",14) }}</a>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.recents" x-transition>
@@ -289,7 +305,7 @@
{{ render_tree_item(page) }}
{% endfor %}
{% if not recent_pages %}
<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No recent pages</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("clock",16) }}</span><span class="page-name text-dim">No recent pages</span></li>
{% endif %}
</ul>
</div>
@@ -303,7 +319,7 @@
<span>Favorites</span>
</div>
<div class="sidebar-section-actions">
<a href="/library?tab=Favorites" class="library-link-btn" title="View in Library" @click.stop>📚</a>
<a href="/library?tab=Favorites" class="library-link-btn" title="View in Library" @click.stop>{{ fd_icon("book",14) }}</a>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.favorites" x-transition>
@@ -312,7 +328,7 @@
{{ render_tree_item(page) }}
{% endfor %}
{% if not favorite_pages %}
<li class="sidebar-item empty-hint"><span class="page-icon">⭐</span><span class="page-name text-dim">No favorites yet</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("star",16) }}</span><span class="page-name text-dim">No favorites yet</span></li>
{% endif %}
</ul>
</div>
@@ -324,7 +340,7 @@
<div class="sidebar-section-title"><span>Agents</span></div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.agents" x-transition>
<ul class="sidebar-items"><li class="sidebar-item empty-hint"><span class="page-icon">🤖</span><span class="page-name text-dim">No agents configured</span></li></ul>
<ul class="sidebar-items"><li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("bot",16) }}</span><span class="page-name text-dim">No agents configured</span></li></ul>
</div>
</div>
@@ -336,7 +352,7 @@
<span>Shared</span>
</div>
<div class="sidebar-section-actions">
<a href="/library?tab=Shared" class="library-link-btn" title="View in Library" @click.stop>📚</a>
<a href="/library?tab=Shared" class="library-link-btn" title="View in Library" @click.stop>{{ fd_icon("book",14) }}</a>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.shared" x-transition>
@@ -345,7 +361,7 @@
{{ render_tree_item(page) }}
{% endfor %}
{% if not shared_pages %}
<li class="sidebar-item empty-hint"><span class="page-icon">👥</span><span class="page-name text-dim">No shared pages</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("users",16) }}</span><span class="page-name text-dim">No shared pages</span></li>
{% endif %}
</ul>
</div>
@@ -359,7 +375,7 @@
<span>Published</span>
</div>
<div class="sidebar-section-actions">
<a href="/library?tab=Published" class="library-link-btn" title="View in Library" @click.stop>📚</a>
<a href="/library?tab=Published" class="library-link-btn" title="View in Library" @click.stop>{{ fd_icon("book",14) }}</a>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.published" x-transition>
@@ -368,7 +384,7 @@
{{ render_tree_item(page) }}
{% endfor %}
{% if not published_pages %}
<li class="sidebar-item empty-hint"><span class="page-icon">🌐</span><span class="page-name text-dim">No published pages</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("globe",16) }}</span><span class="page-name text-dim">No published pages</span></li>
{% endif %}
</ul>
</div>
@@ -383,7 +399,7 @@
<span>Private</span>
</div>
<div class="sidebar-section-actions">
<a href="/library?tab=Private" class="library-link-btn" title="View in Library" @click.stop>📚</a>
<a href="/library?tab=Private" class="library-link-btn" title="View in Library" @click.stop>{{ fd_icon("book",14) }}</a>
</div>
</div>
<div class="sidebar-section-items" x-show="sectionsOpen.private" x-transition>
@@ -392,7 +408,7 @@
{{ render_tree_item(page) }}
{% endfor %}
{% if not private_pages %}
<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No pages yet</span></li>
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("file",16) }}</span><span class="page-name text-dim">No pages yet</span></li>
{% endif %}
</ul>
</div>
@@ -403,16 +419,16 @@
<!-- Bottom links (always visible, no section wrapper) -->
<div class="sidebar-static-links" style="padding: 4px 12px;">
<a href="/library" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<span class="page-icon">📚</span><span class="page-name" style="font-size:14px;">Library</span>
<span class="page-icon page-icon-svg">{{ fd_icon("book",16) }}</span><span class="page-name" style="font-size:14px;display:flex;align-items:center;gap:4px;">Library</span>
</a>
<a href="/my-tasks" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<span class="page-icon">✅</span><span class="page-name" style="font-size:14px;">My Tasks</span>
<span class="page-icon page-icon-svg">{{ fd_icon("check-square",16) }}</span><span class="page-name" style="font-size:14px;display:flex;align-items:center;gap:4px;">My Tasks</span>
</a>
<a href="/trash" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<span class="page-icon">🗑️</span><span class="page-name" style="font-size:14px;">Trash</span>
<span class="page-icon page-icon-svg">{{ fd_icon("trash",16) }}</span><span class="page-name" style="font-size:14px;display:flex;align-items:center;gap:4px;">Trash</span>
</a>
<a href="/help" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<span class="page-icon">❓</span><span class="page-name" style="font-size:14px;">Help</span>
<span class="page-icon page-icon-svg">{{ fd_icon("help-circle",16) }}</span><span class="page-name" style="font-size:14px;display:flex;align-items:center;gap:4px;">Help</span>
</a>
</div>
@@ -420,7 +436,7 @@
<div class="sidebar-footer">
<div style="display:flex; gap:6px;">
<button class="sidebar-footer-btn" style="flex:1; justify-content:center; background:var(--bg-hover); border-radius:20px;">
<span style="font-size:14px;">💬</span> New chat
<span style="margin-right:4px;">{{ fd_icon("message-square",14) }}</span>New chat
<span class="shortcut">Ctrl+O</span>
</button>
{% if has_active_workspace %}
@@ -581,6 +597,18 @@
});
})();
// ── Global SVG icon helper (used by JS-rendered elements) ─────
function getSvgIcon(name, size) {
size = size || 16;
var svgs = {
folder: '<svg width="'+size+'" height="'+size+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>',
file: '<svg width="'+size+'" height="'+size+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
star: '<svg width="'+size+'" height="'+size+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
trash: '<svg width="'+size+'" height="'+size+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>'
};
return svgs[name] || '';
}
// ── Global FlowDeck namespace ────────────────────────────────
// All "New Page" and "New Folder" buttons across the app call
// these functions for consistent behaviour.
@@ -885,10 +913,10 @@
const favList = document.querySelector('.sidebar-items[data-section="favorites"]');
if (!favList) return;
if (items.length === 0) {
favList.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon">⭐</span><span class="page-name text-dim">No favorites yet</span></li>';
favList.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">'+getSvgIcon('star',16)+'</span><span class="page-name text-dim">No favorites yet</span></li>';
} else {
favList.innerHTML = items.map(function(item) {
const icon = item.icon || '📄';
var icon = item.icon || '<span class="page-icon-svg">'+getSvgIcon('file',14)+'</span>';
const name = (item.title || 'Untitled');
const safeName = name.replace(/'/g, "\\'").replace(/"/g, '&quot;');
const url = item.url || ('/pages/' + item.id);
@@ -1154,13 +1182,13 @@
}
container.innerHTML = '';
if (!this.giteaTree.length) {
container.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon">📄</span><span class="page-name text-dim">No files yet</span></li>';
container.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">'+getSvgIcon('file',16)+'</span><span class="page-name text-dim">No files yet</span></li>';
return;
}
this.giteaTree.forEach(function(item) {
var li = document.createElement('li');
li.className = 'sidebar-item';
var icon = item.type === 'dir' ? '📁' : '📄';
var icon = item.type === 'dir' ? '<span class="page-icon-svg">'+getSvgIcon('folder',14)+'</span>' : '<span class="page-icon-svg">'+getSvgIcon('file',14)+'</span>';
li.innerHTML = '<span class="page-icon">' + icon + '</span><span class="page-name">' + item.name + '</span>';
container.appendChild(li);
});