@ansonlai/docx-redline-js 0.6.0 → 0.6.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.
@@ -183,6 +183,80 @@ function collectDocumentCommentAnchors(paragraphNodes, revisionView) {
183
183
  return anchors;
184
184
  }
185
185
 
186
+ function selectInspectionParagraphs(allParagraphs, options) {
187
+ let matches = allParagraphs;
188
+ if (options.revisedOnly) matches = matches.filter(item => item.hasRevisions);
189
+ if (options.inTable != null) matches = matches.filter(item => item.inTable === !!options.inTable);
190
+ if (options.skipEmpty) matches = matches.filter(item => item.text.length > 0);
191
+ if (options.search) {
192
+ const needle = String(options.search).toLowerCase();
193
+ matches = matches.filter(item => item.text.toLowerCase().includes(needle));
194
+ }
195
+ if (Array.isArray(options.indexes)) {
196
+ const indexes = new Set(options.indexes);
197
+ matches = matches.filter(item => indexes.has(item.index));
198
+ }
199
+ let rangeStart = 1;
200
+ let rangeEnd = allParagraphs.length;
201
+ if (options.range) {
202
+ rangeStart = Number(options.range.start ?? options.range[0]);
203
+ rangeEnd = Number(options.range.end ?? options.range[1]);
204
+ matches = matches.filter(item => item.index >= rangeStart && item.index <= rangeEnd);
205
+ }
206
+
207
+ const totalMatches = matches.length;
208
+ const after = Number.isInteger(options.after) && options.after > 0 ? options.after : null;
209
+ const remaining = after == null ? matches : matches.filter(item => item.index > after);
210
+ const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : null;
211
+ const selectedMatches = limit == null ? remaining : remaining.slice(0, limit);
212
+ const truncated = selectedMatches.length < remaining.length;
213
+ const around = Number.isInteger(options.around) && options.around > 0 ? options.around : 0;
214
+ const exposeSelection = !!options.search || around > 0 || limit != null || after != null;
215
+
216
+ let paragraphs = selectedMatches;
217
+ if (around > 0 && options.search) {
218
+ const directIndexes = new Set(selectedMatches.map(item => item.index));
219
+ const contextFor = new Map();
220
+ for (const match of selectedMatches) {
221
+ const start = Math.max(rangeStart, match.index - around);
222
+ const end = Math.min(rangeEnd, match.index + around);
223
+ for (let index = start; index <= end; index++) {
224
+ if (directIndexes.has(index)) continue;
225
+ const owners = contextFor.get(index) || [];
226
+ owners.push(match.index);
227
+ contextFor.set(index, owners);
228
+ }
229
+ }
230
+ const returnedIndexes = new Set([...directIndexes, ...contextFor.keys()]);
231
+ paragraphs = allParagraphs
232
+ .filter(item => returnedIndexes.has(item.index))
233
+ .map(item => directIndexes.has(item.index)
234
+ ? { ...item, selectionRole: 'match' }
235
+ : { ...item, selectionRole: 'context', contextFor: contextFor.get(item.index) || [] });
236
+ } else if (exposeSelection) {
237
+ paragraphs = selectedMatches.map(item => ({ ...item, selectionRole: 'match' }));
238
+ }
239
+
240
+ return {
241
+ paragraphs,
242
+ ...(exposeSelection ? {
243
+ selection: {
244
+ ...(options.search ? { search: String(options.search), caseSensitive: false } : {}),
245
+ totalMatches,
246
+ returnedMatches: selectedMatches.length,
247
+ returnedParagraphs: paragraphs.length,
248
+ truncated,
249
+ nextAfter: truncated && selectedMatches.length > 0
250
+ ? selectedMatches[selectedMatches.length - 1].index
251
+ : null,
252
+ ...(limit != null ? { limit } : {}),
253
+ ...(after != null ? { after } : {}),
254
+ ...(around > 0 ? { around } : {})
255
+ }
256
+ } : {})
257
+ };
258
+ }
259
+
186
260
  /** Read-only, stable document-parts inspection for agents and package adapters. */
