@iqual/nuxt-icms 1.6.1 → 1.6.2-rc.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.
Files changed (260) hide show
  1. package/README.md +86 -0
  2. package/__tests__/anchors.spec.ts +43 -0
  3. package/__tests__/contract/blokkliComponents.spec.ts +59 -0
  4. package/__tests__/contract/componentFragments.spec.ts +24 -0
  5. package/__tests__/contract/languages.spec.ts +63 -0
  6. package/__tests__/contract/project.ts +144 -0
  7. package/__tests__/hreflang.spec.ts +39 -0
  8. package/{tests → __tests__}/pages.spec.ts +14 -5
  9. package/app.config.ts +15 -0
  10. package/app.vue +3 -21
  11. package/assets/css/backend.css +56 -0
  12. package/assets/css/base/base.css +2 -2
  13. package/assets/css/base/typography.css +2 -2
  14. package/assets/css/ckeditor.css +3 -4
  15. package/assets/css/components/decorations.css +1 -1
  16. package/assets/css/components/editor.css +1 -1
  17. package/assets/css/components/grid-container.css +19 -19
  18. package/assets/css/fe-fixes.css +6 -12
  19. package/assets/css/theme/colors/color-primary.css +33 -11
  20. package/assets/css/theme/colors/color-secondary.css +33 -11
  21. package/assets/css/utilities/utilities.css +1 -1
  22. package/assets/symbols/anchor.svg +3 -0
  23. package/blokkli.editAdapter.ts +56 -0
  24. package/bundles.ts +6 -6
  25. package/cache/__tests__/pageCache.spec.ts +540 -0
  26. package/cache/cacheTagRegistry.ts +124 -0
  27. package/cache/enabledForRequest.ts +21 -0
  28. package/cache/multiCache.serverOptions.ts +58 -0
  29. package/cache/pageCache.ts +360 -0
  30. package/components/Blokkli/Anchor/icon.svg +3 -0
  31. package/components/Blokkli/Anchor/index.vue +13 -0
  32. package/components/Blokkli/AnchorNavigation/icon.svg +8 -0
  33. package/components/Blokkli/AnchorNavigation/index.vue +9 -0
  34. package/components/Blokkli/Card/Element/index.vue +0 -1
  35. package/components/Blokkli/Collapsible/Element/index.vue +5 -0
  36. package/components/Blokkli/FAQ/Element/index.vue +0 -1
  37. package/components/Blokkli/Fragment/Separator/icon.svg +4 -0
  38. package/components/Blokkli/Fragment/Separator/index.vue +87 -0
  39. package/components/Blokkli/Gallery/index.vue +1 -3
  40. package/components/Blokkli/IconText/Element/index.vue +0 -1
  41. package/components/Blokkli/Introduction/index.vue +0 -1
  42. package/components/Blokkli/Listing/icon.svg +9 -0
  43. package/components/Blokkli/Listing/index.vue +24 -0
  44. package/components/Blokkli/Logo/Element/index.vue +0 -1
  45. package/components/Blokkli/Table/index.vue +0 -1
  46. package/components/Blokkli/Team/Element/index.vue +0 -1
  47. package/components/Blokkli/Team/Layout/index.vue +0 -1
  48. package/components/Blokkli/Testimonial/Element/index.vue +0 -1
  49. package/components/Blokkli/Text/index.vue +0 -1
  50. package/components/DesignSystem/Atoms/Button.vue +3 -0
  51. package/components/DesignSystem/Atoms/GridRow.vue +3 -1
  52. package/components/DesignSystem/Molecules/ButtonGroup.vue +5 -4
  53. package/components/DesignSystem/Molecules/IconText.vue +6 -6
  54. package/components/DesignSystem/Molecules/MediaLightbox.vue +8 -8
  55. package/components/DesignSystem/Molecules/MediaSlider.vue +9 -11
  56. package/components/DesignSystem/Molecules/Partnership.vue +7 -4
  57. package/components/DesignSystem/Molecules/Table.vue +35 -19
  58. package/components/DesignSystem/Molecules/TitleComposition.vue +46 -15
  59. package/components/DesignSystem/Molecules/iFrame.vue +2 -2
  60. package/components/DesignSystem/Organisms/CallToAction.vue +8 -7
  61. package/components/DesignSystem/Organisms/CallToActionFeature.vue +8 -7
  62. package/components/DesignSystem/Organisms/CallToActionFullwidth.vue +9 -8
  63. package/components/DesignSystem/Organisms/ContentBlock.vue +26 -16
  64. package/components/DesignSystem/Organisms/FacetedListing.vue +38 -17
  65. package/components/DesignSystem/Organisms/FactCard.vue +6 -7
  66. package/components/DesignSystem/Organisms/FeatureCard.vue +14 -14
  67. package/components/DesignSystem/Organisms/FlipTeaser.vue +19 -19
  68. package/components/DesignSystem/Organisms/HeaderHero.vue +11 -11
  69. package/components/DesignSystem/Organisms/HeaderTextImage.vue +10 -10
  70. package/components/DesignSystem/Organisms/LinkList.vue +20 -12
  71. package/components/DesignSystem/Organisms/LogoElement.vue +7 -4
  72. package/components/DesignSystem/Organisms/Map.vue +1 -1
  73. package/components/DesignSystem/Organisms/SearchResults.vue +15 -6
  74. package/components/DesignSystem/Organisms/SpotlightCard.vue +18 -18
  75. package/components/DesignSystem/Organisms/TeamElement.vue +19 -18
  76. package/components/DesignSystem/Organisms/TeaserCard.vue +16 -17
  77. package/components/DesignSystem/Organisms/TeaserSimple.vue +4 -31
  78. package/components/DesignSystem/Organisms/Testimonial.vue +13 -13
  79. package/components/DesignSystem/Organisms/TextCard.vue +10 -10
  80. package/components/DesignSystem/Organisms/TimelineElement.vue +20 -19
  81. package/components/DesignSystem/Organisms/TwoColumnTextElement.vue +5 -5
  82. package/components/DesignSystem/README.md +59 -40
  83. package/components/Drupal/Breadcrumb/index.vue +25 -12
  84. package/components/Drupal/Messages/index.vue +9 -4
  85. package/components/Drupal/Webform/index.vue +271 -51
  86. package/components/Drupal/Webform/mutation.graphql +17 -1
  87. package/components/Form/HiddenInput.vue +1 -4
  88. package/components/Form/SelectInput.vue +1 -1
  89. package/components/Form/SubmitButton.vue +2 -2
  90. package/components/Form/TextAreaInput.vue +3 -3
  91. package/components/Form/TextEmailInput.vue +1 -1
  92. package/components/Form/TextInput.vue +1 -1
  93. package/components/Form/TextPhoneInput.vue +1 -1
  94. package/components/Form/WebformMarkup.vue +1 -1
  95. package/components/Form/composables/useFormDefaultValue.ts +9 -13
  96. package/components/Form/composables/useFormElement.ts +5 -3
  97. package/components/Form/utils/types.ts +3 -3
  98. package/components/Media/Image/fragment.graphql +4 -0
  99. package/components/Media/Image/index.vue +27 -17
  100. package/components/Media/RemoteVideo/index.vue +3 -3
  101. package/components/Media/Video/index.vue +1 -1
  102. package/components/Node/Icms/Page/SearchResult/fragment.graphql +1 -1
  103. package/components/Node/Icms/Page/SearchResult/index.vue +2 -2
  104. package/components/Node/Icms/Page/Teaser/index.vue +1 -1
  105. package/components/Node/Icms/Page/index.vue +8 -2
  106. package/components/Paragraph/Anchor/fragment.graphql +4 -0
  107. package/components/Paragraph/Anchor/index.vue +48 -0
  108. package/components/Paragraph/AnchorNavigation/fragment.graphql +3 -0
  109. package/components/Paragraph/AnchorNavigation/index.vue +154 -0
  110. package/components/Paragraph/BlokkliFragment/fragment.graphql +5 -0
  111. package/components/Paragraph/Button/Element/fragment.graphql +1 -1
  112. package/components/Paragraph/Button/Element/index.vue +13 -8
  113. package/components/Paragraph/Card/Element/index.vue +1 -1
  114. package/components/Paragraph/Card/Layout/index.vue +9 -9
  115. package/components/Paragraph/Collapsible/Element/index.vue +2 -2
  116. package/components/Paragraph/Collapsible/Layout/index.vue +19 -18
  117. package/components/Paragraph/Contact/index.vue +17 -17
  118. package/components/Paragraph/Downloads/fragment.graphql +1 -1
  119. package/components/Paragraph/Downloads/index.vue +13 -13
  120. package/components/Paragraph/FAQ/Element/index.vue +4 -3
  121. package/components/Paragraph/FAQ/Layout/index.vue +42 -21
  122. package/components/Paragraph/FromLibrary/fragment.graphql +17 -0
  123. package/components/Paragraph/Gallery/index.vue +14 -14
  124. package/components/Paragraph/HeroImage/index.vue +26 -19
  125. package/components/Paragraph/IconText/Element/index.vue +4 -4
  126. package/components/Paragraph/IconText/Layout/index.vue +12 -12
  127. package/components/Paragraph/Introduction/index.vue +17 -17
  128. package/components/Paragraph/LinkLists/Element/fragment.graphql +1 -1
  129. package/components/Paragraph/LinkLists/Element/index.vue +5 -5
  130. package/components/Paragraph/LinkLists/Layout/index.vue +9 -9
  131. package/components/Paragraph/Listing/fragment.graphql +14 -0
  132. package/components/Paragraph/Listing/index.vue +91 -0
  133. package/components/Paragraph/Logo/Element/fragment.graphql +1 -1
  134. package/components/Paragraph/Logo/Element/index.vue +7 -4
  135. package/components/Paragraph/Media/index.vue +23 -21
  136. package/components/Paragraph/Partnerships/Element/fragment.graphql +1 -1
  137. package/components/Paragraph/Partnerships/Element/index.vue +10 -7
  138. package/components/Paragraph/Partnerships/Layout/index.vue +13 -13
  139. package/components/Paragraph/SingleTestimonial/index.vue +13 -13
  140. package/components/Paragraph/Table/index.vue +12 -12
  141. package/components/Paragraph/Team/Element/index.vue +16 -14
  142. package/components/Paragraph/Team/Layout/index.vue +9 -9
  143. package/components/Paragraph/Teasers/index.vue +14 -17
  144. package/components/Paragraph/Testimonial/Element/index.vue +19 -18
  145. package/components/Paragraph/Testimonial/Layout/index.vue +24 -18
  146. package/components/Paragraph/Text/fragment.graphql +1 -1
  147. package/components/Paragraph/Text/index.vue +8 -8
  148. package/components/Paragraph/TextMedia/index.vue +22 -22
  149. package/components/Paragraph/Timeline/Element/index.vue +19 -19
  150. package/components/Paragraph/Timeline/Layout/index.vue +51 -20
  151. package/components/Paragraph/TwoColumnText/Element/index.vue +13 -13
  152. package/components/Paragraph/TwoColumnText/Layout/index.vue +1 -1
  153. package/components/Paragraph/iFrame/index.vue +8 -8
  154. package/components/Search/Facets/FacetDropdownUI.vue +25 -31
  155. package/components/Search/Facets/FacetedSearchUI.vue +72 -48
  156. package/components/Search/Facets/fragment.graphql +20 -15
  157. package/components/Section/Footer/index.vue +55 -32
  158. package/components/Section/Header/Buttons.vue +4 -5
  159. package/components/Section/Header/DesktopMainMenu.vue +5 -4
  160. package/components/Section/Header/DesktopMainMenuDropdown.vue +5 -5
  161. package/components/Section/Header/LanguageDropdown.vue +2 -3
  162. package/components/Section/Header/Logo.vue +3 -3
  163. package/components/Section/Header/MainButtons.vue +4 -5
  164. package/components/Section/Header/MetaButtons.vue +5 -6
  165. package/components/Section/Header/MetaMenu.vue +1 -1
  166. package/components/Section/Header/MobileMainMenu.vue +1 -1
  167. package/components/Section/Header/MobileMainMenuItem.vue +2 -2
  168. package/components/Section/Header/index.vue +6 -13
  169. package/composables/__tests__/useImageSources.spec.ts +91 -0
  170. package/composables/breadcrumb.ts +12 -7
  171. package/composables/useAccordionScroll.ts +12 -4
  172. package/composables/useAllowedFragments.ts +14 -0
  173. package/composables/useAnchors.ts +48 -0
  174. package/composables/useChildTitleWeight.ts +1 -1
  175. package/composables/useCurrentLangcodeEnum.ts +1 -1
  176. package/composables/useCurrentNodeId.ts +8 -0
  177. package/composables/useDrupalUrl.ts +15 -0
  178. package/composables/useEnrichFacetLabels.ts +8 -6
  179. package/composables/useEntityRegistry.ts +3 -3
  180. package/composables/useGlobalConfig.ts +1 -2
  181. package/composables/useGraphqlCacheability.ts +12 -1
  182. package/composables/useHreflangLinks.ts +54 -0
  183. package/composables/useImageSources.ts +70 -28
  184. package/composables/useInitData.ts +1 -2
  185. package/composables/useIsHomepage.ts +1 -1
  186. package/composables/useLinkUrl.ts +30 -0
  187. package/composables/useMediaLightbox.ts +62 -50
  188. package/composables/useMenu.ts +1 -3
  189. package/composables/useMessageBus.ts +14 -7
  190. package/composables/useMobileMenuOverlayBus.ts +1 -1
  191. package/composables/useNavigationOffset.ts +49 -6
  192. package/composables/usePageCache.ts +33 -0
  193. package/composables/useSiteShell.ts +11 -0
  194. package/composables/useTheme.ts +17 -13
  195. package/config/__tests__/stubs/nuxt-multi-cache-config.ts +2 -0
  196. package/config/blokkli.ts +7 -13
  197. package/config/easyTexts.ts +1 -1
  198. package/config/graphqlMiddleware.ts +11 -4
  199. package/config/index.ts +5 -2
  200. package/config/languageNegotiation.ts +1 -1
  201. package/config/multiCache.ts +6 -1
  202. package/config/postcss-drupal/postcss.config.cjs +2 -2
  203. package/config/runtimeConfig.ts +7 -2
  204. package/config/svgIconSprite.ts +1 -2
  205. package/config/vuepal.ts +16 -18
  206. package/error.vue +1 -22
  207. package/layouts/default.vue +2 -6
  208. package/layouts/error.vue +7 -15
  209. package/modules/blokkliFragments.ts +76 -0
  210. package/modules/dynamicEntityComponentResolver.ts +47 -47
  211. package/modules/dynamicParagraphResolver.ts +43 -8
  212. package/modules/pageCache.ts +106 -0
  213. package/nuxt.config.ts +62 -33
  214. package/package.json +23 -22
  215. package/pages/[...slug]/index.vue +25 -4
  216. package/pages/index.vue +3 -3
  217. package/pages/node/[nid]/index.vue +2 -3
  218. package/patches/@blokkli+editor+2.0.0-alpha.13.patch +15 -1
  219. package/plugins/currentNode.ts +17 -0
  220. package/plugins/graphqlMiddleware.ts +10 -2
  221. package/plugins/initData.ts +15 -7
  222. package/plugins/registerNodeIcmsPageComponents.ts +6 -2
  223. package/plugins/siteShell.client.ts +94 -0
  224. package/queries/facetTermLabels.graphql +1 -5
  225. package/queries/fragment.linkUrl.graphql +12 -0
  226. package/queries/query.contentSearch.graphql +17 -2
  227. package/queries/query.initData.graphql +1 -1
  228. package/scripts/create-shade.ts +13 -4
  229. package/scripts/update-icms.ts +3 -1
  230. package/server/__tests__/graphqlMiddleware.serverOptions.spec.ts +2 -2
  231. package/server/__tests__/turnstile.spec.ts +119 -0
  232. package/server/api/icms/blokkli/last-changed.get.ts +24 -0
  233. package/server/api/icon/[...params].ts +3 -9
  234. package/server/graphqlMiddleware.serverOptions.ts +9 -8
  235. package/server/helpers/index.ts +0 -1
  236. package/server/middleware/turnstile.ts +51 -0
  237. package/server/plugins/frameAncestors.ts +20 -0
  238. package/server/plugins/pageCache.ts +59 -0
  239. package/server/utils/__tests__/accessDeniedHandover.spec.ts +49 -0
  240. package/server/utils/__tests__/backendRequestHeaders.spec.ts +115 -0
  241. package/server/utils/accessDeniedHandover.ts +48 -0
  242. package/server/utils/backendRequestHeaders.ts +51 -0
  243. package/server/utils/cacheability.ts +4 -4
  244. package/shared/__tests__/blokkliState.spec.ts +54 -0
  245. package/shared/__tests__/csp.spec.ts +53 -0
  246. package/shared/__tests__/drupalUrl.spec.ts +50 -0
  247. package/shared/blokkliState.ts +40 -0
  248. package/shared/csp.ts +47 -0
  249. package/shared/drupalUrl.ts +30 -0
  250. package/tailwind.config.ts +0 -5
  251. package/types/breadcrumb.ts +22 -0
  252. package/types/global.ts +15 -14
  253. package/types/langcode.ts +16 -0
  254. package/types/layerConfig.ts +84 -0
  255. package/utils/pageCache.ts +95 -0
  256. package/utils/turnstile.ts +35 -0
  257. package/composables/useWebformEnrichment.ts +0 -68
  258. package/server/__tests__/graphqlMiddleware.ts +0 -41
  259. package/server/multiCache.serverOptions.ts +0 -32
  260. package/vitest.config.mts +0 -28
