fix(library): layout Notion-style — drag+checkbox+OPEN dans colonne name, hover-only CSS corrigé
- 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:
+45
-48
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user