feat: Add improved html editor using CodeMirror

This commit is contained in:
Dries Augustyns
2025-12-09 10:15:33 +01:00
parent 433e796c04
commit 672f1e6657
6 changed files with 395 additions and 12 deletions
@@ -10,6 +10,7 @@ import {Variable} from './VariableExtension';
import {setAvailableVariables, VariableMention} from './VariableMention';
import {Toolbar} from './Toolbar';
import {ResizableImage} from './ResizableImage';
import {HtmlEditor} from './HtmlEditor';
import {useContactFields, useContacts} from '../../lib/hooks/useContacts';
import {useConfig} from '../../lib/hooks/useConfig';
import {useEffect, useRef, useState} from 'react';
@@ -556,12 +557,11 @@ export function EmailEditor({value, onChange, placeholder, subject, from, replyT
</>
) : (
<>
<div className="min-h-[400px] max-h-[600px] overflow-y-auto">
<textarea
<div className="min-h-[400px] max-h-[600px] overflow-hidden">
<HtmlEditor
value={htmlContent}
onChange={e => handleHtmlChange(e.target.value)}
onChange={handleHtmlChange}
placeholder={placeholder || 'Your next email starts here!'}
className="w-full h-full px-4 py-3 text-sm font-mono resize-none min-h-[400px] focus:outline-none"
/>
</div>
{selectedContactId && (
@@ -0,0 +1,103 @@
import CodeMirror from '@uiw/react-codemirror';
import {html} from '@codemirror/lang-html';
import {EditorView} from '@codemirror/view';
import {useMemo} from 'react';
interface HtmlEditorProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
}
export function HtmlEditor({value, onChange, placeholder}: HtmlEditorProps) {
// Custom theme configuration for better integration with the app
const theme = EditorView.theme({
'&': {
fontSize: '14px',
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
height: '100%',
},
'.cm-content': {
minHeight: '400px',
padding: '12px 16px',
caretColor: '#1f2937',
},
'.cm-line': {
padding: '0',
lineHeight: '1.6',
},
'.cm-gutters': {
backgroundColor: '#f9fafb',
color: '#9ca3af',
border: 'none',
borderRight: '1px solid #e5e7eb',
paddingRight: '8px',
paddingLeft: '8px',
},
'.cm-activeLineGutter': {
backgroundColor: '#f3f4f6',
color: '#374151',
},
'.cm-activeLine': {
backgroundColor: '#f9fafb',
},
'.cm-selectionBackground, ::selection': {
backgroundColor: '#dbeafe !important',
},
'.cm-focused .cm-selectionBackground, .cm-focused ::selection': {
backgroundColor: '#bfdbfe !important',
},
'.cm-cursor': {
borderLeftColor: '#1f2937',
},
'&.cm-focused': {
outline: 'none',
},
'.cm-scroller': {
overflow: 'auto',
maxHeight: '600px',
},
});
// Extensions with HTML support and line numbers
const extensions = useMemo(
() => [
html(),
theme,
EditorView.lineWrapping, // Enable line wrapping for long lines
],
[theme]
);
return (
<div className="html-editor-container">
<CodeMirror
value={value}
height="100%"
extensions={extensions}
onChange={onChange}
placeholder={placeholder}
basicSetup={{
lineNumbers: true,
highlightActiveLineGutter: true,
highlightActiveLine: true,
foldGutter: true,
dropCursor: true,
allowMultipleSelections: true,
indentOnInput: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
rectangularSelection: true,
crosshairCursor: true,
highlightSelectionMatches: true,
closeBracketsKeymap: true,
searchKeymap: true,
foldKeymap: true,
completionKeymap: true,
lintKeymap: true,
}}
/>
</div>
);
}