@superdoc/mcp 1.2.1-next.1 → 1.2.1-next.3

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.
Files changed (2) hide show
  1. package/dist/index.js +534 -108
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -31314,7 +31314,7 @@ var init_host = __esm(() => {
31314
31314
  });
31315
31315
 
31316
31316
  // ../../packages/sdk/langs/node/src/runtime/sdk-version.generated.ts
31317
- var SDK_VERSION = "2.15.1-next.1";
31317
+ var SDK_VERSION = "2.15.1-next.3";
31318
31318
 
31319
31319
  // ../../packages/sdk/langs/node/src/runtime/embedded-platform.ts
31320
31320
  import { chmodSync, existsSync } from "node:fs";
@@ -140090,7 +140090,9 @@ function markFullParagraphInsertions(xml, fragments, structural, index, namespac
140090
140090
  continue;
140091
140091
  const structuralInParagraph = structuralByParagraph.get(`${paragraph.start}:${paragraph.end}`) ?? [];
140092
140092
  const terminalDeletionOfPendingInsertion = marker.structuralKind === "paragraph-mark-insertion" && structuralInParagraph.length === 2 && structuralInParagraph[0] === marker && structuralInParagraph[1]?.structuralKind === "paragraph-mark-deletion";
140093
- if (structuralInParagraph.length !== 1 && !terminalDeletionOfPendingInsertion)
140093
+ const extraStructural = structuralInParagraph.filter((fragment2) => fragment2 !== marker);
140094
+ const extrasArePasteObjects = extraStructural.every((fragment2) => fragment2.structuralKind === "footnote-insertion" || fragment2.structuralKind === "endnote-insertion" || fragment2.structuralKind === "image-insertion" || fragment2.structuralKind === "field-insertion");
140095
+ if (structuralInParagraph.length !== 1 && !terminalDeletionOfPendingInsertion && !(extraStructural.length > 0 && extrasArePasteObjects))
140094
140096
  continue;
140095
140097
  const paragraphFragments = (fragmentsByParagraphStart.get(paragraph.start) ?? []).filter((candidate) => candidate.byteStart >= paragraph.openEnd && candidate.byteEnd <= paragraph.closeStart).sort((left, right) => left.byteStart - right.byteStart);
140096
140098
  const markerIdentity = buildReviewIdentity({
@@ -162775,7 +162777,7 @@ var init_collapsed_inline_insertion_site = __esm(() => {
162775
162777
  });
162776
162778
 
162777
162779
  // ../../../v2/editor-core/dist/document/transactions/compile-edit-text-inline-plan.js
162778
- function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, collapsedObjectBoundary, collapsedInlineAffinity, inheritedNamespaceBindings = {}) {
162780
+ function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, collapsedObjectBoundary, collapsedInlineAffinity, inheritedNamespaceBindings = {}, collapsedOutsideContentControl = false) {
162779
162781
  const xml = TD21.decode(paragraphBytes);
162780
162782
  const model = parseParagraphInlineMap(xml, inheritedNamespaceBindings);
162781
162783
  const leaves = model.leaves();
@@ -162818,7 +162820,7 @@ function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, c
162818
162820
  if (beside)
162819
162821
  return beside;
162820
162822
  }
162821
- const scope = resolveEditScope(model.pieces, startOffset, endOffset, collapsedInlineAffinity);
162823
+ const scope = resolveEditScope(model.pieces, startOffset, endOffset, collapsedInlineAffinity, collapsedOutsideContentControl);
162822
162824
  if (!scope.ok)
162823
162825
  return { ok: false, reason: scope.reason };
162824
162826
  const pieces = scope.pieces;
@@ -162830,7 +162832,7 @@ function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, c
162830
162832
  return { ok: false, reason: "invalid-range" };
162831
162833
  }
162832
162834
  const hasEditableLeaf = pieces.some((piece) => isVisibleText(piece) || isLiveControl(piece) || startOffset !== endOffset && isLiveNoteReference(piece));
162833
- if (!hasEditableLeaf) {
162835
+ if (!hasEditableLeaf && !(collapsedOutsideContentControl && startOffset === endOffset && newText.length > 0)) {
162834
162836
  if (newText.length === 0 && scope.wrapperRemovalOps.length > 0) {
162835
162837
  return {
162836
162838
  ok: true,
@@ -162847,7 +162849,7 @@ function planDirectInlineEdit(paragraphBytes, startOffset, endOffset, newText, c
162847
162849
  }
162848
162850
  if (startOffset === endOffset) {
162849
162851
  const plan2 = planCollapsedInsert(paragraphBytes, xml, pieces, leaves, startOffset, newText, visibleLength === 0, inlineContext, collapsedInlineAffinity);
162850
- return plan2.ok && scope.requiresCollaborationSessionSync ? { ...plan2, requiresCollaborationSessionSync: true } : plan2;
162852
+ return plan2.ok && (scope.requiresCollaborationSessionSync || collapsedOutsideContentControl) ? { ...plan2, requiresCollaborationSessionSync: true } : plan2;
162851
162853
  }
162852
162854
  const plan = planReplace(paragraphBytes, pieces, leaves, startOffset, endOffset, newText, scope.wrapperRemovalOps);
162853
162855
  return plan.ok && (scope.requiresCollaborationSessionSync || preservesCrossedBookmarks) ? { ...plan, requiresCollaborationSessionSync: true } : plan;
@@ -163439,13 +163441,13 @@ function hasNonWordMarkerAtCollapsedMoveBoundary(pieces, at, coordinateSpace = "
163439
163441
  function firstNonFieldRangeStartMarker(markers) {
163440
163442
  return markers.find((piece) => piece.role === "range-start" && !isFieldMarker(piece.markerKind)) ?? null;
163441
163443
  }
163442
- function resolveEditScope(pieces, start, end, collapsedInlineAffinity) {
163444
+ function resolveEditScope(pieces, start, end, collapsedInlineAffinity, collapsedOutsideContentControl = false) {
163443
163445
  let current = pieces;
163444
163446
  let descended = false;
163445
163447
  let emptySdt;
163446
163448
  let requiresCollaborationSessionSync = false;
163447
163449
  for (;; ) {
163448
- const container = current.find((piece) => piece.kind === "container" && containerContainsRange(piece, start, end)) ?? (start === end ? findCollapsedEdgeContainer(current, start, collapsedInlineAffinity == null) : undefined);
163450
+ const container = current.find((piece) => piece.kind === "container" && containerContainsRange(piece, start, end)) ?? (start === end ? findCollapsedEdgeContainer(current, start, collapsedInlineAffinity, collapsedOutsideContentControl) : undefined);
163449
163451
  if (!container) {
163450
163452
  if (start === end) {
163451
163453
  return {
@@ -163548,9 +163550,24 @@ function containerContainsRange(piece, start, end) {
163548
163550
  return piece.visibleStart < start && start < piece.visibleEnd;
163549
163551
  return piece.visibleStart <= start && end <= piece.visibleEnd;
163550
163552
  }
163551
- function findCollapsedEdgeContainer(pieces, at, allowEmptySdt = false) {
163553
+ function findCollapsedEdgeContainer(pieces, at, collapsedInlineAffinity, collapsedOutsideContentControl = false) {
163552
163554
  const emptySdtsAtPoint = pieces.filter((piece) => piece.kind === "container" && piece.containerKind === "sdt" && piece.visibleStart === at && piece.visibleEnd === at);
163553
- return pieces.find((piece) => piece.kind === "container" && (allowEmptySdt && emptySdtsAtPoint.length === 1 && piece.containerKind === "sdt" && piece.visibleStart === at && piece.visibleEnd === at || piece.visibleEnd > piece.visibleStart && (piece.visibleStart === at && (piece.containerKind === "sdt" || !hasVisibleSiblingEndingAt(pieces, at)) || piece.visibleEnd === at && (piece.containerKind === "sdt" ? !hasVisibleSiblingStartingAt(pieces, at, "sdt") : !hasVisibleSiblingStartingAt(pieces, at)))));
163555
+ return pieces.find((piece) => {
163556
+ if (piece.kind !== "container")
163557
+ return false;
163558
+ if (collapsedOutsideContentControl && piece.containerKind === "sdt")
163559
+ return false;
163560
+ if (piece.visibleStart === piece.visibleEnd) {
163561
+ return collapsedInlineAffinity == null && emptySdtsAtPoint.length === 1 && piece.containerKind === "sdt" && piece.visibleStart === at;
163562
+ }
163563
+ if (piece.visibleStart === at) {
163564
+ return piece.containerKind === "sdt" ? collapsedInlineAffinity !== "upstream" : !hasVisibleSiblingEndingAt(pieces, at);
163565
+ }
163566
+ if (piece.visibleEnd === at) {
163567
+ return piece.containerKind === "sdt" ? collapsedInlineAffinity !== "downstream" && !hasVisibleSiblingStartingAt(pieces, at, "sdt") : !hasVisibleSiblingStartingAt(pieces, at);
163568
+ }
163569
+ return false;
163570
+ });
163554
163571
  }
163555
163572
  function planEmptySdtInsert(paragraphBytes, piece, newText) {
163556
163573
  const raw = piece.rawXml;
@@ -168023,10 +168040,13 @@ function projectWordLikeReviewGroupsWithLookupContext(lookupContext, input) {
168023
168040
  const partXml = input.partXmlByUri.get(partUri);
168024
168041
  if (!partXml)
168025
168042
  continue;
168026
- if (isBodyStoryId(storyId)) {
168043
+ if (isBodyStoryId(storyId) || isNotesStoryId(storyId)) {
168027
168044
  projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entries, input);
168028
168045
  }
168029
168046
  projectFullParagraphInsertions(lookupContext, storyId, partUri, partXml, entries, input);
168047
+ if (isBodyStoryId(storyId) || isNotesStoryId(storyId)) {
168048
+ foldSameRsidPastedParagraphParents(storyId, partXml, input);
168049
+ }
168030
168050
  }
168031
168051
  }
168032
168052
  function projectFullParagraphInsertions(lookupContext, storyId, partUri, partXml, entries, input) {
@@ -168190,6 +168210,230 @@ function materializeFullParagraphInsertion(lookupContext, args) {
168190
168210
  for (const fragment2 of objectFragments)
168191
168211
  input.structuralKeyToLogicalId.set(fragment2.entityKey, id);
168192
168212
  }
168213
+ function foldSameRsidPastedParagraphParents(storyId, partXml, input) {
168214
+ const storyEntries = input.byStory.get(storyId);
168215
+ if (!storyEntries)
168216
+ return;
168217
+ const parents = storyEntries.filter((entry) => isFoldablePastedParagraphParent(entry)).sort((left, right) => left.documentOrderStart - right.documentOrderStart);
168218
+ if (parents.length < 2)
168219
+ return;
168220
+ const clusters = [];
168221
+ let current = [];
168222
+ const flush = () => {
168223
+ if (current.length >= 2)
168224
+ clusters.push(current);
168225
+ current = [];
168226
+ };
168227
+ for (const parent of parents) {
168228
+ const previous = current.at(-1);
168229
+ if (!previous) {
168230
+ current = [parent];
168231
+ continue;
168232
+ }
168233
+ if (canFoldPastedParagraphParents(partXml, previous, parent, storyEntries)) {
168234
+ current.push(parent);
168235
+ continue;
168236
+ }
168237
+ flush();
168238
+ current = [parent];
168239
+ }
168240
+ flush();
168241
+ for (const cluster of clusters) {
168242
+ mergePastedParagraphParents(storyId, partXml, cluster, input);
168243
+ }
168244
+ }
168245
+ function isFoldablePastedParagraphParent(entry) {
168246
+ const kind = entry.reviewGroup?.kind;
168247
+ return entry.reviewGroup?.role === "parent" && (kind === "word-full-paragraph-insertion" || kind === "word-inserted-paragraph-range") && entry.subtype !== "table-split" && entry.subtype !== "image-insertion" && entry.subtype !== "list-item-insertion" && entry.subtype !== "field-insertion" && !entry.reviewGroup?.members.some((member) => member.subtype === "field-insertion") && pastedParagraphParentRsid(entry) != null;
168248
+ }
168249
+ function pastedParagraphParentRsid(entry) {
168250
+ const mark = entry.structuralFragments.find((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion" || fragment2.structuralKind === "paragraph-insertion");
168251
+ const rsid = mark?.enclosingParagraphRsid ?? entry.sourceIds.rsids[0];
168252
+ return rsid ? rsid.toUpperCase() : null;
168253
+ }
168254
+ function pastedParagraphParentBounds(entry) {
168255
+ const marks = entry.structuralFragments.filter((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion" || fragment2.structuralKind === "paragraph-insertion");
168256
+ const starts = marks.map((fragment2) => fragment2.enclosingElementStart).filter((value) => value != null);
168257
+ const ends = marks.map((fragment2) => fragment2.enclosingElementEnd).filter((value) => value != null);
168258
+ if (starts.length === 0 || ends.length === 0)
168259
+ return null;
168260
+ return { start: Math.min(...starts), end: Math.max(...ends) };
168261
+ }
168262
+ function canFoldPastedParagraphParents(partXml, previous, next, storyEntries) {
168263
+ if (pastedParagraphParentRsid(previous) !== pastedParagraphParentRsid(next))
168264
+ return false;
168265
+ if (previous.subtype !== next.subtype)
168266
+ return false;
168267
+ if (!isSameReviewGroupIdentity(buildReviewIdentity({
168268
+ name: previous.author.name,
168269
+ email: previous.authorEmail,
168270
+ initials: previous.author.initials
168271
+ }), buildReviewIdentity({
168272
+ name: next.author.name,
168273
+ email: next.authorEmail,
168274
+ initials: next.author.initials
168275
+ }))) {
168276
+ return false;
168277
+ }
168278
+ const previousBounds = pastedParagraphParentBounds(previous);
168279
+ const nextBounds = pastedParagraphParentBounds(next);
168280
+ if (!previousBounds || !nextBounds || nextBounds.start < previousBounds.end)
168281
+ return false;
168282
+ const stripped = partXml.slice(previousBounds.end, nextBounds.start).replace(/<\/?[\w.-]+:(?:footnote|endnote)\b[^>]*>/g, "");
168283
+ if (!isCleanGapXml(stripped))
168284
+ return false;
168285
+ return !hasInterveningVisibleReviewable(previous, next, storyEntries);
168286
+ }
168287
+ function hasInterveningVisibleReviewable(previous, next, storyEntries) {
168288
+ const spanStart = Math.min(previous.documentOrderStart, next.documentOrderStart);
168289
+ const spanEnd = Math.max(previous.documentOrderEnd, next.documentOrderEnd);
168290
+ const ownedIds = new Set([
168291
+ previous.id,
168292
+ next.id,
168293
+ ...previous.reviewGroup?.ownedChildIds ?? [],
168294
+ ...next.reviewGroup?.ownedChildIds ?? []
168295
+ ]);
168296
+ for (const entry of storyEntries) {
168297
+ if (ownedIds.has(entry.id))
168298
+ continue;
168299
+ if (entry.reviewGroup?.role === "child")
168300
+ continue;
168301
+ if (entry.subsumption?.role === "child")
168302
+ continue;
168303
+ if (entry.documentOrderStart > spanStart && entry.documentOrderStart < spanEnd) {
168304
+ return true;
168305
+ }
168306
+ }
168307
+ return false;
168308
+ }
168309
+ function mergePastedParagraphParents(storyId, partXml, cluster, input) {
168310
+ const head = cluster[0];
168311
+ const memberEntries = [];
168312
+ const seenMemberIds = new Set;
168313
+ const addMember = (entry) => {
168314
+ if (!entry || seenMemberIds.has(entry.id))
168315
+ return;
168316
+ seenMemberIds.add(entry.id);
168317
+ memberEntries.push(entry);
168318
+ };
168319
+ for (const parent2 of cluster) {
168320
+ for (const childId of parent2.reviewGroup?.ownedChildIds ?? []) {
168321
+ addMember(input.byId.get(childId));
168322
+ }
168323
+ }
168324
+ if (memberEntries.length === 0 || memberEntries.some((entry) => entry.reviewGroup?.role === "child" && cluster.every((parent2) => parent2.id !== entry.reviewGroup?.parentId))) {
168325
+ return;
168326
+ }
168327
+ const textFragments = uniqueFragments(cluster.flatMap((parent2) => parent2.fragments));
168328
+ const structuralFragments = uniqueStructuralFragments(cluster.flatMap((parent2) => parent2.structuralFragments)).sort((left, right) => left.byteStart - right.byteStart);
168329
+ const members = cluster.flatMap((parent2) => parent2.reviewGroup?.members ?? []);
168330
+ const rsids = uniqueNonEmptyStrings(cluster.flatMap((parent2) => parent2.sourceIds.rsids));
168331
+ const memberWordIds = uniqueNonEmptyStrings([
168332
+ ...cluster.flatMap((parent2) => [
168333
+ parent2.sourceIds.wordIdInsert ?? "",
168334
+ ...parent2.sourceIds.wordIdOther
168335
+ ]),
168336
+ ...members.map((member) => member.wordId ?? "")
168337
+ ]);
168338
+ const wordIdInsert = head.sourceIds.wordIdInsert ?? memberWordIds[0];
168339
+ const wordIdOther = memberWordIds.filter((id) => id !== wordIdInsert);
168340
+ const canonicalText = cluster.map((parent2) => {
168341
+ const text = parent2.after.text ?? "";
168342
+ return text.endsWith(`
168343
+ `) ? text : `${text}
168344
+ `;
168345
+ }).join("");
168346
+ const markParagraphIds = cluster.map((parent2) => {
168347
+ const start = parent2.structuralFragments.find((fragment2) => fragment2.structuralKind === "paragraph-mark-insertion")?.enclosingElementStart;
168348
+ return start != null ? readParagraphIdAt(partXml, start) : null;
168349
+ });
168350
+ const markKey = markParagraphIds.every((paraId) => paraId != null) ? markParagraphIds.map((paraId) => `p:${paraId}`).join(",") : cluster.map((parent2) => parent2.id).join(",");
168351
+ const identityKey = buildGroupIdentityKey(buildReviewIdentity({
168352
+ name: head.author.name,
168353
+ email: head.authorEmail,
168354
+ initials: head.author.initials
168355
+ }));
168356
+ const groupHash = hashGroupKey(["word-inserted-paragraph-range", storyId, identityKey, `pm:${markKey}`, `tx:${textFragments.length}`].join("|"));
168357
+ const groupId = buildLogicalId(storyId, "ins", "word-paragraph-range", groupHash);
168358
+ const noteIds = uniqueNonEmptyStrings(cluster.flatMap((entry) => ownedNoteIds(entry)));
168359
+ const parent = {
168360
+ ...head,
168361
+ id: groupId,
168362
+ type: "insertion",
168363
+ subtype: "text-insertion",
168364
+ documentOrderStart: Math.min(...cluster.map((entry) => entry.documentOrderStart)),
168365
+ documentOrderEnd: Math.max(...cluster.map((entry) => entry.documentOrderEnd)),
168366
+ noteId: noteIds[0] ?? cluster.find((entry) => entry.noteId != null)?.noteId ?? head.noteId,
168367
+ noteIds: noteIds.length > 0 ? noteIds : null,
168368
+ sourceIds: { wordIdInsert, wordIdOther, rsids },
168369
+ revisionGroupId: groupId,
168370
+ after: { text: canonicalText },
168371
+ contributingRsids: rsids,
168372
+ fragments: textFragments,
168373
+ structuralFragments,
168374
+ structuralDelta: null,
168375
+ excerpt: canonicalText,
168376
+ reviewGroup: {
168377
+ kind: "word-inserted-paragraph-range",
168378
+ role: "parent",
168379
+ parentId: null,
168380
+ ownedChildIds: memberEntries.map((entry) => entry.id),
168381
+ duplicateDisplayPolicy: "subsumed",
168382
+ members
168383
+ }
168384
+ };
168385
+ for (const entry of memberEntries) {
168386
+ const mutable = entry;
168387
+ mutable.reviewGroup = {
168388
+ kind: "word-inserted-paragraph-range",
168389
+ role: "child",
168390
+ parentId: groupId,
168391
+ ownedChildIds: [],
168392
+ duplicateDisplayPolicy: "subsumed",
168393
+ members: []
168394
+ };
168395
+ mutable.revisionGroupId = groupId;
168396
+ }
168397
+ const storyArr = input.byStory.get(storyId);
168398
+ if (!storyArr)
168399
+ return;
168400
+ for (const dissolved of cluster) {
168401
+ input.byId.delete(dissolved.id);
168402
+ const index = storyArr.indexOf(dissolved);
168403
+ if (index >= 0)
168404
+ storyArr.splice(index, 1);
168405
+ }
168406
+ input.byId.set(groupId, parent);
168407
+ storyArr.push(parent);
168408
+ for (const fragment2 of textFragments)
168409
+ input.rsidToLogicalId.set(fragment2.rsid, groupId);
168410
+ for (const fragment2 of structuralFragments)
168411
+ input.structuralKeyToLogicalId.set(fragment2.entityKey, groupId);
168412
+ }
168413
+ function uniqueFragments(fragments) {
168414
+ const seen = new Set;
168415
+ const out = [];
168416
+ for (const fragment2 of fragments) {
168417
+ const key2 = `${fragment2.byteStart}:${fragment2.byteEnd}:${fragment2.rsid}`;
168418
+ if (seen.has(key2))
168419
+ continue;
168420
+ seen.add(key2);
168421
+ out.push(fragment2);
168422
+ }
168423
+ return out;
168424
+ }
168425
+ function uniqueStructuralFragments(fragments) {
168426
+ const seen = new Set;
168427
+ const out = [];
168428
+ for (const fragment2 of fragments) {
168429
+ const key2 = fragment2.entityKey;
168430
+ if (seen.has(key2))
168431
+ continue;
168432
+ seen.add(key2);
168433
+ out.push(fragment2);
168434
+ }
168435
+ return out;
168436
+ }
168193
168437
  function classifyFullParagraphInsertion(lookupContext, partXml, paragraphStart, paragraphEnd, paragraphXml) {
168194
168438
  const parent = findImmediateParentElementAtPosition(partXml, paragraphStart, lookupContext);
168195
168439
  if (parent) {
@@ -168385,14 +168629,19 @@ function buildTrackedFieldLocationKey(lookupContext, partXml, fragment2) {
168385
168629
  function extractDeletionText(deletion, partXml) {
168386
168630
  return deletion.fragments.map((fragment2) => extractVisibleTextFromXml(partXml.slice(fragment2.byteStart, fragment2.byteEnd), "del")).join("");
168387
168631
  }
168632
+ function isAbsorbableInlineObjectInsertion(entry, fragment2) {
168633
+ const kind = entry.subtype;
168634
+ return (kind === "footnote-insertion" || kind === "endnote-insertion" || kind === "image-insertion" || kind === "field-insertion") && fragment2.structuralKind === kind && entry.reviewGroup == null && entry.subsumption == null;
168635
+ }
168388
168636
  function projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entries, input) {
168389
168637
  const markCandidates = collectMarkCandidates(storyId, partXml, entries);
168390
168638
  if (markCandidates.candidatesByParagraphStart.size === 0)
168391
168639
  return;
168392
- const textCandidates = collectTextCandidates(lookupContext, partXml, entries);
168640
+ const textCandidates = collectTextCandidates(lookupContext, partXml, entries, storyId);
168393
168641
  if (textCandidates.length === 0)
168394
168642
  return;
168395
- const paragraphs = buildParagraphInfos(lookupContext, partXml, markCandidates, textCandidates);
168643
+ const objectCandidates = collectObjectCandidates(lookupContext, partXml, entries);
168644
+ const paragraphs = buildParagraphInfos(lookupContext, partXml, markCandidates, textCandidates, objectCandidates);
168396
168645
  if (paragraphs.length === 0)
168397
168646
  return;
168398
168647
  const atoms = buildAtomSequence(partXml, paragraphs);
@@ -168403,17 +168652,33 @@ function projectStoryReviewGroups(lookupContext, storyId, partUri, partXml, entr
168403
168652
  }
168404
168653
  }
168405
168654
  }
168406
- function splitDistinctFullParagraphRuns(run) {
168407
- const members = run.filter((atom) => atom.kind !== "blocker");
168408
- const fullParagraphRsidsByStart = new Map(members.flatMap((atom) => {
168655
+ function paragraphRunHasImageInsertion(paragraphRun) {
168656
+ return paragraphRun.some((atom) => atom.kind === "object" && atom.candidate.entry.subtype === "image-insertion");
168657
+ }
168658
+ function paragraphRunFullParagraphRsid(paragraphRun) {
168659
+ for (const atom of paragraphRun) {
168409
168660
  if (atom.kind !== "mark" || atom.candidate.fragment.fullParagraphInsertion !== true)
168410
- return [];
168661
+ continue;
168411
168662
  const rsid = atom.candidate.fragment.enclosingParagraphRsid;
168412
- return rsid ? [[atom.candidate.paragraphStart, rsid.toUpperCase()]] : [];
168413
- }));
168414
- if (fullParagraphRsidsByStart.size <= 1 || new Set(fullParagraphRsidsByStart.values()).size <= 1) {
168415
- return [run];
168663
+ return rsid ? rsid.toUpperCase() : null;
168416
168664
  }
168665
+ return null;
168666
+ }
168667
+ function paragraphRunIsFullParagraphInsertion(paragraphRun) {
168668
+ return paragraphRun.some((atom) => atom.kind === "mark" && atom.candidate.fragment.fullParagraphInsertion === true);
168669
+ }
168670
+ function canContinueFullParagraphRun(currentRun, nextParagraphRun, currentRsid, nextRsid, anyStampedFullParagraph) {
168671
+ if (paragraphRunHasImageInsertion(currentRun) !== paragraphRunHasImageInsertion(nextParagraphRun)) {
168672
+ return false;
168673
+ }
168674
+ if (!anyStampedFullParagraph)
168675
+ return true;
168676
+ if (currentRsid == null || nextRsid == null)
168677
+ return false;
168678
+ return currentRsid === nextRsid;
168679
+ }
168680
+ function splitDistinctFullParagraphRuns(run) {
168681
+ const members = run.filter((atom) => atom.kind !== "blocker");
168417
168682
  const paragraphRuns = [];
168418
168683
  for (const member of members) {
168419
168684
  const current = paragraphRuns.at(-1);
@@ -168423,6 +168688,11 @@ function splitDistinctFullParagraphRuns(run) {
168423
168688
  paragraphRuns.push([member]);
168424
168689
  }
168425
168690
  }
168691
+ const fullParagraphRuns = paragraphRuns.filter(paragraphRunIsFullParagraphInsertion);
168692
+ if (fullParagraphRuns.length <= 1) {
168693
+ return [run];
168694
+ }
168695
+ const anyStampedFullParagraph = fullParagraphRuns.some((paragraphRun) => paragraphRunFullParagraphRsid(paragraphRun) != null);
168426
168696
  const projectedRuns = [];
168427
168697
  let currentRun = [];
168428
168698
  let currentRsid = null;
@@ -168434,9 +168704,7 @@ function splitDistinctFullParagraphRuns(run) {
168434
168704
  currentRsid = null;
168435
168705
  };
168436
168706
  for (const paragraphRun of paragraphRuns) {
168437
- const paragraphStart = paragraphRun[0].candidate.paragraphStart;
168438
- const paragraphRsid = fullParagraphRsidsByStart.get(paragraphStart) ?? null;
168439
- if (paragraphRsid === null) {
168707
+ if (!paragraphRunIsFullParagraphInsertion(paragraphRun)) {
168440
168708
  if (currentRsid !== null && paragraphRun.every((atom) => atom.kind === "text")) {
168441
168709
  currentRun.push(...paragraphRun);
168442
168710
  continue;
@@ -168445,14 +168713,19 @@ function splitDistinctFullParagraphRuns(run) {
168445
168713
  currentRun = [...paragraphRun];
168446
168714
  continue;
168447
168715
  }
168448
- if (currentRsid === paragraphRsid) {
168716
+ const paragraphRsid = paragraphRunFullParagraphRsid(paragraphRun);
168717
+ if (currentRun.length > 0 && canContinueFullParagraphRun(currentRun, paragraphRun, currentRsid, paragraphRsid, anyStampedFullParagraph)) {
168449
168718
  currentRun.push(...paragraphRun);
168719
+ if (currentRsid == null)
168720
+ currentRsid = paragraphRsid;
168450
168721
  continue;
168451
168722
  }
168452
- if (currentRsid === null && currentRun.every((atom) => atom.kind === "mark")) {
168453
- currentRsid = paragraphRsid;
168454
- currentRun = [...currentRun, ...paragraphRun];
168455
- continue;
168723
+ if (currentRsid === null && currentRun.length > 0 && currentRun.every((atom) => atom.kind === "mark")) {
168724
+ if (canContinueFullParagraphRun(currentRun, paragraphRun, paragraphRsid, paragraphRsid, anyStampedFullParagraph)) {
168725
+ currentRsid = paragraphRsid;
168726
+ currentRun = [...currentRun, ...paragraphRun];
168727
+ continue;
168728
+ }
168456
168729
  }
168457
168730
  flushCurrentRun();
168458
168731
  currentRsid = paragraphRsid;
@@ -168473,10 +168746,10 @@ function collectMarkCandidates(storyId, partXml, entries) {
168473
168746
  continue;
168474
168747
  if (fragment2.enclosingElementStart === undefined || fragment2.enclosingElementEnd === undefined)
168475
168748
  continue;
168476
- const isCandidateShape = entry.subtype === "paragraph-mark-insertion" && fragment2.structuralKind === "paragraph-mark-insertion" && entry.structuralFragments.length === 1 && entry.noteId == null && entry.subsumption == null && entry.reviewGroup == null;
168749
+ const isCandidateShape = entry.subtype === "paragraph-mark-insertion" && fragment2.structuralKind === "paragraph-mark-insertion" && entry.structuralFragments.length === 1 && (entry.noteId == null || isNotesStoryId(storyId)) && entry.subsumption == null && entry.reviewGroup == null;
168477
168750
  if (isCandidateShape) {
168478
168751
  markFragments.push({ entry, fragment: fragment2 });
168479
- } else if (isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries)) {} else {
168752
+ } else if (isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries)) {} else if (isAbsorbableInlineObjectInsertion(entry, fragment2)) {} else {
168480
168753
  blockedParagraphStarts.add(fragment2.enclosingElementStart);
168481
168754
  }
168482
168755
  }
@@ -168517,12 +168790,12 @@ function isTerminalDeletionOfPendingFullParagraphInsertion(fragment2, entries) {
168517
168790
  const markers = entries.flatMap((entry) => entry.structuralFragments).filter((candidate2) => candidate2.enclosingElementStart === paragraphStart && candidate2.enclosingElementEnd === paragraphEnd && (candidate2.structuralKind === "paragraph-mark-insertion" || candidate2.structuralKind === "paragraph-mark-deletion")).sort((left, right) => left.byteStart - right.byteStart);
168518
168791
  return markers.length === 2 && markers[0]?.structuralKind === "paragraph-mark-insertion" && markers[0].fullParagraphInsertion === true && markers[1] === fragment2;
168519
168792
  }
168520
- function collectTextCandidates(lookupContext, partXml, entries) {
168793
+ function collectTextCandidates(lookupContext, partXml, entries, storyId) {
168521
168794
  const out = [];
168522
168795
  for (const entry of entries) {
168523
168796
  if (entry.type !== "insertion" || entry.subtype !== "text-insertion")
168524
168797
  continue;
168525
- if (entry.noteId != null)
168798
+ if (entry.noteId != null && !isNotesStoryId(storyId))
168526
168799
  continue;
168527
168800
  if (entry.sides || entry.movePair)
168528
168801
  continue;
@@ -168567,14 +168840,60 @@ function collectTextCandidates(lookupContext, partXml, entries) {
168567
168840
  }
168568
168841
  return out;
168569
168842
  }
168843
+ function collectObjectCandidates(lookupContext, partXml, entries) {
168844
+ const out = [];
168845
+ for (const entry of entries) {
168846
+ if (entry.type !== "structural")
168847
+ continue;
168848
+ if (entry.sides || entry.movePair)
168849
+ continue;
168850
+ if (entry.subsumption || entry.reviewGroup)
168851
+ continue;
168852
+ if (entry.structuralFragments.length === 0)
168853
+ continue;
168854
+ if (!entry.structuralFragments.every((fragment2) => isAbsorbableInlineObjectInsertion(entry, fragment2))) {
168855
+ continue;
168856
+ }
168857
+ let paragraphStart = null;
168858
+ let eligible = true;
168859
+ for (const fragment2 of entry.structuralFragments) {
168860
+ const paragraph = findEnclosingElementAtPosition(partXml, fragment2.byteStart, "p", undefined, lookupContext);
168861
+ if (!paragraph) {
168862
+ eligible = false;
168863
+ break;
168864
+ }
168865
+ if (paragraphStart == null)
168866
+ paragraphStart = paragraph.start;
168867
+ if (paragraph.start !== paragraphStart) {
168868
+ eligible = false;
168869
+ break;
168870
+ }
168871
+ }
168872
+ if (!eligible || paragraphStart == null)
168873
+ continue;
168874
+ const headFragment = entry.structuralFragments[0];
168875
+ out.push({
168876
+ entry,
168877
+ fragments: [...entry.structuralFragments].sort((a, b) => a.byteStart - b.byteStart),
168878
+ paragraphStart,
168879
+ identity: buildReviewIdentity({
168880
+ name: entry.author.name,
168881
+ email: entry.authorEmail ?? headFragment.authorEmail,
168882
+ initials: entry.author.initials
168883
+ }),
168884
+ initials: entry.author.initials
168885
+ });
168886
+ }
168887
+ return out;
168888
+ }
168570
168889
  function isSimpleInlineInsertionWrapper(wrapperXml) {
168571
168890
  const innerMatch = /^<[\w.-]+:ins\b[^>]*>([\s\S]*)<\/[\w.-]+:ins>\s*$/.exec(wrapperXml);
168572
168891
  if (!innerMatch)
168573
168892
  return false;
168574
168893
  const inner = innerMatch[1] ?? "";
168575
- return !/<[\w.-]+:(?:ins|del|moveFrom|moveTo|drawing|tbl|p|fldSimple|fldChar|sdt|footnoteReference|endnoteReference|commentReference)\b/.test(inner);
168894
+ return !/<[\w.-]+:(?:ins|del|moveFrom|moveTo)\b/.test(inner);
168576
168895
  }
168577
- function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168896
+ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates, objectCandidates = []) {
168578
168897
  const byStart = new Map;
168579
168898
  const ensure = (start, end) => {
168580
168899
  let info = byStart.get(start);
@@ -168585,6 +168904,7 @@ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168585
168904
  mark: null,
168586
168905
  markBlocked: marks.blockedParagraphStarts.has(start),
168587
168906
  textFragments: [],
168907
+ objectFragments: [],
168588
168908
  invalid: false
168589
168909
  };
168590
168910
  byStart.set(start, info);
@@ -168604,17 +168924,28 @@ function buildParagraphInfos(lookupContext, partXml, marks, textCandidates) {
168604
168924
  info.textFragments.push({ candidate: candidate2, fragment: fragment2 });
168605
168925
  }
168606
168926
  }
168927
+ for (const candidate2 of objectCandidates) {
168928
+ const paragraph = findEnclosingElementAtPosition(partXml, candidate2.fragments[0].byteStart, "p", undefined, lookupContext);
168929
+ if (!paragraph)
168930
+ continue;
168931
+ const info = ensure(paragraph.start, paragraph.end);
168932
+ for (const fragment2 of candidate2.fragments) {
168933
+ info.objectFragments.push({ candidate: candidate2, fragment: fragment2 });
168934
+ }
168935
+ }
168607
168936
  const paragraphs = Array.from(byStart.values()).sort((a, b) => a.start - b.start);
168608
168937
  for (const paragraph of paragraphs) {
168609
168938
  paragraph.textFragments.sort((a, b) => a.fragment.byteStart - b.fragment.byteStart);
168939
+ paragraph.objectFragments.sort((a, b) => a.fragment.byteStart - b.fragment.byteStart);
168610
168940
  paragraph.invalid = !isEligibleGroupParagraph(lookupContext, partXml, paragraph);
168611
168941
  }
168612
168942
  return paragraphs;
168613
168943
  }
168614
168944
  function isEligibleGroupParagraph(lookupContext, partXml, paragraph) {
168615
168945
  const parent = findImmediateParentElementAtPosition(partXml, paragraph.start, lookupContext);
168616
- if (!parent || parent.localName !== "body")
168946
+ if (!parent || parent.localName !== "body" && parent.localName !== "footnote" && parent.localName !== "endnote") {
168617
168947
  return false;
168948
+ }
168618
168949
  const paragraphXml = partXml.slice(paragraph.start, paragraph.end);
168619
168950
  const pPrXml = readLeadingParagraphPropertyBlock(paragraphXml)?.xml ?? "";
168620
168951
  if (pPrXml && /<[\w.-]+:sectPr\b/.test(pPrXml))
@@ -168659,7 +168990,7 @@ function buildAtomSequence(partXml, paragraphs) {
168659
168990
  for (const paragraph of paragraphs) {
168660
168991
  if (previous) {
168661
168992
  const between = partXml.slice(previous.end, paragraph.start);
168662
- if (!isCleanGapXml(between))
168993
+ if (!isCleanGroupingGap(between, previous, paragraph))
168663
168994
  atoms.push({ kind: "blocker" });
168664
168995
  }
168665
168996
  previous = paragraph;
@@ -168729,17 +169060,21 @@ function appendParagraphAtoms(partXml, paragraph, atoms, hyperlinkTags) {
168729
169060
  return isCleanGapXml(gap + partXml.slice(cursor2, end));
168730
169061
  };
168731
169062
  let cursor = contentStart;
168732
- for (const { candidate: candidate2, fragment: fragment2 } of paragraph.textFragments) {
168733
- if (fragment2.byteStart < cursor || fragment2.byteEnd > contentEnd) {
169063
+ const contentAtoms = [
169064
+ ...paragraph.textFragments.map(({ candidate: candidate2, fragment: fragment2 }) => ({ kind: "text", candidate: candidate2, fragment: fragment2 })),
169065
+ ...paragraph.objectFragments.map(({ candidate: candidate2, fragment: fragment2 }) => ({ kind: "object", candidate: candidate2, fragment: fragment2 }))
169066
+ ].sort((left, right) => left.fragment.byteStart - right.fragment.byteStart);
169067
+ for (const atom of contentAtoms) {
169068
+ if (atom.fragment.byteStart < cursor || atom.fragment.byteEnd > contentEnd) {
168734
169069
  atoms.push({ kind: "blocker" });
168735
- cursor = Math.max(cursor, fragment2.byteEnd);
169070
+ cursor = Math.max(cursor, atom.fragment.byteEnd);
168736
169071
  continue;
168737
169072
  }
168738
- if (!cleanGap(cursor, fragment2.byteStart)) {
169073
+ if (!cleanGap(cursor, atom.fragment.byteStart)) {
168739
169074
  atoms.push({ kind: "blocker" });
168740
169075
  }
168741
- atoms.push({ kind: "text", candidate: candidate2, fragment: fragment2 });
168742
- cursor = fragment2.byteEnd;
169076
+ atoms.push(atom);
169077
+ cursor = atom.fragment.byteEnd;
168743
169078
  }
168744
169079
  if (!cleanGap(cursor, contentEnd)) {
168745
169080
  atoms.push({ kind: "blocker" });
@@ -168757,6 +169092,16 @@ function isCleanGapXml(xml) {
168757
169092
  return false;
168758
169093
  return containsOnlyAllowedPairingMarkers(xml);
168759
169094
  }
169095
+ function isCleanGroupingGap(between, previous, next) {
169096
+ const stripped = between.replace(/<\/?[\w.-]+:(?:footnote|endnote)\b[^>]*>/g, "");
169097
+ if (!isCleanGapXml(stripped))
169098
+ return false;
169099
+ if (stripped === between)
169100
+ return true;
169101
+ const left = previous.mark?.fragment.enclosingParagraphRsid;
169102
+ const right = next.mark?.fragment.enclosingParagraphRsid;
169103
+ return Boolean(left && right && left.toUpperCase() === right.toUpperCase());
169104
+ }
168760
169105
  function collectSameIdentityRuns(atoms) {
168761
169106
  const runs = [];
168762
169107
  let current = [];
@@ -168791,17 +169136,17 @@ function isSameGroupIdentity(a, b) {
168791
169136
  function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, input) {
168792
169137
  const textAtoms = run.filter((atom) => atom.kind === "text");
168793
169138
  const markAtoms = run.filter((atom) => atom.kind === "mark");
169139
+ const objectAtoms = run.filter((atom) => atom.kind === "object");
168794
169140
  if (textAtoms.length === 0 || markAtoms.length === 0)
168795
169141
  return;
168796
169142
  const carrierParagraphStarts = new Set([
168797
169143
  ...textAtoms.map((atom) => atom.candidate.paragraphStart),
169144
+ ...objectAtoms.map((atom) => atom.candidate.paragraphStart),
168798
169145
  ...markAtoms.map((atom) => atom.candidate.paragraphStart)
168799
169146
  ]);
168800
- const fullParagraphStarts = new Set(markAtoms.filter((atom) => atom.candidate.fragment.fullParagraphInsertion === true && atom.candidate.fragment.enclosingParagraphRsid !== undefined).map((atom) => atom.candidate.paragraphStart));
168801
- let listInsertion;
168802
- if ([...carrierParagraphStarts].every((paragraphStart) => fullParagraphStarts.has(paragraphStart))) {
168803
- if (markAtoms.length !== 1)
168804
- return;
169147
+ const fullParagraphStarts = new Set(markAtoms.filter((atom) => atom.candidate.fragment.fullParagraphInsertion === true).map((atom) => atom.candidate.paragraphStart));
169148
+ let fullParagraphOwner;
169149
+ if (markAtoms.length === 1 && [...carrierParagraphStarts].every((paragraphStart) => fullParagraphStarts.has(paragraphStart))) {
168805
169150
  const marker = markAtoms[0].candidate.fragment;
168806
169151
  const paragraphStart = marker.enclosingElementStart;
168807
169152
  const paragraphEnd = marker.enclosingElementEnd;
@@ -168811,12 +169156,12 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168811
169156
  const subtype = classifyFullParagraphInsertion(lookupContext, partXml, paragraphStart, paragraphEnd, paragraphXml).subtype;
168812
169157
  if (subtype === "table-split")
168813
169158
  return;
168814
- if (subtype === "list-item-insertion") {
169159
+ if (subtype === "list-item-insertion" || objectAtoms.length > 0) {
168815
169160
  const candidate2 = markAtoms[0].candidate;
168816
- const ownsStructuralContent = input.byStory.get(storyId)?.some((entry) => entry.type === "structural" && entry !== candidate2.entry && entry.structuralFragments.some((fragment2) => fragment2.byteStart >= paragraphStart && fragment2.byteEnd <= paragraphEnd));
169161
+ const ownsStructuralContent = input.byStory.get(storyId)?.some((entry) => entry.type === "structural" && entry !== candidate2.entry && !objectAtoms.some((atom) => atom.candidate.entry === entry) && entry.structuralFragments.some((fragment2) => fragment2.byteStart >= paragraphStart && fragment2.byteEnd <= paragraphEnd));
168817
169162
  if (ownsStructuralContent)
168818
169163
  return;
168819
- listInsertion = candidate2;
169164
+ fullParagraphOwner = candidate2;
168820
169165
  }
168821
169166
  }
168822
169167
  const memberEntries = [];
@@ -168839,15 +169184,15 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168839
169184
  const markKey = markParagraphIds.every((paraId) => paraId != null) ? markParagraphIds.map((paraId) => `p:${paraId}`).join(",") : markOrdinals.join(",");
168840
169185
  const groupHash = hashGroupKey(["word-inserted-paragraph-range", storyId, identityKey, `pm:${markKey}`, `tx:${textAtoms.length}`].join("|"));
168841
169186
  const groupId = buildLogicalId(storyId, "ins", "word-paragraph-range", groupHash);
168842
- if (listInsertion) {
169187
+ if (fullParagraphOwner) {
168843
169188
  materializeFullParagraphInsertion(lookupContext, {
168844
169189
  storyId,
168845
169190
  partUri,
168846
169191
  partXml,
168847
- structuralEntry: listInsertion.entry,
168848
- marker: listInsertion.fragment,
169192
+ structuralEntry: fullParagraphOwner.entry,
169193
+ marker: fullParagraphOwner.fragment,
168849
169194
  textEntries: uniqueEntries(textAtoms.map((atom) => atom.candidate.entry)),
168850
- objectEntries: [],
169195
+ objectEntries: uniqueEntries(objectAtoms.map((atom) => atom.candidate.entry)),
168851
169196
  logicalId: groupId,
168852
169197
  input
168853
169198
  });
@@ -168855,8 +169200,11 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168855
169200
  }
168856
169201
  const canonicalText = buildCanonicalGroupText(partXml, run);
168857
169202
  const fragments = textAtoms.map((atom) => atom.fragment);
168858
- const structuralFragments = markAtoms.map((atom) => atom.candidate.fragment);
168859
- const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? { start: atom.fragment.byteStart, end: atom.fragment.byteEnd } : { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd });
169203
+ const structuralFragments = [
169204
+ ...objectAtoms.map((atom) => atom.fragment),
169205
+ ...markAtoms.map((atom) => atom.candidate.fragment)
169206
+ ].sort((left, right) => left.byteStart - right.byteStart);
169207
+ const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "mark" ? { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd } : { start: atom.fragment.byteStart, end: atom.fragment.byteEnd });
168860
169208
  const documentOrderStart = Math.min(...semanticPositions.map((position) => position.start));
168861
169209
  const documentOrderEnd = Math.max(...semanticPositions.map((position) => position.end));
168862
169210
  const textEntries = uniqueEntries(textAtoms.map((atom) => atom.candidate.entry));
@@ -168872,26 +169220,45 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168872
169220
  ...structuralFragments.map((fragment2) => fragment2.date)
168873
169221
  ]);
168874
169222
  const headEntry = memberEntries[0];
168875
- const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? {
168876
- logicalId: atom.candidate.entry.id,
168877
- carrier: "text",
168878
- partUri,
168879
- storyId,
168880
- rsid: atom.fragment.rsid,
168881
- ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
168882
- subtype: "text-insertion",
168883
- byteStart: atom.fragment.byteStart,
168884
- byteEnd: atom.fragment.byteEnd
168885
- } : {
168886
- logicalId: atom.candidate.entry.id,
168887
- carrier: "structural",
168888
- partUri,
168889
- storyId,
168890
- entityKey: atom.candidate.fragment.entityKey,
168891
- ...atom.candidate.fragment.wId ? { wordId: atom.candidate.fragment.wId } : {},
168892
- subtype: "paragraph-mark-insertion",
168893
- byteStart: atom.candidate.fragment.byteStart,
168894
- byteEnd: atom.candidate.fragment.byteEnd
169223
+ const ownedNotes = uniqueNonEmptyStrings(memberEntries.flatMap((entry) => ownedNoteIds(entry)));
169224
+ const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => {
169225
+ if (atom.kind === "text") {
169226
+ return {
169227
+ logicalId: atom.candidate.entry.id,
169228
+ carrier: "text",
169229
+ partUri,
169230
+ storyId,
169231
+ rsid: atom.fragment.rsid,
169232
+ ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
169233
+ subtype: "text-insertion",
169234
+ byteStart: atom.fragment.byteStart,
169235
+ byteEnd: atom.fragment.byteEnd
169236
+ };
169237
+ }
169238
+ if (atom.kind === "object") {
169239
+ return {
169240
+ logicalId: atom.candidate.entry.id,
169241
+ carrier: "structural",
169242
+ partUri,
169243
+ storyId,
169244
+ entityKey: atom.fragment.entityKey,
169245
+ ...atom.fragment.wId ? { wordId: atom.fragment.wId } : {},
169246
+ subtype: atom.fragment.structuralKind,
169247
+ byteStart: atom.fragment.byteStart,
169248
+ byteEnd: atom.fragment.byteEnd
169249
+ };
169250
+ }
169251
+ return {
169252
+ logicalId: atom.candidate.entry.id,
169253
+ carrier: "structural",
169254
+ partUri,
169255
+ storyId,
169256
+ entityKey: atom.candidate.fragment.entityKey,
169257
+ ...atom.candidate.fragment.wId ? { wordId: atom.candidate.fragment.wId } : {},
169258
+ subtype: "paragraph-mark-insertion",
169259
+ byteStart: atom.candidate.fragment.byteStart,
169260
+ byteEnd: atom.candidate.fragment.byteEnd
169261
+ };
168895
169262
  });
168896
169263
  const parent = {
168897
169264
  id: groupId,
@@ -168900,7 +169267,8 @@ function materializeReviewGroup(lookupContext, storyId, partUri, partXml, run, i
168900
169267
  state: "open",
168901
169268
  storyId,
168902
169269
  partUri,
168903
- noteId: null,
169270
+ noteId: ownedNotes[0] ?? memberEntries.find((entry) => entry.noteId != null)?.noteId ?? null,
169271
+ noteIds: ownedNotes.length > 0 ? ownedNotes : null,
168904
169272
  documentOrderStart,
168905
169273
  documentOrderEnd,
168906
169274
  author: { name: headEntry.author.name, initials: headEntry.author.initials },
@@ -169014,9 +169382,17 @@ function uniqueNonEmptyStrings(values) {
169014
169382
  }
169015
169383
  return out;
169016
169384
  }
169385
+ function ownedNoteIds(entry) {
169386
+ if (entry.noteIds && entry.noteIds.length > 0)
169387
+ return [...entry.noteIds];
169388
+ return entry.noteId != null ? [entry.noteId] : [];
169389
+ }
169017
169390
  function isBodyStoryId(storyId) {
169018
169391
  return storyId.startsWith("main:") || storyId.endsWith(":/word/document.xml");
169019
169392
  }
169393
+ function isNotesStoryId(storyId) {
169394
+ return storyId.startsWith("footnotes:") || storyId.startsWith("endnotes:");
169395
+ }
169020
169396
  function buildBodyParagraphOrdinalIndexWithLookupContext(lookupContext, partXml) {
169021
169397
  const bodyOpen = /<([\w.-]+):body\b[^>]*>/.exec(partXml);
169022
169398
  if (!bodyOpen) {
@@ -169997,13 +170373,14 @@ function materializeDeletionGroup(storyId, partUri, partXml, run, carrierOrdinal
169997
170373
  ].filter((ordinal) => ordinal !== undefined).sort((a, b) => a - b);
169998
170374
  const memberParagraphTokens = run.filter((atom) => atom.kind !== "blocker").map((atom) => {
169999
170375
  const paraId = readParagraphIdAt(partXml, atom.candidate.paragraphStart);
170000
- return paraId != null ? `${atom.kind === "text" ? "t" : "m"}@p:${paraId}` : null;
170376
+ const kindToken = atom.kind === "mark" ? "m" : atom.kind === "object" ? "o" : "t";
170377
+ return paraId != null ? `${kindToken}@p:${paraId}` : null;
170001
170378
  });
170002
170379
  const memberKey = memberParagraphTokens.every((token) => token != null) ? memberParagraphTokens.join(",") : carrierOrdinals.join(",");
170003
170380
  const groupHash = hashGroupKey(["word-deleted-paragraph-range", storyId, partUri, `co:${memberKey}`].join("|"));
170004
170381
  const groupId = buildLogicalId(storyId, "del", "word-deleted-paragraph-range", groupHash);
170005
170382
  const canonicalText = buildCanonicalDeletionGroupText(partXml, run);
170006
- const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? { start: atom.fragment.byteStart, end: atom.fragment.byteEnd } : { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd });
170383
+ const semanticPositions = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "mark" ? { start: atom.candidate.paragraphEnd, end: atom.candidate.paragraphEnd } : { start: atom.fragment.byteStart, end: atom.fragment.byteEnd });
170007
170384
  const documentOrderStart = Math.min(...semanticPositions.map((position) => position.start));
170008
170385
  const documentOrderEnd = Math.max(...semanticPositions.map((position) => position.end));
170009
170386
  const textEntries = uniqueEntries(textAtoms.map((atom) => atom.candidate.entry));
@@ -170021,7 +170398,7 @@ function materializeDeletionGroup(storyId, partUri, partXml, run, carrierOrdinal
170021
170398
  const headEntry = memberEntries[0];
170022
170399
  const hasDeletedText = textAtoms.length > 0;
170023
170400
  const structuralDelta = hasDeletedText ? null : buildDeletedParagraphMarkRangeStructuralDelta(partXml, structuralFragments);
170024
- const members = run.filter((atom) => atom.kind !== "blocker").map((atom) => atom.kind === "text" ? {
170401
+ const members = run.filter((atom) => atom.kind === "text" || atom.kind === "mark").map((atom) => atom.kind === "text" ? {
170025
170402
  logicalId: atom.candidate.entry.id,
170026
170403
  carrier: "text",
170027
170404
  partUri,
@@ -174115,6 +174492,7 @@ function buildStructuralLogical(lookupContext, entityKey, group, noteId, partXml
174115
174492
  documentOrderStart,
174116
174493
  documentOrderEnd,
174117
174494
  author: { name: head.author.name, initials: head.author.initials },
174495
+ authorEmail: head.authorEmail ?? null,
174118
174496
  date: head.date,
174119
174497
  sourceIds,
174120
174498
  revisionGroupId: id,
@@ -180832,7 +181210,7 @@ function reserveRevisionWIdBlock(state, count) {
180832
181210
  }
180833
181211
  return base2;
180834
181212
  }
180835
- function planRevisionWIdBlock(state, count) {
181213
+ function planRevisionWIdBlock(state, count, options) {
180836
181214
  let max2 = state.idReservations.revisionWIds.max;
180837
181215
  if (!state.idReservations.revisionWIds.seeded) {
180838
181216
  for (const [, bytes] of state.packageBytes.entries()) {
@@ -180841,6 +181219,9 @@ function planRevisionWIdBlock(state, count) {
180841
181219
  max2 = value;
180842
181220
  }
180843
181221
  }
181222
+ if (options?.afterMax != null && options.afterMax > max2) {
181223
+ max2 = options.afterMax;
181224
+ }
180844
181225
  const size = Math.max(1, Math.floor(count));
180845
181226
  return Array.from({ length: size }, (_, index) => max2 + index + 1);
180846
181227
  }
@@ -195828,7 +196209,7 @@ function rewriteLocalizedDirect(bytes, window2, op, inheritedNamespaceBindings =
195828
196209
  return rewritten;
195829
196210
  return { ok: true, bytes: TE16.encode(rewritten.xml), runPropertiesXml: "" };
195830
196211
  }
195831
- const plan = planDirectInlineEdit(paragraphBytes, op.on.startOffset, op.on.endOffset, op.newText, op.on.collapsedObjectBoundary, op.on.collapsedInlineAffinity, inheritedNamespaceBindings);
196212
+ const plan = planDirectInlineEdit(paragraphBytes, op.on.startOffset, op.on.endOffset, op.newText, op.on.collapsedObjectBoundary, op.on.collapsedInlineAffinity, inheritedNamespaceBindings, op.on.collapsedOutsideContentControl);
195832
196213
  if (plan.ok) {
195833
196214
  return {
195834
196215
  ok: true,
@@ -216733,7 +217114,8 @@ function compilePastedNotesMetadata(input) {
216733
217114
  paragraph,
216734
217115
  type,
216735
217116
  includeMarker: index === 0,
216736
- paraSalt: paraSalt += 1
217117
+ paraSalt: paraSalt += 1,
217118
+ trackedStamp: input.trackedStamp ?? null
216737
217119
  })).join("");
216738
217120
  fragments.push(`<w:${type} w:id="${escapeXmlAttribute(destinationId)}">${bodyXml}</w:${type}>`);
216739
217121
  }
@@ -216837,7 +217219,18 @@ function renderNoteParagraphXml(input) {
216837
217219
  const markerStyle = input.type === "footnote" ? "FootnoteReference" : "EndnoteReference";
216838
217220
  const markerLocal = input.type === "footnote" ? "footnoteRef" : "endnoteRef";
216839
217221
  const marker = input.includeMarker ? `<w:r><w:rPr><w:rStyle w:val="${markerStyle}"/></w:rPr><w:${markerLocal}/></w:r>` : "";
216840
- return `<w:p w14:paraId="${escapeXmlAttribute(paraId)}">` + `<w:pPr><w:pStyle w:val="${escapeXmlAttribute(style)}"/></w:pPr>` + `${marker}${renderPasteRuns(noteBodyRuns(input.paragraph))}</w:p>`;
217222
+ const runs = noteBodyRuns(input.paragraph);
217223
+ const inner = `${marker}${renderPasteRuns(runs)}`;
217224
+ const pPrXml = `<w:pPr><w:pStyle w:val="${escapeXmlAttribute(style)}"/></w:pPr>`;
217225
+ const stamp = input.trackedStamp;
217226
+ const pPr = stamp ? renderTrackedParagraphMarkProperties(pPrXml, stamp) : pPrXml;
217227
+ const body = stamp && inner ? wrapNoteTrackedInsertion(inner, stamp) : inner;
217228
+ const rsid = stamp ? ` w:rsidR="${escapeXmlAttribute(stamp.rsid)}"` : "";
217229
+ return `<w:p w14:paraId="${escapeXmlAttribute(paraId)}"${rsid}>` + `${pPr}${body}</w:p>`;
217230
+ }
217231
+ function wrapNoteTrackedInsertion(inner, stamp) {
217232
+ const id = stamp.allocateWId();
217233
+ return `${renderTrackedChangeOpen("ins", id, stamp, true)}${inner}</w:ins>`;
216841
217234
  }
216842
217235
  function noteBodyRuns(paragraph) {
216843
217236
  if (!paragraph.inlines)
@@ -232266,12 +232659,19 @@ function preparePasteFragmentMutation(input) {
232266
232659
  if (!review.ok)
232267
232660
  return rejected18(txId, review.reason);
232268
232661
  const mergedSidecarPerPart = { ...listExtras.perPart, ...imageExtras.value.perPart, ...review.perPart };
232662
+ const pastedNotes = op.fragment.notes ?? [];
232663
+ const noteParagraphs = pastedNotes.flatMap((note) => note.paragraphs);
232664
+ const notesStamp = changeMode === "tracked" && pastedNotes.length > 0 ? pickTrackedStamp(input, trackedFragmentCarrierBudget(noteParagraphs) + noteParagraphs.length) : null;
232665
+ if (changeMode === "tracked" && pastedNotes.length > 0 && !notesStamp) {
232666
+ return rejected18(txId, "no-author-configured");
232667
+ }
232269
232668
  const notes = compilePastedNotesMetadata({
232270
232669
  state,
232271
232670
  blocks: resolvedBlocks,
232272
- notes: op.fragment.notes ?? [],
232671
+ notes: pastedNotes,
232273
232672
  documentRelsBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, DOCUMENT_RELS_PART_URI9),
232274
- contentTypesBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, CONTENT_TYPES_PART_URI11)
232673
+ contentTypesBytes: plannedPackagePartBytes(state, mergedSidecarPerPart, CONTENT_TYPES_PART_URI11),
232674
+ trackedStamp: notesStamp
232275
232675
  });
232276
232676
  if (!notes.ok)
232277
232677
  return rejected18(txId, notes.reason);
@@ -232287,7 +232687,8 @@ function preparePasteFragmentMutation(input) {
232287
232687
  patchMaps: { ...listExtras.patchMaps, ...imageExtras.value.patchMaps, ...review.patchMaps, ...notes.patchMaps },
232288
232688
  createdRefs: review.createdRefs,
232289
232689
  commentIdMap: review.commentIdMap,
232290
- noteIdMap: notes.noteIdMap
232690
+ noteIdMap: notes.noteIdMap,
232691
+ revisionWIds: usedTrackedWIds(notesStamp)
232291
232692
  };
232292
232693
  if (isStoryTarget(pasteTarget)) {
232293
232694
  if (preflight.plan.target.kind !== "story")
@@ -232495,7 +232896,9 @@ function runTrackedWholeStoryReplaceCommit(input, target, rendered, blocks, extr
232495
232896
  const preview = prepare();
232496
232897
  if (!preview)
232497
232898
  return rejected18(txId, "paste-tracked-structural-unsupported");
232498
- const revisionWIds = planRevisionWIdBlock(state, preview.wordIds.length);
232899
+ const revisionWIds = planRevisionWIdBlock(state, preview.wordIds.length, {
232900
+ afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds)
232901
+ });
232499
232902
  const replacement = prepare(revisionWIds);
232500
232903
  if (!replacement)
232501
232904
  return rejected18(txId, "paste-tracked-structural-unsupported");
@@ -232525,7 +232928,7 @@ function runTrackedWholeStoryReplaceCommit(input, target, rendered, blocks, extr
232525
232928
  extraPatchMaps: extras.patchMaps,
232526
232929
  packagePartUris: inlineExtras.partUris,
232527
232930
  storyWideProjection: true,
232528
- revisionWIds,
232931
+ revisionWIds: [...revisionWIds, ...extras.revisionWIds ?? []],
232529
232932
  indexRefreshScope: { perPart: { [target.partUri]: {
232530
232933
  beforeWindow: { startByte: target.byteStart, endByte: target.byteEnd },
232531
232934
  kind: "wide-rewrite",
@@ -232611,7 +233014,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232611
233014
  return rejected18(txId, "paste-target-unsupported");
232612
233015
  const changeMode = op.changeMode ?? "direct";
232613
233016
  const anchorXml = TD64.decode(bytes.subarray(anchor.byteStart, anchor.byteEnd));
232614
- const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + (target.placement === "replace" ? trackedBlockDeletionCarrierBudget(anchorXml) : 2)) : null;
233017
+ const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + (target.placement === "replace" ? trackedBlockDeletionCarrierBudget(anchorXml) : 2), { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) }) : null;
232615
233018
  if (changeMode === "tracked" && !trackedStamp)
232616
233019
  return rejected18(txId, "no-author-configured");
232617
233020
  const trackedDeletedAnchor = trackedStamp && target.placement === "replace" ? renderTrackedBlockDeletion({
@@ -232763,7 +233166,7 @@ function runParagraphBlockPasteCommit(input, target, blocks, extras, inlineExtra
232763
233166
  extraPatchMaps: { ...extras.patchMaps, ...commentCascade.patchMaps, ...resourceCleanup.patchMaps },
232764
233167
  packagePartUris: [...inlineExtras.partUris, ...resourceCleanup.packagePartUris],
232765
233168
  storyWideProjection: target.placement === "replace" || blocks.some((block) => block.kind === "table"),
232766
- revisionWIds: usedTrackedWIds(trackedStamp),
233169
+ revisionWIds: usedTrackedWIds(trackedStamp, extras.revisionWIds),
232767
233170
  ...target.placement === "replace" ? {
232768
233171
  indexRefreshScope: {
232769
233172
  perPart: {
@@ -232788,7 +233191,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232788
233191
  return rejected18(txId, "paste-target-unsupported");
232789
233192
  }
232790
233193
  const replacedParagraphs = index.entries.filter((entry) => entry.byteStart >= target.byteStart && entry.byteEnd <= target.byteEnd);
232791
- const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + table.rows.length + 2) : null;
233194
+ const trackedStamp = changeMode === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + table.rows.length + 2, { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) }) : null;
232792
233195
  if (changeMode === "tracked" && !trackedStamp)
232793
233196
  return rejected18(txId, "no-author-configured");
232794
233197
  const replacedXml = TD64.decode(bytes.subarray(target.byteStart, target.byteEnd));
@@ -232893,7 +233296,7 @@ function runWholeTableReplaceCommit(input, target, blocks, extras, inlineExtras)
232893
233296
  extraPatchMaps: { ...extras.patchMaps, ...commentCascade.patchMaps },
232894
233297
  packagePartUris: inlineExtras.partUris,
232895
233298
  storyWideProjection: true,
232896
- revisionWIds: usedTrackedWIds(trackedStamp),
233299
+ revisionWIds: usedTrackedWIds(trackedStamp, extras.revisionWIds),
232897
233300
  indexRefreshScope: {
232898
233301
  perPart: {
232899
233302
  [target.partUri]: {
@@ -232928,7 +233331,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
232928
233331
  if (!rangeIntegrity.ok)
232929
233332
  return rejected18(txId, "invalid-range");
232930
233333
  const coveredEntries = index.entries.filter((entry) => entry.ordinal >= start.ordinal && entry.ordinal <= end.ordinal);
232931
- const stamp = pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + coveredEntries.length * 3 + 4);
233334
+ const stamp = pickTrackedStamp(input, trackedFragmentCarrierBudget(blocks) + coveredEntries.length * 3 + 4, { afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds) });
232932
233335
  if (!stamp)
232933
233336
  return rejected18(txId, "no-author-configured");
232934
233337
  const patches = [];
@@ -233030,7 +233433,7 @@ function runCrossParagraphTrackedPasteCommit(input, target, blocks, extras, inli
233030
233433
  extraPatchMaps: extras.patchMaps,
233031
233434
  packagePartUris: inlineExtras.partUris,
233032
233435
  storyWideProjection: true,
233033
- revisionWIds: usedTrackedWIds(stamp),
233436
+ revisionWIds: usedTrackedWIds(stamp, extras.revisionWIds),
233034
233437
  indexRefreshScope: {
233035
233438
  perPart: {
233036
233439
  [target.partUri]: {
@@ -233070,7 +233473,9 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
233070
233473
  return rejected18(txId, "invalid-range");
233071
233474
  const endpointXml = TD64.decode(bytes.subarray(start.byteStart, start.byteEnd)) + TD64.decode(bytes.subarray(end.byteStart, end.byteEnd));
233072
233475
  const wrapperCount = [...endpointXml.matchAll(/<(?:[\w.-]+:)?(?:ins|del|moveFrom|moveTo)\b/g)].length;
233073
- const plannedWIds = planRevisionWIdBlock(state, Math.max(1, wrapperCount * 2 + 4));
233476
+ const plannedWIds = planRevisionWIdBlock(state, Math.max(1, wrapperCount * 2 + 4), {
233477
+ afterMax: afterMaxFromPlannedRevisionWIds(extras.revisionWIds)
233478
+ });
233074
233479
  let usedWIds = 0;
233075
233480
  let splicePlan;
233076
233481
  let mintSalt = 0;
@@ -233147,7 +233552,7 @@ function runCrossParagraphPasteCommit(input, target, blocks, extras, inlineExtra
233147
233552
  packagePartUris: inlineExtras.partUris,
233148
233553
  storyWideProjection: true,
233149
233554
  indexRefreshScope: contribution.indexRefreshScope,
233150
- revisionWIds: plannedWIds.slice(0, usedWIds)
233555
+ revisionWIds: [...plannedWIds.slice(0, usedWIds), ...extras.revisionWIds ?? []]
233151
233556
  });
233152
233557
  }
233153
233558
  function collectRemovedBookmarkRefs(ownerXml, removedXml, storyId) {
@@ -233223,7 +233628,7 @@ function tryRunTableRowPasteCommit(input, pasteTarget, pastedBlocks, additionalP
233223
233628
  scopeHintOpenTag: matchParagraphOpenTag(targetParagraphXml) ?? ""
233224
233629
  });
233225
233630
  const createdParagraphIds = [];
233226
- const trackedStamp = (input.op.changeMode ?? "direct") === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(pastedBlocks) + 2) : null;
233631
+ const trackedStamp = (input.op.changeMode ?? "direct") === "tracked" ? pickTrackedStamp(input, trackedFragmentCarrierBudget(pastedBlocks) + 2, { afterMax: afterMaxFromPlannedRevisionWIds(additionalPatches.revisionWIds) }) : null;
233227
233632
  if ((input.op.changeMode ?? "direct") === "tracked" && !trackedStamp) {
233228
233633
  return rejected18(transactionId, "no-author-configured");
233229
233634
  }
@@ -233310,7 +233715,7 @@ function tryRunTableRowPasteCommit(input, pasteTarget, pastedBlocks, additionalP
233310
233715
  provisionalToFinal: Object.fromEntries(createdParagraphIds.map((paragraphId, index) => [`${transactionId}:paste:${index}`, paragraphId])),
233311
233716
  extraPatchMaps: additionalPatches.patchMaps,
233312
233717
  storyWideProjection: true,
233313
- revisionWIds: usedTrackedWIds(trackedStamp)
233718
+ revisionWIds: usedTrackedWIds(trackedStamp, additionalPatches.revisionWIds)
233314
233719
  });
233315
233720
  }
233316
233721
  function findContainingTableCell(tableCatalog, targetParagraph) {
@@ -233710,7 +234115,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233710
234115
  const changeMode = input.op.changeMode ?? "direct";
233711
234116
  const wrapAllTracked = changeMode === "tracked";
233712
234117
  const fragmentTracked = fragmentHasTrackedInsertion(resolvedBlocks);
233713
- const trackedStamp = wrapAllTracked || fragmentTracked ? pickTrackedStamp(input, trackedFragmentCarrierBudget(resolvedBlocks) + 4) : null;
234118
+ const trackedStamp = wrapAllTracked || fragmentTracked ? pickTrackedStamp(input, trackedFragmentCarrierBudget(resolvedBlocks) + 4, { afterMax: afterMaxFromPlannedRevisionWIds(listExtras.revisionWIds) }) : null;
233714
234119
  if ((wrapAllTracked || fragmentTracked) && !trackedStamp)
233715
234120
  return rejected18(txId, "no-author-configured");
233716
234121
  let mintSalt = 0;
@@ -233840,7 +234245,7 @@ function runPasteSpliceCommit(input, target, resolvedBlocks, listExtras, imageEx
233840
234245
  extraPatchMaps: listExtras.patchMaps,
233841
234246
  storyWideProjection: resolvedBlocks.some((block) => block.kind === "table"),
233842
234247
  packagePartUris: imageExtras.partUris,
233843
- revisionWIds: usedTrackedWIds(trackedStamp),
234248
+ revisionWIds: usedTrackedWIds(trackedStamp, listExtras.revisionWIds),
233844
234249
  indexRefreshScope: {
233845
234250
  perPart: {
233846
234251
  [partUri]: {
@@ -234860,11 +235265,11 @@ function injectTrackedMarkIntoLastParagraph(xml, stamp, kind) {
234860
235265
  const replacement = injectTrackedParagraphMarkIntoXml(match[0], stamp, kind);
234861
235266
  return xml.slice(0, match.index) + replacement + xml.slice(match.index + match[0].length);
234862
235267
  }
234863
- function pickTrackedStamp(input, capacity) {
235268
+ function pickTrackedStamp(input, capacity, options) {
234864
235269
  const author = input.authorOverride ?? input.author;
234865
235270
  if (!author)
234866
235271
  return null;
234867
- const plannedWIds = planRevisionWIdBlock(input.state, Math.max(1, capacity));
235272
+ const plannedWIds = planRevisionWIdBlock(input.state, Math.max(1, capacity), options);
234868
235273
  const stamp = {
234869
235274
  rsid: allocateTxRsid(input.txId),
234870
235275
  plannedWIds,
@@ -234881,8 +235286,17 @@ function pickTrackedStamp(input, capacity) {
234881
235286
  };
234882
235287
  return stamp;
234883
235288
  }
234884
- function usedTrackedWIds(stamp) {
234885
- return stamp ? stamp.plannedWIds.slice(0, stamp.usedWIdCount) : undefined;
235289
+ function afterMaxFromPlannedRevisionWIds(revisionWIds) {
235290
+ if (!revisionWIds || revisionWIds.length === 0)
235291
+ return;
235292
+ return Math.max(...revisionWIds);
235293
+ }
235294
+ function usedTrackedWIds(stamp, extra) {
235295
+ const ids = [
235296
+ ...stamp ? stamp.plannedWIds.slice(0, stamp.usedWIdCount) : [],
235297
+ ...extra ?? []
235298
+ ];
235299
+ return ids.length > 0 ? ids : undefined;
234886
235300
  }
234887
235301
  function replayTrackedStamp(source) {
234888
235302
  const replay = {
@@ -291804,8 +292218,13 @@ class V2SelectionMutationAdapter {
291804
292218
  const tx = begun.tx;
291805
292219
  const editTextStartedAt = nowMs6();
291806
292220
  const collapsedInlineAffinity = selectionCollapsedInlineAffinity(options);
292221
+ const collapsedOutsideContentControl = options?.collapsedOutsideContentControl === true;
291807
292222
  const kernelEdits = edits.map((edit, index2) => ({
291808
- on: collapsedInlineAffinity && edit.on.startOffset === edit.on.endOffset ? { ...edit.on, collapsedInlineAffinity } : edit.on,
292223
+ on: edit.on.startOffset === edit.on.endOffset ? {
292224
+ ...edit.on,
292225
+ ...collapsedInlineAffinity ? { collapsedInlineAffinity } : {},
292226
+ ...collapsedOutsideContentControl ? { collapsedOutsideContentControl: true } : {}
292227
+ } : edit.on,
291809
292228
  newText: edit.newText,
291810
292229
  ...options?.changeMode === "tracked" && (isRewrite || edits.length > 1) && !typingHistoryMetadata(options) && story.storyKind !== "textbox" ? {
291811
292230
  rewriteGroupId: `${tx.txId}:0`,
@@ -357366,7 +357785,14 @@ function logicalEntryMatchesStoryFilter(entry, filter) {
357366
357785
  return isBodyStoryId2(entry.storyId);
357367
357786
  if (!filter.storyIds?.has(entry.storyId))
357368
357787
  return false;
357369
- return !filter.noteId || entry.noteId === filter.noteId;
357788
+ if (!filter.noteId)
357789
+ return true;
357790
+ return ownedLogicalNoteIds(entry).includes(filter.noteId);
357791
+ }
357792
+ function ownedLogicalNoteIds(entry) {
357793
+ if (entry.noteIds && entry.noteIds.length > 0)
357794
+ return entry.noteIds;
357795
+ return entry.noteId != null ? [entry.noteId] : [];
357370
357796
  }
357371
357797
  function isVisibleLogicalChange(entry) {
357372
357798
  return isVisibleLogicalTrackedChange(entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/mcp",
3
- "version": "1.2.1-next.1",
3
+ "version": "1.2.1-next.3",
4
4
  "description": "Model Context Protocol server for SuperDoc, giving an AI agent tools to read and edit .docx files.",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -24,13 +24,13 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "@modelcontextprotocol/sdk": "^1.26.0",
27
- "@superdoc/sdk": "2.15.1-next.1",
27
+ "@superdoc/sdk": "2.15.1-next.3",
28
28
  "debug": "^4.4.3",
29
29
  "zod": "^4.3.6"
30
30
  },
31
31
  "devDependencies": {
32
- "superdoc": "2.18.1-next.1",
33
- "@superdoc/docx-engine": "0.17.1-next.1",
32
+ "superdoc": "2.18.1-next.3",
33
+ "@superdoc/docx-engine": "0.17.1-next.3",
34
34
  "@superdoc/document-api": "0.1.0-alpha.0",
35
35
  "@types/bun": "^1.3.8",
36
36
  "@types/node": "22.19.2",