From 3542cba002132fef58543d59d837d0841a4b2690 Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 11 Apr 2020 23:26:02 -0500 Subject: [PATCH] Notes TypeScript --- .../controllers/abstract/pure_ctrl.ts | 8 +- .../notes/{note_utils.js => note_utils.ts} | 96 ++--- .../controllers/notes/{notes.js => notes.ts} | 392 ++++++++++-------- .../javascripts/services/keyboardManager.ts | 4 +- app/assets/javascripts/services/state.ts | 34 +- app/assets/javascripts/types.ts | 2 +- app/assets/templates/notes.pug | 10 +- 7 files changed, 301 insertions(+), 245 deletions(-) rename app/assets/javascripts/controllers/notes/{note_utils.js => note_utils.ts} (66%) rename app/assets/javascripts/controllers/notes/{notes.js => notes.ts} (57%) diff --git a/app/assets/javascripts/controllers/abstract/pure_ctrl.ts b/app/assets/javascripts/controllers/abstract/pure_ctrl.ts index b894dc36c..96caf59a2 100644 --- a/app/assets/javascripts/controllers/abstract/pure_ctrl.ts +++ b/app/assets/javascripts/controllers/abstract/pure_ctrl.ts @@ -1,3 +1,4 @@ +import { AppStateEvent } from '@/services/state'; import { WebApplication } from './../../application'; import { ApplicationEvent } from 'snjs'; @@ -82,10 +83,11 @@ export class PureCtrl { } addAppStateObserver() { - this.unsubState = this.application!.getAppState() - .addObserver((eventName: any, data: any) => { + this.unsubState = this.application!.getAppState().addObserver( + async (eventName, data) => { this.onAppStateEvent(eventName, data); - }); + } + ); } onAppStateEvent(eventName: any, data: any) { diff --git a/app/assets/javascripts/controllers/notes/note_utils.js b/app/assets/javascripts/controllers/notes/note_utils.ts similarity index 66% rename from app/assets/javascripts/controllers/notes/note_utils.js rename to app/assets/javascripts/controllers/notes/note_utils.ts index e4459eb9e..61feb3749 100644 --- a/app/assets/javascripts/controllers/notes/note_utils.js +++ b/app/assets/javascripts/controllers/notes/note_utils.ts @@ -1,51 +1,55 @@ -export const SORT_KEY_CREATED_AT = 'created_at'; -export const SORT_KEY_UPDATED_AT = 'updated_at'; -export const SORT_KEY_CLIENT_UPDATED_AT = 'client_updated_at'; -export const SORT_KEY_TITLE = 'title'; +import { SNNote, SNTag } from 'snjs'; -export function filterAndSortNotes({ - notes, - selectedTag, - showArchived, - hidePinned, - filterText, - sortBy, - reverse -}) { - const filtered = filterNotes({ +export enum NoteSortKey { + CreatedAt = 'created_at', + UpdatedAt = 'updated_at', + ClientUpdatedAt = 'client_updated_at', + Title = 'title', +} + +export function filterAndSortNotes( + notes: SNNote[], + selectedTag: SNTag, + showArchived: boolean, + hidePinned: boolean, + filterText: string, + sortBy: string, + reverse: boolean, +) { + const filtered = filterNotes( notes, selectedTag, showArchived, hidePinned, filterText, - }); - const sorted = sortNotes({ - notes: filtered, + ); + const sorted = sortNotes( + filtered, sortBy, reverse - }); + ); return sorted; } -export function filterNotes({ - notes, - selectedTag, - showArchived, - hidePinned, - filterText -}) { +export function filterNotes( + notes: SNNote[], + selectedTag: SNTag, + showArchived: boolean, + hidePinned: boolean, + filterText: string +) { return notes.filter((note) => { let canShowArchived = showArchived; const canShowPinned = !hidePinned; - const isTrash = selectedTag.content.isTrashTag; - if (!isTrash && note.content.trashed) { + const isTrash = selectedTag.isTrashTag; + if (!isTrash && note.trashed) { return false; } const isSmartTag = selectedTag.isSmartTag(); if (isSmartTag) { canShowArchived = ( canShowArchived || - selectedTag.content.isArchiveTag || + selectedTag.isArchiveTag || isTrash ); } @@ -55,17 +59,14 @@ export function filterNotes({ ) { return false; } - return noteMatchesQuery({ - note, - query: filterText - }); + return noteMatchesQuery(note, filterText); }); } -function noteMatchesQuery({ - note, - query -}) { +function noteMatchesQuery( + note: SNNote, + query: string +) { if(query.length === 0) { return true; } @@ -93,12 +94,12 @@ function noteMatchesQuery({ return matchesTitle || matchesBody; } -function stringBetweenQuotes(text) { +function stringBetweenQuotes(text: string) { const matches = text.match(/"(.*?)"/); return matches ? matches[1] : null; } -function stringIsUuid(text) { +function stringIsUuid(text: string) { const matches = text.match( /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/ ); @@ -106,12 +107,12 @@ function stringIsUuid(text) { return matches ? true : false; } -export function sortNotes({ - notes = [], - sortBy, - reverse -}) { - const sortValueFn = (a, b, pinCheck = false) => { +export function sortNotes( + notes: SNNote[] = [], + sortBy: string, + reverse: boolean +) { + const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => { if (a.dummy) { return -1; } if (b.dummy) { return 1; } if (!pinCheck) { @@ -121,14 +122,13 @@ export function sortNotes({ if (a.pinned) { return -1; } if (b.pinned) { return 1; } } - - let aValue = a[sortBy] || ''; - let bValue = b[sortBy] || ''; + let aValue = (a as any)[sortBy] || ''; + let bValue = (a as any)[sortBy] || ''; let vector = 1; if (reverse) { vector *= -1; } - if (sortBy === SORT_KEY_TITLE) { + if (sortBy === NoteSortKey.Title) { aValue = aValue.toLowerCase(); bValue = bValue.toLowerCase(); if (aValue.length === 0 && bValue.length === 0) { diff --git a/app/assets/javascripts/controllers/notes/notes.js b/app/assets/javascripts/controllers/notes/notes.ts similarity index 57% rename from app/assets/javascripts/controllers/notes/notes.js rename to app/assets/javascripts/controllers/notes/notes.ts index 5c682bf56..45a2400d7 100644 --- a/app/assets/javascripts/controllers/notes/notes.js +++ b/app/assets/javascripts/controllers/notes/notes.ts @@ -1,7 +1,8 @@ +import { PanelPuppet, WebDirective } from './../../types'; import angular from 'angular'; import template from '%/notes.pug'; -import { ApplicationEvent, ContentTypes, removeFromArray } from 'snjs'; -import { PureCtrl } from '@Controllers'; +import { ApplicationEvent, ContentType, removeFromArray, SNNote, SNTag } from 'snjs'; +import { PureCtrl } from '@Controllers/abstract/pure_ctrl'; import { AppStateEvent } from '@/services/state'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { @@ -11,12 +12,31 @@ import { PANEL_NAME_NOTES } from '@/controllers/constants'; import { - SORT_KEY_CREATED_AT, - SORT_KEY_UPDATED_AT, - SORT_KEY_CLIENT_UPDATED_AT, - SORT_KEY_TITLE, + NoteSortKey, filterAndSortNotes } from './note_utils'; +import { UuidString } from '@/../../../../snjs/dist/@types/types'; + +type NotesState = { + tag?: SNTag + notes?: SNNote[] + renderedNotes?: SNNote[] + selectedNote?: SNNote + sortBy?: string + sortReverse?: boolean + showArchived?: boolean + hidePinned?: boolean + hideNotePreview?: boolean + hideDate?: boolean + hideTags?: boolean + noteFilter: { text: string } + mutable: { showMenu: boolean } +} + +type NoteFlag = { + text: string + class: 'info' | 'neutral' | 'warning' | 'success' | 'danger' +} /** * This is the height of a note cell with nothing but the title, @@ -29,10 +49,19 @@ const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable'; class NotesCtrl extends PureCtrl { + private panelPuppet?: PanelPuppet + private reloadNotesPromise?: any + private notesToDisplay = 0 + private pageSize = 0 + private searchSubmitted = false + private newNoteKeyObserver: any + private nextNoteKeyObserver: any + private previousNoteKeyObserver: any + private searchKeyObserver: any + private noteFlags: Partial> = {} + /* @ngInject */ - constructor( - $timeout, - ) { + constructor($timeout: ng.ITimeoutService, ) { super($timeout); this.resetPagination(); } @@ -46,53 +75,62 @@ class NotesCtrl extends PureCtrl { onReady: () => this.reloadPreferences() }; this.onWindowResize = this.onWindowResize.bind(this); + this.onPanelResize = this.onPanelResize.bind(this); window.addEventListener('resize', this.onWindowResize, true); this.registerKeyboardShortcuts(); } onWindowResize() { - this.resetPagination({ - keepCurrentIfLarger: true - }); + this.resetPagination(true); } deinit() { - this.panelPuppet.onReady = null; - this.panelPuppet = null; + this.panelPuppet!.onReady = undefined; + this.panelPuppet = undefined; window.removeEventListener('resize', this.onWindowResize, true); - this.onWindowResize = null; - this.onPanelResize = null; + (this.onWindowResize as any) = undefined; + (this.onPanelResize as any) = undefined; + this.newNoteKeyObserver(); + this.nextNoteKeyObserver(); + this.previousNoteKeyObserver(); + this.searchKeyObserver(); + this.newNoteKeyObserver = undefined; + this.nextNoteKeyObserver = undefined; + this.previousNoteKeyObserver = undefined; + this.searchKeyObserver = undefined; super.deinit(); } + getState() { + return this.state as NotesState; + } + getInitialState() { return { notes: [], renderedNotes: [], - selectedNote: null, - tag: null, - sortBy: null, - showArchived: null, - hidePinned: null, - sortReverse: null, - panelTitle: null, mutable: { showMenu: false }, noteFilter: { text: '' }, - }; + } as NotesState; } - onAppLaunch() { + async onAppLaunch() { super.onAppLaunch(); this.streamNotesAndTags(); this.reloadPreferences(); } /** @override */ - onAppStateEvent(eventName, data) { + onAppStateEvent(eventName: AppStateEvent, data?: any) { if (eventName === AppStateEvent.TagChanged) { - this.handleTagChange(this.application.getAppState().getSelectedTag(), data.previousTag); + this.handleTagChange( + this.application!.getAppState().getSelectedTag()!, + data.previousTag + ); } else if (eventName === AppStateEvent.NoteChanged) { - this.handleNoteSelection(this.application.getAppState().getSelectedNote()); + this.handleNoteSelection( + this.application!.getAppState().getSelectedNote()! + ); } else if (eventName === AppStateEvent.PreferencesChanged) { this.reloadPreferences(); this.reloadNotes(); @@ -102,12 +140,12 @@ class NotesCtrl extends PureCtrl { } /** @override */ - async onAppEvent(eventName) { + async onAppEvent(eventName: ApplicationEvent) { if (eventName === ApplicationEvent.SignedIn) { /** Delete dummy note if applicable */ - if (this.state.selectedNote && this.state.selectedNote.dummy) { - this.application.deleteItemLocally({ item: this.state.selectedNote }); - await this.selectNote(null); + if (this.getState().selectedNote && this.getState().selectedNote!.dummy) { + this.application!.deleteItemLocally(this.getState().selectedNote!); + await this.selectNote(undefined); await this.reloadNotes(); } } else if (eventName === ApplicationEvent.CompletedSync) { @@ -125,7 +163,7 @@ class NotesCtrl extends PureCtrl { * that may be in progress. This is the sync alternative to `async getMostValidNotes` */ getPossiblyStaleNotes() { - return this.state.notes; + return this.getState().notes!; } /** @@ -146,21 +184,21 @@ class NotesCtrl extends PureCtrl { * @access private */ async createPlaceholderNote() { - const selectedTag = this.application.getAppState().getSelectedTag(); - if (selectedTag.isSmartTag() && !selectedTag.content.isAllTag) { + const selectedTag = this.application!.getAppState().getSelectedTag()!; + if (selectedTag.isSmartTag() && !selectedTag.isAllTag) { return; } return this.createNewNote(); } streamNotesAndTags() { - this.application.streamItems({ - contentType: [ContentType.Note, ContentType.Tag], - stream: async ({ items }) => { + this.application!.streamItems( + [ContentType.Note, ContentType.Tag], + async (items) => { await this.reloadNotes(); - const selectedNote = this.state.selectedNote; + const selectedNote = this.getState().selectedNote; if (selectedNote) { - const discarded = selectedNote.deleted || selectedNote.content.trashed; + const discarded = selectedNote.deleted || selectedNote.trashed; if (discarded) { this.selectNextOrCreateNew(); } @@ -169,87 +207,83 @@ class NotesCtrl extends PureCtrl { } /** Note has changed values, reset its flags */ - const notes = items.filter((item) => item.content_type === ContentType.Note); + const notes = items.filter((item) => item.content_type === ContentType.Note) as SNNote[]; for (const note of notes) { - if(note.deleted) { + if (note.deleted) { continue; } this.loadFlagsForNote(note); - note.cachedCreatedAtString = note.createdAtString(); - note.cachedUpdatedAtString = note.updatedAtString(); } } - }); + ); } - async selectNote(note) { - return this.application.getAppState().setSelectedNote(note); + async selectNote(note?: SNNote) { + return this.application!.getAppState().setSelectedNote(note); } async createNewNote() { - const selectedTag = this.application.getAppState().getSelectedTag(); + const selectedTag = this.application!.getAppState().getSelectedTag(); if (!selectedTag) { throw 'Attempting to create note with no selected tag'; } let title; let isDummyNote = true; if (this.isFiltering()) { - title = this.state.noteFilter.text; + title = this.getState().noteFilter.text; isDummyNote = false; - } else if (this.state.selectedNote && this.state.selectedNote.dummy) { + } else if (this.getState().selectedNote && this.getState().selectedNote!.dummy) { return; } else { - title = `Note ${this.state.notes.length + 1}`; + title = `Note ${this.getState().notes!.length + 1}`; } - const newNote = await this.application.createManagedItem({ - contentType: ContentType.Note, - content: { + const newNote = await this.application!.createManagedItem( + ContentType.Note, + { text: '', - title: title + title: title, + references: [] }, - override: { - dummy: isDummyNote, - client_updated_at: new Date() + true, + { + dummy: isDummyNote } - }); - this.application.setItemNeedsSync({ item: newNote }); + ) as SNNote; if (!selectedTag.isSmartTag()) { - selectedTag.addItemAsRelationship(newNote); - this.application.setItemNeedsSync({ item: selectedTag }); + this.application!.changeItem(selectedTag.uuid, (mutator) => { + mutator.addItemAsRelationship(newNote); + }); } this.selectNote(newNote); } - async handleTagChange(tag, previousTag) { - if (this.state.selectedNote && this.state.selectedNote.dummy) { - await this.application.deleteItemLocally({ item: this.state.selectedNote }); - if (previousTag) { - removeFromArray(previousTag.notes, this.state.selectedNote); - } - await this.selectNote(null); + async handleTagChange(tag: SNTag, previousTag?: SNTag) { + if (this.getState().selectedNote && this.getState().selectedNote!.dummy) { + await this.application!.deleteItemLocally(this.getState().selectedNote!); + await this.selectNote(undefined); } await this.setState({ tag: tag }); this.resetScrollPosition(); this.setShowMenuFalse(); await this.setNoteFilterText(''); - this.application.getDesktopService().searchText(); + this.application!.getDesktopService().searchText(); this.resetPagination(); /* 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(); - if (this.state.notes.length > 0) { + if (this.getState().notes!.length > 0) { this.selectFirstNote(); } else if (dbLoaded) { - if (!tag.isSmartTag() || tag.content.isAllTag) { + if (!tag.isSmartTag() || tag.isAllTag) { this.createPlaceholderNote(); } else if ( - this.state.selectedNote && - !this.state.notes.includes(this.state.selectedNote) + this.getState().selectedNote && + !this.getState().notes!.includes(this.getState().selectedNote!) ) { - this.selectNote(null); + this.selectNote(undefined); } } } @@ -262,8 +296,8 @@ class NotesCtrl extends PureCtrl { } } - async removeNoteFromList(note) { - const notes = this.state.notes; + async removeNoteFromList(note: SNNote) { + const notes = this.getState().notes!; removeFromArray(notes, note); await this.setState({ notes: notes, @@ -277,23 +311,24 @@ class NotesCtrl extends PureCtrl { } async performPeloadNotes() { - if (!this.state.tag) { + const tag = this.getState().tag!; + if (!tag) { return; } - const notes = filterAndSortNotes({ - notes: this.state.tag.notes, - selectedTag: this.state.tag, - showArchived: this.state.showArchived, - hidePinned: this.state.hidePinned, - filterText: this.state.noteFilter.text.toLowerCase(), - sortBy: this.state.sortBy, - reverse: this.state.sortReverse - }); + const tagNotes = this.appState.getTagNotes(tag); + const notes = filterAndSortNotes( + tagNotes, + tag, + this.getState().showArchived!, + this.getState().hidePinned!, + this.getState().noteFilter.text.toLowerCase(), + this.getState().sortBy!, + this.getState().sortReverse! + ); for (const note of notes) { if (note.errorDecrypting) { this.loadFlagsForNote(note); } - note.shouldShowTags = this.shouldShowTagsForNote(note); } await this.setState({ notes: notes, @@ -305,19 +340,19 @@ class NotesCtrl extends PureCtrl { setShowMenuFalse() { this.setState({ mutable: { - ...this.state.mutable, + ...this.getState().mutable, showMenu: false } }); } - async handleNoteSelection(note) { - const previousNote = this.state.selectedNote; + async handleNoteSelection(note: SNNote) { + const previousNote = this.getState().selectedNote; if (previousNote === note) { return; } if (previousNote && previousNote.dummy) { - await this.application.deleteItemLocally({ item: previousNote }); + await this.application!.deleteItemLocally(previousNote); this.removeNoteFromList(previousNote); } await this.setState({ @@ -326,49 +361,49 @@ class NotesCtrl extends PureCtrl { if (!note) { return; } - this.selectedIndex = Math.max(0, this.displayableNotes().indexOf(note)); - if (note.content.conflict_of) { - note.content.conflict_of = null; - this.application.saveItem({ item: note }); + if (note.conflictOf) { + this.application!.changeAndSaveItem(note.uuid, (mutator) => { + mutator.conflictOf = undefined; + }) } if (this.isFiltering()) { - this.application.getDesktopService().searchText(this.state.noteFilter.text); + this.application!.getDesktopService().searchText(this.getState().noteFilter.text); } } reloadPreferences() { - const viewOptions = {}; - const prevSortValue = this.state.sortBy; - let sortBy = this.application.getPrefsService().getValue( + const viewOptions = {} as NotesState; + const prevSortValue = this.getState().sortBy; + let sortBy = this.application!.getPrefsService().getValue( PrefKeys.SortNotesBy, - SORT_KEY_CREATED_AT + NoteSortKey.CreatedAt ); - if (sortBy === SORT_KEY_UPDATED_AT) { + if (sortBy === NoteSortKey.UpdatedAt) { /** Use client_updated_at instead */ - sortBy = SORT_KEY_CLIENT_UPDATED_AT; + sortBy = NoteSortKey.ClientUpdatedAt; } viewOptions.sortBy = sortBy; - viewOptions.sortReverse = this.application.getPrefsService().getValue( + viewOptions.sortReverse = this.application!.getPrefsService().getValue( PrefKeys.SortNotesReverse, false ); - viewOptions.showArchived = this.application.getPrefsService().getValue( + viewOptions.showArchived = this.application!.getPrefsService().getValue( PrefKeys.NotesShowArchived, false ); - viewOptions.hidePinned = this.application.getPrefsService().getValue( + viewOptions.hidePinned = this.application!.getPrefsService().getValue( PrefKeys.NotesHidePinned, false ); - viewOptions.hideNotePreview = this.application.getPrefsService().getValue( + viewOptions.hideNotePreview = this.application!.getPrefsService().getValue( PrefKeys.NotesHideNotePreview, false ); - viewOptions.hideDate = this.application.getPrefsService().getValue( + viewOptions.hideDate = this.application!.getPrefsService().getValue( PrefKeys.NotesHideDate, false ); - viewOptions.hideTags = this.application.getPrefsService().getValue( + viewOptions.hideTags = this.application!.getPrefsService().getValue( PrefKeys.NotesHideTags, false ); @@ -378,27 +413,32 @@ class NotesCtrl extends PureCtrl { if (prevSortValue && prevSortValue !== sortBy) { this.selectFirstNote(); } - const width = this.application.getPrefsService().getValue( + const width = this.application!.getPrefsService().getValue( PrefKeys.NotesPanelWidth ); - if (width && this.panelPuppet.ready) { - this.panelPuppet.setWidth(width); - if (this.panelPuppet.isCollapsed()) { - this.application.getAppState().panelDidResize( + if (width && this.panelPuppet!.ready) { + this.panelPuppet!.setWidth!(width); + if (this.panelPuppet!.isCollapsed!()) { + this.application!.getAppState().panelDidResize( PANEL_NAME_NOTES, - this.panelPuppet.isCollapsed() + this.panelPuppet!.isCollapsed!() ); } } } - onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => { - this.application.getPrefsService().setUserPrefValue( + onPanelResize( + newWidth: number, + lastLeft: number, + isAtMaxWidth: boolean, + isCollapsed: boolean + ) { + this.application!.getPrefsService().setUserPrefValue( PrefKeys.NotesPanelWidth, newWidth ); - this.application.getPrefsService().syncUserPreferences(); - this.application.getAppState().panelDidResize( + this.application!.getPrefsService().syncUserPreferences(); + this.application!.getAppState().panelDidResize( PANEL_NAME_NOTES, isCollapsed ); @@ -408,11 +448,11 @@ class NotesCtrl extends PureCtrl { this.notesToDisplay += this.pageSize; this.reloadNotes(); if (this.searchSubmitted) { - this.application.getDesktopService().searchText(this.state.noteFilter.text); + this.application!.getDesktopService().searchText(this.getState().noteFilter.text); } } - resetPagination({ keepCurrentIfLarger } = {}) { + resetPagination(keepCurrentIfLarger = false) { const clientHeight = document.documentElement.clientHeight; this.pageSize = Math.ceil(clientHeight / MIN_NOTE_CELL_HEIGHT); if (this.pageSize === 0) { @@ -427,10 +467,10 @@ class NotesCtrl extends PureCtrl { reloadPanelTitle() { let title; if (this.isFiltering()) { - const resultCount = this.state.notes.length; + const resultCount = this.getState().notes!.length; title = `${resultCount} search results`; - } else if (this.state.tag) { - title = `${this.state.tag.title}`; + } else if (this.getState().tag) { + title = `${this.getState().tag!.title}`; } this.setState({ panelTitle: title @@ -439,27 +479,27 @@ class NotesCtrl extends PureCtrl { optionsSubtitle() { let base = ""; - if (this.state.sortBy === 'created_at') { + if (this.getState().sortBy === 'created_at') { base += " Date Added"; - } else if (this.state.sortBy === 'client_updated_at') { + } else if (this.getState().sortBy === 'client_updated_at') { base += " Date Modified"; - } else if (this.state.sortBy === 'title') { + } else if (this.getState().sortBy === 'title') { base += " Title"; } - if (this.state.showArchived) { + if (this.getState().showArchived) { base += " | + Archived"; } - if (this.state.hidePinned) { + if (this.getState().hidePinned) { base += " | – Pinned"; } - if (this.state.sortReverse) { + if (this.getState().sortReverse) { base += " | Reversed"; } return base; } - loadFlagsForNote(note) { - const flags = []; + loadFlagsForNote(note: SNNote) { + const flags = [] as NoteFlag[]; if (note.pinned) { flags.push({ text: "Pinned", @@ -472,7 +512,7 @@ class NotesCtrl extends PureCtrl { class: 'warning' }); } - if (note.content.protected) { + if (note.protected) { flags.push({ text: "Protected", class: 'success' @@ -484,13 +524,13 @@ class NotesCtrl extends PureCtrl { class: 'neutral' }); } - if (note.content.trashed) { + if (note.trashed) { flags.push({ text: "Deleted", class: 'danger' }); } - if (note.content.conflict_of) { + if (note.conflictOf) { flags.push({ text: "Conflicted Copy", class: 'danger' @@ -515,19 +555,19 @@ class NotesCtrl extends PureCtrl { class: 'danger' }); } - note.flags = flags; + this.noteFlags[note.uuid] = flags; return flags; } displayableNotes() { - return this.state.notes; + return this.getState().notes!; } getFirstNonProtectedNote() { const displayableNotes = this.displayableNotes(); let index = 0; let note = displayableNotes[index]; - while (note && note.content.protected) { + while (note && note.protected) { index++; if (index >= displayableNotes.length) { break; @@ -546,7 +586,9 @@ class NotesCtrl extends PureCtrl { selectNextNote() { const displayableNotes = this.displayableNotes(); - const currentIndex = displayableNotes.indexOf(this.state.selectedNote); + const currentIndex = displayableNotes.findIndex((candidate) => { + return candidate.uuid === this.getState().selectedNote!.uuid + }); if (currentIndex + 1 < displayableNotes.length) { this.selectNote(displayableNotes[currentIndex + 1]); } @@ -556,16 +598,16 @@ class NotesCtrl extends PureCtrl { const note = this.getFirstNonProtectedNote(); if (note) { this.selectNote(note); - } else if (!this.state.tag || !this.state.tag.isSmartTag()) { + } else if (!this.getState().tag || !this.getState().tag!.isSmartTag()) { this.createPlaceholderNote(); } else { - this.selectNote(null); + this.selectNote(undefined); } } selectPreviousNote() { const displayableNotes = this.displayableNotes(); - const currentIndex = displayableNotes.indexOf(this.state.selectedNote); + const currentIndex = displayableNotes.indexOf(this.getState().selectedNote!); if (currentIndex - 1 >= 0) { this.selectNote(displayableNotes[currentIndex - 1]); return true; @@ -575,14 +617,14 @@ class NotesCtrl extends PureCtrl { } isFiltering() { - return this.state.noteFilter.text && - this.state.noteFilter.text.length > 0; + return this.getState().noteFilter.text && + this.getState().noteFilter.text.length > 0; } - async setNoteFilterText(text) { + async setNoteFilterText(text: string) { await this.setState({ noteFilter: { - ...this.state.noteFilter, + ...this.getState().noteFilter, text: text } }); @@ -609,66 +651,49 @@ class NotesCtrl extends PureCtrl { * enter before highlighting desktop search results. */ this.searchSubmitted = true; - this.application.getDesktopService().searchText(this.state.noteFilter.text); + this.application!.getDesktopService().searchText(this.getState().noteFilter.text); } selectedMenuItem() { this.setShowMenuFalse(); } - togglePrefKey(key) { - this.application.getPrefsService().setUserPrefValue(key, !this.state[key]); - this.application.getPrefsService().syncUserPreferences(); + togglePrefKey(key: string) { + this.application!.getPrefsService().setUserPrefValue(key, !this.state[key]); + this.application!.getPrefsService().syncUserPreferences(); } selectedSortByCreated() { - this.setSortBy(SORT_KEY_CREATED_AT); + this.setSortBy(NoteSortKey.CreatedAt); } selectedSortByUpdated() { - this.setSortBy(SORT_KEY_CLIENT_UPDATED_AT); + this.setSortBy(NoteSortKey.ClientUpdatedAt); } selectedSortByTitle() { - this.setSortBy(SORT_KEY_TITLE); + this.setSortBy(NoteSortKey.Title); } toggleReverseSort() { this.selectedMenuItem(); - this.application.getPrefsService().setUserPrefValue( + this.application!.getPrefsService().setUserPrefValue( PrefKeys.SortNotesReverse, - !this.state.sortReverse + !this.getState().sortReverse ); - this.application.getPrefsService().syncUserPreferences(); + this.application!.getPrefsService().syncUserPreferences(); } - setSortBy(type) { - this.application.getPrefsService().setUserPrefValue( + setSortBy(type: NoteSortKey) { + this.application!.getPrefsService().setUserPrefValue( PrefKeys.SortNotesBy, type ); - this.application.getPrefsService().syncUserPreferences(); - } - - shouldShowTagsForNote(note) { - if (this.state.hideTags || note.content.protected) { - return false; - } - if (this.state.tag.content.isAllTag) { - return note.tags && note.tags.length > 0; - } - if (this.state.tag.isSmartTag()) { - return true; - } - /** - * Inside a tag, only show tags string if - * note contains tags other than this.state.tag - */ - return note.tags && note.tags.length > 1; + this.application!.getPrefsService().syncUserPreferences(); } getSearchBar() { - return document.getElementById(ELEMENT_ID_SEARCH_BAR); + return document.getElementById(ELEMENT_ID_SEARCH_BAR)!; } registerKeyboardShortcuts() { @@ -677,7 +702,7 @@ class NotesCtrl extends PureCtrl { * use Control modifier as well. These rules don't apply to desktop, but * probably better to be consistent. */ - this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ + this.newNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ key: 'n', modifiers: [ KeyboardModifier.Meta, @@ -689,7 +714,7 @@ class NotesCtrl extends PureCtrl { } }); - this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ + this.nextNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ key: KeyboardKey.Down, elements: [ document.body, @@ -704,7 +729,7 @@ class NotesCtrl extends PureCtrl { } }); - this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({ + this.previousNoteKeyObserver = this.application!.getKeyboardService().addKeyObserver({ key: KeyboardKey.Up, element: document.body, onKeyDown: (event) => { @@ -712,7 +737,7 @@ class NotesCtrl extends PureCtrl { } }); - this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({ + this.searchKeyObserver = this.application!.getKeyboardService().addKeyObserver({ key: "f", modifiers: [ KeyboardModifier.Meta, @@ -726,8 +751,9 @@ class NotesCtrl extends PureCtrl { } } -export class NotesPanel { +export class NotesPanel extends WebDirective { constructor() { + super(); this.template = template; this.replace = true; this.controller = NotesCtrl; diff --git a/app/assets/javascripts/services/keyboardManager.ts b/app/assets/javascripts/services/keyboardManager.ts index 25aaf7ac2..35ab89bc4 100644 --- a/app/assets/javascripts/services/keyboardManager.ts +++ b/app/assets/javascripts/services/keyboardManager.ts @@ -20,7 +20,7 @@ enum KeyboardKeyEvent { }; type KeyboardObserver = { - key?: KeyboardKey + key?: KeyboardKey | string modifiers?: KeyboardModifier[] onKeyDown?: (event: KeyboardEvent) => void onKeyUp?: (event: KeyboardEvent) => void @@ -87,7 +87,7 @@ export class KeyboardManager { eventMatchesKeyAndModifiers( event: KeyboardEvent, - key: KeyboardKey, + key: KeyboardKey | string, modifiers: KeyboardModifier[] = [] ) { const eventModifiers = this.modifiersForEvent(event); diff --git a/app/assets/javascripts/services/state.ts b/app/assets/javascripts/services/state.ts index 4935ded9c..f0d8c3b75 100644 --- a/app/assets/javascripts/services/state.ts +++ b/app/assets/javascripts/services/state.ts @@ -1,7 +1,7 @@ import { WebApplication } from './../application'; import { isDesktopApplication } from '@/utils'; import pull from 'lodash/pull'; -import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs } from 'snjs'; +import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs, ContentType } from 'snjs'; export enum AppStateEvent { TagChanged = 1, @@ -48,6 +48,7 @@ export class AppState { this.application = application; this.registerVisibilityObservers(); this.addAppEventObserver(); + this.streamNotesAndTags(); const onVisibilityChange = () => { const visible = document.visibilityState === "visible"; @@ -73,6 +74,27 @@ export class AppState { this.onVisibilityChange = undefined; } + streamNotesAndTags() { + this.application!.streamItems( + [ContentType.Note, ContentType.Tag], + async (items) => { + if(this.selectedNote) { + const matchingNote = items.find((candidate) => candidate.uuid === this.selectedNote!.uuid); + if(matchingNote) { + this.selectedNote = matchingNote as SNNote; + } + } + if (this.selectedTag) { + const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid); + if (matchingTag) { + this.selectedTag = matchingTag as SNTag; + } + } + } + ); + } + + addAppEventObserver() { this.unsubApp = this.application.addEventObserver(async (eventName) => { if (eventName === ApplicationEvent.Started) { @@ -166,12 +188,20 @@ export class AppState { } } + /** Returns the tags that are referncing this note */ getNoteTags(note: SNNote) { - return this.application.referencesForItem(note).filter((ref) => { + return this.application.referencingForItem(note).filter((ref) => { return ref.content_type === note.content_type; }) as SNTag[] } + /** Returns the notes this tag references */ + getTagNotes(tag: SNTag) { + return this.application.referencesForItem(tag).filter((ref) => { + return ref.content_type === tag.content_type; + }) as SNNote[] + } + getSelectedTag() { return this.selectedTag; } diff --git a/app/assets/javascripts/types.ts b/app/assets/javascripts/types.ts index 2537af606..b9116fe6b 100644 --- a/app/assets/javascripts/types.ts +++ b/app/assets/javascripts/types.ts @@ -19,7 +19,7 @@ export interface PasswordWizardScope extends Partial { } export type PanelPuppet = { - onReady: () => void + onReady?: () => void ready?: boolean setWidth?: (width: number) => void setLeft?: (left: number) => void diff --git a/app/assets/templates/notes.pug b/app/assets/templates/notes.pug index c2a77497f..c34df24dc 100644 --- a/app/assets/templates/notes.pug +++ b/app/assets/templates/notes.pug @@ -112,8 +112,8 @@ ng-class="{'selected' : self.state.selectedNote == note}" ng-click='self.selectNote(note, true)' ) - .note-flags(ng-show='note.flags.length > 0') - .flag(ng-class='flag.class', ng-repeat='flag in note.flags') + .note-flags(ng-show='self.noteFlags[note.uuid].length > 0') + .flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]') .label {{flag.text}} .name(ng-show='note.title') | {{note.title}} @@ -135,11 +135,9 @@ ) {{note.text}} .date.faded(ng-show='!self.state.hideDate') span(ng-show="self.state.sortBy == 'client_updated_at'") - | Modified {{note.cachedUpdatedAtString || 'Now'}} + | Modified {{note.updatedAtString || 'Now'}} span(ng-show="self.state.sortBy != 'client_updated_at'") - | {{note.cachedCreatedAtString || 'Now'}} - .tags-string(ng-show='note.shouldShowTags') - .faded {{note.savedTagsString || note.tagsString()}} + | {{note.createdAtString || 'Now'}} panel-resizer( collapsable="true" control="self.panelPuppet"