feat(web): add Vue workspace with file tree, tabs, autosave and live sync
- Login and first-run setup screens - File tree with inline create/rename, context menu, drag-and-drop move and trash with undo; all operations update the UI first and roll back on failure - Tabs keep a CodeMirror state each; edits autosave after 500 ms with If-Match revisions, unsent edits persist in IndexedDB, and conflicts or remote deletions surface as banners with explicit choices - WebSocket events sync external edits, moves and deletions into open tabs; reconnects trigger a full resync - Pasted or dropped images show instantly and upload to note.assets/ - Responsive layout with a swipe-in sidebar drawer on phones - Switch to TypeScript 6: vue-tsc does not support TypeScript 7 yet
This commit is contained in:
1 parent
c4d2bd4636
commit
02d904056c
28 files changed
+2231
-398
No files matched your search
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
import { basename } from './paths'
|
||||
import TrashDialog from './TrashDialog.vue'
|
||||
import { tree } from './tree'
|
||||
import TreeNode from './TreeNode.vue'
|
||||
import { createFolder, createNote, move } from './workspace'
|
||||
|
||||
const emit = defineEmits<{ logout: [] }>()
|
||||
const trashOpen = ref(false)
|
||||
const rootDrop = ref(false)
|
||||
|
||||
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-sidebar-head">
|
||||
<span class="rm-brand">RawMark</span>
|
||||
<button class="rm-icon-btn" title="新建笔记" @click="createNote('')"><Icon name="noteAdd" /></button>
|
||||
<button class="rm-icon-btn" title="新建文件夹" @click="createFolder('')"><Icon name="folderAdd" /></button>
|
||||
</div>
|
||||
<div class="rm-tree" :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>
|
||||
<div class="rm-sidebar-foot">
|
||||
<button class="rm-foot-btn" @click="trashOpen = true"><Icon name="trash" />回收站</button>
|
||||
<button class="rm-foot-btn" @click="emit('logout')"><Icon name="logout" />退出</button>
|
||||
</div>
|
||||
<TrashDialog v-if="trashOpen" @close="trashOpen = false" />
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user