From 1206e7e50fd097890b4cee5e34ba4aa61ce18d87 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Mon, 6 Apr 2026 16:00:00 +0200 Subject: [PATCH] added sorting option --- documentation/FEATURES.md | 2 +- frontend/app.js | 74 +++++++++++++++++++++++++++++++++++---- frontend/index.html | 14 ++++++-- locales/de-DE.json | 6 ++++ locales/en-GB.json | 6 ++++ locales/en-US.json | 6 ++++ locales/es-ES.json | 6 ++++ locales/fr-FR.json | 6 ++++ locales/hu-HU.json | 6 ++++ locales/it-IT.json | 6 ++++ locales/ja-JP.json | 6 ++++ locales/ru-RU.json | 6 ++++ locales/sl-SI.json | 6 ++++ locales/zh-CN.json | 6 ++++ 14 files changed, 146 insertions(+), 10 deletions(-) diff --git a/documentation/FEATURES.md b/documentation/FEATURES.md index dfb100c..b8ef0db 100644 --- a/documentation/FEATURES.md +++ b/documentation/FEATURES.md @@ -27,7 +27,7 @@ ### Organization - **Folder hierarchy** - Organize notes in nested folders - **Drag & drop** - Move notes and folders effortlessly -- **Alphabetical sorting** - Find notes quickly +- **Flexible sorting** - Sort notes by name (A-Z, Z-A), date (newest, oldest), or size (largest, smallest) - **Rename anything** - Files and folders, instantly - **Visual tree view** - Expandable/collapsible navigation - **Hide system folders** - Toggle to hide `_attachments`, `_templates` and other underscore-prefixed folders from sidebar diff --git a/frontend/app.js b/frontend/app.js index 4acd1a0..ea09c09 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -21,6 +21,8 @@ const LOCAL_SETTINGS = { tagsExpanded: { key: 'tagsExpanded', type: 'boolean', default: false }, hideUnderscoreFolders: { key: 'hideUnderscoreFolders', type: 'boolean', default: false }, tabInsertsTab: { key: 'tabInsertsTab', type: 'boolean', default: false }, + // String settings + sortMode: { key: 'sortMode', type: 'string', default: 'a-z' }, // Number settings with validation sidebarWidth: { key: 'sidebarWidth', type: 'number', default: CONFIG.DEFAULT_SIDEBAR_WIDTH, min: 200, max: 600 }, editorWidth: { key: 'editorWidth', type: 'number', default: 50, min: 20, max: 80 }, @@ -219,6 +221,10 @@ function noteApp() { // Tab key inserts tab character instead of changing focus tabInsertsTab: localStorage.getItem('tabInsertsTab') === 'true', + // Note sorting mode (a-z, z-a, newest, oldest, largest, smallest) + sortMode: localStorage.getItem('sortMode') || 'a-z', + sortKey: 0, // Incremented to force tree re-render + // Icon rail / panel state activePanel: 'files', // 'files', 'search', 'tags', 'settings' @@ -852,6 +858,60 @@ function noteApp() { this.autoSave(); }, + // Sort mode configuration + sortModes: ['a-z', 'z-a', 'newest', 'oldest', 'largest', 'smallest'], + sortModeIcons: { + 'a-z': 'A↓', + 'z-a': 'Z↓', + 'newest': '🕐↓', + 'oldest': '🕐↑', + 'largest': '📄↓', + 'smallest': '📄↑' + }, + + // Cycle through sort modes + cycleSortMode() { + const currentIndex = this.sortModes.indexOf(this.sortMode); + const nextIndex = (currentIndex + 1) % this.sortModes.length; + this.sortMode = this.sortModes[nextIndex]; + localStorage.setItem('sortMode', this.sortMode); + // Rebuild tree to apply new sort order + this.buildFolderTree(); + }, + + // Get current sort icon + getSortIcon() { + return this.sortModeIcons[this.sortMode] || 'A↓'; + }, + + // Get sort comparator based on current mode (for notes) + getSortComparator() { + switch (this.sortMode) { + case 'z-a': + return (a, b) => b.name.toLowerCase().localeCompare(a.name.toLowerCase()); + case 'newest': + return (a, b) => (b.modified || '').localeCompare(a.modified || ''); + case 'oldest': + return (a, b) => (a.modified || '').localeCompare(b.modified || ''); + case 'largest': + return (a, b) => (b.size || 0) - (a.size || 0); + case 'smallest': + return (a, b) => (a.size || 0) - (b.size || 0); + case 'a-z': + default: + return (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()); + } + }, + + // Get sort comparator for folders (only A-Z/Z-A, others default to A-Z) + getFolderSortComparator() { + if (this.sortMode === 'z-a') { + return (a, b) => b.name.toLowerCase().localeCompare(a.name.toLowerCase()); + } + // Default: A-Z for all other modes + return (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()); + }, + // Update syntax highlight overlay (debounced, called on input) updateSyntaxHighlight() { if (!this.syntaxHighlightEnabled) return; @@ -1757,7 +1817,7 @@ function noteApp() { const sortNotes = (obj) => { if (obj.notes && obj.notes.length > 0) { // Create a new sorted array instead of mutating for Alpine reactivity - obj.notes = [...obj.notes].sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + obj.notes = [...obj.notes].sort(this.getSortComparator()); } if (obj.children && Object.keys(obj.children).length > 0) { Object.values(obj.children).forEach(child => sortNotes(child)); @@ -1766,7 +1826,7 @@ function noteApp() { // Sort notes in root (create new array for reactivity) if (tree['__root__'] && tree['__root__'].notes) { - tree['__root__'].notes = [...tree['__root__'].notes].sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + tree['__root__'].notes = [...tree['__root__'].notes].sort(this.getSortComparator()); } // Sort notes in all folders @@ -1886,7 +1946,8 @@ function noteApp() { // Render folder recursively (helper for deep nesting) // Uses data-* attributes to store path/name, avoiding JS string escaping issues - renderFolderRecursive(folder, level = 0, isTopLevel = false) { + // sortKey parameter triggers Alpine re-render when sort mode changes + renderFolderRecursive(folder, level = 0, isTopLevel = false, sortKey = 0) { if (!folder) return ''; let html = ''; @@ -1966,8 +2027,8 @@ function noteApp() { if (folder.children && Object.keys(folder.children).length > 0) { const children = Object.entries(folder.children) .filter(([k, v]) => !this.hideUnderscoreFolders || !v.name.startsWith('_')) - .sort((a, b) => a[1].name.toLowerCase().localeCompare(b[1].name.toLowerCase())); - + .sort((a, b) => this.getFolderSortComparator()(a[1], b[1])); + children.forEach(([childKey, childFolder]) => { html += this.renderFolderRecursive(childFolder, 0, false); }); @@ -2033,7 +2094,8 @@ function noteApp() { }, // Render root-level items (notes and media not in any folder) - renderRootItems() { + // sortKey parameter triggers Alpine re-render when sort mode changes + renderRootItems(sortKey = 0) { const root = this.folderTree['__root__']; if (!root || !root.notes || root.notes.length === 0) { return ''; diff --git a/frontend/index.html b/frontend/index.html index a9ebe8d..1e7aa1f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1591,7 +1591,15 @@
- +