Initial commit: Phase 1+2 prototype
- Go backend with SQLite, JWT auth, file CRUD - Vue 3 frontend with split/raw/WYSIWYG editor modes - Markdown preview (marked, GFM) - Formatting toolbar + keyboard shortcuts - File tree with search, create, delete - Light/dark theme toggle - Admin panel (user management) - Preferences (timezone, theme, default mode) - Shared documents section (placeholder) - Export: PDF, HTML, MD - Build daemon (Python, stdlib only) - Build job queue API - Docker deployment
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div ref="editorRoot" class="milkdown-editor"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||
import { Editor, rootCtx, defaultValueCtx } from '@milkdown/core'
|
||||
import { commonmark } from '@milkdown/preset-commonmark'
|
||||
import { gfm } from '@milkdown/preset-gfm'
|
||||
import { listener, listenerCtx } from '@milkdown/plugin-listener'
|
||||
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
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (suppressUpdate || !editorInstance) return
|
||||
// Only update if content actually differs (avoid cursor jump)
|
||||
// For now, we don't force-update the editor from outside
|
||||
// since the user is typing in it directly
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (editorInstance) {
|
||||
editorInstance.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.milkdown-editor {
|
||||
height: 100%;
|
||||
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; padding-bottom: 0.3em; border-bottom: 1px solid #313244; }
|
||||
.milkdown-editor .milkdown h2 { font-size: 1.5em; margin: 0.83em 0; padding-bottom: 0.3em; border-bottom: 1px solid #313244; }
|
||||
.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>
|
||||
Reference in New Issue
Block a user