documentation-hub 5.7.2
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/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlankLineManager - Rule-based blank line management engine.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the old phase-based "remove all, then add back" approach with
|
|
5
|
+
* a rule-based system that applies explicit rules first, then preserves
|
|
6
|
+
* original blank lines where no rule matched.
|
|
7
|
+
*
|
|
8
|
+
* Processing order:
|
|
9
|
+
* 1. Remove SDT wrappers
|
|
10
|
+
* 2. Apply REMOVAL rules (absolute constraints)
|
|
11
|
+
* 3. Apply ADDITION rules (absolute requirements)
|
|
12
|
+
* 4. Apply PRESERVATION fallback (keep original if no rule matched)
|
|
13
|
+
* 5. Apply INDENTATION rules
|
|
14
|
+
* 6. Dedup (safety net - remove adjacent blanks)
|
|
15
|
+
* 7. Normalize blank line styles to Normal
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Document, Paragraph, Table, TableCell } from "docxmlater";
|
|
19
|
+
import { logger } from "@/utils/logger";
|
|
20
|
+
import { clearCustom } from "./helpers/clearCustom";
|
|
21
|
+
import { isParagraphBlank } from "./helpers/paragraphChecks";
|
|
22
|
+
import { createBlankParagraph } from "./helpers/blankLineInsertion";
|
|
23
|
+
import { getImageRunFromParagraph, isImageSmall } from "./helpers/imageChecks";
|
|
24
|
+
import { tableHasNestedContent } from "./helpers/tableGuards";
|
|
25
|
+
import type { BlankLineSnapshot } from "./helpers/blankLineSnapshot";
|
|
26
|
+
import {
|
|
27
|
+
wasOriginallyBlankAtBody,
|
|
28
|
+
wasOriginallyBlankInCell,
|
|
29
|
+
} from "./helpers/blankLineSnapshot";
|
|
30
|
+
import { removalRules } from "./rules/removalRules";
|
|
31
|
+
import { additionRules } from "./rules/additionRules";
|
|
32
|
+
import { applyIndentationRules } from "./rules/indentationRules";
|
|
33
|
+
import type {
|
|
34
|
+
RuleContext,
|
|
35
|
+
BlankLineProcessingOptions,
|
|
36
|
+
RuleEngineResult,
|
|
37
|
+
BlankLineRule,
|
|
38
|
+
} from "./rules/ruleTypes";
|
|
39
|
+
import type { BlankLineOptions } from "./types";
|
|
40
|
+
|
|
41
|
+
const log = logger.namespace("BlankLineManager");
|
|
42
|
+
|
|
43
|
+
function buildBlankLineOptions(
|
|
44
|
+
normalStyle?: BlankLineProcessingOptions['normalStyleFormatting']
|
|
45
|
+
): BlankLineOptions {
|
|
46
|
+
return {
|
|
47
|
+
spacingAfter: normalStyle?.spaceAfter ?? 120,
|
|
48
|
+
spacingBefore: normalStyle?.spaceBefore,
|
|
49
|
+
lineSpacing: normalStyle?.lineSpacing,
|
|
50
|
+
fontSize: normalStyle?.fontSize,
|
|
51
|
+
fontFamily: normalStyle?.fontFamily,
|
|
52
|
+
markAsPreserved: true,
|
|
53
|
+
style: "Normal",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class BlankLineManager {
|
|
58
|
+
/**
|
|
59
|
+
* Primary entry point for rule-based blank line processing.
|
|
60
|
+
* Must be called AFTER list normalization is complete.
|
|
61
|
+
*/
|
|
62
|
+
processBlankLines(
|
|
63
|
+
doc: Document,
|
|
64
|
+
snapshot: BlankLineSnapshot,
|
|
65
|
+
options: BlankLineProcessingOptions
|
|
66
|
+
): RuleEngineResult {
|
|
67
|
+
const result: RuleEngineResult = {
|
|
68
|
+
removed: 0,
|
|
69
|
+
added: 0,
|
|
70
|
+
preserved: 0,
|
|
71
|
+
indentationFixed: 0,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const blankOpts = buildBlankLineOptions(options.normalStyleFormatting);
|
|
75
|
+
|
|
76
|
+
// Step 1: Remove SDT wrappers
|
|
77
|
+
clearCustom(doc);
|
|
78
|
+
|
|
79
|
+
// NOTE: Small indent removal (< 0.25") now runs earlier in WordDocumentProcessor,
|
|
80
|
+
// before applyListContinuationIndentation, so that trivially-indented paragraphs
|
|
81
|
+
// are normalized to zero before continuation indentation logic evaluates them.
|
|
82
|
+
|
|
83
|
+
// Step 2: Apply removal rules (walk body + cells, remove blanks where rules match)
|
|
84
|
+
result.removed += this.applyRemovalRulesBody(doc);
|
|
85
|
+
result.removed += this.applyRemovalRulesCells(doc);
|
|
86
|
+
|
|
87
|
+
// Step 3: Apply addition rules (walk body + cells, add blanks where rules match)
|
|
88
|
+
result.added += this.applyAdditionRulesBody(doc, options, blankOpts);
|
|
89
|
+
result.added += this.applyAdditionRulesCells(doc, options, blankOpts);
|
|
90
|
+
|
|
91
|
+
// Step 4: Apply preservation fallback (keep original blanks where no rule matched)
|
|
92
|
+
result.preserved += this.applyPreservationFallbackBody(doc, snapshot, blankOpts);
|
|
93
|
+
result.preserved += this.applyPreservationFallbackCells(doc, snapshot, blankOpts);
|
|
94
|
+
|
|
95
|
+
// Step 5: Apply indentation rules
|
|
96
|
+
result.indentationFixed = applyIndentationRules(doc, options);
|
|
97
|
+
|
|
98
|
+
// Step 6: Final dedup pass (remove adjacent blank lines)
|
|
99
|
+
const dedupRemoved = this.dedup(doc);
|
|
100
|
+
result.removed += dedupRemoved;
|
|
101
|
+
|
|
102
|
+
// Step 7: Normalize all blank line styles to Normal
|
|
103
|
+
this.normalizeBlankLineStyles(doc, blankOpts);
|
|
104
|
+
|
|
105
|
+
log.info(
|
|
106
|
+
`Rule engine complete: ${result.removed} removed, ${result.added} added, ` +
|
|
107
|
+
`${result.preserved} preserved, ${result.indentationFixed} indentation fixes`
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Apply removal rules to body-level blank paragraphs.
|
|
115
|
+
* Iterates backwards to safely remove elements.
|
|
116
|
+
*/
|
|
117
|
+
private applyRemovalRulesBody(doc: Document): number {
|
|
118
|
+
let removed = 0;
|
|
119
|
+
|
|
120
|
+
for (let i = doc.getBodyElementCount() - 1; i >= 0; i--) {
|
|
121
|
+
const element = doc.getBodyElementAt(i);
|
|
122
|
+
if (!(element instanceof Paragraph)) continue;
|
|
123
|
+
if (!isParagraphBlank(element)) continue;
|
|
124
|
+
if (element.isPreserved()) continue; // Protect field paragraphs (TOC)
|
|
125
|
+
|
|
126
|
+
const ctx = this.buildBodyContext(doc, i);
|
|
127
|
+
const matchedRule = this.findMatchingRule(removalRules, ctx, "body");
|
|
128
|
+
|
|
129
|
+
if (matchedRule) {
|
|
130
|
+
log.debug(`Removal rule "${matchedRule.id}" matched at body index ${i}`);
|
|
131
|
+
doc.removeBodyElementAt(i);
|
|
132
|
+
removed++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return removed;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Apply removal rules to blank paragraphs inside table cells.
|
|
141
|
+
* Iterates backwards within each cell for safe removal.
|
|
142
|
+
*/
|
|
143
|
+
private applyRemovalRulesCells(doc: Document): number {
|
|
144
|
+
let removed = 0;
|
|
145
|
+
|
|
146
|
+
for (const table of doc.getAllTables()) {
|
|
147
|
+
if (tableHasNestedContent(table)) continue;
|
|
148
|
+
|
|
149
|
+
for (const row of table.getRows()) {
|
|
150
|
+
for (const cell of row.getCells()) {
|
|
151
|
+
let paras = cell.getParagraphs();
|
|
152
|
+
|
|
153
|
+
for (let ci = paras.length - 1; ci >= 0; ci--) {
|
|
154
|
+
// Keep at least one paragraph per cell (ECMA-376)
|
|
155
|
+
if (paras.length <= 1) break;
|
|
156
|
+
|
|
157
|
+
const para = paras[ci];
|
|
158
|
+
if (!para || !isParagraphBlank(para)) continue;
|
|
159
|
+
if (para.isPreserved()) continue;
|
|
160
|
+
|
|
161
|
+
const ctx = this.buildCellContext(doc, cell, ci, paras, table);
|
|
162
|
+
const matchedRule = this.findMatchingRule(removalRules, ctx, "cell");
|
|
163
|
+
|
|
164
|
+
if (matchedRule) {
|
|
165
|
+
log.debug(`Removal rule "${matchedRule.id}" matched in cell at index ${ci}`);
|
|
166
|
+
cell.removeParagraph(ci);
|
|
167
|
+
removed++;
|
|
168
|
+
paras = cell.getParagraphs();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return removed;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Apply addition rules to body-level positions.
|
|
180
|
+
* Iterates forward, inserting blanks where addition rules match.
|
|
181
|
+
*/
|
|
182
|
+
private applyAdditionRulesBody(
|
|
183
|
+
doc: Document,
|
|
184
|
+
options: BlankLineProcessingOptions,
|
|
185
|
+
blankOpts: BlankLineOptions
|
|
186
|
+
): number {
|
|
187
|
+
let added = 0;
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < doc.getBodyElementCount(); i++) {
|
|
190
|
+
const element = doc.getBodyElementAt(i);
|
|
191
|
+
const ctx = this.buildBodyContext(doc, i);
|
|
192
|
+
const matchedRule = this.findMatchingRule(additionRules, ctx, "body");
|
|
193
|
+
|
|
194
|
+
if (matchedRule) {
|
|
195
|
+
// Clear indentation from navigation hyperlink paragraphs
|
|
196
|
+
if (matchedRule.id === "add-above-top-of-doc-hyperlink") {
|
|
197
|
+
const targetPara = ctx.nextElement;
|
|
198
|
+
if (targetPara instanceof Paragraph) {
|
|
199
|
+
const indent = targetPara.getFormatting()?.indentation?.left;
|
|
200
|
+
if (indent && indent > 0) {
|
|
201
|
+
targetPara.setLeftIndent(0);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Determine if this rule wants a blank BEFORE the next element or AFTER the current
|
|
207
|
+
const isBefore =
|
|
208
|
+
matchedRule.id === "add-above-top-of-doc-hyperlink" ||
|
|
209
|
+
matchedRule.id === "add-above-warning" ||
|
|
210
|
+
matchedRule.id === "add-before-first-1x1-table" ||
|
|
211
|
+
matchedRule.id === "add-above-bold-colon-no-indent";
|
|
212
|
+
|
|
213
|
+
if (isBefore) {
|
|
214
|
+
// These rules want a blank BEFORE the next element
|
|
215
|
+
// Check if a blank already exists between current and next
|
|
216
|
+
const nextIdx = i + 1;
|
|
217
|
+
if (nextIdx < doc.getBodyElementCount()) {
|
|
218
|
+
const nextEl = doc.getBodyElementAt(nextIdx);
|
|
219
|
+
if (nextEl instanceof Paragraph && isParagraphBlank(nextEl)) {
|
|
220
|
+
continue; // Already a blank
|
|
221
|
+
}
|
|
222
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
223
|
+
doc.insertBodyElementAt(nextIdx, blankPara);
|
|
224
|
+
added++;
|
|
225
|
+
i++; // Skip past inserted blank
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
// Standard "after" rules - ensure blank after current element
|
|
229
|
+
const nextIdx = i + 1;
|
|
230
|
+
if (nextIdx < doc.getBodyElementCount()) {
|
|
231
|
+
const nextEl = doc.getBodyElementAt(nextIdx);
|
|
232
|
+
if (nextEl instanceof Paragraph && isParagraphBlank(nextEl)) {
|
|
233
|
+
continue; // Already a blank
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
237
|
+
doc.insertBodyElementAt(i + 1, blankPara);
|
|
238
|
+
added++;
|
|
239
|
+
i++; // Skip past inserted blank
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Special: large images need blank ABOVE as well
|
|
244
|
+
if (element instanceof Paragraph && !isParagraphBlank(element)) {
|
|
245
|
+
const imageRun = getImageRunFromParagraph(element);
|
|
246
|
+
if (imageRun) {
|
|
247
|
+
const image = imageRun.getImageElement();
|
|
248
|
+
if (!isImageSmall(image) && i > 0) {
|
|
249
|
+
const prevEl = doc.getBodyElementAt(i - 1);
|
|
250
|
+
if (!(prevEl instanceof Paragraph && isParagraphBlank(prevEl))) {
|
|
251
|
+
// Don't add blank above image if previous is centered text
|
|
252
|
+
const isCenteredText =
|
|
253
|
+
prevEl instanceof Paragraph &&
|
|
254
|
+
prevEl.getAlignment() === "center" &&
|
|
255
|
+
!!prevEl.getText()?.trim();
|
|
256
|
+
if (!isCenteredText) {
|
|
257
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
258
|
+
doc.insertBodyElementAt(i, blankPara);
|
|
259
|
+
added++;
|
|
260
|
+
i++; // Skip past inserted blank
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return added;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Apply addition rules to table cell positions.
|
|
273
|
+
*/
|
|
274
|
+
private applyAdditionRulesCells(
|
|
275
|
+
doc: Document,
|
|
276
|
+
options: BlankLineProcessingOptions,
|
|
277
|
+
blankOpts: BlankLineOptions
|
|
278
|
+
): number {
|
|
279
|
+
let added = 0;
|
|
280
|
+
|
|
281
|
+
for (const table of doc.getAllTables()) {
|
|
282
|
+
if (tableHasNestedContent(table)) continue;
|
|
283
|
+
|
|
284
|
+
for (const row of table.getRows()) {
|
|
285
|
+
for (const cell of row.getCells()) {
|
|
286
|
+
let paras = cell.getParagraphs();
|
|
287
|
+
|
|
288
|
+
for (let ci = 0; ci < paras.length; ci++) {
|
|
289
|
+
paras = cell.getParagraphs();
|
|
290
|
+
const para = paras[ci];
|
|
291
|
+
if (!para) continue;
|
|
292
|
+
|
|
293
|
+
const ctx = this.buildCellContext(doc, cell, ci, paras, table);
|
|
294
|
+
const matchedRule = this.findMatchingRule(additionRules, ctx, "cell");
|
|
295
|
+
|
|
296
|
+
if (matchedRule) {
|
|
297
|
+
const isLastInCell = ci === paras.length - 1;
|
|
298
|
+
if (isLastInCell) continue; // Don't add blank at end of cell
|
|
299
|
+
|
|
300
|
+
const nextPara = paras[ci + 1];
|
|
301
|
+
if (nextPara && isParagraphBlank(nextPara)) continue; // Already has blank
|
|
302
|
+
|
|
303
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
304
|
+
cell.addParagraphAt(ci + 1, blankPara);
|
|
305
|
+
added++;
|
|
306
|
+
ci++; // Skip past inserted blank
|
|
307
|
+
paras = cell.getParagraphs();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return added;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Preservation fallback for body-level positions.
|
|
319
|
+
* For positions where no rule matched, if the original document had a blank there,
|
|
320
|
+
* insert one to preserve the original spacing.
|
|
321
|
+
*/
|
|
322
|
+
private applyPreservationFallbackBody(
|
|
323
|
+
doc: Document,
|
|
324
|
+
snapshot: BlankLineSnapshot,
|
|
325
|
+
blankOpts: BlankLineOptions
|
|
326
|
+
): number {
|
|
327
|
+
let preserved = 0;
|
|
328
|
+
|
|
329
|
+
for (let i = 0; i < doc.getBodyElementCount() - 1; i++) {
|
|
330
|
+
const next = doc.getBodyElementAt(i + 1);
|
|
331
|
+
|
|
332
|
+
// If there's already a blank at the next position, skip
|
|
333
|
+
if (next instanceof Paragraph && isParagraphBlank(next)) continue;
|
|
334
|
+
|
|
335
|
+
// Check if a blank existed here originally
|
|
336
|
+
if (wasOriginallyBlankAtBody(snapshot, doc, i + 1)) {
|
|
337
|
+
// Check if any removal rule would explicitly remove a blank here
|
|
338
|
+
const removalMatch = this.findMatchingRemovalForPosition(doc, i, i + 1);
|
|
339
|
+
if (removalMatch) continue;
|
|
340
|
+
|
|
341
|
+
// No rule matched and original had a blank - preserve it
|
|
342
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
343
|
+
doc.insertBodyElementAt(i + 1, blankPara);
|
|
344
|
+
preserved++;
|
|
345
|
+
i++; // Skip past inserted blank
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return preserved;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Preservation fallback for cell-level positions.
|
|
354
|
+
*/
|
|
355
|
+
private applyPreservationFallbackCells(
|
|
356
|
+
doc: Document,
|
|
357
|
+
snapshot: BlankLineSnapshot,
|
|
358
|
+
blankOpts: BlankLineOptions
|
|
359
|
+
): number {
|
|
360
|
+
let preserved = 0;
|
|
361
|
+
let tableIndex = 0;
|
|
362
|
+
|
|
363
|
+
for (const table of doc.getAllTables()) {
|
|
364
|
+
if (tableHasNestedContent(table)) {
|
|
365
|
+
tableIndex++;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let firstCellText = "";
|
|
370
|
+
try {
|
|
371
|
+
const firstCell = table.getCell(0, 0);
|
|
372
|
+
if (firstCell) {
|
|
373
|
+
firstCellText = firstCell
|
|
374
|
+
.getParagraphs()
|
|
375
|
+
.map((p) => p.getText())
|
|
376
|
+
.join(" ")
|
|
377
|
+
.substring(0, 20);
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
// Skip
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const rows = table.getRows();
|
|
384
|
+
for (let ri = 0; ri < rows.length; ri++) {
|
|
385
|
+
const cells = rows[ri].getCells();
|
|
386
|
+
for (let colIdx = 0; colIdx < cells.length; colIdx++) {
|
|
387
|
+
const cell = cells[colIdx];
|
|
388
|
+
let paras = cell.getParagraphs();
|
|
389
|
+
const cellId = `t${tableIndex}_r${ri}_c${colIdx}_${firstCellText.substring(0, 20)}`;
|
|
390
|
+
|
|
391
|
+
for (let ci = 0; ci < paras.length - 1; ci++) {
|
|
392
|
+
const nextPara = paras[ci + 1];
|
|
393
|
+
if (!nextPara || isParagraphBlank(nextPara)) continue;
|
|
394
|
+
|
|
395
|
+
if (wasOriginallyBlankInCell(snapshot, cell, ci + 1, cellId)) {
|
|
396
|
+
// Don't preserve blank at very end of cell
|
|
397
|
+
if (ci + 1 >= paras.length - 1) continue;
|
|
398
|
+
|
|
399
|
+
// Check if a removal rule would remove this blank
|
|
400
|
+
const removalMatch = this.findMatchingCellRemovalForPosition(
|
|
401
|
+
doc, cell, paras, ci, ci + 1, table
|
|
402
|
+
);
|
|
403
|
+
if (removalMatch) continue;
|
|
404
|
+
|
|
405
|
+
const blankPara = createBlankParagraph(blankOpts);
|
|
406
|
+
cell.addParagraphAt(ci + 1, blankPara);
|
|
407
|
+
preserved++;
|
|
408
|
+
ci++;
|
|
409
|
+
paras = cell.getParagraphs();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
tableIndex++;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return preserved;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Check if any removal rule would match at a hypothetical blank at the given position.
|
|
422
|
+
*/
|
|
423
|
+
private findMatchingRemovalForPosition(
|
|
424
|
+
doc: Document,
|
|
425
|
+
prevIndex: number,
|
|
426
|
+
blankIndex: number
|
|
427
|
+
): BlankLineRule | null {
|
|
428
|
+
const prev = doc.getBodyElementAt(prevIndex);
|
|
429
|
+
const next =
|
|
430
|
+
blankIndex < doc.getBodyElementCount()
|
|
431
|
+
? doc.getBodyElementAt(blankIndex)
|
|
432
|
+
: undefined;
|
|
433
|
+
|
|
434
|
+
const ctx: RuleContext = {
|
|
435
|
+
doc,
|
|
436
|
+
currentIndex: blankIndex,
|
|
437
|
+
currentElement: Paragraph.create(), // Simulate a blank paragraph
|
|
438
|
+
prevElement:
|
|
439
|
+
prev instanceof Paragraph || prev instanceof Table ? prev : undefined,
|
|
440
|
+
nextElement:
|
|
441
|
+
next instanceof Paragraph || next instanceof Table ? next : undefined,
|
|
442
|
+
scope: "body",
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
for (const rule of removalRules) {
|
|
446
|
+
if (rule.scope !== "body" && rule.scope !== "both") continue;
|
|
447
|
+
if (rule.matches(ctx)) return rule;
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Check if any removal rule would match a hypothetical blank at a cell position.
|
|
454
|
+
*/
|
|
455
|
+
private findMatchingCellRemovalForPosition(
|
|
456
|
+
doc: Document,
|
|
457
|
+
cell: TableCell,
|
|
458
|
+
paras: Paragraph[],
|
|
459
|
+
prevIndex: number,
|
|
460
|
+
nextIndex: number,
|
|
461
|
+
parentTable: Table
|
|
462
|
+
): BlankLineRule | null {
|
|
463
|
+
const ctx: RuleContext = {
|
|
464
|
+
doc,
|
|
465
|
+
currentIndex: prevIndex + 1,
|
|
466
|
+
currentElement: Paragraph.create(), // Simulate a blank paragraph
|
|
467
|
+
prevElement: prevIndex >= 0 ? paras[prevIndex] : undefined,
|
|
468
|
+
nextElement: nextIndex < paras.length ? paras[nextIndex] : undefined,
|
|
469
|
+
scope: "cell",
|
|
470
|
+
cell,
|
|
471
|
+
cellParagraphs: paras,
|
|
472
|
+
cellParaIndex: prevIndex + 1,
|
|
473
|
+
parentTable,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
for (const rule of removalRules) {
|
|
477
|
+
if (rule.scope !== "cell" && rule.scope !== "both") continue;
|
|
478
|
+
if (rule.matches(ctx)) return rule;
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Remove adjacent blank paragraphs (dedup safety net).
|
|
485
|
+
*/
|
|
486
|
+
private dedup(doc: Document): number {
|
|
487
|
+
let removed = 0;
|
|
488
|
+
|
|
489
|
+
// Body dedup (iterate backwards)
|
|
490
|
+
for (let i = doc.getBodyElementCount() - 1; i > 0; i--) {
|
|
491
|
+
const current = doc.getBodyElementAt(i);
|
|
492
|
+
const prev = doc.getBodyElementAt(i - 1);
|
|
493
|
+
|
|
494
|
+
if (
|
|
495
|
+
current instanceof Paragraph &&
|
|
496
|
+
prev instanceof Paragraph &&
|
|
497
|
+
isParagraphBlank(current) &&
|
|
498
|
+
isParagraphBlank(prev)
|
|
499
|
+
) {
|
|
500
|
+
doc.removeBodyElementAt(i);
|
|
501
|
+
removed++;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Cell dedup
|
|
506
|
+
for (const table of doc.getAllTables()) {
|
|
507
|
+
if (tableHasNestedContent(table)) continue;
|
|
508
|
+
|
|
509
|
+
for (const row of table.getRows()) {
|
|
510
|
+
for (const cell of row.getCells()) {
|
|
511
|
+
let paras = cell.getParagraphs();
|
|
512
|
+
|
|
513
|
+
// Remove adjacent blanks
|
|
514
|
+
for (let ci = paras.length - 1; ci > 0; ci--) {
|
|
515
|
+
if (paras.length <= 1) break;
|
|
516
|
+
|
|
517
|
+
const current = paras[ci];
|
|
518
|
+
const prev = paras[ci - 1];
|
|
519
|
+
|
|
520
|
+
if (current && prev && isParagraphBlank(current) && isParagraphBlank(prev)) {
|
|
521
|
+
cell.removeParagraph(ci);
|
|
522
|
+
removed++;
|
|
523
|
+
paras = cell.getParagraphs();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Remove trailing blanks (no blank between last visual element and cell end)
|
|
528
|
+
while (paras.length > 1 && isParagraphBlank(paras[paras.length - 1])) {
|
|
529
|
+
cell.removeParagraph(paras.length - 1);
|
|
530
|
+
removed++;
|
|
531
|
+
paras = cell.getParagraphs();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (removed > 0) {
|
|
538
|
+
log.debug(`Dedup removed ${removed} adjacent blank paragraphs`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return removed;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Ensure all blank paragraphs have Normal style with correct formatting.
|
|
546
|
+
*/
|
|
547
|
+
private normalizeBlankLineStyles(doc: Document, opts: BlankLineOptions): void {
|
|
548
|
+
const applyFormatting = (para: Paragraph) => {
|
|
549
|
+
para.setStyle(opts.style);
|
|
550
|
+
if (opts.spacingBefore !== undefined) {
|
|
551
|
+
para.setSpaceBefore(opts.spacingBefore);
|
|
552
|
+
}
|
|
553
|
+
para.setSpaceAfter(opts.spacingAfter);
|
|
554
|
+
if (opts.lineSpacing !== undefined) {
|
|
555
|
+
para.setLineSpacing(opts.lineSpacing);
|
|
556
|
+
}
|
|
557
|
+
// Apply font/size to existing runs (paragraph mark formatting)
|
|
558
|
+
if (opts.fontSize || opts.fontFamily) {
|
|
559
|
+
const runs = para.getRuns();
|
|
560
|
+
for (const run of runs) {
|
|
561
|
+
if (opts.fontSize) run.setSize(opts.fontSize);
|
|
562
|
+
if (opts.fontFamily) run.setFont(opts.fontFamily);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// Body
|
|
568
|
+
for (let i = 0; i < doc.getBodyElementCount(); i++) {
|
|
569
|
+
const element = doc.getBodyElementAt(i);
|
|
570
|
+
if (element instanceof Paragraph && isParagraphBlank(element)) {
|
|
571
|
+
applyFormatting(element);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Cells
|
|
576
|
+
for (const table of doc.getAllTables()) {
|
|
577
|
+
for (const row of table.getRows()) {
|
|
578
|
+
for (const cell of row.getCells()) {
|
|
579
|
+
for (const para of cell.getParagraphs()) {
|
|
580
|
+
if (isParagraphBlank(para)) {
|
|
581
|
+
applyFormatting(para);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Build a RuleContext for a body-level element.
|
|
591
|
+
*/
|
|
592
|
+
private buildBodyContext(doc: Document, index: number): RuleContext {
|
|
593
|
+
const element = doc.getBodyElementAt(index);
|
|
594
|
+
const prev = index > 0 ? doc.getBodyElementAt(index - 1) : undefined;
|
|
595
|
+
const next =
|
|
596
|
+
index < doc.getBodyElementCount() - 1
|
|
597
|
+
? doc.getBodyElementAt(index + 1)
|
|
598
|
+
: undefined;
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
doc,
|
|
602
|
+
currentIndex: index,
|
|
603
|
+
currentElement:
|
|
604
|
+
element instanceof Paragraph || element instanceof Table
|
|
605
|
+
? element
|
|
606
|
+
: (element as any),
|
|
607
|
+
prevElement:
|
|
608
|
+
prev instanceof Paragraph || prev instanceof Table ? prev : undefined,
|
|
609
|
+
nextElement:
|
|
610
|
+
next instanceof Paragraph || next instanceof Table ? next : undefined,
|
|
611
|
+
scope: "body",
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Build a RuleContext for a cell-level paragraph.
|
|
617
|
+
*/
|
|
618
|
+
private buildCellContext(
|
|
619
|
+
doc: Document,
|
|
620
|
+
cell: TableCell,
|
|
621
|
+
paraIndex: number,
|
|
622
|
+
paragraphs: Paragraph[],
|
|
623
|
+
parentTable: Table
|
|
624
|
+
): RuleContext {
|
|
625
|
+
return {
|
|
626
|
+
doc,
|
|
627
|
+
currentIndex: paraIndex,
|
|
628
|
+
currentElement: paragraphs[paraIndex],
|
|
629
|
+
prevElement: paraIndex > 0 ? paragraphs[paraIndex - 1] : undefined,
|
|
630
|
+
nextElement:
|
|
631
|
+
paraIndex < paragraphs.length - 1
|
|
632
|
+
? paragraphs[paraIndex + 1]
|
|
633
|
+
: undefined,
|
|
634
|
+
scope: "cell",
|
|
635
|
+
cell,
|
|
636
|
+
cellParagraphs: paragraphs,
|
|
637
|
+
cellParaIndex: paraIndex,
|
|
638
|
+
parentTable,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Find the first matching rule for the given context and scope.
|
|
644
|
+
*/
|
|
645
|
+
private findMatchingRule(
|
|
646
|
+
rules: BlankLineRule[],
|
|
647
|
+
ctx: RuleContext,
|
|
648
|
+
scope: "body" | "cell"
|
|
649
|
+
): BlankLineRule | null {
|
|
650
|
+
for (const rule of rules) {
|
|
651
|
+
if (rule.scope !== scope && rule.scope !== "both") continue;
|
|
652
|
+
if (rule.matches(ctx)) return rule;
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
export const blankLineManager = new BlankLineManager();
|