Files
markdown-hub/frontend/src/components/MilkdownEditor.vue
T

143 lines
5.1 KiB
Vue

<template>
<div class="milkdown-wrap">
<div class="milkdown-toolbar">
<button @click="cmd('ToggleBold')" title="Bold"><b>B</b></button>
<button @click="cmd('ToggleItalic')" title="Italic"><i>I</i></button>
<button @click="cmd('ToggleStrikethrough')" title="Strikethrough"><s>S</s></button>
<button @click="cmd('ToggleInlineCode')" title="Code">&lt;/&gt;</button>
<span class="sep"></span>
<button @click="cmd('WrapInHeading', 1)" title="H1">H1</button>
<button @click="cmd('WrapInHeading', 2)" title="H2">H2</button>
<button @click="cmd('WrapInHeading', 3)" title="H3">H3</button>
<span class="sep"></span>
<button @click="cmd('WrapInBulletList')" title="Bullet list"></button>
<button @click="cmd('WrapInOrderedList')" title="Ordered list">1.</button>
<button @click="cmd('TurnIntoBlockquote')" title="Quote"></button>
<button @click="cmd('InsertHr')" title="Horizontal rule"></button>
<button @click="cmd('InsertLink')" title="Link">🔗</button>
</div>
<div ref="editorRoot" class="milkdown-editor"></div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { Editor, rootCtx, defaultValueCtx, editorViewCtx } from '@milkdown/core'
import { commonmark, toggleStrongCommand, toggleEmphasisCommand, toggleInlineCodeCommand, wrapInHeadingCommand, wrapInBulletListCommand, wrapInOrderedListCommand, wrapInBlockquoteCommand, insertHrCommand } from '@milkdown/preset-commonmark'
import { gfm, toggleStrikethroughCommand } from '@milkdown/preset-gfm'
import { listener, listenerCtx } from '@milkdown/plugin-listener'
import { callCommand } from '@milkdown/utils'
import { nord } from '@milkdown/theme-nord'
import '@milkdown/theme-nord/style.css'
const props = defineProps({
modelValue: { type: String, default: '' },
})
const emit = defineEmits(['update:modelValue'])
const editorRoot = ref(null)
let editorInstance = null
let suppressUpdate = false
const commands = {
ToggleBold: toggleStrongCommand.key,
ToggleItalic: toggleEmphasisCommand.key,
ToggleStrikethrough: toggleStrikethroughCommand.key,
ToggleInlineCode: toggleInlineCodeCommand.key,
WrapInBulletList: wrapInBulletListCommand.key,
WrapInOrderedList: wrapInOrderedListCommand.key,
TurnIntoBlockquote: wrapInBlockquoteCommand.key,
InsertHr: insertHrCommand.key,
WrapInHeading: wrapInHeadingCommand.key,
}
function cmd(name, payload) {
if (!editorInstance) return
editorInstance.action(callCommand(commands[name], payload))
}
onMounted(async () => {
editorInstance = await Editor.make()
.config((ctx) => {
ctx.set(rootCtx, editorRoot.value)
ctx.set(defaultValueCtx, props.modelValue)
ctx.get(listenerCtx).markdownUpdated((ctx, markdown, prevMarkdown) => {
if (markdown !== prevMarkdown) {
suppressUpdate = true
emit('update:modelValue', markdown)
setTimeout(() => { suppressUpdate = false }, 0)
}
})
})
.config(nord)
.use(commonmark)
.use(gfm)
.use(listener)
.create()
})
onBeforeUnmount(() => {
if (editorInstance) {
editorInstance.destroy()
}
})
</script>
<style>
.milkdown-wrap {
display: flex;
flex-direction: column;
height: 100%;
}
.milkdown-toolbar {
display: flex;
gap: 2px;
padding: 6px 12px;
border-bottom: 1px solid var(--border, #313244);
background: var(--bg-secondary, #181825);
flex-shrink: 0;
}
.milkdown-toolbar button {
background: transparent;
border: 1px solid transparent;
color: var(--text, #cdd6f4);
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
min-width: 28px;
}
.milkdown-toolbar button:hover {
background: var(--bg-hover, #313244);
border-color: var(--border, #313244);
}
.milkdown-toolbar .sep {
width: 1px;
background: var(--border, #313244);
margin: 2px 6px;
}
.milkdown-editor {
flex: 1;
overflow-y: auto;
}
.milkdown-editor .milkdown {
padding: 20px;
min-height: 100%;
outline: none;
}
.milkdown-editor .milkdown p { margin: 0 0 16px; }
.milkdown-editor .milkdown h1 { font-size: 2em; margin: 0.67em 0; }
.milkdown-editor .milkdown h2 { font-size: 1.5em; margin: 0.83em 0; }
.milkdown-editor .milkdown h3 { font-size: 1.25em; margin: 1em 0; }
.milkdown-editor .milkdown code { background: #313244; padding: 2px 6px; border-radius: 3px; font-family: 'JetBrains Mono', monospace; }
.milkdown-editor .milkdown pre { background: #313244; padding: 16px; border-radius: 6px; overflow-x: auto; margin: 0 0 16px; }
.milkdown-editor .milkdown blockquote { border-left: 4px solid #89b4fa; padding: 0 16px; color: #a6adc8; margin: 0 0 16px; }
.milkdown-editor .milkdown ul, .milkdown-editor .milkdown ol { padding-left: 2em; margin: 0 0 16px; }
.milkdown-editor .milkdown table { border-collapse: collapse; width: 100%; margin: 0 0 16px; }
.milkdown-editor .milkdown th, .milkdown-editor .milkdown td { border: 1px solid #45475a; padding: 8px 12px; }
.milkdown-editor .milkdown th { background: #313244; }
.milkdown-editor .milkdown hr { border: none; border-top: 1px solid #45475a; margin: 24px 0; }
.milkdown-editor .milkdown a { color: #89b4fa; }
</style>