Add WYSIWYG Markdown editor (EasyMDE) to wiki pages
Deploy to LXC / deploy (push) Successful in 21s

Reusable MarkdownEditor component using EasyMDE:
- Toolbar: bold, italic, headings, quotes, lists, links, images,
  tables, code blocks, preview, side-by-side, fullscreen
- Dark mode support with custom CSS overrides
- Value syncs via bind:value for form submission
- Applied to both wiki new and edit pages
- Replaces the plain textarea + manual preview toggle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 16:03:37 +07:00
parent f937394b5e
commit bc73595018
5 changed files with 189 additions and 46 deletions
+111
View File
@@ -0,0 +1,111 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { browser } from '$app/environment';
interface Props {
name?: string;
value?: string;
placeholder?: string;
}
let { name = 'content', value = $bindable(''), placeholder = 'Write in Markdown...' }: Props = $props();
let textareaEl: HTMLTextAreaElement;
let editor: any = null;
onMount(async () => {
if (!browser) return;
const EasyMDE = (await import('easymde')).default;
await import('easymde/dist/easymde.min.css');
editor = new EasyMDE({
element: textareaEl,
initialValue: value,
spellChecker: false,
autofocus: false,
placeholder,
status: false,
minHeight: '300px',
toolbar: [
'bold', 'italic', 'heading', '|',
'quote', 'unordered-list', 'ordered-list', '|',
'link', 'image', 'table', 'code', '|',
'preview', 'side-by-side', 'fullscreen', '|',
'guide'
],
sideBySideFullscreen: false
});
editor.codemirror.on('change', () => {
value = editor.value();
});
});
onDestroy(() => {
if (editor) {
editor.toTextArea();
editor = null;
}
});
</script>
<div class="markdown-editor">
<textarea bind:this={textareaEl} id={name}></textarea>
<input type="hidden" {name} value={value} />
</div>
<style>
.markdown-editor :global(.EasyMDEContainer) {
border: none;
}
.markdown-editor :global(.EasyMDEContainer .CodeMirror) {
border: 1px solid #d1d5db;
border-radius: 0.375rem;
font-family: ui-monospace, monospace;
font-size: 0.875rem;
}
.markdown-editor :global(.editor-toolbar) {
border: 1px solid #d1d5db;
border-bottom: none;
border-radius: 0.375rem 0.375rem 0 0;
opacity: 1;
}
.markdown-editor :global(.editor-toolbar button) {
color: #6b7280 !important;
}
.markdown-editor :global(.editor-toolbar button:hover) {
background: #f3f4f6;
}
.markdown-editor :global(.editor-toolbar button.active) {
background: #e5e7eb;
}
/* Dark mode */
:global(.dark) .markdown-editor :global(.CodeMirror) {
background: #374151;
color: #f3f4f6;
border-color: #4b5563;
}
:global(.dark) .markdown-editor :global(.editor-toolbar) {
background: #1f2937;
border-color: #4b5563;
}
:global(.dark) .markdown-editor :global(.editor-toolbar button) {
color: #9ca3af !important;
}
:global(.dark) .markdown-editor :global(.editor-toolbar button:hover) {
background: #374151;
}
:global(.dark) .markdown-editor :global(.CodeMirror-cursor) {
border-color: #f3f4f6;
}
:global(.dark) .markdown-editor :global(.editor-preview) {
background: #374151;
color: #f3f4f6;
}
:global(.dark) .markdown-editor :global(.editor-preview-side) {
background: #374151;
color: #f3f4f6;
border-color: #4b5563;
}
</style>