134 lines
4.5 KiB
Vue
134 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
import { t, translate } from '@/i18n'
|
|
|
|
import { computed, ref } from 'vue'
|
|
import ApiFieldTable from '@/components/ApiFieldTable.vue'
|
|
import CodeSnippet from '@/components/CodeSnippet.vue'
|
|
import { RATE_LIMIT_SENTENCE, endpointExamples, type ApiEndpoint } from '@/data/apiCatalogue'
|
|
import { CHIP_CLASS, METHOD_CLASS, endpointAnchor } from '@/lib/apiReference'
|
|
|
|
const props = defineProps<{
|
|
endpoint: ApiEndpoint
|
|
origin: string
|
|
}>()
|
|
|
|
const open = ref(false)
|
|
const activeTab = ref('fetch')
|
|
|
|
const examples = computed(() => endpointExamples(props.endpoint, props.origin))
|
|
const rowLabel = computed(
|
|
() => `${props.endpoint.method} ${props.endpoint.path}. ${props.endpoint.description}`,
|
|
)
|
|
const bodyTitle = computed(() => {
|
|
if (props.endpoint.bodyKind === 'multipart') {
|
|
return t('Request body · multipart/form-data, 1 file field')
|
|
}
|
|
return props.endpoint.bodyKind === 'json' ? t('Request body · JSON') : t('Request body')
|
|
})
|
|
|
|
const snippetTabs = computed(() => [
|
|
{ k: 'fetch', label: 'fetch', code: examples.value.fetch },
|
|
{ k: 'curl', label: 'curl', code: examples.value.curl },
|
|
{ k: 'response', label: t('Response'), code: examples.value.response },
|
|
])
|
|
</script>
|
|
|
|
<template>
|
|
<UCollapsible
|
|
v-model:open="open"
|
|
:data-anchor="endpointAnchor(endpoint.id)"
|
|
class="scroll-mt-22 lg:scroll-mt-14 border-t border-default first:border-t-0"
|
|
:ui="{ content: 'border-t border-muted' }"
|
|
>
|
|
<button
|
|
type="button"
|
|
:aria-label="rowLabel"
|
|
class="flex min-h-11 w-full cursor-pointer flex-col items-stretch gap-1.5 px-3 py-3 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary lg:grid lg:grid-cols-[300px_minmax(0,1fr)_auto_16px] lg:items-center lg:gap-4 lg:px-4 lg:py-0"
|
|
:class="open ? 'bg-muted' : ''"
|
|
>
|
|
<span class="flex min-w-0 items-center gap-2.5">
|
|
<UBadge
|
|
:label="endpoint.method"
|
|
color="neutral"
|
|
variant="outline"
|
|
:class="METHOD_CLASS[endpoint.method]"
|
|
/>
|
|
<span class="min-w-0 truncate font-mono text-sm font-medium text-highlighted">{{
|
|
endpoint.path
|
|
}}</span>
|
|
<span class="flex-1 lg:hidden" />
|
|
<UIcon
|
|
:name="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
|
|
class="size-4 shrink-0 text-muted lg:hidden"
|
|
/>
|
|
</span>
|
|
<span class="min-w-0 text-toned lg:truncate">{{ endpoint.description }}</span>
|
|
<span class="flex flex-wrap gap-1.5">
|
|
<UBadge
|
|
:label="translate(endpoint.access)"
|
|
color="neutral"
|
|
variant="outline"
|
|
:class="
|
|
endpoint.access === 'Owner only' || endpoint.access === 'Owner or admin'
|
|
? CHIP_CLASS.strong
|
|
: CHIP_CLASS.neutral
|
|
"
|
|
/>
|
|
<UBadge
|
|
:label="t('API key OK')"
|
|
icon="i-lucide-check"
|
|
color="success"
|
|
variant="soft"
|
|
:class="CHIP_CLASS.success"
|
|
:ui="{ leadingIcon: 'size-3' }"
|
|
/>
|
|
</span>
|
|
<UIcon
|
|
:name="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
|
|
class="hidden size-4 text-muted lg:block"
|
|
/>
|
|
</button>
|
|
|
|
<template #content>
|
|
<div class="flex flex-col gap-3.5 px-3 pt-2.5 pb-4 lg:px-4">
|
|
<div class="flex flex-col gap-1.5">
|
|
<span class="font-semibold text-highlighted">{{ t('Query parameters') }}</span>
|
|
<ApiFieldTable v-if="endpoint.query.length > 0" :fields="endpoint.query" />
|
|
<span v-else class="text-muted">{{ t('No query parameters.') }}</span>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1.5">
|
|
<span class="font-semibold text-highlighted">{{ bodyTitle }}</span>
|
|
<ApiFieldTable v-if="endpoint.body.length > 0" :fields="endpoint.body" />
|
|
<span v-else class="text-muted">{{ t('No request body.') }}</span>
|
|
</div>
|
|
|
|
<CodeSnippet v-model:active="activeTab" :tabs="snippetTabs" />
|
|
|
|
<div v-if="endpoint.rateLimited" class="flex gap-2.5 text-toned">
|
|
<UIcon name="i-lucide-info" class="mt-0.5 size-4 shrink-0 text-info" />
|
|
<span class="text-pretty">{{ translate(RATE_LIMIT_SENTENCE) }}</span>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1.5">
|
|
<span class="font-semibold text-highlighted">{{ t('Errors') }}</span>
|
|
<div
|
|
v-for="error in endpoint.errors"
|
|
:key="`${error.status}-${error.caption}`"
|
|
:title="error.caption"
|
|
class="flex items-baseline gap-2.5"
|
|
>
|
|
<UBadge
|
|
:label="String(error.status)"
|
|
color="neutral"
|
|
variant="outline"
|
|
class="h-5 w-9 shrink-0 justify-center rounded-none border border-accented px-0 py-0 font-mono text-xs font-medium text-toned ring-0"
|
|
/>
|
|
<span class="min-w-0 text-pretty text-toned">{{ error.caption }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UCollapsible>
|
|
</template>
|