diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index 92fc97980..9562bea5b 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -177,7 +177,18 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio
- +
diff --git a/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx b/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx index 45d20aa20..af032ea03 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ContentList.tsx @@ -1,32 +1,44 @@ import { WebApplication } from '@/Application/Application' import { KeyboardKey } from '@/Services/IOService' -import { ViewControllerManager } from '@/Services/ViewControllerManager' import { UuidString } from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react' import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants/Constants' import { ListableContentItem } from './Types/ListableContentItem' import ContentListItem from './ContentListItem' +import { ItemListController } from '@/Controllers/ItemList/ItemListController' +import { FilesController } from '@/Controllers/FilesController' +import { SelectedItemsController } from '@/Controllers/SelectedItemsController' +import { NavigationController } from '@/Controllers/Navigation/NavigationController' +import { NotesController } from '@/Controllers/NotesController' +import { ElementIds } from '@/Constants/ElementIDs' type Props = { application: WebApplication - viewControllerManager: ViewControllerManager + filesController: FilesController + itemListController: ItemListController items: ListableContentItem[] + navigationController: NavigationController + notesController: NotesController + selectionController: SelectedItemsController selectedItems: Record paginate: () => void } const ContentList: FunctionComponent = ({ application, - viewControllerManager, + filesController, + itemListController, items, + navigationController, + notesController, + selectionController, selectedItems, paginate, }) => { - const { selectPreviousItem, selectNextItem } = viewControllerManager.itemListController - const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = - viewControllerManager.itemListController.webDisplayOptions - const { sortBy } = viewControllerManager.itemListController.displayOptions + const { selectPreviousItem, selectNextItem } = itemListController + const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions + const { sortBy } = itemListController.displayOptions const onScroll: UIEventHandler = useCallback( (e) => { @@ -55,7 +67,7 @@ const ContentList: FunctionComponent = ({ return (
= ({ hideTags={hideTags} hideIcon={hideEditorIcon} sortBy={sortBy} - filesController={viewControllerManager.filesController} - selectionController={viewControllerManager.selectionController} - navigationController={viewControllerManager.navigationController} - notesController={viewControllerManager.notesController} + filesController={filesController} + selectionController={selectionController} + navigationController={navigationController} + notesController={notesController} /> ))}
diff --git a/packages/web/src/javascripts/Components/ContentListView/ContentListOptionsMenu.tsx b/packages/web/src/javascripts/Components/ContentListView/ContentListOptionsMenu.tsx index ce6243c9b..b6d3311f2 100644 --- a/packages/web/src/javascripts/Components/ContentListView/ContentListOptionsMenu.tsx +++ b/packages/web/src/javascripts/Components/ContentListView/ContentListOptionsMenu.tsx @@ -7,22 +7,22 @@ import Menu from '@/Components/Menu/Menu' import MenuItem from '@/Components/Menu/MenuItem' import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator' import { MenuItemType } from '@/Components/Menu/MenuItemType' -import { ViewControllerManager } from '@/Services/ViewControllerManager' +import { NavigationController } from '@/Controllers/Navigation/NavigationController' type Props = { application: WebApplication - viewControllerManager: ViewControllerManager closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void closeDisplayOptionsMenu: () => void isOpen: boolean + navigationController: NavigationController } const ContentListOptionsMenu: FunctionComponent = ({ closeDisplayOptionsMenu, closeOnBlur, application, - viewControllerManager, isOpen, + navigationController, }) => { const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt)) const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false)) @@ -174,7 +174,7 @@ const ContentListOptionsMenu: FunctionComponent = ({
View
- {viewControllerManager.navigationController.selectedUuid !== SystemViewId.Files && ( + {navigationController.selectedUuid !== SystemViewId.Files && ( = ({ application, viewControllerManager }) => { +const ContentListView: FunctionComponent = ({ + accountMenuController, + application, + filesController, + itemListController, + navigationController, + noAccountWarningController, + noteTagsController, + notesController, + searchOptionsController, + selectionController, +}) => { const itemsViewPanelRef = useRef(null) const displayOptionsMenuRef = useRef(null) @@ -47,9 +75,9 @@ const ContentListView: FunctionComponent = ({ application, viewController paginate, panelWidth, createNewNote, - } = viewControllerManager.itemListController + } = itemListController - const { selectedItems } = viewControllerManager.selectionController + const { selectedItems } = selectionController const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false) const [focusedSearch, setFocusedSearch] = useState(false) @@ -57,17 +85,17 @@ const ContentListView: FunctionComponent = ({ application, viewController const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu) const isFilesSmartView = useMemo( - () => viewControllerManager.navigationController.selected?.uuid === SystemViewId.Files, - [viewControllerManager.navigationController.selected?.uuid], + () => navigationController.selected?.uuid === SystemViewId.Files, + [navigationController.selected?.uuid], ) const addNewItem = useCallback(() => { if (isFilesSmartView) { - void viewControllerManager.filesController.uploadNewFile() + void filesController.uploadNewFile() } else { void createNewNote() } - }, [viewControllerManager.filesController, createNewNote, isFilesSmartView]) + }, [filesController, createNewNote, isFilesSmartView]) useEffect(() => { /** @@ -75,7 +103,7 @@ const ContentListView: FunctionComponent = ({ application, viewController * use Control modifier as well. These rules don't apply to desktop, but * probably better to be consistent. */ - const newNoteKeyObserver = application.io.addKeyObserver({ + const disposeNewNoteKeyObserver = application.io.addKeyObserver({ key: 'n', modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl], onKeyDown: (event) => { @@ -84,7 +112,7 @@ const ContentListView: FunctionComponent = ({ application, viewController }, }) - const nextNoteKeyObserver = application.io.addKeyObserver({ + const disposeNextNoteKeyObserver = application.io.addKeyObserver({ key: KeyboardKey.Down, elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])], onKeyDown: () => { @@ -95,7 +123,7 @@ const ContentListView: FunctionComponent = ({ application, viewController }, }) - const previousNoteKeyObserver = application.io.addKeyObserver({ + const disposePreviousNoteKeyObserver = application.io.addKeyObserver({ key: KeyboardKey.Up, element: document.body, onKeyDown: () => { @@ -103,7 +131,7 @@ const ContentListView: FunctionComponent = ({ application, viewController }, }) - const searchKeyObserver = application.io.addKeyObserver({ + const disposeSearchKeyObserver = application.io.addKeyObserver({ key: 'f', modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift], onKeyDown: () => { @@ -113,13 +141,37 @@ const ContentListView: FunctionComponent = ({ application, viewController }, }) + const disposeSelectAllKeyObserver = application.io.addKeyObserver({ + key: 'a', + modifiers: [KeyboardModifier.Ctrl], + onKeyDown: (event) => { + const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`) + + if (!isTargetInsideContentList) { + return + } + + event.preventDefault() + selectionController.selectAll() + }, + }) + return () => { - newNoteKeyObserver() - nextNoteKeyObserver() - previousNoteKeyObserver() - searchKeyObserver() + disposeNewNoteKeyObserver() + disposeNextNoteKeyObserver() + disposePreviousNoteKeyObserver() + disposeSearchKeyObserver() + disposeSelectAllKeyObserver() } - }, [addNewItem, application.io, createNewNote, searchBarElement, selectNextItem, selectPreviousItem]) + }, [ + addNewItem, + application.io, + createNewNote, + searchBarElement, + selectNextItem, + selectPreviousItem, + selectionController, + ]) const onNoteFilterTextChange: ChangeEventHandler = useCallback( (e) => { @@ -143,15 +195,15 @@ const ContentListView: FunctionComponent = ({ application, viewController const panelResizeFinishCallback: ResizeFinishCallback = useCallback( (width, _lastLeft, _isMaxWidth, isCollapsed) => { application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error) - viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth() + noteTagsController.reloadTagsContainerMaxWidth() application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed) }, - [viewControllerManager, application], + [application, noteTagsController], ) const panelWidthEventCallback = useCallback(() => { - viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth() - }, [viewControllerManager]) + noteTagsController.reloadTagsContainerMaxWidth() + }, [noteTagsController]) const toggleDisplayOptionsMenu = useCallback(() => { setShowDisplayOptionsMenu(!showDisplayOptionsMenu) @@ -207,11 +259,14 @@ const ContentListView: FunctionComponent = ({ application, viewController {(focusedSearch || noteFilterText) && (
- +
)}
- +
@@ -234,10 +289,10 @@ const ContentListView: FunctionComponent = ({ application, viewController {showDisplayOptionsMenu && ( )} @@ -253,8 +308,12 @@ const ContentListView: FunctionComponent = ({ application, viewController items={renderedItems} selectedItems={selectedItems} application={application} - viewControllerManager={viewControllerManager} paginate={paginate} + filesController={filesController} + itemListController={itemListController} + navigationController={navigationController} + notesController={notesController} + selectionController={selectionController} /> ) : null}
diff --git a/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarning.tsx b/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarning.tsx index 1dbf56cfb..4d3788b92 100644 --- a/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarning.tsx +++ b/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarning.tsx @@ -1,49 +1,22 @@ -import Icon from '@/Components/Icon/Icon' -import { ViewControllerManager } from '@/Services/ViewControllerManager' +import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController' +import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController' import { observer } from 'mobx-react-lite' -import { MouseEventHandler, useCallback } from 'react' +import NoAccountWarningContent from './NoAccountWarningContent' -type Props = { viewControllerManager: ViewControllerManager } - -const NoAccountWarning = observer(({ viewControllerManager }: Props) => { - const showAccountMenu: MouseEventHandler = useCallback( - (event) => { - event.stopPropagation() - viewControllerManager.accountMenuController.setShow(true) - }, - [viewControllerManager], - ) - - const hideWarning = useCallback(() => { - viewControllerManager.noAccountWarningController.hide() - }, [viewControllerManager]) - - return ( -
-

Data not backed up

-

Sign in or register to back up your notes.

- - -
- ) -}) - -NoAccountWarning.displayName = 'NoAccountWarning' - -const NoAccountWarningWrapper = ({ viewControllerManager }: Props) => { - const canShow = viewControllerManager.noAccountWarningController.show - - return canShow ? : null +type Props = { + accountMenuController: AccountMenuController + noAccountWarningController: NoAccountWarningController } -export default observer(NoAccountWarningWrapper) +const NoAccountWarning = ({ accountMenuController, noAccountWarningController }: Props) => { + const canShow = noAccountWarningController.show + + return canShow ? ( + + ) : null +} + +export default observer(NoAccountWarning) diff --git a/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarningContent.tsx b/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarningContent.tsx new file mode 100644 index 000000000..d8bbaef76 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoAccountWarning/NoAccountWarningContent.tsx @@ -0,0 +1,45 @@ +import Icon from '@/Components/Icon/Icon' +import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController' +import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController' +import { observer } from 'mobx-react-lite' +import { MouseEventHandler, useCallback } from 'react' + +type Props = { + accountMenuController: AccountMenuController + noAccountWarningController: NoAccountWarningController +} + +const NoAccountWarningContent = ({ accountMenuController, noAccountWarningController }: Props) => { + const showAccountMenu: MouseEventHandler = useCallback( + (event) => { + event.stopPropagation() + accountMenuController.setShow(true) + }, + [accountMenuController], + ) + + const hideWarning = useCallback(() => { + noAccountWarningController.hide() + }, [noAccountWarningController]) + + return ( +
+

Data not backed up

+

Sign in or register to back up your notes.

+ + +
+ ) +} + +export default observer(NoAccountWarningContent) diff --git a/packages/web/src/javascripts/Components/SearchOptions/SearchOptions.tsx b/packages/web/src/javascripts/Components/SearchOptions/SearchOptions.tsx index b643fee14..abb5e0f14 100644 --- a/packages/web/src/javascripts/Components/SearchOptions/SearchOptions.tsx +++ b/packages/web/src/javascripts/Components/SearchOptions/SearchOptions.tsx @@ -1,17 +1,15 @@ -import { ViewControllerManager } from '@/Services/ViewControllerManager' import { WebApplication } from '@/Application/Application' import { observer } from 'mobx-react-lite' import Bubble from '@/Components/Bubble/Bubble' import { useCallback } from 'react' +import { SearchOptionsController } from '@/Controllers/SearchOptionsController' type Props = { - viewControllerManager: ViewControllerManager application: WebApplication + searchOptions: SearchOptionsController } -const SearchOptions = ({ viewControllerManager }: Props) => { - const { searchOptionsController: searchOptions } = viewControllerManager - +const SearchOptions = ({ searchOptions }: Props) => { const { includeProtectedContents, includeArchived, includeTrashed } = searchOptions const toggleIncludeProtectedContents = useCallback(async () => { diff --git a/packages/web/src/javascripts/Constants/ElementIDs.ts b/packages/web/src/javascripts/Constants/ElementIDs.ts index 778265154..854d47960 100644 --- a/packages/web/src/javascripts/Constants/ElementIDs.ts +++ b/packages/web/src/javascripts/Constants/ElementIDs.ts @@ -5,4 +5,5 @@ export const ElementIds = { FileTextPreview: 'file-text-preview', EditorContent: 'editor-content', EditorColumn: 'editor-column', + ContentList: 'notes-scrollable', } diff --git a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts index c5fb096b7..677d5803e 100644 --- a/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts +++ b/packages/web/src/javascripts/Controllers/ItemList/ItemListController.ts @@ -215,6 +215,10 @@ export class ItemListController extends AbstractViewController implements Intern } } + public get listLength() { + return this.renderedItems.length + } + public getActiveItemController(): NoteViewController | FileViewController | undefined { return this.application.itemControllerGroup.activeItemViewController } diff --git a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts index 86ddc7d26..d60e41a46 100644 --- a/packages/web/src/javascripts/Controllers/SelectedItemsController.ts +++ b/packages/web/src/javascripts/Controllers/SelectedItemsController.ts @@ -105,11 +105,19 @@ export class SelectedItemsController extends AbstractViewController { this.selectedItems[item.uuid] = item } - private selectItemsRange = async (selectedItem: ListableContentItem): Promise => { + private selectItemsRange = async ({ + selectedItem, + startingIndex, + endingIndex, + }: { + selectedItem?: ListableContentItem + startingIndex?: number + endingIndex?: number + }): Promise => { const items = this.itemListController.renderedItems - const lastSelectedItemIndex = items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid) - const selectedItemIndex = items.findIndex((item) => item.uuid == selectedItem.uuid) + const lastSelectedItemIndex = startingIndex ?? items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid) + const selectedItemIndex = endingIndex ?? items.findIndex((item) => item.uuid == selectedItem?.uuid) let itemsToSelect = [] if (selectedItemIndex > lastSelectedItemIndex) { @@ -151,6 +159,13 @@ export class SelectedItemsController extends AbstractViewController { this.lastSelectedItem = item } + selectAll = () => { + void this.selectItemsRange({ + startingIndex: 0, + endingIndex: this.itemListController.listLength - 1, + }) + } + private deselectAll = (): void => { this.setSelectedItems({}) @@ -184,7 +199,7 @@ export class SelectedItemsController extends AbstractViewController { this.lastSelectedItem = item } } else if (userTriggered && hasShift) { - await this.selectItemsRange(item) + await this.selectItemsRange({ selectedItem: item }) } else { const shouldSelectNote = hasMoreThanOneSelected || !this.selectedItems[uuid] if (shouldSelectNote && isAuthorizedForAccess) {