diff --git a/web/app/app.js b/web/app/app.js
index 2548988..33c2551 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: '',
+ tabs: [],
+ tabHistory: [],
finderUri: '',
finderQuery: '',
showGraph: false,
@@ -81,12 +81,12 @@ export default {
this.showFinder = false;
this.finderQuery = '';
if (value === null) {
- this.resetActiveFilename();
+ 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);
}
@@ -97,9 +97,9 @@ 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;
- (index === -1) ? this.notes.push(note) : this.notes.splice(index + 1, 0, note);
- this.activateNote(note.Filename);
+ this.notes.push(note);
+ this.addTab('note', note.Filename, insertAfterActive);
+ this.activateTab(note.Filename);
})
.catch(e => {
this.addAlert({type: 'error', title: 'Error fetching note', body: e.Error, sticky: true})
@@ -137,7 +137,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 +215,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 +227,8 @@ export default {
ghost: true,
};
this.notes.push(ghost);
- this.activateNote(ghost.Filename);
+ this.addTab('note', 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 +263,41 @@ 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.activeFilename) {
- this.activeFilenamePrevious = this.activeFilename;
- this.activeFilename = filename;
+ addTab(tabType, tabId, insertAfterActive = false) {
+ const tab = {type: tabType, id: tabId}
+ const index = this.tabs.findIndex(t => t.id === this.activeTabId);
+ if (insertAfterActive && index !== -1) {
+ this.tabs.splice(index + 1, 0, tab);
+ } else {
+ this.tabs.push(tab);
}
},
- resetActiveFilename() {
- if (this.activeFilename) {
- const af = this.activeFilename;
- this.activeFilename = '';
- this.$nextTick(() => { this.activeFilename = af; });
- }
+ activateTab(tabId) {
+ if (tabId == this.activeTabId) return;
+ this.tabHistory = this.tabHistory.filter(id => id !== tabId);
+ this.tabHistory.push(tabId);
+ },
+ 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) });
+ },
+ moveTab(tabId, newIndex) {
+ const index = this.tabs.findIndex(t => t.id === 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.id === tabId);
+ if (index !== -1) this.tabs.splice(index, 1);
+ this.tabHistory = this.tabHistory.filter(id => id !== tabId);
},
async closeNote(filename, confirmIfModified = true) {
const index = this.notes.findIndex(note => note.Filename === filename);
@@ -293,30 +312,7 @@ export default {
}
this.notes.splice(index, 1);
- const notesLength = this.notes.length;
- switch(notesLength) {
- case 0:
- this.activeFilename = '';
- this.activeFilenamePrevious = '';
- break;
- case 1:
- this.activeFilename = this.notes[0].Filename;
- this.activeFilenamePrevious = '';
- 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;
- }
- this.activeFilenamePrevious = (this.activeFilename !== lastFilename) ? lastFilename : this.notes.at(-2).Filename;
- break;
- }
- },
- 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]);
+ this.closeTab(filename);
},
addAlert({type, title, body, sticky = false}) {
this.alerts.push({type, title, body, sticky, id: Date.now().toString(36)});
@@ -372,7 +368,7 @@ export default {
setTimeout(() => {
if (this.keySequence.length > 0) {
this.keySequence = [];
- this.resetActiveFilename();
+ this.refocusActiveTab();
}
}, 2000);
return;
@@ -397,9 +393,9 @@ export default {
break;
case `${leaderKey} KeyN KeyK`:
this.keySequence = [];
- this.activeFilename
- ? this.openFinder('/api/raw/links?color=true&filename=' + this.activeFilename)
- : this.openFinder('/api/raw/links?color=true');
+ const tab = this.tabs.find(t => t.id === this.activeTabId);
+ const extraParams = tab?.type === 'note' ? `&filename=${tab.id}` : '';
+ this.openFinder('/api/raw/links?color=true' + extraParams);
break;
case `${leaderKey} KeyN KeyS`:
this.keySequence = [];
@@ -460,6 +456,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/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..32ea2bc 100644
--- a/web/app/nav-tabs.js
+++ b/web/app/nav-tabs.js
@@ -1,41 +1,40 @@
var t = `
-
-
+
+
-
-
-
+
+
-
+
-
`
@@ -43,8 +42,8 @@ var t = `
import Icon from './icon.js'
export default {
components: { Icon },
- props: ['notes', 'activeFilename', 'activeFilenamePrevious'],
- emits: ['note-activate', 'note-close', 'note-move'],
+ props: ['tabs', 'activeTabId', 'previousTabId', 'notes'],
+ emits: ['tab-activate', 'tab-move', 'tab-close', 'note-close'],
data() {
return {
dragTab: null,
@@ -53,34 +52,60 @@ export default {
},
methods: {
dragStart(index, event) {
- this.$emit('note-activate', this.notes[index].Filename)
+ this.$emit('tab-activate', this.tabs[index].id)
this.dragTab = index;
event.dataTransfer.dropEffect = 'move';
},
dragDrop(index) {
index = (index > this.dragTab) ? index : index + 1;
- this.$emit('note-move', this.notes[this.dragTab].Filename, index);
+ this.$emit('tab-move', this.tabs[this.dragTab].id, index);
+ },
+ isActive(tabId) {
+ return this.activeTabId == tabId;
+ },
+ handleClose(tabId, tabType) {
+ if (tabType === 'note') {
+ this.$emit('note-close', tabId);
+ return;
+ }
+ this.$emit('tab-close', tabId);
},
handleKeyPress(event) {
if (event.target.tagName !== 'BODY') return
if (event.ctrlKey && event.code == 'Digit6') {
- this.activeFilenamePrevious && this.$emit('note-activate', this.activeFilenamePrevious);
+ this.previousTabId && this.$emit('tab-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.tabs.findIndex(t => t.id === this.activeTabId);
if (index === -1) return;
const movement = event.code === 'KeyL' ? 1 : -1;
- const newIndex = (index + movement + this.notes.length) % this.notes.length;
- this.$emit('note-activate', this.notes[newIndex].Filename);
+ const newIndex = (index + movement + this.tabs.length) % this.tabs.length;
+ this.$emit('tab-activate', this.tabs[newIndex].id);
event.preventDefault();
return;
}
},
},
+ computed: {
+ getTabs() {
+ return this.tabs.map(tab => {
+ if (tab.type === 'note') {
+ const note = this.notes.find(n => n.Filename === tab.id);
+ return {
+ id: tab.id,
+ type: tab.type,
+ title: note.Title,
+ titleHover: `${note.Title} (${note.Filename})`,
+ isModified: note.isModified || false,
+ };
+ }
+ });
+ },
+ },
mounted() {
document.addEventListener('keydown', this.handleKeyPress);
},
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); },