切换markdown渲染为cm6
This commit is contained in:
108
src/App.vue
108
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import MarkdownEditor from "./components/MarkdownEditor.vue";
|
||||
@@ -23,6 +23,72 @@ interface DirEntry {
|
||||
const folderPath = ref("");
|
||||
const dirEntries = ref<DirEntry[]>([]);
|
||||
|
||||
// ---- editor zoom (Ctrl+scroll) ----
|
||||
const ZOOM_MIN = 0.7;
|
||||
const ZOOM_MAX = 1.5;
|
||||
const ZOOM_STEP = 0.1;
|
||||
|
||||
function loadZoomPref(): number {
|
||||
try {
|
||||
const stored = localStorage.getItem("editor-zoom");
|
||||
if (stored) {
|
||||
const val = parseFloat(stored);
|
||||
if (!isNaN(val) && val >= ZOOM_MIN && val <= ZOOM_MAX) return val;
|
||||
}
|
||||
} catch { /* localStorage unavailable */ }
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
const editorZoom = ref(loadZoomPref());
|
||||
const showZoomIndicator = ref(false);
|
||||
let zoomIndicatorTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function applyZoomCssVars() {
|
||||
document.documentElement.style.setProperty("--editor-zoom", String(editorZoom.value));
|
||||
}
|
||||
|
||||
function saveZoomPref() {
|
||||
try { localStorage.setItem("editor-zoom", String(editorZoom.value)); } catch { /* noop */ }
|
||||
}
|
||||
|
||||
function onEditorWheel(e: WheelEvent) {
|
||||
if (!e.ctrlKey && !e.metaKey) return;
|
||||
e.preventDefault();
|
||||
const delta = e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP;
|
||||
const next = Math.round((editorZoom.value + delta) * 10) / 10;
|
||||
editorZoom.value = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, next));
|
||||
applyZoomCssVars();
|
||||
saveZoomPref();
|
||||
|
||||
// Show zoom indicator and reset timer
|
||||
showZoomIndicator.value = true;
|
||||
if (zoomIndicatorTimer) clearTimeout(zoomIndicatorTimer);
|
||||
zoomIndicatorTimer = setTimeout(() => { showZoomIndicator.value = false; }, 1500);
|
||||
}
|
||||
|
||||
// ---- full-width mode ----
|
||||
function loadFullWidthPref(): boolean {
|
||||
try {
|
||||
return localStorage.getItem("editor-full-width") === "true";
|
||||
} catch { return false; }
|
||||
}
|
||||
const isFullWidth = ref(loadFullWidthPref());
|
||||
|
||||
function onToggleFullWidth(val: boolean) {
|
||||
isFullWidth.value = val;
|
||||
try { localStorage.setItem("editor-full-width", String(val)); } catch { /* noop */ }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
applyZoomCssVars();
|
||||
window.addEventListener("wheel", onEditorWheel, { passive: false });
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("wheel", onEditorWheel);
|
||||
if (zoomIndicatorTimer) clearTimeout(zoomIndicatorTimer);
|
||||
});
|
||||
|
||||
function stripMarkdown(md: string): string {
|
||||
return md
|
||||
// Remove images 
|
||||
@@ -55,13 +121,11 @@ function onUpdate(md: string) {
|
||||
}
|
||||
|
||||
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}`) {
|
||||
// Navigate to heading in the CodeMirror editor
|
||||
const className = `.cm-lp-h${heading.level}`;
|
||||
const headingEls = document.querySelectorAll(className);
|
||||
for (const el of headingEls) {
|
||||
if (el.textContent?.trim() === heading.text) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
break;
|
||||
}
|
||||
@@ -202,21 +266,34 @@ listen("menu-event", (event) => {
|
||||
:entries="dirEntries"
|
||||
:content="content"
|
||||
:current-file-path="currentFilePath"
|
||||
:is-full-width="isFullWidth"
|
||||
@heading-click="onHeadingClick"
|
||||
@open-folder="onOpenFolder"
|
||||
@toggle-folder="onToggleFolder"
|
||||
@open-file="onOpenFile"
|
||||
@toggle-full-width="onToggleFullWidth"
|
||||
/>
|
||||
|
||||
<!-- Right: Editor area -->
|
||||
<main class="flex-1 flex flex-col overflow-y-auto">
|
||||
<main class="flex-1 flex flex-col overflow-hidden relative">
|
||||
<MarkdownEditor
|
||||
v-model="content"
|
||||
:model-value="content"
|
||||
:is-full-width="isFullWidth"
|
||||
placeholder="输入 Markdown 内容... 支持标题、粗体、斜体、代码块等 ✨"
|
||||
@update:model-value="onUpdate"
|
||||
@save-shortcut="doSave"
|
||||
class="flex-1 w-full"
|
||||
/>
|
||||
|
||||
<!-- Zoom indicator -->
|
||||
<Transition name="zoom-fade">
|
||||
<div
|
||||
v-if="showZoomIndicator"
|
||||
class="fixed top-4 right-4 z-50 px-2.5 py-1 rounded-md bg-[#38342e]/80 text-white text-xs font-medium pointer-events-none select-none backdrop-blur-sm"
|
||||
>
|
||||
{{ Math.round(editorZoom * 100) }}%
|
||||
</div>
|
||||
</Transition>
|
||||
</main>
|
||||
</div>
|
||||
<footer class="border-t border-[#e8e4da] bg-[#f4f1eb]/60 backdrop-blur-sm">
|
||||
@@ -243,4 +320,15 @@ listen("menu-event", (event) => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.zoom-fade-enter-active,
|
||||
.zoom-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.zoom-fade-enter-from,
|
||||
.zoom-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user