feat: select multiple notes in list
This commit is contained in:
@@ -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';
|
||||
171
app/assets/javascripts/services/ioService.ts
Normal file
171
app/assets/javascripts/services/ioService.ts
Normal 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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
66
app/assets/javascripts/ui_models/app_state/notes_state.ts
Normal file
66
app/assets/javascripts/ui_models/app_state/notes_state.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user