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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/bin/md-redline +255 -0
  4. package/bin/test-windows.ps1 +70 -0
  5. package/dist/assets/_baseFor-Ck08IaSF.js +1 -0
  6. package/dist/assets/arc-DI2g9LXK.js +1 -0
  7. package/dist/assets/architecture-YZFGNWBL-BDgMfc-b.js +1 -0
  8. package/dist/assets/architectureDiagram-Q4EWVU46-Dg1hcUEa.js +36 -0
  9. package/dist/assets/array-DOVTz2Mq.js +1 -0
  10. package/dist/assets/blockDiagram-DXYQGD6D-BAXkTCAk.js +132 -0
  11. package/dist/assets/c4Diagram-AHTNJAMY-BIkgwQSx.js +10 -0
  12. package/dist/assets/channel-DPCihw7y.js +1 -0
  13. package/dist/assets/chunk-2KRD3SAO-Dc_tBGsw.js +1 -0
  14. package/dist/assets/chunk-336JU56O-Dhi-ID9Y.js +2 -0
  15. package/dist/assets/chunk-426QAEUC-DnFdrNMW.js +1 -0
  16. package/dist/assets/chunk-4BX2VUAB-Z63FkGov.js +1 -0
  17. package/dist/assets/chunk-4TB4RGXK-BAiBlfyy.js +206 -0
  18. package/dist/assets/chunk-55IACEB6-BXDWXbxy.js +1 -0
  19. package/dist/assets/chunk-5FUZZQ4R-C72e1c_O.js +62 -0
  20. package/dist/assets/chunk-5PVQY5BW-BBHW_uCu.js +2 -0
  21. package/dist/assets/chunk-67CJDMHE-3Cf_D9m6.js +1 -0
  22. package/dist/assets/chunk-7N4EOEYR-DAXUXJ2c.js +1 -0
  23. package/dist/assets/chunk-AA7GKIK3-Dr7fOryc.js +1 -0
  24. package/dist/assets/chunk-BSJP7CBP-BmsSs1Nt.js +1 -0
  25. package/dist/assets/chunk-CIAEETIT-QDzV-X_Y.js +1 -0
  26. package/dist/assets/chunk-EDXVE4YY-C25WFHxY.js +1 -0
  27. package/dist/assets/chunk-ENJZ2VHE-_OzxcZOU.js +10 -0
  28. package/dist/assets/chunk-FMBD7UC4-CjsTKY4u.js +15 -0
  29. package/dist/assets/chunk-FOC6F5B3-g-xaH5nc.js +1 -0
  30. package/dist/assets/chunk-ICPOFSXX-iKiUSjDK.js +121 -0
  31. package/dist/assets/chunk-K5T4RW27-CKR-lPBN.js +94 -0
  32. package/dist/assets/chunk-KGLVRYIC-DRccT-B_.js +1 -0
  33. package/dist/assets/chunk-LIHQZDEY-DTbMwMXj.js +1 -0
  34. package/dist/assets/chunk-ORNJ4GCN-DlerdcWX.js +1 -0
  35. package/dist/assets/chunk-OYMX7WX6-Dekv1on2.js +231 -0
  36. package/dist/assets/chunk-QZHKN3VN-BHu0RdKl.js +1 -0
  37. package/dist/assets/chunk-U2HBQHQK-BvtlVHAg.js +70 -0
  38. package/dist/assets/chunk-X2U36JSP-BI_g8mub.js +1 -0
  39. package/dist/assets/chunk-XPW4576I-B39JkmSE.js +32 -0
  40. package/dist/assets/chunk-YZCP3GAM-BfPcXRm2.js +1 -0
  41. package/dist/assets/chunk-ZZ45TVLE-Bg4q68wZ.js +1 -0
  42. package/dist/assets/classDiagram-6PBFFD2Q-p73p727_.js +1 -0
  43. package/dist/assets/classDiagram-v2-HSJHXN6E-C4Ftpivp.js +1 -0
  44. package/dist/assets/clone-CI9aUwHe.js +1 -0
  45. package/dist/assets/cose-bilkent-S5V4N54A-7BpAeDh5.js +1 -0
  46. package/dist/assets/cytoscape.esm-DoTFyJaN.js +321 -0
  47. package/dist/assets/dagre-CilMRazv.js +1 -0
  48. package/dist/assets/dagre-KV5264BT-DDMqpjkB.js +4 -0
  49. package/dist/assets/defaultLocale-Ck2Xxk-C.js +1 -0
  50. package/dist/assets/diagram-5BDNPKRD-BFeyfnCx.js +10 -0
  51. package/dist/assets/diagram-G4DWMVQ6-DoqT-PtF.js +24 -0
  52. package/dist/assets/diagram-MMDJMWI5-BPV6KADk.js +43 -0
  53. package/dist/assets/diagram-TYMM5635-okvcTBtl.js +24 -0
  54. package/dist/assets/dist-C_eddq6m.js +1 -0
  55. package/dist/assets/erDiagram-SMLLAGMA-Dl-Ixy8n.js +85 -0
  56. package/dist/assets/flatten-B8XIuT0x.js +1 -0
  57. package/dist/assets/flowDiagram-DWJPFMVM-CsqWAx5r.js +162 -0
  58. package/dist/assets/ganttDiagram-T4ZO3ILL-mIt6zVeF.js +292 -0
  59. package/dist/assets/gitGraph-7Q5UKJZL-COXHGMvj.js +1 -0
  60. package/dist/assets/gitGraphDiagram-UUTBAWPF-syVqZJX_.js +106 -0
  61. package/dist/assets/graphlib-Bpd0q3yO.js +1 -0
  62. package/dist/assets/index-BoggyWS0.css +2 -0
  63. package/dist/assets/index-aLvjHQW4.js +104 -0
  64. package/dist/assets/info-OMHHGYJF-B-0wfxwL.js +1 -0
  65. package/dist/assets/infoDiagram-42DDH7IO-C0_uqsVa.js +2 -0
  66. package/dist/assets/init-Bft5Ffpj.js +1 -0
  67. package/dist/assets/isEmpty-BrFi5AqV.js +1 -0
  68. package/dist/assets/ishikawaDiagram-UXIWVN3A-CTjFbDBV.js +70 -0
  69. package/dist/assets/journeyDiagram-VCZTEJTY-BDBcej1q.js +139 -0
  70. package/dist/assets/kanban-definition-6JOO6SKY-Ylgzakw7.js +89 -0
  71. package/dist/assets/katex-Uj9wLT16.js +265 -0
  72. package/dist/assets/line-CRxEwpOv.js +1 -0
  73. package/dist/assets/linear-PDPfFByd.js +1 -0
  74. package/dist/assets/mermaid-parser.core-CY-XNOOy.js +4 -0
  75. package/dist/assets/mermaid.core-BPlTADIX.js +11 -0
  76. package/dist/assets/mindmap-definition-QFDTVHPH-TefzJnBM.js +96 -0
  77. package/dist/assets/ordinal-DIg8h6NI.js +1 -0
  78. package/dist/assets/packet-4T2RLAQJ-BW1T_A-C.js +1 -0
  79. package/dist/assets/path-DfRbCp9y.js +1 -0
  80. package/dist/assets/pie-ZZUOXDRM-DkKU-SFu.js +1 -0
  81. package/dist/assets/pieDiagram-DEJITSTG-BCXuaeEy.js +30 -0
  82. package/dist/assets/quadrantDiagram-34T5L4WZ-VSBAicWL.js +7 -0
  83. package/dist/assets/radar-PYXPWWZC-CYvTacKJ.js +1 -0
  84. package/dist/assets/reduce-CV2X8n1a.js +1 -0
  85. package/dist/assets/requirementDiagram-MS252O5E-4NeL9Z6J.js +84 -0
  86. package/dist/assets/rough.esm-Bbn_-PMU.js +1 -0
  87. package/dist/assets/sankeyDiagram-XADWPNL6-DMBSDnrH.js +10 -0
  88. package/dist/assets/sequenceDiagram-FGHM5R23-DVpzcZUi.js +157 -0
  89. package/dist/assets/src-PKe5NtkK.js +1 -0
  90. package/dist/assets/stateDiagram-FHFEXIEX-BkHTlCjL.js +1 -0
  91. package/dist/assets/stateDiagram-v2-QKLJ7IA2-nMeWu9fP.js +1 -0
  92. package/dist/assets/timeline-definition-GMOUNBTQ-CyLt92nf.js +120 -0
  93. package/dist/assets/treeView-SZITEDCU-BUgcJ4eR.js +1 -0
  94. package/dist/assets/treemap-W4RFUUIX-BIWGQ4Pw.js +1 -0
  95. package/dist/assets/vennDiagram-DHZGUBPP-BCK0xB_m.js +34 -0
  96. package/dist/assets/wardley-RL74JXVD-DMZZRlby.js +1 -0
  97. package/dist/assets/wardleyDiagram-NUSXRM2D-BisBgfsF.js +20 -0
  98. package/dist/assets/xychartDiagram-5P7HB3ND-D_REDciv.js +7 -0
  99. package/dist/favicon.svg +15 -0
  100. package/dist/index.html +14 -0
  101. package/dist/screenshot.png +0 -0
  102. package/index.html +13 -0
  103. package/package.json +105 -0
  104. package/public/favicon.svg +15 -0
  105. package/public/screenshot.png +0 -0
  106. package/server/index.test.ts +814 -0
  107. package/server/index.ts +736 -0
  108. package/server/preferences.test.ts +126 -0
  109. package/server/preferences.ts +76 -0
  110. package/src/App.tsx +1620 -0
  111. package/src/components/ActionButton.tsx +41 -0
  112. package/src/components/CommandPalette.tsx +191 -0
  113. package/src/components/CommentCard.tsx +556 -0
  114. package/src/components/CommentForm.tsx +285 -0
  115. package/src/components/CommentSidebar.tsx +428 -0
  116. package/src/components/ConfirmDialog.tsx +64 -0
  117. package/src/components/ContextMenu.tsx +220 -0
  118. package/src/components/DragHandles.tsx +48 -0
  119. package/src/components/FileExplorer.tsx +251 -0
  120. package/src/components/FileOpener.tsx +304 -0
  121. package/src/components/IconButton.tsx +32 -0
  122. package/src/components/KeyboardShortcutsPanel.tsx +136 -0
  123. package/src/components/MarkdownViewer.tsx +682 -0
  124. package/src/components/RawView.tsx +798 -0
  125. package/src/components/SearchBar.tsx +129 -0
  126. package/src/components/Separator.tsx +7 -0
  127. package/src/components/SettingsPanel.tsx +813 -0
  128. package/src/components/SplitIconButton.tsx +133 -0
  129. package/src/components/TabBar.tsx +594 -0
  130. package/src/components/TableOfContents.tsx +70 -0
  131. package/src/components/ThemeSelector.tsx +159 -0
  132. package/src/components/Toast.tsx +99 -0
  133. package/src/components/Toolbar.tsx +161 -0
  134. package/src/components/iconButtonVariants.ts +19 -0
  135. package/src/components/rawView.test.ts +291 -0
  136. package/src/contexts/SettingsContext.tsx +120 -0
  137. package/src/hooks/useAuthor.test.ts +58 -0
  138. package/src/hooks/useAuthor.ts +69 -0
  139. package/src/hooks/useAutoResize.ts +20 -0
  140. package/src/hooks/useCommentCardTriggers.ts +20 -0
  141. package/src/hooks/useComments.test.ts +773 -0
  142. package/src/hooks/useComments.ts +332 -0
  143. package/src/hooks/useContextMenu.ts +48 -0
  144. package/src/hooks/useContextMenuItems.ts +392 -0
  145. package/src/hooks/useDiffSnapshot.test.ts +130 -0
  146. package/src/hooks/useDiffSnapshot.ts +67 -0
  147. package/src/hooks/useDragHandles.ts +417 -0
  148. package/src/hooks/useFileWatcher.ts +45 -0
  149. package/src/hooks/useHeadingTracking.ts +84 -0
  150. package/src/hooks/useMermaidRenderer.ts +75 -0
  151. package/src/hooks/useModalState.ts +22 -0
  152. package/src/hooks/usePageVisible.test.ts +69 -0
  153. package/src/hooks/usePageVisible.ts +19 -0
  154. package/src/hooks/usePaneLayout.test.ts +108 -0
  155. package/src/hooks/usePaneLayout.ts +102 -0
  156. package/src/hooks/useRecentFiles.test.ts +103 -0
  157. package/src/hooks/useRecentFiles.ts +99 -0
  158. package/src/hooks/useResizablePanel.test.ts +84 -0
  159. package/src/hooks/useResizablePanel.ts +118 -0
  160. package/src/hooks/useSearch.test.ts +72 -0
  161. package/src/hooks/useSearch.ts +53 -0
  162. package/src/hooks/useSelection.ts +48 -0
  163. package/src/hooks/useSessionPersistence.test.ts +59 -0
  164. package/src/hooks/useSessionPersistence.ts +43 -0
  165. package/src/hooks/useTabs.test.ts +127 -0
  166. package/src/hooks/useTabs.ts +561 -0
  167. package/src/hooks/useThemePersistence.ts +41 -0
  168. package/src/hooks/useToast.ts +27 -0
  169. package/src/index.css +1047 -0
  170. package/src/lib/agent-prompts.test.ts +34 -0
  171. package/src/lib/agent-prompts.ts +68 -0
  172. package/src/lib/comment-editor-state.ts +6 -0
  173. package/src/lib/comment-parser.test.ts +1959 -0
  174. package/src/lib/comment-parser.ts +1021 -0
  175. package/src/lib/diff.test.ts +164 -0
  176. package/src/lib/diff.ts +139 -0
  177. package/src/lib/heading-slugs.test.ts +85 -0
  178. package/src/lib/heading-slugs.ts +44 -0
  179. package/src/lib/http.test.ts +43 -0
  180. package/src/lib/http.ts +29 -0
  181. package/src/lib/mermaid-highlights.test.ts +517 -0
  182. package/src/lib/mermaid-highlights.ts +936 -0
  183. package/src/lib/mermaid-renderer.test.ts +114 -0
  184. package/src/lib/mermaid-renderer.ts +89 -0
  185. package/src/lib/path-utils.test.ts +17 -0
  186. package/src/lib/path-utils.ts +7 -0
  187. package/src/lib/platform.test.ts +58 -0
  188. package/src/lib/platform.ts +14 -0
  189. package/src/lib/preferences-client.test.ts +177 -0
  190. package/src/lib/preferences-client.ts +94 -0
  191. package/src/lib/selection-resolver.test.ts +118 -0
  192. package/src/lib/selection-resolver.ts +37 -0
  193. package/src/lib/settings.test.ts +152 -0
  194. package/src/lib/settings.ts +78 -0
  195. package/src/lib/shortcut-label.tsx +18 -0
  196. package/src/lib/themes.ts +21 -0
  197. package/src/lib/visible-text.test.ts +86 -0
  198. package/src/lib/visible-text.ts +77 -0
  199. package/src/main.tsx +22 -0
  200. package/src/markdown/pipeline.test.ts +82 -0
  201. package/src/markdown/pipeline.ts +33 -0
  202. package/src/types.test.ts +43 -0
  203. package/src/types.ts +46 -0
  204. package/tsconfig.app.json +28 -0
  205. package/tsconfig.json +7 -0
  206. package/tsconfig.node.json +26 -0
  207. package/vite.config.ts +50 -0
