@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.
- package/COLLABORATION_ROADMAP.md +14 -6
- package/README.md +16 -0
- package/dist/{0~3320.js → 0~5600.js} +271 -13
- package/dist/0~controlled-editor-composition.js +167 -15
- package/dist/0~document-editor.js +65 -10
- package/dist/0~work-docx-export.js +143 -9
- package/dist/0~work-docx-import.js +5 -120
- package/dist/0~work-office-diagnostics.js +16 -5
- package/dist/4174.js +93 -8
- package/dist/internal/features/work/editors/controlled-editor-composition.d.ts +53 -7
- package/dist/internal/features/work/editors/use-document-pagination.d.ts +3 -1
- package/dist/internal/features/work/work-document-changes.d.ts +4 -2
- package/dist/internal/features/work/work-docx-change-import.d.ts +10 -1
- package/dist/internal/features/work/work-docx-move-revision-export.d.ts +35 -0
- package/dist/internal/features/work/work-types.d.ts +3 -1
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +9 -0
- package/docs/latest/en/browser-editor-architecture.md +14 -2
- package/package.json +11 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __webpack_require__ } from "./0~rslib-runtime.js";
|
|
2
2
|
import { contentTypeForPart, directChildren, OoxmlPackage, parseXml, firstDescendant, isDocumentParagraphArtBorderStyle, descendants, directChild, xmlContainsAnyElement, attribute, documentRunBorderIsVisible } from "./6968.js";
|
|
3
3
|
import { normalizeDocumentBookmarkName, docxBookmarkReferenceTarget, DOCUMENT_LEGACY_TEXT_EFFECT_NAMES, normalizeDocumentBookmarkNativeId, supportedDocxDocumentFieldInstruction, supportedDocxBookmarkReferenceInstruction, documentCitationTagsFromInstruction, docxDocumentFieldKind, docxDocumentFieldTarget } from "./4174.js";
|
|
4
|
-
import { isSupportedDocxEquationPlacement, inspectDocxPageSize, inspectDocxContentControls, hasInvalidDocxFieldStructure, createDocxTableStyleResolver, inspectDocxRunBorder, inspectDocxEquation, inspectDocxPageMarginSettings, inspectDocxRunShading, supportedDocxIndexField, docxCaptionSequenceKind, inspectDocxPageBorders, docxCaptionBookmark, inspectDocxLegacyTextEffects, inspectDocxPageMargins, inspectDocxPaperSource, createDocxParagraphStyleResolver, inspectDocxRunFonts, supportedDocxTableOfContentsField, docxFieldOccurrences, supportedDocxIndexEntryField, inspectDocxTextBoxes, markDocxParagraphBorders, isSupportedDocxParagraphFormattingChange, docxFieldOccurrenceIsInlineEditable, inspectDocxHiddenText, isSupportedDocxRunFormattingChange, isSupportedDocxNumberingChange, markDocxParagraphShading, docxFieldInstructions } from "./0~
|
|
4
|
+
import { isSupportedDocxEquationPlacement, inspectDocxPageSize, inspectDocxContentControls, hasInvalidDocxFieldStructure, createDocxTableStyleResolver, inspectDocxRunBorder, inspectDocxEquation, inspectDocxPageMarginSettings, inspectDocxRunShading, supportedDocxMovePairCount, supportedDocxIndexField, docxCaptionSequenceKind, inspectDocxPageBorders, isSupportedDocxMoveChange, docxCaptionBookmark, inspectDocxLegacyTextEffects, inspectDocxPageMargins, inspectDocxPaperSource, createDocxParagraphStyleResolver, inspectDocxRunFonts, supportedDocxTableOfContentsField, docxFieldOccurrences, supportedDocxIndexEntryField, inspectDocxTextBoxes, markDocxParagraphBorders, isSupportedDocxParagraphFormattingChange, docxFieldOccurrenceIsInlineEditable, inspectDocxHiddenText, isSupportedDocxRunFormattingChange, isSupportedDocxNumberingChange, markDocxParagraphShading, docxFieldInstructions } from "./0~5600.js";
|
|
5
5
|
import { normalizeDocumentHref } from "./0~work-document-links.js";
|
|
6
6
|
import { xmlAttributeLocalName, resolveDocxOpenTypeFeatures, inspectDocxProofingLanguages, readDocxBibliography, inspectDocxEmphasisMark, createDocxThemeResolver, DOCX_WORD_2010_NAMESPACE, inspectDocxNoProof, parseDocxParagraphDefaultCollapsed, readDocxImageTransform, DOCX_WORDPROCESSING_NAMESPACES, xmlAttributeNamespace, inspectDocxKerningThresholdHalfPoints } from "./0~3534.js";
|
|
7
7
|
import { updateSpreadsheetWorksheetCompatibilitySummary, inspectXlsxPivotTables, isSupportedXlsxChartLegend, isSupportedXlsxChartPlotLayout, xlsxChartNodeUsesStackedGrouping, isSupportedXlsxChartSeriesFormatting, readXlsxFormulaFeaturesFromPackage, inspectXlsxHeaderFooterText, emptySpreadsheetWorksheetCompatibilitySummary, isSupportedXlsxWorksheetImageContentType, MAX_XLSX_WORKSHEET_IMAGE_BYTES, xlsxChartSeriesFormattingShapeProperties, diagnoseXlsxProtection, isEditableXlsxPaperSizeCode, isSupportedXlsxCombinationChartNodes } from "./4166.js";
|
|
@@ -2223,6 +2223,14 @@ async function analyzeDocxCompatibility(file, messages, sourcePackage) {
|
|
|
2223
2223
|
...descendants(document, 'ins'),
|
|
2224
2224
|
...descendants(document, 'del')
|
|
2225
2225
|
];
|
|
2226
|
+
const moveFromRevisions = descendants(document, 'moveFrom');
|
|
2227
|
+
const moveToRevisions = descendants(document, 'moveTo');
|
|
2228
|
+
const moveRevisionCount = moveFromRevisions.length + moveToRevisions.length;
|
|
2229
|
+
const supportedMovePairCount = supportedDocxMovePairCount(document);
|
|
2230
|
+
const supportedMoveRevisionCount = [
|
|
2231
|
+
...moveFromRevisions,
|
|
2232
|
+
...moveToRevisions
|
|
2233
|
+
].filter(isSupportedDocxMoveChange).length;
|
|
2226
2234
|
const runFormattingRevisions = descendants(document, 'rPrChange');
|
|
2227
2235
|
const paragraphFormattingRevisions = descendants(document, 'pPrChange');
|
|
2228
2236
|
const numberingRevisions = descendants(document, 'numberingChange');
|
|
@@ -2233,14 +2241,17 @@ async function analyzeDocxCompatibility(file, messages, sourcePackage) {
|
|
|
2233
2241
|
if (supportedRunFormattingRevisionCount) issues.push(work_office_diagnostics_issue('docx.revisions.formatting', 'Character-formatting revisions', `${supportedRunFormattingRevisionCount} bounded character-formatting revision(s) preserve author, date, current formatting, and prior bold, italic, underline, strike, text case, hidden text, outline, shadow, emboss, imprint, subscript, superscript, font, size, color, highlight, character borders, character shading, character scale, spacing, kerning threshold, emphasis mark, baseline position, grid state, proofing languages, and explicit proofing state. They remain reviewable in Work and round-trip as native w:rPrChange records.`, 'info'));
|
|
2234
2242
|
if (supportedParagraphFormattingRevisionCount) issues.push(work_office_diagnostics_issue('docx.revisions.paragraph-formatting', 'Paragraph-formatting revisions', `${supportedParagraphFormattingRevisionCount} bounded paragraph-formatting revision(s) preserve author, date, current formatting, and prior alignment, direction, indentation, spacing, pagination, outline, tab-stop, border, shading, and collapsed state. They remain reviewable in Work and round-trip as native w:pPrChange records.`, 'info'));
|
|
2235
2243
|
if (supportedNumberingRevisionCount) issues.push(work_office_diagnostics_issue('docx.revisions.numbering', 'Numbering revisions', `${supportedNumberingRevisionCount} bounded ordered-list numbering revision(s) preserve author, date, prior start, and common decimal, letter, or Roman formats. Contiguous list-item records remain reviewable as one Work change and round-trip as native w:numberingChange records.`, 'info'));
|
|
2236
|
-
if (
|
|
2237
|
-
|
|
2238
|
-
'
|
|
2244
|
+
if (supportedMovePairCount) issues.push(work_office_diagnostics_issue('docx.revisions.move', 'Move revisions', `${supportedMovePairCount} bounded text move revision(s) preserve author, date, source and destination text, remain reviewable as one atomic Work change, and round-trip as native w:moveFrom and w:moveTo records. Rich content, range markers, and relationship-bound moves remain on the compatibility path.`, 'info'));
|
|
2245
|
+
if (textRevisions.some((revision)=>!descendants(revision, 't').length && !descendants(revision, 'delText').length) || supportedRunFormattingRevisionCount !== runFormattingRevisions.length || supportedParagraphFormattingRevisionCount !== paragraphFormattingRevisions.length || supportedNumberingRevisionCount !== numberingRevisions.length || supportedMoveRevisionCount !== moveRevisionCount || supportedMoveRevisionCount !== 2 * supportedMovePairCount || [
|
|
2246
|
+
'moveFromRangeStart',
|
|
2247
|
+
'moveFromRangeEnd',
|
|
2248
|
+
'moveToRangeStart',
|
|
2249
|
+
'moveToRangeEnd',
|
|
2239
2250
|
'tblPrChange',
|
|
2240
2251
|
'trPrChange',
|
|
2241
2252
|
'tcPrChange',
|
|
2242
2253
|
'sectPrChange'
|
|
2243
|
-
].some((name)=>descendants(document, name).length)) issues.push(work_office_diagnostics_issue('docx.revisions.structural', 'Structural revisions', 'Moved content plus unsupported character formatting, paragraph formatting, numbering, section, row, cell, and table-property revisions may be normalized; Work currently reviews body-text insertions/deletions and bounded character-, paragraph-, and ordered-list-numbering subsets.'));
|
|
2254
|
+
].some((name)=>descendants(document, name).length)) issues.push(work_office_diagnostics_issue('docx.revisions.structural', 'Structural revisions', 'Moved content plus unsupported character formatting, paragraph formatting, numbering, section, row, cell, and table-property revisions may be normalized; Work currently reviews body-text insertions/deletions and bounded text moves, character-, paragraph-, and ordered-list-numbering subsets.'));
|
|
2244
2255
|
if (captionDiagnostics.hasUnsupportedFields) issues.push(work_office_diagnostics_issue('docx.fields', 'Fields', 'Fields beyond supported body fields, citations, bibliographies, caption SEQ fields, and bookmark or caption REF fields are converted to their current displayed value.'));
|
|
2245
2256
|
const sectionProperties = descendants(document, 'sectPr');
|
|
2246
2257
|
const columnProperties = descendants(document, 'cols');
|
package/dist/4174.js
CHANGED
|
@@ -10096,12 +10096,23 @@ const DocumentChange = Mark.create({
|
|
|
10096
10096
|
parseHTML: (element)=>{
|
|
10097
10097
|
const declared = element.getAttribute('data-change-kind');
|
|
10098
10098
|
if ('formatting' === declared) return 'formatting';
|
|
10099
|
+
if ('move' === declared) return 'move';
|
|
10099
10100
|
return 'del' === element.tagName.toLowerCase() ? 'deletion' : 'insertion';
|
|
10100
10101
|
},
|
|
10101
10102
|
renderHTML: (attributes)=>({
|
|
10102
10103
|
'data-change-kind': attributes.kind
|
|
10103
10104
|
})
|
|
10104
10105
|
},
|
|
10106
|
+
moveRole: {
|
|
10107
|
+
default: '',
|
|
10108
|
+
parseHTML: (element)=>{
|
|
10109
|
+
const role = element.getAttribute('data-change-move-role');
|
|
10110
|
+
return 'from' === role || 'to' === role ? role : '';
|
|
10111
|
+
},
|
|
10112
|
+
renderHTML: (attributes)=>'from' === attributes.moveRole || 'to' === attributes.moveRole ? {
|
|
10113
|
+
'data-change-move-role': attributes.moveRole
|
|
10114
|
+
} : {}
|
|
10115
|
+
},
|
|
10105
10116
|
id: {
|
|
10106
10117
|
default: '',
|
|
10107
10118
|
parseHTML: (element)=>element.getAttribute('data-change-id') ?? '',
|
|
@@ -10189,7 +10200,7 @@ const DocumentChange = Mark.create({
|
|
|
10189
10200
|
];
|
|
10190
10201
|
},
|
|
10191
10202
|
renderHTML ({ mark, HTMLAttributes }) {
|
|
10192
|
-
const tag = 'deletion' === mark.attrs.kind ? 'del' : 'formatting' === mark.attrs.kind ? 'span' : 'ins';
|
|
10203
|
+
const tag = 'deletion' === mark.attrs.kind ? 'del' : 'move' === mark.attrs.kind && 'from' === mark.attrs.moveRole ? 'del' : 'formatting' === mark.attrs.kind ? 'span' : 'ins';
|
|
10193
10204
|
return [
|
|
10194
10205
|
tag,
|
|
10195
10206
|
mergeAttributes(HTMLAttributes, {
|
|
@@ -10289,6 +10300,7 @@ const DocumentChange = Mark.create({
|
|
|
10289
10300
|
function collectDocumentChanges(document1) {
|
|
10290
10301
|
const changes = new Map();
|
|
10291
10302
|
const blockChangeRanges = new Map();
|
|
10303
|
+
const moveTextParts = new Map();
|
|
10292
10304
|
document1.descendants((node, position)=>{
|
|
10293
10305
|
if (('paragraph' === node.type.name || 'heading' === node.type.name) && ('insertion' === node.attrs.blockChangeKind || 'deletion' === node.attrs.blockChangeKind)) {
|
|
10294
10306
|
const kind = node.attrs.blockChangeKind;
|
|
@@ -10365,6 +10377,7 @@ function collectDocumentChanges(document1) {
|
|
|
10365
10377
|
const mark = work_document_changes_documentChangeMark(node.marks);
|
|
10366
10378
|
if (!mark) return;
|
|
10367
10379
|
const kind = changeKind(mark.attrs.kind);
|
|
10380
|
+
const moveRole = documentMoveRole(mark.attrs.moveRole);
|
|
10368
10381
|
const id = work_document_changes_stringAttribute(mark.attrs.id) || `change-at-${position}`;
|
|
10369
10382
|
const key = `${kind}:${id}`;
|
|
10370
10383
|
if (blockChangeRanges.get(key)?.some((range)=>position >= range.from && position + node.nodeSize <= range.to)) return;
|
|
@@ -10372,12 +10385,38 @@ function collectDocumentChanges(document1) {
|
|
|
10372
10385
|
if (current) {
|
|
10373
10386
|
current.from = Math.min(current.from, position);
|
|
10374
10387
|
current.to = Math.max(current.to, position + node.nodeSize);
|
|
10375
|
-
|
|
10388
|
+
if ('move' === kind && moveRole) {
|
|
10389
|
+
const parts = moveTextParts.get(key) ?? {};
|
|
10390
|
+
const previous = parts[moveRole];
|
|
10391
|
+
parts[moveRole] = previous ? {
|
|
10392
|
+
text: previous.text + node.text,
|
|
10393
|
+
from: Math.min(previous.from, position),
|
|
10394
|
+
to: Math.max(previous.to, position + node.nodeSize)
|
|
10395
|
+
} : {
|
|
10396
|
+
text: node.text,
|
|
10397
|
+
from: position,
|
|
10398
|
+
to: position + node.nodeSize
|
|
10399
|
+
};
|
|
10400
|
+
moveTextParts.set(key, parts);
|
|
10401
|
+
if (current.moveRole !== moveRole) delete current.moveRole;
|
|
10402
|
+
} else current.text += node.text;
|
|
10376
10403
|
return;
|
|
10377
10404
|
}
|
|
10405
|
+
if ('move' === kind && moveRole) {
|
|
10406
|
+
const parts = moveTextParts.get(key) ?? {};
|
|
10407
|
+
parts[moveRole] = {
|
|
10408
|
+
text: node.text,
|
|
10409
|
+
from: position,
|
|
10410
|
+
to: position + node.nodeSize
|
|
10411
|
+
};
|
|
10412
|
+
moveTextParts.set(key, parts);
|
|
10413
|
+
}
|
|
10378
10414
|
changes.set(key, {
|
|
10379
10415
|
id,
|
|
10380
10416
|
kind,
|
|
10417
|
+
...moveRole ? {
|
|
10418
|
+
moveRole
|
|
10419
|
+
} : {},
|
|
10381
10420
|
...work_document_changes_stringAttribute(mark.attrs.actorId) ? {
|
|
10382
10421
|
actorId: work_document_changes_stringAttribute(mark.attrs.actorId)
|
|
10383
10422
|
} : {},
|
|
@@ -10385,9 +10424,27 @@ function collectDocumentChanges(document1) {
|
|
|
10385
10424
|
date: work_document_changes_stringAttribute(mark.attrs.date),
|
|
10386
10425
|
from: position,
|
|
10387
10426
|
to: position + node.nodeSize,
|
|
10388
|
-
text: node.text
|
|
10427
|
+
text: node.text ?? ''
|
|
10389
10428
|
});
|
|
10390
10429
|
});
|
|
10430
|
+
for (const [key, parts] of moveTextParts){
|
|
10431
|
+
const change = changes.get(key);
|
|
10432
|
+
if (!change) continue;
|
|
10433
|
+
const source = parts.from;
|
|
10434
|
+
const destination = parts.to;
|
|
10435
|
+
if (source && destination) {
|
|
10436
|
+
change.text = source.text === destination.text ? source.text : `${source.text} → ${destination.text}`;
|
|
10437
|
+
change.from = destination.from;
|
|
10438
|
+
change.to = destination.to;
|
|
10439
|
+
} else {
|
|
10440
|
+
const only = source ?? destination;
|
|
10441
|
+
if (only) {
|
|
10442
|
+
change.text = only.text;
|
|
10443
|
+
change.from = only.from;
|
|
10444
|
+
change.to = only.to;
|
|
10445
|
+
}
|
|
10446
|
+
}
|
|
10447
|
+
}
|
|
10391
10448
|
return Array.from(changes.values()).sort((left, right)=>left.from - right.from);
|
|
10392
10449
|
}
|
|
10393
10450
|
function resolveAllDocumentChanges(document1, decision) {
|
|
@@ -10409,7 +10466,7 @@ function resolveDocumentChangesTransaction(state, tr, type, decision, ids) {
|
|
|
10409
10466
|
const numberingSegments = numberingChangeSegments(state.doc).filter((segment)=>!ids || ids.has(segment.id));
|
|
10410
10467
|
const blockSegments = blockChangeSegments(state.doc).filter((segment)=>!ids || ids.has(segment.id));
|
|
10411
10468
|
if (!segments.length && !paragraphSegments.length && !numberingSegments.length && !blockSegments.length) return 0;
|
|
10412
|
-
if (paragraphSegments.some((segment)=>!parseDocumentParagraphFormatting(segment.before)) || numberingSegments.some((segment)=>!parseDocumentNumberingChange(segment.before)) || 'reject' === decision && segments.some((segment)=>'formatting' === segment.kind && !parseDocumentCharacterFormatting(segment.before))) return 0;
|
|
10469
|
+
if (paragraphSegments.some((segment)=>!parseDocumentParagraphFormatting(segment.before)) || numberingSegments.some((segment)=>!parseDocumentNumberingChange(segment.before)) || 'reject' === decision && segments.some((segment)=>'formatting' === segment.kind && !parseDocumentCharacterFormatting(segment.before)) || segments.some((segment)=>'move' === segment.kind && 'from' !== segment.moveRole && 'to' !== segment.moveRole) || !validMoveSegments(segments)) return 0;
|
|
10413
10470
|
closeHistory(tr);
|
|
10414
10471
|
tr.setMeta(documentChangePluginKey, {
|
|
10415
10472
|
decision
|
|
@@ -10425,6 +10482,12 @@ function resolveDocumentChangesTransaction(state, tr, type, decision, ids) {
|
|
|
10425
10482
|
if ('reject' === decision) formattingRejections.push(segment);
|
|
10426
10483
|
continue;
|
|
10427
10484
|
}
|
|
10485
|
+
if ('move' === segment.kind) {
|
|
10486
|
+
const source = 'from' === segment.moveRole;
|
|
10487
|
+
const removeContent = 'accept' === decision ? source : !source;
|
|
10488
|
+
(removeContent ? contentDeletions : markRemovals).push(segment);
|
|
10489
|
+
continue;
|
|
10490
|
+
}
|
|
10428
10491
|
const removeMark = 'accept' === decision && 'insertion' === segment.kind || 'reject' === decision && 'deletion' === segment.kind;
|
|
10429
10492
|
(removeMark ? markRemovals : contentDeletions).push(segment);
|
|
10430
10493
|
}
|
|
@@ -10507,16 +10570,35 @@ function documentChangeSegments(document1, type) {
|
|
|
10507
10570
|
if (!node.isText) return;
|
|
10508
10571
|
const mark = node.marks.find((candidate)=>candidate.type === type);
|
|
10509
10572
|
if (!mark) return;
|
|
10573
|
+
const moveRole = documentMoveRole(mark.attrs.moveRole);
|
|
10510
10574
|
segments.push({
|
|
10511
10575
|
id: work_document_changes_stringAttribute(mark.attrs.id) || `change-at-${position}`,
|
|
10512
10576
|
kind: changeKind(mark.attrs.kind),
|
|
10577
|
+
...moveRole ? {
|
|
10578
|
+
moveRole
|
|
10579
|
+
} : {},
|
|
10513
10580
|
from: position,
|
|
10514
10581
|
to: position + node.nodeSize,
|
|
10515
|
-
before: work_document_changes_stringAttribute(mark.attrs.before)
|
|
10582
|
+
before: work_document_changes_stringAttribute(mark.attrs.before),
|
|
10583
|
+
text: node.text ?? ''
|
|
10516
10584
|
});
|
|
10517
10585
|
});
|
|
10518
10586
|
return segments;
|
|
10519
10587
|
}
|
|
10588
|
+
function validMoveSegments(segments) {
|
|
10589
|
+
const moves = new Map();
|
|
10590
|
+
for (const segment of segments){
|
|
10591
|
+
if ('move' !== segment.kind) continue;
|
|
10592
|
+
if ('from' !== segment.moveRole && 'to' !== segment.moveRole) return false;
|
|
10593
|
+
const current = moves.get(segment.id) ?? {
|
|
10594
|
+
from: '',
|
|
10595
|
+
to: ''
|
|
10596
|
+
};
|
|
10597
|
+
current[segment.moveRole] += segment.text;
|
|
10598
|
+
moves.set(segment.id, current);
|
|
10599
|
+
}
|
|
10600
|
+
return Array.from(moves.values()).every(({ from, to })=>Boolean(from && to && from === to));
|
|
10601
|
+
}
|
|
10520
10602
|
function paragraphChangeSegments(document1) {
|
|
10521
10603
|
const segments = [];
|
|
10522
10604
|
document1.descendants((node, position)=>{
|
|
@@ -10613,9 +10695,12 @@ function work_document_changes_documentChangeMark(marks) {
|
|
|
10613
10695
|
return marks.find((mark)=>'documentChange' === mark.type.name);
|
|
10614
10696
|
}
|
|
10615
10697
|
function changeKind(value) {
|
|
10616
|
-
if ('deletion' === value || 'formatting' === value) return value;
|
|
10698
|
+
if ('deletion' === value || 'formatting' === value || 'move' === value) return value;
|
|
10617
10699
|
return 'insertion';
|
|
10618
10700
|
}
|
|
10701
|
+
function documentMoveRole(value) {
|
|
10702
|
+
return 'from' === value || 'to' === value ? value : null;
|
|
10703
|
+
}
|
|
10619
10704
|
function paragraphChangeAttribute(field, htmlName) {
|
|
10620
10705
|
const modelName = `paragraphChange${field[0]?.toUpperCase() ?? ''}${field.slice(1)}`;
|
|
10621
10706
|
return {
|
|
@@ -21439,9 +21524,9 @@ function changeIdentity(decision) {
|
|
|
21439
21524
|
return `${decision.changeKind}:${decision.changeId}`;
|
|
21440
21525
|
}
|
|
21441
21526
|
function office_document_collaboration_change_decisions_changeKind(value, shared) {
|
|
21442
|
-
if ('insertion' === value || 'deletion' === value || 'formatting' === value || 'paragraph-formatting' === value || 'numbering' === value) return value;
|
|
21527
|
+
if ('insertion' === value || 'deletion' === value || 'formatting' === value || 'paragraph-formatting' === value || 'numbering' === value || 'move' === value) return value;
|
|
21443
21528
|
if (shared) invalidSharedSidecars('tracked-change decision kind');
|
|
21444
|
-
invalidInputSidecars('an insertion, deletion, formatting, paragraph-formatting, or
|
|
21529
|
+
invalidInputSidecars('an insertion, deletion, formatting, paragraph-formatting, numbering, or move tracked-change kind');
|
|
21445
21530
|
}
|
|
21446
21531
|
function decisionAction(value, shared) {
|
|
21447
21532
|
if ('accept' === value || 'reject' === value) return value;
|
|
@@ -1,20 +1,66 @@
|
|
|
1
1
|
import type { Editor } from '@tiptap/core';
|
|
2
|
+
import type { Transaction } from '@tiptap/pm/state';
|
|
3
|
+
export interface ControlledCompositionRange {
|
|
4
|
+
from: number;
|
|
5
|
+
to: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ControlledCompositionSnapshot {
|
|
8
|
+
id: number;
|
|
9
|
+
range: ControlledCompositionRange | null;
|
|
10
|
+
text: string | null;
|
|
11
|
+
}
|
|
12
|
+
type CompositionSettledHandler = (editor: Editor, snapshot: ControlledCompositionSnapshot) => void;
|
|
2
13
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
14
|
+
* Tracks the browser-owned part of a controlled TipTap composition.
|
|
15
|
+
*
|
|
16
|
+
* WebKit can expose the provisional Latin spelling to ProseMirror, dispatch
|
|
17
|
+
* `compositionend`, and only then deliver the committed CJK text. Merely
|
|
18
|
+
* delaying `onChange` leaves both strings in the document. This controller
|
|
19
|
+
* keeps the original composition range mapped through every transaction and
|
|
20
|
+
* gives the owning editor one bounded opportunity to replace that range with
|
|
21
|
+
* the committed text after the browser has finished its DOM flush.
|
|
8
22
|
*/
|
|
9
23
|
export declare class ControlledEditorComposition {
|
|
10
24
|
private active;
|
|
25
|
+
private committedInputSeen;
|
|
26
|
+
private committedText;
|
|
27
|
+
private compositionData;
|
|
28
|
+
private deadline;
|
|
11
29
|
private generation;
|
|
30
|
+
private awaitingCommittedTransaction;
|
|
31
|
+
private range;
|
|
32
|
+
private rangeFrozen;
|
|
12
33
|
private settling;
|
|
34
|
+
private settleAt;
|
|
35
|
+
private settleHandler;
|
|
36
|
+
private settleEditor;
|
|
37
|
+
private sessionId;
|
|
13
38
|
private timer;
|
|
14
|
-
|
|
15
|
-
|
|
39
|
+
/** Start tracking the selection that the browser is about to compose over. */
|
|
40
|
+
start(editor?: Editor | null): void;
|
|
41
|
+
/**
|
|
42
|
+
* Keep the composition range aligned with ProseMirror's current document.
|
|
43
|
+
* This includes the transaction generated when a browser first exposes the
|
|
44
|
+
* provisional pinyin and the transaction generated by a late committed
|
|
45
|
+
* input event.
|
|
46
|
+
*/
|
|
47
|
+
recordTransaction(transaction: Transaction): void;
|
|
48
|
+
/** Observe beforeinput/input without taking ownership of the browser event. */
|
|
49
|
+
noteInput(event: Event): void;
|
|
50
|
+
/**
|
|
51
|
+
* End a composition. The two-argument form is retained for the Markdown and
|
|
52
|
+
* presentation editors; document editing passes compositionend.data as the
|
|
53
|
+
* middle argument so late WebKit input can be distinguished from typing.
|
|
54
|
+
*/
|
|
55
|
+
end(editor: Editor, dataOrHandler: string | null | undefined | CompositionSettledHandler, maybeHandler?: CompositionSettledHandler): void;
|
|
16
56
|
isBlocking(editor?: Editor | null): boolean;
|
|
17
57
|
destroy(): void;
|
|
58
|
+
private selectionRange;
|
|
59
|
+
private currentRangeText;
|
|
60
|
+
private snapshot;
|
|
61
|
+
private scheduleQuietSettlement;
|
|
18
62
|
private scheduleSettlement;
|
|
63
|
+
private clearSettlement;
|
|
19
64
|
private cancelTimer;
|
|
20
65
|
}
|
|
66
|
+
export {};
|
|
@@ -31,9 +31,11 @@ export interface DocumentPaginationPageDescriptorDerivation {
|
|
|
31
31
|
derivedPageCount: number;
|
|
32
32
|
}
|
|
33
33
|
export interface UseDocumentPaginationOptions {
|
|
34
|
+
compositionRevision?: number;
|
|
34
35
|
editor: Editor | null;
|
|
35
36
|
documentRevision: number;
|
|
36
37
|
enabled: boolean;
|
|
38
|
+
isComposing?: () => boolean;
|
|
37
39
|
layoutKey: string;
|
|
38
40
|
page: OfficeKernelPageMetrics;
|
|
39
41
|
selectionVersion: number;
|
|
@@ -49,7 +51,7 @@ export interface UseDocumentPaginationValue {
|
|
|
49
51
|
paginating: boolean;
|
|
50
52
|
resolveFieldContext: WorkDocumentFieldContextResolver | null;
|
|
51
53
|
}
|
|
52
|
-
export declare function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, page, selectionVersion, wasmUrl, layoutFonts, loadedLayoutFontIds, }: UseDocumentPaginationOptions): UseDocumentPaginationValue;
|
|
54
|
+
export declare function useDocumentPagination({ compositionRevision, editor, documentRevision, enabled, isComposing, layoutKey, page, selectionVersion, wasmUrl, layoutFonts, loadedLayoutFontIds, }: UseDocumentPaginationOptions): UseDocumentPaginationValue;
|
|
53
55
|
export declare function createDocumentFieldPaginationContextResolver(pagination: DocumentPaginationResult): WorkDocumentFieldContextResolver;
|
|
54
56
|
export declare function documentResizeObservationPositions(blocks: readonly MeasuredDocumentLayoutBlock[]): ReadonlyMap<HTMLElement, number>;
|
|
55
57
|
export declare function documentResizeObservationTargets(editorDom: HTMLElement, blocks: readonly MeasuredDocumentLayoutBlock[]): {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Mark } from '@tiptap/core';
|
|
2
2
|
import { type Node as ProseMirrorNode } from '@tiptap/pm/model';
|
|
3
|
-
import type { WorkDocumentChangeKind } from './work-types';
|
|
4
|
-
export type { WorkDocumentChangeKind } from './work-types';
|
|
3
|
+
import type { WorkDocumentChangeKind, WorkDocumentMoveRole } from './work-types';
|
|
4
|
+
export type { WorkDocumentChangeKind, WorkDocumentMoveRole, } from './work-types';
|
|
5
5
|
export interface WorkDocumentChangeIdentity {
|
|
6
6
|
id: string;
|
|
7
7
|
actorId?: string;
|
|
@@ -10,6 +10,8 @@ export interface WorkDocumentChangeIdentity {
|
|
|
10
10
|
}
|
|
11
11
|
export interface WorkDocumentChange extends WorkDocumentChangeIdentity {
|
|
12
12
|
kind: WorkDocumentChangeKind;
|
|
13
|
+
/** Present when a change is one side of a native Word move revision. */
|
|
14
|
+
moveRole?: WorkDocumentMoveRole;
|
|
13
15
|
from: number;
|
|
14
16
|
to: number;
|
|
15
17
|
text: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { WorkDocumentChangeIdentity, WorkDocumentChangeKind } from './work-document-changes';
|
|
1
|
+
import type { WorkDocumentChangeIdentity, WorkDocumentChangeKind, WorkDocumentMoveRole } from './work-document-changes';
|
|
2
2
|
export interface ImportedDocxChangeMarker extends WorkDocumentChangeIdentity {
|
|
3
3
|
kind: WorkDocumentChangeKind;
|
|
4
|
+
moveRole?: WorkDocumentMoveRole;
|
|
4
5
|
start: string;
|
|
5
6
|
end: string;
|
|
6
7
|
}
|
|
@@ -10,3 +11,11 @@ export interface ImportedDocxChangeMarkers {
|
|
|
10
11
|
export declare function markDocxTextChanges(document: Document): ImportedDocxChangeMarkers;
|
|
11
12
|
export declare function applyImportedDocxChangeMarkers(document: Document, markers: ImportedDocxChangeMarkers): void;
|
|
12
13
|
export declare function hasImportedDocxChangeMarkers(markers: ImportedDocxChangeMarkers): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Returns whether one move wrapper uses the bounded, text-only Word shape.
|
|
16
|
+
* Range-marker moves and relationship-bearing children intentionally remain on
|
|
17
|
+
* the compatibility path until their structural semantics are modelled.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isSupportedDocxMoveChange(element: Element): boolean;
|
|
20
|
+
/** Counts complete, paired move revisions in a WordprocessingML story. */
|
|
21
|
+
export declare function supportedDocxMovePairCount(document: Document): number;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type DocxMoveRevisionRole = 'from' | 'to';
|
|
2
|
+
export interface DocxMoveRevisionRegistration {
|
|
3
|
+
kind: 'move-from' | 'move-to';
|
|
4
|
+
wireId: number;
|
|
5
|
+
id: number;
|
|
6
|
+
author: string;
|
|
7
|
+
date: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DocxMoveRevisionPatch {
|
|
10
|
+
wireId: number;
|
|
11
|
+
id: number;
|
|
12
|
+
role: DocxMoveRevisionRole;
|
|
13
|
+
author: string;
|
|
14
|
+
date: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Allocates transient revision IDs while the `docx` package creates its
|
|
18
|
+
* ordinary `w:ins`/`w:del` wrappers. The post-pack patcher changes those
|
|
19
|
+
* wrappers to native `w:moveFrom`/`w:moveTo` elements and restores the shared
|
|
20
|
+
* logical revision ID. Negative wire IDs cannot collide with the positive
|
|
21
|
+
* IDs emitted for ordinary tracked changes.
|
|
22
|
+
*/
|
|
23
|
+
export declare class DocxMoveRevisionPatchCollector {
|
|
24
|
+
readonly patches: DocxMoveRevisionPatch[];
|
|
25
|
+
private readonly registrations;
|
|
26
|
+
private nextWireId;
|
|
27
|
+
register(element: HTMLElement, id: number, revisionDate?: string): DocxMoveRevisionRegistration | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Converts the transient wrappers emitted by `docx` into native Word move
|
|
31
|
+
* revisions. Every registered side must be emitted at least once; a missing
|
|
32
|
+
* side is treated as a programming or package-generation error rather than
|
|
33
|
+
* silently producing a half-move that Word would interpret differently.
|
|
34
|
+
*/
|
|
35
|
+
export declare function patchDocxMoveRevisions(buffer: ArrayBuffer, patches: readonly DocxMoveRevisionPatch[]): Promise<ArrayBuffer>;
|
|
@@ -106,7 +106,9 @@ export interface WorkDocumentContent {
|
|
|
106
106
|
comments?: WorkDocumentComment[];
|
|
107
107
|
bibliography?: WorkDocumentBibliography;
|
|
108
108
|
}
|
|
109
|
-
export type WorkDocumentChangeKind = 'insertion' | 'deletion' | 'formatting' | 'paragraph-formatting' | 'numbering';
|
|
109
|
+
export type WorkDocumentChangeKind = 'insertion' | 'deletion' | 'formatting' | 'paragraph-formatting' | 'numbering' | 'move';
|
|
110
|
+
/** The side of a native Word move revision represented by a text mark. */
|
|
111
|
+
export type WorkDocumentMoveRole = 'from' | 'to';
|
|
110
112
|
export type WorkDocumentChangeDecisionAction = 'accept' | 'reject';
|
|
111
113
|
/**
|
|
112
114
|
* Immutable audit record created when an editor accepts or rejects one
|
package/dist/office-kernel.wasm
CHANGED
|
Binary file
|
package/dist/styles.css
CHANGED
|
@@ -9256,6 +9256,10 @@ button.work-office-collaboration-participant:hover .work-office-collaboration-lo
|
|
|
9256
9256
|
border-left-color: #0f7b78;
|
|
9257
9257
|
}
|
|
9258
9258
|
|
|
9259
|
+
.work-document-change-item.move {
|
|
9260
|
+
border-left-color: #b06a1f;
|
|
9261
|
+
}
|
|
9262
|
+
|
|
9259
9263
|
.work-document-change-summary {
|
|
9260
9264
|
min-width: 0;
|
|
9261
9265
|
color: var(--a3s-ink);
|
|
@@ -9292,6 +9296,11 @@ button.work-office-collaboration-participant:hover .work-office-collaboration-lo
|
|
|
9292
9296
|
background: #e5f5f3;
|
|
9293
9297
|
}
|
|
9294
9298
|
|
|
9299
|
+
.work-document-change-list .work-document-change-item.move .work-document-change-summary > span {
|
|
9300
|
+
color: #8a4f0f;
|
|
9301
|
+
background: #fff3df;
|
|
9302
|
+
}
|
|
9303
|
+
|
|
9295
9304
|
.work-document-change-summary strong {
|
|
9296
9305
|
text-overflow: ellipsis;
|
|
9297
9306
|
white-space: nowrap;
|
|
@@ -283,6 +283,14 @@ DOCX import/export maps that model to strict or transitional `w:pPrChange`,
|
|
|
283
283
|
while Yjs stores the same node attributes and immutable decision audit without a
|
|
284
284
|
parallel transport model.
|
|
285
285
|
|
|
286
|
+
Text-only move revisions are a paired inline concern rather than two unrelated
|
|
287
|
+
insert/delete cards. A `move` identity keeps explicit `from` and `to` roles;
|
|
288
|
+
review navigation targets the destination side, while accept or reject validates
|
|
289
|
+
and resolves both equal-text sides in one transaction. Native DOCX import/export
|
|
290
|
+
maps the pair to strict or transitional `w:moveFrom`/`w:moveTo`, and the Yjs
|
|
291
|
+
sidecar stores one immutable `changeKind: "move"` decision record for the
|
|
292
|
+
complete intent.
|
|
293
|
+
|
|
286
294
|
Document comparison is a pure planning boundary before the editor transaction.
|
|
287
295
|
The current and imported TipTap/ProseMirror trees remain immutable while a
|
|
288
296
|
deterministic weighted block alignment pairs related paragraphs and headings;
|
|
@@ -291,7 +299,9 @@ and paragraph-property snapshots produce character- and paragraph-formatting
|
|
|
291
299
|
revisions. Stable semantic signatures seed generated identities, so the same
|
|
292
300
|
inputs and options produce the same ordering and IDs. Only a fully admitted
|
|
293
301
|
plan replaces the mounted document, and that replacement is one transaction,
|
|
294
|
-
one controlled publication, and one Undo record.
|
|
302
|
+
one controlled publication, and one Undo record. The planner does not infer
|
|
303
|
+
move pairs from arbitrary delete/insert similarities; native paired moves remain
|
|
304
|
+
reviewable when they already exist.
|
|
295
305
|
|
|
296
306
|
Combine validates rather than guesses. It rejects every imported revision on
|
|
297
307
|
an immutable reviewed snapshot and compares the resulting semantic signature
|
|
@@ -580,7 +590,9 @@ The public formatting fixture also exercises the non-windowed review boundary
|
|
|
580
590
|
with independent character- and paragraph-formatting cards. Focused A3S Test
|
|
581
591
|
suites reject each kind separately, verify that text and the other revision
|
|
582
592
|
remain intact, and prove full paragraph alignment, indentation, spacing, and
|
|
583
|
-
line-height restoration with clean browser diagnostics.
|
|
593
|
+
line-height restoration with clean browser diagnostics. Focused move fixtures
|
|
594
|
+
verify paired source/destination resolution, destination-only navigation,
|
|
595
|
+
immutable collaboration audit, and native DOCX reopen without marker leakage.
|
|
584
596
|
|
|
585
597
|
Spreadsheet now uses a persistent browser Rust/WASM calculation session. The
|
|
586
598
|
editor initializes it with a sparse workbook replacement, sends bounded cell
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a3s-lab/office",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Open-source collaborative browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"office",
|
|
@@ -87,12 +87,14 @@
|
|
|
87
87
|
"playground:preview": "rspress preview -c website/rspress.config.ts --host 127.0.0.1 --port 4175",
|
|
88
88
|
"playground:typecheck": "tsc --noEmit -p playground/tsconfig.json",
|
|
89
89
|
"playground:ime:webkit": "playwright test --config playwright.ime.config.ts",
|
|
90
|
+
"playground:testkit": "playwright test --config playwright.testkit.config.ts",
|
|
90
91
|
"playground:visual": "playwright test --config playwright.config.ts",
|
|
91
92
|
"playground:visual:docs-changelog": "playwright test visual-tests/docs-changelog.functional.spec.ts --config playwright.config.ts",
|
|
92
93
|
"playground:visual:home": "playwright test visual-tests/playground-home.functional.spec.ts --config playwright.config.ts",
|
|
93
94
|
"playground:visual:document-emphasis": "playwright test visual-tests/document-emphasis.functional.spec.ts --config playwright.config.ts",
|
|
94
95
|
"playground:visual:document-index": "playwright test visual-tests/document-index.functional.spec.ts --config playwright.config.ts",
|
|
95
96
|
"playground:visual:document-comparison": "playwright test visual-tests/document-comparison.functional.spec.ts --config playwright.config.ts",
|
|
97
|
+
"playground:visual:document-move-revision": "playwright test visual-tests/document-move-revision.functional.spec.ts --config playwright.config.ts",
|
|
96
98
|
"playground:visual:document-hidden-text": "playwright test visual-tests/document-hidden-text.functional.spec.ts --config playwright.config.ts",
|
|
97
99
|
"playground:visual:document-legacy-text-effects": "playwright test visual-tests/document-legacy-text-effects.functional.spec.ts --config playwright.config.ts",
|
|
98
100
|
"playground:visual:document-proofing-languages": "playwright test visual-tests/document-proofing-languages.functional.spec.ts --config playwright.config.ts",
|
|
@@ -140,8 +142,12 @@
|
|
|
140
142
|
"performance:pdf": "bun .a3s-test/performance/generate-fixtures.ts --pdf-only && bun .a3s-test/performance/benchmark-pdf.ts --url http://127.0.0.1:4175/playground/ --runs 3 --timeout-ms 120000",
|
|
141
143
|
"performance:presentation": "bun .a3s-test/performance/generate-fixtures.ts --presentations-only && bun .a3s-test/performance/benchmark-presentation.ts --url http://127.0.0.1:4175/playground/ --runs 3 --timeout-ms 120000 --modes default,content-visibility",
|
|
142
144
|
"test": "rstest",
|
|
143
|
-
"test:e2e": "bun run test:e2e:fixtures &&
|
|
144
|
-
"test:e2e:check": "bun run test:e2e:fixtures &&
|
|
145
|
+
"test:e2e": "bun run test:e2e:fixtures && A3S_TEST_SUITE=all bash scripts/run-a3s-test-web-gate.sh && bun run test:e2e:testkit",
|
|
146
|
+
"test:e2e:check": "bun run test:e2e:fixtures && bun scripts/check-a3s-test-suites.ts",
|
|
147
|
+
"test:e2e:testkit": "bash scripts/run-a3s-testkit-ui-gate.sh",
|
|
148
|
+
"test:e2e:testkit:check": "A3S_TEST_SUITE=office-testkit-ui.acl bun scripts/check-a3s-test-suites.ts",
|
|
149
|
+
"test:e2e:all": "bun run test:e2e",
|
|
150
|
+
"test:e2e:all:check": "bun run test:e2e:check",
|
|
145
151
|
"test:e2e:writer-text-box": "bun run test:e2e:fixtures && A3S_TEST_SUITE=tests/e2e/word-text-box-phone.acl bash scripts/run-a3s-test-web-gate.sh",
|
|
146
152
|
"test:e2e:writer-text-box:check": "a3s-test check tests/e2e/word-text-box-phone.acl --json",
|
|
147
153
|
"test:e2e:writer-content-control": "bun run test:e2e:fixtures && A3S_TEST_SUITE=tests/e2e/word-content-controls-phone.acl bash scripts/run-a3s-test-web-gate.sh",
|
|
@@ -293,6 +299,8 @@
|
|
|
293
299
|
"test:e2e:writer-formatting-revision:check": "a3s-test check tests/e2e/word-formatting-revision.acl --json",
|
|
294
300
|
"test:e2e:writer-paragraph-formatting-revision": "a3s-test run tests/e2e/word-paragraph-formatting-revision.acl --json",
|
|
295
301
|
"test:e2e:writer-paragraph-formatting-revision:check": "a3s-test check tests/e2e/word-paragraph-formatting-revision.acl --json",
|
|
302
|
+
"test:e2e:writer-move-revision": "a3s-test run tests/e2e/word-move-revision.acl --json",
|
|
303
|
+
"test:e2e:writer-move-revision:check": "a3s-test check tests/e2e/word-move-revision.acl --json",
|
|
296
304
|
"test:e2e:writer-status": "a3s-test run tests/e2e/word-status-bar.acl --json",
|
|
297
305
|
"test:e2e:writer-status:check": "a3s-test check tests/e2e/word-status-bar.acl --json",
|
|
298
306
|
"test:wps-layout": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/test-wps-layout-parity.ps1",
|