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:
2026-05-22 19:48:48 +02:00
commit 0c1047d390
26 changed files with 6206 additions and 0 deletions
@@ -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>