From 64915a7b947aee1b643d7900795650a4987ef734 Mon Sep 17 00:00:00 2001 From: eezd Date: Sat, 17 Oct 2026 17:44:05 +0800 Subject: [PATCH] Add tab reordering, sidebar resize and collapse, and outline panel - Drag tabs to reorder; context menu to close, close others, close right - Resizable sidebar width (synced setting), collapsible with Mod+\ - Outline panel (Mod+Shift+O): right column on desktop, overlay on mobile - Mobile: title opens quick switcher, swipe left closes the drawer, 44px touch targets --- web/src/app/Icon.vue | 1 + web/src/app/OutlinePanel.vue | 107 +++++++++++++++++++++++++++++++++++ web/src/app/TabBar.vue | 88 +++++++++++++++++++++++++++- web/src/app/Workspace.vue | 54 ++++++++++++++++-- web/src/app/app.css | 41 ++++++++++++-- web/src/app/settings.ts | 9 ++- web/src/app/workspace.ts | 37 +++++++++++- 7 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 web/src/app/OutlinePanel.vue diff --git a/web/src/app/Icon.vue b/web/src/app/Icon.vue index 0b0240c..35676e1 100644 --- a/web/src/app/Icon.vue +++ b/web/src/app/Icon.vue @@ -19,6 +19,7 @@ const paths = { settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z', palette: 'M12 22a10 10 0 1 1 10-10c0 2.8-2.2 4-4 4h-2a2 2 0 0 0-1.5 3.3A1.7 1.7 0 0 1 12 22zM7.5 10.5h.01M12 7.5h.01M16.5 10.5h.01', user: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z', + outline: 'M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01', command: 'M18 3a3 3 0 0 0-3 3v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 0 0 0-6z', } export type IconName = keyof typeof paths diff --git a/web/src/app/OutlinePanel.vue b/web/src/app/OutlinePanel.vue new file mode 100644 index 0000000..cd0acff --- /dev/null +++ b/web/src/app/OutlinePanel.vue @@ -0,0 +1,107 @@ + + + diff --git a/web/src/app/TabBar.vue b/web/src/app/TabBar.vue index 3c6f1d0..ef3d33e 100644 --- a/web/src/app/TabBar.vue +++ b/web/src/app/TabBar.vue @@ -1,21 +1,94 @@ diff --git a/web/src/app/Workspace.vue b/web/src/app/Workspace.vue index 8d127c1..d0500ac 100644 --- a/web/src/app/Workspace.vue +++ b/web/src/app/Workspace.vue @@ -7,10 +7,12 @@ import Icon from './Icon.vue' import MobileToolbar from './MobileToolbar.vue' import MoveDialog from './MoveDialog.vue' import NoteBanner from './NoteBanner.vue' +import OutlinePanel from './OutlinePanel.vue' import QuickSwitcher from './QuickSwitcher.vue' import SettingsDialog from './SettingsDialog.vue' import ShareDialog from './ShareDialog.vue' import { noteTitle } from './paths' +import { SIDEBAR_WIDTH, settings, updateSettings } from './settings' import Sidebar from './Sidebar.vue' import TabBar from './TabBar.vue' import { activeTab, flushAll, startWorkspace, stopWorkspace, toast, ws } from './workspace' @@ -19,6 +21,26 @@ const emit = defineEmits<{ logout: [] }>() const title = computed(() => (ws.active ? noteTitle(ws.active) : 'RawMark')) const saving = computed(() => activeTab()?.dirty) +// 拖动侧栏右缘调整宽度:拖动中只改 CSS 变量,松手后保存到设置 +function startResize(e: PointerEvent) { + const handle = e.currentTarget as HTMLElement + handle.setPointerCapture(e.pointerId) + const startX = e.clientX + const startWidth = settings.sidebarWidth + let width = startWidth + const onMove = (ev: PointerEvent) => { + width = Math.round(Math.min(SIDEBAR_WIDTH.max, Math.max(SIDEBAR_WIDTH.min, startWidth + ev.clientX - startX))) + document.documentElement.style.setProperty('--rm-sidebar-width', width + 'px') + } + const onUp = () => { + handle.removeEventListener('pointermove', onMove) + handle.removeEventListener('pointerup', onUp) + if (width !== startWidth) updateSettings({ sidebarWidth: width }).catch(() => toast('保存侧栏宽度失败')) + } + handle.addEventListener('pointermove', onMove) + handle.addEventListener('pointerup', onUp) +} + async function logout() { await flushAll() try { @@ -33,14 +55,26 @@ async function logout() { // 移动端:从屏幕左边缘右滑打开侧栏 let touchX: number | null = null +// 侧栏打开时向左滑关闭(以水平方向为主,避免与列表上下滚动冲突) +let touchStart: { x: number; y: number } | null = null function onTouchStart(e: TouchEvent) { - touchX = e.touches[0].clientX < 24 ? e.touches[0].clientX : null + const t = e.touches[0] + touchX = t.clientX < 24 ? t.clientX : null + touchStart = { x: t.clientX, y: t.clientY } } function onTouchMove(e: TouchEvent) { - if (touchX !== null && e.touches[0].clientX - touchX > 60) { + const t = e.touches[0] + if (touchX !== null && t.clientX - touchX > 60) { ws.sidebarOpen = true touchX = null } + if (ws.sidebarOpen && touchStart) { + const dx = t.clientX - touchStart.x + if (dx < -60 && Math.abs(t.clientY - touchStart.y) < Math.abs(dx) / 2) { + ws.sidebarOpen = false + touchStart = null + } + } } const unregisterLogout = registerCommand({ id: 'app.logout', title: '退出登录', run: () => void logout() }) @@ -53,14 +87,23 @@ onBeforeUnmount(() => {