a tool for shared writing and social publishing
at update/delete-blocks 104 lines 3.8 kB view raw
1import { useSelectingMouse } from "components/SelectionManager/selectionState"; 2import { MouseEvent, useCallback } 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; 15 16// Track scrolling state for mobile 17let isScrolling = false; 18let scrollTimeout: number | null = null; 19 20if (typeof window !== "undefined") { 21 window.addEventListener( 22 "scroll", 23 () => { 24 isScrolling = true; 25 if (scrollTimeout) window.clearTimeout(scrollTimeout); 26 scrollTimeout = window.setTimeout(() => { 27 isScrolling = false; 28 }, 150); 29 }, 30 true, 31 ); 32} 33export function useBlockMouseHandlers(props: Block) { 34 let entity_set = useEntitySetContext(); 35 let isMobile = useIsMobile(); 36 let { rep } = useReplicache(); 37 let onMouseDown = useCallback( 38 (e: MouseEvent) => { 39 if ((e.target as Element).getAttribute("data-draggable")) return; 40 if ((e.target as Element).tagName === "BUTTON") return; 41 if ((e.target as Element).tagName === "SELECT") return; 42 if ((e.target as Element).tagName === "OPTION") return; 43 if (isMobile && isScrolling) return; 44 if (!entity_set.permissions.write) return; 45 useSelectingMouse.setState({ start: props.value }); 46 if (e.shiftKey) { 47 if ( 48 useUIState.getState().selectedBlocks[0]?.value === props.value && 49 useUIState.getState().selectedBlocks.length === 1 50 ) 51 return; 52 e.preventDefault(); 53 useUIState.getState().addBlockToSelection(props); 54 } else { 55 if (e.isDefaultPrevented()) return; 56 useUIState.getState().setFocusedBlock({ 57 entityType: "block", 58 entityID: props.value, 59 parent: props.parent, 60 }); 61 useUIState.getState().setSelectedBlock(props); 62 63 // scroll to the page containing the block, if offscreen 64 let parentPage = elementId.page(props.parent).container; 65 setTimeout(() => { 66 scrollIntoViewIfNeeded( 67 document.getElementById(parentPage), 68 false, 69 "smooth", 70 ); 71 }, 50); 72 } 73 }, 74 [props, entity_set.permissions.write, isMobile], 75 ); 76 let onMouseEnter = useCallback( 77 async (e: MouseEvent) => { 78 if (isMobile && isScrolling) return; 79 if (!entity_set.permissions.write) return; 80 if (debounce) window.clearTimeout(debounce); 81 debounce = window.setTimeout(async () => { 82 debounce = null; 83 if (e.buttons !== 1) return; 84 let selection = useSelectingMouse.getState(); 85 if (!selection.start) return; 86 let siblings = 87 (await rep?.query((tx) => getBlocksWithType(tx, props.parent))) || []; 88 let startIndex = siblings.findIndex((b) => b.value === selection.start); 89 if (startIndex === -1) return; 90 let endIndex = siblings.findIndex((b) => b.value === props.value); 91 let start = Math.min(startIndex, endIndex); 92 let end = Math.max(startIndex, endIndex); 93 let selected = siblings.slice(start, end + 1).map((b) => ({ 94 value: b.value, 95 position: b.position, 96 parent: props.parent, 97 })); 98 useUIState.getState().setSelectedBlocks(selected); 99 }, 15); 100 }, 101 [rep, props, entity_set.permissions.write, isMobile], 102 ); 103 return { onMouseDown, onMouseEnter }; 104}