feat: Re-enabled multiple selection on mobile, with improved UI (#2609)
This commit is contained in:
@@ -33,6 +33,9 @@ import EmptyFilesView from './EmptyFilesView'
|
|||||||
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
import { PaneLayout } from '@/Controllers/PaneController/PaneLayout'
|
||||||
import { usePaneSwipeGesture } from '../Panes/usePaneGesture'
|
import { usePaneSwipeGesture } from '../Panes/usePaneGesture'
|
||||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||||
|
import Icon from '../Icon/Icon'
|
||||||
|
import MobileMultiSelectionToolbar from './MobileMultiSelectionToolbar'
|
||||||
|
import StyledTooltip from '../StyledTooltip/StyledTooltip'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -53,6 +56,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
noAccountWarningController,
|
noAccountWarningController,
|
||||||
searchOptionsController,
|
searchOptionsController,
|
||||||
linkingController,
|
linkingController,
|
||||||
|
notesController,
|
||||||
} = application
|
} = application
|
||||||
|
|
||||||
const { setPaneLayout, panes } = useResponsiveAppPane()
|
const { setPaneLayout, panes } = useResponsiveAppPane()
|
||||||
@@ -285,7 +289,7 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
aria-label={'Notes & Files'}
|
aria-label={'Notes & Files'}
|
||||||
ref={mergeRefs([innerRef, setElement])}
|
ref={mergeRefs([innerRef, setElement])}
|
||||||
>
|
>
|
||||||
{isMobileScreen && (
|
{isMobileScreen && !itemListController.isMultipleSelectionMode && (
|
||||||
<FloatingAddButton onClick={addNewItem} label={addButtonLabel} style={dailyMode ? 'danger' : 'info'} />
|
<FloatingAddButton onClick={addNewItem} label={addButtonLabel} style={dailyMode ? 'danger' : 'info'} />
|
||||||
)}
|
)}
|
||||||
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
|
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
|
||||||
@@ -319,6 +323,33 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{itemListController.isMultipleSelectionMode && (
|
||||||
|
<div className="flex items-center border-b border-l-2 border-border border-l-transparent py-2.5 pr-4">
|
||||||
|
<div className="px-4">
|
||||||
|
<StyledTooltip label="Select all items" showOnHover showOnMobile>
|
||||||
|
<button
|
||||||
|
className="ml-auto rounded border border-border p-1 hover:bg-contrast"
|
||||||
|
onClick={() => {
|
||||||
|
itemListController.selectAll()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="select-all" size="medium" />
|
||||||
|
</button>
|
||||||
|
</StyledTooltip>
|
||||||
|
</div>
|
||||||
|
<div className="text-base font-semibold md:text-sm">{itemListController.selectedItemsCount} selected</div>
|
||||||
|
<StyledTooltip label="Cancel multiple selection" showOnHover showOnMobile>
|
||||||
|
<button
|
||||||
|
className="ml-auto rounded border border-border p-1 hover:bg-contrast"
|
||||||
|
onClick={() => {
|
||||||
|
itemListController.cancelMultipleSelection()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon type="close" size="medium" />
|
||||||
|
</button>
|
||||||
|
</StyledTooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{selectedAsTag && dailyMode && (
|
{selectedAsTag && dailyMode && (
|
||||||
<DailyContentList
|
<DailyContentList
|
||||||
items={items}
|
items={items}
|
||||||
@@ -350,6 +381,9 @@ const ContentListView = forwardRef<HTMLDivElement, Props>(
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
{isMobileScreen && itemListController.isMultipleSelectionMode && (
|
||||||
|
<MobileMultiSelectionToolbar notesController={notesController} />
|
||||||
|
)}
|
||||||
<div className="absolute bottom-0 h-safe-bottom w-full" />
|
<div className="absolute bottom-0 h-safe-bottom w-full" />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
|
import Icon from '../Icon/Icon'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
notesController: NotesController
|
||||||
|
}
|
||||||
|
|
||||||
|
const MobileMultiSelectionToolbar = ({ notesController }: Props) => {
|
||||||
|
const { selectedNotes } = notesController
|
||||||
|
|
||||||
|
const archived = selectedNotes.some((note) => note.archived)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full bg-contrast pb-safe-bottom">
|
||||||
|
<button
|
||||||
|
className="flex-grow px-2 py-3 active:bg-passive-3"
|
||||||
|
onClick={() => notesController.togglePinSelectedNotes()}
|
||||||
|
>
|
||||||
|
<Icon type="pin" className="mx-auto text-info" size="large" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex-grow px-2 py-3 active:bg-passive-3"
|
||||||
|
onClick={() => notesController.toggleArchiveSelectedNotes().catch(console.error)}
|
||||||
|
>
|
||||||
|
<Icon type={archived ? 'unarchive' : 'archive'} className="mx-auto text-info" size="large" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex-grow px-2 py-3 active:bg-passive-3"
|
||||||
|
onClick={() => notesController.setTrashSelectedNotes(true).catch(console.error)}
|
||||||
|
>
|
||||||
|
<Icon type="trash" className="mx-auto text-info" size="large" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="flex-grow px-2 py-3 active:bg-passive-3"
|
||||||
|
onClick={() => notesController.setContextMenuOpen(true)}
|
||||||
|
>
|
||||||
|
<Icon type="more" className="mx-auto text-info" size="large" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MobileMultiSelectionToolbar
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isFile, SNNote } from '@standardnotes/snjs'
|
import { isFile, SNNote } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback, useRef } from 'react'
|
import { FunctionComponent, MouseEvent, useCallback, useRef } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import ListItemConflictIndicator from './ListItemConflictIndicator'
|
import ListItemConflictIndicator from './ListItemConflictIndicator'
|
||||||
import ListItemFlagIcons from './ListItemFlagIcons'
|
import ListItemFlagIcons from './ListItemFlagIcons'
|
||||||
@@ -17,6 +17,7 @@ import ListItemVaultInfo from './ListItemVaultInfo'
|
|||||||
import { NoteDragDataFormat } from '../Tags/DragNDrop'
|
import { NoteDragDataFormat } from '../Tags/DragNDrop'
|
||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
import useItem from '@/Hooks/useItem'
|
import useItem from '@/Hooks/useItem'
|
||||||
|
import CheckIndicator from '../Checkbox/CheckIndicator'
|
||||||
|
|
||||||
const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
||||||
application,
|
application,
|
||||||
@@ -50,7 +51,17 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
|||||||
notesController.setContextMenuOpen(true)
|
notesController.setContextMenuOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openContextMenu = async (posX: number, posY: number) => {
|
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||||
|
|
||||||
|
const handleContextMenuEvent = async (posX: number, posY: number) => {
|
||||||
|
if (isMobileScreen) {
|
||||||
|
if (!application.itemListController.isMultipleSelectionMode) {
|
||||||
|
application.itemListController.replaceSelection(item)
|
||||||
|
}
|
||||||
|
application.itemListController.enableMultipleSelectionMode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let shouldOpenContextMenu = selected
|
let shouldOpenContextMenu = selected
|
||||||
|
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
@@ -65,17 +76,26 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = useCallback(async () => {
|
const onClick = useCallback(
|
||||||
await onSelect(item, true)
|
(event: MouseEvent) => {
|
||||||
}, [item, onSelect])
|
if ((event.ctrlKey || event.metaKey) && !application.itemListController.isMultipleSelectionMode) {
|
||||||
|
application.itemListController.enableMultipleSelectionMode()
|
||||||
|
}
|
||||||
|
if (selected && !application.itemListController.isMultipleSelectionMode) {
|
||||||
|
application.itemListController.openSingleSelectedItem({ userTriggered: true }).catch(console.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onSelect(item, true).catch(console.error)
|
||||||
|
},
|
||||||
|
[application.itemListController, item, onSelect, selected],
|
||||||
|
)
|
||||||
|
|
||||||
useContextMenuEvent(listItemRef, openContextMenu)
|
useContextMenuEvent(listItemRef, handleContextMenuEvent)
|
||||||
|
|
||||||
log(LoggingDomain.ItemsList, 'Rendering note list item', item.title)
|
log(LoggingDomain.ItemsList, 'Rendering note list item', item.title)
|
||||||
|
|
||||||
const hasOffsetBorder = !isNextItemTiled
|
const hasOffsetBorder = !isNextItemTiled
|
||||||
|
|
||||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
|
||||||
const dragPreview = useRef<HTMLDivElement>()
|
const dragPreview = useRef<HTMLDivElement>()
|
||||||
|
|
||||||
const createDragPreview = () => {
|
const createDragPreview = () => {
|
||||||
@@ -108,14 +128,18 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
|||||||
ref={listItemRef}
|
ref={listItemRef}
|
||||||
role="button"
|
role="button"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'content-list-item flex w-full cursor-pointer items-stretch text-text',
|
'content-list-item flex w-full cursor-pointer items-stretch border-l-2 text-text',
|
||||||
selected && `selected border-l-2 border-solid border-accessory-tint-${tint}`,
|
selected
|
||||||
isPreviousItemTiled && 'mt-3 border-t border-solid border-t-border',
|
? `selected ${
|
||||||
isNextItemTiled && 'mb-3 border-b border-solid border-b-border',
|
application.itemListController.isMultipleSelectionMode ? 'border-info' : `border-accessory-tint-${tint}`
|
||||||
|
}`
|
||||||
|
: 'border-transparent',
|
||||||
|
isPreviousItemTiled && 'mt-3 border-t border-t-border',
|
||||||
|
isNextItemTiled && 'mb-3 border-b border-b-border',
|
||||||
)}
|
)}
|
||||||
id={item.uuid}
|
id={item.uuid}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
draggable={!isMobileScreen}
|
draggable={!isMobileScreen && !application.itemListController.isMultipleSelectionMode}
|
||||||
onDragStart={(event) => {
|
onDragStart={(event) => {
|
||||||
if (!listItemRef.current) {
|
if (!listItemRef.current) {
|
||||||
return
|
return
|
||||||
@@ -133,7 +157,11 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps<SNNote>> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!hideIcon ? (
|
{application.itemListController.isMultipleSelectionMode ? (
|
||||||
|
<div className="mr-0 flex flex-col items-center justify-between gap-2 p-4 pr-4">
|
||||||
|
<CheckIndicator className="md:!h-5 md:!w-5" checked={selected} />
|
||||||
|
</div>
|
||||||
|
) : !hideIcon ? (
|
||||||
<div className="mr-0 flex flex-col items-center justify-between gap-2 p-4 pr-4">
|
<div className="mr-0 flex flex-col items-center justify-between gap-2 p-4 pr-4">
|
||||||
<Icon type={icon} className={`text-accessory-tint-${tint}`} />
|
<Icon type={icon} className={`text-accessory-tint-${tint}`} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const IconNameToSvgMapping = {
|
|||||||
'premium-feature': icons.PremiumFeatureIcon,
|
'premium-feature': icons.PremiumFeatureIcon,
|
||||||
'rich-text': icons.RichTextIcon,
|
'rich-text': icons.RichTextIcon,
|
||||||
'safe-square': icons.SafeSquareIcon,
|
'safe-square': icons.SafeSquareIcon,
|
||||||
|
'select-all': icons.SelectAllIcon,
|
||||||
'sort-descending': icons.SortDescendingIcon,
|
'sort-descending': icons.SortDescendingIcon,
|
||||||
'star-circle-filled': icons.StarCircleFilled,
|
'star-circle-filled': icons.StarCircleFilled,
|
||||||
'star-filled': icons.StarFilledIcon,
|
'star-filled': icons.StarFilledIcon,
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
|||||||
</MenuSection>
|
</MenuSection>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MenuSection>
|
<MenuSection className={notes.length > 1 ? 'md:!mb-2' : ''}>
|
||||||
{application.featuresController.isVaultsEnabled() && (
|
{application.featuresController.isVaultsEnabled() && (
|
||||||
<AddToVaultMenuOption
|
<AddToVaultMenuOption
|
||||||
iconClassName={iconClass}
|
iconClassName={iconClass}
|
||||||
@@ -507,7 +507,7 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
|||||||
)}
|
)}
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
|
|
||||||
{notes.length === 1 ? (
|
{notes.length === 1 && (
|
||||||
<>
|
<>
|
||||||
{notes[0].noteType === NoteType.Super && (
|
{notes[0].noteType === NoteType.Super && (
|
||||||
<SuperNoteOptions
|
<SuperNoteOptions
|
||||||
@@ -538,8 +538,6 @@ const NotesOptions = ({ notes, closeMenu }: NotesOptionsProps) => {
|
|||||||
|
|
||||||
<NoteSizeWarning note={notes[0]} />
|
<NoteSizeWarning note={notes[0]} />
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<div className="h-2" />
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ModalOverlay isOpen={showExportSuperModal} close={closeSuperExportModal}>
|
<ModalOverlay isOpen={showExportSuperModal} close={closeSuperExportModal}>
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ export class ItemListController
|
|||||||
selectedUuids: Set<UuidString> = observable(new Set<UuidString>())
|
selectedUuids: Set<UuidString> = observable(new Set<UuidString>())
|
||||||
selectedItems: Record<UuidString, ListableContentItem> = {}
|
selectedItems: Record<UuidString, ListableContentItem> = {}
|
||||||
|
|
||||||
|
isMultipleSelectionMode = false
|
||||||
|
|
||||||
override deinit() {
|
override deinit() {
|
||||||
super.deinit()
|
super.deinit()
|
||||||
;(this.noteFilterText as unknown) = undefined
|
;(this.noteFilterText as unknown) = undefined
|
||||||
@@ -169,6 +171,10 @@ export class ItemListController
|
|||||||
setSelectedItems: action,
|
setSelectedItems: action,
|
||||||
|
|
||||||
hydrateFromPersistedValue: action,
|
hydrateFromPersistedValue: action,
|
||||||
|
|
||||||
|
isMultipleSelectionMode: observable,
|
||||||
|
enableMultipleSelectionMode: action,
|
||||||
|
cancelMultipleSelection: action,
|
||||||
})
|
})
|
||||||
|
|
||||||
eventBus.addEventHandler(this, CrossControllerEvent.TagChanged)
|
eventBus.addEventHandler(this, CrossControllerEvent.TagChanged)
|
||||||
@@ -257,6 +263,17 @@ export class ItemListController
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
this.disposers.push(
|
||||||
|
reaction(
|
||||||
|
() => this.selectedItemsCount,
|
||||||
|
() => {
|
||||||
|
if (this.selectedItemsCount === 0) {
|
||||||
|
this.cancelMultipleSelection()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
this.resetPagination(true)
|
this.resetPagination(true)
|
||||||
}
|
}
|
||||||
@@ -1079,6 +1096,9 @@ export class ItemListController
|
|||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.setSelectedUuids(this.selectedUuids.add(item.uuid))
|
this.setSelectedUuids(this.selectedUuids.add(item.uuid))
|
||||||
this.lastSelectedItem = item
|
this.lastSelectedItem = item
|
||||||
|
if (this.selectedItemsCount > 1 && !this.isMultipleSelectionMode) {
|
||||||
|
this.enableMultipleSelectionMode()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1086,6 +1106,8 @@ export class ItemListController
|
|||||||
cancelMultipleSelection = () => {
|
cancelMultipleSelection = () => {
|
||||||
this.keyboardService.cancelAllKeyboardModifiers()
|
this.keyboardService.cancelAllKeyboardModifiers()
|
||||||
|
|
||||||
|
this.isMultipleSelectionMode = false
|
||||||
|
|
||||||
const firstSelectedItem = this.firstSelectedItem
|
const firstSelectedItem = this.firstSelectedItem
|
||||||
|
|
||||||
if (firstSelectedItem) {
|
if (firstSelectedItem) {
|
||||||
@@ -1095,7 +1117,7 @@ export class ItemListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceSelection = (item: ListableContentItem): void => {
|
replaceSelection = (item: ListableContentItem): void => {
|
||||||
this.deselectAll()
|
this.deselectAll()
|
||||||
runInAction(() => this.setSelectedUuids(this.selectedUuids.add(item.uuid)))
|
runInAction(() => this.setSelectedUuids(this.selectedUuids.add(item.uuid)))
|
||||||
|
|
||||||
@@ -1103,10 +1125,11 @@ export class ItemListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectAll = () => {
|
selectAll = () => {
|
||||||
void this.selectItemsRange({
|
const allItems = this.items.filter((item) => !item.protected)
|
||||||
startingIndex: 0,
|
const lastItem = allItems[allItems.length - 1]
|
||||||
endingIndex: this.listLength - 1,
|
this.setSelectedUuids(new Set(Uuids(allItems)))
|
||||||
})
|
this.lastSelectedItem = lastItem
|
||||||
|
this.enableMultipleSelectionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
deselectAll = (): void => {
|
deselectAll = (): void => {
|
||||||
@@ -1136,6 +1159,10 @@ export class ItemListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableMultipleSelectionMode = () => {
|
||||||
|
this.isMultipleSelectionMode = true
|
||||||
|
}
|
||||||
|
|
||||||
selectItem = async (
|
selectItem = async (
|
||||||
uuid: UuidString,
|
uuid: UuidString,
|
||||||
userTriggered?: boolean,
|
userTriggered?: boolean,
|
||||||
@@ -1152,14 +1179,13 @@ export class ItemListController
|
|||||||
|
|
||||||
log(LoggingDomain.Selection, 'Select item', item.uuid)
|
log(LoggingDomain.Selection, 'Select item', item.uuid)
|
||||||
|
|
||||||
const supportsMultipleSelection = this.options.allowMultipleSelection
|
|
||||||
const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta)
|
|
||||||
const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl)
|
|
||||||
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
|
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
|
||||||
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
||||||
const isAuthorizedForAccess = await this.protections.authorizeItemAccess(item)
|
const isAuthorizedForAccess = await this.protections.authorizeItemAccess(item)
|
||||||
|
|
||||||
if (supportsMultipleSelection && userTriggered && (hasMeta || hasCtrl)) {
|
if (userTriggered && hasShift) {
|
||||||
|
await this.selectItemsRange({ selectedItem: item })
|
||||||
|
} else if (userTriggered && this.isMultipleSelectionMode) {
|
||||||
if (this.selectedUuids.has(uuid) && hasMoreThanOneSelected) {
|
if (this.selectedUuids.has(uuid) && hasMoreThanOneSelected) {
|
||||||
this.removeSelectedItem(uuid)
|
this.removeSelectedItem(uuid)
|
||||||
} else if (isAuthorizedForAccess) {
|
} else if (isAuthorizedForAccess) {
|
||||||
@@ -1167,17 +1193,17 @@ export class ItemListController
|
|||||||
this.setSelectedUuids(this.selectedUuids)
|
this.setSelectedUuids(this.selectedUuids)
|
||||||
this.lastSelectedItem = item
|
this.lastSelectedItem = item
|
||||||
}
|
}
|
||||||
} else if (supportsMultipleSelection && userTriggered && hasShift) {
|
if (this.selectedItemsCount === 1) {
|
||||||
await this.selectItemsRange({ selectedItem: item })
|
this.cancelMultipleSelection()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedUuids.has(uuid)
|
const shouldSelectNote = hasMoreThanOneSelected || !this.selectedUuids.has(uuid)
|
||||||
if (shouldSelectNote && isAuthorizedForAccess) {
|
if (shouldSelectNote && isAuthorizedForAccess) {
|
||||||
this.replaceSelection(item)
|
this.replaceSelection(item)
|
||||||
|
await this.openSingleSelectedItem({ userTriggered: userTriggered ?? false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.openSingleSelectedItem({ userTriggered: userTriggered ?? false })
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
didSelect: this.selectedUuids.has(uuid),
|
didSelect: this.selectedUuids.has(uuid),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,17 @@ export class NotesController
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleArchiveSelectedNotes(): Promise<void> {
|
||||||
|
const notes = this.selectedNotes
|
||||||
|
const archived = notes.some((note) => note.archived)
|
||||||
|
|
||||||
|
if (!archived) {
|
||||||
|
await this.setArchiveSelectedNotes(true)
|
||||||
|
} else {
|
||||||
|
await this.setArchiveSelectedNotes(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async setProtectSelectedNotes(protect: boolean): Promise<void> {
|
async setProtectSelectedNotes(protect: boolean): Promise<void> {
|
||||||
const selectedNotes = this.getSelectedNotesList()
|
const selectedNotes = this.getSelectedNotesList()
|
||||||
if (protect) {
|
if (protect) {
|
||||||
|
|||||||
Reference in New Issue
Block a user