Add desktop sidebar toggle with persistent state, search result highlighting, and improved filter icon positioning
This commit is contained in:
parent
8e1ae4be26
commit
6f694148db
@ -368,6 +368,32 @@
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sidebar toggle (desktop)
|
||||
// ---------------------------------------------------------------------------
|
||||
function initSidebarToggle() {
|
||||
const toggleBtn = document.getElementById("sidebar-toggle-btn");
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
const resizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
|
||||
if (!toggleBtn || !sidebar || !resizeHandle) return;
|
||||
|
||||
// Restore saved state
|
||||
const savedState = localStorage.getItem("obsigate-sidebar-hidden");
|
||||
if (savedState === "true") {
|
||||
sidebar.classList.add("hidden");
|
||||
resizeHandle.classList.add("hidden");
|
||||
toggleBtn.classList.add("active");
|
||||
}
|
||||
|
||||
toggleBtn.addEventListener("click", () => {
|
||||
const isHidden = sidebar.classList.toggle("hidden");
|
||||
resizeHandle.classList.toggle("hidden", isHidden);
|
||||
toggleBtn.classList.toggle("active", isHidden);
|
||||
localStorage.setItem("obsigate-sidebar-hidden", isHidden ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mobile sidebar
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1580,10 +1606,26 @@
|
||||
|
||||
const container = el("div", { class: "search-results" });
|
||||
data.results.forEach((r) => {
|
||||
// Create title with highlighting
|
||||
const titleDiv = el("div", { class: "search-result-title" });
|
||||
if (query && query.trim()) {
|
||||
highlightSearchText(titleDiv, r.title, query, searchCaseSensitive);
|
||||
} else {
|
||||
titleDiv.textContent = r.title;
|
||||
}
|
||||
|
||||
// Create snippet with highlighting
|
||||
const snippetDiv = el("div", { class: "search-result-snippet" });
|
||||
if (query && query.trim() && r.snippet) {
|
||||
highlightSearchText(snippetDiv, r.snippet, query, searchCaseSensitive);
|
||||
} else {
|
||||
snippetDiv.textContent = r.snippet || "";
|
||||
}
|
||||
|
||||
const item = el("div", { class: "search-result-item" }, [
|
||||
el("div", { class: "search-result-title" }, [document.createTextNode(r.title)]),
|
||||
titleDiv,
|
||||
el("div", { class: "search-result-vault" }, [document.createTextNode(r.vault + " / " + r.path)]),
|
||||
el("div", { class: "search-result-snippet" }, [document.createTextNode(r.snippet || "")]),
|
||||
snippetDiv,
|
||||
]);
|
||||
|
||||
if (r.tags && r.tags.length > 0) {
|
||||
@ -1764,6 +1806,40 @@
|
||||
}
|
||||
}
|
||||
|
||||
function highlightSearchText(container, text, query, caseSensitive) {
|
||||
container.textContent = "";
|
||||
if (!query || !text) {
|
||||
container.appendChild(document.createTextNode(text || ""));
|
||||
return;
|
||||
}
|
||||
|
||||
const source = caseSensitive ? text : text.toLowerCase();
|
||||
const needle = caseSensitive ? query : query.toLowerCase();
|
||||
let start = 0;
|
||||
let index = source.indexOf(needle, start);
|
||||
|
||||
if (index === -1) {
|
||||
container.appendChild(document.createTextNode(text));
|
||||
return;
|
||||
}
|
||||
|
||||
while (index !== -1) {
|
||||
if (index > start) {
|
||||
container.appendChild(document.createTextNode(text.slice(start, index)));
|
||||
}
|
||||
const mark = el("mark", { class: "search-highlight" }, [
|
||||
document.createTextNode(text.slice(index, index + query.length)),
|
||||
]);
|
||||
container.appendChild(mark);
|
||||
start = index + query.length;
|
||||
index = source.indexOf(needle, start);
|
||||
}
|
||||
|
||||
if (start < text.length) {
|
||||
container.appendChild(document.createTextNode(text.slice(start)));
|
||||
}
|
||||
}
|
||||
|
||||
function showWelcome() {
|
||||
const area = document.getElementById("content-area");
|
||||
area.innerHTML = `
|
||||
@ -2015,6 +2091,7 @@
|
||||
document.getElementById("theme-toggle").addEventListener("click", toggleTheme);
|
||||
document.getElementById("header-logo").addEventListener("click", goHome);
|
||||
initSearch();
|
||||
initSidebarToggle();
|
||||
initMobile();
|
||||
initVaultContext();
|
||||
initSidebarTabs();
|
||||
|
||||
@ -68,6 +68,9 @@
|
||||
<button class="hamburger-btn" id="hamburger-btn" title="Menu">
|
||||
<i data-lucide="menu" style="width:20px;height:20px"></i>
|
||||
</button>
|
||||
<button class="sidebar-toggle-btn" id="sidebar-toggle-btn" title="Afficher/Masquer la sidebar">
|
||||
<i data-lucide="sidebar" style="width:20px;height:20px"></i>
|
||||
</button>
|
||||
|
||||
<div class="header-logo" id="header-logo">
|
||||
<i data-lucide="book-open" style="width:20px;height:20px"></i>
|
||||
@ -161,8 +164,8 @@
|
||||
|
||||
<!-- Sidebar filter -->
|
||||
<div class="sidebar-filter">
|
||||
<i data-lucide="filter" class="sidebar-filter-icon" style="width:14px;height:14px"></i>
|
||||
<div class="sidebar-filter-input-wrapper">
|
||||
<i data-lucide="filter" class="sidebar-filter-icon" style="width:14px;height:14px"></i>
|
||||
<input type="text" id="sidebar-filter-input" placeholder="Filtrer fichiers..." autocomplete="off">
|
||||
<div class="sidebar-filter-actions">
|
||||
<button class="sidebar-filter-btn" id="sidebar-filter-case-btn" type="button" title="Respecter la casse" aria-label="Respecter la casse">
|
||||
|
||||
@ -137,6 +137,25 @@ a:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn {
|
||||
display: flex;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: color 200ms ease;
|
||||
}
|
||||
.sidebar-toggle-btn:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.sidebar-toggle-btn.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 700;
|
||||
@ -544,10 +563,17 @@ select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: background 200ms ease;
|
||||
transition: background 200ms ease, transform 300ms ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar.hidden {
|
||||
transform: translateX(-100%);
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
|
||||
/* --- Sidebar filter --- */
|
||||
.sidebar-filter {
|
||||
@ -557,7 +583,6 @@ select {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.sidebar-filter-input-wrapper {
|
||||
@ -863,7 +888,7 @@ select {
|
||||
cursor: ew-resize;
|
||||
background: transparent;
|
||||
flex-shrink: 0;
|
||||
transition: background 150ms ease;
|
||||
transition: background 150ms ease, opacity 300ms ease;
|
||||
z-index: 10;
|
||||
}
|
||||
.sidebar-resize-handle:hover,
|
||||
@ -871,6 +896,11 @@ select {
|
||||
background: var(--accent);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sidebar-resize-handle.hidden {
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- Content Area --- */
|
||||
.content-area {
|
||||
@ -1238,6 +1268,20 @@ select {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.search-result-snippet .search-highlight {
|
||||
background: color-mix(in srgb, var(--accent) 25%, transparent);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
padding: 1px 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.search-result-title .search-highlight {
|
||||
background: color-mix(in srgb, var(--accent) 25%, transparent);
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
padding: 1px 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.search-result-tags {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
@ -1758,6 +1802,10 @@ body.resizing-v {
|
||||
.hamburger-btn {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar-toggle-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
gap: 6px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user