From f3c8e4cecc7ff1bd1224b6c7f864d4b479d11238 Mon Sep 17 00:00:00 2001 From: Alon Swartz Date: Tue, 8 Jul 2025 13:34:39 +0300 Subject: [PATCH 1/4] web/app: tabs - rename filename vars to activeTabId and previousTabId --- web/app/app.js | 52 +++++++++++++++++++++--------------------- web/app/graph-panel.js | 4 ++-- web/app/nav-tabs.js | 14 ++++++------ web/app/note.js | 6 ++--- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/web/app/app.js b/web/app/app.js index 2548988..c800677 100644 --- a/web/app/app.js +++ b/web/app/app.js @@ -7,16 +7,16 @@ var t = ` - +
-
@@ -56,8 +56,8 @@ export default { data() { return { notes: [], - activeFilename: '', - activeFilenamePrevious: '', + activeTabId: '', + previousTabId: '', finderUri: '', finderQuery: '', showGraph: false, @@ -81,7 +81,7 @@ export default { this.showFinder = false; this.finderQuery = ''; if (value === null) { - this.resetActiveFilename(); + this.resetActiveTabId(); } else { const note = this.notes.find(note => note.Filename === value.Filename); if (note) { @@ -97,7 +97,7 @@ export default { .then(r => r.ok ? r.json() : r.json().then(e => Promise.reject(e))) .then(note => { note.Linenum = linenum; - const index = insertAfterActive ? this.notes.findIndex(note => note.Filename === this.activeFilename) : -1; + const index = insertAfterActive ? this.notes.findIndex(note => note.Filename === this.activeTabId) : -1; (index === -1) ? this.notes.push(note) : this.notes.splice(index + 1, 0, note); this.activateNote(note.Filename); }) @@ -268,16 +268,16 @@ export default { } }, activateNote(filename) { - if (filename !== this.activeFilename) { - this.activeFilenamePrevious = this.activeFilename; - this.activeFilename = filename; + if (filename !== this.activeTabId) { + this.previousTabId = this.activeTabId; + this.activeTabId = filename; } }, - resetActiveFilename() { - if (this.activeFilename) { - const af = this.activeFilename; - this.activeFilename = ''; - this.$nextTick(() => { this.activeFilename = af; }); + resetActiveTabId() { + if (this.activeTabId) { + const af = this.activeTabId; + this.activeTabId = ''; + this.$nextTick(() => { this.activeTabId = af; }); } }, async closeNote(filename, confirmIfModified = true) { @@ -296,20 +296,20 @@ export default { const notesLength = this.notes.length; switch(notesLength) { case 0: - this.activeFilename = ''; - this.activeFilenamePrevious = ''; + this.activeTabId = ''; + this.previousTabId = ''; break; case 1: - this.activeFilename = this.notes[0].Filename; - this.activeFilenamePrevious = ''; + this.activeTabId = this.notes[0].Filename; + this.previousTabId = ''; break; default: const lastFilename = this.notes.at(-1).Filename; - if (filename == this.activeFilename) { - const previousExists = this.notes.some(note => note.Filename === this.activeFilenamePrevious); - this.activeFilename = previousExists ? this.activeFilenamePrevious : lastFilename; + if (filename == this.activeTabId) { + const previousExists = this.notes.some(note => note.Filename === this.previousTabId); + this.activeTabId = previousExists ? this.previousTabId : lastFilename; } - this.activeFilenamePrevious = (this.activeFilename !== lastFilename) ? lastFilename : this.notes.at(-2).Filename; + this.previousTabId = (this.activeTabId !== lastFilename) ? lastFilename : this.notes.at(-2).Filename; break; } }, @@ -372,7 +372,7 @@ export default { setTimeout(() => { if (this.keySequence.length > 0) { this.keySequence = []; - this.resetActiveFilename(); + this.resetActiveTabId(); } }, 2000); return; @@ -397,8 +397,8 @@ export default { break; case `${leaderKey} KeyN KeyK`: this.keySequence = []; - this.activeFilename - ? this.openFinder('/api/raw/links?color=true&filename=' + this.activeFilename) + this.activeTabId + ? this.openFinder('/api/raw/links?color=true&filename=' + this.activeTabId) : this.openFinder('/api/raw/links?color=true'); break; case `${leaderKey} KeyN KeyS`: diff --git a/web/app/graph-panel.js b/web/app/graph-panel.js index fc3055d..2068162 100644 --- a/web/app/graph-panel.js +++ b/web/app/graph-panel.js @@ -91,7 +91,7 @@ import Icon from './icon.js' import GraphD3 from './graph-d3.js' export default { components: { Pane, Icon, GraphD3 }, - props: ['activeFilename', 'lastSave'], + props: ['activeTabId', 'lastSave'], emits: ['note-open'], data() { return { @@ -141,7 +141,7 @@ export default { const queryWords = this.query.toLowerCase().split(' '); return this.graphData.nodes.filter(node => queryWords.every(queryWord => node.title.toLowerCase().includes(queryWord))).map(node => node.id); } - return (this.display.emphasizeActive.value && this.activeFilename) ? [this.activeFilename] : null; + return (this.display.emphasizeActive.value && this.activeTabId) ? [this.activeTabId] : null; }, }, created() { diff --git a/web/app/nav-tabs.js b/web/app/nav-tabs.js index 4fd41da..47c484b 100644 --- a/web/app/nav-tabs.js +++ b/web/app/nav-tabs.js @@ -1,7 +1,7 @@ var t = `
` @@ -43,7 +43,7 @@ var t = ` import Icon from './icon.js' export default { components: { Icon }, - props: ['notes', 'activeFilename', 'activeFilenamePrevious'], + props: ['notes', 'activeTabId', 'previousTabId'], emits: ['note-activate', 'note-close', 'note-move'], data() { return { @@ -65,13 +65,13 @@ export default { if (event.target.tagName !== 'BODY') return if (event.ctrlKey && event.code == 'Digit6') { - this.activeFilenamePrevious && this.$emit('note-activate', this.activeFilenamePrevious); + this.previousTabId && this.$emit('note-activate', this.previousTabId); event.preventDefault(); return; } if (event.ctrlKey && (event.code == 'KeyH' || event.code == 'KeyL')) { - const index = this.notes.findIndex(note => note.Filename === this.activeFilename); + const index = this.notes.findIndex(note => note.Filename === this.activeTabId); if (index === -1) return; const movement = event.code === 'KeyL' ? 1 : -1; const newIndex = (index + movement + this.notes.length) % this.notes.length; diff --git a/web/app/note.js b/web/app/note.js index f435178..2ceb95e 100644 --- a/web/app/note.js +++ b/web/app/note.js @@ -37,7 +37,7 @@ import Finder from './finder.js' import Icon from './icon.js' export default { components: { NoteSidebar, NoteStatusbar, Finder, Icon }, - props: ['note', 'activeFilename'], + props: ['note', 'activeTabId'], emits: ['note-open', 'note-close', 'note-save', 'note-delete', 'finder-open'], data() { return { @@ -180,7 +180,7 @@ export default { }); }, handleKeyPress(event) { - if (event.target.tagName !== 'BODY' || this.note.Filename !== this.activeFilename) return; + if (event.target.tagName !== 'BODY' || this.note.Filename !== this.activeTabId) return; if (event.ctrlKey && event.code === 'KeyS') { this.handleSave(); event.preventDefault(); @@ -322,7 +322,7 @@ export default { document.removeEventListener('keydown', this.handleKeyPress); }, watch: { - 'activeFilename': function(newVal) { if (this.$notesiumState.editorVimMode && this.note.Filename == newVal) this.$nextTick(() => { this.cm.focus(); }); }, + 'activeTabId': function(newVal) { if (this.$notesiumState.editorVimMode && this.note.Filename == newVal) this.$nextTick(() => { this.cm.focus(); }); }, 'note.Linenum': function(newVal) { this.lineNumberHL(newVal); if (this.$notesiumState.editorVimMode) this.$nextTick(() => { this.cm.focus(); }); }, 'note.Mtime': function() { this.cm.doc.markClean(); }, '$notesiumState.editorLineWrapping': function(newVal) { this.cm.setOption("lineWrapping", newVal); }, From e10091af8b718ac1a7a4d5907eb528a7a4a61495 Mon Sep 17 00:00:00 2001 From: Alon Swartz Date: Tue, 8 Jul 2025 14:06:45 +0300 Subject: [PATCH 2/4] web/app: tabs - improve and simplify tab tracking via tabHistory stack --- web/app/app.js | 73 ++++++++++++++++++++------------------------- web/app/nav-tabs.js | 10 +++---- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/web/app/app.js b/web/app/app.js index c800677..fb7016e 100644 --- a/web/app/app.js +++ b/web/app/app.js @@ -12,7 +12,7 @@ var t = `
@@ -56,8 +56,7 @@ export default { data() { return { notes: [], - activeTabId: '', - previousTabId: '', + tabHistory: [], finderUri: '', finderQuery: '', showGraph: false, @@ -81,12 +80,12 @@ export default { this.showFinder = false; this.finderQuery = ''; if (value === null) { - this.resetActiveTabId(); + this.refocusActiveTab(); } else { const note = this.notes.find(note => note.Filename === value.Filename); if (note) { note.Linenum = value.Linenum; - this.activateNote(value.Filename); + this.activateTab(value.Filename); } else { this.fetchNote(value.Filename, value.Linenum); } @@ -99,7 +98,7 @@ export default { note.Linenum = linenum; const index = insertAfterActive ? this.notes.findIndex(note => note.Filename === this.activeTabId) : -1; (index === -1) ? this.notes.push(note) : this.notes.splice(index + 1, 0, note); - this.activateNote(note.Filename); + this.activateTab(note.Filename); }) .catch(e => { this.addAlert({type: 'error', title: 'Error fetching note', body: e.Error, sticky: true}) @@ -137,7 +136,7 @@ export default { } this.notes[index] = note; - this.activateNote(note.Filename); + this.activateTab(note.Filename); // track lastSave to force sidepanel refresh this.lastSave = note.Mtime; @@ -215,7 +214,7 @@ export default { if (noteInfo.exists === "true") { this.openNote(noteInfo.filename, 1); return; } const index = this.notes.findIndex(note => note.Filename === noteInfo.filename); - if (index !== -1) { this.activateNote(noteInfo.filename); return; } + if (index !== -1) { this.activateTab(noteInfo.filename); return; } const ghost = { Filename: noteInfo.filename, @@ -227,7 +226,7 @@ export default { ghost: true, }; this.notes.push(ghost); - this.activateNote(ghost.Filename); + this.activateTab(ghost.Filename); }) .catch(e => { this.addAlert({type: 'error', title: 'Error retrieving new note metadata', body: e.Error, sticky: true}); @@ -262,23 +261,25 @@ export default { const index = this.notes.findIndex(note => note.Filename === filename); if (index !== -1) { this.notes[index].Linenum = linenum; - this.activateNote(filename); + this.activateTab(filename); } else { this.fetchNote(filename, linenum, true); } }, - activateNote(filename) { - if (filename !== this.activeTabId) { - this.previousTabId = this.activeTabId; - this.activeTabId = filename; - } + activateTab(tabId) { + if (tabId == this.activeTabId) return; + this.tabHistory = this.tabHistory.filter(id => id !== tabId); + this.tabHistory.push(tabId); }, - resetActiveTabId() { - if (this.activeTabId) { - const af = this.activeTabId; - this.activeTabId = ''; - this.$nextTick(() => { this.activeTabId = af; }); - } + refocusActiveTab() { + // required for cancelled keybind and finder + if (!this.activeTabId) return; + const tabId = this.activeTabId; + this.tabHistory = this.tabHistory.filter(id => id !== tabId); + this.$nextTick(() => { this.activateTab(tabId) }); + }, + closeTab(tabId) { + this.tabHistory = this.tabHistory.filter(id => id !== tabId); }, async closeNote(filename, confirmIfModified = true) { const index = this.notes.findIndex(note => note.Filename === filename); @@ -293,25 +294,7 @@ export default { } this.notes.splice(index, 1); - const notesLength = this.notes.length; - switch(notesLength) { - case 0: - this.activeTabId = ''; - this.previousTabId = ''; - break; - case 1: - this.activeTabId = this.notes[0].Filename; - this.previousTabId = ''; - break; - default: - const lastFilename = this.notes.at(-1).Filename; - if (filename == this.activeTabId) { - const previousExists = this.notes.some(note => note.Filename === this.previousTabId); - this.activeTabId = previousExists ? this.previousTabId : lastFilename; - } - this.previousTabId = (this.activeTabId !== lastFilename) ? lastFilename : this.notes.at(-2).Filename; - break; - } + this.closeTab(filename); }, moveNote(filename, newIndex) { const index = this.notes.findIndex(note => note.Filename === filename); @@ -372,7 +355,7 @@ export default { setTimeout(() => { if (this.keySequence.length > 0) { this.keySequence = []; - this.resetActiveTabId(); + this.refocusActiveTab(); } }, 2000); return; @@ -460,6 +443,14 @@ export default { .catch(e => { console.error(e); }); }, }, + computed: { + activeTabId() { + return this.tabHistory[this.tabHistory.length - 1] || ''; + }, + previousTabId() { + return this.tabHistory[this.tabHistory.length - 2] || ''; + }, + }, mounted() { document.addEventListener('keydown', this.handleKeyPress); window.addEventListener('beforeunload', this.handleBeforeUnload); diff --git a/web/app/nav-tabs.js b/web/app/nav-tabs.js index 47c484b..f8145bb 100644 --- a/web/app/nav-tabs.js +++ b/web/app/nav-tabs.js @@ -4,7 +4,7 @@ var t = `
-
Date: Tue, 8 Jul 2025 16:12:42 +0300 Subject: [PATCH 3/4] web/app: tabs - decouple open tabs and ordering from notes array --- web/app/app.js | 32 +++++++++++++++++--------- web/app/nav-tabs.js | 55 ++++++++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/web/app/app.js b/web/app/app.js index fb7016e..2b3e8f6 100644 --- a/web/app/app.js +++ b/web/app/app.js @@ -11,11 +11,11 @@ var t = `
- +
@@ -56,6 +56,7 @@ export default { data() { return { notes: [], + tabs: [], tabHistory: [], finderUri: '', finderQuery: '', @@ -96,8 +97,8 @@ export default { .then(r => r.ok ? r.json() : r.json().then(e => Promise.reject(e))) .then(note => { note.Linenum = linenum; - const index = insertAfterActive ? this.notes.findIndex(note => note.Filename === this.activeTabId) : -1; - (index === -1) ? this.notes.push(note) : this.notes.splice(index + 1, 0, note); + this.notes.push(note); + this.addTab(note.Filename, insertAfterActive); this.activateTab(note.Filename); }) .catch(e => { @@ -226,6 +227,7 @@ export default { ghost: true, }; this.notes.push(ghost); + this.addTab(ghost.Filename); this.activateTab(ghost.Filename); }) .catch(e => { @@ -266,6 +268,14 @@ export default { this.fetchNote(filename, linenum, true); } }, + addTab(tabId, insertAfterActive = false) { + const index = this.tabs.findIndex(t => t === this.activeTabId); + if (insertAfterActive && index !== -1) { + this.tabs.splice(index + 1, 0, tabId); + } else { + this.tabs.push(tabId); + } + }, activateTab(tabId) { if (tabId == this.activeTabId) return; this.tabHistory = this.tabHistory.filter(id => id !== tabId); @@ -278,7 +288,14 @@ export default { this.tabHistory = this.tabHistory.filter(id => id !== tabId); this.$nextTick(() => { this.activateTab(tabId) }); }, + moveTab(tabId, newIndex) { + const index = this.tabs.findIndex(t => t === tabId); + if (index === -1) return; + this.tabs.splice(newIndex, 0, this.tabs.splice(index, 1)[0]); + }, closeTab(tabId) { + const index = this.tabs.findIndex(t => t === tabId); + if (index !== -1) this.tabs.splice(index, 1); this.tabHistory = this.tabHistory.filter(id => id !== tabId); }, async closeNote(filename, confirmIfModified = true) { @@ -296,11 +313,6 @@ export default { this.notes.splice(index, 1); this.closeTab(filename); }, - moveNote(filename, newIndex) { - const index = this.notes.findIndex(note => note.Filename === filename); - if (index === -1) return; - this.notes.splice(newIndex, 0, this.notes.splice(index, 1)[0]); - }, addAlert({type, title, body, sticky = false}) { this.alerts.push({type, title, body, sticky, id: Date.now().toString(36)}); }, diff --git a/web/app/nav-tabs.js b/web/app/nav-tabs.js index f8145bb..03df33c 100644 --- a/web/app/nav-tabs.js +++ b/web/app/nav-tabs.js @@ -1,41 +1,40 @@ var t = `
-