Remove dummy concept in favor of editor group and editors

This commit is contained in:
Mo Bitar
2020-04-14 15:01:32 -05:00
parent ef66170ba4
commit 9cf99896a5
81 changed files with 8136 additions and 7179 deletions

View File

@@ -0,0 +1,151 @@
import { SNNote, SNTag } from 'snjs';
export enum NoteSortKey {
CreatedAt = 'created_at',
UpdatedAt = 'updated_at',
ClientUpdatedAt = 'client_updated_at',
Title = 'title',
}
export function filterAndSortNotes(
notes: SNNote[],
selectedTag: SNTag,
showArchived: boolean,
hidePinned: boolean,
filterText: string,
sortBy: string,
reverse: boolean,
) {
const filtered = filterNotes(
notes,
selectedTag,
showArchived,
hidePinned,
filterText,
);
const sorted = sortNotes(
filtered,
sortBy,
reverse
);
return sorted;
}
export function filterNotes(
notes: SNNote[],
selectedTag: SNTag,
showArchived: boolean,
hidePinned: boolean,
filterText: string
) {
return notes.filter((note) => {
let canShowArchived = showArchived;
const canShowPinned = !hidePinned;
const isTrash = selectedTag.isTrashTag;
if (!isTrash && note.trashed) {
return false;
}
const isSmartTag = selectedTag.isSmartTag();
if (isSmartTag) {
canShowArchived = (
canShowArchived ||
selectedTag.isArchiveTag ||
isTrash
);
}
if (
(note.archived && !canShowArchived) ||
(note.pinned && !canShowPinned)
) {
return false;
}
return noteMatchesQuery(note, filterText);
});
}
function noteMatchesQuery(
note: SNNote,
query: string
) {
if(query.length === 0) {
return true;
}
const title = note.safeTitle().toLowerCase();
const text = note.safeText().toLowerCase();
const lowercaseText = query.toLowerCase();
const quotedText = stringBetweenQuotes(lowercaseText);
if(quotedText) {
return title.includes(quotedText) || text.includes(quotedText);
}
if (stringIsUuid(lowercaseText)) {
return note.uuid === lowercaseText;
}
const words = lowercaseText.split(" ");
const matchesTitle = words.every((word) => {
return title.indexOf(word) >= 0;
});
const matchesBody = words.every((word) => {
return text.indexOf(word) >= 0;
});
return matchesTitle || matchesBody;
}
function stringBetweenQuotes(text: string) {
const matches = text.match(/"(.*?)"/);
return matches ? matches[1] : null;
}
function stringIsUuid(text: string) {
const matches = text.match(
/\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/
);
// eslint-disable-next-line no-unneeded-ternary
return matches ? true : false;
}
export function sortNotes(
notes: SNNote[] = [],
sortBy: string,
reverse: boolean
) {
const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => {
if (!pinCheck) {
if (a.pinned && b.pinned) {
return sortValueFn(a, b, true);
}
if (a.pinned) { return -1; }
if (b.pinned) { return 1; }
}
let aValue = (a as any)[sortBy] || '';
let bValue = (b as any)[sortBy] || '';
let vector = 1;
if (reverse) {
vector *= -1;
}
if (sortBy === NoteSortKey.Title) {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
if (aValue.length === 0 && bValue.length === 0) {
return 0;
} else if (aValue.length === 0 && bValue.length !== 0) {
return 1 * vector;
} else if (aValue.length !== 0 && bValue.length === 0) {
return -1 * vector;
} else {
vector *= -1;
}
}
if (aValue > bValue) { return -1 * vector; }
else if (aValue < bValue) { return 1 * vector; }
return 0;
};
const result = notes.sort(function (a, b) {
return sortValueFn(a, b);
});
return result;
}

View File

