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
+86
-2
@@ -1,21 +1,94 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
import { noteTitle } from './paths'
|
||||
import { closeTab, ws } from './workspace'
|
||||
|
||||
// 拖动排序:拖到某个标签的左半边插到它前面,右半边插到它后面
|
||||
const TAB_TYPE = 'application/x-rawmark-tab'
|
||||
const dragging = ref<string | null>(null)
|
||||
const dropAt = ref<{ path: string; after: boolean } | null>(null)
|
||||
|
||||
function onDragStart(e: DragEvent, path: string) {
|
||||
dragging.value = path
|
||||
e.dataTransfer!.setData(TAB_TYPE, path)
|
||||
e.dataTransfer!.effectAllowed = 'move'
|
||||
}
|
||||
|
||||
function onDragOver(e: DragEvent, path: string) {
|
||||
if (!e.dataTransfer?.types.includes(TAB_TYPE)) return
|
||||
e.preventDefault()
|
||||
const r = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
||||
dropAt.value = { path, after: e.clientX > r.left + r.width / 2 }
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent) {
|
||||
const from = dragging.value
|
||||
const target = dropAt.value
|
||||
dragging.value = dropAt.value = null
|
||||
if (!from || !target || from === target.path) return
|
||||
e.preventDefault()
|
||||
const tab = ws.tabs.splice(
|
||||
ws.tabs.findIndex((t) => t.path === from),
|
||||
1,
|
||||
)[0]
|
||||
const i = ws.tabs.findIndex((t) => t.path === target.path) + (target.after ? 1 : 0)
|
||||
ws.tabs.splice(i, 0, tab)
|
||||
}
|
||||
|
||||
function onDragEnd() {
|
||||
dragging.value = dropAt.value = null
|
||||
}
|
||||
|
||||
// ---- 右键菜单 ----
|
||||
const menu = ref<{ path: string; x: number; y: number } | null>(null)
|
||||
|
||||
function openMenu(e: MouseEvent, path: string) {
|
||||
menu.value = { path, x: Math.min(e.clientX, innerWidth - 200), y: e.clientY }
|
||||
}
|
||||
|
||||
async function closeMany(keep: (index: number, path: string) => boolean) {
|
||||
const paths = ws.tabs.filter((t, i) => !keep(i, t.path)).map((t) => t.path)
|
||||
menu.value = null
|
||||
for (const p of paths) await closeTab(p)
|
||||
}
|
||||
|
||||
function closeOthers(path: string) {
|
||||
void closeMany((_, p) => p === path)
|
||||
}
|
||||
|
||||
function closeRight(path: string) {
|
||||
const at = ws.tabs.findIndex((t) => t.path === path)
|
||||
void closeMany((i) => i <= at)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="rm-tabs" role="tablist">
|
||||
<nav class="rm-tabs" :class="{ empty: !ws.tabs.length && !ws.sidebarCollapsed }" role="tablist">
|
||||
<button v-if="ws.sidebarCollapsed" class="rm-icon-btn rm-tabs-side" title="显示侧栏(Ctrl+\)" @click="ws.sidebarCollapsed = false">
|
||||
<Icon name="menu" />
|
||||
</button>
|
||||
<div
|
||||
v-for="tab in ws.tabs"
|
||||
:key="tab.path"
|
||||
class="rm-tab"
|
||||
:class="{ active: ws.active === tab.path }"
|
||||
:class="{
|
||||
active: ws.active === tab.path,
|
||||
dragging: dragging === tab.path,
|
||||
'drop-before': dropAt?.path === tab.path && !dropAt.after,
|
||||
'drop-after': dropAt?.path === tab.path && dropAt.after,
|
||||
}"
|
||||
role="tab"
|
||||
:aria-selected="ws.active === tab.path"
|
||||
:title="tab.path"
|
||||
draggable="true"
|
||||
@click="ws.active = tab.path"
|
||||
@auxclick.prevent="$event.button === 1 && closeTab(tab.path)"
|
||||
@contextmenu.prevent="openMenu($event, tab.path)"
|
||||
@dragstart="onDragStart($event, tab.path)"
|
||||
@dragover="onDragOver($event, tab.path)"
|
||||
@drop="onDrop"
|
||||
@dragend="onDragEnd"
|
||||
>
|
||||
<span class="rm-tab-title">{{ noteTitle(tab.path) }}</span>
|
||||
<button class="rm-tab-close" :class="{ dirty: tab.dirty }" title="关闭" @click.stop="closeTab(tab.path)">
|
||||
@@ -23,5 +96,16 @@ import { closeTab, ws } from './workspace'
|
||||
<Icon name="close" :size="12" />
|
||||
</button>
|
||||
</div>
|
||||
<button v-if="ws.active" class="rm-icon-btn rm-tabs-outline" :class="{ on: ws.outlineOpen }" title="大纲(Ctrl+Shift+O)" @click="ws.outlineOpen = !ws.outlineOpen">
|
||||
<Icon name="outline" />
|
||||
</button>
|
||||
<template v-if="menu">
|
||||
<div class="rm-menu-backdrop" @click="menu = null" @contextmenu.prevent="menu = null" />
|
||||
<div class="rm-menu rm-tab-menu" :style="{ left: menu.x + 'px', top: menu.y + 'px' }">
|
||||
<button @click="closeMany((_, p) => p !== menu!.path)">关闭</button>
|
||||
<button @click="closeOthers(menu.path)">关闭其他标签页</button>
|
||||
<button @click="closeRight(menu.path)">关闭右侧标签页</button>
|
||||
</div>
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
||||
Reference in new issue
Block a user