@panphora/clayjs 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +19 -0
- package/dist/clay.standalone.js +69739 -41784
- package/dist/server/checkpoint.cjs +70690 -0
- package/dist/server/checkpoint.mjs +8 -0
- package/dist/server/rooms.cjs +72039 -0
- package/dist/server/rooms.mjs +66 -0
- package/docs/guide.md +95 -94
- package/entries/clay.js +22 -0
- package/package.json +16 -2
- package/packed-contract.json +17 -3
- package/src/core/conflict-download.js +11 -2
- package/src/core/host-meta.js +75 -0
- package/src/core/persist.js +11 -0
- package/src/core/save-core.js +266 -31
- package/src/core/save.js +62 -32
- package/src/core/snapshot.js +39 -13
- package/src/core/style-comparisons.js +37 -0
- package/src/dom/insert-style-tag.js +11 -12
- package/src/lib/effect.js +130 -0
- package/src/lib/style-adoption.js +9 -0
- package/src/lib/style.js +44 -0
- package/src/live/fast-lane.js +347 -60
- package/src/live/local-lane.js +32 -6
- package/src/live/ops.js +77 -0
- package/src/live/provenance.js +129 -0
- package/src/live/save-history.js +188 -0
- package/src/live/server-lane.js +9 -2
- package/src/live/store.js +194 -16
- package/src/loader-logic.js +7 -3
- package/src/loader.js +6 -0
- package/src/page/address.js +20 -0
- package/src/page/adopt-boot.js +189 -0
- package/src/page/adopt.js +240 -0
- package/src/page/ai-tool.js +188 -0
- package/src/page/announce.js +21 -0
- package/src/page/blocks.js +1121 -0
- package/src/page/blocksel.js +269 -0
- package/src/page/board-drag.js +226 -0
- package/src/page/board-panel.js +353 -0
- package/src/page/board-style.js +146 -0
- package/src/page/board-view.js +447 -0
- package/src/page/board.js +41 -0
- package/src/page/chart.js +220 -0
- package/src/page/checklist.js +143 -0
- package/src/page/chrome.js +587 -0
- package/src/page/csv-import.js +169 -0
- package/src/page/drag.js +202 -0
- package/src/page/drop.js +112 -0
- package/src/page/editor.js +384 -0
- package/src/page/highlight.js +268 -0
- package/src/page/inline.js +60 -1
- package/src/page/items.js +159 -0
- package/src/page/link.js +247 -0
- package/src/page/markdown.js +71 -0
- package/src/page/media.js +570 -0
- package/src/page/merge.js +130 -0
- package/src/page/model.js +71 -9
- package/src/page/placeholder.js +23 -0
- package/src/page/projection.js +647 -0
- package/src/page/recover.js +193 -0
- package/src/page/rich/cell.js +16 -0
- package/src/page/rich/chart.js +104 -0
- package/src/page/rich/csv.js +118 -0
- package/src/page/rich/page-tool.js +86 -0
- package/src/page/rich/tool-runtime.js +195 -0
- package/src/page/save-projection.js +53 -0
- package/src/page/save-states.js +85 -0
- package/src/page/schema.js +12 -5
- package/src/page/table-kinds.js +20 -0
- package/src/page/table-paste.js +57 -0
- package/src/page/table-select.js +244 -0
- package/src/page/table-style.js +25 -0
- package/src/page/table.js +650 -0
- package/src/page/themes/content.css +46 -0
- package/src/page/themes/light-1.css +269 -0
- package/src/page/themes/light-2.css +211 -0
- package/src/page/themes/light-3.css +130 -0
- package/src/page/themes/light-4.css +299 -0
- package/src/page/tool-data.js +25 -0
- package/src/page/tool-view.js +68 -0
- package/src/page/tools.js +133 -0
- package/src/page/view.js +136 -0
- package/src/page/ybind/author.js +25 -0
- package/src/page/ybind/binding.js +92 -0
- package/src/page/ybind/board-commands.js +170 -0
- package/src/page/ybind/board.js +89 -0
- package/src/page/ybind/bridge.js +382 -0
- package/src/page/ybind/conversion.js +26 -0
- package/src/page/ybind/core.js +7 -0
- package/src/page/ybind/decode.js +208 -0
- package/src/page/ybind/doc.js +58 -0
- package/src/page/ybind/external.js +190 -0
- package/src/page/ybind/group.js +124 -0
- package/src/page/ybind/index.js +12 -0
- package/src/page/ybind/layout.js +27 -0
- package/src/page/ybind/marks.js +18 -0
- package/src/page/ybind/migrate.js +152 -0
- package/src/page/ybind/moves.js +288 -0
- package/src/page/ybind/origins.js +257 -0
- package/src/page/ybind/parts.js +44 -0
- package/src/page/ybind/projection-map.js +92 -0
- package/src/page/ybind/provider.js +285 -0
- package/src/page/ybind/recovery.js +411 -0
- package/src/page/ybind/seed.js +27 -0
- package/src/page/ybind/table.js +166 -0
- package/src/page/ybind/tokens.js +93 -0
- package/src/page/ybind/walk.js +333 -0
- package/src/page/ybind/writer.js +307 -0
- package/src/plugins/indicator.js +16 -6
- package/src/plugins/live.js +742 -65
- package/src/plugins/page.js +547 -0
- package/src/plugins/undo.js +60 -3
- package/src/plugins/visitor-drafts/prompt.js +4 -6
- package/src/server/checkpoint/index.js +218 -0
- package/src/server/rooms/index.js +59 -0
- package/src/server/rooms/live-data-ops.js +369 -0
- package/src/server/rooms/live-data-rooms.js +965 -0
- package/src/server/rooms/live-data-tags.js +49 -0
- package/src/server/rooms/live-page-history.js +41 -0
- package/src/server/rooms/live-provenance.js +142 -0
- package/src/server/rooms/page-admission.js +79 -0
- package/src/server/rooms/page-conversion.js +62 -0
- package/src/server/rooms/page-rebase.js +108 -0
- package/src/server/rooms/page-state.js +91 -0
- package/src/server/rooms/page-write-admission.js +63 -0
- package/src/server/rooms/page-write-guard.js +23 -0
- package/src/server/rooms/recent.js +18 -0
- package/src/server/rooms/spec-wire.js +17 -0
- package/src/standalone.js +22 -0
- package/src/sync/live-sync.js +13 -2
- package/src/sync/stream.js +62 -16
- package/src/ui/index.js +4 -0
- package/src/ui/toast.js +21 -5
- package/src/vendor/pierre/pierre.vendor.js +3 -0
- package/src/vendor/yjs.vendor.js +7 -0
- package/src/page/binding.js +0 -647
- package/src/page/replay.js +0 -578
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { attrKeys, sameJson } from "./layout.js";
|
|
2
|
+
import { blockJson } from "../model.js";
|
|
3
|
+
import { marksKey, marksAttrs } from "./marks.js";
|
|
4
|
+
import { createProjectionMap } from "./projection-map.js";
|
|
5
|
+
|
|
6
|
+
export const implicitFlags = (doc, visible) => {
|
|
7
|
+
const byId = new Map(), out = [];
|
|
8
|
+
for (const segment of visible ?? []) {
|
|
9
|
+
const flags = byId.get(segment.id) ?? [];
|
|
10
|
+
flags.push(segment.implicit);
|
|
11
|
+
byId.set(segment.id, flags);
|
|
12
|
+
}
|
|
13
|
+
const next = new Map();
|
|
14
|
+
doc.forEach((block) => {
|
|
15
|
+
const id = block.attrs.id;
|
|
16
|
+
const index = next.get(id) ?? 0;
|
|
17
|
+
out.push(byId.get(id)?.[index] ?? false);
|
|
18
|
+
next.set(id, index + 1);
|
|
19
|
+
});
|
|
20
|
+
return out;
|
|
21
|
+
};
|
|
22
|
+
const pickAttrs = (node) => { const o = {}; for (const k of attrKeys(node.type)) o[k] = node.attrs[k]; if (node.type.name === "table") o.rows = blockJson(node).rows; return o; };
|
|
23
|
+
|
|
24
|
+
export function tokensOf(doc, alias = new Map(), visible = null) {
|
|
25
|
+
const toks = [];
|
|
26
|
+
const flags = implicitFlags(doc, visible);
|
|
27
|
+
doc.forEach((block, _o, index) => {
|
|
28
|
+
const implicit = flags[index] && !alias.has(block.attrs.id);
|
|
29
|
+
const brk = visible ? visible[index]?.brk ?? null : null;
|
|
30
|
+
if (!implicit) toks.push({ k: "b", id: flags[index] && alias.has(block.attrs.id) ? alias.get(block.attrs.id) : block.attrs.id, t: block.type.name, attrs: pickAttrs(block), brk });
|
|
31
|
+
if (block.isTextblock) block.content.forEach((n) => {
|
|
32
|
+
if (n.isText) { const ms = block.type.name === "code_block" ? [] : n.marks; const mk = marksKey(ms), ma = marksAttrs(ms); for (let i = 0; i < n.text.length; i++) toks.push({ k: "c", ch: n.text[i], mk, ma }); }
|
|
33
|
+
else if (n.type.name === "hard_break") toks.push({ k: "h" });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
return toks;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function tokenIndex(doc, alias = new Map(), visible = null) {
|
|
40
|
+
const out = new Int32Array(doc.content.size + 1);
|
|
41
|
+
let t = 0;
|
|
42
|
+
const flags = implicitFlags(doc, visible);
|
|
43
|
+
doc.forEach((block, o, index) => {
|
|
44
|
+
const implicit = flags[index] && !alias.has(block.attrs.id);
|
|
45
|
+
const open = implicit ? 0 : 1;
|
|
46
|
+
if (block.isTextblock) {
|
|
47
|
+
const n = block.content.size;
|
|
48
|
+
out[o] = t;
|
|
49
|
+
for (let j = 0; j <= n; j++) out[o + 1 + j] = t + open + j;
|
|
50
|
+
out[o + 2 + n] = t + open + n;
|
|
51
|
+
t += open + n;
|
|
52
|
+
} else {
|
|
53
|
+
out[o] = t;
|
|
54
|
+
out[o + block.nodeSize] = t + open;
|
|
55
|
+
t += open;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function textTokenRange(doc, from, to) {
|
|
62
|
+
const left = doc.resolve(from), right = doc.resolve(to);
|
|
63
|
+
if (left.depth !== 1 || right.depth !== 1 || left.index(0) !== right.index(0) || !left.parent.isTextblock) throw new RangeError("token range must stay inside one text block");
|
|
64
|
+
const target = left.index(0);
|
|
65
|
+
let prefix = 0, offset = 0, block = null;
|
|
66
|
+
doc.forEach((node, at, index) => {
|
|
67
|
+
if (index < target) prefix += node.isTextblock ? node.content.size + 1 : 1;
|
|
68
|
+
else if (index === target) { block = node; offset = at; }
|
|
69
|
+
});
|
|
70
|
+
const startOffset = from - offset - 1, endOffset = to - offset - 1;
|
|
71
|
+
const tokens = [];
|
|
72
|
+
let cursor = 0;
|
|
73
|
+
block.content.forEach((node) => {
|
|
74
|
+
const a = Math.max(0, startOffset - cursor), b = Math.min(node.nodeSize, endOffset - cursor);
|
|
75
|
+
if (a < b) {
|
|
76
|
+
if (node.isText) {
|
|
77
|
+
const marks = block.type.name === "code_block" ? [] : node.marks;
|
|
78
|
+
const mk = marksKey(marks), ma = marksAttrs(marks);
|
|
79
|
+
for (let i = a; i < b; i++) tokens.push({ k: "c", ch: node.text[i], mk, ma });
|
|
80
|
+
} else if (node.type.name === "hard_break") tokens.push({ k: "h" });
|
|
81
|
+
}
|
|
82
|
+
cursor += node.nodeSize;
|
|
83
|
+
});
|
|
84
|
+
return { start: prefix + 1 + startOffset, end: prefix + 1 + endOffset, tokens };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const sameTok = (a, b) => a.k === b.k && (a.k === "b" ? a.id === b.id && a.t === b.t && sameJson(a.attrs, b.attrs) : a.k === "h" || (a.ch === b.ch && a.mk === b.mk));
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
export const yAfter = (holes, k) => { let n = k; for (const h of holes) if (h.at <= k) n += h.len; return n; };
|
|
91
|
+
export const yBefore = (holes, k) => { let n = k; for (const h of holes) if (h.at < k) n += h.len; return n; };
|
|
92
|
+
|
|
93
|
+
export const projectionOf = (tokens, holes, physicalLength) => createProjectionMap({ tokens, holes }, physicalLength);
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
2
|
+
import { schema } from "../schema.js";
|
|
3
|
+
import { idKey, parseKey, sameJson } from "./layout.js";
|
|
4
|
+
import { marksFromAttributes, marksKey } from "./marks.js";
|
|
5
|
+
import { aliasKey, createOriginIndex } from "./origins.js";
|
|
6
|
+
import { COPY_LOST, copyActiveKey, copyLostKey, parseGoneIntents, recoveredRoot, recoveredSource } from "./recovery.js";
|
|
7
|
+
|
|
8
|
+
export const hashText = (str) => { let h = 2166136261; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0).toString(36); };
|
|
9
|
+
export const covers = (it, id) => it.id.client === id.client && id.clock >= it.id.clock && id.clock < it.id.clock + it.length;
|
|
10
|
+
|
|
11
|
+
const lostCopySource = (cover) => {
|
|
12
|
+
if (typeof cover !== "string" || cover.charCodeAt(0) !== 91) return null;
|
|
13
|
+
let parts;
|
|
14
|
+
try { parts = JSON.parse(cover); } catch { return null; }
|
|
15
|
+
return Array.isArray(parts) && parts.length === 2 && parts[0] === COPY_LOST && typeof parts[1] === "string" ? parts[1] : null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const pickCopy = (list) => list.reduce((x, y) => (y.brk.client < x.brk.client || (y.brk.client === x.brk.client && y.brk.clock < x.brk.clock) ? y : x));
|
|
19
|
+
|
|
20
|
+
export function walk(pd, index = null) {
|
|
21
|
+
const owned = index ? null : createOriginIndex(pd.origins);
|
|
22
|
+
try {
|
|
23
|
+
return walkFlow(pd, owned ?? index);
|
|
24
|
+
} finally {
|
|
25
|
+
if (owned) owned.dispose();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function walkFlow(pd, index) {
|
|
30
|
+
const store = pd.ydoc.store;
|
|
31
|
+
const intents = parseGoneIntents(pd.gone);
|
|
32
|
+
const liveBySource = new Map();
|
|
33
|
+
const markerCount = new Map();
|
|
34
|
+
for (let it = pd.flow._start; it; it = it.right) {
|
|
35
|
+
if (it.deleted || !(it.content instanceof Y.ContentEmbed) || it.content.embed.b === undefined) continue;
|
|
36
|
+
markerCount.set(it.content.embed.b, (markerCount.get(it.content.embed.b) ?? 0) + 1);
|
|
37
|
+
const sourceKey = idKey(index.resolve(it.id));
|
|
38
|
+
const prior = liveBySource.get(sourceKey);
|
|
39
|
+
if (!prior || it.id.client < prior.id.client || (it.id.client === prior.id.client && it.id.clock < prior.id.clock)) liveBySource.set(sourceKey, it);
|
|
40
|
+
}
|
|
41
|
+
const liveSources = new Set(liveBySource.keys());
|
|
42
|
+
const movedSources = new Set();
|
|
43
|
+
const retired = [];
|
|
44
|
+
const candidateSets = new Map();
|
|
45
|
+
for (const key of pd.origins.keys()) {
|
|
46
|
+
let parts;
|
|
47
|
+
try { parts = JSON.parse(key); } catch { continue; }
|
|
48
|
+
if (!Array.isArray(parts) || parts.length !== 3 || (parts[0] !== "boundary" && parts[0] !== "alias")) continue;
|
|
49
|
+
const [, client, clock] = parts;
|
|
50
|
+
if (!Number.isSafeInteger(client) || client < 0 || !Number.isSafeInteger(clock) || clock < 0) continue;
|
|
51
|
+
if (parts[0] === "boundary") {
|
|
52
|
+
const sourceKey = `${client}.${clock}`;
|
|
53
|
+
const record = index.boundaryOf({ client, clock });
|
|
54
|
+
if (record && record.kind === "copy") {
|
|
55
|
+
if (intents.copyActive.has(sourceKey)) { if (record.recoveryOf) movedSources.add(record.recoveryOf); }
|
|
56
|
+
else if (liveSources.has(sourceKey)) retired.push({ key: copyActiveKey(sourceKey), sourceKey, source: { client, clock }, operationId: null, blockId: record.blockId, end: null, sv: [], legacy: false, implicit: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const clocks = candidateSets.get(client) ?? candidateSets.set(client, new Set()).get(client);
|
|
60
|
+
clocks.add(clock);
|
|
61
|
+
}
|
|
62
|
+
const promotedCopies = [];
|
|
63
|
+
const implicitKeys = new Set(retired.map((removal) => removal.key));
|
|
64
|
+
for (const restore of intents.restores) for (const cover of restore.covers) {
|
|
65
|
+
const sourceKey = lostCopySource(cover);
|
|
66
|
+
if (sourceKey === null || implicitKeys.has(cover) || !liveBySource.has(sourceKey)) continue;
|
|
67
|
+
implicitKeys.add(cover);
|
|
68
|
+
promotedCopies.push({ key: cover, sourceKey, source: parseKey(sourceKey), operationId: null, blockId: liveBySource.get(sourceKey).content.embed.b, end: null, sv: [], legacy: false, implicit: true });
|
|
69
|
+
}
|
|
70
|
+
const removals = retired.length || promotedCopies.length ? [...intents.removals, ...retired, ...promotedCopies] : intents.removals;
|
|
71
|
+
const starts = new Map();
|
|
72
|
+
const removalStarts = new Map();
|
|
73
|
+
const removalsBySource = new Map();
|
|
74
|
+
for (const removal of removals) {
|
|
75
|
+
const sourceList = removalsBySource.get(removal.sourceKey) ?? removalsBySource.set(removal.sourceKey, []).get(removal.sourceKey);
|
|
76
|
+
sourceList.push(removal);
|
|
77
|
+
let it = null;
|
|
78
|
+
if (removal.implicit) it = liveBySource.get(removal.sourceKey);
|
|
79
|
+
else try { it = Y.getItem(store, removal.source); } catch { it = null; }
|
|
80
|
+
const stableSource = idKey(index.resolve(removal.source));
|
|
81
|
+
const live = liveBySource.get(stableSource);
|
|
82
|
+
if ((!it || it.deleted) && live?.content.embed.b === removal.blockId) it = live;
|
|
83
|
+
if (!it) continue;
|
|
84
|
+
if (removal.legacy) {
|
|
85
|
+
if (!it.deleted && it.content instanceof Y.ContentEmbed && it.content.embed.b === removal.blockId && idKey(index.resolve(it.id)) === stableSource) starts.set(it, { key: removal.key, blockId: removal.blockId, end: removal.end ? parseKey(removal.end) : null });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const list = removalStarts.get(it) ?? removalStarts.set(it, []).get(it);
|
|
89
|
+
list.push(removal);
|
|
90
|
+
}
|
|
91
|
+
const coverBySource = new Map();
|
|
92
|
+
const restoredSources = new Set(intents.restores.map((restore) => restore.sourceKey));
|
|
93
|
+
const recoveryRecord = (it, id) => {
|
|
94
|
+
const source = recoveredSource(id);
|
|
95
|
+
if (source === null) return null;
|
|
96
|
+
const record = index.boundaryOf({ client: it.id.client, clock: it.id.clock });
|
|
97
|
+
return record && record.blockId === id && record.recoveryOf === source ? record : null;
|
|
98
|
+
};
|
|
99
|
+
const boundarySource = (it, id) => {
|
|
100
|
+
const record = recoveryRecord(it, id);
|
|
101
|
+
return record && record.kind === "restore" ? record.recoveryOf : null;
|
|
102
|
+
};
|
|
103
|
+
const movedSource = (it, id) => {
|
|
104
|
+
const record = recoveryRecord(it, id);
|
|
105
|
+
return record && record.kind === "copy" ? record.recoveryOf : null;
|
|
106
|
+
};
|
|
107
|
+
const markerSource = (it, id) => {
|
|
108
|
+
const source = boundarySource(it, id);
|
|
109
|
+
return source !== null && restoredSources.has(source) && removalsBySource.has(source) ? source : null;
|
|
110
|
+
};
|
|
111
|
+
for (const restore of intents.restores) {
|
|
112
|
+
const set = coverBySource.get(restore.sourceKey) ?? coverBySource.set(restore.sourceKey, new Set()).get(restore.sourceKey);
|
|
113
|
+
for (const key of restore.covers) set.add(key);
|
|
114
|
+
}
|
|
115
|
+
const recoveredMarkers = new Map();
|
|
116
|
+
for (let it = pd.flow._start; it; it = it.right) {
|
|
117
|
+
if (it.deleted) continue;
|
|
118
|
+
const source = it.content instanceof Y.ContentEmbed ? markerSource(it, it.content.embed && it.content.embed.b) : null;
|
|
119
|
+
if (source === null) continue;
|
|
120
|
+
const list = recoveredMarkers.get(source) ?? recoveredMarkers.set(source, []).get(source);
|
|
121
|
+
list.push(it);
|
|
122
|
+
}
|
|
123
|
+
const exposed = (sourceKey) => {
|
|
124
|
+
const removals = removalsBySource.get(sourceKey);
|
|
125
|
+
if (!removals || !removals.length) return false;
|
|
126
|
+
const cover = coverBySource.get(sourceKey);
|
|
127
|
+
if (!cover) return false;
|
|
128
|
+
for (const removal of removals) if (!cover.has(removal.key)) return false;
|
|
129
|
+
return (recoveredMarkers.get(sourceKey)?.length ?? 0) > 0 || movedSources.has(sourceKey);
|
|
130
|
+
};
|
|
131
|
+
const segs = [];
|
|
132
|
+
const orphans = [];
|
|
133
|
+
const hidden = [];
|
|
134
|
+
const fences = [];
|
|
135
|
+
let cur = null, prevId = "start", hiding = null, at = 0;
|
|
136
|
+
const cattrs = {};
|
|
137
|
+
const typeOf = (id) => { const m = pd.attrs.get(id); return (m && m.get("t")) || "paragraph"; };
|
|
138
|
+
const hideUnit = (len) => {
|
|
139
|
+
if (!len) return;
|
|
140
|
+
if (cur && !cur.atom) cur.runs.push({ hole: len });
|
|
141
|
+
else segs.push({ kind: "h", len, key: null });
|
|
142
|
+
};
|
|
143
|
+
const openRecovered = (sourceKey, it, moved = false) => {
|
|
144
|
+
const root = recoveredRoot(sourceKey, removalsBySource.get(sourceKey) ?? [], intents);
|
|
145
|
+
const id = `recovered:${sourceKey}`;
|
|
146
|
+
const t = root ? root.type : "paragraph";
|
|
147
|
+
cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: !schema.nodes[t]?.isTextblock, recovered: true, restored: !moved, sourceKey, t, fields: root ? root.attrs : null };
|
|
148
|
+
segs.push(cur);
|
|
149
|
+
prevId = id;
|
|
150
|
+
};
|
|
151
|
+
const open = (id, brk, implicit) => {
|
|
152
|
+
cur = { kind: "b", id, baseId: id, brk, implicit, runs: [], len: implicit ? 0 : 1, atom: false, items: !implicit && (markerCount.get(id) ?? 0) > 1 ? [] : null };
|
|
153
|
+
if (!implicit) { cur.t = typeOf(id); cur.atom = !schema.nodes[cur.t]?.isTextblock; }
|
|
154
|
+
else cur.t = "paragraph";
|
|
155
|
+
segs.push(cur);
|
|
156
|
+
prevId = id;
|
|
157
|
+
};
|
|
158
|
+
const flush = () => {
|
|
159
|
+
if (!hiding) return;
|
|
160
|
+
if (hiding.len) {
|
|
161
|
+
if (hiding.resume) hiding.resume.runs.push({ hole: hiding.len });
|
|
162
|
+
else segs.push({ kind: "h", len: hiding.len, key: hiding.active[0]?.key });
|
|
163
|
+
}
|
|
164
|
+
cur = hiding.resume;
|
|
165
|
+
hiding = null;
|
|
166
|
+
};
|
|
167
|
+
const settle = (a) => {
|
|
168
|
+
if (a.legacy && a.text) orphans.push({ key: a.key, id: a.blockId, text: a.text, h: hashText(a.text) });
|
|
169
|
+
hidden.push({ key: a.key, sourceKey: a.sourceKey, blockId: a.blockId, end: a.end, sv: a.sv, at: a.at, live: a.live, items: a.items });
|
|
170
|
+
};
|
|
171
|
+
const matchesEnd = (it, end) => {
|
|
172
|
+
if (covers(it, end)) return true;
|
|
173
|
+
return markerIdsWithin(it).some((id) => {
|
|
174
|
+
if (!pd.origins.has(aliasKey(id.client, id.clock))) return false;
|
|
175
|
+
const resolved = index.resolve(id);
|
|
176
|
+
return resolved.client === end.client && resolved.clock === end.clock;
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
const markerClocks = new Map([...candidateSets].map(([client, clocks]) => [client, [...clocks].sort((a, b) => a - b)]));
|
|
180
|
+
const markerIdsWithin = (it) => {
|
|
181
|
+
const clocks = markerClocks.get(it.id.client) ?? [];
|
|
182
|
+
let lo = 0, hi = clocks.length;
|
|
183
|
+
while (lo < hi) { const mid = (lo + hi) >> 1; if (clocks[mid] < it.id.clock) lo = mid + 1; else hi = mid; }
|
|
184
|
+
const ids = [];
|
|
185
|
+
for (let i = lo; i < clocks.length && clocks[i] < it.id.clock + it.length; i++) ids.push({ client: it.id.client, clock: clocks[i] });
|
|
186
|
+
return ids;
|
|
187
|
+
};
|
|
188
|
+
const proj = [];
|
|
189
|
+
const endsOf = (removal) => ({ blockId: removal.blockId, end: removal.end ? parseKey(removal.end) : null, implicit: removal.implicit === true, sourceKey: removal.sourceKey });
|
|
190
|
+
for (let it = pd.flow._start; it; it = it.right) {
|
|
191
|
+
const here = at;
|
|
192
|
+
if (!it.deleted && it.countable) at += it.length;
|
|
193
|
+
if (hiding || proj.length) {
|
|
194
|
+
const edge = !it.deleted && it.content instanceof Y.ContentEmbed ? it.content.embed.b : undefined;
|
|
195
|
+
const boundary = edge !== undefined && boundarySource(it, edge) === null;
|
|
196
|
+
const deletedEdges = it.deleted ? markerIdsWithin(it).flatMap((id) => {
|
|
197
|
+
const sourceKey = idKey(index.resolve(id));
|
|
198
|
+
const record = index.boundaryOf(id);
|
|
199
|
+
if (!record || record.kind !== "removed" || liveSources.has(sourceKey)) return [];
|
|
200
|
+
return [{ blockId: record.blockId, sourceKey }];
|
|
201
|
+
}) : [];
|
|
202
|
+
const ends = (a) => a.end ? matchesEnd(it, a.end) : a.implicit ? boundary || deletedEdges.some((e) => e.sourceKey !== a.sourceKey) : (boundary && edge !== a.blockId) || deletedEdges.some((e) => e.blockId !== a.blockId);
|
|
203
|
+
if (hiding) {
|
|
204
|
+
const keep = [];
|
|
205
|
+
for (const a of hiding.active) { if (ends(a)) settle(a); else keep.push(a); }
|
|
206
|
+
hiding.active = keep;
|
|
207
|
+
if (!keep.length) flush();
|
|
208
|
+
}
|
|
209
|
+
for (let i = proj.length - 1; i >= 0; i--) {
|
|
210
|
+
proj[i].active = proj[i].active.filter((a) => !ends(a));
|
|
211
|
+
if (!proj[i].active.length) proj.splice(i, 1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const removals = removalStarts.get(it);
|
|
215
|
+
if (removals) {
|
|
216
|
+
if (!hiding && exposed(removals[0].sourceKey)) {
|
|
217
|
+
if (!it.deleted) hideUnit(it.length);
|
|
218
|
+
proj.push({ active: removals.map(endsOf), sourceKey: removals[0].sourceKey, root: null });
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!hiding && it.deleted) fences.push({ client: it.id.client, clock: it.id.clock });
|
|
222
|
+
for (const removal of removals) {
|
|
223
|
+
const a = { legacy: false, implicit: removal.implicit === true, key: removal.key, sourceKey: removal.sourceKey, blockId: removal.blockId, end: removal.end ? parseKey(removal.end) : null, sv: removal.sv, at: here, live: !it.deleted, items: [] };
|
|
224
|
+
if (hiding) hiding.active.push(a);
|
|
225
|
+
else hiding = { active: [a], len: 0, resume: cur };
|
|
226
|
+
}
|
|
227
|
+
if (!it.deleted) hiding.len += it.length;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const start = starts.get(it);
|
|
231
|
+
if (start) {
|
|
232
|
+
if (!hiding && exposed(start.key)) {
|
|
233
|
+
if (!it.deleted) hideUnit(it.length);
|
|
234
|
+
proj.push({ active: [{ blockId: start.blockId, end: start.end }], sourceKey: start.key, root: null });
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const a = { legacy: true, key: start.key, sourceKey: start.key, blockId: start.blockId, end: start.end, sv: [], at: here, live: true, items: [], text: "" };
|
|
238
|
+
if (hiding) hiding.active.push(a);
|
|
239
|
+
else hiding = { active: [a], len: 0, resume: cur };
|
|
240
|
+
hiding.len += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (it.deleted) continue;
|
|
244
|
+
const c = it.content;
|
|
245
|
+
if (c instanceof Y.ContentFormat) { if (c.value === null) delete cattrs[c.key]; else cattrs[c.key] = c.value; continue; }
|
|
246
|
+
if (hiding) {
|
|
247
|
+
if (c instanceof Y.ContentString || c instanceof Y.ContentEmbed) {
|
|
248
|
+
const piece = c instanceof Y.ContentString ? c.str : c.embed.b !== undefined ? "\u00b6" : "\n";
|
|
249
|
+
const raw = { ...cattrs };
|
|
250
|
+
for (const a of hiding.active) {
|
|
251
|
+
if (a.legacy) a.text += piece;
|
|
252
|
+
a.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: c instanceof Y.ContentString ? "text" : c.embed.b !== undefined ? "marker" : "br", text: c instanceof Y.ContentString ? c.str : null, blockId: c instanceof Y.ContentEmbed && c.embed.b !== undefined ? c.embed.b : null, raw });
|
|
253
|
+
}
|
|
254
|
+
hiding.len += it.length;
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (c instanceof Y.ContentFormat) { if (c.value === null) delete cattrs[c.key]; else cattrs[c.key] = c.value; continue; }
|
|
259
|
+
if (c instanceof Y.ContentEmbed) {
|
|
260
|
+
const source = markerSource(it, c.embed.b);
|
|
261
|
+
if (source !== null) {
|
|
262
|
+
const target = proj.find((entry) => entry.sourceKey === source && !entry.root);
|
|
263
|
+
if (target && !movedSources.has(source)) { openRecovered(source, it); target.root = cur; }
|
|
264
|
+
else hideUnit(it.length);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (boundarySource(it, c.embed.b) !== null) { hideUnit(it.length); continue; }
|
|
268
|
+
const moved = movedSource(it, c.embed.b);
|
|
269
|
+
if (moved !== null) { openRecovered(moved, it, true); continue; }
|
|
270
|
+
const e = c.embed;
|
|
271
|
+
if (e.b !== undefined) open(e.b, { client: it.id.client, clock: it.id.clock }, false);
|
|
272
|
+
else {
|
|
273
|
+
if (!cur || cur.atom) open(`~${prevId}`, null, true);
|
|
274
|
+
cur.runs.push({ br: true });
|
|
275
|
+
if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "br", text: null, blockId: null, raw: { ...cattrs } });
|
|
276
|
+
cur.len += 1;
|
|
277
|
+
}
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (c instanceof Y.ContentString) {
|
|
281
|
+
if (!cur || cur.atom) open(`~${prevId}`, null, true);
|
|
282
|
+
const marks = marksFromAttributes(cattrs, cur.t);
|
|
283
|
+
cur.runs.push({ text: c.str, marks, mk: marksKey(marks) });
|
|
284
|
+
if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "text", text: c.str, blockId: null, raw: { ...cattrs } });
|
|
285
|
+
cur.len += c.str.length;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (hiding) { for (const a of hiding.active) settle(a); flush(); }
|
|
289
|
+
const groups = new Map();
|
|
290
|
+
for (const s of segs) {
|
|
291
|
+
if (s.kind !== "b" || s.implicit) continue;
|
|
292
|
+
(groups.get(s.baseId) ?? groups.set(s.baseId, []).get(s.baseId)).push(s);
|
|
293
|
+
}
|
|
294
|
+
const textOfSeg = (s) => s.runs.map((r) => (r.br ? "\n" : r.hole ? "" : r.text)).join("");
|
|
295
|
+
const copies = new Map();
|
|
296
|
+
const lostCopies = [];
|
|
297
|
+
for (const [base, list] of groups) {
|
|
298
|
+
if (list.length < 2) continue;
|
|
299
|
+
const win = pickCopy(list);
|
|
300
|
+
for (const s of list) {
|
|
301
|
+
if (s === win) continue;
|
|
302
|
+
s.hide = true;
|
|
303
|
+
(copies.get(base) ?? copies.set(base, []).get(base)).push(idKey(s.brk));
|
|
304
|
+
if (!(sameJson(s.runs, win.runs) && s.t === win.t) && textOfSeg(s)) {
|
|
305
|
+
orphans.push({ key: `d${s.brk.client}.${s.brk.clock}`, id: base, text: textOfSeg(s), h: hashText(textOfSeg(s)), copy: true });
|
|
306
|
+
if (s.items?.length) {
|
|
307
|
+
const sourceKey = idKey(index.resolve(s.brk));
|
|
308
|
+
const key = copyLostKey(sourceKey);
|
|
309
|
+
lostCopies.push({ key, sourceKey, source: { client: s.brk.client, clock: s.brk.clock }, operationId: null, blockId: base, end: null, sv: [], legacy: false, implicit: true });
|
|
310
|
+
hidden.push({ key, sourceKey, blockId: base, end: null, sv: [], at: s.items[0].at, live: true, items: s.items });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const visible = [];
|
|
316
|
+
const holes = [];
|
|
317
|
+
let tokens = 0;
|
|
318
|
+
for (const s of segs) {
|
|
319
|
+
if (s.kind === "h") { holes.push({ at: tokens, len: s.len }); continue; }
|
|
320
|
+
if (s.hide) {
|
|
321
|
+
holes.push({ at: tokens, len: s.len + s.runs.reduce((n, r) => n + (r.hole ?? 0), 0) });
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
visible.push(s);
|
|
325
|
+
let off = s.implicit ? 0 : 1;
|
|
326
|
+
for (const r of s.runs) {
|
|
327
|
+
if (r.hole) holes.push({ at: tokens + off, len: r.hole });
|
|
328
|
+
else off += r.br ? 1 : r.text.length;
|
|
329
|
+
}
|
|
330
|
+
tokens += s.len;
|
|
331
|
+
}
|
|
332
|
+
return { visible, holes, orphans, tokens, copies, hidden, removals: lostCopies.length ? [...removals, ...lostCopies] : removals, fences };
|
|
333
|
+
}
|