a tool for shared writing and social publishing

don't render block overlay if not ios

+119 -24
+64 -22
components/Blocks.tsx
··· 49 49 ?.focus(); 50 50 }, 10); 51 51 } else { 52 - focusBlock(lastBlock, "end", "bottom"); 52 + focusBlock(lastBlock, { type: "end" }); 53 53 } 54 54 } 55 55 }} ··· 68 68 ); 69 69 })} 70 70 <NewBlockButton lastBlock={lastBlock || null} entityID={props.entityID} /> 71 + <div className="shrink-0 h-[50vh]" /> 71 72 </div> 72 73 ); 73 74 } ··· 147 148 e.preventDefault(); 148 149 let block = props.nextBlock; 149 150 if (block && useUIState.getState().selectedBlock.length <= 1) 150 - focusBlock(block, useEditorStates.getState().lastXPosition, "top"); 151 + focusBlock(block, { 152 + type: "top", 153 + left: useEditorStates.getState().lastXPosition, 154 + }); 151 155 if (!block) return; 152 156 } 153 157 if (e.key === "ArrowUp") { 154 158 e.preventDefault(); 155 159 let block = props.previousBlock; 156 160 if (block && useUIState.getState().selectedBlock.length <= 1) { 157 - focusBlock(block, useEditorStates.getState().lastXPosition, "bottom"); 161 + focusBlock(block, { 162 + type: "bottom", 163 + left: useEditorStates.getState().lastXPosition, 164 + }); 158 165 } 159 166 if (!block) return; 160 167 } ··· 164 171 r.mutate.removeBlock({ blockEntity: props.entityID }); 165 172 useUIState.getState().closeCard(props.entityID); 166 173 let block = props.previousBlock; 167 - if (block) focusBlock(block, "end", "bottom"); 174 + if (block) focusBlock(block, { type: "end" }); 168 175 } 169 176 if (e.key === "Enter") { 170 177 let newEntityID = crypto.randomUUID(); ··· 260 267 ); 261 268 } 262 269 263 - export function focusBlock( 264 - block: Block, 265 - left: number | "end" | "start", 266 - top: "top" | "bottom", 267 - ) { 270 + type Position = 271 + | { 272 + type: "start"; 273 + } 274 + | { type: "end" } 275 + | { 276 + type: "coord"; 277 + top: number; 278 + left: number; 279 + } 280 + | { 281 + type: "top"; 282 + left: number; 283 + } 284 + | { 285 + type: "bottom"; 286 + left: number; 287 + }; 288 + export function focusBlock(block: Block, position: Position) { 268 289 if (block.type !== "text" && block.type !== "heading") { 269 290 useUIState.getState().setSelectedBlock(block); 270 291 return true; ··· 272 293 let nextBlockID = block.value; 273 294 let nextBlock = useEditorStates.getState().editorStates[nextBlockID]; 274 295 if (!nextBlock || !nextBlock.view) return; 275 - nextBlock.view.focus(); 296 + nextBlock.view.dom.focus({ preventScroll: true }); 276 297 let nextBlockViewClientRect = nextBlock.view.dom.getBoundingClientRect(); 277 298 let tr = nextBlock.editor.tr; 278 - let pos = 279 - left === "end" 280 - ? { pos: tr.doc.content.size - 1 } 281 - : left === "start" 282 - ? { pos: 1 } 283 - : nextBlock.view.posAtCoords({ 284 - top: 285 - top === "top" 286 - ? nextBlockViewClientRect.top + 12 287 - : nextBlockViewClientRect.bottom - 12, 288 - left, 289 - }); 299 + let pos: { pos: number } | null = null; 300 + switch (position.type) { 301 + case "end": { 302 + pos = { pos: tr.doc.content.size - 1 }; 303 + break; 304 + } 305 + case "start": { 306 + pos = { pos: 1 }; 307 + break; 308 + } 309 + case "top": { 310 + pos = nextBlock.view.posAtCoords({ 311 + top: nextBlockViewClientRect.top + 12, 312 + left: position.left, 313 + }); 314 + break; 315 + } 316 + case "bottom": { 317 + pos = nextBlock.view.posAtCoords({ 318 + top: nextBlockViewClientRect.bottom - 12, 319 + left: position.left, 320 + }); 321 + break; 322 + } 323 + case "coord": { 324 + pos = nextBlock.view.posAtCoords({ 325 + top: position.top, 326 + left: position.left, 327 + }); 328 + break; 329 + } 330 + } 331 + 290 332 let newState = nextBlock.editor.apply( 291 333 tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 0)), 292 334 );
+6 -2
components/TextBlock/index.tsx
··· 60 60 let selected = useUIState((s) => 61 61 s.selectedBlock.find((b) => b.value === props.entityID), 62 62 ); 63 - if (selected) return null; 63 + let [initialRender, setInitialRender] = useState(true); 64 + useEffect(() => { 65 + setInitialRender(false); 66 + }, []); 67 + if (selected || initialRender || !isIOS()) return null; 64 68 return ( 65 69 <div 66 - style={{ display: isIOS() ? "none" : undefined }} 67 70 className="h-full w-full absolute cursor-text" 68 71 onMouseDown={(e) => { 69 72 e.preventDefault(); 70 73 let target = e.target; 74 + console.log("what"); 71 75 focusBlock(props, { 72 76 type: "coord", 73 77 top: e.clientY,
+31
src/state/useEditorState.ts
··· 1 + import { create } from "zustand"; 2 + import { EditorState } from "prosemirror-state"; 3 + import { EditorView } from "prosemirror-view"; 4 + export let useEditorStates = create(() => ({ 5 + lastXPosition: 0, 6 + editorStates: {} as { 7 + [entity: string]: 8 + | { 9 + editor: InstanceType<typeof EditorState>; 10 + view?: InstanceType<typeof EditorView>; 11 + } 12 + | undefined; 13 + }, 14 + })); 15 + 16 + export const setEditorState = ( 17 + entityID: string, 18 + s: { 19 + editor: InstanceType<typeof EditorState>; 20 + }, 21 + ) => { 22 + useEditorStates.setState((oldState) => { 23 + let existingState = oldState.editorStates[entityID]; 24 + return { 25 + editorStates: { 26 + ...oldState.editorStates, 27 + [entityID]: { ...existingState, ...s }, 28 + }, 29 + }; 30 + }); 31 + };
src/utils/focusBlock.ts

This is a binary file and will not be displayed.

+18
src/utils/isVisible.ts
··· 1 + export async function isVisible(el: Element) { 2 + return new Promise<boolean>((resolve) => { 3 + const observer = new IntersectionObserver( 4 + (entries, observer) => { 5 + entries.forEach((entry) => { 6 + resolve(entry.isIntersecting); 7 + observer.unobserve(entry.target); 8 + }); 9 + }, 10 + { 11 + root: null, // Use the viewport as the root 12 + threshold: 0.1, // Trigger when 10% of the element is visible 13 + }, 14 + ); 15 + 16 + observer.observe(el); 17 + }); 18 + }