Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ NUXT_PUBLIC_PRODUCT_DIRECTUS_URL=your_url_here
NUXT_IMAGE_DOMAINS=your_url_here
ALGOLIA_API_KEY=
ALGOLIA_APPLICATION_ID=
DIRECTUS_URL=
7 changes: 4 additions & 3 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { spec } from '@directus/openapi';
import { useRoute } from 'vue-router';
import { nextTick, watch } from 'vue';

const route = useRoute();

const { data: navigation } = useAsyncData('navigation', () => fetchContentNavigation());
Expand All @@ -19,11 +20,11 @@ const { data: files } = useLazyFetch<ParsedContent[]>('/api/search.json', { defa
const updateLinks = () => {
nextTick(() => {
const links = document.querySelectorAll('a');
links.forEach(link => {
links.forEach((link) => {
const href = link.getAttribute('href');
if (
href?.startsWith('http') &&
link.hostname !== window.location.hostname
href?.startsWith('http')
&& link.hostname !== window.location.hostname
) {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
Expand Down
62 changes: 62 additions & 0 deletions app/components/DocsBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup lang="ts">
const { data: banner } = useFetch('/api/banner');

const dismissedBanners = useCookie('directus-dismissed-banners', {
default: () => [] as string[],
});

const bannerVisible = computed(() => {
if (!unref(banner)) return false;
return unref(dismissedBanners).includes(unref(banner)!.id) === false;
});

const dismiss = (id: string) => {
dismissedBanners.value = [...unref(dismissedBanners), id];
};

const iconName = computed(() => {
if (!unref(banner)) return null;
return getIconName(unref(banner)!.icon);
});
</script>

<template>
<div
v-if="banner && bannerVisible"
class="bg-foreground cursor-pointer h-8"
>
<UContainer class="h-full flex items-center gap-x-4">
<NuxtLink
class="flex-grow h-full flex items-center text-background no-underline text-xs leading-xs font-semibold group"
:href="banner.link ?? undefined"
>
<Icon
v-if="iconName"
class="mr-2 size-5"
:name="iconName"
/>
<span
class="whitespace-nowrap overflow-hidden text-ellipsis"
v-html="banner.content"
/>
<Icon
class="hidden md:block transform duration-150 ease-out ml-1 group-hover:translate-x-1 size-5"
name="material-symbols:arrow-forward"
/>
</NuxtLink>

<button
aria-label="Close"
class="text-background"
:padded="false"
icon="material-symbols:close"
@click="dismiss(banner.id)"
>
<Icon
name="material-symbols:close"
class="size-5"
/>
</button>
</UContainer>
</div>
</template>
4 changes: 4 additions & 0 deletions app/components/DocsHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ const algoliaNavigator = {
:links="links"
:ui="route.path.startsWith('/api') ? { container: 'max-w-screen' } : {}"
>
<template #top>
<DocsBanner />
</template>

<template #logo>
<LogoDocs class="w-auto h-8 shrink-0" />
</template>
Expand Down
74 changes: 74 additions & 0 deletions app/utils/icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const filledIcons = [
'all_inclusive',
'apartment',
'api',
'api',
'arrow_back',
'arrow_forward',
'arrow_outward',
'autopay',
'autostop',
'avg_pace',
'cached',
'check',
'checklist',
'checklist_rtl',
'close',
'cloudy',
'code',
'compare_arrows',
'cruelty_free',
'dynamic_feed',
'electrical_services',
'emoji_people',
'expand_less',
'expand_more',
'full_stacked_bar_chart',
'functions',
'globe_uk',
'home_app_logo',
'horizontal_rule',
'laps',
'link',
'image_search',
'insights',
'login',
'menu_rounded',
'money_off',
'monitoring',
'online_prediction',
'open_in_new',
'password',
'partner_exchange',
'post_add',
'public',
'published_with_changes',
'query_stats',
'repeat',
'search',
'security',
'sort_by_alpha',
'sports_martial_arts',
'support',
'sync_alt',
'timeline',
'translate',
'trending_up',
'update',
'webhook',
'work',
'support_agent',
'edit_sharp',
];

export function getIconName(name: string): string | undefined {
if (!name) return;
// Convert the icon coming from the API to the name of the icon component
// Directus uses Google Material Icons and the icon values are snake_case (e.g. "account_circle")
const prefix = 'material-symbols:';
// Change snake case to kebab case
const kebabCase = name.replace(/_/g, '-');
// If the icon is one of the filled icons, do not add the suffix '-outline'. Needed because of descrepancies between the Google Material Font we use in Directus icon interface and the Iconify library.
const iconName = prefix + kebabCase + (filledIcons.includes(name) ? '' : '-outline');
return iconName;
}
1 change: 1 addition & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default defineNuxtConfig({
},
},
},
directusUrl: process.env.DIRECTUS_URL,
},

build: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@directus/openapi": "0.1.15",
"@directus/sdk": "^19.1.0",
"@docsearch/css": "3.8.3",
"@docsearch/js": "3.8.3",
"@iconify-json/heroicons-outline": "1.2.1",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions server/api/banner.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default defineCachedEventHandler(async () => {
try {
const [data] = await directusServer.request(readItems('site_banners', {
fields: ['id', 'icon', 'content', 'link'],
filter: {
show_on: {
// @ts-expect-error - _contains works for csv fields in Directus
_contains: 'docs',
},
},
sort: ['-date_created'],
limit: 1,
}));

if (!data) return {};

return data;
}
catch (error) {
console.error(error);
return {};
}
}, {
maxAge: 60 * 5, // 5 minutes
swr: true,
});
26 changes: 26 additions & 0 deletions server/utils/directus-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { $fetch } from 'ofetch';
import {
createDirectus,
readItem,
readItems,
rest,
type QueryFilter,
} from '@directus/sdk';
import type { Schema } from '#shared/types/schema';

const {
directusUrl,
} = useRuntimeConfig();

const directusServer = createDirectus<Schema>(directusUrl as string, {
globals: {
fetch: $fetch,
},
}).with(rest());

export {
directusServer,
readItem,
readItems,
};
export type { QueryFilter };
16 changes: 16 additions & 0 deletions shared/types/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface SiteBanner {
id: string;
status: string;
sort: number | null;
user_created: string | null;
date_created: string | null;
user_updated: string | null;
date_updated: string | null;
link: string | null;
content: string | null;
icon: string;
show_on: string[];
}
export interface Schema {
site_banners: SiteBanner[];
}