All checks were successful
Deploy / deploy (push) Successful in 9s
Shows v:dev locally and v:<commit-hash> in deployed builds. Variable passed as build arg through Docker Compose. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
118 lines
3.3 KiB
Vue
118 lines
3.3 KiB
Vue
<script setup>
|
|
import { ref, computed, watch } from 'vue'
|
|
import { useAuth } from './composables/useAuth.js'
|
|
import { getBookmarks, triggerCategorize } from './api.js'
|
|
import { sections } from './sections.js'
|
|
import BookmarkForm from './components/BookmarkForm.vue'
|
|
import BookmarkList from './components/BookmarkList.vue'
|
|
|
|
const { user, loading, loginWithGoogle, logout } = useAuth()
|
|
|
|
const bookmarks = ref([])
|
|
const activeIndex = ref(4) // N = default (primer plano)
|
|
const categorizing = ref(false)
|
|
const status = ref('')
|
|
|
|
const currentSection = computed(() => sections[activeIndex.value])
|
|
|
|
const filtered = computed(() => {
|
|
const letter = currentSection.value.letter.toLowerCase()
|
|
return bookmarks.value.filter(b => {
|
|
if (!b.category) return letter === 'n'
|
|
return b.category.charAt(0).toLowerCase() === letter
|
|
})
|
|
})
|
|
|
|
const year = new Date().getFullYear()
|
|
const version = import.meta.env.VITE_DEPLOYED_VERSION || 'dev'
|
|
|
|
async function load() {
|
|
if (!user.value) return
|
|
bookmarks.value = await getBookmarks()
|
|
}
|
|
|
|
function onCreated(bookmark) {
|
|
bookmarks.value.unshift(bookmark)
|
|
}
|
|
|
|
function onDeleted(id) {
|
|
bookmarks.value = bookmarks.value.filter(b => b.id !== id)
|
|
}
|
|
|
|
async function categorize() {
|
|
categorizing.value = true
|
|
status.value = ''
|
|
try {
|
|
const res = await triggerCategorize()
|
|
status.value = `${res.categorized} categorized`
|
|
await load()
|
|
} catch (e) {
|
|
status.value = `Error: ${e.message}`
|
|
} finally {
|
|
categorizing.value = false
|
|
}
|
|
}
|
|
|
|
watch(user, (u) => { if (u) load() })
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="loading-screen">loading...</div>
|
|
|
|
<!-- Login -->
|
|
<div v-else-if="!user" class="login-screen">
|
|
<div class="login-title">ramon</div>
|
|
<div class="login-subtitle">personal bookmarks</div>
|
|
<button class="btn-google" @click="loginWithGoogle">Sign in with Google</button>
|
|
</div>
|
|
|
|
<!-- App -->
|
|
<div v-else class="layout">
|
|
<div class="panel-left">
|
|
<div class="header-bar">
|
|
<span class="user-email">{{ user.email }}</span>
|
|
<button class="btn-logout" @click="logout">logout</button>
|
|
</div>
|
|
|
|
<div class="section-subtitle">{{ currentSection.subtitle }}</div>
|
|
<div class="section-title">{{ currentSection.title }}</div>
|
|
|
|
<div class="section-divider"></div>
|
|
|
|
<div class="section-meta">
|
|
<span class="section-year">{{ year }}</span>
|
|
<span class="section-count">{{ filtered.length }} links</span>
|
|
</div>
|
|
|
|
<div class="section-description">{{ currentSection.description }}</div>
|
|
|
|
<BookmarkForm @created="onCreated" />
|
|
|
|
<div class="actions-bar">
|
|
<button class="btn-action" @click="categorize" :disabled="categorizing">
|
|
{{ categorizing ? 'categorizing...' : 'categorize' }}
|
|
</button>
|
|
<span v-if="status" class="status">{{ status }}</span>
|
|
</div>
|
|
|
|
<div class="content-area">
|
|
<BookmarkList :bookmarks="filtered" @deleted="onDeleted" />
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="panel-right">
|
|
<button
|
|
v-for="(section, i) in sections"
|
|
:key="section.letter"
|
|
class="nav-tab"
|
|
:class="{ active: activeIndex === i }"
|
|
@click="activeIndex = i"
|
|
>{{ section.letter }}</button>
|
|
</nav>
|
|
|
|
<footer class="footer">
|
|
<span class="footer-version">v:{{ version }}</span>
|
|
</footer>
|
|
</div>
|
|
</template>
|