@@ -0,0 +1,149 @@
#notes-column.sn-component.section.notes(aria-label='Notes')
.content
#notes-title-bar.section-title-bar
.padded
.section-title-bar-header
.title {{self.state.panelTitle}}
.sk-button.contrast.wide(
ng-click='self.createNewNote()',
title='Create a new note in the selected tag'
)
.sk-label
i.icon.ion-plus.add-button
.filter-section(role='search')
input#search-bar.filter-bar(
ng-blur='self.onFilterEnter()',
ng-change='self.filterTextChanged()',
ng-keyup='$event.keyCode == 13 && self.onFilterEnter();',
ng-model='self.state.noteFilter.text',
placeholder='Search',
select-on-click='true',
title='Searches notes in the currently selected tag'
)
#search-clear-button(
ng-click='self.clearFilterText();',
ng-show='self.state.noteFilter.text'
) ✕
#notes-menu-bar.sn-component
.sk-app-bar.no-edges
.left
.sk-app-bar-item(
ng-class="{'selected' : self.state.mutable.showMenu}",
ng-click='self.state.mutable.showMenu = !self.state.mutable.showMenu'
)
.sk-app-bar-item-column
.sk-label
| Options
.sk-app-bar-item-column
.sk-sublabel {{self.optionsSubtitle()}}
#notes-options-menu.sk-menu-panel.dropdown-menu(
ng-show='self.state.mutable.showMenu'
)
.sk-menu-panel-header
.sk-menu-panel-header-title Sort By
a.info.sk-h5(ng-click='self.toggleReverseSort()')
| {{self.state.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}}
menu-row(
action="self.selectedMenuItem(); self.selectedSortByCreated()"
circle="self.state.sortBy == 'created_at' && 'success'"
desc="'Sort notes by newest first'"
label="'Date Added'"
)
menu-row(
action="self.selectedMenuItem(); self.selectedSortByUpdated()"
circle="self.state.sortBy == 'client_updated_at' && 'success'"
desc="'Sort notes with the most recently updated first'"
label="'Date Modified'"
)
menu-row(
action="self.selectedMenuItem(); self.selectedSortByTitle()"
circle="self.state.sortBy == 'title' && 'success'"
desc="'Sort notes alphabetically by their title'"
label="'Title'"
)
.sk-menu-panel-section
.sk-menu-panel-header
.sk-menu-panel-header-title Display
menu-row(
action="self.selectedMenuItem(); self.toggleWebPrefKey('showArchived')"
circle="self.state.showArchived ? 'success' : 'danger'"
desc=`'Archived notes are usually hidden.
You can explicitly show them with this option.'`
faded="!self.state.showArchived"
label="'Archived Notes'"
)
menu-row(
action="self.selectedMenuItem(); self.toggleWebPrefKey('hidePinned')"
circle="self.state.hidePinned ? 'danger' : 'success'"
desc=`'Pinned notes always appear on top. You can hide them temporarily
with this option so you can focus on other notes in the list.'`
faded="self.state.hidePinned"
label="'Pinned Notes'"
)
menu-row(
action="self.selectedMenuItem(); self.toggleWebPrefKey('hideNotePreview')"
circle="self.state.hideNotePreview ? 'danger' : 'success'"
desc="'Hide the note preview for a more condensed list of notes'"
faded="self.state.hideNotePreview"
label="'Note Preview'"
)
menu-row(
action="self.selectedMenuItem(); self.toggleWebPrefKey('hideDate')"
circle="self.state.hideDate ? 'danger' : 'success'"
desc="'Hide the date displayed in each row'"
faded="self.state.hideDate"
label="'Date'"
)
menu-row(
action="self.selectedMenuItem(); self.toggleWebPrefKey('hideTags')"
circle="self.state.hideTags ? 'danger' : 'success'"
desc="'Hide the list of tags associated with each note'"
faded="self.state.hideTags"
label="'Tags'"
)
.scrollable
#notes-scrollable.infinite-scroll(
can-load='true',
infinite-scroll='self.paginate()',
threshold='200'
)
.note(
ng-repeat='note in self.state.renderedNotes track by note.uuid'
ng-class="{'selected' : self.activeEditorNote == note}"
ng-click='self.selectNote(note)'
)
.note-flags(ng-show='self.noteFlags[note.uuid].length > 0')
.flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]')
.label {{flag.text}}
.name(ng-show='note.title')
| {{note.title}}
.note-preview(
ng-if=`
!self.state.hideNotePreview &&
!note.hidePreview &&
!note.protected`
)
.html-preview(
ng-bind-html='note.preview_html',
ng-show='note.preview_html'
)
.plain-preview(
ng-show='!note.preview_html && note.preview_plain'
) {{note.preview_plain}}
.default-preview(
ng-show='!note.preview_html && !note.preview_plain'
) {{note.text}}
.date.faded(ng-show='!self.state.hideDate')
span(ng-show="self.state.sortBy == 'client_updated_at'")
| Modified {{note.updatedAtString || 'Now'}}
span(ng-show="self.state.sortBy != 'client_updated_at'")
| {{note.createdAtString || 'Now'}}
panel-resizer(
collapsable="true"
control="self.panelPuppet"
default-width="300"
hoverable="true"
on-resize-finish="self.onPanelResize()"
panel-id="'notes-column'"
)

View File

