first commit

This commit is contained in:
2026-07-01 00:22:05 +08:00
commit e4efa4b54d
48 changed files with 7983 additions and 0 deletions

132
src/App.vue Normal file
View File

@@ -0,0 +1,132 @@
<script setup lang="ts">
import { ref } from "vue";
import MarkdownEditor from "./components/MarkdownEditor.vue";
import DirectorySidebar from "./components/DirectorySidebar.vue";
import OutlinePanel from "./components/OutlinePanel.vue";
const content = ref("");
const wordCount = ref(0);
const charCount = ref(0);
// Panel state
const showOutline = ref(true);
function onUpdate(html: string) {
content.value = html;
// Strip HTML tags for counting
const text = html.replace(/<[^>]*>/g, "");
charCount.value = text.length;
wordCount.value = text.trim() ? text.trim().split(/\s+/).length : 0;
}
function onHeadingClick(heading: { text: string; level: number }) {
// Navigate to heading in the editor — find the heading element and scroll it into view
const editorEl = document.querySelector(".tiptap-editor .ProseMirror");
if (!editorEl) return;
const headingTags = editorEl.querySelectorAll("h1, h2, h3");
for (const el of headingTags) {
if (el.textContent?.trim() === heading.text && el.tagName === `H${heading.level}`) {
el.scrollIntoView({ behavior: "smooth", block: "start" });
break;
}
}
}
</script>
<template>
<div class="min-h-screen bg-gray-950 text-gray-100 flex flex-col">
<!-- Header -->
<header
class="flex items-center justify-between px-6 py-3 border-b border-gray-800 bg-gray-900/70 backdrop-blur-md sticky top-0 z-10"
>
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-lg bg-indigo-600 flex items-center justify-center text-sm font-bold"
>
M
</div>
<h1 class="text-lg font-semibold tracking-tight">Markdown Editor</h1>
</div>
<div class="flex items-center gap-4">
<!-- Outline toggle -->
<button
class="flex items-center gap-1 px-2.5 py-1.5 text-xs rounded-md transition-colors"
:class="
showOutline
? 'bg-indigo-600/30 text-indigo-300'
: 'text-gray-400 hover:text-white hover:bg-gray-800'
"
title="切换大纲面板"
@click="showOutline = !showOutline"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3.5 h-3.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h7" />
</svg>
大纲
</button>
<span class="w-px h-3 bg-gray-600" />
<span class="text-xs text-gray-400">{{ wordCount.toLocaleString() }} </span>
<span class="w-px h-3 bg-gray-600" />
<span class="text-xs text-gray-400">{{ charCount.toLocaleString() }} </span>
</div>
</header>
<!-- Three-column body -->
<div class="flex-1 flex overflow-hidden">
<!-- Left: Directory sidebar -->
<DirectorySidebar />
<!-- Middle: Outline panel -->
<transition name="outline-slide">
<OutlinePanel
v-if="showOutline"
:html-content="content"
@heading-click="onHeadingClick"
/>
</transition>
<!-- Right: Editor area -->
<main class="flex-1 flex justify-center overflow-y-auto px-4 py-8">
<div class="w-full max-w-4xl">
<MarkdownEditor
v-model="content"
placeholder="输入 Markdown 内容... 支持标题、粗体、斜体、代码块等 ✨"
@update:model-value="onUpdate"
/>
</div>
</main>
</div>
<!-- Footer -->
<footer
class="text-center text-xs text-gray-600 py-4 border-t border-gray-800/50"
>
基于 TipTap · 支持 Markdown 快捷输入
</footer>
</div>
</template>
<style>
/* Outline panel slide transition */
.outline-slide-enter-active,
.outline-slide-leave-active {
transition: width 0.25s ease, opacity 0.2s ease;
overflow: hidden;
}
.outline-slide-enter-from,
.outline-slide-leave-to {
width: 0 !important;
opacity: 0;
}
</style>