@arbidocs/coedit 0.5.20 → 0.5.22
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/dist/encoding.cjs +54 -56
- package/dist/encoding.cjs.map +1 -1
- package/dist/encoding.d.cts +50 -14
- package/dist/encoding.d.ts +50 -14
- package/dist/encoding.js +54 -57
- package/dist/encoding.js.map +1 -1
- package/dist/hocuspocus.cjs +211 -109
- package/dist/hocuspocus.cjs.map +1 -1
- package/dist/hocuspocus.js +211 -109
- package/dist/hocuspocus.js.map +1 -1
- package/dist/index.cjs +210 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +210 -108
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +214 -112
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +214 -112
- package/dist/server.js.map +1 -1
- package/dist/template.cjs +3 -2
- package/dist/template.cjs.map +1 -1
- package/dist/template.d.cts +5 -0
- package/dist/template.d.ts +5 -0
- package/dist/template.js +3 -2
- package/dist/template.js.map +1 -1
- package/dist/{versions-DXiFQMA6.d.cts → versions-XSYTVmOU.d.cts} +1 -1
- package/dist/{versions-DXiFQMA6.d.ts → versions-XSYTVmOU.d.ts} +1 -1
- package/package.json +1 -1
package/dist/hocuspocus.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
2
|
import { Awareness } from 'y-protocols/awareness';
|
|
3
3
|
import { HocuspocusProvider } from '@hocuspocus/provider';
|
|
4
|
-
import { writeOoxmlPackage, ORIGIN_IDS, readOoxmlPart, validateExternalTarget, relsPartNameFor, contentTypesPartBytes, withContentTypeOverride } from '@docx-editor.dev/core/store';
|
|
4
|
+
import { writeOoxmlPackage, ORIGIN_IDS, readOoxmlPart, validateExternalTarget, relsPartNameFor, contentTypesPartBytes, withContentTypeOverride, readXml, XML_NAMESPACE_URI } from '@docx-editor.dev/core/store';
|
|
5
5
|
import { createCollaborationStatusTracker, safeParticipantColor } from '@docx-editor.dev/core/collaboration';
|
|
6
6
|
import { historyGroupOfJournal, mapOffsetAcrossText } from '@docx-editor.dev/core/collaboration/replication';
|
|
7
7
|
|
|
@@ -316,6 +316,92 @@ function writeUnits(text, units) {
|
|
|
316
316
|
function isRichParagraph(map) {
|
|
317
317
|
return map.has("inline");
|
|
318
318
|
}
|
|
319
|
+
var PACKED = "xml";
|
|
320
|
+
function isPackedElement(map) {
|
|
321
|
+
return map.has(PACKED);
|
|
322
|
+
}
|
|
323
|
+
function isCompact(map) {
|
|
324
|
+
return isRichParagraph(map) || isPackedElement(map);
|
|
325
|
+
}
|
|
326
|
+
var TEXT_BEARING = /* @__PURE__ */ new Set(["p", "t", "delText", "instrText", "delInstrText"]);
|
|
327
|
+
function packable(xml) {
|
|
328
|
+
if (xml.ns === WML && TEXT_BEARING.has(xml.local)) return false;
|
|
329
|
+
return xml.children.every((c) => typeof c === "string" || packable(c));
|
|
330
|
+
}
|
|
331
|
+
function createPacked(xml, scope) {
|
|
332
|
+
const map = new Y.Map();
|
|
333
|
+
map.set(PACKED, serializeXml(xml, scope));
|
|
334
|
+
return map;
|
|
335
|
+
}
|
|
336
|
+
function repack(map, xml) {
|
|
337
|
+
const next = serializeXml(xml, scopeOf(map));
|
|
338
|
+
if (map.get(PACKED) !== next) map.set(PACKED, next);
|
|
339
|
+
}
|
|
340
|
+
function scopeOf(node) {
|
|
341
|
+
const chain = [];
|
|
342
|
+
let parent = node.parent;
|
|
343
|
+
while (parent instanceof Y.Array) {
|
|
344
|
+
const owner = parent.parent;
|
|
345
|
+
if (!(owner instanceof Y.Map)) break;
|
|
346
|
+
chain.push(owner);
|
|
347
|
+
parent = owner.parent;
|
|
348
|
+
}
|
|
349
|
+
const scope = /* @__PURE__ */ new Map();
|
|
350
|
+
for (const el of chain.reverse()) {
|
|
351
|
+
for (const b of el.get("bindings").toArray()) scope.set(b.prefix, b.ns);
|
|
352
|
+
}
|
|
353
|
+
return scope;
|
|
354
|
+
}
|
|
355
|
+
function scopeWithin(el) {
|
|
356
|
+
const scope = scopeOf(el);
|
|
357
|
+
if (!isPackedElement(el)) {
|
|
358
|
+
for (const b of el.get("bindings").toArray()) scope.set(b.prefix, b.ns);
|
|
359
|
+
}
|
|
360
|
+
return scope;
|
|
361
|
+
}
|
|
362
|
+
var unpackLimits = { maxBytes: 64 * 1024 * 1024, maxElements: 2e6 };
|
|
363
|
+
function unpack(text, scope) {
|
|
364
|
+
const read = readXml(text, unpackLimits);
|
|
365
|
+
if (!read.ok) throw new Error(`collab: a packed element is not readable XML (${read.reason})`);
|
|
366
|
+
const convert = (node, outer) => {
|
|
367
|
+
const bindings = [];
|
|
368
|
+
const named = [];
|
|
369
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
370
|
+
if (name === "xmlns") bindings.push({ prefix: "", ns: value });
|
|
371
|
+
else if (name.startsWith("xmlns:")) bindings.push({ prefix: name.slice(6), ns: value });
|
|
372
|
+
else named.push([name, value]);
|
|
373
|
+
}
|
|
374
|
+
const inner = new Map(outer);
|
|
375
|
+
for (const b of bindings) inner.set(b.prefix, b.ns);
|
|
376
|
+
const resolve = (prefix) => {
|
|
377
|
+
if (prefix === "xml") return XML_NAMESPACE_URI;
|
|
378
|
+
const ns = inner.get(prefix);
|
|
379
|
+
if (ns === void 0)
|
|
380
|
+
throw new Error(`collab: prefix ${prefix} is not bound in a packed element`);
|
|
381
|
+
return ns;
|
|
382
|
+
};
|
|
383
|
+
const colon = node.name.indexOf(":");
|
|
384
|
+
const xml = {
|
|
385
|
+
ns: colon < 0 ? inner.get("") ?? "" : resolve(node.name.slice(0, colon)),
|
|
386
|
+
local: colon < 0 ? node.name : node.name.slice(colon + 1),
|
|
387
|
+
attrs: named.map(([name, value]) => {
|
|
388
|
+
const at = name.indexOf(":");
|
|
389
|
+
if (at < 0) return { ns: "", local: name, value };
|
|
390
|
+
const prefix = name.slice(0, at);
|
|
391
|
+
return { ns: resolve(prefix), local: name.slice(at + 1), prefix, value };
|
|
392
|
+
}),
|
|
393
|
+
bindings,
|
|
394
|
+
children: node.children.map(
|
|
395
|
+
(child) => child.type === "text" ? child.value : convert(child, inner)
|
|
396
|
+
)
|
|
397
|
+
};
|
|
398
|
+
if (colon >= 0) xml.prefix = node.name.slice(0, colon);
|
|
399
|
+
return xml;
|
|
400
|
+
};
|
|
401
|
+
const root = read.nodes.find((n) => n.type === "element");
|
|
402
|
+
if (!root || root.type !== "element") throw new Error("collab: a packed element is empty");
|
|
403
|
+
return convert(root, scope);
|
|
404
|
+
}
|
|
319
405
|
function isParagraphEl(el) {
|
|
320
406
|
return el.ns === WML && el.local === "p";
|
|
321
407
|
}
|
|
@@ -323,47 +409,53 @@ function fieldsOf(map, xml) {
|
|
|
323
409
|
map.set("ns", xml.ns);
|
|
324
410
|
map.set("local", xml.local);
|
|
325
411
|
if (xml.prefix !== void 0) map.set("prefix", xml.prefix);
|
|
326
|
-
const attrs = new
|
|
412
|
+
const attrs = new Y.Array();
|
|
327
413
|
attrs.push(xml.attrs.map((a) => ({ ...a })));
|
|
328
414
|
map.set("attrs", attrs);
|
|
329
|
-
const bindings = new
|
|
415
|
+
const bindings = new Y.Array();
|
|
330
416
|
bindings.push(xml.bindings.map((b) => ({ ...b })));
|
|
331
417
|
map.set("bindings", bindings);
|
|
332
418
|
}
|
|
333
419
|
function newText(value) {
|
|
334
|
-
const text = new
|
|
420
|
+
const text = new Y.Text();
|
|
335
421
|
text.insert(0, value);
|
|
336
422
|
return text;
|
|
337
423
|
}
|
|
338
|
-
function createRichParagraph(xml, encoded) {
|
|
339
|
-
const map = new
|
|
424
|
+
function createRichParagraph(xml, encoded, scope = /* @__PURE__ */ new Map()) {
|
|
425
|
+
const map = new Y.Map();
|
|
340
426
|
fieldsOf(map, xml);
|
|
341
|
-
const
|
|
342
|
-
children
|
|
427
|
+
const inner = withBindings(scope, xml.bindings);
|
|
428
|
+
const children = new Y.Array();
|
|
429
|
+
children.push(encoded.head.map((el) => xmlToLive(el, inner)));
|
|
343
430
|
map.set("children", children);
|
|
344
|
-
const inline = new
|
|
431
|
+
const inline = new Y.Text();
|
|
345
432
|
writeUnits(inline, encoded.units);
|
|
346
433
|
map.set("inline", inline);
|
|
347
434
|
return map;
|
|
348
435
|
}
|
|
349
|
-
function
|
|
436
|
+
function withBindings(scope, bindings) {
|
|
437
|
+
const inner = new Map(scope);
|
|
438
|
+
for (const b of bindings) inner.set(b.prefix, b.ns);
|
|
439
|
+
return inner;
|
|
440
|
+
}
|
|
441
|
+
function xmlToLive(xml, scope = /* @__PURE__ */ new Map(), root = false) {
|
|
350
442
|
if (isParagraphEl(xml)) {
|
|
351
443
|
const encoded = encodeParagraph(xml.children);
|
|
352
|
-
if (encoded) return createRichParagraph(xml, encoded);
|
|
444
|
+
if (encoded) return createRichParagraph(xml, encoded, scope);
|
|
353
445
|
}
|
|
354
|
-
return
|
|
355
|
-
|
|
356
|
-
function xmlToGenericLive(xml) {
|
|
357
|
-
const map = new Y3.Map();
|
|
446
|
+
if (!root && packable(xml)) return createPacked(xml, scope);
|
|
447
|
+
const map = new Y.Map();
|
|
358
448
|
fieldsOf(map, xml);
|
|
359
|
-
const
|
|
360
|
-
children
|
|
449
|
+
const inner = withBindings(scope, xml.bindings);
|
|
450
|
+
const children = new Y.Array();
|
|
451
|
+
children.push(xml.children.map((c) => typeof c === "string" ? newText(c) : xmlToLive(c, inner)));
|
|
361
452
|
map.set("children", children);
|
|
362
453
|
return map;
|
|
363
454
|
}
|
|
364
455
|
function liveToXml(map) {
|
|
456
|
+
if (isPackedElement(map)) return unpack(map.get(PACKED), scopeOf(map));
|
|
365
457
|
const prefix = map.get("prefix");
|
|
366
|
-
const stored = map.get("children").toArray().map((child) => child instanceof
|
|
458
|
+
const stored = map.get("children").toArray().map((child) => child instanceof Y.Text ? child.toString() : liveToXml(child));
|
|
367
459
|
const children = isRichParagraph(map) ? [...stored, ...decodeInline(readUnits(map.get("inline")))] : stored;
|
|
368
460
|
const xml = {
|
|
369
461
|
ns: map.get("ns"),
|
|
@@ -378,14 +470,13 @@ function liveToXml(map) {
|
|
|
378
470
|
var escapeText = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
379
471
|
var escapeAttr = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/\t/g, "	").replace(/\n/g, " ").replace(/\r/g, " ");
|
|
380
472
|
var qualified = (prefix, local) => prefix ? `${prefix}:${local}` : local;
|
|
381
|
-
function
|
|
382
|
-
const inScope =
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
for (const b of el.bindings) {
|
|
473
|
+
function startTag(prefix, local, bindings, attrs, scope) {
|
|
474
|
+
const inScope = withBindings(scope, bindings);
|
|
475
|
+
let out = `<${qualified(prefix, local)}`;
|
|
476
|
+
for (const b of bindings) {
|
|
386
477
|
out += b.prefix === "" ? ` xmlns="${escapeAttr(b.ns)}"` : ` xmlns:${b.prefix}="${escapeAttr(b.ns)}"`;
|
|
387
478
|
}
|
|
388
|
-
for (const a of
|
|
479
|
+
for (const a of attrs) {
|
|
389
480
|
let p = a.prefix;
|
|
390
481
|
if (a.ns !== "" && !p) {
|
|
391
482
|
for (const [candidate, uri] of inScope) {
|
|
@@ -397,48 +488,41 @@ function serializeXml(el, scope) {
|
|
|
397
488
|
}
|
|
398
489
|
out += ` ${qualified(p, a.local)}="${escapeAttr(a.value)}"`;
|
|
399
490
|
}
|
|
491
|
+
return [out, inScope];
|
|
492
|
+
}
|
|
493
|
+
function serializeXml(el, scope) {
|
|
494
|
+
const [out, inScope] = startTag(el.prefix, el.local, el.bindings, el.attrs, scope);
|
|
400
495
|
if (el.children.length === 0) return `${out}/>`;
|
|
401
|
-
|
|
496
|
+
let body = "";
|
|
402
497
|
for (const child of el.children) {
|
|
403
|
-
|
|
498
|
+
body += typeof child === "string" ? escapeText(child) : serializeXml(child, inScope);
|
|
404
499
|
}
|
|
405
|
-
return `${out}</${qualified(el.prefix, el.local)}>`;
|
|
500
|
+
return `${out}>${body}</${qualified(el.prefix, el.local)}>`;
|
|
406
501
|
}
|
|
407
502
|
function serializeLive(el, scope) {
|
|
408
|
-
if (
|
|
503
|
+
if (isPackedElement(el)) return el.get(PACKED);
|
|
409
504
|
const prefix = el.get("prefix");
|
|
410
505
|
const local = el.get("local");
|
|
411
506
|
const bindings = el.get("bindings").toArray();
|
|
412
507
|
const attrs = el.get("attrs").toArray();
|
|
413
508
|
const children = el.get("children").toArray();
|
|
414
|
-
const inScope =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
out += b.prefix === "" ? ` xmlns="${escapeAttr(b.ns)}"` : ` xmlns:${b.prefix}="${escapeAttr(b.ns)}"`;
|
|
419
|
-
}
|
|
420
|
-
for (const a of attrs) {
|
|
421
|
-
let p = a.prefix;
|
|
422
|
-
if (a.ns !== "" && !p) {
|
|
423
|
-
for (const [candidate, uri] of inScope) {
|
|
424
|
-
if (candidate !== "" && uri === a.ns) {
|
|
425
|
-
p = candidate;
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
out += ` ${qualified(p, a.local)}="${escapeAttr(a.value)}"`;
|
|
431
|
-
}
|
|
432
|
-
if (children.length === 0) return `${out}/>`;
|
|
433
|
-
out += ">";
|
|
509
|
+
const [out, inScope] = startTag(prefix, local, bindings, attrs, scope);
|
|
510
|
+
const inline = isRichParagraph(el) ? decodeInline(readUnits(el.get("inline"))) : [];
|
|
511
|
+
if (children.length === 0 && inline.length === 0) return `${out}/>`;
|
|
512
|
+
let body = "";
|
|
434
513
|
for (const child of children) {
|
|
435
|
-
|
|
514
|
+
body += child instanceof Y.Text ? escapeText(child.toString()) : serializeLive(child, inScope);
|
|
436
515
|
}
|
|
437
|
-
|
|
516
|
+
for (const child of inline) body += serializeXml(child, inScope);
|
|
517
|
+
return `${out}>${body}</${qualified(prefix, local)}>`;
|
|
438
518
|
}
|
|
439
519
|
|
|
440
520
|
// src/encoding.ts
|
|
441
|
-
var COLLAB_FORMAT = "arbi-docx/
|
|
521
|
+
var COLLAB_FORMAT = "arbi-docx/2";
|
|
522
|
+
var READABLE_FORMATS = /* @__PURE__ */ new Set(["arbi-docx/1", COLLAB_FORMAT]);
|
|
523
|
+
function isReadableFormat(format) {
|
|
524
|
+
return format !== void 0 && READABLE_FORMATS.has(format);
|
|
525
|
+
}
|
|
442
526
|
function isBootstrapped(doc) {
|
|
443
527
|
return doc.getMap("meta").get("format") !== void 0;
|
|
444
528
|
}
|
|
@@ -735,7 +819,7 @@ var NodeRegistry = class {
|
|
|
735
819
|
require(id) {
|
|
736
820
|
let entry = this.entries.get(id) ?? this.resolveInsideParagraph(id);
|
|
737
821
|
if (!entry) throw new RegistryError(`journal names unknown node ${id}`);
|
|
738
|
-
if ("live" in entry && entry.live instanceof
|
|
822
|
+
if ("live" in entry && entry.live instanceof Y.Map && isCompact(entry.live)) {
|
|
739
823
|
this.openParagraph(entry.live);
|
|
740
824
|
entry = this.entries.get(id);
|
|
741
825
|
}
|
|
@@ -753,7 +837,7 @@ var NodeRegistry = class {
|
|
|
753
837
|
for (let length = path.length - 1; length >= 1; length--) {
|
|
754
838
|
const ancestor = this.entries.get(id.slice(0, hash + 1) + path.slice(0, length).join("."));
|
|
755
839
|
if (!ancestor) continue;
|
|
756
|
-
if ("live" in ancestor && ancestor.live instanceof
|
|
840
|
+
if ("live" in ancestor && ancestor.live instanceof Y.Map && isCompact(ancestor.live)) {
|
|
757
841
|
this.openParagraph(ancestor.live);
|
|
758
842
|
return this.entries.get(id);
|
|
759
843
|
}
|
|
@@ -785,13 +869,13 @@ var NodeRegistry = class {
|
|
|
785
869
|
/**
|
|
786
870
|
* Walk a mirror root and register every node under its positional id, `${part}#0` for the root and
|
|
787
871
|
* `${parent}.${index}` below it. This is the id scheme core assigns when it reads a part. A rich
|
|
788
|
-
* paragraph is registered as one node; its inner nodes appear when it is opened.
|
|
872
|
+
* paragraph or a packed element is registered as one node; its inner nodes appear when it is opened.
|
|
789
873
|
*/
|
|
790
874
|
registerTree(partName, root) {
|
|
791
875
|
const walk = (node, id) => {
|
|
792
876
|
this.registerLive(id, node);
|
|
793
|
-
if (node instanceof
|
|
794
|
-
if (
|
|
877
|
+
if (node instanceof Y.Text) return;
|
|
878
|
+
if (isCompact(node)) return;
|
|
795
879
|
childrenOf(node).forEach((child, index) => walk(child, `${id}.${index}`));
|
|
796
880
|
};
|
|
797
881
|
walk(root, `${partName}#0`);
|
|
@@ -848,7 +932,7 @@ var NodeRegistry = class {
|
|
|
848
932
|
for (const child of children) this.own(child, owner);
|
|
849
933
|
if (owner) this.dirty.add(owner);
|
|
850
934
|
}
|
|
851
|
-
/** Write every changed open paragraph back into the mirror. Call at the end of a journal. */
|
|
935
|
+
/** Write every changed open paragraph or packed element back into the mirror. Call at the end of a journal. */
|
|
852
936
|
flushRich() {
|
|
853
937
|
for (const live of [...this.dirty]) {
|
|
854
938
|
this.dirty.delete(live);
|
|
@@ -859,6 +943,11 @@ var NodeRegistry = class {
|
|
|
859
943
|
writeBack(record) {
|
|
860
944
|
const { live, root } = record;
|
|
861
945
|
const xml = plainToXml(root);
|
|
946
|
+
if (isPackedElement(live)) {
|
|
947
|
+
if (packable(xml)) repack(live, xml);
|
|
948
|
+
else this.degrade(record);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
862
951
|
const encoded = encodeParagraph(xml.children);
|
|
863
952
|
if (!encoded) {
|
|
864
953
|
this.degrade(record);
|
|
@@ -875,21 +964,22 @@ var NodeRegistry = class {
|
|
|
875
964
|
bindings.push(xml.bindings.map((b) => ({ ...b })));
|
|
876
965
|
}
|
|
877
966
|
const head = childrenOf(live);
|
|
878
|
-
const
|
|
879
|
-
|
|
967
|
+
const scope = scopeWithin(live);
|
|
968
|
+
const stored = head.toArray().map((child) => serializeLive(child, scope)).join("");
|
|
969
|
+
if (stored !== encoded.head.map((el) => serializeXml(el, scope)).join("")) {
|
|
880
970
|
head.delete(0, head.length);
|
|
881
|
-
head.push(encoded.head.map((el) => xmlToLive(el)));
|
|
971
|
+
head.push(encoded.head.map((el) => xmlToLive(el, scope)));
|
|
882
972
|
}
|
|
883
973
|
const inline = live.get("inline");
|
|
884
974
|
applyUnits(inline, readUnits(inline), encoded.units);
|
|
885
975
|
}
|
|
886
|
-
/** The paragraph can no longer be held as rich text: store it as
|
|
976
|
+
/** The paragraph can no longer be held as rich text, or the element packed: store it as a tree. */
|
|
887
977
|
degrade(record) {
|
|
888
978
|
const parent = record.live.parent;
|
|
889
|
-
if (!(parent instanceof
|
|
979
|
+
if (!(parent instanceof Y.Array))
|
|
890
980
|
throw new RegistryError("a paragraph outside a parent cannot change form");
|
|
891
981
|
const index = parent.toArray().indexOf(record.live);
|
|
892
|
-
const generic = this.toGenericLive(record.root);
|
|
982
|
+
const generic = this.toGenericLive(record.root, scopeOf(record.live));
|
|
893
983
|
this.open.delete(record.live);
|
|
894
984
|
parent.delete(index, 1);
|
|
895
985
|
parent.insert(index, [generic]);
|
|
@@ -904,8 +994,8 @@ var NodeRegistry = class {
|
|
|
904
994
|
snapshotLive(node) {
|
|
905
995
|
const id = this.liveIds.get(node);
|
|
906
996
|
if (id === void 0) throw new RegistryError("live node has no registered id");
|
|
907
|
-
if (node instanceof
|
|
908
|
-
if (
|
|
997
|
+
if (node instanceof Y.Text) return { type: "text", id, value: node.toString() };
|
|
998
|
+
if (isCompact(node)) {
|
|
909
999
|
return this.open.get(node)?.root ?? xmlToPlain(liveToXml(node), id);
|
|
910
1000
|
}
|
|
911
1001
|
const prefix = node.get("prefix");
|
|
@@ -933,8 +1023,8 @@ var NodeRegistry = class {
|
|
|
933
1023
|
if (id !== void 0 && this.isCurrentLive(id, node)) {
|
|
934
1024
|
this.entries.set(id, { plain });
|
|
935
1025
|
}
|
|
936
|
-
if (node instanceof
|
|
937
|
-
if (
|
|
1026
|
+
if (node instanceof Y.Text || plain.type === "text") return;
|
|
1027
|
+
if (isCompact(node)) {
|
|
938
1028
|
this.open.delete(node);
|
|
939
1029
|
this.dirty.delete(node);
|
|
940
1030
|
return;
|
|
@@ -942,53 +1032,61 @@ var NodeRegistry = class {
|
|
|
942
1032
|
const liveChildren = childrenOf(node).toArray();
|
|
943
1033
|
liveChildren.forEach((child, index) => this.detachInto(child, plain.children[index]));
|
|
944
1034
|
}
|
|
945
|
-
/**
|
|
946
|
-
|
|
1035
|
+
/**
|
|
1036
|
+
* Build Yjs types for a plain subtree placed where `scope` is in force, and register every node as
|
|
1037
|
+
* live. A rich paragraph or packed element stays open on the plain tree, which is already current.
|
|
1038
|
+
* A part's `root` is never packed.
|
|
1039
|
+
*/
|
|
1040
|
+
toLive(plain, scope = /* @__PURE__ */ new Map(), root = false) {
|
|
947
1041
|
if (plain.type === "text") {
|
|
948
|
-
const text = new
|
|
1042
|
+
const text = new Y.Text();
|
|
949
1043
|
text.insert(0, plain.value);
|
|
950
1044
|
this.registerLive(plain.id, text);
|
|
951
1045
|
return text;
|
|
952
1046
|
}
|
|
1047
|
+
const xml = plainToXml(plain);
|
|
1048
|
+
let map;
|
|
953
1049
|
if (isParagraphEl(plain)) {
|
|
954
|
-
const xml = plainToXml(plain);
|
|
955
1050
|
const encoded = encodeParagraph(xml.children);
|
|
956
|
-
if (encoded)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
1051
|
+
if (encoded) map = createRichParagraph(xml, encoded, scope);
|
|
1052
|
+
}
|
|
1053
|
+
if (!map && !root && packable(xml)) map = xmlToLive(xml, scope);
|
|
1054
|
+
if (map) {
|
|
1055
|
+
this.liveIds.set(map, plain.id);
|
|
1056
|
+
this.registerPlain(plain);
|
|
1057
|
+
this.own(plain, map);
|
|
1058
|
+
this.open.set(map, { live: map, root: plain, id: plain.id });
|
|
1059
|
+
return map;
|
|
964
1060
|
}
|
|
965
|
-
return this.toGenericLive(plain);
|
|
1061
|
+
return this.toGenericLive(plain, scope);
|
|
966
1062
|
}
|
|
967
|
-
toGenericLive(plain) {
|
|
1063
|
+
toGenericLive(plain, scope) {
|
|
968
1064
|
const map = xmlToGenericShell(plain);
|
|
969
|
-
const
|
|
970
|
-
children
|
|
1065
|
+
const inner = withBindings(scope, plain.bindings);
|
|
1066
|
+
const children = new Y.Array();
|
|
1067
|
+
children.push(plain.children.map((child) => this.toLive(child, inner)));
|
|
971
1068
|
map.set("children", children);
|
|
972
1069
|
this.registerLive(plain.id, map);
|
|
973
1070
|
return map;
|
|
974
1071
|
}
|
|
975
1072
|
/**
|
|
976
|
-
* A live node that can be inserted into a parent
|
|
977
|
-
* attached node is copied, because a Yjs type cannot
|
|
1073
|
+
* A live node that can be inserted into a parent (`scope` is in force there), or a part's `root`.
|
|
1074
|
+
* Anything not yet attached is built fresh; an attached node is copied, because a Yjs type cannot
|
|
1075
|
+
* have two parents.
|
|
978
1076
|
*/
|
|
979
|
-
attachable(id) {
|
|
980
|
-
return this.toLive(this.snapshot(id));
|
|
1077
|
+
attachable(id, scope = /* @__PURE__ */ new Map(), root = false) {
|
|
1078
|
+
return this.toLive(this.snapshot(id), scope, root);
|
|
981
1079
|
}
|
|
982
1080
|
};
|
|
983
1081
|
function xmlToGenericShell(plain) {
|
|
984
|
-
const map = new
|
|
1082
|
+
const map = new Y.Map();
|
|
985
1083
|
map.set("ns", plain.ns);
|
|
986
1084
|
map.set("local", plain.local);
|
|
987
1085
|
if (plain.prefix !== void 0) map.set("prefix", plain.prefix);
|
|
988
|
-
const attrs = new
|
|
1086
|
+
const attrs = new Y.Array();
|
|
989
1087
|
attrs.push(plain.attrs.map((a) => ({ ...a })));
|
|
990
1088
|
map.set("attrs", attrs);
|
|
991
|
-
const bindings = new
|
|
1089
|
+
const bindings = new Y.Array();
|
|
992
1090
|
bindings.push(plain.bindings.map((b) => ({ ...b })));
|
|
993
1091
|
map.set("bindings", bindings);
|
|
994
1092
|
return map;
|
|
@@ -1053,7 +1151,7 @@ function setNamespaceBinding(registry, id, prefix, uri) {
|
|
|
1053
1151
|
}
|
|
1054
1152
|
}
|
|
1055
1153
|
function asLiveElement(node, id) {
|
|
1056
|
-
if (node instanceof
|
|
1154
|
+
if (node instanceof Y.Text) throw new JournalError(`${id} is a text node, not an element`);
|
|
1057
1155
|
return node;
|
|
1058
1156
|
}
|
|
1059
1157
|
function asPlainElement(node, id) {
|
|
@@ -1069,7 +1167,7 @@ function spliceText(registry, id, start, deleteCount, insert) {
|
|
|
1069
1167
|
return;
|
|
1070
1168
|
}
|
|
1071
1169
|
const text = entry.live;
|
|
1072
|
-
if (!(text instanceof
|
|
1170
|
+
if (!(text instanceof Y.Text)) throw new JournalError(`${id} is not a text node`);
|
|
1073
1171
|
if (start < 0 || start + deleteCount > text.length) {
|
|
1074
1172
|
throw new JournalError(`text splice out of range on ${id}`);
|
|
1075
1173
|
}
|
|
@@ -1082,27 +1180,29 @@ function detachRemoved(registry, parent, start, count) {
|
|
|
1082
1180
|
function spliceChildren(registry, parentId, start, deleteCount, childIds) {
|
|
1083
1181
|
const parentEntry = registry.require(parentId);
|
|
1084
1182
|
if ("plain" in parentEntry) {
|
|
1085
|
-
const
|
|
1086
|
-
if (start < 0 || start + deleteCount >
|
|
1183
|
+
const parent2 = asPlainElement(parentEntry.plain, parentId);
|
|
1184
|
+
if (start < 0 || start + deleteCount > parent2.children.length) {
|
|
1087
1185
|
throw new JournalError(`child splice out of range on ${parentId}`);
|
|
1088
1186
|
}
|
|
1089
1187
|
const inserted2 = childIds.map((id) => registry.snapshot(id));
|
|
1090
|
-
|
|
1091
|
-
registry.adopt(
|
|
1188
|
+
parent2.children.splice(start, deleteCount, ...inserted2);
|
|
1189
|
+
registry.adopt(parent2, inserted2);
|
|
1092
1190
|
return;
|
|
1093
1191
|
}
|
|
1094
|
-
const
|
|
1192
|
+
const parent = asLiveElement(parentEntry.live, parentId);
|
|
1193
|
+
const children = childrenOf(parent);
|
|
1095
1194
|
if (start < 0 || start + deleteCount > children.length) {
|
|
1096
1195
|
throw new JournalError(`child splice out of range on ${parentId}`);
|
|
1097
1196
|
}
|
|
1098
1197
|
detachRemoved(registry, children, start, deleteCount);
|
|
1099
|
-
const
|
|
1198
|
+
const scope = scopeWithin(parent);
|
|
1199
|
+
const inserted = childIds.map((id) => registry.attachable(id, scope));
|
|
1100
1200
|
if (deleteCount > 0) children.delete(start, deleteCount);
|
|
1101
1201
|
if (inserted.length > 0) children.insert(start, inserted);
|
|
1102
1202
|
}
|
|
1103
1203
|
function locate(registry, id, node) {
|
|
1104
1204
|
const children = node.parent;
|
|
1105
|
-
if (!(children instanceof
|
|
1205
|
+
if (!(children instanceof Y.Array)) throw new JournalError(`${id} is not attached`);
|
|
1106
1206
|
const index = children.toArray().indexOf(node);
|
|
1107
1207
|
if (index < 0) throw new JournalError(`${id} is not in its parent`);
|
|
1108
1208
|
return { children, index };
|
|
@@ -1129,8 +1229,10 @@ function moveNode(registry, id, destinationId, destinationIndex) {
|
|
|
1129
1229
|
registry.adopt(destination2, [copy]);
|
|
1130
1230
|
return;
|
|
1131
1231
|
}
|
|
1132
|
-
const destination =
|
|
1133
|
-
destination.insert(destinationIndex, [
|
|
1232
|
+
const destination = asLiveElement(destinationEntry.live, destinationId);
|
|
1233
|
+
childrenOf(destination).insert(destinationIndex, [
|
|
1234
|
+
registry.toLive(copy, scopeWithin(destination))
|
|
1235
|
+
]);
|
|
1134
1236
|
}
|
|
1135
1237
|
function renameNode(registry, id, qname) {
|
|
1136
1238
|
const entry = registry.require(id);
|
|
@@ -1145,7 +1247,7 @@ function renameNode(registry, id, qname) {
|
|
|
1145
1247
|
return true;
|
|
1146
1248
|
}
|
|
1147
1249
|
const live = entry.live;
|
|
1148
|
-
if (live instanceof
|
|
1250
|
+
if (live instanceof Y.Text || isCompact(live)) return false;
|
|
1149
1251
|
if (live.get("ns") !== qname.namespaceUri) live.set("ns", qname.namespaceUri);
|
|
1150
1252
|
if (live.get("local") !== qname.localName) live.set("local", qname.localName);
|
|
1151
1253
|
if (qname.prefix === void 0) {
|
|
@@ -1175,7 +1277,7 @@ function putNode(registry, effect) {
|
|
|
1175
1277
|
}
|
|
1176
1278
|
function rootFor(registry, id) {
|
|
1177
1279
|
const entry = registry.require(id);
|
|
1178
|
-
const live = "live" in entry && entry.live.parent === null ? entry.live : registry.attachable(id);
|
|
1280
|
+
const live = "live" in entry && entry.live.parent === null ? entry.live : registry.attachable(id, /* @__PURE__ */ new Map(), true);
|
|
1179
1281
|
return asLiveElement(live, id);
|
|
1180
1282
|
}
|
|
1181
1283
|
function applyEffect(doc, registry, effect, context) {
|
|
@@ -1219,7 +1321,7 @@ function applyEffect(doc, registry, effect, context) {
|
|
|
1219
1321
|
const rels = doc.getMap("rels");
|
|
1220
1322
|
let byId = rels.get(effect.owner);
|
|
1221
1323
|
if (!byId) {
|
|
1222
|
-
byId = new
|
|
1324
|
+
byId = new Y.Map();
|
|
1223
1325
|
rels.set(effect.owner, byId);
|
|
1224
1326
|
}
|
|
1225
1327
|
byId.set(effect.record.id, { ...effect.record });
|
|
@@ -1424,7 +1526,7 @@ var DocxCollaborationSession = class {
|
|
|
1424
1526
|
this.doc = options.doc;
|
|
1425
1527
|
this.awareness = options.awareness;
|
|
1426
1528
|
this.offlineEditing = options.offlineEditing ?? false;
|
|
1427
|
-
this.undoManager = new
|
|
1529
|
+
this.undoManager = new Y.UndoManager(
|
|
1428
1530
|
[
|
|
1429
1531
|
this.doc.getMap("parts"),
|
|
1430
1532
|
this.doc.getMap("rels"),
|
|
@@ -1464,7 +1566,7 @@ var DocxCollaborationSession = class {
|
|
|
1464
1566
|
this.setStatus("error", "not-initialized");
|
|
1465
1567
|
return;
|
|
1466
1568
|
}
|
|
1467
|
-
if (readFormat(this.doc)
|
|
1569
|
+
if (!isReadableFormat(readFormat(this.doc))) {
|
|
1468
1570
|
this.setStatus("error", "collaboration-format-mismatch", readFormat(this.doc));
|
|
1469
1571
|
return;
|
|
1470
1572
|
}
|
|
@@ -1738,7 +1840,7 @@ function resolveAddress(port, address) {
|
|
|
1738
1840
|
};
|
|
1739
1841
|
}
|
|
1740
1842
|
function vectorKey(doc) {
|
|
1741
|
-
return Array.from(
|
|
1843
|
+
return Array.from(Y.encodeStateVector(doc)).join(",");
|
|
1742
1844
|
}
|
|
1743
1845
|
function randomToken() {
|
|
1744
1846
|
if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
@@ -1757,7 +1859,7 @@ var CollaborationJoinError = class extends Error {
|
|
|
1757
1859
|
}
|
|
1758
1860
|
};
|
|
1759
1861
|
async function createHocuspocusCollaboration(options) {
|
|
1760
|
-
const ydoc = new
|
|
1862
|
+
const ydoc = new Y.Doc();
|
|
1761
1863
|
const awareness = new Awareness(ydoc);
|
|
1762
1864
|
const session = new DocxCollaborationSession({
|
|
1763
1865
|
doc: ydoc,
|