feat: select multiple notes in list

This commit is contained in:
Baptiste Grob
2021-04-06 18:09:40 +02:00
parent 9599f30ad4
commit 0f53361689
11 changed files with 354 additions and 273 deletions

View File

@@ -1,8 +0,0 @@
export { AlertService } from './alertService';
export { ArchiveManager } from './archiveManager';
export { DesktopManager } from './desktopManager';
export { KeyboardManager } from './keyboardManager';
export { AutolockService } from './autolock_service';
export { NativeExtManager } from './nativeExtManager';
export { StatusManager } from './statusManager';
export { ThemeManager } from './themeManager';

View File

@@ -0,0 +1,171 @@
import { removeFromArray } from '@standardnotes/snjs';
export enum KeyboardKey {
Tab = 'Tab',
Backspace = 'Backspace',
Up = 'ArrowUp',
Down = 'ArrowDown',
}
export enum KeyboardModifier {
Shift = 'Shift',
Ctrl = 'Control',
/** ⌘ key on Mac, ⊞ key on Windows */
Meta = 'Meta',
Alt = 'Alt',
}
enum KeyboardKeyEvent {
Down = 'KeyEventDown',
Up = 'KeyEventUp',
}
type KeyboardObserver = {
key?: KeyboardKey | string;
modifiers?: KeyboardModifier[];
onKeyDown?: (event: KeyboardEvent) => void;
onKeyUp?: (event: KeyboardEvent) => void;
element?: HTMLElement;
elements?: HTMLElement[];
notElement?: HTMLElement;
notElementIds?: string[];
};
export class IOService {
readonly activeModifiers = new Set<KeyboardModifier>();
private observers: KeyboardObserver[] = [];
constructor(private isMac: boolean) {
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
public deinit() {
this.observers.length = 0;
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
(this.handleKeyDown as unknown) = undefined;
(this.handleKeyUp as unknown) = undefined;
}
handleKeyDown = (event: KeyboardEvent) => {
for (const modifier of this.modifiersForEvent(event)) {
switch (modifier) {
case KeyboardModifier.Meta: {
if (this.isMac) {
this.activeModifiers.add(modifier);
}
break;
}
case KeyboardModifier.Ctrl: {
if (!this.isMac) {
this.activeModifiers.add(modifier);
}
break;
}
default: {
this.activeModifiers.add(modifier);
break;
}
}
}
this.notifyObserver(event, KeyboardKeyEvent.Down);
};
handleKeyUp = (event: KeyboardEvent) => {
for (const modifier of this.modifiersForEvent(event)) {
this.activeModifiers.delete(modifier);
}
this.notifyObserver(event, KeyboardKeyEvent.Up);
};
modifiersForEvent(event: KeyboardEvent) {
const allModifiers = Object.values(KeyboardModifier);
const eventModifiers = allModifiers.filter((modifier) => {
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
const matches =
((event.ctrlKey || event.key === KeyboardModifier.Ctrl) &&
modifier === KeyboardModifier.Ctrl) ||
((event.metaKey || event.key === KeyboardModifier.Meta) &&
modifier === KeyboardModifier.Meta) ||
((event.altKey || event.key === KeyboardModifier.Alt) &&
modifier === KeyboardModifier.Alt) ||
((event.shiftKey || event.key === KeyboardModifier.Shift) &&
modifier === KeyboardModifier.Shift);
return matches;
});
return eventModifiers;
}
eventMatchesKeyAndModifiers(
event: KeyboardEvent,
key: KeyboardKey | string,
modifiers: KeyboardModifier[] = []
) {
const eventModifiers = this.modifiersForEvent(event);
if (eventModifiers.length !== modifiers.length) {
return false;
}
for (const modifier of modifiers) {
if (!eventModifiers.includes(modifier)) {
return false;
}
}
// Modifers match, check key
if (!key) {
return true;
}
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
// In our case we don't differentiate between the two.
return key.toLowerCase() === event.key.toLowerCase();
}
notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent) {
const target = event.target as HTMLElement;
for (const observer of this.observers) {
if (observer.element && event.target !== observer.element) {
continue;
}
if (observer.elements && !observer.elements.includes(target)) {
continue;
}
if (observer.notElement && observer.notElement === event.target) {
continue;
}
if (
observer.notElementIds &&
observer.notElementIds.includes(target.id)
) {
continue;
}
if (
this.eventMatchesKeyAndModifiers(
event,
observer.key!,
observer.modifiers
)
) {
const callback =
keyEvent === KeyboardKeyEvent.Down
? observer.onKeyDown
: observer.onKeyUp;
if (callback) {
callback(event);
}
}
}
}
addKeyObserver(observer: KeyboardObserver) {
this.observers.push(observer);
return () => {
removeFromArray(this.observers, observer);
};
}
}

