feat: unified header across all pages (Notion-inspired breadcrumb + actions)
Created a shared _header.html template with: - Hamburger button (toggle sidebar) - Breadcrumb navigation with clickable segments and separators - Page icon + title display - Right-side action buttons (configurable per page) - Dropdown panel CSS for More/New menus Updated all pages to use the unified header: - workspaces.html: Home / Workspaces + login/settings - workspace.html: Workspace — Projects + login/settings - local_workspace.html: workspace name - gitea_workspace.html: Home / owner/repo + New/Upload/Refresh/Settings - page_editor.html: title + Edited/Share/Copy/Favorite/More dropdown - settings.html: Settings Added CSS for breadcrumb, dropdown panel components.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
{# ── Unified Header (Notion-inspired) ──
|
||||
Parameters available in calling context:
|
||||
breadcrumb_items: list of {label: str, url: str|None} — last item = current page
|
||||
page_icon: str — emoji icon
|
||||
right_actions: str — raw HTML for right-side action buttons
|
||||
hamburger_click: str — Alpine expression for hamburger button (default: opens sidebar)
|
||||
hide_hamburger: bool — if truthy, hides the hamburger button
|
||||
#}
|
||||
<header class="topbar unified-header">
|
||||
{% if not hide_hamburger %}
|
||||
<button class="hamburger-btn"
|
||||
@click="{{ hamburger_click|default('sidebarCollapsed ? (sidebarCollapsed = false) : (mobileSidebarOpen = true, sidebarCollapsed = false)') }}"
|
||||
title="Toggle sidebar">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="3" y1="6" x2="21" y2="6"/>
|
||||
<line x1="3" y1="12" x2="21" y2="12"/>
|
||||
<line x1="3" y1="18" x2="21" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<div class="topbar-left header-breadcrumb">
|
||||
{% if page_icon %}
|
||||
<span class="header-page-icon">{{ page_icon }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if breadcrumb_items %}
|
||||
{% for item in breadcrumb_items %}
|
||||
{% if item.url and not loop.last %}
|
||||
<a href="{{ item.url }}" class="breadcrumb-link">{{ item.label }}</a>
|
||||
<span class="breadcrumb-sep">/</span>
|
||||
{% elif loop.last %}
|
||||
<span class="breadcrumb-current">{{ item.label }}</span>
|
||||
{% else %}
|
||||
<span class="breadcrumb-link disabled">{{ item.label }}</span>
|
||||
<span class="breadcrumb-sep">/</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<span class="breadcrumb-current">{{ page_title|default('FlowDeck') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="topbar-right header-actions">
|
||||
{{ right_actions|safe if right_actions else '' }}
|
||||
</div>
|
||||
</header>
|
||||
+1
-41
@@ -329,47 +329,7 @@
|
||||
|
||||
<!-- Topbar -->
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<!-- Hamburger / uncollapse sidebar -->
|
||||
<button class="hamburger-btn" @click="sidebarCollapsed ? (sidebarCollapsed = false) : (mobileSidebarOpen = true, sidebarCollapsed = false)" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<div class="topbar-breadcrumb">
|
||||
<span x-text="workspaceName"></span>
|
||||
<span class="text-dim">/</span>
|
||||
<span class="text-dim" x-text="currentPage"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topbar-center">
|
||||
<div class="topbar-page-title">
|
||||
<span class="page-icon" x-text="pageIcon"></span>
|
||||
<span x-text="pageTitle"></span>
|
||||
</div>
|
||||
<div class="topbar-page-status">
|
||||
<span>🔒</span> Private
|
||||
<span style="cursor:pointer;">▾</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<span class="text-xs text-dim">Edited <span x-text="lastEdited"></span></span>
|
||||
<button class="topbar-btn">🔔</button>
|
||||
<button class="topbar-btn">🕒</button>
|
||||
<button class="topbar-btn">🔍</button>
|
||||
<button class="topbar-btn share-btn">🔗 Share ▾</button>
|
||||
<button class="topbar-btn">⭐</button>
|
||||
<button class="topbar-btn" @click="showNewPageMenu = !showNewPageMenu" style="position:relative;">
|
||||
New ▾
|
||||
<div class="dropdown-panel" :class="{ visible: showNewPageMenu }" style="top:100%; right:0; left:auto; min-width:200px;" @click.stop>
|
||||
<div class="dropdown-option" @click="createEmptyPage()"><span class="option-icon">📄</span> Empty page</div>
|
||||
<div class="dropdown-option" @click="createDatabase()"><span class="option-icon">📊</span> Database</div>
|
||||
<div class="dropdown-option" @click="createBoard()"><span class="option-icon">📋</span> Kanban board</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
<!-- Content -->
|
||||
|
||||
@@ -4,22 +4,10 @@
|
||||
{% block page_icon %}🔗{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<a href="/workspaces" class="breadcrumb" style="color:rgba(255,255,255,.5);text-decoration:none;">Home</a>
|
||||
<span class="page-title-topbar">{{ owner }}/{{ repo }}</span>
|
||||
</div>
|
||||
<div class="topbar-right" style="display:flex;align-items:center;gap:6px;">
|
||||
<button class="page-action-btn" @click="showNewFile=!showNewFile" title="New file">📄+</button>
|
||||
<button class="page-action-btn" @click="triggerUpload()" title="Upload file">📤</button>
|
||||
<input type="file" id="gitea-upload-input" style="display:none" @change="doUpload($event)" multiple>
|
||||
<button class="page-action-btn" @click="refreshTree()" title="Refresh">🔄</button>
|
||||
<button class="page-action-btn" onclick="window.location='/accounts/settings'" title="Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
{% set breadcrumb_items = [{"label": "Home", "url": "/workspaces"}, {"label": owner ~ "/" ~ repo, "url": None}] %}
|
||||
{% set page_icon = "🔗" %}
|
||||
{% set right_actions = '<button class="topbar-btn" @click="showNewFile=!showNewFile" title="New file">📄+</button><button class="topbar-btn" @click="triggerUpload()" title="Upload file">📤</button><input type="file" id="gitea-upload-input" style="display:none" @change="doUpload($event)" multiple><button class="topbar-btn" @click="refreshTree()" title="Refresh">🔄</button><a href="/accounts/settings" class="topbar-btn" title="Settings">⚙</a>' %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -4,20 +4,9 @@
|
||||
{% block page_icon %}📁{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="sidebarCollapsed ? (sidebarCollapsed = false) : (mobileSidebarOpen = true, sidebarCollapsed = false)" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="page-title-topbar">
|
||||
{% if breadcrumb %}
|
||||
{% for b in breadcrumb %}
|
||||
/ <a href="/local-workspace?folder={{ b.id }}" style="color:rgba(255,255,255,.8);text-decoration:none;">{{ b.name }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
{% set page_icon = "📁" %}
|
||||
{% set page_title = workspace_name %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{% extends "base.html" %} {% block page_icon %}📄{% endblock %} {% block
|
||||
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
||||
<header class="topbar" style="background:var(--bg-primary);">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5);font-size:13px">{{ page.title }}</span>
|
||||
</div>
|
||||
</header>
|
||||
{% set page_icon = "📄" %}
|
||||
{% set page_title = page.title %}
|
||||
{% set right_actions = '<span class="topbar-edited">Edited just now</span><button class="topbar-btn share-btn" @click="shareOpen = !shareOpen">🔒 Share ▾</button><button class="topbar-btn" @click="copyPageLink" title="Copy link">🔗</button><button class="topbar-btn star-btn" @click="toggleFavorite">⭐</button><button class="topbar-btn" @click="moreOpen = !moreOpen" style="position:relative">⋯<div class="dropdown-panel" :class="{ visible: moreOpen }" @click.outside="moreOpen=false" style="top:100%;right:0;left:auto;min-width:200px"><div class="dropdown-option" @click="moreOpen=false">📋 Duplicate</div><div class="dropdown-option" @click="moreOpen=false">🗑 Move to Trash</div><div class="dropdown-option" @click="moreOpen=false">🔗 Copy link</div><div class="dropdown-option" @click="moreOpen=false">📤 Export</div></div></button>' %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %} {% block content %}
|
||||
<div class="page-editor-wrapper" x-data="editorState()">
|
||||
<!-- ═══════════ Top Action Bar ═══════════ -->
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
{% block page_icon %}⚙️{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="page-title-topbar">⚙️ Settings</span>
|
||||
</div>
|
||||
</header>
|
||||
{% set page_icon = "⚙" %}
|
||||
{% set page_title = "Settings" %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -4,19 +4,10 @@
|
||||
{% block page_icon %}🏠{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Workspace</span>
|
||||
<span class="page-title-topbar">Projects</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="page-action-btn" onclick="window.location='/auth/login?provider=local'" title="Login">🔑</button>
|
||||
<button class="page-action-btn" onclick="window.location='/accounts/settings'" title="Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
{% set page_icon = "🏠" %}
|
||||
{% set page_title = "Workspace — Projects" %}
|
||||
{% set right_actions = '<a href="/auth/login?provider=local" class="topbar-btn" title="Login">🔑</a><a href="/accounts/settings" class="topbar-btn" title="Settings">⚙</a>' %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -4,18 +4,10 @@
|
||||
{% block page_icon %}🏠{% endblock %}
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Home</span>
|
||||
<span class="page-title-topbar">Workspaces</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="page-action-btn" onclick="window.location='/accounts/settings'" title="Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
{% set breadcrumb_items = [{"label": "Home", "url": "/workspaces"}, {"label": "Workspaces", "url": None}] %}
|
||||
{% set page_icon = "🏠" %}
|
||||
{% set right_actions = '<a href="/auth/login?provider=local" class="topbar-btn" title="Login">🔑</a><a href="/accounts/settings" class="topbar-btn" title="Settings">⚙</a>' %}
|
||||
{% include '_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@@ -807,6 +807,104 @@ input, select, textarea { font-family: inherit; color: inherit; }
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* ── Unified Header Breadcrumb ── */
|
||||
.header-breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.header-page-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.breadcrumb-link {
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
transition: background 100ms, color 100ms;
|
||||
}
|
||||
|
||||
.breadcrumb-link:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.breadcrumb-link.disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.breadcrumb-sep {
|
||||
color: var(--text-tertiary);
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.breadcrumb-current {
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Dropdown panel (reusable) ── */
|
||||
.dropdown-panel {
|
||||
position: absolute;
|
||||
display: none;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,.4);
|
||||
z-index: 1000;
|
||||
padding: 4px;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.dropdown-panel.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown-option:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
font-size: 16px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.topbar-breadcrumb a:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user