187
261
  export function inspectDocumentParts(parts, options = {}) {
188
262
  const documentPart = parseXml(parts?.documentXml, 'word/document.xml', true);
@@ -213,12 +287,17 @@ export function inspectDocumentParts(parts, options = {}) {
213
287
  const index = zeroIndex + 1;
214
288
  const provision = list?.label && list.format !== 'bullet' ? list.label : null;
215
289
  const headingText = nearestHeading?.text || null;
216
- const humanReference = [provision, headingText, text.slice(0, options.excerptLength || 120)].filter(Boolean).join(' — ');
290
+ const excerpt = text.slice(0, options.excerptLength || 120);
291
+ const humanReference = level
292
+ ? text
293
+ : (provision
294
+ ? [provision, headingText].filter(Boolean).join(' — ')
295
+ : (headingText ? [headingText, excerpt].filter(Boolean).join(' — ') : excerpt));
217
296
  const segments = extractParagraphRevisionSegments(paragraph);
218
297
  return {
219
298
  index, ref: `P${index}`, paragraphId: getParagraphId(paragraph),
220
299
  fingerprint: createParagraphFingerprint(paragraph, { text, index, revisionView }), revisionView,
221
- text, exactText: text, excerpt: text.slice(0, options.excerptLength || 120), humanReference, inTable: hasAncestor(paragraph, 'tc'), table: structure.table,
300
+ text, exactText: text, excerpt, humanReference, provision, inTable: hasAncestor(paragraph, 'tc'), table: structure.table,
222
301
  styleId, headingLevel: level, nearestHeading, list, structuralReferences: structure.references, hasRevisions: authors.length > 0, revisionAuthors: authors, commentIds: ids,
223
302
  segments
224
303
  };
@@ -229,12 +308,8 @@ export function inspectDocumentParts(parts, options = {}) {
229
308
  definition.anchoredText ??= commentAnchors.get(id) || paragraph.text;
230
309
  comments.set(id, definition);
231
310
  }
232
- if (options.revisedOnly) paragraphs = paragraphs.filter(item => item.hasRevisions);
233
- if (options.inTable != null) paragraphs = paragraphs.filter(item => item.inTable === !!options.inTable);
234
- if (options.skipEmpty) paragraphs = paragraphs.filter(item => item.text.length > 0);
235
- if (options.search) { const needle = String(options.search).toLowerCase(); paragraphs = paragraphs.filter(item => item.text.toLowerCase().includes(needle)); }
236
- if (Array.isArray(options.indexes)) { const indexes = new Set(options.indexes); paragraphs = paragraphs.filter(item => indexes.has(item.index)); }
237
- if (options.range) { const start = Number(options.range.start ?? options.range[0]); const end = Number(options.range.end ?? options.range[1]); paragraphs = paragraphs.filter(item => item.index >= start && item.index <= end); }
311
+ const selected = selectInspectionParagraphs(paragraphs, options);
312
+ paragraphs = selected.paragraphs;
238
313
  const allRevisionAuthors = [...new Set(paragraphs.flatMap(item => item.revisionAuthors))].sort();
239
314
  const coveredEntries = extractDocumentPartsEntries(parts);
240
315
  const coveredParts = coveredEntries.map(e => e.name).sort();
@@ -251,6 +326,7 @@ export function inspectDocumentParts(parts, options = {}) {
251
326
  revisionToken,
252
327
  coveredParts,
253
328
  paragraphs,
329
+ ...(selected.selection ? { selection: selected.selection } : {}),
254
330
  comments: [...comments.values()],
255
331
  revisionAuthors: allRevisionAuthors,
256
332
  commentAuthors: [...new Set([...comments.values()].map(item => item.author).filter(Boolean))].sort(),