Merge pull request #68 from gamosoft/features/ui-revamp
Features/UI revamp
This commit is contained in:
commit
cae68cfc3b
|
|
@ -36,6 +36,7 @@ function noteApp() {
|
||||||
appName: 'NoteDiscovery',
|
appName: 'NoteDiscovery',
|
||||||
appTagline: 'Your Self-Hosted Knowledge Base',
|
appTagline: 'Your Self-Hosted Knowledge Base',
|
||||||
appVersion: '0.0.0',
|
appVersion: '0.0.0',
|
||||||
|
authEnabled: false,
|
||||||
notes: [],
|
notes: [],
|
||||||
currentNote: '',
|
currentNote: '',
|
||||||
currentNoteName: '',
|
currentNoteName: '',
|
||||||
|
|
@ -60,6 +61,9 @@ function noteApp() {
|
||||||
currentTheme: 'light',
|
currentTheme: 'light',
|
||||||
availableThemes: [],
|
availableThemes: [],
|
||||||
|
|
||||||
|
// Icon rail / panel state
|
||||||
|
activePanel: 'files', // 'files', 'search', 'tags', 'settings'
|
||||||
|
|
||||||
// Folder state
|
// Folder state
|
||||||
folderTree: [],
|
folderTree: [],
|
||||||
allFolders: [],
|
allFolders: [],
|
||||||
|
|
@ -462,6 +466,7 @@ function noteApp() {
|
||||||
this.appName = config.name;
|
this.appName = config.name;
|
||||||
this.appTagline = config.tagline;
|
this.appTagline = config.tagline;
|
||||||
this.appVersion = config.version || '0.0.0';
|
this.appVersion = config.version || '0.0.0';
|
||||||
|
this.authEnabled = config.authentication?.enabled || false;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load config:', error);
|
console.error('Failed to load config:', error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,150 @@
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icon Rail (VS Code-style navigation) */
|
||||||
|
.icon-rail {
|
||||||
|
width: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
border-right: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-btn:hover {
|
||||||
|
background-color: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-btn.active {
|
||||||
|
background-color: var(--accent-light);
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-btn svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-logo {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rail-logo:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar panel (content area next to icon rail) */
|
||||||
|
.sidebar-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel-header {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile bottom tabs */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.icon-rail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tabs {
|
||||||
|
display: flex !important;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 56px;
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
border-top: 1px solid var(--border-primary);
|
||||||
|
z-index: 1001;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tab {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.15s ease, background-color 0.15s ease;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tab:active {
|
||||||
|
background-color: var(--bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tab.active {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tab svg {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-bottom-tab span {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.mobile-bottom-tabs {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Scrollbar styling */
|
/* Scrollbar styling */
|
||||||
.custom-scrollbar::-webkit-scrollbar,
|
.custom-scrollbar::-webkit-scrollbar,
|
||||||
.editor-textarea::-webkit-scrollbar,
|
.editor-textarea::-webkit-scrollbar,
|
||||||
|
|
@ -817,252 +961,132 @@
|
||||||
<!-- Main Container -->
|
<!-- Main Container -->
|
||||||
<div class="flex h-screen overflow-hidden">
|
<div class="flex h-screen overflow-hidden">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar with Icon Rail -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col mobile-sidebar"
|
class="flex mobile-sidebar"
|
||||||
:class="{'mobile-sidebar-open': mobileSidebarOpen}"
|
:class="{'mobile-sidebar-open': mobileSidebarOpen}"
|
||||||
:style="'width: ' + sidebarWidth + 'px; background-color: var(--bg-secondary); border-right: 1px solid var(--border-primary); min-width: 200px; max-width: 600px;'"
|
:style="'width: ' + sidebarWidth + 'px; background-color: var(--bg-secondary); min-width: 200px; max-width: 600px;'"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Icon Rail -->
|
||||||
<div class="flex-shrink-0 p-4 border-b" style="border-color: var(--border-primary);">
|
<div class="icon-rail">
|
||||||
<button
|
<!-- Logo -->
|
||||||
class="flex items-center gap-2 mb-3 hover:opacity-80 transition"
|
<img
|
||||||
|
src="/static/logo.svg"
|
||||||
|
alt="NoteDiscovery"
|
||||||
|
class="icon-rail-logo"
|
||||||
@click="goHome()"
|
@click="goHome()"
|
||||||
aria-label="Go to homepage"
|
title="Go to homepage"
|
||||||
style="background: none; border: none; outline: none; cursor: pointer;"
|
|
||||||
>
|
>
|
||||||
<img src="/static/logo.svg" alt="NoteDiscovery Logo" class="w-8 h-8">
|
|
||||||
<h1 class="text-xl font-bold" style="color: var(--text-primary);" x-text="appName"></h1>
|
<!-- Files -->
|
||||||
|
<button
|
||||||
|
class="icon-rail-btn"
|
||||||
|
:class="{'active': activePanel === 'files'}"
|
||||||
|
@click="activePanel = 'files'"
|
||||||
|
title="Files"
|
||||||
|
aria-label="Files panel"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- Logout Button (only show if auth is enabled) -->
|
|
||||||
<div x-data="{ authEnabled: false }" x-init="fetch('/api/config').then(r => r.json()).then(d => authEnabled = d.authentication?.enabled || false)">
|
|
||||||
<a x-show="authEnabled"
|
|
||||||
href="/logout"
|
|
||||||
class="flex items-center justify-center gap-2 px-3 py-2 text-sm rounded transition-colors"
|
|
||||||
style="background-color: var(--bg-secondary); color: var(--text-secondary); border: 1px solid var(--border-primary);"
|
|
||||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'; this.style.color='var(--text-primary)'"
|
|
||||||
onmouseout="this.style.backgroundColor='var(--bg-secondary)'; this.style.color='var(--text-secondary)'">
|
|
||||||
<span>🔒</span>
|
|
||||||
<span>Logout</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<div class="flex-shrink-0 px-2 py-2 border-b" style="border-color: var(--border-primary);">
|
<button
|
||||||
<div class="relative">
|
class="icon-rail-btn relative"
|
||||||
<input
|
:class="{'active': activePanel === 'search'}"
|
||||||
type="text"
|
@click="activePanel = 'search'"
|
||||||
x-model="searchQuery"
|
title="Search"
|
||||||
@input="searchNotes()"
|
aria-label="Search panel"
|
||||||
placeholder="Search..."
|
|
||||||
class="w-full px-2 py-1.5 pl-7 pr-7 text-xs rounded focus:outline-none focus:ring-1"
|
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
|
||||||
>
|
>
|
||||||
<!-- Search Icon -->
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<svg class="absolute left-2 top-2 w-3.5 h-3.5" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Clear Button -->
|
<!-- Search results badge -->
|
||||||
<button
|
<span
|
||||||
x-show="searchQuery.length > 0"
|
x-show="searchQuery.trim() && searchResults.length > 0"
|
||||||
@click="searchQuery = ''; searchResults = []; currentSearchHighlight = ''; clearSearchHighlights();"
|
x-cloak
|
||||||
class="absolute right-1.5 top-1.5 w-4 h-4 flex items-center justify-center rounded-full hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
class="absolute -top-1 -right-1 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
|
||||||
style="color: var(--text-tertiary);"
|
style="background-color: var(--accent-primary); color: white;"
|
||||||
title="Clear search"
|
x-text="searchResults.length > 9 ? '9+' : searchResults.length"
|
||||||
>
|
></span>
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Match Navigation (shown when there are matches in current note) -->
|
<!-- Tags -->
|
||||||
<div
|
|
||||||
x-show="totalMatches > 0"
|
|
||||||
class="flex items-center justify-between mt-1.5 px-2 py-0.5 rounded text-xs"
|
|
||||||
style="background-color: var(--bg-secondary); color: var(--text-secondary);"
|
|
||||||
>
|
|
||||||
<span x-text="`Match ${currentMatchIndex + 1} of ${totalMatches}`"></span>
|
|
||||||
<div class="flex gap-1">
|
|
||||||
<button
|
<button
|
||||||
@click="previousMatch()"
|
class="icon-rail-btn relative"
|
||||||
class="px-2 py-1 rounded hover:bg-opacity-80 transition-colors"
|
:class="{'active': activePanel === 'tags'}"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary);"
|
@click="activePanel = 'tags'"
|
||||||
title="Previous match (Shift+F3)"
|
title="Tags"
|
||||||
|
aria-label="Tags panel"
|
||||||
>
|
>
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="nextMatch()"
|
|
||||||
class="px-2 py-1 rounded hover:bg-opacity-80 transition-colors"
|
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary);"
|
|
||||||
title="Next match (F3)"
|
|
||||||
>
|
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tags Panel -->
|
|
||||||
<div class="flex-shrink-0 border-b" style="border-color: var(--border-primary);">
|
|
||||||
<!-- Tags Header -->
|
|
||||||
<div
|
|
||||||
@click="tagsExpanded = !tagsExpanded"
|
|
||||||
class="flex items-center justify-between px-3 py-2 cursor-pointer hover:bg-opacity-10 transition-colors"
|
|
||||||
style="background-color: var(--bg-secondary);"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<svg class="w-4 h-4" style="color: var(--text-tertiary);" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-sm font-medium" style="color: var(--text-primary);">Tags</span>
|
<!-- Filtered notes badge (shows result count when tags selected) -->
|
||||||
<span class="text-xs px-1.5 py-0.5 rounded" style="background-color: var(--bg-tertiary); color: var(--text-tertiary);" x-text="Object.keys(allTags).length"></span>
|
<span
|
||||||
</div>
|
x-show="selectedTags.length > 0"
|
||||||
<svg
|
x-cloak
|
||||||
class="w-4 h-4 transform transition-transform"
|
class="absolute -top-1 -right-1 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
|
||||||
:class="{'rotate-180': tagsExpanded}"
|
|
||||||
style="color: var(--text-tertiary);"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tags List -->
|
|
||||||
<div x-show="tagsExpanded" class="px-3 py-2 max-h-48 overflow-y-auto custom-scrollbar">
|
|
||||||
<!-- Clear Filter Button -->
|
|
||||||
<div x-show="selectedTags.length > 0" class="mb-2">
|
|
||||||
<button
|
|
||||||
@click="clearTagFilters()"
|
|
||||||
class="w-full px-2 py-1 text-xs rounded flex items-center justify-center gap-1"
|
|
||||||
style="background-color: var(--accent-primary); color: white;"
|
style="background-color: var(--accent-primary); color: white;"
|
||||||
title="Clear tag filters"
|
x-text="searchResults.length > 9 ? '9+' : searchResults.length"
|
||||||
>
|
></span>
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
Clear (<span x-text="selectedTags.length"></span>)
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tag Chips -->
|
<div class="icon-rail-spacer"></div>
|
||||||
<div class="flex flex-wrap gap-1.5">
|
|
||||||
<template x-for="[tag, count] in sortedTags" :key="tag">
|
<!-- Graph -->
|
||||||
<button
|
<button
|
||||||
@click="toggleTag(tag)"
|
class="icon-rail-btn"
|
||||||
class="px-2 py-1 text-xs rounded-full transition-all hover:brightness-110"
|
:class="{'active': showGraph}"
|
||||||
:style="selectedTags.includes(tag)
|
@click="showGraph = true; initGraph()"
|
||||||
? 'background-color: var(--accent-primary); color: white; font-weight: 600;'
|
title="Graph View"
|
||||||
: 'background-color: var(--bg-tertiary); color: var(--text-primary);'"
|
aria-label="Graph view"
|
||||||
:title="`Filter by ${tag} (${count} notes)`"
|
|
||||||
>
|
>
|
||||||
<span x-text="tag"></span>
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<span class="opacity-75" x-text="` (${count})`"></span>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Settings -->
|
||||||
|
<button
|
||||||
|
class="icon-rail-btn"
|
||||||
|
:class="{'active': activePanel === 'settings'}"
|
||||||
|
@click="activePanel = 'settings'"
|
||||||
|
title="Settings"
|
||||||
|
aria-label="Settings panel"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Sidebar Panel -->
|
||||||
<div x-show="Object.keys(allTags).length === 0" class="text-xs text-center py-2" style="color: var(--text-tertiary);">
|
<div class="sidebar-panel" style="border-right: 1px solid var(--border-primary);">
|
||||||
No tags found. Add tags to notes using YAML frontmatter:
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- New Item Dropdown -->
|
<!-- ==================== FILES PANEL ==================== -->
|
||||||
<div class="flex-shrink-0 px-2 py-1.5 border-b" style="border-color: var(--border-primary);">
|
<div x-show="activePanel === 'files'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||||
|
<!-- Header with + New -->
|
||||||
|
<div class="flex-shrink-0 px-2 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Files</span>
|
||||||
<button
|
<button
|
||||||
@click="dropdownTargetFolder = ''; toggleNewDropdown($event)"
|
@click="dropdownTargetFolder = ''; toggleNewDropdown($event)"
|
||||||
class="w-full px-3 py-1.5 text-xs font-medium text-white rounded focus:outline-none focus:ring-1 flex items-center justify-center gap-1.5"
|
class="px-2 py-1 text-xs font-medium text-white rounded focus:outline-none flex items-center gap-1"
|
||||||
style="background-color: var(--accent-primary);"
|
style="background-color: var(--accent-primary);"
|
||||||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||||
>
|
>
|
||||||
<span>+ New</span>
|
|
||||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
New
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notes List -->
|
<!-- Folder Tree -->
|
||||||
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
<div class="flex-1 overflow-y-auto custom-scrollbar p-2">
|
||||||
<template x-if="searchResults.length > 0">
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="text-xs px-2 py-1 flex items-center justify-between" style="color: var(--text-tertiary);">
|
|
||||||
<span x-text="searchQuery.trim() ? 'Search Results' : 'Filtered Notes'"></span>
|
|
||||||
<span x-text="`${searchResults.length} note${searchResults.length === 1 ? '' : 's'}`"></span>
|
|
||||||
</div>
|
|
||||||
<template x-for="note in searchResults" :key="note.path">
|
|
||||||
<div
|
|
||||||
@click="openItem(note.path, note.type, searchQuery)"
|
|
||||||
class="px-2 py-1.5 text-sm cursor-pointer rounded"
|
|
||||||
:style="currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
|
|
||||||
onmouseover="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='var(--bg-hover)'"
|
|
||||||
onmouseout="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='transparent'"
|
|
||||||
:title="note.path"
|
|
||||||
>
|
|
||||||
<div class="font-medium truncate" x-text="note.name"></div>
|
|
||||||
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-html="(note.matches && note.matches.length > 0) ? note.matches[0].context : (note.folder || 'Root')"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- No Results Message (when filters active but no matches) -->
|
|
||||||
<template x-if="searchResults.length === 0 && (selectedTags.length > 0 || searchQuery.trim())">
|
|
||||||
<div class="p-6 text-center">
|
|
||||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-50" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
||||||
</svg>
|
|
||||||
<h3 class="text-lg font-medium mb-2" style="color: var(--text-primary);">No matches found</h3>
|
|
||||||
<p class="text-sm mb-4" style="color: var(--text-secondary);">
|
|
||||||
<template x-if="searchQuery.trim() && selectedTags.length > 0">
|
|
||||||
<span>No notes match both your search and selected tags</span>
|
|
||||||
</template>
|
|
||||||
<template x-if="searchQuery.trim() && selectedTags.length === 0">
|
|
||||||
<span>No notes contain "<span x-text="searchQuery"></span>"</span>
|
|
||||||
</template>
|
|
||||||
<template x-if="!searchQuery.trim() && selectedTags.length > 0">
|
|
||||||
<span>No notes have the selected tag<span x-text="selectedTags.length > 1 ? 's' : ''"></span></span>
|
|
||||||
</template>
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2 justify-center">
|
|
||||||
<button
|
|
||||||
x-show="searchQuery.trim()"
|
|
||||||
@click="searchQuery = ''; searchNotes();"
|
|
||||||
class="px-3 py-1.5 text-sm rounded transition-colors"
|
|
||||||
style="background-color: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
|
||||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
|
||||||
onmouseout="this.style.backgroundColor='var(--bg-secondary)'"
|
|
||||||
>
|
|
||||||
Clear search
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
x-show="selectedTags.length > 0"
|
|
||||||
@click="clearTagFilters()"
|
|
||||||
class="px-3 py-1.5 text-sm rounded transition-colors"
|
|
||||||
style="background-color: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
|
||||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
|
||||||
onmouseout="this.style.backgroundColor='var(--bg-secondary)'"
|
|
||||||
>
|
|
||||||
Clear tags
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Folder Tree (when no filters active) -->
|
|
||||||
<template x-if="searchResults.length === 0 && selectedTags.length === 0 && !searchQuery.trim()">
|
|
||||||
<div class="p-2">
|
|
||||||
<div class="text-xs px-2 py-1 mb-2" style="color: var(--text-tertiary);">
|
<div class="text-xs px-2 py-1 mb-2" style="color: var(--text-tertiary);">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<span>Folders & Notes</span>
|
<span>Folders & Notes</span>
|
||||||
|
|
@ -1147,53 +1171,251 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END FILES PANEL -->
|
||||||
|
|
||||||
|
<!-- ==================== SEARCH PANEL ==================== -->
|
||||||
|
<div x-show="activePanel === 'search'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||||
|
<!-- Search Header -->
|
||||||
|
<div class="flex-shrink-0 px-3 py-2 border-b" style="border-color: var(--border-primary);">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Search</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Input -->
|
||||||
|
<div class="flex-shrink-0 px-2 py-2 border-b" style="border-color: var(--border-primary);">
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
x-model="searchQuery"
|
||||||
|
@input="searchNotes()"
|
||||||
|
placeholder="Search notes..."
|
||||||
|
class="w-full px-2 py-1.5 pl-7 pr-7 text-xs rounded focus:outline-none focus:ring-1"
|
||||||
|
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
||||||
|
>
|
||||||
|
<svg class="absolute left-2 top-2 w-3.5 h-3.5" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<button
|
||||||
|
x-show="searchQuery.length > 0"
|
||||||
|
@click="searchQuery = ''; searchResults = []; currentSearchHighlight = ''; clearSearchHighlights();"
|
||||||
|
class="absolute right-1.5 top-1.5 w-4 h-4 flex items-center justify-center rounded-full hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
|
||||||
|
style="color: var(--text-tertiary);"
|
||||||
|
title="Clear search"
|
||||||
|
>
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Match Navigation -->
|
||||||
|
<div
|
||||||
|
x-show="totalMatches > 0"
|
||||||
|
class="flex items-center justify-between mt-1.5 px-2 py-0.5 rounded text-xs"
|
||||||
|
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
|
||||||
|
>
|
||||||
|
<span x-text="`Match ${currentMatchIndex + 1} of ${totalMatches}`"></span>
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<button @click="previousMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" title="Previous (Shift+F3)">
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
|
||||||
|
</button>
|
||||||
|
<button @click="nextMatch()" class="px-2 py-1 rounded hover:bg-opacity-80" style="background-color: var(--bg-primary); color: var(--text-primary);" title="Next (F3)">
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Search Results -->
|
||||||
|
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
||||||
|
<template x-if="searchQuery.trim() && searchResults.length > 0">
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="text-xs px-2 py-1 mb-1" style="color: var(--text-tertiary);">
|
||||||
|
<span x-text="`${searchResults.length} result${searchResults.length === 1 ? '' : 's'}`"></span>
|
||||||
|
</div>
|
||||||
|
<template x-for="note in searchResults" :key="note.path">
|
||||||
|
<div
|
||||||
|
@click="openItem(note.path, note.type, searchQuery)"
|
||||||
|
class="px-2 py-1.5 text-sm cursor-pointer rounded mb-1"
|
||||||
|
:style="currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
|
||||||
|
onmouseover="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='var(--bg-hover)'"
|
||||||
|
onmouseout="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='transparent'"
|
||||||
|
>
|
||||||
|
<div class="font-medium truncate" x-text="note.name"></div>
|
||||||
|
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-html="(note.matches && note.matches.length > 0) ? note.matches[0].context : (note.folder || 'Root')"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="searchQuery.trim() && searchResults.length === 0">
|
||||||
|
<div class="p-6 text-center">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm" style="color: var(--text-tertiary);">No notes contain "<span x-text="searchQuery"></span>"</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!searchQuery.trim()">
|
||||||
|
<div class="p-6 text-center">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm" style="color: var(--text-tertiary);">Type to search your notes</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END SEARCH PANEL -->
|
||||||
|
|
||||||
|
<!-- ==================== TAGS PANEL ==================== -->
|
||||||
|
<div x-show="activePanel === 'tags'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
|
||||||
|
<!-- Tags Header -->
|
||||||
|
<div class="flex-shrink-0 px-3 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Tags</span>
|
||||||
|
<span class="text-xs px-1.5 py-0.5 rounded" style="background-color: var(--bg-tertiary); color: var(--text-tertiary);" x-text="Object.keys(allTags).length"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Filters -->
|
||||||
|
<div x-show="selectedTags.length > 0" class="flex-shrink-0 px-2 py-2 border-b" style="border-color: var(--border-primary);">
|
||||||
|
<button
|
||||||
|
@click="clearTagFilters()"
|
||||||
|
class="w-full px-2 py-1.5 text-xs rounded flex items-center justify-center gap-1"
|
||||||
|
style="background-color: var(--accent-primary); color: white;"
|
||||||
|
title="Clear tag filters"
|
||||||
|
>
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
Clear Filters (<span x-text="selectedTags.length"></span>)
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrollable content: Tags + Filtered Results -->
|
||||||
|
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
||||||
|
<!-- Tag Chips -->
|
||||||
|
<div class="p-3">
|
||||||
|
<template x-if="Object.keys(allTags).length > 0">
|
||||||
|
<div class="flex flex-wrap gap-1.5">
|
||||||
|
<template x-for="[tag, count] in sortedTags" :key="tag">
|
||||||
|
<button
|
||||||
|
@click="toggleTag(tag)"
|
||||||
|
class="px-2 py-1 text-xs rounded-full transition-all hover:brightness-110"
|
||||||
|
:style="selectedTags.includes(tag)
|
||||||
|
? 'background-color: var(--accent-primary); color: white; font-weight: 600;'
|
||||||
|
: 'background-color: var(--bg-tertiary); color: var(--text-primary);'"
|
||||||
|
:title="`Filter by ${tag} (${count} notes)`"
|
||||||
|
>
|
||||||
|
<span x-text="tag"></span>
|
||||||
|
<span class="opacity-75" x-text="` (${count})`"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="Object.keys(allTags).length === 0">
|
||||||
|
<div class="text-center py-6">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
<p class="text-sm mb-2" style="color: var(--text-tertiary);">No tags found</p>
|
||||||
|
<p class="text-xs" style="color: var(--text-tertiary);">Add tags using YAML frontmatter</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Filtered Notes (shown immediately after tags when selected) -->
|
||||||
<div class="flex-shrink-0 p-3 border-t" style="border-color: var(--border-primary);">
|
<template x-if="selectedTags.length > 0">
|
||||||
<!-- Theme Selector and Graph Button -->
|
<div class="border-t" style="border-color: var(--border-primary);">
|
||||||
<div class="flex gap-2 mb-2">
|
<div class="px-3 py-2 text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">
|
||||||
|
Filtered Notes (<span x-text="searchResults.length"></span>)
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<template x-for="note in searchResults" :key="note.path">
|
||||||
|
<div
|
||||||
|
@click="openItem(note.path, note.type)"
|
||||||
|
class="px-3 py-1.5 text-sm cursor-pointer"
|
||||||
|
:style="currentNote === note.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
|
||||||
|
onmouseover="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='var(--bg-hover)'"
|
||||||
|
onmouseout="if(this.style.backgroundColor !== 'var(--accent-light)') this.style.backgroundColor='transparent'"
|
||||||
|
>
|
||||||
|
<span class="truncate block" x-text="note.name"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="searchResults.length === 0" class="px-3 py-4 text-center text-xs" style="color: var(--text-tertiary);">
|
||||||
|
No notes match selected tags
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END TAGS PANEL -->
|
||||||
|
|
||||||
|
<!-- ==================== SETTINGS PANEL ==================== -->
|
||||||
|
<template x-if="activePanel === 'settings'">
|
||||||
|
<div class="flex flex-col h-full">
|
||||||
|
<!-- Settings Header -->
|
||||||
|
<div class="flex-shrink-0 px-3 py-2 border-b" style="border-color: var(--border-primary);">
|
||||||
|
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);">Settings</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scrollable content -->
|
||||||
|
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
||||||
|
<div class="p-3">
|
||||||
|
<!-- Theme -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);">Theme</label>
|
||||||
<select
|
<select
|
||||||
x-model="currentTheme"
|
x-model="currentTheme"
|
||||||
@change="setTheme(currentTheme)"
|
@change="setTheme(currentTheme)"
|
||||||
class="flex-1 px-2 py-1.5 text-xs rounded focus:outline-none focus:ring-2 focus:ring-opacity-50"
|
class="w-full px-2 py-2 text-sm rounded focus:outline-none focus:ring-2 focus:ring-opacity-50"
|
||||||
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); cursor: pointer;"
|
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); cursor: pointer;"
|
||||||
title="Select theme"
|
|
||||||
>
|
>
|
||||||
<template x-for="theme in availableThemes" :key="theme.id">
|
<template x-for="theme in availableThemes" :key="theme.id">
|
||||||
<option :value="theme.id" x-text="theme.name"></option>
|
<option :value="theme.id" x-text="theme.name"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<button
|
|
||||||
@click="showGraph = true; initGraph()"
|
|
||||||
class="px-2 py-1.5 text-xs rounded transition-colors"
|
|
||||||
:style="showGraph ? 'background-color: var(--accent-primary); color: white;' : 'background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);'"
|
|
||||||
title="View note connections graph"
|
|
||||||
>
|
|
||||||
🔗
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Logout (if auth enabled) - uses authEnabled from main app state -->
|
||||||
<div class="text-xs text-center" style="color: var(--text-tertiary);">
|
<div x-show="authEnabled" class="mb-4">
|
||||||
|
<label class="block text-xs font-medium mb-2" style="color: var(--text-secondary);">Account</label>
|
||||||
|
<a
|
||||||
|
href="/logout"
|
||||||
|
class="flex items-center justify-center gap-2 px-3 py-2 text-sm rounded transition-colors"
|
||||||
|
style="background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary);"
|
||||||
|
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||||
|
onmouseout="this.style.backgroundColor='var(--bg-primary)'"
|
||||||
|
>
|
||||||
|
🔒 Logout
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Info (inside scroll area) -->
|
||||||
|
<div class="p-3 mt-4 border-t text-center" style="border-color: var(--border-primary);">
|
||||||
|
<div class="text-xs" style="color: var(--text-tertiary);">
|
||||||
<span x-text="notes.length"></span> notes
|
<span x-text="notes.length"></span> notes
|
||||||
<span class="mx-1">·</span>
|
<span class="mx-1">·</span>
|
||||||
<span x-text="'v' + appVersion"></span>
|
<span x-text="'v' + appVersion"></span>
|
||||||
<span class="mx-1">·</span>
|
</div>
|
||||||
<a
|
<a
|
||||||
href="https://www.notediscovery.com"
|
href="https://www.notediscovery.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="hover:underline"
|
class="text-xs hover:underline"
|
||||||
style="color: var(--accent-primary);"
|
style="color: var(--accent-primary);"
|
||||||
title="Visit NoteDiscovery website"
|
|
||||||
>
|
>
|
||||||
Website
|
notediscovery.com
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- END SETTINGS PANEL -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- END Sidebar Panel -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Resize Handle -->
|
<!-- Resize Handle -->
|
||||||
<div
|
<div
|
||||||
|
|
@ -2023,6 +2245,82 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Bottom Tabs -->
|
||||||
|
<nav class="mobile-bottom-tabs" style="display: none;" role="navigation" aria-label="Main navigation">
|
||||||
|
<button
|
||||||
|
class="mobile-bottom-tab"
|
||||||
|
:class="{'active': activePanel === 'files'}"
|
||||||
|
@click="activePanel = 'files'; mobileSidebarOpen = true;"
|
||||||
|
aria-label="Files"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Files</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mobile-bottom-tab relative"
|
||||||
|
:class="{'active': activePanel === 'search'}"
|
||||||
|
@click="activePanel = 'search'; mobileSidebarOpen = true;"
|
||||||
|
aria-label="Search"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Search</span>
|
||||||
|
<!-- Search results badge -->
|
||||||
|
<span
|
||||||
|
x-show="searchQuery.trim() && searchResults.length > 0"
|
||||||
|
x-cloak
|
||||||
|
class="absolute top-1 right-1/4 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
|
||||||
|
style="background-color: var(--accent-primary); color: white;"
|
||||||
|
x-text="searchResults.length > 9 ? '9+' : searchResults.length"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mobile-bottom-tab relative"
|
||||||
|
:class="{'active': activePanel === 'tags'}"
|
||||||
|
@click="activePanel = 'tags'; mobileSidebarOpen = true;"
|
||||||
|
aria-label="Tags"
|
||||||
|
>
|
||||||
|
<svg fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
|
||||||
|
<path fill-rule="evenodd" d="M17.707 9.293a1 1 0 010 1.414l-7 7a1 1 0 01-1.414 0l-7-7A.997.997 0 012 10V5a3 3 0 013-3h5c.256 0 .512.098.707.293l7 7zM5 6a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
<span>Tags</span>
|
||||||
|
<!-- Filtered notes badge (shows result count when tags selected) -->
|
||||||
|
<span
|
||||||
|
x-show="selectedTags.length > 0"
|
||||||
|
x-cloak
|
||||||
|
class="absolute top-1 right-1/4 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
|
||||||
|
style="background-color: var(--accent-primary); color: white;"
|
||||||
|
x-text="searchResults.length > 9 ? '9+' : searchResults.length"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mobile-bottom-tab"
|
||||||
|
:class="{'active': showGraph}"
|
||||||
|
@click="showGraph = true; initGraph(); mobileSidebarOpen = false;"
|
||||||
|
aria-label="Graph"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Graph</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mobile-bottom-tab"
|
||||||
|
:class="{'active': activePanel === 'settings'}"
|
||||||
|
@click="activePanel = 'settings'; mobileSidebarOpen = true;"
|
||||||
|
aria-label="Settings"
|
||||||
|
>
|
||||||
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Settings</span>
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<script src="/static/app.js"></script>
|
<script src="/static/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -146,3 +146,8 @@ git push origin "v$Version"
|
||||||
Write-Host "`nRelease $Version completed successfully!" -ForegroundColor Green
|
Write-Host "`nRelease $Version completed successfully!" -ForegroundColor Green
|
||||||
Write-Host "Tag: v$Version" -ForegroundColor Cyan
|
Write-Host "Tag: v$Version" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Open GitHub Actions page to monitor build status
|
||||||
|
$actionsUrl = "https://github.com/gamosoft/NoteDiscovery/actions"
|
||||||
|
Write-Host "`nOpening GitHub Actions to monitor build status..." -ForegroundColor Yellow
|
||||||
|
Start-Process $actionsUrl
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue