fix: WORKSPSACES section + remaining emoji icons replaced
- base.html: séparateur entre version et Log out, _local_workspaces_for_user - board.py: _local_workspaces_for_user helper, app_version, fix emoji icons - dashboard.py: _local_workspaces_for_user helper - library.html: 📚📁📄🕒⭐👥🌐🔒📦 → SVG + icon map - workspaces.html: 📁🔍🗑📝 → SVG - local_workspace.html: page_icon, empty states, icon functions → SVG - settings.html: 👤🔔 → SVG nav icons - _icons.html: +'bell' icon - CSS: .nav-icon-inline, .empty-state-icon - 143 tests passent
This commit is contained in:
+21
-3
@@ -216,6 +216,22 @@ def _load_children(parent_id: int) -> list:
|
||||
return children
|
||||
|
||||
|
||||
def _local_workspaces_for_user(user: dict | None, workspace_id: int = 0) -> list[dict]:
|
||||
"""Return list of local workspaces for a user."""
|
||||
if not user:
|
||||
return []
|
||||
try:
|
||||
from app.db import get_conn
|
||||
with get_conn() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT id, name FROM workspaces WHERE owner_id = ? ORDER BY name",
|
||||
(user["id"],)
|
||||
).fetchall()
|
||||
return [{"id": r["id"], "name": r["name"]} for r in rows]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
ws_name = user.get("login", "Bruno") if user else "Bruno"
|
||||
@@ -280,7 +296,7 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
active = first
|
||||
recent.append({
|
||||
"id": f"{owner}/{repo}/{view}",
|
||||
"name": label, "icon": "📁" if first else "·",
|
||||
"name": label, "icon": "folder" if first else "",
|
||||
"url": f"/board/{owner}/{repo}?view={view}",
|
||||
"active": active, "indent": indent,
|
||||
"depth": indent, "has_children": False, "children": [],
|
||||
@@ -380,7 +396,9 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict:
|
||||
"auth_method": auth_method,
|
||||
"gitea_linked": gitea_linked,
|
||||
"github_linked": github_linked,
|
||||
"has_active_workspace": has_active_workspace}
|
||||
"has_active_workspace": has_active_workspace,
|
||||
"app_version": "4.0.3",
|
||||
"local_workspaces": _local_workspaces_for_user(user)}
|
||||
|
||||
|
||||
def _extract_ai_keywords(owner: str, repo: str, labels: list[dict], body: str = ""):
|
||||
@@ -510,7 +528,7 @@ async def library_page(request: Request, owner: str = Query(default=""), repo: s
|
||||
"icon": "📄",
|
||||
"url": f"/pages/{page['id']}",
|
||||
"created_by": "You",
|
||||
"source": page.get("workspace") or "🔒 Private",
|
||||
"source": page.get("workspace") or "Private",
|
||||
"last_edited": page.get("updated_at", "now"),
|
||||
"last_visited": page.get("updated_at", "now"),
|
||||
})
|
||||
|
||||
+16
-13
@@ -31,6 +31,21 @@ def _get_user_or_redirect(request: Request):
|
||||
return user
|
||||
|
||||
|
||||
def _local_workspaces_for_user(user: dict | None, workspace_id: int = 0) -> list[dict]:
|
||||
"""Return list of local workspaces for a user."""
|
||||
if not user:
|
||||
return []
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT id, name FROM workspaces WHERE owner_id = ? ORDER BY name",
|
||||
(user["id"],)
|
||||
).fetchall()
|
||||
return [{"id": r["id"], "name": r["name"]} for r in rows]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool = True) -> dict:
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
ws = user.get("login", "Bruno") if user else "Bruno"
|
||||
@@ -176,19 +191,7 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool =
|
||||
"app_version": "4.0.3",
|
||||
}
|
||||
|
||||
# List local workspaces for user menu
|
||||
local_workspaces = []
|
||||
if user:
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
ws_rows = conn.execute(
|
||||
"SELECT id, name FROM workspaces WHERE owner_id = ? ORDER BY name",
|
||||
(user["id"],)
|
||||
).fetchall()
|
||||
local_workspaces = [{"id": r["id"], "name": r["name"]} for r in ws_rows]
|
||||
except Exception:
|
||||
pass
|
||||
sidebar["local_workspaces"] = local_workspaces
|
||||
sidebar["local_workspaces"] = _local_workspaces_for_user(user)
|
||||
|
||||
return sidebar
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
@mouseleave="dragCloseTimer()">
|
||||
<template x-for="h in crumb.hidden" :key="h.id">
|
||||
<div class="fd-nav-item" @click.stop="go(h.url); closeAll()">
|
||||
<span class="fd-nav-ico" x-text="h.icon || '📄'"></span>
|
||||
<span class="fd-nav-ico" :title="h.icon || 'file'"></span>
|
||||
<span class="fd-nav-label" x-text="h.label"></span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -110,7 +110,7 @@
|
||||
<div class="fd-nav-item"
|
||||
@mouseenter="openSub(item)"
|
||||
@click.stop="go(item.url); closeAll()">
|
||||
<span class="fd-nav-ico" x-text="item.icon || '📄'"></span>
|
||||
<span class="fd-nav-ico" :title="item.icon || 'file'"></span>
|
||||
<span class="fd-nav-label" x-text="item.name"></span>
|
||||
<span class="fd-nav-arrow" x-show="item.has_children">›</span>
|
||||
<div class="fd-nav-menu fd-nav-submenu" x-show="subOpenId === item.id" x-cloak
|
||||
@@ -118,7 +118,7 @@
|
||||
@mouseleave="dragCloseTimer()">
|
||||
<template x-for="s in subItems" :key="s.id">
|
||||
<div class="fd-nav-item" @click.stop="go(s.url); closeAll()">
|
||||
<span class="fd-nav-ico" x-text="s.icon || '📄'"></span>
|
||||
<span class="fd-nav-ico" :title="s.icon || 'file'"></span>
|
||||
<span class="fd-nav-label" x-text="s.name"></span>
|
||||
<span class="fd-nav-arrow" x-show="s.has_children">›</span>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
<svg width="{{s}}" height="{{s}}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
||||
{%- elif name == 'plus' -%}
|
||||
<svg width="{{s}}" height="{{s}}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||
{%- elif name == 'bell' -%}
|
||||
<svg width="{{s}}" height="{{s}}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
{%- elif name == 'chevron-down' -%}
|
||||
<svg width="{{s}}" height="{{s}}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
{%- elif name == 'inbox' -%}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
{% else %}
|
||||
<span class="tree-toggle-placeholder"></span>
|
||||
{% endif %}
|
||||
<span class="page-icon">{{ page.icon }}</span>
|
||||
<span class="page-icon">{% if page.icon in ("folder","file","star","link","trash","book") %}{{ fd_icon(page.icon,14) }}{% else %}{{ page.icon }}{% endif %}</span>
|
||||
<span class="page-name">{{ page.name }}</span>
|
||||
<span class="item-actions">
|
||||
<button class="item-action-btn" @click.stop="newSubPage('{{ page.id }}')">+</button>
|
||||
@@ -191,6 +191,7 @@
|
||||
{% endif %}
|
||||
<div class="um-sep"></div>
|
||||
<div class="um-version">FlowDeck v{{ app_version }}</div>
|
||||
<div class="um-sep"></div>
|
||||
<a href="/auth/logout" class="um-item um-danger" @click="workspaceMenuOpen=false">{{ fd_icon("log-out", 16) }} Log out</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block page_icon %}📚{% endblock %}
|
||||
{% block page_icon %}{{ fd_icon("book",18) }}{% endblock %}
|
||||
{% block page_title %}Library{% endblock %}
|
||||
{% block title_prefix %}Library{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
{% set page_icon = "📚" %}
|
||||
{% set page_icon = "book" %}
|
||||
{% set page_title = "Library" %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
@@ -305,7 +305,7 @@
|
||||
<div class="lib-loading" id="lib-loading" x-show="loading">
|
||||
<p x-show="!noWorkspace">Loading…</p>
|
||||
<div x-show="noWorkspace" style="padding:40px;text-align:center;">
|
||||
<div style="font-size:48px;margin-bottom:16px;">📁</div>
|
||||
<div style="margin-bottom:16px;">{{ fd_icon("folder",32) }}</div>
|
||||
<h3 style="font-size:18px;margin:0 0 8px;">No workspace open</h3>
|
||||
<p style="color:var(--text-dim);font-size:14px;margin:0 0 20px;">Open a workspace to browse and manage your library items.</p>
|
||||
<a href="/workspaces" class="btn btn-primary" style="display:inline-block;text-decoration:none;padding:8px 20px;">Go to Workspaces</a>
|
||||
@@ -449,7 +449,7 @@
|
||||
<div x-show="peekLoading" class="peek-loading">Loading…</div>
|
||||
<div x-show="!peekLoading && peekContent" x-html="peekContent"></div>
|
||||
<div x-show="!peekLoading && !peekContent" class="peek-empty">
|
||||
<div style="font-size:48px;margin-bottom:8px;">📄</div>
|
||||
<div style="margin-bottom:8px;">{{ fd_icon("file",32) }}</div>
|
||||
<div style="font-size:14px;color:var(--text-secondary);">No preview available</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -782,13 +782,13 @@ function libraryPage() {
|
||||
},
|
||||
|
||||
// ── Empty states ──
|
||||
emptyIcon: '📄',
|
||||
emptyIcon: 'file',
|
||||
emptyTitle: 'No items',
|
||||
emptyText: '',
|
||||
|
||||
_recomputeEmpty() {
|
||||
var icons = {recents:'🕒',favorites:'⭐',shared:'👥',published:'🌐',private:'🔒',workspace:'📁',repository:'📦'};
|
||||
this.emptyIcon = icons[this.tab] || '📄';
|
||||
var icons = {recents:'clock',favorites:'star',shared:'users',published:'globe',private:'lock',workspace:'folder',repository:'folder'};
|
||||
this.emptyIcon = icons[this.tab] || 'file';
|
||||
var titles = {recents:'No recent pages',favorites:'No favorites',shared:'No shared pages',
|
||||
published:'No published pages',private:'No private pages',workspace:'No workspace pages',
|
||||
repository:'Empty repository'};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% extends "base.html" %}
|
||||
{% block page_title %}{{ workspace_name }}{% endblock %}
|
||||
{% block title_prefix %}{{ workspace_name }}{% endblock %}
|
||||
{% block page_icon %}📁{% endblock %}
|
||||
{% block page_icon %}{{ fd_icon("folder",18) }}{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
{% set page_icon = "📁" %}
|
||||
{% set page_icon = "folder" %}
|
||||
{% set page_title = workspace_name %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
@@ -377,7 +377,7 @@ window._wsInitData = (function() {
|
||||
sortBy: '',
|
||||
viewMode: localStorage.getItem('fd_default_view') || 'tree',
|
||||
viewMenuOpen: false,
|
||||
viewIcons: {tree:'🌳',list:'📋',details:'📊',title:'🏷️',content:'📝'},
|
||||
viewIcons: {tree:'',list:'',details:'',title:'',content:''},
|
||||
viewLabels: {tree:'Tree',list:'List',details:'Details',title:'Cards',content:'Content'},
|
||||
|
||||
// Context menu
|
||||
@@ -677,7 +677,7 @@ window._wsInitData = (function() {
|
||||
async _loadPreviewContent(node) {
|
||||
if (node.is_folder) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:40px;color:var(--text-secondary)">'
|
||||
+ '<span style="font-size:48px;display:block;margin-bottom:12px">📁</span>'
|
||||
+ '<span class="empty-state-icon">'+getSvgIcon('folder',32)+'</span>'
|
||||
+ '<div style="font-size:14px">' + (node.child_count||0) + ' item(s)</div>'
|
||||
+ '<a href="/local-workspace?folder=' + node.id + '" class="btn btn-secondary" style="display:inline-block;text-decoration:none;margin-top:16px">Open folder</a>'
|
||||
+ '</div>';
|
||||
@@ -719,7 +719,7 @@ window._wsInitData = (function() {
|
||||
}
|
||||
} catch(e) {}
|
||||
preview = '<div style="text-align:center;padding:20px;color:var(--text-secondary)">'
|
||||
+ '<span style="font-size:48px;display:block;margin-bottom:8px">📄</span>'
|
||||
+ '<span class="empty-state-icon">'+getSvgIcon('file',32)+'</span>'
|
||||
+ (node.size_display || '') + ' — open to view</div>';
|
||||
} else if (format === 'blocks') {
|
||||
try {
|
||||
@@ -1475,23 +1475,23 @@ window._wsInitData = (function() {
|
||||
|
||||
// ── File type icon ──
|
||||
_fileIcon(name, isFolder, contentFormat) {
|
||||
if (isFolder) return '📁';
|
||||
if (isFolder) return '<span class="page-icon-svg">'+getSvgIcon('folder',14)+'</span>';
|
||||
// FlowDeck internal pages (no extension, blocks/markdown format)
|
||||
if (contentFormat && contentFormat !== 'file') return '📝';
|
||||
if (contentFormat && contentFormat !== 'file') return '<span class="page-icon-svg">'+getSvgIcon('edit',14)+'</span>';
|
||||
var n = (name || '').toLowerCase();
|
||||
if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(n)) return '🖼️';
|
||||
if (/\.pdf$/.test(n)) return '📕';
|
||||
if (/\.(md|markdown)$/.test(n)) return '📝';
|
||||
if (/\.(md|markdown)$/.test(n)) return '<span class="page-icon-svg">'+getSvgIcon('edit',14)+'</span>';
|
||||
if (/\.(py)$/.test(n)) return '🐍';
|
||||
if (/\.(js|jsx|ts|tsx)$/.test(n)) return '📜';
|
||||
if (/\.(html?|xml)$/.test(n)) return '🌐';
|
||||
if (/\.css$/.test(n)) return '🎨';
|
||||
if (/\.json$/.test(n)) return '📋';
|
||||
if (/\.json$/.test(n)) return '<span class="page-icon-svg">'+getSvgIcon('file',14)+'</span>';
|
||||
if (/\.sql$/.test(n)) return '🗃️';
|
||||
if (/\.(sh|bash|zsh)$/.test(n)) return '💻';
|
||||
if (/\.ps1$/.test(n)) return '⚡';
|
||||
if (/\.(rs|go|java|rb|php|c|cpp|h|swift|kt|scala|r)$/.test(n)) return '📜';
|
||||
if (/\.(txt|log)$/.test(n)) return '📄';
|
||||
if (/\.(txt|log)$/.test(n)) return '<span class="page-icon-svg">'+getSvgIcon('file',14)+'</span>';
|
||||
if (/\.(zip|tar|gz|rar|7z)$/.test(n)) return '📦';
|
||||
return '📄';
|
||||
},
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
<!-- Left navigation -->
|
||||
<div class="settings-nav">
|
||||
<div class="settings-nav-header">Account</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='account' }" @click="activeSection='account'">👤 My account</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='notifications' }" @click="activeSection='notifications'">🔔 Notifications</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='account' }" @click="activeSection='account'"><span class="nav-icon-inline">{{ fd_icon("user",14) }}</span> My account</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='notifications' }" @click="activeSection='notifications'"><span class="nav-icon-inline">{{ fd_icon("bell",14) }}</span> Notifications</div>
|
||||
<div class="settings-nav-header">Workspace</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='workspace' }" @click="activeSection='workspace'">📋 General</div>
|
||||
<div class="settings-nav-item" :class="{ active: activeSection==='tags' }" @click="activeSection='tags'">🏷️ Tags</div>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
<!-- ── Local Workspaces ── -->
|
||||
<div class="ws-section">
|
||||
<div class="ws-section-title">📁 My Workspaces <span class="badge" x-text="workspaces.length"></span></div>
|
||||
<div class="ws-section-title">{{ fd_icon("folder",16) }} My Workspaces <span class="badge" x-text="workspaces.length"></span></div>
|
||||
<div class="ws-grid">
|
||||
<template x-for="ws in workspaces" :key="'local-'+ws.id">
|
||||
<div class="ws-card" :class="{active:ws.id===activeId}" @click="selectLocal(ws)">
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="ws-card-count" x-text="ws.page_count+' page(s)'"></div>
|
||||
<div class="ws-card-actions" @click.stop>
|
||||
<button class="btn-sm" @click="renameWs(ws)">✏️</button>
|
||||
<button class="btn-sm" style="color:var(--danger);border-color:rgba(255,80,80,.3)" @click="deleteWs(ws)">🗑</button>
|
||||
<button class="btn-sm" style="color:var(--danger);border-color:rgba(255,80,80,.3)" @click="deleteWs(ws)">{{ fd_icon("trash",14) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -83,7 +83,7 @@
|
||||
<div>Connect your Gitea account to see your projects.</div>
|
||||
<a class="gitea-connect" href="/auth/login?provider=gitea&mode=link">🔗 Connect Gitea</a>
|
||||
<span style="margin:0 8px;color:var(--text-dim);">or</span>
|
||||
<a class="gitea-connect" href="/auth/login?provider=gitea">📝 Register with Gitea</a>
|
||||
<a class="gitea-connect" href="/auth/login?provider=gitea">{{ fd_icon("edit",14) }} Register with Gitea</a>
|
||||
</div>
|
||||
|
||||
<!-- Loading skeletons -->
|
||||
|
||||
@@ -524,6 +524,9 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
||||
.page-icon-svg svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.nav-icon-inline { margin-right: 6px; vertical-align: middle; display: inline-flex; align-items: center; }
|
||||
.nav-icon-inline svg { vertical-align: middle; }
|
||||
.empty-state-icon { display: inline-flex; align-items: center; justify-content: center; margin-bottom: 12px; }
|
||||
|
||||
.sidebar-item:active .page-icon {
|
||||
cursor: grabbing;
|
||||
|
||||
Reference in New Issue
Block a user