style.css gains the classes the views spelled out in full: .section-label (17 sites), .hint (20), .form-error (13), .alert-error (5), .row-card (5), .list-empty (5), .field (5), .page-shell (3), and the small row action .btn-sm (4) / .btn-sm-danger (3). Only exact runs moved, so nothing renders differently; spacing a site adds beyond a run stays a utility beside it. Kept: the Reminders and Timeline small buttons. They carry no text colour and inherit it, so putting them on .btn-sm would recolour them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">People</h2>
|
||||
<h2 class="section-label">People</h2>
|
||||
|
||||
<OneTimeLink
|
||||
v-if="fresh"
|
||||
@@ -71,14 +71,14 @@ onMounted(() => {
|
||||
@done="fresh = null"
|
||||
/>
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="form-error">{{ error }}</p>
|
||||
|
||||
<div v-if="loading" class="py-6 text-center text-sm text-neutral-400">Loading…</div>
|
||||
<div v-if="loading" class="list-empty">Loading…</div>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="a in accounts"
|
||||
:key="a.id"
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium text-neutral-800 dark:text-neutral-100">{{ a.display_name }}</p>
|
||||
@@ -86,7 +86,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand disabled:opacity-50 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
||||
class="shrink-0 btn-sm"
|
||||
:disabled="busy === a.id"
|
||||
@click="resetLink(a)"
|
||||
>
|
||||
|
||||
@@ -93,11 +93,11 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">Activity</h2>
|
||||
<h2 class="section-label">Activity</h2>
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="form-error">{{ error }}</p>
|
||||
|
||||
<div v-if="loading" class="py-6 text-center text-sm text-neutral-400">Loading…</div>
|
||||
<div v-if="loading" class="list-empty">Loading…</div>
|
||||
<p v-else-if="!events.length" class="text-sm text-neutral-400">Nothing yet.</p>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
@@ -118,7 +118,7 @@ onMounted(() => {
|
||||
<button
|
||||
v-if="more"
|
||||
type="button"
|
||||
class="self-start rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand disabled:opacity-50 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
||||
class="self-start btn-sm"
|
||||
:disabled="loadingMore"
|
||||
@click="loadMore"
|
||||
>
|
||||
|
||||
@@ -396,7 +396,7 @@ async function signOut() {
|
||||
</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">Tags</span>
|
||||
<span class="section-label">Tags</span>
|
||||
<button
|
||||
type="button"
|
||||
class="text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-200"
|
||||
|
||||
@@ -132,6 +132,6 @@ onMounted(async () => {
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="px-4 py-6 text-center text-sm text-neutral-400">No matches</p>
|
||||
<p v-else class="px-4 list-empty">No matches</p>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
@@ -42,7 +42,7 @@ async function toggle() {
|
||||
<section v-if="status?.is_appimage" class="mt-10 border-t border-neutral-200 pt-8 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">Applications menu</h2>
|
||||
<div class="mt-3 flex items-center justify-between gap-4">
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="hint">
|
||||
{{ status.is_integrated ? "Inkwell is in your applications menu." : "Not in your applications menu." }}
|
||||
</p>
|
||||
<BaseButton
|
||||
|
||||
@@ -140,16 +140,16 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">Groups</h2>
|
||||
<h2 class="section-label">Groups</h2>
|
||||
|
||||
<form class="flex flex-wrap items-end gap-3" @submit.prevent="create">
|
||||
<BaseInput id="group-name" v-model="name" label="New group" placeholder="Family" class="min-w-48 flex-1" />
|
||||
<BaseButton type="submit" :loading="creating" :disabled="!name.trim()">Create group</BaseButton>
|
||||
</form>
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="form-error">{{ error }}</p>
|
||||
|
||||
<div v-if="loading" class="py-6 text-center text-sm text-neutral-400">Loading…</div>
|
||||
<div v-if="loading" class="list-empty">Loading…</div>
|
||||
<p v-else-if="empty" class="text-sm text-neutral-400">No groups yet.</p>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
@@ -167,7 +167,7 @@ onMounted(() => {
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand disabled:opacity-50 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
||||
class="btn-sm"
|
||||
:disabled="busy === g.id"
|
||||
@click="rename(g)"
|
||||
>
|
||||
@@ -175,7 +175,7 @@ onMounted(() => {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-red-600 hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand disabled:opacity-50 dark:border-neutral-700 dark:text-red-400 dark:hover:bg-red-950/40"
|
||||
class="btn-sm-danger"
|
||||
:disabled="busy === g.id"
|
||||
@click="remove(g)"
|
||||
>
|
||||
@@ -216,7 +216,7 @@ onMounted(() => {
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md border border-neutral-300 px-2.5 py-1.5 text-xs text-neutral-700 hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand disabled:opacity-50 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800"
|
||||
class="btn-sm py-1.5"
|
||||
:disabled="busy === g.id || !picks[g.id]"
|
||||
>
|
||||
Add
|
||||
|
||||
@@ -109,7 +109,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">Invites</h2>
|
||||
<h2 class="section-label">Invites</h2>
|
||||
|
||||
<OneTimeLink
|
||||
v-if="freshLink"
|
||||
@@ -132,7 +132,7 @@ onMounted(() => {
|
||||
<select
|
||||
id="invite-days"
|
||||
v-model.number="days"
|
||||
class="rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="field"
|
||||
>
|
||||
<option v-for="l in LIFETIMES" :key="l.days" :value="l.days">{{ l.label }}</option>
|
||||
</select>
|
||||
@@ -140,15 +140,15 @@ onMounted(() => {
|
||||
<BaseButton type="submit" :loading="creating">Create invite</BaseButton>
|
||||
</form>
|
||||
|
||||
<p v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="form-error">{{ error }}</p>
|
||||
|
||||
<div v-if="loading" class="py-6 text-center text-sm text-neutral-400">Loading…</div>
|
||||
<div v-if="loading" class="list-empty">Loading…</div>
|
||||
<p v-else-if="!invites.length" class="text-sm text-neutral-400">No invites yet.</p>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="i in invites"
|
||||
:key="i.id"
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-medium text-neutral-800 dark:text-neutral-100">
|
||||
@@ -159,7 +159,7 @@ onMounted(() => {
|
||||
<button
|
||||
v-if="i.status === 'pending'"
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-red-600 hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:text-red-400 dark:hover:bg-red-950/40"
|
||||
class="shrink-0 btn-sm-danger"
|
||||
@click="revoke(i)"
|
||||
>
|
||||
Revoke
|
||||
|
||||
@@ -761,7 +761,7 @@ function revPreview(rev: NoteRevision): string {
|
||||
v-if="!isCreate && showHistory"
|
||||
class="flex flex-col gap-1 border-t border-neutral-100 pt-2 dark:border-neutral-800"
|
||||
>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-neutral-400">History</p>
|
||||
<p class="section-label">History</p>
|
||||
<p v-if="!revisions.length" class="text-xs text-neutral-400">
|
||||
No earlier versions yet — your edits will show up here.
|
||||
</p>
|
||||
|
||||
@@ -115,7 +115,7 @@ onMounted(load);
|
||||
<p v-if="loading" class="py-4 text-center text-sm text-neutral-400">Loading…</p>
|
||||
|
||||
<div v-else-if="loadError" class="py-4 text-center">
|
||||
<p class="text-sm text-red-600 dark:text-red-400">{{ loadError }}</p>
|
||||
<p class="form-error">{{ loadError }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 rounded-md border border-neutral-300 px-3 py-1 text-sm hover:bg-neutral-100 dark:border-neutral-700 dark:hover:bg-neutral-800"
|
||||
@@ -126,7 +126,7 @@ onMounted(load);
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<p v-if="!anyone" class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-if="!anyone" class="hint">
|
||||
No one else has an account here yet. An admin can invite people from Settings.
|
||||
</p>
|
||||
|
||||
@@ -156,10 +156,10 @@ onMounted(load);
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p v-if="error" role="alert" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" role="alert" class="form-error">{{ error }}</p>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-neutral-400">Shared with</p>
|
||||
<p class="section-label">Shared with</p>
|
||||
<p v-if="!shares.length" class="text-sm text-neutral-400">Only you.</p>
|
||||
<ul v-else class="flex flex-col gap-1">
|
||||
<li v-for="s in shares" :key="s.id" class="flex items-center gap-2 rounded-lg px-1 py-1">
|
||||
|
||||
@@ -290,4 +290,52 @@ body {
|
||||
.nav-link-active {
|
||||
@apply bg-neutral-200 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100;
|
||||
}
|
||||
/* The small bordered action on a row in Account, Admin and Sync: Rename, Revoke,
|
||||
* Sign out. Two colours, neutral and destructive; spacing a site needs beyond
|
||||
* these (shrink-0, self-start, py-1.5) stays a utility beside it. */
|
||||
.btn-sm {
|
||||
@apply rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-neutral-700
|
||||
hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
|
||||
disabled:opacity-50 dark:border-neutral-700 dark:text-neutral-200 dark:hover:bg-neutral-800;
|
||||
}
|
||||
.btn-sm-danger {
|
||||
@apply rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-red-600
|
||||
hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
|
||||
disabled:opacity-50 dark:border-neutral-700 dark:text-red-400 dark:hover:bg-red-950/40;
|
||||
}
|
||||
/* A plain text or number input on a settings page; its width is the site's. */
|
||||
.field {
|
||||
@apply rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900
|
||||
shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand
|
||||
dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100;
|
||||
}
|
||||
/* A settings or account page: one centred column. */
|
||||
.page-shell {
|
||||
@apply mx-auto min-h-full max-w-2xl px-4 py-8;
|
||||
}
|
||||
/* The small caps heading over a group of rows or controls. */
|
||||
.section-label {
|
||||
@apply text-xs font-semibold uppercase tracking-wide text-neutral-400;
|
||||
}
|
||||
/* One bordered row in a list of things you can act on: a device, an account. */
|
||||
.row-card {
|
||||
@apply flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4
|
||||
py-3 dark:border-neutral-800;
|
||||
}
|
||||
/* The line a list shows when it has nothing in it. */
|
||||
.list-empty {
|
||||
@apply py-6 text-center text-sm text-neutral-400;
|
||||
}
|
||||
/* Secondary text under a heading or beside a control. */
|
||||
.hint {
|
||||
@apply text-sm text-neutral-500 dark:text-neutral-400;
|
||||
}
|
||||
/* A field's or a form's own error, in a line of text. */
|
||||
.form-error {
|
||||
@apply text-sm text-red-600 dark:text-red-400;
|
||||
}
|
||||
/* A form's error as a banner over its submit button. */
|
||||
.alert-error {
|
||||
@apply rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Account" />
|
||||
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Linked devices</h2>
|
||||
<p class="mb-6 max-w-xl text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<h2 class="mb-2 section-label">Linked devices</h2>
|
||||
<p class="mb-6 max-w-xl hint">
|
||||
Link the Inkwell desktop or mobile app to sync your notes. Create a device token here, then
|
||||
paste it into the app when it asks to connect. You can revoke a device at any time.
|
||||
</p>
|
||||
@@ -199,7 +199,7 @@ onMounted(() => {
|
||||
<BaseButton type="submit" :loading="creating">Create token</BaseButton>
|
||||
</form>
|
||||
|
||||
<p v-if="error" class="mb-4 text-sm text-red-600 dark:text-red-400">{{ error }}</p>
|
||||
<p v-if="error" class="mb-4 form-error">{{ error }}</p>
|
||||
|
||||
<!-- Device list -->
|
||||
<div v-if="devices.loading" class="py-10 text-center text-sm text-neutral-400">Loading…</div>
|
||||
@@ -207,13 +207,13 @@ onMounted(() => {
|
||||
v-else-if="!devices.items.length"
|
||||
class="rounded-xl border border-dashed border-neutral-300 py-10 text-center dark:border-neutral-700"
|
||||
>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">No devices linked yet.</p>
|
||||
<p class="hint">No devices linked yet.</p>
|
||||
</div>
|
||||
<ul v-else class="flex flex-col gap-2">
|
||||
<li
|
||||
v-for="d in devices.items"
|
||||
:key="d.id"
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<span class="text-neutral-400"><Icon name="device" /></span>
|
||||
@@ -226,7 +226,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="shrink-0 rounded-md border border-neutral-300 px-2.5 py-1 text-xs text-red-600 hover:bg-red-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:text-red-400 dark:hover:bg-red-950/40"
|
||||
class="shrink-0 btn-sm-danger"
|
||||
@click="revoke(d.id, d.name)"
|
||||
>
|
||||
Revoke
|
||||
@@ -235,9 +235,9 @@ onMounted(() => {
|
||||
</ul>
|
||||
|
||||
<template v-if="storage">
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Storage</h2>
|
||||
<h2 class="mb-2 mt-10 section-label">Storage</h2>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">Attachments</p>
|
||||
@@ -247,8 +247,8 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Password</h2>
|
||||
<p class="mb-4 text-sm text-neutral-500 dark:text-neutral-400">Changing it signs you out everywhere else.</p>
|
||||
<h2 class="mb-2 mt-10 section-label">Password</h2>
|
||||
<p class="mb-4 hint">Changing it signs you out everywhere else.</p>
|
||||
<form class="flex max-w-sm flex-col gap-3" @submit.prevent="changePassword">
|
||||
<BaseInput
|
||||
id="current-password"
|
||||
@@ -273,9 +273,9 @@ onMounted(() => {
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2 class="mb-2 mt-10 text-xs font-semibold uppercase tracking-wide text-neutral-400">Sessions</h2>
|
||||
<h2 class="mb-2 mt-10 section-label">Sessions</h2>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 rounded-xl border border-neutral-200 px-4 py-3 dark:border-neutral-800"
|
||||
class="row-card"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">Sign out everywhere else</p>
|
||||
|
||||
@@ -350,11 +350,11 @@ const gridBinds = computed(() => ({
|
||||
<template v-else>
|
||||
<template v-if="isMainBoard">
|
||||
<section v-if="pinnedNotes.length">
|
||||
<h2 class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">Pinned</h2>
|
||||
<h2 class="mb-2 section-label">Pinned</h2>
|
||||
<NoteGrid :notes="pinnedNotes" v-bind="gridBinds" />
|
||||
</section>
|
||||
<section v-if="otherNotes.length" :class="pinnedNotes.length ? 'mt-8' : ''">
|
||||
<h2 v-if="pinnedNotes.length" class="mb-2 text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<h2 v-if="pinnedNotes.length" class="mb-2 section-label">
|
||||
Others
|
||||
</h2>
|
||||
<NoteGrid :notes="otherNotes" v-bind="gridBinds" />
|
||||
|
||||
@@ -44,7 +44,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Forgot your password?</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">We'll email you a link to choose a new one.</p>
|
||||
<p class="mt-1 hint">We'll email you a link to choose a new one.</p>
|
||||
</div>
|
||||
|
||||
<p
|
||||
@@ -67,14 +67,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Send reset link</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Back to sign in</RouterLink
|
||||
>
|
||||
|
||||
@@ -52,7 +52,7 @@ async function submit() {
|
||||
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>
|
||||
<p class="mt-1 hint">Sign in to your thoughts.</p>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-4" novalidate @submit.prevent="submit">
|
||||
@@ -77,7 +77,7 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
@@ -90,7 +90,7 @@ async function submit() {
|
||||
>
|
||||
</form>
|
||||
|
||||
<p v-if="config.allowRegistration" class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-if="config.allowRegistration" class="mt-6 text-center hint">
|
||||
No account?
|
||||
<RouterLink to="/register" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Create one</RouterLink
|
||||
|
||||
@@ -52,7 +52,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{{ invite ? "You're invited" : "Create your space" }}</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
{{ invite ? `Create your account on ${config.siteName}.` : "Start capturing in seconds." }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -86,14 +86,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Create account</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
Already have an account?
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Sign in</RouterLink
|
||||
|
||||
@@ -58,7 +58,7 @@ async function submit() {
|
||||
height="48"
|
||||
/>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Choose a new password</h1>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
You'll be signed out everywhere else, and your apps will ask you to sign in again.
|
||||
</p>
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@ async function submit() {
|
||||
<p
|
||||
v-if="!token"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
This link is incomplete. Ask your admin for a new one.
|
||||
</p>
|
||||
@@ -91,14 +91,14 @@ async function submit() {
|
||||
<p
|
||||
v-if="error"
|
||||
role="alert"
|
||||
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
||||
class="alert-error"
|
||||
>
|
||||
{{ error }}
|
||||
</p>
|
||||
<BaseButton type="submit" :loading="loading">Set password</BaseButton>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-6 text-center hint">
|
||||
<RouterLink to="/login" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
||||
>Back to sign in</RouterLink
|
||||
>
|
||||
|
||||
@@ -94,13 +94,13 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Settings" />
|
||||
|
||||
<AsyncState :loading="loading" :error="loadError" error-title="Couldn't load settings" @retry="load">
|
||||
<form class="flex flex-col gap-8" @submit.prevent="save">
|
||||
<section v-for="group in groups" :key="group.name" class="flex flex-col gap-5">
|
||||
<h2 class="text-xs font-semibold uppercase tracking-wide text-neutral-400">{{ group.name }}</h2>
|
||||
<h2 class="section-label">{{ group.name }}</h2>
|
||||
|
||||
<div v-for="it in group.entries" :key="it.key" class="flex flex-col gap-1">
|
||||
<div class="flex items-center justify-between gap-6">
|
||||
@@ -126,14 +126,14 @@ onMounted(load);
|
||||
type="number"
|
||||
:min="it.minimum ?? undefined"
|
||||
:max="it.maximum ?? undefined"
|
||||
class="w-28 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-28 field"
|
||||
:value="Number(it.value)"
|
||||
@input="it.value = Number(($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<select
|
||||
v-else-if="it.choices"
|
||||
:id="it.key"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@change="it.value = ($event.target as HTMLSelectElement).value"
|
||||
>
|
||||
@@ -145,7 +145,7 @@ onMounted(load);
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
:placeholder="it.is_set ? 'Saved — type to replace' : ''"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@input="it.value = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
@@ -153,7 +153,7 @@ onMounted(load);
|
||||
v-else
|
||||
:id="it.key"
|
||||
type="text"
|
||||
class="w-56 rounded-lg border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-100"
|
||||
class="w-56 field"
|
||||
:value="String(it.value)"
|
||||
@input="it.value = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
@@ -178,7 +178,7 @@ onMounted(load);
|
||||
<BaseButton type="submit" :loading="saving" :disabled="!dirty">Save changes</BaseButton>
|
||||
<span v-if="saved && !dirty" class="text-sm text-green-600 dark:text-green-400">Saved.</span>
|
||||
<span v-else-if="!dirty && !error" class="text-sm text-neutral-400">No unsaved changes</span>
|
||||
<span v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</span>
|
||||
<span v-if="error" class="form-error">{{ error }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</AsyncState>
|
||||
|
||||
@@ -356,7 +356,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto min-h-full max-w-2xl px-4 py-8">
|
||||
<div class="page-shell">
|
||||
<PageHeader title="Sync" />
|
||||
|
||||
<div v-if="loading" class="py-10 text-center text-sm text-neutral-400">Loading…</div>
|
||||
@@ -392,7 +392,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
<p v-if="degraded.length" class="mt-3 text-xs text-amber-600 dark:text-amber-400">
|
||||
This server doesn't support: {{ degraded.join(", ") }}. Everything else syncs normally.
|
||||
</p>
|
||||
<p v-if="syncError" class="mt-3 text-sm text-red-600 dark:text-red-400">{{ syncError }}</p>
|
||||
<p v-if="syncError" class="mt-3 form-error">{{ syncError }}</p>
|
||||
<p v-else-if="autoError" class="mt-3 text-xs text-amber-600 dark:text-amber-400">
|
||||
The last automatic sync ({{ new Date(lastCycle!.at_ms).toLocaleTimeString() }}) didn't
|
||||
go through: {{ autoError }} It will keep trying on its own.
|
||||
@@ -428,7 +428,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
<p class="text-sm font-medium text-neutral-800 dark:text-neutral-100">
|
||||
Working offline on this device
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
Everything works without a server — your notes are stored on this machine. Connect an
|
||||
Inkwell server if you want them to reach your other devices.
|
||||
</p>
|
||||
@@ -452,7 +452,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
Uses https unless you type http:// yourself.
|
||||
</p>
|
||||
|
||||
<p v-if="probeError" class="text-sm text-red-600 dark:text-red-400">{{ probeError }}</p>
|
||||
<p v-if="probeError" class="form-error">{{ probeError }}</p>
|
||||
|
||||
<!-- What answered, BEFORE any credentials are handed over -->
|
||||
<div
|
||||
@@ -531,7 +531,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<p v-if="linkError" class="text-sm text-red-600 dark:text-red-400">{{ linkError }}</p>
|
||||
<p v-if="linkError" class="form-error">{{ linkError }}</p>
|
||||
|
||||
<div>
|
||||
<BaseButton type="submit" :loading="linking" :disabled="!canLink">
|
||||
@@ -546,7 +546,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
updates: a hotkey that writes to the local store needs no server. -->
|
||||
<section class="mt-10 border-t border-neutral-200 pt-8 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">Quick capture</h2>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
A system-wide shortcut that opens a small window to write a note in, without
|
||||
bringing this one forward.
|
||||
</p>
|
||||
@@ -570,7 +570,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</BaseButton>
|
||||
</div>
|
||||
|
||||
<p v-if="shortcutError" class="mt-2 text-sm text-red-600 dark:text-red-400">
|
||||
<p v-if="shortcutError" class="mt-2 form-error">
|
||||
{{ shortcutError }}
|
||||
</p>
|
||||
|
||||
@@ -585,10 +585,10 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
{{ shortcut.shortcut }} is saved but isn't active — something else on this
|
||||
system is holding it. Try a different combination.
|
||||
</p>
|
||||
<p v-else-if="shortcut.registered" class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-else-if="shortcut.registered" class="mt-2 hint">
|
||||
Press {{ shortcut.shortcut }} anywhere to capture a note.
|
||||
</p>
|
||||
<p v-else class="mt-2 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p v-else class="mt-2 hint">
|
||||
Off. There's no default on purpose — any combination picked for you is one
|
||||
taken away from something else on your machine.
|
||||
<button
|
||||
@@ -605,12 +605,12 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
has never touched a server still updates itself. -->
|
||||
<section class="mt-10 border-t border-neutral-200 pt-8 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">App updates</h2>
|
||||
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mt-1 hint">
|
||||
This is version {{ update?.current_version ?? "—" }}.
|
||||
</p>
|
||||
|
||||
<fieldset v-if="offeredServer" class="mt-4">
|
||||
<legend class="text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<legend class="section-label">
|
||||
Updates from
|
||||
</legend>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
@@ -650,7 +650,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
|
||||
<!-- A server holds one build, so a channel only means something on the forge. -->
|
||||
<fieldset v-if="source === null" class="mt-4">
|
||||
<legend class="text-xs font-semibold uppercase tracking-wide text-neutral-400">
|
||||
<legend class="section-label">
|
||||
Channel
|
||||
</legend>
|
||||
<div class="mt-2 flex flex-col gap-2">
|
||||
@@ -704,7 +704,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</div>
|
||||
<p
|
||||
v-else-if="checkedOnce && !updateError"
|
||||
class="mt-4 text-sm text-neutral-500 dark:text-neutral-400"
|
||||
class="mt-4 hint"
|
||||
>
|
||||
<template v-if="source">You're on the newest build {{ source }} has.</template>
|
||||
<template v-else>
|
||||
@@ -712,7 +712,7 @@ onBeforeUnmount(() => stopSyncListener?.());
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<p v-if="updateError" class="mt-4 text-sm text-red-600 dark:text-red-400">{{ updateError }}</p>
|
||||
<p v-if="updateError" class="mt-4 form-error">{{ updateError }}</p>
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<BaseButton variant="ghost" :loading="checking" @click="checkUpdates">
|
||||
|
||||
@@ -137,7 +137,7 @@ onMounted(load);
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-5 text-sm text-neutral-500 dark:text-neutral-400">
|
||||
<p class="mb-5 hint">
|
||||
Your notes by when you captured them{{ hasRange ? " — filtered to the chosen dates" : "" }}.
|
||||
</p>
|
||||
|
||||
@@ -150,7 +150,7 @@ onMounted(load);
|
||||
<EmptyState v-if="items.length === 0" :title="empty.title" :subtitle="empty.subtitle" />
|
||||
<div v-else class="flex flex-col gap-8">
|
||||
<section v-for="group in groups" :key="group.key">
|
||||
<h2 class="mb-3 text-xs font-semibold uppercase tracking-wide text-neutral-400">{{ group.label }}</h2>
|
||||
<h2 class="mb-3 section-label">{{ group.label }}</h2>
|
||||
<NoteGrid :notes="group.notes" @open="openEditor" />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user