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.
Files changed (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. package/vite.config.ts +152 -0
@@ -0,0 +1,901 @@
1
+ /**
2
+ * EditorActionHandlers - Service for handling editor quick actions
3
+ *
4
+ * Provides implementations for all quick actions that manipulate documents
5
+ * using docxmlater APIs.
6
+ *
7
+ * Categories:
8
+ * - Text formatting (bold, italic, underline, etc.)
9
+ * - Paragraph styles (headings, normal, list paragraph)
10
+ * - Table operations (rows, columns, merge, split, shading)
11
+ * - Hyperlinks (insert, remove)
12
+ * - Tracked changes (accept all, reject all)
13
+ * - Structure (page break)
14
+ */
15
+
16
+ import type { Document, Paragraph, Table, Run } from 'docxmlater';
17
+ import type { QuickActionId, EditorSelection, CellSelection } from '@/types/editor';
18
+
19
+ /**
20
+ * Result of an editor action
21
+ */
22
+ export interface ActionResult {
23
+ success: boolean;
24
+ error?: string;
25
+ description: string;
26
+ }
27
+
28
+ /**
29
+ * Context for editor actions
30
+ */
31
+ export interface ActionContext {
32
+ document: Document;
33
+ selection: EditorSelection | null;
34
+ tableSelection: CellSelection | null;
35
+ selectedTableIndex: number | null;
36
+ tableShadingSettings?: {
37
+ header2Shading: string;
38
+ otherShading: string;
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Handler function type
44
+ */
45
+ type ActionHandler = (context: ActionContext) => Promise<ActionResult>;
46
+
47
+ /**
48
+ * Helper to get paragraph from document by index
49
+ */
50
+ function getParagraphAtIndex(doc: Document, index: number): Paragraph | null {
51
+ const elements = doc.getBodyElements();
52
+ const element = elements[index];
53
+ if (element && (element as any).type === 'paragraph') {
54
+ return element as Paragraph;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Helper to get table from document by index
61
+ */
62
+ function getTableAtIndex(doc: Document, index: number): Table | null {
63
+ const elements = doc.getBodyElements();
64
+ const element = elements[index];
65
+ if (element && (element as any).type === 'table') {
66
+ return element as Table;
67
+ }
68
+ return null;
69
+ }
70
+
71
+ /**
72
+ * Map of action handlers by action ID
73
+ */
74
+ const actionHandlers: Partial<Record<QuickActionId, ActionHandler>> = {
75
+ // Text Formatting
76
+ bold: async (ctx) => {
77
+ if (!ctx.selection) {
78
+ return { success: false, error: 'No text selected', description: 'Toggle bold' };
79
+ }
80
+
81
+ try {
82
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
83
+ if (!paragraph) {
84
+ return { success: false, error: 'Paragraph not found', description: 'Toggle bold' };
85
+ }
86
+
87
+ const runs = paragraph.getRuns();
88
+
89
+ // Toggle bold on selected runs
90
+ for (
91
+ let i = ctx.selection.runStartIndex;
92
+ i <= ctx.selection.runEndIndex && i < runs.length;
93
+ i++
94
+ ) {
95
+ const run = runs[i];
96
+ const formatting = run.getFormatting();
97
+ const currentBold = formatting.bold || false;
98
+ run.setBold(!currentBold);
99
+ }
100
+
101
+ return { success: true, description: 'Toggle bold formatting' };
102
+ } catch (error) {
103
+ return {
104
+ success: false,
105
+ error: error instanceof Error ? error.message : 'Failed to toggle bold',
106
+ description: 'Toggle bold',
107
+ };
108
+ }
109
+ },
110
+
111
+ italic: async (ctx) => {
112
+ if (!ctx.selection) {
113
+ return { success: false, error: 'No text selected', description: 'Toggle italic' };
114
+ }
115
+
116
+ try {
117
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
118
+ if (!paragraph) {
119
+ return { success: false, error: 'Paragraph not found', description: 'Toggle italic' };
120
+ }
121
+
122
+ const runs = paragraph.getRuns();
123
+
124
+ for (
125
+ let i = ctx.selection.runStartIndex;
126
+ i <= ctx.selection.runEndIndex && i < runs.length;
127
+ i++
128
+ ) {
129
+ const run = runs[i];
130
+ const formatting = run.getFormatting();
131
+ const currentItalic = formatting.italic || false;
132
+ run.setItalic(!currentItalic);
133
+ }
134
+
135
+ return { success: true, description: 'Toggle italic formatting' };
136
+ } catch (error) {
137
+ return {
138
+ success: false,
139
+ error: error instanceof Error ? error.message : 'Failed to toggle italic',
140
+ description: 'Toggle italic',
141
+ };
142
+ }
143
+ },
144
+
145
+ underline: async (ctx) => {
146
+ if (!ctx.selection) {
147
+ return { success: false, error: 'No text selected', description: 'Toggle underline' };
148
+ }
149
+
150
+ try {
151
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
152
+ if (!paragraph) {
153
+ return { success: false, error: 'Paragraph not found', description: 'Toggle underline' };
154
+ }
155
+
156
+ const runs = paragraph.getRuns();
157
+
158
+ for (
159
+ let i = ctx.selection.runStartIndex;
160
+ i <= ctx.selection.runEndIndex && i < runs.length;
161
+ i++
162
+ ) {
163
+ const run = runs[i];
164
+ const formatting = run.getFormatting();
165
+ const currentUnderline = formatting.underline;
166
+ run.setUnderline(currentUnderline ? undefined : 'single');
167
+ }
168
+
169
+ return { success: true, description: 'Toggle underline formatting' };
170
+ } catch (error) {
171
+ return {
172
+ success: false,
173
+ error: error instanceof Error ? error.message : 'Failed to toggle underline',
174
+ description: 'Toggle underline',
175
+ };
176
+ }
177
+ },
178
+
179
+ 'clear-formatting': async (ctx) => {
180
+ if (!ctx.selection) {
181
+ return {
182
+ success: false,
183
+ error: 'No text selected',
184
+ description: 'Clear formatting',
185
+ };
186
+ }
187
+
188
+ try {
189
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
190
+ if (!paragraph) {
191
+ return { success: false, error: 'Paragraph not found', description: 'Clear formatting' };
192
+ }
193
+
194
+ const runs = paragraph.getRuns();
195
+
196
+ for (
197
+ let i = ctx.selection.runStartIndex;
198
+ i <= ctx.selection.runEndIndex && i < runs.length;
199
+ i++
200
+ ) {
201
+ const run = runs[i];
202
+ run.setBold(false);
203
+ run.setItalic(false);
204
+ run.setUnderline(undefined);
205
+ run.setStrike(false);
206
+ // Color and highlight would need their own clear methods if available
207
+ }
208
+
209
+ return { success: true, description: 'Clear all formatting from selection' };
210
+ } catch (error) {
211
+ return {
212
+ success: false,
213
+ error: error instanceof Error ? error.message : 'Failed to clear formatting',
214
+ description: 'Clear formatting',
215
+ };
216
+ }
217
+ },
218
+
219
+ // Paragraph Styles
220
+ 'style-heading1': async (ctx) => {
221
+ if (!ctx.selection) {
222
+ return { success: false, error: 'No paragraph selected', description: 'Apply Heading 1' };
223
+ }
224
+
225
+ try {
226
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
227
+ if (!paragraph) {
228
+ return { success: false, error: 'Paragraph not found', description: 'Apply Heading 1' };
229
+ }
230
+
231
+ paragraph.setStyle('Heading1');
232
+ return { success: true, description: 'Apply Heading 1 style' };
233
+ } catch (error) {
234
+ return {
235
+ success: false,
236
+ error: error instanceof Error ? error.message : 'Failed to apply style',
237
+ description: 'Apply Heading 1',
238
+ };
239
+ }
240
+ },
241
+
242
+ 'style-heading2': async (ctx) => {
243
+ if (!ctx.selection) {
244
+ return { success: false, error: 'No paragraph selected', description: 'Apply Heading 2' };
245
+ }
246
+
247
+ try {
248
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
249
+ if (!paragraph) {
250
+ return { success: false, error: 'Paragraph not found', description: 'Apply Heading 2' };
251
+ }
252
+
253
+ paragraph.setStyle('Heading2');
254
+ return { success: true, description: 'Apply Heading 2 style' };
255
+ } catch (error) {
256
+ return {
257
+ success: false,
258
+ error: error instanceof Error ? error.message : 'Failed to apply style',
259
+ description: 'Apply Heading 2',
260
+ };
261
+ }
262
+ },
263
+
264
+ 'style-normal': async (ctx) => {
265
+ if (!ctx.selection) {
266
+ return { success: false, error: 'No paragraph selected', description: 'Apply Normal' };
267
+ }
268
+
269
+ try {
270
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
271
+ if (!paragraph) {
272
+ return { success: false, error: 'Paragraph not found', description: 'Apply Normal' };
273
+ }
274
+
275
+ paragraph.setStyle('Normal');
276
+ return { success: true, description: 'Apply Normal style' };
277
+ } catch (error) {
278
+ return {
279
+ success: false,
280
+ error: error instanceof Error ? error.message : 'Failed to apply style',
281
+ description: 'Apply Normal',
282
+ };
283
+ }
284
+ },
285
+
286
+ 'style-list-paragraph': async (ctx) => {
287
+ if (!ctx.selection) {
288
+ return {
289
+ success: false,
290
+ error: 'No paragraph selected',
291
+ description: 'Apply List Paragraph',
292
+ };
293
+ }
294
+
295
+ try {
296
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
297
+ if (!paragraph) {
298
+ return { success: false, error: 'Paragraph not found', description: 'Apply List Paragraph' };
299
+ }
300
+
301
+ paragraph.setStyle('ListParagraph');
302
+ return { success: true, description: 'Apply List Paragraph style' };
303
+ } catch (error) {
304
+ return {
305
+ success: false,
306
+ error: error instanceof Error ? error.message : 'Failed to apply style',
307
+ description: 'Apply List Paragraph',
308
+ };
309
+ }
310
+ },
311
+
312
+ // Table Shading
313
+ 'apply-h2-shading': async (ctx) => {
314
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
315
+ return { success: false, error: 'No table cells selected', description: 'Apply H2 shading' };
316
+ }
317
+
318
+ if (!ctx.tableShadingSettings) {
319
+ return { success: false, error: 'No shading settings', description: 'Apply H2 shading' };
320
+ }
321
+
322
+ try {
323
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
324
+ if (!table) {
325
+ return { success: false, error: 'Table not found', description: 'Apply H2 shading' };
326
+ }
327
+
328
+ const rows = table.getRows();
329
+ const { startRow, endRow, startCol, endCol } = ctx.tableSelection;
330
+
331
+ const minRow = Math.min(startRow, endRow);
332
+ const maxRow = Math.max(startRow, endRow);
333
+ const minCol = Math.min(startCol, endCol);
334
+ const maxCol = Math.max(startCol, endCol);
335
+
336
+ for (let r = minRow; r <= maxRow && r < rows.length; r++) {
337
+ const cells = rows[r].getCells();
338
+ for (let c = minCol; c <= maxCol && c < cells.length; c++) {
339
+ cells[c].setShading({ fill: ctx.tableShadingSettings.header2Shading });
340
+ }
341
+ }
342
+
343
+ return { success: true, description: 'Apply Header 2 shading' };
344
+ } catch (error) {
345
+ return {
346
+ success: false,
347
+ error: error instanceof Error ? error.message : 'Failed to apply shading',
348
+ description: 'Apply H2 shading',
349
+ };
350
+ }
351
+ },
352
+
353
+ 'apply-other-shading': async (ctx) => {
354
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
355
+ return {
356
+ success: false,
357
+ error: 'No table cells selected',
358
+ description: 'Apply other shading',
359
+ };
360
+ }
361
+
362
+ if (!ctx.tableShadingSettings) {
363
+ return { success: false, error: 'No shading settings', description: 'Apply other shading' };
364
+ }
365
+
366
+ try {
367
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
368
+ if (!table) {
369
+ return { success: false, error: 'Table not found', description: 'Apply other shading' };
370
+ }
371
+
372
+ const rows = table.getRows();
373
+ const { startRow, endRow, startCol, endCol } = ctx.tableSelection;
374
+
375
+ const minRow = Math.min(startRow, endRow);
376
+ const maxRow = Math.max(startRow, endRow);
377
+ const minCol = Math.min(startCol, endCol);
378
+ const maxCol = Math.max(startCol, endCol);
379
+
380
+ for (let r = minRow; r <= maxRow && r < rows.length; r++) {
381
+ const cells = rows[r].getCells();
382
+ for (let c = minCol; c <= maxCol && c < cells.length; c++) {
383
+ cells[c].setShading({ fill: ctx.tableShadingSettings.otherShading });
384
+ }
385
+ }
386
+
387
+ return { success: true, description: 'Apply other shading' };
388
+ } catch (error) {
389
+ return {
390
+ success: false,
391
+ error: error instanceof Error ? error.message : 'Failed to apply shading',
392
+ description: 'Apply other shading',
393
+ };
394
+ }
395
+ },
396
+
397
+ // Table Row Operations
398
+ 'table-add-row-above': async (ctx) => {
399
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
400
+ return { success: false, error: 'No table selected', description: 'Insert row above' };
401
+ }
402
+
403
+ try {
404
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
405
+ if (!table) {
406
+ return { success: false, error: 'Table not found', description: 'Insert row above' };
407
+ }
408
+
409
+ const targetRow = Math.min(ctx.tableSelection.startRow, ctx.tableSelection.endRow);
410
+ table.insertRow(targetRow);
411
+
412
+ return { success: true, description: 'Insert row above selection' };
413
+ } catch (error) {
414
+ return {
415
+ success: false,
416
+ error: error instanceof Error ? error.message : 'Failed to insert row',
417
+ description: 'Insert row above',
418
+ };
419
+ }
420
+ },
421
+
422
+ 'table-add-row-below': async (ctx) => {
423
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
424
+ return { success: false, error: 'No table selected', description: 'Insert row below' };
425
+ }
426
+
427
+ try {
428
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
429
+ if (!table) {
430
+ return { success: false, error: 'Table not found', description: 'Insert row below' };
431
+ }
432
+
433
+ const targetRow = Math.max(ctx.tableSelection.startRow, ctx.tableSelection.endRow) + 1;
434
+ table.insertRow(targetRow);
435
+
436
+ return { success: true, description: 'Insert row below selection' };
437
+ } catch (error) {
438
+ return {
439
+ success: false,
440
+ error: error instanceof Error ? error.message : 'Failed to insert row',
441
+ description: 'Insert row below',
442
+ };
443
+ }
444
+ },
445
+
446
+ 'table-delete-row': async (ctx) => {
447
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
448
+ return { success: false, error: 'No table selected', description: 'Delete row' };
449
+ }
450
+
451
+ try {
452
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
453
+ if (!table) {
454
+ return { success: false, error: 'Table not found', description: 'Delete row' };
455
+ }
456
+
457
+ const rows = table.getRows();
458
+ if (rows.length <= 1) {
459
+ return { success: false, error: 'Cannot delete last row', description: 'Delete row' };
460
+ }
461
+
462
+ const minRow = Math.min(ctx.tableSelection.startRow, ctx.tableSelection.endRow);
463
+ const maxRow = Math.max(ctx.tableSelection.startRow, ctx.tableSelection.endRow);
464
+
465
+ // Delete rows from bottom to top to maintain indices
466
+ for (let r = maxRow; r >= minRow; r--) {
467
+ table.removeRow(r);
468
+ }
469
+
470
+ return { success: true, description: 'Delete selected rows' };
471
+ } catch (error) {
472
+ return {
473
+ success: false,
474
+ error: error instanceof Error ? error.message : 'Failed to delete row',
475
+ description: 'Delete row',
476
+ };
477
+ }
478
+ },
479
+
480
+ // Table Column Operations
481
+ 'table-add-col-left': async (ctx) => {
482
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
483
+ return { success: false, error: 'No table selected', description: 'Insert column left' };
484
+ }
485
+
486
+ try {
487
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
488
+ if (!table) {
489
+ return { success: false, error: 'Table not found', description: 'Insert column left' };
490
+ }
491
+
492
+ const targetCol = Math.min(ctx.tableSelection.startCol, ctx.tableSelection.endCol);
493
+ table.addColumn(targetCol);
494
+
495
+ return { success: true, description: 'Insert column to the left' };
496
+ } catch (error) {
497
+ return {
498
+ success: false,
499
+ error: error instanceof Error ? error.message : 'Failed to insert column',
500
+ description: 'Insert column left',
501
+ };
502
+ }
503
+ },
504
+
505
+ 'table-add-col-right': async (ctx) => {
506
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
507
+ return { success: false, error: 'No table selected', description: 'Insert column right' };
508
+ }
509
+
510
+ try {
511
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
512
+ if (!table) {
513
+ return { success: false, error: 'Table not found', description: 'Insert column right' };
514
+ }
515
+
516
+ const targetCol = Math.max(ctx.tableSelection.startCol, ctx.tableSelection.endCol) + 1;
517
+ table.addColumn(targetCol);
518
+
519
+ return { success: true, description: 'Insert column to the right' };
520
+ } catch (error) {
521
+ return {
522
+ success: false,
523
+ error: error instanceof Error ? error.message : 'Failed to insert column',
524
+ description: 'Insert column right',
525
+ };
526
+ }
527
+ },
528
+
529
+ 'table-delete-col': async (ctx) => {
530
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
531
+ return { success: false, error: 'No table selected', description: 'Delete column' };
532
+ }
533
+
534
+ try {
535
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
536
+ if (!table) {
537
+ return { success: false, error: 'Table not found', description: 'Delete column' };
538
+ }
539
+
540
+ const rows = table.getRows();
541
+ if (rows.length > 0 && rows[0].getCells().length <= 1) {
542
+ return { success: false, error: 'Cannot delete last column', description: 'Delete column' };
543
+ }
544
+
545
+ const minCol = Math.min(ctx.tableSelection.startCol, ctx.tableSelection.endCol);
546
+ const maxCol = Math.max(ctx.tableSelection.startCol, ctx.tableSelection.endCol);
547
+
548
+ // Delete columns from right to left to maintain indices
549
+ for (let c = maxCol; c >= minCol; c--) {
550
+ table.removeColumn(c);
551
+ }
552
+
553
+ return { success: true, description: 'Delete selected columns' };
554
+ } catch (error) {
555
+ return {
556
+ success: false,
557
+ error: error instanceof Error ? error.message : 'Failed to delete column',
558
+ description: 'Delete column',
559
+ };
560
+ }
561
+ },
562
+
563
+ // Cell Operations
564
+ 'table-merge-cells': async (ctx) => {
565
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
566
+ return { success: false, error: 'No cells selected', description: 'Merge cells' };
567
+ }
568
+
569
+ try {
570
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
571
+ if (!table) {
572
+ return { success: false, error: 'Table not found', description: 'Merge cells' };
573
+ }
574
+
575
+ const { startRow, endRow, startCol, endCol } = ctx.tableSelection;
576
+
577
+ table.mergeCells(
578
+ Math.min(startRow, endRow),
579
+ Math.min(startCol, endCol),
580
+ Math.max(startRow, endRow),
581
+ Math.max(startCol, endCol)
582
+ );
583
+
584
+ return { success: true, description: 'Merge selected cells' };
585
+ } catch (error) {
586
+ return {
587
+ success: false,
588
+ error: error instanceof Error ? error.message : 'Failed to merge cells',
589
+ description: 'Merge cells',
590
+ };
591
+ }
592
+ },
593
+
594
+ 'table-split-cell': async (_ctx) => {
595
+ // Note: docxmlater may not have a direct split cell API
596
+ return {
597
+ success: false,
598
+ error: 'Split cell not yet implemented',
599
+ description: 'Split cell',
600
+ };
601
+ },
602
+
603
+ 'table-vertical-align': async (ctx) => {
604
+ if (!ctx.tableSelection || ctx.selectedTableIndex === null) {
605
+ return { success: false, error: 'No cells selected', description: 'Set vertical alignment' };
606
+ }
607
+
608
+ try {
609
+ const table = getTableAtIndex(ctx.document, ctx.selectedTableIndex);
610
+ if (!table) {
611
+ return { success: false, error: 'Table not found', description: 'Set vertical alignment' };
612
+ }
613
+
614
+ const rows = table.getRows();
615
+ const { startRow, endRow, startCol, endCol } = ctx.tableSelection;
616
+
617
+ const minRow = Math.min(startRow, endRow);
618
+ const maxRow = Math.max(startRow, endRow);
619
+ const minCol = Math.min(startCol, endCol);
620
+ const maxCol = Math.max(startCol, endCol);
621
+
622
+ for (let r = minRow; r <= maxRow && r < rows.length; r++) {
623
+ const cells = rows[r].getCells();
624
+ for (let c = minCol; c <= maxCol && c < cells.length; c++) {
625
+ cells[c].setVerticalAlignment('center');
626
+ }
627
+ }
628
+
629
+ return { success: true, description: 'Set vertical alignment to center' };
630
+ } catch (error) {
631
+ return {
632
+ success: false,
633
+ error: error instanceof Error ? error.message : 'Failed to set alignment',
634
+ description: 'Set vertical alignment',
635
+ };
636
+ }
637
+ },
638
+
639
+ 'table-cell-borders': async (_ctx) => {
640
+ return {
641
+ success: false,
642
+ error: 'Cell borders dialog not yet implemented',
643
+ description: 'Configure cell borders',
644
+ };
645
+ },
646
+
647
+ 'table-cell-shading': async (_ctx) => {
648
+ return {
649
+ success: false,
650
+ error: 'Cell shading dialog not yet implemented',
651
+ description: 'Configure cell shading',
652
+ };
653
+ },
654
+
655
+ // Structure
656
+ 'page-break': async (ctx) => {
657
+ if (!ctx.selection) {
658
+ return { success: false, error: 'No position selected', description: 'Insert page break' };
659
+ }
660
+
661
+ try {
662
+ const paragraph = getParagraphAtIndex(ctx.document, ctx.selection.paragraphIndex);
663
+ if (!paragraph) {
664
+ return { success: false, error: 'Paragraph not found', description: 'Insert page break' };
665
+ }
666
+
667
+ paragraph.setPageBreakBefore(true);
668
+
669
+ return { success: true, description: 'Insert page break before paragraph' };
670
+ } catch (error) {
671
+ return {
672
+ success: false,
673
+ error: error instanceof Error ? error.message : 'Failed to insert page break',
674
+ description: 'Insert page break',
675
+ };
676
+ }
677
+ },
678
+
679
+ 'find-replace': async (_ctx) => {
680
+ return {
681
+ success: false,
682
+ error: 'Find & Replace dialog not yet implemented',
683
+ description: 'Open Find & Replace',
684
+ };
685
+ },
686
+
687
+ // Hyperlinks
688
+ 'insert-hyperlink': async (_ctx) => {
689
+ return {
690
+ success: false,
691
+ error: 'Insert hyperlink dialog not yet implemented',
692
+ description: 'Insert hyperlink',
693
+ };
694
+ },
695
+
696
+ 'remove-hyperlink': async (ctx) => {
697
+ if (!ctx.selection) {
698
+ return { success: false, error: 'No text selected', description: 'Remove hyperlink' };
699
+ }
700
+
701
+ try {
702
+ const paragraphs = ctx.document.getAllParagraphs();
703
+ const para = paragraphs[ctx.selection.paragraphIndex];
704
+ if (!para) {
705
+ return { success: false, error: 'Paragraph not found', description: 'Remove hyperlink' };
706
+ }
707
+
708
+ // Get paragraph content to find hyperlinks
709
+ const content = para.getContent();
710
+ let removedCount = 0;
711
+
712
+ // Find and remove hyperlinks in the paragraph
713
+ for (const item of content) {
714
+ // Check if item is a hyperlink using duck typing
715
+ if (item && typeof (item as any).getUrl === 'function') {
716
+ const hyperlink = item as any;
717
+ const hyperlinkText = hyperlink.getText() || '';
718
+
719
+ // Convert hyperlink to plain text run
720
+ // Create a new run with the hyperlink's text
721
+ if (typeof hyperlink.convertToRun === 'function') {
722
+ // Use docxmlater's built-in conversion if available
723
+ hyperlink.convertToRun();
724
+ removedCount++;
725
+ } else if (typeof para.replaceContent === 'function') {
726
+ // Alternative: replace hyperlink with a text run
727
+ const Run = (await import('docxmlater')).Run;
728
+ const newRun = Run.create(hyperlinkText);
729
+ para.replaceContent(hyperlink, [newRun]);
730
+ removedCount++;
731
+ }
732
+ }
733
+ }
734
+
735
+ if (removedCount > 0) {
736
+ return { success: true, description: `Removed ${removedCount} hyperlink(s)` };
737
+ }
738
+
739
+ return {
740
+ success: false,
741
+ error: 'No hyperlinks found in selection',
742
+ description: 'Remove hyperlink',
743
+ };
744
+ } catch (error) {
745
+ return {
746
+ success: false,
747
+ error: error instanceof Error ? error.message : 'Failed to remove hyperlink',
748
+ description: 'Remove hyperlink',
749
+ };
750
+ }
751
+ },
752
+
753
+ // Tracked Changes
754
+ 'accept-all-changes': async (ctx) => {
755
+ try {
756
+ const revisionManager = (ctx.document as any).getRevisionManager?.();
757
+
758
+ if (revisionManager) {
759
+ revisionManager.acceptAll?.();
760
+ return { success: true, description: 'Accept all tracked changes' };
761
+ }
762
+
763
+ return {
764
+ success: false,
765
+ error: 'No revision manager available',
766
+ description: 'Accept all changes',
767
+ };
768
+ } catch (error) {
769
+ return {
770
+ success: false,
771
+ error: error instanceof Error ? error.message : 'Failed to accept changes',
772
+ description: 'Accept all changes',
773
+ };
774
+ }
775
+ },
776
+
777
+ 'reject-all-changes': async (ctx) => {
778
+ try {
779
+ const revisionManager = (ctx.document as any).getRevisionManager?.();
780
+
781
+ if (revisionManager) {
782
+ revisionManager.rejectAll?.();
783
+ return { success: true, description: 'Reject all tracked changes' };
784
+ }
785
+
786
+ return {
787
+ success: false,
788
+ error: 'No revision manager available',
789
+ description: 'Reject all changes',
790
+ };
791
+ } catch (error) {
792
+ return {
793
+ success: false,
794
+ error: error instanceof Error ? error.message : 'Failed to reject changes',
795
+ description: 'Reject all changes',
796
+ };
797
+ }
798
+ },
799
+ };
800
+
801
+ /**
802
+ * Execute an editor action
803
+ */
804
+ export async function executeAction(
805
+ actionId: QuickActionId,
806
+ context: ActionContext
807
+ ): Promise<ActionResult> {
808
+ const handler = actionHandlers[actionId];
809
+
810
+ if (!handler) {
811
+ return {
812
+ success: false,
813
+ error: `No handler for action: ${actionId}`,
814
+ description: `Execute ${actionId}`,
815
+ };
816
+ }
817
+
818
+ return handler(context);
819
+ }
820
+
821
+ /**
822
+ * Check if an action is available given the current context
823
+ */
824
+ export function isActionAvailable(
825
+ actionId: QuickActionId,
826
+ context: Omit<ActionContext, 'document'>
827
+ ): boolean {
828
+ // Text formatting actions require text selection
829
+ const textFormatActions: QuickActionId[] = [
830
+ 'bold',
831
+ 'italic',
832
+ 'underline',
833
+ 'clear-formatting',
834
+ ];
835
+
836
+ if (textFormatActions.includes(actionId)) {
837
+ return context.selection !== null;
838
+ }
839
+
840
+ // Paragraph style actions require paragraph selection
841
+ const paragraphStyleActions: QuickActionId[] = [
842
+ 'style-heading1',
843
+ 'style-heading2',
844
+ 'style-normal',
845
+ 'style-list-paragraph',
846
+ 'page-break',
847
+ ];
848
+
849
+ if (paragraphStyleActions.includes(actionId)) {
850
+ return context.selection !== null;
851
+ }
852
+
853
+ // Table actions require table selection
854
+ const tableActions: QuickActionId[] = [
855
+ 'apply-h2-shading',
856
+ 'apply-other-shading',
857
+ 'table-add-row-above',
858
+ 'table-add-row-below',
859
+ 'table-delete-row',
860
+ 'table-add-col-left',
861
+ 'table-add-col-right',
862
+ 'table-delete-col',
863
+ 'table-merge-cells',
864
+ 'table-split-cell',
865
+ 'table-vertical-align',
866
+ 'table-cell-borders',
867
+ 'table-cell-shading',
868
+ ];
869
+
870
+ if (tableActions.includes(actionId)) {
871
+ return context.tableSelection !== null && context.selectedTableIndex !== null;
872
+ }
873
+
874
+ // Shading actions also require shading settings
875
+ if (actionId === 'apply-h2-shading' || actionId === 'apply-other-shading') {
876
+ return (
877
+ context.tableSelection !== null &&
878
+ context.selectedTableIndex !== null &&
879
+ context.tableShadingSettings !== undefined
880
+ );
881
+ }
882
+
883
+ // Global actions are always available
884
+ const globalActions: QuickActionId[] = [
885
+ 'find-replace',
886
+ 'accept-all-changes',
887
+ 'reject-all-changes',
888
+ ];
889
+
890
+ if (globalActions.includes(actionId)) {
891
+ return true;
892
+ }
893
+
894
+ // Default to requiring some selection
895
+ return context.selection !== null || context.tableSelection !== null;
896
+ }
897
+
898
+ export default {
899
+ executeAction,
900
+ isActionAvailable,
901
+ };