fix(library): layout Notion-style — drag+checkbox+OPEN dans colonne name, hover-only CSS corrigé
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- Drag handle (6 dots), checkbox, OPEN bouton intégrés dans la colonne Page name
- CSS hover-only: opacity 0 → 1 au mouseover (pas de visibilité permanente)
- Checkbox checked toujours visible (opacity:1) même sans hover
- OPEN bouton aligné à droite via margin-left:auto dans le name cell
- Double-clic sur le nom ouvre la page, simple clic = rename
- toggleExpand() force la réactivité Alpine via this.items = [...this.items]
- Colonnes drag/select/open séparées supprimées (layout Notion)
This commit is contained in:
2026-07-19 13:12:44 -04:00
parent b7c9401c92
commit 31fba6da39
+45 -48
View File
@@ -41,42 +41,44 @@
.lib-table-header .th.sortable:hover{color:var(--text-primary);}
.lib-table-body{}
/* Column widths */
.lib-col-select{width:32px;text-align:center;flex-shrink:0;}
.lib-col-drag{width:28px;flex-shrink:0;}
/* Column widths — Notion-style: no separate drag/select columns */
.lib-col-name{flex:1;min-width:0;}
.lib-col-author{width:160px;flex-shrink:0;}
.lib-col-source{width:130px;flex-shrink:0;}
.lib-col-edited{width:100px;flex-shrink:0;}
.lib-col-visited{width:100px;flex-shrink:0;}
.lib-col-open{width:80px;text-align:right;flex-shrink:0;}
/* ── Row ── */
.lib-row{display:flex;align-items:center;height:36px;transition:background 0.1s;position:relative;}
.lib-row:hover{background:var(--bg-hover);cursor:pointer;}
.lib-row:hover{background:var(--bg-hover);}
.lib-row.selected{background:rgba(35,131,226,0.08);}
.lib-row .row-cell{display:flex;align-items:center;padding:0 8px;height:100%;overflow:hidden;}
/* Hover-only elements */
.lib-row .hover-only{opacity:0.35;transition:opacity 0.15s;}
/* ── Name cell — contains drag handle, checkbox, chevron, icon, name, OPEN ── */
.row-name{display:flex;align-items:center;gap:4px;min-width:0;flex:1;position:relative;}
.row-icon{font-size:15px;flex-shrink:0;width:20px;text-align:center;}
.row-title{font-size:13px;font-weight:400;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;cursor:pointer;}
.row-title:hover{color:var(--accent);}
.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{opacity:0;transition:opacity 0.12s;flex-shrink:0;}
.lib-row:hover .hover-only{opacity:1;}
/* Drag handle — 6 dots */
.drag-handle{display:flex;flex-wrap:wrap;width:16px;height:16px;cursor:grab;align-content:space-between;justify-content:space-between;flex-shrink:0;opacity:0.45;}
.lib-row:hover .drag-handle{opacity:1;}
/* 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 .dot{width:3px;height:3px;background:var(--text-tertiary);border-radius:50%;}
.drag-handle:active{cursor:grabbing;}
/* Checkbox */
.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.15s;}
.lib-row:hover .lib-checkbox{border-color:var(--text-secondary);}
.lib-checkbox.checked{background:var(--accent);border-color:var(--accent);}
/* 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:hover{border-color:var(--text-primary);}
.lib-checkbox.checked{background:var(--accent);border-color:var(--accent);opacity:1;}
.lib-checkbox.checked::after{content:'✓';color:#fff;font-size:10px;font-weight:700;}
/* ── Name cell ── */
.row-name{display:flex;align-items:center;gap:6px;min-width:0;flex:1;}
.row-icon{font-size:15px;flex-shrink:0;width:20px;text-align:center;}
.row-title{font-size:13px;font-weight:400;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;}
.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;}
/* OPEN button (hover-only, right side of name cell) */
.btn-open{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border:1px solid var(--border);border-radius:4px;font-size:11px;color:var(--text-secondary);background:var(--bg-tertiary);cursor:pointer;white-space:nowrap;transition:all 0.1s;flex-shrink:0;text-decoration:none;margin-left:auto;}
.btn-open:hover{background:var(--bg-hover);color:var(--text-primary);border-color:var(--text-primary);}
/* Expand chevron */
.tree-chevron{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:8px;padding:0;border-radius:3px;transition:transform 0.15s;}
@@ -84,6 +86,7 @@
.tree-chevron.open{transform:rotate(90deg);}
/* Indent levels */
.indent-0{padding-left:4px;}
.indent-1{padding-left:24px!important;}
.indent-2{padding-left:44px!important;}
.indent-3{padding-left:64px!important;}
@@ -98,10 +101,9 @@
/* ── Time cells ── */
.row-time{font-size:13px;color:var(--text-secondary);white-space:nowrap;}
/* ── OPEN button ── */
.btn-open{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border:1px solid var(--border);border-radius:4px;font-size:12px;color:var(--text-secondary);background:var(--bg-tertiary);cursor:pointer;white-space:nowrap;transition:all 0.1s;opacity:0;}
.lib-row:hover .btn-open{opacity:1;}
.btn-open:hover{background:var(--bg-hover);color:var(--text-primary);border-color:var(--text-primary);}
/* "+ Add new" under expanded parent */
.add-new-under{padding:4px 8px 4px 28px;color:var(--text-tertiary);font-size:12px;cursor:pointer;transition:color 0.15s;}
.add-new-under:hover{color:var(--text-primary);}
/* ── Side Peek Panel ── */
.peek-overlay{position:fixed;top:0;right:0;width:560px;max-width:90vw;height:100vh;background:var(--bg-modal);border-left:1px solid var(--border);box-shadow:-8px 0 32px rgba(0,0,0,0.25);z-index:500;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 0.2s cubic-bezier(0.16,1,0.3,1);}
@@ -206,33 +208,26 @@
<!-- ── Table ── -->
<div class="lib-table" x-show="!loading && flatItems.length > 0" id="lib-table">
<div class="lib-table-header">
<div class="th lib-col-drag"></div>
<div class="th lib-col-select">
<div class="lib-checkbox" :class="{ checked: allSelected }" @click="toggleSelectAll()"></div>
</div>
<div class="th lib-col-name sortable" @click="sortBy='name';doSort()">Page name</div>
<div class="th lib-col-author sortable" @click="sortBy='author';doSort()">Created by</div>
<div class="th lib-col-source">Source</div>
<div class="th lib-col-edited sortable" @click="sortBy='edited';doSort()">Last edited</div>
<div class="th lib-col-visited sortable" @click="sortBy='visited';doSort()">Last visited</div>
<div class="th lib-col-open"></div>
</div>
<div class="lib-table-body" id="lib-table-body">
<template x-for="(item, idx) in flatItems" :key="item.id">
<div class="lib-row" :class="{ selected: !!selected[item.id] }" :data-lib-id="item.id">
<div class="row-cell lib-col-drag">
<div class="drag-handle hover-only" style="margin:auto;" title="Drag to move">
<span class="dot"></span><span class="dot"></span>
<span class="dot"></span><span class="dot"></span>
<span class="dot"></span><span class="dot"></span>
</div>
</div>
<div class="row-cell lib-col-select">
<div class="lib-checkbox" :class="{ checked: selected[item.id] }"
@click.stop="toggleItem(item)"></div>
</div>
<div class="row-cell lib-col-name" :class="'indent-' + (item.depth || 0)">
<div class="row-name">
<!-- Drag handle (hover-only) -->
<div class="drag-handle hover-only" title="Drag to move">
<span class="dot"></span><span class="dot"></span>
<span class="dot"></span><span class="dot"></span>
<span class="dot"></span><span class="dot"></span>
</div>
<!-- Checkbox (hover-only, always visible if checked) -->
<div class="lib-checkbox hover-only" :class="{ checked: selected[item.id] }"
@click.stop="toggleItem(item)"></div>
<!-- Expand chevron -->
<button x-show="item.has_children" class="tree-chevron"
:class="{ open: expanded[item.id] }"
@@ -243,13 +238,18 @@
<!-- Title or inline rename -->
<span class="row-title" x-show="renamingId !== item.id"
@click.stop="startRename(item)"
x-text="item.title"></span>
<input x-show="renamingId === item.id" class="row-title"
@dblclick.stop="openItem(item)"
x-text="item.title"
title="Click to rename, double-click to open"></span>
<input x-show="renamingId === item.id"
x-model="renameValue"
@keydown.enter="commitRename(item)"
@keydown.escape="renamingId=null"
@blur="commitRename(item)"
@click.stop>
@click.stop
style="flex:1;min-width:80px;">
<!-- OPEN button (hover-only, right-aligned) -->
<button class="btn-open hover-only" @click.stop="openPeek(item)" title="Open preview">⤢ Open</button>
</div>
</div>
<div class="row-cell lib-col-author">
@@ -267,9 +267,6 @@
<div class="row-cell lib-col-visited">
<span class="row-time" x-text="formatTime(item.visited_at)"></span>
</div>
<div class="row-cell lib-col-open" style="text-align:right;">
<button class="btn-open" @click.stop="openPeek(item)">⤢ Open</button>
</div>
</div>
</template>
</div>
@@ -489,6 +486,7 @@ function libraryPage() {
// Collapse
delete this.expanded[item.id];
this.expanded = Object.assign({}, this.expanded);
this.items = [...this.items]; // force reactivity
return;
}
// Expand — load children if not loaded yet
@@ -500,13 +498,12 @@ function libraryPage() {
var d = await r.json();
item.children = d.items || [];
// Mark the tree for depth calculation
if (typeof this._markChildren === 'function') {
this._markChildren(item.children, (item.depth || 0) + 1);
}
this._markChildren(item.children, (item.depth || 0) + 1);
} catch(e) {
item.children = [];
}
}
this.items = [...this.items]; // force Alpine reactivity
},
_markChildren(nodes, depth) {