diff --git a/packages/frontend/core/src/components/page-list/collections/collection-list-item.css.ts b/packages/frontend/core/src/components/page-list/collections/collection-list-item.css.ts index 253497a715..edb3eabb27 100644 --- a/packages/frontend/core/src/components/page-list/collections/collection-list-item.css.ts +++ b/packages/frontend/core/src/components/page-list/collections/collection-list-item.css.ts @@ -106,6 +106,7 @@ export const titleCell = style({ maxWidth: 'calc(100% - 64px)', flex: 1, whiteSpace: 'nowrap', + userSelect: 'none', }); export const titleCellMain = style({ overflow: 'hidden', diff --git a/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx b/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx index 3cadf80521..8a1d8ead8b 100644 --- a/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx +++ b/packages/frontend/core/src/components/page-list/collections/collection-list-item.tsx @@ -5,6 +5,7 @@ import type { PropsWithChildren } from 'react'; import { useCallback, useMemo } from 'react'; import { Link } from 'react-router-dom'; +import { selectionStateAtom, useAtom } from '../scoped-atoms'; import type { CollectionListItemProps, DraggableTitleCellData, @@ -172,14 +173,28 @@ function CollectionListItemWrapper({ children, draggable, }: collectionListWrapperProps) { + const [selectionState, setSelectionActive] = useAtom(selectionStateAtom); const handleClick = useCallback( (e: React.MouseEvent) => { - if (onClick) { + if (!selectionState.selectable) { + return; + } + if (e.shiftKey) { stopPropagation(e); - onClick(); + setSelectionActive(true); + onClick?.(); + return; + } + if (selectionState.selectionActive) { + return onClick?.(); } }, - [onClick] + [ + onClick, + selectionState.selectable, + selectionState.selectionActive, + setSelectionActive, + ] ); const commonProps = useMemo( diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-item.css.ts b/packages/frontend/core/src/components/page-list/docs/page-list-item.css.ts index 253497a715..edb3eabb27 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-item.css.ts +++ b/packages/frontend/core/src/components/page-list/docs/page-list-item.css.ts @@ -106,6 +106,7 @@ export const titleCell = style({ maxWidth: 'calc(100% - 64px)', flex: 1, whiteSpace: 'nowrap', + userSelect: 'none', }); export const titleCellMain = style({ overflow: 'hidden', diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx index 70d0cbab30..8bb8071050 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-item.tsx @@ -6,6 +6,7 @@ import type { PropsWithChildren } from 'react'; import { useCallback, useMemo } from 'react'; import { WorkbenchLink } from '../../../modules/workbench/view/workbench-link'; +import { selectionStateAtom, useAtom } from '../scoped-atoms'; import type { DraggableTitleCellData, PageListItemProps } from '../types'; import { usePageDisplayProperties } from '../use-page-display-properties'; import { ColWrapper, formatDate, stopPropagation } from '../utils'; @@ -237,14 +238,22 @@ function PageListItemWrapper({ children, draggable, }: PageListWrapperProps) { + const [selectionState, setSelectionActive] = useAtom(selectionStateAtom); const handleClick = useCallback( (e: React.MouseEvent) => { - if (onClick) { - stopPropagation(e); - onClick(); + if (!selectionState.selectable) { + return false; } + stopPropagation(e); + if (e.shiftKey) { + setSelectionActive(true); + onClick?.(); + return true; + } + onClick?.(); + return false; }, - [onClick] + [onClick, selectionState.selectable, setSelectionActive] ); const commonProps = useMemo( @@ -255,7 +264,7 @@ function PageListItemWrapper({ className: styles.root, 'data-clickable': !!onClick || !!to, 'data-dragging': isDragging, - onClick: handleClick, + onClick: onClick ? handleClick : undefined, }), [pageId, draggable, onClick, to, isDragging, handleClick] ); diff --git a/packages/frontend/core/src/components/page-list/group-definitions.css.ts b/packages/frontend/core/src/components/page-list/group-definitions.css.ts index 254c267abb..20fc12f5cc 100644 --- a/packages/frontend/core/src/components/page-list/group-definitions.css.ts +++ b/packages/frontend/core/src/components/page-list/group-definitions.css.ts @@ -25,6 +25,7 @@ export const pageCount = style({ fontSize: cssVar('fontBase'), lineHeight: '1.6em', color: cssVar('textSecondaryColor'), + marginRight: '12px', }); export const favouritedIcon = style({ diff --git a/packages/frontend/core/src/components/page-list/list.tsx b/packages/frontend/core/src/components/page-list/list.tsx index 52cbae8d6e..76dbc16a49 100644 --- a/packages/frontend/core/src/components/page-list/list.tsx +++ b/packages/frontend/core/src/components/page-list/list.tsx @@ -67,6 +67,7 @@ const useItemSelectionStateEffect = () => { return; } setSelectionActive(false); + selectionState.onSelectedIdsChange?.([]); }; const escHandler = (e: KeyboardEvent) => { @@ -75,6 +76,7 @@ const useItemSelectionStateEffect = () => { } if (e.key === 'Escape') { setSelectionActive(false); + selectionState.onSelectedIdsChange?.([]); } }; @@ -88,6 +90,7 @@ const useItemSelectionStateEffect = () => { } return; }, [ + selectionState, selectionState.selectable, selectionState.selectionActive, setSelectionActive, diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 5c845ccbe5..35c56a1c6a 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -329,7 +329,7 @@ function pageMetaToListItemProp( createDate: new Date(item.createDate), updatedDate: item.updatedDate ? new Date(item.updatedDate) : undefined, to: props.rowAsLink && !props.selectable ? `/${item.id}` : undefined, - onClick: props.selectable ? toggleSelection : undefined, + onClick: toggleSelection, icon: ( , operations: props.operationsRenderer?.(item), selectable: props.selectable, @@ -412,7 +412,7 @@ function tagMetaToListItemProp( tagId: item.id, title: item.title, to: props.rowAsLink && !props.selectable ? `/tag/${item.id}` : undefined, - onClick: props.selectable ? toggleSelection : undefined, + onClick: toggleSelection, color: item.color, pageCount: item.pageCount, operations: props.operationsRenderer?.(item), diff --git a/packages/frontend/core/src/components/page-list/tags/tag-list-item.css.ts b/packages/frontend/core/src/components/page-list/tags/tag-list-item.css.ts index 2763a12974..e3a09d33d8 100644 --- a/packages/frontend/core/src/components/page-list/tags/tag-list-item.css.ts +++ b/packages/frontend/core/src/components/page-list/tags/tag-list-item.css.ts @@ -105,6 +105,7 @@ export const titleCell = style({ maxWidth: 'calc(100% - 64px)', flex: 1, whiteSpace: 'nowrap', + userSelect: 'none', }); export const titleCellMain = style({ overflow: 'hidden', diff --git a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx index 270229616a..a1c2bea7b0 100644 --- a/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx +++ b/packages/frontend/core/src/components/page-list/tags/tag-list-item.tsx @@ -5,6 +5,7 @@ import type { PropsWithChildren } from 'react'; import { useCallback, useMemo } from 'react'; import { Link } from 'react-router-dom'; +import { selectionStateAtom, useAtom } from '../scoped-atoms'; import type { DraggableTitleCellData, TagListItemProps } from '../types'; import { ColWrapper, stopPropagation } from '../utils'; import * as styles from './tag-list-item.css'; @@ -165,14 +166,28 @@ function TagListItemWrapper({ children, draggable, }: TagListWrapperProps) { + const [selectionState, setSelectionActive] = useAtom(selectionStateAtom); const handleClick = useCallback( (e: React.MouseEvent) => { - if (onClick) { + if (!selectionState.selectable) { + return; + } + if (e.shiftKey) { stopPropagation(e); - onClick(); + setSelectionActive(true); + onClick?.(); + return; + } + if (selectionState.selectionActive) { + return onClick?.(); } }, - [onClick] + [ + onClick, + selectionState.selectable, + selectionState.selectionActive, + setSelectionActive, + ] ); const commonProps = useMemo( diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 5ceda1ef3a..4a7f7fccf0 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -20,6 +20,9 @@ export const WorkbenchLink = ({ (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); + if (onClick?.(event)) { + return; + } if (event.ctrlKey || event.metaKey) { if (appSettings.enableMultiView && environment.isDesktop) { @@ -34,7 +37,6 @@ export const WorkbenchLink = ({ } else { workbench.open(to); } - onClick?.(event); }, [appSettings.enableMultiView, basename, onClick, to, workbench] );