Merge branch 'release/10.5.0'

This commit is contained in:
Mo
2021-12-28 10:52:29 -06:00
19 changed files with 268 additions and 253 deletions

View File

@@ -30,8 +30,8 @@ import { AccountSwitcher } from './views/account_switcher/account_switcher';
import { import {
ApplicationGroupView, ApplicationGroupView,
ApplicationView, ApplicationView,
EditorGroupView, NoteGroupViewDirective,
EditorView, NoteViewDirective,
TagsView, TagsView,
FooterView, FooterView,
ChallengeModal, ChallengeModal,
@@ -141,8 +141,8 @@ const startApplication: StartApplication = async function startApplication(
.module('app') .module('app')
.directive('applicationGroupView', () => new ApplicationGroupView()) .directive('applicationGroupView', () => new ApplicationGroupView())
.directive('applicationView', () => new ApplicationView()) .directive('applicationView', () => new ApplicationView())
.directive('editorGroupView', () => new EditorGroupView()) .directive('noteGroupView', () => new NoteGroupViewDirective())
.directive('editorView', () => new EditorView()) .directive('noteView', () => new NoteViewDirective())
.directive('tagsView', () => new TagsView()) .directive('tagsView', () => new TagsView())
.directive('footerView', () => new FooterView()); .directive('footerView', () => new FooterView());

View File

@@ -2,7 +2,7 @@ import { Bridge } from '@/services/bridge';
import { storage, StorageKey } from '@/services/localStorage'; import { storage, StorageKey } from '@/services/localStorage';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { AccountMenuState } from '@/ui_models/app_state/account_menu_state'; import { AccountMenuState } from '@/ui_models/app_state/account_menu_state';
import { Editor } from '@/ui_models/editor'; import { NoteViewController } from '@/views/note_view/note_view_controller';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { import {
ApplicationEvent, ApplicationEvent,
@@ -226,9 +226,9 @@ export class AppState {
storage.set(StorageKey.ShowBetaWarning, true); storage.set(StorageKey.ShowBetaWarning, true);
} }
async createEditor(title?: string) { async openNewNote(title?: string) {
if (!this.multiEditorSupport) { if (!this.multiEditorSupport) {
this.closeActiveEditor(); this.closeActiveNoteController();
} }
const activeTagUuid = this.selectedTag const activeTagUuid = this.selectedTag
? this.selectedTag.isSmartTag ? this.selectedTag.isSmartTag
@@ -236,37 +236,37 @@ export class AppState {
: this.selectedTag.uuid : this.selectedTag.uuid
: undefined; : undefined;
await this.application.editorGroup.createEditor( await this.application.noteControllerGroup.createNoteView(
undefined, undefined,
title, title,
activeTagUuid activeTagUuid
); );
} }
getActiveEditor() { getActiveNoteController() {
return this.application.editorGroup.editors[0]; return this.application.noteControllerGroup.noteControllers[0];
} }
getEditors() { getNoteControllers() {
return this.application.editorGroup.editors; return this.application.noteControllerGroup.noteControllers;
} }
closeEditor(editor: Editor) { closeNoteController(controller: NoteViewController) {
this.application.editorGroup.closeEditor(editor); this.application.noteControllerGroup.closeNoteView(controller);
} }
closeActiveEditor() { closeActiveNoteController() {
this.application.editorGroup.closeActiveEditor(); this.application.noteControllerGroup.closeActiveNoteView();
} }
closeAllEditors() { closeAllNoteControllers() {
this.application.editorGroup.closeAllEditors(); this.application.noteControllerGroup.closeAllNoteViews();
} }
editorForNote(note: SNNote) { noteControllerForNote(note: SNNote) {
for (const editor of this.getEditors()) { for (const controller of this.getNoteControllers()) {
if (editor.note.uuid === note.uuid) { if (controller.note.uuid === note.uuid) {
return editor; return controller;
} }
} }
} }
@@ -275,31 +275,31 @@ export class AppState {
this.application.streamItems( this.application.streamItems(
[ContentType.Note, ContentType.Tag], [ContentType.Note, ContentType.Tag],
async (items, source) => { async (items, source) => {
/** Close any editors for deleted/trashed/archived notes */ /** Close any note controllers for deleted/trashed/archived notes */
if (source === PayloadSource.PreSyncSave) { if (source === PayloadSource.PreSyncSave) {
const notes = items.filter( const notes = items.filter(
(candidate) => candidate.content_type === ContentType.Note (candidate) => candidate.content_type === ContentType.Note
) as SNNote[]; ) as SNNote[];
for (const note of notes) { for (const note of notes) {
const editor = this.editorForNote(note); const noteController = this.noteControllerForNote(note);
if (!editor) { if (!noteController) {
continue; continue;
} }
if (note.deleted) { if (note.deleted) {
this.closeEditor(editor); this.closeNoteController(noteController);
} else if ( } else if (
note.trashed && note.trashed &&
!this.selectedTag?.isTrashTag && !this.selectedTag?.isTrashTag &&
!this.searchOptions.includeTrashed !this.searchOptions.includeTrashed
) { ) {
this.closeEditor(editor); this.closeNoteController(noteController);
} else if ( } else if (
note.archived && note.archived &&
!this.selectedTag?.isArchiveTag && !this.selectedTag?.isArchiveTag &&
!this.searchOptions.includeArchived && !this.searchOptions.includeArchived &&
!this.application.getPreference(PrefKey.NotesShowArchived, false) !this.application.getPreference(PrefKey.NotesShowArchived, false)
) { ) {
this.closeEditor(editor); this.closeNoteController(noteController);
} }
} }
} }

View File

@@ -48,7 +48,7 @@ export class NoteTagsState {
} }
get activeNote(): SNNote | undefined { get activeNote(): SNNote | undefined {
return this.appState.notes.activeEditor?.note; return this.appState.notes.activeNoteController?.note;
} }
get autocompleteTagHintVisible(): boolean { get autocompleteTagHintVisible(): boolean {

View File

@@ -17,7 +17,7 @@ import {
runInAction, runInAction,
} from 'mobx'; } from 'mobx';
import { WebApplication } from '../application'; import { WebApplication } from '../application';
import { Editor } from '../editor'; import { NoteViewController } from '@/views/note_view/note_view_controller';
import { AppState } from './app_state'; import { AppState } from './app_state';
export class NotesState { export class NotesState {
@@ -68,8 +68,8 @@ export class NotesState {
); );
} }
get activeEditor(): Editor | undefined { get activeNoteController(): NoteViewController | undefined {
return this.application.editorGroup.editors[0]; return this.application.noteControllerGroup.noteControllers[0];
} }
get selectedNotesCount(): number { get selectedNotesCount(): number {
@@ -151,13 +151,13 @@ export class NotesState {
} }
if (this.selectedNotesCount === 1) { if (this.selectedNotesCount === 1) {
await this.openEditor(Object.keys(this.selectedNotes)[0]); await this.openNote(Object.keys(this.selectedNotes)[0]);
} }
} }
} }
private async openEditor(noteUuid: string): Promise<void> { private async openNote(noteUuid: string): Promise<void> {
if (this.activeEditor?.note?.uuid === noteUuid) { if (this.activeNoteController?.note?.uuid === noteUuid) {
return; return;
} }
@@ -167,11 +167,11 @@ export class NotesState {
return; return;
} }
if (this.activeEditor) { if (this.activeNoteController) {
this.application.editorGroup.closeActiveEditor(); this.application.noteControllerGroup.closeActiveNoteView();
} }
await this.application.editorGroup.createEditor(noteUuid); await this.application.noteControllerGroup.createNoteView(noteUuid);
this.appState.noteTags.reloadTags(); this.appState.noteTags.reloadTags();
await this.onActiveEditorChanged(); await this.onActiveEditorChanged();

View File

@@ -70,7 +70,7 @@ export class NotesViewState {
appObservers.push( appObservers.push(
application.streamItems(ContentType.Note, () => { application.streamItems(ContentType.Note, () => {
this.reloadNotes(); this.reloadNotes();
const activeNote = this.appState.notes.activeEditor?.note; const activeNote = this.appState.notes.activeNoteController?.note;
if (this.application.getAppState().notes.selectedNotesCount < 2) { if (this.application.getAppState().notes.selectedNotesCount < 2) {
if (activeNote) { if (activeNote) {
const discarded = activeNote.deleted || activeNote.trashed; const discarded = activeNote.deleted || activeNote.trashed;
@@ -102,7 +102,7 @@ export class NotesViewState {
this.reloadPreferences(); this.reloadPreferences();
}, ApplicationEvent.PreferencesChanged), }, ApplicationEvent.PreferencesChanged),
application.addEventObserver(async () => { application.addEventObserver(async () => {
this.appState.closeAllEditors(); this.appState.closeAllNoteControllers();
this.selectFirstNote(); this.selectFirstNote();
this.setCompletedFullSync(false); this.setCompletedFullSync(false);
}, ApplicationEvent.SignedIn), }, ApplicationEvent.SignedIn),
@@ -111,7 +111,8 @@ export class NotesViewState {
if ( if (
this.notes.length === 0 && this.notes.length === 0 &&
this.appState.selectedTag?.isAllTag && this.appState.selectedTag?.isAllTag &&
this.noteFilterText === '' this.noteFilterText === '' &&
!this.appState.notes.activeNoteController
) { ) {
this.createPlaceholderNote(); this.createPlaceholderNote();
} }
@@ -191,7 +192,7 @@ export class NotesViewState {
} }
get activeEditorNote() { get activeEditorNote() {
return this.appState.notes.activeEditor?.note; return this.appState.notes.activeNoteController?.note;
} }
reloadPanelTitle = () => { reloadPanelTitle = () => {
@@ -318,19 +319,15 @@ export class NotesViewState {
} }
}; };
createNewNote = async (focusNewNote = true) => { createNewNote = async () => {
this.appState.notes.unselectNotes(); this.appState.notes.unselectNotes();
let title = `Note ${this.notes.length + 1}`; let title = `Note ${this.notes.length + 1}`;
if (this.isFiltering) { if (this.isFiltering) {
title = this.noteFilterText; title = this.noteFilterText;
} }
await this.appState.createEditor(title); await this.appState.openNewNote(title);
this.reloadNotes(); this.reloadNotes();
this.appState.noteTags.reloadTags(); this.appState.noteTags.reloadTags();
const noteTitleEditorElement = document.getElementById('note-title-editor');
if (focusNewNote) {
noteTitleEditorElement?.focus();
}
}; };
createPlaceholderNote = () => { createPlaceholderNote = () => {
@@ -338,7 +335,7 @@ export class NotesViewState {
if (selectedTag && selectedTag.isSmartTag && !selectedTag.isAllTag) { if (selectedTag && selectedTag.isSmartTag && !selectedTag.isAllTag) {
return; return;
} }
return this.createNewNote(false); return this.createNewNote();
}; };
get optionsSubtitle(): string { get optionsSubtitle(): string {
@@ -436,7 +433,7 @@ export class NotesViewState {
if (note) { if (note) {
this.selectNote(note, false, false); this.selectNote(note, false, false);
} else { } else {
this.appState.closeActiveEditor(); this.appState.closeActiveNoteController();
} }
}; };
@@ -467,7 +464,7 @@ export class NotesViewState {
}; };
handleEditorChange = async () => { handleEditorChange = async () => {
const activeNote = this.appState.getActiveEditor()?.note; const activeNote = this.appState.getActiveNoteController()?.note;
if (activeNote && activeNote.conflictOf) { if (activeNote && activeNote.conflictOf) {
this.application.changeAndSaveItem(activeNote.uuid, (mutator) => { this.application.changeAndSaveItem(activeNote.uuid, (mutator) => {
mutator.conflictOf = undefined; mutator.conflictOf = undefined;
@@ -505,7 +502,7 @@ export class NotesViewState {
this.activeEditorNote && this.activeEditorNote &&
!this.notes.includes(this.activeEditorNote) !this.notes.includes(this.activeEditorNote)
) { ) {
this.appState.closeActiveEditor(); this.appState.closeActiveNoteController();
} }
} }
}; };

View File

@@ -10,7 +10,7 @@ import { StatusManager } from '@/services/statusManager';
import { ThemeManager } from '@/services/themeManager'; import { ThemeManager } from '@/services/themeManager';
import { PasswordWizardScope, PasswordWizardType } from '@/types'; import { PasswordWizardScope, PasswordWizardType } from '@/types';
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { EditorGroup } from '@/ui_models/editor_group'; import { NoteGroupController } from '@/views/note_group_view/note_group_controller';
import { AppVersion } from '@/version'; import { AppVersion } from '@/version';
import { WebDeviceInterface } from '@/web_device_interface'; import { WebDeviceInterface } from '@/web_device_interface';
import { import {
@@ -36,7 +36,7 @@ export class WebApplication extends SNApplication {
private scope?: angular.IScope; private scope?: angular.IScope;
private webServices!: WebServices; private webServices!: WebServices;
private currentAuthenticationElement?: angular.IRootElementService; private currentAuthenticationElement?: angular.IRootElementService;
public editorGroup: EditorGroup; public noteControllerGroup: NoteGroupController;
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -66,7 +66,7 @@ export class WebApplication extends SNApplication {
this.$compile = $compile; this.$compile = $compile;
this.scope = scope; this.scope = scope;
deviceInterface.setApplication(this); deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this); this.noteControllerGroup = new NoteGroupController(this);
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this); this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
} }
@@ -80,7 +80,7 @@ export class WebApplication extends SNApplication {
} }
this.webServices = {} as WebServices; this.webServices = {} as WebServices;
(this.$compile as unknown) = undefined; (this.$compile as unknown) = undefined;
this.editorGroup.deinit(); this.noteControllerGroup.deinit();
(this.scope as any).application = undefined; (this.scope as any).application = undefined;
this.scope!.$destroy(); this.scope!.$destroy();
this.scope = undefined; this.scope = undefined;

View File

@@ -1,79 +0,0 @@
import { removeFromArray, UuidString } from '@standardnotes/snjs';
import { Editor } from './editor';
import { WebApplication } from './application';
type EditorGroupChangeCallback = () => void;
export class EditorGroup {
public editors: Editor[] = [];
private application: WebApplication;
changeObservers: EditorGroupChangeCallback[] = [];
constructor(application: WebApplication) {
this.application = application;
}
public deinit() {
(this.application as unknown) = undefined;
for (const editor of this.editors) {
this.deleteEditor(editor);
}
}
async createEditor(
noteUuid?: string,
noteTitle?: string,
noteTag?: UuidString
) {
const editor = new Editor(this.application, noteUuid, noteTitle, noteTag);
await editor.initialize();
this.editors.push(editor);
this.notifyObservers();
}
deleteEditor(editor: Editor) {
editor.deinit();
removeFromArray(this.editors, editor);
}
closeEditor(editor: Editor) {
this.deleteEditor(editor);
this.notifyObservers();
}
closeActiveEditor() {
const activeEditor = this.activeEditor;
if (activeEditor) {
this.deleteEditor(activeEditor);
}
}
closeAllEditors() {
for (const editor of this.editors) {
this.deleteEditor(editor);
}
}
get activeEditor() {
return this.editors[0];
}
/**
* Notifies observer when the active editor has changed.
*/
public addChangeObserver(callback: EditorGroupChangeCallback) {
this.changeObservers.push(callback);
if (this.activeEditor) {
callback();
}
return () => {
removeFromArray(this.changeObservers, callback);
};
}
private notifyObservers() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -10,7 +10,7 @@
application='self.application' application='self.application'
app-state='self.appState' app-state='self.appState'
) )
editor-group-view.flex-grow(application='self.application') note-group-view.flex-grow(application='self.application')
footer-view( footer-view(
ng-if='!self.state.needsUnlock && self.state.launched' ng-if='!self.state.needsUnlock && self.state.launched'

View File

@@ -1,43 +0,0 @@
import { WebDirective } from './../../types';
import template from './editor-group-view.pug';
import { Editor } from '@/ui_models/editor';
import { PureViewCtrl } from '../abstract/pure_view_ctrl';
class EditorGroupViewCtrl extends PureViewCtrl<unknown, {
showMultipleSelectedNotes: boolean
}> {
public editors: Editor[] = []
/* @ngInject */
constructor($timeout: ng.ITimeoutService,) {
super($timeout);
this.state = {
showMultipleSelectedNotes: false
};
}
$onInit() {
this.application.editorGroup.addChangeObserver(() => {
this.editors = this.application.editorGroup.editors;
});
this.autorun(() => {
this.setState({
showMultipleSelectedNotes: this.appState.notes.selectedNotesCount > 1
});
});
}
}
export class EditorGroupView extends WebDirective {
constructor() {
super();
this.template = template;
this.controller = EditorGroupViewCtrl;
this.controllerAs = 'self';
this.bindToController = true;
this.scope = {
application: '='
};
}
}

View File

@@ -1,8 +1,8 @@
export { PureViewCtrl } from './abstract/pure_view_ctrl'; export { PureViewCtrl } from './abstract/pure_view_ctrl';
export { ApplicationGroupView } from './application_group/application_group_view'; export { ApplicationGroupView } from './application_group/application_group_view';
export { ApplicationView } from './application/application_view'; export { ApplicationView } from './application/application_view';
export { EditorGroupView } from './editor_group/editor_group_view'; export { NoteGroupViewDirective } from './note_group_view/note_group_view';
export { EditorView } from './editor/editor_view'; export { NoteViewDirective } from './note_view/note_view';
export { FooterView } from './footer/footer_view'; export { FooterView } from './footer/footer_view';
export { TagsView } from './tags/tags_view'; export { TagsView } from './tags/tags_view';
export { ChallengeModal } from './challenge_modal/challenge_modal'; export { ChallengeModal } from './challenge_modal/challenge_modal';

View File

@@ -6,9 +6,9 @@
) )
.flex-grow.h-full( .flex-grow.h-full(
ng-if='!self.state.showMultipleSelectedNotes' ng-if='!self.state.showMultipleSelectedNotes'
ng-repeat='editor in self.editors' ng-repeat='controller in self.controllers'
) )
editor-view( note-view(
application='self.application' application='self.application'
editor='editor' controller='controller'
) )

View File

@@ -0,0 +1,84 @@
import { removeFromArray, UuidString } from '@standardnotes/snjs';
import { NoteViewController } from '@/views/note_view/note_view_controller';
import { WebApplication } from '@/ui_models/application';
type NoteControllerGroupChangeCallback = () => void;
export class NoteGroupController {
public noteControllers: NoteViewController[] = [];
private application: WebApplication;
changeObservers: NoteControllerGroupChangeCallback[] = [];
constructor(application: WebApplication) {
this.application = application;
}
public deinit() {
(this.application as unknown) = undefined;
for (const controller of this.noteControllers) {
this.deleteNoteView(controller);
}
}
async createNoteView(
noteUuid?: string,
noteTitle?: string,
noteTag?: UuidString
) {
const controller = new NoteViewController(
this.application,
noteUuid,
noteTitle,
noteTag
);
await controller.initialize();
this.noteControllers.push(controller);
this.notifyObservers();
}
deleteNoteView(controller: NoteViewController) {
controller.deinit();
removeFromArray(this.noteControllers, controller);
}
closeNoteView(controller: NoteViewController) {
this.deleteNoteView(controller);
this.notifyObservers();
}
closeActiveNoteView() {
const activeController = this.activeNoteViewController;
if (activeController) {
this.deleteNoteView(activeController);
}
}
closeAllNoteViews() {
for (const controller of this.noteControllers) {
this.deleteNoteView(controller);
}
}
get activeNoteViewController() {
return this.noteControllers[0];
}
/**
* Notifies observer when the active controller has changed.
*/
public addChangeObserver(callback: NoteControllerGroupChangeCallback) {
this.changeObservers.push(callback);
if (this.activeNoteViewController) {
callback();
}
return () => {
removeFromArray(this.changeObservers, callback);
};
}
private notifyObservers() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -0,0 +1,45 @@
import { WebDirective } from './../../types';
import template from './note-group-view.pug';
import { NoteViewController } from '@/views/note_view/note_view_controller';
import { PureViewCtrl } from '../abstract/pure_view_ctrl';
class NoteGroupView extends PureViewCtrl<
unknown,
{
showMultipleSelectedNotes: boolean;
}
> {
public controllers: NoteViewController[] = [];
/* @ngInject */
constructor($timeout: ng.ITimeoutService) {
super($timeout);
this.state = {
showMultipleSelectedNotes: false,
};
}
$onInit() {
this.application.noteControllerGroup.addChangeObserver(() => {
this.controllers = this.application.noteControllerGroup.noteControllers;
});
this.autorun(() => {
this.setState({
showMultipleSelectedNotes: this.appState.notes.selectedNotesCount > 1,
});
});
}
}
export class NoteGroupViewDirective extends WebDirective {
constructor() {
super();
this.template = template;
this.controller = NoteGroupView;
this.controllerAs = 'self';
this.bindToController = true;
this.scope = {
application: '=',
};
}
}

View File

@@ -114,7 +114,7 @@
component-view.component-view( component-view.component-view(
component-viewer='self.state.editorComponentViewer', component-viewer='self.state.editorComponentViewer',
ng-if='self.state.editorComponentViewer', ng-if='self.state.editorComponentViewer',
on-load='self.onEditorLoad', on-load='self.onEditorComponentLoad',
request-reload='self.editorComponentViewerRequestsReload' request-reload='self.editorComponentViewerRequestsReload'
application='self.application' application='self.application'
app-state='self.appState' app-state='self.appState'

View File

@@ -2,19 +2,19 @@
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import { EditorViewCtrl } from '@Views/editor/editor_view'; import { NoteView } from '@Views/note_view/note_view';
import { import {
ApplicationEvent, ApplicationEvent,
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction, ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
} from '@standardnotes/snjs/'; } from '@standardnotes/snjs/';
describe('editor-view', () => { describe('editor-view', () => {
let ctrl: EditorViewCtrl; let ctrl: NoteView;
let setShowProtectedWarningSpy: jest.SpyInstance; let setShowProtectedWarningSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
const $timeout = {} as jest.Mocked<ng.ITimeoutService>; const $timeout = {} as jest.Mocked<ng.ITimeoutService>;
ctrl = new EditorViewCtrl($timeout); ctrl = new NoteView($timeout);
setShowProtectedWarningSpy = jest.spyOn(ctrl, 'setShowProtectedOverlay'); setShowProtectedWarningSpy = jest.spyOn(ctrl, 'setShowProtectedOverlay');

View File

@@ -1,5 +1,5 @@
import { STRING_SAVING_WHILE_DOCUMENT_HIDDEN } from './../../strings'; import { STRING_SAVING_WHILE_DOCUMENT_HIDDEN } from './../../strings';
import { Editor } from '@/ui_models/editor'; import { NoteViewController } from '@/views/note_view/note_view_controller';
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
import { PanelPuppet, WebDirective } from '@/types'; import { PanelPuppet, WebDirective } from '@/types';
import angular from 'angular'; import angular from 'angular';
@@ -21,9 +21,9 @@ import {
ItemMutator, ItemMutator,
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction, ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import { isDesktopApplication } from '@/utils'; import { debounce, isDesktopApplication } from '@/utils';
import { KeyboardModifier, KeyboardKey } from '@/services/ioService'; import { KeyboardModifier, KeyboardKey } from '@/services/ioService';
import template from './editor-view.pug'; import template from './note-view.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { EventSource } from '@/ui_models/app_state'; import { EventSource } from '@/ui_models/app_state';
import { import {
@@ -90,10 +90,10 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
); );
} }
export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> { export class NoteView extends PureViewCtrl<unknown, EditorState> {
/** Passed through template */ /** Passed through template */
readonly application!: WebApplication; readonly application!: WebApplication;
readonly editor!: Editor; readonly controller!: NoteViewController;
private leftPanelPuppet?: PanelPuppet; private leftPanelPuppet?: PanelPuppet;
private rightPanelPuppet?: PanelPuppet; private rightPanelPuppet?: PanelPuppet;
@@ -101,7 +101,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
private statusTimeout?: ng.IPromise<void>; private statusTimeout?: ng.IPromise<void>;
private lastEditorFocusEventSource?: EventSource; private lastEditorFocusEventSource?: EventSource;
public editorValues: EditorValues = { title: '', text: '' }; public editorValues: EditorValues = { title: '', text: '' };
onEditorLoad?: () => void; onEditorComponentLoad?: () => void;
private scrollPosition = 0; private scrollPosition = 0;
private removeTrashKeyObserver?: () => void; private removeTrashKeyObserver?: () => void;
@@ -127,9 +127,13 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.resetScrollPosition = this.resetScrollPosition.bind(this); this.resetScrollPosition = this.resetScrollPosition.bind(this);
this.editorComponentViewerRequestsReload = this.editorComponentViewerRequestsReload =
this.editorComponentViewerRequestsReload.bind(this); this.editorComponentViewerRequestsReload.bind(this);
this.onEditorLoad = () => { this.onEditorComponentLoad = () => {
this.application.getDesktopService().redoSearch(); this.application.getDesktopService().redoSearch();
}; };
this.debounceReloadEditorComponent = debounce(
this.debounceReloadEditorComponent.bind(this),
25
);
} }
deinit() { deinit() {
@@ -144,7 +148,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.removeTabObserver = undefined; this.removeTabObserver = undefined;
this.leftPanelPuppet = undefined; this.leftPanelPuppet = undefined;
this.rightPanelPuppet = undefined; this.rightPanelPuppet = undefined;
this.onEditorLoad = undefined; this.onEditorComponentLoad = undefined;
this.saveTimeout = undefined; this.saveTimeout = undefined;
this.statusTimeout = undefined; this.statusTimeout = undefined;
(this.onPanelResizeFinish as unknown) = undefined; (this.onPanelResizeFinish as unknown) = undefined;
@@ -157,14 +161,14 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
get note() { get note() {
return this.editor.note; return this.controller.note;
} }
$onInit() { $onInit() {
super.$onInit(); super.$onInit();
this.registerKeyboardShortcuts(); this.registerKeyboardShortcuts();
this.editor.setOnNoteValueChange((note, source) => { this.controller.setOnNoteInnerValueChange((note, source) => {
this.onNoteChanges(note, source); this.onNoteInnerChange(note, source);
}); });
this.autorun(() => { this.autorun(() => {
this.setState({ this.setState({
@@ -180,12 +184,14 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.reloadPreferences(); this.reloadPreferences();
if (this.note.dirty) { if (this.controller.isTemplateNote) {
this.showSavingStatus(); this.$timeout(() => {
this.focusTitle();
});
} }
} }
private onNoteChanges(note: SNNote, source: PayloadSource): void { private onNoteInnerChange(note: SNNote, source: PayloadSource): void {
if (note.uuid !== this.note.uuid) { if (note.uuid !== this.note.uuid) {
throw Error('Editor received changes for non-current note'); throw Error('Editor received changes for non-current note');
} }
@@ -214,6 +220,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
) { ) {
this.showSavingStatus(); this.showSavingStatus();
} else if ( } else if (
this.state.noteStatus &&
note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime() note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()
) { ) {
this.showAllChangesSavedStatus(); this.showAllChangesSavedStatus();
@@ -376,7 +383,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
if (!this.note) return; if (!this.note) return;
await this.reloadStackComponents(); await this.reloadStackComponents();
await this.reloadEditorComponent(); this.debounceReloadEditorComponent();
} }
); );
} }
@@ -403,13 +410,22 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
}); });
} }
/**
* Calling reloadEditorComponent successively without waiting for state to settle
* can result in componentViewers being dealloced twice
*/
debounceReloadEditorComponent() {
this.reloadEditorComponent();
}
private async reloadEditorComponent() { private async reloadEditorComponent() {
const newEditor = this.application.componentManager.editorForNote( const newEditor = this.application.componentManager.editorForNote(
this.note this.note
); );
/** Editors cannot interact with template notes so the note must be inserted */ /** Editors cannot interact with template notes so the note must be inserted */
if (newEditor && this.editor.isTemplateNote) { if (newEditor && this.controller.isTemplateNote) {
await this.editor.insertTemplatedNote(); await this.controller.insertTemplatedNote();
this.associateComponentWithCurrentNote(newEditor);
} }
const currentComponentViewer = this.state.editorComponentViewer; const currentComponentViewer = this.state.editorComponentViewer;
@@ -463,8 +479,8 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
const transactions: TransactionalMutation[] = []; const transactions: TransactionalMutation[] = [];
this.setMenuState('showEditorMenu', false); this.setMenuState('showEditorMenu', false);
if (this.appState.getActiveEditor()?.isTemplateNote) { if (this.appState.getActiveNoteController()?.isTemplateNote) {
await this.appState.getActiveEditor().insertTemplatedNote(); await this.appState.getActiveNoteController().insertTemplatedNote();
} }
if (this.note.locked) { if (this.note.locked) {
this.application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT); this.application.alertService.alert(STRING_EDIT_LOCKED_ATTEMPT);
@@ -550,7 +566,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
) { ) {
const title = editorValues.title; const title = editorValues.title;
const text = editorValues.text; const text = editorValues.text;
const isTemplate = this.editor.isTemplateNote; const isTemplate = this.controller.isTemplateNote;
if (document.hidden) { if (document.hidden) {
this.application.alertService.alert(STRING_SAVING_WHILE_DOCUMENT_HIDDEN); this.application.alertService.alert(STRING_SAVING_WHILE_DOCUMENT_HIDDEN);
return; return;
@@ -560,7 +576,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
return; return;
} }
if (isTemplate) { if (isTemplate) {
await this.editor.insertTemplatedNote(); await this.controller.insertTemplatedNote();
} }
if (!this.application.findItem(note.uuid)) { if (!this.application.findItem(note.uuid)) {
this.application.alertService.alert(STRING_INVALID_NOTE); this.application.alertService.alert(STRING_INVALID_NOTE);
@@ -598,7 +614,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
this.saveTimeout = this.$timeout(() => { this.saveTimeout = this.$timeout(() => {
this.application.sync(); this.application.sync();
if (closeAfterSync) { if (closeAfterSync) {
this.appState.closeEditor(this.editor); this.appState.closeNoteController(this.controller);
} }
}, syncDebouceMs); }, syncDebouceMs);
} }
@@ -703,7 +719,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
async deleteNote(permanently: boolean) { async deleteNote(permanently: boolean) {
if (this.editor.isTemplateNote) { if (this.controller.isTemplateNote) {
this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT); this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
return; return;
} }
@@ -1023,17 +1039,17 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
} }
} }
export class EditorView extends WebDirective { export class NoteViewDirective extends WebDirective {
constructor() { constructor() {
super(); super();
this.restrict = 'E'; this.restrict = 'E';
this.scope = { this.scope = {
editor: '=', controller: '=',
application: '=', application: '=',
}; };
this.template = template; this.template = template;
this.replace = true; this.replace = true;
this.controller = EditorViewCtrl; this.controller = NoteView;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = true; this.bindToController = true;
} }

View File

@@ -5,9 +5,9 @@ import {
UuidString, UuidString,
SNTag, SNTag,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import { WebApplication } from './application'; import { WebApplication } from '@/ui_models/application';
export class Editor { export class NoteViewController {
public note!: SNNote; public note!: SNNote;
private application: WebApplication; private application: WebApplication;
private onNoteValueChange?: (note: SNNote, source: PayloadSource) => void; private onNoteValueChange?: (note: SNNote, source: PayloadSource) => void;
@@ -28,7 +28,21 @@ export class Editor {
async initialize(): Promise<void> { async initialize(): Promise<void> {
if (!this.note) { if (!this.note) {
await this.createTemplateNote(this.defaultTitle, this.defaultTag); const note = (await this.application.createTemplateItem(
ContentType.Note,
{
text: '',
title: this.defaultTitle,
references: [],
}
)) as SNNote;
if (this.defaultTag) {
const tag = this.application.findItem(this.defaultTag) as SNTag;
await this.application.addTagHierarchyToNote(note, tag);
}
this.isTemplateNote = true;
this.note = note;
this.onNoteValueChange?.(this.note, this.note.payload.source);
} }
this.streamItems(); this.streamItems();
} }
@@ -67,29 +81,10 @@ export class Editor {
} }
/** /**
* Reverts the editor to a blank state, removing any existing note from view, * Register to be notified when the controller's note's inner values change
* and creating a placeholder note.
*/
async createTemplateNote(defaultTitle?: string, noteTag?: UuidString) {
const note = (await this.application.createTemplateItem(ContentType.Note, {
text: '',
title: defaultTitle,
references: [],
})) as SNNote;
if (noteTag) {
const tag = this.application.findItem(noteTag) as SNTag;
await this.application.addTagHierarchyToNote(note, tag);
}
this.isTemplateNote = true;
this.note = note;
this.onNoteValueChange?.(this.note, this.note.payload.source);
}
/**
* Register to be notified when the editor's note's values change
* (and thus a new object reference is created) * (and thus a new object reference is created)
*/ */
public setOnNoteValueChange( public setOnNoteInnerValueChange(
callback: (note: SNNote, source: PayloadSource) => void callback: (note: SNNote, source: PayloadSource) => void
) { ) {
this.onNoteValueChange = callback; this.onNoteValueChange = callback;

View File

@@ -1,6 +1,6 @@
{ {
"name": "standard-notes-web", "name": "standard-notes-web",
"version": "3.9.10", "version": "3.9.11",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -84,10 +84,10 @@
"@reach/checkbox": "^0.16.0", "@reach/checkbox": "^0.16.0",
"@reach/dialog": "^0.16.2", "@reach/dialog": "^0.16.2",
"@reach/listbox": "^0.16.2", "@reach/listbox": "^0.16.2",
"@standardnotes/features": "1.10.2", "@standardnotes/features": "1.15.1",
"@reach/tooltip": "^0.16.2", "@reach/tooltip": "^0.16.2",
"@standardnotes/sncrypto-web": "1.5.3", "@standardnotes/sncrypto-web": "1.5.3",
"@standardnotes/snjs": "2.29.0", "@standardnotes/snjs": "2.30.0",
"mobx": "^6.3.5", "mobx": "^6.3.5",
"mobx-react-lite": "^3.2.2", "mobx-react-lite": "^3.2.2",
"preact": "^10.5.15", "preact": "^10.5.15",

View File

@@ -2623,14 +2623,6 @@
dependencies: dependencies:
"@standardnotes/auth" "^3.8.1" "@standardnotes/auth" "^3.8.1"
"@standardnotes/features@1.10.2":
version "1.10.2"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.10.2.tgz#a0783f66c00e21cb7692edc0cea95ec25a0253a5"
integrity sha512-Zh6EMjli4mL6jlXEhMyU3qYIKFJj5kuhbxtHXiErUGIDy+s1hHY+THFFO53Jdga2+8wgcATWlmSBY7dieVA8uA==
dependencies:
"@standardnotes/auth" "3.8.3"
"@standardnotes/common" "^1.2.1"
"@standardnotes/features@1.11.0": "@standardnotes/features@1.11.0":
version "1.11.0" version "1.11.0"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.11.0.tgz#66e960a20358c5f58b6be4e19226b34df6f4efbf" resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.11.0.tgz#66e960a20358c5f58b6be4e19226b34df6f4efbf"
@@ -2639,6 +2631,14 @@
"@standardnotes/auth" "3.8.3" "@standardnotes/auth" "3.8.3"
"@standardnotes/common" "^1.2.1" "@standardnotes/common" "^1.2.1"
"@standardnotes/features@1.15.1":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.15.1.tgz#3880d73fb32ab952359e70fb313abfdf14023c7d"
integrity sha512-QurBvYwfNrcAKLwE7Z/1EsFpZ6Vuwp8E9O7jlMlzf0GyMXY/djCooeXjmoCWed+B/qJ4sdDdtptGyMsjKVeJCw==
dependencies:
"@standardnotes/auth" "3.8.3"
"@standardnotes/common" "1.2.1"
"@standardnotes/settings@^1.8.0": "@standardnotes/settings@^1.8.0":
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.8.0.tgz#d7bd1f35c3b500d12ba73f5f385b1019baae3efc" resolved "https://registry.yarnpkg.com/@standardnotes/settings/-/settings-1.8.0.tgz#d7bd1f35c3b500d12ba73f5f385b1019baae3efc"
@@ -2658,10 +2658,10 @@
buffer "^6.0.3" buffer "^6.0.3"
libsodium-wrappers "^0.7.9" libsodium-wrappers "^0.7.9"
"@standardnotes/snjs@2.29.0": "@standardnotes/snjs@2.30.0":
version "2.29.0" version "2.30.0"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.29.0.tgz#6c7c6ccd983df4a1a5e2063647eb731304002fd9" resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.30.0.tgz#12abda65e8069273347bbf6786d6a99203260023"
integrity sha512-Y+GpNiFyJtVr2W3nVbC2zljtXpBlqe3cB4+R1REE0V4hnQBaq/HE6PaUd80TnFj99Kl8lowyH/o4bNV3+CjGgg== integrity sha512-0s7NYtO8edvY3/MNwx/Ic+fIDXiSnaVhdBWlaIOreVHwUl15LkHyN8mptR8QLtHOjOhvaguew+KCvmXXmkNjTA==
dependencies: dependencies:
"@standardnotes/auth" "3.8.1" "@standardnotes/auth" "3.8.1"
"@standardnotes/common" "1.2.1" "@standardnotes/common" "1.2.1"