@ansonlai/docx-redline-js 0.8.0 → 0.8.1
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/CHANGELOG.md +539 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1083 -163
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +107 -107
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +1083 -163
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1083 -163
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +69 -8
- package/docs/schemas/document-operations.schema.json +29 -0
- package/document/docx-document.js +416 -22
- package/engine/reconstruction-writer.js +18 -0
- package/index.d.ts +598 -575
- package/index.js +349 -348
- package/node/cli-help.js +5 -5
- package/node/cli.js +760 -754
- package/node/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
- package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
- package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
- package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
- package/scripts/generate-word-package-integrity-cases.mjs +141 -0
- package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
- package/scripts/word-com-package-integrity.ps1 +98 -0
- package/services/batch-operation-orchestrator.js +262 -259
- package/services/comment-replies.js +96 -95
- package/services/comment-thread-parts.js +280 -0
- package/services/document-inspection.js +366 -365
- package/services/document-operation-applier.js +230 -202
- package/services/document-operation-contract.js +17 -2
- package/services/error-recovery.js +6 -0
- package/services/headers-footers.js +206 -0
- package/services/operation-batch-compiler.js +1 -0
- package/services/operation-preflight.js +231 -218
- package/services/package-parts.js +80 -0
- package/services/standalone-docx-plumbing.js +631 -569
- package/services/standalone-operation-runner.d.ts +19 -3
|
@@ -399,7 +399,7 @@ algorithms:
|
|
|
399
399
|
- Shell/file wrappers invoke the `docx-redline` CLI and preserve its JSON stdout
|
|
400
400
|
and exit code.
|
|
401
401
|
- Node byte-oriented wrappers use `openDocx`, `inspect`, `applyOperations`,
|
|
402
|
-
`resolveRevisions`, `deleteComments`, and `toBuffer`.
|
|
402
|
+
`resolveRevisions`, `deleteComments`, `resolveComment`, and `toBuffer`.
|
|
403
403
|
- XML-only hosts use the standalone runner and remain responsible for package
|
|
404
404
|
artifacts returned beside `documentXml`.
|
|
405
405
|
- Paragraph/range hosts use root exports and remain responsible for deciding how
|
|
@@ -609,7 +609,9 @@ normalize or reconstruct `exactText`. Operation files follow
|
|
|
609
609
|
- `preflight` checks targets, anchors, revisions, conflicts, authors, and needed artifacts without mutation (read-only).
|
|
610
610
|
- `apply` applies an operation file transactionally with automatic rollback and internal markup validation.
|
|
611
611
|
- `accept` and `reject` resolve revisions selected by `--author` or `--all-authors`.
|
|
612
|
-
- `delete-comments` removes matching definitions and document anchors together
|
|
612
|
+
- `delete-comments` removes matching definitions and document anchors together, selected by `--author`,
|
|
613
|
+
`--all-authors`, or `--comment-id <id[,id]>` (ids from `inspect`). Deleting a thread root also deletes its
|
|
614
|
+
replies; unknown ids fail with `COMMENT_NOT_FOUND`.
|
|
613
615
|
- A whole-paragraph delete stops with `COMMENTED_CONTENT_DELETE` when the
|
|
614
616
|
paragraph has an existing comment. Surface the returned reviewer and comment
|
|
615
617
|
text for human follow-up; do not silently convert this into comment removal.
|
|
@@ -656,8 +658,57 @@ To reply inside an existing Word comment thread, use the comment ID returned by
|
|
|
656
658
|
{ "type": "comment_reply", "parentCommentId": "8", "commentContent": "Agreed; updated.", "author": "Editor" }
|
|
657
659
|
```
|
|
658
660
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
+
A reply is defined in `word/comments.xml` and linked to its thread in `word/commentsExtended.xml`
|
|
662
|
+
(`w15:paraIdParent`, keyed on the last paragraph of each comment, as Word does). Word only displays a
|
|
663
|
+
reply that also has its own `commentRangeStart`, `commentRangeEnd` and `commentReference` around the same
|
|
664
|
+
range as its thread, so the operation adds those markers next to the thread's existing ones. A reply to a
|
|
665
|
+
reply joins the root thread. If the parent has no anchor in the body the operation fails with
|
|
666
|
+
`PARENT_ANCHOR_NOT_FOUND`.
|
|
667
|
+
|
|
668
|
+
#### Headers and footers
|
|
669
|
+
|
|
670
|
+
`inspect().headersFooters` lists every header/footer part the document uses, with its paragraph text:
|
|
671
|
+
`path`, `kind`, `type` (`default`/`first`/`even`), `appliesToSections`, `active`, `hasFields`. Add `part` to an
|
|
672
|
+
operation to edit one instead of the body; everything else in the operation is unchanged:
|
|
673
|
+
|
|
674
|
+
```json
|
|
675
|
+
{ "type": "replace", "part": { "kind": "footer", "type": "default", "section": 0 },
|
|
676
|
+
"target": { "exactText": "Draft v1" }, "modified": "Draft v2" }
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
`part` is a selector (`kind`, optional `type` defaulting to `default`, optional zero-based `section`) or a part path such as
|
|
680
|
+
`word/footer2.xml`. Omitting `part` always targets the body; headers and footers are opt-in. Rules:
|
|
681
|
+
|
|
682
|
+
- A footer or header shared by several sections is one part; the result lists `partSections`. A section with no reference of its
|
|
683
|
+
own inherits the previous section's part (Word's "link to previous"), and `section` finds it.
|
|
684
|
+
- Several distinct parts matching a selector fail with `PART_AMBIGUOUS` (add `section` or use the path); none fail with
|
|
685
|
+
`PART_NOT_FOUND`. A bad selector rejects the whole request before anything is written.
|
|
686
|
+
- Fields are atomic. An edit that would change a `PAGE`/`NUMPAGES`/`DATE` field's text fails with `FIELD_EDIT_REFUSED`; edit the
|
|
687
|
+
text around the field instead.
|
|
688
|
+
- `comment`, `comment_reply` and `comment_resolve` with `part` fail with `COMMENT_IN_HEADER_FOOTER` (Word has no comments in headers or footers).
|
|
689
|
+
- Body and part operations can share a batch; results keep the original operation indexes and carry `part`. Atomic batches roll
|
|
690
|
+
back across body and parts. Revision ids stay unique across the whole package.
|
|
691
|
+
- `accept` / `reject` (API and CLI) also resolve tracked changes inside headers and footers.
|
|
692
|
+
- Creating a header/footer that does not exist is not supported.
|
|
693
|
+
- `exactText` in a header or footer is the whole paragraph text as `headersFooters[].paragraphs` shows it, including the
|
|
694
|
+
current result of any field (for example `Page 1`).
|
|
695
|
+
|
|
696
|
+
#### Resolving comment threads
|
|
697
|
+
|
|
698
|
+
Resolve (or reopen with `"resolved": false`) the thread containing a comment id from `inspect`:
|
|
699
|
+
|
|
700
|
+
```json
|
|
701
|
+
{ "type": "comment_resolve", "commentId": "8" }
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The API form is `await doc.resolveComment(commentId, { resolved })`. Word resolves whole threads, so any id in
|
|
705
|
+
the thread updates the root and every reply together; a reply added to a resolved thread is written resolved,
|
|
706
|
+
as Word does. Only `word/commentsExtended.xml` changes. Resolving an already-resolved thread is `no_change`.
|
|
707
|
+
Unknown ids fail with `COMMENT_NOT_FOUND`, in `preflight` as well as `apply`. Operations run in batch order, so
|
|
708
|
+
`comment_reply` followed by `comment_resolve` closes the thread including the new reply. Like any comment
|
|
709
|
+
operation it is refused with `part`.
|
|
710
|
+
|
|
711
|
+
Resolving and deleting other reviewers' comments are review decisions: do them only when the user asked.
|
|
661
712
|
|
|
662
713
|
#### Legacy skill wrapper migration
|
|
663
714
|
|
|
@@ -724,6 +775,11 @@ When the CLI or runner returns an error code, follow these specific recovery act
|
|
|
724
775
|
| `REJECTED_INSERTION_STATE_REQUIRED` / `UNSAFE_REVISION_BOUNDARY` | An explicit rejected-view insertion did not resolve to supported plain run text inside a wholly foreign-deleted paragraph. | Do not fall back to a generic edit. Narrow the exact anchor/offset, or handle comments, bookmarks, fields, hyperlinks, moves, or other structural boundaries manually. |
|
|
725
776
|
| `GENERATED_OOXML_INVALID` | The operation introduced a new validation error relative to its baseline. | Treat the operation as unapplied and inspect `generatedIssues`; correct the generating operation or builder rather than repairing or accepting the source document's unrelated baseline defects. |
|
|
726
777
|
| `UNSAFE_DELETED_TABLE_ROW` / `UNSUPPORTED_MOVE_REVISION` / `SECTION_BREAK_PARAGRAPH` / `UNSAFE_PARAGRAPH_PLACEMENT` | Paragraph restoration cannot preserve the source structural boundary safely. | Do not retry as an ordinary redline. Resolve the row/move/section/placement condition manually or narrow the restoration to a safe paragraph. |
|
|
778
|
+
| `PART_NOT_FOUND` / `PART_AMBIGUOUS` | The `part` selector matched no header/footer, or several. | Re-run `inspect` and use `headersFooters[].path`, or add `section`. |
|
|
779
|
+
| `FIELD_EDIT_REFUSED` | The edit would change a field (PAGE, NUMPAGES, DATE) in a header or footer. | Edit the text around the field. |
|
|
780
|
+
| `COMMENT_IN_HEADER_FOOTER` | A comment operation carried `part`. | Comment on body text instead. |
|
|
781
|
+
| `COMMENT_NOT_FOUND` | `comment_resolve` or `delete-comments --comment-id` named an id that is not in the document. | Re-run `inspect` and use `comments[].id`. |
|
|
782
|
+
| `PARENT_COMMENT_NOT_FOUND` / `PARENT_ANCHOR_NOT_FOUND` | A reply's parent id is unknown, or the parent has no anchor in the body. | Re-run `inspect`; reply to a comment that is anchored in the body. |
|
|
727
783
|
| `COMMENTED_CONTENT_MERGE` / `COMMENTED_CONTENT_DELETE` | Operation would overwrite, revert, or delete content with comments. | Fails closed to prevent orphaned comment threads. Report the comment author and text to the user; resolve the comment before re-editing. |
|
|
728
784
|
| `INVALID_OPERATION` | Operation object violates schema or has incompatible fields. | Validate the JSON structure against [`document-operations.schema.json`](schemas/document-operations.schema.json) before targeting is attempted. |
|
|
729
785
|
| `STRUCTURED_CONTENT_INVALID` | Malformed Markdown table or structure in replacement text. | Ensure tables include a separator row (`\| --- \| --- \|`) and consistent column counts; do not downgrade to raw text. |
|
|
@@ -732,11 +788,16 @@ When the CLI or runner returns an error code, follow these specific recovery act
|
|
|
732
788
|
|
|
733
789
|
#### Document Scope & Boundary Invariants
|
|
734
790
|
|
|
735
|
-
The `docx-redline` engine and CLI operate
|
|
791
|
+
The `docx-redline` engine and CLI operate on the **main document body** and, when an operation sets `part`, on
|
|
792
|
+
**existing header and footer parts**:
|
|
736
793
|
|
|
737
|
-
- **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments,
|
|
738
|
-
|
|
739
|
-
-
|
|
794
|
+
- **Supported Content**: Body paragraphs, numbered/bulleted lists, tables and table cells, comments, comment replies,
|
|
795
|
+
resolving/reopening comment threads, and text in existing headers and footers (see *Headers and footers*).
|
|
796
|
+
- **Unsupported Content**: Footnotes, endnotes, floating text boxes, shape drawings, images, watermarks, embedded macros,
|
|
797
|
+
creating a header or footer that does not exist, editing field codes or results (`PAGE`, `NUMPAGES`, `DATE`), and
|
|
798
|
+
comments in headers or footers (Word has none).
|
|
799
|
+
- Do not attempt to target footnote citations or text boxes with `docx-redline`. Header/footer text is edited only with
|
|
800
|
+
an explicit `part`; body searches never match it.
|
|
740
801
|
|
|
741
802
|
### Convert paragraph text into a Word list
|
|
742
803
|
|
|
@@ -71,6 +71,22 @@
|
|
|
71
71
|
}
|
|
72
72
|
]
|
|
73
73
|
},
|
|
74
|
+
"headerFooterPart": {
|
|
75
|
+
"description": "Targets a header or footer instead of the body. Use a part path from inspect().headersFooters, or a selector. Comments are not supported in headers or footers.",
|
|
76
|
+
"oneOf": [
|
|
77
|
+
{ "type": "string", "minLength": 1, "examples": ["word/footer2.xml"] },
|
|
78
|
+
{
|
|
79
|
+
"type": "object",
|
|
80
|
+
"required": ["kind"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"kind": { "enum": ["header", "footer"] },
|
|
83
|
+
"type": { "enum": ["default", "first", "even"], "description": "Defaults to default." },
|
|
84
|
+
"section": { "type": "integer", "minimum": 0, "description": "Zero-based section; required when several sections have their own part." }
|
|
85
|
+
},
|
|
86
|
+
"additionalProperties": false
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
74
90
|
"base": {
|
|
75
91
|
"type": "object",
|
|
76
92
|
"required": ["type", "target"],
|
|
@@ -78,6 +94,7 @@
|
|
|
78
94
|
"operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
79
95
|
"captureKey": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
80
96
|
"target": { "$ref": "#/$defs/target" }, "targetRef": { "type": ["integer", "string", "null"] },
|
|
97
|
+
"part": { "$ref": "#/$defs/headerFooterPart" },
|
|
81
98
|
"author": { "type": "string", "minLength": 1 }, "generateRedlines": { "type": "boolean" },
|
|
82
99
|
"existingRevisions": { "enum": ["merge-same-author", "slice-cross-author", "reject-input", "accept-all-first", "accept-all-first-keep-normalized"] },
|
|
83
100
|
"pairReplacements": { "type": "boolean" },
|
|
@@ -109,6 +126,18 @@
|
|
|
109
126
|
},
|
|
110
127
|
"additionalProperties": false
|
|
111
128
|
},
|
|
129
|
+
{
|
|
130
|
+
"type": "object",
|
|
131
|
+
"description": "Resolve or reopen the comment thread containing commentId. Resolved is thread-level in Word, so the root and every reply change together. Body-only; not allowed with part.",
|
|
132
|
+
"required": ["type", "commentId"],
|
|
133
|
+
"properties": {
|
|
134
|
+
"type": { "const": "comment_resolve" },
|
|
135
|
+
"operationId": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
136
|
+
"commentId": { "type": ["integer", "string"] },
|
|
137
|
+
"resolved": { "type": "boolean", "default": true }
|
|
138
|
+
},
|
|
139
|
+
"additionalProperties": false
|
|
140
|
+
},
|
|
112
141
|
{
|
|
113
142
|
"allOf": [ { "$ref": "#/$defs/base" }, { "type": "object", "required": ["replacements"], "properties": { "type": { "enum": ["redline", "replace"] }, "replacements": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/localizedReplacement" } } } } ]
|
|
114
143
|
},
|