docxmlater 10.0.3 → 10.0.4

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.
@@ -279,6 +279,15 @@ export interface ParagraphFormatting {
279
279
  /** Date when the paragraph mark was deleted */
280
280
  date: Date;
281
281
  };
282
+ /** Paragraph mark insertion tracking (for inserted ¶ symbols) */
283
+ paragraphMarkInsertion?: {
284
+ /** Unique revision ID */
285
+ id: number;
286
+ /** Author who inserted the paragraph mark */
287
+ author: string;
288
+ /** Date when the paragraph mark was inserted */
289
+ date: Date;
290
+ };
282
291
  /** True when the original XML had numId=0 (explicitly suppressed numbering) */
283
292
  numberingSuppressed?: boolean;
284
293
  }
@@ -2800,6 +2809,42 @@ export class Paragraph {
2800
2809
  return !!this.formatting.paragraphMarkDeletion;
2801
2810
  }
2802
2811
 
2812
+ /**
2813
+ * Marks the paragraph mark (¶ symbol) as inserted via tracked changes.
2814
+ * This adds a `w:ins` element inside `w:pPr/w:rPr` indicating the insertion
2815
+ * of the ¶ symbol.
2816
+ *
2817
+ * @param id - Unique revision ID
2818
+ * @param author - Author who inserted the paragraph mark
2819
+ * @param date - Date when the insertion occurred (defaults to now)
2820
+ * @returns This paragraph for chaining
2821
+ */
2822
+ markParagraphMarkAsInserted(id: number, author: string, date?: Date): this {
2823
+ this.formatting.paragraphMarkInsertion = {
2824
+ id,
2825
+ author,
2826
+ date: date || new Date(),
2827
+ };
2828
+ return this;
2829
+ }
2830
+
2831
+ /**
2832
+ * Clears the paragraph mark insertion marker
2833
+ * @returns This paragraph for chaining
2834
+ */
2835
+ clearParagraphMarkInsertion(): this {
2836
+ delete this.formatting.paragraphMarkInsertion;
2837
+ return this;
2838
+ }
2839
+
2840
+ /**
2841
+ * Checks if the paragraph mark is marked as inserted
2842
+ * @returns True if the paragraph mark is inserted
2843
+ */
2844
+ isParagraphMarkInserted(): boolean {
2845
+ return !!this.formatting.paragraphMarkInsertion;
2846
+ }
2847
+
2803
2848
  /**
2804
2849
  * Converts the paragraph to WordprocessingML XML element
2805
2850
  *
@@ -3137,7 +3182,8 @@ export class Paragraph {
3137
3182
  // Per CT_PPr, w:rPr comes after all CT_PPrBase elements and before w:sectPr/w:pPrChange
3138
3183
  if (
3139
3184
  this.formatting.paragraphMarkRunProperties ||
3140
- this.formatting.paragraphMarkDeletion
3185
+ this.formatting.paragraphMarkDeletion ||
3186
+ this.formatting.paragraphMarkInsertion
3141
3187
  ) {
3142
3188
  const rPrChildren: XMLElement[] = [];
3143
3189
 
@@ -3156,6 +3202,20 @@ export class Paragraph {
3156
3202
  }
3157
3203
  }
3158
3204
 
3205
+ // Per CT_ParaRPr schema, w:ins precedes w:del in the sequence
3206
+ // Add insertion marker if the paragraph mark is inserted (w:ins)
3207
+ // Per ECMA-376 Part 1 §17.13.5.18 - tracks insertion of paragraph mark
3208
+ if (this.formatting.paragraphMarkInsertion) {
3209
+ const ins = this.formatting.paragraphMarkInsertion;
3210
+ rPrChildren.push(
3211
+ XMLBuilder.wSelf("ins", {
3212
+ "w:id": ins.id.toString(),
3213
+ "w:author": ins.author,
3214
+ "w:date": formatDateForXml(ins.date),
3215
+ })
3216
+ );
3217
+ }
3218
+
3159
3219
  // Add deletion marker if the paragraph mark is deleted (w:del)
3160
3220
  // Per ECMA-376 Part 1 §17.13.5.14 - tracks deletion of paragraph mark
3161
3221
  if (this.formatting.paragraphMarkDeletion) {
@@ -568,6 +568,26 @@ function acceptRevisionsInParagraph(
568
568
  }
569
569
  }
570
570
 
571
+ // Clear paragraph mark deletion tracking if accepting deletions
572
+ // This removes the w:del element from w:pPr/w:rPr
573
+ if (options.acceptDeletions) {
574
+ const formatting = paragraph.getFormatting();
575
+ if (formatting.paragraphMarkDeletion) {
576
+ paragraph.clearParagraphMarkDeletion();
577
+ result.deletionsAccepted++;
578
+ }
579
+ }
580
+
581
+ // Clear paragraph mark insertion tracking if accepting insertions
582
+ // This removes the w:ins element from w:pPr/w:rPr
583
+ if (options.acceptInsertions) {
584
+ const formatting = paragraph.getFormatting();
585
+ if (formatting.paragraphMarkInsertion) {
586
+ paragraph.clearParagraphMarkInsertion();
587
+ result.insertionsAccepted++;
588
+ }
589
+ }
590
+
571
591
  return result;
572
592
  }
573
593
 
@@ -576,7 +596,15 @@ function acceptRevisionsInParagraph(
576
596
  */
