"use client"; import { Fact, useEntity, useReplicache } from "src/replicache"; import { useIsPageFocused, useUIState } from "src/useUIState"; import { foldBlocks, unfoldBlocks } from "src/utils/foldBlocks"; import { useFoldedBlocks } from "components/FoldStateProvider"; import { filterBlocksForZoom, isBlockHidden } from "src/replicache/getBlocks"; import { useBlocks } from "src/hooks/queries/useBlocks"; import { useEditorStates } from "src/state/useEditorState"; import { useEntitySetContext } from "components/EntitySetProvider"; import { isTextBlock } from "src/utils/isTextBlock"; import { focusBlock } from "src/utils/focusBlock"; import { addBlockBelow, focusNewTextBlock } from "src/utils/addBlockBelow"; import { Block } from "./Block"; import { useEffect, useMemo, useState } from "react"; import { addShortcut } from "src/shortcuts"; import { useHandleDrop } from "./useHandleDrop"; import { listDndPages } from "./ListDndState"; import { useFootnoteContext } from "components/Footnotes/FootnoteContext"; export function Blocks(props: { entityID: string }) { let zoomedBlock = useUIState((s) => s.zoomedBlocks[props.entityID]); let isPageFocused = useIsPageFocused(props.entityID); let pageBlocks = useBlocks(props.entityID); let blocks = useMemo( () => filterBlocksForZoom(pageBlocks, zoomedBlock), [pageBlocks, zoomedBlock], ); let { rep } = useReplicache(); let foldedBlocks = useFoldedBlocks(); let foldableParentSet = useMemo(() => foldableParents(blocks), [blocks]); // Folds on ancestors outside the zoomed view don't hide anything inside it. let visibleBlocks = useMemo(() => { let folded = foldedBlocks.filter((entity) => foldableParentSet.has(entity)); return blocks.filter((block) => !isBlockHidden(block, folded)); }, [blocks, foldedBlocks, foldableParentSet]); let zoomDepth = zoomedBlock ? blocks[0]?.listData?.depth : undefined; let hasBlocks = pageBlocks.length > 0; useEffect(() => { if (zoomedBlock && hasBlocks && zoomDepth === undefined) useUIState.getState().zoomOutOfBlock(props.entityID); }, [hasBlocks, props.entityID, zoomDepth, zoomedBlock]); useEffect(() => { if (!isPageFocused) return; return addShortcut([ { altKey: true, metaKey: true, key: "ArrowUp", shift: true, handler: () => { foldBlocks(rep, [...foldableParentSet]); }, }, { altKey: true, metaKey: true, key: "ArrowDown", shift: true, handler: () => { unfoldBlocks(rep, [...foldableParentSet]); }, }, ]); }, [foldableParentSet, isPageFocused, rep]); let lastRootBlock = zoomedBlock ? blocks.findLast((block) => block.listData?.parent === zoomedBlock) : blocks.findLast((block) => !block.listData || block.listData.depth === 1); let lastVisibleBlock = visibleBlocks.at(-1); // Make this page a drop target for list items dragged from any open page // (the DndContext lives in ListDndProvider, above all pages). useEffect(() => { listDndPages.set(props.entityID, { pageID: props.entityID, blocks: visibleBlocks, zoomDepth, }); return () => void listDndPages.delete(props.entityID); }, [props.entityID, visibleBlocks, zoomDepth]); let { footnotes } = useFootnoteContext(); let [areFootnotes, setAreFootnotes] = useState(false); useEffect(() => { setAreFootnotes(footnotes.length > 0); }, [footnotes.length]); return (