tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
27
pulls
pipelines
autofocus editor when footnote is selecter
cozylittle.house
1 week ago
212fabd2
ce10ee81
+64
-36
4 changed files
expand all
collapse all
unified
split
components
Blocks
TextBlock
mountProsemirror.ts
Footnotes
FootnoteEditor.tsx
FootnotePopover.tsx
usePageFootnotes.ts
+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
93
-
// On mobile/tablet, show popover
93
93
+
// On mobile/tablet or canvas, show popover
94
94
let isDesktop = window.matchMedia("(min-width: 1280px)").matches;
95
95
-
if (!isDesktop) {
95
95
+
let isCanvas = propsRef.current.pageType === "canvas";
96
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
118
-
let pm = editor.querySelector(".ProseMirror") as HTMLElement | null;
119
119
+
let pm = editor.querySelector(
120
120
+
".ProseMirror",
121
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
190
-
<FootnoteItemLayout
191
191
-
index={props.index}
192
192
-
indexAction={() => {
193
193
-
let ref = document.querySelector(
194
194
-
`.footnote-ref[data-footnote-id="${props.footnoteEntityID}"]`,
195
195
-
);
196
196
-
if (ref) {
197
197
-
ref.scrollIntoView({ behavior: "smooth", block: "center" });
190
190
+
<div data-footnote-editor={props.footnoteEntityID}>
191
191
+
<FootnoteItemLayout
192
192
+
index={props.index}
193
193
+
indexAction={() => {
194
194
+
let pm = mountRef.current?.querySelector(
195
195
+
".ProseMirror",
196
196
+
) as HTMLElement | null;
197
197
+
if (pm) {
198
198
+
pm.focus();
199
199
+
}
200
200
+
}}
201
201
+
trailing={
202
202
+
props.editable && props.onDelete ? (
203
203
+
<FootnoteDeleteButton
204
204
+
footnoteEntityID={props.footnoteEntityID}
205
205
+
onDelete={props.onDelete}
206
206
+
/>
207
207
+
) : undefined
198
208
}
199
199
-
}}
200
200
-
trailing={
201
201
-
props.editable && props.onDelete ? (
202
202
-
<FootnoteDeleteButton
203
203
-
footnoteEntityID={props.footnoteEntityID}
204
204
-
onDelete={props.onDelete}
205
205
-
/>
206
206
-
) : undefined
207
207
-
}
208
208
-
>
209
209
-
<div ref={mountRef} className="outline-hidden" />
210
210
-
</FootnoteItemLayout>
209
209
+
>
210
210
+
<div ref={mountRef} className="outline-hidden" />
211
211
+
</FootnoteItemLayout>
212
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
32
-
let [position, setPosition] = useState<{ top: number; left: number; arrowLeft: number } | null>(null);
32
32
+
let [position, setPosition] = useState<{
33
33
+
top: number;
34
34
+
left: number;
35
35
+
arrowLeft: number;
36
36
+
} | null>(null);
33
37
34
34
-
let footnote = footnotes.find((fn) => fn.footnoteEntityID === activeFootnoteID);
38
38
+
let footnote = footnotes.find(
39
39
+
(fn) => fn.footnoteEntityID === activeFootnoteID,
40
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
41
-
let container = anchorElement.closest('.postPageContent');
47
47
+
let container = anchorElement.closest(".postPageContent");
42
48
if (!container) return footnote.index;
43
43
-
let allRefs = Array.from(container.querySelectorAll('.footnote-ref'));
49
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
61
-
left = Math.max(padding, Math.min(left, window.innerWidth - popoverWidth - padding));
67
67
+
left = Math.max(
68
68
+
padding,
69
69
+
Math.min(left, window.innerWidth - popoverWidth - padding),
70
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
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
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
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
17
-
let blocks = await scan.eav(pageID, "card/block");
18
18
-
let sorted = blocks.toSorted((a, b) =>
19
19
-
a.data.position > b.data.position ? 1 : -1,
20
20
-
);
17
17
+
let cardBlocks = await scan.eav(pageID, "card/block");
18
18
+
let canvasBlocks = await scan.eav(pageID, "canvas/block");
19
19
+
20
20
+
let sortedCardBlocks = cardBlocks
21
21
+
.map((b) => ({ value: b.data.value, position: b.data.position }))
22
22
+
.toSorted((a, b) => (a.position > b.position ? 1 : -1));
23
23
+
24
24
+
let sortedCanvasBlocks = canvasBlocks
25
25
+
.map((b) => ({ value: b.data.value, position: b.data.position }))
26
26
+
.toSorted((a, b) => {
27
27
+
if (a.position.y === b.position.y) return a.position.x - b.position.x;
28
28
+
return a.position.y - b.position.y;
29
29
+
});
30
30
+
31
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
27
-
let blockFootnotes = await scan.eav(block.data.value, "block/footnote");
38
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
34
-
blockID: block.data.value,
45
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
47
-
return data || { pageID, footnotes: [], indexMap: {} as Record<string, number> };
58
58
+
return (
59
59
+
data || { pageID, footnotes: [], indexMap: {} as Record<string, number> }
60
60
+
);
48
61
}