@iqual/nuxt-icms 1.2.1 → 1.2.2
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/components/DesignSystem/Molecules/MediaLightbox.vue +273 -0
- package/components/DesignSystem/Molecules/MediaSlider.vue +19 -25
- package/components/DesignSystem/README.md +489 -0
- package/components/Media/RemoteVideo/index.vue +3 -3
- package/components/Node/Icms/Page/index.vue +5 -3
- package/components/Paragraph/Card/Layout/index.vue +2 -2
- package/components/Paragraph/Contact/index.vue +1 -2
- package/components/Paragraph/Downloads/index.vue +1 -2
- package/components/Paragraph/FAQ/Layout/index.vue +1 -2
- package/components/Paragraph/FlipTeasers/index.vue +2 -2
- package/components/Paragraph/Gallery/index.vue +16 -14
- package/components/Paragraph/IconText/Layout/index.vue +1 -2
- package/components/Paragraph/Table/index.vue +2 -2
- package/components/Paragraph/Team/Layout/index.vue +2 -2
- package/components/Paragraph/Teasers/index.vue +3 -3
- package/components/Paragraph/Timeline/Layout/index.vue +6 -3
- package/components/Paragraph/iFrame/index.vue +16 -2
- package/components/Section/Footer/index.vue +15 -14
- package/components/Section/Header/GlobalMenu.vue +34 -34
- package/components/Section/Header/MetaMenu.vue +17 -19
- package/components/Section/Header/index.vue +21 -22
- package/composables/useGlobalConfig.ts +57 -0
- package/composables/useGraphqlCacheability.ts +22 -0
- package/composables/useInitData.ts +20 -112
- package/composables/useMediaLightbox.ts +114 -0
- package/composables/useMenu.ts +66 -0
- package/modules/dynamicEntityComponentResolver.ts +0 -20
- package/package.json +1 -2
- package/plugins/graphqlMiddleware.ts +27 -68
- package/plugins/initData.ts +105 -0
- package/{composables → queries}/query.initData.graphql +2 -8
- package/queries/query.loadTranslations.graphql +5 -0
- package/types/initData.ts +1 -0
- package/components/Section/Header/SearchLink.vue +0 -26
- package/composables/useLightbox.ts +0 -81
- package/composables/useMainMenuLinks.ts +0 -69
- package/composables/useMetaMenuLinks.ts +0 -44
- package/types/fslightbox-vue.d.ts +0 -1
|
@@ -1,118 +1,26 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
footerMenuLinks: MenuLinkTreeElementFirstFragment[]
|
|
11
|
-
footerMetaMenuLinks: MenuLinkTreeElementFirstFragment[]
|
|
12
|
-
translations: Record<string, string | [string, string]>
|
|
13
|
-
globalConfig: {
|
|
14
|
-
companyLogo?: GlobalConfigFragment['companyLogo']
|
|
15
|
-
companyLogoAlt?: GlobalConfigFragment['companyLogoAlt']
|
|
16
|
-
companyName?: GlobalConfigFragment['companyName']
|
|
17
|
-
companyDescription?: GlobalConfigFragment['companyDescription']
|
|
18
|
-
address?: GlobalConfigFragment['address']
|
|
19
|
-
phoneNumber?: GlobalConfigFragment['phoneNumber']
|
|
20
|
-
emailAddress?: GlobalConfigFragment['emailAddress']
|
|
21
|
-
metaLinks?: GlobalConfigFragment['metaLinks']
|
|
22
|
-
contactButtons?: GlobalConfigFragment['contactButtons']
|
|
23
|
-
socialMediaLinks?: GlobalConfigFragment['socialMediaLinks']
|
|
1
|
+
import type { InitData, MappedMenuLink } from '../plugins/initData'
|
|
2
|
+
|
|
3
|
+
export type UseInitData = {
|
|
4
|
+
globalConfig: ComputedRef<InitData['globalConfig']>
|
|
5
|
+
menus: {
|
|
6
|
+
metaMenu: ComputedRef<MappedMenuLink[]>
|
|
7
|
+
mainMenu: ComputedRef<MappedMenuLink[]>
|
|
8
|
+
footerMenu: ComputedRef<MappedMenuLink[]>
|
|
9
|
+
footerMetaMenu: ComputedRef<MappedMenuLink[]>
|
|
24
10
|
}
|
|
25
11
|
}
|
|
26
12
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
): Record<string, string | [string, string]> {
|
|
30
|
-
if (!v) {
|
|
31
|
-
return {}
|
|
32
|
-
}
|
|
33
|
-
return Object.entries(
|
|
34
|
-
(v.translations || {}) as Record<
|
|
35
|
-
string,
|
|
36
|
-
string | { singular?: string; plural?: string }
|
|
37
|
-
>,
|
|
38
|
-
).reduce<Record<string, string | [string, string]>>(
|
|
39
|
-
(acc, [fullKey, value]) => {
|
|
40
|
-
const keyWithDots = fullKey.replace('__', '.')
|
|
41
|
-
if (typeof value === 'string') {
|
|
42
|
-
acc[keyWithDots] = value
|
|
43
|
-
} else if (typeof value === 'object' && value.plural && value.singular) {
|
|
44
|
-
acc[keyWithDots] = [value.singular, value.plural]
|
|
45
|
-
}
|
|
46
|
-
return acc
|
|
47
|
-
},
|
|
48
|
-
{},
|
|
49
|
-
)
|
|
50
|
-
}
|
|
13
|
+
export default function (): UseInitData {
|
|
14
|
+
const data = useState<InitData | null>('initData', () => null)
|
|
51
15
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'initData_' + currentLanguage.value,
|
|
60
|
-
event,
|
|
61
|
-
)
|
|
62
|
-
if (value) {
|
|
63
|
-
data.value = value
|
|
64
|
-
return data
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If the data is not in the cache, we fetch it from the payload / useState().
|
|
68
|
-
if (data.value) {
|
|
69
|
-
return data
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const config = useRuntimeConfig()
|
|
73
|
-
// Read the host from .env NUXT_HOST to support multisite setup.
|
|
74
|
-
// Read the host from .env NUXT_HOST to support multisite setup.
|
|
75
|
-
const url = useRequestURL()
|
|
76
|
-
let host = url.hostname
|
|
77
|
-
if (import.meta.server) {
|
|
78
|
-
host = config.requestHost
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Fetch the data from the server.
|
|
82
|
-
data.value = await useGraphqlQuery({
|
|
83
|
-
name: 'initData',
|
|
84
|
-
fetchOptions: {
|
|
85
|
-
query: {
|
|
86
|
-
language: currentLanguage.value,
|
|
87
|
-
__server: 'true',
|
|
88
|
-
},
|
|
89
|
-
headers: {
|
|
90
|
-
host,
|
|
91
|
-
'x-forwarded-host': host,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
}).then((v) => {
|
|
95
|
-
const initData = {
|
|
96
|
-
metaMenuLinks: v.data.metaMenu?.links || [],
|
|
97
|
-
mainMenuLinks: v.data.mainMenu?.links || [],
|
|
98
|
-
footerMenuLinks: v.data.footerMenu?.links || [],
|
|
99
|
-
footerMetaMenuLinks: v.data.footerMetaMenu?.links || [],
|
|
100
|
-
globalConfig: v.data.globalConfig || {},
|
|
101
|
-
translations: getTranslations(v.data),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// The cache tags are coming from the onServerResponse() function in the graphqlMiddleware.
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
import.meta.server &&
|
|
108
|
-
v.__cacheability?.isCacheable &&
|
|
109
|
-
v.__cacheability.tagsNuxt
|
|
110
|
-
) {
|
|
111
|
-
addToCache(initData, v.__cacheability.tagsNuxt)
|
|
16
|
+
return {
|
|
17
|
+
globalConfig: computed(() => data.value?.globalConfig || {}),
|
|
18
|
+
menus: {
|
|
19
|
+
metaMenu: computed(() => data.value?.metaMenuLinks || []),
|
|
20
|
+
mainMenu: computed(() => data.value?.mainMenuLinks || []),
|
|
21
|
+
footerMenu: computed(() => data.value?.footerMenuLinks || []),
|
|
22
|
+
footerMetaMenu: computed(() => data.value?.footerMetaMenuLinks || []),
|
|
112
23
|
}
|
|
113
|
-
|
|
114
|
-
return initData
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
return data
|
|
24
|
+
}
|
|
118
25
|
}
|
|
26
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { MediaFragment } from '#graphql-operations'
|
|
2
|
+
|
|
3
|
+
export function useMediaLightbox(media: (MediaFragment | null)[] | undefined) {
|
|
4
|
+
const isOpen = ref(false)
|
|
5
|
+
const currentIndex = ref(0)
|
|
6
|
+
|
|
7
|
+
const openLightbox = (index: number) => {
|
|
8
|
+
currentIndex.value = index
|
|
9
|
+
isOpen.value = true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const closeLightbox = () => {
|
|
13
|
+
isOpen.value = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const setCurrentIndex = (index: number) => {
|
|
17
|
+
currentIndex.value = index
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// For backward compatibility with the old fsLightbox API
|
|
21
|
+
const toggleLightbox = computed({
|
|
22
|
+
get: () => isOpen.value,
|
|
23
|
+
set: (value: boolean) => {
|
|
24
|
+
isOpen.value = value
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// For backward compatibility - fsLightbox used 1-based indexing
|
|
29
|
+
const currentSlide = computed({
|
|
30
|
+
get: () => currentIndex.value + 1,
|
|
31
|
+
set: (value: number) => {
|
|
32
|
+
currentIndex.value = value - 1
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Copy exact logic from the working composable
|
|
37
|
+
const computedMedia = computed(() =>
|
|
38
|
+
media?.map(item => {
|
|
39
|
+
if (!item) return ''
|
|
40
|
+
switch (item.type) {
|
|
41
|
+
case 'MediaImage':
|
|
42
|
+
return (item as any).sourceUrl?.path || ''
|
|
43
|
+
case 'MediaVideo':
|
|
44
|
+
return (item as any).url?.path || ''
|
|
45
|
+
case 'MediaRemoteVideo':
|
|
46
|
+
return (item as any).link || ''
|
|
47
|
+
default:
|
|
48
|
+
return ''
|
|
49
|
+
}
|
|
50
|
+
}).filter(Boolean) || []
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// Expose filtered media objects for Media components
|
|
54
|
+
const mediaObjects = computed(() => media?.filter(Boolean) || [])
|
|
55
|
+
|
|
56
|
+
// Copy exact logic from the working composable
|
|
57
|
+
const computedCustomAttributes = computed(() =>
|
|
58
|
+
media?.map(item => {
|
|
59
|
+
if (!item) return {}
|
|
60
|
+
|
|
61
|
+
switch (item.type) {
|
|
62
|
+
case 'MediaImage':
|
|
63
|
+
return {
|
|
64
|
+
alt: (item as any).alt?.alt || '',
|
|
65
|
+
title: item.title || ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 'MediaVideo':
|
|
69
|
+
return {
|
|
70
|
+
alt: ((item as any).thumbnailOverride || (item as any).thumbnail)?.alt || '',
|
|
71
|
+
title: item.title || '',
|
|
72
|
+
poster: ((item as any).thumbnailOverride || (item as any).thumbnail)?.entity?.uri?.first?.url || '',
|
|
73
|
+
controls: true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case 'MediaRemoteVideo':
|
|
77
|
+
return {
|
|
78
|
+
alt: ((item as any).thumbnailOverride || (item as any).thumbnail)?.alt || '',
|
|
79
|
+
title: item.title || '',
|
|
80
|
+
poster: ((item as any).thumbnailOverride || (item as any).thumbnail)?.entity?.uri?.first?.url || ''
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
default:
|
|
84
|
+
return {}
|
|
85
|
+
}
|
|
86
|
+
}) || []
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
// Copy exact logic from the working composable
|
|
90
|
+
const computedCaptions = computed(() =>
|
|
91
|
+
media?.map(item => (item as any)?.caption || '') || []
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
// New API for custom lightbox
|
|
96
|
+
isOpen: readonly(isOpen),
|
|
97
|
+
currentIndex: readonly(currentIndex),
|
|
98
|
+
openLightbox,
|
|
99
|
+
closeLightbox,
|
|
100
|
+
setCurrentIndex,
|
|
101
|
+
|
|
102
|
+
// Backward compatibility with fsLightbox API
|
|
103
|
+
toggleLightbox,
|
|
104
|
+
currentSlide,
|
|
105
|
+
|
|
106
|
+
// Data for lightbox component
|
|
107
|
+
sources: computedMedia,
|
|
108
|
+
customAttributes: computedCustomAttributes,
|
|
109
|
+
captions: computedCaptions,
|
|
110
|
+
|
|
111
|
+
// Media objects for Media components
|
|
112
|
+
mediaObjects
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { MappedMenuLink } from '../plugins/initData'
|
|
2
|
+
import type { MenuName } from '../types/initData'
|
|
3
|
+
|
|
4
|
+
function isLinkTreeActive(
|
|
5
|
+
tree: MappedMenuLink & { subtree?: Array<MappedMenuLink> },
|
|
6
|
+
currentRoutePath: string,
|
|
7
|
+
): boolean {
|
|
8
|
+
if (tree.href === currentRoutePath) {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (tree.subtree) {
|
|
13
|
+
return tree.subtree.some((subtreeEl) =>
|
|
14
|
+
isLinkTreeActive(subtreeEl, currentRoutePath),
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function useMenu(menuName: MenuName) {
|
|
22
|
+
const { menus } = useInitData()
|
|
23
|
+
|
|
24
|
+
const menuLinks = menus[menuName]
|
|
25
|
+
const currentRoute = useRoute()
|
|
26
|
+
|
|
27
|
+
const menuItems = computed(() => {
|
|
28
|
+
return menuLinks.value
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const menuItemActive = computed(() => {
|
|
32
|
+
return menuLinks.value.find((l) =>
|
|
33
|
+
isLinkTreeActive(l, currentRoute.path),
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const menuItemActiveLevel1 = computed(() => {
|
|
38
|
+
return menuItemActive.value?.children?.find((l) =>
|
|
39
|
+
isLinkTreeActive(l, currentRoute.path),
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const menuItemActiveLevel2 = computed(() => {
|
|
44
|
+
const activeSubtree = menuItemActiveLevel1.value?.children
|
|
45
|
+
if (activeSubtree) {
|
|
46
|
+
return activeSubtree.find((l) => isLinkTreeActive(l, currentRoute.path))
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const menuTiemActiveLevel3 = computed(() => {
|
|
52
|
+
const activeSubtree = menuItemActiveLevel2.value?.children
|
|
53
|
+
if (activeSubtree) {
|
|
54
|
+
return activeSubtree.find((l) => isLinkTreeActive(l, currentRoute.path))
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
menuItems,
|
|
61
|
+
menuItemActive,
|
|
62
|
+
menuItemActiveLevel1,
|
|
63
|
+
menuItemActiveLevel2,
|
|
64
|
+
menuTiemActiveLevel3,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -106,26 +106,6 @@ export default defineNuxtModule({
|
|
|
106
106
|
src: resolve(nuxt.options.buildDir, 'dynamic-entity-component-resolver.plugin.mjs')
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
-
// Collect all page teaser fragments from the Nuxt layers.
|
|
110
|
-
const paragraphFragments = nuxt.options._layers
|
|
111
|
-
.map(layer => layer.config?.icms?.components?.fragments)
|
|
112
|
-
.filter(Boolean)
|
|
113
|
-
.flat()
|
|
114
|
-
.map((fragmentName) => {
|
|
115
|
-
return `...${fragmentName}`
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const fragment = `
|
|
119
|
-
fragment paragraph on Paragraph {
|
|
120
|
-
id
|
|
121
|
-
parentId
|
|
122
|
-
${paragraphFragments.join('\n ')}
|
|
123
|
-
}
|
|
124
|
-
`
|
|
125
|
-
nuxt.hooks.hookOnce('nuxt-graphql-middleware:init', (context) => {
|
|
126
|
-
context.addDocument('fragment.paragraph.graphql', fragment)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
109
|
// Private helper to extract unique fragment values from any routeQueries object
|
|
130
110
|
function extractFragments(routeQueries: any): string[] {
|
|
131
111
|
const fragments = new Set<string>();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iqual/nuxt-icms",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "ICMS Base",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"class-variance-authority": "^0.7.1",
|
|
27
27
|
"clsx": "^2.1.1",
|
|
28
28
|
"embla-carousel-vue": "^8.6.0",
|
|
29
|
-
"fslightbox-vue": "^2.2.1",
|
|
30
29
|
"lucide-vue-next": "^0.469.0",
|
|
31
30
|
"nuxt-easy-texts": "^2.0.0",
|
|
32
31
|
"nuxt-graphql-middleware": "^5.1.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineNuxtPlugin } from 'nuxt/app'
|
|
2
|
-
import type { DrupalMessage } from '
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import type { DrupalMessage } from '../composables/useDrupalMessages'
|
|
3
|
+
import type { GraphqlResponseTyped } from '#nuxt-graphql-middleware/response'
|
|
4
|
+
import { buildProxyRequestHeaders } from '~/shared/request'
|
|
5
5
|
|
|
6
6
|
type GraphqlMessengerMessage = {
|
|
7
7
|
type: string
|
|
@@ -30,21 +30,24 @@ function extractMessages(data: GraphqlResponseTyped): DrupalMessage[] {
|
|
|
30
30
|
* This is only called when performing a query or mutation from within the nuxt
|
|
31
31
|
* app (e.g. not via custom server routes).
|
|
32
32
|
*/
|
|
33
|
-
export default defineNuxtPlugin(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
export default defineNuxtPlugin({
|
|
34
|
+
name: 'starterkit:graphql-plugin',
|
|
35
|
+
dependsOn: ['nuxt-graphql-middleware-provide-state', 'nuxt:router'],
|
|
36
|
+
setup() {
|
|
37
|
+
const state = useGraphqlState()
|
|
38
|
+
const { messages } = useDrupalMessages()
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const event = import.meta.server ? useRequestEvent() : undefined
|
|
41
|
+
|
|
42
|
+
if (!state) {
|
|
43
|
+
throw new Error('Missing nuxt-graphql-middleware state.')
|
|
44
|
+
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
onResponse(result) {
|
|
46
|
+
if (import.meta.server && event) {
|
|
47
|
+
state.fetchOptions.headers = buildProxyRequestHeaders(event)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
state.fetchOptions.onResponse = (result) => {
|
|
48
51
|
const data = result.response?._data
|
|
49
52
|
if (!data) {
|
|
50
53
|
return
|
|
@@ -56,60 +59,16 @@ export default defineNuxtPlugin(() => {
|
|
|
56
59
|
if (!exists) {
|
|
57
60
|
messages.value.push(v)
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
if (import.meta.server) {
|
|
65
|
-
const event = useRequestEvent()
|
|
66
|
-
|
|
67
|
-
useCDNHeaders((helper) => {
|
|
68
|
-
if (!data.__cacheability?.isCacheable) {
|
|
69
|
-
helper.private()
|
|
70
|
-
return
|
|
62
|
+
if (import.meta.server) {
|
|
63
|
+
// When there are messages, we have to make the whole request uncacheable.
|
|
64
|
+
useCDNHeaders((v) => v.private())
|
|
71
65
|
}
|
|
72
|
-
|
|
73
|
-
helper
|
|
74
|
-
.public()
|
|
75
|
-
.setNumeric('sharedMaxAge', data.__cacheability.sharedMaxAge)
|
|
76
|
-
.addTags(data.__cacheability.tagsCdn)
|
|
77
|
-
}, event)
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
onRequest({ options, request }) {
|
|
82
|
-
// if (import.meta.server && import.meta.dev) {
|
|
83
|
-
// console.log('GraphQL Query: ' + request)
|
|
84
|
-
// }
|
|
85
|
-
try {
|
|
86
|
-
if (!options.params) {
|
|
87
|
-
options.params = {}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/*
|
|
91
|
-
// Add the build hash to every GraphQL request.
|
|
92
|
-
// We do this so that after a deployment, if the user is using the
|
|
93
|
-
// "new" version of the app, the request URL issued is now different
|
|
94
|
-
// than the previous one and thus will not be served from cache.
|
|
95
|
-
options.params.__h = config.public.buildHash
|
|
96
|
-
|
|
97
|
-
// Add the current language to the URL
|
|
98
|
-
options.params.__l = language.value
|
|
99
|
-
|
|
100
|
-
if (import.meta.server) {
|
|
101
|
-
options.params.__server = 'true'
|
|
102
66
|
}
|
|
103
|
-
|
|
67
|
+
})
|
|
104
68
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
options.headers?.append(key, requestHeaders[key])
|
|
108
|
-
})
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
110
|
-
} catch (e) {
|
|
111
|
-
// Do nothing.
|
|
69
|
+
if (import.meta.server && event) {
|
|
70
|
+
useGraphqlCacheability(event, result.response?._data)
|
|
112
71
|
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
115
74
|
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
import type {
|
|
3
|
+
GlobalConfigFragment,
|
|
4
|
+
InitDataQuery,
|
|
5
|
+
MenuLinkTreeElementFirstFragment,
|
|
6
|
+
MenuLinkTreeFragment,
|
|
7
|
+
} from '#graphql-operations'
|
|
8
|
+
import type { GraphqlResponse } from '#nuxt-graphql-middleware/response'
|
|
9
|
+
type InitDataResponse = GraphqlResponse<InitDataQuery>
|
|
10
|
+
export type MappedMenuLink = {
|
|
11
|
+
id: string
|
|
12
|
+
label: string
|
|
13
|
+
href?: string
|
|
14
|
+
children?: MappedMenuLink[]
|
|
15
|
+
}
|
|
16
|
+
export interface InitData {
|
|
17
|
+
metaMenuLinks: MappedMenuLink[]
|
|
18
|
+
mainMenuLinks: MappedMenuLink[]
|
|
19
|
+
footerMenuLinks: MappedMenuLink[]
|
|
20
|
+
footerMetaMenuLinks: MappedMenuLink[]
|
|
21
|
+
globalConfig: GlobalConfigFragment
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Maps a MenuLinkTreeElementFirstFragment array to a MappedMenuLink array.
|
|
25
|
+
*/
|
|
26
|
+
export function mapMenuLinkTree(
|
|
27
|
+
menuLinks?: (MenuLinkTreeFragment | MenuLinkTreeElementFirstFragment)[],
|
|
28
|
+
context: { count: number } = { count: 0 },
|
|
29
|
+
): MappedMenuLink[] {
|
|
30
|
+
if (!menuLinks) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
return menuLinks.map((menuLink) => {
|
|
34
|
+
const id = `menu-item-${context.count++}`
|
|
35
|
+
// Extract the basic properties
|
|
36
|
+
const label = menuLink.link.label
|
|
37
|
+
const href = menuLink.link.url?.path
|
|
38
|
+
// Process children if they exist
|
|
39
|
+
const children =
|
|
40
|
+
'subtree' in menuLink && menuLink.subtree && menuLink.subtree.length > 0
|
|
41
|
+
? mapMenuLinkTree(menuLink.subtree, context)
|
|
42
|
+
: undefined
|
|
43
|
+
// Return the mapped menu link
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
label,
|
|
47
|
+
href,
|
|
48
|
+
children,
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
function mapInitData(response: InitDataResponse): InitData {
|
|
53
|
+
if (response.data) {
|
|
54
|
+
return {
|
|
55
|
+
metaMenuLinks: mapMenuLinkTree(response.data.metaMenu?.links),
|
|
56
|
+
mainMenuLinks: mapMenuLinkTree(response.data.mainMenu?.links),
|
|
57
|
+
footerMenuLinks: mapMenuLinkTree(response.data.footerMenu?.links),
|
|
58
|
+
footerMetaMenuLinks: mapMenuLinkTree(response.data.footerMetaMenu?.links),
|
|
59
|
+
globalConfig: response.data.globalConfig || {},
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error('Failed to load init data.')
|
|
63
|
+
}
|
|
64
|
+
export default defineNuxtPlugin({
|
|
65
|
+
name: 'starterkit:init-data',
|
|
66
|
+
dependsOn: [
|
|
67
|
+
'nuxt-graphql-middleware-provide-state',
|
|
68
|
+
'starterkit:graphql-plugin',
|
|
69
|
+
'nuxt:router',
|
|
70
|
+
],
|
|
71
|
+
async setup() {
|
|
72
|
+
const data = useState<InitData | null>('initData', () => null)
|
|
73
|
+
if (import.meta.server) {
|
|
74
|
+
const event = useRequestEvent()
|
|
75
|
+
const language = useCurrentLanguage()
|
|
76
|
+
if (!event) {
|
|
77
|
+
throw new Error('Missing request event.')
|
|
78
|
+
}
|
|
79
|
+
const { value: cachedResponse, addToCache } =
|
|
80
|
+
await useDataCache<InitDataResponse>(
|
|
81
|
+
'initData_' + language.value,
|
|
82
|
+
event,
|
|
83
|
+
)
|
|
84
|
+
const response = cachedResponse || (await useGraphqlQuery('initData'))
|
|
85
|
+
data.value = mapInitData(response)
|
|
86
|
+
if (!cachedResponse && response.extensions.cacheability?.isCacheable) {
|
|
87
|
+
await addToCache(response, response.extensions.cacheability?.tagsNuxt)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (import.meta.client) {
|
|
91
|
+
async function loadInitData() {
|
|
92
|
+
data.value = await useGraphqlQuery('initData', null, {
|
|
93
|
+
graphqlCaching: {
|
|
94
|
+
client: true,
|
|
95
|
+
},
|
|
96
|
+
}).then(mapInitData)
|
|
97
|
+
}
|
|
98
|
+
if (!data.value) {
|
|
99
|
+
await loadInitData()
|
|
100
|
+
}
|
|
101
|
+
const language = useCurrentLanguage()
|
|
102
|
+
watch(language, loadInitData)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
})
|
|
@@ -83,16 +83,10 @@ fragment globalConfig on TranslatableConfigPagesGlobal {
|
|
|
83
83
|
phoneNumber: fieldIcmsPhoneNumber
|
|
84
84
|
emailAddress: fieldIcmsEmailAddress
|
|
85
85
|
contactButtons: fieldIcmsContactButtons {
|
|
86
|
-
...
|
|
87
|
-
props {
|
|
88
|
-
...paragraphIcmsButtonElement
|
|
89
|
-
}
|
|
86
|
+
...paragraphIcmsButtonElement
|
|
90
87
|
}
|
|
91
88
|
metaLinks: fieldIcmsMetaLinks {
|
|
92
|
-
...
|
|
93
|
-
props {
|
|
94
|
-
...paragraphIcmsButtonElement
|
|
95
|
-
}
|
|
89
|
+
...paragraphIcmsButtonElement
|
|
96
90
|
}
|
|
97
91
|
socialMediaLinks: fieldIcmsSocialMediaLinks {
|
|
98
92
|
uri {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type MenuName = 'metaMenu' | 'mainMenu' | 'footerMenu' | 'footerMetaMenu'
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="pr-4 font-medium lg:pr-12"
|
|
4
|
-
:class="{
|
|
5
|
-
'sticky top-0': isSticky,
|
|
6
|
-
}"
|
|
7
|
-
>
|
|
8
|
-
<nuxt-link
|
|
9
|
-
:to="{ name: 'search' }"
|
|
10
|
-
class="flex h-10 items-center gap-1 px-3 text-primary-500 lg:h-[50px]"
|
|
11
|
-
>
|
|
12
|
-
<span class="sr-only lg:not-sr-only">
|
|
13
|
-
{{ $texts('header.search', 'Suche') }}
|
|
14
|
-
</span>
|
|
15
|
-
<SpriteSymbol name="magnifyingglass" class="size-5" />
|
|
16
|
-
</nuxt-link>
|
|
17
|
-
</div>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script lang="ts" setup>
|
|
21
|
-
defineProps<{
|
|
22
|
-
isSticky: boolean
|
|
23
|
-
}>()
|
|
24
|
-
|
|
25
|
-
const { $texts } = useNuxtApp()
|
|
26
|
-
</script>
|