@a3s-lab/office 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -110,8 +110,8 @@ Redis/NATS fan-out plus a distributed writer/lock policy.
110
110
 
111
111
  Status: browser collaboration foundation, participant roster, remote
112
112
  selection/caret projection, participant navigation, durable review-only
113
- comment and suggestion modes, character- and paragraph-formatting revisions,
114
- immutable decision audit, and native text/options/bounded structural
113
+ comment and suggestion modes, character-, paragraph-formatting, numbering, and
114
+ text-only move revisions, immutable decision audit, and native text/options/bounded structural
115
115
  paragraph/comment/suggestion mutations implemented; rich native mutation
116
116
  parity remains pending.
117
117
 
@@ -182,6 +182,14 @@ parity remains pending.
182
182
  level, tab stops, borders, shading, and collapsed state without changing
183
183
  text. Supported strict or transitional DOCX `w:pPrChange` records use the
184
184
  same model and export natively.
185
+ - Text-only move revisions create one paired `move` identity with `from` and
186
+ `to` roles. Matching strict/transitional `w:moveFrom`/`w:moveTo` records are
187
+ imported and exported natively; accept removes the source, reject removes the
188
+ destination, and one immutable decision audit record covers both sides. The
189
+ browser review binding and Yrs projection validate exact text, identity,
190
+ author, and date, while rich/range/relationship-bound or unpaired moves fail
191
+ closed as structural diagnostics. The closed native suggestion mutations
192
+ remain insertion/deletion-only.
185
193
  - Rust, CLI, MCP, and A3S Code expose `document-comment-create`,
186
194
  `document-comment-reply`, `document-comment-set-resolved`,
187
195
  `document-comment-delete`, `document-suggestion-create`, and
@@ -199,10 +207,10 @@ parity remains pending.
199
207
  content, structure, options, non-suggestion formatting, authorship, order,
200
208
  claims, anchors, foreign deletion, or foreign suggestion changes fail before
201
209
  the durable store changes.
202
- - Native Yrs validation admits only the bounded character- and
203
- paragraph-formatting schemas, preserves them across restart and
204
- duplicate/reordered delivery, exposes `formatting` and
205
- `paragraph-formatting` in immutable decision projections, and makes
210
+ - Native Yrs validation admits only the bounded character-, paragraph-formatting,
211
+ numbering, and move schemas, preserves them across restart and
212
+ duplicate/reordered delivery, exposes `formatting`, `paragraph-formatting`,
213
+ `numbering`, and `move` in immutable decision projections, and makes
206
214
  `suggest` updates retain existing formatting revisions byte-semantically.
207
215
  Formatting revisions are Document change kinds, not native
208
216
  insertion/deletion suggestion kinds; the current closed
package/README.md CHANGED
@@ -309,6 +309,22 @@ and [CLI reference](./docs/latest/en/cli-reference.md).
309
309
 
310
310
  ## Current release
311
311
 
312
+ Version `0.49.0` adds bounded native Writer move revisions and hardens the
313
+ local PDF workflow:
314
+
315
+ - Strict and transitional DOCX `w:moveFrom`/`w:moveTo` text pairs import as one
316
+ atomic `move` review item. Accept/reject resolves both source and destination
317
+ sides together, the immutable collaboration audit records the single final
318
+ decision, and export/reopen restores native move records with no private
319
+ marker leakage.
320
+ - Rich, range-marker, relationship-bound, malformed, and unpaired moves remain
321
+ visible as diagnosed compatibility boundaries. The review pane labels moves
322
+ distinctly and navigates to the destination range without selecting the text
323
+ between the two sides.
324
+ - The Playground now serves the pinned PDFium WebAssembly asset from a stable
325
+ origin-relative path, keeping direct PDF imports ready in desktop and compact
326
+ layouts.
327
+
312
328
  Version `0.48.1` fixes the Writer floating selection toolbar's split emphasis
313
329
  controls without changing the document model or public command contract:
314
330
 
@@ -1,6 +1,6 @@
1
1
  import { documentRunBorderDomAttributes, normalizeDocumentRunBorder, normalizeDocumentParagraphBorder, serializeDocumentRunBorder, serializeDocumentRunShading, documentIndexEntryHtml, attribute as work_ooxml_package_attribute, documentScriptFontFallbackSlots, documentScriptFontsDomAttributes, documentHighlightFromDocxValue, serializeDocxThemeReference, directChildren as work_ooxml_package_directChildren, documentParagraphShadingDomAttributes, documentTableOfContentsHtml, documentParagraphBordersDomAttributes, normalizeDocumentScriptFontHint, normalizeDocumentScriptFonts, DOCUMENT_PARAGRAPH_BORDER_STYLES, normalizeDocumentParagraphBorders as work_document_paragraph_borders_normalizeDocumentParagraphBorders, DOCUMENT_PARAGRAPH_BORDER_EDGES, documentScriptFontFamily, normalizeDocumentThemeFont, normalizeDocumentParagraphId, documentHighlightDomAttributes, descendants, documentIndexHtml, documentHighlightCssColor, xmlNamespacePrefix, normalizeDocumentFontName, documentProofingDomAttributes, work_document_paragraph_shading_DOCUMENT_PARAGRAPH_SHADING_PATTERNS, directChild, documentScriptFontSegments, childPath, parseDocumentTableOfContentsInstruction, documentRunShadingDomAttributes } from "./6968.js";
