md-redline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/md-redline +255 -0
- package/bin/test-windows.ps1 +70 -0
- package/dist/assets/_baseFor-Ck08IaSF.js +1 -0
- package/dist/assets/arc-DI2g9LXK.js +1 -0
- package/dist/assets/architecture-YZFGNWBL-BDgMfc-b.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-Dg1hcUEa.js +36 -0
- package/dist/assets/array-DOVTz2Mq.js +1 -0
- package/dist/assets/blockDiagram-DXYQGD6D-BAXkTCAk.js +132 -0
- package/dist/assets/c4Diagram-AHTNJAMY-BIkgwQSx.js +10 -0
- package/dist/assets/channel-DPCihw7y.js +1 -0
- package/dist/assets/chunk-2KRD3SAO-Dc_tBGsw.js +1 -0
- package/dist/assets/chunk-336JU56O-Dhi-ID9Y.js +2 -0
- package/dist/assets/chunk-426QAEUC-DnFdrNMW.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-Z63FkGov.js +1 -0
- package/dist/assets/chunk-4TB4RGXK-BAiBlfyy.js +206 -0
- package/dist/assets/chunk-55IACEB6-BXDWXbxy.js +1 -0
- package/dist/assets/chunk-5FUZZQ4R-C72e1c_O.js +62 -0
- package/dist/assets/chunk-5PVQY5BW-BBHW_uCu.js +2 -0
- package/dist/assets/chunk-67CJDMHE-3Cf_D9m6.js +1 -0
- package/dist/assets/chunk-7N4EOEYR-DAXUXJ2c.js +1 -0
- package/dist/assets/chunk-AA7GKIK3-Dr7fOryc.js +1 -0
- package/dist/assets/chunk-BSJP7CBP-BmsSs1Nt.js +1 -0
- package/dist/assets/chunk-CIAEETIT-QDzV-X_Y.js +1 -0
- package/dist/assets/chunk-EDXVE4YY-C25WFHxY.js +1 -0
- package/dist/assets/chunk-ENJZ2VHE-_OzxcZOU.js +10 -0
- package/dist/assets/chunk-FMBD7UC4-CjsTKY4u.js +15 -0
- package/dist/assets/chunk-FOC6F5B3-g-xaH5nc.js +1 -0
- package/dist/assets/chunk-ICPOFSXX-iKiUSjDK.js +121 -0
- package/dist/assets/chunk-K5T4RW27-CKR-lPBN.js +94 -0
- package/dist/assets/chunk-KGLVRYIC-DRccT-B_.js +1 -0
- package/dist/assets/chunk-LIHQZDEY-DTbMwMXj.js +1 -0
- package/dist/assets/chunk-ORNJ4GCN-DlerdcWX.js +1 -0
- package/dist/assets/chunk-OYMX7WX6-Dekv1on2.js +231 -0
- package/dist/assets/chunk-QZHKN3VN-BHu0RdKl.js +1 -0
- package/dist/assets/chunk-U2HBQHQK-BvtlVHAg.js +70 -0
- package/dist/assets/chunk-X2U36JSP-BI_g8mub.js +1 -0
- package/dist/assets/chunk-XPW4576I-B39JkmSE.js +32 -0
- package/dist/assets/chunk-YZCP3GAM-BfPcXRm2.js +1 -0
- package/dist/assets/chunk-ZZ45TVLE-Bg4q68wZ.js +1 -0
- package/dist/assets/classDiagram-6PBFFD2Q-p73p727_.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-C4Ftpivp.js +1 -0
- package/dist/assets/clone-CI9aUwHe.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-7BpAeDh5.js +1 -0
- package/dist/assets/cytoscape.esm-DoTFyJaN.js +321 -0
- package/dist/assets/dagre-CilMRazv.js +1 -0
- package/dist/assets/dagre-KV5264BT-DDMqpjkB.js +4 -0
- package/dist/assets/defaultLocale-Ck2Xxk-C.js +1 -0
- package/dist/assets/diagram-5BDNPKRD-BFeyfnCx.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-DoqT-PtF.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-BPV6KADk.js +43 -0
- package/dist/assets/diagram-TYMM5635-okvcTBtl.js +24 -0
- package/dist/assets/dist-C_eddq6m.js +1 -0
- package/dist/assets/erDiagram-SMLLAGMA-Dl-Ixy8n.js +85 -0
- package/dist/assets/flatten-B8XIuT0x.js +1 -0
- package/dist/assets/flowDiagram-DWJPFMVM-CsqWAx5r.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-mIt6zVeF.js +292 -0
- package/dist/assets/gitGraph-7Q5UKJZL-COXHGMvj.js +1 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-syVqZJX_.js +106 -0
- package/dist/assets/graphlib-Bpd0q3yO.js +1 -0
- package/dist/assets/index-BoggyWS0.css +2 -0
- package/dist/assets/index-aLvjHQW4.js +104 -0
- package/dist/assets/info-OMHHGYJF-B-0wfxwL.js +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-C0_uqsVa.js +2 -0
- package/dist/assets/init-Bft5Ffpj.js +1 -0
- package/dist/assets/isEmpty-BrFi5AqV.js +1 -0
- package/dist/assets/ishikawaDiagram-UXIWVN3A-CTjFbDBV.js +70 -0
- package/dist/assets/journeyDiagram-VCZTEJTY-BDBcej1q.js +139 -0
- package/dist/assets/kanban-definition-6JOO6SKY-Ylgzakw7.js +89 -0
- package/dist/assets/katex-Uj9wLT16.js +265 -0
- package/dist/assets/line-CRxEwpOv.js +1 -0
- package/dist/assets/linear-PDPfFByd.js +1 -0
- package/dist/assets/mermaid-parser.core-CY-XNOOy.js +4 -0
- package/dist/assets/mermaid.core-BPlTADIX.js +11 -0
- package/dist/assets/mindmap-definition-QFDTVHPH-TefzJnBM.js +96 -0
- package/dist/assets/ordinal-DIg8h6NI.js +1 -0
- package/dist/assets/packet-4T2RLAQJ-BW1T_A-C.js +1 -0
- package/dist/assets/path-DfRbCp9y.js +1 -0
- package/dist/assets/pie-ZZUOXDRM-DkKU-SFu.js +1 -0
- package/dist/assets/pieDiagram-DEJITSTG-BCXuaeEy.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-VSBAicWL.js +7 -0
- package/dist/assets/radar-PYXPWWZC-CYvTacKJ.js +1 -0
- package/dist/assets/reduce-CV2X8n1a.js +1 -0
- package/dist/assets/requirementDiagram-MS252O5E-4NeL9Z6J.js +84 -0
- package/dist/assets/rough.esm-Bbn_-PMU.js +1 -0
- package/dist/assets/sankeyDiagram-XADWPNL6-DMBSDnrH.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-DVpzcZUi.js +157 -0
- package/dist/assets/src-PKe5NtkK.js +1 -0
- package/dist/assets/stateDiagram-FHFEXIEX-BkHTlCjL.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-nMeWu9fP.js +1 -0
- package/dist/assets/timeline-definition-GMOUNBTQ-CyLt92nf.js +120 -0
- package/dist/assets/treeView-SZITEDCU-BUgcJ4eR.js +1 -0
- package/dist/assets/treemap-W4RFUUIX-BIWGQ4Pw.js +1 -0
- package/dist/assets/vennDiagram-DHZGUBPP-BCK0xB_m.js +34 -0
- package/dist/assets/wardley-RL74JXVD-DMZZRlby.js +1 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-BisBgfsF.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-D_REDciv.js +7 -0
- package/dist/favicon.svg +15 -0
- package/dist/index.html +14 -0
- package/dist/screenshot.png +0 -0
- package/index.html +13 -0
- package/package.json +105 -0
- package/public/favicon.svg +15 -0
- package/public/screenshot.png +0 -0
- package/server/index.test.ts +814 -0
- package/server/index.ts +736 -0
- package/server/preferences.test.ts +126 -0
- package/server/preferences.ts +76 -0
- package/src/App.tsx +1620 -0
- package/src/components/ActionButton.tsx +41 -0
- package/src/components/CommandPalette.tsx +191 -0
- package/src/components/CommentCard.tsx +556 -0
- package/src/components/CommentForm.tsx +285 -0
- package/src/components/CommentSidebar.tsx +428 -0
- package/src/components/ConfirmDialog.tsx +64 -0
- package/src/components/ContextMenu.tsx +220 -0
- package/src/components/DragHandles.tsx +48 -0
- package/src/components/FileExplorer.tsx +251 -0
- package/src/components/FileOpener.tsx +304 -0
- package/src/components/IconButton.tsx +32 -0
- package/src/components/KeyboardShortcutsPanel.tsx +136 -0
- package/src/components/MarkdownViewer.tsx +682 -0
- package/src/components/RawView.tsx +798 -0
- package/src/components/SearchBar.tsx +129 -0
- package/src/components/Separator.tsx +7 -0
- package/src/components/SettingsPanel.tsx +813 -0
- package/src/components/SplitIconButton.tsx +133 -0
- package/src/components/TabBar.tsx +594 -0
- package/src/components/TableOfContents.tsx +70 -0
- package/src/components/ThemeSelector.tsx +159 -0
- package/src/components/Toast.tsx +99 -0
- package/src/components/Toolbar.tsx +161 -0
- package/src/components/iconButtonVariants.ts +19 -0
- package/src/components/rawView.test.ts +291 -0
- package/src/contexts/SettingsContext.tsx +120 -0
- package/src/hooks/useAuthor.test.ts +58 -0
- package/src/hooks/useAuthor.ts +69 -0
- package/src/hooks/useAutoResize.ts +20 -0
- package/src/hooks/useCommentCardTriggers.ts +20 -0
- package/src/hooks/useComments.test.ts +773 -0
- package/src/hooks/useComments.ts +332 -0
- package/src/hooks/useContextMenu.ts +48 -0
- package/src/hooks/useContextMenuItems.ts +392 -0
- package/src/hooks/useDiffSnapshot.test.ts +130 -0
- package/src/hooks/useDiffSnapshot.ts +67 -0
- package/src/hooks/useDragHandles.ts +417 -0
- package/src/hooks/useFileWatcher.ts +45 -0
- package/src/hooks/useHeadingTracking.ts +84 -0
- package/src/hooks/useMermaidRenderer.ts +75 -0
- package/src/hooks/useModalState.ts +22 -0
- package/src/hooks/usePageVisible.test.ts +69 -0
- package/src/hooks/usePageVisible.ts +19 -0
- package/src/hooks/usePaneLayout.test.ts +108 -0
- package/src/hooks/usePaneLayout.ts +102 -0
- package/src/hooks/useRecentFiles.test.ts +103 -0
- package/src/hooks/useRecentFiles.ts +99 -0
- package/src/hooks/useResizablePanel.test.ts +84 -0
- package/src/hooks/useResizablePanel.ts +118 -0
- package/src/hooks/useSearch.test.ts +72 -0
- package/src/hooks/useSearch.ts +53 -0
- package/src/hooks/useSelection.ts +48 -0
- package/src/hooks/useSessionPersistence.test.ts +59 -0
- package/src/hooks/useSessionPersistence.ts +43 -0
- package/src/hooks/useTabs.test.ts +127 -0
- package/src/hooks/useTabs.ts +561 -0
- package/src/hooks/useThemePersistence.ts +41 -0
- package/src/hooks/useToast.ts +27 -0
- package/src/index.css +1047 -0
- package/src/lib/agent-prompts.test.ts +34 -0
- package/src/lib/agent-prompts.ts +68 -0
- package/src/lib/comment-editor-state.ts +6 -0
- package/src/lib/comment-parser.test.ts +1959 -0
- package/src/lib/comment-parser.ts +1021 -0
- package/src/lib/diff.test.ts +164 -0
- package/src/lib/diff.ts +139 -0
- package/src/lib/heading-slugs.test.ts +85 -0
- package/src/lib/heading-slugs.ts +44 -0
- package/src/lib/http.test.ts +43 -0
- package/src/lib/http.ts +29 -0
- package/src/lib/mermaid-highlights.test.ts +517 -0
- package/src/lib/mermaid-highlights.ts +936 -0
- package/src/lib/mermaid-renderer.test.ts +114 -0
- package/src/lib/mermaid-renderer.ts +89 -0
- package/src/lib/path-utils.test.ts +17 -0
- package/src/lib/path-utils.ts +7 -0
- package/src/lib/platform.test.ts +58 -0
- package/src/lib/platform.ts +14 -0
- package/src/lib/preferences-client.test.ts +177 -0
- package/src/lib/preferences-client.ts +94 -0
- package/src/lib/selection-resolver.test.ts +118 -0
- package/src/lib/selection-resolver.ts +37 -0
- package/src/lib/settings.test.ts +152 -0
- package/src/lib/settings.ts +78 -0
- package/src/lib/shortcut-label.tsx +18 -0
- package/src/lib/themes.ts +21 -0
- package/src/lib/visible-text.test.ts +86 -0
- package/src/lib/visible-text.ts +77 -0
- package/src/main.tsx +22 -0
- package/src/markdown/pipeline.test.ts +82 -0
- package/src/markdown/pipeline.ts +33 -0
- package/src/types.test.ts +43 -0
- package/src/types.ts +46 -0
- package/tsconfig.app.json +28 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +50 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useThemePersistence } from '../hooks/useThemePersistence';
|
|
3
|
+
import { useSettings } from '../contexts/SettingsContext';
|
|
4
|
+
import type { CommentTemplate } from '../lib/settings';
|
|
5
|
+
import { DEFAULT_TEMPLATES } from '../lib/settings';
|
|
6
|
+
import { LIGHT_THEMES, DARK_THEMES } from '../lib/themes';
|
|
7
|
+
|
|
8
|
+
type Section = 'templates' | 'general' | 'theme';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
author: string;
|
|
14
|
+
onAuthorChange: (name: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function SettingsPanel({ open, onClose, author, onAuthorChange }: Props) {
|
|
18
|
+
const {
|
|
19
|
+
settings,
|
|
20
|
+
updateTemplates,
|
|
21
|
+
updateCommentMaxLength,
|
|
22
|
+
updateShowTemplatesByDefault,
|
|
23
|
+
updateEnableResolve,
|
|
24
|
+
updateQuickComment,
|
|
25
|
+
resetTemplates,
|
|
26
|
+
} = useSettings();
|
|
27
|
+
const { theme, setTheme } = useThemePersistence();
|
|
28
|
+
const [activeSection, setActiveSection] = useState<Section>('general');
|
|
29
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
|
|
31
|
+
// Local draft state for templates editing
|
|
32
|
+
const [draftTemplates, setDraftTemplates] = useState<CommentTemplate[]>(settings.templates);
|
|
33
|
+
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
34
|
+
const [editLabel, setEditLabel] = useState('');
|
|
35
|
+
const [editText, setEditText] = useState('');
|
|
36
|
+
const [addingNew, setAddingNew] = useState(false);
|
|
37
|
+
const [newLabel, setNewLabel] = useState('');
|
|
38
|
+
const [newText, setNewText] = useState('');
|
|
39
|
+
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
|
40
|
+
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
|
41
|
+
|
|
42
|
+
// Local draft state for general settings
|
|
43
|
+
const [draftAuthor, setDraftAuthor] = useState(author);
|
|
44
|
+
const [draftMaxLength, setDraftMaxLength] = useState(String(settings.commentMaxLength));
|
|
45
|
+
|
|
46
|
+
const authorInputRef = useRef<HTMLInputElement>(null);
|
|
47
|
+
const newLabelRef = useRef<HTMLInputElement>(null);
|
|
48
|
+
|
|
49
|
+
// Sync drafts when panel opens (not during editing to avoid discarding in-progress changes)
|
|
50
|
+
const prevOpenRef = useRef(false);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (open && !prevOpenRef.current) {
|
|
53
|
+
setDraftTemplates(settings.templates);
|
|
54
|
+
setDraftAuthor(author);
|
|
55
|
+
setDraftMaxLength(String(settings.commentMaxLength));
|
|
56
|
+
setEditingIndex(null);
|
|
57
|
+
setAddingNew(false);
|
|
58
|
+
}
|
|
59
|
+
prevOpenRef.current = open;
|
|
60
|
+
}, [open, settings.templates, author, settings.commentMaxLength]);
|
|
61
|
+
|
|
62
|
+
// Focus new template label input when adding
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (addingNew && newLabelRef.current) {
|
|
65
|
+
newLabelRef.current.focus();
|
|
66
|
+
}
|
|
67
|
+
}, [addingNew]);
|
|
68
|
+
|
|
69
|
+
// Close on Escape
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!open) return;
|
|
72
|
+
const handler = (e: KeyboardEvent) => {
|
|
73
|
+
if (e.key === 'Escape') {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
onClose();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener('keydown', handler);
|
|
79
|
+
return () => document.removeEventListener('keydown', handler);
|
|
80
|
+
}, [open, onClose]);
|
|
81
|
+
|
|
82
|
+
// --- Template operations ---
|
|
83
|
+
const handleDeleteTemplate = useCallback(
|
|
84
|
+
(index: number) => {
|
|
85
|
+
const next = draftTemplates.filter((_, i) => i !== index);
|
|
86
|
+
setDraftTemplates(next);
|
|
87
|
+
updateTemplates(next);
|
|
88
|
+
if (editingIndex === index) setEditingIndex(null);
|
|
89
|
+
},
|
|
90
|
+
[draftTemplates, editingIndex, updateTemplates],
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const handleStartEdit = useCallback(
|
|
94
|
+
(index: number) => {
|
|
95
|
+
setEditingIndex(index);
|
|
96
|
+
setEditLabel(draftTemplates[index].label);
|
|
97
|
+
setEditText(draftTemplates[index].text);
|
|
98
|
+
setAddingNew(false);
|
|
99
|
+
},
|
|
100
|
+
[draftTemplates],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const handleSaveEdit = useCallback(() => {
|
|
104
|
+
if (editingIndex === null || !editLabel.trim() || !editText.trim()) return;
|
|
105
|
+
const next = [...draftTemplates];
|
|
106
|
+
next[editingIndex] = { label: editLabel.trim(), text: editText.trim() };
|
|
107
|
+
setDraftTemplates(next);
|
|
108
|
+
updateTemplates(next);
|
|
109
|
+
setEditingIndex(null);
|
|
110
|
+
}, [editingIndex, editLabel, editText, draftTemplates, updateTemplates]);
|
|
111
|
+
|
|
112
|
+
const handleAddTemplate = useCallback(() => {
|
|
113
|
+
if (!newLabel.trim() || !newText.trim()) return;
|
|
114
|
+
const next = [...draftTemplates, { label: newLabel.trim(), text: newText.trim() }];
|
|
115
|
+
setDraftTemplates(next);
|
|
116
|
+
updateTemplates(next);
|
|
117
|
+
setNewLabel('');
|
|
118
|
+
setNewText('');
|
|
119
|
+
setAddingNew(false);
|
|
120
|
+
}, [newLabel, newText, draftTemplates, updateTemplates]);
|
|
121
|
+
|
|
122
|
+
const handleResetTemplates = useCallback(() => {
|
|
123
|
+
resetTemplates();
|
|
124
|
+
setDraftTemplates(DEFAULT_TEMPLATES);
|
|
125
|
+
setEditingIndex(null);
|
|
126
|
+
setAddingNew(false);
|
|
127
|
+
}, [resetTemplates]);
|
|
128
|
+
|
|
129
|
+
const handleDragStart = useCallback((index: number) => {
|
|
130
|
+
setDragIndex(index);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
const handleDragOver = useCallback((e: React.DragEvent, index: number) => {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
setDragOverIndex(index);
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const handleDrop = useCallback(
|
|
139
|
+
(targetIndex: number) => {
|
|
140
|
+
if (dragIndex === null || dragIndex === targetIndex) {
|
|
141
|
+
setDragIndex(null);
|
|
142
|
+
setDragOverIndex(null);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const next = [...draftTemplates];
|
|
146
|
+
const [moved] = next.splice(dragIndex, 1);
|
|
147
|
+
const adjustedTarget = targetIndex > dragIndex ? targetIndex - 1 : targetIndex;
|
|
148
|
+
next.splice(adjustedTarget, 0, moved);
|
|
149
|
+
setDraftTemplates(next);
|
|
150
|
+
updateTemplates(next);
|
|
151
|
+
if (editingIndex === dragIndex) setEditingIndex(adjustedTarget);
|
|
152
|
+
setDragIndex(null);
|
|
153
|
+
setDragOverIndex(null);
|
|
154
|
+
},
|
|
155
|
+
[dragIndex, draftTemplates, editingIndex, updateTemplates],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const handleDragEnd = useCallback(() => {
|
|
159
|
+
setDragIndex(null);
|
|
160
|
+
setDragOverIndex(null);
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
// --- General settings ---
|
|
164
|
+
const handleAuthorBlur = useCallback(() => {
|
|
165
|
+
const trimmed = draftAuthor.trim() || 'User';
|
|
166
|
+
setDraftAuthor(trimmed);
|
|
167
|
+
onAuthorChange(trimmed);
|
|
168
|
+
}, [draftAuthor, onAuthorChange]);
|
|
169
|
+
|
|
170
|
+
const handleMaxLengthBlur = useCallback(() => {
|
|
171
|
+
const num = parseInt(draftMaxLength);
|
|
172
|
+
const valid = !isNaN(num) && num > 0 ? num : 1000;
|
|
173
|
+
setDraftMaxLength(String(valid));
|
|
174
|
+
updateCommentMaxLength(valid);
|
|
175
|
+
}, [draftMaxLength, updateCommentMaxLength]);
|
|
176
|
+
|
|
177
|
+
if (!open) return null;
|
|
178
|
+
|
|
179
|
+
const sections: { key: Section; label: string; icon: React.ReactNode }[] = [
|
|
180
|
+
{
|
|
181
|
+
key: 'general',
|
|
182
|
+
label: 'General',
|
|
183
|
+
icon: (
|
|
184
|
+
<svg
|
|
185
|
+
className="w-4 h-4"
|
|
186
|
+
fill="none"
|
|
187
|
+
viewBox="0 0 24 24"
|
|
188
|
+
stroke="currentColor"
|
|
189
|
+
strokeWidth={2}
|
|
190
|
+
>
|
|
191
|
+
<path
|
|
192
|
+
strokeLinecap="round"
|
|
193
|
+
strokeLinejoin="round"
|
|
194
|
+
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"
|
|
195
|
+
/>
|
|
196
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
197
|
+
</svg>
|
|
198
|
+
),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
key: 'templates',
|
|
202
|
+
label: 'Templates',
|
|
203
|
+
icon: (
|
|
204
|
+
<svg
|
|
205
|
+
className="w-4 h-4"
|
|
206
|
+
fill="none"
|
|
207
|
+
viewBox="0 0 24 24"
|
|
208
|
+
stroke="currentColor"
|
|
209
|
+
strokeWidth={2}
|
|
210
|
+
>
|
|
211
|
+
<path
|
|
212
|
+
strokeLinecap="round"
|
|
213
|
+
strokeLinejoin="round"
|
|
214
|
+
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
|
|
215
|
+
/>
|
|
216
|
+
</svg>
|
|
217
|
+
),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
key: 'theme',
|
|
221
|
+
label: 'Theme',
|
|
222
|
+
icon: (
|
|
223
|
+
<svg
|
|
224
|
+
className="w-4 h-4"
|
|
225
|
+
fill="none"
|
|
226
|
+
viewBox="0 0 24 24"
|
|
227
|
+
stroke="currentColor"
|
|
228
|
+
strokeWidth={2}
|
|
229
|
+
>
|
|
230
|
+
<path
|
|
231
|
+
strokeLinecap="round"
|
|
232
|
+
strokeLinejoin="round"
|
|
233
|
+
d="M4.098 19.902a3.75 3.75 0 005.304 0l6.401-6.402M6.75 21A3.75 3.75 0 013 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 003.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008z"
|
|
234
|
+
/>
|
|
235
|
+
</svg>
|
|
236
|
+
),
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div className="fixed inset-0 z-[100] flex items-center justify-center" onClick={onClose}>
|
|
242
|
+
{/* Backdrop */}
|
|
243
|
+
<div className="absolute inset-0 bg-black/40" />
|
|
244
|
+
|
|
245
|
+
{/* Panel */}
|
|
246
|
+
<div
|
|
247
|
+
ref={panelRef}
|
|
248
|
+
className="relative w-full max-w-3xl max-h-[85vh] bg-surface-raised rounded-xl shadow-2xl border border-border overflow-hidden flex flex-col"
|
|
249
|
+
onClick={(e) => e.stopPropagation()}
|
|
250
|
+
>
|
|
251
|
+
{/* Header */}
|
|
252
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-border shrink-0">
|
|
253
|
+
<h2 className="text-sm font-semibold text-content">Settings</h2>
|
|
254
|
+
<button
|
|
255
|
+
onClick={onClose}
|
|
256
|
+
className="p-1 rounded text-content-muted hover:text-content-secondary hover:bg-tint transition-colors"
|
|
257
|
+
>
|
|
258
|
+
<svg
|
|
259
|
+
className="w-4 h-4"
|
|
260
|
+
viewBox="0 0 24 24"
|
|
261
|
+
fill="none"
|
|
262
|
+
stroke="currentColor"
|
|
263
|
+
strokeWidth={2}
|
|
264
|
+
>
|
|
265
|
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
266
|
+
</svg>
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<div className="flex h-[580px]">
|
|
271
|
+
{/* Sidebar navigation */}
|
|
272
|
+
<div className="w-40 border-r border-border bg-surface-secondary shrink-0 py-2">
|
|
273
|
+
{sections.map((s) => (
|
|
274
|
+
<button
|
|
275
|
+
key={s.key}
|
|
276
|
+
onClick={() => setActiveSection(s.key)}
|
|
277
|
+
className={`w-full text-left px-4 py-2 flex items-center gap-2.5 text-sm transition-colors ${
|
|
278
|
+
activeSection === s.key
|
|
279
|
+
? 'bg-primary-bg text-primary-text font-medium'
|
|
280
|
+
: 'text-content-secondary hover:bg-tint hover:text-content'
|
|
281
|
+
}`}
|
|
282
|
+
>
|
|
283
|
+
{s.icon}
|
|
284
|
+
{s.label}
|
|
285
|
+
</button>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
{/* Content area */}
|
|
290
|
+
<div className="flex-1 overflow-y-auto p-5">
|
|
291
|
+
{activeSection === 'templates' && (
|
|
292
|
+
<div>
|
|
293
|
+
{/* Show templates by default — standalone preference */}
|
|
294
|
+
<label className="flex items-center justify-between gap-4">
|
|
295
|
+
<div>
|
|
296
|
+
<h3 className="text-sm font-semibold text-content">
|
|
297
|
+
Show templates by default
|
|
298
|
+
</h3>
|
|
299
|
+
<p className="text-xs text-content-muted mt-0.5">
|
|
300
|
+
Automatically show the template picker when adding a new comment.
|
|
301
|
+
</p>
|
|
302
|
+
</div>
|
|
303
|
+
<button
|
|
304
|
+
role="switch"
|
|
305
|
+
aria-checked={settings.showTemplatesByDefault}
|
|
306
|
+
onClick={() => updateShowTemplatesByDefault(!settings.showTemplatesByDefault)}
|
|
307
|
+
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
|
|
308
|
+
settings.showTemplatesByDefault ? 'bg-primary' : 'bg-border'
|
|
309
|
+
}`}
|
|
310
|
+
>
|
|
311
|
+
<span
|
|
312
|
+
className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
|
|
313
|
+
settings.showTemplatesByDefault ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
|
314
|
+
}`}
|
|
315
|
+
/>
|
|
316
|
+
</button>
|
|
317
|
+
</label>
|
|
318
|
+
|
|
319
|
+
{/* Divider */}
|
|
320
|
+
<div className="border-t border-border-subtle my-4" />
|
|
321
|
+
|
|
322
|
+
{/* Template list header */}
|
|
323
|
+
<div className="flex items-center justify-between mb-1">
|
|
324
|
+
<h3 className="text-sm font-semibold text-content">Templates</h3>
|
|
325
|
+
<button
|
|
326
|
+
onClick={handleResetTemplates}
|
|
327
|
+
className="text-xs px-2.5 py-1 rounded-md border border-border-subtle text-content-secondary hover:bg-tint transition-colors"
|
|
328
|
+
>
|
|
329
|
+
Reset to defaults
|
|
330
|
+
</button>
|
|
331
|
+
</div>
|
|
332
|
+
<p className="text-xs text-content-muted mb-3">
|
|
333
|
+
Drag to reorder. The order here matches the template picker.
|
|
334
|
+
</p>
|
|
335
|
+
|
|
336
|
+
{/* Template list */}
|
|
337
|
+
<div className="space-y-1">
|
|
338
|
+
{draftTemplates.map((t, i) => (
|
|
339
|
+
<div
|
|
340
|
+
key={`${t.label}-${t.text}`}
|
|
341
|
+
draggable={editingIndex !== i}
|
|
342
|
+
onDragStart={() => handleDragStart(i)}
|
|
343
|
+
onDragOver={(e) => handleDragOver(e, i)}
|
|
344
|
+
onDrop={() => handleDrop(i)}
|
|
345
|
+
onDragEnd={handleDragEnd}
|
|
346
|
+
>
|
|
347
|
+
{editingIndex === i ? (
|
|
348
|
+
/* Editing inline */
|
|
349
|
+
<div className="rounded-lg border border-primary-border bg-primary-bg p-3 space-y-2">
|
|
350
|
+
<input
|
|
351
|
+
value={editLabel}
|
|
352
|
+
onChange={(e) => setEditLabel(e.target.value)}
|
|
353
|
+
placeholder="Label"
|
|
354
|
+
className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
355
|
+
/>
|
|
356
|
+
<textarea
|
|
357
|
+
value={editText}
|
|
358
|
+
onChange={(e) => setEditText(e.target.value)}
|
|
359
|
+
placeholder="Template text"
|
|
360
|
+
rows={2}
|
|
361
|
+
className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
362
|
+
/>
|
|
363
|
+
<div className="flex gap-2 justify-end">
|
|
364
|
+
<button
|
|
365
|
+
onClick={() => setEditingIndex(null)}
|
|
366
|
+
className="text-xs px-2.5 py-1 rounded-md text-content-secondary hover:bg-tint transition-colors"
|
|
367
|
+
>
|
|
368
|
+
Cancel
|
|
369
|
+
</button>
|
|
370
|
+
<button
|
|
371
|
+
onClick={handleSaveEdit}
|
|
372
|
+
disabled={!editLabel.trim() || !editText.trim()}
|
|
373
|
+
className="text-xs px-2.5 py-1 rounded-md bg-primary text-on-primary hover:bg-primary-hover transition-colors disabled:opacity-40"
|
|
374
|
+
>
|
|
375
|
+
Save
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
) : (
|
|
380
|
+
/* Display row */
|
|
381
|
+
<div
|
|
382
|
+
className={`group flex items-center gap-2 rounded-lg px-3 py-2 transition-colors ${
|
|
383
|
+
dragOverIndex === i && dragIndex !== i
|
|
384
|
+
? 'border-t-2 border-primary'
|
|
385
|
+
: 'border-t-2 border-transparent'
|
|
386
|
+
} ${dragIndex === i ? 'opacity-40' : 'hover:bg-tint'}`}
|
|
387
|
+
>
|
|
388
|
+
{/* Drag handle */}
|
|
389
|
+
<span className="shrink-0 cursor-grab active:cursor-grabbing text-content-faint hover:text-content-muted">
|
|
390
|
+
<svg
|
|
391
|
+
className="w-4 h-4"
|
|
392
|
+
fill="none"
|
|
393
|
+
viewBox="0 0 24 24"
|
|
394
|
+
stroke="currentColor"
|
|
395
|
+
strokeWidth={2}
|
|
396
|
+
>
|
|
397
|
+
<path
|
|
398
|
+
strokeLinecap="round"
|
|
399
|
+
strokeLinejoin="round"
|
|
400
|
+
d="M3.75 9h16.5m-16.5 6.75h16.5"
|
|
401
|
+
/>
|
|
402
|
+
</svg>
|
|
403
|
+
</span>
|
|
404
|
+
|
|
405
|
+
{/* Label + text preview */}
|
|
406
|
+
<div className="flex-1 min-w-0">
|
|
407
|
+
<span className="text-sm text-content font-medium">{t.label}</span>
|
|
408
|
+
<p className="text-xs text-content-muted truncate">{t.text}</p>
|
|
409
|
+
</div>
|
|
410
|
+
|
|
411
|
+
{/* Actions */}
|
|
412
|
+
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
|
|
413
|
+
<button
|
|
414
|
+
onClick={() => handleStartEdit(i)}
|
|
415
|
+
className="p-1 rounded text-content-muted hover:text-primary-text hover:bg-tint-primary transition-colors"
|
|
416
|
+
title="Edit"
|
|
417
|
+
>
|
|
418
|
+
<svg
|
|
419
|
+
className="w-3.5 h-3.5"
|
|
420
|
+
fill="none"
|
|
421
|
+
viewBox="0 0 24 24"
|
|
422
|
+
stroke="currentColor"
|
|
423
|
+
strokeWidth={2}
|
|
424
|
+
>
|
|
425
|
+
<path
|
|
426
|
+
strokeLinecap="round"
|
|
427
|
+
strokeLinejoin="round"
|
|
428
|
+
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487z"
|
|
429
|
+
/>
|
|
430
|
+
</svg>
|
|
431
|
+
</button>
|
|
432
|
+
<button
|
|
433
|
+
onClick={() => handleDeleteTemplate(i)}
|
|
434
|
+
className="p-1 rounded text-content-muted hover:text-danger-text hover:bg-tint-danger transition-colors"
|
|
435
|
+
title="Delete"
|
|
436
|
+
>
|
|
437
|
+
<svg
|
|
438
|
+
className="w-3.5 h-3.5"
|
|
439
|
+
fill="none"
|
|
440
|
+
viewBox="0 0 24 24"
|
|
441
|
+
stroke="currentColor"
|
|
442
|
+
strokeWidth={2}
|
|
443
|
+
>
|
|
444
|
+
<path
|
|
445
|
+
strokeLinecap="round"
|
|
446
|
+
strokeLinejoin="round"
|
|
447
|
+
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
|
448
|
+
/>
|
|
449
|
+
</svg>
|
|
450
|
+
</button>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
</div>
|
|
455
|
+
))}
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
{/* Add new template */}
|
|
459
|
+
{addingNew ? (
|
|
460
|
+
<div className="mt-3 rounded-lg border border-primary-border bg-primary-bg p-3 space-y-2">
|
|
461
|
+
<input
|
|
462
|
+
ref={newLabelRef}
|
|
463
|
+
value={newLabel}
|
|
464
|
+
onChange={(e) => setNewLabel(e.target.value)}
|
|
465
|
+
placeholder="Label (e.g. Clarify)"
|
|
466
|
+
className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
467
|
+
onKeyDown={(e) => {
|
|
468
|
+
if (e.key === 'Escape') {
|
|
469
|
+
setAddingNew(false);
|
|
470
|
+
setNewLabel('');
|
|
471
|
+
setNewText('');
|
|
472
|
+
}
|
|
473
|
+
}}
|
|
474
|
+
/>
|
|
475
|
+
<textarea
|
|
476
|
+
value={newText}
|
|
477
|
+
onChange={(e) => setNewText(e.target.value)}
|
|
478
|
+
placeholder="Template text (e.g. Please clarify this section.)"
|
|
479
|
+
rows={2}
|
|
480
|
+
className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
481
|
+
onKeyDown={(e) => {
|
|
482
|
+
if (e.key === 'Escape') {
|
|
483
|
+
setAddingNew(false);
|
|
484
|
+
setNewLabel('');
|
|
485
|
+
setNewText('');
|
|
486
|
+
}
|
|
487
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
488
|
+
e.preventDefault();
|
|
489
|
+
handleAddTemplate();
|
|
490
|
+
}
|
|
491
|
+
}}
|
|
492
|
+
/>
|
|
493
|
+
<div className="flex gap-2 justify-end">
|
|
494
|
+
<button
|
|
495
|
+
onClick={() => {
|
|
496
|
+
setAddingNew(false);
|
|
497
|
+
setNewLabel('');
|
|
498
|
+
setNewText('');
|
|
499
|
+
}}
|
|
500
|
+
className="text-xs px-2.5 py-1 rounded-md text-content-secondary hover:bg-tint transition-colors"
|
|
501
|
+
>
|
|
502
|
+
Cancel
|
|
503
|
+
</button>
|
|
504
|
+
<button
|
|
505
|
+
onClick={handleAddTemplate}
|
|
506
|
+
disabled={!newLabel.trim() || !newText.trim()}
|
|
507
|
+
className="text-xs px-2.5 py-1 rounded-md bg-primary text-on-primary hover:bg-primary-hover transition-colors disabled:opacity-40"
|
|
508
|
+
>
|
|
509
|
+
Add
|
|
510
|
+
</button>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
) : (
|
|
514
|
+
<button
|
|
515
|
+
onClick={() => {
|
|
516
|
+
setAddingNew(true);
|
|
517
|
+
setEditingIndex(null);
|
|
518
|
+
}}
|
|
519
|
+
className="mt-3 w-full text-left px-3 py-2 rounded-lg border border-dashed border-border-subtle text-sm text-content-muted hover:text-primary-text hover:border-primary-border hover:bg-tint-primary transition-colors flex items-center gap-2"
|
|
520
|
+
>
|
|
521
|
+
<svg
|
|
522
|
+
className="w-4 h-4"
|
|
523
|
+
fill="none"
|
|
524
|
+
viewBox="0 0 24 24"
|
|
525
|
+
stroke="currentColor"
|
|
526
|
+
strokeWidth={2}
|
|
527
|
+
>
|
|
528
|
+
<path
|
|
529
|
+
strokeLinecap="round"
|
|
530
|
+
strokeLinejoin="round"
|
|
531
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
532
|
+
/>
|
|
533
|
+
</svg>
|
|
534
|
+
Add template
|
|
535
|
+
</button>
|
|
536
|
+
)}
|
|
537
|
+
</div>
|
|
538
|
+
)}
|
|
539
|
+
|
|
540
|
+
{activeSection === 'general' && (
|
|
541
|
+
<div className="space-y-6">
|
|
542
|
+
{/* Author Name */}
|
|
543
|
+
<div>
|
|
544
|
+
<h3 className="text-sm font-semibold text-content mb-1">Author Name</h3>
|
|
545
|
+
<p className="text-xs text-content-muted mb-2">
|
|
546
|
+
Name attached to your comments and replies.
|
|
547
|
+
</p>
|
|
548
|
+
<input
|
|
549
|
+
ref={authorInputRef}
|
|
550
|
+
value={draftAuthor}
|
|
551
|
+
onChange={(e) => setDraftAuthor(e.target.value)}
|
|
552
|
+
onBlur={handleAuthorBlur}
|
|
553
|
+
onKeyDown={(e) => {
|
|
554
|
+
if (e.key === 'Enter') {
|
|
555
|
+
e.preventDefault();
|
|
556
|
+
handleAuthorBlur();
|
|
557
|
+
(e.target as HTMLInputElement).blur();
|
|
558
|
+
}
|
|
559
|
+
}}
|
|
560
|
+
className="w-60 text-sm px-3 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
561
|
+
placeholder="Your name"
|
|
562
|
+
/>
|
|
563
|
+
</div>
|
|
564
|
+
|
|
565
|
+
{/* Enable Resolve */}
|
|
566
|
+
<div>
|
|
567
|
+
<label className="flex items-center justify-between gap-4">
|
|
568
|
+
<div>
|
|
569
|
+
<h3 className="text-sm font-semibold text-content">
|
|
570
|
+
Enable resolve workflow
|
|
571
|
+
</h3>
|
|
572
|
+
<p className="text-xs text-content-muted mt-0.5">
|
|
573
|
+
Adds resolve and reopen actions for reviewing with humans. Leave off when
|
|
574
|
+
working with AI agents.
|
|
575
|
+
</p>
|
|
576
|
+
</div>
|
|
577
|
+
<button
|
|
578
|
+
role="switch"
|
|
579
|
+
aria-checked={settings.enableResolve}
|
|
580
|
+
onClick={() => updateEnableResolve(!settings.enableResolve)}
|
|
581
|
+
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
|
|
582
|
+
settings.enableResolve ? 'bg-primary' : 'bg-border'
|
|
583
|
+
}`}
|
|
584
|
+
>
|
|
585
|
+
<span
|
|
586
|
+
className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
|
|
587
|
+
settings.enableResolve ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
|
588
|
+
}`}
|
|
589
|
+
/>
|
|
590
|
+
</button>
|
|
591
|
+
</label>
|
|
592
|
+
</div>
|
|
593
|
+
|
|
594
|
+
{/* Quick Comment */}
|
|
595
|
+
<div>
|
|
596
|
+
<label className="flex items-center justify-between gap-4">
|
|
597
|
+
<div>
|
|
598
|
+
<h3 className="text-sm font-semibold text-content">Quick comment</h3>
|
|
599
|
+
<p className="text-xs text-content-muted mt-0.5">
|
|
600
|
+
Open the comment form immediately when text is selected, skipping the
|
|
601
|
+
"Comment" button.
|
|
602
|
+
</p>
|
|
603
|
+
</div>
|
|
604
|
+
<button
|
|
605
|
+
role="switch"
|
|
606
|
+
aria-checked={settings.quickComment}
|
|
607
|
+
onClick={() => updateQuickComment(!settings.quickComment)}
|
|
608
|
+
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
|
|
609
|
+
settings.quickComment ? 'bg-primary' : 'bg-border'
|
|
610
|
+
}`}
|
|
611
|
+
>
|
|
612
|
+
<span
|
|
613
|
+
className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
|
|
614
|
+
settings.quickComment ? 'translate-x-[18px]' : 'translate-x-[3px]'
|
|
615
|
+
}`}
|
|
616
|
+
/>
|
|
617
|
+
</button>
|
|
618
|
+
</label>
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
{/* Comment Max Length */}
|
|
622
|
+
<div>
|
|
623
|
+
<h3 className="text-sm font-semibold text-content mb-1">Comment Max Length</h3>
|
|
624
|
+
<p className="text-xs text-content-muted mb-2">
|
|
625
|
+
Maximum characters per comment. Long inline markers can confuse AI agents
|
|
626
|
+
parsing the file.
|
|
627
|
+
</p>
|
|
628
|
+
<input
|
|
629
|
+
type="number"
|
|
630
|
+
min="50"
|
|
631
|
+
max="10000"
|
|
632
|
+
value={draftMaxLength}
|
|
633
|
+
onChange={(e) => setDraftMaxLength(e.target.value)}
|
|
634
|
+
onBlur={handleMaxLengthBlur}
|
|
635
|
+
onKeyDown={(e) => {
|
|
636
|
+
if (e.key === 'Enter') {
|
|
637
|
+
e.preventDefault();
|
|
638
|
+
handleMaxLengthBlur();
|
|
639
|
+
(e.target as HTMLInputElement).blur();
|
|
640
|
+
}
|
|
641
|
+
}}
|
|
642
|
+
className="w-32 text-sm px-3 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
|
643
|
+
/>
|
|
644
|
+
<span className="text-xs text-content-muted ml-2">characters</span>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
)}
|
|
648
|
+
|
|
649
|
+
{activeSection === 'theme' && (
|
|
650
|
+
<div>
|
|
651
|
+
<h3 className="text-sm font-semibold text-content mb-1">Theme</h3>
|
|
652
|
+
<p className="text-xs text-content-muted mb-4">
|
|
653
|
+
Choose a color theme for the interface.
|
|
654
|
+
</p>
|
|
655
|
+
|
|
656
|
+
{/* System theme */}
|
|
657
|
+
<button
|
|
658
|
+
onClick={() => setTheme('system')}
|
|
659
|
+
className={`w-full text-left px-4 py-3 rounded-lg border-2 transition-colors mb-5 flex items-center gap-3 ${
|
|
660
|
+
theme === 'system'
|
|
661
|
+
? 'border-primary bg-primary-bg'
|
|
662
|
+
: 'border-border hover:border-primary-border hover:bg-tint'
|
|
663
|
+
}`}
|
|
664
|
+
>
|
|
665
|
+
<svg
|
|
666
|
+
className={`w-5 h-5 ${theme === 'system' ? 'text-primary-text' : 'text-content-muted'}`}
|
|
667
|
+
fill="none"
|
|
668
|
+
viewBox="0 0 24 24"
|
|
669
|
+
stroke="currentColor"
|
|
670
|
+
strokeWidth={1.5}
|
|
671
|
+
>
|
|
672
|
+
<path
|
|
673
|
+
strokeLinecap="round"
|
|
674
|
+
strokeLinejoin="round"
|
|
675
|
+
d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25A2.25 2.25 0 015.25 3h13.5A2.25 2.25 0 0121 5.25z"
|
|
676
|
+
/>
|
|
677
|
+
</svg>
|
|
678
|
+
<div>
|
|
679
|
+
<span
|
|
680
|
+
className={`text-sm font-medium ${theme === 'system' ? 'text-primary-text' : 'text-content'}`}
|
|
681
|
+
>
|
|
682
|
+
System
|
|
683
|
+
</span>
|
|
684
|
+
<p className="text-xs text-content-muted">Follows your OS appearance setting</p>
|
|
685
|
+
</div>
|
|
686
|
+
{theme === 'system' && (
|
|
687
|
+
<svg
|
|
688
|
+
className="w-3.5 h-3.5 ml-auto text-primary-text"
|
|
689
|
+
fill="none"
|
|
690
|
+
viewBox="0 0 24 24"
|
|
691
|
+
stroke="currentColor"
|
|
692
|
+
strokeWidth={2}
|
|
693
|
+
>
|
|
694
|
+
<path
|
|
695
|
+
strokeLinecap="round"
|
|
696
|
+
strokeLinejoin="round"
|
|
697
|
+
d="M4.5 12.75l6 6 9-13.5"
|
|
698
|
+
/>
|
|
699
|
+
</svg>
|
|
700
|
+
)}
|
|
701
|
+
</button>
|
|
702
|
+
|
|
703
|
+
{/* Light themes */}
|
|
704
|
+
<p className="text-xs font-medium text-content-muted uppercase tracking-wider mb-2">
|
|
705
|
+
Light
|
|
706
|
+
</p>
|
|
707
|
+
<div className="grid grid-cols-2 gap-3 mb-5">
|
|
708
|
+
{LIGHT_THEMES.map((t) => (
|
|
709
|
+
<button
|
|
710
|
+
key={t.key}
|
|
711
|
+
onClick={() => setTheme(t.key)}
|
|
712
|
+
className={`text-left px-4 py-3 rounded-lg border-2 transition-colors ${
|
|
713
|
+
theme === t.key
|
|
714
|
+
? 'border-primary bg-primary-bg'
|
|
715
|
+
: 'border-border hover:border-primary-border hover:bg-tint'
|
|
716
|
+
}`}
|
|
717
|
+
>
|
|
718
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
719
|
+
<div className="flex gap-1">
|
|
720
|
+
{t.colors.map((c, i) => (
|
|
721
|
+
<div
|
|
722
|
+
key={i}
|
|
723
|
+
className="w-4 h-4 rounded-full border border-border-subtle"
|
|
724
|
+
style={{ backgroundColor: c }}
|
|
725
|
+
/>
|
|
726
|
+
))}
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
<span
|
|
730
|
+
className={`text-sm font-medium ${
|
|
731
|
+
theme === t.key ? 'text-primary-text' : 'text-content'
|
|
732
|
+
}`}
|
|
733
|
+
>
|
|
734
|
+
{t.label}
|
|
735
|
+
</span>
|
|
736
|
+
{theme === t.key && (
|
|
737
|
+
<svg
|
|
738
|
+
className="inline-block w-3.5 h-3.5 ml-1.5 text-primary-text"
|
|
739
|
+
fill="none"
|
|
740
|
+
viewBox="0 0 24 24"
|
|
741
|
+
stroke="currentColor"
|
|
742
|
+
strokeWidth={2}
|
|
743
|
+
>
|
|
744
|
+
<path
|
|
745
|
+
strokeLinecap="round"
|
|
746
|
+
strokeLinejoin="round"
|
|
747
|
+
d="M4.5 12.75l6 6 9-13.5"
|
|
748
|
+
/>
|
|
749
|
+
</svg>
|
|
750
|
+
)}
|
|
751
|
+
</button>
|
|
752
|
+
))}
|
|
753
|
+
</div>
|
|
754
|
+
|
|
755
|
+
{/* Dark themes */}
|
|
756
|
+
<p className="text-xs font-medium text-content-muted uppercase tracking-wider mb-2">
|
|
757
|
+
Dark
|
|
758
|
+
</p>
|
|
759
|
+
<div className="grid grid-cols-2 gap-3">
|
|
760
|
+
{DARK_THEMES.map((t) => (
|
|
761
|
+
<button
|
|
762
|
+
key={t.key}
|
|
763
|
+
onClick={() => setTheme(t.key)}
|
|
764
|
+
className={`text-left px-4 py-3 rounded-lg border-2 transition-colors ${
|
|
765
|
+
theme === t.key
|
|
766
|
+
? 'border-primary bg-primary-bg'
|
|
767
|
+
: 'border-border hover:border-primary-border hover:bg-tint'
|
|
768
|
+
}`}
|
|
769
|
+
>
|
|
770
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
771
|
+
<div className="flex gap-1">
|
|
772
|
+
{t.colors.map((c, i) => (
|
|
773
|
+
<div
|
|
774
|
+
key={i}
|
|
775
|
+
className="w-4 h-4 rounded-full border border-border-subtle"
|
|
776
|
+
style={{ backgroundColor: c }}
|
|
777
|
+
/>
|
|
778
|
+
))}
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
<span
|
|
782
|
+
className={`text-sm font-medium ${
|
|
783
|
+
theme === t.key ? 'text-primary-text' : 'text-content'
|
|
784
|
+
}`}
|
|
785
|
+
>
|
|
786
|
+
{t.label}
|
|
787
|
+
</span>
|
|
788
|
+
{theme === t.key && (
|
|
789
|
+
<svg
|
|
790
|
+
className="inline-block w-3.5 h-3.5 ml-1.5 text-primary-text"
|
|
791
|
+
fill="none"
|
|
792
|
+
viewBox="0 0 24 24"
|
|
793
|
+
stroke="currentColor"
|
|
794
|
+
strokeWidth={2}
|
|
795
|
+
>
|
|
796
|
+
<path
|
|
797
|
+
strokeLinecap="round"
|
|
798
|
+
strokeLinejoin="round"
|
|
799
|
+
d="M4.5 12.75l6 6 9-13.5"
|
|
800
|
+
/>
|
|
801
|
+
</svg>
|
|
802
|
+
)}
|
|
803
|
+
</button>
|
|
804
|
+
))}
|
|
805
|
+
</div>
|
|
806
|
+
</div>
|
|
807
|
+
)}
|
|
808
|
+
</div>
|
|
809
|
+
</div>
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
);
|
|
813
|
+
}
|