577
597
  export function paragraphHasRevisions(paragraph: Paragraph): boolean {
578
598
  const content = paragraph.getContent();
579
- return content.some((item) => item instanceof Revision);
599
+ if (content.some((item) => item instanceof Revision)) {
600
+ return true;
601
+ }
602
+ // Also check paragraph mark revision markers in w:pPr/w:rPr
603
+ const formatting = paragraph.getFormatting();
604
+ if (formatting.paragraphMarkDeletion || formatting.paragraphMarkInsertion) {
605
+ return true;
606
+ }
607
+ return false;
580
608
  }
581
609
 
582
610
  /**
@@ -238,6 +238,76 @@ export class SelectiveRevisionAcceptor {
238
238
 
239
239
  // Replace paragraph content with the transformed content
240
240
  paragraph.setContent(newContent);
241
+
242
+ // Handle paragraph mark revision markers (w:del/w:ins in w:pPr/w:rPr)
243
+ // Both accept and reject clear the marker — these are metadata-only markers (no content),
244
+ // so there is no content to add or remove, only the marker itself to clear.
245
+ const formatting = paragraph.getFormatting();
246
+ if (formatting.paragraphMarkDeletion) {
247
+ const del = formatting.paragraphMarkDeletion;
248
+ if (this.matchesMarkerCriteria(del, criteria)) {
249
+ paragraph.clearParagraphMarkDeletion();
250
+ processedIds.push(del.id.toString());
251
+ } else {
252
+ remainingIds.push(del.id.toString());
253
+ }
254
+ }
255
+ if (formatting.paragraphMarkInsertion) {
256
+ const ins = formatting.paragraphMarkInsertion;
257
+ if (this.matchesMarkerCriteria(ins, criteria)) {
258
+ paragraph.clearParagraphMarkInsertion();
259
+ processedIds.push(ins.id.toString());
260
+ } else {
261
+ remainingIds.push(ins.id.toString());
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Check if a paragraph mark revision marker matches the given criteria.
268
+ * Simplified version of matchesCriteria for non-Revision marker objects.
269
+ */
270
+ private static matchesMarkerCriteria(
271
+ marker: { id: number; author: string; date: Date },
272
+ criteria: SelectionCriteria
273
+ ): boolean {
274
+ // If no criteria specified, match nothing
275
+ if (
276
+ !criteria.ids &&
277
+ !criteria.types &&
278
+ !criteria.authors &&
279
+ !criteria.dateRange &&
280
+ !criteria.categories &&
281
+ !criteria.custom
282
+ ) {
283
+ return false;
284
+ }
285
+
286
+ // Filter by IDs
287
+ if (criteria.ids && !criteria.ids.includes(marker.id)) {
288
+ return false;
289
+ }
290
+
291
+ // Filter by authors
292
+ if (criteria.authors && !criteria.authors.includes(marker.author)) {
293
+ return false;
294
+ }
295
+
296
+ // Filter by date range
297
+ if (criteria.dateRange) {
298
+ if (marker.date < criteria.dateRange.start || marker.date > criteria.dateRange.end) {
299
+ return false;
300
+ }
301
+ }
302
+
303
+ // types, categories, and custom filters don't apply to paragraph mark markers
304
+ // (they are not Revision objects with a type/category)
305
+ // If criteria only specifies types/categories/custom, markers won't match
306
+ if (criteria.types || criteria.categories || criteria.custom) {
307
+ return false;
308
+ }
309
+
310
+ return true;
241
311
  }
242
312
 
243
313
  /**