a tool for shared writing and social publishing
1import { EditorView } from "prosemirror-view";
2import { v7 } from "uuid";
3import { Replicache } from "replicache";
4import type { ReplicacheMutators } from "src/replicache";
5import { schema } from "./schema";
6import { generateKeyBetween } from "fractional-indexing";
7import { scanIndex } from "src/replicache/utils";
8
9export async function insertFootnote(
10 view: EditorView,
11 blockID: string,
12 rep: Replicache<ReplicacheMutators>,
13 permissionSet: string,
14) {
15 let footnoteEntityID = v7();
16
17 let existingFootnotes = await rep.query(async (tx) => {
18 let scan = scanIndex(tx);
19 return scan.eav(blockID, "block/footnote");
20 });
21
22 // Build a map from footnoteEntityID to its fractional-index position
23 let positionByEntityID: Record<string, string> = {};
24 for (let f of existingFootnotes) {
25 positionByEntityID[f.data.value] = f.data.position;
26 }
27
28 // Find footnotes that appear before and after the insertion point in the text
29 let { from } = view.state.selection;
30 let beforePosition: string | null = null;
31 let afterPosition: string | null = null;
32 let foundInsertionPoint = false;
33
34 view.state.doc.descendants((node, pos) => {
35 if (node.type.name === "footnote") {
36 let entityID = node.attrs.footnoteEntityID;
37 let p = positionByEntityID[entityID];
38 if (p !== undefined) {
39 if (pos < from) {
40 // This footnote is before the insertion point
41 if (beforePosition === null || p > beforePosition) {
42 beforePosition = p;
43 }
44 } else {
45 // This footnote is at or after the insertion point
46 if (!foundInsertionPoint) {
47 afterPosition = p;
48 foundInsertionPoint = true;
49 } else if (afterPosition !== null && p < afterPosition) {
50 afterPosition = p;
51 }
52 }
53 }
54 }
55 });
56
57 let position = generateKeyBetween(beforePosition, afterPosition);
58
59 await rep.mutate.createFootnote({
60 footnoteEntityID,
61 blockID,
62 permission_set: permissionSet,
63 position,
64 });
65
66 let node = schema.nodes.footnote.create({ footnoteEntityID });
67 let tr = view.state.tr.insert(from, node);
68 view.dispatch(tr);
69
70 return footnoteEntityID;
71}