Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 58 additions & 54 deletions web/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ var t = `
<SidePanel v-if="$notesiumState.showLabelsPanel || $notesiumState.showNotesPanel"
:lastSave="lastSave" @note-open="openNote" @note-new="newNote" @finder-open="openFinder" />

<GraphPanel v-if="$notesiumState.showGraphPanel" :lastSave=graphPanelWatcher :activeFilename=activeFilename @note-open="openNote" />
<GraphPanel v-if="$notesiumState.showGraphPanel" :lastSave=graphPanelWatcher :activeTabId=activeTabId @note-open="openNote" />

<div class="flex flex-col h-full w-full overflow-x-auto">
<nav class="flex bg-gray-200 text-gray-800">
<NavTabs :notes=notes :activeFilename=activeFilename :activeFilenamePrevious=activeFilenamePrevious
@note-activate="activateNote" @note-close="closeNote" @note-move="moveNote" />
<NavTabs :tabs=tabs :activeTabId=activeTabId :previousTabId=previousTabId :notes=notes
@tab-activate="activateTab" @tab-move="moveTab" @tab-close="closeTab" @note-close="closeNote" />
</nav>
<main class="h-full overflow-hidden bg-gray-50">
<Empty v-if="notes.length == 0" @note-new="newNote" @note-daily="dailyNote" @finder-open="openFinder" @graph-open="showGraph=true" />
<Note v-show="note.Filename == activeFilename" :note=note v-for="note in notes" :key="note.Filename" :activeFilename=activeFilename
<Empty v-if="tabs.length == 0" @note-new="newNote" @note-daily="dailyNote" @finder-open="openFinder" @graph-open="showGraph=true" />
<Note v-show="note.Filename == activeTabId" :note=note v-for="note in notes" :key="note.Filename" :activeTabId=activeTabId
@note-open="openNote" @note-close="closeNote" @note-save="saveNote" @note-delete="deleteNote" @finder-open="openFinder" />
</main>
</div>
Expand Down Expand Up @@ -56,8 +56,8 @@ export default {
data() {
return {
notes: [],
activeFilename: '',
activeFilenamePrevious: '',
tabs: [],
tabHistory: [],
finderUri: '',
finderQuery: '',
showGraph: false,
Expand All @@ -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);
}
Expand All @@ -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})
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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});
Expand Down Expand Up @@ -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);
Expand All @@ -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)});
Expand Down Expand Up @@ -372,7 +368,7 @@ export default {
setTimeout(() => {
if (this.keySequence.length > 0) {
this.keySequence = [];
this.resetActiveFilename();
this.refocusActiveTab();
}
}, 2000);
return;
Expand All @@ -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 = [];
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions web/app/graph-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
67 changes: 46 additions & 21 deletions web/app/nav-tabs.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,49 @@
var t = `
<div class="flex flex-nowrap max-w-full w-full h-9 overflow-x-hidden items-center content-center px-2 mr-6">
<template v-for="note, index in notes" :key="note.Filename">
<div :class="(note.Filename == activeFilename) ? 'text-gray-50' : 'text-transparent'" class="relative h-full">
<template v-for="tab, index in getTabs" :key="tab.id">
<div :class="isActive(tab.id) ? 'text-gray-50' : 'text-transparent'" class="relative h-full">
<svg class="absolute right-0 bottom-0" fill="currentColor" width="7" height="7"><path d="M 0 7 A 7 7 0 0 0 7 0 L 7 7 Z"></path></svg>
</div>
<div @click="$emit('note-activate', note.Filename)"
<div @click="$emit('tab-activate', tab.id)"
draggable="true"
@dragstart="dragStart(index, $event)"
@dragend="dragTab = dragOver = null"
@dragover.prevent
@dragfinish.prevent
:title="note.Title + ' (' + note.Filename + ')'"
:class="(note.Filename == activeFilename) ? 'bg-gray-50 text-gray-800' : 'hover:bg-gray-100/75 hover:text-gray-700 text-gray-500'"
:title="tab.titleHover"
:class="isActive(tab.id) ? 'bg-gray-50 text-gray-800' : 'hover:bg-gray-100/75 hover:text-gray-700 text-gray-500'"
class="flex rounded-t-lg justify-between basis-52 truncate text-xs h-full items-center pl-3 pr-2 cursor-pointer">
<span class="truncate pt-px">
<span v-show="note.isModified" class="inline-block h-2 w-2 rounded-full bg-yellow-400 mr-2"></span>
<span v-text="note.Title"></span>
<span v-show="tab.isModified" class="inline-block h-2 w-2 rounded-full bg-yellow-400 mr-2"></span>
<span v-text="tab.title"></span>
</span>
<span @click.stop="$emit('note-close', note.Filename)" class="hover:bg-gray-300 hover:rounded-full">
<span @click.stop="handleClose(tab.id, tab.type)" class="hover:bg-gray-300 hover:rounded-full">
<Icon name="mini-x-mark" size="h-4 w-4" />
</span>
</div>
<div :class="(note.Filename == activeFilename) ? 'text-gray-50' : 'text-transparent'" class="relative h-full">
<div :class="isActive(tab.id) ? 'text-gray-50' : 'text-transparent'" class="relative h-full">
<svg class="absolute bottom-0" fill="currentColor" width="7" height="7"><path d="M 0 0 A 7 7 0 0 0 7 7 L 0 7 Z"></path></svg>
</div>

<span
v-if="(dragTab != null && dragTab != index && index != dragTab - 1)"
v-text="dragOver === index ? notes[dragTab].Title : '|'"
v-text="dragOver === index ? getTabs[dragTab].title : '|'"
@dragenter="dragOver = index"
@dragleave="dragOver = null"
@drop.prevent="dragDrop(index)"
@dragover.prevent
:class="(dragOver === index) ? 'basis-52 truncate bg-gray-400 text-white text-xs pl-3 pr-2' : ''"
:class="{'basis-52 truncate bg-gray-400 text-white text-xs pl-3 pr-2': (dragOver === index)}"
class="flex z-50 h-full justify-between items-center text-gray-500">
</span>
<span v-else :class="(note.Filename != activeFilename) ? 'text-gray-300' : 'text-transparent'" class="z-1 -mr-1">|</span>
<span v-else :class="!isActive(tab.id) ? 'text-gray-300' : 'text-transparent'" class="z-1 -mr-1">|</span>
</template>
</div>
`

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,
Expand All @@ -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);
},
Expand Down
6 changes: 3 additions & 3 deletions web/app/note.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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); },
Expand Down
Loading