import diff from "npm:fast-diff"; import { Packet } from "../proto.ts"; import { CausalTree, CausalTreeOp } from "../sync/ordt/causal-tree.ts"; import { PlainTextOperation, PlainTextORDT } from "../sync/ordt/plain-text.ts"; const textarea = document.querySelector("textarea")!; const pt = new PlainTextORDT(); let me: string | undefined = undefined; pt.onevent = (event, textIndex, affectedLength) => { if (event.at[0] === me) return; let start = textarea.selectionStart, end = textarea.selectionEnd; if (textIndex <= start) start += affectedLength; if (textIndex <= end) end += affectedLength; textarea.value = pt.render()[0]; textarea.setSelectionRange(start, end); }; const socket = new WebSocket("/api/connect"); const initialized = Promise.withResolvers(); socket.addEventListener("message", ev => { const packet = JSON.parse(ev.data) as Packet; if (packet.t === "init") { for (const op of packet.ops) pt.apply(op); initialized.resolve(packet); } else if (packet.t === "op") { pt.apply(packet.op); } }); const initPacket = await initialized.promise; me = initPacket.you; textarea.value = pt.render()[0]; textarea.addEventListener("input", () => { const text = pt.render()[0]; const diffResults = diff(text, textarea.value, textarea.selectionStart); let idx = 0; for (const [id, str] of diffResults) { if (id === diff.INSERT) { let parentOp = pt.operations[pt.findOpAtTextIndex(idx)]; for (const glyph of str) { const op: CausalTreeOp = { type: "insert", at: [me, ++pt.clock], parent: parentOp, sequence: glyph, }; pt.apply(op); socket.send(JSON.stringify(CausalTree.toWeakOp(op))); parentOp = op; idx += glyph.length; } } if (id === diff.EQUAL) { idx += str.length; } if (id === diff.DELETE) { for (const glyph of str) { const parentOp = pt.operations[pt.findOpAtTextIndex(idx + glyph.length)]; const op: CausalTreeOp = { type: "delete", at: [me, ++pt.clock], parent: parentOp, }; pt.apply(op); socket.send(JSON.stringify(CausalTree.toWeakOp(op))); } } } }); Object.defineProperty(globalThis, "pt", { value: pt }); Object.defineProperty(globalThis, "textarea", { value: textarea });