feat: ctrl+a to select all items (#1123)
This commit is contained in:
@@ -177,7 +177,18 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
<div className={platformString + ' main-ui-view sn-component'}>
|
<div className={platformString + ' main-ui-view sn-component'}>
|
||||||
<div id="app" className={appClass + ' app app-column-container'}>
|
<div id="app" className={appClass + ' app app-column-container'}>
|
||||||
<Navigation application={application} />
|
<Navigation application={application} />
|
||||||
<ContentListView application={application} viewControllerManager={viewControllerManager} />
|
<ContentListView
|
||||||
|
application={application}
|
||||||
|
accountMenuController={viewControllerManager.accountMenuController}
|
||||||
|
filesController={viewControllerManager.filesController}
|
||||||
|
itemListController={viewControllerManager.itemListController}
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
||||||
|
noteTagsController={viewControllerManager.noteTagsController}
|
||||||
|
notesController={viewControllerManager.notesController}
|
||||||
|
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||||
|
selectionController={viewControllerManager.selectionController}
|
||||||
|
/>
|
||||||
<NoteGroupView application={application} />
|
<NoteGroupView application={application} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,44 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { KeyboardKey } from '@/Services/IOService'
|
import { KeyboardKey } from '@/Services/IOService'
|
||||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
|
||||||
import { UuidString } from '@standardnotes/snjs'
|
import { UuidString } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
|
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
|
||||||
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants/Constants'
|
import { FOCUSABLE_BUT_NOT_TABBABLE, NOTES_LIST_SCROLL_THRESHOLD } from '@/Constants/Constants'
|
||||||
import { ListableContentItem } from './Types/ListableContentItem'
|
import { ListableContentItem } from './Types/ListableContentItem'
|
||||||
import ContentListItem from './ContentListItem'
|
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 = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
viewControllerManager: ViewControllerManager
|
filesController: FilesController
|
||||||
|
itemListController: ItemListController
|
||||||
items: ListableContentItem[]
|
items: ListableContentItem[]
|
||||||
|
navigationController: NavigationController
|
||||||
|
notesController: NotesController
|
||||||
|
selectionController: SelectedItemsController
|
||||||
selectedItems: Record<UuidString, ListableContentItem>
|
selectedItems: Record<UuidString, ListableContentItem>
|
||||||
paginate: () => void
|
paginate: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentList: FunctionComponent<Props> = ({
|
const ContentList: FunctionComponent<Props> = ({
|
||||||
application,
|
application,
|
||||||
viewControllerManager,
|
filesController,
|
||||||
|
itemListController,
|
||||||
items,
|
items,
|
||||||
|
navigationController,
|
||||||
|
notesController,
|
||||||
|
selectionController,
|
||||||
selectedItems,
|
selectedItems,
|
||||||
paginate,
|
paginate,
|
||||||
}) => {
|
}) => {
|
||||||
const { selectPreviousItem, selectNextItem } = viewControllerManager.itemListController
|
const { selectPreviousItem, selectNextItem } = itemListController
|
||||||
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } =
|
const { hideTags, hideDate, hideNotePreview, hideEditorIcon } = itemListController.webDisplayOptions
|
||||||
viewControllerManager.itemListController.webDisplayOptions
|
const { sortBy } = itemListController.displayOptions
|
||||||
const { sortBy } = viewControllerManager.itemListController.displayOptions
|
|
||||||
|
|
||||||
const onScroll: UIEventHandler = useCallback(
|
const onScroll: UIEventHandler = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
@@ -55,7 +67,7 @@ const ContentList: FunctionComponent<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="infinite-scroll focus:shadow-none focus:outline-none"
|
className="infinite-scroll focus:shadow-none focus:outline-none"
|
||||||
id="notes-scrollable"
|
id={ElementIds.ContentList}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
@@ -71,10 +83,10 @@ const ContentList: FunctionComponent<Props> = ({
|
|||||||
hideTags={hideTags}
|
hideTags={hideTags}
|
||||||
hideIcon={hideEditorIcon}
|
hideIcon={hideEditorIcon}
|
||||||
sortBy={sortBy}
|
sortBy={sortBy}
|
||||||
filesController={viewControllerManager.filesController}
|
filesController={filesController}
|
||||||
selectionController={viewControllerManager.selectionController}
|
selectionController={selectionController}
|
||||||
navigationController={viewControllerManager.navigationController}
|
navigationController={navigationController}
|
||||||
notesController={viewControllerManager.notesController}
|
notesController={notesController}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,22 +7,22 @@ import Menu from '@/Components/Menu/Menu'
|
|||||||
import MenuItem from '@/Components/Menu/MenuItem'
|
import MenuItem from '@/Components/Menu/MenuItem'
|
||||||
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
|
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
|
||||||
import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
||||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
viewControllerManager: ViewControllerManager
|
|
||||||
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void
|
||||||
closeDisplayOptionsMenu: () => void
|
closeDisplayOptionsMenu: () => void
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
|
navigationController: NavigationController
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentListOptionsMenu: FunctionComponent<Props> = ({
|
const ContentListOptionsMenu: FunctionComponent<Props> = ({
|
||||||
closeDisplayOptionsMenu,
|
closeDisplayOptionsMenu,
|
||||||
closeOnBlur,
|
closeOnBlur,
|
||||||
application,
|
application,
|
||||||
viewControllerManager,
|
|
||||||
isOpen,
|
isOpen,
|
||||||
|
navigationController,
|
||||||
}) => {
|
}) => {
|
||||||
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
|
const [sortBy, setSortBy] = useState(() => application.getPreference(PrefKey.SortNotesBy, CollectionSort.CreatedAt))
|
||||||
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
|
const [sortReverse, setSortReverse] = useState(() => application.getPreference(PrefKey.SortNotesReverse, false))
|
||||||
@@ -174,7 +174,7 @@ const ContentListOptionsMenu: FunctionComponent<Props> = ({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItemSeparator />
|
<MenuItemSeparator />
|
||||||
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
|
<div className="px-3 py-1 text-xs font-semibold color-text uppercase">View</div>
|
||||||
{viewControllerManager.navigationController.selectedUuid !== SystemViewId.Files && (
|
{navigationController.selectedUuid !== SystemViewId.Files && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
type={MenuItemType.SwitchButton}
|
type={MenuItemType.SwitchButton}
|
||||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { KeyboardKey, KeyboardModifier } from '@/Services/IOService'
|
import { KeyboardKey, KeyboardModifier } from '@/Services/IOService'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
|
||||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||||
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
|
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
@@ -15,20 +14,49 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import ContentList from '@/Components/ContentListView/ContentList'
|
import ContentList from '@/Components/ContentListView/ContentList'
|
||||||
import NoAccountWarningWrapper from '@/Components/NoAccountWarning/NoAccountWarning'
|
import NoAccountWarning from '@/Components/NoAccountWarning/NoAccountWarning'
|
||||||
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
|
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
|
||||||
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||||
import ContentListOptionsMenu from './ContentListOptionsMenu'
|
import ContentListOptionsMenu from './ContentListOptionsMenu'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
|
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||||
|
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||||
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
|
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||||
|
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
||||||
|
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||||
|
import { NotesController } from '@/Controllers/NotesController'
|
||||||
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
accountMenuController: AccountMenuController
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
viewControllerManager: ViewControllerManager
|
filesController: FilesController
|
||||||
|
itemListController: ItemListController
|
||||||
|
navigationController: NavigationController
|
||||||
|
noAccountWarningController: NoAccountWarningController
|
||||||
|
noteTagsController: NoteTagsController
|
||||||
|
notesController: NotesController
|
||||||
|
searchOptionsController: SearchOptionsController
|
||||||
|
selectionController: SelectedItemsController
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContentListView: FunctionComponent<Props> = ({ application, viewControllerManager }) => {
|
const ContentListView: FunctionComponent<Props> = ({
|
||||||
|
accountMenuController,
|
||||||
|
application,
|
||||||
|
filesController,
|
||||||
|
itemListController,
|
||||||
|
navigationController,
|
||||||
|
noAccountWarningController,
|
||||||
|
noteTagsController,
|
||||||
|
notesController,
|
||||||
|
searchOptionsController,
|
||||||
|
selectionController,
|
||||||
|
}) => {
|
||||||
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
|
const itemsViewPanelRef = useRef<HTMLDivElement>(null)
|
||||||
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
|
const displayOptionsMenuRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@@ -47,9 +75,9 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
paginate,
|
paginate,
|
||||||
panelWidth,
|
panelWidth,
|
||||||
createNewNote,
|
createNewNote,
|
||||||
} = viewControllerManager.itemListController
|
} = itemListController
|
||||||
|
|
||||||
const { selectedItems } = viewControllerManager.selectionController
|
const { selectedItems } = selectionController
|
||||||
|
|
||||||
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
const [showDisplayOptionsMenu, setShowDisplayOptionsMenu] = useState(false)
|
||||||
const [focusedSearch, setFocusedSearch] = useState(false)
|
const [focusedSearch, setFocusedSearch] = useState(false)
|
||||||
@@ -57,17 +85,17 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
|
const [closeDisplayOptMenuOnBlur] = useCloseOnBlur(displayOptionsMenuRef, setShowDisplayOptionsMenu)
|
||||||
|
|
||||||
const isFilesSmartView = useMemo(
|
const isFilesSmartView = useMemo(
|
||||||
() => viewControllerManager.navigationController.selected?.uuid === SystemViewId.Files,
|
() => navigationController.selected?.uuid === SystemViewId.Files,
|
||||||
[viewControllerManager.navigationController.selected?.uuid],
|
[navigationController.selected?.uuid],
|
||||||
)
|
)
|
||||||
|
|
||||||
const addNewItem = useCallback(() => {
|
const addNewItem = useCallback(() => {
|
||||||
if (isFilesSmartView) {
|
if (isFilesSmartView) {
|
||||||
void viewControllerManager.filesController.uploadNewFile()
|
void filesController.uploadNewFile()
|
||||||
} else {
|
} else {
|
||||||
void createNewNote()
|
void createNewNote()
|
||||||
}
|
}
|
||||||
}, [viewControllerManager.filesController, createNewNote, isFilesSmartView])
|
}, [filesController, createNewNote, isFilesSmartView])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +103,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
* use Control modifier as well. These rules don't apply to desktop, but
|
* use Control modifier as well. These rules don't apply to desktop, but
|
||||||
* probably better to be consistent.
|
* probably better to be consistent.
|
||||||
*/
|
*/
|
||||||
const newNoteKeyObserver = application.io.addKeyObserver({
|
const disposeNewNoteKeyObserver = application.io.addKeyObserver({
|
||||||
key: 'n',
|
key: 'n',
|
||||||
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl],
|
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl],
|
||||||
onKeyDown: (event) => {
|
onKeyDown: (event) => {
|
||||||
@@ -84,7 +112,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const nextNoteKeyObserver = application.io.addKeyObserver({
|
const disposeNextNoteKeyObserver = application.io.addKeyObserver({
|
||||||
key: KeyboardKey.Down,
|
key: KeyboardKey.Down,
|
||||||
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
|
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
@@ -95,7 +123,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const previousNoteKeyObserver = application.io.addKeyObserver({
|
const disposePreviousNoteKeyObserver = application.io.addKeyObserver({
|
||||||
key: KeyboardKey.Up,
|
key: KeyboardKey.Up,
|
||||||
element: document.body,
|
element: document.body,
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
@@ -103,7 +131,7 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const searchKeyObserver = application.io.addKeyObserver({
|
const disposeSearchKeyObserver = application.io.addKeyObserver({
|
||||||
key: 'f',
|
key: 'f',
|
||||||
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift],
|
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift],
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
@@ -113,13 +141,37 @@ const ContentListView: FunctionComponent<Props> = ({ 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 () => {
|
return () => {
|
||||||
newNoteKeyObserver()
|
disposeNewNoteKeyObserver()
|
||||||
nextNoteKeyObserver()
|
disposeNextNoteKeyObserver()
|
||||||
previousNoteKeyObserver()
|
disposePreviousNoteKeyObserver()
|
||||||
searchKeyObserver()
|
disposeSearchKeyObserver()
|
||||||
|
disposeSelectAllKeyObserver()
|
||||||
}
|
}
|
||||||
}, [addNewItem, application.io, createNewNote, searchBarElement, selectNextItem, selectPreviousItem])
|
}, [
|
||||||
|
addNewItem,
|
||||||
|
application.io,
|
||||||
|
createNewNote,
|
||||||
|
searchBarElement,
|
||||||
|
selectNextItem,
|
||||||
|
selectPreviousItem,
|
||||||
|
selectionController,
|
||||||
|
])
|
||||||
|
|
||||||
const onNoteFilterTextChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
const onNoteFilterTextChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
@@ -143,15 +195,15 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
||||||
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||||
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
application.setPreference(PrefKey.NotesPanelWidth, width).catch(console.error)
|
||||||
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
|
noteTagsController.reloadTagsContainerMaxWidth()
|
||||||
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
|
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, isCollapsed)
|
||||||
},
|
},
|
||||||
[viewControllerManager, application],
|
[application, noteTagsController],
|
||||||
)
|
)
|
||||||
|
|
||||||
const panelWidthEventCallback = useCallback(() => {
|
const panelWidthEventCallback = useCallback(() => {
|
||||||
viewControllerManager.noteTagsController.reloadTagsContainerMaxWidth()
|
noteTagsController.reloadTagsContainerMaxWidth()
|
||||||
}, [viewControllerManager])
|
}, [noteTagsController])
|
||||||
|
|
||||||
const toggleDisplayOptionsMenu = useCallback(() => {
|
const toggleDisplayOptionsMenu = useCallback(() => {
|
||||||
setShowDisplayOptionsMenu(!showDisplayOptionsMenu)
|
setShowDisplayOptionsMenu(!showDisplayOptionsMenu)
|
||||||
@@ -207,11 +259,14 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
|
|
||||||
{(focusedSearch || noteFilterText) && (
|
{(focusedSearch || noteFilterText) && (
|
||||||
<div className="animate-fade-from-top">
|
<div className="animate-fade-from-top">
|
||||||
<SearchOptions application={application} viewControllerManager={viewControllerManager} />
|
<SearchOptions application={application} searchOptions={searchOptionsController} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<NoAccountWarningWrapper viewControllerManager={viewControllerManager} />
|
<NoAccountWarning
|
||||||
|
accountMenuController={accountMenuController}
|
||||||
|
noAccountWarningController={noAccountWarningController}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
|
<div id="items-menu-bar" className="sn-component" ref={displayOptionsMenuRef}>
|
||||||
<div className="sk-app-bar no-edges">
|
<div className="sk-app-bar no-edges">
|
||||||
@@ -234,10 +289,10 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
{showDisplayOptionsMenu && (
|
{showDisplayOptionsMenu && (
|
||||||
<ContentListOptionsMenu
|
<ContentListOptionsMenu
|
||||||
application={application}
|
application={application}
|
||||||
viewControllerManager={viewControllerManager}
|
|
||||||
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
closeDisplayOptionsMenu={toggleDisplayOptionsMenu}
|
||||||
closeOnBlur={closeDisplayOptMenuOnBlur}
|
closeOnBlur={closeDisplayOptMenuOnBlur}
|
||||||
isOpen={showDisplayOptionsMenu}
|
isOpen={showDisplayOptionsMenu}
|
||||||
|
navigationController={navigationController}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
@@ -253,8 +308,12 @@ const ContentListView: FunctionComponent<Props> = ({ application, viewController
|
|||||||
items={renderedItems}
|
items={renderedItems}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
application={application}
|
application={application}
|
||||||
viewControllerManager={viewControllerManager}
|
|
||||||
paginate={paginate}
|
paginate={paginate}
|
||||||
|
filesController={filesController}
|
||||||
|
itemListController={itemListController}
|
||||||
|
navigationController={navigationController}
|
||||||
|
notesController={notesController}
|
||||||
|
selectionController={selectionController}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,49 +1,22 @@
|
|||||||
import Icon from '@/Components/Icon/Icon'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { MouseEventHandler, useCallback } from 'react'
|
import NoAccountWarningContent from './NoAccountWarningContent'
|
||||||
|
|
||||||
type Props = { viewControllerManager: ViewControllerManager }
|
type Props = {
|
||||||
|
accountMenuController: AccountMenuController
|
||||||
const NoAccountWarning = observer(({ viewControllerManager }: Props) => {
|
noAccountWarningController: NoAccountWarningController
|
||||||
const showAccountMenu: MouseEventHandler = useCallback(
|
|
||||||
(event) => {
|
|
||||||
event.stopPropagation()
|
|
||||||
viewControllerManager.accountMenuController.setShow(true)
|
|
||||||
},
|
|
||||||
[viewControllerManager],
|
|
||||||
)
|
|
||||||
|
|
||||||
const hideWarning = useCallback(() => {
|
|
||||||
viewControllerManager.noAccountWarningController.hide()
|
|
||||||
}, [viewControllerManager])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mt-4 p-4 rounded-md shadow-sm grid grid-template-cols-1fr">
|
|
||||||
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
|
|
||||||
<p className="m-0 mt-1 col-start-1 col-end-3">Sign in or register to back up your notes.</p>
|
|
||||||
<button className="sn-button small info mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
|
|
||||||
Open Account menu
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={hideWarning}
|
|
||||||
title="Ignore warning"
|
|
||||||
aria-label="Ignore warning"
|
|
||||||
style={{ height: '20px' }}
|
|
||||||
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
|
|
||||||
>
|
|
||||||
<Icon type="close" className="block" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
NoAccountWarning.displayName = 'NoAccountWarning'
|
|
||||||
|
|
||||||
const NoAccountWarningWrapper = ({ viewControllerManager }: Props) => {
|
|
||||||
const canShow = viewControllerManager.noAccountWarningController.show
|
|
||||||
|
|
||||||
return canShow ? <NoAccountWarning viewControllerManager={viewControllerManager} /> : null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default observer(NoAccountWarningWrapper)
|
const NoAccountWarning = ({ accountMenuController, noAccountWarningController }: Props) => {
|
||||||
|
const canShow = noAccountWarningController.show
|
||||||
|
|
||||||
|
return canShow ? (
|
||||||
|
<NoAccountWarningContent
|
||||||
|
accountMenuController={accountMenuController}
|
||||||
|
noAccountWarningController={noAccountWarningController}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(NoAccountWarning)
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<div className="mt-4 p-4 rounded-md shadow-sm grid grid-template-cols-1fr">
|
||||||
|
<h1 className="sk-h3 m-0 font-semibold">Data not backed up</h1>
|
||||||
|
<p className="m-0 mt-1 col-start-1 col-end-3">Sign in or register to back up your notes.</p>
|
||||||
|
<button className="sn-button small info mt-3 col-start-1 col-end-3 justify-self-start" onClick={showAccountMenu}>
|
||||||
|
Open Account menu
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={hideWarning}
|
||||||
|
title="Ignore warning"
|
||||||
|
aria-label="Ignore warning"
|
||||||
|
style={{ height: '20px' }}
|
||||||
|
className="border-0 m-0 p-0 bg-transparent cursor-pointer rounded-md col-start-2 row-start-1 color-neutral hover:color-info"
|
||||||
|
>
|
||||||
|
<Icon type="close" className="block" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(NoAccountWarningContent)
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import Bubble from '@/Components/Bubble/Bubble'
|
import Bubble from '@/Components/Bubble/Bubble'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
viewControllerManager: ViewControllerManager
|
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
|
searchOptions: SearchOptionsController
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchOptions = ({ viewControllerManager }: Props) => {
|
const SearchOptions = ({ searchOptions }: Props) => {
|
||||||
const { searchOptionsController: searchOptions } = viewControllerManager
|
|
||||||
|
|
||||||
const { includeProtectedContents, includeArchived, includeTrashed } = searchOptions
|
const { includeProtectedContents, includeArchived, includeTrashed } = searchOptions
|
||||||
|
|
||||||
const toggleIncludeProtectedContents = useCallback(async () => {
|
const toggleIncludeProtectedContents = useCallback(async () => {
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export const ElementIds = {
|
|||||||
FileTextPreview: 'file-text-preview',
|
FileTextPreview: 'file-text-preview',
|
||||||
EditorContent: 'editor-content',
|
EditorContent: 'editor-content',
|
||||||
EditorColumn: 'editor-column',
|
EditorColumn: 'editor-column',
|
||||||
|
ContentList: 'notes-scrollable',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,6 +215,10 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get listLength() {
|
||||||
|
return this.renderedItems.length
|
||||||
|
}
|
||||||
|
|
||||||
public getActiveItemController(): NoteViewController | FileViewController | undefined {
|
public getActiveItemController(): NoteViewController | FileViewController | undefined {
|
||||||
return this.application.itemControllerGroup.activeItemViewController
|
return this.application.itemControllerGroup.activeItemViewController
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,11 +105,19 @@ export class SelectedItemsController extends AbstractViewController {
|
|||||||
this.selectedItems[item.uuid] = item
|
this.selectedItems[item.uuid] = item
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectItemsRange = async (selectedItem: ListableContentItem): Promise<void> => {
|
private selectItemsRange = async ({
|
||||||
|
selectedItem,
|
||||||
|
startingIndex,
|
||||||
|
endingIndex,
|
||||||
|
}: {
|
||||||
|
selectedItem?: ListableContentItem
|
||||||
|
startingIndex?: number
|
||||||
|
endingIndex?: number
|
||||||
|
}): Promise<void> => {
|
||||||
const items = this.itemListController.renderedItems
|
const items = this.itemListController.renderedItems
|
||||||
|
|
||||||
const lastSelectedItemIndex = items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid)
|
const lastSelectedItemIndex = startingIndex ?? items.findIndex((item) => item.uuid == this.lastSelectedItem?.uuid)
|
||||||
const selectedItemIndex = items.findIndex((item) => item.uuid == selectedItem.uuid)
|
const selectedItemIndex = endingIndex ?? items.findIndex((item) => item.uuid == selectedItem?.uuid)
|
||||||
|
|
||||||
let itemsToSelect = []
|
let itemsToSelect = []
|
||||||
if (selectedItemIndex > lastSelectedItemIndex) {
|
if (selectedItemIndex > lastSelectedItemIndex) {
|
||||||
@@ -151,6 +159,13 @@ export class SelectedItemsController extends AbstractViewController {
|
|||||||
this.lastSelectedItem = item
|
this.lastSelectedItem = item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectAll = () => {
|
||||||
|
void this.selectItemsRange({
|
||||||
|
startingIndex: 0,
|
||||||
|
endingIndex: this.itemListController.listLength - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private deselectAll = (): void => {
|
private deselectAll = (): void => {
|
||||||
this.setSelectedItems({})
|
this.setSelectedItems({})
|
||||||
|
|
||||||
@@ -184,7 +199,7 @@ export class SelectedItemsController extends AbstractViewController {
|
|||||||
this.lastSelectedItem = item
|
this.lastSelectedItem = item
|
||||||
}
|
}
|
||||||
} else if (userTriggered && hasShift) {
|
} else if (userTriggered && hasShift) {
|
||||||
await this.selectItemsRange(item)
|
await this.selectItemsRange({ selectedItem: item })
|
||||||
} else {
|
} else {
|
||||||
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedItems[uuid]
|
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedItems[uuid]
|
||||||
if (shouldSelectNote && isAuthorizedForAccess) {
|
if (shouldSelectNote && isAuthorizedForAccess) {
|
||||||
|
|||||||
Reference in New Issue
Block a user