a tool for shared writing and social publishing
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}