770 lines
20 KiB
TypeScript
770 lines
20 KiB
TypeScript
import { Editor } from '@/ui_models/editor';
|
||
import { PanelPuppet, WebDirective } from './../../types';
|
||
import angular from 'angular';
|
||
import template from './notes-view.pug';
|
||
import {
|
||
ApplicationEvent,
|
||
ContentType,
|
||
removeFromArray,
|
||
SNNote,
|
||
SNTag,
|
||
WebPrefKey,
|
||
findInArray,
|
||
CollectionSort,
|
||
SNSmartTag
|
||
} from 'snjs';
|
||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||
import { AppStateEvent } from '@/ui_models/app_state';
|
||
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
|
||
import {
|
||
PANEL_NAME_NOTES
|
||
} from '@/views/constants';
|
||
import {
|
||
NoteSortKey,
|
||
notePassesFilter
|
||
} from './note_utils';
|
||
import { UuidString } from '@/../../../../snjs/dist/@types/types';
|
||
|
||
type NotesState = {
|
||
panelTitle: string
|
||
notes?: SNNote[]
|
||
renderedNotes?: SNNote[]
|
||
sortBy?: string
|
||
sortReverse?: boolean
|
||
showArchived?: boolean
|
||
hidePinned?: boolean
|
||
hideNotePreview?: boolean
|
||
hideDate?: boolean
|
||
noteFilter: { text: string }
|
||
mutable: { showMenu: boolean }
|
||
}
|
||
|
||
type NoteFlag = {
|
||
text: string
|
||
class: 'info' | 'neutral' | 'warning' | 'success' | 'danger'
|
||
}
|
||
|
||
/**
|
||
* This is the height of a note cell with nothing but the title,
|
||
* which *is* a display option
|
||
*/
|
||
const MIN_NOTE_CELL_HEIGHT = 51.0;
|
||
const DEFAULT_LIST_NUM_NOTES = 20;
|
||
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
|
||
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
|
||
|
||
class NotesViewCtrl extends PureViewCtrl {
|
||
|
||
private panelPuppet?: PanelPuppet
|
||
private reloadNotesPromise?: any
|
||
private notesToDisplay = 0
|
||
private pageSize = 0
|
||
private searchSubmitted = false
|
||
private newNoteKeyObserver: any
|
||
private nextNoteKeyObserver: any
|
||
private previousNoteKeyObserver: any
|
||
private searchKeyObserver: any
|
||
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
|
||
private unsubEditorChange: any
|
||
|
||
/* @ngInject */
|
||
constructor($timeout: ng.ITimeoutService, ) {
|
||
super($timeout);
|
||
this.resetPagination();
|
||
}
|
||
|
||
$onInit() {
|
||
super.$onInit();
|
||
this.panelPuppet = {
|
||
onReady: () => this.reloadPanelWidth()
|
||
};
|
||
this.onWindowResize = this.onWindowResize.bind(this);
|
||
this.onPanelResize = this.onPanelResize.bind(this);
|
||
window.addEventListener('resize', this.onWindowResize, true);
|
||
this.registerKeyboardShortcuts();
|
||
}
|
||
|
||
onWindowResize() {
|
||
this.resetPagination(true);
|
||
}
|
||
|
||
deinit() {
|
||
this.panelPuppet!.onReady = undefined;
|
||
this.panelPuppet = undefined;
|
||
window.removeEventListener('resize', this.onWindowResize, true);
|
||
(this.onWindowResize as any) = undefined;
|
||
(this.onPanelResize as any) = undefined;
|
||
this.newNoteKeyObserver();
|
||
this.nextNoteKeyObserver();
|
||
this.previousNoteKeyObserver();
|
||
this.searchKeyObserver();
|
||
this.newNoteKeyObserver = undefined;
|
||
this.nextNoteKeyObserver = undefined;
|
||
this.previousNoteKeyObserver = undefined;
|
||
this.searchKeyObserver = undefined;
|
||
super.deinit();
|
||
}
|
||
|
||
getState() {
|
||
return this.state as NotesState;
|
||
}
|
||
|
||
async setNotesState(state: Partial<NotesState>) {
|
||
return this.setState(state);
|
||
}
|
||
|
||
getInitialState() {
|
||
return {
|
||
notes: [],
|
||
renderedNotes: [],
|
||
mutable: { showMenu: false },
|
||
noteFilter: { text: '' },
|
||
} as Partial<NotesState>;
|
||
}
|
||
|
||
async onAppLaunch() {
|
||
super.onAppLaunch();
|
||
this.streamNotesAndTags();
|
||
this.reloadPreferences();
|
||
}
|
||
|
||
/** @override */
|
||
onAppStateEvent(eventName: AppStateEvent, data?: any) {
|
||
if (eventName === AppStateEvent.TagChanged) {
|
||
this.handleTagChange(
|
||
this.application!.getAppState().getSelectedTag()!
|
||
);
|
||
} else if (eventName === AppStateEvent.ActiveEditorChanged) {
|
||
this.handleEditorChange();
|
||
} else if (eventName === AppStateEvent.PreferencesChanged) {
|
||
this.reloadPreferences();
|
||
} else if (eventName === AppStateEvent.EditorFocused) {
|
||
this.setShowMenuFalse();
|
||
}
|
||
}
|
||
|
||
/** @template */
|
||
public get activeEditorNote() {
|
||
return this.appState.getActiveEditor()?.note;
|
||
}
|
||
|
||
public get editorNotes() {
|
||
return this.appState.getEditors().map((editor) => editor.note);
|
||
}
|
||
|
||
/** @override */
|
||
async onAppEvent(eventName: ApplicationEvent) {
|
||
if (eventName === ApplicationEvent.SignedIn) {
|
||
this.appState.closeAllEditors();
|
||
this.selectFirstNote();
|
||
} else if (eventName === ApplicationEvent.CompletedSync) {
|
||
this.getMostValidNotes().then((notes) => {
|
||
if (notes.length === 0) {
|
||
this.createPlaceholderNote();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Access the current state notes without awaiting any potential reloads
|
||
* that may be in progress. This is the sync alternative to `async getMostValidNotes`
|
||
*/
|
||
private getPossiblyStaleNotes() {
|
||
return this.getState().notes!;
|
||
}
|
||
|
||
/**
|
||
* Access the current state notes after waiting for any pending reloads.
|
||
* This returns the most up to date notes, but is the asyncronous counterpart
|
||
* to `getPossiblyStaleNotes`
|
||
*/
|
||
private async getMostValidNotes() {
|
||
await this.reloadNotesPromise;
|
||
return this.getPossiblyStaleNotes();
|
||
}
|
||
|
||
/**
|
||
* Triggered programatically to create a new placeholder note
|
||
* when conditions allow for it. This is as opposed to creating a new note
|
||
* as part of user interaction (pressing the + button).
|
||
*/
|
||
private async createPlaceholderNote() {
|
||
const selectedTag = this.application!.getAppState().getSelectedTag()!;
|
||
if (selectedTag.isSmartTag() && !selectedTag.isAllTag) {
|
||
return;
|
||
}
|
||
return this.createNewNote();
|
||
}
|
||
|
||
streamNotesAndTags() {
|
||
this.application!.streamItems(
|
||
[ContentType.Note],
|
||
async (items) => {
|
||
const notes = items as SNNote[];
|
||
/** Note has changed values, reset its flags */
|
||
for (const note of notes) {
|
||
if (note.deleted) {
|
||
continue;
|
||
}
|
||
this.loadFlagsForNote(note);
|
||
}
|
||
/** If a note changes, it will be queried against the existing filter;
|
||
* we dont need to reload display options */
|
||
await this.reloadNotes();
|
||
const activeNote = this.activeEditorNote;
|
||
if (activeNote) {
|
||
const discarded = activeNote.deleted || activeNote.trashed;
|
||
if (discarded) {
|
||
this.selectNextOrCreateNew();
|
||
}
|
||
} else {
|
||
this.selectFirstNote();
|
||
}
|
||
}
|
||
);
|
||
|
||
this.application!.streamItems(
|
||
[ContentType.Tag],
|
||
async (items) => {
|
||
const tags = items as SNTag[];
|
||
/** A tag could have changed its relationships, so we need to reload the filter */
|
||
this.reloadNotesDisplayOptions();
|
||
await this.reloadNotes();
|
||
if (findInArray(tags, 'uuid', this.appState.selectedTag?.uuid)) {
|
||
/** Tag title could have changed */
|
||
this.reloadPanelTitle();
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
async selectNote(note: SNNote) {
|
||
this.appState.openEditor(note.uuid);
|
||
}
|
||
|
||
async createNewNote() {
|
||
let title = `Note ${this.getState().notes!.length + 1}`;
|
||
if (this.isFiltering()) {
|
||
title = this.getState().noteFilter.text;
|
||
}
|
||
this.appState.createEditor(title);
|
||
}
|
||
|
||
async handleTagChange(tag: SNTag) {
|
||
this.resetScrollPosition();
|
||
this.setShowMenuFalse();
|
||
await this.setNoteFilterText('');
|
||
this.application!.getDesktopService().searchText();
|
||
this.resetPagination();
|
||
|
||
/* Capture db load state before beginning reloadNotes,
|
||
since this status may change during reload */
|
||
const dbLoaded = this.application!.isDatabaseLoaded();
|
||
this.reloadNotesDisplayOptions();
|
||
await this.reloadNotes();
|
||
|
||
if (this.getState().notes!.length > 0) {
|
||
this.selectFirstNote();
|
||
} else if (dbLoaded) {
|
||
if (!tag.isSmartTag() || tag.isAllTag) {
|
||
this.createPlaceholderNote();
|
||
} else if (
|
||
this.activeEditorNote &&
|
||
!this.getState().notes!.includes(this.activeEditorNote!)
|
||
) {
|
||
this.appState.closeActiveEditor();
|
||
}
|
||
}
|
||
}
|
||
|
||
resetScrollPosition() {
|
||
const scrollable = document.getElementById(ELEMENT_ID_SCROLL_CONTAINER);
|
||
if (scrollable) {
|
||
scrollable.scrollTop = 0;
|
||
scrollable.scrollLeft = 0;
|
||
}
|
||
}
|
||
|
||
async removeNoteFromList(note: SNNote) {
|
||
const notes = this.getState().notes!;
|
||
removeFromArray(notes, note);
|
||
await this.setNotesState({
|
||
notes: notes,
|
||
renderedNotes: notes.slice(0, this.notesToDisplay)
|
||
});
|
||
}
|
||
|
||
private async reloadNotes() {
|
||
this.reloadNotesPromise = this.performPeloadNotes();
|
||
return this.reloadNotesPromise;
|
||
}
|
||
|
||
/**
|
||
* Note that reloading display options destroys the current index and rebuilds it,
|
||
* so call sparingly. The runtime complexity of destroying and building
|
||
* an index is roughly O(n^2).
|
||
*/
|
||
private reloadNotesDisplayOptions() {
|
||
const tag = this.appState.selectedTag!;
|
||
this.application.setDisplayOptions(
|
||
ContentType.Note,
|
||
this.getState().sortBy! as CollectionSort,
|
||
this.getState().sortReverse! ? 'asc' : 'dsc',
|
||
(note: SNNote) => {
|
||
const matchesTag = tag.isSmartTag()
|
||
? note.satisfiesPredicate((tag as SNSmartTag).predicate)
|
||
: tag.hasRelationshipWithItem(note);
|
||
return matchesTag && notePassesFilter(
|
||
note,
|
||
this.appState.selectedTag!,
|
||
this.getState().showArchived!,
|
||
this.getState().hidePinned!,
|
||
this.getState().noteFilter.text.toLowerCase()
|
||
)
|
||
}
|
||
)
|
||
}
|
||
|
||
private async performPeloadNotes() {
|
||
const tag = this.appState.selectedTag!;
|
||
if (!tag) {
|
||
return;
|
||
}
|
||
const notes = this.application.getDisplayableItems(ContentType.Note) as SNNote[];
|
||
await this.setNotesState({
|
||
notes: notes,
|
||
renderedNotes: notes.slice(0, this.notesToDisplay)
|
||
});
|
||
this.reloadPanelTitle();
|
||
}
|
||
|
||
setShowMenuFalse() {
|
||
this.setNotesState({
|
||
mutable: {
|
||
...this.getState().mutable,
|
||
showMenu: false
|
||
}
|
||
});
|
||
}
|
||
|
||
async handleEditorChange() {
|
||
const activeNote = this.appState.getActiveEditor().note;
|
||
if (activeNote && activeNote.conflictOf) {
|
||
this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => {
|
||
mutator.conflictOf = undefined;
|
||
})
|
||
}
|
||
if (this.isFiltering()) {
|
||
this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
|
||
}
|
||
}
|
||
|
||
async reloadPreferences() {
|
||
const viewOptions = {} as NotesState;
|
||
const prevSortValue = this.getState().sortBy;
|
||
let sortBy = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.SortNotesBy,
|
||
NoteSortKey.CreatedAt
|
||
);
|
||
if (sortBy === NoteSortKey.UpdatedAt || sortBy === NoteSortKey.ClientUpdatedAt) {
|
||
/** Use UserUpdatedAt instead */
|
||
sortBy = NoteSortKey.UserUpdatedAt;
|
||
}
|
||
viewOptions.sortBy = sortBy;
|
||
viewOptions.sortReverse = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.SortNotesReverse,
|
||
false
|
||
);
|
||
viewOptions.showArchived = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.NotesShowArchived,
|
||
false
|
||
);
|
||
viewOptions.hidePinned = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.NotesHidePinned,
|
||
false
|
||
);
|
||
viewOptions.hideNotePreview = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.NotesHideNotePreview,
|
||
false
|
||
);
|
||
viewOptions.hideDate = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.NotesHideDate,
|
||
false
|
||
);
|
||
const state = this.getState();
|
||
const displayOptionsChanged = (
|
||
viewOptions.sortBy !== state.sortBy ||
|
||
viewOptions.sortReverse !== state.sortReverse ||
|
||
viewOptions.hidePinned !== state.hidePinned ||
|
||
viewOptions.showArchived !== state.showArchived
|
||
);
|
||
await this.setNotesState({
|
||
...viewOptions
|
||
});
|
||
this.reloadPanelWidth();
|
||
if (displayOptionsChanged) {
|
||
this.reloadNotesDisplayOptions();
|
||
}
|
||
await this.reloadNotes();
|
||
if (prevSortValue && prevSortValue !== sortBy) {
|
||
this.selectFirstNote();
|
||
}
|
||
}
|
||
|
||
reloadPanelWidth() {
|
||
const width = this.application!.getPrefsService().getValue(
|
||
WebPrefKey.NotesPanelWidth
|
||
);
|
||
if (width && this.panelPuppet!.ready) {
|
||
this.panelPuppet!.setWidth!(width);
|
||
if (this.panelPuppet!.isCollapsed!()) {
|
||
this.application!.getAppState().panelDidResize(
|
||
PANEL_NAME_NOTES,
|
||
this.panelPuppet!.isCollapsed!()
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
onPanelResize(
|
||
newWidth: number,
|
||
_: number,
|
||
__: boolean,
|
||
isCollapsed: boolean
|
||
) {
|
||
this.application!.getPrefsService().setUserPrefValue(
|
||
WebPrefKey.NotesPanelWidth,
|
||
newWidth,
|
||
true
|
||
);
|
||
this.application!.getAppState().panelDidResize(
|
||
PANEL_NAME_NOTES,
|
||
isCollapsed
|
||
);
|
||
}
|
||
|
||
paginate() {
|
||
this.notesToDisplay += this.pageSize;
|
||
this.reloadNotes();
|
||
if (this.searchSubmitted) {
|
||
this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
|
||
}
|
||
}
|
||
|
||
resetPagination(keepCurrentIfLarger = false) {
|
||
const clientHeight = document.documentElement.clientHeight;
|
||
this.pageSize = Math.ceil(clientHeight / MIN_NOTE_CELL_HEIGHT);
|
||
if (this.pageSize === 0) {
|
||
this.pageSize = DEFAULT_LIST_NUM_NOTES;
|
||
}
|
||
if (keepCurrentIfLarger && this.notesToDisplay > this.pageSize) {
|
||
return;
|
||
}
|
||
this.notesToDisplay = this.pageSize;
|
||
}
|
||
|
||
reloadPanelTitle() {
|
||
let title;
|
||
if (this.isFiltering()) {
|
||
const resultCount = this.getState().notes!.length;
|
||
title = `${resultCount} search results`;
|
||
} else if (this.appState.selectedTag) {
|
||
title = `${this.appState.selectedTag.title}`;
|
||
}
|
||
this.setNotesState({
|
||
panelTitle: title
|
||
});
|
||
}
|
||
|
||
optionsSubtitle() {
|
||
let base = "";
|
||
if (this.getState().sortBy === NoteSortKey.CreatedAt) {
|
||
base += " Date Added";
|
||
} else if (this.getState().sortBy === NoteSortKey.UserUpdatedAt) {
|
||
base += " Date Modified";
|
||
} else if (this.getState().sortBy === NoteSortKey.Title) {
|
||
base += " Title";
|
||
}
|
||
if (this.getState().showArchived) {
|
||
base += " | + Archived";
|
||
}
|
||
if (this.getState().hidePinned) {
|
||
base += " | – Pinned";
|
||
}
|
||
if (this.getState().sortReverse) {
|
||
base += " | Reversed";
|
||
}
|
||
return base;
|
||
}
|
||
|
||
loadFlagsForNote(note: SNNote) {
|
||
const flags = [] as NoteFlag[];
|
||
if (note.pinned) {
|
||
flags.push({
|
||
text: "Pinned",
|
||
class: 'info'
|
||
});
|
||
}
|
||
if (note.archived) {
|
||
flags.push({
|
||
text: "Archived",
|
||
class: 'warning'
|
||
});
|
||
}
|
||
if (note.protected) {
|
||
flags.push({
|
||
text: "Protected",
|
||
class: 'success'
|
||
});
|
||
}
|
||
if (note.locked) {
|
||
flags.push({
|
||
text: "Locked",
|
||
class: 'neutral'
|
||
});
|
||
}
|
||
if (note.trashed) {
|
||
flags.push({
|
||
text: "Deleted",
|
||
class: 'danger'
|
||
});
|
||
}
|
||
if (note.conflictOf) {
|
||
flags.push({
|
||
text: "Conflicted Copy",
|
||
class: 'danger'
|
||
});
|
||
}
|
||
if (note.errorDecrypting) {
|
||
if (note.waitingForKey) {
|
||
flags.push({
|
||
text: "Waiting For Keys",
|
||
class: 'info'
|
||
});
|
||
} else {
|
||
flags.push({
|
||
text: "Missing Keys",
|
||
class: 'danger'
|
||
});
|
||
}
|
||
}
|
||
if (note.deleted) {
|
||
flags.push({
|
||
text: "Deletion Pending Sync",
|
||
class: 'danger'
|
||
});
|
||
}
|
||
this.noteFlags[note.uuid] = flags;
|
||
}
|
||
|
||
displayableNotes() {
|
||
return this.getState().notes!;
|
||
}
|
||
|
||
getFirstNonProtectedNote() {
|
||
const displayableNotes = this.displayableNotes();
|
||
let index = 0;
|
||
let note = displayableNotes[index];
|
||
while (note && note.protected) {
|
||
index++;
|
||
if (index >= displayableNotes.length) {
|
||
break;
|
||
}
|
||
note = displayableNotes[index];
|
||
}
|
||
return note;
|
||
}
|
||
|
||
selectFirstNote() {
|
||
const note = this.getFirstNonProtectedNote();
|
||
if (note) {
|
||
this.selectNote(note);
|
||
}
|
||
}
|
||
|
||
selectNextNote() {
|
||
const displayableNotes = this.displayableNotes();
|
||
const currentIndex = displayableNotes.findIndex((candidate) => {
|
||
return candidate.uuid === this.activeEditorNote!.uuid
|
||
});
|
||
if (currentIndex + 1 < displayableNotes.length) {
|
||
this.selectNote(displayableNotes[currentIndex + 1]);
|
||
}
|
||
}
|
||
|
||
selectNextOrCreateNew() {
|
||
const note = this.getFirstNonProtectedNote();
|
||
if (note) {
|
||
this.selectNote(note);
|
||
} else if (!this.appState.selectedTag || !this.appState.selectedTag.isSmartTag()) {
|
||
this.createPlaceholderNote();
|
||
} else {
|
||
this.appState.closeActiveEditor();
|
||
}
|
||
}
|
||
|
||
selectPreviousNote() {
|
||
const displayableNotes = this.displayableNotes();
|
||
const currentIndex = displayableNotes.indexOf(this.activeEditorNote!);
|
||
if (currentIndex - 1 >= 0) {
|
||
this.selectNote(displayableNotes[currentIndex - 1]);
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
isFiltering() {
|
||
return this.getState().noteFilter.text &&
|
||
this.getState().noteFilter.text.length > 0;
|
||
}
|
||
|
||
async setNoteFilterText(text: string) {
|
||
await this.setNotesState({
|
||
noteFilter: {
|
||
...this.getState().noteFilter,
|
||
text: text
|
||
}
|
||
});
|
||
}
|
||
|
||
async clearFilterText() {
|
||
await this.setNoteFilterText('');
|
||
this.onFilterEnter();
|
||
this.filterTextChanged();
|
||
this.resetPagination();
|
||
}
|
||
|
||
async filterTextChanged() {
|
||
if (this.searchSubmitted) {
|
||
this.searchSubmitted = false;
|
||
}
|
||
this.reloadNotesDisplayOptions();
|
||
await this.reloadNotes();
|
||
}
|
||
|
||
onFilterEnter() {
|
||
/**
|
||
* For Desktop, performing a search right away causes
|
||
* input to lose focus. We wait until user explicity hits
|
||
* enter before highlighting desktop search results.
|
||
*/
|
||
this.searchSubmitted = true;
|
||
this.application!.getDesktopService().searchText(this.getState().noteFilter.text);
|
||
}
|
||
|
||
selectedMenuItem() {
|
||
this.setShowMenuFalse();
|
||
}
|
||
|
||
toggleWebPrefKey(key: WebPrefKey) {
|
||
this.application!.getPrefsService().setUserPrefValue(
|
||
key,
|
||
!this.state[key],
|
||
true
|
||
);
|
||
}
|
||
|
||
selectedSortByCreated() {
|
||
this.setSortBy(NoteSortKey.CreatedAt);
|
||
}
|
||
|
||
selectedSortByUpdated() {
|
||
this.setSortBy(NoteSortKey.ClientUpdatedAt);
|
||
}
|
||
|
||
selectedSortByTitle() {
|
||
this.setSortBy(NoteSortKey.Title);
|
||
}
|
||
|
||
toggleReverseSort() {
|
||
this.selectedMenuItem();
|
||
this.application!.getPrefsService().setUserPrefValue(
|
||
WebPrefKey.SortNotesReverse,
|
||
!this.getState().sortReverse,
|
||
true
|
||
);
|
||
}
|
||
|
||
setSortBy(type: NoteSortKey) {
|
||
this.application!.getPrefsService().setUserPrefValue(
|
||
WebPrefKey.SortNotesBy,
|
||
type,
|
||
true
|
||
);
|
||
}
|
||
|
||
getSearchBar() {
|
||
return document.getElementById(ELEMENT_ID_SEARCH_BAR)!;
|
||
}
|
||
|
||
registerKeyboardShortcuts() {
|
||
/**
|
||
* In the browser we're not allowed to override cmd/ctrl + n, so we have to
|
||
* use Control modifier as well. These rules don't apply to desktop, but
|
||
* probably better to be consistent.
|
||
*/
|
||
this.newNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
|
||
key: 'n',
|
||
modifiers: [
|
||
KeyboardModifier.Meta,
|
||
KeyboardModifier.Ctrl
|
||
],
|
||
onKeyDown: (event) => {
|
||
event.preventDefault();
|
||
this.createNewNote();
|
||
}
|
||
});
|
||
|
||
this.nextNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
|
||
key: KeyboardKey.Down,
|
||
elements: [
|
||
document.body,
|
||
this.getSearchBar()
|
||
],
|
||
onKeyDown: (event) => {
|
||
const searchBar = this.getSearchBar();
|
||
if (searchBar === document.activeElement) {
|
||
searchBar.blur();
|
||
}
|
||
this.selectNextNote();
|
||
}
|
||
});
|
||
|
||
this.previousNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({
|
||
key: KeyboardKey.Up,
|
||
element: document.body,
|
||
onKeyDown: (event) => {
|
||
this.selectPreviousNote();
|
||
}
|
||
});
|
||
|
||
this.searchKeyObserver = this.application!.getKeyboardService().addKeyObserver({
|
||
key: "f",
|
||
modifiers: [
|
||
KeyboardModifier.Meta,
|
||
KeyboardModifier.Shift
|
||
],
|
||
onKeyDown: (event) => {
|
||
const searchBar = this.getSearchBar();
|
||
if (searchBar) { searchBar.focus(); };
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
export class NotesView extends WebDirective {
|
||
constructor() {
|
||
super();
|
||
this.template = template;
|
||
this.replace = true;
|
||
this.controller = NotesViewCtrl;
|
||
this.controllerAs = 'self';
|
||
this.bindToController = true;
|
||
this.scope = {
|
||
application: '='
|
||
};
|
||
}
|
||
}
|