完善编辑功能
This commit is contained in:
@@ -60,9 +60,9 @@
|
|||||||
"updatedAt": 1783674217586
|
"updatedAt": 1783674217586
|
||||||
},
|
},
|
||||||
"topic_20260710-090003_60717bd90da5ec68": {
|
"topic_20260710-090003_60717bd90da5ec68": {
|
||||||
"stage": 1,
|
"stage": 3,
|
||||||
"userTurns": 1,
|
"userTurns": 3,
|
||||||
"basisHash": "cd60850543a6985b",
|
"basisHash": "de6e56c00454cedd",
|
||||||
"updatedAt": 1783700550206
|
"updatedAt": 1784540365461
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,5 +50,5 @@
|
|||||||
"topic_20260710-034724_3c6fb29d8d0a4119": "目录树中鼠标悬浮弹框dropmenu…",
|
"topic_20260710-034724_3c6fb29d8d0a4119": "目录树中鼠标悬浮弹框dropmenu…",
|
||||||
"topic_20260710-054543_5e7c427932aecd7b": "现在在markdown的编辑区域中…",
|
"topic_20260710-054543_5e7c427932aecd7b": "现在在markdown的编辑区域中…",
|
||||||
"topic_20260710-063053_353d579222d3b70b": "这个应用里有一个全局搜索的功能,帮我…",
|
"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">
|
<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 {listen, type EventCallback, type EventName} from "@tauri-apps/api/event";
|
||||||
import {convertFileSrc, invoke, isTauri} from "@tauri-apps/api/core";
|
import {convertFileSrc, invoke, isTauri} from "@tauri-apps/api/core";
|
||||||
import {getCurrentWindow, type CloseRequestedEvent} from "@tauri-apps/api/window";
|
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 workspaceRefreshTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let workspaceRefreshInFlight = false;
|
let workspaceRefreshInFlight = false;
|
||||||
let workspaceRefreshQueued = false;
|
let workspaceRefreshQueued = false;
|
||||||
let syncInFlight = false;
|
const syncInFlight = ref(false);
|
||||||
let fileOpenRequest = 0;
|
let fileOpenRequest = 0;
|
||||||
let saveNoticeTimer: ReturnType<typeof setTimeout> | null = null;
|
let saveNoticeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
let closeSaveInFlight = false;
|
let closeSaveInFlight = false;
|
||||||
@@ -247,7 +247,6 @@ const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
|||||||
const appTheme = ref<AppTheme>(loadAppTheme());
|
const appTheme = ref<AppTheme>(loadAppTheme());
|
||||||
const renderSingleLineBreaks = ref(loadRenderSingleLineBreaks());
|
const renderSingleLineBreaks = ref(loadRenderSingleLineBreaks());
|
||||||
const paragraphIndent = ref(loadParagraphIndent());
|
const paragraphIndent = ref(loadParagraphIndent());
|
||||||
const showMoreMenu = ref(false);
|
|
||||||
const settingsRequest = ref(0);
|
const settingsRequest = ref(0);
|
||||||
const settingsInitialPage = ref<"about" | "editor" | "sync">("about");
|
const settingsInitialPage = ref<"about" | "editor" | "sync">("about");
|
||||||
const markdownEditorRef = ref<{ flushPendingChanges: () => string | undefined; scrollToLine: (line: number) => void } | null>(null);
|
const markdownEditorRef = ref<{ flushPendingChanges: () => string | undefined; scrollToLine: (line: number) => void } | null>(null);
|
||||||
@@ -398,23 +397,6 @@ function updateAppTheme(theme: AppTheme) {
|
|||||||
saveAppTheme(theme);
|
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(() => {
|
onMounted(() => {
|
||||||
applyAppTheme(appTheme.value);
|
applyAppTheme(appTheme.value);
|
||||||
applyZoomCssVars();
|
applyZoomCssVars();
|
||||||
@@ -503,7 +485,6 @@ function resetMarkdownEditorState() {
|
|||||||
saveStatus.value = "";
|
saveStatus.value = "";
|
||||||
showOutline.value = false;
|
showOutline.value = false;
|
||||||
documentMode.value = defaultDocumentMode.value;
|
documentMode.value = defaultDocumentMode.value;
|
||||||
showMoreMenu.value = false;
|
|
||||||
editorResetKey.value += 1;
|
editorResetKey.value += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,36 +779,41 @@ async function refreshCurrentOpenFileFromDisk() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runConfiguredSync(trigger: "startup" | "save" | "manual") {
|
async function runConfiguredSync(trigger: "startup" | "save" | "manual") {
|
||||||
if (!folderPath.value || syncInFlight) return false;
|
if (!folderPath.value || syncInFlight.value) return false;
|
||||||
|
|
||||||
const config = await loadSyncConfig();
|
const config = await loadSyncConfig();
|
||||||
if (trigger !== "manual" && !shouldRunSync(config, trigger)) return false;
|
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") {
|
if (trigger !== "save") {
|
||||||
const saved = await saveCurrentWorkspaceDocumentBeforeSwitch();
|
const saved = await saveCurrentWorkspaceDocumentBeforeSwitch();
|
||||||
if (!saved) return false;
|
if (!saved) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
syncInFlight = true;
|
syncInFlight.value = true;
|
||||||
|
showSaveNotice("正在同步...", "success");
|
||||||
try {
|
try {
|
||||||
const result = await invoke<SyncResult>("sync_now", {
|
const result = await invoke<SyncResult>("sync_now", {
|
||||||
workspace: folderPath.value,
|
workspace: folderPath.value,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
saveStatus.value = describeSyncResult(result);
|
saveStatus.value = describeSyncResult(result);
|
||||||
setTimeout(() => {
|
showSaveNotice(describeSyncResult(result), "success");
|
||||||
saveStatus.value = "";
|
|
||||||
}, 3600);
|
|
||||||
await refreshWorkspaceTree();
|
await refreshWorkspaceTree();
|
||||||
await refreshCurrentOpenFileFromDisk();
|
await refreshCurrentOpenFileFromDisk();
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Sync failed:", e);
|
console.error("Sync failed:", e);
|
||||||
saveStatus.value = `同步失败: ${e}`;
|
saveStatus.value = `同步失败: ${e}`;
|
||||||
|
showSaveNotice(`同步失败: ${e}`, "error");
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
syncInFlight = false;
|
syncInFlight.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -915,7 +901,6 @@ function openMediaPreview(path: string, fileType: Extract<WorkspaceFileType, "im
|
|||||||
hasDraftDocument.value = false;
|
hasDraftDocument.value = false;
|
||||||
saveStatus.value = "";
|
saveStatus.value = "";
|
||||||
showOutline.value = false;
|
showOutline.value = false;
|
||||||
showMoreMenu.value = false;
|
|
||||||
editorResetKey.value += 1;
|
editorResetKey.value += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1380,34 +1365,16 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- More menu -->
|
<!-- Sync button -->
|
||||||
<div v-if="isMarkdownDocument" class="relative">
|
<button
|
||||||
<button
|
v-if="folderPath"
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
title="更多"
|
:title="syncInFlight ? '正在同步...' : '同步工作目录'"
|
||||||
@click="showMoreMenu = !showMoreMenu"
|
:disabled="syncInFlight"
|
||||||
>
|
@click="runConfiguredSync('manual')"
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
>
|
||||||
<circle cx="3" cy="8" r="1.5"/>
|
<i :class="syncInFlight ? 'ri-loader-4-line sync-spinning' : 'ri-loop-left-line'"></i>
|
||||||
<circle cx="8" cy="8" r="1.5"/>
|
</button>
|
||||||
<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>
|
||||||
|
|
||||||
<!-- Window controls -->
|
<!-- Window controls -->
|
||||||
@@ -1872,38 +1839,6 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
box-shadow: 0 1px 3px var(--app-shadow);
|
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 {
|
.start-actions {
|
||||||
background:
|
background:
|
||||||
@@ -1934,4 +1869,15 @@ async function handleSearchResultClick(filePath: string, lineNumber: number) {
|
|||||||
text-decoration-color: currentColor;
|
text-decoration-color: currentColor;
|
||||||
outline: none;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -1148,16 +1148,7 @@ defineExpose({
|
|||||||
.markdown-preview h3 { font-size: 1.125em; }
|
.markdown-preview h3 { font-size: 1.125em; }
|
||||||
.markdown-preview h4 { font-size: 1em; }
|
.markdown-preview h4 { font-size: 1em; }
|
||||||
|
|
||||||
.markdown-preview p { margin: 0; text-indent: var(--paragraph-indent, 0); }
|
.markdown-preview p { margin: 0.8em 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 strong { font-weight: 700; color: var(--app-text-strong); }
|
.markdown-preview strong { font-weight: 700; color: var(--app-text-strong); }
|
||||||
.markdown-preview em { font-style: italic; }
|
.markdown-preview em { font-style: italic; }
|
||||||
|
|||||||
Reference in New Issue
Block a user