2
+ import { hasUnsupportedContentControlSemanticChild, docxThemeFont, XML_NAMESPACE as work_docx_settings_xml_XML_NAMESPACE, hasContentControlRelationshipReference, parseDocumentIndexEntryInstruction, resolveDocxProofing, parseDocumentIndexInstruction, CONTENT_CONTROL_WORD_2012_NAMESPACE, DOCX_WORD_2010_NAMESPACE, XMLNS_NAMESPACE as work_docx_settings_xml_XMLNS_NAMESPACE, STRICT_WORDPROCESSING_NAMESPACE, docxCharacterSpacingTwipsFromProperties, parseBoundedDocxInteger, xmlAttributeLocalName, isDocxWordElement, resolveDocxOpenTypeFeatures, resolveDocxEmphasisMark, docxCharacterScalePercentFromProperties, docxCharacterPositionHalfPointsFromProperties, docxThemeColor, parseDocxParagraphDefaultCollapsed, resolveDocxKerningThresholdHalfPoints, resolveDocxThemeResolver as work_docx_theme_resolveDocxThemeResolver, DOCX_WORDPROCESSING_NAMESPACES, xmlAttributeNamespace, parseDocxTwipsMeasure } from "./0~3534.js";
2
3
  import { normalizeDocumentEmphasisMark, normalizeDocumentPageBorders, textBoxCss, documentCharacterPositionDomAttributes, DOCUMENT_PAGE_BORDER_EDGES, normalizeDocumentTabStops, documentTextCaseFromWordFlags, documentStrikeDomAttributes, serializeDocumentNumberingChange, numberingTypeFromFormat, normalizeDocumentUnderlineColor, documentAutoLineHeight, documentKerningDomAttributes, documentCharacterScaleDomAttributes, textBoxDomAttributes, serializeDocumentParagraphFormatting, normalizeDocumentContentControlProperties, documentEquationText, normalizeDocumentPaperSource, documentLegacyTextEffectsDomAttributes, documentLegacyTextEffectsConflict, documentCharacterSpacingDomAttributes, normalizeDocumentParagraphIndent, normalizeDocumentEquation, documentOpenTypeDomAttributes, normalizeDocumentTextBoxProperties, renderDocumentAutoLineHeight, contentControlDomAttributes, documentUnderlineDomAttributes, MAX_DOCUMENT_NUMBERING_START, normalizeDocumentUnderlineStyle, applyDocumentTextCaseStyle, documentHiddenTextDomAttributes, importedDocumentCharacterFormatting, DOCUMENT_LEGACY_TEXT_EFFECT_NAMES, normalizeDocumentPageGeometry, createDocumentEquationElement, serializeDocumentTabStops, documentEmphasisMarkDomAttributes, normalizeDocumentPageMargins, documentWordLineHeightFactor } from "./4174.js";
