添加预览图片功能

This commit is contained in:
2026-07-19 19:15:22 +08:00
parent 164e658e5b
commit 3c6b142098
11 changed files with 196 additions and 113 deletions

View File

@@ -58,5 +58,11 @@
"userTurns": 4,
"basisHash": "aaae120b1f33c2f2",
"updatedAt": 1783674217586
},
"topic_20260710-090003_60717bd90da5ec68": {
"stage": 1,
"userTurns": 1,
"basisHash": "cd60850543a6985b",
"updatedAt": 1783700550206
}
}

View File

@@ -1,14 +1,3 @@
{
"topic_20260630-144952_fd0584fe8f266d6a": 1782830992641,
"topic_20260703-022850_50330b4eb93f116d": 1783045730616,
"topic_20260706-145507_076a32df5f4276b5": 1783349707873,
"topic_20260706-153317_238f65a7a470703c": 1783351997029,
"topic_20260710-024855_5c4b1833149d66bb": 1783651735446,
"topic_20260710-030936_cedad2907c3954fe": 1783652976310,
"topic_20260710-032907_600adc6b00791607": 1783654147796,
"topic_20260710-034033_6432ee6566f7b432": 1783654833341,
"topic_20260710-034724_3c6fb29d8d0a4119": 1783655244536,
"topic_20260710-054543_5e7c427932aecd7b": 1783662343716,
"topic_20260710-063053_353d579222d3b70b": 1783665053391,
"topic_20260710-090003_60717bd90da5ec68": 1783674003004
"topic_20260707-131941_481a70d2be9881dd": 1783430381000
}

View File

@@ -38,6 +38,7 @@
"topic_20260707-093602_5c75f77654cab0c3": "manual",
"topic_20260707-094645_05255aaa45d5f673": "manual",
"topic_20260707-095503_bf879b65e4666516": "auto",
"topic_20260707-131941_481a70d2be9881dd": "manual",
"topic_20260708-020616_9ab54d98daf65053": "auto",
"topic_20260708-093419_71d1fea2e4b5a40f": "auto",
"topic_20260708-095428_990617acd4168b40": "auto",

View File

@@ -38,6 +38,8 @@
"topic_20260707-093602_5c75f77654cab0c3": "隐藏单栏和双栏的切换功能把mark…",
"topic_20260707-094645_05255aaa45d5f673": "帮我移除markdownEditor…",
"topic_20260707-095503_bf879b65e4666516": "帮我移除markdownEditor…",
"topic_20260707-131941_481a70d2be9881dd": "新的会话",
"topic_20260707-132824_49b096d569ffa3d3": "现在帮我把这个markdownEdi…",
"topic_20260708-020616_9ab54d98daf65053": "帮我分析一下现在的markdownE…",
"topic_20260708-093419_71d1fea2e4b5a40f": "src/components/Dir…",
"topic_20260708-095428_990617acd4168b40": "帮我检查一下这个应用在进行放大缩小的…",
@@ -48,5 +50,5 @@
"topic_20260710-034724_3c6fb29d8d0a4119": "目录树中鼠标悬浮弹框dropmenu…",
"topic_20260710-054543_5e7c427932aecd7b": "现在在markdown的编辑区域中…",
"topic_20260710-063053_353d579222d3b70b": "这个应用里有一个全局搜索的功能,帮我…",
"topic_20260710-090003_60717bd90da5ec68": "新的会话"
"topic_20260710-090003_60717bd90da5ec68": "当用户在markdownEditor…"
}

