Remove dummy concept in favor of editor group and editors

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

View File

@@ -5,18 +5,17 @@ declare const __VERSION__: string
import angular from 'angular'; import angular from 'angular';
import { configRoutes } from './routes'; import { configRoutes } from './routes';
import { import { ApplicationGroup } from './ui_models/application_group';
ApplicationManager
} from './applicationManager';
import { import {
Root, ApplicationGroupView,
ApplicationView, ApplicationView,
TagsPanel, EditorGroupView,
NotesPanel, EditorView,
EditorPanel, TagsView,
Footer NotesView,
} from './controllers'; FooterView
} from '@/views';
import { import {
autofocus, autofocus,
@@ -62,13 +61,13 @@ angular
// Controllers // Controllers
angular angular
.module('app') .module('app')
.directive('root', () => new Root()) .directive('applicationGroupView', () => new ApplicationGroupView())
.directive('applicationView', () => new ApplicationView()) .directive('applicationView', () => new ApplicationView())
.directive('tagsPanel', () => new TagsPanel()) .directive('editorGroupView', () => new EditorGroupView())
.directive('notesPanel', () => new NotesPanel()) .directive('editorView', () => new EditorView())
.directive('editorPanel', () => new EditorPanel()) .directive('tagsView', () => new TagsView())
.directive('footer', () => new Footer()) .directive('notesView', () => new NotesView())
// .directive('lockScreen', () => new LockScreen()); .directive('footerView', () => new FooterView())
// Directives - Functional // Directives - Functional
angular angular
@@ -78,9 +77,7 @@ angular
.directive('delayHide', delayHide) .directive('delayHide', delayHide)
.directive('elemReady', elemReady) .directive('elemReady', elemReady)
.directive('fileChange', fileChange) .directive('fileChange', fileChange)
.directive('infiniteScroll', [ .directive('infiniteScroll', [infiniteScroll])
infiniteScroll
])
.directive('lowercase', lowercase) .directive('lowercase', lowercase)
.directive('selectOnClick', ['$window', selectOnClick]) .directive('selectOnClick', ['$window', selectOnClick])
.directive('snEnter', snEnter); .directive('snEnter', snEnter);
@@ -93,11 +90,6 @@ angular
.directive('challengeModal', () => new ChallengeModal()) .directive('challengeModal', () => new ChallengeModal())
.directive('componentModal', () => new ComponentModal()) .directive('componentModal', () => new ComponentModal())
.directive('componentView', () => new ComponentView()) .directive('componentView', () => new ComponentView())
// .directive(
// 'componentView',
// ($rootScope, componentManager, desktopManager, $timeout) =>
// new ComponentView($rootScope, componentManager, desktopManager, $timeout)
// )
.directive('editorMenu', () => new EditorMenu()) .directive('editorMenu', () => new EditorMenu())
.directive('inputModal', () => new InputModal()) .directive('inputModal', () => new InputModal())
.directive('menuRow', () => new MenuRow()) .directive('menuRow', () => new MenuRow())
@@ -116,4 +108,4 @@ angular
.filter('trusted', ['$sce', trusted]); .filter('trusted', ['$sce', trusted]);
// Services // Services
angular.module('app').service('applicationManager', ApplicationManager); angular.module('app').service('mainApplicationGroup', ApplicationGroup);

View File

@@ -1,7 +0,0 @@
export { PureCtrl } from './abstract/pure_ctrl';
export { EditorPanel } from './editor';
export { Footer } from './footer';
export { NotesPanel } from './notes/notes';
export { TagsPanel } from './tags';
export { Root } from './root';
export { ApplicationView } from './applicationView';

View File

@@ -1,37 +0,0 @@
import { ApplicationManager } from './../applicationManager';
import { WebDirective } from './../types';
import template from '%/root.pug';
import { WebApplication } from '@/application';
class RootCtrl {
private $timeout: ng.ITimeoutService
private applicationManager: ApplicationManager
public applications: WebApplication[] = []
/* @ngInject */
constructor($timeout: ng.ITimeoutService, applicationManager: ApplicationManager) {
this.$timeout = $timeout;
this.applicationManager = applicationManager;
this.applicationManager.addApplicationChangeObserver(() => {
this.reload();
});
}
reload() {
this.$timeout(() => {
this.applications = this.applicationManager.getApplications();
});
}
}
export class Root extends WebDirective {
constructor() {
super();
this.template = template;
this.controller = RootCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
}
}

View File

@@ -1,6 +1,4 @@
import { WebApplication } from './application';
import { SNAlertService } from "../../../../snjs/dist/@types"; import { SNAlertService } from "../../../../snjs/dist/@types";
import { RawPayload } from '../../../../snjs/dist/@types/protocol/payloads/generator';
const DB_NAME = 'standardnotes'; const DB_NAME = 'standardnotes';
const STORE_NAME = 'items'; const STORE_NAME = 'items';

View File

@@ -1,16 +1,20 @@
import { debounce } from '@/utils';
/* @ngInject */ /* @ngInject */
export function infiniteScroll() { export function infiniteScroll() {
return { return {
link: function (scope: ng.IScope, elem: JQLite, attrs: any) { link: function (scope: ng.IScope, elem: JQLite, attrs: any) {
const scopeAny = scope as any; const scopeAny = scope as any;
const offset = parseInt(attrs.threshold) || 0; const offset = parseInt(attrs.threshold) || 0;
const e = elem[0]; const element = elem[0];
scopeAny.paginate = debounce(() => {
scope.$apply(attrs.infiniteScroll);
}, 100);
scopeAny.onScroll = () => { scopeAny.onScroll = () => {
if ( if (
scope.$eval(attrs.canLoad) && scope.$eval(attrs.canLoad) &&
e.scrollTop + e.offsetHeight >= e.scrollHeight - offset element.scrollTop + element.offsetHeight >= element.scrollHeight - offset
) { ) {
scope.$apply(attrs.infiniteScroll); scopeAny.paginate();
} }
}; };
elem.on('scroll', scopeAny.onScroll); elem.on('scroll', scopeAny.onScroll);

View File

@@ -2,7 +2,7 @@ import { WebDirective } from './../../types';
import { isDesktopApplication, isNullOrUndefined } from '@/utils'; import { isDesktopApplication, isNullOrUndefined } from '@/utils';
import template from '%/directives/account-menu.pug'; import template from '%/directives/account-menu.pug';
import { ProtectedAction, ContentType } from 'snjs'; import { ProtectedAction, ContentType } from 'snjs';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE, STRING_ACCOUNT_MENU_UNCHECK_MERGE,
STRING_SIGN_OUT_CONFIRMATION, STRING_SIGN_OUT_CONFIRMATION,
@@ -59,7 +59,7 @@ type AccountMenuState = {
importData: any importData: any
} }
class AccountMenuCtrl extends PureCtrl { class AccountMenuCtrl extends PureViewCtrl {
public appVersion: string public appVersion: string
private syncStatus?: SyncOpStatus private syncStatus?: SyncOpStatus

View File

@@ -1,7 +1,7 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import template from '%/directives/actions-menu.pug'; import template from '%/directives/actions-menu.pug';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { SNItem, Action, SNActionsExtension } from '@/../../../../snjs/dist/@types'; import { SNItem, Action, SNActionsExtension } from '@/../../../../snjs/dist/@types';
import { ActionResponse } from '@/../../../../snjs/dist/@types/services/actions_service'; import { ActionResponse } from '@/../../../../snjs/dist/@types/services/actions_service';
@@ -10,7 +10,7 @@ type ActionsMenuScope = {
item: SNItem item: SNItem
} }
class ActionsMenuCtrl extends PureCtrl implements ActionsMenuScope { class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
application!: WebApplication application!: WebApplication
item!: SNItem item!: SNItem

View File

@@ -1,5 +1,5 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { WebApplication } from './../../application'; import { WebApplication } from '@/ui_models/application';
import template from '%/directives/challenge-modal.pug'; import template from '%/directives/challenge-modal.pug';
import { import {
ChallengeType, ChallengeType,
@@ -8,7 +8,7 @@ import {
Challenge, Challenge,
ChallengeOrchestrator ChallengeOrchestrator
} from 'snjs'; } from 'snjs';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
type InputValue = { type InputValue = {
value: string value: string
@@ -28,7 +28,7 @@ type ChallengeModalState = {
processing: boolean processing: boolean
} }
class ChallengeModalCtrl extends PureCtrl implements ChallengeModalScope { class ChallengeModalCtrl extends PureViewCtrl implements ChallengeModalScope {
private $element: JQLite private $element: JQLite
private processingTypes: ChallengeType[] = [] private processingTypes: ChallengeType[] = []
application!: WebApplication application!: WebApplication
@@ -159,7 +159,8 @@ export class ChallengeModal extends WebDirective {
this.template = template; this.template = template;
this.controller = ChallengeModalCtrl; this.controller = ChallengeModalCtrl;
this.controllerAs = 'ctrl'; this.controllerAs = 'ctrl';
this.bindToController = { this.bindToController = true;
this.scope = {
challenge: '=', challenge: '=',
orchestrator: '=', orchestrator: '=',
application: '=' application: '='

View File

@@ -1,4 +1,4 @@
import { WebApplication } from './../../application'; import { WebApplication } from '@/ui_models/application';
import { SNComponent } from 'snjs'; import { SNComponent } from 'snjs';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import template from '%/directives/component-modal.pug'; import template from '%/directives/component-modal.pug';

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { SNComponent } from 'snjs'; import { SNComponent } from 'snjs';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import template from '%/directives/component-view.pug'; import template from '%/directives/component-view.pug';

View File

@@ -1,9 +1,9 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { SNComponent, SNItem, ComponentArea } from 'snjs'; import { SNComponent, SNItem, ComponentArea } from 'snjs';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import template from '%/directives/editor-menu.pug'; import template from '%/directives/editor-menu.pug';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models'; import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
interface EditorMenuScope { interface EditorMenuScope {
@@ -13,7 +13,7 @@ interface EditorMenuScope {
application: WebApplication application: WebApplication
} }
class EditorMenuCtrl extends PureCtrl implements EditorMenuScope { class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
callback!: (component: SNComponent) => void callback!: (component: SNComponent) => void
selectedEditor!: SNComponent selectedEditor!: SNComponent

View File

@@ -84,7 +84,7 @@ class PanelResizerCtrl implements PanelResizerScope {
this.$timeout = $timeout; this.$timeout = $timeout;
/** To allow for registering events */ /** To allow for registering events */
this.handleResize = this.handleResize.bind(this); this.handleResize = debounce(this.handleResize.bind(this), 250);
this.onMouseMove = this.onMouseMove.bind(this); this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseUp = this.onMouseUp.bind(this); this.onMouseUp = this.onMouseUp.bind(this);
this.onMouseDown = this.onMouseDown.bind(this); this.onMouseDown = this.onMouseDown.bind(this);
@@ -163,13 +163,11 @@ class PanelResizerCtrl implements PanelResizerScope {
} }
handleResize() { handleResize() {
debounce(() => { this.reloadDefaultValues();
this.reloadDefaultValues(); this.handleWidthEvent();
this.handleWidthEvent(); this.$timeout(() => {
this.$timeout(() => { this.finishSettingWidth();
this.finishSettingWidth(); });
});
}, 250);
} }
getParentRect() { getParentRect() {

View File

@@ -1,7 +1,7 @@
import { WebApplication } from './../../application'; import { WebApplication } from '@/ui_models/application';
import { PasswordWizardScope, PasswordWizardType, WebDirective } from './../../types'; import { PasswordWizardScope, PasswordWizardType, WebDirective } from './../../types';
import template from '%/directives/password-wizard.pug'; import template from '%/directives/password-wizard.pug';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
const DEFAULT_CONTINUE_TITLE = "Continue"; const DEFAULT_CONTINUE_TITLE = "Continue";
const Steps = { const Steps = {
@@ -9,7 +9,7 @@ const Steps = {
FinishStep: 2 FinishStep: 2
}; };
class PasswordWizardCtrl extends PureCtrl implements PasswordWizardScope { class PasswordWizardCtrl extends PureViewCtrl implements PasswordWizardScope {
$element: JQLite $element: JQLite
application!: WebApplication application!: WebApplication
type!: PasswordWizardType type!: PasswordWizardType

View File

@@ -1,5 +1,5 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { ProtectedAction, PrivilegeCredential, PrivilegeSessionLength } from 'snjs'; import { ProtectedAction, PrivilegeCredential, PrivilegeSessionLength } from 'snjs';
import template from '%/directives/privileges-auth-modal.pug'; import template from '%/directives/privileges-auth-modal.pug';

View File

@@ -1,8 +1,8 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import template from '%/directives/privileges-management-modal.pug'; import template from '%/directives/privileges-management-modal.pug';
import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from 'snjs'; import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from 'snjs';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { PrivilegeMutator } from '@/../../../../snjs/dist/@types/models'; import { PrivilegeMutator } from '@/../../../../snjs/dist/@types/models';
type DisplayInfo = { type DisplayInfo = {
@@ -10,7 +10,7 @@ type DisplayInfo = {
prompt: string prompt: string
} }
class PrivilegesManagementModalCtrl extends PureCtrl { class PrivilegesManagementModalCtrl extends PureViewCtrl {
hasPasscode = false hasPasscode = false
hasAccount = false hasAccount = false

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { import {
ContentType, ContentType,

View File

@@ -1,5 +1,5 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import template from '%/directives/session-history-menu.pug'; import template from '%/directives/session-history-menu.pug';
import { SNItem, ItemHistoryEntry, ItemHistory } from '@/../../../../snjs/dist/@types'; import { SNItem, ItemHistoryEntry, ItemHistory } from '@/../../../../snjs/dist/@types';

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import template from '%/directives/sync-resolution-menu.pug'; import template from '%/directives/sync-resolution-menu.pug';

View File

@@ -4,8 +4,8 @@ import { SKAlert } from 'sn-stylekit';
export class AlertService extends SNAlertService { export class AlertService extends SNAlertService {
async alert( async alert(
title: string,
text: string, text: string,
title: string,
closeButtonText = 'OK', closeButtonText = 'OK',
onClose: () => void onClose: () => void
) { ) {
@@ -28,8 +28,8 @@ export class AlertService extends SNAlertService {
} }
async confirm( async confirm(
title: string,
text: string, text: string,
title: string,
confirmButtonText = 'Confirm', confirmButtonText = 'Confirm',
cancelButtonText = 'Cancel', cancelButtonText = 'Cancel',
onConfirm: () => void, onConfirm: () => void,

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote } from 'snjs'; import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote } from 'snjs';
export class ArchiveManager { export class ArchiveManager {

View File

@@ -1,6 +1,6 @@
import { SNComponent, PurePayload, ComponentMutator, AppDataField } from 'snjs'; import { SNComponent, PurePayload, ComponentMutator, AppDataField } from 'snjs';
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
// An interface used by the Desktop app to interact with SN // An interface used by the Desktop app to interact with SN
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs'; import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs';

View File

@@ -1,4 +1,4 @@
import { WebApplication } from './../application'; import { WebApplication } from '@/ui_models/application';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { import {
SNPredicate, SNPredicate,
ContentType, ContentType,

View File

@@ -1,11 +1,20 @@
import { WebApplication } from './../application';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import pull from 'lodash/pull'; import pull from 'lodash/pull';
import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs, ContentType, SNSmartTag } from 'snjs'; import {
ProtectedAction,
ApplicationEvent,
SNTag,
SNNote,
SNUserPrefs,
ContentType,
SNSmartTag
} from 'snjs';
import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor';
export enum AppStateEvent { export enum AppStateEvent {
TagChanged = 1, TagChanged = 1,
NoteChanged = 2, ActiveEditorChanged = 2,
PreferencesChanged = 3, PreferencesChanged = 3,
PanelResized = 4, PanelResized = 4,
EditorFocused = 5, EditorFocused = 5,
@@ -34,8 +43,8 @@ export class AppState {
rootScopeCleanup2: any rootScopeCleanup2: any
onVisibilityChange: any onVisibilityChange: any
selectedTag?: SNTag selectedTag?: SNTag
selectedNote?: SNNote
userPreferences?: SNUserPrefs userPreferences?: SNUserPrefs
multiEditorEnabled = false
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -74,16 +83,90 @@ export class AppState {
this.onVisibilityChange = undefined; this.onVisibilityChange = undefined;
} }
/**
* Creates a new editor if one doesn't exist. If one does, we'll replace the
* editor's note with an empty one.
*/
createEditor(title?: string) {
const activeEditor = this.getActiveEditor();
if (!activeEditor || this.multiEditorEnabled) {
this.application.editorGroup.createEditor(title);
} else {
activeEditor.reset(title);
}
}
async openEditor(noteUuid: string) {
const note = this.application.findItem(noteUuid) as SNNote;
const run = async () => {
const activeEditor = this.getActiveEditor();
if (!activeEditor || this.multiEditorEnabled) {
this.application.editorGroup.createEditor(noteUuid);
} else {
activeEditor.setNote(note);
}
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
};
if (note && note.safeContent.protected &&
await this.application.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ViewProtectedNotes
)) {
return new Promise((resolve) => {
this.application.presentPrivilegesModal(
ProtectedAction.ViewProtectedNotes,
() => {
run().then(resolve);
}
);
});
} else {
return run();
}
}
getActiveEditor() {
return this.application.editorGroup.editors[0];
}
getEditors() {
return this.application.editorGroup.editors;
}
closeEditor(editor: Editor) {
this.application.editorGroup.closeEditor(editor);
}
closeActiveEditor() {
this.application.editorGroup.closeActiveEditor();
}
closeAllEditors() {
this.application.editorGroup.closeAllEditors();
}
editorForNote(note: SNNote) {
for (const editor of this.getEditors()) {
if (editor.note.uuid === note.uuid) {
return editor;
}
}
}
streamNotesAndTags() { streamNotesAndTags() {
this.application!.streamItems( this.application!.streamItems(
[ContentType.Note, ContentType.Tag], [ContentType.Note, ContentType.Tag],
async (items) => { async (items) => {
if(this.selectedNote) { /** Close any editors for deleted notes */
const matchingNote = items.find((candidate) => candidate.uuid === this.selectedNote!.uuid); const notes = items.filter((candidate) => candidate.content_type === ContentType.Note) as SNNote[];
if(matchingNote) { for (const note of notes) {
this.selectedNote = matchingNote as SNNote; if (note.deleted) {
const editor = this.editorForNote(note);
if (editor) {
this.closeEditor(editor);
}
} }
} }
if (this.selectedTag) { if (this.selectedTag) {
const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid); const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid);
if (matchingTag) { if (matchingTag) {
@@ -161,32 +244,6 @@ export class AppState {
); );
} }
async setSelectedNote(note?: SNNote) {
const run = async () => {
const previousNote = this.selectedNote;
this.selectedNote = note;
await this.notifyEvent(
AppStateEvent.NoteChanged,
{ previousNote: previousNote }
);
};
if (note && note.safeContent.protected &&
await this.application.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ViewProtectedNotes
)) {
return new Promise((resolve) => {
this.application.presentPrivilegesModal(
ProtectedAction.ViewProtectedNotes,
() => {
run().then(resolve);
}
);
});
} else {
return run();
}
}
/** Returns the tags that are referncing this note */ /** Returns the tags that are referncing this note */
getNoteTags(note: SNNote) { getNoteTags(note: SNNote) {
return this.application.referencingForItem(note).filter((ref) => { return this.application.referencingForItem(note).filter((ref) => {
@@ -196,11 +253,11 @@ export class AppState {
/** Returns the notes this tag references */ /** Returns the notes this tag references */
getTagNotes(tag: SNTag) { getTagNotes(tag: SNTag) {
if(tag.isSmartTag()) { if (tag.isSmartTag()) {
return this.application.notesMatchingSmartTag(tag as SNSmartTag); return this.application.notesMatchingSmartTag(tag as SNSmartTag);
} else { } else {
return this.application.referencesForItem(tag).filter((ref) => { return this.application.referencesForItem(tag).filter((ref) => {
return ref.content_type === tag.content_type; return ref.content_type === ContentType.Note;
}) as SNNote[] }) as SNNote[]
} }
} }
@@ -209,10 +266,6 @@ export class AppState {
return this.selectedTag; return this.selectedTag;
} }
getSelectedNote() {
return this.selectedNote;
}
setUserPreferences(preferences: SNUserPrefs) { setUserPreferences(preferences: SNUserPrefs) {
this.userPreferences = preferences; this.userPreferences = preferences;
this.notifyEvent( this.notifyEvent(

View File

@@ -1,5 +1,5 @@
import { removeFromArray } from 'snjs'; import { removeFromArray } from 'snjs';
import { FooterStatus } from './../types'; import { FooterStatus } from '@/types';
type StatusCallback = (string: string) => void type StatusCallback = (string: string) => void

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import _ from 'lodash'; import _ from 'lodash';
import { import {
StorageValueModes, StorageValueModes,

View File

@@ -14,7 +14,9 @@
"paths": { "paths": {
"%/*": ["../templates/*"], "%/*": ["../templates/*"],
"@/*": ["./*"], "@/*": ["./*"],
"@Controllers/*": ["./controllers/*"] "@Controllers/*": ["./controllers/*"],
"@Views/*": ["./views/*"],
"@Services/*": ["./services/*"],
} }
} }
} }

View File

@@ -1,5 +1,6 @@
import { InputModalScope } from './directives/views/inputModal'; import { EditorGroup } from '@/ui_models/editor_group';
import { PasswordWizardType, PasswordWizardScope } from './types'; import { InputModalScope } from '@/directives/views/inputModal';
import { PasswordWizardType, PasswordWizardScope } from '@/types';
import { import {
Environment, Environment,
SNApplication, SNApplication,
@@ -23,7 +24,7 @@ import {
ThemeManager, ThemeManager,
PreferencesManager, PreferencesManager,
KeyboardManager KeyboardManager
} from './services'; } from '@/services';
type WebServices = { type WebServices = {
appState: AppState appState: AppState
@@ -44,6 +45,7 @@ export class WebApplication extends SNApplication {
private onDeinit?: (app: WebApplication) => void private onDeinit?: (app: WebApplication) => void
private webServices!: WebServices private webServices!: WebServices
private currentAuthenticationElement?: JQLite private currentAuthenticationElement?: JQLite
public editorGroup: EditorGroup
/* @ngInject */ /* @ngInject */
constructor( constructor(
@@ -71,6 +73,7 @@ export class WebApplication extends SNApplication {
this.scope = scope; this.scope = scope;
this.onDeinit = onDeinit; this.onDeinit = onDeinit;
deviceInterface.setApplication(this); deviceInterface.setApplication(this);
this.editorGroup = new EditorGroup(this);
} }
/** @override */ /** @override */
@@ -86,6 +89,7 @@ export class WebApplication extends SNApplication {
this.onDeinit!(this); this.onDeinit!(this);
this.onDeinit = undefined; this.onDeinit = undefined;
this.$compile = undefined; this.$compile = undefined;
this.editorGroup.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

@@ -10,11 +10,11 @@ import {
StatusManager, StatusManager,
ThemeManager, ThemeManager,
AppState AppState
} from './services'; } from '@/services';
type AppManagerChangeCallback = () => void type AppManagerChangeCallback = () => void
export class ApplicationManager { export class ApplicationGroup {
$compile: ng.ICompileService $compile: ng.ICompileService
$rootScope: ng.IRootScopeService $rootScope: ng.IRootScopeService

View File

@@ -0,0 +1,104 @@
import { SNNote, ContentType, PayloadSource } from 'snjs';
import { WebApplication } from './application';
export class Editor {
public note!: SNNote
private application: WebApplication
private _onNoteChange?: () => void
private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void
private removeStreamObserver: () => void
public isTemplateNote = true
constructor(
application: WebApplication,
noteUuid?: string,
noteTitle?: string
) {
this.application = application;
if (noteUuid) {
this.note = application.findItem(noteUuid) as SNNote;
} else {
this.reset(noteTitle);
}
this.removeStreamObserver = this.application.streamItems(
ContentType.Note,
async (items, source) => {
await this.handleNoteStream(items as SNNote[], source);
}
);
}
private async handleNoteStream(notes: SNNote[], source?: PayloadSource) {
/** Update our note object reference whenever it changes */
const matchingNote = notes.find((item) => {
return item.uuid === this.note.uuid;
}) as SNNote;
if (matchingNote) {
this.isTemplateNote = false;
this.note = matchingNote;
this._onNoteValueChange!(matchingNote, source);
}
}
async insertTemplatedNote() {
return this.application.insertItem(this.note);
}
/**
* Reverts the editor to a blank state, removing any existing note from view,
* and creating a placeholder note.
*/
async reset(noteTitle?: string) {
const note = await this.application.createTemplateItem(
ContentType.Note,
{
text: '',
title: noteTitle || '',
references: []
}
);
this.isTemplateNote = true;
this.setNote(note as SNNote);
}
deinit() {
this.removeStreamObserver();
(this.removeStreamObserver as any) = undefined;
this._onNoteChange = undefined;
(this.application as any) = undefined;
this._onNoteChange = undefined;
this._onNoteValueChange = undefined;
}
/**
* Register to be notified when the editor's note changes.
*/
public onNoteChange(onNoteChange: () => void) {
this._onNoteChange = onNoteChange;
if (this.note) {
onNoteChange();
}
}
/**
* Register to be notified when the editor's note's values change
* (and thus a new object reference is created)
*/
public onNoteValueChange(
onNoteValueChange: (note: SNNote, source?: PayloadSource) => void
) {
this._onNoteValueChange = onNoteValueChange;
}
/**
* Sets the editor contents by setting its note.
*/
public setNote(note: SNNote) {
this.note = note;
if (this._onNoteChange) {
this._onNoteChange();
}
}
}

View File

@@ -0,0 +1,69 @@
import { removeFromArray } from '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 any) = undefined;
for (const editor of this.editors) {
this.deleteEditor(editor);
}
}
createEditor(noteUuid?: string, noteTitle?: string) {
const editor = new Editor(this.application, noteUuid, noteTitle);
this.editors.push(editor);
this.notifyObservers();
}
deleteEditor(editor: Editor) {
editor.deinit();
removeFromArray(this.editors, editor);
}
closeEditor(editor: Editor) {
this.deleteEditor(editor);
this.notifyObservers();
}
closeActiveEditor() {
this.deleteEditor(this.editors[0]);
}
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();
}
}
private notifyObservers() {
for (const observer of this.changeObservers) {
observer();
}
}
}

View File

@@ -1,11 +1,10 @@
import { AppStateEvent } from '@/services/state';
import { WebApplication } from './../../application';
import { ApplicationEvent } from 'snjs'; import { ApplicationEvent } from 'snjs';
import { WebApplication } from '@/ui_models/application';
export type CtrlState = Partial<Record<string, any>> export type CtrlState = Partial<Record<string, any>>
export type CtrlProps = Partial<Record<string, any>> export type CtrlProps = Partial<Record<string, any>>
export class PureCtrl { export class PureViewCtrl {
$timeout: ng.ITimeoutService $timeout: ng.ITimeoutService
/** Passed through templates */ /** Passed through templates */
application?: WebApplication application?: WebApplication

View File

@@ -5,11 +5,13 @@
ng-class='self.state.appClass', ng-class='self.state.appClass',
ng-if='!self.state.needsUnlock && self.state.ready' ng-if='!self.state.needsUnlock && self.state.ready'
) )
tags-panel(application='self.application') tags-view(application='self.application')
notes-panel(application='self.application') notes-view(application='self.application')
editor-panel(application='self.application') editor-group-view(
application='self.application'
)
footer( footer-view(
ng-if='!self.state.needsUnlock && self.state.ready' ng-if='!self.state.needsUnlock && self.state.ready'
application='self.application' application='self.application'
) )

View File

@@ -1,21 +1,21 @@
import { PanelPuppet, WebDirective, PermissionsModalScope, ModalComponentScope } from './../types'; import { WebDirective, PermissionsModalScope, ModalComponentScope } from '@/types';
import { getPlatformString } from '@/utils'; import { getPlatformString } from '@/utils';
import template from '%/application-view.pug'; import template from './application-view.pug';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';
import { ApplicationEvent, SNComponent } from 'snjs'; import { ApplicationEvent, SNComponent } from 'snjs';
import angular from 'angular'; import angular from 'angular';
import { import {
PANEL_NAME_NOTES, PANEL_NAME_NOTES,
PANEL_NAME_TAGS PANEL_NAME_TAGS
} from '@/controllers/constants'; } from '@/views/constants';
import { import {
STRING_SESSION_EXPIRED, STRING_SESSION_EXPIRED,
STRING_DEFAULT_FILE_ERROR STRING_DEFAULT_FILE_ERROR
} from '@/strings'; } from '@/strings';
import { PureCtrl } from './abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { PermissionDialog } from '@/../../../../snjs/dist/@types/services/component_manager'; import { PermissionDialog } from '@/../../../../snjs/dist/@types/services/component_manager';
class ApplicationViewCtrl extends PureCtrl { class ApplicationViewCtrl extends PureViewCtrl {
private $compile?: ng.ICompileService private $compile?: ng.ICompileService
private $location?: ng.ILocationService private $location?: ng.ILocationService
private $rootScope?: ng.IRootScopeService private $rootScope?: ng.IRootScopeService
@@ -47,7 +47,7 @@ class ApplicationViewCtrl extends PureCtrl {
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this); this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
this.addDragDropHandlers(); this.addDragDropHandlers();
} }
deinit() { deinit() {
this.$location = undefined; this.$location = undefined;
this.$rootScope = undefined; this.$rootScope = undefined;
@@ -61,7 +61,7 @@ class ApplicationViewCtrl extends PureCtrl {
(this.presentPermissionsDialog as any) = undefined; (this.presentPermissionsDialog as any) = undefined;
super.deinit(); super.deinit();
} }
$onInit() { $onInit() {
super.$onInit(); super.$onInit();
this.loadApplication(); this.loadApplication();
@@ -226,7 +226,7 @@ class ApplicationViewCtrl extends PureCtrl {
scope.component = dialog.component; scope.component = dialog.component;
scope.callback = dialog.callback; scope.callback = dialog.callback;
const el = this.$compile!( const el = this.$compile!(
"<permissions-modal component='component' permissions-string='permissionsString'" "<permissions-modal component='component' permissions-string='permissionsString'"
+ " callback='callback' class='sk-modal'></permissions-modal>" + " callback='callback' class='sk-modal'></permissions-modal>"
)(scope as any); )(scope as any);
angular.element(document.body).append(el); angular.element(document.body).append(el);
@@ -310,7 +310,8 @@ export class ApplicationView extends WebDirective {
this.controller = ApplicationViewCtrl; this.controller = ApplicationViewCtrl;
this.replace = true; this.replace = true;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = { this.bindToController = true;
this.scope = {
application: '=' application: '='
}; };
} }

View File

@@ -0,0 +1,40 @@
import { ApplicationGroup } from '@/ui_models/application_group';
import { WebDirective } from '@/types';
import template from './application-group-view.pug';
import { WebApplication } from '@/ui_models/application';
class ApplicationGroupViewCtrl {
private $timeout: ng.ITimeoutService
private applicationGroup: ApplicationGroup
public applications: WebApplication[] = []
/* @ngInject */
constructor(
$timeout: ng.ITimeoutService,
mainApplicationGroup: ApplicationGroup
) {
this.$timeout = $timeout;
this.applicationGroup = mainApplicationGroup;
this.applicationGroup.addApplicationChangeObserver(() => {
this.reload();
});
}
reload() {
this.$timeout(() => {
this.applications = this.applicationGroup.getApplications();
});
}
}
export class ApplicationGroupView extends WebDirective {
constructor() {
super();
this.template = template;
this.controller = ApplicationGroupViewCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
}
}

View File

@@ -13,7 +13,7 @@
| {{self.lockText}} | {{self.lockText}}
#editor-title-bar.section-title-bar( #editor-title-bar.section-title-bar(
ng-class="{'locked' : self.noteLocked}", ng-class="{'locked' : self.noteLocked}",
ng-show='self.state.note && !self.state.note.errorDecrypting' ng-show='self.note && !self.note.errorDecrypting'
) )
.title .title
input#note-title-editor.input( input#note-title-editor.input(
@@ -48,7 +48,7 @@
spellcheck='false', spellcheck='false',
type='text' type='text'
) )
.sn-component(ng-if='self.state.note') .sn-component(ng-if='self.note')
#editor-menu-bar.sk-app-bar.no-edges #editor-menu-bar.sk-app-bar.no-edges
.left .left
.sk-app-bar-item( .sk-app-bar-item(
@@ -65,12 +65,12 @@
menu-row( menu-row(
action='self.selectedMenuItem(true); self.togglePin()', action='self.selectedMenuItem(true); self.togglePin()',
desc="'Pin or unpin a note from the top of your list'", desc="'Pin or unpin a note from the top of your list'",
label="self.state.note.pinned ? 'Unpin' : 'Pin'" label="self.note.pinned ? 'Unpin' : 'Pin'"
) )
menu-row( menu-row(
action='self.selectedMenuItem(true); self.toggleArchiveNote()', action='self.selectedMenuItem(true); self.toggleArchiveNote()',
desc="'Archive or unarchive a note from your Archived system tag'", desc="'Archive or unarchive a note from your Archived system tag'",
label="self.state.note.archived ? 'Unarchive' : 'Archive'" label="self.note.archived ? 'Unarchive' : 'Archive'"
) )
menu-row( menu-row(
action='self.selectedMenuItem(true); self.toggleLockNote()', action='self.selectedMenuItem(true); self.toggleLockNote()',
@@ -81,11 +81,11 @@
action='self.selectedMenuItem(true); self.toggleProtectNote()', action='self.selectedMenuItem(true); self.toggleProtectNote()',
desc=`'Protecting a note will require credentials to view desc=`'Protecting a note will require credentials to view
it (Manage Privileges via Account menu)'`, it (Manage Privileges via Account menu)'`,
label="self.state.note.protected ? 'Unprotect' : 'Protect'" label="self.note.protected ? 'Unprotect' : 'Protect'"
) )
menu-row( menu-row(
action='self.selectedMenuItem(true); self.toggleNotePreview()', action='self.selectedMenuItem(true); self.toggleNotePreview()',
circle="self.state.note.hidePreview ? 'danger' : 'success'", circle="self.note.hidePreview ? 'danger' : 'success'",
circle-align="'right'", circle-align="'right'",
desc="'Hide or unhide the note preview from the list of notes'", desc="'Hide or unhide the note preview from the list of notes'",
label="'Preview'" label="'Preview'"
@@ -94,22 +94,22 @@
action='self.selectedMenuItem(); self.deleteNote()', action='self.selectedMenuItem(); self.deleteNote()',
desc="'Send this note to the trash'", desc="'Send this note to the trash'",
label="'Move to Trash'", label="'Move to Trash'",
ng-show='!self.state.altKeyDown && !self.state.note.trashed && !self.state.note.errorDecrypting', ng-show='!self.state.altKeyDown && !self.note.trashed && !self.note.errorDecrypting',
stylekit-class="'warning'" stylekit-class="'warning'"
) )
menu-row( menu-row(
action='self.selectedMenuItem(); self.deleteNotePermanantely()', action='self.selectedMenuItem(); self.deleteNotePermanantely()',
desc="'Delete this note permanently from all your devices'", desc="'Delete this note permanently from all your devices'",
label="'Delete Permanently'", label="'Delete Permanently'",
ng-show='!self.state.note.trashed && self.state.note.errorDecrypting', ng-show='!self.note.trashed && self.note.errorDecrypting',
stylekit-class="'danger'" stylekit-class="'danger'"
) )
div(ng-if='self.state.note.trashed || self.state.altKeyDown') div(ng-if='self.note.trashed || self.state.altKeyDown')
menu-row( menu-row(
action='self.selectedMenuItem(true); self.restoreTrashedNote()', action='self.selectedMenuItem(true); self.restoreTrashedNote()',
desc="'Undelete this note and restore it back into your notes'", desc="'Undelete this note and restore it back into your notes'",
label="'Restore'", label="'Restore'",
ng-show='self.state.note.trashed', ng-show='self.note.trashed',
stylekit-class="'info'" stylekit-class="'info'"
) )
menu-row( menu-row(
@@ -122,7 +122,7 @@
action='self.selectedMenuItem(true); self.emptyTrash()', action='self.selectedMenuItem(true); self.emptyTrash()',
desc="'Permanently delete all notes in the trash'", desc="'Permanently delete all notes in the trash'",
label="'Empty Trash'", label="'Empty Trash'",
ng-show='self.state.note.trashed || !self.state.altKeyDown', ng-show='self.note.trashed || !self.state.altKeyDown',
stylekit-class="'danger'", stylekit-class="'danger'",
subtitle="self.getTrashCount() + ' notes in trash'" subtitle="self.getTrashCount() + ' notes in trash'"
) )
@@ -164,7 +164,7 @@
.sk-label Editor .sk-label Editor
editor-menu( editor-menu(
callback='self.editorMenuOnSelect()', callback='self.editorMenuOnSelect()',
current-item='self.state.note', current-item='self.note',
ng-if='self.state.showEditorMenu', ng-if='self.state.showEditorMenu',
selected-editor='self.state.selectedEditor', selected-editor='self.state.selectedEditor',
application='self.application' application='self.application'
@@ -177,7 +177,7 @@
) )
.sk-label Actions .sk-label Actions
actions-menu( actions-menu(
item='self.state.note', item='self.note',
ng-if='self.state.showExtensions', ng-if='self.state.showExtensions',
application='self.application' application='self.application'
) )
@@ -188,12 +188,12 @@
) )
.sk-label Session History .sk-label Session History
session-history-menu( session-history-menu(
item='self.state.note', item='self.note',
ng-if='self.state.showSessionHistory', ng-if='self.state.showSessionHistory',
application='self.application' application='self.application'
) )
#editor-content.editor-content( #editor-content.editor-content(
ng-if='self.state.noteReady && !self.state.note.errorDecrypting' ng-if='self.state.noteReady && !self.note.errorDecrypting'
) )
panel-resizer.left( panel-resizer.left(
control='self.leftPanelPuppet', control='self.leftPanelPuppet',
@@ -231,11 +231,11 @@
panel-id="'editor-content'", panel-id="'editor-content'",
property="'right'" property="'right'"
) )
.section(ng-show='self.state.note.errorDecrypting') .section(ng-show='self.note.errorDecrypting')
p.medium-padding(style='padding-top: 0 !important;') p.medium-padding(style='padding-top: 0 !important;')
| There was an error decrypting this item. Ensure you are running the | 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. | latest version of this app, then sign out and sign back in to try again.
#editor-pane-component-stack(ng-show='self.state.note') #editor-pane-component-stack(ng-show='self.note')
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.componentStack.length') #component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.componentStack.length')
.left .left
.sk-app-bar-item( .sk-app-bar-item(

View File

@@ -1,5 +1,6 @@
import { WebApplication } from './../application'; import { Editor } from '@/ui_models/editor';
import { PanelPuppet, WebDirective } from './../types'; import { WebApplication } from '@/ui_models/application';
import { PanelPuppet, WebDirective } from '@/types';
import angular from 'angular'; import angular from 'angular';
import { import {
ApplicationEvent, ApplicationEvent,
@@ -19,8 +20,8 @@ import {
import find from 'lodash/find'; import find from 'lodash/find';
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import template from '%/editor.pug'; import template from './editor-view.pug';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent, EventSource } from '@/services/state'; import { AppStateEvent, EventSource } from '@/services/state';
import { import {
STRING_DELETED_NOTE, STRING_DELETED_NOTE,
@@ -39,12 +40,6 @@ const SAVE_TIMEOUT_DEBOUNCE = 350;
const SAVE_TIMEOUT_NO_DEBOUNCE = 100; const SAVE_TIMEOUT_NO_DEBOUNCE = 100;
const EDITOR_DEBOUNCE = 200; const EDITOR_DEBOUNCE = 200;
const AppDataKeys = {
Pinned: 'pinned',
Locked: 'locked',
Archived: 'archived',
PrefersPlainEditor: 'prefersPlainEditor'
};
const ElementIds = { const ElementIds = {
NoteTextEditor: 'note-text-editor', NoteTextEditor: 'note-text-editor',
NoteTitleEditor: 'note-title-editor', NoteTitleEditor: 'note-title-editor',
@@ -63,9 +58,8 @@ type NoteStatus = {
} }
type EditorState = { type EditorState = {
note: SNNote
saveError?: any saveError?: any
selectedEditor?: SNComponent editorComponent?: SNComponent
noteStatus?: NoteStatus noteStatus?: NoteStatus
tagsAsStrings?: string tagsAsStrings?: string
marginResizersEnabled?: boolean marginResizersEnabled?: boolean
@@ -73,6 +67,12 @@ type EditorState = {
isDesktop?: boolean isDesktop?: boolean
tagsComponent?: SNComponent tagsComponent?: SNComponent
componentStack?: SNComponent[] componentStack?: SNComponent[]
syncTakingTooLong: boolean
showExtensions: boolean
noteReady: boolean
showOptionsMenu: boolean
altKeyDown: boolean
spellcheck: boolean
/** Fields that can be directly mutated by the template */ /** Fields that can be directly mutated by the template */
mutable: {} mutable: {}
} }
@@ -83,9 +83,16 @@ type EditorValues = {
tagsInputValue?: string tagsInputValue?: string
} }
class EditorCtrl extends PureCtrl { interface EditorViewScope {
application: WebApplication
editor: Editor
}
class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
/** Passed through template */ /** Passed through template */
readonly application!: WebApplication readonly application!: WebApplication
readonly editor!: Editor
private leftPanelPuppet?: PanelPuppet private leftPanelPuppet?: PanelPuppet
private rightPanelPuppet?: PanelPuppet private rightPanelPuppet?: PanelPuppet
private unregisterComponent: any private unregisterComponent: any
@@ -150,9 +157,23 @@ class EditorCtrl extends PureCtrl {
return this.state as EditorState; return this.state as EditorState;
} }
get note() {
return this.editor.note;
}
$onInit() { $onInit() {
super.$onInit(); super.$onInit();
this.registerKeyboardShortcuts(); 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;
this.reloadTagsString();
}
})
} }
/** @override */ /** @override */
@@ -168,6 +189,10 @@ class EditorCtrl extends PureCtrl {
}; };
} }
async setEditorState(state: Partial<EditorState>) {
return this.setState(state);
}
async onAppLaunch() { async onAppLaunch() {
await super.onAppLaunch(); await super.onAppLaunch();
this.streamItems(); this.streamItems();
@@ -176,29 +201,21 @@ class EditorCtrl extends PureCtrl {
/** @override */ /** @override */
onAppStateEvent(eventName: AppStateEvent, data: any) { onAppStateEvent(eventName: AppStateEvent, data: any) {
if (eventName === AppStateEvent.NoteChanged) { if (eventName === AppStateEvent.PreferencesChanged) {
this.handleNoteSelectionChange(
this.application.getAppState().getSelectedNote()!,
data.previousNote
);
} else if (eventName === AppStateEvent.PreferencesChanged) {
this.reloadPreferences(); this.reloadPreferences();
} }
} }
/** @override */ /** @override */
onAppEvent(eventName: ApplicationEvent) { onAppEvent(eventName: ApplicationEvent) {
if (!this.getState().note) {
return;
}
if (eventName === ApplicationEvent.HighLatencySync) { if (eventName === ApplicationEvent.HighLatencySync) {
this.setState({ syncTakingTooLong: true }); this.setEditorState({ syncTakingTooLong: true });
} else if (eventName === ApplicationEvent.CompletedSync) { } else if (eventName === ApplicationEvent.CompletedSync) {
this.setState({ syncTakingTooLong: false }); this.setEditorState({ syncTakingTooLong: false });
if (this.getState().note.dirty) { if (this.note.dirty) {
/** if we're still dirty, don't change status, a sync is likely upcoming. */ /** if we're still dirty, don't change status, a sync is likely upcoming. */
} else { } else {
const saved = this.getState().note.lastSyncEnd! > this.getState().note.lastSyncBegan!; const saved = this.note.lastSyncEnd! > this.note.lastSyncBegan!;
const isInErrorState = this.getState().saveError; const isInErrorState = this.getState().saveError;
if (isInErrorState || saved) { if (isInErrorState || saved) {
this.showAllChangesSavedStatus(); this.showAllChangesSavedStatus();
@@ -210,7 +227,7 @@ class EditorCtrl extends PureCtrl {
* Otherwise, it means the originating sync came from somewhere else * Otherwise, it means the originating sync came from somewhere else
* and we don't want to display an error here. * and we don't want to display an error here.
*/ */
if (this.getState().note.dirty) { if (this.note.dirty) {
this.showErrorStatus(); this.showErrorStatus();
} }
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) { } else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
@@ -221,6 +238,50 @@ class EditorCtrl extends PureCtrl {
} }
} }
async handleEditorNoteChange() {
const note = this.editor.note;
await this.setEditorState({
showExtensions: false,
showOptionsMenu: false,
altKeyDown: false,
noteStatus: undefined
});
this.editorValues.title = note.title;
this.editorValues.text = note.text;
if (!note) {
this.setEditorState({
noteReady: false
});
return;
}
const associatedEditor = this.editorForNote(note);
if (associatedEditor && associatedEditor !== this.getState().editorComponent) {
/**
* Setting note to not ready will remove the editor from view in a flash,
* so we only want to do this if switching between external editors
*/
this.setEditorState({
noteReady: false,
editorComponent: associatedEditor
});
} else if (!associatedEditor) {
/** No editor */
this.setEditorState({
editorComponent: undefined
});
}
await this.setEditorState({
noteReady: true,
});
this.reloadTagsString();
this.reloadPreferences();
if (note.safeText().length === 0) {
this.focusTitle();
}
this.reloadComponentContext();
}
/** /**
* Because note.locked accesses note.content.appData, * Because note.locked accesses note.content.appData,
* we do not want to expose the template to direct access to note.locked, * we do not want to expose the template to direct access to note.locked,
@@ -230,57 +291,24 @@ class EditorCtrl extends PureCtrl {
* on a deleted note. * on a deleted note.
*/ */
get noteLocked() { get noteLocked() {
if (!this.getState().note || this.getState().note.deleted) { if (!this.note || this.note.deleted) {
return false; return false;
} }
return this.getState().note.locked; return this.note.locked;
} }
streamItems() { streamItems() {
this.application.streamItems(
ContentType.Note,
async (items, source) => {
const currentNote = this.getState().note;
if (!currentNote) {
return;
}
const matchingNote = items.find((item) => {
return item.uuid === currentNote.uuid;
}) as SNNote;
if (!matchingNote) {
return;
}
if (matchingNote?.deleted) {
await this.setState({
note: undefined,
noteReady: false
});
return;
} else {
await this.setState({
note: matchingNote
});
}
if (!isPayloadSourceRetrieved(source!)) {
return;
}
this.editorValues.title = matchingNote.title;
this.editorValues.text = matchingNote.text;
this.reloadTagsString();
}
);
this.application.streamItems( this.application.streamItems(
ContentType.Tag, ContentType.Tag,
(items) => { (items) => {
if (!this.getState().note) { if (!this.note) {
return; return;
} }
for (const tag of items) { for (const tag of items) {
if ( if (
!this.editorValues.tagsInputValue || !this.editorValues.tagsInputValue ||
tag.deleted || tag.deleted ||
tag.hasRelationshipWithItem(this.getState().note) tag.hasRelationshipWithItem(this.note)
) { ) {
this.reloadTagsString(); this.reloadTagsString();
break; break;
@@ -293,7 +321,7 @@ class EditorCtrl extends PureCtrl {
ContentType.Component, ContentType.Component,
async (items) => { async (items) => {
const components = items as SNComponent[]; const components = items as SNComponent[];
if (!this.getState().note) { if (!this.note) {
return; return;
} }
/** Reload componentStack in case new ones were added or removed */ /** Reload componentStack in case new ones were added or removed */
@@ -306,9 +334,9 @@ class EditorCtrl extends PureCtrl {
return; return;
} }
/** Find the most recent editor for note */ /** Find the most recent editor for note */
const editor = this.editorForNote(this.getState().note); const editor = this.editorForNote(this.note);
this.setState({ this.setEditorState({
selectedEditor: editor editorComponent: editor
}); });
if (!editor) { if (!editor) {
this.reloadFont(); this.reloadFont();
@@ -317,62 +345,12 @@ class EditorCtrl extends PureCtrl {
); );
} }
async handleNoteSelectionChange(note: SNNote, previousNote?: SNNote) {
await this.setState({
note: note,
showExtensions: false,
showOptionsMenu: false,
altKeyDown: false,
noteStatus: null
});
this.editorValues.title = note.title;
this.editorValues.text = note.text;
if (!note) {
this.setState({
noteReady: false
});
return;
}
const associatedEditor = this.editorForNote(note);
if (associatedEditor && associatedEditor !== this.getState().selectedEditor) {
/**
* Setting note to not ready will remove the editor from view in a flash,
* so we only want to do this if switching between external editors
*/
this.setState({
noteReady: false,
selectedEditor: associatedEditor
});
} else if (!associatedEditor) {
/** No editor */
this.setState({
selectedEditor: null
});
}
await this.setState({
noteReady: true,
});
this.reloadTagsString();
this.reloadPreferences();
if (note.dummy) {
this.focusTitle();
}
if (previousNote && previousNote !== note) {
if (previousNote.dummy) {
this.performNoteDeletion(previousNote);
}
}
this.reloadComponentContext();
}
editorForNote(note: SNNote) { editorForNote(note: SNNote) {
return this.application.componentManager!.editorForNote(note); return this.application.componentManager!.editorForNote(note);
} }
setMenuState(menu: string, state: boolean) { setMenuState(menu: string, state: boolean) {
this.setState({ this.setEditorState({
[menu]: state [menu]: state
}); });
this.closeAllMenus(menu); this.closeAllMenus(menu);
@@ -395,7 +373,7 @@ class EditorCtrl extends PureCtrl {
menuState[candidate] = false; menuState[candidate] = false;
} }
} }
this.setState(menuState); this.setEditorState(menuState);
} }
editorMenuOnSelect(component: SNComponent) { editorMenuOnSelect(component: SNComponent) {
@@ -403,10 +381,10 @@ class EditorCtrl extends PureCtrl {
/** If plain editor or other editor */ /** If plain editor or other editor */
this.setMenuState('showEditorMenu', false); this.setMenuState('showEditorMenu', false);
const editor = component; const editor = component;
if (this.getState().selectedEditor && editor !== this.getState().selectedEditor) { if (this.getState().editorComponent && editor !== this.getState().editorComponent) {
this.disassociateComponentWithCurrentNote(this.getState().selectedEditor!); this.disassociateComponentWithCurrentNote(this.getState().editorComponent!);
} }
const note = this.getState().note; const note = this.note;
if (editor) { if (editor) {
const prefersPlain = note.prefersPlainEditor; const prefersPlain = note.prefersPlainEditor;
if (prefersPlain) { if (prefersPlain) {
@@ -427,8 +405,8 @@ class EditorCtrl extends PureCtrl {
this.reloadFont(); this.reloadFont();
} }
this.setState({ this.setEditorState({
selectedEditor: editor editorComponent: editor
}); });
} else if (component.area === 'editor-stack') { } else if (component.area === 'editor-stack') {
this.toggleStackComponentForCurrentItem(component); this.toggleStackComponentForCurrentItem(component);
@@ -440,7 +418,7 @@ class EditorCtrl extends PureCtrl {
hasAvailableExtensions() { hasAvailableExtensions() {
return this.application.actionsManager!. return this.application.actionsManager!.
extensionsInContextOfItem(this.getState().note).length > 0; extensionsInContextOfItem(this.note).length > 0;
} }
performFirefoxPinnedTabFix() { performFirefoxPinnedTabFix() {
@@ -454,14 +432,14 @@ class EditorCtrl extends PureCtrl {
} }
} }
saveNote( async saveNote(
bypassDebouncer = false, bypassDebouncer = false,
isUserModified = false, isUserModified = false,
dontUpdatePreviews = false, dontUpdatePreviews = false,
customMutate?: (mutator: NoteMutator) => void customMutate?: (mutator: NoteMutator) => void
) { ) {
this.performFirefoxPinnedTabFix(); this.performFirefoxPinnedTabFix();
const note = this.getState().note; const note = this.note;
if (note.deleted) { if (note.deleted) {
this.application.alertService!.alert( this.application.alertService!.alert(
@@ -469,15 +447,16 @@ class EditorCtrl extends PureCtrl {
); );
return; return;
} }
if (this.editor.isTemplateNote) {
await this.editor.insertTemplatedNote();
}
if (!this.application.findItem(note.uuid)) { if (!this.application.findItem(note.uuid)) {
this.application.alertService!.alert( this.application.alertService!.alert(
STRING_INVALID_NOTE STRING_INVALID_NOTE
); );
return; return;
} }
this.showSavingStatus(); this.showSavingStatus();
this.application.changeItem(note.uuid, (mutator) => { this.application.changeItem(note.uuid, (mutator) => {
const noteMutator = mutator as NoteMutator; const noteMutator = mutator as NoteMutator;
if (customMutate) { if (customMutate) {
@@ -516,7 +495,7 @@ class EditorCtrl extends PureCtrl {
} }
showAllChangesSavedStatus() { showAllChangesSavedStatus() {
this.setState({ this.setEditorState({
saveError: false, saveError: false,
syncTakingTooLong: false syncTakingTooLong: false
}); });
@@ -532,7 +511,7 @@ class EditorCtrl extends PureCtrl {
desc: "Changes saved offline" desc: "Changes saved offline"
}; };
} }
this.setState({ this.setEditorState({
saveError: true, saveError: true,
syncTakingTooLong: false syncTakingTooLong: false
}); });
@@ -541,11 +520,11 @@ class EditorCtrl extends PureCtrl {
setStatus(status: { message: string, date?: Date }, wait = true) { setStatus(status: { message: string, date?: Date }, wait = true) {
let waitForMs; let waitForMs;
if (!this.getState().noteStatus || !this.getState().noteStatus!.date) { if (!this.state.noteStatus || !this.state.noteStatus!.date) {
waitForMs = 0; waitForMs = 0;
} else { } else {
waitForMs = MINIMUM_STATUS_DURATION - ( waitForMs = MINIMUM_STATUS_DURATION - (
new Date().getTime() - this.getState().noteStatus!.date!.getTime() new Date().getTime() - this.state.noteStatus!.date!.getTime()
); );
} }
if (!wait || waitForMs < 0) { if (!wait || waitForMs < 0) {
@@ -556,7 +535,7 @@ class EditorCtrl extends PureCtrl {
} }
this.statusTimeout = this.$timeout(() => { this.statusTimeout = this.$timeout(() => {
status.date = new Date(); status.date = new Date();
this.setState({ this.setEditorState({
noteStatus: status noteStatus: status
}); });
}, waitForMs); }, waitForMs);
@@ -619,21 +598,21 @@ class EditorCtrl extends PureCtrl {
} }
async deleteNote(permanently: boolean) { async deleteNote(permanently: boolean) {
if (this.getState().note.dummy) { if (this.editor.isTemplateNote) {
this.application.alertService!.alert( this.application.alertService!.alert(
STRING_DELETE_PLACEHOLDER_ATTEMPT STRING_DELETE_PLACEHOLDER_ATTEMPT
); );
return; return;
} }
const run = () => { const run = () => {
if (this.getState().note.locked) { if (this.note.locked) {
this.application.alertService!.alert( this.application.alertService!.alert(
STRING_DELETE_LOCKED_ATTEMPT STRING_DELETE_LOCKED_ATTEMPT
); );
return; return;
} }
const title = this.getState().note.safeTitle().length const title = this.note.safeTitle().length
? `'${this.getState().note.title}'` ? `'${this.note.title}'`
: "this note"; : "this note";
const text = StringDeleteNote( const text = StringDeleteNote(
title, title,
@@ -646,7 +625,7 @@ class EditorCtrl extends PureCtrl {
undefined, undefined,
() => { () => {
if (permanently) { if (permanently) {
this.performNoteDeletion(this.getState().note); this.performNoteDeletion(this.note);
} else { } else {
this.saveNote( this.saveNote(
true, true,
@@ -657,8 +636,7 @@ class EditorCtrl extends PureCtrl {
} }
); );
} }
this.application.getAppState().setSelectedNote(undefined); this.appState.closeEditor(this.editor);
this.setMenuState('showOptionsMenu', false);
}, },
undefined, undefined,
true, true,
@@ -681,16 +659,6 @@ class EditorCtrl extends PureCtrl {
performNoteDeletion(note: SNNote) { performNoteDeletion(note: SNNote) {
this.application.deleteItem(note); this.application.deleteItem(note);
if (note === this.getState().note) {
this.setState({
note: null
});
}
if (note.dummy) {
this.application.deleteItemLocally(note);
return;
}
this.application.sync();
} }
restoreTrashedNote() { restoreTrashedNote() {
@@ -702,7 +670,7 @@ class EditorCtrl extends PureCtrl {
mutator.trashed = false; mutator.trashed = false;
} }
); );
this.application.getAppState().setSelectedNote(undefined); this.appState.closeEditor(this.editor);
} }
deleteNotePermanantely() { deleteNotePermanantely() {
@@ -735,7 +703,7 @@ class EditorCtrl extends PureCtrl {
false, false,
true, true,
(mutator) => { (mutator) => {
mutator.pinned = !this.getState().note.pinned mutator.pinned = !this.note.pinned
} }
); );
} }
@@ -746,7 +714,7 @@ class EditorCtrl extends PureCtrl {
false, false,
true, true,
(mutator) => { (mutator) => {
mutator.locked = !this.getState().note.locked mutator.locked = !this.note.locked
} }
); );
} }
@@ -757,7 +725,7 @@ class EditorCtrl extends PureCtrl {
false, false,
true, true,
(mutator) => { (mutator) => {
mutator.protected = !this.getState().note.protected mutator.protected = !this.note.protected
} }
); );
/** Show privileges manager if protection is not yet set up */ /** Show privileges manager if protection is not yet set up */
@@ -776,7 +744,7 @@ class EditorCtrl extends PureCtrl {
false, false,
true, true,
(mutator) => { (mutator) => {
mutator.hidePreview = !this.getState().note.hidePreview mutator.hidePreview = !this.note.hidePreview
} }
); );
} }
@@ -787,21 +755,21 @@ class EditorCtrl extends PureCtrl {
false, false,
true, true,
(mutator) => { (mutator) => {
mutator.archived = !this.getState().note.archived mutator.archived = !this.note.archived
} }
); );
} }
reloadTagsString() { reloadTagsString() {
const tags = this.appState.getNoteTags(this.getState().note); const tags = this.appState.getNoteTags(this.note);
const string = SNTag.arrayToDisplayString(tags); const string = SNTag.arrayToDisplayString(tags);
this.updateUI(() => { this.updateUI(() => {
this.editorValues.tagsInputValue = string; this.editorValues.tagsInputValue = string;
}) })
} }
addTag(tag: SNTag) { private addTag(tag: SNTag) {
const tags = this.appState.getNoteTags(this.getState().note); const tags = this.appState.getNoteTags(this.note);
const strings = tags.map((currentTag) => { const strings = tags.map((currentTag) => {
return currentTag.title; return currentTag.title;
}); });
@@ -810,7 +778,7 @@ class EditorCtrl extends PureCtrl {
} }
removeTag(tag: SNTag) { removeTag(tag: SNTag) {
const tags = this.appState.getNoteTags(this.getState().note); const tags = this.appState.getNoteTags(this.note);
const strings = tags.map((currentTag) => { const strings = tags.map((currentTag) => {
return currentTag.title; return currentTag.title;
}).filter((title) => { }).filter((title) => {
@@ -819,7 +787,7 @@ class EditorCtrl extends PureCtrl {
this.saveTagsFromStrings(strings); this.saveTagsFromStrings(strings);
} }
async saveTagsFromStrings(strings?: string[]) { public async saveTagsFromStrings(strings?: string[]) {
if ( if (
!strings !strings
&& this.editorValues.tagsInputValue === this.getState().tagsAsStrings && this.editorValues.tagsInputValue === this.getState().tagsAsStrings
@@ -836,10 +804,8 @@ class EditorCtrl extends PureCtrl {
return string.trim(); return string.trim();
}); });
} }
const note = this.note;
const note = this.getState().note;
const currentTags = this.appState.getNoteTags(note); const currentTags = this.appState.getNoteTags(note);
const removeTags = []; const removeTags = [];
for (const tag of currentTags) { for (const tag of currentTags) {
if (strings.indexOf(tag.title) === -1) { if (strings.indexOf(tag.title) === -1) {
@@ -907,7 +873,7 @@ class EditorCtrl extends PureCtrl {
WebPrefKey.EditorResizersEnabled, WebPrefKey.EditorResizersEnabled,
true true
); );
this.setState({ this.setEditorState({
monospaceEnabled, monospaceEnabled,
spellcheck, spellcheck,
marginResizersEnabled marginResizersEnabled
@@ -973,10 +939,10 @@ class EditorCtrl extends PureCtrl {
if (key === WebPrefKey.EditorSpellcheck) { if (key === WebPrefKey.EditorSpellcheck) {
/** Allows textarea to reload */ /** Allows textarea to reload */
await this.setState({ await this.setEditorState({
noteReady: false noteReady: false
}); });
this.setState({ this.setEditorState({
noteReady: true noteReady: true
}); });
this.reloadFont(); this.reloadFont();
@@ -1000,42 +966,42 @@ class EditorCtrl extends PureCtrl {
], ],
activationHandler: (component) => { activationHandler: (component) => {
if (component.area === 'note-tags') { if (component.area === 'note-tags') {
this.setState({ this.setEditorState({
tagsComponent: component.active ? component : null tagsComponent: component.active ? component : undefined
}); });
} else if (component.area === 'editor-editor') { } else if (component.area === 'editor-editor') {
if ( if (
component === this.getState().selectedEditor && component === this.getState().editorComponent &&
!component.active !component.active
) { ) {
this.setState({ selectedEditor: null }); this.setEditorState({ editorComponent: undefined });
} }
else if (this.getState().selectedEditor) { else if (this.getState().editorComponent) {
if (this.getState().selectedEditor!.active && this.getState().note) { if (this.getState().editorComponent!.active && this.note) {
if ( if (
component.isExplicitlyEnabledForItem(this.getState().note) component.isExplicitlyEnabledForItem(this.note)
&& !this.getState().selectedEditor!.isExplicitlyEnabledForItem(this.getState().note) && !this.getState().editorComponent!.isExplicitlyEnabledForItem(this.note)
) { ) {
this.setState({ selectedEditor: component }); this.setEditorState({ editorComponent: component });
} }
} }
} }
else if (this.getState().note) { else if (this.note) {
const enableable = ( const enableable = (
component.isExplicitlyEnabledForItem(this.getState().note) component.isExplicitlyEnabledForItem(this.note)
|| component.isDefaultEditor() || component.isDefaultEditor()
); );
if ( if (
component.active component.active
&& enableable && enableable
) { ) {
this.setState({ selectedEditor: component }); this.setEditorState({ editorComponent: component });
} else { } else {
/** /**
* Not a candidate, and no qualified editor. * Not a candidate, and no qualified editor.
* Disable the current editor. * Disable the current editor.
*/ */
this.setState({ selectedEditor: null }); this.setEditorState({ editorComponent: undefined });
} }
} }
@@ -1045,11 +1011,11 @@ class EditorCtrl extends PureCtrl {
}, },
contextRequestHandler: (component) => { contextRequestHandler: (component) => {
if ( if (
component === this.getState().selectedEditor || component === this.getState().editorComponent ||
component === this.getState().tagsComponent || component === this.getState().tagsComponent ||
this.getState().componentStack!.includes(component) this.getState().componentStack!.includes(component)
) { ) {
return this.getState().note; return this.note;
} }
}, },
focusHandler: (component, focused) => { focusHandler: (component, focused) => {
@@ -1093,7 +1059,7 @@ class EditorCtrl extends PureCtrl {
else if (action === ComponentAction.SaveItems) { else if (action === ComponentAction.SaveItems) {
const includesNote = data.items.map((item: RawPayload) => { const includesNote = data.items.map((item: RawPayload) => {
return item.uuid; return item.uuid;
}).includes(this.getState().note.uuid); }).includes(this.note.uuid);
if (includesNote) { if (includesNote) {
this.showSavingStatus(); this.showSavingStatus();
} }
@@ -1109,19 +1075,19 @@ class EditorCtrl extends PureCtrl {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
}); });
this.setState({ this.setEditorState({
componentStack: components componentStack: components
}); });
} }
reloadComponentContext() { reloadComponentContext() {
this.reloadComponentStackArray(); this.reloadComponentStackArray();
if (this.getState().note) { if (this.note) {
for (const component of this.getState().componentStack!) { for (const component of this.getState().componentStack!) {
if (component.active) { if (component.active) {
this.application.componentManager!.setComponentHidden( this.application.componentManager!.setComponentHidden(
component, component,
!component.isExplicitlyEnabledForItem(this.getState().note) !component.isExplicitlyEnabledForItem(this.note)
); );
} }
} }
@@ -1148,7 +1114,7 @@ class EditorCtrl extends PureCtrl {
} }
disassociateComponentWithCurrentNote(component: SNComponent) { disassociateComponentWithCurrentNote(component: SNComponent) {
const note = this.getState().note; const note = this.note;
this.application.changeAndSaveItem(component.uuid, (m) => { this.application.changeAndSaveItem(component.uuid, (m) => {
const mutator = m as ComponentMutator; const mutator = m as ComponentMutator;
mutator.removeAssociatedItemId(note.uuid); mutator.removeAssociatedItemId(note.uuid);
@@ -1157,7 +1123,7 @@ class EditorCtrl extends PureCtrl {
} }
associateComponentWithCurrentNote(component: SNComponent) { associateComponentWithCurrentNote(component: SNComponent) {
const note = this.getState().note; const note = this.note;
this.application.changeAndSaveItem(component.uuid, (m) => { this.application.changeAndSaveItem(component.uuid, (m) => {
const mutator = m as ComponentMutator; const mutator = m as ComponentMutator;
mutator.removeDisassociatedItemId(note.uuid); mutator.removeDisassociatedItemId(note.uuid);
@@ -1171,12 +1137,12 @@ class EditorCtrl extends PureCtrl {
KeyboardModifier.Alt KeyboardModifier.Alt
], ],
onKeyDown: () => { onKeyDown: () => {
this.setState({ this.setEditorState({
altKeyDown: true altKeyDown: true
}); });
}, },
onKeyUp: () => { onKeyUp: () => {
this.setState({ this.setEditorState({
altKeyDown: false altKeyDown: false
}); });
} }
@@ -1223,7 +1189,7 @@ class EditorCtrl extends PureCtrl {
element: editor, element: editor,
key: KeyboardKey.Tab, key: KeyboardKey.Tab,
onKeyDown: (event) => { onKeyDown: (event) => {
if (this.getState().note.locked || event.shiftKey) { if (this.note.locked || event.shiftKey) {
return; return;
} }
event.preventDefault(); event.preventDefault();
@@ -1260,16 +1226,17 @@ class EditorCtrl extends PureCtrl {
} }
} }
export class EditorPanel extends WebDirective { export class EditorView extends WebDirective {
constructor() { constructor() {
super(); super();
this.restrict = 'E'; this.restrict = 'E';
this.scope = { this.scope = {
editor: '=',
application: '=' application: '='
}; };
this.template = template; this.template = template;
this.replace = true; this.replace = true;
this.controller = EditorCtrl; this.controller = EditorViewCtrl;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = true; this.bindToController = true;
} }

View File

@@ -0,0 +1,5 @@
editor-view(
ng-repeat='editor in self.editors'
application='self.application'
editor='editor'
)

View File

@@ -0,0 +1,35 @@
import { WebApplication } from '@/ui_models/application';
import { WebDirective } from './../../types';
import template from './editor-group-view.pug';
import { Editor } from '@/ui_models/editor';
class EditorGroupViewCtrl {
private application!: WebApplication
public editors: Editor[] = []
/* @ngInject */
constructor() {
}
$onInit() {
this.application.editorGroup.addChangeObserver(() => {
this.editors = this.application.editorGroup.editors;
})
}
}
export class EditorGroupView extends WebDirective {
constructor() {
super();
this.template = template;
this.controller = EditorGroupViewCtrl;
this.replace = true;
this.controllerAs = 'self';
this.bindToController = true;
this.scope = {
application: '='
};
}
}

View File

@@ -1,4 +1,4 @@
import { FooterStatus, WebDirective } from './../types'; import { FooterStatus, WebDirective } from '@/types';
import { dateToLocalizedString } from '@/utils'; import { dateToLocalizedString } from '@/utils';
import { import {
ApplicationEvent, ApplicationEvent,
@@ -10,13 +10,13 @@ import {
ComponentArea, ComponentArea,
ComponentAction ComponentAction
} from 'snjs'; } from 'snjs';
import template from '%/footer.pug'; import template from './footer-view.pug';
import { AppStateEvent, EventSource } from '@/services/state'; import { AppStateEvent, EventSource } from '@/services/state';
import { import {
STRING_GENERIC_SYNC_ERROR, STRING_GENERIC_SYNC_ERROR,
STRING_NEW_UPDATE_READY STRING_NEW_UPDATE_READY
} from '@/strings'; } from '@/strings';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models'; import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
type DockShortcut = { type DockShortcut = {
@@ -29,7 +29,7 @@ type DockShortcut = {
} }
} }
class FooterCtrl extends PureCtrl { class FooterViewCtrl extends PureViewCtrl {
private $rootScope: ng.IRootScopeService private $rootScope: ng.IRootScopeService
private rooms: SNComponent[] = [] private rooms: SNComponent[] = []
@@ -427,15 +427,16 @@ class FooterCtrl extends PureCtrl {
} }
} }
export class Footer extends WebDirective { export class FooterView extends WebDirective {
constructor() { constructor() {
super(); super();
this.restrict = 'E'; this.restrict = 'E';
this.template = template; this.template = template;
this.controller = FooterCtrl; this.controller = FooterViewCtrl;
this.replace = true; this.replace = true;
this.controllerAs = 'ctrl'; this.controllerAs = 'ctrl';
this.bindToController = { this.bindToController = true;
this.scope = {
application: '=' application: '='
}; };
} }

View File

@@ -0,0 +1,11 @@
export { PureViewCtrl } from './abstract/pure_view_ctrl';
export { ApplicationGroupView } from './application_group/application_group_view';
export { ApplicationView } from './application/application_view';
export { EditorGroupView } from './editor_group/editor_group_view';
export { EditorView } from './editor/editor_view';
export { FooterView } from './footer/footer_view';
export { NotesView } from './notes/notes_view';
export { TagsView } from './tags/tags_view';

View File

@@ -111,10 +111,8 @@ export function sortNotes(
notes: SNNote[] = [], notes: SNNote[] = [],
sortBy: string, sortBy: string,
reverse: boolean reverse: boolean
) { ) {
const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => { const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => {
if (a.dummy) { return -1; }
if (b.dummy) { return 1; }
if (!pinCheck) { if (!pinCheck) {
if (a.pinned && b.pinned) { if (a.pinned && b.pinned) {
return sortValueFn(a, b, true); return sortValueFn(a, b, true);
@@ -123,7 +121,7 @@ export function sortNotes(
if (b.pinned) { return 1; } if (b.pinned) { return 1; }
} }
let aValue = (a as any)[sortBy] || ''; let aValue = (a as any)[sortBy] || '';
let bValue = (a as any)[sortBy] || ''; let bValue = (b as any)[sortBy] || '';
let vector = 1; let vector = 1;
if (reverse) { if (reverse) {
vector *= -1; vector *= -1;

View File

@@ -109,8 +109,8 @@
) )
.note( .note(
ng-repeat='note in self.state.renderedNotes track by note.uuid' ng-repeat='note in self.state.renderedNotes track by note.uuid'
ng-class="{'selected' : self.selectedNote == note}" ng-class="{'selected' : self.activeEditorNote == note}"
ng-click='self.selectNote(note, true)' ng-click='self.selectNote(note)'
) )
.note-flags(ng-show='self.noteFlags[note.uuid].length > 0') .note-flags(ng-show='self.noteFlags[note.uuid].length > 0')
.flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]') .flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]')
@@ -138,6 +138,7 @@
| Modified {{note.updatedAtString || 'Now'}} | Modified {{note.updatedAtString || 'Now'}}
span(ng-show="self.state.sortBy != 'client_updated_at'") span(ng-show="self.state.sortBy != 'client_updated_at'")
| {{note.createdAtString || 'Now'}} | {{note.createdAtString || 'Now'}}
panel-resizer( panel-resizer(
collapsable="true" collapsable="true"
control="self.panelPuppet" control="self.panelPuppet"

View File

@@ -1,13 +1,20 @@
import { PanelPuppet, WebDirective } from './../../types'; import { PanelPuppet, WebDirective } from './../../types';
import angular from 'angular'; import angular from 'angular';
import template from '%/notes.pug'; import template from './notes-view.pug';
import { ApplicationEvent, ContentType, removeFromArray, SNNote, SNTag, WebPrefKey } from 'snjs'; import {
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; ApplicationEvent,
ContentType,
removeFromArray,
SNNote,
SNTag,
WebPrefKey
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import { import {
PANEL_NAME_NOTES PANEL_NAME_NOTES
} from '@/controllers/constants'; } from '@/views/constants';
import { import {
NoteSortKey, NoteSortKey,
filterAndSortNotes filterAndSortNotes
@@ -15,6 +22,7 @@ import {
import { UuidString } from '@/../../../../snjs/dist/@types/types'; import { UuidString } from '@/../../../../snjs/dist/@types/types';
type NotesState = { type NotesState = {
panelTitle: string
tag?: SNTag tag?: SNTag
notes?: SNNote[] notes?: SNNote[]
renderedNotes?: SNNote[] renderedNotes?: SNNote[]
@@ -43,7 +51,7 @@ const DEFAULT_LIST_NUM_NOTES = 20;
const ELEMENT_ID_SEARCH_BAR = 'search-bar'; const ELEMENT_ID_SEARCH_BAR = 'search-bar';
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable'; const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
class NotesCtrl extends PureCtrl { class NotesViewCtrl extends PureViewCtrl {
private panelPuppet?: PanelPuppet private panelPuppet?: PanelPuppet
private reloadNotesPromise?: any private reloadNotesPromise?: any
@@ -101,13 +109,17 @@ class NotesCtrl extends PureCtrl {
return this.state as NotesState; return this.state as NotesState;
} }
async setNotesState(state: Partial<NotesState>) {
return this.setState(state);
}
getInitialState() { getInitialState() {
return { return {
notes: [], notes: [],
renderedNotes: [], renderedNotes: [],
mutable: { showMenu: false }, mutable: { showMenu: false },
noteFilter: { text: '' }, noteFilter: { text: '' },
} as NotesState; } as Partial<NotesState>;
} }
async onAppLaunch() { async onAppLaunch() {
@@ -120,13 +132,10 @@ class NotesCtrl extends PureCtrl {
onAppStateEvent(eventName: AppStateEvent, data?: any) { onAppStateEvent(eventName: AppStateEvent, data?: any) {
if (eventName === AppStateEvent.TagChanged) { if (eventName === AppStateEvent.TagChanged) {
this.handleTagChange( this.handleTagChange(
this.application!.getAppState().getSelectedTag()!, this.application!.getAppState().getSelectedTag()!
data.previousTag
);
} else if (eventName === AppStateEvent.NoteChanged) {
this.handleNoteSelection(
this.application!.getAppState().getSelectedNote()!
); );
} else if (eventName === AppStateEvent.ActiveEditorChanged) {
this.handleEditorChange();
} else if (eventName === AppStateEvent.PreferencesChanged) { } else if (eventName === AppStateEvent.PreferencesChanged) {
this.reloadPreferences(); this.reloadPreferences();
this.reloadNotes(); this.reloadNotes();
@@ -135,19 +144,19 @@ class NotesCtrl extends PureCtrl {
} }
} }
get selectedNote() { get activeEditorNote() {
return this.appState.getSelectedNote(); const activeEditor = this.appState.getActiveEditor();
return activeEditor && activeEditor.note;
}
public get editorNotes() {
return this.appState.getEditors().map((editor) => editor.note);
} }
/** @override */ /** @override */
async onAppEvent(eventName: ApplicationEvent) { async onAppEvent(eventName: ApplicationEvent) {
if (eventName === ApplicationEvent.SignedIn) { if (eventName === ApplicationEvent.SignedIn) {
/** Delete dummy note if applicable */ this.appState.closeAllEditors();
if (this.selectedNote && this.selectedNote!.dummy) {
this.application!.deleteItemLocally(this.selectedNote!);
await this.selectNote(undefined);
await this.reloadNotes();
}
} else if (eventName === ApplicationEvent.CompletedSync) { } else if (eventName === ApplicationEvent.CompletedSync) {
this.getMostValidNotes().then((notes) => { this.getMostValidNotes().then((notes) => {
if (notes.length === 0) { if (notes.length === 0) {
@@ -196,9 +205,9 @@ class NotesCtrl extends PureCtrl {
[ContentType.Note, ContentType.Tag], [ContentType.Note, ContentType.Tag],
async (items) => { async (items) => {
await this.reloadNotes(); await this.reloadNotes();
const selectedNote = this.selectedNote; const activeNote = this.activeEditorNote;
if (selectedNote) { if (activeNote) {
const discarded = selectedNote.deleted || selectedNote.trashed; const discarded = activeNote.deleted || activeNote.trashed;
if (discarded) { if (discarded) {
this.selectNextOrCreateNew(); this.selectNextOrCreateNew();
} }
@@ -218,51 +227,20 @@ class NotesCtrl extends PureCtrl {
); );
} }
async selectNote(note?: SNNote) { async selectNote(note: SNNote) {
return this.application!.getAppState().setSelectedNote(note); this.appState.openEditor(note.uuid);
} }
async createNewNote() { async createNewNote() {
const selectedTag = this.application!.getAppState().getSelectedTag(); let title = `Note ${this.getState().notes!.length + 1}`;
if (!selectedTag) {
throw 'Attempting to create note with no selected tag';
}
let title;
let isDummyNote = true;
if (this.isFiltering()) { if (this.isFiltering()) {
title = this.getState().noteFilter.text; title = this.getState().noteFilter.text;
isDummyNote = false;
} else if (this.selectedNote && this.selectedNote!.dummy) {
return;
} else {
title = `Note ${this.getState().notes!.length + 1}`;
} }
const newNote = await this.application!.createManagedItem( this.appState.createEditor(title);
ContentType.Note,
{
text: '',
title: title,
references: []
},
true,
{
dummy: isDummyNote
}
) as SNNote;
if (!selectedTag.isSmartTag()) {
this.application!.changeItem(selectedTag.uuid, (mutator) => {
mutator.addItemAsRelationship(newNote);
});
}
this.selectNote(newNote);
} }
async handleTagChange(tag: SNTag, previousTag?: SNTag) { async handleTagChange(tag: SNTag) {
if (this.selectedNote && this.selectedNote!.dummy) { await this.setNotesState({ tag });
await this.application!.deleteItemLocally(this.selectedNote!);
await this.selectNote(undefined);
}
await this.setState({ tag: tag });
this.resetScrollPosition(); this.resetScrollPosition();
this.setShowMenuFalse(); this.setShowMenuFalse();
@@ -270,7 +248,8 @@ class NotesCtrl extends PureCtrl {
this.application!.getDesktopService().searchText(); this.application!.getDesktopService().searchText();
this.resetPagination(); this.resetPagination();
/* Capture db load state before beginning reloadNotes, since this status may change during reload */ /* Capture db load state before beginning reloadNotes,
since this status may change during reload */
const dbLoaded = this.application!.isDatabaseLoaded(); const dbLoaded = this.application!.isDatabaseLoaded();
await this.reloadNotes(); await this.reloadNotes();
@@ -280,10 +259,10 @@ class NotesCtrl extends PureCtrl {
if (!tag.isSmartTag() || tag.isAllTag) { if (!tag.isSmartTag() || tag.isAllTag) {
this.createPlaceholderNote(); this.createPlaceholderNote();
} else if ( } else if (
this.selectedNote && this.activeEditorNote &&
!this.getState().notes!.includes(this.selectedNote!) !this.getState().notes!.includes(this.activeEditorNote!)
) { ) {
this.selectNote(undefined); this.appState.closeActiveEditor();
} }
} }
} }
@@ -299,7 +278,7 @@ class NotesCtrl extends PureCtrl {
async removeNoteFromList(note: SNNote) { async removeNoteFromList(note: SNNote) {
const notes = this.getState().notes!; const notes = this.getState().notes!;
removeFromArray(notes, note); removeFromArray(notes, note);
await this.setState({ await this.setNotesState({
notes: notes, notes: notes,
renderedNotes: notes.slice(0, this.notesToDisplay) renderedNotes: notes.slice(0, this.notesToDisplay)
}); });
@@ -330,7 +309,7 @@ class NotesCtrl extends PureCtrl {
this.loadFlagsForNote(note); this.loadFlagsForNote(note);
} }
} }
await this.setState({ await this.setNotesState({
notes: notes, notes: notes,
renderedNotes: notes.slice(0, this.notesToDisplay) renderedNotes: notes.slice(0, this.notesToDisplay)
}); });
@@ -338,7 +317,7 @@ class NotesCtrl extends PureCtrl {
} }
setShowMenuFalse() { setShowMenuFalse() {
this.setState({ this.setNotesState({
mutable: { mutable: {
...this.getState().mutable, ...this.getState().mutable,
showMenu: false showMenu: false
@@ -346,20 +325,10 @@ class NotesCtrl extends PureCtrl {
}); });
} }
async handleNoteSelection(note: SNNote) { async handleEditorChange() {
const previousNote = this.selectedNote; const activeNote = this.appState.getActiveEditor().note;
if (previousNote === note) { if (activeNote && activeNote.conflictOf) {
return; this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => {
}
if (previousNote && previousNote.dummy) {
await this.application!.deleteItemLocally(previousNote);
this.removeNoteFromList(previousNote);
}
if (!note) {
return;
}
if (note.conflictOf) {
this.application!.changeAndSaveItem(note.uuid, (mutator) => {
mutator.conflictOf = undefined; mutator.conflictOf = undefined;
}) })
} }
@@ -404,7 +373,7 @@ class NotesCtrl extends PureCtrl {
WebPrefKey.NotesHideTags, WebPrefKey.NotesHideTags,
false false
); );
this.setState({ this.setNotesState({
...viewOptions ...viewOptions
}); });
if (prevSortValue && prevSortValue !== sortBy) { if (prevSortValue && prevSortValue !== sortBy) {
@@ -469,7 +438,7 @@ class NotesCtrl extends PureCtrl {
} else if (this.getState().tag) { } else if (this.getState().tag) {
title = `${this.getState().tag!.title}`; title = `${this.getState().tag!.title}`;
} }
this.setState({ this.setNotesState({
panelTitle: title panelTitle: title
}); });
} }
@@ -584,7 +553,7 @@ class NotesCtrl extends PureCtrl {
selectNextNote() { selectNextNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.displayableNotes();
const currentIndex = displayableNotes.findIndex((candidate) => { const currentIndex = displayableNotes.findIndex((candidate) => {
return candidate.uuid === this.selectedNote!.uuid return candidate.uuid === this.activeEditorNote!.uuid
}); });
if (currentIndex + 1 < displayableNotes.length) { if (currentIndex + 1 < displayableNotes.length) {
this.selectNote(displayableNotes[currentIndex + 1]); this.selectNote(displayableNotes[currentIndex + 1]);
@@ -598,13 +567,13 @@ class NotesCtrl extends PureCtrl {
} else if (!this.getState().tag || !this.getState().tag!.isSmartTag()) { } else if (!this.getState().tag || !this.getState().tag!.isSmartTag()) {
this.createPlaceholderNote(); this.createPlaceholderNote();
} else { } else {
this.selectNote(undefined); this.appState.closeActiveEditor();
} }
} }
selectPreviousNote() { selectPreviousNote() {
const displayableNotes = this.displayableNotes(); const displayableNotes = this.displayableNotes();
const currentIndex = displayableNotes.indexOf(this.selectedNote!); const currentIndex = displayableNotes.indexOf(this.activeEditorNote!);
if (currentIndex - 1 >= 0) { if (currentIndex - 1 >= 0) {
this.selectNote(displayableNotes[currentIndex - 1]); this.selectNote(displayableNotes[currentIndex - 1]);
return true; return true;
@@ -619,7 +588,7 @@ class NotesCtrl extends PureCtrl {
} }
async setNoteFilterText(text: string) { async setNoteFilterText(text: string) {
await this.setState({ await this.setNotesState({
noteFilter: { noteFilter: {
...this.getState().noteFilter, ...this.getState().noteFilter,
text: text text: text
@@ -748,12 +717,12 @@ class NotesCtrl extends PureCtrl {
} }
} }
export class NotesPanel extends WebDirective { export class NotesView extends WebDirective {
constructor() { constructor() {
super(); super();
this.template = template; this.template = template;
this.replace = true; this.replace = true;
this.controller = NotesCtrl; this.controller = NotesViewCtrl;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = true; this.bindToController = true;
this.scope = { this.scope = {

View File

@@ -1,7 +1,6 @@
import { WebDirective, PanelPuppet } from './../types'; import { WebDirective, PanelPuppet } from '@/types';
import { WebApplication } from './../application'; import { WebApplication } from '@/ui_models/application';
import { import {
SNNote,
SNTag, SNTag,
ContentType, ContentType,
ApplicationEvent, ApplicationEvent,
@@ -11,17 +10,17 @@ import {
SNComponent, SNComponent,
WebPrefKey WebPrefKey
} from 'snjs'; } from 'snjs';
import template from '%/tags.pug'; import template from './tags-view.pug';
import { AppStateEvent } from '@/services/state'; import { AppStateEvent } from '@/services/state';
import { PANEL_NAME_TAGS } from '@/controllers/constants'; import { PANEL_NAME_TAGS } from '@/views/constants';
import { STRING_DELETE_TAG } from '@/strings'; import { STRING_DELETE_TAG } from '@/strings';
import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { UuidString } from '@/../../../../snjs/dist/@types/types'; import { UuidString } from '@/../../../../snjs/dist/@types/types';
import { TagMutator } from '@/../../../../snjs/dist/@types/models/app/tag'; import { TagMutator } from '@/../../../../snjs/dist/@types/models/app/tag';
type NoteCounts = Partial<Record<string, number>> type NoteCounts = Partial<Record<string, number>>
class TagsPanelCtrl extends PureCtrl { class TagsViewCtrl extends PureViewCtrl {
/** Passed through template */ /** Passed through template */
readonly application!: WebApplication readonly application!: WebApplication
@@ -106,6 +105,10 @@ class TagsPanelCtrl extends PureCtrl {
}); });
if (!matchingTag || matchingTag.deleted) { if (!matchingTag || matchingTag.deleted) {
this.selectTag(this.state.smartTags[0]); this.selectTag(this.state.smartTags[0]);
} else {
this.setState({
selectedTag: matchingTag
})
} }
} }
} }
@@ -342,7 +345,7 @@ class TagsPanelCtrl extends PureCtrl {
} }
} }
export class TagsPanel extends WebDirective { export class TagsView extends WebDirective {
constructor() { constructor() {
super(); super();
this.restrict = 'E'; this.restrict = 'E';
@@ -351,7 +354,7 @@ export class TagsPanel extends WebDirective {
}; };
this.template = template; this.template = template;
this.replace = true; this.replace = true;
this.controller = TagsPanelCtrl; this.controller = TagsViewCtrl;
this.controllerAs = 'self'; this.controllerAs = 'self';
this.bindToController = true; this.bindToController = true;
} }

View File

@@ -1,34 +0,0 @@
#lock-screen.sn-component
.sk-panel
.sk-panel-header
.sk-panel-header-title Passcode Required
.sk-panel-content
.sk-panel-section
form.sk-panel-form.sk-panel-row(ng-submit='ctrl.submitPasscodeForm($event)')
.sk-panel-column.stretch
input#passcode-input.center-text.sk-input.contrast(
autocomplete='new-password',
autofocus='true',
ng-model='ctrl.formData.passcode',
placeholder='Enter Passcode',
should-focus='true',
sn-autofocus='true',
type='password'
)
.sk-button-group.stretch.sk-panel-row.form-submit
button.sk-button.info(type='submit')
.sk-label Unlock
.sk-panel-footer
#passcode-reset
a.sk-a.neutral(
ng-click='ctrl.forgotPasscode()',
ng-if='!ctrl.formData.showRecovery'
) Forgot?
div(ng-if='ctrl.formData.showRecovery')
.sk-p
| If you forgot your application passcode, your only option is to clear
| your local data from this device and sign back in to your account.
.sk-panel-row
a.sk-a.danger.center-text(
ng-click='ctrl.beginDeleteData()'
) Delete Local Data

View File

@@ -44,7 +44,7 @@
</head> </head>
<body> <body>
<root /> <application-group-view />
</body> </body>
</html> </html>

View File

@@ -1,5 +1,5 @@
/// <reference types="angular" /> /// <reference types="angular" />
import { WebApplication } from './../../application'; import { WebApplication } from '@/ui_models/application';
import { SNComponent } from 'snjs'; import { SNComponent } from 'snjs';
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
declare type ComponentModalScope = { declare type ComponentModalScope = {

View File

@@ -1,5 +1,5 @@
import { SNAlertService } from 'snjs'; import { SNAlertService } from 'snjs';
export declare class AlertService extends SNAlertService { export declare class AlertService extends SNAlertService {
alert(title: string, text: string, closeButtonText: string | undefined, onClose: () => void): Promise<unknown>; alert(text: string, title: string, closeButtonText: string | undefined, onClose: () => void): Promise<unknown>;
confirm(title: string, text: string, confirmButtonText: string | undefined, cancelButtonText: string | undefined, onConfirm: () => void, onCancel: () => void, destructive?: boolean): Promise<unknown>; confirm(text: string, title: string, confirmButtonText: string | undefined, cancelButtonText: string | undefined, onConfirm: () => void, onCancel: () => void, destructive?: boolean): Promise<unknown>;
} }

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { SNItem } from 'snjs'; import { SNItem } from 'snjs';
export declare class ArchiveManager { export declare class ArchiveManager {
private readonly application; private readonly application;

View File

@@ -1,6 +1,6 @@
/// <reference types="angular" /> /// <reference types="angular" />
import { SNComponent, PurePayload } from 'snjs'; import { SNComponent, PurePayload } from 'snjs';
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { ApplicationService, ApplicationEvent } from 'snjs'; import { ApplicationService, ApplicationEvent } from 'snjs';
declare type UpdateObserverCallback = (component: SNComponent) => void; declare type UpdateObserverCallback = (component: SNComponent) => void;
declare type ComponentActivationCallback = (payload: PurePayload) => void; declare type ComponentActivationCallback = (payload: PurePayload) => void;

View File

@@ -1,4 +1,4 @@
import { WebApplication } from './../application'; import { WebApplication } from '@/ui_models/application';
export declare class LockManager { export declare class LockManager {
private application; private application;
private unsubState; private unsubState;

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { ApplicationService, WebPrefKey } from 'snjs'; import { ApplicationService, WebPrefKey } from 'snjs';
export declare class PreferencesManager extends ApplicationService { export declare class PreferencesManager extends ApplicationService {
private userPreferences; private userPreferences;

View File

@@ -1,9 +1,10 @@
/// <reference types="angular" /> /// <reference types="angular" />
import { WebApplication } from './../application';
import { SNTag, SNNote, SNUserPrefs } from 'snjs'; import { SNTag, SNNote, SNUserPrefs } from 'snjs';
import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor';
export declare enum AppStateEvent { export declare enum AppStateEvent {
TagChanged = 1, TagChanged = 1,
NoteChanged = 2, ActiveEditorChanged = 2,
PreferencesChanged = 3, PreferencesChanged = 3,
PanelResized = 4, PanelResized = 4,
EditorFocused = 5, EditorFocused = 5,
@@ -29,10 +30,22 @@ export declare class AppState {
rootScopeCleanup2: any; rootScopeCleanup2: any;
onVisibilityChange: any; onVisibilityChange: any;
selectedTag?: SNTag; selectedTag?: SNTag;
selectedNote?: SNNote;
userPreferences?: SNUserPrefs; userPreferences?: SNUserPrefs;
multiEditorEnabled: boolean;
constructor($rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, application: WebApplication); constructor($rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService, application: WebApplication);
deinit(): void; deinit(): void;
/**
* Creates a new editor if one doesn't exist. If one does, we'll replace the
* editor's note with an empty one.
*/
createEditor(title?: string): void;
openEditor(noteUuid: string): Promise<unknown>;
getActiveEditor(): Editor;
getEditors(): Editor[];
closeEditor(editor: Editor): void;
closeActiveEditor(): void;
closeAllEditors(): void;
editorForNote(note: SNNote): Editor | undefined;
streamNotesAndTags(): void; streamNotesAndTags(): void;
addAppEventObserver(): void; addAppEventObserver(): void;
isLocked(): boolean; isLocked(): boolean;
@@ -41,13 +54,11 @@ export declare class AppState {
addObserver(callback: ObserverCallback): () => void; addObserver(callback: ObserverCallback): () => void;
notifyEvent(eventName: AppStateEvent, data?: any): Promise<unknown>; notifyEvent(eventName: AppStateEvent, data?: any): Promise<unknown>;
setSelectedTag(tag: SNTag): void; setSelectedTag(tag: SNTag): void;
setSelectedNote(note?: SNNote): Promise<unknown>;
/** Returns the tags that are referncing this note */ /** Returns the tags that are referncing this note */
getNoteTags(note: SNNote): SNTag[]; getNoteTags(note: SNNote): SNTag[];
/** Returns the notes this tag references */ /** Returns the notes this tag references */
getTagNotes(tag: SNTag): SNNote[]; getTagNotes(tag: SNTag): SNNote[];
getSelectedTag(): SNTag | undefined; getSelectedTag(): SNTag | undefined;
getSelectedNote(): SNNote | undefined;
setUserPreferences(preferences: SNUserPrefs): void; setUserPreferences(preferences: SNUserPrefs): void;
panelDidResize(name: string, collapsed: boolean): void; panelDidResize(name: string, collapsed: boolean): void;
editorDidFocus(eventSource: EventSource): void; editorDidFocus(eventSource: EventSource): void;

View File

@@ -1,4 +1,4 @@
import { FooterStatus } from './../types'; import { FooterStatus } from '@/types';
declare type StatusCallback = (string: string) => void; declare type StatusCallback = (string: string) => void;
export declare class StatusManager { export declare class StatusManager {
private statuses; private statuses;

View File

@@ -1,4 +1,4 @@
import { WebApplication } from '@/application'; import { WebApplication } from '@/ui_models/application';
import { ApplicationService } from 'snjs'; import { ApplicationService } from 'snjs';
export declare class ThemeManager extends ApplicationService { export declare class ThemeManager extends ApplicationService {
private activeThemes; private activeThemes;

View File

@@ -0,0 +1,56 @@
/// <reference types="angular" />
import { EditorGroup } from '@/ui_models/editor_group';
import { PasswordWizardType } from '@/types';
import { SNApplication, Challenge, ChallengeOrchestrator, ProtectedAction } from 'snjs';
import { AppState, DesktopManager, LockManager, ArchiveManager, NativeExtManager, StatusManager, ThemeManager, PreferencesManager, KeyboardManager } from '@/services';
declare type WebServices = {
appState: AppState;
desktopService: DesktopManager;
lockService: LockManager;
archiveService: ArchiveManager;
nativeExtService: NativeExtManager;
statusService: StatusManager;
themeService: ThemeManager;
prefsService: PreferencesManager;
keyboardService: KeyboardManager;
};
export declare class WebApplication extends SNApplication {
private $compile?;
private scope?;
private onDeinit?;
private webServices;
private currentAuthenticationElement?;
editorGroup: EditorGroup;
constructor($compile: ng.ICompileService, $timeout: ng.ITimeoutService, scope: ng.IScope, onDeinit: (app: WebApplication) => void);
/** @override */
deinit(): void;
setWebServices(services: WebServices): void;
/** @access public */
getAppState(): AppState;
/** @access public */
getDesktopService(): DesktopManager;
/** @access public */
getLockService(): LockManager;
/** @access public */
getArchiveService(): ArchiveManager;
/** @access public */
getNativeExtService(): NativeExtManager;
/** @access public */
getStatusService(): StatusManager;
/** @access public */
getThemeService(): ThemeManager;
/** @access public */
getPrefsService(): PreferencesManager;
/** @access public */
getKeyboardService(): KeyboardManager;
checkForSecurityUpdate(): Promise<boolean>;
presentPasswordWizard(type: PasswordWizardType): void;
promptForChallenge(challenge: Challenge, orchestrator: ChallengeOrchestrator): void;
performProtocolUpgrade(): Promise<void>;
presentPrivilegesModal(action: ProtectedAction, onSuccess?: any, onCancel?: any): Promise<void>;
presentPrivilegesManagementModal(): void;
authenticationInProgress(): boolean;
presentPasswordModal(callback: () => void): void;
presentRevisionPreviewModal(uuid: string, content: any): void;
}
export {};

View File

@@ -0,0 +1,26 @@
/// <reference types="angular" />
import { WebApplication } from './application';
declare type AppManagerChangeCallback = () => void;
export declare class ApplicationGroup {
$compile: ng.ICompileService;
$rootScope: ng.IRootScopeService;
$timeout: ng.ITimeoutService;
applications: WebApplication[];
changeObservers: AppManagerChangeCallback[];
activeApplication?: WebApplication;
constructor($compile: ng.ICompileService, $rootScope: ng.IRootScopeService, $timeout: ng.ITimeoutService);
private createDefaultApplication;
/** @callback */
onApplicationDeinit(application: WebApplication): void;
private createNewApplication;
get application(): WebApplication | undefined;
getApplications(): WebApplication[];
/**
* Notifies observer when the active application has changed.
* Any application which is no longer active is destroyed, and
* must be removed from the interface.
*/
addApplicationChangeObserver(callback: AppManagerChangeCallback): void;
private notifyObserversOfAppChange;
}
export {};

View File

@@ -0,0 +1,32 @@
import { SNNote, PayloadSource } from 'snjs';
import { WebApplication } from './application';
export declare class Editor {
note: SNNote;
private application;
private _onNoteChange?;
private _onNoteValueChange?;
private removeStreamObserver;
isTemplateNote: boolean;
constructor(application: WebApplication, noteUuid?: string, noteTitle?: string);
private handleNoteStream;
insertTemplatedNote(): Promise<import("../../../../../snjs/dist/@types").SNItem>;
/**
* Reverts the editor to a blank state, removing any existing note from view,
* and creating a placeholder note.
*/
reset(noteTitle?: string): Promise<void>;
deinit(): void;
/**
* Register to be notified when the editor's note changes.
*/
onNoteChange(onNoteChange: () => void): void;
/**
* Register to be notified when the editor's note's values change
* (and thus a new object reference is created)
*/
onNoteValueChange(onNoteValueChange: (note: SNNote, source?: PayloadSource) => void): void;
/**
* Sets the editor contents by setting its note.
*/
setNote(note: SNNote): void;
}

View File

@@ -0,0 +1,22 @@
import { Editor } from './editor';
import { WebApplication } from './application';
declare type EditorGroupChangeCallback = () => void;
export declare class EditorGroup {
editors: Editor[];
private application;
changeObservers: EditorGroupChangeCallback[];
constructor(application: WebApplication);
deinit(): void;
createEditor(noteUuid?: string, noteTitle?: string): void;
deleteEditor(editor: Editor): void;
closeEditor(editor: Editor): void;
closeActiveEditor(): void;
closeAllEditors(): void;
get activeEditor(): Editor;
/**
* Notifies observer when the active editor has changed.
*/
addChangeObserver(callback: EditorGroupChangeCallback): void;
private notifyObservers;
}
export {};

View File

@@ -0,0 +1,36 @@
/// <reference types="angular" />
import { ApplicationEvent } from 'snjs';
import { WebApplication } from '@/ui_models/application';
export declare type CtrlState = Partial<Record<string, any>>;
export declare type CtrlProps = Partial<Record<string, any>>;
export declare class PureViewCtrl {
$timeout: ng.ITimeoutService;
/** Passed through templates */
application?: WebApplication;
props: CtrlProps;
state: CtrlState;
private unsubApp;
private unsubState;
private stateTimeout;
constructor($timeout: ng.ITimeoutService);
$onInit(): void;
deinit(): void;
$onDestroy(): void;
get appState(): import("../../services").AppState;
/** @private */
resetState(): Promise<void>;
/** @override */
getInitialState(): {};
setState(state: CtrlState): Promise<unknown>;
updateUI(func: () => void): Promise<void>;
initProps(props: CtrlProps): void;
addAppStateObserver(): void;
onAppStateEvent(eventName: any, data: any): void;
addAppEventObserver(): void;
onAppEvent(eventName: ApplicationEvent): void;
/** @override */
onAppStart(): Promise<void>;
onAppLaunch(): Promise<void>;
onAppKeyChange(): Promise<void>;
onAppSync(): void;
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class ApplicationView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class ApplicationGroupView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class ApplicationGroupView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,2 @@
export declare const PANEL_NAME_NOTES = "notes";
export declare const PANEL_NAME_TAGS = "tags";

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class EditorView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from './../../types';
export declare class EditorGroupView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from './../../types';
export declare class EditorGroupView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class FooterView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,8 @@
export { PureViewCtrl } from './abstract/pure_view_ctrl';
export { ApplicationGroupView } from './application_group/application_group_view';
export { ApplicationView } from './application/application_view';
export { EditorGroupView } from './editor_group/editor_group_view';
export { EditorView } from './editor/editor_view';
export { FooterView } from './footer/footer_view';
export { NotesView } from './notes/notes_view';
export { TagsView } from './tags/tags_view';

View File

@@ -0,0 +1,10 @@
import { SNNote, SNTag } from 'snjs';
export declare enum NoteSortKey {
CreatedAt = "created_at",
UpdatedAt = "updated_at",
ClientUpdatedAt = "client_updated_at",
Title = "title"
}
export declare function filterAndSortNotes(notes: SNNote[], selectedTag: SNTag, showArchived: boolean, hidePinned: boolean, filterText: string, sortBy: string, reverse: boolean): SNNote[];
export declare function filterNotes(notes: SNNote[], selectedTag: SNTag, showArchived: boolean, hidePinned: boolean, filterText: string): SNNote[];
export declare function sortNotes(notes: SNNote[] | undefined, sortBy: string, reverse: boolean): SNNote[];

View File

@@ -0,0 +1,4 @@
import { WebDirective } from './../../types';
export declare class NotesView extends WebDirective {
constructor();
}

View File

@@ -0,0 +1,4 @@
import { WebDirective } from '@/types';
export declare class TagsView extends WebDirective {
constructor();
}

13795
dist/javascripts/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@
</head> </head>
<body> <body>
<root /> <application-group-view />
</body> </body>
</html> </html>

View File

@@ -36,7 +36,9 @@ module.exports = {
alias: { alias: {
'%': path.resolve(__dirname, 'app/assets/templates'), '%': path.resolve(__dirname, 'app/assets/templates'),
'@': path.resolve(__dirname, 'app/assets/javascripts'), '@': path.resolve(__dirname, 'app/assets/javascripts'),
'@Controllers': path.resolve(__dirname, 'app/assets/javascripts/controllers') '@Controllers': path.resolve(__dirname, 'app/assets/javascripts/controllers'),
'@Views': path.resolve(__dirname, 'app/assets/javascripts/views'),
'@Services': path.resolve(__dirname, 'app/assets/javascripts/services'),
} }
}, },
module: { module: {