From 4bbd6f7d9d9f507fbf75709e1b5dfc41492c5c0a Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 15 Jan 2026 16:32:35 +0100 Subject: [PATCH 1/8] added quick search --- docs/index.html | 8 +++ documentation/FEATURES.md | 5 +- frontend/app.js | 108 ++++++++++++++++++++++++++++++++++++++ frontend/index.html | 66 +++++++++++++++++++++++ locales/de-DE.json | 9 ++++ locales/en-GB.json | 9 ++++ locales/en-US.json | 9 ++++ locales/es-ES.json | 9 ++++ locales/fr-FR.json | 9 ++++ locales/it-IT.json | 9 ++++ locales/ja-JP.json | 9 ++++ locales/ru-RU.json | 9 ++++ locales/sl-SI.json | 9 ++++ locales/zh-CN.json | 9 ++++ 14 files changed, 276 insertions(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index fc519be..e67a79f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -972,6 +972,14 @@ +
+ 🔍 +
+ Quick Switcher + Jump to any note with Ctrl+Alt+P +
+
+
🔐
diff --git a/documentation/FEATURES.md b/documentation/FEATURES.md index 0c52d73..2a3e674 100644 --- a/documentation/FEATURES.md +++ b/documentation/FEATURES.md @@ -286,6 +286,7 @@ date: {{date}} | Windows/Linux | Mac | Action | |---------------|-----|--------| +| `Ctrl+Alt+P` | `Cmd+Option+P` | Quick Switcher (jump to any note) | | `Ctrl+S` | `Cmd+S` | Save note | | `Ctrl+Alt+N` | `Cmd+Option+N` | New note | | `Ctrl+Alt+F` | `Cmd+Option+F` | New folder | @@ -304,9 +305,11 @@ date: {{date}} |---------------|-----|--------|--------| | `Ctrl+B` | `Cmd+B` | Bold | `**text**` | | `Ctrl+I` | `Cmd+I` | Italic | `*text*` | -| `Ctrl+K` | `Cmd+K` | Insert link | `[text](url)` | +| `Ctrl+K` | `Cmd+K` | Insert link (in editor) | `[text](url)` | | `Ctrl+Alt+T` | `Cmd+Option+T` | Insert table | 3x3 table placeholder | +> **Tip:** Use `Ctrl+Alt+P` to quickly jump to any note from anywhere in the app. + ## 🧘 Zen Mode Full immersive distraction-free writing experience: diff --git a/frontend/app.js b/frontend/app.js index 592fd2f..7ad6f66 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -262,6 +262,12 @@ function noteApp() { shareLinkCopied: false, _sharedNotePaths: new Set(), // O(1) lookup for shared note indicators + // Quick Switcher state (Ctrl+Alt+P) + showQuickSwitcher: false, + quickSwitcherQuery: '', + quickSwitcherIndex: 0, + quickSwitcherResults: [], + // Homepage state selectedHomepageFolder: '', _homepageCache: { @@ -569,6 +575,13 @@ function noteApp() { this.saveNote(); } + // Ctrl/Cmd + Alt + P for Quick Switcher + if ((e.ctrlKey || e.metaKey) && e.altKey && e.code === 'KeyP') { + e.preventDefault(); + this.openQuickSwitcher(); + return; + } + // Ctrl/Cmd + Alt/Option + N for new note if ((e.ctrlKey || e.metaKey) && e.altKey && e.code === 'KeyN') { e.preventDefault(); @@ -5040,6 +5053,101 @@ function noteApp() { return this._sharedNotePaths.has(notePath); }, + // ============================================ + // Quick Switcher (Ctrl+Alt+P) + // ============================================ + + openQuickSwitcher() { + this.showQuickSwitcher = true; + this.quickSwitcherQuery = ''; + this.quickSwitcherIndex = 0; + // Populate initial results + this.quickSwitcherResults = (this.allNotes || []).slice(0, 10); + // Focus the input after the modal renders + this.$nextTick(() => { + const input = document.getElementById('quickSwitcherInput'); + if (input) input.focus(); + }); + }, + + closeQuickSwitcher() { + this.showQuickSwitcher = false; + this.quickSwitcherQuery = ''; + this.quickSwitcherIndex = 0; + }, + + // Filter notes for quick switcher based on query + filterQuickSwitcher(query) { + // Only include actual notes, not images + const notes = (this.notes || []).filter(n => n.type === 'note'); + if (!query || !query.trim()) { + // Show recent notes when no query + return notes.slice(0, 10); + } + const q = query.toLowerCase(); + return notes + .filter(n => + n.name.toLowerCase().includes(q) || + n.path.toLowerCase().includes(q) + ) + .slice(0, 10); + }, + + // Handle keyboard navigation in quick switcher + handleQuickSwitcherKeydown(e) { + const results = this.quickSwitcherResults; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + this.quickSwitcherIndex = Math.min(this.quickSwitcherIndex + 1, results.length - 1); + this.scrollQuickSwitcherIntoView(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + this.quickSwitcherIndex = Math.max(this.quickSwitcherIndex - 1, 0); + this.scrollQuickSwitcherIntoView(); + } else if (e.key === 'Enter') { + e.preventDefault(); + const note = results[this.quickSwitcherIndex]; + if (note) { + this.loadNote(note.path); + this.closeQuickSwitcher(); + } + } else if (e.key === 'Escape') { + e.preventDefault(); + this.closeQuickSwitcher(); + } + }, + + // Scroll selected item into view in quick switcher + scrollQuickSwitcherIntoView() { + const container = document.getElementById('quickSwitcherResults'); + if (!container) return; + + // Get all result items (skip the "no results" template) + const items = container.querySelectorAll('[data-quick-switcher-item]'); + const selectedItem = items[this.quickSwitcherIndex]; + + if (selectedItem) { + // Calculate if we need to scroll + const containerRect = container.getBoundingClientRect(); + const itemRect = selectedItem.getBoundingClientRect(); + + if (itemRect.top < containerRect.top) { + // Item is above visible area + container.scrollTop -= (containerRect.top - itemRect.top); + } else if (itemRect.bottom > containerRect.bottom) { + // Item is below visible area + container.scrollTop += (itemRect.bottom - containerRect.bottom); + } + } + }, + + // Select note from quick switcher by click + selectQuickSwitcherNote(note) { + this.loadNote(note.path); + this.closeQuickSwitcher(); + }, + // Open share modal and fetch current share status async openShareModal() { if (!this.currentNote) return; diff --git a/frontend/index.html b/frontend/index.html index d30edf3..3802665 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2784,6 +2784,72 @@
+ +
+
+ + +
+ +
+ + +
+ + + + +
+ +
+
+ + +
+ ↑↓ + + esc +
+
+
+
Date: Thu, 15 Jan 2026 16:42:34 +0100 Subject: [PATCH 2/8] removed actual shortcut --- docs/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index e67a79f..d12aee6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -976,7 +976,7 @@ 🔍
Quick Switcher - Jump to any note with Ctrl+Alt+P + Jump to any note with a shortcut
From 9e842ecfb027b8c8fefedba82780cdb7714ccac5 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 15 Jan 2026 17:15:44 +0100 Subject: [PATCH 3/8] attempt to fix scroll --- frontend/app.js | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 7ad6f66..c7850e4 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -5120,26 +5120,12 @@ function noteApp() { // Scroll selected item into view in quick switcher scrollQuickSwitcherIntoView() { - const container = document.getElementById('quickSwitcherResults'); - if (!container) return; - - // Get all result items (skip the "no results" template) - const items = container.querySelectorAll('[data-quick-switcher-item]'); - const selectedItem = items[this.quickSwitcherIndex]; - - if (selectedItem) { - // Calculate if we need to scroll - const containerRect = container.getBoundingClientRect(); - const itemRect = selectedItem.getBoundingClientRect(); - - if (itemRect.top < containerRect.top) { - // Item is above visible area - container.scrollTop -= (containerRect.top - itemRect.top); - } else if (itemRect.bottom > containerRect.bottom) { - // Item is below visible area - container.scrollTop += (itemRect.bottom - containerRect.bottom); + this.$nextTick(() => { + const items = document.querySelectorAll('[data-quick-switcher-item]'); + if (items[this.quickSwitcherIndex]) { + items[this.quickSwitcherIndex].scrollIntoView({ block: 'nearest' }); } - } + }); }, // Select note from quick switcher by click From f50d8288fc3cfcef7697e060375e5d6614c2eb43 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 15 Jan 2026 18:19:02 +0100 Subject: [PATCH 4/8] fixed drag & drop notes/images! --- frontend/app.js | 61 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index c7850e4..b9fd8d7 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1998,23 +1998,54 @@ function noteApp() { event.preventDefault(); this.dropTarget = 'editor'; - // Update cursor position as user drags over text + // Focus the textarea const textarea = event.target; - const textLength = textarea.value.length; + if (textarea.tagName !== 'TEXTAREA') return; - // Calculate approximate cursor position based on mouse position - // This gives a rough idea of where the link will be inserted textarea.focus(); - // Try to set cursor at click position (works in most browsers) - if (textarea.setSelectionRange && document.caretPositionFromPoint) { - const pos = document.caretPositionFromPoint(event.clientX, event.clientY); - if (pos && pos.offsetNode === textarea) { - textarea.setSelectionRange(pos.offset, pos.offset); - } + // Calculate cursor position from mouse coordinates + const pos = this.getTextareaCursorFromPoint(textarea, event.clientX, event.clientY); + if (pos >= 0) { + textarea.setSelectionRange(pos, pos); } }, + // Calculate textarea cursor position from mouse coordinates + getTextareaCursorFromPoint(textarea, x, y) { + const rect = textarea.getBoundingClientRect(); + const style = window.getComputedStyle(textarea); + const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2; + const paddingTop = parseFloat(style.paddingTop) || 0; + const paddingLeft = parseFloat(style.paddingLeft) || 0; + + // Calculate which line we're on + const relativeY = y - rect.top - paddingTop + textarea.scrollTop; + const lineIndex = Math.max(0, Math.floor(relativeY / lineHeight)); + + // Split content into lines + const lines = textarea.value.split('\n'); + + // Find the character position at the start of this line + let charPos = 0; + for (let i = 0; i < Math.min(lineIndex, lines.length); i++) { + charPos += lines[i].length + 1; // +1 for newline + } + + // If we're beyond the last line, position at end + if (lineIndex >= lines.length) { + return textarea.value.length; + } + + // Approximate character position within the line based on X coordinate + const relativeX = x - rect.left - paddingLeft; + const charWidth = parseFloat(style.fontSize) * 0.6; // Approximate for monospace + const charInLine = Math.max(0, Math.floor(relativeX / charWidth)); + const lineLength = lines[lineIndex]?.length || 0; + + return charPos + Math.min(charInLine, lineLength); + }, + // Handle dragenter on editor onEditorDragEnter(event) { if (!this.draggedItem) return; @@ -2060,9 +2091,11 @@ function noteApp() { link = `[${noteName}](${encodedPath})`; } - // Insert at cursor position + // Insert at drop position const textarea = event.target; - const cursorPos = textarea.selectionStart || 0; + // Recalculate position from drop coordinates for accuracy + let cursorPos = this.getTextareaCursorFromPoint(textarea, event.clientX, event.clientY); + if (cursorPos < 0) cursorPos = textarea.selectionStart || 0; const textBefore = this.noteContent.substring(0, cursorPos); const textAfter = this.noteContent.substring(cursorPos); @@ -2100,7 +2133,9 @@ function noteApp() { } const textarea = event.target; - const cursorPos = textarea.selectionStart || 0; + // Calculate cursor position from drop coordinates + let cursorPos = this.getTextareaCursorFromPoint(textarea, event.clientX, event.clientY); + if (cursorPos < 0) cursorPos = textarea.selectionStart || 0; // Upload each image for (const file of imageFiles) { From 05768957bf6469ad170781c4210c2ba4480bc56e Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Thu, 15 Jan 2026 18:38:30 +0100 Subject: [PATCH 5/8] fixed image paste from clipboard --- frontend/app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index b9fd8d7..4820983 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2142,7 +2142,7 @@ function noteApp() { try { const imagePath = await this.uploadImage(file, this.currentNote); if (imagePath) { - this.insertImageMarkdown(imagePath, file.name, cursorPos); + await this.insertImageMarkdown(imagePath, file.name, cursorPos); } } catch (error) { ErrorHandler.handle(`upload image ${file.name}`, error); @@ -2176,7 +2176,7 @@ function noteApp() { // Insert image markdown at cursor position using wiki-style syntax // This ensures image links don't break when notes are moved - insertImageMarkdown(imagePath, altText, cursorPos) { + async insertImageMarkdown(imagePath, altText, cursorPos) { // Extract just the filename from the path (e.g., "folder/_attachments/image.png" -> "image.png") const filename = imagePath.split('/').pop(); @@ -2190,6 +2190,9 @@ function noteApp() { ? `![[${filename}|${altWithoutExt}]]` : `![[${filename}]]`; + // Reload notes FIRST to update image lookup maps before preview renders + await this.loadNotes(); + const textBefore = this.noteContent.substring(0, cursorPos); const textAfter = this.noteContent.substring(cursorPos); @@ -2197,9 +2200,6 @@ function noteApp() { // Trigger autosave this.autoSave(); - - // Reload notes to show the new image in sidebar and update lookup maps - this.loadNotes(); }, // Handle paste event for clipboard images @@ -2228,7 +2228,7 @@ function noteApp() { const imagePath = await this.uploadImage(file, this.currentNote); if (imagePath) { - this.insertImageMarkdown(imagePath, filename, cursorPos); + await this.insertImageMarkdown(imagePath, filename, cursorPos); } } catch (error) { ErrorHandler.handle('paste image', error); From 372d82f711b1a2c7111c3b995454e729c92a1aec Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Fri, 16 Jan 2026 11:24:09 +0100 Subject: [PATCH 6/8] added pikapods --- README.md | 19 +++++++++++-------- docs/demo-button.svg | 10 ++++++++++ docs/index.html | 12 ++++++++++++ docs/website-button.svg | 10 ++++++++++ frontend/index.html | 13 ++++++++++++- locales/de-DE.json | 3 ++- locales/en-GB.json | 3 ++- locales/en-US.json | 3 ++- locales/es-ES.json | 3 ++- locales/fr-FR.json | 3 ++- locales/it-IT.json | 3 ++- locales/ja-JP.json | 3 ++- locales/ru-RU.json | 3 ++- locales/sl-SI.json | 3 ++- locales/zh-CN.json | 3 ++- 15 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 docs/demo-button.svg create mode 100644 docs/website-button.svg diff --git a/README.md b/README.md index c8b55dd..e71f591 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ > Your Self-Hosted Knowledge Base -🌐 **[Visit the official website](https://www.notediscovery.com)** - -🚀 **[Try the Live Demo](https://gamosoft-notediscovery-demo.hf.space)** — *Contents reset daily, for demonstration purposes only* - ## What is NoteDiscovery? NoteDiscovery is a **lightweight, self-hosted note-taking application** that puts you in complete control of your knowledge base. Write, organize, and discover your notes with a beautiful, modern interface—all running on your own server. @@ -27,13 +23,20 @@ NoteDiscovery is a **lightweight, self-hosted note-taking application** that put - **Teams** looking for a self-hosted alternative to commercial apps - **Anyone** who values simplicity, speed, and ownership - -## 💖 Thanks for using NoteDiscovery! -If this project has been useful to you, consider supporting its development, it truly makes a difference! +---

- Buy Me a Coffee at ko-fi.com + Official Website +    + Try Live Demo

+

+ Run on PikaPods +    + Buy Me a Coffee at ko-fi.com +

+ +--- ## ✨ Why NoteDiscovery? diff --git a/docs/demo-button.svg b/docs/demo-button.svg new file mode 100644 index 0000000..5880cb8 --- /dev/null +++ b/docs/demo-button.svg @@ -0,0 +1,10 @@ + + + + + + + + + 🚀 Try Live Demo + diff --git a/docs/index.html b/docs/index.html index d12aee6..615e5ad 100644 --- a/docs/index.html +++ b/docs/index.html @@ -695,6 +695,12 @@ Get Started on GitHub →

✨ Demo resets daily · For demonstration purposes only

+
+

☁️ Or deploy your own instance in one click:

+ + Run on PikaPods + +
@@ -1015,6 +1021,12 @@ View on GitHub →

✨ Demo resets daily · For demonstration purposes only

+
+

☁️ Don't want to self-host? Deploy on PikaPods:

+ + Run on PikaPods + +
diff --git a/docs/website-button.svg b/docs/website-button.svg new file mode 100644 index 0000000..ded8c18 --- /dev/null +++ b/docs/website-button.svg @@ -0,0 +1,10 @@ + + + + + + + + + 🌐 Official Website + diff --git a/frontend/index.html b/frontend/index.html index 3802665..b91bdfd 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1795,7 +1795,7 @@ diff --git a/locales/de-DE.json b/locales/de-DE.json index 76a5648..ec9d675 100644 --- a/locales/de-DE.json +++ b/locales/de-DE.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Unterstütze dieses Projekt" + "enjoying_demo": "Unterstütze dieses Projekt", + "deploy_own": "Eigene Instanz bereitstellen" }, "demo": { diff --git a/locales/en-GB.json b/locales/en-GB.json index 0577efd..d3cea60 100644 --- a/locales/en-GB.json +++ b/locales/en-GB.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Support this project" + "enjoying_demo": "Support this project", + "deploy_own": "Deploy your own instance" }, "demo": { diff --git a/locales/en-US.json b/locales/en-US.json index d5fb5ad..e08e791 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Support this project" + "enjoying_demo": "Support this project", + "deploy_own": "Deploy your own instance" }, "demo": { diff --git a/locales/es-ES.json b/locales/es-ES.json index 9d30d9f..9a8ac64 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Apoya este proyecto" + "enjoying_demo": "Apoya este proyecto", + "deploy_own": "Despliega tu propia instancia" }, "demo": { diff --git a/locales/fr-FR.json b/locales/fr-FR.json index 1cc298d..ca3fcc4 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Soutenez ce projet" + "enjoying_demo": "Soutenez ce projet", + "deploy_own": "Déployez votre propre instance" }, "demo": { diff --git a/locales/it-IT.json b/locales/it-IT.json index 16afd77..f39de25 100644 --- a/locales/it-IT.json +++ b/locales/it-IT.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Supporta questo progetto" + "enjoying_demo": "Supporta questo progetto", + "deploy_own": "Crea la tua istanza" }, "demo": { diff --git a/locales/ja-JP.json b/locales/ja-JP.json index 4ef1ba3..3004195 100644 --- a/locales/ja-JP.json +++ b/locales/ja-JP.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "このプロジェクトを支援" + "enjoying_demo": "このプロジェクトを支援", + "deploy_own": "自分のインスタンスをデプロイ" }, "demo": { diff --git a/locales/ru-RU.json b/locales/ru-RU.json index d30e191..d2d5626 100644 --- a/locales/ru-RU.json +++ b/locales/ru-RU.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Поддержать проект" + "enjoying_demo": "Поддержать проект", + "deploy_own": "Развернуть свой экземпляр" }, "demo": { diff --git a/locales/sl-SI.json b/locales/sl-SI.json index d7f3210..eadd6c9 100644 --- a/locales/sl-SI.json +++ b/locales/sl-SI.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "Podprite ta projekt" + "enjoying_demo": "Podprite ta projekt", + "deploy_own": "Namestite svojo instanco" }, "demo": { diff --git a/locales/zh-CN.json b/locales/zh-CN.json index b8abf2c..848eff2 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -272,7 +272,8 @@ }, "support": { - "enjoying_demo": "支持此项目" + "enjoying_demo": "支持此项目", + "deploy_own": "部署您自己的实例" }, "demo": { From 060fd75cb9304927c5867381693388bb31540744 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Fri, 16 Jan 2026 12:12:51 +0100 Subject: [PATCH 7/8] fix port from config.yaml --- run.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/run.py b/run.py index 0d3b7a9..d19e3bc 100644 --- a/run.py +++ b/run.py @@ -15,6 +15,27 @@ try: except ImportError: colorama = None +def get_port(): + """Get port from: 1) ENV variable, 2) config.yaml, 3) default 8000""" + # Priority 1: Environment variable + if os.getenv("PORT"): + return os.getenv("PORT") + + # Priority 2: config.yaml + config_path = Path("config.yaml") + if config_path.exists(): + try: + import yaml + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + if config and 'server' in config and 'port' in config['server']: + return str(config['server']['port']) + except Exception: + pass # Fall through to default + + # Priority 3: Default + return "8000" + def main(): print("🚀 Starting NoteDiscovery...\n") @@ -30,8 +51,8 @@ def main(): Path("data").mkdir(parents=True, exist_ok=True) Path("plugins").mkdir(parents=True, exist_ok=True) - # Get port from environment variable or use default - port = os.getenv("PORT", "8000") + # Get port from config or environment + port = get_port() print("✓ Dependencies installed") print("✓ Directories created") From 52c283618333f384e65f3161928a1bb5aed41c9a Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Fri, 16 Jan 2026 12:16:34 +0100 Subject: [PATCH 8/8] center buttons --- frontend/index.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index b91bdfd..f5b7509 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1795,7 +1795,7 @@