AGPL-3.0-only, a source link in the app, and everything on dev since #5 #6
@@ -1,6 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import ToastHost from "./components/ToastHost.vue";
|
||||
import DesktopIntegrationPrompt from "./components/DesktopIntegrationPrompt.vue";
|
||||
import { usePageTitle } from "./composables/usePageTitle";
|
||||
|
||||
usePageTitle();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -13,6 +13,7 @@ import Icon from "./Icon.vue";
|
||||
import ImportNotes from "./ImportNotes.vue";
|
||||
import LabelsModal from "./LabelsModal.vue";
|
||||
import { isDesktop } from "../desktop/bridge";
|
||||
import { useLensName } from "../composables/useLensName";
|
||||
import { facetsToQuery } from "../notes/facets";
|
||||
import { NOTE_SWATCH_CLASSES, resolveLabelColor } from "../notes/colors";
|
||||
|
||||
@@ -242,34 +243,7 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* What to call the lens currently in view.
|
||||
*
|
||||
* Keyed off the route name rather than each view declaring its own title, so the
|
||||
* label sits in one place and can't go missing (the board never had one)
|
||||
* or drift in styling (timeline and reminders each had their own h1).
|
||||
*
|
||||
* A label lens is named by the label itself — "Groceries" is what the user came
|
||||
* looking for; "Label" would tell them nothing they didn't already know.
|
||||
*/
|
||||
const lensName = computed<string>(() => {
|
||||
switch (route.name) {
|
||||
case "archive":
|
||||
return "Archive";
|
||||
case "trash":
|
||||
return "Trash";
|
||||
case "timeline":
|
||||
return "Timeline";
|
||||
case "reminders":
|
||||
return "Reminders";
|
||||
case "label":
|
||||
// The store may not have loaded yet on a deep link; fall back rather than
|
||||
// flashing an empty slot.
|
||||
return labels.items.find((l) => l.id === String(route.params.id))?.name ?? "Label";
|
||||
default:
|
||||
return "Notes";
|
||||
}
|
||||
});
|
||||
const lensName = useLensName();
|
||||
|
||||
async function signOut() {
|
||||
await session.logout();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { computed, type ComputedRef } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useLabelsStore } from "../stores/labels";
|
||||
|
||||
/**
|
||||
* What to call the lens currently in view.
|
||||
*
|
||||
* Keyed off the route name rather than each view declaring its own title, so the
|
||||
* label sits in one place and can't go missing (the board never had one)
|
||||
* or drift in styling (timeline and reminders each had their own h1). The shell's
|
||||
* header and the browser tab both read it, so the two can't disagree either.
|
||||
*
|
||||
* A label lens is named by the label itself — "Groceries" is what the user came
|
||||
* looking for; "Label" would tell them nothing they didn't already know.
|
||||
*/
|
||||
export function useLensName(): ComputedRef<string> {
|
||||
const route = useRoute();
|
||||
const labels = useLabelsStore();
|
||||
return computed<string>(() => {
|
||||
switch (route.name) {
|
||||
case "archive":
|
||||
return "Archive";
|
||||
case "trash":
|
||||
return "Trash";
|
||||
case "timeline":
|
||||
return "Timeline";
|
||||
case "reminders":
|
||||
return "Reminders";
|
||||
case "label":
|
||||
// The store may not have loaded yet on a deep link; fall back rather than
|
||||
// flashing an empty slot.
|
||||
return labels.items.find((l) => l.id === String(route.params.id))?.name ?? "Label";
|
||||
default:
|
||||
return "Notes";
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { computed, watchEffect } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useConfigStore } from "../stores/config";
|
||||
import { useLensName } from "./useLensName";
|
||||
|
||||
/**
|
||||
* Keep the browser tab titled "<page> · <site name>".
|
||||
*
|
||||
* index.html's static <title> only names the app, and nothing updated it after
|
||||
* that, so every tab read the same — or, where the browser preferred the URL, no
|
||||
* name at all. One writer, mounted once in App.vue: a route outside the shell names
|
||||
* itself with `meta.title`, and a board lens takes the name the shell's header
|
||||
* already shows.
|
||||
*/
|
||||
export function usePageTitle(): void {
|
||||
const route = useRoute();
|
||||
const config = useConfigStore();
|
||||
const lensName = useLensName();
|
||||
|
||||
const page = computed(() => (route.meta.title as string | undefined) ?? lensName.value);
|
||||
|
||||
watchEffect(() => {
|
||||
document.title = `${page.value} · ${config.siteName}`;
|
||||
});
|
||||
}
|
||||
@@ -31,13 +31,13 @@ const router = createRouter({
|
||||
path: "/capture",
|
||||
name: "capture",
|
||||
component: () => import("../views/CaptureView.vue"),
|
||||
meta: { requiresAuth: true, requiresDesktop: true },
|
||||
meta: { title: "Quick capture", requiresAuth: true, requiresDesktop: true },
|
||||
},
|
||||
{
|
||||
path: "/settings",
|
||||
name: "settings",
|
||||
component: () => import("../views/SettingsView.vue"),
|
||||
meta: { requiresAuth: true, requiresAdmin: true },
|
||||
meta: { title: "Settings", requiresAuth: true, requiresAdmin: true },
|
||||
},
|
||||
{
|
||||
// Desktop only: connect this app to a server. Meaningless in the web build,
|
||||
@@ -45,7 +45,7 @@ const router = createRouter({
|
||||
path: "/sync",
|
||||
name: "sync",
|
||||
component: () => import("../views/SyncView.vue"),
|
||||
meta: { requiresAuth: true, requiresDesktop: true },
|
||||
meta: { title: "Sync", requiresAuth: true, requiresDesktop: true },
|
||||
},
|
||||
{
|
||||
// Per-user account: linked devices (native-client sync tokens). Any user.
|
||||
@@ -58,19 +58,19 @@ const router = createRouter({
|
||||
path: "/account",
|
||||
name: "account",
|
||||
component: () => import("../views/AccountView.vue"),
|
||||
meta: { requiresAuth: true, requiresServer: true },
|
||||
meta: { title: "Linked devices", requiresAuth: true, requiresServer: true },
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "login",
|
||||
component: () => import("../views/LoginView.vue"),
|
||||
meta: { guestOnly: true },
|
||||
meta: { title: "Sign in", guestOnly: true },
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
name: "register",
|
||||
component: () => import("../views/RegisterView.vue"),
|
||||
meta: { guestOnly: true },
|
||||
meta: { title: "Create account", guestOnly: true },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -44,11 +44,13 @@ async function submit() {
|
||||
<main class="flex min-h-full items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-sm">
|
||||
<div class="mb-8 text-center">
|
||||
<div
|
||||
class="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-brand text-lg font-black text-neutral-900"
|
||||
>
|
||||
TS
|
||||
</div>
|
||||
<img
|
||||
src="/icon.svg"
|
||||
:alt="config.siteName"
|
||||
class="mx-auto mb-3 h-12 w-12 rounded-xl"
|
||||
width="48"
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Welcome back</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Sign in to your thoughts.</p>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useSessionStore } from "../stores/session";
|
||||
import { useConfigStore } from "../stores/config";
|
||||
import BaseInput from "../components/BaseInput.vue";
|
||||
import BaseButton from "../components/BaseButton.vue";
|
||||
import type { ApiError } from "../api/client";
|
||||
|
||||
const session = useSessionStore();
|
||||
const config = useConfigStore();
|
||||
const router = useRouter();
|
||||
|
||||
const displayName = ref("");
|
||||
@@ -37,11 +39,13 @@ async function submit() {
|
||||
<main class="flex min-h-full items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-sm">
|
||||
<div class="mb-8 text-center">
|
||||
<div
|
||||
class="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-brand text-lg font-black text-neutral-900"
|
||||
>
|
||||
TS
|
||||
</div>
|
||||
<img
|
||||
src="/icon.svg"
|
||||
:alt="config.siteName"
|
||||
class="mx-auto mb-3 h-12 w-12 rounded-xl"
|
||||
width="48"
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Create your space</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Start capturing in seconds.</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user