More things

This commit is contained in:
2026-10-01 02:29:21 +02:00
parent e1df4d3ae1
commit 13b659662e
54 changed files with 4409 additions and 911 deletions

View File

@@ -2,8 +2,9 @@
const { collapsed, toggleSidebar, restoreSidebar } = useSidebar()
const { show: openSearch } = useSearch()
const { show: openSettings } = useSettings()
const { tabs, lastSlug } = useArticleTabs()
const { tabs, lastIdentity } = useArticleTabs()
const { user, isAdmin, signOut } = useAuth()
const { pending: waitingRequests, refresh: refreshPending } = usePendingRequests()
const { show: openSignIn } = useSignInPopup()
const { show: openProfile } = useProfileMenu()
const { t } = useI18n()
@@ -15,21 +16,43 @@ const router = useRouter()
lays their pages side by side, older ones parked with only their spines
showing. The stack stays open all session, but the view of it is only drawn
in the reader — so this button is the way back: press it wherever you are
(the graph, the list, the editor) and every article you have open comes back
into view, stacked as you left it. */
const inReader = computed(() => wikiSlugFromPath(route.path) !== null)
(the graph, the list, the editor, a missing article) and every article you
have open comes back into view, stacked as you left it. In view only when the
route names one of the open pages: a missing article reads like the reader
but holds none of them, so the button still turns back from there. */
const inReader = computed(() => tabAt(tabs.value, route.path) !== null)
function showOpenArticles() {
if (inReader.value) return // the pages are already spread out in view
/* turn back to the page that was last in view — every tab knows its own
address, language versions included */
const target =
tabs.value.find((t) => t.slug === lastSlug.value) ?? tabs.value[tabs.value.length - 1]
tabs.value.find((t) => t.identity === lastIdentity.value) ?? tabs.value[tabs.value.length - 1]
if (target) void router.push(tabHref(target))
}
/* the shortcut hint beside the search bar — "/" keeps focus on the home
page's inline box there, so the popup's universal key is Ctrl/⌘+K */
/* How many requests are waiting is an admin's business: ask once the session
is known to be one — and when a session stops being one, the door goes blank
with the links — and again on every navigation, so the number on the door is
no older than the page under it. While an admin decides at the desk, the
desk itself keeps the count exact (see usePendingRequests). */
watch(
isAdmin,
() => {
if (import.meta.client) void refreshPending()
},
{ immediate: true }
)
watch(
() => route.path,
() => {
if (import.meta.client && isAdmin.value) void refreshPending()
}
)
/* the shortcut hint beside the search bar — refined on the client to the
device's chord ("/" opens the popup too, just not while a field holds
the caret) */
const kbdHint = ref('/')
onMounted(() => {
kbdHint.value = /Mac|iPhone|iPad/i.test(navigator.userAgent) ? '⌘K' : 'Ctrl K'
@@ -100,6 +123,7 @@ const initials = computed(() => (user.value?.username.charAt(0) ?? '?').toUpperC
<NuxtLink
v-if="isAdmin"
to="/admin"
end
class="nav-link"
:title="t('sidebar.adminHint')"
>
@@ -109,6 +133,20 @@ const initials = computed(() => (user.value?.username.charAt(0) ?? '?').toUpperC
</svg>
<span class="hide-collapsed">{{ t('sidebar.admin') }}</span>
</NuxtLink>
<NuxtLink
v-if="isAdmin"
to="/admin/proposals"
class="nav-link sidebar-requests"
:title="waitingRequests ? t('sidebar.requestsWaiting', waitingRequests) : t('sidebar.requestsHint')"
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<path d="M14 2v6h6" />
<path d="m9 15 2 2 4-4" />
</svg>
<span class="hide-collapsed">{{ t('sidebar.requests') }}</span>
<span v-if="waitingRequests" class="requests-count">{{ waitingRequests }}</span>
</NuxtLink>
<NuxtLink to="/new" class="btn btn-primary btn-sm" :title="t('sidebar.newArticle')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round">
<path d="M12 5v14M5 12h14" />