diff --git a/.env.example b/.env.example index b3489568..ea4ad9ea 100644 --- a/.env.example +++ b/.env.example @@ -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= diff --git a/app/app.vue b/app/app.vue index a82e3d8a..7f868345 100644 --- a/app/app.vue +++ b/app/app.vue @@ -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()); @@ -19,11 +20,11 @@ const { data: files } = useLazyFetch('/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'); diff --git a/app/components/DocsBanner.vue b/app/components/DocsBanner.vue new file mode 100644 index 00000000..74613537 --- /dev/null +++ b/app/components/DocsBanner.vue @@ -0,0 +1,62 @@ + + + diff --git a/app/components/DocsHeader.vue b/app/components/DocsHeader.vue index c04b904c..f7355d3d 100644 --- a/app/components/DocsHeader.vue +++ b/app/components/DocsHeader.vue @@ -115,6 +115,10 @@ const algoliaNavigator = { :links="links" :ui="route.path.startsWith('/api') ? { container: 'max-w-screen' } : {}" > + + diff --git a/app/utils/icons.ts b/app/utils/icons.ts new file mode 100644 index 00000000..c67fd1c2 --- /dev/null +++ b/app/utils/icons.ts @@ -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; +} diff --git a/nuxt.config.ts b/nuxt.config.ts index 9ca540b3..0e1684bd 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -97,6 +97,7 @@ export default defineNuxtConfig({ }, }, }, + directusUrl: process.env.DIRECTUS_URL, }, build: { diff --git a/package.json b/package.json index 2c355587..595c6352 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd24bf0b..a1893d5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@directus/openapi': specifier: 0.1.15 version: 0.1.15 + '@directus/sdk': + specifier: ^19.1.0 + version: 19.1.0 '@docsearch/css': specifier: 3.8.3 version: 3.8.3 @@ -410,6 +413,10 @@ packages: '@directus/openapi@0.1.15': resolution: {integrity: sha512-QGM3dWVRxpLhA2qi83MLNewxHIjY5PFftdCnssEpgD5IEAO1+s9z2F7zpEtMOKLcnRoH4H2+ttKN6bKlbV+bIw==} + '@directus/sdk@19.1.0': + resolution: {integrity: sha512-Nqem9BsvvGyVtAa69mGPtoMoMVkZxdIREdsWvvTzNF4/1XqaFfEiFL7PhtUNfc46/Nufus2+QUKYQbNiAWe3ZA==} + engines: {node: '>=22'} + '@docsearch/css@3.8.3': resolution: {integrity: sha512-1nELpMV40JDLJ6rpVVFX48R1jsBFIQ6RnEQDsLFGmzOjPWTOMlZqUcXcvRx8VmYV/TqnS1l784Ofz+ZEb+wEOQ==} @@ -7158,6 +7165,8 @@ snapshots: '@directus/openapi@0.1.15': {} + '@directus/sdk@19.1.0': {} + '@docsearch/css@3.8.3': {} '@docsearch/js@3.8.3(@algolia/client-search@5.19.0)(search-insights@2.17.3)': diff --git a/server/api/banner.get.ts b/server/api/banner.get.ts new file mode 100644 index 00000000..9c5b14fa --- /dev/null +++ b/server/api/banner.get.ts @@ -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, +}); diff --git a/server/utils/directus-server.ts b/server/utils/directus-server.ts new file mode 100644 index 00000000..4af91d76 --- /dev/null +++ b/server/utils/directus-server.ts @@ -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(directusUrl as string, { + globals: { + fetch: $fetch, + }, +}).with(rest()); + +export { + directusServer, + readItem, + readItems, +}; +export type { QueryFilter }; diff --git a/shared/types/schema.ts b/shared/types/schema.ts new file mode 100644 index 00000000..cc2f904e --- /dev/null +++ b/shared/types/schema.ts @@ -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[]; +}