- Migration 0005: generated tsvector column (title A + body B) + GIN index on notes; GET /api/notes/search?q= (websearch_to_tsquery, ts_rank, ACL-scoped, excludes trash), labels merged into results. - Persistent AppShell layout (parent route + <RouterView> children) so the new top search box keeps focus across board/search/label navigation. - SearchView (debounced live search from the shell → /search?q=, results masonry, no-match empty state); BoardView/SearchView render inside the shared shell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
import { createRouter, createWebHistory } from "vue-router";
|
|
import { useSessionStore } from "../stores/session";
|
|
import { useConfigStore } from "../stores/config";
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory(),
|
|
routes: [
|
|
{
|
|
// Persistent authed shell (sidebar + top bar + search); children render in it.
|
|
path: "/",
|
|
component: () => import("../components/AppShell.vue"),
|
|
meta: { requiresAuth: true },
|
|
children: [
|
|
{ path: "", name: "board", component: () => import("../views/BoardView.vue") },
|
|
{ path: "archive", name: "archive", component: () => import("../views/BoardView.vue") },
|
|
{ path: "trash", name: "trash", component: () => import("../views/BoardView.vue") },
|
|
{ path: "label/:id", name: "label", component: () => import("../views/BoardView.vue") },
|
|
{ path: "search", name: "search", component: () => import("../views/SearchView.vue") },
|
|
],
|
|
},
|
|
{
|
|
path: "/settings",
|
|
name: "settings",
|
|
component: () => import("../views/SettingsView.vue"),
|
|
meta: { requiresAuth: true, requiresAdmin: true },
|
|
},
|
|
{
|
|
path: "/login",
|
|
name: "login",
|
|
component: () => import("../views/LoginView.vue"),
|
|
meta: { guestOnly: true },
|
|
},
|
|
{
|
|
path: "/register",
|
|
name: "register",
|
|
component: () => import("../views/RegisterView.vue"),
|
|
meta: { guestOnly: true },
|
|
},
|
|
],
|
|
});
|
|
|
|
router.beforeEach(async (to) => {
|
|
const session = useSessionStore();
|
|
const config = useConfigStore();
|
|
await config.load();
|
|
if (!session.loaded) {
|
|
await session.fetchMe();
|
|
}
|
|
if (to.meta.requiresAuth && !session.user) {
|
|
return { name: "login", query: to.fullPath !== "/" ? { redirect: to.fullPath } : undefined };
|
|
}
|
|
if (to.meta.requiresAdmin && !session.user?.is_admin) {
|
|
return { name: "board" };
|
|
}
|
|
if (to.name === "register" && !config.allowRegistration) {
|
|
return { name: "login" };
|
|
}
|
|
if (to.meta.guestOnly && session.user) {
|
|
return { name: "board" };
|
|
}
|
|
return true;
|
|
});
|
|
|
|
export default router;
|