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
This commit is contained in:
1 parent
91da6e116e
commit
64915a7b94
7 files changed
+322
-15
No files matched your search
@@ -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(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rm-shell" :class="{ 'sidebar-open': ws.sidebarOpen }" @touchstart.passive="onTouchStart" @touchmove.passive="onTouchMove">
|
||||
<aside class="rm-sidebar"><Sidebar @logout="logout" /></aside>
|
||||
<div
|
||||
class="rm-shell"
|
||||
:class="{ 'sidebar-open': ws.sidebarOpen, 'sidebar-collapsed': ws.sidebarCollapsed }"
|
||||
@touchstart.passive="onTouchStart"
|
||||
@touchmove.passive="onTouchMove"
|
||||
>
|
||||
<aside class="rm-sidebar">
|
||||
<Sidebar @logout="logout" />
|
||||
<div class="rm-sidebar-resize" title="拖动调整宽度" @pointerdown.prevent="startResize" />
|
||||
</aside>
|
||||
<div class="rm-backdrop" @click="ws.sidebarOpen = false" />
|
||||
<main class="rm-main">
|
||||
<header class="rm-mobile-bar">
|
||||
<button class="rm-icon-btn" title="目录" @click="ws.sidebarOpen = true"><Icon name="menu" :size="20" /></button>
|
||||
<span class="rm-mobile-title">{{ title }}</span>
|
||||
<button class="rm-mobile-title" title="切换笔记" @click="ws.switcher = ''">{{ title }}</button>
|
||||
<span class="rm-save-state" :class="{ on: saving }" />
|
||||
<button v-if="ws.active" class="rm-icon-btn" title="大纲" @click="ws.outlineOpen = !ws.outlineOpen"><Icon name="outline" :size="20" /></button>
|
||||
<button class="rm-icon-btn" title="快速打开" @click="ws.switcher = ''"><Icon name="search" :size="20" /></button>
|
||||
<button class="rm-icon-btn" title="命令" @click="ws.switcher = '>'"><Icon name="command" :size="20" /></button>
|
||||
</header>
|
||||
@@ -69,6 +112,7 @@ onBeforeUnmount(() => {
|
||||
<EditorPane />
|
||||
<MobileToolbar />
|
||||
</main>
|
||||
<OutlinePanel v-if="ws.outlineOpen && ws.active" />
|
||||
<QuickSwitcher v-if="ws.switcher !== null" />
|
||||
<SettingsDialog v-if="ws.settings" />
|
||||
<ShareDialog v-if="ws.sharing" :path="ws.sharing" @close="ws.sharing = null" />
|
||||
|
||||
Reference in new issue
Block a user