View File

@@ -1,147 +0,0 @@
import { removeFromArray } from '@standardnotes/snjs';
export enum KeyboardKey {
Tab = "Tab",
Backspace = "Backspace",
Up = "ArrowUp",
Down = "ArrowDown",
}
export enum KeyboardModifier {
Shift = "Shift",
Ctrl = "Control",
/** ⌘ key on Mac, ⊞ key on Windows */
Meta = "Meta",
Alt = "Alt",
}
enum KeyboardKeyEvent {
Down = "KeyEventDown",
Up = "KeyEventUp"
}
type KeyboardObserver = {
key?: KeyboardKey | string
modifiers?: KeyboardModifier[]
onKeyDown?: (event: KeyboardEvent) => void
onKeyUp?: (event: KeyboardEvent) => void
element?: HTMLElement
elements?: HTMLElement[]
notElement?: HTMLElement
notElementIds?: string[]
}
export class KeyboardManager {
private observers: KeyboardObserver[] = []
private handleKeyDown: any
private handleKeyUp: any
constructor() {
this.handleKeyDown = (event: KeyboardEvent) => {
this.notifyObserver(event, KeyboardKeyEvent.Down);
};
this.handleKeyUp = (event: KeyboardEvent) => {
this.notifyObserver(event, KeyboardKeyEvent.Up);
};
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
public deinit() {
this.observers.length = 0;
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
this.handleKeyDown = undefined;
this.handleKeyUp = undefined;
}
modifiersForEvent(event: KeyboardEvent) {
const allModifiers = Object.values(KeyboardModifier);
const eventModifiers = allModifiers.filter((modifier) => {
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
const matches = (
(
(event.ctrlKey || event.key === KeyboardModifier.Ctrl)
&& modifier === KeyboardModifier.Ctrl
) ||
(
(event.metaKey || event.key === KeyboardModifier.Meta)
&& modifier === KeyboardModifier.Meta
) ||
(
(event.altKey || event.key === KeyboardModifier.Alt)
&& modifier === KeyboardModifier.Alt
) ||
(
(event.shiftKey || event.key === KeyboardModifier.Shift)
&& modifier === KeyboardModifier.Shift
)
);
return matches;
});
return eventModifiers;
}
eventMatchesKeyAndModifiers(
event: KeyboardEvent,
key: KeyboardKey | string,
modifiers: KeyboardModifier[] = []
) {
const eventModifiers = this.modifiersForEvent(event);
if (eventModifiers.length !== modifiers.length) {
return false;
}
for (const modifier of modifiers) {
if (!eventModifiers.includes(modifier)) {
return false;
}
}
// Modifers match, check key
if (!key) {
return true;
}
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
// In our case we don't differentiate between the two.
return key.toLowerCase() === event.key.toLowerCase();
}
notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent) {
const target = event.target as HTMLElement;
for (const observer of this.observers) {
if (observer.element && event.target !== observer.element) {
continue;
}
if (observer.elements && !observer.elements.includes(target)) {
continue;
}
if (observer.notElement && observer.notElement === event.target) {
continue;
}
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
continue;
}
if (this.eventMatchesKeyAndModifiers(event, observer.key!, observer.modifiers)) {
const callback = keyEvent === KeyboardKeyEvent.Down
? observer.onKeyDown
: observer.onKeyUp;
if (callback) {
callback(event);
}
}
}
}
addKeyObserver(observer: KeyboardObserver) {
this.observers.push(observer);
return () => {
removeFromArray(this.observers, observer);
};
}
}

View File

