@iqual/nuxt-icms 1.0.13 → 1.1.0
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/Blokkli/Timeline/Element/icms_timeline_element.svg +7 -0
- package/components/Blokkli/Timeline/Element/icon.svg +7 -0
- package/components/Blokkli/Timeline/Element/index.vue +14 -0
- package/components/Blokkli/Timeline/Layout/icms_layout_timeline.svg +16 -0
- package/components/Blokkli/Timeline/Layout/icon.svg +16 -0
- package/components/Blokkli/Timeline/Layout/index.vue +14 -0
- package/components/Blokkli/iFrame/icon-blokkli-option-large-spacing-bottom.svg +1 -0
- package/components/Blokkli/iFrame/icon-blokkli-option-large-spacing-top.svg +1 -0
- package/components/Blokkli/iFrame/icon-blokkli-option-no-spacing.svg +1 -0
- package/components/Blokkli/iFrame/icon-blokkli-option-small-spacing-bottom.svg +1 -0
- package/components/Blokkli/iFrame/icon-blokkli-option-small-spacing-top.svg +1 -0
- package/components/Blokkli/iFrame/{Layout/index.vue → index.vue} +2 -6
- package/components/DesignSystem/Organisms/TimelineElement.vue +92 -0
- package/components/Media/Image/index.vue +1 -1
- package/components/Media/RemoteVideo/index.vue +18 -11
- package/components/Media/Video/index.vue +6 -4
- package/components/Paragraph/Media/index.vue +13 -8
- package/components/Paragraph/Timeline/Element/fragment.graphql +16 -0
- package/components/Paragraph/Timeline/Element/index.vue +31 -0
- package/components/Paragraph/Timeline/Layout/fragment.graphql +14 -0
- package/components/Paragraph/Timeline/Layout/index.vue +325 -0
- package/components/Paragraph/iFrame/{Layout/fragment.graphql → fragment.graphql} +2 -6
- package/components/Paragraph/iFrame/index.vue +56 -0
- package/nuxt.config.ts +17 -10
- package/package.json +1 -1
- package/server/graphqlMiddleware.serverOptions.ts +3 -3
- package/tests/pages.spec.ts +101 -0
- package/vitest.config.mts +2 -2
- package/components/Blokkli/iFrame/Element/icon.svg +0 -5
- package/components/Blokkli/iFrame/Element/index.vue +0 -14
- package/components/Paragraph/iFrame/Element/fragment.graphql +0 -9
- package/components/Paragraph/iFrame/Element/index.vue +0 -16
- package/components/Paragraph/iFrame/Layout/index.vue +0 -161
- package/pages/__tests__/index.ts +0 -77
- /package/components/Blokkli/{iFrame → Timeline}/Layout/icon-blokkli-option-large-spacing-bottom.svg +0 -0
- /package/components/Blokkli/{iFrame → Timeline}/Layout/icon-blokkli-option-large-spacing-top.svg +0 -0
- /package/components/Blokkli/{iFrame → Timeline}/Layout/icon-blokkli-option-no-spacing.svg +0 -0
- /package/components/Blokkli/{iFrame → Timeline}/Layout/icon-blokkli-option-small-spacing-bottom.svg +0 -0
- /package/components/Blokkli/{iFrame → Timeline}/Layout/icon-blokkli-option-small-spacing-top.svg +0 -0
- /package/components/Blokkli/iFrame/{Layout/icon.svg → icon.svg} +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DesignSystemAtomsContainer :color-scheme="colorScheme" :spacing="spacing">
|
|
3
|
+
<DesignSystemAtomsGridRow v-if="pretitle || title || subtitle || text">
|
|
4
|
+
<div class="col-span-12">
|
|
5
|
+
<DesignSystemOrganismsContentBlock
|
|
6
|
+
:pretitle="pretitle"
|
|
7
|
+
:title="title"
|
|
8
|
+
:titleStyle="titleStyle"
|
|
9
|
+
:titleWeight="titleWeight"
|
|
10
|
+
:subtitle="subtitle"
|
|
11
|
+
:text="text"
|
|
12
|
+
:colorScheme="colorScheme"
|
|
13
|
+
:hasDecorativeLine="Boolean(subtitle || text)"
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
</DesignSystemAtomsGridRow>
|
|
17
|
+
|
|
18
|
+
<DesignSystemAtomsGridRow class="mt-10 lg:mt-20">
|
|
19
|
+
<!-- Anchor navigation -->
|
|
20
|
+
<div class="relative col-span-12 mb-6 lg:col-span-4 lg:mb-0">
|
|
21
|
+
<ul
|
|
22
|
+
v-if="!isEditing"
|
|
23
|
+
class="sticky left-0 top-32 z-10 flex flex-col gap-2"
|
|
24
|
+
>
|
|
25
|
+
<li v-for="(item, index) in props.timelineElements" :key="index">
|
|
26
|
+
<button
|
|
27
|
+
@click="handleAnchorLinkClick(Number(index))"
|
|
28
|
+
class="cursor-pointer transition-colors duration-200 ease-in-out"
|
|
29
|
+
:class="{
|
|
30
|
+
'font-semibold': currentIndex === Number(index),
|
|
31
|
+
}"
|
|
32
|
+
>
|
|
33
|
+
{{ item.props?.title }}
|
|
34
|
+
</button>
|
|
35
|
+
</li>
|
|
36
|
+
</ul>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="relative col-span-12 lg:col-span-8">
|
|
40
|
+
<!-- Mobile slider navigation -->
|
|
41
|
+
<div
|
|
42
|
+
v-if="isMobile && !isEditing"
|
|
43
|
+
class="relative col-span-12 mb-10 lg:col-span-4"
|
|
44
|
+
>
|
|
45
|
+
<ul
|
|
46
|
+
:class="decorativeLine({ colorScheme })"
|
|
47
|
+
class="flex h-0.5 flex-nowrap items-center justify-between"
|
|
48
|
+
>
|
|
49
|
+
<li
|
|
50
|
+
v-for="(_, index) in props.timelineElements"
|
|
51
|
+
:key="index"
|
|
52
|
+
class="p-4 first:-translate-x-4 last:translate-x-4"
|
|
53
|
+
>
|
|
54
|
+
<button
|
|
55
|
+
@click="handleAnchorLinkClick(Number(index))"
|
|
56
|
+
:class="
|
|
57
|
+
carouselDot({
|
|
58
|
+
colorScheme,
|
|
59
|
+
active: currentIndex === Number(index),
|
|
60
|
+
})
|
|
61
|
+
"
|
|
62
|
+
></button>
|
|
63
|
+
</li>
|
|
64
|
+
</ul>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Mobile slider -->
|
|
68
|
+
<Carousel
|
|
69
|
+
v-if="isMobile && !isEditing"
|
|
70
|
+
@init-api="setApi"
|
|
71
|
+
:class="carousel({ colorScheme })"
|
|
72
|
+
>
|
|
73
|
+
<BlokkliField
|
|
74
|
+
v-slot="{ items }"
|
|
75
|
+
name="field_icms_timeline_elements"
|
|
76
|
+
:list="timelineElements"
|
|
77
|
+
proxy-mode
|
|
78
|
+
>
|
|
79
|
+
<CarouselContent class="m-0 gap-8">
|
|
80
|
+
<BlokkliItem v-for="item in items" v-bind="item" />
|
|
81
|
+
</CarouselContent>
|
|
82
|
+
</BlokkliField>
|
|
83
|
+
<CarouselPrevious
|
|
84
|
+
class="-bottom-[90px] left-0 top-auto xl:bottom-auto xl:left-16 xl:top-1/2"
|
|
85
|
+
:class="carouselButton({ colorScheme })"
|
|
86
|
+
>
|
|
87
|
+
<ChevronLeft :size="24" :stroke-width="1" />
|
|
88
|
+
</CarouselPrevious>
|
|
89
|
+
<CarouselNext
|
|
90
|
+
class="-bottom-[90px] left-[66px] top-auto xl:bottom-auto xl:left-auto xl:right-16 xl:top-1/2"
|
|
91
|
+
:class="carouselButton({ colorScheme })"
|
|
92
|
+
>
|
|
93
|
+
<ChevronRight :size="24" :stroke-width="1" />
|
|
94
|
+
</CarouselNext>
|
|
95
|
+
</Carousel>
|
|
96
|
+
|
|
97
|
+
<!-- Desktop timeline -->
|
|
98
|
+
<BlokkliField
|
|
99
|
+
v-if="isEditing || !isMobile"
|
|
100
|
+
name="field_icms_timeline_elements"
|
|
101
|
+
:list="timelineElements"
|
|
102
|
+
class="flex flex-col gap-32"
|
|
103
|
+
:class="{ droptarget: isEditing }"
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<!-- Desktop line -->
|
|
107
|
+
<template v-if="!isMobile && !isEditing">
|
|
108
|
+
<div :class="decorativeCircle({ colorScheme })" class="top-0"></div>
|
|
109
|
+
<div
|
|
110
|
+
:class="decorativeLine({ colorScheme })"
|
|
111
|
+
class="absolute left-1/2 top-0 h-full w-0.5 -translate-x-1/2"
|
|
112
|
+
></div>
|
|
113
|
+
<div
|
|
114
|
+
:class="decorativeCircle({ colorScheme })"
|
|
115
|
+
class="bottom-0"
|
|
116
|
+
></div>
|
|
117
|
+
</template>
|
|
118
|
+
</div>
|
|
119
|
+
</DesignSystemAtomsGridRow>
|
|
120
|
+
</DesignSystemAtomsContainer>
|
|
121
|
+
</template>
|
|
122
|
+
|
|
123
|
+
<script lang="ts" setup>
|
|
124
|
+
import type { ParagraphIcmsLayoutTimelineFragment } from '#graphql-operations'
|
|
125
|
+
import { tv } from 'tailwind-variants'
|
|
126
|
+
import type { CarouselApi } from '@iqual/nuxt-icms/components/ui/carousel'
|
|
127
|
+
import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
|
|
128
|
+
|
|
129
|
+
const props = defineProps<ParagraphIcmsLayoutTimelineFragment>()
|
|
130
|
+
|
|
131
|
+
const { options, isEditing } = useAttrs().blokkliData
|
|
132
|
+
|
|
133
|
+
const { isMobile } = useIsMobile()
|
|
134
|
+
|
|
135
|
+
const colorScheme = computed(() => {
|
|
136
|
+
return options.value.colorScheme
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const spacing = computed(() => {
|
|
140
|
+
return { bottom: options.value.spacingBottom, top: options.value.spacingTop }
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const childTitleWeight = useChildTitleWeight(props.titleWeight ?? '')
|
|
144
|
+
|
|
145
|
+
const api = ref<CarouselApi>()
|
|
146
|
+
function setApi(val: CarouselApi) {
|
|
147
|
+
api.value = val
|
|
148
|
+
|
|
149
|
+
val?.on('select', () => {
|
|
150
|
+
const selectedIndex = val.selectedScrollSnap()
|
|
151
|
+
setCurrentIndex(selectedIndex)
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const currentIndex = ref(0)
|
|
156
|
+
const setCurrentIndex = (index: number) => {
|
|
157
|
+
if (index < 0 || index >= (props.timelineElements?.length ?? 0)) return
|
|
158
|
+
currentIndex.value = index
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const scrollToCarousel = (index: number) => {
|
|
162
|
+
if (!index && !api.value) return
|
|
163
|
+
nextTick(() => {
|
|
164
|
+
api.value?.scrollTo(index)
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const scrollToElement = (element: HTMLElement) => {
|
|
169
|
+
if (!element) return
|
|
170
|
+
nextTick(() => {
|
|
171
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const getTimelineElements = () =>
|
|
176
|
+
document.querySelectorAll('[data-timeline-element]')
|
|
177
|
+
|
|
178
|
+
const handleAnchorLinkClick = (index: number) => {
|
|
179
|
+
if (isMobile.value) {
|
|
180
|
+
setCurrentIndex(index)
|
|
181
|
+
scrollToCarousel(index)
|
|
182
|
+
} else {
|
|
183
|
+
const elements = getTimelineElements()
|
|
184
|
+
scrollToElement(elements[index] as HTMLElement)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const timelineObserver = ref<IntersectionObserver | null>(null)
|
|
189
|
+
|
|
190
|
+
onMounted(() => {
|
|
191
|
+
if (isEditing || isMobile.value) return
|
|
192
|
+
|
|
193
|
+
const elements = getTimelineElements()
|
|
194
|
+
|
|
195
|
+
timelineObserver.value = new IntersectionObserver(
|
|
196
|
+
(entries) => {
|
|
197
|
+
entries.forEach((entry) => {
|
|
198
|
+
if (entry.isIntersecting) {
|
|
199
|
+
const index = Array.from(elements).indexOf(
|
|
200
|
+
entry.target as HTMLElement,
|
|
201
|
+
)
|
|
202
|
+
if (index !== -1) {
|
|
203
|
+
setCurrentIndex(index)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
rootMargin: '-10% 0px -80% 0px',
|
|
210
|
+
threshold: 0,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
elements.forEach((element) => {
|
|
215
|
+
timelineObserver.value?.observe(element)
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
onUnmounted(() => {
|
|
220
|
+
timelineObserver.value?.disconnect()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const carousel = tv({
|
|
224
|
+
base: 'rounded-lg',
|
|
225
|
+
variants: {
|
|
226
|
+
colorScheme: {
|
|
227
|
+
none: 'bg-white',
|
|
228
|
+
dark: 'bg-gray-950',
|
|
229
|
+
light: 'bg-gray-50',
|
|
230
|
+
accent: 'bg-primary-600',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
defaultVariants: {
|
|
234
|
+
colorScheme: 'none',
|
|
235
|
+
},
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
const carouselButton = tv({
|
|
239
|
+
base: 'size-10 border-none disabled:opacity-40 @sm:size:12 @md:size-16 [&_svg]:size-auto',
|
|
240
|
+
variants: {
|
|
241
|
+
colorScheme: {
|
|
242
|
+
none: 'bg-gray-50 text-black hover:bg-black hover:text-white dark:bg-gray-50 dark:text-black hover:dark:bg-black hover:dark:text-white',
|
|
243
|
+
dark: 'bg-white text-black hover:bg-gray-100 hover:text-black dark:bg-white dark:text-black hover:dark:bg-gray-100 hover:dark:text-black',
|
|
244
|
+
light:
|
|
245
|
+
'bg-gray-100 text-black hover:bg-black hover:text-white dark:bg-gray-100 dark:text-black hover:dark:bg-black hover:dark:text-white',
|
|
246
|
+
accent:
|
|
247
|
+
'bg-primary-700 text-white hover:bg-primary-800 hover:text-white dark:bg-primary-700 dark:text-white hover:dark:bg-primary-800 hover:dark:text-white',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
defaultVariants: {
|
|
251
|
+
colorScheme: 'none',
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const decorativeLine = tv({
|
|
256
|
+
base: '',
|
|
257
|
+
variants: {
|
|
258
|
+
colorScheme: {
|
|
259
|
+
none: 'bg-gray-200',
|
|
260
|
+
dark: 'bg-white',
|
|
261
|
+
light: 'bg-gray-200',
|
|
262
|
+
accent: 'bg-white',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
defaultVariants: {
|
|
266
|
+
colorScheme: 'none',
|
|
267
|
+
},
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const decorativeCircle = tv({
|
|
271
|
+
base: 'absolute left-1/2 size-4 -translate-x-1/2 border-2 rounded-full z-10',
|
|
272
|
+
variants: {
|
|
273
|
+
colorScheme: {
|
|
274
|
+
none: 'border-gray-200 bg-white',
|
|
275
|
+
dark: 'border-white bg-gray-950',
|
|
276
|
+
light: 'border-gray-200 bg-gray-50',
|
|
277
|
+
accent: 'border-white bg-primary-600',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
defaultVariants: {
|
|
281
|
+
colorScheme: 'none',
|
|
282
|
+
},
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const carouselDot = tv({
|
|
286
|
+
base: 'cursor-pointer transition-colors duration-200 ease-in-out size-4 border-2 rounded-full',
|
|
287
|
+
variants: {
|
|
288
|
+
colorScheme: {
|
|
289
|
+
none: 'bg-white border-gray-200',
|
|
290
|
+
dark: 'bg-gray-950 border-white',
|
|
291
|
+
light: 'bg-gray-50 border-gray-200',
|
|
292
|
+
accent: 'bg-primary-600 border-white',
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
compoundVariants: [
|
|
296
|
+
{
|
|
297
|
+
colorScheme: 'none',
|
|
298
|
+
active: true,
|
|
299
|
+
class: 'bg-gray-200',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
colorScheme: 'dark',
|
|
303
|
+
active: true,
|
|
304
|
+
class: 'bg-white',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
colorScheme: 'light',
|
|
308
|
+
active: true,
|
|
309
|
+
class: 'bg-gray-200',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
colorScheme: 'accent',
|
|
313
|
+
active: true,
|
|
314
|
+
class: 'bg-white',
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
defaultVariants: {
|
|
318
|
+
colorScheme: 'none',
|
|
319
|
+
active: false,
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
provide('colorScheme', colorScheme)
|
|
324
|
+
provide('titleWeight', childTitleWeight)
|
|
325
|
+
</script>
|
|
@@ -4,10 +4,6 @@ fragment paragraphIcmsLayoutIframe on ParagraphIcmsLayoutIframe {
|
|
|
4
4
|
titleWeight: fieldIcmsTitleWeight
|
|
5
5
|
titleStyle: fieldIcmsTitleStyle
|
|
6
6
|
text: fieldIcmsText
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
props {
|
|
10
|
-
...paragraphIcmsIframeElement
|
|
11
|
-
}
|
|
12
|
-
}
|
|
7
|
+
embed: fieldIcmsEmbedCode
|
|
8
|
+
height: fieldIcmsIframeHeight
|
|
13
9
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DesignSystemAtomsContainer :colorScheme="colorScheme" :spacing="spacing">
|
|
3
|
+
<DesignSystemAtomsGridRow>
|
|
4
|
+
<div :class="textWrapperClassList">
|
|
5
|
+
<DesignSystemOrganismsContentBlock
|
|
6
|
+
:pretitle="pretitle"
|
|
7
|
+
:title="title"
|
|
8
|
+
:titleWeight="titleWeight"
|
|
9
|
+
:text="text"
|
|
10
|
+
:colorScheme="colorScheme"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
v-if="embed"
|
|
15
|
+
v-html="embed"
|
|
16
|
+
class="embed-wrapper mt-10"
|
|
17
|
+
:style="{ '--iframe-height': `${props.height || 600}px` }"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
</DesignSystemAtomsGridRow>
|
|
21
|
+
</DesignSystemAtomsContainer>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
import type { ParagraphIcmsLayoutIframeFragment } from '#graphql-operations'
|
|
26
|
+
|
|
27
|
+
const props = defineProps<ParagraphIcmsLayoutIframeFragment>()
|
|
28
|
+
|
|
29
|
+
const { options, isEditing } = useAttrs().blokkliData
|
|
30
|
+
|
|
31
|
+
const textWrapperClassList = computed(() => {
|
|
32
|
+
if (options.value.textWidth === 'medium') {
|
|
33
|
+
return ['col-span-8', 'col-start-3']
|
|
34
|
+
} else if (options.value.textWidth === 'narrow') {
|
|
35
|
+
return ['col-span-8', 'col-start-3', 'md:col-span-6', 'md:col-start-4']
|
|
36
|
+
}
|
|
37
|
+
return ['col-span-12']
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const colorScheme = computed(() => {
|
|
41
|
+
return options.value.colorScheme
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const spacing = computed(() => {
|
|
45
|
+
return { bottom: options.value.spacingBottom, top: options.value.spacingTop }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
provide('isEditing', isEditing)
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
.embed-wrapper > iframe {
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: var(--iframe-height, 600px);
|
|
55
|
+
}
|
|
56
|
+
</style>
|
package/nuxt.config.ts
CHANGED
|
@@ -168,15 +168,21 @@ export default defineNuxtConfig({
|
|
|
168
168
|
drupalRoute: {
|
|
169
169
|
enabled: true,
|
|
170
170
|
routeQueries: {
|
|
171
|
-
nodeIcmsPage: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
nodeIcmsPage: {
|
|
172
|
+
fragments: [
|
|
173
|
+
'nodeIcmsPage'
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
slug: {
|
|
177
|
+
fragments: [
|
|
178
|
+
'nodeIcmsPage',
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
nodeCanonical: {
|
|
182
|
+
fragments: [
|
|
183
|
+
'nodeIcmsPage',
|
|
184
|
+
]
|
|
185
|
+
},
|
|
180
186
|
},
|
|
181
187
|
},
|
|
182
188
|
devMode: {
|
|
@@ -217,6 +223,7 @@ export default defineNuxtConfig({
|
|
|
217
223
|
'paragraphIcmsLayoutMedia',
|
|
218
224
|
'paragraphIcmsLayoutDownloads',
|
|
219
225
|
'paragraphIcmsLayoutIframe',
|
|
226
|
+
'paragraphIcmsLayoutTimeline',
|
|
220
227
|
// Element components
|
|
221
228
|
'paragraphIcmsButtonElement',
|
|
222
229
|
'paragraphIcmsCardElement',
|
|
@@ -228,7 +235,7 @@ export default defineNuxtConfig({
|
|
|
228
235
|
'paragraphIcms2ColTextElement',
|
|
229
236
|
'paragraphIcmsLogoElement',
|
|
230
237
|
'paragraphIcmsPartnershipElement',
|
|
231
|
-
'
|
|
238
|
+
'paragraphIcmsTimelineElement',
|
|
232
239
|
]
|
|
233
240
|
}
|
|
234
241
|
},
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineGraphqlServerOptions } from 'nuxt-graphql-middleware/server-options'
|
|
2
|
-
import { extractCacheability } from '
|
|
3
|
-
import type { GraphqlCacheability } from '
|
|
2
|
+
import { extractCacheability } from '@iqual/nuxt-icms/server/utils/cacheability'
|
|
3
|
+
import type { GraphqlCacheability } from '@iqual/nuxt-icms/server/helpers'
|
|
4
4
|
import { defaultLangcode, langcodes } from '#nuxt-language-negotiation/config'
|
|
5
|
-
import { buildProxyRequestHeaders } from '
|
|
5
|
+
import { buildProxyRequestHeaders } from '@iqual/nuxt-icms/server/helpers/request'
|
|
6
6
|
|
|
7
7
|
export default defineGraphqlServerOptions<{
|
|
8
8
|
extensions: {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { promises as fsp } from 'fs'
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
import * as ts from 'typescript'
|
|
5
|
+
import { parse as parseSfc } from '@vue/compiler-sfc'
|
|
6
|
+
|
|
7
|
+
// Recursively find all Vue files in a directory
|
|
8
|
+
async function findVueFiles(
|
|
9
|
+
dir: string,
|
|
10
|
+
ignorePatterns: string[] = [],
|
|
11
|
+
): Promise<string[]> {
|
|
12
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true })
|
|
13
|
+
const files: string[] = []
|
|
14
|
+
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name)
|
|
17
|
+
|
|
18
|
+
// Skip if path matches any ignore pattern
|
|
19
|
+
if (ignorePatterns.some((pattern) => fullPath.includes(pattern))) {
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
const subFiles = await findVueFiles(fullPath, ignorePatterns)
|
|
25
|
+
files.push(...subFiles)
|
|
26
|
+
} else if (entry.name.endsWith('.vue')) {
|
|
27
|
+
files.push(fullPath)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return files
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Parse a Vue SFC and check if the component's script calls the given function at least once.
|
|
35
|
+
function functionIsCalledAtLeastOnce(
|
|
36
|
+
vueFileContents: string,
|
|
37
|
+
functionName: string,
|
|
38
|
+
): boolean {
|
|
39
|
+
const result = parseSfc(vueFileContents)
|
|
40
|
+
const script = result.descriptor.scriptSetup?.content
|
|
41
|
+
if (!script) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
const sourceFile = ts.createSourceFile(
|
|
45
|
+
'temp.ts',
|
|
46
|
+
script,
|
|
47
|
+
ts.ScriptTarget.ES2015,
|
|
48
|
+
true,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
let isCalled = false
|
|
52
|
+
|
|
53
|
+
function visit(node: ts.Node) {
|
|
54
|
+
if (
|
|
55
|
+
ts.isCallExpression(node) &&
|
|
56
|
+
node.expression.getText(sourceFile) === functionName
|
|
57
|
+
) {
|
|
58
|
+
isCalled = true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isCalled) {
|
|
62
|
+
ts.forEachChild(node, visit)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ts.forEachChild(sourceFile, visit)
|
|
67
|
+
|
|
68
|
+
return isCalled
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('The Nuxt page component', async () => {
|
|
72
|
+
const rootDir = path.resolve(__dirname, './../app/pages')
|
|
73
|
+
const ignorePatterns = ['blokkli']
|
|
74
|
+
|
|
75
|
+
const pageFilePaths = await findVueFiles(rootDir, ignorePatterns)
|
|
76
|
+
console.log(pageFilePaths)
|
|
77
|
+
|
|
78
|
+
const pageComponentFiles = await Promise.all(
|
|
79
|
+
pageFilePaths.map((filePath) => {
|
|
80
|
+
return fsp.readFile(filePath, { encoding: 'utf8' }).then((content) => {
|
|
81
|
+
return {
|
|
82
|
+
filePath,
|
|
83
|
+
content,
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
pageComponentFiles.forEach(({ filePath, content }) => {
|
|
90
|
+
it(`"${filePath}" should call useDrupalRouteQuery()`, () => {
|
|
91
|
+
expect(
|
|
92
|
+
functionIsCalledAtLeastOnce(content, 'useDrupalRouteQuery'),
|
|
93
|
+
).toEqual(true)
|
|
94
|
+
})
|
|
95
|
+
it(`"${filePath}" should call renderPageDependencies()`, () => {
|
|
96
|
+
expect(
|
|
97
|
+
functionIsCalledAtLeastOnce(content, 'renderPageDependencies'),
|
|
98
|
+
).toEqual(true)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
})
|
package/vitest.config.mts
CHANGED
|
@@ -6,9 +6,9 @@ export default defineVitestConfig({
|
|
|
6
6
|
globals: true,
|
|
7
7
|
environment: 'nuxt',
|
|
8
8
|
alias: {
|
|
9
|
-
'~': path.resolve(__dirname, './'),
|
|
9
|
+
'~': path.resolve(__dirname, './app'),
|
|
10
|
+
'~~': path.resolve(__dirname, './'),
|
|
10
11
|
},
|
|
11
|
-
include: ['**/__tests__/*.*'],
|
|
12
12
|
coverage: {
|
|
13
13
|
all: true,
|
|
14
14
|
provider: 'v8',
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg" style="fill:none!important;">
|
|
2
|
-
<path d="M6 1L1.80023 6.03972C1.33667 6.59599 1.33667 7.40401 1.80023 7.96028L6 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
3
|
-
<path d="M12 13L16.1998 7.96028C16.6633 7.40401 16.6633 6.59599 16.1998 6.03972L12 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
4
|
-
<path d="M7.5 10.5L10.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
5
|
-
</svg>
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<ParagraphIFrameElement v-bind="props" :blokkliData="blokkliData" />
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script lang="ts" setup>
|
|
6
|
-
import type { ParagraphIcmsIframeElementFragment } from '#graphql-operations'
|
|
7
|
-
|
|
8
|
-
const props = defineProps<ParagraphIcmsIframeElementFragment>()
|
|
9
|
-
|
|
10
|
-
const blokkliData = defineBlokkli({
|
|
11
|
-
bundle: 'icms_iframe_element',
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
</script>
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<DesignSystemMoleculesIFrame
|
|
3
|
-
:title="title"
|
|
4
|
-
:link="link?.uri?.path"
|
|
5
|
-
:height="height"
|
|
6
|
-
:is-editing="isEditing"
|
|
7
|
-
/>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<script lang="ts" setup>
|
|
11
|
-
import type { ParagraphIcmsIframeElementFragment } from '#graphql-operations'
|
|
12
|
-
|
|
13
|
-
defineProps<ParagraphIcmsIframeElementFragment>()
|
|
14
|
-
|
|
15
|
-
const isEditing = inject<boolean>('isEditing')
|
|
16
|
-
</script>
|