refactor: enhance homepage functionality with improved folder and note handling
- Added file size formatting for notes and improved breadcrumb navigation.
This commit is contained in:
parent
a9e0214b09
commit
da5d82c258
168
frontend/app.js
168
frontend/app.js
|
|
@ -95,81 +95,64 @@ function noteApp() {
|
||||||
// Homepage folder navigation
|
// Homepage folder navigation
|
||||||
selectedHomepageFolder: '',
|
selectedHomepageFolder: '',
|
||||||
|
|
||||||
// Computed properties for homepage (using getters for Alpine reactivity)
|
// Computed-like helpers for homepage (use methods for safer evaluation)
|
||||||
get homepageNotes() {
|
homepageNotes() {
|
||||||
// Safety check - ensure notes is an array
|
if (!this.folderTree || typeof this.folderTree !== 'object') {
|
||||||
if (!Array.isArray(this.notes)) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter notes based on selected folder
|
const folderNode = this.getFolderNode(this.selectedHomepageFolder || '');
|
||||||
if (!this.selectedHomepageFolder) {
|
if (!folderNode || !Array.isArray(folderNode.notes)) {
|
||||||
// Root level - show notes without folder
|
|
||||||
return this.notes.filter(note => !note.folder);
|
|
||||||
} else {
|
|
||||||
// Show notes in selected folder (exact match)
|
|
||||||
return this.notes.filter(note => note.folder === this.selectedHomepageFolder);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get homepageFolders() {
|
|
||||||
// Safety check - ensure allFolders is an array
|
|
||||||
if (!Array.isArray(this.allFolders)) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety check - ensure notes is an array for counting
|
return folderNode.notes;
|
||||||
const notesArray = Array.isArray(this.notes) ? this.notes : [];
|
|
||||||
|
|
||||||
// Get folders for current view
|
|
||||||
if (!this.selectedHomepageFolder) {
|
|
||||||
// Root level - show top-level folders
|
|
||||||
return this.allFolders
|
|
||||||
.filter(folder => !folder.includes('/'))
|
|
||||||
.map(folder => {
|
|
||||||
// Count notes in this folder and all subfolders
|
|
||||||
const noteCount = notesArray.filter(note =>
|
|
||||||
note.folder === folder || (note.folder && note.folder.startsWith(folder + '/'))
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: folder,
|
|
||||||
path: folder,
|
|
||||||
noteCount: noteCount
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Get subfolders of current folder
|
|
||||||
const prefix = this.selectedHomepageFolder + '/';
|
|
||||||
const subfolders = this.allFolders
|
|
||||||
.filter(folder => folder.startsWith(prefix) && folder !== this.selectedHomepageFolder)
|
|
||||||
.map(folder => {
|
|
||||||
// Extract subfolder name (next level after current folder)
|
|
||||||
const relativePath = folder.substring(this.selectedHomepageFolder.length + 1);
|
|
||||||
const subfolderName = relativePath.split('/')[0];
|
|
||||||
const subfolderPath = this.selectedHomepageFolder + '/' + subfolderName;
|
|
||||||
|
|
||||||
// Count notes in this subfolder and all nested subfolders
|
|
||||||
const noteCount = notesArray.filter(note =>
|
|
||||||
note.folder === subfolderPath || (note.folder && note.folder.startsWith(subfolderPath + '/'))
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: subfolderName,
|
|
||||||
path: subfolderPath,
|
|
||||||
noteCount: noteCount
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((folder, index, self) =>
|
|
||||||
// Remove duplicates (same subfolder name)
|
|
||||||
index === self.findIndex(f => f.path === folder.path)
|
|
||||||
);
|
|
||||||
|
|
||||||
return subfolders;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
get homepageBreadcrumb() {
|
// Helper: Format file size nicely
|
||||||
|
formatSize(bytes) {
|
||||||
|
if (bytes === 0 || !bytes) return '0 B';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||||
|
},
|
||||||
|
|
||||||
|
homepageFolders() {
|
||||||
|
if (!this.folderTree || typeof this.folderTree !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let childFolders = [];
|
||||||
|
if (!this.selectedHomepageFolder) {
|
||||||
|
childFolders = Object.entries(this.folderTree)
|
||||||
|
.filter(([key]) => key !== '__root__')
|
||||||
|
.map(([, folder]) => folder);
|
||||||
|
} else {
|
||||||
|
const parentFolder = this.getFolderNode(this.selectedHomepageFolder);
|
||||||
|
if (!parentFolder || !parentFolder.children) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
childFolders = Object.values(parentFolder.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(childFolders) || childFolders.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return childFolders
|
||||||
|
.map(folder => ({
|
||||||
|
name: folder.name,
|
||||||
|
path: folder.path,
|
||||||
|
noteCount: this.calculateFolderNoteCount(folder)
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
||||||
|
},
|
||||||
|
|
||||||
|
homepageBreadcrumb() {
|
||||||
// Always return at least Home breadcrumb
|
// Always return at least Home breadcrumb
|
||||||
// Safety check - ensure we always return an array
|
// Safety check - ensure we always return an array
|
||||||
try {
|
try {
|
||||||
|
|
@ -194,6 +177,56 @@ function noteApp() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getFolderNode(folderPath = '') {
|
||||||
|
if (!this.folderTree || typeof this.folderTree !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!folderPath) {
|
||||||
|
if (this.folderTree['__root__']) {
|
||||||
|
return this.folderTree['__root__'];
|
||||||
|
}
|
||||||
|
return { name: '', path: '', children: {}, notes: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = folderPath.split('/').filter(Boolean);
|
||||||
|
let currentLevel = this.folderTree;
|
||||||
|
let node = null;
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!currentLevel[part]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
node = currentLevel[part];
|
||||||
|
currentLevel = node.children || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateFolderNoteCount(folderNode) {
|
||||||
|
if (!folderNode || typeof folderNode !== 'object') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directNotes = Array.isArray(folderNode.notes) ? folderNode.notes.length : 0;
|
||||||
|
if (!folderNode.children || Object.keys(folderNode.children).length === 0) {
|
||||||
|
return directNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(folderNode.children).reduce(
|
||||||
|
(total, child) => total + this.calculateFolderNoteCount(child),
|
||||||
|
directNotes
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Check if app is empty (no notes and no folders)
|
||||||
|
get isAppEmpty() {
|
||||||
|
const notesArray = Array.isArray(this.notes) ? this.notes : [];
|
||||||
|
const foldersArray = Array.isArray(this.allFolders) ? this.allFolders : [];
|
||||||
|
return notesArray.length === 0 && foldersArray.length === 0;
|
||||||
|
},
|
||||||
|
|
||||||
// Mermaid state cache
|
// Mermaid state cache
|
||||||
lastMermaidTheme: null,
|
lastMermaidTheme: null,
|
||||||
|
|
||||||
|
|
@ -1676,6 +1709,7 @@ function noteApp() {
|
||||||
const note = this.notes.find(n => n.path === this.currentNote);
|
const note = this.notes.find(n => n.path === this.currentNote);
|
||||||
if (note) {
|
if (note) {
|
||||||
note.modified = new Date().toISOString();
|
note.modified = new Date().toISOString();
|
||||||
|
note.size = new Blob([this.noteContent]).size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide "saved" indicator
|
// Hide "saved" indicator
|
||||||
|
|
|
||||||
|
|
@ -720,10 +720,15 @@
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="p-4 border-b" style="border-color: var(--border-primary);">
|
<div class="p-4 border-b" style="border-color: var(--border-primary);">
|
||||||
<div class="flex items-center gap-2 mb-3">
|
<button
|
||||||
|
class="flex items-center gap-2 mb-3 hover:opacity-80 transition"
|
||||||
|
@click="goToHomepageFolder(''); currentNote = ''; currentNoteName = ''; noteContent = ''; mobileSidebarOpen = false"
|
||||||
|
aria-label="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">
|
<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>
|
<h1 class="text-xl font-bold" style="color: var(--text-primary);" x-text="appName"></h1>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<!-- Theme Selector -->
|
<!-- Theme Selector -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
@ -992,148 +997,170 @@
|
||||||
@mouseout="if(!isResizing) $el.style.backgroundColor='transparent'"
|
@mouseout="if(!isResizing) $el.style.backgroundColor='transparent'"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Main Content Area -->
|
<!-- Main Content Area -->
|
||||||
<div class="flex-1 flex flex-col overflow-hidden">
|
<div class="flex-1 flex flex-col overflow-hidden">
|
||||||
|
|
||||||
<template x-if="!currentNote">
|
<template x-if="!currentNote">
|
||||||
<!-- Notes Homepage - Grid View -->
|
<!-- Notes Homepage -->
|
||||||
<div class="flex-1 overflow-y-auto custom-scrollbar" style="background-color: var(--bg-primary);">
|
<div class="flex-1 overflow-y-auto custom-scrollbar" style="background-color: var(--bg-primary);">
|
||||||
<div class="max-w-7xl mx-auto p-6 md:p-8">
|
<!-- Welcome Hero - Show when app is empty -->
|
||||||
<!-- Header -->
|
<template x-if="isAppEmpty">
|
||||||
<div class="mb-6">
|
<div class="flex flex-col items-center justify-center h-full py-16 text-center">
|
||||||
<h1 class="text-3xl font-bold mb-2" style="color: var(--text-primary);" x-text="appName"></h1>
|
<svg class="mx-auto h-16 w-16 mb-4" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<p class="text-lg" style="color: var(--text-secondary);" x-text="appTagline"></p>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h1 class="text-4xl font-bold mb-4" style="color: var(--text-primary);" x-text="appName"></h1>
|
||||||
|
<p class="text-xl mb-8" style="color: var(--text-secondary);" x-text="appTagline"></p>
|
||||||
|
<button
|
||||||
|
@click="createNote()"
|
||||||
|
class="px-8 py-4 text-base font-medium text-white rounded-lg transition-colors"
|
||||||
|
style="background-color: var(--accent-primary);"
|
||||||
|
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||||
|
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||||
|
>
|
||||||
|
Create Your First Note
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Breadcrumb Navigation -->
|
<!-- Grid View - Show when app has content -->
|
||||||
<div class="mb-4 flex items-center gap-2 text-sm" style="color: var(--text-secondary);">
|
<template x-if="!isAppEmpty">
|
||||||
<template x-if="homepageBreadcrumb && Array.isArray(homepageBreadcrumb)">
|
<div class="max-w-7xl mx-auto p-6 md:p-8">
|
||||||
<template x-for="(crumb, index) in homepageBreadcrumb" :key="index">
|
<!-- Header -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="mb-6">
|
||||||
<span
|
<h1 class="text-3xl font-bold mb-2" style="color: var(--text-primary);" x-text="appName"></h1>
|
||||||
x-show="index > 0"
|
<p class="text-lg" style="color: var(--text-secondary);" x-text="appTagline"></p>
|
||||||
class="text-xs"
|
</div>
|
||||||
style="color: var(--text-tertiary);"
|
|
||||||
>/</span>
|
<!-- Breadcrumb Navigation -->
|
||||||
<span
|
<div class="mb-4 flex items-center gap-2 text-sm" style="color: var(--text-secondary);">
|
||||||
@click="goToHomepageFolder(crumb.path)"
|
<template x-if="Array.isArray(homepageBreadcrumb())">
|
||||||
class="cursor-pointer hover:underline"
|
<template x-for="(crumb, index) in homepageBreadcrumb()" :key="index">
|
||||||
:style="crumb.path === selectedHomepageFolder ? 'color: var(--accent-primary); font-weight: 600;' : 'color: var(--text-secondary);'"
|
<div class="flex items-center gap-2">
|
||||||
x-text="crumb.name"
|
<span
|
||||||
></span>
|
x-show="index > 0"
|
||||||
</div>
|
class="text-xs"
|
||||||
|
style="color: var(--text-tertiary);"
|
||||||
|
>/</span>
|
||||||
|
<span
|
||||||
|
@click="goToHomepageFolder(crumb.path)"
|
||||||
|
class="cursor-pointer hover:underline"
|
||||||
|
:style="crumb.path === selectedHomepageFolder ? 'color: var(--accent-primary); font-weight: 600;' : 'color: var(--text-secondary);'"
|
||||||
|
x-text="crumb.name"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stats Bar - Only show when there are notes or folders -->
|
<!-- Stats Bar - Only show when there are notes or folders -->
|
||||||
<template x-if="homepageNotes.length > 0 || homepageFolders.length > 0">
|
<template x-if="homepageNotes().length > 0 || homepageFolders().length > 0">
|
||||||
<div class="mb-6 p-4 rounded-lg" style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary);">
|
<div class="mb-6 p-4 rounded-lg" style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary);">
|
||||||
<div class="flex flex-wrap items-center gap-4 text-sm" style="color: var(--text-secondary);">
|
<div class="flex flex-wrap items-center gap-4 text-sm" style="color: var(--text-secondary);">
|
||||||
<span class="font-semibold" style="color: var(--text-primary);">
|
<span class="font-semibold" style="color: var(--text-primary);">
|
||||||
<span x-text="homepageNotes.length"></span>
|
<span x-text="homepageNotes().length"></span>
|
||||||
<span x-text="homepageNotes.length === 1 ? 'note' : 'notes'"></span>
|
<span x-text="homepageNotes().length === 1 ? 'note' : 'notes'"></span>
|
||||||
</span>
|
</span>
|
||||||
<span x-show="homepageFolders.length > 0">
|
<span x-show="homepageFolders().length > 0">
|
||||||
<span x-text="homepageFolders.length"></span>
|
<span x-text="homepageFolders().length"></span>
|
||||||
<span x-text="homepageFolders.length === 1 ? 'folder' : 'folders'"></span>
|
<span x-text="homepageFolders().length === 1 ? 'folder' : 'folders'"></span>
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
@click="createNote()"
|
||||||
|
class="ml-auto px-4 py-2 text-sm font-medium text-white rounded-lg transition-colors"
|
||||||
|
style="background-color: var(--accent-primary);"
|
||||||
|
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||||
|
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||||
|
>
|
||||||
|
+ New Note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Empty State (for folder view) -->
|
||||||
|
<template x-if="homepageNotes().length === 0 && homepageFolders().length === 0">
|
||||||
|
<div class="flex flex-col items-center justify-center py-16 text-center">
|
||||||
|
<svg class="mx-auto h-24 w-24 mb-4" style="color: var(--text-tertiary); opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||||
|
</svg>
|
||||||
|
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);">No notes yet</h2>
|
||||||
|
<p class="mb-6" style="color: var(--text-secondary);">Create your first note to get started</p>
|
||||||
<button
|
<button
|
||||||
@click="createNote()"
|
@click="createNote()"
|
||||||
class="ml-auto px-4 py-2 text-sm font-medium text-white rounded-lg transition-colors"
|
class="px-6 py-3 text-sm font-medium text-white rounded-lg transition-colors"
|
||||||
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)'"
|
||||||
>
|
>
|
||||||
+ New Note
|
Create Your First Note
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Folders and Notes Grid -->
|
||||||
<template x-if="homepageNotes.length === 0 && homepageFolders.length === 0">
|
<template x-if="homepageNotes().length > 0 || homepageFolders().length > 0">
|
||||||
<div class="flex flex-col items-center justify-center py-16 text-center">
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
||||||
<svg class="mx-auto h-24 w-24 mb-4" style="color: var(--text-tertiary); opacity: 0.5;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<!-- Folders First -->
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
<template x-for="folder in homepageFolders()" :key="folder.path">
|
||||||
</svg>
|
<div
|
||||||
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);">No notes yet</h2>
|
@click="goToHomepageFolder(folder.path)"
|
||||||
<p class="mb-6" style="color: var(--text-secondary);">Create your first note to get started</p>
|
class="p-4 rounded-lg border-2 cursor-pointer transition-all hover:shadow-lg"
|
||||||
<button
|
style="background-color: var(--bg-secondary); border-color: var(--accent-primary); min-height: 140px; display: flex; flex-direction: column; border-width: 2px;"
|
||||||
@click="createNote()"
|
onmouseover="this.style.borderColor='var(--accent-hover)'; this.style.transform='translateY(-2px)'"
|
||||||
class="px-6 py-3 text-sm font-medium text-white rounded-lg transition-colors"
|
onmouseout="this.style.borderColor='var(--accent-primary)'; this.style.transform='translateY(0)'"
|
||||||
style="background-color: var(--accent-primary);"
|
>
|
||||||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
<!-- Folder Icon -->
|
||||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
<div class="mb-2 flex items-center gap-2">
|
||||||
>
|
<svg class="w-6 h-6" style="color: var(--accent-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
Create Your First Note
|
<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>
|
||||||
</button>
|
</svg>
|
||||||
</div>
|
<h3 class="font-semibold text-base" style="color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;" x-text="folder.name"></h3>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<!-- Folders and Notes Grid -->
|
<!-- Folder Metadata -->
|
||||||
<template x-if="homepageNotes.length > 0 || homepageFolders.length > 0">
|
<div class="flex items-center justify-between text-xs mt-auto pt-2" style="color: var(--text-tertiary);">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
|
<span x-text="folder.noteCount + ' ' + (folder.noteCount === 1 ? 'note' : 'notes')"></span>
|
||||||
<!-- Folders First -->
|
<svg class="w-4 h-4" style="color: var(--accent-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<template x-for="folder in homepageFolders" :key="folder.path">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
<div
|
</svg>
|
||||||
@click="goToHomepageFolder(folder.path)"
|
</div>
|
||||||
class="p-4 rounded-lg border-2 cursor-pointer transition-all hover:shadow-lg"
|
|
||||||
style="background-color: var(--bg-secondary); border-color: var(--accent-primary); min-height: 140px; display: flex; flex-direction: column; border-width: 2px;"
|
|
||||||
onmouseover="this.style.borderColor='var(--accent-hover)'; this.style.transform='translateY(-2px)'"
|
|
||||||
onmouseout="this.style.borderColor='var(--accent-primary)'; this.style.transform='translateY(0)'"
|
|
||||||
>
|
|
||||||
<!-- Folder Icon -->
|
|
||||||
<div class="mb-2 flex items-center gap-2">
|
|
||||||
<svg class="w-6 h-6" style="color: var(--accent-primary);" 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>
|
|
||||||
<h3 class="font-semibold text-base" style="color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;" x-text="folder.name"></h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Folder Metadata -->
|
<!-- Notes -->
|
||||||
<div class="flex items-center justify-between text-xs mt-auto pt-2" style="color: var(--text-tertiary);">
|
<template x-for="note in homepageNotes().slice(0, 50)" :key="note.path">
|
||||||
<span x-text="folder.noteCount + ' ' + (folder.noteCount === 1 ? 'note' : 'notes')"></span>
|
<div
|
||||||
<svg class="w-4 h-4" style="color: var(--accent-primary);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
@click="loadNote(note.path)"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
class="p-4 rounded-lg border-2 cursor-pointer transition-all hover:shadow-lg"
|
||||||
</svg>
|
style="background-color: var(--bg-secondary); border-color: var(--border-primary); min-height: 140px; display: flex; flex-direction: column;"
|
||||||
|
onmouseover="this.style.borderColor='var(--accent-primary)'; this.style.transform='translateY(-2px)'"
|
||||||
|
onmouseout="this.style.borderColor='var(--border-primary)'; this.style.transform='translateY(0)'"
|
||||||
|
>
|
||||||
|
<!-- Note Title -->
|
||||||
|
<h3 class="font-semibold text-base mb-2" style="color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;" x-text="note.name"></h3>
|
||||||
|
|
||||||
|
<!-- Metadata -->
|
||||||
|
<div class="flex items-center justify-between text-xs mt-auto pt-2" style="color: var(--text-tertiary);">
|
||||||
|
<span x-text="new Date(note.modified).toLocaleDateString()"></span>
|
||||||
|
<span x-text="formatSize(note.size)"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Notes -->
|
<!-- Show More Indicator -->
|
||||||
<template x-for="note in homepageNotes.slice(0, 50)" :key="note.path">
|
<div x-show="homepageNotes().length > 50" class="mt-6 text-center">
|
||||||
<div
|
<p class="text-sm" style="color: var(--text-secondary);">
|
||||||
@click="loadNote(note.path)"
|
Showing 50 of <span x-text="homepageNotes().length"></span> notes.
|
||||||
class="p-4 rounded-lg border-2 cursor-pointer transition-all hover:shadow-lg"
|
<span class="cursor-pointer underline" style="color: var(--accent-primary);" @click="goToHomepageFolder('')">
|
||||||
style="background-color: var(--bg-secondary); border-color: var(--border-primary); min-height: 140px; display: flex; flex-direction: column;"
|
View all in sidebar
|
||||||
onmouseover="this.style.borderColor='var(--accent-primary)'; this.style.transform='translateY(-2px)'"
|
</span>
|
||||||
onmouseout="this.style.borderColor='var(--border-primary)'; this.style.transform='translateY(0)'"
|
</p>
|
||||||
>
|
|
||||||
<!-- Note Title -->
|
|
||||||
<h3 class="font-semibold text-base mb-2" style="color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;" x-text="note.name"></h3>
|
|
||||||
|
|
||||||
<!-- Metadata -->
|
|
||||||
<div class="flex items-center justify-between text-xs mt-auto pt-2" style="color: var(--text-tertiary);">
|
|
||||||
<span x-text="new Date(note.modified).toLocaleDateString()"></span>
|
|
||||||
<span x-text="(note.size / 1024).toFixed(1) + ' KB'"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Show More Indicator -->
|
|
||||||
<div x-show="homepageNotes.length > 50" class="mt-6 text-center">
|
|
||||||
<p class="text-sm" style="color: var(--text-secondary);">
|
|
||||||
Showing 50 of <span x-text="homepageNotes.length"></span> notes.
|
|
||||||
<span class="cursor-pointer underline" style="color: var(--accent-primary);" @click="goToHomepageFolder('')">
|
|
||||||
View all in sidebar
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue