@stll/folio-core 0.52.0 → 0.53.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/README.md +23 -0
- package/dist/ai-edits/apply.d.ts +26 -1
- package/dist/ai-edits/apply.js +1519 -105
- package/dist/ai-edits/batch-claims.d.ts +130 -0
- package/dist/ai-edits/batch-claims.js +244 -0
- package/dist/ai-edits/blockRange.js +9 -3
- package/dist/ai-edits/character-boundaries.d.ts +43 -0
- package/dist/ai-edits/character-boundaries.js +95 -0
- package/dist/ai-edits/clean-text.d.ts +33 -3
- package/dist/ai-edits/clean-text.js +52 -6
- package/dist/ai-edits/comment-lifecycle.d.ts +18 -0
- package/dist/ai-edits/comment-lifecycle.js +52 -0
- package/dist/ai-edits/headless.d.ts +71 -7
- package/dist/ai-edits/headless.js +381 -41
- package/dist/ai-edits/index.d.ts +2 -1
- package/dist/ai-edits/index.js +2 -1
- package/dist/ai-edits/minimal-replacement.d.ts +21 -6
- package/dist/ai-edits/minimal-replacement.js +134 -15
- package/dist/ai-edits/newListNumbering.d.ts +23 -0
- package/dist/ai-edits/newListNumbering.js +94 -0
- package/dist/ai-edits/note-references.d.ts +96 -0
- package/dist/ai-edits/note-references.js +139 -0
- package/dist/ai-edits/pending-suggestions.d.ts +79 -0
- package/dist/ai-edits/pending-suggestions.js +306 -0
- package/dist/ai-edits/read.d.ts +1 -1
- package/dist/ai-edits/read.js +56 -43
- package/dist/ai-edits/result-validation.d.ts +29 -0
- package/dist/ai-edits/result-validation.js +164 -0
- package/dist/ai-edits/snapshot.d.ts +36 -3
- package/dist/ai-edits/snapshot.js +177 -23
- package/dist/ai-edits/table-cell-mutations.d.ts +1 -1
- package/dist/ai-edits/table-cell-mutations.js +4 -1
- package/dist/ai-edits/table-geometry.d.ts +1 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +38 -16
- package/dist/ai-edits/table-row-column-mutations.js +61 -26
- package/dist/ai-edits/types.d.ts +155 -16
- package/dist/compare/compare.d.ts +1 -1
- package/dist/compare/compare.js +65 -16
- package/dist/compare/content-alignment.js +13 -2
- package/dist/compare/content-types.d.ts +7 -0
- package/dist/compare/content.d.ts +4 -1
- package/dist/compare/content.js +6 -1
- package/dist/compare/formatting.js +2 -1
- package/dist/compare/inline-atoms.d.ts +21 -4
- package/dist/compare/inline-atoms.js +138 -16
- package/dist/compare/plan.js +10 -5
- package/dist/compare/scenario.js +14 -3
- package/dist/compare/section-boundary-properties.js +1 -1
- package/dist/compare/types.d.ts +4 -2
- package/dist/compare/types.js +2 -1
- package/dist/compare/verification.d.ts +1 -1
- package/dist/compare/verification.js +15 -3
- package/dist/controller/committedLayoutDiff.d.ts +25 -0
- package/dist/controller/committedLayoutDiff.js +71 -0
- package/dist/controller/contentControlWidgetController.d.ts +1 -1
- package/dist/controller/folioEditor.d.ts +1 -1
- package/dist/controller/fontReadiness.d.ts +70 -3
- package/dist/controller/fontReadiness.js +100 -10
- package/dist/controller/headerFooterEditorManager.js +6 -1
- package/dist/controller/layoutPipeline.d.ts +12 -10
- package/dist/controller/layoutPipeline.js +62 -9
- package/dist/controller/layoutRunOptions.d.ts +24 -0
- package/dist/controller/layoutRunOptions.js +17 -0
- package/dist/controller/layoutScheduler.d.ts +19 -14
- package/dist/controller/layoutScheduler.js +15 -12
- package/dist/controller/layoutSession.d.ts +26 -2
- package/dist/controller/layoutSession.js +1 -1
- package/dist/controller/noteEditorManager.js +8 -3
- package/dist/document-operations.d.ts +11 -2
- package/dist/document-operations.js +230 -21
- package/dist/docx/altChunk.d.ts +8 -0
- package/dist/docx/altChunk.js +17 -0
- package/dist/docx/attributeRemainder.d.ts +1 -1
- package/dist/docx/blockContentParser.d.ts +64 -1
- package/dist/docx/blockContentParser.js +17 -4
- package/dist/docx/blockCustomXmlShell.d.ts +7 -0
- package/dist/docx/blockCustomXmlShell.js +14 -0
- package/dist/docx/blockPlainText.js +7 -0
- package/dist/docx/commentAnchorIndex.js +1 -1
- package/dist/docx/commentRangeIntegrity.js +8 -3
- package/dist/docx/commentReferenceNormalization.js +1 -1
- package/dist/docx/commentReplyMarkers.js +20 -1
- package/dist/docx/compatibility.d.ts +1 -1
- package/dist/docx/compatibility.js +34 -13
- package/dist/docx/containerChildren.gen.d.ts +1 -1
- package/dist/docx/containerChildren.gen.js +1 -0
- package/dist/docx/documentParser.js +12 -12
- package/dist/docx/drawingIdNormalization.d.ts +3 -1
- package/dist/docx/drawingIdNormalization.js +6 -1
- package/dist/docx/ensureParaIds.js +119 -97
- package/dist/docx/footnoteParser.js +8 -4
- package/dist/docx/headerFooterParser.js +3 -3
- package/dist/docx/headerFooterReferenceNormalization.d.ts +20 -1
- package/dist/docx/headerFooterReferenceNormalization.js +52 -27
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/index.d.ts +1 -1
- package/dist/docx/listNumberingInstances.d.ts +59 -0
- package/dist/docx/listNumberingInstances.js +249 -0
- package/dist/docx/normalizeBaseDirection.js +2 -1
- package/dist/docx/opaqueCarrier.d.ts +7 -0
- package/dist/docx/opaqueCarrier.js +38 -0
- package/dist/docx/packageParts.js +4 -1
- package/dist/docx/paragraphParser.js +91 -24
- package/dist/docx/paragraphPropertySource.d.ts +4 -2
- package/dist/docx/paragraphPropertySource.js +7 -1
- package/dist/docx/paragraphTraversal.js +1 -0
- package/dist/docx/parseWarningMessage.js +4 -1
- package/dist/docx/parser.js +173 -20
- package/dist/docx/rezip.js +27 -5
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sdtPropertiesPatch.js +3 -0
- package/dist/docx/selectiveSave.js +16 -9
- package/dist/docx/selectiveXmlPatch.js +106 -37
- package/dist/docx/serializer/blockCustomXmlSerializer.d.ts +5 -0
- package/dist/docx/serializer/blockCustomXmlSerializer.js +4 -0
- package/dist/docx/serializer/documentSerializer.js +2 -0
- package/dist/docx/serializer/headerFooterSerializer.js +2 -0
- package/dist/docx/serializer/noteSerializer.js +3 -1
- package/dist/docx/serializer/paragraphSerializer.js +33 -9
- package/dist/docx/serializer/tableSerializer.js +25 -38
- package/dist/docx/server/createBilingualDocument.js +3 -3
- package/dist/docx/tableParser.d.ts +157 -1
- package/dist/docx/tableParser.js +89 -10
- package/dist/docx/textBoxParser.js +1 -1
- package/dist/docx/unzip.d.ts +2 -1
- package/dist/docx/unzip.js +1 -1
- package/dist/docx/wordprocessingPrefixes.d.ts +78 -0
- package/dist/docx/wordprocessingPrefixes.js +286 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +136 -0
- package/dist/i18n/messages/catalogs.gen.js +153 -17
- package/dist/i18n/messages/messages.gen.d.ts +8 -0
- package/dist/internal/compare/inline-presentation.d.ts +2 -1
- package/dist/internal/compare/inline-presentation.js +11 -10
- package/dist/internal/headlessRevisionResolutionGuard.d.ts +2 -8
- package/dist/internal/headlessRevisionResolutionGuard.js +2 -11
- package/dist/internal/indexedPositionMap.d.ts +11 -0
- package/dist/internal/indexedPositionMap.js +42 -0
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
- package/dist/internal/revisionResolutionEdits.d.ts +13 -0
- package/dist/internal/revisionResolutionEdits.js +19 -0
- package/dist/internal/revisionResolutionInline.d.ts +24 -0
- package/dist/internal/{headlessRevisionResolution.js → revisionResolutionInline.js} +61 -43
- package/dist/internal/revisionResolutionStep.d.ts +34 -0
- package/dist/internal/revisionResolutionStep.js +282 -0
- package/dist/internal/revisionResolutionTracking.d.ts +11 -0
- package/dist/internal/revisionResolutionTracking.js +124 -0
- package/dist/internal/wholeStoryRevisionResolution.d.ts +56 -0
- package/dist/internal/wholeStoryRevisionResolution.js +530 -0
- package/dist/layout-bridge/convert/markupViewFlow.d.ts +20 -0
- package/dist/layout-bridge/convert/markupViewFlow.js +123 -0
- package/dist/layout-bridge/convert/tableConversion.js +1 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +10 -1
- package/dist/layout-engine/layoutInstrumentation.d.ts +9 -1
- package/dist/layout-engine/layoutInstrumentation.js +4 -1
- package/dist/layout-engine/measure/cache.d.ts +18 -1
- package/dist/layout-engine/measure/cache.js +31 -1
- package/dist/layout-engine/measure/font-metrics.worker.js +3 -7
- package/dist/layout-engine/measure/measureContainer.js +7 -47
- package/dist/layout-engine/measure/measureHelpers.d.ts +6 -1
- package/dist/layout-engine/measure/measureHelpers.js +22 -2
- package/dist/layout-engine/measure/measureWorker.js +2 -3
- package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +6 -7
- package/dist/layout-engine/measure/measureWorkerProtocol.js +1 -2
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/renderPage.js +1 -0
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/managers/ContextMenuManager.js +5 -2
- package/dist/managers/types.d.ts +3 -0
- package/dist/markdown/fromMarkdown.js +2 -1
- package/dist/markdown/internals.d.ts +11 -1
- package/dist/markdown/internals.js +24 -3
- package/dist/markdown/renderBlock.js +39 -9
- package/dist/markdown/renderParagraph.d.ts +13 -1
- package/dist/markdown/renderParagraph.js +85 -38
- package/dist/markdown/renderRuns.js +3 -10
- package/dist/markdown/renderTable.js +24 -12
- package/dist/markdown/trailers.js +2 -2
- package/dist/markdown/types.d.ts +29 -7
- package/dist/paged-layout/incrementalMeasure.d.ts +1 -2
- package/dist/paged-layout/incrementalMeasure.js +1 -9
- package/dist/paged-layout/transactionDirtyRange.d.ts +1 -3
- package/dist/paged-layout/transactionDirtyRange.js +29 -41
- package/dist/panel-layout.d.ts +83 -0
- package/dist/panel-layout.js +96 -0
- package/dist/prosemirror/anchoredTextBoxes.d.ts +37 -0
- package/dist/prosemirror/anchoredTextBoxes.js +156 -0
- package/dist/prosemirror/attrs/index.d.ts +6 -2
- package/dist/prosemirror/attrs/index.js +172 -1
- package/dist/prosemirror/commands/comments.d.ts +7 -8
- package/dist/prosemirror/commands/comments.js +241 -207
- package/dist/prosemirror/commands/contentControls.d.ts +1 -1
- package/dist/prosemirror/commands/formatPainter.d.ts +1 -1
- package/dist/prosemirror/commands/formatting.d.ts +1 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/paragraph.d.ts +7 -1
- package/dist/prosemirror/commands/paragraph.js +9 -1
- package/dist/prosemirror/commands/paragraphBookmarkJoin.d.ts +13 -0
- package/dist/prosemirror/commands/paragraphBookmarkJoin.js +22 -0
- package/dist/prosemirror/commands/pastePlainText.d.ts +1 -1
- package/dist/prosemirror/commands/resolveAllTableChanges.d.ts +40 -0
- package/dist/prosemirror/commands/resolveAllTableChanges.js +615 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.d.ts +15 -0
- package/dist/prosemirror/commands/resolveNodePropertyChangeAttrs.js +46 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.d.ts +16 -0
- package/dist/prosemirror/commands/resolveParagraphProperties.js +59 -0
- package/dist/prosemirror/commands/sectionBreak.js +2 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/contentControlRevisions.d.ts +1 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +181 -31
- package/dist/prosemirror/conversion/toProseDoc.js +39 -4
- package/dist/prosemirror/emptyFieldResultRuns.d.ts +18 -0
- package/dist/prosemirror/emptyFieldResultRuns.js +33 -0
- package/dist/prosemirror/extensions/ExtensionManager.d.ts +1 -1
- package/dist/prosemirror/extensions/ExtensionManager.js +3 -1
- package/dist/prosemirror/extensions/StarterKit.js +3 -1
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +12 -0
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +3 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +161 -123
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +19 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +33 -20
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +31 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +270 -99
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +3 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +2 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.d.ts +12 -0
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +126 -0
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +21 -5
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.d.ts +21 -18
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +37 -17
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +7 -2
- package/dist/prosemirror/extensions/marks/markUtils.js +8 -2
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +10 -0
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +35 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.d.ts +6 -0
- package/dist/prosemirror/extensions/nodes/BlockCustomXmlExtension.js +52 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +3 -3
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +50 -15
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +4 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +19 -18
- package/dist/prosemirror/extensions/types.d.ts +4 -1
- package/dist/prosemirror/findReplaceSelection.js +1 -1
- package/dist/prosemirror/index.d.ts +3 -2
- package/dist/prosemirror/index.js +3 -2
- package/dist/prosemirror/indexedNodeLookup.d.ts +17 -0
- package/dist/prosemirror/indexedNodeLookup.js +103 -0
- package/dist/prosemirror/listAutoformatMarkers.d.ts +19 -0
- package/dist/prosemirror/listAutoformatMarkers.js +98 -0
- package/dist/prosemirror/listInstanceReferences.d.ts +12 -0
- package/dist/prosemirror/listInstanceReferences.js +72 -0
- package/dist/prosemirror/listLabels.d.ts +24 -0
- package/dist/prosemirror/listLabels.js +111 -0
- package/dist/prosemirror/listNumbering.d.ts +110 -0
- package/dist/prosemirror/listNumbering.js +345 -0
- package/dist/prosemirror/listRenderingAttrs.d.ts +11 -2
- package/dist/prosemirror/listRenderingAttrs.js +8 -1
- package/dist/prosemirror/markupViewProjection.d.ts +95 -0
- package/dist/prosemirror/markupViewProjection.js +83 -0
- package/dist/prosemirror/numberingAttr.d.ts +9 -1
- package/dist/prosemirror/numberingAttr.js +9 -1
- package/dist/prosemirror/outlineLevelAttr.js +1 -1
- package/dist/prosemirror/paragraphIndentation.d.ts +10 -1
- package/dist/prosemirror/paragraphIndentation.js +29 -7
- package/dist/prosemirror/paragraphMarkJoin.d.ts +19 -0
- package/dist/prosemirror/paragraphMarkJoin.js +24 -0
- package/dist/prosemirror/plugins/createDocScanPlugin.d.ts +1 -1
- package/dist/prosemirror/plugins/documentNumbering.d.ts +27 -1
- package/dist/prosemirror/plugins/documentNumbering.js +97 -11
- package/dist/prosemirror/plugins/documentStyles.d.ts +10 -1
- package/dist/prosemirror/plugins/documentStyles.js +14 -1
- package/dist/prosemirror/plugins/suggestionMode.d.ts +16 -2
- package/dist/prosemirror/plugins/suggestionMode.js +116 -52
- package/dist/prosemirror/plugins/templateDirectives.d.ts +1 -1
- package/dist/prosemirror/plugins/transactionInvariants.d.ts +32 -0
- package/dist/prosemirror/plugins/transactionInvariants.js +86 -0
- package/dist/prosemirror/positionSweep.d.ts +24 -0
- package/dist/prosemirror/positionSweep.js +222 -0
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +1 -1
- package/dist/prosemirror/rejoinRunCarriers.d.ts +1 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +33 -1
- package/dist/prosemirror/replacedAnnotations.js +170 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +1 -1
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/index.js +3 -0
- package/dist/prosemirror/schema/nodes.d.ts +42 -11
- package/dist/prosemirror/sectionMarkEdits.d.ts +20 -0
- package/dist/prosemirror/sectionMarkEdits.js +59 -0
- package/dist/prosemirror/storyListNumbering.d.ts +17 -0
- package/dist/prosemirror/storyListNumbering.js +102 -0
- package/dist/prosemirror/tableGridMutation.d.ts +48 -2
- package/dist/prosemirror/tableGridMutation.js +106 -1
- package/dist/prosemirror/validation.js +5 -1
- package/dist/redline.js +93 -9
- package/dist/render-dom/BodySelectionOverlay.d.ts +4 -1
- package/dist/render-dom/BodySelectionOverlay.js +3 -2
- package/dist/utils/findReplace.js +2 -2
- package/dist/utils/mergeDocumentContent.js +1 -1
- package/dist/utils/noteReferenceLabels.d.ts +36 -0
- package/dist/utils/noteReferenceLabels.js +32 -0
- package/dist/utils/replaceText.js +1 -1
- package/dist/version-comparison.d.ts +1 -0
- package/dist/version-comparison.js +1 -0
- package/package.json +3 -2
- package/dist/internal/headlessRevisionResolution.d.ts +0 -23
package/dist/ai-edits/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import { FolioAIEditApplyOutcome, FolioAIEditView, FolioRevisionStamp, FolioWord
|
|
|
5
5
|
import { DocPositionRange, clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange } from "./blockRange.js";
|
|
6
6
|
import { buildAnnotatedBlockText } from "./clean-text.js";
|
|
7
7
|
import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationResultBase, FolioDocumentOperationStatus, FolioDocumentOperationStory, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
|
|
8
|
+
import { FOLIO_PENDING_SUGGESTION_VERSION, FolioPendingSuggestionLoadResult, FolioPendingSuggestionRecord, FolioPendingSuggestionRegistry, FolioPendingSuggestionStaleReason, createPendingSuggestionSourceSnapshot } from "./pending-suggestions.js";
|
|
8
9
|
import { FolioCommentAnchor, FolioReviewChange, FolioReviewChangeKind, getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
|
|
9
10
|
import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
10
11
|
import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioApplyDocumentOperationsToStoryOptions, FolioDocumentStory, FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, FolioEditableDocumentStoryHandle, FolioReadReviewedStoryOptions, FolioResolveReviewedStoryOptions, FolioResolvedReviewedView, FolioReviewedStory, FolioReviewedView, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
|
|
11
12
|
import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
|
|
12
13
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
13
|
-
export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
|
|
14
|
+
export { type ApplyFolioDocumentOperationsOptions, type DocPositionRange, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_PENDING_SUGGESTION_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIEditView, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIInsertFormattingScope, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioAITextRangeHandle, type FolioApplyDocumentOperationsToStoryOptions, type FolioCommentAnchor, type FolioContentParagraphKind, type FolioDocumentNavigationTarget, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocumentOutline, type FolioDocumentOutlineEntry, type FolioDocumentSection, type FolioDocumentSectionHandle, type FolioDocumentSectionReadResult, type FolioDocumentStory, type FolioDocumentStoryHandle, FolioDocumentStoryNotFoundError, type FolioEditableDocumentStoryHandle, type FolioPendingSuggestionLoadResult, type FolioPendingSuggestionRecord, FolioPendingSuggestionRegistry, type FolioPendingSuggestionStaleReason, type FolioReadReviewedStoryOptions, type FolioResolveReviewedStoryOptions, type FolioResolvedReviewedView, type FolioReviewChange, type FolioReviewChangeKind, type FolioReviewedStory, type FolioReviewedView, type FolioRevisionStamp, type FolioWordDiffOptions, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, createPendingSuggestionSourceSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
|
package/dist/ai-edits/index.js
CHANGED
|
@@ -4,8 +4,9 @@ import { applyFolioAIEditOperations } from "./apply.js";
|
|
|
4
4
|
import { clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange } from "./blockRange.js";
|
|
5
5
|
import { buildAnnotatedBlockText } from "./clean-text.js";
|
|
6
6
|
import { FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, UnsupportedFolioReviewedViewError, isFolioResolvedReviewedView, isFolioReviewedView } from "./headless.js";
|
|
7
|
+
import { FOLIO_PENDING_SUGGESTION_VERSION, FolioPendingSuggestionRegistry, createPendingSuggestionSourceSnapshot } from "./pending-suggestions.js";
|
|
7
8
|
import { getCommentAnchorsFromDoc, getTrackedChangesFromDoc } from "./read.js";
|
|
8
9
|
import { getFolioDocumentOutline, readFolioDocumentSection } from "./scoped-reading.js";
|
|
9
10
|
import { createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, hashFolioAIBlockText, isFolioAIContentBlock, normalizeFolioAIBlockText } from "./snapshot.js";
|
|
10
11
|
import { WORD_DIFF_GRANULARITIES, diffWordSegments } from "./word-diff.js";
|
|
11
|
-
export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
|
|
12
|
+
export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FOLIO_PENDING_SUGGESTION_VERSION, FOLIO_RESOLVED_REVIEWED_VIEWS, FOLIO_REVIEWED_VIEWS, FolioDocumentStoryNotFoundError, FolioPendingSuggestionRegistry, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, UnsupportedFolioReviewedViewError, WORD_DIFF_GRANULARITIES, applyFolioAIEditOperations, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, buildAnnotatedBlockText, clampRangeToDocSize, createFolioAIEditSnapshot, createFolioAIEditSnapshotWithStyleResolver, createFolioAITextRangeHandle, createPendingSuggestionSourceSnapshot, diffWordSegments, getCommentAnchorsFromDoc, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioDocumentOutline, getFolioParaIdFromBlockId, getTrackedChangesFromDoc, hashFolioAIBlockText, isFolioAIContentBlock, isFolioDocumentOperationModeSupported, isFolioResolvedReviewedView, isFolioReviewedView, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, readFolioDocumentSection, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange };
|
|
@@ -36,11 +36,13 @@ declare const shortestTokenDiff: (before: readonly string[], after: readonly str
|
|
|
36
36
|
*
|
|
37
37
|
* Words (with their leading whitespace, and edge punctuation on its own; see
|
|
38
38
|
* `tokenizeWords`) are aligned by the shortest edit script, and each changed
|
|
39
|
-
* stretch is then trimmed to
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* both
|
|
43
|
-
*
|
|
39
|
+
* stretch is then trimmed: to whole words it shares at either end, and once
|
|
40
|
+
* what is left lies within one word on both sides, to the letters that
|
|
41
|
+
* actually differ. Appending a letter to a word that spans two runs leaves
|
|
42
|
+
* both runs alone, and a rewrite of several words never keeps a letter two of
|
|
43
|
+
* them merely happen to share. A rewrite past {@link MAX_TOKEN_EDITS}, or a
|
|
44
|
+
* diff that does not reconstruct both strings exactly (it never should), is
|
|
45
|
+
* one change over the whole span, trimmed the same way.
|
|
44
46
|
*/
|
|
45
47
|
declare const planTextChanges: (source: string, replacement: string) => TextChange[];
|
|
46
48
|
/**
|
|
@@ -58,7 +60,20 @@ declare const changesFromSegments: (segments: readonly WordDiffSegment[]) => Tex
|
|
|
58
60
|
* `spans` are in the same coordinates as the changes (the source span's).
|
|
59
61
|
*/
|
|
60
62
|
declare const widenChangesToAtomicSpans: (source: string, changes: readonly TextChange[], spans: readonly AtomicTextSpan[]) => TextChange[];
|
|
63
|
+
/**
|
|
64
|
+
* `changes` cut around every atomic span (a field result) that `kept`, another
|
|
65
|
+
* plan of the same replacement, leaves untouched.
|
|
66
|
+
*
|
|
67
|
+
* A redline's changes are cut for a reader and may swallow a field whose
|
|
68
|
+
* displayed text the replacement repeats; rewriting it deletes the field and
|
|
69
|
+
* inserts its text as plain text, which the direct edit, keeping the field,
|
|
70
|
+
* never does. Cutting the change around the field keeps it in both modes,
|
|
71
|
+
* and a piece of the cut that no longer changes anything is dropped. Where
|
|
72
|
+
* the field's text does not stand in the change's new text exactly where
|
|
73
|
+
* `kept` puts it, the change is left whole.
|
|
74
|
+
*/
|
|
75
|
+
declare const keepAtomicSpans: (source: string, replacement: string, changes: readonly TextChange[], kept: readonly TextChange[], spans: readonly AtomicTextSpan[]) => TextChange[];
|
|
61
76
|
/** `source` with `changes` applied; the invariant the planner owes. */
|
|
62
77
|
declare const applyTextChanges: (source: string, changes: readonly TextChange[]) => string;
|
|
63
78
|
//#endregion
|
|
64
|
-
export { AtomicTextSpan, MAX_TOKEN_EDITS, TextChange, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
79
|
+
export { AtomicTextSpan, MAX_TOKEN_EDITS, TextChange, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, keepAtomicSpans, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
@@ -36,14 +36,24 @@ import { tokenizeWords } from "./word-diff.js";
|
|
|
36
36
|
* A replacement carrying inline emphasis markup (`**bold**`) states its own
|
|
37
37
|
* formatting and still replaces the match whole in direct mode.
|
|
38
38
|
*
|
|
39
|
+
* A change across several words is trimmed only to whole words it shares
|
|
40
|
+
* with the replacement at either end ({@link planTextChanges}): the letters
|
|
41
|
+
* two different words happen to share are not the same text, and keeping one
|
|
42
|
+
* would leave part of a new word in the formatting of the word it replaced.
|
|
43
|
+
*
|
|
39
44
|
* Tracked-changes and suggested modes cut their changes from the redline diff
|
|
40
45
|
* instead ({@link changesFromSegments}: word or character granularity, as the
|
|
41
46
|
* caller asked), and map them onto the document by the same rules. A change
|
|
42
47
|
* marks exactly the characters it removes as a deletion, which keeps their
|
|
43
|
-
* runs, and writes its text as an insertion after them
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
48
|
+
* runs, and writes its text as an insertion after them. The insertion's
|
|
49
|
+
* formatting is not decided by the redline's cut: every character of it takes
|
|
50
|
+
* the formatting the direct edit gives that character, so accepting the
|
|
51
|
+
* redline leaves exactly what the direct edit writes, although the redline
|
|
52
|
+
* rewrites whole words the direct edit only touches. Nothing outside a change
|
|
53
|
+
* is wrapped in a revision.
|
|
54
|
+
*
|
|
55
|
+
* Whichever mode applies it, a replacement that rewrites characters of more
|
|
56
|
+
* than one formatting reports `uniformReplacementFormatting` in its receipt.
|
|
47
57
|
*
|
|
48
58
|
* This module is the pure half: clean-text offsets in, changes out. The
|
|
49
59
|
* applier maps each change onto the document.
|
|
@@ -64,15 +74,64 @@ const commonSuffixLength = (a, b) => {
|
|
|
64
74
|
while (length < limit && a.charCodeAt(a.length - 1 - length) === b.charCodeAt(b.length - 1 - length)) length++;
|
|
65
75
|
return length > 0 && isLowSurrogate(a.charCodeAt(a.length - length)) ? length - 1 : length;
|
|
66
76
|
};
|
|
67
|
-
/**
|
|
77
|
+
/** A letter, digit, combining mark or connector: what a word is made of. */
|
|
78
|
+
const WORD_CHARACTER = /^[\p{L}\p{N}\p{M}\p{Pc}]$/u;
|
|
79
|
+
const codePointBefore = (text, index) => {
|
|
80
|
+
const low = text.charCodeAt(index - 1);
|
|
81
|
+
return index >= 2 && isLowSurrogate(low) && isHighSurrogate(text.charCodeAt(index - 2)) ? text.slice(index - 2, index) : text.slice(index - 1, index);
|
|
82
|
+
};
|
|
83
|
+
const codePointAt = (text, index) => String.fromCodePoint(text.codePointAt(index) ?? 0);
|
|
84
|
+
/** Whether `index` separates two words of `text` rather than two letters of one. */
|
|
85
|
+
const isWordBoundary = (text, index) => index <= 0 || index >= text.length || !WORD_CHARACTER.test(codePointBefore(text, index)) || !WORD_CHARACTER.test(codePointAt(text, index));
|
|
86
|
+
/** At most one word, with whatever separators around it. */
|
|
87
|
+
const SINGLE_WORD = /^[^\p{L}\p{N}\p{M}\p{Pc}]*[\p{L}\p{N}\p{M}\p{Pc}]*[^\p{L}\p{N}\p{M}\p{Pc}]*$/u;
|
|
88
|
+
/**
|
|
89
|
+
* The longest length up to `length` that ends (`fromEnd` false) or starts
|
|
90
|
+
* (`fromEnd` true) on a word boundary of both strings.
|
|
91
|
+
*/
|
|
92
|
+
const wordAlignedLength = (left, right, length, fromEnd) => {
|
|
93
|
+
for (let candidate = length; candidate > 0; candidate--) {
|
|
94
|
+
const leftIndex = fromEnd ? left.length - candidate : candidate;
|
|
95
|
+
const rightIndex = fromEnd ? right.length - candidate : candidate;
|
|
96
|
+
if (isWordBoundary(left, leftIndex) && isWordBoundary(right, rightIndex)) return candidate;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* `change` without the characters its deleted and inserted text share at either
|
|
102
|
+
* end.
|
|
103
|
+
*
|
|
104
|
+
* Within one word, letter by letter: `Supplier` to `Suppliers` inserts one `s`
|
|
105
|
+
* and leaves both runs of a word split across two alone. A change spanning
|
|
106
|
+
* several words keeps only whole words it shares at an end: the `s` that
|
|
107
|
+
* `agrees` and `performs` happen to end with is a coincidence, and keeping it
|
|
108
|
+
* would leave the last letter of `performs` in the formatting `agrees` had
|
|
109
|
+
* while the rest of the word takes the formatting of the text it replaces.
|
|
110
|
+
*/
|
|
68
111
|
const trimChange = (source, change) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
112
|
+
let removed = source.slice(change.start, change.end);
|
|
113
|
+
let inserted = change.text;
|
|
114
|
+
let prefix = 0;
|
|
115
|
+
let suffix = 0;
|
|
116
|
+
for (const granularity of ["word", "letter"]) {
|
|
117
|
+
const withinOneWord = SINGLE_WORD.test(removed) && SINGLE_WORD.test(inserted);
|
|
118
|
+
if (granularity === "letter" && !withinOneWord) break;
|
|
119
|
+
const sharedPrefix = commonPrefixLength(removed, inserted);
|
|
120
|
+
const nextPrefix = withinOneWord ? sharedPrefix : wordAlignedLength(removed, inserted, sharedPrefix, false);
|
|
121
|
+
removed = removed.slice(nextPrefix);
|
|
122
|
+
inserted = inserted.slice(nextPrefix);
|
|
123
|
+
const sharedSuffix = commonSuffixLength(removed, inserted);
|
|
124
|
+
const nextSuffix = withinOneWord ? sharedSuffix : wordAlignedLength(removed, inserted, sharedSuffix, true);
|
|
125
|
+
removed = removed.slice(0, removed.length - nextSuffix);
|
|
126
|
+
inserted = inserted.slice(0, inserted.length - nextSuffix);
|
|
127
|
+
prefix += nextPrefix;
|
|
128
|
+
suffix += nextSuffix;
|
|
129
|
+
if (withinOneWord) break;
|
|
130
|
+
}
|
|
72
131
|
const trimmed = {
|
|
73
132
|
start: change.start + prefix,
|
|
74
133
|
end: change.end - suffix,
|
|
75
|
-
text:
|
|
134
|
+
text: inserted
|
|
76
135
|
};
|
|
77
136
|
return trimmed.start === trimmed.end && trimmed.text.length === 0 ? null : trimmed;
|
|
78
137
|
};
|
|
@@ -169,11 +228,13 @@ const shortestTokenDiff = (before, after, maxEdits = MAX_TOKEN_EDITS) => {
|
|
|
169
228
|
*
|
|
170
229
|
* Words (with their leading whitespace, and edge punctuation on its own; see
|
|
171
230
|
* `tokenizeWords`) are aligned by the shortest edit script, and each changed
|
|
172
|
-
* stretch is then trimmed to
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* both
|
|
176
|
-
*
|
|
231
|
+
* stretch is then trimmed: to whole words it shares at either end, and once
|
|
232
|
+
* what is left lies within one word on both sides, to the letters that
|
|
233
|
+
* actually differ. Appending a letter to a word that spans two runs leaves
|
|
234
|
+
* both runs alone, and a rewrite of several words never keeps a letter two of
|
|
235
|
+
* them merely happen to share. A rewrite past {@link MAX_TOKEN_EDITS}, or a
|
|
236
|
+
* diff that does not reconstruct both strings exactly (it never should), is
|
|
237
|
+
* one change over the whole span, trimmed the same way.
|
|
177
238
|
*/
|
|
178
239
|
const planTextChanges = (source, replacement) => {
|
|
179
240
|
if (source === replacement) return [];
|
|
@@ -268,6 +329,64 @@ const widenChangesToAtomicSpans = (source, changes, spans) => {
|
|
|
268
329
|
}
|
|
269
330
|
return merged;
|
|
270
331
|
};
|
|
332
|
+
/** Where each character `changes` keeps of `source` lands in the result. */
|
|
333
|
+
const keptPositions = (source, changes) => {
|
|
334
|
+
const kept = /* @__PURE__ */ new Map();
|
|
335
|
+
let shift = 0;
|
|
336
|
+
let cursor = 0;
|
|
337
|
+
for (const change of changes) {
|
|
338
|
+
for (; cursor < change.start; cursor++) kept.set(cursor, cursor + shift);
|
|
339
|
+
cursor = change.end;
|
|
340
|
+
shift += change.text.length - (change.end - change.start);
|
|
341
|
+
}
|
|
342
|
+
for (; cursor < source.length; cursor++) kept.set(cursor, cursor + shift);
|
|
343
|
+
return kept;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* `changes` cut around every atomic span (a field result) that `kept`, another
|
|
347
|
+
* plan of the same replacement, leaves untouched.
|
|
348
|
+
*
|
|
349
|
+
* A redline's changes are cut for a reader and may swallow a field whose
|
|
350
|
+
* displayed text the replacement repeats; rewriting it deletes the field and
|
|
351
|
+
* inserts its text as plain text, which the direct edit, keeping the field,
|
|
352
|
+
* never does. Cutting the change around the field keeps it in both modes,
|
|
353
|
+
* and a piece of the cut that no longer changes anything is dropped. Where
|
|
354
|
+
* the field's text does not stand in the change's new text exactly where
|
|
355
|
+
* `kept` puts it, the change is left whole.
|
|
356
|
+
*/
|
|
357
|
+
const keepAtomicSpans = (source, replacement, changes, kept, spans) => {
|
|
358
|
+
if (spans.length === 0) return [...changes];
|
|
359
|
+
const keptAt = keptPositions(source, kept);
|
|
360
|
+
const cut = [];
|
|
361
|
+
let shift = 0;
|
|
362
|
+
for (const change of changes) {
|
|
363
|
+
const replacementStart = change.start + shift;
|
|
364
|
+
shift += change.text.length - (change.end - change.start);
|
|
365
|
+
let sourceCursor = change.start;
|
|
366
|
+
let textCursor = 0;
|
|
367
|
+
for (const { offset, length } of spans) {
|
|
368
|
+
if (offset < sourceCursor || offset + length > change.end || length === 0) continue;
|
|
369
|
+
const lands = keptAt.get(offset);
|
|
370
|
+
const wholeAndInOrder = lands !== void 0 && Array.from({ length }, (_, index) => keptAt.get(offset + index)).every((position, index) => position === lands + index);
|
|
371
|
+
const local = lands === void 0 ? -1 : lands - replacementStart;
|
|
372
|
+
if (!wholeAndInOrder || local < textCursor || change.text.slice(local, local + length) !== source.slice(offset, offset + length)) continue;
|
|
373
|
+
cut.push({
|
|
374
|
+
start: sourceCursor,
|
|
375
|
+
end: offset,
|
|
376
|
+
text: change.text.slice(textCursor, local)
|
|
377
|
+
});
|
|
378
|
+
sourceCursor = offset + length;
|
|
379
|
+
textCursor = local + length;
|
|
380
|
+
}
|
|
381
|
+
cut.push({
|
|
382
|
+
start: sourceCursor,
|
|
383
|
+
end: change.end,
|
|
384
|
+
text: change.text.slice(textCursor)
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
const pieces = cut.filter(({ start, end, text }) => (start < end || text.length > 0) && source.slice(start, end) !== text);
|
|
388
|
+
return applyTextChanges(source, pieces) === replacement ? pieces : [...changes];
|
|
389
|
+
};
|
|
271
390
|
/** `source` with `changes` applied; the invariant the planner owes. */
|
|
272
391
|
const applyTextChanges = (source, changes) => {
|
|
273
392
|
let result = "";
|
|
@@ -279,4 +398,4 @@ const applyTextChanges = (source, changes) => {
|
|
|
279
398
|
return result + source.slice(cursor);
|
|
280
399
|
};
|
|
281
400
|
//#endregion
|
|
282
|
-
export { MAX_TOKEN_EDITS, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
401
|
+
export { MAX_TOKEN_EDITS, applyTextChanges, changesFromSegments, commonPrefixLength, commonSuffixLength, keepAtomicSpans, planTextChanges, shortestTokenDiff, widenChangesToAtomicSpans };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FolioAIEditOperation, FolioAIListNumbering, FolioAIListReference, FolioAINewListReference } from "./types.js";
|
|
2
|
+
import { NumberingMap } from "../docx/numberingParser.js";
|
|
3
|
+
//#region src/ai-edits/newListNumbering.d.ts
|
|
4
|
+
/** Whether `numbering` asks for a new list rather than naming an instance. */
|
|
5
|
+
declare const isFolioAINewListReference: (numbering: FolioAIListNumbering) => numbering is FolioAINewListReference;
|
|
6
|
+
/**
|
|
7
|
+
* The concrete instance an operation's numbering names once new lists are
|
|
8
|
+
* resolved. A new-list request reaching this point skipped resolution, which
|
|
9
|
+
* is a bug in the apply path rather than a malformed operation.
|
|
10
|
+
*/
|
|
11
|
+
declare const concreteListReference: (numbering: FolioAIListNumbering) => FolioAIListReference;
|
|
12
|
+
type ResolvedNewLists = {
|
|
13
|
+
operations: FolioAIEditOperation[];
|
|
14
|
+
numbering: NumberingMap | null;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Replace every new-list request with a reference to an instance minted for
|
|
18
|
+
* its operation: one instance per operation and kind. `numbering` is the map
|
|
19
|
+
* the batch applies against; the result's map adds the minted instances.
|
|
20
|
+
*/
|
|
21
|
+
declare const resolveNewListOperations: (operations: readonly FolioAIEditOperation[], numbering: NumberingMap | null) => ResolvedNewLists;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { concreteListReference, isFolioAINewListReference, resolveNewListOperations };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mintListInstance } from "../docx/listNumberingInstances.js";
|
|
2
|
+
import { createNumberingMap } from "../docx/numberingParser.js";
|
|
3
|
+
import { panic } from "better-result";
|
|
4
|
+
//#region src/ai-edits/newListNumbering.ts
|
|
5
|
+
/**
|
|
6
|
+
* Operations that start a new list (`numbering: { start: "new", kind }`)
|
|
7
|
+
* resolved to the numbering instance each one defines.
|
|
8
|
+
*
|
|
9
|
+
* The instance is minted against the document's numbering (the package's
|
|
10
|
+
* definitions plus every instance its paragraphs already define), so its id
|
|
11
|
+
* collides with nothing, and against the instances earlier operations in the
|
|
12
|
+
* batch minted, so two operations start two lists. The paragraphs the
|
|
13
|
+
* operation numbers then carry the instance's rendering, which is what
|
|
14
|
+
* defines it in the saved package (see `docx/listNumberingInstances.ts`).
|
|
15
|
+
*/
|
|
16
|
+
/** Whether `numbering` asks for a new list rather than naming an instance. */
|
|
17
|
+
const isFolioAINewListReference = (numbering) => "start" in numbering;
|
|
18
|
+
/**
|
|
19
|
+
* The concrete instance an operation's numbering names once new lists are
|
|
20
|
+
* resolved. A new-list request reaching this point skipped resolution, which
|
|
21
|
+
* is a bug in the apply path rather than a malformed operation.
|
|
22
|
+
*/
|
|
23
|
+
const concreteListReference = (numbering) => {
|
|
24
|
+
if (isFolioAINewListReference(numbering)) return panic("A new-list numbering request reached apply unresolved");
|
|
25
|
+
return numbering;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Replace every new-list request with a reference to an instance minted for
|
|
29
|
+
* its operation: one instance per operation and kind. `numbering` is the map
|
|
30
|
+
* the batch applies against; the result's map adds the minted instances.
|
|
31
|
+
*/
|
|
32
|
+
const resolveNewListOperations = (operations, numbering) => {
|
|
33
|
+
let definitions = numbering?.definitions;
|
|
34
|
+
let minted = false;
|
|
35
|
+
const resolveOperation = (operation) => {
|
|
36
|
+
const instances = /* @__PURE__ */ new Map();
|
|
37
|
+
const resolve = (value) => {
|
|
38
|
+
if (value === null || value === void 0 || !isFolioAINewListReference(value)) return value;
|
|
39
|
+
let numId = instances.get(value.kind);
|
|
40
|
+
if (numId === void 0) {
|
|
41
|
+
const instance = mintListInstance(definitions, { kind: value.kind });
|
|
42
|
+
definitions = instance.definitions;
|
|
43
|
+
numId = instance.numId;
|
|
44
|
+
instances.set(value.kind, numId);
|
|
45
|
+
minted = true;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
numId,
|
|
49
|
+
level: value.level ?? 0
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const resolveProperties = (properties) => properties?.numbering ? {
|
|
53
|
+
...properties,
|
|
54
|
+
numbering: resolve(properties.numbering)
|
|
55
|
+
} : properties;
|
|
56
|
+
switch (operation.type) {
|
|
57
|
+
case "insertAfterBlock":
|
|
58
|
+
case "insertBeforeBlock": return operation.numbering ? {
|
|
59
|
+
...operation,
|
|
60
|
+
numbering: resolve(operation.numbering)
|
|
61
|
+
} : operation;
|
|
62
|
+
case "setBlockParagraphProperties": return operation.properties.numbering ? {
|
|
63
|
+
...operation,
|
|
64
|
+
properties: {
|
|
65
|
+
...operation.properties,
|
|
66
|
+
numbering: resolve(operation.properties.numbering)
|
|
67
|
+
}
|
|
68
|
+
} : operation;
|
|
69
|
+
case "splitBlock": {
|
|
70
|
+
const firstParagraphProperties = resolveProperties(operation.firstParagraphProperties);
|
|
71
|
+
const secondParagraphProperties = resolveProperties(operation.secondParagraphProperties);
|
|
72
|
+
return {
|
|
73
|
+
...operation,
|
|
74
|
+
...firstParagraphProperties && { firstParagraphProperties },
|
|
75
|
+
...secondParagraphProperties && { secondParagraphProperties }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
case "mergeBlockWithNext": {
|
|
79
|
+
const mergedParagraphProperties = resolveProperties(operation.mergedParagraphProperties);
|
|
80
|
+
return {
|
|
81
|
+
...operation,
|
|
82
|
+
...mergedParagraphProperties && { mergedParagraphProperties }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
default: return operation;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
operations: operations.map(resolveOperation),
|
|
90
|
+
numbering: minted && definitions ? createNumberingMap(definitions) : numbering
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
export { concreteListReference, isFolioAINewListReference, resolveNewListOperations };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { NoteReferenceKind } from "../utils/noteReferenceLabels.js";
|
|
2
|
+
import { TextChange } from "./minimal-replacement.js";
|
|
3
|
+
import { CleanBlockText } from "./clean-text.js";
|
|
4
|
+
import { Node } from "prosemirror-model";
|
|
5
|
+
//#region src/ai-edits/note-references.d.ts
|
|
6
|
+
/** Which note a reference names: its kind and its package id. */
|
|
7
|
+
type NoteReferenceIdentity = {
|
|
8
|
+
noteType: NoteReferenceKind;
|
|
9
|
+
noteId: string;
|
|
10
|
+
};
|
|
11
|
+
/** The reference a text node carries, or `null` for ordinary text. */
|
|
12
|
+
declare const noteReferenceOf: (node: Node) => NoteReferenceIdentity | null;
|
|
13
|
+
/**
|
|
14
|
+
* The marker each reference of a story shows, keyed by note: numbered once
|
|
15
|
+
* for a whole story so every block reads the same number the Markdown export
|
|
16
|
+
* gives it, and so an edit elsewhere in one batch cannot renumber a block the
|
|
17
|
+
* batch resolved earlier.
|
|
18
|
+
*/
|
|
19
|
+
type NoteReferenceLabels = {
|
|
20
|
+
labelOf: (reference: NoteReferenceIdentity) => string | undefined;
|
|
21
|
+
};
|
|
22
|
+
/** Labels that grow as the blocks of a story are numbered, in reading order. */
|
|
23
|
+
type NoteReferenceLabeler = NoteReferenceLabels & {
|
|
24
|
+
/**
|
|
25
|
+
* Number the references of one block. A reference the clean view omits
|
|
26
|
+
* (tracked-deleted, hidden) takes no number, exactly as its characters take
|
|
27
|
+
* no place in the clean text.
|
|
28
|
+
*/
|
|
29
|
+
numberBlock: (block: Node) => void;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The one numbering every AI reader of a story shares. The snapshot numbers
|
|
33
|
+
* block by block inside its own walk; `collectNoteReferenceLabels` (with the
|
|
34
|
+
* snapshot, so both skip what the snapshot skips) numbers a whole story.
|
|
35
|
+
*/
|
|
36
|
+
declare const createNoteReferenceLabeler: () => NoteReferenceLabeler;
|
|
37
|
+
/**
|
|
38
|
+
* The text a reader shows for an inline text node: a note reference's marker
|
|
39
|
+
* instead of its package id. A reference the clean view omits (a tracked
|
|
40
|
+
* deletion) has no number there, and reads as an unnumbered marker.
|
|
41
|
+
*/
|
|
42
|
+
declare const readerTextOf: (node: Node, labels: NoteReferenceLabels) => string;
|
|
43
|
+
/** A note reference's marker inside a span of clean text, relative to the span. */
|
|
44
|
+
type NoteReferenceSpan = {
|
|
45
|
+
offset: number;
|
|
46
|
+
length: number;
|
|
47
|
+
};
|
|
48
|
+
type StructuralBoundaries = Pick<CleanBlockText, "structuralBoundaries">;
|
|
49
|
+
/** The markers lying wholly inside clean text `[start, end)`, relative to `start`. */
|
|
50
|
+
declare const noteReferenceSpansWithin: ({ structuralBoundaries }: StructuralBoundaries, start: number, end: number) => NoteReferenceSpan[];
|
|
51
|
+
/** Whether clean text `[start, end)` starts or ends inside a reference's marker. */
|
|
52
|
+
declare const cutsIntoNoteReference: ({ structuralBoundaries }: StructuralBoundaries, start: number, end: number) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* @internal Run `apply` with its text operations free to remove note
|
|
55
|
+
* references and to write marker text in place of new ones.
|
|
56
|
+
*
|
|
57
|
+
* Only a document comparison does this. Its operations come from a revised
|
|
58
|
+
* document that adds and removes references, so refusing them would drop the
|
|
59
|
+
* edit; each marker it writes is turned back into the reference the revised
|
|
60
|
+
* document holds once the operations land (`compare/inline-atoms.ts`), and a
|
|
61
|
+
* removed reference is a tracked deletion of the reference itself. Every other
|
|
62
|
+
* caller keeps the protection. The scope is synchronous, like the applier.
|
|
63
|
+
*/
|
|
64
|
+
declare const withComparisonNoteReferenceEdits: <T>(apply: () => T) => T;
|
|
65
|
+
/** @internal Whether the running apply belongs to a comparison (see above). */
|
|
66
|
+
declare const noteReferenceEditsAllowed: () => boolean;
|
|
67
|
+
/** The pieces of a replacement between the markers it has to keep. */
|
|
68
|
+
type NoteReferenceSegments = {
|
|
69
|
+
/** The source text between markers, with its offset in the source. */
|
|
70
|
+
source: {
|
|
71
|
+
offset: number;
|
|
72
|
+
text: string;
|
|
73
|
+
}[];
|
|
74
|
+
/** What each source piece becomes. */
|
|
75
|
+
replacement: string[];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Pair a text replacement with the reference markers its source holds.
|
|
79
|
+
*
|
|
80
|
+
* A reference is not text, so a replacement may only rewrite the prose around
|
|
81
|
+
* it: every marker of `source` has to reappear in `replacement`, in order, and
|
|
82
|
+
* the prose between two markers becomes the prose between the same two. A
|
|
83
|
+
* replacement that drops, reorders or rewrites a marker — or writes a new
|
|
84
|
+
* marker-shaped string, which would read as a reference and be none — answers
|
|
85
|
+
* `null`: the edit is refused rather than deleting a reference, or leaving
|
|
86
|
+
* text behind that claims to be one.
|
|
87
|
+
*/
|
|
88
|
+
declare const segmentsAroundNoteReferences: (source: string, replacement: string, spans: readonly NoteReferenceSpan[]) => NoteReferenceSegments | null;
|
|
89
|
+
/**
|
|
90
|
+
* Plan a replacement's changes piece by piece between the markers it keeps, so
|
|
91
|
+
* no change ever touches a reference. `null` when the replacement does not
|
|
92
|
+
* keep them (see {@link segmentsAroundNoteReferences}).
|
|
93
|
+
*/
|
|
94
|
+
declare const planChangesAroundNoteReferences: (source: string, replacement: string, spans: readonly NoteReferenceSpan[], planChanges: (source: string, replacement: string) => readonly TextChange[]) => TextChange[] | null;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { NoteReferenceIdentity, NoteReferenceLabeler, NoteReferenceLabels, NoteReferenceSpan, createNoteReferenceLabeler, cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, readerTextOf, segmentsAroundNoteReferences, withComparisonNoteReferenceEdits };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createNoteReferenceNumbering, noteReferenceMarker } from "../utils/noteReferenceLabels.js";
|
|
2
|
+
//#region src/ai-edits/note-references.ts
|
|
3
|
+
const NOTE_REFERENCE_MARK = "footnoteRef";
|
|
4
|
+
const DELETION_MARK = "deletion";
|
|
5
|
+
const HIDDEN_MARK = "hidden";
|
|
6
|
+
/** The reference a text node carries, or `null` for ordinary text. */
|
|
7
|
+
const noteReferenceOf = (node) => {
|
|
8
|
+
if (!node.isText) return null;
|
|
9
|
+
const mark = node.marks.find((candidate) => candidate.type.name === NOTE_REFERENCE_MARK);
|
|
10
|
+
if (!mark) return null;
|
|
11
|
+
return {
|
|
12
|
+
noteType: mark.attrs["noteType"] === "endnote" ? "endnote" : "footnote",
|
|
13
|
+
noteId: String(mark.attrs["id"])
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const labelKey = ({ noteType, noteId }) => `${noteType}:${noteId}`;
|
|
17
|
+
const isOmittedFromCleanView = (node) => node.marks.some((mark) => mark.type.name === DELETION_MARK || mark.type.name === HIDDEN_MARK);
|
|
18
|
+
/**
|
|
19
|
+
* The one numbering every AI reader of a story shares. The snapshot numbers
|
|
20
|
+
* block by block inside its own walk; `collectNoteReferenceLabels` (with the
|
|
21
|
+
* snapshot, so both skip what the snapshot skips) numbers a whole story.
|
|
22
|
+
*/
|
|
23
|
+
const createNoteReferenceLabeler = () => {
|
|
24
|
+
const labels = /* @__PURE__ */ new Map();
|
|
25
|
+
const numbering = createNoteReferenceNumbering();
|
|
26
|
+
return {
|
|
27
|
+
labelOf: (reference) => labels.get(labelKey(reference)),
|
|
28
|
+
numberBlock: (block) => {
|
|
29
|
+
block.descendants((node) => {
|
|
30
|
+
const reference = noteReferenceOf(node);
|
|
31
|
+
if (reference === null || isOmittedFromCleanView(node)) return true;
|
|
32
|
+
const { displayNumber, first } = numbering.next(reference.noteType, reference.noteId);
|
|
33
|
+
if (first) labels.set(labelKey(reference), noteReferenceMarker(reference.noteType, displayNumber));
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The text a reader shows for an inline text node: a note reference's marker
|
|
41
|
+
* instead of its package id. A reference the clean view omits (a tracked
|
|
42
|
+
* deletion) has no number there, and reads as an unnumbered marker.
|
|
43
|
+
*/
|
|
44
|
+
const readerTextOf = (node, labels) => {
|
|
45
|
+
const reference = noteReferenceOf(node);
|
|
46
|
+
if (reference === null) return node.text ?? "";
|
|
47
|
+
return labels.labelOf(reference) ?? (reference.noteType === "endnote" ? UNNUMBERED_ENDNOTE_MARKER : UNNUMBERED_FOOTNOTE_MARKER);
|
|
48
|
+
};
|
|
49
|
+
const UNNUMBERED_FOOTNOTE_MARKER = "[^?]";
|
|
50
|
+
const UNNUMBERED_ENDNOTE_MARKER = "[^e?]";
|
|
51
|
+
/** The markers lying wholly inside clean text `[start, end)`, relative to `start`. */
|
|
52
|
+
const noteReferenceSpansWithin = ({ structuralBoundaries }, start, end) => structuralBoundaries.flatMap((boundary) => boundary.type === "noteReference" && boundary.offset >= start && boundary.offset + boundary.length <= end ? [{
|
|
53
|
+
offset: boundary.offset - start,
|
|
54
|
+
length: boundary.length
|
|
55
|
+
}] : []);
|
|
56
|
+
/** Whether clean text `[start, end)` starts or ends inside a reference's marker. */
|
|
57
|
+
const cutsIntoNoteReference = ({ structuralBoundaries }, start, end) => structuralBoundaries.some((boundary) => boundary.type === "noteReference" && [start, end].some((offset) => offset > boundary.offset && offset < boundary.offset + boundary.length));
|
|
58
|
+
let comparisonEditsNoteReferences = false;
|
|
59
|
+
/**
|
|
60
|
+
* @internal Run `apply` with its text operations free to remove note
|
|
61
|
+
* references and to write marker text in place of new ones.
|
|
62
|
+
*
|
|
63
|
+
* Only a document comparison does this. Its operations come from a revised
|
|
64
|
+
* document that adds and removes references, so refusing them would drop the
|
|
65
|
+
* edit; each marker it writes is turned back into the reference the revised
|
|
66
|
+
* document holds once the operations land (`compare/inline-atoms.ts`), and a
|
|
67
|
+
* removed reference is a tracked deletion of the reference itself. Every other
|
|
68
|
+
* caller keeps the protection. The scope is synchronous, like the applier.
|
|
69
|
+
*/
|
|
70
|
+
const withComparisonNoteReferenceEdits = (apply) => {
|
|
71
|
+
const previous = comparisonEditsNoteReferences;
|
|
72
|
+
comparisonEditsNoteReferences = true;
|
|
73
|
+
try {
|
|
74
|
+
return apply();
|
|
75
|
+
} finally {
|
|
76
|
+
comparisonEditsNoteReferences = previous;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/** @internal Whether the running apply belongs to a comparison (see above). */
|
|
80
|
+
const noteReferenceEditsAllowed = () => comparisonEditsNoteReferences;
|
|
81
|
+
const NOTE_MARKER_PATTERN = /\[\^e?[1-9]\d*\]/gu;
|
|
82
|
+
const markerCount = (text) => text.match(NOTE_MARKER_PATTERN)?.length ?? 0;
|
|
83
|
+
/**
|
|
84
|
+
* Pair a text replacement with the reference markers its source holds.
|
|
85
|
+
*
|
|
86
|
+
* A reference is not text, so a replacement may only rewrite the prose around
|
|
87
|
+
* it: every marker of `source` has to reappear in `replacement`, in order, and
|
|
88
|
+
* the prose between two markers becomes the prose between the same two. A
|
|
89
|
+
* replacement that drops, reorders or rewrites a marker — or writes a new
|
|
90
|
+
* marker-shaped string, which would read as a reference and be none — answers
|
|
91
|
+
* `null`: the edit is refused rather than deleting a reference, or leaving
|
|
92
|
+
* text behind that claims to be one.
|
|
93
|
+
*/
|
|
94
|
+
const segmentsAroundNoteReferences = (source, replacement, spans) => {
|
|
95
|
+
const segments = {
|
|
96
|
+
source: [],
|
|
97
|
+
replacement: []
|
|
98
|
+
};
|
|
99
|
+
let sourceCursor = 0;
|
|
100
|
+
let replacementCursor = 0;
|
|
101
|
+
for (const { offset, length } of spans) {
|
|
102
|
+
const marker = source.slice(offset, offset + length);
|
|
103
|
+
const at = replacement.indexOf(marker, replacementCursor);
|
|
104
|
+
if (at === -1) return null;
|
|
105
|
+
segments.source.push({
|
|
106
|
+
offset: sourceCursor,
|
|
107
|
+
text: source.slice(sourceCursor, offset)
|
|
108
|
+
});
|
|
109
|
+
segments.replacement.push(replacement.slice(replacementCursor, at));
|
|
110
|
+
sourceCursor = offset + length;
|
|
111
|
+
replacementCursor = at + marker.length;
|
|
112
|
+
}
|
|
113
|
+
segments.source.push({
|
|
114
|
+
offset: sourceCursor,
|
|
115
|
+
text: source.slice(sourceCursor)
|
|
116
|
+
});
|
|
117
|
+
segments.replacement.push(replacement.slice(replacementCursor));
|
|
118
|
+
for (const [index, piece] of segments.replacement.entries()) if (markerCount(piece) > markerCount(segments.source[index]?.text ?? "")) return null;
|
|
119
|
+
const sourceProse = segments.source.map(({ text }) => text).join("");
|
|
120
|
+
const replacementProse = segments.replacement.join("");
|
|
121
|
+
if (markerCount(replacementProse) > markerCount(sourceProse)) return null;
|
|
122
|
+
return segments;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Plan a replacement's changes piece by piece between the markers it keeps, so
|
|
126
|
+
* no change ever touches a reference. `null` when the replacement does not
|
|
127
|
+
* keep them (see {@link segmentsAroundNoteReferences}).
|
|
128
|
+
*/
|
|
129
|
+
const planChangesAroundNoteReferences = (source, replacement, spans, planChanges) => {
|
|
130
|
+
const segments = segmentsAroundNoteReferences(source, replacement, spans);
|
|
131
|
+
if (segments === null) return null;
|
|
132
|
+
return segments.source.flatMap(({ offset, text }, index) => planChanges(text, segments.replacement[index] ?? "").map((change) => ({
|
|
133
|
+
start: change.start + offset,
|
|
134
|
+
end: change.end + offset,
|
|
135
|
+
text: change.text
|
|
136
|
+
})));
|
|
137
|
+
};
|
|
138
|
+
//#endregion
|
|
139
|
+
export { createNoteReferenceLabeler, cutsIntoNoteReference, noteReferenceEditsAllowed, noteReferenceOf, noteReferenceSpansWithin, planChangesAroundNoteReferences, readerTextOf, segmentsAroundNoteReferences, withComparisonNoteReferenceEdits };
|