3
- import { hasUnsupportedContentControlSemanticChild, docxThemeFont, hasContentControlRelationshipReference, XML_NAMESPACE as work_docx_settings_xml_XML_NAMESPACE, parseDocumentIndexEntryInstruction, resolveDocxProofing, parseDocumentIndexInstruction, CONTENT_CONTROL_WORD_2012_NAMESPACE, DOCX_WORD_2010_NAMESPACE, XMLNS_NAMESPACE as work_docx_settings_xml_XMLNS_NAMESPACE, STRICT_WORDPROCESSING_NAMESPACE, docxCharacterSpacingTwipsFromProperties, parseBoundedDocxInteger, isDocxWordElement, xmlAttributeLocalName, resolveDocxOpenTypeFeatures, resolveDocxEmphasisMark, docxCharacterScalePercentFromProperties, docxCharacterPositionHalfPointsFromProperties, docxThemeColor, parseDocxParagraphDefaultCollapsed, resolveDocxKerningThresholdHalfPoints, resolveDocxThemeResolver as work_docx_theme_resolveDocxThemeResolver, DOCX_WORDPROCESSING_NAMESPACES, xmlAttributeNamespace, parseDocxTwipsMeasure } from "./0~3534.js";
4
4
  function docxCaptionSequenceKind(instruction) {
5
5
  const identifier = /^\s*SEQ\s+([^\s\\]+)/i.exec(instruction)?.[1]?.toLowerCase();
6
6
  if (!identifier) return null;
@@ -159,9 +159,267 @@ function containsRange(outer, inner) {
159
159
  return outer.start >= 0 && inner.start >= 0 && outer.start < inner.start && outer.end >= inner.end;
160
160
  }
161
161
  const WORD_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
162
+ const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
163
+ const MOVE_FROM = 'moveFrom';
164
+ const MOVE_TO = 'moveTo';
165
+ const MAX_MOVE_REVISIONS = 65536;
166
+ const MAX_MOVE_TEXT_LENGTH = 1000000;
167
+ const MAX_MOVE_DATE_LENGTH = 64;
168
+ function markDocxTextChanges(document) {
169
+ const changes = [];
170
+ const movePairs = supportedMovePairs(document);
171
+ const moveByElement = new Map();
172
+ for (const pair of movePairs){
173
+ moveByElement.set(pair.from.element, pair);
174
+ moveByElement.set(pair.to.element, pair);
175
+ }
176
+ const processedMoves = new Set();
177
+ let markerIndex = 0;
178
+ const revisions = [
179
+ ...descendants(document, 'ins'),
180
+ ...descendants(document, 'del'),
181
+ ...movePairs.flatMap((pair)=>[
182
+ pair.from.element,
183
+ pair.to.element
184
+ ])
185
+ ].sort(compareDocumentOrder);
186
+ for (const revision of revisions){
187
+ if (!revision.parentNode || closestRevision(revision.parentElement)) continue;
188
+ const move = moveByElement.get(revision);
189
+ if (move) {
190
+ if (processedMoves.has(move)) continue;
191
+ processedMoves.add(move);
192
+ const id = uniqueChangeId(`docx-move-${move.from.id}`, changes);
193
+ const ordered = [
194
+ move.from,
195
+ move.to
196
+ ].sort((left, right)=>compareDocumentOrder(left.element, right.element));
197
+ for (const side of ordered){
198
+ markerIndex += 1;
199
+ if ('from' === side.role) convertDeletedText(document, side.element);
200
+ const marker = {
201
+ id,
202
+ kind: 'move',
203
+ moveRole: side.role,
204
+ author: side.author,
205
+ date: side.date,
206
+ start: `__A3S_WORK_CHANGE_START_${markerIndex}__`,
207
+ end: `__A3S_WORK_CHANGE_END_${markerIndex}__`
208
+ };
209
+ unwrapRevision(document, side.element, marker.start, marker.end);
210
+ changes.push(marker);
211
+ }
212
+ continue;
213
+ }
214
+ const kind = 'del' === revision.localName ? 'deletion' : 'insertion';
215
+ if (!revisionText(revision, kind)) continue;
216
+ if ('deletion' === kind) convertDeletedText(document, revision);
217
+ markerIndex += 1;
218
+ const sourceId = work_ooxml_package_attribute(revision, 'id')?.trim() ?? '';
219
+ const id = uniqueChangeId(sourceId ? `docx-change-${sourceId}` : `docx-change-${markerIndex}`, changes);
220
+ const marker = {
221
+ id,
222
+ kind,
223
+ author: work_ooxml_package_attribute(revision, 'author')?.trim() || '未知审阅者',
224
+ date: normalizeDate(work_ooxml_package_attribute(revision, 'date')),
225
+ start: `__A3S_WORK_CHANGE_START_${markerIndex}__`,
226
+ end: `__A3S_WORK_CHANGE_END_${markerIndex}__`
227
+ };
228
+ unwrapRevision(document, revision, marker.start, marker.end);
229
+ changes.push(marker);
230
+ }
231
+ return {
232
+ changes
233
+ };
234
+ }
235
+ function applyImportedDocxChangeMarkers(document, markers) {
236
+ for (const marker of markers.changes){
237
+ const isMoveFrom = 'move' === marker.kind && 'from' === marker.moveRole;
238
+ const element = document.createElement('deletion' === marker.kind || isMoveFrom ? 'del' : 'ins');
239
+ element.dataset.documentChange = 'true';
240
+ element.dataset.changeKind = marker.kind;
241
+ if ('move' === marker.kind && marker.moveRole) element.dataset.changeMoveRole = marker.moveRole;
242
+ element.dataset.changeId = marker.id;
243
+ element.dataset.changeAuthor = marker.author;
244
+ element.dataset.changeDate = marker.date;
245
+ wrapMarkerRange(document.body, marker.start, marker.end, element);
246
+ }
247
+ }
248
+ function hasImportedDocxChangeMarkers(markers) {
249
+ return markers.changes.length > 0;
250
+ }
251
+ function revisionText(revision, kind) {
252
+ const names = 'deletion' === kind ? [
253
+ 'delText',
254
+ 't'
255
+ ] : [
256
+ 't'
257
+ ];
258
+ return names.flatMap((name)=>descendants(revision, name)).map((element)=>element.textContent ?? '').join('');
259
+ }
260
+ function isSupportedDocxMoveChange(element) {
261
+ return null !== supportedMoveRevision(element);
262
+ }
263
+ function supportedDocxMovePairCount(document) {
264
+ return supportedMovePairs(document).length;
265
+ }
266
+ function supportedMovePairs(document) {
267
+ const from = descendants(document, MOVE_FROM).map((element)=>supportedMoveRevision(element, 'from')).filter((value)=>null !== value);
268
+ const to = descendants(document, MOVE_TO).map((element)=>supportedMoveRevision(element, 'to')).filter((value)=>null !== value);
269
+ const fromById = uniqueMoveById(from);
270
+ const toById = uniqueMoveById(to);
271
+ const pairs = [];
272
+ for (const [id, source] of fromById){
273
+ const destination = toById.get(id);
274
+ if (!(!destination || source.author !== destination.author || source.date !== destination.date || source.text !== destination.text || closestRevision(source.element.parentElement) || closestRevision(destination.element.parentElement))) pairs.push({
275
+ from: source,
276
+ to: destination
277
+ });
278
+ }
279
+ return pairs.slice(0, MAX_MOVE_REVISIONS);
280
+ }
281
+ function uniqueMoveById(revisions) {
282
+ const result = new Map();
283
+ const duplicates = new Set();
284
+ for (const revision of revisions)if (!duplicates.has(revision.id)) {
285
+ if (result.has(revision.id)) {
286
+ result.delete(revision.id);
287
+ duplicates.add(revision.id);
288
+ continue;
289
+ }
290
+ result.set(revision.id, revision);
291
+ }
292
+ return result;
293
+ }
294
+ function supportedMoveRevision(element, role) {
295
+ const resolvedRole = role ?? (element.localName === MOVE_FROM ? 'from' : element.localName === MOVE_TO ? 'to' : null);
296
+ if (!resolvedRole || !DOCX_WORDPROCESSING_NAMESPACES.has(element.namespaceURI ?? '') || element.localName !== MOVE_FROM && element.localName !== MOVE_TO || null !== element.parentElement && element.namespaceURI !== element.parentElement.namespaceURI) return null;
297
+ const id = wordAttribute(element, 'id')?.trim() ?? '';
298
+ const author = wordAttribute(element, 'author')?.trim() ?? '';
299
+ const rawDate = wordAttribute(element, 'date');
300
+ const text = moveRevisionText(element, resolvedRole);
301
+ if (!/^\d{1,10}$/.test(id) || !author || author.length > 255 || /[\u0000-\u001f\u007f]/.test(author) || null !== rawDate && rawDate.length > MAX_MOVE_DATE_LENGTH || null !== rawDate && !Number.isFinite(Date.parse(rawDate)) || !text || text.length > MAX_MOVE_TEXT_LENGTH || hasUnsupportedWordAttribute(element) || !moveChildrenAreTextOnly(element, resolvedRole)) return null;
302
+ return {
303
+ element,
304
+ role: resolvedRole,
305
+ id,
306
+ author,
307
+ date: normalizeDate(rawDate),
308
+ text
309
+ };
310
+ }
311
+ function moveRevisionText(revision, role) {
312
+ const names = 'from' === role ? [
313
+ 'delText'
314
+ ] : [
315
+ 't'
316
+ ];
317
+ return names.flatMap((name)=>descendants(revision, name)).map((element)=>element.textContent ?? '').join('');
318
+ }
319
+ function moveChildrenAreTextOnly(revision, role) {
320
+ const allowedText = 'from' === role ? new Set([
321
+ 'delText'
322
+ ]) : new Set([
323
+ 't'
324
+ ]);
325
+ const runs = Array.from(revision.children);
326
+ if (!runs.length || runs.some((run)=>'r' !== run.localName || !DOCX_WORDPROCESSING_NAMESPACES.has(run.namespaceURI ?? ''))) return false;
327
+ for (const run of runs){
328
+ const properties = Array.from(run.children).filter((child)=>'rPr' === child.localName);
329
+ if (properties.length > 1) return false;
330
+ for (const child of Array.from(run.children)){
331
+ if (!DOCX_WORDPROCESSING_NAMESPACES.has(child.namespaceURI ?? '')) return false;
332
+ if ('rPr' === child.localName) {
333
+ if (Array.from(child.querySelectorAll('*')).some((descendant)=>!DOCX_WORDPROCESSING_NAMESPACES.has(descendant.namespaceURI ?? ''))) return false;
334
+ continue;
335
+ }
336
+ if (!allowedText.has(child.localName)) return false;
337
+ if (child.querySelector('*')) return false;
338
+ }
339
+ if (!Array.from(run.children).some((child)=>allowedText.has(child.localName))) return false;
340
+ }
341
+ return true;
342
+ }
343
+ function wordAttribute(element, localName) {
344
+ const namespace = element.namespaceURI;
345
+ if (!namespace) return null;
346
+ const matches = Array.from(element.attributes).filter((candidate)=>xmlAttributeLocalName(candidate) === localName && xmlAttributeNamespace(element, candidate) === namespace);
347
+ return 1 === matches.length ? matches[0]?.value ?? null : null;
348
+ }
349
+ function hasUnsupportedWordAttribute(element) {
350
+ const namespace = element.namespaceURI;
351
+ const supported = new Set([
352
+ 'id',
353
+ 'author',
354
+ 'date'
355
+ ]);
356
+ return Array.from(element.attributes).some((candidate)=>xmlAttributeNamespace(element, candidate) === namespace && !supported.has(xmlAttributeLocalName(candidate)));
357
+ }
358
+ function convertDeletedText(document, revision) {
359
+ for (const deleted of descendants(revision, 'delText')){
360
+ const text = document.createElementNS(WORD_NAMESPACE, 'w:t');
361
+ const preserve = deleted.getAttributeNS(XML_NAMESPACE, 'space') ?? deleted.getAttribute('xml:space');
362
+ if (preserve) text.setAttributeNS(XML_NAMESPACE, 'xml:space', preserve);
363
+ text.textContent = deleted.textContent;
364
+ deleted.replaceWith(text);
365
+ }
366
+ }
367
+ function unwrapRevision(document, revision, start, end) {
368
+ const parent = revision.parentNode;
369
+ if (!parent) return;
370
+ parent.insertBefore(work_docx_change_import_markerRun(document, start), revision);
371
+ while(revision.firstChild)parent.insertBefore(revision.firstChild, revision);
372
+ parent.insertBefore(work_docx_change_import_markerRun(document, end), revision);
373
+ revision.remove();
374
+ }
375
+ function work_docx_change_import_markerRun(document, value) {
376
+ const run = document.createElementNS(WORD_NAMESPACE, 'w:r');
377
+ const text = document.createElementNS(WORD_NAMESPACE, 'w:t');
378
+ text.setAttributeNS(XML_NAMESPACE, 'xml:space', 'preserve');
379
+ text.textContent = value;
380
+ run.append(text);
381
+ return run;
382
+ }
383
+ function wrapMarkerRange(root, start, end, wrapper) {
384
+ const html = root.innerHTML;
385
+ const startIndex = html.indexOf(start);
386
+ const endIndex = html.indexOf(end, startIndex + start.length);
387
+ if (startIndex < 0 || endIndex < 0) return false;
388
+ const closingTag = `</${wrapper.localName}>`;
389
+ const serializedWrapper = wrapper.outerHTML;
390
+ if (!serializedWrapper.endsWith(closingTag)) return false;
391
+ const openingTag = serializedWrapper.slice(0, -closingTag.length);
392
+ root.innerHTML = `${html.slice(0, startIndex)}${openingTag}${html.slice(startIndex + start.length, endIndex)}${closingTag}${html.slice(endIndex + end.length)}`;
393
+ return true;
394
+ }
395
+ function closestRevision(element) {
396
+ let current = element;
397
+ while(current){
398
+ if ('ins' === current.localName || 'del' === current.localName || current.localName === MOVE_FROM || current.localName === MOVE_TO) return current;
399
+ current = current.parentElement;
400
+ }
401
+ return null;
402
+ }
403
+ function uniqueChangeId(base, changes) {
404
+ const ids = new Set(changes.map((change)=>change.id));
405
+ if (!ids.has(base)) return base;
406
+ let suffix = 2;
407
+ while(ids.has(`${base}-${suffix}`))suffix += 1;
408
+ return `${base}-${suffix}`;
409
+ }
410
+ function normalizeDate(value) {
411
+ if (!value) return '';
412
+ const time = Date.parse(value);
413
+ return Number.isFinite(time) ? new Date(time).toISOString() : '';
414
+ }
415
+ function compareDocumentOrder(left, right) {
416
+ if (left === right) return 0;
417
+ return left.compareDocumentPosition(right) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
418
+ }
419
+ const work_docx_content_control_import_WORD_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
162
420
  const STRICT_WORD_NAMESPACE = 'http://purl.oclc.org/ooxml/wordprocessingml/main';
163
421
  const WORD_NAMESPACES = new Set([
164
- WORD_NAMESPACE,
422
+ work_docx_content_control_import_WORD_NAMESPACE,
165
423
  STRICT_WORD_NAMESPACE
166
424
  ]);
167
425
  const MARKUP_COMPATIBILITY_NAMESPACE = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
@@ -342,7 +600,7 @@ function readProperties(properties) {
342
600
  if (id && !validLeaf(id, new Set([
343
601
  'val'
344
602
  ]))) return null;
345
- const nativeId = id ? parseNativeId(wordAttribute(id, 'val')) : null;
603
+ const nativeId = id ? parseNativeId(work_docx_content_control_import_wordAttribute(id, 'val')) : null;
346
604
  if (id && null === nativeId) return null;
347
605
  const alias = readStringProperty(groups.get('alias')?.[0]);
348
606
  const tag = readStringProperty(groups.get('tag')?.[0]);
@@ -351,7 +609,7 @@ function readProperties(properties) {
351
609
  if (lockElement && !validLeaf(lockElement, new Set([
352
610
  'val'
353
611
  ]))) return null;
354
- const lockValue = lockElement ? wordAttribute(lockElement, 'val') : null;
612
+ const lockValue = lockElement ? work_docx_content_control_import_wordAttribute(lockElement, 'val') : null;
355
613
  const lock = lockValue ?? 'unlocked';
356
614
  if (!LOCK_VALUES.has(lock)) return null;
357
615
  const textElement = groups.get('text')?.[0];
@@ -361,7 +619,7 @@ function readProperties(properties) {
361
619
  'multiLine'
362
620
  ]))) return null;
