feat(admin): use the item description's markdown editor for email templates (#131) #133
@@ -8,6 +8,10 @@ import Col from 'antd/es/col';
|
|||||||
import Alert from 'antd/es/alert';
|
import Alert from 'antd/es/alert';
|
||||||
import Typography from 'antd/es/typography';
|
import Typography from 'antd/es/typography';
|
||||||
import message from 'antd/es/message';
|
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 {
|
import {
|
||||||
EmailTemplate,
|
EmailTemplate,
|
||||||
saveEmailTemplate,
|
saveEmailTemplate,
|
||||||
@@ -27,6 +31,7 @@ type Props = Readonly<{
|
|||||||
const PREVIEW_DEBOUNCE_MS = 400;
|
const PREVIEW_DEBOUNCE_MS = 400;
|
||||||
|
|
||||||
export default function EmailTemplateEditor({ template, onChanged }: Props) {
|
export default function EmailTemplateEditor({ template, onChanged }: Props) {
|
||||||
|
const { mode } = useThemeMode();
|
||||||
// Falls back to the default so the editor starts from the real copy rather
|
// 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",
|
// than an empty box. `subject`/`body` being null means "never customised",
|
||||||
// which is why the badge on the tab can say so.
|
// which is why the badge on the tab can say so.
|
||||||
@@ -111,13 +116,28 @@ export default function EmailTemplateEditor({ template, onChanged }: Props) {
|
|||||||
placeholder="Subject"
|
placeholder="Subject"
|
||||||
style={{ marginBottom: 8 }}
|
style={{ marginBottom: 8 }}
|
||||||
/>
|
/>
|
||||||
<Input.TextArea
|
{/* The same editor the item description uses, so an admin who has
|
||||||
aria-label={`${template.label} body`}
|
learned one toolbar has learned both. data-color-mode is what makes
|
||||||
value={body}
|
it follow the admin's dark mode, exactly as on the item form.
|
||||||
onChange={(e) => setBody(e.target.value)}
|
|
||||||
autoSize={{ minRows: 10, maxRows: 24 }}
|
preview="edit" rather than the item form's "live": MDEditor's own
|
||||||
style={{ fontFamily: 'monospace', marginBottom: 12 }}
|
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={(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>
|
<Space>
|
||||||
<Button type="primary" loading={saving} onClick={handleSave}>
|
<Button type="primary" loading={saving} onClick={handleSave}>
|
||||||
|
|||||||
Reference in New Issue
Block a user