@@ -0,0 +1,285 @@
1
+ import { useState, useRef, useEffect, useLayoutEffect } from 'react';
2
+ import type { SelectionInfo } from '../types';
3
+ import { useAutoResize } from '../hooks/useAutoResize';
4
+ import { useSettings } from '../contexts/SettingsContext';
5
+ import { getPrimaryModifierLabel } from '../lib/platform';
6
+
7
+ interface Props {
8
+ selection: SelectionInfo;
9
+ autoExpand?: boolean;
10
+ onSubmit: (
11
+ anchor: string,
12
+ text: string,
13
+ contextBefore: string,
14
+ contextAfter: string,
15
+ hintOffset: number,
16
+ ) => void;
17
+ onCancel: () => void;
18
+ onLock: () => void;
19
+ }
20
+
21
+ export function CommentForm({ selection, autoExpand, onSubmit, onCancel, onLock }: Props) {
22
+ const { settings } = useSettings();
23
+ const TEMPLATES = settings.templates;
24
+ const COMMENT_MAX_LENGTH = settings.commentMaxLength;
25
+ const modLabel = getPrimaryModifierLabel();
26
+ const [isExpanded, setIsExpanded] = useState(!!settings.quickComment);
27
+ const [text, setText] = useState('');
28
+ const [showTemplates, setShowTemplates] = useState(settings.showTemplatesByDefault);
29
+ const inputRef = useRef<HTMLTextAreaElement>(null);
30
+ const formRef = useRef<HTMLDivElement>(null);
31
+ const [formSize, setFormSize] = useState<{ height: number; width: number } | null>(null);
32
+ useAutoResize(inputRef, text);
33
+
34
+ useEffect(() => {
35
+ if (isExpanded && inputRef.current) {
36
+ inputRef.current.focus();
37
+ }
38
+ }, [isExpanded]);
39
+
40
+ // Auto-expand when triggered by keyboard shortcut
41
+ useEffect(() => {
42
+ if (autoExpand && !isExpanded) {
43
+ onLock();
44
+ setIsExpanded(true);
45
+ }
46
+ }, [autoExpand, isExpanded, onLock]);
47
+
48
+ // Quick comment: lock selection on mount when starting expanded
49
+ useEffect(() => {
50
+ if (settings.quickComment) {
51
+ onLock();
52
+ }
53
+ // Only run on mount — quickComment won't change mid-lifecycle of this instance
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, []);
56
+
57
+ // Click outside: dismiss if expanded with empty text.
58
+ useEffect(() => {
59
+ if (!isExpanded) return;
60
+ const handler = (e: MouseEvent) => {
61
+ if (formRef.current && !formRef.current.contains(e.target as Node) && !text.trim()) {
62
+ onCancel();
63
+ }
64
+ };
65
+ document.addEventListener('mousedown', handler);
66
+ return () => document.removeEventListener('mousedown', handler);
67
+ }, [isExpanded, text, onCancel]);
68
+
69
+ // Reset expanded state when selection changes
70
+ const selectionKey = `${selection.text}:${selection.rect.top}:${selection.rect.left}`;
71
+ const prevSelectionKeyRef = useRef(selectionKey);
72
+ useEffect(() => {
73
+ if (prevSelectionKeyRef.current !== selectionKey) {
74
+ prevSelectionKeyRef.current = selectionKey;
75
+ if (!settings.quickComment) setIsExpanded(false);
76
+ setText('');
77
+ setShowTemplates(settings.showTemplatesByDefault);
78
+ setFormSize(null);
79
+ }
80
+ }, [selectionKey, settings.quickComment, settings.showTemplatesByDefault]);
81
+
82
+ useLayoutEffect(() => {
83
+ const node = formRef.current;
84
+ if (!node) return;
85
+
86
+ const nextSize = {
87
+ height: Math.ceil(node.getBoundingClientRect().height),
88
+ width: Math.ceil(node.getBoundingClientRect().width),
89
+ };
90
+
91
+ setFormSize((current) =>
92
+ current?.height === nextSize.height && current?.width === nextSize.width ? current : nextSize,
93
+ );
94
+ }, [isExpanded, showTemplates, text, selectionKey]);
95
+
96
+ // Position the form near the selection
97
+ const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
98
+ const viewportPadding = 12;
99
+ const viewportHeight = window.innerHeight;
100
+ const viewportWidth = window.innerWidth;
101
+ const formHeight = formSize?.height ?? (isExpanded ? (showTemplates ? 280 : 220) : 44);
102
+ const formWidth = formSize?.width ?? (isExpanded ? 320 : 120);
103
+ const belowTop = selection.rect.bottom + 8;
104
+ const aboveTop = selection.rect.top - formHeight - 8;
105
+ const showAbove =
106
+ belowTop + formHeight > viewportHeight - viewportPadding && aboveTop >= viewportPadding;
107
+ const top = clamp(
108
+ showAbove ? aboveTop : belowTop,
109
+ viewportPadding,
110
+ Math.max(viewportPadding, viewportHeight - formHeight - viewportPadding),
111
+ );
112
+ const left = clamp(
113
+ selection.rect.left,
114
+ viewportPadding,
115
+ Math.max(viewportPadding, viewportWidth - formWidth - viewportPadding),
116
+ );
117
+
118
+ const style: React.CSSProperties = {
119
+ position: 'fixed',
120
+ left: `${left}px`,
121
+ top: `${top}px`,
122
+ maxHeight: `${Math.max(0, viewportHeight - viewportPadding * 2)}px`,
123
+ zIndex: 50,
124
+ };
125
+
126
+ const handleSubmit = () => {
127
+ if (!text.trim() || text.length > COMMENT_MAX_LENGTH) return;
128
+ onSubmit(
129
+ selection.text,
130
+ text.trim(),
131
+ selection.contextBefore,
132
+ selection.contextAfter,
133
+ selection.offset,
134
+ );
135
+ setText('');
136
+ setIsExpanded(false);
137
+ setShowTemplates(false);
138
+ };
139
+
140
+ const handleKeyDown = (e: React.KeyboardEvent) => {
141
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
142
+ e.preventDefault();
143
+ handleSubmit();
144
+ }
145
+ if (e.key === 'Escape') {
146
+ e.preventDefault();
147
+ onCancel();
148
+ }
149
+ };
150
+
151
+ const handleExpand = () => {
152
+ onLock(); // Lock the selection so mouseup events don't clear it
153
+ setIsExpanded(true);
154
+ };
155
+
156
+ const handleTemplateClick = (template: string) => {
157
+ setText(template);
158
+ setShowTemplates(false);
159
+ inputRef.current?.focus();
160
+ };
161
+
162
+ if (!isExpanded) {
163
+ return (
164
+ <div ref={formRef} style={style} data-comment-form>
165
+ <button
166
+ onMouseDown={(e) => e.preventDefault()} // Prevent stealing focus/clearing selection
167
+ onClick={handleExpand}
168
+ className="flex items-center gap-1.5 px-3 py-1.5 bg-primary text-on-primary text-sm font-medium rounded-lg shadow-lg hover:bg-primary-hover transition-colors"
169
+ >
170
+ <svg
171
+ className="w-3.5 h-3.5"
172
+ fill="none"
173
+ viewBox="0 0 24 24"
174
+ stroke="currentColor"
175
+ strokeWidth={2}
176
+ >
177
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
178
+ </svg>
179
+ Comment
180
+ </button>
181
+ </div>
182
+ );
183
+ }
184
+
185
+ return (
186
+ <div
187
+ ref={formRef}
188
+ style={style}
189
+ data-comment-form
190
+ className="w-80 bg-surface-raised rounded-xl shadow-xl border border-border overflow-x-hidden overflow-y-auto"
191
+ >
192
+ {/* Anchor preview */}
193
+ <div className="px-3 pt-3 pb-2 border-b border-border-subtle bg-surface-secondary">
194
+ <p className="text-xs text-content-secondary mb-1">Commenting on:</p>
195
+ <p className="text-xs font-mono text-comment-anchor-text bg-comment-anchor-bg rounded px-2 py-1 truncate border border-comment-anchor-border">
196
+ &ldquo;{selection.text}&rdquo;
197
+ </p>
198
+ </div>
199
+
200
+ {/* Templates */}
201
+ {showTemplates && (
202
+ <div className="px-3 pt-2 pb-1 border-b border-border-subtle bg-surface-secondary">
203
+ <p className="text-xs text-content-secondary mb-1.5">Quick templates:</p>
204
+ <div className="flex flex-wrap gap-1 mb-1">
205
+ {TEMPLATES.map((t) => (
206
+ <button
207
+ key={t.label}
208
+ onClick={() => handleTemplateClick(t.text)}
209
+ className="text-[10px] px-2 py-1 rounded-md bg-surface border border-border-subtle text-content-secondary hover:bg-tint-primary hover:text-primary-text hover:border-primary-border transition-colors"
210
+ >
211
+ {t.label}
212
+ </button>
213
+ ))}
214
+ </div>
215
+ </div>
216
+ )}
217
+
218
+ {/* Input */}
219
+ <div className="p-3">
220
+ <textarea
221
+ ref={inputRef}
222
+ value={text}
223
+ onChange={(e) => setText(e.target.value)}
224
+ onKeyDown={handleKeyDown}
225
+ placeholder="Add your comment..."
226
+ rows={1}
227
+ maxLength={COMMENT_MAX_LENGTH}
228
+ className="w-full text-sm border border-border-subtle rounded-lg px-3 py-2 resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent placeholder:text-content-muted bg-surface text-content overflow-hidden"
229
+ />
230
+ {text.length > COMMENT_MAX_LENGTH * 0.8 && (
231
+ <p
232
+ className={`text-right text-xs mt-1 ${
233
+ text.length >= COMMENT_MAX_LENGTH ? 'text-danger font-medium' : 'text-content-muted'
234
+ }`}
235
+ >
236
+ {text.length}/{COMMENT_MAX_LENGTH}
237
+ </p>
238
+ )}
239
+ <div className="flex items-center justify-between mt-2">
240
+ <div className="flex items-center gap-2">
241
+ <span className="text-xs text-content-muted">{modLabel}+Enter</span>
242
+ <button
243
+ onClick={() => setShowTemplates(!showTemplates)}
244
+ className={`text-xs px-1.5 py-0.5 rounded transition-colors ${
245
+ showTemplates
246
+ ? 'bg-primary-bg-strong text-primary-text'
247
+ : 'text-content-muted hover:text-primary-text hover:bg-tint-primary'
248
+ }`}
249
+ title="Quick templates"
250
+ >
251
+ <svg
252
+ className="w-3.5 h-3.5"
253
+ fill="none"
254
+ viewBox="0 0 24 24"
255
+ stroke="currentColor"
256
+ strokeWidth={2}
257
+ >
258
+ <path
259
+ strokeLinecap="round"
260
+ strokeLinejoin="round"
261
+ d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
262
+ />
263
+ </svg>
264
+ </button>
265
+ </div>
266
+ <div className="flex gap-2">
267
+ <button
268
+ onClick={onCancel}
269
+ className="text-xs px-3 py-1.5 text-content-secondary hover:bg-tint rounded-md transition-colors"
270
+ >
271
+ Cancel
272
+ </button>
273
+ <button
274
+ onClick={handleSubmit}
275
+ disabled={!text.trim() || text.length > COMMENT_MAX_LENGTH}
276
+ className="text-xs px-3 py-1.5 bg-primary text-on-primary rounded-md hover:bg-primary-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
277
+ >
278
+ Comment
279
+ </button>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ );
285
+ }