添加目录树和大纲宽度调整功能

This commit is contained in:
2026-07-08 17:54:02 +08:00
parent a719b70156
commit 848cc7a385
3 changed files with 205 additions and 8 deletions

View File

@@ -37,5 +37,5 @@
"topic_20260707-092711_af706c3fcc7e435e": "帮我检查一下app.vue里的too…", "topic_20260707-092711_af706c3fcc7e435e": "帮我检查一下app.vue里的too…",
"topic_20260707-095503_bf879b65e4666516": "帮我移除markdownEditor…", "topic_20260707-095503_bf879b65e4666516": "帮我移除markdownEditor…",
"topic_20260708-020616_9ab54d98daf65053": "帮我分析一下现在的markdownE…", "topic_20260708-020616_9ab54d98daf65053": "帮我分析一下现在的markdownE…",
"topic_20260708-093419_71d1fea2e4b5a40f": "新的会话" "topic_20260708-093419_71d1fea2e4b5a40f": "src/components/Dir…"
} }

View File

@@ -67,16 +67,75 @@ const rootEntry = computed<DirEntry | null>(() => {
// ---- sidebar view mode ---- // ---- sidebar view mode ----
type ViewMode = "folder" | "search" | "setting"; type ViewMode = "folder" | "search" | "setting";
const activeView = ref<ViewMode>("folder"); const activeView = ref<ViewMode>("folder");
const expanded = ref(true);
// ---- sidebar resize ----
const MIN_SIDEBAR_WIDTH = 180;
const MAX_SIDEBAR_WIDTH = 500;
const COLLAPSED_WIDTH = 48;
const DEFAULT_EXPANDED_WIDTH = 288;
const sidebarWidth = ref(DEFAULT_EXPANDED_WIDTH);
const lastExpandedWidth = ref(DEFAULT_EXPANDED_WIDTH);
const isResizing = ref(false);
const isCollapsed = computed(() => sidebarWidth.value <= COLLAPSED_WIDTH);
function expand() {
sidebarWidth.value = lastExpandedWidth.value;
}
function collapse() {
sidebarWidth.value = COLLAPSED_WIDTH;
}
function toggleCollapse() {
if (isCollapsed.value) {
expand();
} else {
collapse();
}
}
function switchView(view: ViewMode) { function switchView(view: ViewMode) {
if (activeView.value === view) { if (activeView.value === view) {
expanded.value = !expanded.value; toggleCollapse();
} else { } else {
activeView.value = view; activeView.value = view;
expanded.value = true; if (isCollapsed.value) {
expand();
} }
} }
}
// ---- resize handle drag ----
let resizeStartX = 0;
let resizeStartWidth = 0;
function startResize(e: MouseEvent) {
resizeStartX = e.clientX;
resizeStartWidth = sidebarWidth.value;
isResizing.value = true;
document.addEventListener("mousemove", onResize);
document.addEventListener("mouseup", stopResize);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
e.preventDefault();
}
function onResize(e: MouseEvent) {
if (!isResizing.value) return;
const delta = e.clientX - resizeStartX;
const newWidth = Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, resizeStartWidth + delta));
sidebarWidth.value = newWidth;
lastExpandedWidth.value = newWidth;
}
function stopResize() {
isResizing.value = false;
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
document.body.style.cursor = "";
document.body.style.userSelect = "";
}
// ---- workspace switcher ---- // ---- workspace switcher ----
const workspaceSwitcherRef = ref<HTMLElement | null>(null); const workspaceSwitcherRef = ref<HTMLElement | null>(null);
@@ -240,6 +299,10 @@ watch(() => props.folderPath, () => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
document.removeEventListener("click", onDocumentClick); document.removeEventListener("click", onDocumentClick);
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
document.body.style.cursor = "";
document.body.style.userSelect = "";
}); });
// ---- settings modal ---- // ---- settings modal ----
@@ -259,7 +322,9 @@ function openSettings(page: SettingsPage) {
<template> <template>
<aside <aside
:class="['directory-sidebar flex flex-row transition-all duration-300 ease-in-out overflow-hidden shrink-0', expanded ? 'w-72' : 'w-12']" class="directory-sidebar flex flex-row overflow-hidden shrink-0"
:class="{ 'transition-all duration-300 ease-in-out': !isResizing }"
:style="{ width: sidebarWidth + 'px' }"
> >
<!-- ============ Icon bar ============ --> <!-- ============ Icon bar ============ -->
<div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3"> <div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3">
@@ -293,7 +358,7 @@ function openSettings(page: SettingsPage) {
</div> </div>
<!-- ============ Expanded panel ============ --> <!-- ============ Expanded panel ============ -->
<div v-show="expanded" 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">
<!-- File tree --> <!-- File tree -->
@@ -413,6 +478,16 @@ function openSettings(page: SettingsPage) {
</button> </button>
</div> </div>
</div> </div>
<!-- ============ Resize handle ============ -->
<div
v-show="!isCollapsed"
class="sidebar-resize-handle"
:class="{ 'sidebar-resize-handle-active': isResizing }"
@mousedown="startResize"
>
<div class="sidebar-resize-handle-bar"></div>
</div>
</aside> </aside>
<SettingsModal <SettingsModal
@@ -563,6 +638,7 @@ function openSettings(page: SettingsPage) {
<style scoped> <style scoped>
.directory-sidebar { .directory-sidebar {
position: relative;
border-right: 1px solid var(--app-border); border-right: 1px solid var(--app-border);
background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent); background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent);
} }
@@ -571,6 +647,39 @@ function openSettings(page: SettingsPage) {
border-left: 1px solid var(--app-border); border-left: 1px solid var(--app-border);
} }
/* ---- Resize handle ---- */
.sidebar-resize-handle {
position: absolute;
top: 0;
right: -2px;
bottom: 0;
z-index: 25;
display: flex;
width: 7px;
align-items: center;
justify-content: center;
cursor: col-resize;
transition: background-color 0.15s ease;
}
.sidebar-resize-handle:hover,
.sidebar-resize-handle-active {
background: color-mix(in srgb, var(--app-accent) 18%, transparent);
}
.sidebar-resize-handle-bar {
width: 1px;
height: 100%;
border-radius: 1px;
background: transparent;
transition: background-color 0.15s ease;
}
.sidebar-resize-handle:hover .sidebar-resize-handle-bar,
.sidebar-resize-handle-active .sidebar-resize-handle-bar {
background: color-mix(in srgb, var(--app-accent) 36%, transparent);
}
.sidebar-icon-btn { .sidebar-icon-btn {
color: var(--app-subtle); color: var(--app-subtle);
} }

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue"; import { ref, computed, onBeforeUnmount } from "vue";
const props = defineProps<{ const props = defineProps<{
markdownContent: string; markdownContent: string;
@@ -42,11 +42,56 @@ const headings = computed<OutlineItem[]>(() => {
const onHeadingClick = (item: OutlineItem) => { const onHeadingClick = (item: OutlineItem) => {
emit("headingClick", { text: item.text, level: item.level }); emit("headingClick", { text: item.text, level: item.level });
}; };
// ---- panel resize ----
const MIN_PANEL_WIDTH = 150;
const MAX_PANEL_WIDTH = 500;
const DEFAULT_PANEL_WIDTH = 208; // w-52
const panelWidth = ref(DEFAULT_PANEL_WIDTH);
const isResizing = ref(false);
let resizeStartX = 0;
let resizeStartWidth = 0;
function startResize(e: MouseEvent) {
resizeStartX = e.clientX;
resizeStartWidth = panelWidth.value;
isResizing.value = true;
document.addEventListener("mousemove", onResize);
document.addEventListener("mouseup", stopResize);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
e.preventDefault();
}
function onResize(e: MouseEvent) {
if (!isResizing.value) return;
const delta = e.clientX - resizeStartX;
panelWidth.value = Math.min(MAX_PANEL_WIDTH, Math.max(MIN_PANEL_WIDTH, resizeStartWidth + delta));
}
function stopResize() {
isResizing.value = false;
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
document.body.style.cursor = "";
document.body.style.userSelect = "";
}
onBeforeUnmount(() => {
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
document.body.style.cursor = "";
document.body.style.userSelect = "";
});
</script> </script>
<template> <template>
<aside <aside
class="outline-panel flex flex-col w-52 shrink-0 overflow-hidden" class="outline-panel flex flex-col shrink-0 overflow-hidden"
:class="{ 'transition-all duration-300 ease-in-out': !isResizing }"
:style="{ width: panelWidth + 'px' }"
> >
<!-- Header --> <!-- Header -->
<div class="outline-panel-header flex items-center px-3 py-3"> <div class="outline-panel-header flex items-center px-3 py-3">
@@ -81,15 +126,58 @@ const onHeadingClick = (item: OutlineItem) => {
</div> </div>
</template> </template>
</div> </div>
<!-- Resize handle -->
<div
class="outline-resize-handle"
:class="{ 'outline-resize-handle-active': isResizing }"
@mousedown="startResize"
>
<div class="outline-resize-handle-bar"></div>
</div>
</aside> </aside>
</template> </template>
<style scoped> <style scoped>
.outline-panel { .outline-panel {
position: relative;
border-right: 1px solid var(--app-border); border-right: 1px solid var(--app-border);
background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent); background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent);
} }
/* ---- Resize handle ---- */
.outline-resize-handle {
position: absolute;
top: 0;
right: -2px;
bottom: 0;
z-index: 25;
display: flex;
width: 7px;
align-items: center;
justify-content: center;
cursor: col-resize;
transition: background-color 0.15s ease;
}
.outline-resize-handle:hover,
.outline-resize-handle-active {
background: color-mix(in srgb, var(--app-accent) 18%, transparent);
}
.outline-resize-handle-bar {
width: 1px;
height: 100%;
border-radius: 1px;
background: transparent;
transition: background-color 0.15s ease;
}
.outline-resize-handle:hover .outline-resize-handle-bar,
.outline-resize-handle-active .outline-resize-handle-bar {
background: color-mix(in srgb, var(--app-accent) 36%, transparent);
}
.outline-panel-header { .outline-panel-header {
border-bottom: 1px solid var(--app-border); border-bottom: 1px solid var(--app-border);
} }