a tool for shared writing and social publishing

autofocus editor when footnote is selecter

+64 -36
+6 -3
components/Blocks/TextBlock/mountProsemirror.ts
··· 90 90 let sup = supEl.closest(".footnote-ref") as HTMLElement | null; 91 91 if (!sup) return; 92 92 93 - // On mobile/tablet, show popover 93 + // On mobile/tablet or canvas, show popover 94 94 let isDesktop = window.matchMedia("(min-width: 1280px)").matches; 95 - if (!isDesktop) { 95 + let isCanvas = propsRef.current.pageType === "canvas"; 96 + if (!isDesktop || isCanvas) { 96 97 let store = useFootnotePopoverStore.getState(); 97 98 if (store.activeFootnoteID === footnoteID) { 98 99 store.close(); ··· 115 116 } 116 117 if (editor) { 117 118 editor.scrollIntoView({ behavior: "smooth", block: "nearest" }); 118 - let pm = editor.querySelector(".ProseMirror") as HTMLElement | null; 119 + let pm = editor.querySelector( 120 + ".ProseMirror", 121 + ) as HTMLElement | null; 119 122 if (pm) { 120 123 setTimeout(() => pm!.focus(), 100); 121 124 }
+22 -20
components/Footnotes/FootnoteEditor.tsx
··· 187 187 ]); 188 188 189 189 return ( 190 - <FootnoteItemLayout 191 - index={props.index} 192 - indexAction={() => { 193 - let ref = document.querySelector( 194 - `.footnote-ref[data-footnote-id="${props.footnoteEntityID}"]`, 195 - ); 196 - if (ref) { 197 - ref.scrollIntoView({ behavior: "smooth", block: "center" }); 190 + <div data-footnote-editor={props.footnoteEntityID}> 191 + <FootnoteItemLayout 192 + index={props.index} 193 + indexAction={() => { 194 + let pm = mountRef.current?.querySelector( 195 + ".ProseMirror", 196 + ) as HTMLElement | null; 197 + if (pm) { 198 + pm.focus(); 199 + } 200 + }} 201 + trailing={ 202 + props.editable && props.onDelete ? ( 203 + <FootnoteDeleteButton 204 + footnoteEntityID={props.footnoteEntityID} 205 + onDelete={props.onDelete} 206 + /> 207 + ) : undefined 198 208 } 199 - }} 200 - trailing={ 201 - props.editable && props.onDelete ? ( 202 - <FootnoteDeleteButton 203 - footnoteEntityID={props.footnoteEntityID} 204 - onDelete={props.onDelete} 205 - /> 206 - ) : undefined 207 - } 208 - > 209 - <div ref={mountRef} className="outline-hidden" /> 210 - </FootnoteItemLayout> 209 + > 210 + <div ref={mountRef} className="outline-hidden" /> 211 + </FootnoteItemLayout> 212 + </div> 211 213 ); 212 214 } 213 215
+16 -6
components/Footnotes/FootnotePopover.tsx
··· 29 29 let { permissions } = useEntitySetContext(); 30 30 let rep = useReplicache(); 31 31 let popoverRef = useRef<HTMLDivElement>(null); 32 - let [position, setPosition] = useState<{ top: number; left: number; arrowLeft: number } | null>(null); 32 + let [position, setPosition] = useState<{ 33 + top: number; 34 + left: number; 35 + arrowLeft: number; 36 + } | null>(null); 33 37 34 - let footnote = footnotes.find((fn) => fn.footnoteEntityID === activeFootnoteID); 38 + let footnote = footnotes.find( 39 + (fn) => fn.footnoteEntityID === activeFootnoteID, 40 + ); 35 41 36 42 // Compute the displayed index from DOM order (matching CSS counters) 37 43 // rather than the data model order, which may differ if footnotes 38 44 // were inserted out of order within a block. 39 45 let displayIndex = useMemo(() => { 40 46 if (!anchorElement || !footnote) return footnote?.index ?? 0; 41 - let container = anchorElement.closest('.postPageContent'); 47 + let container = anchorElement.closest(".postPageContent"); 42 48 if (!container) return footnote.index; 43 - let allRefs = Array.from(container.querySelectorAll('.footnote-ref')); 49 + let allRefs = Array.from(container.querySelectorAll(".footnote-ref")); 44 50 let pos = allRefs.indexOf(anchorElement); 45 51 return pos >= 0 ? pos + 1 : footnote.index; 46 52 }, [anchorElement, footnote]); ··· 58 64 59 65 // Clamp horizontal position 60 66 let padding = 12; 61 - left = Math.max(padding, Math.min(left, window.innerWidth - popoverWidth - padding)); 67 + left = Math.max( 68 + padding, 69 + Math.min(left, window.innerWidth - popoverWidth - padding), 70 + ); 62 71 63 72 // Arrow position relative to popover 64 73 let arrowLeft = anchorRect.left + anchorRect.width / 2 - left; ··· 113 122 return ( 114 123 <div 115 124 ref={popoverRef} 116 - className="footnote-popover lg:hidden fixed z-50 bg-bg-page border border-border rounded-lg shadow-md px-3 py-2 w-[min(calc(100vw-24px),320px)]" 125 + className="footnote-popover fixed z-50 bg-bg-page border border-border rounded-lg shadow-md px-3 py-2 w-[min(calc(100vw-24px),320px)]" 117 126 style={{ 118 127 top: position?.top ?? -9999, 119 128 left: position?.left ?? -9999, ··· 124 133 footnoteEntityID={footnote.footnoteEntityID} 125 134 index={displayIndex} 126 135 editable={permissions.write} 136 + autoFocus={permissions.write} 127 137 onDelete={ 128 138 permissions.write 129 139 ? () => {
+20 -7
components/Footnotes/usePageFootnotes.ts
··· 14 14 rep?.rep, 15 15 async (tx) => { 16 16 let scan = scanIndex(tx); 17 - let blocks = await scan.eav(pageID, "card/block"); 18 - let sorted = blocks.toSorted((a, b) => 19 - a.data.position > b.data.position ? 1 : -1, 20 - ); 17 + let cardBlocks = await scan.eav(pageID, "card/block"); 18 + let canvasBlocks = await scan.eav(pageID, "canvas/block"); 19 + 20 + let sortedCardBlocks = cardBlocks 21 + .map((b) => ({ value: b.data.value, position: b.data.position })) 22 + .toSorted((a, b) => (a.position > b.position ? 1 : -1)); 23 + 24 + let sortedCanvasBlocks = canvasBlocks 25 + .map((b) => ({ value: b.data.value, position: b.data.position })) 26 + .toSorted((a, b) => { 27 + if (a.position.y === b.position.y) return a.position.x - b.position.x; 28 + return a.position.y - b.position.y; 29 + }); 30 + 31 + let sorted = [...sortedCardBlocks, ...sortedCanvasBlocks]; 21 32 22 33 let footnotes: FootnoteInfo[] = []; 23 34 let indexMap: Record<string, number> = {}; 24 35 let idx = 1; 25 36 26 37 for (let block of sorted) { 27 - let blockFootnotes = await scan.eav(block.data.value, "block/footnote"); 38 + let blockFootnotes = await scan.eav(block.value, "block/footnote"); 28 39 let sortedFootnotes = blockFootnotes.toSorted((a, b) => 29 40 a.data.position > b.data.position ? 1 : -1, 30 41 ); 31 42 for (let fn of sortedFootnotes) { 32 43 footnotes.push({ 33 44 footnoteEntityID: fn.data.value, 34 - blockID: block.data.value, 45 + blockID: block.value, 35 46 index: idx, 36 47 }); 37 48 indexMap[fn.data.value] = idx; ··· 44 55 { dependencies: [pageID] }, 45 56 ); 46 57 47 - return data || { pageID, footnotes: [], indexMap: {} as Record<string, number> }; 58 + return ( 59 + data || { pageID, footnotes: [], indexMap: {} as Record<string, number> } 60 + ); 48 61 }