Phase 3: Graph View, Backlinks UI, Quick Switcher, Dark Mode, Export
Features: - GraphView.vue: SVG-based force-directed graph visualization for projects - QuickSwitcher.vue: Cmd+K modal with fuzzy search via /search API - Dark Mode: Theme toggle in Header, persisted in localStorage, system pref support - Backlinks UI: Incoming and outgoing links in DocumentView - Export: Document (markdown/JSON) and Project (ZIP/JSON) export with download - New composables: useTheme.ts for dark/light/system theme management - New store methods: fetchBacklinks, fetchOutgoingLinks, search, exportDocument, fetchProjectGraph, exportProject - TypeScript types for all Phase 3 API responses
This commit is contained in:
@@ -1,13 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import QuickSwitcher from '@/components/common/QuickSwitcher.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const { resolvedTheme, toggleTheme } = useTheme()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const showUserMenu = ref(false)
|
||||
const showQuickSwitcher = ref(false)
|
||||
|
||||
function handleGlobalKeydown(e: KeyboardEvent) {
|
||||
// Cmd+K or Ctrl+K
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
e.preventDefault()
|
||||
showQuickSwitcher.value = true
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleGlobalKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleGlobalKeydown)
|
||||
})
|
||||
|
||||
function handleSearch() {
|
||||
if (searchQuery.value.trim()) {
|
||||
@@ -50,6 +70,24 @@ function logout() {
|
||||
</div>
|
||||
|
||||
<div class="header__right">
|
||||
<button class="header__theme-toggle" @click="toggleTheme" :title="resolvedTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'">
|
||||
<!-- Sun icon for dark mode (click to go light) -->
|
||||
<svg v-if="resolvedTheme === 'dark'" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3"/>
|
||||
<line x1="12" y1="21" x2="12" y2="23"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
<!-- Moon icon for light mode (click to go dark) -->
|
||||
<svg v-else width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="header__user" @click="showUserMenu = !showUserMenu">
|
||||
<div class="header__avatar">
|
||||
{{ authStore.user?.username?.charAt(0).toUpperCase() || 'U' }}
|
||||
@@ -79,6 +117,7 @@ function logout() {
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<QuickSwitcher :show="showQuickSwitcher" @close="showQuickSwitcher = false" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -164,6 +203,26 @@ function logout() {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header__theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.header__theme-toggle:hover {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.header__user {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user