Initial commit: Phase 1+2 prototype
- 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
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user