@kirchdev/duxt 0.3.4 → 0.3.5-dev-main.43-3310f4b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/app/app.vue +17 -11
- package/app/assets/css/duxt.css +47 -6
- package/app/components/DuxtAnnouncements.vue +117 -0
- package/app/components/DuxtBrunoEntries.vue +62 -0
- package/app/components/DuxtCodeBlock.vue +18 -34
- package/app/components/DuxtCodeToolbar.vue +14 -0
- package/app/components/DuxtCopyButton.vue +33 -0
- package/app/components/DuxtCopyPage.vue +32 -30
- package/app/components/DuxtFooter.vue +39 -8
- package/app/components/DuxtHeader.vue +107 -48
- package/app/components/DuxtLandingProse.vue +1 -1
- package/app/components/DuxtLinkLabel.vue +36 -0
- package/app/components/DuxtLiveWindow.vue +8 -8
- package/app/components/DuxtLocale.vue +10 -1
- package/app/components/DuxtNavigation.vue +2 -2
- package/app/components/DuxtOpenApiClient.vue +285 -90
- package/app/components/DuxtOpenApiMedia.vue +30 -18
- package/app/components/DuxtOpenApiSchema.vue +10 -7
- package/app/components/DuxtPageFeedback.vue +7 -0
- package/app/components/DuxtPageInfo.vue +15 -12
- package/app/components/DuxtPageNav.vue +41 -27
- package/app/components/DuxtPreviewSkeleton.vue +4 -4
- package/app/components/DuxtProgress.vue +2 -14
- package/app/components/DuxtSearch.vue +230 -90
- package/app/components/DuxtSearchInput.vue +70 -0
- package/app/components/DuxtSections.vue +11 -1
- package/app/components/DuxtSegmented.vue +64 -0
- package/app/components/DuxtShortcuts.vue +16 -11
- package/app/components/DuxtShortcutsTrigger.vue +49 -0
- package/app/components/DuxtSkipLink.vue +1 -1
- package/app/components/DuxtToc.vue +16 -10
- package/app/components/DuxtTranslationBanner.vue +1 -1
- package/app/components/DuxtVersion.vue +53 -12
- package/app/components/DuxtVersionBanner.vue +5 -5
- package/app/components/content/AccordionItem.vue +2 -2
- package/app/components/content/BrunoOverview.vue +260 -0
- package/app/components/content/BrunoRequest.vue +141 -0
- package/app/components/content/BrunoRequests.vue +104 -0
- package/app/components/content/Callout.vue +5 -5
- package/app/components/content/ChangelogReleases.vue +161 -40
- package/app/components/content/CodeGroup.vue +41 -37
- package/app/components/content/Field.vue +1 -1
- package/app/components/content/FileTree.vue +1 -1
- package/app/components/content/OpenApiOperation.vue +4 -4
- package/app/components/content/OpenApiOperations.vue +1 -1
- package/app/components/content/OpenApiOverview.vue +1 -1
- package/app/components/content/PackageManagers.vue +42 -58
- package/app/components/content/PageCards.vue +7 -4
- package/app/components/content/Preview.vue +15 -39
- package/app/components/content/ProseImg.vue +596 -49
- package/app/components/content/Since.vue +1 -1
- package/app/components/content/Steps.vue +6 -4
- package/app/components/content/TabGroup.vue +13 -12
- package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +1 -1
- package/app/components/ui/command/CommandShortcut.vue +1 -1
- package/app/components/ui/dialog/DialogContent.vue +2 -2
- package/app/components/ui/dialog/DialogHeader.vue +1 -1
- package/app/components/ui/dialog/DialogScrollContent.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +2 -2
- package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +1 -1
- package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +2 -2
- package/app/components/ui/navigation-menu/NavigationMenuContent.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +1 -1
- package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +1 -1
- package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
- package/app/components/ui/select/SelectItem.vue +2 -2
- package/app/components/ui/sheet/SheetContent.vue +29 -3
- package/app/components/ui/sidebar/Sidebar.vue +28 -4
- package/app/components/ui/sidebar/SidebarGroupAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarInset.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuAction.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuBadge.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSub.vue +1 -1
- package/app/components/ui/sidebar/SidebarMenuSubButton.vue +1 -1
- package/app/components/ui/sidebar/SidebarProvider.vue +7 -16
- package/app/components/ui/sidebar/index.ts +10 -2
- package/app/components/ui/sidebar/utils.ts +0 -1
- package/app/components/ui/sonner/Sonner.vue +18 -0
- package/app/components/ui/tabs/TabsList.vue +13 -3
- package/app/components/ui/tabs/TabsTrigger.vue +24 -3
- package/app/components/ui/tooltip/TooltipContent.vue +2 -2
- package/app/composables/useActiveHeading.ts +3 -25
- package/app/composables/useDuxtAnalytics.ts +50 -0
- package/app/composables/useDuxtAnimatedHeight.ts +8 -38
- package/app/composables/useDuxtAnnouncements.ts +153 -0
- package/app/composables/useDuxtCopy.ts +51 -0
- package/app/composables/useDuxtDirection.ts +23 -0
- package/app/composables/useDuxtNavigation.ts +28 -8
- package/app/composables/useDuxtPath.ts +3 -5
- package/app/composables/useDuxtSearch.ts +87 -35
- package/app/composables/useDuxtSection.ts +15 -1
- package/app/composables/useDuxtSectionRow.ts +6 -5
- package/app/composables/useDuxtShortcuts.ts +319 -20
- package/app/composables/useDuxtVersion.ts +57 -6
- package/app/composables/useDuxtViewportMeasure.ts +37 -0
- package/app/composables/useFuzzySearch.ts +9 -4
- package/app/composables/useRecentPages.ts +14 -17
- package/app/error.vue +112 -74
- package/app/layouts/changelog.vue +9 -1
- package/app/layouts/default.vue +9 -0
- package/app/layouts/docs.vue +9 -1
- package/app/layouts/reference.vue +9 -1
- package/app/middleware/duxt-section-layout.global.ts +1 -3
- package/app/pages/[...slug].vue +146 -35
- package/app/pages/index.vue +15 -23
- package/app/types/duxt.d.ts +312 -4
- package/app/utils/analytics.ts +91 -0
- package/app/utils/announcements.ts +205 -0
- package/app/utils/changelog.ts +0 -28
- package/app/utils/code-highlight.ts +3 -1
- package/app/utils/contributor-avatar.ts +27 -0
- package/app/utils/direction.ts +43 -0
- package/app/utils/duxt-config.ts +43 -0
- package/app/utils/duxt-text.ts +10 -0
- package/app/utils/format-date.ts +28 -0
- package/app/utils/generated-sections.ts +68 -12
- package/app/utils/generated-toc.ts +14 -7
- package/app/utils/locale-path.ts +43 -0
- package/app/utils/navigation-tree.ts +51 -1
- package/app/utils/nearest-page.ts +8 -0
- package/app/utils/openapi.ts +16 -11
- package/app/utils/page-controls.ts +215 -0
- package/app/utils/pill.ts +61 -0
- package/app/utils/prose-image.ts +128 -0
- package/app/utils/search-context.ts +110 -0
- package/app/utils/search-display.ts +248 -0
- package/app/utils/section-scope.ts +65 -4
- package/app/utils/stored-json.ts +31 -0
- package/app/utils/version-choices.ts +6 -13
- package/app/utils/version-paths.ts +21 -0
- package/bin/duxt-cache-key.mjs +61 -0
- package/bin/duxt-og-cache.mjs +58 -0
- package/bin/{duxt-report.mjs → duxt.mjs} +13 -7
- package/bruno-model.ts +169 -0
- package/bruno-parse.ts +656 -0
- package/bruno-zip.ts +195 -0
- package/cli.ts +240 -0
- package/duxt-app-config.ts +18 -1
- package/frontmatter.ts +49 -0
- package/git-contributors.ts +322 -0
- package/i18n/locales/de/duxt/announcement.json +7 -0
- package/i18n/locales/de/duxt/bruno.json +25 -0
- package/i18n/locales/de/duxt/changelog.json +1 -1
- package/i18n/locales/de/duxt/code.json +2 -1
- package/i18n/locales/de/duxt/page.json +9 -0
- package/i18n/locales/de/duxt/shortcuts.json +1 -0
- package/i18n/locales/de/duxt/theme.json +2 -1
- package/i18n/locales/en/duxt/announcement.json +7 -0
- package/i18n/locales/en/duxt/bruno.json +25 -0
- package/i18n/locales/en/duxt/changelog.json +1 -1
- package/i18n/locales/en/duxt/code.json +2 -1
- package/i18n/locales/en/duxt/page.json +9 -0
- package/i18n/locales/en/duxt/shortcuts.json +1 -0
- package/i18n/locales/en/duxt/theme.json +2 -1
- package/i18n/locales/es/duxt/announcement.json +7 -0
- package/i18n/locales/es/duxt/bruno.json +25 -0
- package/i18n/locales/es/duxt/changelog.json +1 -1
- package/i18n/locales/es/duxt/code.json +2 -1
- package/i18n/locales/es/duxt/page.json +9 -0
- package/i18n/locales/es/duxt/shortcuts.json +1 -0
- package/i18n/locales/es/duxt/theme.json +2 -1
- package/i18n/locales/fr/duxt/announcement.json +7 -0
- package/i18n/locales/fr/duxt/bruno.json +25 -0
- package/i18n/locales/fr/duxt/changelog.json +1 -1
- package/i18n/locales/fr/duxt/code.json +2 -1
- package/i18n/locales/fr/duxt/page.json +9 -0
- package/i18n/locales/fr/duxt/shortcuts.json +1 -0
- package/i18n/locales/fr/duxt/theme.json +2 -1
- package/i18n/locales/pt/duxt/announcement.json +7 -0
- package/i18n/locales/pt/duxt/bruno.json +25 -0
- package/i18n/locales/pt/duxt/changelog.json +1 -1
- package/i18n/locales/pt/duxt/code.json +2 -1
- package/i18n/locales/pt/duxt/page.json +9 -0
- package/i18n/locales/pt/duxt/shortcuts.json +1 -0
- package/i18n/locales/pt/duxt/theme.json +2 -1
- package/modules/bruno.ts +118 -0
- package/modules/config.ts +26 -0
- package/modules/devtools.ts +2 -3
- package/modules/git-meta.ts +16 -47
- package/modules/search-records.ts +132 -0
- package/nuxt.config.ts +81 -7
- package/og-image-cache.ts +742 -0
- package/package.json +41 -9
- package/public/devtools/checks.html +2 -2
- package/public/devtools/config.html +59 -4
- package/public/devtools/pages.html +8 -1
- package/public/devtools/paths.html +1 -1
- package/public/devtools/sources.html +2 -2
- package/public/devtools/versions.html +3 -0
- package/report.ts +28 -19
- package/search-records.ts +380 -0
- package/section-input.ts +142 -0
- package/section-markdown.ts +67 -0
- package/section-reports.ts +10 -4
- package/sections-bruno.ts +571 -0
- package/sections-changelog.ts +76 -43
- package/sections-openapi.ts +12 -83
- package/sections-resolve.ts +174 -8
- package/sections.ts +33 -10
- package/server/devtools/shell.ts +1 -1
- package/server/mcp/tools/list-pages.ts +77 -36
- package/server/mcp/tools/list-versions.ts +84 -0
- package/server/mcp/tools/read-page.ts +80 -26
- package/server/mcp/tools/search-docs.ts +87 -53
- package/server/middleware/raw-markdown.ts +1 -1
- package/server/routes/llms-full.txt.get.ts +6 -8
- package/server/routes/llms.txt.get.ts +1 -1
- package/server/utils/duxt-server-text.ts +0 -13
- package/server/utils/llms-pages.ts +24 -9
- package/server/utils/mcp-docs.ts +577 -0
- package/sources-cache.ts +376 -0
- package/sources-git.ts +186 -31
- package/sources-resolve.ts +59 -1
- package/sources.ts +122 -13
- package/tfplugindocs.ts +158 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* The visible way into the shortcut sheet.
|
|
4
|
+
*
|
|
5
|
+
* `?` opens the sheet, and a reader can only press `?` if they already know the
|
|
6
|
+
* sheet is there — which is the whole discoverability problem, and one no
|
|
7
|
+
* keystroke can solve for itself. So the keys get a control: an icon beside the
|
|
8
|
+
* search, in the header, on every page.
|
|
9
|
+
*
|
|
10
|
+
* It draws the key it duplicates rather than restating it, and draws nothing
|
|
11
|
+
* where `shortcuts.singleCharacter: false` has unbound `?` — in which case this
|
|
12
|
+
* button is not a shortcut for the sheet, it is the only door to it.
|
|
13
|
+
*/
|
|
14
|
+
defineOptions({
|
|
15
|
+
inheritAttrs: false
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const { open } = useDuxtShortcutSheet();
|
|
19
|
+
const { hint } = useDuxtShortcuts();
|
|
20
|
+
const helpHint = computed(() => hint('help'));
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<UiTooltip>
|
|
25
|
+
<UiTooltipTrigger as-child>
|
|
26
|
+
<UiButton
|
|
27
|
+
v-bind="$attrs"
|
|
28
|
+
variant="ghost"
|
|
29
|
+
size="icon"
|
|
30
|
+
:aria-label="$t('duxt.shortcuts.open')"
|
|
31
|
+
:aria-keyshortcuts="helpHint"
|
|
32
|
+
@click="open = !open"
|
|
33
|
+
>
|
|
34
|
+
<Icon name="lucide:keyboard" class="size-4" />
|
|
35
|
+
</UiButton>
|
|
36
|
+
</UiTooltipTrigger>
|
|
37
|
+
|
|
38
|
+
<UiTooltipContent side="bottom" class="flex items-center gap-2">
|
|
39
|
+
{{ $t('duxt.shortcuts.open') }}
|
|
40
|
+
<kbd
|
|
41
|
+
v-if="helpHint"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
class="rounded border border-neutral-50/25 px-1.5 font-mono text-[10px]"
|
|
44
|
+
>
|
|
45
|
+
{{ helpHint }}
|
|
46
|
+
</kbd>
|
|
47
|
+
</UiTooltipContent>
|
|
48
|
+
</UiTooltip>
|
|
49
|
+
</template>
|
|
@@ -26,7 +26,7 @@ function jump(event: MouseEvent) {
|
|
|
26
26
|
<template>
|
|
27
27
|
<a
|
|
28
28
|
:href="target"
|
|
29
|
-
class="sr-only rounded-md bg-background px-4 py-2 text-sm font-medium ring-2 ring-ring focus:not-sr-only focus:fixed focus:top-3 focus:
|
|
29
|
+
class="sr-only rounded-md bg-background px-4 py-2 text-sm font-medium ring-2 ring-ring focus:not-sr-only focus:fixed focus:top-3 focus:start-3 focus:z-100"
|
|
30
30
|
@click="jump"
|
|
31
31
|
>
|
|
32
32
|
{{ $t('duxt.nav.skip') }}
|
|
@@ -19,18 +19,18 @@ const ids = computed(() =>
|
|
|
19
19
|
])
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
const active = useActiveHeading(ids);
|
|
22
|
+
const active = useActiveHeading(ids, duxt.toc?.scrollOffset);
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<template>
|
|
26
26
|
<div class="space-y-8 text-sm">
|
|
27
27
|
<nav v-if="links?.length" :aria-label="$t('duxt.toc.title')">
|
|
28
28
|
<p class="mb-3 font-medium">{{ $t('duxt.toc.title') }}</p>
|
|
29
|
-
<ul class="space-y-1 border-
|
|
29
|
+
<ul class="space-y-1 border-s">
|
|
30
30
|
<li v-for="link in links" :key="link.id">
|
|
31
31
|
<a
|
|
32
32
|
:href="`#${link.id}`"
|
|
33
|
-
class="-
|
|
33
|
+
class="-ms-px block border-s py-1 ps-4 transition-colors"
|
|
34
34
|
:class="
|
|
35
35
|
active === link.id
|
|
36
36
|
? 'border-primary text-foreground'
|
|
@@ -43,7 +43,7 @@ const active = useActiveHeading(ids);
|
|
|
43
43
|
<li v-for="child in link.children" :key="child.id">
|
|
44
44
|
<a
|
|
45
45
|
:href="`#${child.id}`"
|
|
46
|
-
class="-
|
|
46
|
+
class="-ms-px block border-s py-1 ps-7 transition-colors"
|
|
47
47
|
:class="
|
|
48
48
|
active === child.id
|
|
49
49
|
? 'border-primary text-foreground'
|
|
@@ -69,15 +69,21 @@ const active = useActiveHeading(ids);
|
|
|
69
69
|
<NuxtLink
|
|
70
70
|
:to="localeLink(link.to)"
|
|
71
71
|
:target="link.external ? '_blank' : undefined"
|
|
72
|
-
class="flex items-
|
|
72
|
+
class="flex items-start gap-2 text-muted-foreground transition-colors hover:text-foreground"
|
|
73
73
|
>
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
<!-- `mt-0.5` centres the icon on the FIRST line, not on a label
|
|
75
|
+
that wrapped onto two. -->
|
|
76
76
|
<Icon
|
|
77
|
-
v-if="link.
|
|
78
|
-
name="
|
|
79
|
-
class="size-
|
|
77
|
+
v-if="link.icon"
|
|
78
|
+
:name="link.icon"
|
|
79
|
+
class="mt-0.5 size-4 shrink-0"
|
|
80
80
|
/>
|
|
81
|
+
<!-- The arrow stays with the label's last word: as a flex sibling
|
|
82
|
+
it was pushed to the row's far edge whenever the label
|
|
83
|
+
wrapped. See DuxtLinkLabel. -->
|
|
84
|
+
<span class="min-w-0">
|
|
85
|
+
<DuxtLinkLabel :label="link.label" :external="link.external" />
|
|
86
|
+
</span>
|
|
81
87
|
</NuxtLink>
|
|
82
88
|
</li>
|
|
83
89
|
</ul>
|
|
@@ -44,7 +44,7 @@ const shown = computed(() => {
|
|
|
44
44
|
information about the page rather than a warning about its content. -->
|
|
45
45
|
<UiAlert
|
|
46
46
|
role="status"
|
|
47
|
-
class="mb-6 flex items-start gap-2.5 rounded-md border border-
|
|
47
|
+
class="mb-6 flex items-start gap-2.5 rounded-md border border-s-2 border-s-sky-500 bg-muted/30 px-3 py-2.5"
|
|
48
48
|
>
|
|
49
49
|
<Icon
|
|
50
50
|
name="lucide:languages"
|
|
@@ -35,13 +35,6 @@ const { source } = useDuxtCollection();
|
|
|
35
35
|
*/
|
|
36
36
|
const { choices: versions } = useDuxtVersion();
|
|
37
37
|
|
|
38
|
-
/** A global changelog switches between its source's documentation editions. */
|
|
39
|
-
const changelog = computed(
|
|
40
|
-
() =>
|
|
41
|
-
source.value?.generated?.type === 'changelog' &&
|
|
42
|
-
source.value.generated.versioning === 'global'
|
|
43
|
-
);
|
|
44
|
-
|
|
45
38
|
const current = computed(() =>
|
|
46
39
|
sourceForPath(
|
|
47
40
|
path.value,
|
|
@@ -69,14 +62,62 @@ function caption(version: DuxtLink) {
|
|
|
69
62
|
return te(value) ? t(value) : value;
|
|
70
63
|
}
|
|
71
64
|
|
|
72
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* The pages this one would become in each version, checked where they may not
|
|
67
|
+
* exist.
|
|
68
|
+
*
|
|
69
|
+
* Only inside a per-version CHANGELOG, and only below its overview: a release
|
|
70
|
+
* page carries a release slug, and an older version's changelog has no page
|
|
71
|
+
* for a release cut after it. Those are looked up in the target's own
|
|
72
|
+
* collection; any other page keeps its place exactly as `versionPath` puts it,
|
|
73
|
+
* because every other per-version tree is the same tree at another ref.
|
|
74
|
+
*
|
|
75
|
+
* Nothing is read after the first `await` but plain values captured before it —
|
|
76
|
+
* the rule `tests/async-data-context.test.ts` holds.
|
|
77
|
+
*/
|
|
78
|
+
const { data: missing } = useAsyncData(
|
|
79
|
+
() => `duxt-version-missing-${path.value}`,
|
|
80
|
+
async () => {
|
|
81
|
+
const sources = duxt.resolvedSources ?? [];
|
|
82
|
+
const own = source.value;
|
|
83
|
+
if (own?.generated?.type !== 'changelog' || path.value === own.prefix) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const gone: string[] = [];
|
|
88
|
+
|
|
89
|
+
for (const version of versions.value) {
|
|
90
|
+
const target = versionPath(
|
|
91
|
+
path.value,
|
|
92
|
+
current.value?.to,
|
|
93
|
+
version.to ?? '/'
|
|
94
|
+
);
|
|
95
|
+
const owner = sourceForPath(target, sources);
|
|
96
|
+
if (!owner || owner.generated?.type !== 'changelog') continue;
|
|
97
|
+
|
|
98
|
+
const hit = await queryCollection(owner.collection as DuxtCollectionArg)
|
|
99
|
+
.path(target)
|
|
100
|
+
.select('path')
|
|
101
|
+
.first();
|
|
102
|
+
if (!hit) gone.push(target);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return gone;
|
|
106
|
+
},
|
|
107
|
+
{ default: () => [], watch: [path] }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Same page, other version: swap the prefix rather than jumping to its root —
|
|
112
|
+
* unless the target never published that page, in which case its own root.
|
|
113
|
+
*/
|
|
73
114
|
function pathIn(version: { to?: string }) {
|
|
74
115
|
return localeLink(
|
|
75
|
-
|
|
116
|
+
versionSwitchPath(
|
|
76
117
|
path.value,
|
|
77
118
|
current.value?.to,
|
|
78
119
|
version.to ?? '/',
|
|
79
|
-
|
|
120
|
+
new Set(missing.value)
|
|
80
121
|
)
|
|
81
122
|
);
|
|
82
123
|
}
|
|
@@ -99,7 +140,7 @@ function pathIn(version: { to?: string }) {
|
|
|
99
140
|
<button
|
|
100
141
|
v-if="props.variant === 'block'"
|
|
101
142
|
type="button"
|
|
102
|
-
class="flex w-full cursor-pointer items-center gap-2 rounded-md border p-2 text-
|
|
143
|
+
class="flex w-full cursor-pointer items-center gap-2 rounded-md border p-2 text-start transition-colors hover:bg-accent"
|
|
103
144
|
>
|
|
104
145
|
<span
|
|
105
146
|
class="flex aspect-square size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground"
|
|
@@ -160,7 +201,7 @@ function pathIn(version: { to?: string }) {
|
|
|
160
201
|
<span class="font-mono text-xs">{{ version.label }}</span>
|
|
161
202
|
<span
|
|
162
203
|
v-if="caption(version)"
|
|
163
|
-
class="
|
|
204
|
+
class="ms-auto text-xs text-muted-foreground"
|
|
164
205
|
>
|
|
165
206
|
{{ caption(version) }}
|
|
166
207
|
</span>
|
|
@@ -41,22 +41,22 @@ const offersUpgrade = computed(() =>
|
|
|
41
41
|
*/
|
|
42
42
|
const TONES = {
|
|
43
43
|
upcoming: {
|
|
44
|
-
rule: 'border-
|
|
44
|
+
rule: 'border-s-sky-500',
|
|
45
45
|
text: 'text-sky-500',
|
|
46
46
|
icon: 'lucide:flask-conical'
|
|
47
47
|
},
|
|
48
48
|
old: {
|
|
49
|
-
rule: 'border-
|
|
49
|
+
rule: 'border-s-amber-500',
|
|
50
50
|
text: 'text-amber-500',
|
|
51
51
|
icon: 'lucide:triangle-alert'
|
|
52
52
|
},
|
|
53
53
|
deprecated: {
|
|
54
|
-
rule: 'border-
|
|
54
|
+
rule: 'border-s-red-500',
|
|
55
55
|
text: 'text-red-500',
|
|
56
56
|
icon: 'lucide:triangle-alert'
|
|
57
57
|
},
|
|
58
58
|
eol: {
|
|
59
|
-
rule: 'border-
|
|
59
|
+
rule: 'border-s-red-500',
|
|
60
60
|
text: 'text-red-500',
|
|
61
61
|
icon: 'lucide:circle-off'
|
|
62
62
|
}
|
|
@@ -74,7 +74,7 @@ const tone = computed(
|
|
|
74
74
|
<UiAlert
|
|
75
75
|
v-if="shouldWarn"
|
|
76
76
|
role="status"
|
|
77
|
-
class="mb-6 flex items-start gap-2.5 rounded-md border border-
|
|
77
|
+
class="mb-6 flex items-start gap-2.5 rounded-md border border-s-2 bg-muted/30 px-3 py-2.5"
|
|
78
78
|
:class="tone.rule"
|
|
79
79
|
>
|
|
80
80
|
<Icon
|
|
@@ -21,13 +21,13 @@ const value = parent?.register(props.label ?? '') ?? 'item-0';
|
|
|
21
21
|
<Item :value="value" class="border-b last:border-b-0">
|
|
22
22
|
<AccordionHeader>
|
|
23
23
|
<AccordionTrigger
|
|
24
|
-
class="group flex w-full cursor-pointer items-center gap-2 px-4 py-3 text-
|
|
24
|
+
class="group flex w-full cursor-pointer items-center gap-2 px-4 py-3 text-start text-sm font-medium hover:bg-accent/50"
|
|
25
25
|
>
|
|
26
26
|
<Icon v-if="icon" :name="icon" class="size-4 text-muted-foreground" />
|
|
27
27
|
<span class="min-w-0">{{ label }}</span>
|
|
28
28
|
<Icon
|
|
29
29
|
name="lucide:chevron-down"
|
|
30
|
-
class="
|
|
30
|
+
class="ms-auto size-4 shrink-0 text-muted-foreground transition-transform group-data-[state=open]:rotate-180"
|
|
31
31
|
/>
|
|
32
32
|
</AccordionTrigger>
|
|
33
33
|
</AccordionHeader>
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DuxtBrunoAuth, DuxtBrunoEntry } from '../../../bruno-model';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The collection's front page: what is in it, and how to get it.
|
|
6
|
+
*
|
|
7
|
+
* MDC: `::bruno-overview` on the section's index page, with `collection.bru`'s
|
|
8
|
+
* own `docs` block in the slot.
|
|
9
|
+
*
|
|
10
|
+
* THE DOWNLOAD IS THE POINT, and it is why this page is not simply a table of
|
|
11
|
+
* contents. A reference tells you what the requests are; a Bruno collection is
|
|
12
|
+
* a thing you OPEN, and a reader who wants the requests wants the files. The
|
|
13
|
+
* archive is built from the exact version and locale of the page they are on —
|
|
14
|
+
* which is what "Fetch in Bruno" below it cannot offer, since it clones
|
|
15
|
+
* whatever the repository's HEAD is today, and the page says so rather than
|
|
16
|
+
* offering the two as if they were the same thing.
|
|
17
|
+
*
|
|
18
|
+
* The Bruno button is a PLAIN LINK. Bruno publishes a `button.js` that draws
|
|
19
|
+
* one; loading it would put a third-party script on every page of a
|
|
20
|
+
* documentation site to render an anchor this site can render itself.
|
|
21
|
+
*/
|
|
22
|
+
const props = defineProps<{
|
|
23
|
+
name?: string;
|
|
24
|
+
version?: string;
|
|
25
|
+
auth?: DuxtBrunoAuth;
|
|
26
|
+
headers?: DuxtBrunoEntry[];
|
|
27
|
+
/** How many environment files the collection has — counted, never read. */
|
|
28
|
+
environments?: number;
|
|
29
|
+
/** Absent for a remote collection: nothing outside can build the archive. */
|
|
30
|
+
download?: string;
|
|
31
|
+
/** Absent unless the site declared a public git URL. */
|
|
32
|
+
fetch?: string;
|
|
33
|
+
groups?: {
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
requests?: number;
|
|
37
|
+
to: string;
|
|
38
|
+
}[];
|
|
39
|
+
requests?: { name: string; method: string; url: string; to: string }[];
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
const localeLink = useDuxtLink();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Every request in the collection, its folders' included.
|
|
46
|
+
*
|
|
47
|
+
* Counted here rather than written into the props, because the figure has to
|
|
48
|
+
* agree with the two lists below it on this very page — and a number the parser
|
|
49
|
+
* sent separately is a number that can disagree with them.
|
|
50
|
+
*/
|
|
51
|
+
const total = computed(
|
|
52
|
+
() =>
|
|
53
|
+
(props.requests?.length ?? 0) +
|
|
54
|
+
(props.groups ?? []).reduce((all, group) => all + (group.requests ?? 0), 0)
|
|
55
|
+
);
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div>
|
|
60
|
+
<slot />
|
|
61
|
+
|
|
62
|
+
<div class="not-typeset">
|
|
63
|
+
<!-- ACQUISITION FIRST. Everything below is the table of contents, and a
|
|
64
|
+
reader who came for the collection itself should not have to scroll
|
|
65
|
+
past it. -->
|
|
66
|
+
<section v-if="download || fetch" class="mt-8">
|
|
67
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.getIt') }}</h2>
|
|
68
|
+
|
|
69
|
+
<div class="mt-3 flex flex-wrap items-center gap-3">
|
|
70
|
+
<UiButton v-if="download" as-child variant="default">
|
|
71
|
+
<a :href="download" download>
|
|
72
|
+
<Icon name="lucide:download" aria-hidden="true" class="size-4" />
|
|
73
|
+
{{ $t('duxt.bruno.download') }}
|
|
74
|
+
</a>
|
|
75
|
+
</UiButton>
|
|
76
|
+
|
|
77
|
+
<!-- `simple-icons`, and the repository's icon rule says exactly why:
|
|
78
|
+
this is a third party AS ITSELF — the button hands the collection
|
|
79
|
+
to Bruno — where the section's own navbar entry, which names a
|
|
80
|
+
part of the documentation, is `lucide`. -->
|
|
81
|
+
<UiButton v-if="fetch" as-child variant="outline">
|
|
82
|
+
<a :href="fetch" rel="noopener noreferrer" target="_blank">
|
|
83
|
+
<Icon
|
|
84
|
+
name="simple-icons:bruno"
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
class="size-4"
|
|
87
|
+
/>
|
|
88
|
+
{{ $t('duxt.bruno.fetch') }}
|
|
89
|
+
</a>
|
|
90
|
+
</UiButton>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<p v-if="download" class="mt-2 text-sm text-muted-foreground">
|
|
94
|
+
{{ $t('duxt.bruno.downloadHint') }}
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<p v-if="fetch" class="mt-1 text-sm text-muted-foreground">
|
|
98
|
+
{{ $t('duxt.bruno.fetchHint') }}
|
|
99
|
+
</p>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<dl
|
|
103
|
+
v-if="version || groups?.length || environments"
|
|
104
|
+
class="mt-8 grid grid-cols-2 gap-x-6 gap-y-7 border-b pb-5 sm:grid-cols-4"
|
|
105
|
+
>
|
|
106
|
+
<div v-if="version" class="flex flex-col gap-1">
|
|
107
|
+
<dd
|
|
108
|
+
class="font-mono text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
109
|
+
>
|
|
110
|
+
{{ version }}
|
|
111
|
+
</dd>
|
|
112
|
+
<dt
|
|
113
|
+
class="flex items-center gap-1.5 text-xs font-medium text-primary"
|
|
114
|
+
>
|
|
115
|
+
<Icon name="lucide:tag" class="size-3.5" />
|
|
116
|
+
{{ $t('duxt.bruno.format') }}
|
|
117
|
+
</dt>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="flex flex-col gap-1">
|
|
121
|
+
<dd
|
|
122
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
123
|
+
>
|
|
124
|
+
{{ total }}
|
|
125
|
+
</dd>
|
|
126
|
+
<dt
|
|
127
|
+
class="flex items-center gap-1.5 text-xs font-medium text-primary"
|
|
128
|
+
>
|
|
129
|
+
<Icon name="lucide:arrow-left-right" class="size-3.5" />
|
|
130
|
+
{{ $t('duxt.bruno.requests') }}
|
|
131
|
+
</dt>
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<div v-if="groups?.length" class="flex flex-col gap-1">
|
|
135
|
+
<dd
|
|
136
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
137
|
+
>
|
|
138
|
+
{{ groups.length }}
|
|
139
|
+
</dd>
|
|
140
|
+
<dt
|
|
141
|
+
class="flex items-center gap-1.5 text-xs font-medium text-primary"
|
|
142
|
+
>
|
|
143
|
+
<Icon name="lucide:folder-tree" class="size-3.5" />
|
|
144
|
+
{{ $t('duxt.bruno.folders') }}
|
|
145
|
+
</dt>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div v-if="environments" class="flex flex-col gap-1">
|
|
149
|
+
<dd
|
|
150
|
+
class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
|
|
151
|
+
>
|
|
152
|
+
{{ environments }}
|
|
153
|
+
</dd>
|
|
154
|
+
<dt
|
|
155
|
+
class="flex items-center gap-1.5 text-xs font-medium text-primary"
|
|
156
|
+
>
|
|
157
|
+
<Icon name="lucide:lock" class="size-3.5" />
|
|
158
|
+
{{ $t('duxt.bruno.environments') }}
|
|
159
|
+
</dt>
|
|
160
|
+
</div>
|
|
161
|
+
</dl>
|
|
162
|
+
|
|
163
|
+
<section v-if="groups?.length" class="mt-8">
|
|
164
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.folders') }}</h2>
|
|
165
|
+
|
|
166
|
+
<ul class="mt-3 -mx-3">
|
|
167
|
+
<li
|
|
168
|
+
v-for="group in groups"
|
|
169
|
+
:key="group.to"
|
|
170
|
+
class="border-t border-border/60 first:border-t-0"
|
|
171
|
+
>
|
|
172
|
+
<NuxtLink
|
|
173
|
+
:to="localeLink(group.to) ?? group.to"
|
|
174
|
+
class="group flex items-start gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40"
|
|
175
|
+
>
|
|
176
|
+
<span class="min-w-0 flex-1">
|
|
177
|
+
<span class="flex flex-wrap items-baseline gap-x-3">
|
|
178
|
+
<span class="font-medium">{{ group.name }}</span>
|
|
179
|
+
<span class="text-xs text-muted-foreground tabular-nums">
|
|
180
|
+
{{ group.requests }}
|
|
181
|
+
</span>
|
|
182
|
+
</span>
|
|
183
|
+
|
|
184
|
+
<span
|
|
185
|
+
v-if="group.description"
|
|
186
|
+
class="mt-1 line-clamp-2 block text-sm text-muted-foreground"
|
|
187
|
+
>
|
|
188
|
+
{{ group.description }}
|
|
189
|
+
</span>
|
|
190
|
+
</span>
|
|
191
|
+
|
|
192
|
+
<Icon
|
|
193
|
+
name="lucide:arrow-right"
|
|
194
|
+
aria-hidden="true"
|
|
195
|
+
class="mt-1 size-3.5 shrink-0 text-muted-foreground rtl:-scale-x-100 transition-transform ltr:group-hover:translate-x-0.5 rtl:group-hover:-translate-x-0.5"
|
|
196
|
+
/>
|
|
197
|
+
</NuxtLink>
|
|
198
|
+
</li>
|
|
199
|
+
</ul>
|
|
200
|
+
</section>
|
|
201
|
+
|
|
202
|
+
<section v-if="requests?.length" class="mt-8">
|
|
203
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.requests') }}</h2>
|
|
204
|
+
|
|
205
|
+
<ul class="mt-3 -mx-3">
|
|
206
|
+
<li
|
|
207
|
+
v-for="entry in requests"
|
|
208
|
+
:key="entry.to"
|
|
209
|
+
class="border-t border-border/60 first:border-t-0"
|
|
210
|
+
>
|
|
211
|
+
<NuxtLink
|
|
212
|
+
:to="localeLink(entry.to) ?? entry.to"
|
|
213
|
+
class="flex flex-wrap items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40"
|
|
214
|
+
>
|
|
215
|
+
<DuxtOpenApiMethod :method="entry.method" class="w-16" />
|
|
216
|
+
|
|
217
|
+
<span class="font-medium sm:me-auto">{{ entry.name }}</span>
|
|
218
|
+
|
|
219
|
+
<!-- The auto margin is on the NAME above, not here: this is a
|
|
220
|
+
`<code>`, which the stylesheet pins to left-to-right, and a
|
|
221
|
+
`ms-auto` resolved against that would push the URL back to
|
|
222
|
+
the start of a right-to-left row. -->
|
|
223
|
+
<code
|
|
224
|
+
class="w-full font-mono text-xs break-all text-muted-foreground sm:w-auto"
|
|
225
|
+
>
|
|
226
|
+
{{ entry.url }}
|
|
227
|
+
</code>
|
|
228
|
+
</NuxtLink>
|
|
229
|
+
</li>
|
|
230
|
+
</ul>
|
|
231
|
+
</section>
|
|
232
|
+
|
|
233
|
+
<section v-if="auth || headers?.length" class="mt-8">
|
|
234
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.shared') }}</h2>
|
|
235
|
+
|
|
236
|
+
<p v-if="auth" class="mt-2 text-sm text-muted-foreground">
|
|
237
|
+
{{ $t('duxt.bruno.auth') }}:
|
|
238
|
+
<code class="font-mono">{{ auth.mode }}</code>
|
|
239
|
+
</p>
|
|
240
|
+
|
|
241
|
+
<div v-if="headers?.length" class="mt-3">
|
|
242
|
+
<DuxtBrunoEntries :entries="headers" />
|
|
243
|
+
</div>
|
|
244
|
+
</section>
|
|
245
|
+
|
|
246
|
+
<!-- STATED, ALWAYS. A reader has to know that what they are looking at is
|
|
247
|
+
less than the collection: the environments hold the hosts and the
|
|
248
|
+
keys, and the scripts and tests are code. None of it is on this site,
|
|
249
|
+
and saying so is what stops the page reading as the whole truth. -->
|
|
250
|
+
<p class="mt-8 flex items-start gap-2 text-sm text-muted-foreground">
|
|
251
|
+
<Icon
|
|
252
|
+
name="lucide:shield"
|
|
253
|
+
aria-hidden="true"
|
|
254
|
+
class="mt-0.5 size-4 shrink-0"
|
|
255
|
+
/>
|
|
256
|
+
<span>{{ $t('duxt.bruno.notPublished') }}</span>
|
|
257
|
+
</p>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</template>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { DuxtBrunoRequest } from '../../../bruno-model';
|
|
3
|
+
import type {
|
|
4
|
+
DuxtOpenApiOperation,
|
|
5
|
+
DuxtOpenApiServer
|
|
6
|
+
} from '../../../openapi-model';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One Bruno request, as the two columns of the reference.
|
|
10
|
+
*
|
|
11
|
+
* MDC: the `bruno` section type writes `::bruno-request` into every request
|
|
12
|
+
* page, with the structure as props and the file's own `docs` block in the
|
|
13
|
+
* slot — Markdown, because that is what a `docs` block is.
|
|
14
|
+
*
|
|
15
|
+
* THE SAME TWO COLUMNS `OpenApiOperation` DRAWS, and that is the answer this
|
|
16
|
+
* type was filed to find: a second API section is not a second rendering path.
|
|
17
|
+
* The left column is what the collection says; the right is the existing try-it
|
|
18
|
+
* client, handed an operation the parser adapted at build time.
|
|
19
|
+
*
|
|
20
|
+
* WHAT IS NOT HERE is the honest half. A Bruno collection has no response
|
|
21
|
+
* schemas, no reusable components and no field descriptions, so the page has no
|
|
22
|
+
* "Responses" section and no schema tree — and does not invent one. Scripts and
|
|
23
|
+
* tests are reported as PRESENT and never shown: they are code written against
|
|
24
|
+
* Bruno's runtime, and a documentation site neither prints nor runs it.
|
|
25
|
+
*/
|
|
26
|
+
defineProps<{
|
|
27
|
+
request: DuxtBrunoRequest;
|
|
28
|
+
/** Present only where the site opted into a try-it client. */
|
|
29
|
+
operation?: DuxtOpenApiOperation;
|
|
30
|
+
servers?: DuxtOpenApiServer[];
|
|
31
|
+
}>();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<!-- The same grid `OpenApiOperation` uses, and deliberately the same numbers:
|
|
36
|
+
the two page kinds sit in one section row, and a reader moving between
|
|
37
|
+
them should not feel the column move. -->
|
|
38
|
+
<div
|
|
39
|
+
class="grid gap-8 xl:grid-cols-[minmax(0,1fr)_23rem] 2xl:grid-cols-[minmax(0,1fr)_30rem]"
|
|
40
|
+
>
|
|
41
|
+
<div class="min-w-0">
|
|
42
|
+
<div class="not-typeset flex flex-wrap items-center gap-2">
|
|
43
|
+
<DuxtOpenApiMethod :method="request.method" class="h-6 px-2" />
|
|
44
|
+
|
|
45
|
+
<code class="font-mono text-sm break-all">{{ request.url }}</code>
|
|
46
|
+
|
|
47
|
+
<!-- The url gets the same badge the entries table gives a withheld
|
|
48
|
+
value, and for the same reason: a blank `?api_key=` reads as a
|
|
49
|
+
parameter the collection forgot to fill in unless the page says
|
|
50
|
+
it was taken out. -->
|
|
51
|
+
<UiBadge v-if="request.urlRedacted" variant="secondary">
|
|
52
|
+
{{ $t('duxt.bruno.withheld') }}
|
|
53
|
+
</UiBadge>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="mt-4">
|
|
57
|
+
<slot />
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div class="not-typeset">
|
|
61
|
+
<section v-if="request.auth" class="mt-8">
|
|
62
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.auth') }}</h2>
|
|
63
|
+
|
|
64
|
+
<p class="mt-2 text-sm text-muted-foreground">
|
|
65
|
+
<code class="font-mono">{{ request.auth.mode }}</code>
|
|
66
|
+
</p>
|
|
67
|
+
</section>
|
|
68
|
+
|
|
69
|
+
<section v-if="request.params?.length" class="mt-8">
|
|
70
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.parameters') }}</h2>
|
|
71
|
+
|
|
72
|
+
<div class="mt-3">
|
|
73
|
+
<DuxtBrunoEntries :entries="request.params" />
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<section v-if="request.headers?.length" class="mt-8">
|
|
78
|
+
<h2 class="duxt-label">{{ $t('duxt.bruno.headers') }}</h2>
|
|
79
|
+
|
|
80
|
+
<div class="mt-3">
|
|
81
|
+
<DuxtBrunoEntries :entries="request.headers" />
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
|
|
85
|
+
<section v-if="request.body" class="mt-8">
|
|
86
|
+
<h2 class="duxt-label">
|
|
87
|
+
{{ $t('duxt.bruno.body') }}
|
|
88
|
+
<span class="ms-1 font-mono text-xs text-muted-foreground">
|
|
89
|
+
{{ request.body.type }}
|
|
90
|
+
</span>
|
|
91
|
+
</h2>
|
|
92
|
+
|
|
93
|
+
<div class="mt-3">
|
|
94
|
+
<DuxtCodeBlock
|
|
95
|
+
v-if="request.body.text"
|
|
96
|
+
:code="request.body.text"
|
|
97
|
+
:language="request.body.language ?? 'text'"
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<DuxtBrunoEntries v-else :entries="request.body.entries" />
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div v-if="request.body.variables" class="mt-3">
|
|
104
|
+
<DuxtCodeBlock :code="request.body.variables" language="json" />
|
|
105
|
+
</div>
|
|
106
|
+
</section>
|
|
107
|
+
|
|
108
|
+
<!-- SAID, NOT SHOWN. A reader has to know the page is not the whole
|
|
109
|
+
request — a collection whose pre-request script signs a token
|
|
110
|
+
behaves differently from what is printed above — and the code is
|
|
111
|
+
neither published nor executed. The download is where it lives. -->
|
|
112
|
+
<p
|
|
113
|
+
v-if="request.script || request.tests"
|
|
114
|
+
class="mt-8 flex items-start gap-2 text-sm text-muted-foreground"
|
|
115
|
+
>
|
|
116
|
+
<Icon
|
|
117
|
+
name="lucide:file-code"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
class="mt-0.5 size-4 shrink-0"
|
|
120
|
+
/>
|
|
121
|
+
<span>{{ $t('duxt.bruno.hasCode') }}</span>
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
<!-- Only where the site opted in. Without a declared public base URL the
|
|
127
|
+
page is static, which is the decision rather than a gap: a Bruno
|
|
128
|
+
collection points at whatever its author could reach, and a send button
|
|
129
|
+
aimed at somebody's staging host is a failure the page cannot explain. -->
|
|
130
|
+
<div
|
|
131
|
+
v-if="operation"
|
|
132
|
+
class="not-typeset min-w-0 scroll-mt-[var(--duxt-header-offset)]"
|
|
133
|
+
>
|
|
134
|
+
<div
|
|
135
|
+
class="xl:sticky xl:top-[calc(var(--duxt-header-offset)+1rem)] xl:max-h-[calc(100vh-var(--duxt-header-offset)-2.5rem)] xl:overflow-y-auto xl:pe-1"
|
|
136
|
+
>
|
|
137
|
+
<DuxtOpenApiClient :operation="operation" :servers="servers" />
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|