@@ -0,0 +1,732 @@
import { PanelPuppet, WebDirective } from './../../types';
import angular from 'angular';
import template from './notes-view.pug';
import {
ApplicationEvent,
ContentType,
removeFromArray,
SNNote,
SNTag,
WebPrefKey
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent } from '@/services/state';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import {
PANEL_NAME_NOTES
} from '@/views/constants';
import {
NoteSortKey,
filterAndSortNotes
} from './note_utils';
import { UuidString } from '@/../../../../snjs/dist/@types/types';
type NotesState = {
panelTitle: string
tag?: SNTag
notes?: SNNote[]
renderedNotes?: SNNote[]
sortBy?: string
sortReverse?: boolean
showArchived?: boolean
hidePinned?: boolean
hideNotePreview?: boolean
hideDate?: boolean
hideTags?: 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[]>> = {}
/* @ngInject */
constructor($timeout: ng.ITimeoutService, ) {
super($timeout);
this.resetPagination();
}
$onInit() {
super.$onInit();
angular.element(document).ready(() => {
this.reloadPreferences();
});
this.panelPuppet = {
onReady: () => this.reloadPreferences()
};
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();
this.reloadNotes();
} else if (eventName === AppStateEvent.EditorFocused) {
this.setShowMenuFalse();
}
}
get activeEditorNote() {
const activeEditor = this.appState.getActiveEditor();
return activeEditor && activeEditor.note;
}
public get editorNotes() {
return this.appState.getEditors().map((editor) => editor.note);
}
/** @override */
async onAppEvent(eventName: ApplicationEvent) {
if (eventName === ApplicationEvent.SignedIn) {
this.appState.closeAllEditors();
} else if (eventName === ApplicationEvent.CompletedSync) {
this.getMostValidNotes().then((notes) => {
if (notes.length === 0) {
this.createPlaceholderNote();
}
});
}
}
/**
* @access private
* Access the current state notes without awaiting any potential reloads
* that may be in progress. This is the sync alternative to `async getMostValidNotes`
*/
getPossiblyStaleNotes() {
return this.getState().notes!;
}
/**
* @access private
* 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`
*/
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).
* @access private
*/
async createPlaceholderNote() {
const selectedTag = this.application!.getAppState().getSelectedTag()!;
if (selectedTag.isSmartTag() && !selectedTag.isAllTag) {
return;
}
return this.createNewNote();
}
streamNotesAndTags() {
this.application!.streamItems(
[ContentType.Note, ContentType.Tag],
async (items) => {
await this.reloadNotes();
const activeNote = this.activeEditorNote;
if (activeNote) {
const discarded = activeNote.deleted || activeNote.trashed;
if (discarded) {
this.selectNextOrCreateNew();
}
} else {
this.selectFirstNote();
}
/** Note has changed values, reset its flags */
const notes = items.filter((item) => item.content_type === ContentType.Note) as SNNote[];
for (const note of notes) {
if (note.deleted) {
continue;
}
this.loadFlagsForNote(note);
}
}
);
}
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) {
await this.setNotesState({ tag });
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();
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)
});
}
async reloadNotes() {
this.reloadNotesPromise = this.performPeloadNotes();
return this.reloadNotesPromise;
}
async performPeloadNotes() {
const tag = this.getState().tag!;
if (!tag) {
return;
}
const tagNotes = this.appState.getTagNotes(tag);
const notes = filterAndSortNotes(
tagNotes,
tag,
this.getState().showArchived!,
this.getState().hidePinned!,
this.getState().noteFilter.text.toLowerCase(),
this.getState().sortBy!,
this.getState().sortReverse!
);
for (const note of notes) {
if (note.errorDecrypting) {
this.loadFlagsForNote(note);
}
}
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);
}
}
reloadPreferences() {
const viewOptions = {} as NotesState;
const prevSortValue = this.getState().sortBy;
let sortBy = this.application!.getPrefsService().getValue(
WebPrefKey.SortNotesBy,
NoteSortKey.CreatedAt
);
if (sortBy === NoteSortKey.UpdatedAt) {
/** Use client_updated_at instead */
sortBy = NoteSortKey.ClientUpdatedAt;
}
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
);
viewOptions.hideTags = this.application!.getPrefsService().getValue(
WebPrefKey.NotesHideTags,
false
);
this.setNotesState({
...viewOptions
});
if (prevSortValue && prevSortValue !== sortBy) {
this.selectFirstNote();
}
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,
lastLeft: number,
isAtMaxWidth: boolean,
isCollapsed: boolean
) {
this.application!.getPrefsService().setUserPrefValue(
WebPrefKey.NotesPanelWidth,
newWidth
);
this.application!.getPrefsService().syncUserPreferences();
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.getState().tag) {
title = `${this.getState().tag!.title}`;
}
this.setNotesState({
panelTitle: title
});
}
optionsSubtitle() {
let base = "";
if (this.getState().sortBy === 'created_at') {
base += " Date Added";
} else if (this.getState().sortBy === 'client_updated_at') {
base += " Date Modified";
} else if (this.getState().sortBy === '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;
return 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.getState().tag || !this.getState().tag!.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;
}
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]);
this.application!.getPrefsService().syncUserPreferences();
}
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
);
this.application!.getPrefsService().syncUserPreferences();
}
setSortBy(type: NoteSortKey) {
this.application!.getPrefsService().setUserPrefValue(
WebPrefKey.SortNotesBy,
type
);
this.application!.getPrefsService().syncUserPreferences();
}
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: '='
};
}
}