Files
RawMark/web/src/app/Sidebar.vue
T

77 lines
4.2 KiB
Vue

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import Icon from './Icon.vue'
import { basename } from './paths'
import { commandTitle } from './commands'
import { tree } from './tree'
import TreeNode from './TreeNode.vue'
import SearchPanel from './SearchPanel.vue'
import { createFolder, createNote, move, openSearch, ws } from './workspace'
import { sidebarViews } from '../plugins/registry'
import PluginMount from './PluginMount.vue'
const emit = defineEmits<{ logout: [] }>()
const rootDrop = ref(false)
const rootMenu = ref<{ x: number; y: number } | null>(null)
function rootContext(event: MouseEvent) {
if (event.target instanceof Element && event.target.closest('.rm-tree-row')) return
event.preventDefault()
rootMenu.value = { x: Math.max(4, Math.min(event.clientX, innerWidth - 170)), y: Math.max(4, Math.min(event.clientY, innerHeight - 50)) }
}
const selectedView = ref('')
const currentView = computed(() => sidebarViews.get(selectedView.value))
watch(() => ws.searchOpen, () => { selectedView.value = '' })
function onRootDragOver(e: DragEvent) {
if (!e.dataTransfer?.types.includes('application/x-rawmark-path')) return
e.preventDefault()
rootDrop.value = true
}
function onRootDrop(e: DragEvent) {
rootDrop.value = false
const src = e.dataTransfer?.getData('application/x-rawmark-path')
if (!src || !src.includes('/')) return
e.preventDefault()
void move(src, basename(src))
}
</script>
<template>
<div class="rm-sidebar-inner">
<div class="rm-vault-heading">
<span class="rm-vault-icon"><Icon name="folder" :size="22" /></span>
<div><strong>个人知识库 · Vault</strong><small>Personal Knowledge Vault</small></div>
<button class="rm-icon-btn" title="折叠目录" @click="ws.sidebarCollapsed = true; ws.sidebarOpen = false"><Icon name="menu" /></button>
</div>
<div class="rm-sidebar-head">
<button class="rm-icon-btn" :class="{ on: ws.searchOpen }" :title="commandTitle('app.search')" @click="ws.searchOpen ? (ws.searchOpen = false) : openSearch()"><Icon name="search" /><span>搜索</span></button>
<button class="rm-icon-btn" title="新建笔记" @click="createNote('')"><Icon name="noteAdd" /><span>新建笔记</span></button>
<button class="rm-icon-btn" title="新建文件夹" @click="createFolder('')"><Icon name="folderAdd" /><span>新建目录</span></button>
<button class="rm-icon-btn" title="导入" @click="ws.importing = ''"><Icon name="import" /><span>导入文档</span></button>
</div>
<div class="rm-tree-label">文件目录 <span>· VAULT EXPLORER</span></div>
<nav v-if="sidebarViews.size" class="rm-plugin-tabs" aria-label="侧栏视图">
<button class="rm-btn" :class="{ on: !currentView }" @click="selectedView = ''">文件</button>
<button v-for="[id, pluginView] in sidebarViews" :key="id" class="rm-btn" :class="{ on: selectedView === id }" @click="selectedView = id">{{ pluginView.title }}</button>
</nav>
<PluginMount v-if="currentView" :key="selectedView" :view="currentView" />
<SearchPanel v-else-if="ws.searchOpen" />
<div v-else class="rm-tree" @contextmenu="rootContext" :class="{ drop: rootDrop }" @dragover="onRootDragOver" @dragleave="rootDrop = false" @drop="onRootDrop">
<ul class="rm-tree-list">
<TreeNode v-for="node in tree" :key="node.path" :node="node" :depth="0" />
</ul>
<p v-if="!tree.length" class="rm-tree-empty">还没有笔记</p>
</div>
<template v-if="rootMenu">
<div class="rm-menu-backdrop" @click="rootMenu = null" @contextmenu.prevent="rootMenu = null" />
<div class="rm-menu rm-context-menu" :style="{ left: rootMenu.x + 'px', top: rootMenu.y + 'px' }"><button @click="ws.images = ''; rootMenu = null"><Icon name="image" />图片管理</button></div>
</template>
<div class="rm-vault-status"><span class="rm-avatar">RM</span><div><strong>个人工作区</strong><small>Markdown 文件 · 自动保存</small></div></div>
<div class="rm-sidebar-foot">
<button class="rm-foot-btn" :title="commandTitle('app.settings')" @click="ws.settings = 'index'"><Icon name="settings" />设置</button>
<button class="rm-foot-btn" @click="emit('logout')"><Icon name="logout" />退出</button>
</div>
</div>
</template>