优化功能
This commit is contained in:
@@ -30,10 +30,10 @@
|
|||||||
"updatedAt": 1783653328367
|
"updatedAt": 1783653328367
|
||||||
},
|
},
|
||||||
"topic_20260710-032907_600adc6b00791607": {
|
"topic_20260710-032907_600adc6b00791607": {
|
||||||
"stage": 1,
|
"stage": 3,
|
||||||
"userTurns": 1,
|
"userTurns": 3,
|
||||||
"basisHash": "60a646e63a54c3bf",
|
"basisHash": "179afc4a827fe6eb",
|
||||||
"updatedAt": 1783669646165
|
"updatedAt": 1783671091019
|
||||||
},
|
},
|
||||||
"topic_20260710-034033_6432ee6566f7b432": {
|
"topic_20260710-034033_6432ee6566f7b432": {
|
||||||
"stage": 1,
|
"stage": 1,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {ref, computed, onBeforeUnmount, watch, nextTick} from "vue";
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import SettingsModal from "./SettingsModal.vue";
|
import SettingsModal from "./SettingsModal.vue";
|
||||||
import TreeEntry from "./TreeEntry.vue";
|
import TreeEntry from "./TreeEntry.vue";
|
||||||
|
import PromptDialog from "./PromptDialog.vue";
|
||||||
|
|
||||||
type SettingsPage = "about" | "editor";
|
type SettingsPage = "about" | "editor";
|
||||||
type DocumentMode = "preview" | "edit" | "mixed";
|
type DocumentMode = "preview" | "edit" | "mixed";
|
||||||
@@ -524,6 +525,59 @@ function openSettings(page: SettingsPage) {
|
|||||||
showSettings.value = true;
|
showSettings.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- root create prompt ----
|
||||||
|
const promptShow = ref(false);
|
||||||
|
const promptTitle = ref("");
|
||||||
|
const promptMessage = ref("");
|
||||||
|
const promptPlaceholder = ref("");
|
||||||
|
const promptDefaultValue = ref("");
|
||||||
|
let promptCallback: ((value: string) => void) | null = null;
|
||||||
|
|
||||||
|
function openPrompt(
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
placeholder: string,
|
||||||
|
defaultValue: string,
|
||||||
|
cb: (value: string) => void,
|
||||||
|
) {
|
||||||
|
promptTitle.value = title;
|
||||||
|
promptMessage.value = message;
|
||||||
|
promptPlaceholder.value = placeholder;
|
||||||
|
promptDefaultValue.value = defaultValue;
|
||||||
|
promptCallback = cb;
|
||||||
|
promptShow.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPromptConfirm(value: string) {
|
||||||
|
promptShow.value = false;
|
||||||
|
promptCallback?.(value);
|
||||||
|
promptCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPromptCancel() {
|
||||||
|
promptShow.value = false;
|
||||||
|
promptCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinWorkspacePath(name: string) {
|
||||||
|
const base = props.folderPath.replace(/[\\/]+$/, "");
|
||||||
|
return `${base}/${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateRootFolder() {
|
||||||
|
if (!props.folderPath) return;
|
||||||
|
openPrompt("新建文件夹", "", "输入文件夹名称", "", (name) => {
|
||||||
|
emit("createFolder", joinWorkspacePath(name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateRootFile() {
|
||||||
|
if (!props.folderPath) return;
|
||||||
|
openPrompt("新建 MD 文件", "", "输入文件名称(无需 .md 后缀)", "", (name) => {
|
||||||
|
emit("createFile", joinWorkspacePath(`${name}.md`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -568,10 +622,32 @@ function openSettings(page: SettingsPage) {
|
|||||||
<div v-show="!isCollapsed" class="directory-sidebar-panel flex-1 flex flex-col overflow-hidden">
|
<div v-show="!isCollapsed" class="directory-sidebar-panel flex-1 flex flex-col overflow-hidden">
|
||||||
<!-- ===== FOLDER VIEW ===== -->
|
<!-- ===== FOLDER VIEW ===== -->
|
||||||
<div v-if="activeView === 'folder'" class="flex flex-col flex-1 overflow-hidden">
|
<div v-if="activeView === 'folder'" class="flex flex-col flex-1 overflow-hidden">
|
||||||
|
<!-- Tree toolbar -->
|
||||||
|
<div
|
||||||
|
v-if="folderPath"
|
||||||
|
class="tree-toolbar flex items-center gap-1 px-3 py-1.5"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="tree-toolbar-btn"
|
||||||
|
type="button"
|
||||||
|
title="新建文件夹"
|
||||||
|
@click="handleCreateRootFolder"
|
||||||
|
>
|
||||||
|
<i class="ri-folder-add-line"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tree-toolbar-btn"
|
||||||
|
type="button"
|
||||||
|
title="新建 MD 文件"
|
||||||
|
@click="handleCreateRootFile"
|
||||||
|
>
|
||||||
|
<i class="ri-file-add-line"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<!-- File tree -->
|
<!-- File tree -->
|
||||||
<div
|
<div
|
||||||
class="flex-1 overflow-y-auto px-2 py-2 flex flex-col gap-0.5"
|
class="flex-1 overflow-y-auto px-2 py-2 flex flex-col gap-0.5"
|
||||||
|
@contextmenu.prevent
|
||||||
>
|
>
|
||||||
<TreeEntry
|
<TreeEntry
|
||||||
v-for="entry in entries"
|
v-for="entry in entries"
|
||||||
@@ -872,6 +948,16 @@ function openSettings(page: SettingsPage) {
|
|||||||
@update-app-theme="(theme: AppTheme) => emit('updateAppTheme', theme)"
|
@update-app-theme="(theme: AppTheme) => emit('updateAppTheme', theme)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PromptDialog
|
||||||
|
:show="promptShow"
|
||||||
|
:title="promptTitle"
|
||||||
|
:message="promptMessage"
|
||||||
|
:placeholder="promptPlaceholder"
|
||||||
|
:model-value="promptDefaultValue"
|
||||||
|
@confirm="onPromptConfirm"
|
||||||
|
@cancel="onPromptCancel"
|
||||||
|
/>
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div
|
<div
|
||||||
v-if="showWorkspaceManager"
|
v-if="showWorkspaceManager"
|
||||||
@@ -1079,6 +1165,31 @@ function openSettings(page: SettingsPage) {
|
|||||||
color: var(--app-accent);
|
color: var(--app-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tree-toolbar {
|
||||||
|
border-bottom: 1px solid var(--app-border);
|
||||||
|
background: color-mix(in srgb, var(--app-sidebar-bg) 88%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-toolbar-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--app-muted);
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-toolbar-btn:hover {
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-search-input {
|
.sidebar-search-input {
|
||||||
border: 1px solid var(--app-border-strong);
|
border: 1px solid var(--app-border-strong);
|
||||||
background: var(--app-hover);
|
background: var(--app-hover);
|
||||||
|
|||||||
@@ -7,14 +7,6 @@ export default { name: "TreeEntry" };
|
|||||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||||
import ConfirmDialog from "./ConfirmDialog.vue";
|
import ConfirmDialog from "./ConfirmDialog.vue";
|
||||||
import PromptDialog from "./PromptDialog.vue";
|
import PromptDialog from "./PromptDialog.vue";
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
|
|
||||||
interface DirEntry {
|
interface DirEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -305,40 +297,6 @@ function onContextMenu(event: MouseEvent) {
|
|||||||
></i>
|
></i>
|
||||||
<i class="tree-entry-icon ri-folder-2-line text-base shrink-0"></i>
|
<i class="tree-entry-icon ri-folder-2-line text-base shrink-0"></i>
|
||||||
<span class="truncate flex-1 min-w-0">{{ entry.name }}</span>
|
<span class="truncate flex-1 min-w-0">{{ entry.name }}</span>
|
||||||
|
|
||||||
<!-- More button + DropdownMenu -->
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
class="tree-more-btn shrink-0 ml-auto opacity-0 group-hover:opacity-100 transition-opacity focus-visible:opacity-100"
|
|
||||||
title="更多操作"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<i class="ri-more-line"></i>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent @click.stop>
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem @click="handleCreateFolder">
|
|
||||||
<i class="ri-folder-add-line"></i>
|
|
||||||
<span>新建文件夹</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem @click="handleCreateFile">
|
|
||||||
<i class="ri-file-add-line"></i>
|
|
||||||
<span>新建 MD 文件</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem @click="handleRename">
|
|
||||||
<i class="ri-edit-line"></i>
|
|
||||||
<span>重命名</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem variant="destructive" @click="handleDelete">
|
|
||||||
<i class="ri-delete-bin-line"></i>
|
|
||||||
<span>删除文件夹</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Children (recursive) -->
|
<!-- Children (recursive) -->
|
||||||
@@ -375,32 +333,6 @@ function onContextMenu(event: MouseEvent) {
|
|||||||
>
|
>
|
||||||
<i :class="['tree-entry-icon', fileIcon, 'text-sm shrink-0']"></i>
|
<i :class="['tree-entry-icon', fileIcon, 'text-sm shrink-0']"></i>
|
||||||
<span class="truncate flex-1 min-w-0">{{ entry.name }}</span>
|
<span class="truncate flex-1 min-w-0">{{ entry.name }}</span>
|
||||||
|
|
||||||
<!-- More button + DropdownMenu -->
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
class="tree-more-btn shrink-0 ml-auto opacity-0 group-hover:opacity-100 transition-opacity focus-visible:opacity-100"
|
|
||||||
title="更多操作"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<i class="ri-more-line"></i>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent @click.stop>
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem @click="handleRename">
|
|
||||||
<i class="ri-edit-line"></i>
|
|
||||||
<span>重命名</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem variant="destructive" @click="handleDelete">
|
|
||||||
<i class="ri-delete-bin-line"></i>
|
|
||||||
<span>删除</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -435,30 +367,6 @@ function onContextMenu(event: MouseEvent) {
|
|||||||
color: var(--app-accent);
|
color: var(--app-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── More button ────────────────────────────── */
|
|
||||||
.tree-more-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--app-muted);
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition:
|
|
||||||
background-color 0.12s ease,
|
|
||||||
color 0.12s ease;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-more-btn:hover {
|
|
||||||
background: var(--app-border);
|
|
||||||
color: var(--app-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tree-context-menu {
|
.tree-context-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
Reference in New Issue
Block a user