package/config/blokkli.ts CHANGED
@@ -1,22 +1,16 @@
1
1
  import drupal from '@blokkli/editor/drupal'
2
2
  import { join } from 'node:path'
3
3
  import { defineNuxtConfigProperty, currentLayerDir } from '.'
4
- import tailwindConfig from '../tailwind.config'
5
- // import resolveConfig from 'tailwindcss/resolveConfig'
6
4
 
7
- // const fullTailwindConfig = resolveConfig(tailwindConfig)
8
- // const tailwindColors = fullTailwindConfig.theme.colors as Record<string, any>
9
5
  const IS_DEV = process.env.NODE_ENV === 'development'
10
6
 
11
- // Helper function to transform Tailwind's rgb(xx, xx, xx) colors to blökkli's theme [xx, xx, xx]
12
- const transformColor = (color: string): number[] => {
13
- return color.substring(4, color.length - 1).replace(/ /g, '').split(',').map(Number)
14
- }
15
-
16
- export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
7
+ export default defineNuxtConfigProperty<'blokkli'>(() => {
17
8
  return {
18
-
19
9
  modules: [drupal()],
10
+ // The layer's adapter extends blökkli's Drupal adapter so that Drupal
11
+ // forms load from the backend domain. An app-level
12
+ // app/blokkli.editAdapter.ts still takes precedence.
13
+ editAdapterPath: join(currentLayerDir, 'blokkli.editAdapter.ts'),
20
14
  theme: {
21
15
  accent: {
22
16
  '50': [254, 245, 238],
@@ -167,7 +161,7 @@ export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
167
161
  right: {
168
162
  label: 'Right',
169
163
  icon: 'icon-blokkli-option-align-h-right',
170
- }
164
+ },
171
165
  },
172
166
  },
173
167
  alignHorizontalReduced: {
@@ -183,7 +177,7 @@ export default defineNuxtConfigProperty<'blokkli'>((ctx) => {
183
177
  right: {
184
178
  label: 'Right',
185
179
  icon: 'icon-blokkli-option-align-h-right',
186
- }
180
+ },
187
181
  },
188
182
  },
189
183
  alignVerticalReduced: {
@@ -10,4 +10,4 @@ export default defineNuxtConfigProperty<'easyTexts'>(() => {
10
10
  },
11
11
  ],
12
12
  }
13
- })
13
+ })
@@ -3,8 +3,15 @@ import { join } from 'path'
3
3
 
