Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179import { TextSelection } from "prosemirror-state";import { useUIState } from "src/useUIState";import { Block } from "components/Blocks/Block";import { elementId } from "src/utils/elementId";
import { useEditorStates } from "src/state/useEditorState";import { scrollIntoViewIfNeeded } from "./scrollIntoViewIfNeeded";import { getPosAtCoordinates } from "./getCoordinatesInTextarea";import { flushSync } from "react-dom";
export function focusBlock( block: Pick<Block, "type" | "entityID" | "parent">, position: Position,) { // focus the block flushSync(() => { useUIState.getState().focusAndSelectBlock(block); }); scrollIntoViewIfNeeded( document.getElementById(elementId.block(block.entityID).container), false, ); if (block.type === "math" || block.type === "code") { let el = document.getElementById( elementId.block(block.entityID).input, ) as HTMLTextAreaElement; let pos; if (position.type === "start") { pos = { offset: 0 }; }
if (position.type === "end") { pos = { offset: el.textContent?.length || 0 }; } if (position.type === "top" || position.type === "bottom") { let inputRect = el?.getBoundingClientRect(); let left = Math.max(position.left, inputRect?.left || 0); let top = position.type === "top" ? (inputRect?.top || 0) + 10 : (inputRect?.bottom || 0) - 10; pos = getPosAtCoordinates(left, top); }
if (pos?.offset !== undefined) { // trying to focus the block in a subpage causes the page to flash and scroll back to the parent page. // idk how to fix so i'm giving up -- celine // el?.focus(); requestAnimationFrame(() => { el?.setSelectionRange(pos.offset, pos.offset); }); } }
// if its not a text block, that's all we need to do if ( block.type !== "text" && block.type !== "heading" && block.type !== "blockquote" ) { return true; } // if its a text block, and not an empty block that is last on the page, // focus the editor using the mouse position if needed let nextBlockID = block.entityID; let nextBlock = useEditorStates.getState().editorStates[nextBlockID]; if (!nextBlock || !nextBlock.view) { waitForViewAndFocus(nextBlockID, position); return; } applyFocusToView(nextBlock, position);}
function applyFocusToView( nextBlock: NonNullable< ReturnType<typeof useEditorStates.getState>["editorStates"][string] >, position: Position,) { if (!nextBlock.view) return; let nextBlockViewClientRect = nextBlock.view.dom.getBoundingClientRect(); let tr = nextBlock.editor.tr; let pos: { pos: number } | null = null; switch (position.type) { case "end": { pos = { pos: tr.doc.content.size - 1 }; break; } case "start": { pos = { pos: 1 }; break; } case "top": { pos = nextBlock.view.posAtCoords({ top: nextBlockViewClientRect.top + 12, left: Math.max(position.left, nextBlockViewClientRect.left), }); break; } case "bottom": { pos = nextBlock.view.posAtCoords({ top: nextBlockViewClientRect.bottom - 12, left: Math.max(position.left, nextBlockViewClientRect.left), }); break; } case "coord": { pos = nextBlock.view.posAtCoords({ top: position.top, left: position.left, }); break; } case "afterFootnote": { tr.doc.descendants((node, nodePos) => { if (pos) return false; if ( node.type.name === "footnote" && node.attrs.footnoteEntityID === position.footnoteEntityID ) { pos = { pos: nodePos + node.nodeSize }; return false; } }); break; } }
nextBlock.view.dispatch( tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 1)), ); nextBlock.view.focus();}
// Used after a bulk paste, when the new block's ProseMirror view hasn't// finished mounting yet. Caps at 30 frames so an off-screen / never-mounted// block doesn't keep us polling forever.function waitForViewAndFocus( entityID: string, position: Position, maxAttempts = 30,) { let attempts = 0; const tick = () => { const state = useEditorStates.getState().editorStates[entityID]; if (state?.view) { applyFocusToView(state, position); return; } attempts++; if (attempts >= maxAttempts) return; requestAnimationFrame(tick); }; requestAnimationFrame(tick);}
type Position = | { type: "start"; } | { type: "end" } | { type: "coord"; top: number; left: number; } | { type: "top"; left: number; } | { type: "bottom"; left: number; } | { type: "afterFootnote"; footnoteEntityID: string; };