feat: component viewer (#781)
* wip: component viewer * feat: get component status from component viewer * fix: remove unused property * chore(deps): snjs 2.29.0 * fix: import location
This commit is contained in:
@@ -3,29 +3,26 @@ 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>>
|
||||
export type CtrlState = Partial<Record<string, any>>;
|
||||
export type CtrlProps = Partial<Record<string, any>>;
|
||||
|
||||
export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
||||
$timeout: ng.ITimeoutService
|
||||
$timeout: ng.ITimeoutService;
|
||||
/** Passed through templates */
|
||||
application!: WebApplication
|
||||
state: S = {} as any
|
||||
private unsubApp: any
|
||||
private unsubState: any
|
||||
private stateTimeout?: ng.IPromise<void>
|
||||
application!: WebApplication;
|
||||
state: S = {} as any;
|
||||
private unsubApp: any;
|
||||
private unsubState: any;
|
||||
private stateTimeout?: ng.IPromise<void>;
|
||||
/**
|
||||
* Subclasses can optionally add an ng-if=ctrl.templateReady to make sure that
|
||||
* no Angular handlebars/syntax render in the UI before display data is ready.
|
||||
*/
|
||||
protected templateReady = false
|
||||
protected templateReady = false;
|
||||
private reactionDisposers: IReactionDisposer[] = [];
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$timeout: ng.ITimeoutService,
|
||||
public props: P = {} as any
|
||||
) {
|
||||
constructor($timeout: ng.ITimeoutService, public props: P = {} as any) {
|
||||
this.$timeout = $timeout;
|
||||
}
|
||||
|
||||
@@ -91,8 +88,7 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
||||
|
||||
/** @override */
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
afterStateChange(): void {
|
||||
}
|
||||
afterStateChange(): void {}
|
||||
|
||||
/** @returns a promise that resolves after the UI has been updated. */
|
||||
flushUI(): angular.IPromise<void> {
|
||||
@@ -129,22 +125,24 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
||||
if (this.application!.isLaunched()) {
|
||||
this.onAppLaunch();
|
||||
}
|
||||
this.unsubApp = this.application!.addEventObserver(async (eventName, data: any) => {
|
||||
this.onAppEvent(eventName, data);
|
||||
if (eventName === ApplicationEvent.Started) {
|
||||
await this.onAppStart();
|
||||
} else if (eventName === ApplicationEvent.Launched) {
|
||||
await this.onAppLaunch();
|
||||
} else if (eventName === ApplicationEvent.CompletedIncrementalSync) {
|
||||
this.onAppIncrementalSync();
|
||||
} else if (eventName === ApplicationEvent.CompletedFullSync) {
|
||||
this.onAppFullSync();
|
||||
} else if (eventName === ApplicationEvent.KeyStatusChanged) {
|
||||
this.onAppKeyChange();
|
||||
} else if (eventName === ApplicationEvent.LocalDataLoaded) {
|
||||
this.onLocalDataLoaded();
|
||||
this.unsubApp = this.application!.addEventObserver(
|
||||
async (eventName, data: any) => {
|
||||
this.onAppEvent(eventName, data);
|
||||
if (eventName === ApplicationEvent.Started) {
|
||||
await this.onAppStart();
|
||||
} else if (eventName === ApplicationEvent.Launched) {
|
||||
await this.onAppLaunch();
|
||||
} else if (eventName === ApplicationEvent.CompletedIncrementalSync) {
|
||||
this.onAppIncrementalSync();
|
||||
} else if (eventName === ApplicationEvent.CompletedFullSync) {
|
||||
this.onAppFullSync();
|
||||
} else if (eventName === ApplicationEvent.KeyStatusChanged) {
|
||||
this.onAppKeyChange();
|
||||
} else if (eventName === ApplicationEvent.LocalDataLoaded) {
|
||||
this.onLocalDataLoaded();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
onAppEvent(eventName: ApplicationEvent, data?: any) {
|
||||
@@ -175,5 +173,4 @@ export class PureViewCtrl<P = CtrlProps, S = CtrlState> {
|
||||
onAppFullSync() {
|
||||
/** Optional override */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,14 +115,17 @@ class ApplicationViewCtrl extends PureViewCtrl<
|
||||
/** @override */
|
||||
async onAppEvent(eventName: ApplicationEvent) {
|
||||
super.onAppEvent(eventName);
|
||||
if (eventName === ApplicationEvent.LocalDatabaseReadError) {
|
||||
alertDialog({
|
||||
text: 'Unable to load local database. Please restart the app and try again.',
|
||||
});
|
||||
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
|
||||
alertDialog({
|
||||
text: 'Unable to write to local database. Please restart the app and try again.',
|
||||
});
|
||||
switch (eventName) {
|
||||
case ApplicationEvent.LocalDatabaseReadError:
|
||||
alertDialog({
|
||||
text: 'Unable to load local database. Please restart the app and try again.',
|
||||
});
|
||||
break;
|
||||
case ApplicationEvent.LocalDatabaseWriteError:
|
||||
alertDialog({
|
||||
text: 'Unable to write to local database. Please restart the app and try again.',
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +219,7 @@ class ChallengeModalCtrl extends PureViewCtrl<unknown, ChallengeModalState> {
|
||||
|
||||
$onDestroy() {
|
||||
render(<></>, this.$element[0]);
|
||||
super.$onDestroy();
|
||||
}
|
||||
|
||||
private render() {
|
||||
|
||||
@@ -34,10 +34,8 @@
|
||||
)
|
||||
.title.overflow-auto
|
||||
input#note-title-editor.input(
|
||||
ng-blur='self.onTitleBlur()',
|
||||
ng-change='self.onTitleChange()',
|
||||
ng-disabled='self.noteLocked',
|
||||
ng-focus='self.onTitleFocus()',
|
||||
ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)',
|
||||
ng-model='self.editorValues.title',
|
||||
select-on-focus='true',
|
||||
@@ -76,7 +74,7 @@
|
||||
callback='self.editorMenuOnSelect',
|
||||
current-item='self.note',
|
||||
ng-if='self.state.showEditorMenu',
|
||||
selected-editor-uuid='self.state.editorComponent && self.state.editorComponent.uuid',
|
||||
selected-editor-uuid='self.state.editorComponentViewer && self.state.editorComponentViewer.component.uuid',
|
||||
application='self.application'
|
||||
)
|
||||
.sk-app-bar-item(
|
||||
@@ -114,9 +112,10 @@
|
||||
property="'left'"
|
||||
)
|
||||
component-view.component-view(
|
||||
component-uuid='self.state.editorComponent.uuid',
|
||||
ng-if='self.state.editorComponent && !self.state.editorUnloading',
|
||||
component-viewer='self.state.editorComponentViewer',
|
||||
ng-if='self.state.editorComponentViewer',
|
||||
on-load='self.onEditorLoad',
|
||||
request-reload='self.editorComponentViewerRequestsReload'
|
||||
application='self.application'
|
||||
app-state='self.appState'
|
||||
)
|
||||
@@ -126,7 +125,7 @@
|
||||
ng-change='self.contentChanged()',
|
||||
ng-click='self.clickedTextArea()',
|
||||
ng-focus='self.onContentFocus()',
|
||||
ng-if='!self.state.editorComponent && !self.state.textareaUnloading',
|
||||
ng-if='self.state.editorStateDidLoad && !self.state.editorComponentViewer && !self.state.textareaUnloading',
|
||||
ng-model='self.editorValues.text',
|
||||
ng-model-options='{ debounce: self.state.editorDebounce}',
|
||||
ng-readonly='self.noteLocked',
|
||||
@@ -156,24 +155,23 @@
|
||||
| There was an error decrypting this item. Ensure you are running the
|
||||
| latest version of this app, then sign out and sign back in to try again.
|
||||
#editor-pane-component-stack(ng-if='!self.note.errorDecrypting' ng-show='self.note')
|
||||
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.stackComponents.length')
|
||||
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.availableStackComponents.length')
|
||||
.left
|
||||
.sk-app-bar-item(
|
||||
ng-repeat='component in self.state.stackComponents track by component.uuid'
|
||||
ng-click='self.toggleStackComponentForCurrentItem(component)',
|
||||
ng-repeat='component in self.state.availableStackComponents track by component.uuid'
|
||||
ng-click='self.toggleStackComponent(component)',
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.small(
|
||||
ng-class="{'info' : !self.stackComponentHidden(component) && component.active, 'neutral' : self.stackComponentHidden(component) || !component.active}"
|
||||
ng-class="{'info' : self.stackComponentExpanded(component) && component.active, 'neutral' : !self.stackComponentExpanded(component)}"
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-label {{component.name}}
|
||||
.sn-component
|
||||
component-view.component-view.component-stack-item(
|
||||
ng-repeat='component in self.state.stackComponents track by component.uuid',
|
||||
component-uuid='component.uuid',
|
||||
ng-repeat='viewer in self.state.stackComponentViewers track by viewer.componentUuid',
|
||||
component-viewer='viewer',
|
||||
manual-dealloc='true',
|
||||
ng-show='!self.stackComponentHidden(component)',
|
||||
application='self.application'
|
||||
app-state='self.appState'
|
||||
)
|
||||
|
||||
@@ -11,11 +11,14 @@ import {
|
||||
SNComponent,
|
||||
SNNote,
|
||||
NoteMutator,
|
||||
Uuids,
|
||||
ComponentArea,
|
||||
PrefKey,
|
||||
ComponentMutator,
|
||||
PayloadSource,
|
||||
ComponentViewer,
|
||||
ComponentManagerEvent,
|
||||
TransactionalMutation,
|
||||
ItemMutator,
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||
} from '@standardnotes/snjs';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
@@ -52,8 +55,10 @@ type NoteStatus = {
|
||||
};
|
||||
|
||||
type EditorState = {
|
||||
stackComponents: SNComponent[];
|
||||
editorComponent?: SNComponent;
|
||||
availableStackComponents: SNComponent[];
|
||||
stackComponentViewers: ComponentViewer[];
|
||||
editorComponentViewer?: ComponentViewer;
|
||||
editorStateDidLoad: boolean;
|
||||
saveError?: any;
|
||||
noteStatus?: NoteStatus;
|
||||
marginResizersEnabled?: boolean;
|
||||
@@ -64,11 +69,6 @@ type EditorState = {
|
||||
showEditorMenu: boolean;
|
||||
showHistoryMenu: boolean;
|
||||
spellcheck: boolean;
|
||||
/**
|
||||
* Setting to false then true will allow the current editor component-view to be destroyed
|
||||
* then re-initialized. Used when changing between component editors.
|
||||
*/
|
||||
editorUnloading: boolean;
|
||||
/** Setting to true then false will allow the main content textarea to be destroyed
|
||||
* then re-initialized. Used when reloading spellcheck status. */
|
||||
textareaUnloading: boolean;
|
||||
@@ -97,7 +97,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
|
||||
private leftPanelPuppet?: PanelPuppet;
|
||||
private rightPanelPuppet?: PanelPuppet;
|
||||
private unregisterComponent: any;
|
||||
private saveTimeout?: ng.IPromise<void>;
|
||||
private statusTimeout?: ng.IPromise<void>;
|
||||
private lastEditorFocusEventSource?: EventSource;
|
||||
@@ -105,10 +104,11 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
onEditorLoad?: () => void;
|
||||
|
||||
private scrollPosition = 0;
|
||||
private removeTrashKeyObserver?: any;
|
||||
private removeTabObserver?: any;
|
||||
private removeTrashKeyObserver?: () => void;
|
||||
private removeTabObserver?: () => void;
|
||||
private removeComponentStreamObserver?: () => void;
|
||||
private removeComponentManagerObserver?: () => void;
|
||||
|
||||
private removeComponentsObserver!: () => void;
|
||||
private protectionTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/* @ngInject */
|
||||
@@ -125,25 +125,26 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this);
|
||||
this.setScrollPosition = this.setScrollPosition.bind(this);
|
||||
this.resetScrollPosition = this.resetScrollPosition.bind(this);
|
||||
this.editorComponentViewerRequestsReload =
|
||||
this.editorComponentViewerRequestsReload.bind(this);
|
||||
this.onEditorLoad = () => {
|
||||
this.application.getDesktopService().redoSearch();
|
||||
};
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.clearNoteProtectionInactivityTimer();
|
||||
this.editor.clearNoteChangeListener();
|
||||
this.removeComponentsObserver();
|
||||
(this.removeComponentsObserver as unknown) = undefined;
|
||||
this.removeTrashKeyObserver();
|
||||
this.removeComponentStreamObserver?.();
|
||||
(this.removeComponentStreamObserver as unknown) = undefined;
|
||||
this.removeComponentManagerObserver?.();
|
||||
(this.removeComponentManagerObserver as unknown) = undefined;
|
||||
this.removeTrashKeyObserver?.();
|
||||
this.removeTrashKeyObserver = undefined;
|
||||
this.removeTabObserver && this.removeTabObserver();
|
||||
this.clearNoteProtectionInactivityTimer();
|
||||
this.removeTabObserver?.();
|
||||
this.removeTabObserver = undefined;
|
||||
this.leftPanelPuppet = undefined;
|
||||
this.rightPanelPuppet = undefined;
|
||||
this.onEditorLoad = undefined;
|
||||
this.unregisterComponent();
|
||||
this.unregisterComponent = undefined;
|
||||
this.saveTimeout = undefined;
|
||||
this.statusTimeout = undefined;
|
||||
(this.onPanelResizeFinish as unknown) = undefined;
|
||||
@@ -162,55 +163,82 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
$onInit() {
|
||||
super.$onInit();
|
||||
this.registerKeyboardShortcuts();
|
||||
this.editor.onNoteChange(() => {
|
||||
this.handleEditorNoteChange();
|
||||
});
|
||||
this.editor.onNoteValueChange((note, source) => {
|
||||
if (isPayloadSourceRetrieved(source!)) {
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
}
|
||||
if (!this.editorValues.title) {
|
||||
this.editorValues.title = note.title;
|
||||
}
|
||||
if (!this.editorValues.text) {
|
||||
this.editorValues.text = note.text;
|
||||
}
|
||||
|
||||
const isTemplateNoteInsertedToBeInteractableWithEditor =
|
||||
source === PayloadSource.Constructor && note.dirty;
|
||||
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.lastSyncBegan || note.dirty) {
|
||||
if (note.lastSyncEnd) {
|
||||
if (
|
||||
note.dirty ||
|
||||
note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()
|
||||
) {
|
||||
this.showSavingStatus();
|
||||
} else if (
|
||||
note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()
|
||||
) {
|
||||
this.showAllChangesSavedStatus();
|
||||
}
|
||||
} else {
|
||||
this.showSavingStatus();
|
||||
}
|
||||
}
|
||||
this.editor.setOnNoteValueChange((note, source) => {
|
||||
this.onNoteChanges(note, source);
|
||||
});
|
||||
this.autorun(() => {
|
||||
this.setState({
|
||||
showProtectedWarning: this.appState.notes.showProtectedWarning,
|
||||
});
|
||||
});
|
||||
this.reloadEditorComponent();
|
||||
this.reloadStackComponents();
|
||||
|
||||
const showProtectedWarning =
|
||||
this.note.protected && !this.application.hasProtectionSources();
|
||||
this.setShowProtectedOverlay(showProtectedWarning);
|
||||
|
||||
this.reloadPreferences();
|
||||
|
||||
if (this.note.dirty) {
|
||||
this.showSavingStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private onNoteChanges(note: SNNote, source: PayloadSource): void {
|
||||
if (note.uuid !== this.note.uuid) {
|
||||
throw Error('Editor received changes for non-current note');
|
||||
}
|
||||
if (isPayloadSourceRetrieved(source)) {
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
}
|
||||
if (!this.editorValues.title) {
|
||||
this.editorValues.title = note.title;
|
||||
}
|
||||
if (!this.editorValues.text) {
|
||||
this.editorValues.text = note.text;
|
||||
}
|
||||
|
||||
const isTemplateNoteInsertedToBeInteractableWithEditor =
|
||||
source === PayloadSource.Constructor && note.dirty;
|
||||
if (isTemplateNoteInsertedToBeInteractableWithEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (note.lastSyncBegan || note.dirty) {
|
||||
if (note.lastSyncEnd) {
|
||||
if (
|
||||
note.dirty ||
|
||||
note.lastSyncBegan!.getTime() > note.lastSyncEnd!.getTime()
|
||||
) {
|
||||
this.showSavingStatus();
|
||||
} else if (
|
||||
note.lastSyncEnd!.getTime() > note.lastSyncBegan!.getTime()
|
||||
) {
|
||||
this.showAllChangesSavedStatus();
|
||||
}
|
||||
} else {
|
||||
this.showSavingStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$onDestroy(): void {
|
||||
if (this.state.editorComponentViewer) {
|
||||
this.application.componentManager?.destroyComponentViewer(
|
||||
this.state.editorComponentViewer
|
||||
);
|
||||
}
|
||||
super.$onDestroy();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getInitialState() {
|
||||
return {
|
||||
stackComponents: [],
|
||||
availableStackComponents: [],
|
||||
stackComponentViewers: [],
|
||||
editorStateDidLoad: false,
|
||||
editorDebounce: EDITOR_DEBOUNCE,
|
||||
isDesktop: isDesktopApplication(),
|
||||
spellcheck: true,
|
||||
@@ -219,7 +247,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
showEditorMenu: false,
|
||||
showHistoryMenu: false,
|
||||
noteStatus: undefined,
|
||||
editorUnloading: false,
|
||||
textareaUnloading: false,
|
||||
showProtectedWarning: false,
|
||||
} as EditorState;
|
||||
@@ -228,7 +255,7 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
async onAppLaunch() {
|
||||
await super.onAppLaunch();
|
||||
this.streamItems();
|
||||
this.registerComponentHandler();
|
||||
this.registerComponentManagerEventObserver();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
@@ -310,34 +337,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
}
|
||||
}
|
||||
|
||||
async handleEditorNoteChange() {
|
||||
this.clearNoteProtectionInactivityTimer();
|
||||
this.cancelPendingSetStatus();
|
||||
const note = this.editor.note;
|
||||
|
||||
const showProtectedWarning =
|
||||
note.protected &&
|
||||
(!this.application.hasProtectionSources() ||
|
||||
this.application.getProtectionSessionExpiryDate().getTime() <
|
||||
Date.now());
|
||||
|
||||
this.setShowProtectedOverlay(showProtectedWarning);
|
||||
await this.setState({
|
||||
showActionsMenu: false,
|
||||
showEditorMenu: false,
|
||||
showHistoryMenu: false,
|
||||
noteStatus: undefined,
|
||||
});
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
this.reloadEditor();
|
||||
this.reloadPreferences();
|
||||
this.reloadStackComponents();
|
||||
if (note.dirty) {
|
||||
this.showSavingStatus();
|
||||
}
|
||||
}
|
||||
|
||||
async dismissProtectedWarning() {
|
||||
let showNoteContents = true;
|
||||
if (this.application.hasProtectionSources()) {
|
||||
@@ -366,20 +365,45 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
}
|
||||
|
||||
streamItems() {
|
||||
this.removeComponentsObserver = this.application.streamItems(
|
||||
this.removeComponentStreamObserver = this.application.streamItems(
|
||||
ContentType.Component,
|
||||
async (_items, source) => {
|
||||
if (isPayloadSourceInternalChange(source!)) {
|
||||
if (
|
||||
isPayloadSourceInternalChange(source) ||
|
||||
source === PayloadSource.InitialObserverRegistrationPush
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!this.note) return;
|
||||
this.reloadStackComponents();
|
||||
this.reloadEditor();
|
||||
await this.reloadStackComponents();
|
||||
await this.reloadEditorComponent();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async reloadEditor() {
|
||||
private createComponentViewer(component: SNComponent) {
|
||||
const viewer = this.application.componentManager.createComponentViewer(
|
||||
component,
|
||||
this.note.uuid
|
||||
);
|
||||
return viewer;
|
||||
}
|
||||
|
||||
public async editorComponentViewerRequestsReload(
|
||||
viewer: ComponentViewer
|
||||
): Promise<void> {
|
||||
const component = viewer.component;
|
||||
this.application.componentManager.destroyComponentViewer(viewer);
|
||||
await this.setState({
|
||||
editorComponentViewer: undefined,
|
||||
});
|
||||
await this.setState({
|
||||
editorComponentViewer: this.createComponentViewer(component),
|
||||
editorStateDidLoad: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async reloadEditorComponent() {
|
||||
const newEditor = this.application.componentManager.editorForNote(
|
||||
this.note
|
||||
);
|
||||
@@ -387,22 +411,29 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
if (newEditor && this.editor.isTemplateNote) {
|
||||
await this.editor.insertTemplatedNote();
|
||||
}
|
||||
const currentEditor = this.state.editorComponent;
|
||||
if (currentEditor?.uuid !== newEditor?.uuid) {
|
||||
const currentComponentViewer = this.state.editorComponentViewer;
|
||||
|
||||
if (currentComponentViewer?.componentUuid !== newEditor?.uuid) {
|
||||
if (currentComponentViewer) {
|
||||
this.application.componentManager.destroyComponentViewer(
|
||||
currentComponentViewer
|
||||
);
|
||||
}
|
||||
await this.setState({
|
||||
/** Unload current component view so that we create a new one */
|
||||
editorUnloading: true,
|
||||
});
|
||||
await this.setState({
|
||||
/** Reload component view */
|
||||
editorComponent: newEditor,
|
||||
editorUnloading: false,
|
||||
editorComponentViewer: undefined,
|
||||
});
|
||||
if (newEditor) {
|
||||
await this.setState({
|
||||
editorComponentViewer: this.createComponentViewer(newEditor),
|
||||
editorStateDidLoad: true,
|
||||
});
|
||||
}
|
||||
this.reloadFont();
|
||||
} else {
|
||||
await this.setState({
|
||||
editorStateDidLoad: true,
|
||||
});
|
||||
}
|
||||
this.application.componentManager.contextItemDidChangeInArea(
|
||||
ComponentArea.Editor
|
||||
);
|
||||
}
|
||||
|
||||
setMenuState(menu: string, state: boolean) {
|
||||
@@ -429,6 +460,8 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
}
|
||||
|
||||
async editorMenuOnSelect(component?: SNComponent) {
|
||||
const transactions: TransactionalMutation[] = [];
|
||||
|
||||
this.setMenuState('showEditorMenu', false);
|
||||
if (this.appState.getActiveEditor()?.isTemplateNote) {
|
||||
await this.appState.getActiveEditor().insertTemplatedNote();
|
||||
@@ -439,43 +472,56 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
}
|
||||
if (!component) {
|
||||
if (!this.note.prefersPlainEditor) {
|
||||
await this.application.changeItem(this.note.uuid, (mutator) => {
|
||||
const noteMutator = mutator as NoteMutator;
|
||||
noteMutator.prefersPlainEditor = true;
|
||||
transactions.push({
|
||||
itemUuid: this.note.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const noteMutator = m as NoteMutator;
|
||||
noteMutator.prefersPlainEditor = true;
|
||||
},
|
||||
});
|
||||
this.reloadEditor();
|
||||
}
|
||||
if (
|
||||
this.state.editorComponent?.isExplicitlyEnabledForItem(this.note.uuid)
|
||||
this.state.editorComponentViewer?.component.isExplicitlyEnabledForItem(
|
||||
this.note.uuid
|
||||
)
|
||||
) {
|
||||
await this.disassociateComponentWithCurrentNote(
|
||||
this.state.editorComponent
|
||||
transactions.push(
|
||||
this.transactionForDisassociateComponentWithCurrentNote(
|
||||
this.state.editorComponentViewer.component
|
||||
)
|
||||
);
|
||||
}
|
||||
this.reloadFont();
|
||||
} else if (component.area === ComponentArea.Editor) {
|
||||
const currentEditor = this.state.editorComponent;
|
||||
const currentEditor = this.state.editorComponentViewer?.component;
|
||||
if (currentEditor && component.uuid !== currentEditor.uuid) {
|
||||
await this.disassociateComponentWithCurrentNote(currentEditor);
|
||||
transactions.push(
|
||||
this.transactionForDisassociateComponentWithCurrentNote(currentEditor)
|
||||
);
|
||||
}
|
||||
const prefersPlain = this.note.prefersPlainEditor;
|
||||
if (prefersPlain) {
|
||||
await this.application.changeItem(this.note.uuid, (mutator) => {
|
||||
const noteMutator = mutator as NoteMutator;
|
||||
noteMutator.prefersPlainEditor = false;
|
||||
transactions.push({
|
||||
itemUuid: this.note.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const noteMutator = m as NoteMutator;
|
||||
noteMutator.prefersPlainEditor = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
await this.associateComponentWithCurrentNote(component);
|
||||
} else if (component.area === ComponentArea.EditorStack) {
|
||||
await this.toggleStackComponentForCurrentItem(component);
|
||||
transactions.push(
|
||||
this.transactionForAssociateComponentWithCurrentNote(component)
|
||||
);
|
||||
}
|
||||
|
||||
await this.application.runTransactionalMutations(transactions);
|
||||
/** Dirtying can happen above */
|
||||
this.application.sync();
|
||||
}
|
||||
|
||||
hasAvailableExtensions() {
|
||||
return (
|
||||
this.application.actionsManager!.extensionsInContextOfItem(this.note)
|
||||
this.application.actionsManager.extensionsInContextOfItem(this.note)
|
||||
.length > 0
|
||||
);
|
||||
}
|
||||
@@ -534,7 +580,9 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
const truncate = noteText.length > NOTE_PREVIEW_CHAR_LIMIT;
|
||||
const substring = noteText.substring(0, NOTE_PREVIEW_CHAR_LIMIT);
|
||||
const previewPlain = substring + (truncate ? STRING_ELLIPSES : '');
|
||||
// eslint-disable-next-line camelcase
|
||||
noteMutator.preview_plain = previewPlain;
|
||||
// eslint-disable-next-line camelcase
|
||||
noteMutator.preview_html = undefined;
|
||||
}
|
||||
},
|
||||
@@ -643,12 +691,6 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
this.closeAllMenus();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onTitleFocus() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onTitleBlur() {}
|
||||
|
||||
onContentFocus() {
|
||||
this.application
|
||||
.getAppState()
|
||||
@@ -662,11 +704,11 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
|
||||
async deleteNote(permanently: boolean) {
|
||||
if (this.editor.isTemplateNote) {
|
||||
this.application.alertService!.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
|
||||
this.application.alertService.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
|
||||
return;
|
||||
}
|
||||
if (this.note.locked) {
|
||||
this.application.alertService!.alert(STRING_DELETE_LOCKED_ATTEMPT);
|
||||
this.application.alertService.alert(STRING_DELETE_LOCKED_ATTEMPT);
|
||||
return;
|
||||
}
|
||||
const title = this.note.safeTitle().length
|
||||
@@ -782,25 +824,15 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
|
||||
/** @components */
|
||||
|
||||
registerComponentHandler() {
|
||||
this.unregisterComponent =
|
||||
this.application.componentManager.registerHandler({
|
||||
identifier: 'editor',
|
||||
areas: [ComponentArea.EditorStack, ComponentArea.Editor],
|
||||
contextRequestHandler: (componentUuid) => {
|
||||
const currentEditor = this.state.editorComponent;
|
||||
if (
|
||||
componentUuid === currentEditor?.uuid ||
|
||||
Uuids(this.state.stackComponents).includes(componentUuid)
|
||||
) {
|
||||
return this.note;
|
||||
}
|
||||
},
|
||||
focusHandler: (component, focused) => {
|
||||
if (component.isEditor() && focused) {
|
||||
registerComponentManagerEventObserver() {
|
||||
this.removeComponentManagerObserver =
|
||||
this.application.componentManager.addEventObserver((eventName, data) => {
|
||||
if (eventName === ComponentManagerEvent.ViewerDidFocus) {
|
||||
const viewer = data?.componentViewer;
|
||||
if (viewer?.component.isEditor) {
|
||||
this.closeAllMenus();
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -810,58 +842,98 @@ export class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
.componentsForArea(ComponentArea.EditorStack)
|
||||
.filter((component) => component.active)
|
||||
);
|
||||
if (this.note) {
|
||||
for (const component of stackComponents) {
|
||||
if (component.active) {
|
||||
this.application.componentManager.setComponentHidden(
|
||||
component,
|
||||
!component.isExplicitlyEnabledForItem(this.note.uuid)
|
||||
);
|
||||
}
|
||||
const enabledComponents = stackComponents.filter((component) => {
|
||||
return component.isExplicitlyEnabledForItem(this.note.uuid);
|
||||
});
|
||||
|
||||
const needsNewViewer = enabledComponents.filter((component) => {
|
||||
const hasExistingViewer = this.state.stackComponentViewers.find(
|
||||
(viewer) => viewer.componentUuid === component.uuid
|
||||
);
|
||||
return !hasExistingViewer;
|
||||
});
|
||||
|
||||
const needsDestroyViewer = this.state.stackComponentViewers.filter(
|
||||
(viewer) => {
|
||||
const viewerComponentExistsInEnabledComponents = enabledComponents.find(
|
||||
(component) => {
|
||||
return component.uuid === viewer.componentUuid;
|
||||
}
|
||||
);
|
||||
return !viewerComponentExistsInEnabledComponents;
|
||||
}
|
||||
);
|
||||
|
||||
const newViewers: ComponentViewer[] = [];
|
||||
for (const component of needsNewViewer) {
|
||||
newViewers.push(
|
||||
this.application.componentManager.createComponentViewer(
|
||||
component,
|
||||
this.note.uuid
|
||||
)
|
||||
);
|
||||
}
|
||||
await this.setState({ stackComponents });
|
||||
this.application.componentManager.contextItemDidChangeInArea(
|
||||
ComponentArea.EditorStack
|
||||
|
||||
for (const viewer of needsDestroyViewer) {
|
||||
this.application.componentManager.destroyComponentViewer(viewer);
|
||||
}
|
||||
await this.setState({
|
||||
availableStackComponents: stackComponents,
|
||||
stackComponentViewers: newViewers,
|
||||
});
|
||||
}
|
||||
|
||||
stackComponentExpanded(component: SNComponent): boolean {
|
||||
return !!this.state.stackComponentViewers.find(
|
||||
(viewer) => viewer.componentUuid === component.uuid
|
||||
);
|
||||
}
|
||||
|
||||
stackComponentHidden(component: SNComponent) {
|
||||
return this.application.componentManager?.isComponentHidden(component);
|
||||
}
|
||||
|
||||
async toggleStackComponentForCurrentItem(component: SNComponent) {
|
||||
const hidden =
|
||||
this.application.componentManager.isComponentHidden(component);
|
||||
if (hidden || !component.active) {
|
||||
this.application.componentManager.setComponentHidden(component, false);
|
||||
async toggleStackComponent(component: SNComponent) {
|
||||
if (!component.isExplicitlyEnabledForItem(this.note.uuid)) {
|
||||
await this.associateComponentWithCurrentNote(component);
|
||||
this.application.componentManager.contextItemDidChangeInArea(
|
||||
ComponentArea.EditorStack
|
||||
);
|
||||
} else {
|
||||
this.application.componentManager.setComponentHidden(component, true);
|
||||
await this.disassociateComponentWithCurrentNote(component);
|
||||
}
|
||||
this.application.sync();
|
||||
}
|
||||
|
||||
async disassociateComponentWithCurrentNote(component: SNComponent) {
|
||||
return this.application.runTransactionalMutation(
|
||||
this.transactionForDisassociateComponentWithCurrentNote(component)
|
||||
);
|
||||
}
|
||||
|
||||
transactionForDisassociateComponentWithCurrentNote(component: SNComponent) {
|
||||
const note = this.note;
|
||||
return this.application.changeItem(component.uuid, (m) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeAssociatedItemId(note.uuid);
|
||||
mutator.disassociateWithItem(note.uuid);
|
||||
});
|
||||
const transaction: TransactionalMutation = {
|
||||
itemUuid: component.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeAssociatedItemId(note.uuid);
|
||||
mutator.disassociateWithItem(note.uuid);
|
||||
},
|
||||
};
|
||||
return transaction;
|
||||
}
|
||||
|
||||
async associateComponentWithCurrentNote(component: SNComponent) {
|
||||
return this.application.runTransactionalMutation(
|
||||
this.transactionForAssociateComponentWithCurrentNote(component)
|
||||
);
|
||||
}
|
||||
|
||||
transactionForAssociateComponentWithCurrentNote(component: SNComponent) {
|
||||
const note = this.note;
|
||||
return this.application.changeItem(component.uuid, (m) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeDisassociatedItemId(note.uuid);
|
||||
mutator.associateWithItem(note.uuid);
|
||||
});
|
||||
const transaction: TransactionalMutation = {
|
||||
itemUuid: component.uuid,
|
||||
mutate: (m: ItemMutator) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeDisassociatedItemId(note.uuid);
|
||||
mutator.associateWithItem(note.uuid);
|
||||
},
|
||||
};
|
||||
return transaction;
|
||||
}
|
||||
|
||||
registerKeyboardShortcuts() {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
ApplicationEvent,
|
||||
ContentType,
|
||||
SNTheme,
|
||||
ComponentArea,
|
||||
CollectionSort,
|
||||
} from '@standardnotes/snjs';
|
||||
import template from './footer-view.pug';
|
||||
@@ -43,7 +42,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
||||
> {
|
||||
private $rootScope: ng.IRootScopeService;
|
||||
private showSyncResolution = false;
|
||||
private unregisterComponent: any;
|
||||
private rootScopeListener2: any;
|
||||
public arbitraryStatusMessage?: string;
|
||||
public user?: any;
|
||||
@@ -73,8 +71,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
||||
deinit() {
|
||||
for (const remove of this.observerRemovers) remove();
|
||||
this.observerRemovers.length = 0;
|
||||
this.unregisterComponent();
|
||||
this.unregisterComponent = undefined;
|
||||
this.rootScopeListener2();
|
||||
this.rootScopeListener2 = undefined;
|
||||
(this.closeAccountMenu as unknown) = undefined;
|
||||
@@ -146,7 +142,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
||||
this.updateOfflineStatus();
|
||||
this.findErrors();
|
||||
this.streamItems();
|
||||
this.registerComponentHandler();
|
||||
}
|
||||
|
||||
reloadUser() {
|
||||
@@ -273,25 +268,6 @@ class FooterViewCtrl extends PureViewCtrl<
|
||||
);
|
||||
}
|
||||
|
||||
registerComponentHandler() {
|
||||
this.unregisterComponent =
|
||||
this.application.componentManager.registerHandler({
|
||||
identifier: 'room-bar',
|
||||
areas: [ComponentArea.Modal],
|
||||
focusHandler: (component, focused) => {
|
||||
if (component.isEditor() && focused) {
|
||||
if (
|
||||
component.package_info?.identifier ===
|
||||
'org.standardnotes.standard-sheets'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.closeAccountMenu();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateSyncStatus() {
|
||||
const statusManager = this.application.getStatusManager();
|
||||
const syncStatus = this.application.getSyncStatus();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#tags-column.sn-component.section.tags(aria-label='Tags')
|
||||
.component-view-container(ng-if='self.component')
|
||||
.component-view-container(ng-if='self.state.componentViewer')
|
||||
component-view.component-view(
|
||||
component-uuid='self.component.uuid',
|
||||
component-viewer='self.state.componentViewer',
|
||||
application='self.application'
|
||||
app-state='self.appState'
|
||||
)
|
||||
#tags-content.content(ng-if='!(self.component)')
|
||||
#tags-content.content(ng-if='!(self.state.componentViewer)')
|
||||
.tags-title-section.section-title-bar
|
||||
.section-title-bar-header
|
||||
.sk-h3.title
|
||||
|
||||
@@ -6,7 +6,11 @@ import {
|
||||
ApplicationEvent,
|
||||
ComponentAction,
|
||||
ComponentArea,
|
||||
ComponentViewer,
|
||||
ContentType,
|
||||
isPayloadSourceInternalChange,
|
||||
MessageData,
|
||||
PayloadSource,
|
||||
PrefKey,
|
||||
SNComponent,
|
||||
SNSmartTag,
|
||||
@@ -22,14 +26,14 @@ type TagState = {
|
||||
smartTags: SNSmartTag[];
|
||||
noteCounts: NoteCounts;
|
||||
selectedTag?: SNTag;
|
||||
componentViewer?: ComponentViewer;
|
||||
};
|
||||
|
||||
class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||
/** Passed through template */
|
||||
readonly application!: WebApplication;
|
||||
private readonly panelPuppet: PanelPuppet;
|
||||
private unregisterComponent?: any;
|
||||
component?: SNComponent;
|
||||
private unregisterComponent?: () => void;
|
||||
/** The original name of the edtingTag before it began editing */
|
||||
formData: { tagTitle?: string } = {};
|
||||
titles: Partial<Record<UuidString, string>> = {};
|
||||
@@ -46,9 +50,9 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||
|
||||
deinit() {
|
||||
this.removeTagsObserver?.();
|
||||
(this.removeTagsObserver as any) = undefined;
|
||||
(this.removeFoldersObserver as any) = undefined;
|
||||
this.unregisterComponent();
|
||||
(this.removeTagsObserver as unknown) = undefined;
|
||||
(this.removeFoldersObserver as unknown) = undefined;
|
||||
this.unregisterComponent?.();
|
||||
this.unregisterComponent = undefined;
|
||||
super.deinit();
|
||||
}
|
||||
@@ -64,15 +68,10 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
async onAppStart() {
|
||||
super.onAppStart();
|
||||
this.registerComponentHandler();
|
||||
}
|
||||
|
||||
async onAppLaunch() {
|
||||
super.onAppLaunch();
|
||||
this.loadPreferences();
|
||||
this.beginStreamingItems();
|
||||
this.streamForFoldersComponent();
|
||||
|
||||
const smartTags = this.application.getSmartTags();
|
||||
this.setState({ smartTags });
|
||||
@@ -85,13 +84,78 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||
this.reloadNoteCounts();
|
||||
}
|
||||
|
||||
beginStreamingItems() {
|
||||
async setFoldersComponent(component?: SNComponent) {
|
||||
if (this.state.componentViewer) {
|
||||
this.application.componentManager.destroyComponentViewer(
|
||||
this.state.componentViewer
|
||||
);
|
||||
await this.setState({ componentViewer: undefined });
|
||||
}
|
||||
if (component) {
|
||||
await this.setState({
|
||||
componentViewer:
|
||||
this.application.componentManager.createComponentViewer(
|
||||
component,
|
||||
undefined,
|
||||
this.handleFoldersComponentMessage.bind(this)
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleFoldersComponentMessage(
|
||||
action: ComponentAction,
|
||||
data: MessageData
|
||||
): void {
|
||||
if (action === ComponentAction.SelectItem) {
|
||||
const item = data.item;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.content_type === ContentType.Tag) {
|
||||
const matchingTag = this.application.findItem(item.uuid);
|
||||
|
||||
if (matchingTag) {
|
||||
this.selectTag(matchingTag as SNTag);
|
||||
}
|
||||
} else if (item.content_type === ContentType.SmartTag) {
|
||||
const matchingTag = this.getState().smartTags.find(
|
||||
(t) => t.uuid === item.uuid
|
||||
);
|
||||
|
||||
if (matchingTag) {
|
||||
this.selectTag(matchingTag);
|
||||
}
|
||||
}
|
||||
} else if (action === ComponentAction.ClearSelection) {
|
||||
this.selectTag(this.getState().smartTags[0]);
|
||||
}
|
||||
}
|
||||
|
||||
streamForFoldersComponent() {
|
||||
this.removeFoldersObserver = this.application.streamItems(
|
||||
[ContentType.Component],
|
||||
async () => {
|
||||
this.component = this.application.componentManager
|
||||
.componentsForArea(ComponentArea.TagsList).find((component) => component.active);
|
||||
});
|
||||
async (items, source) => {
|
||||
if (
|
||||
isPayloadSourceInternalChange(source) ||
|
||||
source === PayloadSource.InitialObserverRegistrationPush
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const components = items as SNComponent[];
|
||||
const hasFoldersChange = !!components.find(
|
||||
(component) => component.area === ComponentArea.TagsList
|
||||
);
|
||||
if (hasFoldersChange) {
|
||||
this.setFoldersComponent(
|
||||
this.application.componentManager
|
||||
.componentsForArea(ComponentArea.TagsList)
|
||||
.find((component) => component.active)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.removeTagsObserver = this.application.streamItems(
|
||||
[ContentType.Tag, ContentType.SmartTag],
|
||||
@@ -200,41 +264,6 @@ class TagsViewCtrl extends PureViewCtrl<unknown, TagState> {
|
||||
this.application.getAppState().panelDidResize(PANEL_NAME_TAGS, isCollapsed);
|
||||
};
|
||||
|
||||
registerComponentHandler() {
|
||||
this.unregisterComponent =
|
||||
this.application.componentManager.registerHandler({
|
||||
identifier: 'tags',
|
||||
areas: [ComponentArea.TagsList],
|
||||
actionHandler: (_, action, data) => {
|
||||
if (action === ComponentAction.SelectItem) {
|
||||
const item = data.item;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.content_type === ContentType.Tag) {
|
||||
const matchingTag = this.application.findItem(item.uuid);
|
||||
|
||||
if (matchingTag) {
|
||||
this.selectTag(matchingTag as SNTag);
|
||||
}
|
||||
} else if (item.content_type === ContentType.SmartTag) {
|
||||
const matchingTag = this.getState().smartTags.find(
|
||||
(t) => t.uuid === item.uuid
|
||||
);
|
||||
|
||||
if (matchingTag) {
|
||||
this.selectTag(matchingTag);
|
||||
}
|
||||
}
|
||||
} else if (action === ComponentAction.ClearSelection) {
|
||||
this.selectTag(this.getState().smartTags[0]);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async selectTag(tag: SNTag) {
|
||||
if (tag.conflictOf) {
|
||||
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
|
||||
|
||||
Reference in New Issue
Block a user