4
4
  const NUXT_BACKEND_URL = process.env.NUXT_BACKEND_URL
5
5
  const IS_DEV = process.env.NODE_ENV === 'development'
6
+ // A dev-mode prepare on CI (the linting workflow needs one) has no Drupal
7
+ // backend to introspect — the committed schema.graphql is the source there.
8
+ const IS_CI = !!process.env.CI
9
+ // `make frontend-schema` forces a download regardless of mode: locally to
10
+ // refresh the committed schema, in the testing workflow's build job (where a
11
+ // Drupal backend exists) to detect a stale one.
12
+ const FORCE_DOWNLOAD = process.env.NUXT_GRAPHQL_DOWNLOAD_SCHEMA === 'true'
6
13
 
7
- export default defineNuxtConfigProperty<'graphqlMiddleware'>((ctx) => {
14
+ export default defineNuxtConfigProperty<'graphqlMiddleware'>(() => {
8
15
  return {
9
16
  serverApiPrefix: '/api/graphql',
10
17
  autoImportPatterns: [
@@ -20,10 +27,10 @@ export default defineNuxtConfigProperty<'graphqlMiddleware'>((ctx) => {
20
27
  '~/server/**/*.{gql,graphql}',
21
28
  '~/sites/**/*.{gql,graphql}',
22
29
  '!node_modules',
23
- join(currentLayerDir, './**/*.{gql,graphql}')
30
+ join(currentLayerDir, './**/*.{gql,graphql}'),
24
31
  ],
25
32
  documents: [],
26
- downloadSchema: IS_DEV,
33
+ downloadSchema: FORCE_DOWNLOAD || (IS_DEV && !IS_CI),
27
34
  graphqlEndpoint: `${NUXT_BACKEND_URL}/graphql`, // If we have Multisite Setup we need to use NUXT_HOST here.
28
35
  codegenConfig: {
29
36
  skipUnusedFragments: true,
@@ -36,4 +43,4 @@ export default defineNuxtConfigProperty<'graphqlMiddleware'>((ctx) => {
36
43
  },
37
44
  },
38
45
  }
39
- })
46
+ })
package/config/index.ts CHANGED
@@ -3,7 +3,10 @@ import type { NuxtConfig } from '@nuxt/schema'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { dirname, join } from 'node:path'
5
5
 
6
- export const currentLayerDir = join(dirname(fileURLToPath(import.meta.url)), '..')
6
+ export const currentLayerDir = join(
7
+ dirname(fileURLToPath(import.meta.url)),
8
+ '..',
9
+ )
7
10
 
8
11
  type ModuleConfigContext = {
9
12
  DEV: boolean
@@ -21,4 +24,4 @@ export function defineNuxtConfigProperty<T extends keyof NuxtConfig>(
21
24
  cb: (ctx: ModuleConfigContext) => NuxtConfig[T],
22
25
  ) {
23
26
  return cb(ctx)
24
- }
27
+ }
@@ -5,7 +5,7 @@ import {
5
5
  acceptLanguage,
6
6
  } from 'nuxt-language-negotiation/negotiators'
7
7
 
8
- export default defineNuxtConfigProperty<'languageNegotiation'>((ctx) => {
8
+ export default defineNuxtConfigProperty<'languageNegotiation'>(() => {
9
9
  return {
10
10
  negotiators: [
11
11
  pathPrefix(),
@@ -8,8 +8,10 @@ export default defineNuxtConfigProperty<'multiCache'>((ctx) => {
8
8
  data: {
9
9
  enabled: true,
10
10
  },
11
+ // Built in, but pages only land in it via usePageCache() and only in
12
+ // the default page cache mode (runtimeConfig.pageCache).
11
13
  route: {
12
- enabled: false,
14
+ enabled: true,
13
15
  },
14
16
  api: {
15
17
  enabled: true,
@@ -17,6 +19,9 @@ export default defineNuxtConfigProperty<'multiCache'>((ctx) => {
17
19
  prefix: '/api/multi-cache',
18
20
  cacheTagInvalidationDelay: 5000,
19
21
  },
22
+ // Collects the cacheability of every SSR response. The headers are only
23
+ // sent as-is in the `varnish` page cache mode; the default mode rewrites
24
+ // them to browser-only headers (server/plugins/pageCache.ts).
20
25
  cdn: {
21
26
  enabled: true,
22
27
  cacheControlHeader: 'Cache-Control',
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  plugins: {
3
- "@tailwindcss/postcss": {},
3
+ '@tailwindcss/postcss': {},
4
4
  'postcss-nesting': {},
5
5
  'postcss-hexrgba': {
6
6
  colorFunctionNotation: 'modern',
@@ -10,4 +10,4 @@ module.exports = {
10
10
  preset: 'default',
11
11
  },
12
12
  },
13
- }
13
+ }
@@ -1,14 +1,19 @@
1
1
  import { defineNuxtConfigProperty } from '.'
2
+ import { pageCacheDefaults } from '../utils/pageCache'
2
3
 
3
- export default defineNuxtConfigProperty<'runtimeConfig'>(() => {
4
+ export default defineNuxtConfigProperty<'runtimeConfig'>((ctx) => {
4
5
  return {
5
6
  backendUrl: '',
6
7
  requestHost: '',
7
8
  drupalGraphqlToken: process.env.DRUPAL_GRAPHQL_TOKEN || '',
8
9
  elasticsearchUrl: '',
9
10
  elasticsearchPrefix: '',
11
+ // Page cache mode and budgets (ICMS-268), overridable per project in
12
+ // nuxt.config.ts or via NUXT_PAGE_CACHE_* environment variables. See
13
+ // utils/pageCache.ts. The route cache is off in development.
14
+ pageCache: pageCacheDefaults(ctx.DEV),
10
15
  public: {
11
- imageBaseUrl: ''
16
+ imageBaseUrl: '',
12
17
  },
13
18
  }
14
19
  })
@@ -8,9 +8,8 @@ export default defineNuxtConfigProperty<'svgIconSprite'>(() => {
8
8
  importPatterns: [
9
9
  join(currentLayerDir, './assets/symbols/**/*.svg'),
10
10
  'app/assets/symbols/**/*.svg',
11
- ]
11
+ ],
12
12
  },
13
13
  },
14
14
  }
15
15
  })
16
-
package/config/vuepal.ts CHANGED
@@ -1,24 +1,28 @@
1
1
  import { defineNuxtConfigProperty } from '.'
2
- import { resolve, dirname } from 'path'
3
- import { fileURLToPath } from 'url'
4
- import { existsSync } from 'fs'
5
2
 
6
3
  const NUXT_REQUEST_HOST = process.env.NUXT_REQUEST_HOST
7
4
  const IS_DEV = process.env.NODE_ENV === 'development'
5
+ // A dev-mode prepare on CI (the linting workflow needs one) has no local
6
+ // Drupal checkout to write into.
7
+ const IS_CI = !!process.env.CI
8
8
 
9
- export default defineNuxtConfigProperty<'vuepal'>((ctx) => {
9
+ export default defineNuxtConfigProperty<'vuepal'>(() => {
10
10
  return {
11
11
  frontendRouting: {
12
12
  enabled: true,
13
- outputPath: IS_DEV
14
- ? './../drupal/config/default/frontend_routing.settings.yml'
15
- : './app/tmp.yml',
13
+ outputPath:
14
+ IS_DEV && !IS_CI
15
+ ? './../drupal/config/default/frontend_routing.settings.yml'
16
+ : './app/tmp.yml',
16
17
  },
18
+ // The admin UI is Drupal's: editors browse the frontend inside the
19
+ // backend's site shell (docs/routing.md), so the frontend renders no
20
+ // toolbar or local tasks of its own.
17
21
  adminToolbar: {
18
- enabled: true,
22
+ enabled: false,
19
23
  },
20
24
  localTasks: {
21
- enabled: true,
25
+ enabled: false,
22
26
  },
23
27
  languageSwitchLinks: {
24
28
  enabled: true,
@@ -27,19 +31,13 @@ export default defineNuxtConfigProperty<'vuepal'>((ctx) => {
27
31
  enabled: true,
28
32
  routeQueries: {
29
33
  nodeIcmsPage: {
30
- fragments: [
31
- 'nodeIcmsPage'
32
- ]
34
+ fragments: ['nodeIcmsPage'],
33
35
  },
34
36
  slug: {
35
- fragments: [
36
- 'nodeIcmsPage',
37
- ]
37
+ fragments: ['nodeIcmsPage'],
38
38
  },
39
39
  nodeCanonical: {
40
- fragments: [
41
- 'nodeIcmsPage',
42
- ]
40
+ fragments: ['nodeIcmsPage'],
43
41
  },
44
42
  },
45
43
  },
package/error.vue CHANGED
@@ -1,34 +1,13 @@
1
1
  <template>
2
2
  <div class="bk-main-canvas relative">
3
- <ClientOnly>
4
- <div
5
- v-if="drupalUser.accessToolbar && !isEditing"
6
- class="hidden border-b border-b-gray-100 bg-white 2xl:block"
7
- >
8
- <VuepalAdminToolbar :key="language" />
9
- <div class="flex">
10
- <div class="mx-auto w-auto bg-white py-8 xl:min-w-[1174px]">
11
- <VuepalLocalTasks />
12
- </div>
13
- </div>
14
- </div>
15
- </ClientOnly>
3
+ <NuxtLoadingIndicator color="var(--primary-500)" :height="3" />
16
4
  <NuxtLayout name="error" />
17
5
  </div>
18
6
  </template>
19
7
 
20
8
  <script setup lang="ts">
21
- const route = useRoute()
22
- const drupalUser = useDrupalUser()
23
9
  const language = useCurrentLanguage()
24
10
 
25
-
26
- const isEditing = computed(
27
- () =>
28
- !!(route.query.blokkliEditing || route.query.blokkliPreview) &&
29
- drupalUser.value.accessToolbar,
30
- )
31
-
32
11
  useHead({
33
12
  htmlAttrs: {
34
13
  lang: language.value,
@@ -2,10 +2,6 @@
2
2
  <div>
3
3
  <SectionHeader />
4
4
 
5
- <!-- <NuxtPageDependency>
6
- <DrupalBreadcrumb v-if="showBreadcrumb" :links="breadcrumb" />
7
- </NuxtPageDependency> -->
8
-
9
5
  <div>
10
6
  <ClientOnly>
11
7
  <DrupalMessages v-if="!isEditing" />
@@ -21,6 +17,7 @@
21
17
  const route = useRoute()
22
18
  const drupalUser = useDrupalUser()
23
19
  const language = useCurrentLanguage()
20
+ const hreflangLinks = useHreflangLinks()
24
21
 
25
22
  const node = route.meta.drupalFrontendRoute
26
23
  ? ref(null)
@@ -30,6 +27,7 @@ useHead({
30
27
  htmlAttrs: {
31
28
  lang: language.value,
32
29
  },
30
+ link: hreflangLinks,
33
31
  meta: [
34
32
  {
35
33
  name: 'robots',
@@ -48,6 +46,4 @@ const isEditing = computed(
48
46
  !!(route.query.blokkliEditing || route.query.blokkliPreview) &&
49
47
  drupalUser.value.accessToolbar,
50
48
  )
51
-
52
- const showBreadcrumb = computed(() => !route.meta.hideBreadcrumb)
53
49
  </script>
package/layouts/error.vue CHANGED
@@ -2,10 +2,6 @@
2
2
  <div>
3
3
  <SectionHeader />
4
4
 
5
- <!-- <NuxtPageDependency>
6
- <DrupalBreadcrumb v-if="showBreadcrumb" :links="breadcrumb" />
7
- </NuxtPageDependency> -->
8
-
9
5
  <div>
10
6
  <ClientOnly>
11
7
  <DrupalMessages v-if="!isEditing" />
@@ -14,7 +10,7 @@
14
10
  <div class="container mx-auto grid grid-cols-1 gap-6 py-12">
15
11
  <div class="ck-editor text-center">
16
12
  <h1>Whooops!</h1>
17
- <p v-html="errorMessage.replace(/\n/g, '<br>')"></p>
13
+ <p v-html="errorMessage.replace(/\n/g, '<br>')" />
18
14
  <NuxtLink
19
15
  class="mt-8 inline-block underline"
20
16
  :to="`/${currentLanguage}`"
@@ -45,25 +41,21 @@ const isEditing = computed(
45
41
  drupalUser.value.accessToolbar,
46
42
  )
47
43
 
48
- const showBreadcrumb = computed(() => !route.meta.hideBreadcrumb)
49
-
50
44
  const currentLanguage = useCurrentLanguage()
51
45
 
52
46
  const error = useError()
53
47
  const errorCode = error.value?.statusCode || 'Unknown error'
54
48
 
55
- const errorMessages = {
49
+ const UNKNOWN_ERROR_MESSAGE = 'An unknown error occurred.'
50
+
51
+ const errorMessages: Record<string, string> = {
56
52
  404: "We can't find the page \nyou are looking for.",
57
53
  500: 'An error occurred on the server. Please try again later.',
58
- 'Unknown error': 'An unknown error occurred.',
59
54
  }
60
55
 
61
- const errorMessage = computed(() => {
62
- if (errorMessages[errorCode]) {
63
- return errorMessages[errorCode]
64
- }
65
- return errorMessages['Unknown error']
66
- })
56
+ const errorMessage = computed(
57
+ () => errorMessages[String(errorCode)] ?? UNKNOWN_ERROR_MESSAGE,
58
+ )
67
59
 
68
60
  useHead({
69
61
  title: errorMessage,
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Wires up blökkli fragments for the layer stack.
3
+ *
4
+ * Fragments (`defineBlokkliFragment()`) are blocks whose content lives only in
5
+ * the frontend; Drupal stores nothing but the fragment's name, so *which*
6
+ * fragments editors may place is a frontend decision. This module
7
+ *
8
+ * 1. collects the fragments blökkli discovered in all layers and exposes their
9
+ * names as `icms.allowedFragments` in the app config, from where
10
+ * `useAllowedFragments()` passes them to the page-level `<BlokkliField>` —
11
+ * a fragment only needs to exist to be offered,
12
+ * 2. disables blökkli's `fragments` feature when a layer sets
13
+ * `icms.blokkliFragments: false` or no fragment exists — otherwise the
14
+ * editor shows an "Add fragment" button opening an empty dialog, because
15
+ * the feature itself only depends on Drupal exposing the
16
+ * `blokkli_fragment` paragraph type.
17
+ *
18
+ * @example
19
+ * // In the nuxt.config.ts of a project that does not want fragments
20
+ * export default defineNuxtConfig({
21
+ * icms: {
22
+ * blokkliFragments: false,
23
+ * },
24
+ * })
25
+ */
26
+ import { defineNuxtModule } from 'nuxt/kit'
27
+ import type { IcmsLayerConfig } from '../types/layerConfig'
28
+
29
+ export default defineNuxtModule({
30
+ meta: {
31
+ name: 'blokkliFragments',
32
+ },
33
+ setup(_options, nuxt) {
34
+ // The project layer comes first, so its setting wins.
35
+ const enabled =
36
+ nuxt.options._layers
37
+ .map(
38
+ (layer) =>
39
+ (layer.config?.icms as IcmsLayerConfig | undefined)
40
+ ?.blokkliFragments,
41
+ )
42
+ .find((value) => typeof value === 'boolean') ?? true
43
+
44
+ // The features hook runs before the blocks hook, so the decision to
45
+ // disable is taken once the fragments are known.
46
+ let feature: { disable: () => void } | undefined
47
+ nuxt.hooks.hook('blokkli:alter-features', ({ features }) => {
48
+ feature = features.find((v) => v.getDefinition()?.id === 'fragments')
49
+ })
50
+
51
+ nuxt.hooks.hook('blokkli:alter-blocks', ({ blocks }) => {
52
+ const allowedFragments = enabled
53
+ ? [
54
+ ...new Set(
55
+ blocks
56
+ .filter((block) => block.type === 'fragment')
57
+ .map((block) => block.definition?.name)
58
+ .filter((name): name is string => typeof name === 'string'),
59
+ ),
60
+ ].sort()
61
+ : []
62
+
63
+ // Cast: the type of `appConfig` is generated from the very value written
64
+ // here, so it narrows `allowedFragments` to whatever the last build had.
65
+ const appConfig = nuxt.options.appConfig as Record<string, unknown>
66
+ appConfig.icms = {
67
+ ...(appConfig.icms as Record<string, unknown> | undefined),
68
+ allowedFragments,
69
+ }
70
+
71
+ if (!allowedFragments.length) {
72
+ feature?.disable()
73
+ }
74
+ })
75
+ },
76
+ })
@@ -6,100 +6,100 @@
6
6
  * proper override priority (main app overrides layers).
7
7
  */
8
8
 
9
- import { defineNuxtModule, addPlugin, addTemplate, createResolver } from '@nuxt/kit'
9
+ import { defineNuxtModule } from '@nuxt/kit'
10
+ import type { IcmsLayerConfig } from '../types/layerConfig'
10
11
 
11
12
  export default defineNuxtModule({
12
13
  meta: { name: 'dynamicEntityComponentResolver' },
13
14
  setup(_options, nuxt) {
14
-
15
15
  // Private helper to extract unique fragment values from any routeQueries object.
16
- function extractFragments(routeQueries: any): string[] {
17
- const fragments = new Set<string>();
16
+ function extractFragments(routeQueries: unknown): string[] {
17
+ const fragments = new Set<string>()
18
18
 
19
- function traverse(obj: any) {
19
+ function traverse(obj: unknown) {
20
20
  if (Array.isArray(obj)) {
21
- obj.forEach(item => traverse(item));
21
+ obj.forEach((item) => traverse(item))
22
22
  } else if (obj && typeof obj === 'object') {
23
- for (const key in obj) {
24
- if (key === 'fragments' && Array.isArray(obj[key])) {
25
- obj[key].forEach(frag => {
26
- if (typeof frag === 'string') fragments.add(frag);
27
- });
23
+ for (const [key, value] of Object.entries(obj)) {
24
+ if (key === 'fragments' && Array.isArray(value)) {
25
+ value.forEach((frag) => {
26
+ if (typeof frag === 'string') fragments.add(frag)
27
+ })
28
28
  } else {
29
- traverse(obj[key]);
29
+ traverse(value)
30
30
  }
31
31
  }
32
32
  }
33
33
  }
34
34
 
35
- traverse(routeQueries);
36
- return Array.from(fragments);
35
+ traverse(routeQueries)
36
+ return Array.from(fragments)
37
37
  }
38
38
 
39
+ const layerConfigs: IcmsLayerConfig[] = nuxt.options._layers
40
+ .map((layer) => layer.config?.icms)
41
+ .filter((config): config is IcmsLayerConfig => !!config)
42
+
39
43
  // Creates graphQL fragments for all reoute queries.
40
44
  const entityFragments = nuxt.options._layers
41
- .map((layer, idx) => {
42
- const queries = layer.config?.vuepal?.drupalRoute?.routeQueries;
43
- const fragments = extractFragments(queries);
44
- return fragments;
45
+ .map((layer) => {
46
+ const vuepal = layer.config?.vuepal
47
+ const queries = vuepal ? vuepal.drupalRoute?.routeQueries : undefined
48
+ return extractFragments(queries)
45
49
  })
46
- .filter(arr => Array.isArray(arr) && arr.length)
50
+ .filter((arr) => Array.isArray(arr) && arr.length)
47
51
  .flat()
48
- .filter(Boolean);
52
+ .filter(Boolean)
49
53
 
50
54
  // Extend entity fragments (index)
51
- const entityFragmentsExtend = nuxt.options._layers
52
- .map((layer, idx) => {
53
- return layer.config?.icms?.extendFragments?.entity;
54
- })
55
- .filter(arr => Array.isArray(arr) && arr.length)
55
+ const entityFragmentsExtend = layerConfigs
56
+ .map((config) => config.extendFragments?.entity)
57
+ .filter((arr) => Array.isArray(arr) && arr.length)
56
58
  .flat()
57
- .filter(Boolean);
59
+ .filter(Boolean)
58
60
 
59
61
  // Extend entity fragments (Teaser)
60
- const entityTeaserFragmentsExtend = nuxt.options._layers
61
- .map((layer, idx) => {
62
- return layer.config?.icms?.extendFragments?.teaser;
63
- })
64
- .filter(arr => Array.isArray(arr) && arr.length)
62
+ const entityTeaserFragmentsExtend = layerConfigs
63
+ .map((config) => config.extendFragments?.teaser)
64
+ .filter((arr) => Array.isArray(arr) && arr.length)
65
65
  .flat()
66
- .filter(Boolean);
66
+ .filter(Boolean)
67
67
 
68
68
  // Extend entity fragments (SearchResult)
69
- const entitySearchResultFragmentsExtend = nuxt.options._layers
70
- .map((layer, idx) => {
71
- return layer.config?.icms?.extendFragments?.searchResult;
72
- })
73
- .filter(arr => Array.isArray(arr) && arr.length)
69
+ const entitySearchResultFragmentsExtend = layerConfigs
70
+ .map((config) => config.extendFragments?.searchResult)
71
+ .filter((arr) => Array.isArray(arr) && arr.length)
74
72
  .flat()
75
- .filter(Boolean);
73
+ .filter(Boolean)
76
74
 
77
75
  const entityFagment = `
78
76
  fragment entity on Entity {
79
77
  __typename
80
- ${entityFragments.map((fragmentName) => fragmentName ? `...${fragmentName}` : '').join('\n ')}
81
- ${entityFragmentsExtend.map((fragmentName) => fragmentName ? `...${fragmentName}` : '').join('\n ')}
78
+ ${entityFragments.map((fragmentName) => (fragmentName ? `...${fragmentName}` : '')).join('\n ')}
79
+ ${entityFragmentsExtend.map((fragmentName) => (fragmentName ? `...${fragmentName}` : '')).join('\n ')}
82
80
  }
83
81
  `
84
82
  const entityTeaserFagment = `
85
83
  fragment entityTeaser on Entity {
86
84
  __typename
87
- ${entityFragments.map((fragmentName) => fragmentName ? `...${fragmentName}Teaser` : '').join('\n ')}
88
- ${entityTeaserFragmentsExtend.map((fragmentName) => fragmentName ? `...${fragmentName}` : '').join('\n ')}
85
+ ${entityFragments.map((fragmentName) => (fragmentName ? `...${fragmentName}Teaser` : '')).join('\n ')}
86
+ ${entityTeaserFragmentsExtend.map((fragmentName) => (fragmentName ? `...${fragmentName}` : '')).join('\n ')}
89
87
  }
90
88
  `
91
89
  const entitySearchResultFagment = `
92
90
  fragment entitySearchResult on Entity {
93
91
  __typename
94
- ${entityFragments.map((fragmentName) => fragmentName ? `...${fragmentName}SearchResult` : '').join('\n ')}
95
- ${entitySearchResultFragmentsExtend.map((fragmentName) => fragmentName ? `...${fragmentName}` : '').join('\n ')}
92
+ ${entityFragments.map((fragmentName) => (fragmentName ? `...${fragmentName}SearchResult` : '')).join('\n ')}
93
+ ${entitySearchResultFragmentsExtend.map((fragmentName) => (fragmentName ? `...${fragmentName}` : '')).join('\n ')}
96
94
  }
97
95
  `
98
96
  nuxt.hooks.hookOnce('nuxt-graphql-middleware:init', (context) => {
99
97
  context.addDocument('fragment.entity.graphql', entityFagment)
100
98
  context.addDocument('fragment.entityTeaser.graphql', entityTeaserFagment)
101
- context.addDocument('fragment.entitySearchResult.graphql', entitySearchResultFagment)
99
+ context.addDocument(
100
+ 'fragment.entitySearchResult.graphql',
101
+ entitySearchResultFagment,
102
+ )
102
103
  })
103
- }
104
+ },
104
105
  })
105
-