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

@@ -1,14 +1,14 @@
<script setup lang="ts">
import type { ArticleMeta } from './ArticleCard.vue'
/* Centered search popup: the sidebar search bar (or Ctrl/⌘+K, or "/"
on pages without their own search box) opens it, then it live-searches
/* Centered search popup: the sidebar and home search bars (or Ctrl/⌘+K,
or "/" anywhere but a text field) open it, then it live-searches
articles against the API as you type. ↑/↓ select, ↵ opens, esc closes. */
const api = `${useRuntimeConfig().public.apiBase}/api`
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const { th } = useAppLocale()
const { th, locale } = useAppLocale()
const { open, show, hide, toggle } = useSearch()
const { hide: hideSettings } = useSettings()
const { hide: hideProfile } = useProfileMenu()
@@ -59,11 +59,16 @@ watch(trimmed, () => {
async function runSearch() {
const seq = ++requestSeq
try {
const res = await $fetch<ArticleMeta[]>(`${api}/articles`, {
query: { q: trimmed.value },
/* the popup never shows more than MAX_RESULTS, so that is what it asks
the shelf for rather than every match it has; `prefer` asks each answer
to stand with the reader's own language when the article speaks it, and
`preferFirst` ranks the articles that truly speak it above the ones
shown only through the fallback */
const page = await $fetch<{ articles: ArticleMeta[] }>(`${api}/articles`, {
query: { q: trimmed.value, limit: MAX_RESULTS, prefer: locale.value, preferFirst: '1' },
})
if (seq !== requestSeq) return // a newer search already won
results.value = res.slice(0, MAX_RESULTS)
results.value = page.articles
failed.value = false
} catch {
if (seq !== requestSeq) return
@@ -86,9 +91,8 @@ function onGlobalKeydown(e: KeyboardEvent) {
hide()
return
}
/* "/" also opens the popup — except on the home page, where the key
focuses the hero's inline search, and while typing in a field */
if (e.key === '/' && !open.value && route.path !== '/') {
/* "/" also opens the popup anywhere — except while typing in a field */
if (e.key === '/' && !open.value) {
const t = e.target
if (t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement) return
e.preventDefault()
@@ -105,7 +109,7 @@ onBeforeUnmount(() => {
/* where a result leads: the language address of the version it shows */
function articleHref(a: ArticleMeta): string {
return versionHref(identityOf(a), { language: articleLanguage(a), slug: a.slug })
return versionHref(identityOf(a), articleLanguage(a))
}
function openArticle(article: ArticleMeta) {
@@ -250,7 +254,7 @@ function highlight(text: string): Part[] {
<div class="search-quick-title">{{ t('recent.title') }}</div>
<ul class="search-results">
<li v-for="v in viewed.slice(0, 6)" :key="v.slug">
<NuxtLink :to="`/wiki/${v.slug}`" class="search-result">
<NuxtLink :to="viewedHref(v)" class="search-result">
<span class="sr-title">{{ v.title }}</span>
</NuxtLink>
</li>