documentation-hub 5.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,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
|
+
});
|