155
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"@codemirror/search": "^6.7.1",
"@codemirror/state": "^6.7.1",
"@codemirror/view": "^6.43.6",
"@fancyapps/ui": "^6.1.14",
"@lezer/highlight": "^1.2.3",
"@lucide/vue": "^1.23.0",
"@tauri-apps/api": "^2",
@@ -1460,6 +1461,12 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@fancyapps/ui": {
"version": "6.1.14",
"resolved": "https://registry.npmjs.org/@fancyapps/ui/-/ui-6.1.14.tgz",
"integrity": "sha512-cBV73XXHrc0qMca+PJJ6qay3xweIgsoYse13rGRstgUs0xA5WAv1tgbSzPCIOOjiLs2S2jiVFJxyszgvgyfxMg==",
"license": "SEE LICENSE IN LICENSE.md"
},
"node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz",
@@ -2031,9 +2038,6 @@
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2048,9 +2052,6 @@
"arm"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2065,9 +2066,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2082,9 +2080,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2099,9 +2094,6 @@
"loong64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2116,9 +2108,6 @@
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2133,9 +2122,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2150,9 +2136,6 @@
"ppc64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2167,9 +2150,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2184,9 +2164,6 @@
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2201,9 +2178,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2218,9 +2192,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2235,9 +2206,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2570,9 +2538,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2590,9 +2555,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2610,9 +2572,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2630,9 +2589,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2672,6 +2628,72 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.11.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.2.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.3.2",
"resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz",
@@ -2846,9 +2868,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
@@ -2866,9 +2885,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
@@ -2886,9 +2902,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
@@ -2906,9 +2919,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
@@ -2926,9 +2936,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0 OR MIT",
"optional": true,
"os": [
@@ -5963,9 +5970,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -5987,9 +5991,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6011,9 +6012,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6035,9 +6033,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -6350,7 +6345,7 @@
},
"node_modules/mdast-util-newline-to-break": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
"integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
"license": "MIT",
"dependencies": {
@@ -7940,7 +7935,7 @@
},
"node_modules/remark-breaks": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/remark-breaks/-/remark-breaks-4.0.0.tgz",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
"integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
"license": "MIT",
"dependencies": {

View File

@@ -18,6 +18,7 @@
"@codemirror/search": "^6.7.1",
"@codemirror/state": "^6.7.1",
"@codemirror/view": "^6.43.6",
"@fancyapps/ui": "^6.1.14",
"@lezer/highlight": "^1.2.3",
"@lucide/vue": "^1.23.0",
"@tauri-apps/api": "^2",

23
pnpm-lock.yaml generated
View File

@@ -35,6 +35,9 @@ importers:
'@codemirror/view':
specifier: ^6.43.6
version: 6.43.6
'@fancyapps/ui':
specifier: ^6.1.14
version: 6.1.14
'@lezer/highlight':
specifier: ^1.2.3
version: 1.2.3
@@ -543,6 +546,9 @@ packages:
resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@fancyapps/ui@6.1.14':
resolution: {integrity: sha512-cBV73XXHrc0qMca+PJJ6qay3xweIgsoYse13rGRstgUs0xA5WAv1tgbSzPCIOOjiLs2S2jiVFJxyszgvgyfxMg==}
'@floating-ui/core@1.7.5':
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
@@ -1841,6 +1847,7 @@ packages:
glob@11.0.0:
resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
engines: {node: 20 || >=22}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.0:
@@ -2813,10 +2820,6 @@ packages:
resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==}
engines: {node: '>=18'}
string-width@4.2.0:
resolution: {integrity: sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==}
engines: {node: '>=8'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -3698,6 +3701,8 @@ snapshots:
'@eslint/core': 1.2.1
levn: 0.4.1
'@fancyapps/ui@6.1.14': {}
'@floating-ui/core@1.7.5':
dependencies:
'@floating-ui/utils': 0.2.11
@@ -3749,7 +3754,7 @@ snapshots:
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.0
string-width-cjs: string-width@4.2.3
strip-ansi: 7.2.0
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
@@ -6186,12 +6191,6 @@ snapshots:
stdin-discarder@0.3.2: {}
string-width@4.2.0:
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -6470,7 +6469,7 @@ snapshots:
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
string-width: 4.2.0
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi@8.1.0:

View File

@@ -21,6 +21,11 @@ interface WorkspaceEntry {
path: string;
}
interface TreeExpansionCommand {
id: number;
expanded: boolean;
}
const props = withDefaults(
defineProps<{
@@ -458,6 +463,7 @@ watch(showWorkspaceMenu, (open) => {
watch(() => props.folderPath, () => {
closeWorkspaceMenu();
treeExpansionCommand.value = null;
});
onBeforeUnmount(() => {
@@ -535,6 +541,16 @@ function handleCreateRootFile() {
});
}
const treeExpansionCommand = ref<TreeExpansionCommand | null>(null);
let treeExpansionCommandId = 0;
function setAllFoldersExpanded(expanded: boolean) {
treeExpansionCommand.value = {
id: ++treeExpansionCommandId,
expanded,
};
}
</script>
@@ -600,6 +616,25 @@ function handleCreateRootFile() {
>
<i class="ri-file-add-line"></i>
</button>
<span class="tree-toolbar-separator" aria-hidden="true"></span>
<button
class="tree-toolbar-btn"
type="button"
title="全部展开"
aria-label="全部展开工作目录文件夹"
@click="setAllFoldersExpanded(true)"
>
<i class="ri-expand-vertical-line"></i>
</button>
<button
class="tree-toolbar-btn"
type="button"
title="全部折叠"
aria-label="全部折叠工作目录文件夹"
@click="setAllFoldersExpanded(false)"
>
<i class="ri-collapse-vertical-line"></i>
</button>
</div>
<!-- File tree -->
<div
@@ -612,6 +647,7 @@ function handleCreateRootFile() {
:entry="entry"
:depth="0"
:currentFilePath="currentFilePath"
:expansion-command="treeExpansionCommand"
@toggle-folder="(path: string) => emit('toggleFolder', path)"
@open-file="(path: string) => emit('openFile', path)"
@create-folder="(path: string) => emit('createFolder', path)"
@@ -1095,6 +1131,13 @@ function handleCreateRootFile() {
color: var(--app-text);
}
.tree-toolbar-separator {
width: 1px;
height: 18px;
margin: 0 2px;
background: var(--app-border-strong);
}
.sidebar-search-input {
border: 1px solid var(--app-border-strong);
background: var(--app-hover);

View File

@@ -3,9 +3,12 @@ import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
ref,
watch,
} from "vue";
import { Fancybox } from "@fancyapps/ui";
import "@fancyapps/ui/dist/fancybox/fancybox.css";
import { unified, type Plugin, type Transformer } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
@@ -270,6 +273,7 @@ const isEditingEmptyDocument = computed(
);
let modeScrollRestoreFrame: number | null = null;
let renderRequestId = 0;
const previewImageSelector = ".markdown-preview:not(.markdown-mixed) img";
interface ScrollSnapshot {
ratio: number;
@@ -599,7 +603,17 @@ watch(activeDocumentMode, (mode, oldMode) => {
restoreScrollAfterModeChange(scrollSnapshot);
});
onMounted(() => {
Fancybox.bind(scrollRef.value ?? null, previewImageSelector, {
groupAll: true,
placeFocusBack: true,
theme: "auto",
});
});
onBeforeUnmount(() => {
Fancybox.unbind(scrollRef.value ?? null, previewImageSelector);
if (modeScrollRestoreFrame !== null) {
cancelAnimationFrame(modeScrollRestoreFrame);
}
@@ -1189,6 +1203,10 @@ defineExpose({
margin: 0.5em 0;
}
.markdown-preview:not(.markdown-mixed) img {
cursor: zoom-in;
}
/* ── Mixed mode block editing ────────────────────────────────────── */
.markdown-block {

View File

@@ -142,11 +142,9 @@ const syncTargetDescription = computed(() => {
});
const syncFileDetails = computed(() => {
const files = syncFeedback.value?.result?.files || [];
return [...files].sort((left, right) => {
if (left.action === "skip" && right.action !== "skip") return 1;
if (left.action !== "skip" && right.action === "skip") return -1;
return left.path.localeCompare(right.path);
});
return files
.filter((file) => file.action !== "skip")
.sort((left, right) => left.path.localeCompare(right.path));
});
const menuItems: Array<{
@@ -875,7 +873,7 @@ onBeforeUnmount(() => {
<div class="settings-sync-file-section">
<div class="settings-sync-file-heading">
<strong>文件明细</strong>
<strong>变更文件</strong>
<span>{{ syncFileDetails.length }} 个文件</span>
</div>
<div v-if="syncFileDetails.length" class="settings-sync-file-list">
@@ -887,7 +885,7 @@ onBeforeUnmount(() => {
<span class="settings-sync-file-size">{{ formatFileSize(file.size) }}</span>
</div>
</div>
<p v-else class="settings-sync-file-empty">工作目录与远端都没有文件</p>
<p v-else class="settings-sync-file-empty">本次同步没有文件变更</p>
</div>
</template>
</section>

View File

@@ -15,12 +15,18 @@ interface DirEntry {
children?: DirEntry[];
}
interface TreeExpansionCommand {
id: number;
expanded: boolean;
}
const TREE_CONTEXT_MENU_CLOSE_EVENT = "yurou-tree-context-menu-close";
const props = defineProps<{
entry: DirEntry;
depth: number;
currentFilePath?: string | null;
expansionCommand?: TreeExpansionCommand | null;
}>();
const emit = defineEmits<{
@@ -33,6 +39,7 @@ const emit = defineEmits<{
}>();
const expanded = ref(true);
const handledExpansionCommandId = ref(0);
const contextMenuOpen = ref(false);
const contextMenuPoint = ref<{ x: number; y: number } | null>(null);
const contextMenuStyle = computed(() => {
@@ -76,12 +83,35 @@ function onContextMenuKeydown(event: KeyboardEvent) {
watch(
() => props.entry.children,
(children, oldChildren) => {
if (children !== undefined && oldChildren === undefined) {
if (
children !== undefined &&
oldChildren === undefined &&
props.expansionCommand?.expanded !== false
) {
expanded.value = true;
}
},
);
watch(
() => props.expansionCommand,
(command) => {
if (
!command ||
!props.entry.is_dir ||
command.id <= handledExpansionCommandId.value
) return;
handledExpansionCommandId.value = command.id;
expanded.value = command.expanded;
if (command.expanded && props.entry.children === undefined) {
emit("toggleFolder", props.entry.path);
}
},
{ immediate: true },
);
const isActive = computed(() => {
return props.currentFilePath != null && props.currentFilePath === props.entry.path;
});
@@ -307,6 +337,7 @@ function onContextMenu(event: MouseEvent) {
:entry="child"
:depth="depth + 1"
:currentFilePath="currentFilePath"
:expansion-command="expansionCommand"
@toggle-folder="(path: string) => emit('toggleFolder', path)"
@open-file="(path: string) => emit('openFile', path)"
@create-folder="(path: string) => emit('createFolder', path)"