diff --git a/.yarn/cache/@standardnotes-snjs-npm-2.115.12-223e42486c-808bca0c48.zip b/.yarn/cache/@standardnotes-snjs-npm-2.116.0-6703421b73-27e9eff821.zip similarity index 57% rename from .yarn/cache/@standardnotes-snjs-npm-2.115.12-223e42486c-808bca0c48.zip rename to .yarn/cache/@standardnotes-snjs-npm-2.116.0-6703421b73-27e9eff821.zip index 1fe988b26..110880caf 100644 Binary files a/.yarn/cache/@standardnotes-snjs-npm-2.115.12-223e42486c-808bca0c48.zip and b/.yarn/cache/@standardnotes-snjs-npm-2.116.0-6703421b73-27e9eff821.zip differ diff --git a/packages/web/package.json b/packages/web/package.json index ebf6172b3..3cd6e1053 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -68,7 +68,7 @@ "@standardnotes/icons": "workspace:*", "@standardnotes/services": "^1.13.14", "@standardnotes/sncrypto-web": "1.10.1", - "@standardnotes/snjs": "^2.115.9", + "@standardnotes/snjs": "^2.116.0", "@standardnotes/styles": "workspace:*", "@standardnotes/toast": "workspace:*", "@zip.js/zip.js": "^2.4.10", diff --git a/packages/web/src/javascripts/Application/Application.ts b/packages/web/src/javascripts/Application/Application.ts index b03157b84..e3e09011b 100644 --- a/packages/web/src/javascripts/Application/Application.ts +++ b/packages/web/src/javascripts/Application/Application.ts @@ -11,7 +11,7 @@ import { DeinitSource, Platform, SNApplication, - NoteGroupController, + ItemGroupController, removeFromArray, IconsController, DesktopDeviceInterface, @@ -41,7 +41,7 @@ export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void export class WebApplication extends SNApplication { private webServices!: WebServices private webEventObservers: WebEventObserver[] = [] - public noteControllerGroup: NoteGroupController + public itemControllerGroup: ItemGroupController public iconsController: IconsController private onVisibilityChange: () => void @@ -70,7 +70,7 @@ export class WebApplication extends SNApplication { }) deviceInterface.setApplication(this) - this.noteControllerGroup = new NoteGroupController(this) + this.itemControllerGroup = new ItemGroupController(this) this.iconsController = new IconsController() this.onVisibilityChange = () => { @@ -102,8 +102,8 @@ export class WebApplication extends SNApplication { this.webServices = {} as WebServices - this.noteControllerGroup.deinit() - ;(this.noteControllerGroup as unknown) = undefined + this.itemControllerGroup.deinit() + ;(this.itemControllerGroup as unknown) = undefined this.webEventObservers.length = 0 diff --git a/packages/web/src/javascripts/Components/NoteGroupView/NoteGroupView.tsx b/packages/web/src/javascripts/Components/NoteGroupView/NoteGroupView.tsx index 210611228..3dae5a672 100644 --- a/packages/web/src/javascripts/Components/NoteGroupView/NoteGroupView.tsx +++ b/packages/web/src/javascripts/Components/NoteGroupView/NoteGroupView.tsx @@ -1,4 +1,4 @@ -import { FileItem, NoteViewController } from '@standardnotes/snjs' +import { FileItem, FileViewController, NoteViewController } from '@standardnotes/snjs' import { PureComponent } from '@/Components/Abstract/PureComponent' import { WebApplication } from '@/Application/Application' import MultipleSelectedNotes from '@/Components/MultipleSelectedNotes/MultipleSelectedNotes' @@ -10,7 +10,7 @@ import FileView from '@/Components/FileView/FileView' type State = { showMultipleSelectedNotes: boolean showMultipleSelectedFiles: boolean - controllers: NoteViewController[] + controllers: (NoteViewController | FileViewController)[] selectedFile: FileItem | undefined } @@ -34,9 +34,9 @@ class NoteGroupView extends PureComponent { override componentDidMount(): void { super.componentDidMount() - const controllerGroup = this.application.noteControllerGroup - this.removeChangeObserver = this.application.noteControllerGroup.addActiveControllerChangeObserver(() => { - const controllers = controllerGroup.noteControllers + const controllerGroup = this.application.itemControllerGroup + this.removeChangeObserver = this.application.itemControllerGroup.addActiveControllerChangeObserver(() => { + const controllers = controllerGroup.itemControllers this.setState({ controllers: controllers, }) @@ -110,18 +110,19 @@ class NoteGroupView extends PureComponent { {shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && ( <> {this.state.controllers.map((controller) => { - return + return controller instanceof NoteViewController ? ( + + ) : ( + + ) })} )} - - {shouldNotShowMultipleSelectedItems && this.state.controllers.length < 1 && this.state.selectedFile && ( - - )} ) } diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts b/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts index b71eeefc4..c3819bc5a 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.test.ts @@ -52,7 +52,7 @@ describe('NoteView', () => { it("should hide the note if at the time of the session expiration the note wasn't edited for longer than the allowed idle time", async () => { const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction + 5 - noteViewController.note = { + noteViewController.item = { protected: true, userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000), } as jest.Mocked @@ -65,7 +65,7 @@ describe('NoteView', () => { it('should postpone the note hiding by correct time if the time passed after its last modification is less than the allowed idle time', async () => { const secondsElapsedSinceLastEdit = ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - 3 - noteViewController.note = { + noteViewController.item = { protected: true, userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastEdit * 1000), } as jest.Mocked @@ -87,7 +87,7 @@ describe('NoteView', () => { it('should postpone the note hiding by correct time if the user continued editing it even after the protection session has expired', async () => { const secondsElapsedSinceLastModification = 3 - noteViewController.note = { + noteViewController.item = { protected: true, userModifiedDate: new Date(Date.now() - secondsElapsedSinceLastModification * 1000), } as jest.Mocked @@ -98,7 +98,7 @@ describe('NoteView', () => { ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction - secondsElapsedSinceLastModification jest.advanceTimersByTime((secondsAfterWhichTheNoteShouldHide - 1) * 1000) - noteViewController.note = { + noteViewController.item = { protected: true, userModifiedDate: new Date(), } as jest.Mocked @@ -114,7 +114,7 @@ describe('NoteView', () => { describe('note is unprotected', () => { it('should not call any hiding logic', async () => { - noteViewController.note = { + noteViewController.item = { protected: false, } as jest.Mocked @@ -126,7 +126,7 @@ describe('NoteView', () => { describe('dismissProtectedWarning', () => { beforeEach(() => { - noteViewController.note = { + noteViewController.item = { protected: false, } as jest.Mocked }) diff --git a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx index 2c8f69303..746c7290d 100644 --- a/packages/web/src/javascripts/Components/NoteView/NoteView.tsx +++ b/packages/web/src/javascripts/Components/NoteView/NoteView.tsx @@ -118,7 +118,7 @@ class NoteView extends PureComponent { isDesktop: isDesktopApplication(), lockText: NOTE_EDITING_DISABLED_TEXT, noteStatus: undefined, - noteLocked: this.controller.note.locked, + noteLocked: this.controller.item.locked, showLockedIcon: true, showProtectedWarning: false, spellcheck: true, @@ -180,7 +180,7 @@ class NoteView extends PureComponent { } get note() { - return this.controller.note + return this.controller.item } override componentDidMount(): void { diff --git a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts index b658a19e1..c5fb096b7 100644 --- a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts +++ b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts @@ -15,6 +15,8 @@ import { InternalEventBus, InternalEventHandlerInterface, InternalEventInterface, + FileViewController, + FileItem, } from '@standardnotes/snjs' import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx' import { WebApplication } from '../../Application/Application' @@ -131,7 +133,7 @@ export class ItemListController extends AbstractViewController implements Intern this.disposers.push( application.addEventObserver(async () => { - this.application.noteControllerGroup.closeAllNoteControllers() + this.application.itemControllerGroup.closeAllItemControllers() void this.selectFirstItem() this.setCompletedFullSync(false) }, ApplicationEvent.SignedIn), @@ -145,7 +147,7 @@ export class ItemListController extends AbstractViewController implements Intern this.navigationController.selected instanceof SmartView && this.navigationController.selected.uuid === SystemViewId.AllNotes && this.noteFilterText === '' && - !this.getActiveNoteController() + !this.getActiveItemController() ) { this.createPlaceholderNote()?.catch(console.error) } @@ -213,12 +215,45 @@ export class ItemListController extends AbstractViewController implements Intern } } - public getActiveNoteController(): NoteViewController | undefined { - return this.application.noteControllerGroup.activeNoteViewController + public getActiveItemController(): NoteViewController | FileViewController | undefined { + return this.application.itemControllerGroup.activeItemViewController } public get activeControllerNote(): SNNote | undefined { - return this.getActiveNoteController()?.note + const activeController = this.getActiveItemController() + return activeController instanceof NoteViewController ? activeController.item : undefined + } + + async openNote(uuid: string): Promise { + if (this.activeControllerNote?.uuid === uuid) { + return + } + + const note = this.application.items.findItem(uuid) + if (!note) { + console.warn('Tried accessing a non-existant note of UUID ' + uuid) + return + } + + await this.application.itemControllerGroup.createItemController(note) + + this.noteTagsController.reloadTagsForCurrentNote() + + await this.publishEventSync(CrossControllerEvent.ActiveEditorChanged) + } + + async openFile(fileUuid: string): Promise { + if (this.getActiveItemController()?.item.uuid === fileUuid) { + return + } + + const file = this.application.items.findItem(fileUuid) + if (!file) { + console.warn('Tried accessing a non-existant file of UUID ' + fileUuid) + return + } + + await this.application.itemControllerGroup.createItemController(file) } setCompletedFullSync = (completed: boolean) => { @@ -241,7 +276,7 @@ export class ItemListController extends AbstractViewController implements Intern let title = this.panelTitle if (this.isFiltering) { - const resultCount = this.notes.length + const resultCount = this.items.length title = `${resultCount} search results` } else if (this.navigationController.selected) { title = `${this.navigationController.selected.title}` @@ -283,54 +318,59 @@ export class ItemListController extends AbstractViewController implements Intern this.reloadPanelTitle() } - private async recomputeSelectionAfterItemsReload(itemsReloadSource: ItemsReloadSource) { - const activeController = this.getActiveNoteController() - const activeNote = activeController?.note - const isSearching = this.noteFilterText.length > 0 + private shouldLeaveSelectionUnchanged = (activeController: NoteViewController | FileViewController | undefined) => { const hasMultipleItemsSelected = this.selectionController.selectedItemsCount >= 2 - if (hasMultipleItemsSelected) { - return - } + return ( + hasMultipleItemsSelected || (activeController instanceof NoteViewController && activeController.isTemplateNote) + ) + } - if (itemsReloadSource === ItemsReloadSource.TagChange) { - await this.selectFirstItem() + private shouldSelectFirstItem = (itemsReloadSource: ItemsReloadSource, activeItem: SNNote | FileItem | undefined) => { + return itemsReloadSource === ItemsReloadSource.TagChange || !activeItem + } - return - } + private shouldCloseActiveItem = (activeItem: SNNote | FileItem | undefined) => { + const isSearching = this.noteFilterText.length > 0 + const itemExistsInUpdatedResults = this.items.find((item) => item.uuid === activeItem?.uuid) - if (!activeNote) { - await this.selectFirstItem() + return !itemExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView() + } - return - } - - if (activeController.isTemplateNote) { - return - } - - const noteExistsInUpdatedResults = this.notes.find((note) => note.uuid === activeNote.uuid) - const shouldCloseActiveNote = - !noteExistsInUpdatedResults && !isSearching && this.navigationController.isInAnySystemView() - - if (shouldCloseActiveNote) { - this.closeNoteController(activeController) - this.selectNextItem() - return - } - - const showTrashedNotes = + private shouldSelectNextItemOrCreateNewNote = (activeItem: SNNote | FileItem | undefined) => { + const shouldShowTrashedNotes = this.navigationController.isInSystemView(SystemViewId.TrashedNotes) || this.searchOptionsController.includeTrashed - const showArchivedNotes = + const shouldShowArchivedNotes = this.navigationController.isInSystemView(SystemViewId.ArchivedNotes) || this.searchOptionsController.includeArchived || this.application.getPreference(PrefKey.NotesShowArchived, false) - if ((activeNote.trashed && !showTrashedNotes) || (activeNote.archived && !showArchivedNotes)) { + return (activeItem?.trashed && !shouldShowTrashedNotes) || (activeItem?.archived && !shouldShowArchivedNotes) + } + + private shouldSelectActiveItem = (activeItem: SNNote | FileItem | undefined) => { + return activeItem && !this.selectionController.selectedItems[activeItem.uuid] + } + + private async recomputeSelectionAfterItemsReload(itemsReloadSource: ItemsReloadSource) { + const activeController = this.getActiveItemController() + + if (this.shouldLeaveSelectionUnchanged(activeController)) { + return + } + + const activeItem = activeController?.item + + if (this.shouldSelectFirstItem(itemsReloadSource, activeItem)) { + await this.selectFirstItem() + } else if (this.shouldCloseActiveItem(activeItem) && activeController) { + this.closeItemController(activeController) + this.selectNextItem() + } else if (this.shouldSelectNextItemOrCreateNewNote(activeItem)) { await this.selectNextItemOrCreateNewNote() - } else if (!this.selectionController.selectedItems[activeNote.uuid]) { - await this.selectionController.selectItem(activeNote.uuid).catch(console.error) + } else if (this.shouldSelectActiveItem(activeItem) && activeItem) { + await this.selectionController.selectItem(activeItem.uuid).catch(console.error) } } @@ -423,6 +463,17 @@ export class ItemListController extends AbstractViewController implements Intern } } + async createNewNoteController(title?: string) { + const selectedTag = this.navigationController.selected + + const activeRegularTagUuid = selectedTag instanceof SNTag ? selectedTag.uuid : undefined + + await this.application.itemControllerGroup.createItemController({ + title, + tag: activeRegularTagUuid, + }) + } + createNewNote = async () => { this.notesController.unselectNotes() @@ -435,7 +486,7 @@ export class ItemListController extends AbstractViewController implements Intern title = this.noteFilterText } - await this.notesController.createNewNoteController(title) + await this.createNewNoteController(title) this.noteTagsController.reloadTagsForCurrentNote() } @@ -622,7 +673,7 @@ export class ItemListController extends AbstractViewController implements Intern } handleEditorChange = async () => { - const activeNote = this.application.noteControllerGroup.activeNoteViewController?.note + const activeNote = this.application.itemControllerGroup.activeItemViewController?.item if (activeNote && activeNote.conflictOf) { this.application.mutator @@ -644,14 +695,14 @@ export class ItemListController extends AbstractViewController implements Intern } } - private closeNoteController(controller: NoteViewController): void { - this.application.noteControllerGroup.closeNoteController(controller) + private closeItemController(controller: NoteViewController | FileViewController): void { + this.application.itemControllerGroup.closeItemController(controller) } handleTagChange = () => { - const activeNoteController = this.getActiveNoteController() - if (activeNoteController?.isTemplateNote) { - this.closeNoteController(activeNoteController) + const activeNoteController = this.getActiveItemController() + if (activeNoteController instanceof NoteViewController && activeNoteController.isTemplateNote) { + this.closeItemController(activeNoteController) } this.resetScrollPosition() @@ -681,13 +732,13 @@ export class ItemListController extends AbstractViewController implements Intern } public async insertCurrentIfTemplate(): Promise { - const controller = this.getActiveNoteController() + const controller = this.getActiveItemController() if (!controller) { return } - if (controller.isTemplateNote) { + if (controller instanceof NoteViewController && controller.isTemplateNote) { await controller.insertTemplatedNote() } } diff --git a/packages/web/src/javascripts/Controllers/NotesController.ts b/packages/web/src/javascripts/Controllers/NotesController.ts index 32204c099..a7a99bd7d 100644 --- a/packages/web/src/javascripts/Controllers/NotesController.ts +++ b/packages/web/src/javascripts/Controllers/NotesController.ts @@ -10,7 +10,6 @@ import { SelectedItemsController } from './SelectedItemsController' import { ItemListController } from './ItemList/ItemListController' import { NoteTagsController } from './NoteTagsController' import { NavigationController } from './Navigation/NavigationController' -import { CrossControllerEvent } from './CrossControllerEvent' export class NotesController extends AbstractViewController { lastSelectedNote: SNNote | undefined @@ -84,10 +83,10 @@ export class NotesController extends AbstractViewController { }) }), - this.application.noteControllerGroup.addActiveControllerChangeObserver(() => { - const controllers = this.application.noteControllerGroup.noteControllers + this.application.itemControllerGroup.addActiveControllerChangeObserver(() => { + const controllers = this.application.itemControllerGroup.itemControllers - const activeNoteUuids = controllers.map((c) => c.note.uuid) + const activeNoteUuids = controllers.map((controller) => controller.item.uuid) const selectedUuids = this.getSelectedNotesList().map((n) => n.uuid) @@ -120,32 +119,6 @@ export class NotesController extends AbstractViewController { return this.application.items.trashedItems.length } - async openNote(noteUuid: string): Promise { - if (this.itemListController.activeControllerNote?.uuid === noteUuid) { - return - } - - const note = this.application.items.findItem(noteUuid) as SNNote | undefined - if (!note) { - console.warn('Tried accessing a non-existant note of UUID ' + noteUuid) - return - } - - await this.application.noteControllerGroup.createNoteController(noteUuid) - - this.noteTagsController.reloadTagsForCurrentNote() - - await this.publishEventSync(CrossControllerEvent.ActiveEditorChanged) - } - - async createNewNoteController(title?: string) { - const selectedTag = this.navigationController.selected - - const activeRegularTagUuid = selectedTag && selectedTag instanceof SNTag ? selectedTag.uuid : undefined - - await this.application.noteControllerGroup.createNoteController(undefined, title, activeRegularTagUuid) - } - setContextMenuOpen(open: boolean): void { this.contextMenuOpen = open } diff --git a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts index 42308dbef..86ddc7d26 100644 --- a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts +++ b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts @@ -12,7 +12,6 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx' import { WebApplication } from '../Application/Application' import { AbstractViewController } from './Abstract/AbstractViewController' import { ItemListController } from './ItemList/ItemListController' -import { NotesController } from './NotesController' type SelectedItems = Record @@ -20,12 +19,10 @@ export class SelectedItemsController extends AbstractViewController { lastSelectedItem: ListableContentItem | undefined selectedItems: SelectedItems = {} private itemListController!: ItemListController - private notesController!: NotesController override deinit(): void { super.deinit() ;(this.itemListController as unknown) = undefined - ;(this.notesController as unknown) = undefined } constructor(application: WebApplication, eventBus: InternalEventBus) { @@ -43,9 +40,8 @@ export class SelectedItemsController extends AbstractViewController { }) } - public setServicesPostConstruction(itemListController: ItemListController, notesController: NotesController) { + public setServicesPostConstruction(itemListController: ItemListController) { this.itemListController = itemListController - this.notesController = notesController this.disposers.push( this.application.streamItems( @@ -198,8 +194,11 @@ export class SelectedItemsController extends AbstractViewController { if (this.selectedItemsCount === 1) { const item = Object.values(this.selectedItems)[0] + if (item.content_type === ContentType.Note) { - await this.notesController.openNote(item.uuid) + await this.itemListController.openNote(item.uuid) + } else if (item.content_type === ContentType.File) { + await this.itemListController.openFile(item.uuid) } } diff --git a/packages/web/src/javascripts/Services/ViewControllerManager.ts b/packages/web/src/javascripts/Services/ViewControllerManager.ts index 595b7a6a8..5ab430645 100644 --- a/packages/web/src/javascripts/Services/ViewControllerManager.ts +++ b/packages/web/src/javascripts/Services/ViewControllerManager.ts @@ -84,7 +84,7 @@ export class ViewControllerManager { this.notesController.setServicesPostConstruction(this.itemListController) this.noteTagsController.setServicesPostConstruction(this.itemListController) - this.selectionController.setServicesPostConstruction(this.itemListController, this.notesController) + this.selectionController.setServicesPostConstruction(this.itemListController) this.noAccountWarningController = new NoAccountWarningController(application, this.eventBus) diff --git a/yarn.lock b/yarn.lock index 90ad62c27..e15afca0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5378,9 +5378,9 @@ __metadata: languageName: node linkType: hard -"@standardnotes/snjs@npm:^2.115.9, @standardnotes/snjs@npm:^2.41.1": - version: 2.115.12 - resolution: "@standardnotes/snjs@npm:2.115.12" +"@standardnotes/snjs@npm:^2.116.0, @standardnotes/snjs@npm:^2.41.1": + version: 2.116.0 + resolution: "@standardnotes/snjs@npm:2.116.0" dependencies: "@standardnotes/api": ^1.1.8 "@standardnotes/auth": ^3.19.2 @@ -5395,7 +5395,7 @@ __metadata: "@standardnotes/settings": ^1.14.3 "@standardnotes/sncrypto-common": ^1.9.0 "@standardnotes/utils": ^1.6.10 - checksum: 808bca0c48ca995d678ddc14120f21f144689670f451b2a88be86c9c4d424efadd41fff0648e4a0542a4b000de30331a1486533e9dec70233a4fcab41f7181e7 + checksum: 27e9eff8214def10c299471a318d06fbfbf07fd780819b9f75e6415aa5220d065c57443274bcc8e3ff9def0348950eabdf2ed636724bc01ece72a7b90af5f660 languageName: node linkType: hard @@ -5530,7 +5530,7 @@ __metadata: "@standardnotes/icons": "workspace:*" "@standardnotes/services": ^1.13.14 "@standardnotes/sncrypto-web": 1.10.1 - "@standardnotes/snjs": ^2.115.9 + "@standardnotes/snjs": ^2.116.0 "@standardnotes/styles": "workspace:*" "@standardnotes/toast": "workspace:*" "@types/jest": ^27.4.1