a tool for shared writing and social publishing
at feature/backdate 86 lines 3.4 kB view raw
1import { useSelectingMouse } from "components/SelectionManager/selectionState"; 2import { MouseEvent, useCallback, useRef } from "react"; 3import { useUIState } from "src/useUIState"; 4import { Block } from "./Block"; 5import { isTextBlock } from "src/utils/isTextBlock"; 6import { useEntitySetContext } from "components/EntitySetProvider"; 7import { useReplicache } from "src/replicache"; 8import { getBlocksWithType } from "src/hooks/queries/useBlocks"; 9import { focusBlock } from "src/utils/focusBlock"; 10import { useIsMobile } from "src/hooks/isMobile"; 11import { scrollIntoViewIfNeeded } from "src/utils/scrollIntoViewIfNeeded"; 12import { elementId } from "src/utils/elementId"; 13 14let debounce: number | null = null; 15export function useBlockMouseHandlers(props: Block) { 16 let entity_set = useEntitySetContext(); 17 let isMobile = useIsMobile(); 18 let { rep } = useReplicache(); 19 let onMouseDown = useCallback( 20 (e: MouseEvent) => { 21 if ((e.target as Element).getAttribute("data-draggable")) return; 22 if ((e.target as Element).tagName === "BUTTON") return; 23 if ((e.target as Element).tagName === "SELECT") return; 24 if ((e.target as Element).tagName === "OPTION") return; 25 if (isMobile) return; 26 if (!entity_set.permissions.write) return; 27 useSelectingMouse.setState({ start: props.value }); 28 if (e.shiftKey) { 29 if ( 30 useUIState.getState().selectedBlocks[0]?.value === props.value && 31 useUIState.getState().selectedBlocks.length === 1 32 ) 33 return; 34 e.preventDefault(); 35 useUIState.getState().addBlockToSelection(props); 36 } else { 37 if (e.isDefaultPrevented()) return; 38 useUIState.getState().setFocusedBlock({ 39 entityType: "block", 40 entityID: props.value, 41 parent: props.parent, 42 }); 43 useUIState.getState().setSelectedBlock(props); 44 45 // scroll to the page containing the block, if offscreen 46 let parentPage = elementId.page(props.parent).container; 47 setTimeout(() => { 48 scrollIntoViewIfNeeded( 49 document.getElementById(parentPage), 50 false, 51 "smooth", 52 ); 53 }, 50); 54 } 55 }, 56 [props, entity_set.permissions.write, isMobile], 57 ); 58 let onMouseEnter = useCallback( 59 async (e: MouseEvent) => { 60 if (isMobile) return; 61 if (!entity_set.permissions.write) return; 62 if (debounce) window.clearTimeout(debounce); 63 debounce = window.setTimeout(async () => { 64 debounce = null; 65 if (e.buttons !== 1) return; 66 let selection = useSelectingMouse.getState(); 67 if (!selection.start) return; 68 let siblings = 69 (await rep?.query((tx) => getBlocksWithType(tx, props.parent))) || []; 70 let startIndex = siblings.findIndex((b) => b.value === selection.start); 71 if (startIndex === -1) return; 72 let endIndex = siblings.findIndex((b) => b.value === props.value); 73 let start = Math.min(startIndex, endIndex); 74 let end = Math.max(startIndex, endIndex); 75 let selected = siblings.slice(start, end + 1).map((b) => ({ 76 value: b.value, 77 position: b.position, 78 parent: props.parent, 79 })); 80 useUIState.getState().setSelectedBlocks(selected); 81 }, 15); 82 }, 83 [rep, props, entity_set.permissions.write, isMobile], 84 ); 85 return { onMouseDown, onMouseEnter }; 86}