diff --git a/frontend/src/desktop/DesktopDocumentCard.tsx b/frontend/src/desktop/DesktopDocumentCard.tsx index 3fc40a2..be1988a 100644 --- a/frontend/src/desktop/DesktopDocumentCard.tsx +++ b/frontend/src/desktop/DesktopDocumentCard.tsx @@ -31,7 +31,7 @@ interface DesktopDocumentCardProps { getDocumentAsset?: (...args: any[]) => unknown; handleNavigatorSnapshot?: (...args: any[]) => void; cardPointerHandlers?: React.HTMLAttributes; - onDocumentActivate?: (id: string) => void; + onDocumentActivate?: (id: string, event?: any) => void; onTagDragEnter?: (event: React.DragEvent, docId: DocumentId) => void; onTagDragOver?: (event: React.DragEvent, docId: DocumentId) => void; onTagDragLeave?: (event: React.DragEvent, docId: DocumentId) => void; @@ -116,7 +116,7 @@ const DesktopDocumentCard: React.FC = ({ onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { preventAll(event); - onDocumentActivate?.(doc.id); + onDocumentActivate?.(doc.id, event); } }} > diff --git a/frontend/src/desktop/DesktopPreviewCard.tsx b/frontend/src/desktop/DesktopPreviewCard.tsx index 3f097e8..3925edb 100644 --- a/frontend/src/desktop/DesktopPreviewCard.tsx +++ b/frontend/src/desktop/DesktopPreviewCard.tsx @@ -31,7 +31,7 @@ interface DesktopPreviewCardProps { doc: Document | null; title?: string; ensureAssetUrl?: EnsureAssetUrl | null; - getDocumentAsset: GetDocumentAsset; + getDocumentAsset?: GetDocumentAsset; onNavigatorSnapshot?: (docId: Identifier, snapshot: NavigatorSnapshot | null) => void; shouldLoad?: boolean; } diff --git a/frontend/src/desktop/DesktopWorkspace.tsx b/frontend/src/desktop/DesktopWorkspace.tsx index 6665a8a..a34b717 100644 --- a/frontend/src/desktop/DesktopWorkspace.tsx +++ b/frontend/src/desktop/DesktopWorkspace.tsx @@ -57,7 +57,7 @@ export interface DesktopWorkspaceProps { const DesktopDocumentContainer: React.FC & { onSelect: (ids: string[], extend?: boolean) => void; onDeselect: (ids: string[]) => void; - onDocumentActivate?: (id: string) => void; + onDocumentActivate?: (id: string, event?: any) => void; selection: string[]; }> = React.memo((props) => { const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection } = props; @@ -280,7 +280,7 @@ const DesktopWorkspaceContent: React.FC = ({ ensureAssetUrl={ensureAssetUrl} getDocumentAsset={getDocumentAsset} handleNavigatorSnapshot={() => { }} - onDocumentActivate={(id) => { onDocumentActivate?.({ id } as DeskDocument) }} + onDocumentActivate={(_id, event) => { onDocumentActivate?.(doc, event) }} layoutCard={layoutCard} onTagDragEnter={tagInteractions.handleTagDragEnterDoc} onTagDragOver={tagInteractions.handleTagDragOverDoc} diff --git a/frontend/src/desktop/useCardPointer.ts b/frontend/src/desktop/useCardPointer.ts index 390513d..5890a06 100644 --- a/frontend/src/desktop/useCardPointer.ts +++ b/frontend/src/desktop/useCardPointer.ts @@ -14,11 +14,12 @@ export const useCardPointer = ( selection: string[], onSelect: (ids: string[], extend?: boolean) => void, onDeselect: (ids: string[]) => void, - onDocumentActivate?: (id: string) => void + onDocumentActivate?: (id: string, event?: React.PointerEvent) => void ) => { const [state, setState] = React.useState('idle'); const initialPosition = useRef<{ x: number, y: number } | null>(null); const lastPosition = useRef<{ x: number, y: number } | null>(null); + const lastClickTime = useRef(0); const longPressTimer = useRef | null>(null); @@ -37,8 +38,8 @@ export const useCardPointer = ( }, [state]); const onPointerDown = useCallback((e: React.PointerEvent) => { - // Only left click - if (e.button !== 0) return; + // Allow left (0) and middle (1) click + if (e.button !== 0 && e.button !== 1) return; e.preventDefault(); @@ -201,13 +202,15 @@ export const useCardPointer = ( if (state === 'drag' || state === 'drag-start') { handleDragEnd(card.store, selection, e.pointerId); } else if (state === 'click') { - if (isSelected) { - const isUnobstructed = card.isUnobstructed(); + const now = Date.now(); + if (now - lastClickTime.current < 300) { + onDocumentActivate?.(card.id, e); + } + lastClickTime.current = now; + if (isSelected) { if (hasModifier) { onDeselect([card.id]); - } else if (isUnobstructed) { - onDocumentActivate?.(card.id); } } else { onSelect([card.id], hasModifier); diff --git a/frontend/src/documents/hooks/useDocumentItemLogic.ts b/frontend/src/documents/hooks/useDocumentItemLogic.ts index 2eb46c4..a1d77cf 100644 --- a/frontend/src/documents/hooks/useDocumentItemLogic.ts +++ b/frontend/src/documents/hooks/useDocumentItemLogic.ts @@ -1,5 +1,6 @@ import React, { type DragEvent } from 'react'; import { parseTagTransferPayload } from '../tagTransfer'; +import { createDocumentEntryKey } from '../../app/entryKey'; import type { Document } from '../../types/documents'; import type { DocumentsViewProps } from '../panel/DocumentsPanel'; import type { DocumentViewLogic } from './useDocumentViewLogic'; @@ -14,7 +15,6 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { doc, viewLogic, draggingDocumentIdsSet, - onDocumentClick, onDocumentActivate, onDocumentDragStart, onDocumentDragEnd, @@ -37,6 +37,7 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { savingId: savingDocumentId, attachInputRef: attachDocumentInputRef, }, + handleEntrySelection, } = viewLogic; const isSelected = selectedDocumentIdsSet?.has(doc.id); @@ -50,7 +51,10 @@ export const useDocumentItemLogic = (props: UseDocumentItemLogicProps) => { const allowInlineDocumentEdit = onDocumentRename && isSelected && totalSelectionCount === 1; const handlers = { - onClick: (event: React.MouseEvent) => onDocumentClick?.(doc, event), + onClick: (event: React.MouseEvent) => { + const key = createDocumentEntryKey(doc.id); + handleEntrySelection(key, event); + }, onDoubleClick: (event: React.MouseEvent) => onDocumentActivate?.(doc, event), onDragStart: (event: DragEvent) => onDocumentDragStart?.(event, doc), onDragEnd: (event: DragEvent) => onDocumentDragEnd?.(event), diff --git a/frontend/src/documents/hooks/useDocumentViewLogic.ts b/frontend/src/documents/hooks/useDocumentViewLogic.ts index a4a9a99..9ea765c 100644 --- a/frontend/src/documents/hooks/useDocumentViewLogic.ts +++ b/frontend/src/documents/hooks/useDocumentViewLogic.ts @@ -16,6 +16,7 @@ export const useDocumentViewLogic = ({ selectedDocumentIds, selectedFolderIds, clearSelection, + handleEntrySelection, } = useWorkspaceSelectionContext(); const selectedDocumentIdsSet = useMemo(() => new Set(selectedDocumentIds), [selectedDocumentIds]); @@ -42,6 +43,7 @@ export const useDocumentViewLogic = ({ selectedDocumentIdsSet, selectedFolderIdsSet, clearSelection, + handleEntrySelection, totalSelectionCount, documentRename, folderRename, diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index 4156e45..e73e8ef 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -42,8 +42,6 @@ interface DocumentsPanelProps extends DocumentsPanelInnerProps { selectionValue: WorkspaceSelectionValue; } -const defaultGetDocumentAsset = (_doc?: unknown, _type?: string) => null; - export type DocumentLinkLike = { url?: string | null; mimeType?: string | null }; export interface DocumentsViewProps { @@ -60,8 +58,7 @@ export interface DocumentsViewProps { onFolderDragStart?: (event: DragEvent, folderId: Identifier | 'root') => void; onFolderDragEnd?: (event: DragEvent) => void; onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise | boolean; - onDocumentClick?: DocumentEventHandler; - onDocumentActivate?: DocumentEventHandler; + onDocumentOpen?: DocumentEventHandler; onDocumentDragStart?: (event: DragEvent, document: Document) => void; onDocumentDragEnd?: (event: DragEvent) => void; onDocumentTagDragOver?: (event: DragEvent) => void; @@ -105,7 +102,7 @@ const DocumentsPanelInner: React.FC = ({ tagLookupById, activeCorrespondentIds = [], ensureAssetUrl = null, - getDocumentAsset = defaultGetDocumentAsset, + getDocumentAsset, isSearchLoading = false, viewMode = 'list', onViewModeChange, @@ -345,29 +342,19 @@ const DocumentsPanelInner: React.FC = ({ [isTagDragEvent], ); - const handleDocumentClick = useCallback( - (doc, event) => { - if (!doc || suppressDocumentClickRef.current || !onEntryPointer) { - return; - } - - onEntryPointer( - { type: EntryType.document, id: doc.id, key: `document:${doc.id}`, document: doc }, - event, - ); - }, - [onEntryPointer], - ); - const handleDocumentActivate = useCallback( (doc, event?: React.MouseEvent | KeyboardEvent | null) => { if (!doc) { return; } - if (event && (event.altKey || (event.button === 1))) { + + // Handle Preview (Alt+Click or Middle Click) + if (event && (event.altKey || ((event as React.MouseEvent).button === 1))) { openPreview(doc); return; } + + // Handle Activation (Double Click, Enter, or explicit call) if (onDocumentActivate) { onDocumentActivate(doc); } @@ -435,7 +422,7 @@ const DocumentsPanelInner: React.FC = ({ onFolderDragStart, onFolderDragEnd, onFolderRename, - onDocumentClick: handleDocumentClick, + onDocumentActivate: handleDocumentActivate, onDocumentDragStart: handleDocumentDragStartLocal, onDocumentDragEnd: handleDocumentDragEndLocal,