添加显示图片和视频功能

This commit is contained in:
2026-07-08 17:38:56 +08:00
parent 7ee990f7a2
commit a719b70156
6 changed files with 303 additions and 4 deletions

View File

@@ -93,6 +93,17 @@ interface WorkspaceEntry {
path: string;
}
interface WorkspaceChangedPayload {
root: string;
paths: string[];
kind: string;
}
interface WorkspaceWatchErrorPayload {
root: string;
message: string;
}
const folderPath = ref("");
const dirEntries = ref<DirEntry[]>([]);
const LAST_WORKSPACE_PATH_KEY = "yurou:last-workspace-path";
@@ -100,6 +111,10 @@ const RECENT_WORKSPACES_KEY = "yurou:recent-workspaces";
const MAX_RECENT_WORKSPACES = 12;
const recentWorkspaces = ref<WorkspaceEntry[]>(loadRecentWorkspaces());
const editorResetKey = ref(0);
const WORKSPACE_REFRESH_DELAY_MS = 140;
let workspaceRefreshTimer: ReturnType<typeof setTimeout> | null = null;
let workspaceRefreshInFlight = false;
let workspaceRefreshQueued = false;
// ---- editor zoom (Ctrl+scroll) ----
const ZOOM_MIN = 0.7;
@@ -552,24 +567,49 @@ async function saveCurrentWorkspaceDocumentBeforeSwitch() {
return doSave(contentToSave);
}
function resetWorkspaceAndEditorState() {
function resetWorkspaceAndEditorState(options: { stopWatcher?: boolean } = {}) {
if (options.stopWatcher !== false) {
void stopWorkspaceWatcher();
} else {
clearPendingWorkspaceRefresh();
}
folderPath.value = "";
dirEntries.value = [];
resetMarkdownEditorState();
}
async function startWorkspaceWatcher(path: string) {
if (!isTauri()) return;
try {
await invoke("watch_workspace", {path});
} catch (e) {
console.error("Failed to watch workspace:", e);
}
}
async function stopWorkspaceWatcher() {
clearPendingWorkspaceRefresh();
if (!isTauri()) return;
try {
await invoke("unwatch_workspace");
} catch (e) {
console.error("Failed to stop watching workspace:", e);
}
}
async function switchWorkspace(path: string, options: { persist?: boolean; resetEditor?: boolean } = {}) {
const normalized = normalizeWorkspacePath(path);
if (options.resetEditor !== false) {
const saved = await saveCurrentWorkspaceDocumentBeforeSwitch();
if (!saved) return false;
resetWorkspaceAndEditorState();
resetWorkspaceAndEditorState({stopWatcher: false});
}
const loaded = await loadDir(normalized);
if (!loaded) {
await stopWorkspaceWatcher();
forgetWorkspace(normalized);
const lastWorkspacePath = getLastWorkspacePath();
@@ -580,6 +620,7 @@ async function switchWorkspace(path: string, options: { persist?: boolean; reset
return false;
}
void startWorkspaceWatcher(normalized);
rememberWorkspace(normalized);
if (options.persist !== false) {
@@ -680,6 +721,79 @@ function findEntry(entries: DirEntry[], path: string): DirEntry | null {
return null;
}
async function mergeLoadedDirectoryEntries(freshEntries: DirEntry[], currentEntries: DirEntry[]): Promise<DirEntry[]> {
const currentByPath = new Map(
currentEntries.map((entry) => [getWorkspaceIdentity(entry.path), entry]),
);
return Promise.all(
freshEntries.map(async (freshEntry) => {
const currentEntry = currentByPath.get(getWorkspaceIdentity(freshEntry.path));
if (!freshEntry.is_dir || currentEntry?.children === undefined) {
return freshEntry;
}
try {
const children = await invoke<DirEntry[]>("read_dir", {path: freshEntry.path});
freshEntry.children = await mergeLoadedDirectoryEntries(children, currentEntry.children);
} catch (e) {
console.error("Failed to refresh subdirectory:", e);
freshEntry.children = currentEntry.children;
}
return freshEntry;
}),
);
}
function clearPendingWorkspaceRefresh() {
if (workspaceRefreshTimer) {
clearTimeout(workspaceRefreshTimer);
workspaceRefreshTimer = null;
}
workspaceRefreshQueued = false;
}
function scheduleWorkspaceRefresh(root = folderPath.value) {
const expectedRoot = normalizeWorkspacePath(root);
if (!expectedRoot) return;
if (workspaceRefreshTimer) {
clearTimeout(workspaceRefreshTimer);
}
workspaceRefreshTimer = setTimeout(() => {
workspaceRefreshTimer = null;
void refreshWorkspaceTree(expectedRoot);
}, WORKSPACE_REFRESH_DELAY_MS);
}
async function refreshWorkspaceTree(expectedRoot = folderPath.value) {
const normalizedRoot = normalizeWorkspacePath(expectedRoot);
if (!normalizedRoot || !folderPath.value) return;
if (getWorkspaceIdentity(folderPath.value) !== getWorkspaceIdentity(normalizedRoot)) return;
if (workspaceRefreshInFlight) {
workspaceRefreshQueued = true;
return;
}
workspaceRefreshInFlight = true;
try {
const entries = await invoke<DirEntry[]>("read_dir", {path: normalizedRoot});
if (!folderPath.value || getWorkspaceIdentity(folderPath.value) !== getWorkspaceIdentity(normalizedRoot)) return;
dirEntries.value = await mergeLoadedDirectoryEntries(entries, dirEntries.value);
} catch (e) {
console.error("Failed to refresh workspace:", e);
} finally {
workspaceRefreshInFlight = false;
if (workspaceRefreshQueued) {
workspaceRefreshQueued = false;
scheduleWorkspaceRefresh(normalizedRoot);
}
}
}
async function onToggleFolder(path: string) {
const entry = findEntry(dirEntries.value, path);
if (!entry || entry.children !== undefined) return;
@@ -793,6 +907,19 @@ registerTauriListener<string>("folder-opened", (event) => {
void switchWorkspace(event.payload);
});
registerTauriListener<WorkspaceChangedPayload>("workspace-changed", (event) => {
if (!folderPath.value) return;
if (getWorkspaceIdentity(event.payload.root) !== getWorkspaceIdentity(folderPath.value)) return;
scheduleWorkspaceRefresh(event.payload.root);
});
registerTauriListener<WorkspaceWatchErrorPayload>("workspace-watch-error", (event) => {
if (!folderPath.value) return;
if (getWorkspaceIdentity(event.payload.root) !== getWorkspaceIdentity(folderPath.value)) return;
console.error("Workspace watcher error:", event.payload.message);
scheduleWorkspaceRefresh(event.payload.root);
});
registerTauriListener("menu-save", () => {
doSave();
});