Merge branch 'main' into feature/132-admin-status-multiselect
Linting / lint (pull_request) Successful in 1m49s
SonarQube Analysis / sonarqube (pull_request) Successful in 16m51s

This commit is contained in:
2026-08-23 07:21:56 -05:00
+25 -5
View File
@@ -8,6 +8,10 @@ import Col from 'antd/es/col';
import Alert from 'antd/es/alert';
import Typography from 'antd/es/typography';
import message from 'antd/es/message';
import MDEditor from '@uiw/react-md-editor';
import '@uiw/react-md-editor/markdown-editor.css';
import '@uiw/react-markdown-preview/markdown.css';
import { useThemeMode } from '../theme/ThemeContext';
import {
EmailTemplate,
saveEmailTemplate,
@@ -27,6 +31,7 @@ type Props = Readonly<{
const PREVIEW_DEBOUNCE_MS = 400;
export default function EmailTemplateEditor({ template, onChanged }: Props) {
const { mode } = useThemeMode();
// Falls back to the default so the editor starts from the real copy rather
// than an empty box. `subject`/`body` being null means "never customised",
// which is why the badge on the tab can say so.
@@ -111,13 +116,28 @@ export default function EmailTemplateEditor({ template, onChanged }: Props) {
placeholder="Subject"
style={{ marginBottom: 8 }}
/>
<Input.TextArea
aria-label={`${template.label} body`}
{/* The same editor the item description uses, so an admin who has
learned one toolbar has learned both. data-color-mode is what makes
it follow the admin's dark mode, exactly as on the item form.
preview="edit" rather than the item form's "live": MDEditor's own
preview would render the markdown with a different renderer, showing
a literal {{resetUrl}} and no consent footer. The pane on the right
is the server's rendering of the actual email, and two previews
disagreeing about the thing being edited is worse than one. */}
<div data-color-mode={mode} style={{ marginBottom: 12 }}>
<MDEditor
value={body}
onChange={(e) => setBody(e.target.value)}
autoSize={{ minRows: 10, maxRows: 24 }}
style={{ fontFamily: 'monospace', marginBottom: 12 }}
onChange={(val) => setBody(val || '')}
height={320}
preview="edit"
// Input.TextArea carried this attribute itself. MDEditor owns the
// textarea, so the label has to be passed through — without it every
// email template test loses its handle on the field, and a screen
// reader loses the only thing naming it.
textareaProps={{ 'aria-label': `${template.label} body` }}
/>
</div>
<Space>
<Button type="primary" loading={saving} onClick={handleSave}>