fix(library): visibility hover + workspace filter + tab Repository pour Gitea
- CSS hover-only: visibility:hidden/visible au lieu de opacity (infaillible) - Workspace tab: filtré par workspace_id quand un workspace est actif - Tab Private remplacé par Repository (visible seulement si Gitea workspace actif) - Nouvel endpoint /api/library/repository pour le contenu Gitea - dashboard.py passe is_gitea_workspace, owner, repo au template
This commit is contained in:
@@ -221,6 +221,10 @@ async def library_page(request: Request, owner: str = Query(default=""), repo: s
|
|||||||
|
|
||||||
template = env.get_template("library.html")
|
template = env.get_template("library.html")
|
||||||
sidebar["active_workspace_id"] = sidebar.get("nav_workspace_id", 0)
|
sidebar["active_workspace_id"] = sidebar.get("nav_workspace_id", 0)
|
||||||
|
# Gitea workspace context for Repository tab
|
||||||
|
sidebar["is_gitea_workspace"] = bool(owner and repo)
|
||||||
|
sidebar["gitea_workspace_owner"] = owner
|
||||||
|
sidebar["gitea_workspace_repo"] = repo
|
||||||
return template.render(**sidebar)
|
return template.render(**sidebar)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -271,11 +271,36 @@ async def library_children(page_id: int, request: Request):
|
|||||||
return {"items": items}
|
return {"items": items}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/repository")
|
||||||
|
async def library_repository(
|
||||||
|
request: Request,
|
||||||
|
gitea_owner: str = Query(default=""),
|
||||||
|
gitea_repo: str = Query(default=""),
|
||||||
|
):
|
||||||
|
"""Return Gitea repository pages/files for the Library Repository tab."""
|
||||||
|
if not gitea_owner or not gitea_repo:
|
||||||
|
return {"items": []}
|
||||||
|
|
||||||
|
uid = _get_user_id(request)
|
||||||
|
ws_key = f"{gitea_owner}/{gitea_repo}"
|
||||||
|
query = f"{BASE_SELECT} FROM pages p WHERE p.workspace = ? AND p.deleted_at IS NULL"
|
||||||
|
params = [ws_key]
|
||||||
|
query += " ORDER BY p.updated_at DESC LIMIT 100"
|
||||||
|
|
||||||
|
with get_conn() as conn:
|
||||||
|
rows = conn.execute(query, params).fetchall()
|
||||||
|
|
||||||
|
items = _rows_to_items(rows, uid)
|
||||||
|
_enrich_children(items)
|
||||||
|
return {"items": items}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/workspace")
|
@router.get("/workspace")
|
||||||
async def library_workspace(
|
async def library_workspace(
|
||||||
request: Request,
|
request: Request,
|
||||||
source_type: str = Query(default="all"),
|
source_type: str = Query(default="all"),
|
||||||
tree: int = Query(default=0),
|
tree: int = Query(default=0),
|
||||||
|
workspace_id: int = Query(default=0),
|
||||||
):
|
):
|
||||||
if source_type not in SOURCE_TYPES:
|
if source_type not in SOURCE_TYPES:
|
||||||
source_type = "all"
|
source_type = "all"
|
||||||
@@ -283,6 +308,11 @@ async def library_workspace(
|
|||||||
uid = _get_user_id(request)
|
uid = _get_user_id(request)
|
||||||
query = f"{BASE_SELECT} FROM pages p WHERE p.workspace != '' AND p.parent_section != 'Trash' AND p.deleted_at IS NULL"
|
query = f"{BASE_SELECT} FROM pages p WHERE p.workspace != '' AND p.parent_section != 'Trash' AND p.deleted_at IS NULL"
|
||||||
params: list = []
|
params: list = []
|
||||||
|
|
||||||
|
if workspace_id:
|
||||||
|
query += " AND p.workspace_id = ?"
|
||||||
|
params.append(workspace_id)
|
||||||
|
|
||||||
query, params = _apply_source_filter(query, params, source_type)
|
query, params = _apply_source_filter(query, params, source_type)
|
||||||
query += " ORDER BY p.updated_at DESC LIMIT 50"
|
query += " ORDER BY p.updated_at DESC LIMIT 50"
|
||||||
|
|
||||||
|
|||||||
+33
-11
@@ -12,6 +12,9 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div x-data="libraryPage()" x-init="init()" @keydown.escape="closePeek()">
|
<div x-data="libraryPage()" x-init="init()" @keydown.escape="closePeek()">
|
||||||
<div id="lib-workspace-id" style="display:none;">{{ active_workspace_id or 0 }}</div>
|
<div id="lib-workspace-id" style="display:none;">{{ active_workspace_id or 0 }}</div>
|
||||||
|
<div id="lib-gitea-active" style="display:none;">{{ '1' if is_gitea_workspace else '0' }}</div>
|
||||||
|
<div id="lib-gitea-owner" style="display:none;">{{ gitea_workspace_owner or '' }}</div>
|
||||||
|
<div id="lib-gitea-repo" style="display:none;">{{ gitea_workspace_repo or '' }}</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* ── Library layout ── */
|
/* ── Library layout ── */
|
||||||
@@ -62,18 +65,18 @@
|
|||||||
.row-title input{width:100%;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;}
|
.row-title input{width:100%;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;}
|
||||||
|
|
||||||
/* ── Hover-only elements (appear on row hover) ── */
|
/* ── Hover-only elements (appear on row hover) ── */
|
||||||
.hover-only{opacity:0;transition:opacity 0.12s;flex-shrink:0;}
|
.hover-only{visibility:hidden;opacity:0;transition:opacity 0.12s, visibility 0s 0.12s;flex-shrink:0;}
|
||||||
.lib-row:hover .hover-only{opacity:1;}
|
.lib-row:hover .hover-only{visibility:visible;opacity:1;transition:opacity 0.15s, visibility 0s 0s;}
|
||||||
|
|
||||||
/* Drag handle — 6 dots (hover-only, far left of name cell) */
|
/* Drag handle — 6 dots (hover-only, far left of name cell) */
|
||||||
.drag-handle{display:flex;flex-wrap:wrap;width:14px;height:14px;cursor:grab;align-content:space-between;justify-content:space-between;flex-shrink:0;}
|
.drag-handle{display:flex;flex-wrap:wrap;width:16px;height:16px;cursor:grab;align-content:space-between;justify-content:space-between;flex-shrink:0;}
|
||||||
.drag-handle .dot{width:3px;height:3px;background:var(--text-tertiary);border-radius:50%;}
|
.drag-handle .dot{width:3px;height:3px;background:var(--text-tertiary);border-radius:50%;}
|
||||||
.drag-handle:active{cursor:grabbing;}
|
.drag-handle:active{cursor:grabbing;}
|
||||||
|
|
||||||
/* Checkbox (hover-only) */
|
/* Checkbox (hover-only) */
|
||||||
.lib-checkbox{width:15px;height:15px;border:1.5px solid var(--text-tertiary);border-radius:3px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all 0.1s;}
|
.lib-checkbox{width:15px;height:15px;border:1.5px solid var(--text-tertiary);border-radius:3px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:border-color 0.1s;}
|
||||||
.lib-checkbox:hover{border-color:var(--text-primary);}
|
.lib-checkbox:hover{border-color:var(--text-primary);}
|
||||||
.lib-checkbox.checked{background:var(--accent);border-color:var(--accent);opacity:1;}
|
.lib-checkbox.checked{background:var(--accent);border-color:var(--accent);visibility:visible;opacity:1;}
|
||||||
.lib-checkbox.checked::after{content:'✓';color:#fff;font-size:10px;font-weight:700;}
|
.lib-checkbox.checked::after{content:'✓';color:#fff;font-size:10px;font-weight:700;}
|
||||||
|
|
||||||
/* OPEN button (hover-only, right side of name cell) */
|
/* OPEN button (hover-only, right side of name cell) */
|
||||||
@@ -176,9 +179,12 @@
|
|||||||
<button class="lib-tab" :class="{ active: tab === 'published' }" @click="switchTab('published')">
|
<button class="lib-tab" :class="{ active: tab === 'published' }" @click="switchTab('published')">
|
||||||
<span class="tab-icon">🌐</span> Published
|
<span class="tab-icon">🌐</span> Published
|
||||||
</button>
|
</button>
|
||||||
<button class="lib-tab" :class="{ active: tab === 'private' }" @click="switchTab('private')">
|
<button class="lib-tab" :class="{ active: tab === 'private' }" @click="switchTab('private')" x-show="!isGiteaActive">
|
||||||
<span class="tab-icon">🔒</span> Private
|
<span class="tab-icon">🔒</span> Private
|
||||||
</button>
|
</button>
|
||||||
|
<button class="lib-tab" :class="{ active: tab === 'repository' }" @click="switchTab('repository')" x-show="isGiteaActive">
|
||||||
|
<span class="tab-icon">📦</span> Repository
|
||||||
|
</button>
|
||||||
<button class="lib-tab" :class="{ active: tab === 'workspace' }" @click="switchTab('workspace')">
|
<button class="lib-tab" :class="{ active: tab === 'workspace' }" @click="switchTab('workspace')">
|
||||||
<span class="tab-icon">📁</span> Workspace
|
<span class="tab-icon">📁</span> Workspace
|
||||||
</button>
|
</button>
|
||||||
@@ -330,6 +336,9 @@ function libraryPage() {
|
|||||||
return {
|
return {
|
||||||
tab: 'recents',
|
tab: 'recents',
|
||||||
workspaceId: 0,
|
workspaceId: 0,
|
||||||
|
isGiteaActive: false,
|
||||||
|
giteaOwner: '',
|
||||||
|
giteaRepo: '',
|
||||||
items: [],
|
items: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
searchOpen: false,
|
searchOpen: false,
|
||||||
@@ -388,12 +397,13 @@ function libraryPage() {
|
|||||||
|
|
||||||
// ── Empty states ──
|
// ── Empty states ──
|
||||||
get emptyIcon() {
|
get emptyIcon() {
|
||||||
var icons = {recents:'🕒',favorites:'⭐',shared:'👥',published:'🌐',private:'🔒',workspace:'📁'};
|
var icons = {recents:'🕒',favorites:'⭐',shared:'👥',published:'🌐',private:'🔒',workspace:'📁',repository:'📦'};
|
||||||
return icons[this.tab] || '📄';
|
return icons[this.tab] || '📄';
|
||||||
},
|
},
|
||||||
get emptyTitle() {
|
get emptyTitle() {
|
||||||
var titles = {recents:'No recent pages',favorites:'No favorites',shared:'No shared pages',
|
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'};
|
published:'No published pages',private:'No private pages',workspace:'No workspace pages',
|
||||||
|
repository:'Empty repository'};
|
||||||
return titles[this.tab] || 'No items';
|
return titles[this.tab] || 'No items';
|
||||||
},
|
},
|
||||||
get emptyText() {
|
get emptyText() {
|
||||||
@@ -402,7 +412,8 @@ function libraryPage() {
|
|||||||
shared:'Shared pages from your team will appear here.',
|
shared:'Shared pages from your team will appear here.',
|
||||||
published:'Pages published to the web will appear here.',
|
published:'Pages published to the web will appear here.',
|
||||||
private:'Create a new page with the + button.',
|
private:'Create a new page with the + button.',
|
||||||
workspace:'Pages inside workspaces will appear here.'};
|
workspace:'Pages inside workspaces will appear here.',
|
||||||
|
repository:'Repository files and pages will appear here.'};
|
||||||
return texts[this.tab] || '';
|
return texts[this.tab] || '';
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -410,9 +421,13 @@ function libraryPage() {
|
|||||||
init() {
|
init() {
|
||||||
var wsEl = document.getElementById('lib-workspace-id');
|
var wsEl = document.getElementById('lib-workspace-id');
|
||||||
this.workspaceId = parseInt(wsEl ? wsEl.textContent : '0') || 0;
|
this.workspaceId = parseInt(wsEl ? wsEl.textContent : '0') || 0;
|
||||||
|
var giteaEl = document.getElementById('lib-gitea-active');
|
||||||
|
this.isGiteaActive = (giteaEl ? giteaEl.textContent : '0') === '1';
|
||||||
|
this.giteaOwner = (document.getElementById('lib-gitea-owner') || {}).textContent || '';
|
||||||
|
this.giteaRepo = (document.getElementById('lib-gitea-repo') || {}).textContent || '';
|
||||||
var params = new URLSearchParams(window.location.search);
|
var params = new URLSearchParams(window.location.search);
|
||||||
var t = params.get('tab');
|
var t = params.get('tab');
|
||||||
if (t && ['recents','favorites','shared','published','private','workspace'].indexOf(t) !== -1) {
|
if (t && ['recents','favorites','shared','published','private','workspace','repository'].indexOf(t) !== -1) {
|
||||||
this.tab = t;
|
this.tab = t;
|
||||||
}
|
}
|
||||||
this.loadTab();
|
this.loadTab();
|
||||||
@@ -420,6 +435,7 @@ function libraryPage() {
|
|||||||
|
|
||||||
switchTab(t) {
|
switchTab(t) {
|
||||||
if (this.tab === t) return;
|
if (this.tab === t) return;
|
||||||
|
if (t === 'repository' && !this.isGiteaActive) return;
|
||||||
this.tab = t;
|
this.tab = t;
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.selected = {};
|
this.selected = {};
|
||||||
@@ -443,7 +459,13 @@ function libraryPage() {
|
|||||||
try {
|
try {
|
||||||
var url = '/api/library/' + this.tab;
|
var url = '/api/library/' + this.tab;
|
||||||
var sep = '?';
|
var sep = '?';
|
||||||
if (this.workspaceId) { url += '?workspace_id=' + this.workspaceId; sep = '&'; }
|
if (this.workspaceId && (this.tab === 'recents' || this.tab === 'workspace')) {
|
||||||
|
url += sep + 'workspace_id=' + this.workspaceId; sep = '&';
|
||||||
|
}
|
||||||
|
if (this.tab === 'repository' && this.isGiteaActive) {
|
||||||
|
url += sep + 'gitea_owner=' + encodeURIComponent(this.giteaOwner) + '&gitea_repo=' + encodeURIComponent(this.giteaRepo);
|
||||||
|
sep = '&';
|
||||||
|
}
|
||||||
var r = await fetch(url);
|
var r = await fetch(url);
|
||||||
var d = await r.json();
|
var d = await r.json();
|
||||||
this.items = d.items || [];
|
this.items = d.items || [];
|
||||||
|
|||||||
Reference in New Issue
Block a user