完善编辑功能
This commit is contained in:
@@ -60,9 +60,9 @@
|
||||
"updatedAt": 1783674217586
|
||||
},
|
||||
"topic_20260710-090003_60717bd90da5ec68": {
|
||||
"stage": 1,
|
||||
"userTurns": 1,
|
||||
"basisHash": "cd60850543a6985b",
|
||||
"updatedAt": 1783700550206
|
||||
"stage": 3,
|
||||
"userTurns": 3,
|
||||
"basisHash": "de6e56c00454cedd",
|
||||
"updatedAt": 1784540365461
|
||||
}
|
||||
}
|
||||
@@ -50,5 +50,5 @@
|
||||
"topic_20260710-034724_3c6fb29d8d0a4119": "目录树中鼠标悬浮弹框dropmenu…",
|
||||
"topic_20260710-054543_5e7c427932aecd7b": "现在在markdown的编辑区域中…",
|
||||
"topic_20260710-063053_353d579222d3b70b": "这个应用里有一个全局搜索的功能,帮我…",
|
||||
"topic_20260710-090003_60717bd90da5ec68": "当用户在markdownEditor…"
|
||||
"topic_20260710-090003_60717bd90da5ec68": "D:\\code\\yurou\\src…"
|
||||
}
|
||||
124
src/App.vue
124
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref, watch, onMounted, onUnmounted, nextTick} from "vue";
|
||||
import {computed, ref, onMounted, onUnmounted, nextTick} from "vue";
|
||||
import {listen, type EventCallback, type EventName} from "@tauri-apps/api/event";
|
||||
import {convertFileSrc, invoke, isTauri} from "@tauri-apps/api/core";
|
||||
import {getCurrentWindow, type CloseRequestedEvent} from "@tauri-apps/api/window";
|
||||
@@ -186,7 +186,7 @@ const WORKSPACE_REFRESH_DELAY_MS = 140;
|
||||
let workspaceRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let workspaceRefreshInFlight = false;
|
||||
let workspaceRefreshQueued = false;
|
||||
let syncInFlight = false;
|
||||
const syncInFlight = ref(false);
|
||||
let fileOpenRequest = 0;
|
||||
let saveNoticeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let closeSaveInFlight = false;
|
||||
@@ -247,7 +247,6 @@ const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
||||
const appTheme = ref<AppTheme>(loadAppTheme());
|
||||
const renderSingleLineBreaks = ref(loadRenderSingleLineBreaks());
|
||||
const paragraphIndent = ref(loadParagraphIndent());
|
||||
const showMoreMenu = ref(false);
|
||||
const settingsRequest = ref(0);
|
||||
const settingsInitialPage = ref<"about" | "editor" | "sync">("about");
|
||||
const markdownEditorRef = ref<{ flushPendingChanges: () => string | undefined; scrollToLine: (line: number) => void } | null>(null);
|
||||
@@ -398,23 +397,6 @@ function updateAppTheme(theme: AppTheme) {
|
||||
saveAppTheme(theme);
|
||||
}
|
||||
|
||||
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(() => {
|
||||
applyAppTheme(appTheme.value);
|
||||
applyZoomCssVars();
|
||||
@@ -503,7 +485,6 @@ function resetMarkdownEditorState() {
|
||||
saveStatus.value = "";
|
||||
showOutline.value = false;
|
||||
documentMode.value = defaultDocumentMode.value;
|
||||
showMoreMenu.value = false;
|
||||
editorResetKey.value += 1;
|
||||
}
|
||||
|
||||
@@ -798,36 +779,41 @@ async function refreshCurrentOpenFileFromDisk() {
|
||||
}
|
||||
|
||||
async function runConfiguredSync(trigger: "startup" | "save" | "manual") {
|
||||
if (!folderPath.value || syncInFlight) return false;
|
||||
if (!folderPath.value || syncInFlight.value) return false;
|
||||
|
||||
const config = await loadSyncConfig();
|
||||
if (trigger !== "manual" && !shouldRunSync(config, trigger)) return false;
|
||||
if (!config || config.provider === "none") return false;
|
||||
if (!config || config.provider === "none") {
|
||||
if (trigger === "manual") {
|
||||
showSaveNotice("请先在设置中配置同步方式", "error");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trigger !== "save") {
|
||||
const saved = await saveCurrentWorkspaceDocumentBeforeSwitch();
|
||||
if (!saved) return false;
|
||||
}
|
||||
|
||||
syncInFlight = true;
|
||||
syncInFlight.value = true;
|
||||
showSaveNotice("正在同步...", "success");
|
||||
try {
|
||||
const result = await invoke<SyncResult>("sync_now", {
|
||||
workspace: folderPath.value,
|
||||
config,
|
||||
});
|
||||
saveStatus.value = describeSyncResult(result);
|
||||
setTimeout(() => {
|
||||
saveStatus.value = "";
|
||||
}, 3600);
|
||||
showSaveNotice(describeSyncResult(result), "success");
|
||||
await refreshWorkspaceTree();
|
||||
await refreshCurrentOpenFileFromDisk();
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Sync failed:", e);
|
||||
saveStatus.value = `同步失败: ${e}`;
|
||||
showSaveNotice(`同步失败: ${e}`, "error");
|
||||
return false;
|
||||
} finally {
|
||||
syncInFlight = false;
|
||||
syncInFlight.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,7 +901,6 @@ function openMediaPreview(path: string, fileType: Extract<WorkspaceFileType, "im
|
||||
hasDraftDocument.value = false;
|
||||
saveStatus.value = "";
|
||||
showOutline.value = false;
|
||||
showMoreMenu.value = false;
|
||||
editorResetKey.value += 1;
|
||||
}
|
||||
|
||||
@@ -1380,34 +1365,16 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- More menu -->
|
||||
<div v-if="isMarkdownDocument" 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>
|
||||
<!-- Sync button -->
|
||||
<button
|
||||
v-if="folderPath"
|
||||
class="toolbar-btn"
|
||||
:title="syncInFlight ? '正在同步...' : '同步工作目录'"
|
||||
:disabled="syncInFlight"
|
||||
@click="runConfiguredSync('manual')"
|
||||
>
|
||||
<i :class="syncInFlight ? 'ri-loader-4-line sync-spinning' : 'ri-loop-left-line'"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Window controls -->
|
||||
@@ -1872,38 +1839,6 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
||||
box-shadow: 0 1px 3px var(--app-shadow);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
margin-top: 4px;
|
||||
background: var(--app-surface);
|
||||
border: 1px solid var(--app-border-strong);
|
||||
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: var(--app-text);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--app-surface-muted);
|
||||
}
|
||||
|
||||
.start-actions {
|
||||
background:
|
||||
@@ -1934,4 +1869,15 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
||||
text-decoration-color: currentColor;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Sync button spin animation */
|
||||
@keyframes sync-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.sync-spinning {
|
||||
display: inline-block;
|
||||
animation: sync-spin 1s linear infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1148,16 +1148,7 @@ defineExpose({
|
||||
.markdown-preview h3 { font-size: 1.125em; }
|
||||
.markdown-preview h4 { font-size: 1em; }
|
||||
|
||||
.markdown-preview p { margin: 0; text-indent: var(--paragraph-indent, 0); }
|
||||
|
||||
.markdown-preview:not(.markdown-mixed) > p + p,
|
||||
.markdown-mixed
|
||||
> .markdown-block-paragraph
|
||||
+ .markdown-block-paragraph
|
||||
> .markdown-block-preview
|
||||
> p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.markdown-preview p { margin: 0.8em 0; text-indent: var(--paragraph-indent, 0); }
|
||||
|
||||
.markdown-preview strong { font-weight: 700; color: var(--app-text-strong); }
|
||||
.markdown-preview em { font-style: italic; }
|
||||
|
||||
Reference in New Issue
Block a user