@@ -19,6 +19,7 @@ import { ActionsMenuState } from './actions_menu_state';
import { NoAccountWarningState } from './no_account_warning_state';
import { SyncState } from './sync_state';
import { SearchOptionsState } from './search_options_state';
import { NotesState } from './notes_state';
export enum AppStateEvent {
TagChanged,
@@ -62,7 +63,8 @@ export class AppState {
readonly actionsMenu = new ActionsMenuState();
readonly noAccountWarning: NoAccountWarningState;
readonly sync = new SyncState();
readonly searchOptions;
readonly searchOptions: SearchOptionsState;
readonly notes: NotesState;
isSessionsModalVisible = false;
private appEventObserverRemovers: (() => void)[] = [];
@@ -77,6 +79,12 @@ export class AppState {
this.$timeout = $timeout;
this.$rootScope = $rootScope;
this.application = application;
this.notes = new NotesState(
this.application,
async () => {
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
}
);
this.noAccountWarning = new NoAccountWarningState(
application,
this.appEventObserverRemovers
@@ -175,28 +183,6 @@ export class AppState {
}
}
async openEditor(noteUuid: string): Promise<void> {
if (this.getActiveEditor()?.note?.uuid === noteUuid) {
return;
}
const note = this.application.findItem(noteUuid) as SNNote;
if (!note) {
console.warn('Tried accessing a non-existant note of UUID ' + noteUuid);
return;
}
if (await this.application.authorizeNoteAccess(note)) {
const activeEditor = this.getActiveEditor();
if (!activeEditor) {
this.application.editorGroup.createEditor(noteUuid);
} else {
activeEditor.setNote(note);
}
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
}
}
getActiveEditor() {
return this.application.editorGroup.editors[0];
}

View File

@@ -0,0 +1,66 @@
import { KeyboardModifier } from "@/services/ioService";
import { UuidString, SNNote } from "@standardnotes/snjs";
import { makeObservable, observable, action } from "mobx";
import { WebApplication } from "../application";
import { Editor } from "../editor";
export class NotesState {
selectedNotes: Record<UuidString, SNNote> = {};
constructor(
private application: WebApplication,
private onActiveEditorChanged: () => Promise<void>
) {
makeObservable(this, {
selectedNotes: observable,
selectNote: action,
});
}
get activeEditor(): Editor | undefined {
return this.application.editorGroup.editors[0];
}
async selectNote(note: SNNote): Promise<void> {
if (
this.io.activeModifiers.has(KeyboardModifier.Meta) ||
this.io.activeModifiers.has(KeyboardModifier.Ctrl)
) {
this.selectedNotes[note.uuid] = note;
} else {
this.selectedNotes = {
[note.uuid]: note,
};
}
await this.openEditor(note.uuid);
}
async openEditor(noteUuid: string): Promise<void> {
if (this.activeEditor?.note?.uuid === noteUuid) {
return;
}
const note = this.application.findItem(noteUuid) as SNNote | undefined;
if (!note) {
console.warn('Tried accessing a non-existant note of UUID ' + noteUuid);
return;
}
if (await this.application.authorizeNoteAccess(note)) {
if (!this.activeEditor) {
this.application.editorGroup.createEditor(noteUuid);
} else {
this.activeEditor.setNote(note);
}
await this.onActiveEditorChanged();
if (note.waitingForKey) {
this.application.presentKeyRecoveryWizard();
}
}
}
private get io() {
return this.application.io;
}
}

View File

@@ -9,23 +9,21 @@ import {
SNComponent,
PermissionDialog,
DeinitSource,
Platform,
} from '@standardnotes/snjs';
import angular from 'angular';
import { getPlatform } from '@/utils';
import { AlertService } from '@/services/alertService';
import { WebDeviceInterface } from '@/web_device_interface';
import {
DesktopManager,
AutolockService,
ArchiveManager,
NativeExtManager,
StatusManager,
ThemeManager,
KeyboardManager
} from '@/services';
import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge';
import { WebCrypto } from '@/crypto';
import { AlertService } from '@/services/alertService';
import { AutolockService } from '@/services/autolock_service';
import { ArchiveManager } from '@/services/archiveManager';
import { DesktopManager } from '@/services/desktopManager';
import { IOService } from '@/services/ioService';
import { NativeExtManager } from '@/services/nativeExtManager';
import { StatusManager } from '@/services/statusManager';
import { ThemeManager } from '@/services/themeManager';
type WebServices = {
appState: AppState;
@@ -35,7 +33,7 @@ type WebServices = {
nativeExtService: NativeExtManager;
statusManager: StatusManager;
themeService: ThemeManager;
keyboardService: KeyboardManager;
io: IOService;
}
export class WebApplication extends SNApplication {
@@ -49,6 +47,7 @@ export class WebApplication extends SNApplication {
/* @ngInject */
constructor(
deviceInterface: WebDeviceInterface,
platform: Platform,
identifier: string,
private $compile: angular.ICompileService,
scope: angular.IScope,
@@ -57,7 +56,7 @@ export class WebApplication extends SNApplication {
) {
super(
bridge.environment,
getPlatform(),
platform,
deviceInterface,
WebCrypto,
new AlertService(),
@@ -139,8 +138,8 @@ export class WebApplication extends SNApplication {
return this.webServices.themeService;
}
public getKeyboardService() {
return this.webServices.keyboardService;
public get io() {
return this.webServices.io;
}
async checkForSecurityUpdate() {

View File

@@ -1,24 +1,26 @@
import { WebDeviceInterface } from '@/web_device_interface';
import { WebApplication } from './application';
import { ApplicationDescriptor, SNApplicationGroup, DeviceInterface } from '@standardnotes/snjs';
import {
ArchiveManager,
DesktopManager,
KeyboardManager,
AutolockService,
NativeExtManager,
StatusManager,
ThemeManager
} from '@/services';
ApplicationDescriptor,
SNApplicationGroup,
DeviceInterface,
Platform,
} from '@standardnotes/snjs';
import { AppState } from '@/ui_models/app_state';
import { Bridge } from '@/services/bridge';
import { isDesktopApplication } from '@/utils';
import { getPlatform, isDesktopApplication } from '@/utils';
import { ArchiveManager } from '@/services/archiveManager';
import { DesktopManager } from '@/services/desktopManager';
import { IOService } from '@/services/ioService';
import { AutolockService } from '@/services/autolock_service';
import { StatusManager } from '@/services/statusManager';
import { NativeExtManager } from '@/services/nativeExtManager';
import { ThemeManager } from '@/services/themeManager';
export class ApplicationGroup extends SNApplicationGroup {
$compile: ng.ICompileService
$rootScope: ng.IRootScopeService
$timeout: ng.ITimeoutService
$compile: ng.ICompileService;
$rootScope: ng.IRootScopeService;
$timeout: ng.ITimeoutService;
/* @ngInject */
constructor(
@@ -26,75 +28,72 @@ export class ApplicationGroup extends SNApplicationGroup {
$rootScope: ng.IRootScopeService,
$timeout: ng.ITimeoutService,
private defaultSyncServerHost: string,
private bridge: Bridge,
private bridge: Bridge
) {
super(new WebDeviceInterface(
$timeout,
bridge
));
super(new WebDeviceInterface($timeout, bridge));
this.$compile = $compile;
this.$timeout = $timeout;
this.$rootScope = $rootScope;
}
async initialize(callback?: any) {
async initialize(callback?: any): Promise<void> {
await super.initialize({
applicationCreator: this.createApplication
applicationCreator: this.createApplication,
});
if (isDesktopApplication()) {
Object.defineProperty(window, 'desktopManager', {
get: () => (this.primaryApplication as WebApplication).getDesktopService()
get: () =>
(this.primaryApplication as WebApplication).getDesktopService(),
});
}
}
private createApplication = (descriptor: ApplicationDescriptor, deviceInterface: DeviceInterface) => {
private createApplication = (
descriptor: ApplicationDescriptor,
deviceInterface: DeviceInterface
) => {
const scope = this.$rootScope.$new(true);
const platform = getPlatform();
const application = new WebApplication(
deviceInterface as WebDeviceInterface,
platform,
descriptor.identifier,
this.$compile,
scope,
this.defaultSyncServerHost,
this.bridge,
this.bridge
);
const appState = new AppState(
this.$rootScope,
this.$timeout,
application,
this.bridge,
);
const archiveService = new ArchiveManager(
application
this.bridge
);
const archiveService = new ArchiveManager(application);
const desktopService = new DesktopManager(
this.$rootScope,
this.$timeout,
application,
this.bridge,
this.bridge
);
const keyboardService = new KeyboardManager();
const autolockService = new AutolockService(
application
);
const nativeExtService = new NativeExtManager(
application
);
const statusService = new StatusManager();
const themeService = new ThemeManager(
application,
const io = new IOService(
platform === Platform.MacWeb || platform === Platform.MacDesktop
);
const autolockService = new AutolockService(application);
const nativeExtService = new NativeExtManager(application);
const statusManager = new StatusManager();
const themeService = new ThemeManager(application);
application.setWebServices({
appState,
archiveService,
desktopService,
keyboardService,
io,
autolockService,
nativeExtService,
statusManager: statusService,
themeService
statusManager,
themeService,
});
return application;
}
};
}

View File

@@ -1,5 +1,7 @@
import { ApplicationEvent } from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx';
export type CtrlState = Partial<Record<string, any>>
export type CtrlProps = Partial<Record<string, any>>
@@ -17,6 +19,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
* no Angular handlebars/syntax render in the UI before display data is ready.
*/
protected templateReady = false
private reactionDisposers: IReactionDisposer[] = [];
/* @ngInject */
constructor(
@@ -26,7 +29,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
this.$timeout = $timeout;
}
$onInit() {
$onInit(): void {
this.state = {
...this.getInitialState(),
...this.state,
@@ -36,9 +39,13 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
this.templateReady = true;
}
deinit() {
deinit(): void {
this.unsubApp();
this.unsubState();
for (const disposer of this.reactionDisposers) {
disposer();
}
this.reactionDisposers.length = 0;
this.unsubApp = undefined;
this.unsubState = undefined;
if (this.stateTimeout) {
@@ -46,16 +53,16 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
}
}
$onDestroy() {
$onDestroy(): void {
this.deinit();
}
public get appState() {
return this.application!.getAppState();
public get appState(): AppState {
return this.application.getAppState();
}
/** @private */
async resetState() {
async resetState(): Promise<void> {
this.state = this.getInitialState();
await this.setState(this.state);
}
@@ -65,7 +72,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
return {} as any;
}
async setState(state: Partial<S>) {
async setState(state: Partial<S>): Promise<void> {
if (!this.$timeout) {
return;
}
@@ -88,17 +95,21 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
}
/** @returns a promise that resolves after the UI has been updated. */
flushUI() {
flushUI(): angular.IPromise<void> {
return this.$timeout();
}
initProps(props: CtrlProps) {
initProps(props: CtrlProps): void {
if (Object.keys(this.props).length > 0) {
throw 'Already init-ed props.';
}
this.props = Object.freeze(Object.assign({}, this.props, props));
}
autorun(view: (r: IReactionPublic) => void): void {
this.reactionDisposers.push(autorun(view));
}
addAppStateObserver() {
this.unsubState = this.application!.getAppState().addObserver(
async (eventName, data) => {

View File

@@ -24,7 +24,7 @@ import {
} from '@standardnotes/snjs';
import find from 'lodash/find';
import { isDesktopApplication } from '@/utils';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import { KeyboardModifier, KeyboardKey } from '@/services/ioService';
import template from './editor-view.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { EventSource } from '@/ui_models/app_state';
@@ -1106,7 +1106,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
registerKeyboardShortcuts() {
this.removeAltKeyObserver = this.application
.getKeyboardService()
.io
.addKeyObserver({
modifiers: [KeyboardModifier.Alt],
onKeyDown: () => {
@@ -1122,7 +1122,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
});
this.removeTrashKeyObserver = this.application
.getKeyboardService()
.io
.addKeyObserver({
key: KeyboardKey.Backspace,
notElementIds: [ElementIds.NoteTextEditor, ElementIds.NoteTitleEditor],
@@ -1147,7 +1147,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
ElementIds.NoteTextEditor
)! as HTMLInputElement;
this.removeTabObserver = this.application
.getKeyboardService()
.io
.addKeyObserver({
element: editor,
key: KeyboardKey.Tab,

View File

@@ -127,7 +127,7 @@
)
.note(
ng-repeat='note in self.state.renderedNotes track by note.uuid'
ng-class="{'selected' : self.activeEditorNote.uuid == note.uuid}"
ng-class="{'selected' : self.isNoteSelected(note.uuid) }"
ng-click='self.selectNote(note)'
)
.note-flags(ng-show='self.noteFlags[note.uuid].length > 0')

View File

@@ -14,17 +14,17 @@ import {
} from '@standardnotes/snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent } from '@/ui_models/app_state';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import { KeyboardKey, KeyboardModifier } from '@/services/ioService';
import {
PANEL_NAME_NOTES
} from '@/views/constants';
import { autorun, IReactionDisposer } from 'mobx';
type NotesState = {
type NotesCtrlState = {
panelTitle: string
notes: SNNote[]
renderedNotes: SNNote[]
renderedNotesTags: string[],
selectedNotes: Record<UuidString, SNNote>,
sortBy?: string
sortReverse?: boolean
showArchived?: boolean
@@ -65,7 +65,7 @@ const DEFAULT_LIST_NUM_NOTES = 20;
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
class NotesViewCtrl extends PureViewCtrl<unknown, NotesCtrlState> {
private panelPuppet?: PanelPuppet
private reloadNotesPromise?: any
@@ -78,7 +78,6 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
private searchKeyObserver: any
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
private removeObservers: Array<() => void> = [];
private appStateObserver?: IReactionDisposer;
/* @ngInject */
constructor($timeout: ng.ITimeoutService,) {
@@ -95,7 +94,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.onPanelResize = this.onPanelResize.bind(this);
window.addEventListener('resize', this.onWindowResize, true);
this.registerKeyboardShortcuts();
this.appStateObserver = autorun(async () => {
this.autorun(async () => {
const {
includeProtectedContents,
includeArchived,
@@ -113,6 +112,11 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.reloadNotes();
}
});
this.autorun(() => {
this.setState({
selectedNotes: this.appState.notes.selectedNotes,
});
});
}
onWindowResize() {
@@ -131,7 +135,6 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
this.nextNoteKeyObserver();
this.previousNoteKeyObserver();
this.searchKeyObserver();
this.appStateObserver?.();
this.newNoteKeyObserver = undefined;
this.nextNoteKeyObserver = undefined;
this.previousNoteKeyObserver = undefined;
@@ -139,15 +142,16 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
super.deinit();
}
async setNotesState(state: Partial<NotesState>) {
async setNotesState(state: Partial<NotesCtrlState>) {
return this.setState(state);
}
getInitialState(): NotesState {
getInitialState(): NotesCtrlState {
return {
notes: [],
renderedNotes: [],
renderedNotesTags: [],
selectedNotes: {},
mutable: { showMenu: false },
noteFilter: {
text: '',
@@ -180,9 +184,13 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
}
}
private get activeEditorNote() {
return this.appState.notes.activeEditor?.note;
}
/** @template */
public get activeEditorNote() {
return this.appState?.getActiveEditor()?.note;
public isNoteSelected(uuid: UuidString) {
return !!this.state.selectedNotes[uuid];
}
public get editorNotes() {
@@ -288,12 +296,8 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
));
}
async selectNote(note: SNNote) {
await this.appState.openEditor(note.uuid);
if (note.waitingForKey) {
this.application.presentKeyRecoveryWizard();
}
this.reloadNotes();
selectNote(note: SNNote): Promise<void> {
return this.appState.notes.selectNote(note);
}
async createNewNote() {
@@ -461,7 +465,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
}
async reloadPreferences() {
const viewOptions = {} as NotesState;
const viewOptions = {} as NotesCtrlState;
const prevSortValue = this.state.sortBy;
let sortBy = this.application.getPreference(
PrefKey.SortNotesBy,
@@ -673,7 +677,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
selectNextNote() {
const displayableNotes = this.state.notes;
const currentIndex = displayableNotes.findIndex((candidate) => {
return candidate.uuid === this.activeEditorNote.uuid;
return candidate.uuid === this.activeEditorNote?.uuid;
});
if (currentIndex + 1 < displayableNotes.length) {
this.selectNote(displayableNotes[currentIndex + 1]);
@@ -791,7 +795,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
this.newNoteKeyObserver = this.application.io.addKeyObserver({
key: 'n',
modifiers: [
KeyboardModifier.Meta,
@@ -803,7 +807,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
}
});
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
this.nextNoteKeyObserver = this.application.io.addKeyObserver({
key: KeyboardKey.Down,
elements: [
document.body,
@@ -818,7 +822,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
}
});
this.previousNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
this.previousNoteKeyObserver = this.application.io.addKeyObserver({
key: KeyboardKey.Up,
element: document.body,
onKeyDown: () => {
@@ -826,7 +830,7 @@ class NotesViewCtrl extends PureViewCtrl<unknown, NotesState> {
}
});
this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({
this.searchKeyObserver = this.application.io.addKeyObserver({
key: "f",
modifiers: [
KeyboardModifier.Meta,