From 2bc3658f1abb6be103705783d11acb90e037d722 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 11 Apr 2020 19:42:50 -0500 Subject: [PATCH] Editor TypeScript --- .../controllers/abstract/pure_ctrl.ts | 8 + .../controllers/{editor.js => editor.ts} | 812 ++++++++++-------- app/assets/javascripts/controllers/footer.js | 6 +- .../javascripts/controllers/notes/notes.js | 20 +- .../controllers/{tags.js => tags.ts} | 194 +++-- .../directives/views/revisionPreviewModal.js | 4 +- .../javascripts/services/keyboardManager.js | 125 --- .../javascripts/services/keyboardManager.ts | 147 ++++ .../javascripts/services/nativeExtManager.js | 30 +- .../services/preferencesManager.js | 4 +- app/assets/javascripts/services/state.ts | 8 +- app/assets/javascripts/types.ts | 10 + app/assets/templates/editor.pug | 20 +- app/assets/templates/notes.pug | 4 +- app/assets/templates/tags.pug | 16 +- 15 files changed, 794 insertions(+), 614 deletions(-) rename app/assets/javascripts/controllers/{editor.js => editor.ts} (53%) rename app/assets/javascripts/controllers/{tags.js => tags.ts} (56%) delete mode 100644 app/assets/javascripts/services/keyboardManager.js create mode 100644 app/assets/javascripts/services/keyboardManager.ts diff --git a/app/assets/javascripts/controllers/abstract/pure_ctrl.ts b/app/assets/javascripts/controllers/abstract/pure_ctrl.ts index 1515b5246..b894dc36c 100644 --- a/app/assets/javascripts/controllers/abstract/pure_ctrl.ts +++ b/app/assets/javascripts/controllers/abstract/pure_ctrl.ts @@ -43,6 +43,10 @@ export class PureCtrl { this.deinit(); } + public get appState() { + return this.application!.getAppState(); + } + /** @private */ async resetState() { this.state = this.getInitialState(); @@ -66,6 +70,10 @@ export class PureCtrl { }); } + async updateUI(func: () => void) { + this.$timeout(func); + } + initProps(props: CtrlProps) { if (Object.keys(this.props).length > 0) { throw 'Already init-ed props.'; diff --git a/app/assets/javascripts/controllers/editor.js b/app/assets/javascripts/controllers/editor.ts similarity index 53% rename from app/assets/javascripts/controllers/editor.js rename to app/assets/javascripts/controllers/editor.ts index 3330caac1..75f7953ce 100644 --- a/app/assets/javascripts/controllers/editor.js +++ b/app/assets/javascripts/controllers/editor.ts @@ -1,15 +1,25 @@ +import { WebApplication } from './../application'; +import { PanelPuppet, WebDirective } from './../types'; import angular from 'angular'; import { ApplicationEvent, isPayloadSourceRetrieved, - ContentTypes, - ProtectedAction + ContentType, + ProtectedAction, + SNComponent, + SNNote, + SNTag, + NoteMutator, + Uuids, + ComponentArea, + ComponentAction, + PayloadSource } from 'snjs'; import find from 'lodash/find'; import { isDesktopApplication } from '@/utils'; -import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager'; +import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import template from '%/editor.pug'; -import { PureCtrl } from '@Controllers'; +import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { AppStateEvent, EventSource } from '@/services/state'; import { STRING_DELETED_NOTE, @@ -21,6 +31,8 @@ import { StringEmptyTrash } from '@/strings'; import { PrefKeys } from '@/services/preferencesManager'; +import { RawPayload } from '@/../../../../snjs/dist/@types/protocol/payloads/generator'; +import { ComponentMutator } from '@/../../../../snjs/dist/@types/models'; const NOTE_PREVIEW_CHAR_LIMIT = 80; const MINIMUM_STATUS_DURATION = 400; @@ -46,9 +58,55 @@ const Fonts = { SansSerifFamily: `inherit` }; +type NoteStatus = { + message?: string + date?: Date +} + +type EditorState = { + note: SNNote + saveError?: any + selectedEditor?: SNComponent + noteStatus?: NoteStatus + tagsAsStrings?: string + marginResizersEnabled?: boolean + monospaceEnabled?: boolean + isDesktop?: boolean + tagsComponent?: SNComponent + componentStack?: SNComponent[] + /** Fields that can be directly mutated by the template */ + mutable: { } +} + +type EditorValues = { + title?: string + text?: string + tagsInputValue?: string +} + class EditorCtrl extends PureCtrl { + /** Passed through template */ + readonly application!: WebApplication + private leftPanelPuppet?: PanelPuppet + private rightPanelPuppet?: PanelPuppet + private unregisterComponent: any + private saveTimeout?: ng.IPromise + private statusTimeout?: ng.IPromise + private lastEditorFocusEventSource?: EventSource + public editorValues: EditorValues = {} + onEditorLoad?: () => void + + private removeAltKeyObserver?: any + private removeTrashKeyObserver?: any + private removeDeleteKeyObserver?: any + private removeTabObserver?: any + + prefKeyMonospace: string + prefKeySpellcheck: string + prefKeyMarginResizers: string + /* @ngInject */ - constructor($timeout) { + constructor($timeout: ng.ITimeoutService) { super($timeout); this.leftPanelPuppet = { onReady: () => this.reloadPreferences() @@ -60,18 +118,39 @@ class EditorCtrl extends PureCtrl { this.prefKeyMonospace = PrefKeys.EditorMonospaceEnabled; this.prefKeySpellcheck = PrefKeys.EditorSpellcheck; this.prefKeyMarginResizers = PrefKeys.EditorResizersEnabled; + + this.editorMenuOnSelect = this.editorMenuOnSelect.bind(this); + this.onPanelResizeFinish = this.onPanelResizeFinish.bind(this); + this.onEditorLoad = () => { + this.application!.getDesktopService().redoSearch(); + } } deinit() { - this.removeTabObserver(); - this.leftPanelPuppet = null; - this.rightPanelPuppet = null; - this.onEditorLoad = null; + this.removeAltKeyObserver(); + this.removeAltKeyObserver = undefined; + this.removeTrashKeyObserver(); + this.removeTrashKeyObserver = undefined; + this.removeDeleteKeyObserver(); + this.removeDeleteKeyObserver = undefined; + this.removeTabObserver && this.removeTabObserver(); + this.removeTabObserver = undefined; + this.leftPanelPuppet = undefined; + this.rightPanelPuppet = undefined; + this.onEditorLoad = undefined; this.unregisterComponent(); - this.unregisterComponent = null; + this.unregisterComponent = undefined; + this.saveTimeout = undefined; + this.statusTimeout = undefined; + (this.onPanelResizeFinish as any) = undefined; + (this.editorMenuOnSelect as any) = undefined; super.deinit(); } + getState() { + return this.state as EditorState; + } + $onInit() { super.$onInit(); this.registerKeyboardShortcuts(); @@ -90,17 +169,17 @@ class EditorCtrl extends PureCtrl { }; } - onAppLaunch() { - super.onAppLaunch(); + async onAppLaunch() { + await super.onAppLaunch(); this.streamItems(); this.registerComponentHandler(); } /** @override */ - onAppStateEvent(eventName, data) { + onAppStateEvent(eventName: AppStateEvent, data: any) { if (eventName === AppStateEvent.NoteChanged) { this.handleNoteSelectionChange( - this.application.getAppState().getSelectedNote(), + this.application.getAppState().getSelectedNote()!, data.previousNote ); } else if (eventName === AppStateEvent.PreferencesChanged) { @@ -109,19 +188,19 @@ class EditorCtrl extends PureCtrl { } /** @override */ - onAppEvent(eventName) { - if (!this.state.note) { + onAppEvent(eventName: ApplicationEvent) { + if (!this.getState().note) { return; } if (eventName === ApplicationEvent.HighLatencySync) { this.setState({ syncTakingTooLong: true }); } else if (eventName === ApplicationEvent.CompletedSync) { this.setState({ syncTakingTooLong: false }); - if (this.state.note.dirty) { + if (this.getState().note.dirty) { /** if we're still dirty, don't change status, a sync is likely upcoming. */ } else { - const saved = this.state.note.lastSyncEnd > this.state.note.lastSyncBegan; - const isInErrorState = this.state.saveError; + const saved = this.getState().note.lastSyncEnd! > this.getState().note.lastSyncBegan!; + const isInErrorState = this.getState().saveError; if (isInErrorState || saved) { this.showAllChangesSavedStatus(); } @@ -132,7 +211,7 @@ class EditorCtrl extends PureCtrl { * Otherwise, it means the originating sync came from somewhere else * and we don't want to display an error here. */ - if (this.state.note.dirty) { + if (this.getState().note.dirty) { this.showErrorStatus(); } } else if (eventName === ApplicationEvent.LocalDatabaseWriteError) { @@ -152,78 +231,79 @@ class EditorCtrl extends PureCtrl { * on a deleted note. */ get noteLocked() { - if(!this.state.note || this.state.note.deleted) { + if (!this.getState().note || this.getState().note.deleted) { return false; } - return this.state.note.locked; + return this.getState().note.locked; } streamItems() { - this.application.streamItems({ - contentType: ContentTypes.Note, - stream: async ({ items, source }) => { - if (!this.state.note) { + this.application.streamItems( + ContentType.Note, + async (items, source) => { + const currentNote = this.getState().note; + if (!currentNote) { return; } - if (this.state.note.deleted) { + if (currentNote.deleted) { await this.setState({ note: null, noteReady: false }); return; } - if (this.state.note.content.trashed) { - return; - } - if (!isPayloadSourceRetrieved(source)) { + if (!isPayloadSourceRetrieved(source!)) { return; } const matchingNote = items.find((item) => { - return item.uuid === this.state.note.uuid; - }); + return item.uuid === currentNote.uuid; + }) as SNNote; if (!matchingNote) { return; } + this.editorValues.title = matchingNote.title; + this.editorValues.text = matchingNote.text; this.reloadTagsString(); } - }); + ); - this.application.streamItems({ - contentType: ContentTypes.Tag, - stream: async ({ items }) => { - if (!this.state.note) { + this.application.streamItems( + ContentType.Tag, + (items) => { + if (!this.getState().note) { return; } for (const tag of items) { if ( - !this.state.note.savedTagsString || + !this.editorValues.tagsInputValue || tag.deleted || - tag.hasRelationshipWithItem(this.state.note) + tag.hasRelationshipWithItem(this.getState().note) ) { this.reloadTagsString(); break; } } } - }); + ); - this.application.streamItems({ - contentType: ContentTypes.Component, - stream: async ({ items }) => { - if (!this.state.note) { + this.application.streamItems( + ContentType.Component, + async (items) => { + const components = items as SNComponent[]; + if (!this.getState().note) { return; } /** Reload componentStack in case new ones were added or removed */ this.reloadComponentStackArray(); /** Observe editor changes to see if the current note should update its editor */ - const editors = items.filter(function (item) { - return item.isEditor(); + const editors = components.filter((component) => { + return component.isEditor(); }); if (editors.length === 0) { return; } /** Find the most recent editor for note */ - const editor = this.editorForNote(this.state.note); + const editor = this.editorForNote(this.getState().note); this.setState({ selectedEditor: editor }); @@ -231,12 +311,10 @@ class EditorCtrl extends PureCtrl { this.reloadFont(); } } - }); + ); } - async handleNoteSelectionChange(note, previousNote) { - console.log("SN: handleNoteSelectionChange -> note", note); - console.log(this.application.itemsKeyManager.allItemsKeys); + async handleNoteSelectionChange(note: SNNote, previousNote?: SNNote) { this.setState({ note: this.application.getAppState().getSelectedNote(), showExtensions: false, @@ -251,7 +329,7 @@ class EditorCtrl extends PureCtrl { return; } const associatedEditor = this.editorForNote(note); - if (associatedEditor && associatedEditor !== this.state.selectedEditor) { + 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 @@ -284,29 +362,29 @@ class EditorCtrl extends PureCtrl { this.reloadComponentContext(); } - editorForNote(note) { - return this.application.componentManager.editorForNote(note); + editorForNote(note: SNNote) { + return this.application.componentManager!.editorForNote(note); } - setMenuState(menu, state) { + setMenuState(menu: string, state: boolean) { this.setState({ [menu]: state }); - this.closeAllMenus({ exclude: menu }); + this.closeAllMenus(menu); } - toggleMenu(menu) { + toggleMenu(menu: string) { this.setMenuState(menu, !this.state[menu]); } - closeAllMenus({ exclude } = {}) { + closeAllMenus(exclude?: string) { const allMenus = [ 'showOptionsMenu', 'showEditorMenu', 'showExtensions', 'showSessionHistory' ]; - const menuState = {}; + const menuState: any = {}; for (const candidate of allMenus) { if (candidate !== exclude) { menuState[candidate] = false; @@ -315,36 +393,32 @@ class EditorCtrl extends PureCtrl { this.setState(menuState); } - editorMenuOnSelect = (component) => { + editorMenuOnSelect(component: SNComponent) { if (!component || component.area === 'editor-editor') { /** If plain editor or other editor */ this.setMenuState('showEditorMenu', false); const editor = component; - if (this.state.selectedEditor && editor !== this.state.selectedEditor) { - this.disassociateComponentWithCurrentNote(this.state.selectedEditor); + if (this.getState().selectedEditor && editor !== this.getState().selectedEditor) { + this.disassociateComponentWithCurrentNote(this.getState().selectedEditor!); } + const note = this.getState().note; if (editor) { - const prefersPlain = this.state.note.getAppDataItem( - AppDataKeys.PrefersPlainEditor - ) === true; + const prefersPlain = note.prefersPlainEditor; if (prefersPlain) { - this.state.note.setAppDataItem( - AppDataKeys.PrefersPlainEditor, - false - ); - this.application.setItemNeedsSync({ item: this.state.note }); + this.application.changeItem(note.uuid, (mutator) => { + const noteMutator = mutator as NoteMutator; + noteMutator.prefersPlainEditor = false; + }) } this.associateComponentWithCurrentNote(editor); } else { /** Note prefers plain editor */ - if (!this.state.note.getAppDataItem(AppDataKeys.PrefersPlainEditor)) { - this.state.note.setAppDataItem( - AppDataKeys.PrefersPlainEditor, - true - ); - this.application.setItemNeedsSync({ item: this.state.note }); + if (!note.prefersPlainEditor) { + this.application.changeItem(note.uuid, (mutator) => { + const noteMutator = mutator as NoteMutator; + noteMutator.prefersPlainEditor = true; + }) } - this.reloadFont(); } @@ -360,7 +434,8 @@ class EditorCtrl extends PureCtrl { } hasAvailableExtensions() { - return this.application.actionsManager.extensionsInContextOfItem(this.state.note).length > 0; + return this.application.actionsManager!. + extensionsInContextOfItem(this.getState().note).length > 0; } performFirefoxPinnedTabFix() { @@ -374,41 +449,47 @@ class EditorCtrl extends PureCtrl { } } - saveNote({ - bypassDebouncer, - updateClientModified, - dontUpdatePreviews - }) { + saveNote( + bypassDebouncer = false, + isUserModified = false, + dontUpdatePreviews = false, + customMutate?: (mutator: NoteMutator) => void + ) { this.performFirefoxPinnedTabFix(); - const note = this.state.note; - note.dummy = false; + const note = this.getState().note; + if (note.deleted) { - this.application.alertService.alert({ - text: STRING_DELETED_NOTE - }); + this.application.alertService!.alert( + STRING_DELETED_NOTE + ); return; } - if (!this.application.findItem({ uuid: note.uuid })) { - this.application.alertService.alert({ - text: STRING_INVALID_NOTE - }); + if (!this.application.findItem(note.uuid)) { + this.application.alertService!.alert( + STRING_INVALID_NOTE + ); return; } this.showSavingStatus(); - if (!dontUpdatePreviews) { - const text = note.text || ''; - const truncate = text.length > NOTE_PREVIEW_CHAR_LIMIT; - const substring = text.substring(0, NOTE_PREVIEW_CHAR_LIMIT); - const previewPlain = substring + (truncate ? STRING_ELLIPSES : ''); - note.content.preview_plain = previewPlain; - note.content.preview_html = null; - } - this.application.setItemNeedsSync({ - item: note, - updateUserModifiedDate: updateClientModified - }); + this.application.changeItem(note.uuid, (mutator) => { + const noteMutator = mutator as NoteMutator; + if (customMutate) { + customMutate(noteMutator); + } + noteMutator.title = this.editorValues.title!; + noteMutator.text = this.editorValues.text!; + if (!dontUpdatePreviews) { + const text = note.text || ''; + const truncate = text.length > NOTE_PREVIEW_CHAR_LIMIT; + const substring = text.substring(0, NOTE_PREVIEW_CHAR_LIMIT); + const previewPlain = substring + (truncate ? STRING_ELLIPSES : ''); + noteMutator.preview_plain = previewPlain; + noteMutator.preview_html = undefined; + } + }, isUserModified) + if (this.saveTimeout) { this.$timeout.cancel(this.saveTimeout); } @@ -439,7 +520,7 @@ class EditorCtrl extends PureCtrl { }); } - showErrorStatus(error) { + showErrorStatus(error?: any) { if (!error) { error = { message: "Sync Unreachable", @@ -453,12 +534,14 @@ class EditorCtrl extends PureCtrl { this.setStatus(error); } - setStatus(status, wait = true) { + setStatus(status: { message: string, date?: Date }, wait = true) { let waitForMs; - if (!this.state.noteStatus || !this.state.noteStatus.date) { + if (!this.getState().noteStatus || !this.getState().noteStatus!.date) { waitForMs = 0; } else { - waitForMs = MINIMUM_STATUS_DURATION - (new Date() - this.state.noteStatus.date); + waitForMs = MINIMUM_STATUS_DURATION - ( + new Date().getTime() - this.getState().noteStatus!.date!.getTime() + ); } if (!wait || waitForMs < 0) { waitForMs = 0; @@ -475,22 +558,24 @@ class EditorCtrl extends PureCtrl { } contentChanged() { - this.saveNote({ - updateClientModified: true - }); + this.saveNote( + false, + true + ); } - onTitleEnter($event) { - $event.target.blur(); + onTitleEnter($event: Event) { + ($event.target as HTMLInputElement).blur(); this.onTitleChange(); this.focusEditor(); } onTitleChange() { - this.saveNote({ - dontUpdatePreviews: true, - updateClientModified: true - }); + this.saveNote( + false, + true, + true, + ); } focusEditor() { @@ -502,72 +587,79 @@ class EditorCtrl extends PureCtrl { } focusTitle() { - document.getElementById(ElementIds.NoteTitleEditor).focus(); + document.getElementById(ElementIds.NoteTitleEditor)!.focus(); } clickedTextArea() { this.setMenuState('showOptionsMenu', false); } - onNameFocus() { - this.editingName = true; + onTitleFocus() { + + } + + onTitleBlur() { + } onContentFocus() { - this.application.getAppState().editorDidFocus(this.lastEditorFocusEventSource); - this.lastEditorFocusEventSource = null; + this.application.getAppState().editorDidFocus(this.lastEditorFocusEventSource!); + this.lastEditorFocusEventSource = undefined; } - onNameBlur() { - this.editingName = false; - } - - selectedMenuItem(hide) { + selectedMenuItem(hide: boolean) { if (hide) { this.setMenuState('showOptionsMenu', false); } } - async deleteNote(permanently) { - if (this.state.note.dummy) { - this.application.alertService.alert({ - text: STRING_DELETE_PLACEHOLDER_ATTEMPT - }); + async deleteNote(permanently: boolean) { + if (this.getState().note.dummy) { + this.application.alertService!.alert( + STRING_DELETE_PLACEHOLDER_ATTEMPT + ); return; } const run = () => { - if (this.state.note.locked) { - this.application.alertService.alert({ - text: STRING_DELETE_LOCKED_ATTEMPT - }); + if (this.getState().note.locked) { + this.application.alertService!.alert( + STRING_DELETE_LOCKED_ATTEMPT + ); return; } - const title = this.state.note.safeTitle().length - ? `'${this.state.note.title}'` + const title = this.getState().note.safeTitle().length + ? `'${this.getState().note.title}'` : "this note"; const text = StringDeleteNote( title, permanently ); - this.application.alertService.confirm({ - text: text, - destructive: true, - onConfirm: () => { + this.application.alertService!.confirm( + text, + undefined, + undefined, + undefined, + () => { if (permanently) { - this.performNoteDeletion(this.state.note); + this.performNoteDeletion(this.getState().note); } else { - this.state.note.content.trashed = true; - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.trashed = true; + } + ); } - this.application.getAppState().setSelectedNote(null); + this.application.getAppState().setSelectedNote(undefined); this.setMenuState('showOptionsMenu', false); - } - }); + }, + undefined, + true, + ); }; - const requiresPrivilege = await this.application.privilegesService.actionRequiresPrivilege( + const requiresPrivilege = await this.application.privilegesService!.actionRequiresPrivilege( ProtectedAction.DeleteNote ); if (requiresPrivilege) { @@ -582,27 +674,30 @@ class EditorCtrl extends PureCtrl { } } - performNoteDeletion(note) { - this.application.deleteItem({ item: note }); - if (note === this.state.note) { + performNoteDeletion(note: SNNote) { + this.application.deleteItem(note); + if (note === this.getState().note) { this.setState({ note: null }); } if (note.dummy) { - this.application.deleteItemLocally({ item: note }); + this.application.deleteItemLocally(note); return; } this.application.sync(); } restoreTrashedNote() { - this.state.note.content.trashed = false; - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); - this.application.getAppState().setSelectedNote(null); + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.trashed = false; + } + ); + this.application.getAppState().setSelectedNote(undefined); } deleteNotePermanantely() { @@ -615,47 +710,53 @@ class EditorCtrl extends PureCtrl { emptyTrash() { const count = this.getTrashCount(); - this.application.alertService.confirm({ - text: StringEmptyTrash(count), - destructive: true, - onConfirm: () => { + this.application.alertService!.confirm( + StringEmptyTrash(count), + undefined, + undefined, + undefined, + () => { this.application.emptyTrash(); this.application.sync(); - } - }); + }, + undefined, + true, + ); } togglePin() { - this.state.note.setAppDataItem( - AppDataKeys.Pinned, - !this.state.note.pinned + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.pinned = !this.getState().note.pinned + } ); - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); } toggleLockNote() { - this.state.note.setAppDataItem( - AppDataKeys.Locked, - !this.state.note.locked + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.locked = !this.getState().note.locked + } ); - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); } toggleProtectNote() { - this.state.note.content.protected = !this.state.note.content.protected; - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); - + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.protected = !this.getState().note.protected + } + ); /** Show privileges manager if protection is not yet set up */ - this.application.privilegesService.actionHasPrivilegesConfigured( + this.application.privilegesService!.actionHasPrivilegesConfigured( ProtectedAction.ViewProtectedNotes ).then((configured) => { if (!configured) { @@ -665,93 +766,105 @@ class EditorCtrl extends PureCtrl { } toggleNotePreview() { - this.state.note.content.hidePreview = !this.state.note.content.hidePreview; - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.hidePreview = !this.getState().note.hidePreview + } + ); } toggleArchiveNote() { - this.state.note.setAppDataItem( - AppDataKeys.Archived, - !this.state.note.archived + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.archived = !this.getState().note.archived + } ); - this.saveNote({ - bypassDebouncer: true, - dontUpdatePreviews: true - }); } reloadTagsString() { - this.setState({ - mutable: { - ...this.state.mutable, - tagsString: this.state.note.tagsString() - } - }); + const tags = this.appState.getNoteTags(this.getState().note); + const string = SNTag.arrayToDisplayString(tags); + this.updateUI(() => { + this.editorValues.tagsInputValue = string; + }) } - addTag(tag) { - const strings = this.state.note.tags.map((currentTag) => { + addTag(tag: SNTag) { + const tags = this.appState.getNoteTags(this.getState().note); + const strings = tags.map((currentTag) => { return currentTag.title; }); strings.push(tag.title); - this.saveTags({ strings: strings }); + this.saveTagsFromStrings(strings); } - removeTag(tag) { - const strings = this.state.note.tags.map((currentTag) => { + removeTag(tag: SNTag) { + const tags = this.appState.getNoteTags(this.getState().note); + const strings = tags.map((currentTag) => { return currentTag.title; }).filter((title) => { return title !== tag.title; }); - this.saveTags({ strings: strings }); + this.saveTagsFromStrings(strings); } - async saveTags({ strings } = {}) { - if (!strings && this.state.mutable.tagsString === this.state.note.tagsString()) { + async saveTagsFromStrings(strings?: string[]) { + if ( + !strings + && this.editorValues.tagsInputValue === this.getState().tagsAsStrings + ) { return; } if (!strings) { - strings = this.state.mutable.tagsString.split('#').filter((string) => { - return string.length > 0; - }).map((string) => { - return string.trim(); - }); + strings = this.editorValues.tagsInputValue! + .split('#') + .filter((string) => { + return string.length > 0; + }) + .map((string) => { + return string.trim(); + }); } - this.state.note.dummy = false; - const toRemove = []; - for (const tag of this.state.note.tags) { + const note = this.getState().note; + const currentTags = this.appState.getNoteTags(note); + + const removeTags = []; + for (const tag of currentTags) { if (strings.indexOf(tag.title) === -1) { - toRemove.push(tag); + removeTags.push(tag); } } - for (const tagToRemove of toRemove) { - tagToRemove.removeItemAsRelationship(this.state.note); + for (const tag of removeTags) { + this.application.changeItem(tag.uuid, (mutator) => { + mutator.removeItemAsRelationship(note); + }) } - this.application.setItemsNeedsSync({ items: toRemove }); - const tags = []; - for (const tagString of strings) { + const newRelationships: SNTag[] = []; + for (const title of strings) { const existingRelationship = find( - this.state.note.tags, - { title: tagString } + currentTags, + { title: title } ); if (!existingRelationship) { - tags.push( - await this.application.findOrCreateTag({ title: tagString }) + newRelationships.push( + await this.application.findOrCreateTag(title) ); } } - for (const tag of tags) { - tag.addItemAsRelationship(this.state.note); - } - this.application.saveItems({ items: tags }); + this.application.changeAndSaveItems(Uuids(newRelationships), (mutator) => { + mutator.addItemAsRelationship(note); + }) this.reloadTagsString(); } - onPanelResizeFinish = (width, left, isMaxWidth) => { + onPanelResizeFinish(width: number, left: number, isMaxWidth: boolean) { if (isMaxWidth) { this.application.getPrefsService().setUserPrefValue( PrefKeys.EditorWidth, @@ -763,7 +876,7 @@ class EditorCtrl extends PureCtrl { PrefKeys.EditorWidth, width ); - this.leftPanelPuppet.setWidth(width); + this.leftPanelPuppet!.setWidth!(width); } } if (left !== undefined && left !== null) { @@ -771,7 +884,7 @@ class EditorCtrl extends PureCtrl { PrefKeys.EditorLeft, left ); - this.rightPanelPuppet.setLeft(left); + this.rightPanelPuppet!.setLeft!(left); } this.application.getPrefsService().syncUserPreferences(); } @@ -803,25 +916,25 @@ class EditorCtrl extends PureCtrl { this.reloadFont(); if ( - this.state.marginResizersEnabled && - this.leftPanelPuppet.ready && - this.rightPanelPuppet.ready + this.getState().marginResizersEnabled && + this.leftPanelPuppet!.ready && + this.rightPanelPuppet!.ready ) { const width = this.application.getPrefsService().getValue( PrefKeys.EditorWidth, null ); if (width != null) { - this.leftPanelPuppet.setWidth(width); - this.rightPanelPuppet.setWidth(width); + this.leftPanelPuppet!.setWidth!(width); + this.rightPanelPuppet!.setWidth!(width); } const left = this.application.getPrefsService().getValue( PrefKeys.EditorLeft, null ); if (left != null) { - this.leftPanelPuppet.setLeft(left); - this.rightPanelPuppet.setLeft(left); + this.leftPanelPuppet!.setLeft!(left); + this.rightPanelPuppet!.setLeft!(left); } } } @@ -833,8 +946,8 @@ class EditorCtrl extends PureCtrl { if (!editor) { return; } - if (this.state.monospaceEnabled) { - if (this.state.isDesktop) { + if (this.getState().monospaceEnabled) { + if (this.getState().isDesktop) { editor.style.fontFamily = Fonts.DesktopMonospaceFamily; } else { editor.style.fontFamily = Fonts.WebMonospaceFamily; @@ -844,11 +957,11 @@ class EditorCtrl extends PureCtrl { } } - async toggleKey(key) { - this[key] = !this[key]; + async togglePrefKey(key: string) { + (this as any)[key] = !(this as any)[key]; this.application.getPrefsService().setUserPrefValue( key, - this[key], + (this as any)[key], true ); this.reloadFont(); @@ -862,27 +975,23 @@ class EditorCtrl extends PureCtrl { noteReady: true }); this.reloadFont(); - } else if (key === PrefKeys.EditorResizersEnabled && this[key] === true) { + } else if (key === PrefKeys.EditorResizersEnabled && (this as any)[key] === true) { this.$timeout(() => { - this.leftPanelPuppet.flash(); - this.rightPanelPuppet.flash(); + this.leftPanelPuppet!.flash!(); + this.rightPanelPuppet!.flash!(); }); } } /** @components */ - onEditorLoad = (editor) => { - this.application.getDesktopService().redoSearch(); - } - registerComponentHandler() { - this.unregisterComponent = this.application.componentManager.registerHandler({ + this.unregisterComponent = this.application.componentManager!.registerHandler({ identifier: 'editor', areas: [ - 'note-tags', - 'editor-stack', - 'editor-editor' + ComponentArea.NoteTags, + ComponentArea.EditorStack, + ComponentArea.Editor ], activationHandler: (component) => { if (component.area === 'note-tags') { @@ -891,24 +1000,24 @@ class EditorCtrl extends PureCtrl { }); } else if (component.area === 'editor-editor') { if ( - component === this.state.selectedEditor && + component === this.getState().selectedEditor && !component.active ) { this.setState({ selectedEditor: null }); } - else if (this.state.selectedEditor) { - if (this.state.selectedEditor.active && this.state.note) { + else if (this.getState().selectedEditor) { + if (this.getState().selectedEditor!.active && this.getState().note) { if ( - component.isExplicitlyEnabledForItem(this.state.note) - && !this.state.selectedEditor.isExplicitlyEnabledForItem(this.state.note) + component.isExplicitlyEnabledForItem(this.getState().note) + && !this.getState().selectedEditor!.isExplicitlyEnabledForItem(this.getState().note) ) { this.setState({ selectedEditor: component }); } } } - else if (this.state.note) { + else if (this.getState().note) { const enableable = ( - component.isExplicitlyEnabledForItem(this.state.note) + component.isExplicitlyEnabledForItem(this.getState().note) || component.isDefaultEditor() ); if ( @@ -931,11 +1040,11 @@ class EditorCtrl extends PureCtrl { }, contextRequestHandler: (component) => { if ( - component === this.state.selectedEditor || - component === this.state.tagsComponent || - this.state.componentStack.includes(component) + component === this.getState().selectedEditor || + component === this.getState().tagsComponent || + this.getState().componentStack!.includes(component) ) { - return this.state.note; + return this.getState().note; } }, focusHandler: (component, focused) => { @@ -944,8 +1053,8 @@ class EditorCtrl extends PureCtrl { } }, actionHandler: (component, action, data) => { - if (action === 'set-size') { - const setSize = function (element, size) { + if (action === ComponentAction.SetSize) { + const setSize = function (element: HTMLElement, size: { width: number, height: number }) { const widthString = typeof size.width === 'string' ? size.width : `${data.width}px`; @@ -958,28 +1067,28 @@ class EditorCtrl extends PureCtrl { ); }; if (data.type === 'container') { - if (component.area === 'note-tags') { + if (component.area === ComponentArea.NoteTags) { const container = document.getElementById( ElementIds.NoteTagsComponentContainer ); - setSize(container, data); + setSize(container!, data); } } } - else if (action === 'associate-item') { - if (data.item.content_type === 'Tag') { - const tag = this.application.findItem({ uuid: data.item.uuid }); + else if (action === ComponentAction.AssociateItem) { + if (data.item.content_type === ContentType.Tag) { + const tag = this.application.findItem(data.item.uuid) as SNTag; this.addTag(tag); } } - else if (action === 'deassociate-item') { - const tag = this.application.findItem({ uuid: data.item.uuid }); + else if (action === ComponentAction.DeassociateItem) { + const tag = this.application.findItem(data.item.uuid) as SNTag; this.removeTag(tag); } - else if (action === 'save-items') { - const includesNote = data.items.map((item) => { + else if (action === ComponentAction.SaveItems) { + const includesNote = data.items.map((item: RawPayload) => { return item.uuid; - }).includes(this.state.note.uuid); + }).includes(this.getState().note.uuid); if (includesNote) { this.showSavingStatus(); } @@ -989,7 +1098,8 @@ class EditorCtrl extends PureCtrl { } reloadComponentStackArray() { - const components = this.application.componentManager.componentsForArea('editor-stack') + const components = this.application.componentManager! + .componentsForArea(ComponentArea.EditorStack) .sort((a, b) => { return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; }); @@ -1001,65 +1111,59 @@ class EditorCtrl extends PureCtrl { reloadComponentContext() { this.reloadComponentStackArray(); - if (this.state.note) { - for (const component of this.state.componentStack) { + if (this.getState().note) { + for (const component of this.getState().componentStack!) { if (component.active) { - this.application.componentManager.setComponentHidden( + this.application.componentManager!.setComponentHidden( component, - !component.isExplicitlyEnabledForItem(this.state.note) + !component.isExplicitlyEnabledForItem(this.getState().note) ); } } } - this.application.componentManager.contextItemDidChangeInArea('note-tags'); - this.application.componentManager.contextItemDidChangeInArea('editor-stack'); - this.application.componentManager.contextItemDidChangeInArea('editor-editor'); + this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.NoteTags); + this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.EditorStack); + this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.Editor); } - toggleStackComponentForCurrentItem(component) { - if (component.hidden || !component.active) { - this.application.componentManager.setComponentHidden(component, false); + toggleStackComponentForCurrentItem(component: SNComponent) { + const hidden = this.application.componentManager!.isComponentHidden(component); + if (hidden || !component.active) { + this.application.componentManager!.setComponentHidden(component, false); this.associateComponentWithCurrentNote(component); if (!component.active) { - this.application.componentManager.activateComponent(component); + this.application.componentManager!.activateComponent(component); } - this.application.componentManager.contextItemDidChangeInArea('editor-stack'); + this.application.componentManager!.contextItemDidChangeInArea(ComponentArea.EditorStack); } else { - this.application.componentManager.setComponentHidden(component, true); + this.application.componentManager!.setComponentHidden(component, true); this.disassociateComponentWithCurrentNote(component); } } - disassociateComponentWithCurrentNote(component) { - component.associatedItemIds = component.associatedItemIds.filter((id) => { - return id !== this.state.note.uuid; - }); - - if (!component.disassociatedItemIds.includes(this.state.note.uuid)) { - component.disassociatedItemIds.push(this.state.note.uuid); - } - - this.application.saveItem({ item: component }); + disassociateComponentWithCurrentNote(component: SNComponent) { + const note = this.getState().note; + this.application.changeAndSaveItem(component.uuid, (m) => { + const mutator = m as ComponentMutator; + mutator.removeAssociatedItemId(note.uuid); + mutator.disassociateWithItem(note); + }) } - associateComponentWithCurrentNote(component) { - component.disassociatedItemIds = component.disassociatedItemIds - .filter((id) => { - return id !== this.state.note.uuid; - }); - - if (!component.associatedItemIds.includes(this.state.note.uuid)) { - component.associatedItemIds.push(this.state.note.uuid); - } - - this.application.saveItem({ item: component }); + associateComponentWithCurrentNote(component: SNComponent) { + const note = this.getState().note; + this.application.changeAndSaveItem(component.uuid, (m) => { + const mutator = m as ComponentMutator; + mutator.removeDisassociatedItemId(note.uuid); + mutator.associateWithItem(note); + }) } registerKeyboardShortcuts() { - this.altKeyObserver = this.application.getKeyboardService().addKeyObserver({ + this.removeAltKeyObserver = this.application.getKeyboardService().addKeyObserver({ modifiers: [ - KeyboardModifiers.Alt + KeyboardModifier.Alt ], onKeyDown: () => { this.setState({ @@ -1073,24 +1177,24 @@ class EditorCtrl extends PureCtrl { } }); - this.trashKeyObserver = this.application.getKeyboardService().addKeyObserver({ - key: KeyboardKeys.Backspace, + this.removeTrashKeyObserver = this.application.getKeyboardService().addKeyObserver({ + key: KeyboardKey.Backspace, notElementIds: [ ElementIds.NoteTextEditor, ElementIds.NoteTitleEditor ], - modifiers: [KeyboardModifiers.Meta], + modifiers: [KeyboardModifier.Meta], onKeyDown: () => { - this.deleteNote(); + this.deleteNote(false); }, }); - this.deleteKeyObserver = this.application.getKeyboardService().addKeyObserver({ - key: KeyboardKeys.Backspace, + this.removeDeleteKeyObserver = this.application.getKeyboardService().addKeyObserver({ + key: KeyboardKey.Backspace, modifiers: [ - KeyboardModifiers.Meta, - KeyboardModifiers.Shift, - KeyboardModifiers.Alt + KeyboardModifier.Meta, + KeyboardModifier.Shift, + KeyboardModifier.Alt ], onKeyDown: (event) => { event.preventDefault(); @@ -1100,7 +1204,7 @@ class EditorCtrl extends PureCtrl { } onSystemEditorLoad() { - if (this.tabObserver) { + if (this.removeTabObserver) { return; } /** @@ -1109,14 +1213,12 @@ class EditorCtrl extends PureCtrl { * If the shift key is pressed first, this event is * not fired. */ - const editor = document.getElementById( - ElementIds.NoteTextEditor - ); - this.tabObserver = this.application.getKeyboardService().addKeyObserver({ + const editor = document.getElementById(ElementIds.NoteTextEditor)! as HTMLInputElement; + this.removeTabObserver = this.application.getKeyboardService().addKeyObserver({ element: editor, - key: KeyboardKeys.Tab, + key: KeyboardKey.Tab, onKeyDown: (event) => { - if (this.state.note.locked || event.shiftKey) { + if (this.getState().note.locked || event.shiftKey) { return; } event.preventDefault(); @@ -1128,8 +1230,8 @@ class EditorCtrl extends PureCtrl { ); if (!insertSuccessful) { /** document.execCommand works great on Chrome/Safari but not Firefox */ - const start = editor.selectionStart; - const end = editor.selectionEnd; + const start = editor.selectionStart!; + const end = editor.selectionEnd!; const spaces = ' '; /** Insert 4 spaces */ editor.value = editor.value.substring(0, start) @@ -1137,15 +1239,8 @@ class EditorCtrl extends PureCtrl { /** Place cursor 4 spaces away from where the tab key was pressed */ editor.selectionStart = editor.selectionEnd = start + 4; } - - const note = this.state.note; - note.text = editor.value; - this.setState({ - note: note - }); - this.saveNote({ - bypassDebouncer: true - }); + this.editorValues.text = editor.value; + this.saveNote(true); }, }); @@ -1155,23 +1250,14 @@ class EditorCtrl extends PureCtrl { */ angular.element(editor).one('$destroy', () => { this.removeTabObserver(); + this.removeTabObserver = undefined; }); } - - removeTabObserver() { - if (!this.application) { - return; - } - const keyboardService = this.application.getKeyboardService(); - if (this.tabObserver && keyboardService) { - keyboardService.removeKeyObserver(this.tabObserver); - this.tabObserver = null; - } - } } -export class EditorPanel { +export class EditorPanel extends WebDirective { constructor() { + super(); this.restrict = 'E'; this.scope = { application: '=' diff --git a/app/assets/javascripts/controllers/footer.js b/app/assets/javascripts/controllers/footer.js index eb8018590..4eed8f45e 100644 --- a/app/assets/javascripts/controllers/footer.js +++ b/app/assets/javascripts/controllers/footer.js @@ -157,10 +157,10 @@ class FooterCtrl extends PureCtrl { streamItems() { this.application.streamItems({ - contentType: ContentTypes.Component, + contentType: ContentType.Component, stream: async () => { this.rooms = this.application.getItems({ - contentType: ContentTypes.Component + contentType: ContentType.Component }).filter((candidate) => { return candidate.area === 'rooms' && !candidate.deleted; }); @@ -175,7 +175,7 @@ class FooterCtrl extends PureCtrl { contentType: 'SN|Theme', stream: async () => { const themes = this.application.getDisplayableItems({ - contentType: ContentTypes.Theme + contentType: ContentType.Theme }).filter((candidate) => { return ( !candidate.deleted && diff --git a/app/assets/javascripts/controllers/notes/notes.js b/app/assets/javascripts/controllers/notes/notes.js index bb7fcfe6a..5c682bf56 100644 --- a/app/assets/javascripts/controllers/notes/notes.js +++ b/app/assets/javascripts/controllers/notes/notes.js @@ -3,7 +3,7 @@ import template from '%/notes.pug'; import { ApplicationEvent, ContentTypes, removeFromArray } from 'snjs'; import { PureCtrl } from '@Controllers'; import { AppStateEvent } from '@/services/state'; -import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager'; +import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { PrefKeys } from '@/services/preferencesManager'; @@ -155,7 +155,7 @@ class NotesCtrl extends PureCtrl { streamNotesAndTags() { this.application.streamItems({ - contentType: [ContentTypes.Note, ContentTypes.Tag], + contentType: [ContentType.Note, ContentType.Tag], stream: async ({ items }) => { await this.reloadNotes(); const selectedNote = this.state.selectedNote; @@ -169,7 +169,7 @@ class NotesCtrl extends PureCtrl { } /** Note has changed values, reset its flags */ - const notes = items.filter((item) => item.content_type === ContentTypes.Note); + const notes = items.filter((item) => item.content_type === ContentType.Note); for (const note of notes) { if(note.deleted) { continue; @@ -202,7 +202,7 @@ class NotesCtrl extends PureCtrl { title = `Note ${this.state.notes.length + 1}`; } const newNote = await this.application.createManagedItem({ - contentType: ContentTypes.Note, + contentType: ContentType.Note, content: { text: '', title: title @@ -680,8 +680,8 @@ class NotesCtrl extends PureCtrl { this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: 'n', modifiers: [ - KeyboardModifiers.Meta, - KeyboardModifiers.Ctrl + KeyboardModifier.Meta, + KeyboardModifier.Ctrl ], onKeyDown: (event) => { event.preventDefault(); @@ -690,7 +690,7 @@ class NotesCtrl extends PureCtrl { }); this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ - key: KeyboardKeys.Down, + key: KeyboardKey.Down, elements: [ document.body, this.getSearchBar() @@ -705,7 +705,7 @@ class NotesCtrl extends PureCtrl { }); this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ - key: KeyboardKeys.Up, + key: KeyboardKey.Up, element: document.body, onKeyDown: (event) => { this.selectPreviousNote(); @@ -715,8 +715,8 @@ class NotesCtrl extends PureCtrl { this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({ key: "f", modifiers: [ - KeyboardModifiers.Meta, - KeyboardModifiers.Shift + KeyboardModifier.Meta, + KeyboardModifier.Shift ], onKeyDown: (event) => { const searchBar = this.getSearchBar(); diff --git a/app/assets/javascripts/controllers/tags.js b/app/assets/javascripts/controllers/tags.ts similarity index 56% rename from app/assets/javascripts/controllers/tags.js rename to app/assets/javascripts/controllers/tags.ts index 21e8e4c61..4baca0a78 100644 --- a/app/assets/javascripts/controllers/tags.js +++ b/app/assets/javascripts/controllers/tags.ts @@ -1,21 +1,40 @@ +import { WebDirective, PanelPuppet } from './../types'; +import { WebApplication } from './../application'; import { SNNote, - SNSmartTag, - ContentTypes, + SNTag, + ContentType, ApplicationEvent, - ComponentActions + ComponentAction, + SNSmartTag, + ComponentArea, + SNComponent } from 'snjs'; import template from '%/tags.pug'; import { AppStateEvent } from '@/services/state'; import { PANEL_NAME_TAGS } from '@/controllers/constants'; import { PrefKeys } from '@/services/preferencesManager'; import { STRING_DELETE_TAG } from '@/strings'; -import { PureCtrl } from '@Controllers'; +import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; +import { UuidString } from '@/../../../../snjs/dist/@types/types'; +import { TagMutator } from '@/../../../../snjs/dist/@types/models/app/tag'; + +type NoteCounts = Partial> class TagsPanelCtrl extends PureCtrl { + + /** Passed through template */ + readonly application!: WebApplication + private readonly panelPuppet: PanelPuppet + private unregisterComponent?: any + component?: SNComponent + private editingOriginalName?: string + formData: { tagTitle?: string } = {} + titles: Partial> = {} + /* @ngInject */ constructor( - $timeout, + $timeout: ng.ITimeoutService, ) { super($timeout); this.panelPuppet = { @@ -25,7 +44,7 @@ class TagsPanelCtrl extends PureCtrl { deinit() { this.unregisterComponent(); - this.unregisterComponent = null; + this.unregisterComponent = undefined; super.deinit(); } @@ -37,12 +56,12 @@ class TagsPanelCtrl extends PureCtrl { }; } - onAppStart() { + async onAppStart() { super.onAppStart(); this.registerComponentHandler(); } - onAppLaunch() { + async onAppLaunch() { super.onAppLaunch(); this.loadPreferences(); this.beginStreamingItems(); @@ -64,20 +83,21 @@ class TagsPanelCtrl extends PureCtrl { * @access private */ getMappedTags() { - const tags = this.application.getItems({ contentType: ContentTypes.Tag }); + const tags = this.application.getItems(ContentType.Tag) as SNTag[]; return tags.sort((a, b) => { - return a.content.title < b.content.title ? -1 : 1; + return a.title < b.title ? -1 : 1; }); } beginStreamingItems() { - this.application.streamItems({ - contentType: ContentTypes.Tag, - stream: async ({ items }) => { + this.application.streamItems( + ContentType.Tag, + async (items) => { await this.setState({ tags: this.getMappedTags(), smartTags: this.application.getSmartTags(), }); + this.reloadTitles(items as SNTag[]); this.reloadNoteCounts(); if (this.state.selectedTag) { /** If the selected tag has been deleted, revert to All view. */ @@ -89,11 +109,17 @@ class TagsPanelCtrl extends PureCtrl { } } } - }); + ); + } + + reloadTitles(tags: Array) { + for(const tag of tags) { + this.titles[tag.uuid] = tag.title; + } } /** @override */ - onAppStateEvent(eventName, data) { + onAppStateEvent(eventName: AppStateEvent, data?: any) { if (eventName === AppStateEvent.PreferencesChanged) { this.loadPreferences(); } else if (eventName === AppStateEvent.TagChanged) { @@ -105,7 +131,7 @@ class TagsPanelCtrl extends PureCtrl { /** @override */ - async onAppEvent(eventName) { + async onAppEvent(eventName: ApplicationEvent) { super.onAppEvent(eventName); if (eventName === ApplicationEvent.LocalDataIncrementalLoad) { this.reloadNoteCounts(); @@ -119,19 +145,25 @@ class TagsPanelCtrl extends PureCtrl { } reloadNoteCounts() { - let allTags = []; + let allTags: Array = []; if (this.state.tags) { allTags = allTags.concat(this.state.tags); } if (this.state.smartTags) { allTags = allTags.concat(this.state.smartTags); } - const noteCounts = {}; + const noteCounts: NoteCounts = {}; for (const tag of allTags) { - const validNotes = SNNote.filterDummyNotes(tag.notes).filter((note) => { - return !note.archived && !note.content.trashed; - }); - noteCounts[tag.uuid] = validNotes.length; + if (tag.isSmartTag()) { + const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag); + noteCounts[tag.uuid] = notes.length; + } else { + const notes = this.application.referencesForItem(tag, ContentType.Note) + .filter((note) => { + return !note.archived && !note.trashed; + }) + noteCounts[tag.uuid] = notes.length; + } } this.setState({ noteCounts: noteCounts @@ -144,17 +176,22 @@ class TagsPanelCtrl extends PureCtrl { } const width = this.application.getPrefsService().getValue(PrefKeys.TagsPanelWidth); if (width) { - this.panelPuppet.setWidth(width); - if (this.panelPuppet.isCollapsed()) { + this.panelPuppet.setWidth!(width); + if (this.panelPuppet.isCollapsed!()) { this.application.getAppState().panelDidResize( PANEL_NAME_TAGS, - this.panelPuppet.isCollapsed() + this.panelPuppet.isCollapsed!() ); } } } - onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => { + onPanelResize = ( + newWidth: number, + lastLeft: number, + isAtMaxWidth: boolean, + isCollapsed: boolean + ) => { this.application.getPrefsService().setUserPrefValue( PrefKeys.TagsPanelWidth, newWidth, @@ -167,50 +204,49 @@ class TagsPanelCtrl extends PureCtrl { } registerComponentHandler() { - this.unregisterComponent = this.application.componentManager.registerHandler({ + this.unregisterComponent = this.application.componentManager!.registerHandler({ identifier: 'tags', - areas: ['tags-list'], + areas: [ComponentArea.TagsList], activationHandler: (component) => { this.component = component; }, - contextRequestHandler: (component) => { - return null; + contextRequestHandler: () => { + return undefined; }, actionHandler: (_, action, data) => { - if (action === ComponentActions.SelectItem) { - if (data.item.content_type === ContentTypes.Tag) { - const tag = this.application.findItem({ uuid: data.item.uuid }); + if (action === ComponentAction.SelectItem) { + if (data.item.content_type === ContentType.Tag) { + const tag = this.application.findItem(data.item.uuid); if (tag) { - this.selectTag(tag); + this.selectTag(tag as SNTag); } - } else if (data.item.content_type === ContentTypes.SmartTag) { - this.application.createTemplateItem({ - contentType: ContentTypes.SmartTag, - content: data.item.content - }).then(smartTag => { - this.selectTag(smartTag); + } else if (data.item.content_type === ContentType.SmartTag) { + this.application.createTemplateItem( + ContentType.SmartTag, + data.item.content + ).then(smartTag => { + this.selectTag(smartTag as SNSmartTag); }); } - } else if (action === ComponentActions.ClearSelection) { + } else if (action === ComponentAction.ClearSelection) { this.selectTag(this.state.smartTags[0]); } } }); } - async selectTag(tag) { + async selectTag(tag: SNTag) { if (tag.isSmartTag()) { Object.defineProperty(tag, 'notes', { get: () => { - return this.application.getNotesMatchingSmartTag({ - smartTag: tag - }); + return this.application.notesMatchingSmartTag(tag as SNSmartTag); } }); } - if (tag.content.conflict_of) { - tag.content.conflict_of = null; - this.application.saveItem({ item: tag }); + if (tag.conflictOf) { + this.application.changeAndSaveItem(tag.uuid, (mutator) => { + mutator.conflictOf = undefined; + }) } this.application.getAppState().setSelectedTag(tag); } @@ -219,9 +255,9 @@ class TagsPanelCtrl extends PureCtrl { if (this.state.editingTag) { return; } - const newTag = await this.application.createTemplateItem({ - contentType: ContentTypes.Tag - }); + const newTag = await this.application.createTemplateItem( + ContentType.Tag + ); this.setState({ tags: [newTag].concat(this.state.tags), previousTag: this.state.selectedTag, @@ -231,14 +267,14 @@ class TagsPanelCtrl extends PureCtrl { }); } - tagTitleDidChange(tag) { + onTagTitleChange(tag: SNTag | SNSmartTag) { this.setState({ editingTag: tag }); } - async saveTag($event, tag) { - $event.target.blur(); + async saveTag($event: Event, tag: SNTag) { + ($event.target! as HTMLInputElement).blur(); await this.setState({ editingTag: null, }); @@ -246,8 +282,8 @@ class TagsPanelCtrl extends PureCtrl { if (!tag.title || tag.title.length === 0) { let newSelectedTag = this.state.selectedTag; if (this.state.editingTag) { - tag.title = this.editingOriginalName; - this.editingOriginalName = null; + this.titles[tag.uuid] = this.editingOriginalName; + this.editingOriginalName = undefined; } else if (this.state.newTag) { newSelectedTag = this.state.previousTag; } @@ -259,14 +295,14 @@ class TagsPanelCtrl extends PureCtrl { return; } - this.editingOriginalName = null; + this.editingOriginalName = undefined; - const matchingTag = this.application.findTag({ title: tag.title }); + const matchingTag = this.application.findTag(tag.title); const alreadyExists = matchingTag && matchingTag !== tag; if (this.state.newTag === tag && alreadyExists) { - this.application.alertService.alert({ - text: "A tag with this name already exists." - }); + this.application.alertService!.alert( + "A tag with this name already exists." + ); this.setState({ newTag: null, tags: this.getMappedTags(), @@ -274,40 +310,48 @@ class TagsPanelCtrl extends PureCtrl { }); return; } - - this.application.saveItem({ item: tag }); + this.application.changeAndSaveItem(tag.uuid, (mutator) => { + const tagMutator = mutator as TagMutator; + tagMutator.title = this.titles[tag.uuid]!; + }); this.selectTag(tag); this.setState({ newTag: null }); } - async selectedRenameTag($event, tag) { + async selectedRenameTag(tag: SNTag) { this.editingOriginalName = tag.title; await this.setState({ editingTag: tag }); - document.getElementById('tag-' + tag.uuid).focus(); + document.getElementById('tag-' + tag.uuid)!.focus(); } - selectedDeleteTag(tag) { + selectedDeleteTag(tag: SNTag) { this.removeTag(tag); } - removeTag(tag) { - this.application.alertService.confirm({ - text: STRING_DELETE_TAG, - destructive: true, - onConfirm: () => { - this.application.deleteItem({ item: tag }); + removeTag(tag: SNTag) { + this.application.alertService!.confirm( + STRING_DELETE_TAG, + undefined, + undefined, + undefined, + () => { + /* On confirm */ + this.application.deleteItem(tag); this.selectTag(this.state.smartTags[0]); - } - }); + }, + undefined, + true, + ); } } -export class TagsPanel { +export class TagsPanel extends WebDirective { constructor() { + super(); this.restrict = 'E'; this.scope = { application: '=' diff --git a/app/assets/javascripts/directives/views/revisionPreviewModal.js b/app/assets/javascripts/directives/views/revisionPreviewModal.js index 15a21b092..c58123280 100644 --- a/app/assets/javascripts/directives/views/revisionPreviewModal.js +++ b/app/assets/javascripts/directives/views/revisionPreviewModal.js @@ -27,7 +27,7 @@ class RevisionPreviewModalCtrl { async configure() { this.note = await this.application.createTemplateItem({ - contentType: ContentTypes.Note, + contentType: ContentType.Note, content: this.content }); @@ -45,7 +45,7 @@ class RevisionPreviewModalCtrl { * editor object has non-copyable properties like .window, which cannot be transfered */ const editorCopy = await this.application.createTemplateItem({ - contentType: ContentTypes.Component, + contentType: ContentType.Component, content: editorForNote.content }); this.application.component.setReadonlyStateForComponent(editorCopy, true, true); diff --git a/app/assets/javascripts/services/keyboardManager.js b/app/assets/javascripts/services/keyboardManager.js deleted file mode 100644 index 8cd3144a6..000000000 --- a/app/assets/javascripts/services/keyboardManager.js +++ /dev/null @@ -1,125 +0,0 @@ -/** @public */ -export const KeyboardKeys = { - Tab: "Tab", - Backspace: "Backspace", - Up: "ArrowUp", - Down: "ArrowDown", -}; -/** @public */ -export const KeyboardModifiers = { - Shift: "Shift", - Ctrl: "Control", - /** ⌘ key on Mac, ⊞ key on Windows */ - Meta: "Meta", - Alt: "Alt", -}; -/** @private */ -const KeyboardKeyEvents = { - Down: "KeyEventDown", - Up: "KeyEventUp" -}; - -export class KeyboardManager { - constructor() { - this.observers = []; - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleKeyUp = this.handleKeyUp.bind(this); - window.addEventListener('keydown', this.handleKeyDown); - window.addEventListener('keyup', this.handleKeyUp); - } - - /** @access public */ - deinit() { - this.observers.length = 0; - window.removeEventListener('keydown', this.handleKeyDown); - window.removeEventListener('keyup', this.handleKeyUp); - this.handleKeyDown = null; - this.handleKeyUp = null; - } - - modifiersForEvent(event) { - const allModifiers = Object.keys(KeyboardModifiers).map((key) => KeyboardModifiers[key]); - const eventModifiers = allModifiers.filter((modifier) => { - // For a modifier like ctrlKey, must check both event.ctrlKey and event.key. - // That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true. - const matches = ( - ((event.ctrlKey || event.key === KeyboardModifiers.Ctrl) && modifier === KeyboardModifiers.Ctrl) || - ((event.metaKey || event.key === KeyboardModifiers.Meta) && modifier === KeyboardModifiers.Meta) || - ((event.altKey || event.key === KeyboardModifiers.Alt) && modifier === KeyboardModifiers.Alt) || - ((event.shiftKey || event.key === KeyboardModifiers.Shift) && modifier === KeyboardModifiers.Shift) - ); - - return matches; - }); - - return eventModifiers; - } - - eventMatchesKeyAndModifiers(event, key, modifiers = []) { - const eventModifiers = this.modifiersForEvent(event); - - if (eventModifiers.length !== modifiers.length) { - return false; - } - - for (const modifier of modifiers) { - if (!eventModifiers.includes(modifier)) { - return false; - } - } - - // Modifers match, check key - if (!key) { - return true; - } - - // In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F' - // In our case we don't differentiate between the two. - return key.toLowerCase() === event.key.toLowerCase(); - } - - notifyObserver(event, keyEventType) { - for (const observer of this.observers) { - if (observer.element && event.target !== observer.element) { - continue; - } - - if (observer.elements && !observer.elements.includes(event.target)) { - continue; - } - - if (observer.notElement && observer.notElement === event.target) { - continue; - } - - if (observer.notElementIds && observer.notElementIds.includes(event.target.id)) { - continue; - } - - if (this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) { - const callback = keyEventType === KeyboardKeyEvents.Down ? observer.onKeyDown : observer.onKeyUp; - if (callback) { - callback(event); - } - } - } - } - - handleKeyDown(event) { - this.notifyObserver(event, KeyboardKeyEvents.Down); - } - - handleKeyUp(event) { - this.notifyObserver(event, KeyboardKeyEvents.Up); - } - - addKeyObserver({ key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds }) { - const observer = { key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds }; - this.observers.push(observer); - return observer; - } - - removeKeyObserver(observer) { - this.observers.splice(this.observers.indexOf(observer), 1); - } -} diff --git a/app/assets/javascripts/services/keyboardManager.ts b/app/assets/javascripts/services/keyboardManager.ts new file mode 100644 index 000000000..25aaf7ac2 --- /dev/null +++ b/app/assets/javascripts/services/keyboardManager.ts @@ -0,0 +1,147 @@ +import { removeFromArray } from 'snjs'; +export enum KeyboardKey { + Tab = "Tab", + Backspace = "Backspace", + Up = "ArrowUp", + Down = "ArrowDown", +}; + +export enum KeyboardModifier { + Shift = "Shift", + Ctrl = "Control", + /** ⌘ key on Mac, ⊞ key on Windows */ + Meta = "Meta", + Alt = "Alt", +}; + +enum KeyboardKeyEvent { + Down = "KeyEventDown", + Up = "KeyEventUp" +}; + +type KeyboardObserver = { + key?: KeyboardKey + modifiers?: KeyboardModifier[] + onKeyDown?: (event: KeyboardEvent) => void + onKeyUp?: (event: KeyboardEvent) => void + element?: HTMLElement + elements?: HTMLElement[] + notElement?: HTMLElement + notElementIds?: string[] +} + +export class KeyboardManager { + + private observers: KeyboardObserver[] = [] + private handleKeyDown: any + private handleKeyUp: any + + constructor() { + this.handleKeyDown = (event: KeyboardEvent) => { + this.notifyObserver(event, KeyboardKeyEvent.Down); + } + this.handleKeyUp = (event: KeyboardEvent) => { + this.notifyObserver(event, KeyboardKeyEvent.Up); + } + window.addEventListener('keydown', this.handleKeyDown); + window.addEventListener('keyup', this.handleKeyUp); + } + + public deinit() { + this.observers.length = 0; + window.removeEventListener('keydown', this.handleKeyDown); + window.removeEventListener('keyup', this.handleKeyUp); + this.handleKeyDown = undefined; + this.handleKeyUp = undefined; + } + + modifiersForEvent(event: KeyboardEvent) { + const allModifiers = Object.values(KeyboardModifier); + const eventModifiers = allModifiers.filter((modifier) => { + // For a modifier like ctrlKey, must check both event.ctrlKey and event.key. + // That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true. + const matches = ( + ( + (event.ctrlKey || event.key === KeyboardModifier.Ctrl) + && modifier === KeyboardModifier.Ctrl + ) || + ( + (event.metaKey || event.key === KeyboardModifier.Meta) + && modifier === KeyboardModifier.Meta + ) || + ( + (event.altKey || event.key === KeyboardModifier.Alt) + && modifier === KeyboardModifier.Alt + ) || + ( + (event.shiftKey || event.key === KeyboardModifier.Shift) + && modifier === KeyboardModifier.Shift + ) + ); + + return matches; + }); + + return eventModifiers; + } + + eventMatchesKeyAndModifiers( + event: KeyboardEvent, + key: KeyboardKey, + modifiers: KeyboardModifier[] = [] + ) { + const eventModifiers = this.modifiersForEvent(event); + if (eventModifiers.length !== modifiers.length) { + return false; + } + for (const modifier of modifiers) { + if (!eventModifiers.includes(modifier)) { + return false; + } + } + // Modifers match, check key + if (!key) { + return true; + } + // In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F' + // In our case we don't differentiate between the two. + return key.toLowerCase() === event.key.toLowerCase(); + } + + notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent) { + const target = event.target as HTMLElement; + for (const observer of this.observers) { + if (observer.element && event.target !== observer.element) { + continue; + } + + if (observer.elements && !observer.elements.includes(target)) { + continue; + } + + if (observer.notElement && observer.notElement === event.target) { + continue; + } + + if (observer.notElementIds && observer.notElementIds.includes(target.id)) { + continue; + } + + if (this.eventMatchesKeyAndModifiers(event, observer.key!, observer.modifiers)) { + const callback = keyEvent === KeyboardKeyEvent.Down + ? observer.onKeyDown + : observer.onKeyUp; + if (callback) { + callback(event); + } + } + } + } + + addKeyObserver(observer: KeyboardObserver) { + this.observers.push(observer); + return () => { + removeFromArray(this.observers, observer); + }; + } +} diff --git a/app/assets/javascripts/services/nativeExtManager.js b/app/assets/javascripts/services/nativeExtManager.js index c613c02d5..6548b3f0b 100644 --- a/app/assets/javascripts/services/nativeExtManager.js +++ b/app/assets/javascripts/services/nativeExtManager.js @@ -26,7 +26,7 @@ export class NativeExtManager extends ApplicationService { get extManagerPred() { const extManagerId = 'org.standardnotes.extensions-manager'; return SNPredicate.CompoundPredicate([ - new SNPredicate('content_type', '=', ContentTypes.Component), + new SNPredicate('content_type', '=', ContentType.Component), new SNPredicate('package_info.identifier', '=', extManagerId) ]); } @@ -34,7 +34,7 @@ export class NativeExtManager extends ApplicationService { get batchManagerPred() { const batchMgrId = 'org.standardnotes.batch-manager'; return SNPredicate.CompoundPredicate([ - new SNPredicate('content_type', '=', ContentTypes.Component), + new SNPredicate('content_type', '=', ContentType.Component), new SNPredicate('package_info.identifier', '=', batchMgrId) ]); } @@ -65,8 +65,8 @@ export class NativeExtManager extends ApplicationService { } // Handle addition of SN|ExtensionRepo permission const permission = extensionsManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION); - if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) { - permission.content_types.push(ContentTypes.ExtensionRepo); + if (!permission.content_types.includes(ContentType.ExtensionRepo)) { + permission.content_types.push(ContentType.ExtensionRepo); needsSync = true; } if (needsSync) { @@ -92,13 +92,13 @@ export class NativeExtManager extends ApplicationService { { name: STREAM_ITEMS_PERMISSION, content_types: [ - ContentTypes.Component, - ContentTypes.Theme, - ContentTypes.ServerExtension, - ContentTypes.ActionsExtension, - ContentTypes.Mfa, - ContentTypes.Editor, - ContentTypes.ExtensionRepo + ContentType.Component, + ContentType.Theme, + ContentType.ServerExtension, + ContentType.ActionsExtension, + ContentType.Mfa, + ContentType.Editor, + ContentType.ExtensionRepo ] } ] @@ -110,7 +110,7 @@ export class NativeExtManager extends ApplicationService { } const payload = CreateMaxPayloadFromAnyObject({ object: { - content_type: ContentTypes.Component, + content_type: ContentType.Component, content: content } }); @@ -146,7 +146,7 @@ export class NativeExtManager extends ApplicationService { } const payload = CreateMaxPayloadFromAnyObject({ object: { - content_type: ContentTypes.Component, + content_type: ContentType.Component, content: content } }); @@ -172,8 +172,8 @@ export class NativeExtManager extends ApplicationService { } // Handle addition of SN|ExtensionRepo permission const permission = batchManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION); - if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) { - permission.content_types.push(ContentTypes.ExtensionRepo); + if (!permission.content_types.includes(ContentType.ExtensionRepo)) { + permission.content_types.push(ContentType.ExtensionRepo); needsSync = true; } if (needsSync) { diff --git a/app/assets/javascripts/services/preferencesManager.js b/app/assets/javascripts/services/preferencesManager.js index 9a867765c..0fa19db69 100644 --- a/app/assets/javascripts/services/preferencesManager.js +++ b/app/assets/javascripts/services/preferencesManager.js @@ -33,7 +33,7 @@ export class PreferencesManager extends ApplicationService { streamPreferences() { this.application.streamItems({ - contentType: ContentTypes.UserPrefs, + contentType: ContentType.UserPrefs, stream: () => { this.loadSingleton(); } @@ -41,7 +41,7 @@ export class PreferencesManager extends ApplicationService { } async loadSingleton() { - const contentType = ContentTypes.UserPrefs; + const contentType = ContentType.UserPrefs; const predicate = new SNPredicate('content_type', '=', contentType); this.userPreferences = await this.application.singletonManager.findOrCreateSingleton({ predicate: predicate, diff --git a/app/assets/javascripts/services/state.ts b/app/assets/javascripts/services/state.ts index f4b4c7d56..4935ded9c 100644 --- a/app/assets/javascripts/services/state.ts +++ b/app/assets/javascripts/services/state.ts @@ -140,7 +140,7 @@ export class AppState { ); } - async setSelectedNote(note: SNNote) { + async setSelectedNote(note?: SNNote) { const run = async () => { const previousNote = this.selectedNote; this.selectedNote = note; @@ -166,6 +166,12 @@ export class AppState { } } + getNoteTags(note: SNNote) { + return this.application.referencesForItem(note).filter((ref) => { + return ref.content_type === note.content_type; + }) as SNTag[] + } + getSelectedTag() { return this.selectedTag; } diff --git a/app/assets/javascripts/types.ts b/app/assets/javascripts/types.ts index c7b4f9538..2537af606 100644 --- a/app/assets/javascripts/types.ts +++ b/app/assets/javascripts/types.ts @@ -3,6 +3,7 @@ export class WebDirective implements ng.IDirective { controllerAs?: string; bindToController?: boolean | { [boundProperty: string]: string }; restrict?: string; + replace?: boolean scope?: boolean | { [boundProperty: string]: string }; template?: string | ((tElement: any, tAttrs: any) => string); } @@ -15,4 +16,13 @@ export enum PasswordWizardType { export interface PasswordWizardScope extends Partial { type: PasswordWizardType, application: any +} + +export type PanelPuppet = { + onReady: () => void + ready?: boolean + setWidth?: (width: number) => void + setLeft?: (left: number) => void + isCollapsed?: () => boolean + flash?: () => void } \ No newline at end of file diff --git a/app/assets/templates/editor.pug b/app/assets/templates/editor.pug index 3a918864e..7cc05d44c 100644 --- a/app/assets/templates/editor.pug +++ b/app/assets/templates/editor.pug @@ -17,12 +17,12 @@ ) .title input#note-title-editor.input( - ng-blur='self.onNameBlur()', + ng-blur='self.onTitleBlur()', ng-change='self.onTitleChange()', ng-disabled='self.noteLocked', - ng-focus='self.onNameFocus()', + ng-focus='self.onTitleFocus()', ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)', - ng-model='self.state.note.title', + ng-model='self.editorValues.title', select-on-click='true', spellcheck='false') #save-status @@ -39,11 +39,11 @@ application='self.application' ) input.tags-input( - ng-blur='self.saveTags()', + ng-blur='self.saveTagsFromStrings()', ng-disabled='self.noteLocked', ng-if='!(self.state.tagsComponent && self.state.tagsComponent.active)', ng-keyup='$event.keyCode == 13 && $event.target.blur();', - ng-model='self.state.mutable.tagsString', + ng-model='self.editorValues.tagsInputValue', placeholder='#tags', spellcheck='false', type='text' @@ -85,7 +85,7 @@ ) menu-row( action='self.selectedMenuItem(true); self.toggleNotePreview()', - circle="self.state.note.content.hidePreview ? 'danger' : 'success'", + circle="self.state.note.hidePreview ? 'danger' : 'success'", circle-align="'right'", desc="'Hide or unhide the note preview from the list of notes'", label="'Preview'" @@ -130,7 +130,7 @@ .sk-menu-panel-header .sk-menu-panel-header-title Global Display menu-row( - action="self.selectedMenuItem(true); self.toggleKey(self.prefKeyMonospace)", + action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeyMonospace)", circle="self.state.monospaceEnabled ? 'success' : 'neutral'", desc="'Toggles the font style for the default editor'", disabled='self.state.selectedEditor', @@ -138,7 +138,7 @@ subtitle="self.state.selectedEditor ? 'Not available with editor extensions' : null" ) menu-row( - action="self.selectedMenuItem(true); self.toggleKey(self.prefKeySpellcheck)", + action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeySpellcheck)", circle="self.state.spellcheck ? 'success' : 'neutral'", desc="'Toggles spellcheck for the default editor'", disabled='self.state.selectedEditor', @@ -149,7 +149,7 @@ : (self.state.isDesktop ? 'May degrade editor performance' : null) `) menu-row( - action="self.selectedMenuItem(true); self.toggleKey(self.prefKeyMarginResizers)", + action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeyMarginResizers)", circle="self.state.marginResizersEnabled ? 'success' : 'neutral'", desc="'Allows for editor left and right margins to be resized'", faded='!self.state.marginResizersEnabled', @@ -217,7 +217,7 @@ ng-click='self.clickedTextArea()', ng-focus='self.onContentFocus()', ng-if='!self.state.selectedEditor', - ng-model='self.state.note.text', + ng-model='self.editorValues.text', ng-model-options='{ debounce: self.state.editorDebounce}', ng-readonly='self.noteLocked', ng-trim='false' diff --git a/app/assets/templates/notes.pug b/app/assets/templates/notes.pug index 742a06e47..c2a77497f 100644 --- a/app/assets/templates/notes.pug +++ b/app/assets/templates/notes.pug @@ -120,8 +120,8 @@ .note-preview( ng-if=` !self.state.hideNotePreview && - !note.content.hidePreview && - !note.content.protected` + !note.hidePreview && + !note.protected` ) .html-preview( ng-bind-html='note.content.preview_html', diff --git a/app/assets/templates/tags.pug b/app/assets/templates/tags.pug index 999caf46c..babc71fba 100644 --- a/app/assets/templates/tags.pug +++ b/app/assets/templates/tags.pug @@ -23,7 +23,11 @@ ng-repeat='tag in self.state.smartTags' ) .tag-info - input.title(ng-disabled='true', ng-model='tag.title') + input.title( + ng-disabled='true', + ng-change='self.onTagTitleChange(tag)' + ng-model='self.titles[tag.uuid]' + ) .count(ng-show='tag.content.isAllTag') {{self.state.noteCounts[tag.uuid]}} .tags-title-section.section-title-bar .section-title-bar-header @@ -38,12 +42,12 @@ .tag-icon # input.title( ng-attr-id='tag-{{tag.uuid}}', - ng-blur='self.saveTag($event, tag)', - ng-change='self.tagTitleDidChange(tag)', + ng-blur='self.saveTag($event, tag)' + ng-change='self.onTagTitleChange(tag)', + ng-model='self.titles[tag.uuid]', ng-class="{'editing' : self.state.editingTag == tag}", ng-click='self.selectTag(tag)', ng-keyup='$event.keyCode == 13 && $event.target.blur()', - ng-model='tag.title', should-focus='self.state.newTag || self.state.editingTag == tag', sn-autofocus='true', spellcheck='false' @@ -53,8 +57,8 @@ .danger.small-text.bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys .info.small-text.bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys .menu(ng-show='self.state.selectedTag == tag') - a.item(ng-click='self.selectedRenameTag($event, tag)', ng-show='!self.state.editingTag') Rename - a.item(ng-click='self.saveTag($event, tag)', ng-show='self.state.editingTag') Save + a.item(ng-click='self.selectedRenameTag(tag)' ng-show='!self.state.editingTag') Rename + a.item(ng-click='self.saveTag($event, tag)' ng-show='self.state.editingTag') Save a.item(ng-click='self.selectedDeleteTag(tag)') Delete .no-tags-placeholder(ng-show='self.state.tags.length == 0') | No tags. Create one using the add button above.