Files
2025-08-10 17:04:52 +10:00

108 lines
4.6 KiB
Vue

<template>
<div>
<div v-if="guidesNavigation">
<h1 class="font-display font-bold text-zinc-100">{{ guidesNavigation.title }}</h1>
<ul class="ml-3 mt-1 space-y-2">
<li v-for="guide in guidesNavigation.children">
<NuxtLink :href="guide.path" class="transition text-sm text-zinc-300 hover:text-zinc-100">
<span class="text-blue-400">+ {{ " " }}</span>{{ guide.title }}
</NuxtLink>
</li>
</ul>
</div>
<div class="mt-8">
<div class="grid grid-cols-1">
<input type="email" name="email" id="email"
class="col-start-1 row-start-1 block w-full rounded-md bg-zinc-950 py-1.5 pr-3 pl-10 text-base text-zinc-100 outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-zinc-500 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:pl-9 sm:text-sm/6"
placeholder="/api/v1/..." v-model="query" />
<MagnifyingGlassIcon
class="pointer-events-none col-start-1 row-start-1 ml-3 size-5 self-center text-gray-400 sm:size-4"
aria-hidden="true" />
</div>
</div>
<ul class="mt-4 space-y-3">
<li v-for="[path, methods] in filteredCollapsedNav.entries()">
<span class="font-display text-zinc-100 font-semibold">{{ path }}</span>
<ul class="mt-1 space-y-1">
<li v-for="method in methods">
<NuxtLink
class="transition hover:bg-zinc-900 px-1 py-0.5 rounded ml-1 text-zinc-400 grid grid-cols-4 gap-x-2"
:href="method.path">
<span
:class="[apiMethodColours[method.method], 'text-center text-xs font-bold ring-1 rounded-full px-1 py-0.5']">{{
method.method }}</span>
<div class="col-span-3 text-sm relative whitespace-nowrap overflow-hidden"><span
class="absolute right-0">{{ method.methodlessName }}</span></div>
</NuxtLink>
</li>
</ul>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { MagnifyingGlassIcon } from '@heroicons/vue/16/solid'
import type { ContentNavigationItem } from '@nuxt/content';
const rawAPINav = await queryCollectionNavigation("docs");
const apiNavRoot = rawAPINav.at(0);
if (!apiNavRoot) throw createError({ statusCode: 500, statusMessage: "Failed to fetch API docs navigation", fatal: true });
const collapsedAPINav = ref<Map<string, Array<ContentNavigationItem & { method: string, methodlessName: string }>>>(new Map());
const apiMethods = ["GET", "POST", "DELETE", "PATCH"];
const apiMethodColours: { [key: string]: string } = {
"GET": "text-green-500 bg-green-500/10",
"POST": "text-orange-500 bg-orange-500/10",
"PATCH": "text-yellow-500 bg-yellow-500/10",
"DELETE": "text-red-500 bg-red-500/10",
"WS": "text-blue-500 bg-blue-500/10",
}
function recursivelyCollapseAPINav(nav: ContentNavigationItem, parent?: ContentNavigationItem) {
const potentialMethod = nav.title.split(" ").at(0);
if (!potentialMethod || !apiMethods.includes(potentialMethod.toUpperCase()) || !parent) {
if (nav.children) {
for (const child of nav.children)
recursivelyCollapseAPINav(child, nav);
}
return;
}
const methodlessName = nav.title.slice(potentialMethod.length).trim();
const newValue = collapsedAPINav.value.get(parent.path) ?? [];
newValue.push({ ...nav, method: potentialMethod, methodlessName })
collapsedAPINav.value.set(parent.path, newValue);
}
recursivelyCollapseAPINav(apiNavRoot)
collapsedAPINav.value = new Map([...collapsedAPINav.value].sort((a, b) => a[0].localeCompare(b[0])).map((v) => {
v[1].sort((a, b) => {
const methodSort = a.method.length - b.method.length;
if (methodSort == 0) return a.methodlessName.length - b.methodlessName.length;
return methodSort;
});
return v;
}))
const query = ref("");
const filteredCollapsedNav = computed(() => {
if(!query.value) return collapsedAPINav.value;
const raw = [...collapsedAPINav.value];
const filtered = raw.map((e) => [e[0], e[1].filter((v) => v.title.toLowerCase().includes(query.value.toLowerCase()))] as const);
const stripped = filtered.filter((e) => e[1].length > 0);
return new Map(stripped);
});
const guidesNavigation = (await queryCollectionNavigation("guides")).at(0);
</script>