功能修复
This commit is contained in:
@@ -30,5 +30,9 @@
|
||||
"topic_20260706-142728_07221245f16f20ec": "auto",
|
||||
"topic_20260706-145507_076a32df5f4276b5": "auto",
|
||||
"topic_20260706-153317_238f65a7a470703c": "auto",
|
||||
"topic_20260707-083706_2398eb8e87085000": "auto"
|
||||
"topic_20260707-083706_2398eb8e87085000": "auto",
|
||||
"topic_20260707-084048_75f72710c486f3de": "auto",
|
||||
"topic_20260707-091519_dc062b2bfdf4249c": "auto",
|
||||
"topic_20260707-092315_b8c3939bfc696802": "auto",
|
||||
"topic_20260707-092711_af706c3fcc7e435e": "auto"
|
||||
}
|
||||
@@ -30,5 +30,9 @@
|
||||
"topic_20260706-142728_07221245f16f20ec": "已粘贴文本 #1 · 26 行] …",
|
||||
"topic_20260706-145507_076a32df5f4276b5": "当用户选中了侧边栏中的文件的时候,这…",
|
||||
"topic_20260706-153317_238f65a7a470703c": "文件被选中的高亮颜色不太搭配现在的主…",
|
||||
"topic_20260707-083706_2398eb8e87085000": "这个页面渲染md的时候,关于有序和无…"
|
||||
"topic_20260707-083706_2398eb8e87085000": "这个页面渲染md的时候,关于有序和无…",
|
||||
"topic_20260707-084048_75f72710c486f3de": "在这个文件的区域的头部添加一行内容…",
|
||||
"topic_20260707-091519_dc062b2bfdf4249c": "关于App.vue里的toolabr…",
|
||||
"topic_20260707-092315_b8c3939bfc696802": "当我点击目录树切换显示文件的时候,右…",
|
||||
"topic_20260707-092711_af706c3fcc7e435e": "帮我检查一下app.vue里的too…"
|
||||
}
|
||||
270
src/App.vue
270
src/App.vue
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {ref, watch, onMounted, onUnmounted, computed} from "vue";
|
||||
import {listen} from "@tauri-apps/api/event";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import MarkdownEditor from "./components/MarkdownEditor.vue";
|
||||
import DirectorySidebar from "./components/DirectorySidebar.vue";
|
||||
|
||||
@@ -13,6 +13,11 @@ const charCount = ref(0);
|
||||
const currentFilePath = ref<string | null>(null);
|
||||
const saveStatus = ref("");
|
||||
|
||||
// Check if current file is a Markdown file (for display-mode toggle)
|
||||
const isMarkdownFile = computed(() => {
|
||||
return currentFilePath.value?.toLowerCase().endsWith(".md") ?? false;
|
||||
});
|
||||
|
||||
// Folder state
|
||||
interface DirEntry {
|
||||
name: string;
|
||||
@@ -20,6 +25,7 @@ interface DirEntry {
|
||||
is_dir: boolean;
|
||||
children?: DirEntry[];
|
||||
}
|
||||
|
||||
const folderPath = ref("");
|
||||
const dirEntries = ref<DirEntry[]>([]);
|
||||
|
||||
@@ -35,7 +41,8 @@ function loadZoomPref(): number {
|
||||
const val = parseFloat(stored);
|
||||
if (!isNaN(val) && val >= ZOOM_MIN && val <= ZOOM_MAX) return val;
|
||||
}
|
||||
} catch { /* localStorage unavailable */ }
|
||||
} catch { /* localStorage unavailable */
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
@@ -48,7 +55,10 @@ function applyZoomCssVars() {
|
||||
}
|
||||
|
||||
function saveZoomPref() {
|
||||
try { localStorage.setItem("editor-zoom", String(editorZoom.value)); } catch { /* noop */ }
|
||||
try {
|
||||
localStorage.setItem("editor-zoom", String(editorZoom.value));
|
||||
} catch { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
function onEditorWheel(e: WheelEvent) {
|
||||
@@ -63,20 +73,28 @@ function onEditorWheel(e: WheelEvent) {
|
||||
// Show zoom indicator and reset timer
|
||||
showZoomIndicator.value = true;
|
||||
if (zoomIndicatorTimer) clearTimeout(zoomIndicatorTimer);
|
||||
zoomIndicatorTimer = setTimeout(() => { showZoomIndicator.value = false; }, 1500);
|
||||
zoomIndicatorTimer = setTimeout(() => {
|
||||
showZoomIndicator.value = false;
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// ---- full-width mode ----
|
||||
function loadFullWidthPref(): boolean {
|
||||
try {
|
||||
return localStorage.getItem("editor-full-width") === "true";
|
||||
} catch { return false; }
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const isFullWidth = ref(loadFullWidthPref());
|
||||
|
||||
function onToggleFullWidth(val: boolean) {
|
||||
isFullWidth.value = val;
|
||||
try { localStorage.setItem("editor-full-width", String(val)); } catch { /* noop */ }
|
||||
try {
|
||||
localStorage.setItem("editor-full-width", String(val));
|
||||
} catch { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ---- display mode (split / wysiwyg) ----
|
||||
@@ -86,19 +104,62 @@ function loadDisplayModePref(): DisplayMode {
|
||||
try {
|
||||
const v = localStorage.getItem("editor-display-mode");
|
||||
if (v === "split" || v === "wysiwyg") return v;
|
||||
} catch { /* noop */ }
|
||||
} catch { /* noop */
|
||||
}
|
||||
return "split";
|
||||
}
|
||||
|
||||
const displayMode = ref<DisplayMode>(loadDisplayModePref());
|
||||
|
||||
function onToggleDisplayMode(val: DisplayMode) {
|
||||
displayMode.value = val;
|
||||
try { localStorage.setItem("editor-display-mode", val); } catch { /* noop */ }
|
||||
try {
|
||||
localStorage.setItem("editor-display-mode", val);
|
||||
} catch { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
// ---- toolbar state ----
|
||||
const showThumbnail = ref(true);
|
||||
const previewOnly = ref(false);
|
||||
const showMoreMenu = ref(false);
|
||||
|
||||
function toggleFullWidth() {
|
||||
isFullWidth.value = !isFullWidth.value;
|
||||
try {
|
||||
localStorage.setItem("editor-full-width", String(isFullWidth.value));
|
||||
} catch { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDisplayMode() {
|
||||
displayMode.value = displayMode.value === "split" ? "wysiwyg" : "split";
|
||||
try {
|
||||
localStorage.setItem("editor-display-mode", displayMode.value);
|
||||
} catch { /* noop */
|
||||
}
|
||||
}
|
||||
|
||||
function handleExportPdf() {
|
||||
showMoreMenu.value = false;
|
||||
// TODO: implement PDF export
|
||||
}
|
||||
|
||||
function closeMoreMenu() {
|
||||
showMoreMenu.value = false;
|
||||
}
|
||||
|
||||
watch(showMoreMenu, (val) => {
|
||||
if (val) {
|
||||
setTimeout(() => {
|
||||
document.addEventListener("click", closeMoreMenu, {once: true});
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
applyZoomCssVars();
|
||||
window.addEventListener("wheel", onEditorWheel, { passive: false });
|
||||
window.addEventListener("wheel", onEditorWheel, {passive: false});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -142,7 +203,7 @@ function onHeadingClick(heading: { text: string; level: number }) {
|
||||
const headingEls = document.querySelectorAll(selector);
|
||||
for (const el of headingEls) {
|
||||
if (el.textContent?.trim() === heading.text) {
|
||||
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
el.scrollIntoView({behavior: "smooth", block: "start"});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +212,7 @@ function onHeadingClick(heading: { text: string; level: number }) {
|
||||
async function loadDir(path: string) {
|
||||
folderPath.value = path;
|
||||
try {
|
||||
dirEntries.value = await invoke<DirEntry[]>("read_dir", { path });
|
||||
dirEntries.value = await invoke<DirEntry[]>("read_dir", {path});
|
||||
} catch (e) {
|
||||
console.error("Failed to read directory:", e);
|
||||
dirEntries.value = [];
|
||||
@@ -189,7 +250,7 @@ async function onToggleFolder(path: string) {
|
||||
const entry = findEntry(dirEntries.value, path);
|
||||
if (!entry || entry.children !== undefined) return;
|
||||
try {
|
||||
const children = await invoke<DirEntry[]>("read_dir", { path });
|
||||
const children = await invoke<DirEntry[]>("read_dir", {path});
|
||||
entry.children = children;
|
||||
} catch (e) {
|
||||
console.error("Failed to read subdirectory:", e);
|
||||
@@ -198,7 +259,7 @@ async function onToggleFolder(path: string) {
|
||||
|
||||
async function onOpenFile(path: string) {
|
||||
try {
|
||||
const fileContent = await invoke<string>("read_file", { path });
|
||||
const fileContent = await invoke<string>("read_file", {path});
|
||||
content.value = fileContent;
|
||||
currentFilePath.value = path;
|
||||
saveStatus.value = "";
|
||||
@@ -221,9 +282,11 @@ async function doSave() {
|
||||
}
|
||||
|
||||
try {
|
||||
await invoke("write_file", { path: currentFilePath.value, content: content.value });
|
||||
await invoke("write_file", {path: currentFilePath.value, content: content.value});
|
||||
saveStatus.value = `已保存: ${getFileName(currentFilePath.value)}`;
|
||||
setTimeout(() => { saveStatus.value = ""; }, 3000);
|
||||
setTimeout(() => {
|
||||
saveStatus.value = "";
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
console.error("Save failed:", e);
|
||||
saveStatus.value = `保存失败: ${e}`;
|
||||
@@ -235,9 +298,11 @@ async function doSaveAs() {
|
||||
const chosen = await invoke<string | null>("pick_save_file");
|
||||
if (!chosen) return; // user cancelled
|
||||
currentFilePath.value = chosen;
|
||||
await invoke("write_file", { path: currentFilePath.value, content: content.value });
|
||||
await invoke("write_file", {path: currentFilePath.value, content: content.value});
|
||||
saveStatus.value = `已保存: ${getFileName(currentFilePath.value)}`;
|
||||
setTimeout(() => { saveStatus.value = ""; }, 3000);
|
||||
setTimeout(() => {
|
||||
saveStatus.value = "";
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
console.error("Save failed:", e);
|
||||
saveStatus.value = `保存失败: ${e}`;
|
||||
@@ -284,6 +349,7 @@ listen("menu-event", (event) => {
|
||||
:current-file-path="currentFilePath"
|
||||
:is-full-width="isFullWidth"
|
||||
:display-mode="displayMode"
|
||||
:is-markdown-file="isMarkdownFile"
|
||||
@heading-click="onHeadingClick"
|
||||
@open-folder="onOpenFolder"
|
||||
@toggle-folder="onToggleFolder"
|
||||
@@ -294,10 +360,111 @@ listen("menu-event", (event) => {
|
||||
|
||||
<!-- Right: Editor area -->
|
||||
<main class="flex-1 flex flex-col overflow-hidden relative">
|
||||
<!-- ════════════════ Toolbar ════════════════ -->
|
||||
<div
|
||||
class="flex w-full items-center h-10 px-3 bg-[#faf9f6] border border-[#e0dbcf] border-b-0 select-none shrink-0"
|
||||
>
|
||||
<!-- Left: Thumbnail toggle -->
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
:class="{ 'toolbar-btn-active': showThumbnail }"
|
||||
title="切换缩略图"
|
||||
@click="showThumbnail = !showThumbnail"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="1" y="1" width="6" height="6" rx="1"/>
|
||||
<rect x="9" y="1" width="6" height="6" rx="1"/>
|
||||
<rect x="1" y="9" width="6" height="6" rx="1"/>
|
||||
<rect x="9" y="9" width="6" height="6" rx="1"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Center: Filename -->
|
||||
<div class="flex-1 text-center text-sm text-[#8c877d] font-medium truncate px-3 select-none">
|
||||
{{ currentFilePath ? getFileName(currentFilePath) : '未命名文档' }}
|
||||
</div>
|
||||
|
||||
<!-- Right: Actions -->
|
||||
<div class="flex items-center gap-0.5">
|
||||
<!-- Display mode toggle (only for .md files) -->
|
||||
<button
|
||||
v-if="isMarkdownFile"
|
||||
class="toolbar-btn"
|
||||
:title="displayMode === 'wysiwyg' ? '切换到双栏' : '切换到单栏'"
|
||||
@click="toggleDisplayMode"
|
||||
>
|
||||
<!-- Split icon (shown when single, meaning click to switch to split) -->
|
||||
<svg v-if="displayMode === 'wysiwyg'" width="16" height="16" viewBox="0 0 16 16" fill="none"
|
||||
stroke="currentColor" stroke-width="1.5">
|
||||
<rect x="1" y="2" width="6" height="12" rx="1"/>
|
||||
<rect x="9" y="2" width="6" height="12" rx="1"/>
|
||||
</svg>
|
||||
<!-- Single icon -->
|
||||
<svg v-else width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor"
|
||||
stroke-width="1.5">
|
||||
<rect x="3" y="2" width="10" height="12" rx="1"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Preview/Edit toggle -->
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
:class="{ 'toolbar-btn-active': !previewOnly }"
|
||||
:title="previewOnly ? '切换到编辑模式' : '切换到预览模式'"
|
||||
@click="previewOnly = !previewOnly"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M11.5 1.5l3 3L5 14H2v-3L11.5 1.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Full-width toggle -->
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
:class="{ 'toolbar-btn-active': isFullWidth }"
|
||||
title="切换全宽"
|
||||
@click="toggleFullWidth"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M10 1h5v5M6 15H1v-5M15 1l-5 5M1 15l5-5"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- More menu -->
|
||||
<div class="relative">
|
||||
<button
|
||||
class="toolbar-btn"
|
||||
title="更多"
|
||||
@click="showMoreMenu = !showMoreMenu"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<circle cx="3" cy="8" r="1.5"/>
|
||||
<circle cx="8" cy="8" r="1.5"/>
|
||||
<circle cx="13" cy="8" r="1.5"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
v-if="showMoreMenu"
|
||||
class="dropdown-menu"
|
||||
@click.stop
|
||||
>
|
||||
<button class="dropdown-item" @click="handleExportPdf">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M5 1h6l4 4v9a1 1 0 01-1 1H2a1 1 0 01-1-1V2a1 1 0 011-1z"/>
|
||||
<path d="M5 1v4h4"/>
|
||||
<path d="M5 9h6M5 12h4"/>
|
||||
</svg>
|
||||
导出 PDF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MarkdownEditor
|
||||
:model-value="content"
|
||||
:is-full-width="isFullWidth"
|
||||
:display-mode="displayMode"
|
||||
:preview-only="previewOnly"
|
||||
placeholder="输入 Markdown 内容... 支持标题、粗体、斜体、代码块等 ✨"
|
||||
@update:model-value="onUpdate"
|
||||
@save-shortcut="doSave"
|
||||
@@ -329,10 +496,10 @@ listen("menu-event", (event) => {
|
||||
</span>
|
||||
<span v-else class="text-xs text-[#b8b3a8]">未保存</span>
|
||||
|
||||
<span class="flex-1" />
|
||||
<span class="flex-1"/>
|
||||
|
||||
<span class="text-xs text-[#8c877d]">{{ wordCount.toLocaleString() }} 词</span>
|
||||
<span class="w-px h-3 bg-[#d4cfc4]" />
|
||||
<span class="w-px h-3 bg-[#d4cfc4]"/>
|
||||
<span class="text-xs text-[#8c877d]">{{ charCount.toLocaleString() }} 字</span>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -344,10 +511,71 @@ listen("menu-event", (event) => {
|
||||
.zoom-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.zoom-fade-enter-from,
|
||||
.zoom-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── Toolbar ───────────────────────────────────────────────────── */
|
||||
|
||||
.toolbar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
color: #8c877d;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-btn:hover {
|
||||
background-color: #ede8dd;
|
||||
color: #38342e;
|
||||
}
|
||||
|
||||
.toolbar-btn-active {
|
||||
color: #bf6a3b;
|
||||
background-color: #f4f1ea;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
background: white;
|
||||
border: 1px solid #e0dbcf;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
min-width: 140px;
|
||||
padding: 4px;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
color: #38342e;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f4f1ea;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@ const props = withDefaults(
|
||||
currentFilePath?: string | null;
|
||||
isFullWidth?: boolean;
|
||||
displayMode?: "split" | "wysiwyg";
|
||||
isMarkdownFile?: boolean;
|
||||
}>(),
|
||||
{
|
||||
content: "",
|
||||
currentFilePath: null,
|
||||
isFullWidth: false,
|
||||
displayMode: "split",
|
||||
isMarkdownFile: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -252,8 +254,8 @@ const showSettings = ref(false);
|
||||
</button>
|
||||
</div>
|
||||
<div class="px-4 py-6 space-y-5">
|
||||
<!-- Display mode toggle -->
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Display mode toggle (only for .md files) -->
|
||||
<div v-if="props.isMarkdownFile" class="flex items-center justify-between">
|
||||
<div class="flex-1 mr-3">
|
||||
<p class="text-sm text-[#38342e] font-medium">编辑模式</p>
|
||||
<p class="text-xs text-[#8c877d] mt-0.5 leading-relaxed">
|
||||
|
||||
@@ -13,12 +13,14 @@ const props = withDefaults(
|
||||
placeholder?: string;
|
||||
isFullWidth?: boolean;
|
||||
displayMode?: "split" | "wysiwyg";
|
||||
previewOnly?: boolean;
|
||||
}>(),
|
||||
{
|
||||
modelValue: "",
|
||||
placeholder: "开始写作...",
|
||||
isFullWidth: false,
|
||||
displayMode: "split",
|
||||
previewOnly: false,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -62,6 +64,11 @@ watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (!isSingle.value) renderFullHtml(val);
|
||||
// 切换文件时重置滚动位置到顶部
|
||||
nextTick(() => {
|
||||
if (previewRef.value) previewRef.value.scrollTop = 0;
|
||||
if (splitTextareaRef.value) splitTextareaRef.value.scrollTop = 0;
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -126,11 +133,17 @@ function escapeHtml(s: string): string {
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
const singlePaneRef = ref<HTMLDivElement>();
|
||||
|
||||
// Watch modelValue → re-parse blocks in single mode
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (isSingle.value) parseBlocks(val);
|
||||
// 切换文件时重置滚动位置到顶部
|
||||
nextTick(() => {
|
||||
if (singlePaneRef.value) singlePaneRef.value.scrollTop = 0;
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -318,6 +331,7 @@ function onSplitScroll() {
|
||||
<!-- ════════════════ Single-pane: block editing ════════════════ -->
|
||||
<div
|
||||
v-if="isSingle"
|
||||
ref="singlePaneRef"
|
||||
class="flex-1 overflow-auto p-8 pb-40"
|
||||
:style="{ fontSize: `calc(1rem * var(--editor-zoom, 1))` }"
|
||||
>
|
||||
@@ -351,27 +365,33 @@ function onSplitScroll() {
|
||||
<!-- Rendered state (clickable) -->
|
||||
<div
|
||||
v-else
|
||||
class="
|
||||
markdown-preview block-render
|
||||
cursor-text rounded
|
||||
-mx-1 px-1 py-px
|
||||
hover:bg-[#faf9f6] transition-colors
|
||||
"
|
||||
:class="[
|
||||
'markdown-preview block-render py-px',
|
||||
previewOnly ? '' : 'cursor-text rounded -mx-1 px-1 hover:bg-[#faf9f6] transition-colors',
|
||||
]"
|
||||
v-html="block.html"
|
||||
@click="startEdit(block.id)"
|
||||
@click="!previewOnly && startEdit(block.id)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════ Split-pane mode ════════════════ -->
|
||||
<template v-else>
|
||||
<div class="flex-1 min-w-0 border-r border-[#e0dbcf] flex flex-col">
|
||||
<!-- Textarea column: smoothly collapses width on preview-only -->
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col min-h-0 transition-[width,opacity] duration-300 ease-in-out',
|
||||
previewOnly
|
||||
? 'w-0 overflow-hidden opacity-0 border-r-0'
|
||||
: 'flex-1 min-w-0 border-r border-[#e0dbcf] opacity-100',
|
||||
]"
|
||||
>
|
||||
<textarea
|
||||
ref="splitTextareaRef"
|
||||
:value="props.modelValue"
|
||||
:placeholder="props.placeholder"
|
||||
class="
|
||||
flex-1 w-full resize-none bg-white p-8 pb-40 outline-none
|
||||
flex-1 min-h-0 w-full resize-none bg-white p-8 pb-40 outline-none
|
||||
font-serif text-[#38342e] leading-relaxed
|
||||
placeholder:text-[#b8b3a8]
|
||||
"
|
||||
@@ -382,9 +402,10 @@ function onSplitScroll() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Preview column: smoothly expands to fill available space -->
|
||||
<div
|
||||
ref="previewRef"
|
||||
class="flex-1 min-w-0 overflow-auto p-8 pb-40"
|
||||
class="flex-1 min-w-0 min-h-0 overflow-auto p-8 pb-40 transition-[width] duration-300 ease-in-out"
|
||||
:style="{ fontSize: `calc(1rem * var(--editor-zoom, 1))` }"
|
||||
>
|
||||
<div v-if="renderError" class="text-red-500 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user