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

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-095503_bf879b65e4666516": "帮我移除markdownEditor…",
"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 ----
type ViewMode = "folder" | "search" | "setting";
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) {
if (activeView.value === view) {
expanded.value = !expanded.value;
toggleCollapse();
} else {
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 ----
const workspaceSwitcherRef = ref<HTMLElement | null>(null);
@@ -240,6 +299,10 @@ watch(() => props.folderPath, () => {
onBeforeUnmount(() => {
document.removeEventListener("click", onDocumentClick);
document.removeEventListener("mousemove", onResize);
document.removeEventListener("mouseup", stopResize);
document.body.style.cursor = "";
document.body.style.userSelect = "";
});
// ---- settings modal ----
@@ -259,7 +322,9 @@ function openSettings(page: SettingsPage) {
<template>
<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 ============ -->
<div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3">
@@ -293,7 +358,7 @@ function openSettings(page: SettingsPage) {
</div>
<!-- ============ 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 ===== -->
<div v-if="activeView === 'folder'" class="flex flex-col flex-1 overflow-hidden">
<!-- File tree -->
@@ -413,6 +478,16 @@ function openSettings(page: SettingsPage) {
</button>
</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>
<SettingsModal
@@ -563,6 +638,7 @@ function openSettings(page: SettingsPage) {
<style scoped>
.directory-sidebar {
position: relative;
border-right: 1px solid var(--app-border);
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);
}
/* ---- 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 {
color: var(--app-subtle);
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed } from "vue";
import { ref, computed, onBeforeUnmount } from "vue";
const props = defineProps<{
markdownContent: string;
@@ -42,11 +42,56 @@ const headings = computed<OutlineItem[]>(() => {
const onHeadingClick = (item: OutlineItem) => {
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>
<template>
<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 -->
<div class="outline-panel-header flex items-center px-3 py-3">
@@ -81,15 +126,58 @@ const onHeadingClick = (item: OutlineItem) => {
</div>
</template>
</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>
</template>
<style scoped>
.outline-panel {
position: relative;
border-right: 1px solid var(--app-border);
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 {
border-bottom: 1px solid var(--app-border);
}