0c1047d390
- Go backend with SQLite, JWT auth, file CRUD - Vue 3 frontend with split/raw/WYSIWYG editor modes - Markdown preview (marked, GFM) - Formatting toolbar + keyboard shortcuts - File tree with search, create, delete - Light/dark theme toggle - Admin panel (user management) - Preferences (timezone, theme, default mode) - Shared documents section (placeholder) - Export: PDF, HTML, MD - Build daemon (Python, stdlib only) - Build job queue API - Docker deployment
68 lines
1.9 KiB
Vue
68 lines
1.9 KiB
Vue
<template>
|
||
<div class="file-tree">
|
||
<div v-for="item in files" :key="item.path" class="tree-item">
|
||
<div
|
||
class="tree-node"
|
||
:class="{ selected: item.path === selected, folder: item.isDir }"
|
||
@click="item.isDir ? toggleFolder(item) : $emit('select', item.path)"
|
||
@contextmenu.prevent="showContext($event, item)"
|
||
>
|
||
<span class="icon">{{ item.isDir ? (expanded[item.path] ? '📂' : '📁') : '📄' }}</span>
|
||
<span class="name">{{ item.name }}</span>
|
||
<button class="delete-btn" @click.stop="$emit('delete', item)" title="Delete">×</button>
|
||
</div>
|
||
<div v-if="item.isDir && expanded[item.path] && item.children" class="children">
|
||
<FileTree :files="item.children" :selected="selected" @select="$emit('select', $event)" @delete="$emit('delete', $event)" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive } from 'vue'
|
||
|
||
defineProps({
|
||
files: { type: Array, default: () => [] },
|
||
selected: { type: String, default: '' },
|
||
})
|
||
|
||
defineEmits(['select', 'delete'])
|
||
|
||
const expanded = reactive({})
|
||
|
||
function toggleFolder(item) {
|
||
expanded[item.path] = !expanded[item.path]
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.file-tree { padding: 8px; overflow-y: auto; flex: 1; }
|
||
.tree-item { user-select: none; }
|
||
.tree-node {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
position: relative;
|
||
}
|
||
.tree-node:hover { background: var(--hover-bg, #313244); }
|
||
.tree-node.selected { background: var(--selected-bg, #45475a); }
|
||
.icon { font-size: 14px; }
|
||
.name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.delete-btn {
|
||
display: none;
|
||
background: none;
|
||
border: none;
|
||
color: #f38ba8;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
padding: 0 4px;
|
||
line-height: 1;
|
||
}
|
||
.tree-node:hover .delete-btn { display: block; }
|
||
.children { padding-left: 16px; }
|
||
</style>
|