363
621
  if (richTextElement && !validLeaf(richTextElement, new Set())) return null;
364
- const multilineValue = textElement ? wordAttribute(textElement, 'multiLine') : null;
622
+ const multilineValue = textElement ? work_docx_content_control_import_wordAttribute(textElement, 'multiLine') : null;
365
623
  if (null !== multilineValue && ![
366
624
  '0',
367
625
  '1',
@@ -417,7 +675,7 @@ function replaceControlWithMarkers(document, control, start, end) {
417
675
  return true;
418
676
  }
419
677
  function work_docx_content_control_import_markerRun(document, context, value) {
420
- const namespace = context.namespaceURI ?? WORD_NAMESPACE;
678
+ const namespace = context.namespaceURI ?? work_docx_content_control_import_WORD_NAMESPACE;
421
679
  const prefix = xmlNamespacePrefix(context, namespace) ?? 'w';
422
680
  const run = document.createElementNS(namespace, `${prefix}:r`);
423
681
  const text = document.createElementNS(namespace, `${prefix}:t`);
@@ -535,7 +793,7 @@ function isStructuralContentControl(control) {
535
793
  'docPartList'
536
794
  ].includes(child.localName));
537
795
  }
538
- function wordAttribute(element, localName) {
796
+ function work_docx_content_control_import_wordAttribute(element, localName) {
539
797
  const matches = Array.from(element.attributes).filter((item)=>xmlAttributeLocalName(item) === localName && xmlAttributeNamespace(element, item) === element.namespaceURI && WORD_NAMESPACES.has(element.namespaceURI ?? ''));
540
798
  return 1 === matches.length ? matches[0].value : null;
541
799
  }
@@ -548,7 +806,7 @@ function readStringProperty(element) {
548
806
  if (!validLeaf(element, new Set([
549
807
  'val'
550
808
  ]))) return null;
551
- const value = wordAttribute(element, 'val');
809
+ const value = work_docx_content_control_import_wordAttribute(element, 'val');
552
810
  if (null === value || /[\u0000-\u001f\u007f]/u.test(value)) return null;
553
811
  return value.slice(0, 255);
554
812
  }
@@ -1495,7 +1753,7 @@ const WORD_COMBINE_BRACKETS = new Set([
1495
1753
  'angle',
1496
1754
  'curly'
1497
1755
  ]);
1498
- const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
1756
+ const work_docx_equation_import_XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
1499
1757
  const MAX_IMPORTED_EQUATIONS = 4096;
1500
1758
  const MAX_MATH_DEPTH = 32;
1501
1759
  const MAX_MATH_NODES = 4096;
@@ -4374,7 +4632,7 @@ function hasMeaningfulDirectText(element) {
4374
4632
  return Array.from(element.childNodes).some((node)=>(node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE) && Boolean(node.textContent?.trim()));
4375
4633
  }
4376
4634
  function safeMathTextAttributes(element) {
4377
- return meaningfulAttributes(element).every((attribute)=>xmlAttributeNamespace(element, attribute) === XML_NAMESPACE && 'space' === xmlAttributeLocalName(attribute) && ('default' === attribute.value || 'preserve' === attribute.value));
4635
+ return meaningfulAttributes(element).every((attribute)=>xmlAttributeNamespace(element, attribute) === work_docx_equation_import_XML_NAMESPACE && 'space' === xmlAttributeLocalName(attribute) && ('default' === attribute.value || 'preserve' === attribute.value));
4378
4636
  }
4379
4637
  function mathValue(element) {
4380
4638
  if (!DOCX_MATH_NAMESPACES.has(element.namespaceURI ?? '') || work_ooxml_package_directChildren(element).length || hasMeaningfulDirectText(element)) return null;
@@ -4810,7 +5068,7 @@ function supportedNumberingChange(change) {
4810
5068
  const paragraph = properties?.parentElement;
4811
5069
  if (numbering?.localName !== 'numPr' || numbering.namespaceURI !== namespace || properties?.localName !== 'pPr' || properties.namespaceURI !== namespace || paragraph?.localName !== 'p' || paragraph.namespaceURI !== namespace) return null;
4812
5070
  const numberingChildren = work_ooxml_package_directChildren(numbering);
4813
- if (3 !== numberingChildren.length || numberingChildren.some((child)=>child.namespaceURI !== namespace) || 1 !== work_ooxml_package_directChildren(numbering, 'numberingChange').length || 0 !== change.children.length || hasUnsupportedWordAttribute(change)) return null;
5071
+ if (3 !== numberingChildren.length || numberingChildren.some((child)=>child.namespaceURI !== namespace) || 1 !== work_ooxml_package_directChildren(numbering, 'numberingChange').length || 0 !== change.children.length || work_docx_numbering_change_import_hasUnsupportedWordAttribute(change)) return null;
4814
5072
  const levels = directWordChildren(numbering, 'ilvl');
4815
5073
  const numberingIds = directWordChildren(numbering, 'numId');
4816
5074
  if (1 !== levels.length || 1 !== numberingIds.length) return null;
@@ -4940,7 +5198,7 @@ function work_docx_numbering_change_import_wordAttribute(element, localName) {
4940
5198
  const attributes = Array.from(element.attributes).filter((attribute)=>xmlAttributeLocalName(attribute) === localName && xmlAttributeNamespace(element, attribute) === namespace);
4941
5199
  return 1 === attributes.length ? attributes[0]?.value ?? null : null;
4942
5200
  }
4943
- function hasUnsupportedWordAttribute(element) {
5201
+ function work_docx_numbering_change_import_hasUnsupportedWordAttribute(element) {
4944
5202
  const namespace = element.namespaceURI;
4945
5203
  const supported = new Set([
4946
5204
  'id',
@@ -8933,4 +9191,4 @@ function work_docx_text_box_import_textNodes(root) {
8933
9191
  while(walker.nextNode())nodes.push(walker.currentNode);
8934
9192
  return nodes;
8935
9193
  }
8936
- export { applyImportedDocxContentControlMarkers, applyImportedDocxEquationMarkers, applyImportedDocxIndexMarkers, applyImportedDocxNumberingChangeMarkers, applyImportedDocxParagraphAlignmentMarkers, applyImportedDocxParagraphBorderMarkers, applyImportedDocxParagraphDirectionMarkers, applyImportedDocxParagraphFormattingChangeMarkers, applyImportedDocxParagraphIndentMarkers, applyImportedDocxParagraphPaginationMarkers, applyImportedDocxParagraphShadingMarkers, applyImportedDocxParagraphSpacingMarkers, applyImportedDocxParagraphTabStopMarkers, applyImportedDocxRunFormattingMarkers, applyImportedDocxTableOfContentsMarkers, applyImportedDocxTextBoxMarkers, createDocxParagraphStyleResolver, createDocxTableStyleResolver, createImportedDocxRunFormattingMarkerState, docxCaptionBookmark, docxCaptionReferenceTarget, docxCaptionSequenceKind, docxEquationHtml, docxFieldInstructions, docxFieldOccurrenceIsInlineEditable, docxFieldOccurrences, docxFieldResultText, docxRunPropertySources, docxTableCellStyleLayers, docxTablePropertySources, docxTableRunPropertySources, hasImportedDocxContentControlMarkers, hasImportedDocxIndexMarkers, hasImportedDocxNumberingChangeMarkers, hasImportedDocxParagraphAlignmentMarkers, hasImportedDocxParagraphBorderMarkers, hasImportedDocxParagraphDirectionMarkers, hasImportedDocxParagraphFormattingChangeMarkers, hasImportedDocxParagraphIndentMarkers, hasImportedDocxParagraphPaginationMarkers, hasImportedDocxParagraphShadingMarkers, hasImportedDocxParagraphSpacingMarkers, hasImportedDocxParagraphTabStopMarkers, hasImportedDocxRunFormattingMarkers, hasImportedDocxTableOfContentsMarkers, hasImportedDocxTextBoxMarkers, hasInvalidDocxFieldStructure, importedDocxStrike, importedDocxUnderline, inspectDocxContentControls, inspectDocxEquation, inspectDocxHiddenText, inspectDocxLegacyTextEffects, inspectDocxPageBorders, inspectDocxPageMarginSettings, inspectDocxPageMargins, inspectDocxPageSize, inspectDocxPaperSource, inspectDocxRunBorder, inspectDocxRunFonts, inspectDocxRunShading, inspectDocxTextBoxes, isDocxEquationLikeRoot, isSupportedDocxEquationPlacement, isSupportedDocxNumberingChange, isSupportedDocxParagraphFormattingChange, isSupportedDocxRunFormattingChange, markDocxContentControls, markDocxIndexes, markDocxNumberingChanges, markDocxPackageEquations, markDocxParagraphAlignments, markDocxParagraphBorders, markDocxParagraphDirections, markDocxParagraphFormattingChanges, markDocxParagraphIndents, markDocxParagraphPagination, markDocxParagraphShading, markDocxParagraphSpacing, markDocxParagraphTabStops, markDocxRunFormattingIntoState, markDocxTablesOfContents, markDocxTextBoxes, parseDirectDocxParagraphShading, parseDocxPageBorders, parseDocxPageGeometry, parseDocxPageMargins, parseDocxPaperSource, resolveDocxHiddenText, resolveDocxLegacyTextEffects, resolveDocxParagraphBordersForParagraph, resolveDocxRunBorder, resolveDocxRunShading, resolveDocxTableStyleResolver, supportedDocxIndexEntryField, supportedDocxIndexField, supportedDocxTableOfContentsField };
9194
+ export { applyImportedDocxChangeMarkers, applyImportedDocxContentControlMarkers, applyImportedDocxEquationMarkers, applyImportedDocxIndexMarkers, applyImportedDocxNumberingChangeMarkers, applyImportedDocxParagraphAlignmentMarkers, applyImportedDocxParagraphBorderMarkers, applyImportedDocxParagraphDirectionMarkers, applyImportedDocxParagraphFormattingChangeMarkers, applyImportedDocxParagraphIndentMarkers, applyImportedDocxParagraphPaginationMarkers, applyImportedDocxParagraphShadingMarkers, applyImportedDocxParagraphSpacingMarkers, applyImportedDocxParagraphTabStopMarkers, applyImportedDocxRunFormattingMarkers, applyImportedDocxTableOfContentsMarkers, applyImportedDocxTextBoxMarkers, createDocxParagraphStyleResolver, createDocxTableStyleResolver, createImportedDocxRunFormattingMarkerState, docxCaptionBookmark, docxCaptionReferenceTarget, docxCaptionSequenceKind, docxEquationHtml, docxFieldInstructions, docxFieldOccurrenceIsInlineEditable, docxFieldOccurrences, docxFieldResultText, docxRunPropertySources, docxTableCellStyleLayers, docxTablePropertySources, docxTableRunPropertySources, hasImportedDocxChangeMarkers, hasImportedDocxContentControlMarkers, hasImportedDocxIndexMarkers, hasImportedDocxNumberingChangeMarkers, hasImportedDocxParagraphAlignmentMarkers, hasImportedDocxParagraphBorderMarkers, hasImportedDocxParagraphDirectionMarkers, hasImportedDocxParagraphFormattingChangeMarkers, hasImportedDocxParagraphIndentMarkers, hasImportedDocxParagraphPaginationMarkers, hasImportedDocxParagraphShadingMarkers, hasImportedDocxParagraphSpacingMarkers, hasImportedDocxParagraphTabStopMarkers, hasImportedDocxRunFormattingMarkers, hasImportedDocxTableOfContentsMarkers, hasImportedDocxTextBoxMarkers, hasInvalidDocxFieldStructure, importedDocxStrike, importedDocxUnderline, inspectDocxContentControls, inspectDocxEquation, inspectDocxHiddenText, inspectDocxLegacyTextEffects, inspectDocxPageBorders, inspectDocxPageMarginSettings, inspectDocxPageMargins, inspectDocxPageSize, inspectDocxPaperSource, inspectDocxRunBorder, inspectDocxRunFonts, inspectDocxRunShading, inspectDocxTextBoxes, isDocxEquationLikeRoot, isSupportedDocxEquationPlacement, isSupportedDocxMoveChange, isSupportedDocxNumberingChange, isSupportedDocxParagraphFormattingChange, isSupportedDocxRunFormattingChange, markDocxContentControls, markDocxIndexes, markDocxNumberingChanges, markDocxPackageEquations, markDocxParagraphAlignments, markDocxParagraphBorders, markDocxParagraphDirections, markDocxParagraphFormattingChanges, markDocxParagraphIndents, markDocxParagraphPagination, markDocxParagraphShading, markDocxParagraphSpacing, markDocxParagraphTabStops, markDocxRunFormattingIntoState, markDocxTablesOfContents, markDocxTextBoxes, markDocxTextChanges, parseDirectDocxParagraphShading, parseDocxPageBorders, parseDocxPageGeometry, parseDocxPageMargins, parseDocxPaperSource, resolveDocxHiddenText, resolveDocxLegacyTextEffects, resolveDocxParagraphBordersForParagraph, resolveDocxRunBorder, resolveDocxRunShading, resolveDocxTableStyleResolver, supportedDocxIndexEntryField, supportedDocxIndexField, supportedDocxMovePairCount, supportedDocxTableOfContentsField };
@@ -1,22 +1,114 @@
1
1
  const COMPOSITION_SETTLE_RETRY_MS = 20;
2
2
  const MAX_COMPOSITION_SETTLE_RETRIES = 4;
3
+ const COMPOSITION_SETTLE_QUIET_MS = 24;
4
+ const COMPOSITION_SETTLE_MAX_MS = 240;
5
+ function inputTypeOf(event) {
6
+ const inputType = event.inputType;
7
+ return 'string' == typeof inputType ? inputType : '';
8
+ }
9
+ function inputDataOf(event) {
10
+ const data = event.data;
11
+ return 'string' == typeof data ? data : null;
12
+ }
13
+ function isCommittedInput(event) {
14
+ return 'insertFromComposition' === inputTypeOf(event);
15
+ }
3
16
  class ControlledEditorComposition {
4
17
  active = false;
18
+ committedInputSeen = false;
19
+ committedText = null;
20
+ compositionData = null;
21
+ deadline = 0;
5
22
  generation = 0;
23
+ awaitingCommittedTransaction = false;
24
+ range = null;
25
+ rangeFrozen = false;
6
26
  settling = false;
27
+ settleAt = 0;
28
+ settleHandler = null;
29
+ settleEditor = null;
30
+ sessionId = 0;
7
31
  timer = null;
8
- start() {
32
+ start(editor) {
33
+ if (this.active) {
34
+ if (!this.range && editor && !editor.isDestroyed) this.range = this.selectionRange(editor);
35
+ return;
36
+ }
9
37
  this.generation += 1;
10
38
  this.cancelTimer();
11
39
  this.active = true;
40
+ this.awaitingCommittedTransaction = false;
41
+ this.committedInputSeen = false;
42
+ this.committedText = null;
43
+ this.compositionData = null;
44
+ this.deadline = 0;
45
+ this.range = editor && !editor.isDestroyed ? this.selectionRange(editor) : null;
46
+ this.rangeFrozen = false;
12
47
  this.settling = false;
48
+ this.settleAt = 0;
49
+ this.settleEditor = null;
50
+ this.settleHandler = null;
51
+ this.sessionId += 1;
13
52
  }
14
- end(editor, onSettled) {
53
+ recordTransaction(transaction) {
54
+ if (!this.active && !this.settling || !this.range || this.rangeFrozen || !transaction.docChanged) return;
55
+ const from = transaction.mapping.map(this.range.from, -1);
56
+ const to = transaction.mapping.map(this.range.to, 1);
57
+ this.range = {
58
+ from: Math.min(from, to),
59
+ to: Math.max(from, to)
60
+ };
61
+ if (this.settling && this.awaitingCommittedTransaction) {
62
+ this.awaitingCommittedTransaction = false;
63
+ this.rangeFrozen = true;
64
+ }
65
+ }
66
+ noteInput(event) {
67
+ const data = inputDataOf(event);
68
+ const committed = isCommittedInput(event);
69
+ if (this.active) {
70
+ if (committed && data) {
71
+ this.committedInputSeen = true;
72
+ this.committedText = data;
73
+ this.awaitingCommittedTransaction = true;
74
+ }
75
+ return;
76
+ }
77
+ if (!this.settling) return;
78
+ const matchesCompositionData = Boolean(data && this.compositionData && data === this.compositionData);
79
+ if (!committed && !matchesCompositionData) {
80
+ if (data) {
81
+ this.awaitingCommittedTransaction = false;
82
+ this.rangeFrozen = true;
83
+ this.scheduleQuietSettlement();
84
+ }
85
+ return;
86
+ }
87
+ if (data) this.committedText = data;
88
+ this.committedInputSeen = true;
89
+ this.awaitingCommittedTransaction = true;
90
+ this.scheduleQuietSettlement();
91
+ }
92
+ end(editor, dataOrHandler, maybeHandler) {
93
+ const legacySettlement = 'function' == typeof dataOrHandler;
94
+ const data = legacySettlement ? null : dataOrHandler;
95
+ const handler = 'function' == typeof dataOrHandler ? dataOrHandler : maybeHandler;
96
+ if (!handler || !this.active && !this.settling) return;
97
+ if ('string' == typeof data) {
98
+ this.compositionData = data;
99
+ if (data && !this.committedText) this.committedText = data;
100
+ if (data && this.currentRangeText(editor) === data) this.committedInputSeen = true;
101
+ }
15
102
  const generation = ++this.generation;
16
103
  this.cancelTimer();
17
104
  this.active = false;
18
105
  this.settling = true;
19
- this.scheduleSettlement(editor, onSettled, generation, 0);
106
+ this.deadline = Date.now() + COMPOSITION_SETTLE_MAX_MS;
107
+ this.settleEditor = editor;
108
+ this.settleHandler = handler;
109
+ this.settleAt = legacySettlement ? Date.now() : this.committedInputSeen ? Date.now() + COMPOSITION_SETTLE_QUIET_MS : this.deadline;
110
+ if (legacySettlement) this.deadline = Date.now() + COMPOSITION_SETTLE_RETRY_MS * MAX_COMPOSITION_SETTLE_RETRIES;
111
+ this.scheduleSettlement(generation);
20
112
  }
21
113
  isBlocking(editor) {
22
114
  return this.active || this.settling || Boolean(editor && !editor.isDestroyed && editor.view.composing);
@@ -26,24 +118,84 @@ class ControlledEditorComposition {
26
118
  this.cancelTimer();
27
119
  this.active = false;
28
120
  this.settling = false;
121
+ this.committedInputSeen = false;
122
+ this.committedText = null;
123
+ this.compositionData = null;
124
+ this.deadline = 0;
125
+ this.range = null;
126
+ this.rangeFrozen = false;
127
+ this.settleAt = 0;
128
+ this.settleEditor = null;
129
+ this.settleHandler = null;
130
+ }
131
+ selectionRange(editor) {
132
+ return {
133
+ from: editor.state.selection.from,
134
+ to: editor.state.selection.to
135
+ };
136
+ }
137
+ currentRangeText(editor) {
138
+ if (!this.range || editor.isDestroyed) return null;
139
+ const maximum = editor.state.doc.content.size;
140
+ if (this.range.from < 0 || this.range.to < this.range.from || this.range.to > maximum) return null;
141
+ return editor.state.doc.textBetween(this.range.from, this.range.to, '\n', '\n');
29
142
  }
30
- scheduleSettlement(editor, onSettled, generation, retry) {
31
- const delay = 0 === retry ? 0 : COMPOSITION_SETTLE_RETRY_MS;
143
+ snapshot(editor) {
144
+ const maximum = editor.state.doc.content.size;
145
+ const from = this.range ? Math.max(0, Math.min(maximum, this.range.from)) : null;
146
+ const to = this.range ? Math.max(0, Math.min(maximum, this.range.to)) : null;
147
+ return {
148
+ id: this.sessionId,
149
+ range: null === from || null === to ? null : {
150
+ from: Math.min(from, to),
151
+ to: Math.max(from, to)
152
+ },
153
+ text: this.committedText
154
+ };
155
+ }
156
+ scheduleQuietSettlement() {
157
+ if (!this.settling) return;
158
+ this.settleAt = Math.min(this.deadline, Date.now() + COMPOSITION_SETTLE_QUIET_MS);
159
+ this.scheduleSettlement(this.generation);
160
+ }
161
+ scheduleSettlement(expectedGeneration) {
162
+ this.cancelTimer();
163
+ const wait = Math.max(0, this.settleAt - Date.now());
32
164
  this.timer = setTimeout(()=>{
33
165
  this.timer = null;
34
- if (generation !== this.generation) return;
35
- if (editor.isDestroyed) {
36
- this.settling = false;
37
- return;
38
- }
166
+ if (expectedGeneration !== this.generation || !this.settling) return;
167
+ const editor = this.settleEditor;
168
+ const handler = this.settleHandler;
169
+ if (!editor || !handler || editor.isDestroyed) return void this.clearSettlement();
39
170
  if (editor.view.composing) {
40
- if (retry < MAX_COMPOSITION_SETTLE_RETRIES) this.scheduleSettlement(editor, onSettled, generation, retry + 1);
41
- else this.settling = false;
171
+ if (Date.now() < this.deadline) {
172
+ this.settleAt = Math.min(this.deadline, Date.now() + COMPOSITION_SETTLE_RETRY_MS);
173
+ this.scheduleSettlement(expectedGeneration);
174
+ } else this.clearSettlement();
42
175
  return;
43
176
  }
44
- this.settling = false;
45
- onSettled(editor);
46
- }, delay);
177
+ if (Date.now() < this.settleAt) return void this.scheduleSettlement(expectedGeneration);
178
+ const snapshot = this.snapshot(editor);
179
+ try {
180
+ handler(editor, snapshot);
181
+ } finally{
182
+ this.clearSettlement();
183
+ }
184
+ }, wait);
185
+ }
186
+ clearSettlement() {
187
+ this.active = false;
188
+ this.awaitingCommittedTransaction = false;
189
+ this.settling = false;
190
+ this.committedInputSeen = false;
191
+ this.committedText = null;
192
+ this.compositionData = null;
193
+ this.deadline = 0;
194
+ this.range = null;
195
+ this.rangeFrozen = false;
196
+ this.settleAt = 0;
197
+ this.settleEditor = null;
198
+ this.settleHandler = null;
47
199
  }
48
200
  cancelTimer() {
49
201
  if (null === this.timer) return;