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,1335 @@
1
+ import { IndentationLevel, ListBulletSettings } from '@/types/session';
2
+ import { cn } from '@/utils/cn';
3
+ import {
4
+ AlignCenter,
5
+ AlignJustify,
6
+ AlignLeft,
7
+ AlignRight,
8
+ Bold,
9
+ Check,
10
+ Italic,
11
+ List,
12
+ Lock,
13
+ Underline,
14
+ } from 'lucide-react';
15
+ import { memo, useEffect, useState } from 'react';
16
+
17
+ interface StyleDefinition {
18
+ id: string;
19
+ name: string;
20
+ fontSize: number;
21
+ fontFamily: string;
22
+ bold: boolean; // Required: true = apply bold, false = remove bold
23
+ italic: boolean; // Required: true = apply italic, false = remove italic
24
+ underline: boolean; // Required: true = apply underline, false = remove underline
25
+ preserveBold?: boolean; // Optional: true = preserve existing bold (ignore bold property)
26
+ preserveItalic?: boolean; // Optional: true = preserve existing italic (ignore italic property)
27
+ preserveUnderline?: boolean; // Optional: true = preserve existing underline (ignore underline property)
28
+ preserveCenterAlignment?: boolean; // Optional: true = preserve center alignment if paragraph is already centered
29
+ alignment: 'left' | 'center' | 'right' | 'justify';
30
+ spaceBefore: number;
31
+ spaceAfter: number;
32
+ lineSpacing: number; // 1.0 = single, 1.15 = Word default, 1.5, 2.0 = double
33
+ color: string;
34
+ noSpaceBetweenSame?: boolean;
35
+ indentation?: {
36
+ left?: number; // Left indent in inches (e.g., 0.25" for bullet position)
37
+ firstLine?: number; // First line indent in inches (e.g., 0.5" for text position)
38
+ };
39
+ }
40
+
41
+ const defaultStyles: StyleDefinition[] = [
42
+ {
43
+ id: 'header1',
44
+ name: 'Heading 1',
45
+ fontSize: 18,
46
+ fontFamily: 'Verdana',
47
+ bold: true,
48
+ italic: false,
49
+ underline: false,
50
+ alignment: 'left',
51
+ spaceBefore: 0,
52
+ spaceAfter: 12,
53
+ lineSpacing: 1.0, // Single spacing for headings
54
+ color: '#000000',
55
+ },
56
+ {
57
+ id: 'header2',
58
+ name: 'Heading 2',
59
+ fontSize: 14,
60
+ fontFamily: 'Verdana',
61
+ bold: true,
62
+ italic: false,
63
+ underline: false,
64
+ alignment: 'left',
65
+ spaceBefore: 6,
66
+ spaceAfter: 6,
67
+ lineSpacing: 1.0, // Single spacing for headings
68
+ color: '#000000',
69
+ },
70
+ {
71
+ id: 'header3',
72
+ name: 'Heading 3',
73
+ fontSize: 12,
74
+ fontFamily: 'Verdana',
75
+ bold: true,
76
+ italic: false,
77
+ underline: false,
78
+ alignment: 'left',
79
+ spaceBefore: 3,
80
+ spaceAfter: 3,
81
+ lineSpacing: 1.0, // Single spacing for headings
82
+ color: '#000000',
83
+ },
84
+ {
85
+ id: 'normal',
86
+ name: 'Normal',
87
+ fontSize: 12,
88
+ fontFamily: 'Verdana',
89
+ bold: false, // Not bold by default
90
+ italic: false, // Not italic by default
91
+ underline: false, // Not underlined by default
92
+ preserveBold: true, // Preserve existing bold formatting (Requirement 5)
93
+ preserveItalic: false, // Apply italic setting (not preserved)
94
+ preserveUnderline: false, // Apply underline setting (not preserved)
95
+ preserveCenterAlignment: true, // Preserve center alignment if paragraph is already centered
96
+ alignment: 'left',
97
+ spaceBefore: 3,
98
+ spaceAfter: 3,
99
+ lineSpacing: 1.0, // Changed from 1.15 to 1.0
100
+ color: '#000000',
101
+ noSpaceBetweenSame: false, // Allow spacing between Normal paragraphs (Requirement 5)
102
+ },
103
+ {
104
+ id: 'listParagraph',
105
+ name: 'List Paragraph',
106
+ fontSize: 12,
107
+ fontFamily: 'Verdana',
108
+ bold: false, // Not bold by default
109
+ italic: false, // Not italic by default
110
+ underline: false, // Not underlined by default
111
+ preserveBold: true, // Preserve existing bold formatting (Requirement 6)
112
+ preserveItalic: false, // Apply italic setting (not preserved)
113
+ preserveUnderline: false, // Apply underline setting (not preserved)
114
+ alignment: 'left',
115
+ spaceBefore: 0,
116
+ spaceAfter: 6,
117
+ lineSpacing: 1.0,
118
+ color: '#000000',
119
+ noSpaceBetweenSame: true, // No spacing between list items (Requirement 6)
120
+ indentation: {
121
+ left: 0.25, // Bullet position at 0.25 inches
122
+ firstLine: 0.5, // Text position at 0.5 inches (0.25 additional from left)
123
+ },
124
+ },
125
+ ];
126
+
127
+ const fontSizes = Array.from({ length: 65 }, (_, i) => i + 8); // 8pt to 72pt
128
+ const fontFamilies = ['Verdana', 'Arial', 'Times New Roman', 'Calibri', 'Georgia', 'Helvetica'];
129
+ const spacingOptions = Array.from({ length: 25 }, (_, i) => i * 3); // 0pt to 72pt in increments of 3
130
+ const lineSpacingOptions = [
131
+ { value: 1.0, label: 'Single' },
132
+ { value: 1.15, label: '1.15 (Default)' },
133
+ { value: 1.5, label: '1.5 Lines' },
134
+ { value: 2.0, label: 'Double' },
135
+ ];
136
+
137
+ // Default indentation levels based on documentation best practices
138
+ // Using closed bullet (•) for all levels for consistency
139
+ // Symbol indent: 0.25" base with 0.25" increments per level
140
+ // Text indent: symbol indent + 0.25" hanging indent
141
+ // NOTE: Levels are 0-based (0-8) per DOCX standard
142
+ // NOTE: Using Unicode bullets instead of Wingdings private-use characters for reliable rendering
143
+ // IMPORTANT: These values must match createDefaultListBulletSettings() in SessionContext.tsx
144
+ // Cascading indentation: slider sets L0 symbol indent (default 0.25")
145
+ // Each level: text indent = symbol indent + 0.25", next symbol = prev text + 0.25"
146
+ const defaultIndentationLevels: IndentationLevel[] = [
147
+ { level: 0, symbolIndent: 0.25, textIndent: 0.50, bulletChar: '•', numberedFormat: '1.' },
148
+ { level: 1, symbolIndent: 0.75, textIndent: 1.00, bulletChar: '○', numberedFormat: 'a.' },
149
+ { level: 2, symbolIndent: 1.25, textIndent: 1.50, bulletChar: '•', numberedFormat: 'i.' },
150
+ { level: 3, symbolIndent: 1.75, textIndent: 2.00, bulletChar: '○', numberedFormat: 'A.' },
151
+ { level: 4, symbolIndent: 2.25, textIndent: 2.50, bulletChar: '•', numberedFormat: 'I.' },
152
+ ];
153
+
154
+ const defaultListBulletSettings: ListBulletSettings = {
155
+ enabled: true,
156
+ indentationLevels: defaultIndentationLevels,
157
+ };
158
+
159
+ // Note: defaultTableOfContentsSettings removed - TOC managed via Processing Options
160
+ // Note: defaultTableUniformitySettings removed - Table settings now in Processing Options
161
+
162
+ /**
163
+ * Convert session styles to StyleDefinition format or use defaults.
164
+ * MOVED OUTSIDE COMPONENT: This is a pure function that doesn't depend on component state,
165
+ * so moving it outside fixes the useEffect dependency warning and improves performance.
166
+ */
167
+ const convertToStyleDefinitions = (
168
+ sessionStyles?: Partial<StyleDefinition>[]
169
+ ): StyleDefinition[] => {
170
+ if (!sessionStyles || sessionStyles.length === 0) {
171
+ return defaultStyles;
172
+ }
173
+
174
+ // Map session styles to style definitions
175
+ return sessionStyles.map((sessionStyle) => {
176
+ const defaultStyle = defaultStyles.find((d) => d.id === sessionStyle.id) || defaultStyles[0];
177
+ return {
178
+ id: sessionStyle.id || defaultStyle.id,
179
+ name: sessionStyle.name || defaultStyle.name,
180
+ fontFamily: sessionStyle.fontFamily || defaultStyle.fontFamily,
181
+ fontSize: sessionStyle.fontSize || defaultStyle.fontSize,
182
+ bold: sessionStyle.bold ?? defaultStyle.bold,
183
+ italic: sessionStyle.italic ?? defaultStyle.italic,
184
+ underline: sessionStyle.underline ?? defaultStyle.underline,
185
+ preserveBold: sessionStyle.preserveBold ?? defaultStyle.preserveBold,
186
+ preserveItalic: sessionStyle.preserveItalic ?? defaultStyle.preserveItalic,
187
+ preserveUnderline: sessionStyle.preserveUnderline ?? defaultStyle.preserveUnderline,
188
+ preserveCenterAlignment:
189
+ sessionStyle.preserveCenterAlignment ?? defaultStyle.preserveCenterAlignment,
190
+ alignment: sessionStyle.alignment || defaultStyle.alignment,
191
+ color: sessionStyle.color || defaultStyle.color,
192
+ spaceBefore: sessionStyle.spaceBefore ?? defaultStyle.spaceBefore,
193
+ spaceAfter: sessionStyle.spaceAfter ?? defaultStyle.spaceAfter,
194
+ lineSpacing: sessionStyle.lineSpacing ?? defaultStyle.lineSpacing,
195
+ noSpaceBetweenSame: sessionStyle.noSpaceBetweenSame ?? defaultStyle.noSpaceBetweenSame,
196
+ indentation: sessionStyle.indentation || defaultStyle.indentation,
197
+ };
198
+ });
199
+ };
200
+
201
+ interface TablePaddingSettings {
202
+ padding1x1Top: number;
203
+ padding1x1Bottom: number;
204
+ padding1x1Left: number;
205
+ padding1x1Right: number;
206
+ paddingOtherTop: number;
207
+ paddingOtherBottom: number;
208
+ paddingOtherLeft: number;
209
+ paddingOtherRight: number;
210
+ cellBorderThickness?: number;
211
+ }
212
+
213
+ interface StylesEditorProps {
214
+ initialStyles?: Partial<StyleDefinition>[];
215
+ initialListBulletSettings?: ListBulletSettings;
216
+ onStylesChange?: (styles: StyleDefinition[]) => void;
217
+ onListBulletSettingsChange?: (settings: ListBulletSettings) => void;
218
+ tableHeader2Shading?: string;
219
+ tableOtherShading?: string;
220
+ imageBorderWidth?: number;
221
+ // Table padding props (in inches)
222
+ padding1x1Top?: number;
223
+ padding1x1Bottom?: number;
224
+ padding1x1Left?: number;
225
+ padding1x1Right?: number;
226
+ paddingOtherTop?: number;
227
+ paddingOtherBottom?: number;
228
+ paddingOtherLeft?: number;
229
+ paddingOtherRight?: number;
230
+ cellBorderThickness?: number;
231
+ onTableShadingChange?: (
232
+ header2: string,
233
+ other: string,
234
+ imageBorderWidth?: number,
235
+ paddingSettings?: TablePaddingSettings
236
+ ) => void;
237
+ }
238
+
239
+ // PERFORMANCE: Wrap in memo to prevent re-renders when parent state changes
240
+ export const StylesEditor = memo(function StylesEditor({
241
+ initialStyles,
242
+ initialListBulletSettings,
243
+ onStylesChange,
244
+ onListBulletSettingsChange,
245
+ tableHeader2Shading,
246
+ tableOtherShading,
247
+ imageBorderWidth,
248
+ padding1x1Top,
249
+ padding1x1Bottom,
250
+ padding1x1Left,
251
+ padding1x1Right,
252
+ paddingOtherTop,
253
+ paddingOtherBottom,
254
+ paddingOtherLeft,
255
+ paddingOtherRight,
256
+ cellBorderThickness,
257
+ onTableShadingChange,
258
+ }: StylesEditorProps) {
259
+ // NOTE: convertToStyleDefinitions is now defined outside component for better performance
260
+ // and to fix useEffect dependency warning
261
+
262
+ const [styles, setStyles] = useState<StyleDefinition[]>(() =>
263
+ convertToStyleDefinitions(initialStyles)
264
+ );
265
+ const [listBulletSettings, setListBulletSettings] = useState<ListBulletSettings>(
266
+ initialListBulletSettings || defaultListBulletSettings
267
+ );
268
+ const [localTableHeader2Shading, setLocalTableHeader2Shading] = useState<string>(
269
+ tableHeader2Shading || '#BFBFBF'
270
+ );
271
+ const [localTableOtherShading, setLocalTableOtherShading] = useState<string>(
272
+ tableOtherShading || '#DFDFDF'
273
+ );
274
+ const [localImageBorderWidth, setLocalImageBorderWidth] = useState<number>(
275
+ imageBorderWidth ?? 1.0
276
+ );
277
+ // Clamp padding value to valid range (0-1 inch)
278
+ const clampPadding = (value: number | undefined, defaultValue: number): number => {
279
+ if (value === undefined) return defaultValue;
280
+ return Math.max(0, Math.min(1, value));
281
+ };
282
+
283
+ // Table padding state (in inches) - values clamped to 0-1 inch range
284
+ const [localPadding1x1Top, setLocalPadding1x1Top] = useState<number>(clampPadding(padding1x1Top, 0));
285
+ const [localPadding1x1Bottom, setLocalPadding1x1Bottom] = useState<number>(clampPadding(padding1x1Bottom, 0));
286
+ const [localPadding1x1Left, setLocalPadding1x1Left] = useState<number>(clampPadding(padding1x1Left, 0.08));
287
+ const [localPadding1x1Right, setLocalPadding1x1Right] = useState<number>(clampPadding(padding1x1Right, 0.08));
288
+ const [localPaddingOtherTop, setLocalPaddingOtherTop] = useState<number>(clampPadding(paddingOtherTop, 0));
289
+ const [localPaddingOtherBottom, setLocalPaddingOtherBottom] = useState<number>(clampPadding(paddingOtherBottom, 0));
290
+ const [localPaddingOtherLeft, setLocalPaddingOtherLeft] = useState<number>(clampPadding(paddingOtherLeft, 0.08));
291
+ const [localPaddingOtherRight, setLocalPaddingOtherRight] = useState<number>(clampPadding(paddingOtherRight, 0.08));
292
+ const [localCellBorderThickness, setLocalCellBorderThickness] = useState<number>(cellBorderThickness ?? 0.5);
293
+
294
+ // Sync internal state when external props change
295
+ // This fixes the issue where useState initializer only runs once
296
+ useEffect(() => {
297
+ if (initialStyles) {
298
+ setStyles(convertToStyleDefinitions(initialStyles));
299
+ }
300
+ }, [initialStyles]);
301
+
302
+ useEffect(() => {
303
+ if (initialListBulletSettings) {
304
+ setListBulletSettings(initialListBulletSettings);
305
+ }
306
+ }, [initialListBulletSettings]);
307
+
308
+ useEffect(() => {
309
+ if (tableHeader2Shading !== undefined) {
310
+ setLocalTableHeader2Shading(tableHeader2Shading);
311
+ }
312
+ }, [tableHeader2Shading]);
313
+
314
+ useEffect(() => {
315
+ if (tableOtherShading !== undefined) {
316
+ setLocalTableOtherShading(tableOtherShading);
317
+ }
318
+ }, [tableOtherShading]);
319
+
320
+ useEffect(() => {
321
+ if (imageBorderWidth !== undefined) {
322
+ setLocalImageBorderWidth(imageBorderWidth);
323
+ }
324
+ }, [imageBorderWidth]);
325
+
326
+ useEffect(() => {
327
+ if (cellBorderThickness !== undefined) {
328
+ setLocalCellBorderThickness(cellBorderThickness);
329
+ }
330
+ }, [cellBorderThickness]);
331
+
332
+ // Sync all padding props when they change (single effect for better reliability)
333
+ useEffect(() => {
334
+ if (padding1x1Top !== undefined) setLocalPadding1x1Top(clampPadding(padding1x1Top, 0));
335
+ if (padding1x1Bottom !== undefined) setLocalPadding1x1Bottom(clampPadding(padding1x1Bottom, 0));
336
+ if (padding1x1Left !== undefined) setLocalPadding1x1Left(clampPadding(padding1x1Left, 0.08));
337
+ if (padding1x1Right !== undefined) setLocalPadding1x1Right(clampPadding(padding1x1Right, 0.08));
338
+ if (paddingOtherTop !== undefined) setLocalPaddingOtherTop(clampPadding(paddingOtherTop, 0));
339
+ if (paddingOtherBottom !== undefined) setLocalPaddingOtherBottom(clampPadding(paddingOtherBottom, 0));
340
+ if (paddingOtherLeft !== undefined) setLocalPaddingOtherLeft(clampPadding(paddingOtherLeft, 0.08));
341
+ if (paddingOtherRight !== undefined) setLocalPaddingOtherRight(clampPadding(paddingOtherRight, 0.08));
342
+ }, [
343
+ padding1x1Top, padding1x1Bottom, padding1x1Left, padding1x1Right,
344
+ paddingOtherTop, paddingOtherBottom, paddingOtherLeft, paddingOtherRight
345
+ ]);
346
+
347
+ const updateStyle = (styleId: string, updates: Partial<StyleDefinition>) => {
348
+ const updatedStyles = styles.map((style) =>
349
+ style.id === styleId ? { ...style, ...updates } : style
350
+ );
351
+ setStyles(updatedStyles);
352
+ // Auto-save: immediately call onStylesChange to persist changes
353
+ onStylesChange?.(updatedStyles);
354
+ };
355
+
356
+ const renderStyleEditor = (style: StyleDefinition) => {
357
+ return (
358
+ <div key={style.id} className="space-y-4 p-4 border border-border rounded-lg">
359
+ <h3 className="font-semibold text-base">{style.name}</h3>
360
+
361
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
362
+ {/* Font Settings */}
363
+ <div className="space-y-3">
364
+ <div>
365
+ <label
366
+ htmlFor={`${style.id}-font-family`}
367
+ className="text-sm text-muted-foreground mb-1 block"
368
+ >
369
+ Font Family
370
+ </label>
371
+ <select
372
+ id={`${style.id}-font-family`}
373
+ value={style.fontFamily}
374
+ onChange={(e) => updateStyle(style.id, { fontFamily: e.target.value })}
375
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
376
+ >
377
+ {fontFamilies.map((font) => (
378
+ <option key={font} value={font}>
379
+ {font}
380
+ </option>
381
+ ))}
382
+ </select>
383
+ </div>
384
+
385
+ <div>
386
+ <label
387
+ htmlFor={`${style.id}-font-size`}
388
+ className="text-sm text-muted-foreground mb-1 block"
389
+ >
390
+ Font Size
391
+ </label>
392
+ <select
393
+ id={`${style.id}-font-size`}
394
+ value={style.fontSize}
395
+ onChange={(e) => updateStyle(style.id, { fontSize: Number(e.target.value) })}
396
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
397
+ >
398
+ {fontSizes.map((size) => (
399
+ <option key={size} value={size}>
400
+ {size}pt
401
+ </option>
402
+ ))}
403
+ </select>
404
+ </div>
405
+
406
+ <div>
407
+ <label
408
+ htmlFor={`${style.id}-text-color`}
409
+ className="text-sm text-muted-foreground mb-1 block"
410
+ >
411
+ Text Color
412
+ </label>
413
+ <div className="flex gap-2">
414
+ <input
415
+ id={`${style.id}-text-color`}
416
+ type="color"
417
+ value={style.color}
418
+ onChange={(e) => updateStyle(style.id, { color: e.target.value })}
419
+ className="h-9 w-16 border border-border rounded cursor-pointer"
420
+ aria-label={`${style.name} text color picker`}
421
+ />
422
+ <input
423
+ type="text"
424
+ value={style.color}
425
+ onChange={(e) => updateStyle(style.id, { color: e.target.value })}
426
+ className="flex-1 px-3 py-1.5 text-sm border border-border rounded-md bg-background"
427
+ placeholder="#000000"
428
+ aria-label={`${style.name} text color hex value`}
429
+ />
430
+ </div>
431
+ </div>
432
+ </div>
433
+
434
+ {/* Formatting Options */}
435
+ <div className="space-y-3">
436
+ <div>
437
+ <span className="text-sm text-muted-foreground mb-1 block" id={`${style.id}-formatting-label`}>
438
+ Formatting
439
+ </span>
440
+ {/* Headers: Binary toggles */}
441
+ {(style.id === 'header1' || style.id === 'header2' || style.id === 'header3') && (
442
+ <div className="flex gap-1" role="group" aria-labelledby={`${style.id}-formatting-label`}>
443
+ <button
444
+ onClick={() => updateStyle(style.id, { bold: !style.bold })}
445
+ className={cn(
446
+ 'p-2 rounded transition-all',
447
+ style.bold
448
+ ? 'bg-primary text-primary-foreground'
449
+ : 'bg-muted hover:bg-muted/80'
450
+ )}
451
+ aria-label={`Toggle bold for ${style.name}`}
452
+ aria-pressed={style.bold}
453
+ >
454
+ <Bold className="w-4 h-4" aria-hidden="true" />
455
+ </button>
456
+ <button
457
+ onClick={() => updateStyle(style.id, { italic: !style.italic })}
458
+ className={cn(
459
+ 'p-2 rounded transition-all',
460
+ style.italic
461
+ ? 'bg-primary text-primary-foreground'
462
+ : 'bg-muted hover:bg-muted/80'
463
+ )}
464
+ aria-label={`Toggle italic for ${style.name}`}
465
+ aria-pressed={style.italic}
466
+ >
467
+ <Italic className="w-4 h-4" aria-hidden="true" />
468
+ </button>
469
+ <button
470
+ onClick={() => updateStyle(style.id, { underline: !style.underline })}
471
+ className={cn(
472
+ 'p-2 rounded transition-all',
473
+ style.underline
474
+ ? 'bg-primary text-primary-foreground'
475
+ : 'bg-muted hover:bg-muted/80'
476
+ )}
477
+ aria-label={`Toggle underline for ${style.name}`}
478
+ aria-pressed={style.underline}
479
+ >
480
+ <Underline className="w-4 h-4" aria-hidden="true" />
481
+ </button>
482
+ </div>
483
+ )}
484
+ {/* Normal & ListParagraph: Dual toggles */}
485
+ {(style.id === 'normal' || style.id === 'listParagraph') && (
486
+ <div className="space-y-2">
487
+ {/* Row 1: Format toggles */}
488
+ <div className="flex gap-1">
489
+ <button
490
+ onClick={() => updateStyle(style.id, { bold: !style.bold })}
491
+ disabled={style.preserveBold}
492
+ className={cn(
493
+ 'p-2 rounded transition-all',
494
+ style.preserveBold
495
+ ? 'opacity-50 cursor-not-allowed bg-muted'
496
+ : style.bold
497
+ ? 'bg-primary text-primary-foreground'
498
+ : 'bg-muted hover:bg-muted/80'
499
+ )}
500
+ title={
501
+ style.preserveBold
502
+ ? 'Bold formatting is preserved'
503
+ : style.bold
504
+ ? 'Bold enabled'
505
+ : 'Bold disabled'
506
+ }
507
+ >
508
+ <Bold className="w-4 h-4" />
509
+ </button>
510
+ <button
511
+ onClick={() => updateStyle(style.id, { italic: !style.italic })}
512
+ disabled={style.preserveItalic}
513
+ className={cn(
514
+ 'p-2 rounded transition-all',
515
+ style.preserveItalic
516
+ ? 'opacity-50 cursor-not-allowed bg-muted'
517
+ : style.italic
518
+ ? 'bg-primary text-primary-foreground'
519
+ : 'bg-muted hover:bg-muted/80'
520
+ )}
521
+ title={
522
+ style.preserveItalic
523
+ ? 'Italic formatting is preserved'
524
+ : style.italic
525
+ ? 'Italic enabled'
526
+ : 'Italic disabled'
527
+ }
528
+ >
529
+ <Italic className="w-4 h-4" />
530
+ </button>
531
+ <button
532
+ onClick={() => updateStyle(style.id, { underline: !style.underline })}
533
+ disabled={style.preserveUnderline}
534
+ className={cn(
535
+ 'p-2 rounded transition-all',
536
+ style.preserveUnderline
537
+ ? 'opacity-50 cursor-not-allowed bg-muted'
538
+ : style.underline
539
+ ? 'bg-primary text-primary-foreground'
540
+ : 'bg-muted hover:bg-muted/80'
541
+ )}
542
+ title={
543
+ style.preserveUnderline
544
+ ? 'Underline formatting is preserved'
545
+ : style.underline
546
+ ? 'Underline enabled'
547
+ : 'Underline disabled'
548
+ }
549
+ >
550
+ <Underline className="w-4 h-4" />
551
+ </button>
552
+ </div>
553
+ {/* Row 2: Preserve toggles */}
554
+ <div className="flex gap-2 text-xs">
555
+ <button
556
+ onClick={() => updateStyle(style.id, { preserveBold: !style.preserveBold })}
557
+ className={cn(
558
+ 'flex items-center gap-1 px-2 py-1 rounded transition-all',
559
+ style.preserveBold
560
+ ? 'bg-amber-500/20 text-amber-600 dark:text-amber-400'
561
+ : 'bg-muted hover:bg-muted/80 text-muted-foreground'
562
+ )}
563
+ title={
564
+ style.preserveBold
565
+ ? 'Preserve existing bold formatting'
566
+ : 'Apply bold setting'
567
+ }
568
+ >
569
+ <Lock className="w-3 h-3" />
570
+ <span>Bold</span>
571
+ </button>
572
+ <button
573
+ onClick={() =>
574
+ updateStyle(style.id, { preserveItalic: !style.preserveItalic })
575
+ }
576
+ className={cn(
577
+ 'flex items-center gap-1 px-2 py-1 rounded transition-all',
578
+ style.preserveItalic
579
+ ? 'bg-amber-500/20 text-amber-600 dark:text-amber-400'
580
+ : 'bg-muted hover:bg-muted/80 text-muted-foreground'
581
+ )}
582
+ title={
583
+ style.preserveItalic
584
+ ? 'Preserve existing italic formatting'
585
+ : 'Apply italic setting'
586
+ }
587
+ >
588
+ <Lock className="w-3 h-3" />
589
+ <span>Italic</span>
590
+ </button>
591
+ <button
592
+ onClick={() =>
593
+ updateStyle(style.id, { preserveUnderline: !style.preserveUnderline })
594
+ }
595
+ className={cn(
596
+ 'flex items-center gap-1 px-2 py-1 rounded transition-all',
597
+ style.preserveUnderline
598
+ ? 'bg-amber-500/20 text-amber-600 dark:text-amber-400'
599
+ : 'bg-muted hover:bg-muted/80 text-muted-foreground'
600
+ )}
601
+ title={
602
+ style.preserveUnderline
603
+ ? 'Preserve existing underline formatting'
604
+ : 'Apply underline setting'
605
+ }
606
+ >
607
+ <Lock className="w-3 h-3" />
608
+ <span>Underline</span>
609
+ </button>
610
+ {style.id === 'normal' && (
611
+ <button
612
+ onClick={() =>
613
+ updateStyle(style.id, {
614
+ preserveCenterAlignment: !(style.preserveCenterAlignment ?? true),
615
+ })
616
+ }
617
+ className={cn(
618
+ 'flex items-center gap-1 px-2 py-1 rounded transition-all',
619
+ (style.preserveCenterAlignment ?? true)
620
+ ? 'bg-amber-500/20 text-amber-600 dark:text-amber-400'
621
+ : 'bg-muted hover:bg-muted/80 text-muted-foreground'
622
+ )}
623
+ title={
624
+ (style.preserveCenterAlignment ?? true)
625
+ ? 'Preserve center alignment if paragraph is centered'
626
+ : 'Apply alignment setting to all paragraphs'
627
+ }
628
+ >
629
+ <Lock className="w-3 h-3" />
630
+ <span>Center</span>
631
+ </button>
632
+ )}
633
+ </div>
634
+ </div>
635
+ )}
636
+ </div>
637
+
638
+ <div>
639
+ <label className="text-sm text-muted-foreground mb-1 block">Alignment</label>
640
+ <div className="flex gap-1">
641
+ <button
642
+ onClick={() => updateStyle(style.id, { alignment: 'left' })}
643
+ className={cn(
644
+ 'p-2 rounded transition-all',
645
+ style.alignment === 'left'
646
+ ? 'bg-primary text-primary-foreground'
647
+ : 'bg-muted hover:bg-muted/80'
648
+ )}
649
+ >
650
+ <AlignLeft className="w-4 h-4" />
651
+ </button>
652
+ <button
653
+ onClick={() => updateStyle(style.id, { alignment: 'center' })}
654
+ className={cn(
655
+ 'p-2 rounded transition-all',
656
+ style.alignment === 'center'
657
+ ? 'bg-primary text-primary-foreground'
658
+ : 'bg-muted hover:bg-muted/80'
659
+ )}
660
+ >
661
+ <AlignCenter className="w-4 h-4" />
662
+ </button>
663
+ <button
664
+ onClick={() => updateStyle(style.id, { alignment: 'right' })}
665
+ className={cn(
666
+ 'p-2 rounded transition-all',
667
+ style.alignment === 'right'
668
+ ? 'bg-primary text-primary-foreground'
669
+ : 'bg-muted hover:bg-muted/80'
670
+ )}
671
+ >
672
+ <AlignRight className="w-4 h-4" />
673
+ </button>
674
+ <button
675
+ onClick={() => updateStyle(style.id, { alignment: 'justify' })}
676
+ className={cn(
677
+ 'p-2 rounded transition-all',
678
+ style.alignment === 'justify'
679
+ ? 'bg-primary text-primary-foreground'
680
+ : 'bg-muted hover:bg-muted/80'
681
+ )}
682
+ >
683
+ <AlignJustify className="w-4 h-4" />
684
+ </button>
685
+ </div>
686
+ </div>
687
+
688
+ <div className="flex gap-2">
689
+ <div className="flex-1">
690
+ <label className="text-sm text-muted-foreground mb-1 block">Space Before</label>
691
+ <select
692
+ value={style.spaceBefore}
693
+ onChange={(e) => updateStyle(style.id, { spaceBefore: Number(e.target.value) })}
694
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
695
+ >
696
+ {spacingOptions.map((space) => (
697
+ <option key={space} value={space}>
698
+ {space}pt
699
+ </option>
700
+ ))}
701
+ </select>
702
+ </div>
703
+
704
+ <div className="flex-1">
705
+ <label className="text-sm text-muted-foreground mb-1 block">Space After</label>
706
+ <select
707
+ value={style.spaceAfter}
708
+ onChange={(e) => updateStyle(style.id, { spaceAfter: Number(e.target.value) })}
709
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
710
+ >
711
+ {spacingOptions.map((space) => (
712
+ <option key={space} value={space}>
713
+ {space}pt
714
+ </option>
715
+ ))}
716
+ </select>
717
+ </div>
718
+ </div>
719
+
720
+ <div>
721
+ <label className="text-sm text-muted-foreground mb-1 block">Line Spacing</label>
722
+ <select
723
+ value={style.lineSpacing}
724
+ onChange={(e) => updateStyle(style.id, { lineSpacing: Number(e.target.value) })}
725
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
726
+ >
727
+ {lineSpacingOptions.map((option) => (
728
+ <option key={option.value} value={option.value}>
729
+ {option.label}
730
+ </option>
731
+ ))}
732
+ </select>
733
+ </div>
734
+ </div>
735
+ </div>
736
+
737
+ {/* Style-specific options: Normal and List Paragraph */}
738
+ {(style.id === 'normal' || style.id === 'listParagraph') && (
739
+ <label className="flex items-center gap-3 cursor-pointer">
740
+ <div className="relative">
741
+ <input
742
+ type="checkbox"
743
+ checked={style.noSpaceBetweenSame}
744
+ onChange={() =>
745
+ updateStyle(style.id, { noSpaceBetweenSame: !style.noSpaceBetweenSame })
746
+ }
747
+ className="sr-only"
748
+ />
749
+ <div
750
+ className={cn(
751
+ 'w-5 h-5 rounded border-2 flex items-center justify-center transition-all',
752
+ style.noSpaceBetweenSame
753
+ ? 'bg-primary border-primary checkbox-checked'
754
+ : 'border-border'
755
+ )}
756
+ >
757
+ {style.noSpaceBetweenSame && (
758
+ <Check className="w-3 h-3 text-primary-foreground checkbox-checkmark" />
759
+ )}
760
+ </div>
761
+ </div>
762
+ <span className="text-sm">Don't add space between paragraphs of the same style</span>
763
+ </label>
764
+ )}
765
+
766
+ {/* Preview */}
767
+ <div className="p-3 bg-white rounded-md border border-border">
768
+ <div
769
+ style={{
770
+ fontSize: `${style.fontSize}pt`,
771
+ fontFamily: style.fontFamily,
772
+ fontWeight: style.bold === undefined ? 'normal' : style.bold ? 'bold' : 'normal',
773
+ fontStyle: style.italic === undefined ? 'normal' : style.italic ? 'italic' : 'normal',
774
+ textDecoration:
775
+ style.underline === undefined ? 'none' : style.underline ? 'underline' : 'none',
776
+ textAlign: style.alignment,
777
+ color: style.color,
778
+ }}
779
+ >
780
+ Sample text for {style.name} style
781
+ {(style.bold === undefined ||
782
+ style.italic === undefined ||
783
+ style.underline === undefined) && (
784
+ <span className="text-xs text-muted-foreground ml-2">
785
+ (preview only - actual formatting preserved)
786
+ </span>
787
+ )}
788
+ </div>
789
+ </div>
790
+ </div>
791
+ );
792
+ };
793
+
794
+ // Note: handleSaveStyles function removed - auto-save is now implemented
795
+ // All changes are persisted immediately via the update helper functions
796
+
797
+ const renderListBulletSettings = () => {
798
+ return (
799
+ <div className="space-y-4 p-4 border border-border rounded-lg">
800
+ <div className="flex items-center gap-2">
801
+ <List className="w-5 h-5 text-primary" />
802
+ <h3 className="font-semibold text-base">Lists & Bullets Uniformity</h3>
803
+ </div>
804
+
805
+ <div className="space-y-3">
806
+ <h4 className="text-sm font-medium text-muted-foreground">
807
+ Indentation Settings
808
+ </h4>
809
+ <p className="text-xs text-muted-foreground">
810
+ Adjust symbol position per level. Text follows with 0.25" hanging indent.
811
+ </p>
812
+ <div className="space-y-2">
813
+ <div className="flex items-center justify-between">
814
+ <label className="text-xs text-muted-foreground">
815
+ Symbol Position Increment
816
+ </label>
817
+ <span className="text-sm font-medium tabular-nums">
818
+ {(listBulletSettings.indentationLevels[0]?.symbolIndent || 0.25).toFixed(2)}"
819
+ </span>
820
+ </div>
821
+ <input
822
+ type="range"
823
+ value={listBulletSettings.indentationLevels[0]?.symbolIndent || 0.25}
824
+ onChange={(e) => {
825
+ const startIndent = Number(e.target.value); // Slider sets L0 symbol indent
826
+ const hangingIndent = 0.25; // Fixed hanging indent
827
+ const numberedFormats = ['1.', 'a.', 'i.', 'A.', 'I.'];
828
+ // Cascading: each level's text = symbol + 0.25", next symbol = prev text + 0.25"
829
+ const newLevels: IndentationLevel[] = [];
830
+ let currentSymbol = startIndent;
831
+ for (let i = 0; i < 5; i++) {
832
+ const currentText = currentSymbol + hangingIndent;
833
+ newLevels.push({
834
+ level: i,
835
+ symbolIndent: currentSymbol,
836
+ textIndent: currentText,
837
+ bulletChar: listBulletSettings.indentationLevels[i]?.bulletChar || (i % 2 === 0 ? '•' : '○'),
838
+ numberedFormat: numberedFormats[i],
839
+ });
840
+ currentSymbol = currentText + hangingIndent;
841
+ }
842
+ // Only update local state for visual feedback during drag
843
+ setListBulletSettings({ ...listBulletSettings, indentationLevels: newLevels });
844
+ }}
845
+ onPointerUp={() => {
846
+ // Save to parent only on release
847
+ onListBulletSettingsChange?.(listBulletSettings);
848
+ }}
849
+ onMouseUp={() => {
850
+ // Fallback for non-pointer devices
851
+ onListBulletSettingsChange?.(listBulletSettings);
852
+ }}
853
+ className="w-full h-2 bg-muted rounded-lg appearance-none cursor-pointer accent-primary"
854
+ min="0.25"
855
+ max="1.5"
856
+ step="0.25"
857
+ />
858
+ <div className="flex justify-between text-xxs text-muted-foreground">
859
+ <span>0.25"</span>
860
+ <span>1.5"</span>
861
+ </div>
862
+ </div>
863
+
864
+ {/* Preview of calculated levels */}
865
+ <div className="p-3 bg-muted/20 rounded-md">
866
+ <div className="text-xs text-muted-foreground mb-2">Calculated Indentations:</div>
867
+ <div className="grid grid-cols-5 gap-2 text-xxs">
868
+ {listBulletSettings.indentationLevels.map((level: IndentationLevel) => (
869
+ <div key={level.level} className="text-center">
870
+ <div className="font-medium">L{level.level}</div>
871
+ <div className="text-muted-foreground">
872
+ {level.symbolIndent.toFixed(2)}" / {level.textIndent.toFixed(2)}"
873
+ </div>
874
+ </div>
875
+ ))}
876
+ </div>
877
+ </div>
878
+ </div>
879
+
880
+ {/* Bullet Points Format */}
881
+ <div className="space-y-3">
882
+ <h4 className="text-sm font-medium text-muted-foreground">Bullet Points Format</h4>
883
+ <div className="text-xs text-muted-foreground mb-2">
884
+ Configure bullet symbols for each of the 5 indentation levels.
885
+ </div>
886
+ <div className="grid grid-cols-1 md:grid-cols-5 gap-3">
887
+ {/* Level 0 - Default: Closed */}
888
+ <div>
889
+ <label className="text-xs text-muted-foreground mb-1 block">Level 0</label>
890
+ <select
891
+ value={listBulletSettings.indentationLevels[0]?.bulletChar || '•'}
892
+ onChange={(e) => {
893
+ const newLevels = [...listBulletSettings.indentationLevels];
894
+ newLevels[0] = { ...newLevels[0], bulletChar: e.target.value };
895
+ const newSettings = { ...listBulletSettings, indentationLevels: newLevels };
896
+ setListBulletSettings(newSettings);
897
+ onListBulletSettingsChange?.(newSettings);
898
+ }}
899
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
900
+ >
901
+ <option value="•">• Closed Bullet</option>
902
+ <option value="○">○ Open Bullet</option>
903
+ <option value="■">■ Closed Square</option>
904
+ </select>
905
+ </div>
906
+ {/* Level 1 - Default: Open */}
907
+ <div>
908
+ <label className="text-xs text-muted-foreground mb-1 block">Level 1</label>
909
+ <select
910
+ value={listBulletSettings.indentationLevels[1]?.bulletChar || '○'}
911
+ onChange={(e) => {
912
+ const newLevels = [...listBulletSettings.indentationLevels];
913
+ newLevels[1] = { ...newLevels[1], bulletChar: e.target.value };
914
+ const newSettings = { ...listBulletSettings, indentationLevels: newLevels };
915
+ setListBulletSettings(newSettings);
916
+ onListBulletSettingsChange?.(newSettings);
917
+ }}
918
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
919
+ >
920
+ <option value="•">• Closed Bullet</option>
921
+ <option value="○">○ Open Bullet</option>
922
+ <option value="■">■ Closed Square</option>
923
+ </select>
924
+ </div>
925
+ {/* Level 2 - Default: Closed */}
926
+ <div>
927
+ <label className="text-xs text-muted-foreground mb-1 block">Level 2</label>
928
+ <select
929
+ value={listBulletSettings.indentationLevels[2]?.bulletChar || '•'}
930
+ onChange={(e) => {
931
+ const newLevels = [...listBulletSettings.indentationLevels];
932
+ newLevels[2] = { ...newLevels[2], bulletChar: e.target.value };
933
+ const newSettings = { ...listBulletSettings, indentationLevels: newLevels };
934
+ setListBulletSettings(newSettings);
935
+ onListBulletSettingsChange?.(newSettings);
936
+ }}
937
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
938
+ >
939
+ <option value="•">• Closed Bullet</option>
940
+ <option value="○">○ Open Bullet</option>
941
+ <option value="■">■ Closed Square</option>
942
+ </select>
943
+ </div>
944
+ {/* Level 3 - Default: Open */}
945
+ <div>
946
+ <label className="text-xs text-muted-foreground mb-1 block">Level 3</label>
947
+ <select
948
+ value={listBulletSettings.indentationLevels[3]?.bulletChar || '○'}
949
+ onChange={(e) => {
950
+ const newLevels = [...listBulletSettings.indentationLevels];
951
+ newLevels[3] = { ...newLevels[3], bulletChar: e.target.value };
952
+ const newSettings = { ...listBulletSettings, indentationLevels: newLevels };
953
+ setListBulletSettings(newSettings);
954
+ onListBulletSettingsChange?.(newSettings);
955
+ }}
956
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
957
+ >
958
+ <option value="•">• Closed Bullet</option>
959
+ <option value="○">○ Open Bullet</option>
960
+ <option value="■">■ Closed Square</option>
961
+ </select>
962
+ </div>
963
+ {/* Level 4 - Default: Closed */}
964
+ <div>
965
+ <label className="text-xs text-muted-foreground mb-1 block">Level 4</label>
966
+ <select
967
+ value={listBulletSettings.indentationLevels[4]?.bulletChar || '•'}
968
+ onChange={(e) => {
969
+ const newLevels = [...listBulletSettings.indentationLevels];
970
+ newLevels[4] = { ...newLevels[4], bulletChar: e.target.value };
971
+ const newSettings = { ...listBulletSettings, indentationLevels: newLevels };
972
+ setListBulletSettings(newSettings);
973
+ onListBulletSettingsChange?.(newSettings);
974
+ }}
975
+ className="w-full px-3 py-1.5 text-sm border border-border rounded-md bg-background"
976
+ >
977
+ <option value="•">• Closed Bullet</option>
978
+ <option value="○">○ Open Bullet</option>
979
+ <option value="■">■ Closed Square</option>
980
+ </select>
981
+ </div>
982
+ </div>
983
+ {/* Preview of bullet pattern */}
984
+ <div className="p-3 bg-muted/20 rounded-md">
985
+ <div className="text-xs text-muted-foreground mb-2">Bullet Pattern Preview:</div>
986
+ <div className="grid grid-cols-5 gap-2 text-sm">
987
+ {listBulletSettings.indentationLevels.map((level: IndentationLevel) => (
988
+ <div key={level.level} className="text-center">
989
+ <div className="font-medium text-xs text-muted-foreground">
990
+ L{level.level}
991
+ </div>
992
+ <div className="text-lg">
993
+ {level.bulletChar}
994
+ </div>
995
+ </div>
996
+ ))}
997
+ </div>
998
+ </div>
999
+ </div>
1000
+ </div>
1001
+ );
1002
+ };
1003
+
1004
+ // Table Shading Settings - moved from Processing Options to Styles for better organization
1005
+ const renderTableShadingSettings = () => {
1006
+ // Helper to get current padding settings (and border thickness)
1007
+ const getCurrentPaddingSettings = (): TablePaddingSettings => ({
1008
+ padding1x1Top: localPadding1x1Top,
1009
+ padding1x1Bottom: localPadding1x1Bottom,
1010
+ padding1x1Left: localPadding1x1Left,
1011
+ padding1x1Right: localPadding1x1Right,
1012
+ paddingOtherTop: localPaddingOtherTop,
1013
+ paddingOtherBottom: localPaddingOtherBottom,
1014
+ paddingOtherLeft: localPaddingOtherLeft,
1015
+ paddingOtherRight: localPaddingOtherRight,
1016
+ cellBorderThickness: localCellBorderThickness,
1017
+ });
1018
+
1019
+ const handleHeader2ShadingChange = (value: string) => {
1020
+ setLocalTableHeader2Shading(value);
1021
+ onTableShadingChange?.(value, localTableOtherShading, localImageBorderWidth, getCurrentPaddingSettings());
1022
+ };
1023
+
1024
+ const handleOtherShadingChange = (value: string) => {
1025
+ setLocalTableOtherShading(value);
1026
+ onTableShadingChange?.(localTableHeader2Shading, value, localImageBorderWidth, getCurrentPaddingSettings());
1027
+ };
1028
+
1029
+ const handleImageBorderWidthChange = (value: string) => {
1030
+ const numValue = parseFloat(value);
1031
+ if (!isNaN(numValue) && numValue >= 0.5 && numValue <= 10) {
1032
+ setLocalImageBorderWidth(numValue);
1033
+ onTableShadingChange?.(localTableHeader2Shading, localTableOtherShading, numValue, getCurrentPaddingSettings());
1034
+ }
1035
+ };
1036
+
1037
+ // Handle padding changes - update local state and call parent callback
1038
+ const handlePaddingChange = (
1039
+ field: keyof TablePaddingSettings,
1040
+ value: string,
1041
+ setter: React.Dispatch<React.SetStateAction<number>>
1042
+ ) => {
1043
+ const numValue = parseFloat(value);
1044
+ if (!isNaN(numValue) && numValue >= 0 && numValue <= 1) {
1045
+ setter(numValue);
1046
+ const updatedPadding = { ...getCurrentPaddingSettings(), [field]: numValue };
1047
+ onTableShadingChange?.(localTableHeader2Shading, localTableOtherShading, localImageBorderWidth, updatedPadding);
1048
+ }
1049
+ };
1050
+
1051
+ const handleCellBorderThicknessChange = (value: string) => {
1052
+ const numValue = parseFloat(value);
1053
+ if (!isNaN(numValue)) {
1054
+ setLocalCellBorderThickness(numValue);
1055
+ const updatedPadding = { ...getCurrentPaddingSettings(), cellBorderThickness: numValue };
1056
+ onTableShadingChange?.(localTableHeader2Shading, localTableOtherShading, localImageBorderWidth, updatedPadding);
1057
+ }
1058
+ };
1059
+
1060
+ // Cell border thickness options in points
1061
+ const borderThicknessOptions = [
1062
+ { value: 0.25, label: '0.25 pt' },
1063
+ { value: 0.5, label: '0.5 pt (Default)' },
1064
+ { value: 0.75, label: '0.75 pt' },
1065
+ { value: 1, label: '1 pt' },
1066
+ { value: 1.5, label: '1.5 pt' },
1067
+ { value: 2, label: '2 pt' },
1068
+ { value: 2.25, label: '2.25 pt' },
1069
+ { value: 3, label: '3 pt' },
1070
+ ];
1071
+
1072
+ return (
1073
+ <div className="space-y-4 p-4 border border-border rounded-lg">
1074
+ <div className="flex items-center gap-2">
1075
+ <span className="font-semibold text-base">Table & Image Settings</span>
1076
+ </div>
1077
+ <p className="text-sm text-muted-foreground">
1078
+ Configure default shading colors for table cells, image border width, and cell padding
1079
+ </p>
1080
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
1081
+ <div>
1082
+ <label className="text-sm text-muted-foreground mb-2 block">
1083
+ Header 2 Table Shading
1084
+ </label>
1085
+ <div className="flex gap-2">
1086
+ <input
1087
+ type="color"
1088
+ value={localTableHeader2Shading}
1089
+ onChange={(e) => handleHeader2ShadingChange(e.target.value)}
1090
+ className="h-9 w-16 border border-border rounded cursor-pointer"
1091
+ />
1092
+ <input
1093
+ type="text"
1094
+ value={localTableHeader2Shading}
1095
+ onChange={(e) => handleHeader2ShadingChange(e.target.value)}
1096
+ className="flex-1 px-3 py-1.5 text-sm border border-border rounded-md bg-background"
1097
+ placeholder="#BFBFBF"
1098
+ />
1099
+ </div>
1100
+ </div>
1101
+
1102
+ <div>
1103
+ <label className="text-sm text-muted-foreground mb-2 block">Other Table Shading</label>
1104
+ <div className="flex gap-2">
1105
+ <input
1106
+ type="color"
1107
+ value={localTableOtherShading}
1108
+ onChange={(e) => handleOtherShadingChange(e.target.value)}
1109
+ className="h-9 w-16 border border-border rounded cursor-pointer"
1110
+ />
1111
+ <input
1112
+ type="text"
1113
+ value={localTableOtherShading}
1114
+ onChange={(e) => handleOtherShadingChange(e.target.value)}
1115
+ className="flex-1 px-3 py-1.5 text-sm border border-border rounded-md bg-background"
1116
+ placeholder="#DFDFDF"
1117
+ />
1118
+ </div>
1119
+ </div>
1120
+
1121
+ <div>
1122
+ <label className="text-sm text-muted-foreground mb-2 block">Image Border Width</label>
1123
+ <div className="flex gap-2 items-center">
1124
+ <input
1125
+ type="number"
1126
+ step="0.5"
1127
+ min="0.5"
1128
+ max="10"
1129
+ value={localImageBorderWidth}
1130
+ onChange={(e) => handleImageBorderWidthChange(e.target.value)}
1131
+ className="w-24 px-3 py-1.5 text-sm border border-border rounded-md bg-background"
1132
+ />
1133
+ <span className="text-sm text-muted-foreground">pt</span>
1134
+ </div>
1135
+ <p className="text-xs text-muted-foreground mt-1">
1136
+ Border thickness for centered images (0.5 - 10pt)
1137
+ </p>
1138
+ </div>
1139
+ </div>
1140
+
1141
+ {/* Cell Border Thickness Section */}
1142
+ <div className="pt-4 border-t border-border">
1143
+ <h4 className="text-sm font-medium mb-3">Cell Border Thickness</h4>
1144
+ <p className="text-xs text-muted-foreground mb-4">
1145
+ Set the border thickness for all table cells. Borders with color #FFC000 will preserve their color.
1146
+ </p>
1147
+ <div className="flex gap-2 items-center">
1148
+ <select
1149
+ value={localCellBorderThickness}
1150
+ onChange={(e) => handleCellBorderThicknessChange(e.target.value)}
1151
+ className="w-48 px-3 py-1.5 text-sm border border-border rounded-md bg-background"
1152
+ >
1153
+ {borderThicknessOptions.map((option) => (
1154
+ <option key={option.value} value={option.value}>
1155
+ {option.label}
1156
+ </option>
1157
+ ))}
1158
+ </select>
1159
+ </div>
1160
+ </div>
1161
+
1162
+ {/* Table Cell Padding Section */}
1163
+ <div className="pt-4 border-t border-border">
1164
+ <h4 className="text-sm font-medium mb-3">Table Cell Padding</h4>
1165
+ <p className="text-xs text-muted-foreground mb-4">
1166
+ Set cell padding for 1x1 tables and other tables. Values are in inches.
1167
+ </p>
1168
+
1169
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
1170
+ {/* 1x1 Tables Padding */}
1171
+ <div className="space-y-3">
1172
+ <h5 className="text-sm font-medium text-muted-foreground">1x1 Tables</h5>
1173
+ <div className="grid grid-cols-2 gap-3">
1174
+ <div>
1175
+ <label className="text-xs text-muted-foreground mb-1 block">Top</label>
1176
+ <div className="flex gap-1 items-center">
1177
+ <input
1178
+ type="number"
1179
+ step="0.01"
1180
+ min="0"
1181
+ max="1"
1182
+ value={localPadding1x1Top}
1183
+ onChange={(e) => handlePaddingChange('padding1x1Top', e.target.value, setLocalPadding1x1Top)}
1184
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1185
+ />
1186
+ <span className="text-xs text-muted-foreground">"</span>
1187
+ </div>
1188
+ </div>
1189
+ <div>
1190
+ <label className="text-xs text-muted-foreground mb-1 block">Bottom</label>
1191
+ <div className="flex gap-1 items-center">
1192
+ <input
1193
+ type="number"
1194
+ step="0.01"
1195
+ min="0"
1196
+ max="1"
1197
+ value={localPadding1x1Bottom}
1198
+ onChange={(e) => handlePaddingChange('padding1x1Bottom', e.target.value, setLocalPadding1x1Bottom)}
1199
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1200
+ />
1201
+ <span className="text-xs text-muted-foreground">"</span>
1202
+ </div>
1203
+ </div>
1204
+ <div>
1205
+ <label className="text-xs text-muted-foreground mb-1 block">Left</label>
1206
+ <div className="flex gap-1 items-center">
1207
+ <input
1208
+ type="number"
1209
+ step="0.01"
1210
+ min="0"
1211
+ max="1"
1212
+ value={localPadding1x1Left}
1213
+ onChange={(e) => handlePaddingChange('padding1x1Left', e.target.value, setLocalPadding1x1Left)}
1214
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1215
+ />
1216
+ <span className="text-xs text-muted-foreground">"</span>
1217
+ </div>
1218
+ </div>
1219
+ <div>
1220
+ <label className="text-xs text-muted-foreground mb-1 block">Right</label>
1221
+ <div className="flex gap-1 items-center">
1222
+ <input
1223
+ type="number"
1224
+ step="0.01"
1225
+ min="0"
1226
+ max="1"
1227
+ value={localPadding1x1Right}
1228
+ onChange={(e) => handlePaddingChange('padding1x1Right', e.target.value, setLocalPadding1x1Right)}
1229
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1230
+ />
1231
+ <span className="text-xs text-muted-foreground">"</span>
1232
+ </div>
1233
+ </div>
1234
+ </div>
1235
+ </div>
1236
+
1237
+ {/* Other Tables Padding */}
1238
+ <div className="space-y-3">
1239
+ <h5 className="text-sm font-medium text-muted-foreground">Other Tables</h5>
1240
+ <div className="grid grid-cols-2 gap-3">
1241
+ <div>
1242
+ <label className="text-xs text-muted-foreground mb-1 block">Top</label>
1243
+ <div className="flex gap-1 items-center">
1244
+ <input
1245
+ type="number"
1246
+ step="0.01"
1247
+ min="0"
1248
+ max="1"
1249
+ value={localPaddingOtherTop}
1250
+ onChange={(e) => handlePaddingChange('paddingOtherTop', e.target.value, setLocalPaddingOtherTop)}
1251
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1252
+ />
1253
+ <span className="text-xs text-muted-foreground">"</span>
1254
+ </div>
1255
+ </div>
1256
+ <div>
1257
+ <label className="text-xs text-muted-foreground mb-1 block">Bottom</label>
1258
+ <div className="flex gap-1 items-center">
1259
+ <input
1260
+ type="number"
1261
+ step="0.01"
1262
+ min="0"
1263
+ max="1"
1264
+ value={localPaddingOtherBottom}
1265
+ onChange={(e) => handlePaddingChange('paddingOtherBottom', e.target.value, setLocalPaddingOtherBottom)}
1266
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1267
+ />
1268
+ <span className="text-xs text-muted-foreground">"</span>
1269
+ </div>
1270
+ </div>
1271
+ <div>
1272
+ <label className="text-xs text-muted-foreground mb-1 block">Left</label>
1273
+ <div className="flex gap-1 items-center">
1274
+ <input
1275
+ type="number"
1276
+ step="0.01"
1277
+ min="0"
1278
+ max="1"
1279
+ value={localPaddingOtherLeft}
1280
+ onChange={(e) => handlePaddingChange('paddingOtherLeft', e.target.value, setLocalPaddingOtherLeft)}
1281
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1282
+ />
1283
+ <span className="text-xs text-muted-foreground">"</span>
1284
+ </div>
1285
+ </div>
1286
+ <div>
1287
+ <label className="text-xs text-muted-foreground mb-1 block">Right</label>
1288
+ <div className="flex gap-1 items-center">
1289
+ <input
1290
+ type="number"
1291
+ step="0.01"
1292
+ min="0"
1293
+ max="1"
1294
+ value={localPaddingOtherRight}
1295
+ onChange={(e) => handlePaddingChange('paddingOtherRight', e.target.value, setLocalPaddingOtherRight)}
1296
+ className="w-20 px-2 py-1 text-sm border border-border rounded-md bg-background"
1297
+ />
1298
+ <span className="text-xs text-muted-foreground">"</span>
1299
+ </div>
1300
+ </div>
1301
+ </div>
1302
+ </div>
1303
+ </div>
1304
+ </div>
1305
+ </div>
1306
+ );
1307
+ };
1308
+
1309
+ // Note: Table of Contents settings removed - TOC is now managed automatically via "Update Table of Contents Hyperlinks" checkbox in Processing Options
1310
+
1311
+ return (
1312
+ <div className="space-y-4">
1313
+ {/* Note: Save button removed - all changes auto-save immediately */}
1314
+
1315
+ {/* Document Uniformity Settings */}
1316
+ {renderListBulletSettings()}
1317
+
1318
+ {/* Table Shading Colors */}
1319
+ {renderTableShadingSettings()}
1320
+
1321
+ {/* Divider */}
1322
+ <div className="relative py-4">
1323
+ <div className="absolute inset-0 flex items-center">
1324
+ <div className="w-full border-t border-border"></div>
1325
+ </div>
1326
+ <div className="relative flex justify-center">
1327
+ <span className="bg-background px-4 text-sm text-muted-foreground">Paragraph Styles</span>
1328
+ </div>
1329
+ </div>
1330
+
1331
+ {/* Individual Style Editors */}
1332
+ {styles.map((style) => renderStyleEditor(style))}
1333
+ </div>
1334
+ );
1335
+ });