M2 labels frontend: sidebar shell + label filter, chips, picker, manage
- AppShell: persistent left sidebar (Notes · labels · Archive · Trash) + top bar (site name, admin Settings, sign out); BoardView now renders inside it. - labels store (list/create/rename/delete); Note gains labels[]; notes store gains setLabels + label-aware reconcile + /api/notes?label= loading. - /label/:id route → label-filtered board. - LabelPicker (tag a note, create-on-the-fly) in the editor; label chips shown on cards and in the editor; LabelsModal to create/rename/delete labels. - api client PUT; new icons (note/tag/pencil/plus/check); nav-link styles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useSessionStore } from "../stores/session";
|
||||
import { useConfigStore } from "../stores/config";
|
||||
import { useLabelsStore } from "../stores/labels";
|
||||
import Icon from "./Icon.vue";
|
||||
import LabelsModal from "./LabelsModal.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const session = useSessionStore();
|
||||
const config = useConfigStore();
|
||||
const labels = useLabelsStore();
|
||||
|
||||
const managing = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
if (!labels.loaded) void labels.load();
|
||||
});
|
||||
|
||||
const currentLabelId = computed(() => (route.name === "label" ? String(route.params.id) : null));
|
||||
|
||||
async function signOut() {
|
||||
await session.logout();
|
||||
await router.replace("/login");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex min-h-full flex-col">
|
||||
<header
|
||||
class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4 px-4 py-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-brand text-sm font-black text-neutral-900">
|
||||
TS
|
||||
</div>
|
||||
<span class="hidden font-semibold sm:inline">{{ config.siteName }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="hidden text-sm text-neutral-500 sm:inline dark:text-neutral-400">{{
|
||||
session.user?.display_name
|
||||
}}</span>
|
||||
<RouterLink
|
||||
v-if="session.user?.is_admin"
|
||||
to="/settings"
|
||||
class="icon-btn"
|
||||
title="Settings"
|
||||
aria-label="Settings"
|
||||
>
|
||||
<Icon name="settings" />
|
||||
</RouterLink>
|
||||
<button type="button" class="icon-btn" title="Sign out" aria-label="Sign out" @click="signOut">
|
||||
<Icon name="logout" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-1">
|
||||
<aside class="hidden w-56 shrink-0 border-r border-neutral-200 p-3 sm:block dark:border-neutral-800">
|
||||
<nav class="flex flex-col gap-0.5 text-sm">
|
||||
<RouterLink to="/" class="nav-link" :class="route.name === 'board' ? 'nav-link-active' : ''">
|
||||
<Icon name="note" /> Notes
|
||||
</RouterLink>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between px-3 pb-1">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-neutral-400">Labels</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-200"
|
||||
title="Edit labels"
|
||||
aria-label="Edit labels"
|
||||
@click="managing = true"
|
||||
>
|
||||
<Icon name="pencil" />
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="!labels.items.length" class="px-3 py-1 text-xs text-neutral-400">No labels yet</p>
|
||||
<RouterLink
|
||||
v-for="lb in labels.items"
|
||||
:key="lb.id"
|
||||
:to="`/label/${lb.id}`"
|
||||
class="nav-link"
|
||||
:class="currentLabelId === lb.id ? 'nav-link-active' : ''"
|
||||
>
|
||||
<Icon name="tag" /> <span class="truncate">{{ lb.name }}</span>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/archive" class="nav-link mt-3" :class="route.name === 'archive' ? 'nav-link-active' : ''">
|
||||
<Icon name="archive" /> Archive
|
||||
</RouterLink>
|
||||
<RouterLink to="/trash" class="nav-link" :class="route.name === 'trash' ? 'nav-link-active' : ''">
|
||||
<Icon name="trash" /> Trash
|
||||
</RouterLink>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="min-w-0 flex-1"><slot /></main>
|
||||
</div>
|
||||
|
||||
<LabelsModal v-if="managing" @close="managing = false" />
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user