@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.
- package/README.md +86 -0
- package/__tests__/anchors.spec.ts +43 -0
- package/__tests__/contract/blokkliComponents.spec.ts +59 -0
- package/__tests__/contract/componentFragments.spec.ts +24 -0
- package/__tests__/contract/languages.spec.ts +63 -0
- package/__tests__/contract/project.ts +144 -0
- package/__tests__/hreflang.spec.ts +39 -0
- package/{tests → __tests__}/pages.spec.ts +14 -5
- package/app.config.ts +15 -0
- package/app.vue +3 -21
- package/assets/css/backend.css +56 -0
- package/assets/css/base/base.css +2 -2
- package/assets/css/base/typography.css +2 -2
- package/assets/css/ckeditor.css +3 -4
- package/assets/css/components/decorations.css +1 -1
- package/assets/css/components/editor.css +1 -1
- package/assets/css/components/grid-container.css +19 -19
- package/assets/css/fe-fixes.css +6 -12
- package/assets/css/theme/colors/color-primary.css +33 -11
- package/assets/css/theme/colors/color-secondary.css +33 -11
- package/assets/css/utilities/utilities.css +1 -1
- package/assets/symbols/anchor.svg +3 -0
- package/blokkli.editAdapter.ts +56 -0
- package/bundles.ts +6 -6
- package/cache/__tests__/pageCache.spec.ts +540 -0
- package/cache/cacheTagRegistry.ts +124 -0
- package/cache/enabledForRequest.ts +21 -0
- package/cache/multiCache.serverOptions.ts +58 -0
- package/cache/pageCache.ts +360 -0
- package/components/Blokkli/Anchor/icon.svg +3 -0
- package/components/Blokkli/Anchor/index.vue +13 -0
- package/components/Blokkli/AnchorNavigation/icon.svg +8 -0
- package/components/Blokkli/AnchorNavigation/index.vue +9 -0
- package/components/Blokkli/Card/Element/index.vue +0 -1
- package/components/Blokkli/Collapsible/Element/index.vue +5 -0
- package/components/Blokkli/FAQ/Element/index.vue +0 -1
- package/components/Blokkli/Fragment/Separator/icon.svg +4 -0
- package/components/Blokkli/Fragment/Separator/index.vue +87 -0
- package/components/Blokkli/Gallery/index.vue +1 -3
- package/components/Blokkli/IconText/Element/index.vue +0 -1
- package/components/Blokkli/Introduction/index.vue +0 -1
- package/components/Blokkli/Listing/icon.svg +9 -0
- package/components/Blokkli/Listing/index.vue +24 -0
- package/components/Blokkli/Logo/Element/index.vue +0 -1
- package/components/Blokkli/Table/index.vue +0 -1
- package/components/Blokkli/Team/Element/index.vue +0 -1
- package/components/Blokkli/Team/Layout/index.vue +0 -1
- package/components/Blokkli/Testimonial/Element/index.vue +0 -1
- package/components/Blokkli/Text/index.vue +0 -1
- package/components/DesignSystem/Atoms/Button.vue +3 -0
- package/components/DesignSystem/Atoms/GridRow.vue +3 -1
- package/components/DesignSystem/Molecules/ButtonGroup.vue +5 -4
- package/components/DesignSystem/Molecules/IconText.vue +6 -6
- package/components/DesignSystem/Molecules/MediaLightbox.vue +8 -8
- package/components/DesignSystem/Molecules/MediaSlider.vue +9 -11
- package/components/DesignSystem/Molecules/Partnership.vue +7 -4
- package/components/DesignSystem/Molecules/Table.vue +35 -19
- package/components/DesignSystem/Molecules/TitleComposition.vue +46 -15
- package/components/DesignSystem/Molecules/iFrame.vue +2 -2
- package/components/DesignSystem/Organisms/CallToAction.vue +8 -7
- package/components/DesignSystem/Organisms/CallToActionFeature.vue +8 -7
- package/components/DesignSystem/Organisms/CallToActionFullwidth.vue +9 -8
- package/components/DesignSystem/Organisms/ContentBlock.vue +26 -16
- package/components/DesignSystem/Organisms/FacetedListing.vue +38 -17
- package/components/DesignSystem/Organisms/FactCard.vue +6 -7
- package/components/DesignSystem/Organisms/FeatureCard.vue +14 -14
- package/components/DesignSystem/Organisms/FlipTeaser.vue +19 -19
- package/components/DesignSystem/Organisms/HeaderHero.vue +11 -11
- package/components/DesignSystem/Organisms/HeaderTextImage.vue +10 -10
- package/components/DesignSystem/Organisms/LinkList.vue +20 -12
- package/components/DesignSystem/Organisms/LogoElement.vue +7 -4
- package/components/DesignSystem/Organisms/Map.vue +1 -1
- package/components/DesignSystem/Organisms/SearchResults.vue +15 -6
- package/components/DesignSystem/Organisms/SpotlightCard.vue +18 -18
- package/components/DesignSystem/Organisms/TeamElement.vue +19 -18
- package/components/DesignSystem/Organisms/TeaserCard.vue +16 -17
- package/components/DesignSystem/Organisms/TeaserSimple.vue +4 -31
- package/components/DesignSystem/Organisms/Testimonial.vue +13 -13
- package/components/DesignSystem/Organisms/TextCard.vue +10 -10
- package/components/DesignSystem/Organisms/TimelineElement.vue +20 -19
- package/components/DesignSystem/Organisms/TwoColumnTextElement.vue +5 -5
- package/components/DesignSystem/README.md +59 -40
- package/components/Drupal/Breadcrumb/index.vue +25 -12
- package/components/Drupal/Messages/index.vue +9 -4
- package/components/Drupal/Webform/index.vue +271 -51
- package/components/Drupal/Webform/mutation.graphql +17 -1
- package/components/Form/HiddenInput.vue +1 -4
- package/components/Form/SelectInput.vue +1 -1
- package/components/Form/SubmitButton.vue +2 -2
- package/components/Form/TextAreaInput.vue +3 -3
- package/components/Form/TextEmailInput.vue +1 -1
- package/components/Form/TextInput.vue +1 -1
- package/components/Form/TextPhoneInput.vue +1 -1
- package/components/Form/WebformMarkup.vue +1 -1
- package/components/Form/composables/useFormDefaultValue.ts +9 -13
- package/components/Form/composables/useFormElement.ts +5 -3
- package/components/Form/utils/types.ts +3 -3
- package/components/Media/Image/fragment.graphql +4 -0
- package/components/Media/Image/index.vue +27 -17
- package/components/Media/RemoteVideo/index.vue +3 -3
- package/components/Media/Video/index.vue +1 -1
- package/components/Node/Icms/Page/SearchResult/fragment.graphql +1 -1
- package/components/Node/Icms/Page/SearchResult/index.vue +2 -2
- package/components/Node/Icms/Page/Teaser/index.vue +1 -1
- package/components/Node/Icms/Page/index.vue +8 -2
- package/components/Paragraph/Anchor/fragment.graphql +4 -0
- package/components/Paragraph/Anchor/index.vue +48 -0
- package/components/Paragraph/AnchorNavigation/fragment.graphql +3 -0
- package/components/Paragraph/AnchorNavigation/index.vue +154 -0
- package/components/Paragraph/BlokkliFragment/fragment.graphql +5 -0
- package/components/Paragraph/Button/Element/fragment.graphql +1 -1
- package/components/Paragraph/Button/Element/index.vue +13 -8
- package/components/Paragraph/Card/Element/index.vue +1 -1
- package/components/Paragraph/Card/Layout/index.vue +9 -9
- package/components/Paragraph/Collapsible/Element/index.vue +2 -2
- package/components/Paragraph/Collapsible/Layout/index.vue +19 -18
- package/components/Paragraph/Contact/index.vue +17 -17
- package/components/Paragraph/Downloads/fragment.graphql +1 -1
- package/components/Paragraph/Downloads/index.vue +13 -13
- package/components/Paragraph/FAQ/Element/index.vue +4 -3
- package/components/Paragraph/FAQ/Layout/index.vue +42 -21
- package/components/Paragraph/FromLibrary/fragment.graphql +17 -0
- package/components/Paragraph/Gallery/index.vue +14 -14
- package/components/Paragraph/HeroImage/index.vue +26 -19
- package/components/Paragraph/IconText/Element/index.vue +4 -4
- package/components/Paragraph/IconText/Layout/index.vue +12 -12
- package/components/Paragraph/Introduction/index.vue +17 -17
- package/components/Paragraph/LinkLists/Element/fragment.graphql +1 -1
- package/components/Paragraph/LinkLists/Element/index.vue +5 -5
- package/components/Paragraph/LinkLists/Layout/index.vue +9 -9
- package/components/Paragraph/Listing/fragment.graphql +14 -0
- package/components/Paragraph/Listing/index.vue +91 -0
- package/components/Paragraph/Logo/Element/fragment.graphql +1 -1
- package/components/Paragraph/Logo/Element/index.vue +7 -4
- package/components/Paragraph/Media/index.vue +23 -21
- package/components/Paragraph/Partnerships/Element/fragment.graphql +1 -1
- package/components/Paragraph/Partnerships/Element/index.vue +10 -7
- package/components/Paragraph/Partnerships/Layout/index.vue +13 -13
- package/components/Paragraph/SingleTestimonial/index.vue +13 -13
- package/components/Paragraph/Table/index.vue +12 -12
- package/components/Paragraph/Team/Element/index.vue +16 -14
- package/components/Paragraph/Team/Layout/index.vue +9 -9
- package/components/Paragraph/Teasers/index.vue +14 -17
- package/components/Paragraph/Testimonial/Element/index.vue +19 -18
- package/components/Paragraph/Testimonial/Layout/index.vue +24 -18
- package/components/Paragraph/Text/fragment.graphql +1 -1
- package/components/Paragraph/Text/index.vue +8 -8
- package/components/Paragraph/TextMedia/index.vue +22 -22
- package/components/Paragraph/Timeline/Element/index.vue +19 -19
- package/components/Paragraph/Timeline/Layout/index.vue +51 -20
- package/components/Paragraph/TwoColumnText/Element/index.vue +13 -13
- package/components/Paragraph/TwoColumnText/Layout/index.vue +1 -1
- package/components/Paragraph/iFrame/index.vue +8 -8
- package/components/Search/Facets/FacetDropdownUI.vue +25 -31
- package/components/Search/Facets/FacetedSearchUI.vue +72 -48
- package/components/Search/Facets/fragment.graphql +20 -15
- package/components/Section/Footer/index.vue +55 -32
- package/components/Section/Header/Buttons.vue +4 -5
- package/components/Section/Header/DesktopMainMenu.vue +5 -4
- package/components/Section/Header/DesktopMainMenuDropdown.vue +5 -5
- package/components/Section/Header/LanguageDropdown.vue +2 -3
- package/components/Section/Header/Logo.vue +3 -3
- package/components/Section/Header/MainButtons.vue +4 -5
- package/components/Section/Header/MetaButtons.vue +5 -6
- package/components/Section/Header/MetaMenu.vue +1 -1
- package/components/Section/Header/MobileMainMenu.vue +1 -1
- package/components/Section/Header/MobileMainMenuItem.vue +2 -2
- package/components/Section/Header/index.vue +6 -13
- package/composables/__tests__/useImageSources.spec.ts +91 -0
- package/composables/breadcrumb.ts +12 -7
- package/composables/useAccordionScroll.ts +12 -4
- package/composables/useAllowedFragments.ts +14 -0
- package/composables/useAnchors.ts +48 -0
- package/composables/useChildTitleWeight.ts +1 -1
- package/composables/useCurrentLangcodeEnum.ts +1 -1
- package/composables/useCurrentNodeId.ts +8 -0
- package/composables/useDrupalUrl.ts +15 -0
- package/composables/useEnrichFacetLabels.ts +8 -6
- package/composables/useEntityRegistry.ts +3 -3
- package/composables/useGlobalConfig.ts +1 -2
- package/composables/useGraphqlCacheability.ts +12 -1
- package/composables/useHreflangLinks.ts +54 -0
- package/composables/useImageSources.ts +70 -28
- package/composables/useInitData.ts +1 -2
- package/composables/useIsHomepage.ts +1 -1
- package/composables/useLinkUrl.ts +30 -0
- package/composables/useMediaLightbox.ts +62 -50
- package/composables/useMenu.ts +1 -3
- package/composables/useMessageBus.ts +14 -7
- package/composables/useMobileMenuOverlayBus.ts +1 -1
- package/composables/useNavigationOffset.ts +49 -6
- package/composables/usePageCache.ts +33 -0
- package/composables/useSiteShell.ts +11 -0
- package/composables/useTheme.ts +17 -13
- package/config/__tests__/stubs/nuxt-multi-cache-config.ts +2 -0
- package/config/blokkli.ts +7 -13
- package/config/easyTexts.ts +1 -1
- package/config/graphqlMiddleware.ts +11 -4
- package/config/index.ts +5 -2
- package/config/languageNegotiation.ts +1 -1
- package/config/multiCache.ts +6 -1
- package/config/postcss-drupal/postcss.config.cjs +2 -2
- package/config/runtimeConfig.ts +7 -2
- package/config/svgIconSprite.ts +1 -2
- package/config/vuepal.ts +16 -18
- package/error.vue +1 -22
- package/layouts/default.vue +2 -6
- package/layouts/error.vue +7 -15
- package/modules/blokkliFragments.ts +76 -0
- package/modules/dynamicEntityComponentResolver.ts +47 -47
- package/modules/dynamicParagraphResolver.ts +43 -8
- package/modules/pageCache.ts +106 -0
- package/nuxt.config.ts +62 -33
- package/package.json +23 -22
- package/pages/[...slug]/index.vue +25 -4
- package/pages/index.vue +3 -3
- package/pages/node/[nid]/index.vue +2 -3
- package/patches/@blokkli+editor+2.0.0-alpha.13.patch +15 -1
- package/plugins/currentNode.ts +17 -0
- package/plugins/graphqlMiddleware.ts +10 -2
- package/plugins/initData.ts +15 -7
- package/plugins/registerNodeIcmsPageComponents.ts +6 -2
- package/plugins/siteShell.client.ts +94 -0
- package/queries/facetTermLabels.graphql +1 -5
- package/queries/fragment.linkUrl.graphql +12 -0
- package/queries/query.contentSearch.graphql +17 -2
- package/queries/query.initData.graphql +1 -1
- package/scripts/create-shade.ts +13 -4
- package/scripts/update-icms.ts +3 -1
- package/server/__tests__/graphqlMiddleware.serverOptions.spec.ts +2 -2
- package/server/__tests__/turnstile.spec.ts +119 -0
- package/server/api/icms/blokkli/last-changed.get.ts +24 -0
- package/server/api/icon/[...params].ts +3 -9
- package/server/graphqlMiddleware.serverOptions.ts +9 -8
- package/server/helpers/index.ts +0 -1
- package/server/middleware/turnstile.ts +51 -0
- package/server/plugins/frameAncestors.ts +20 -0
- package/server/plugins/pageCache.ts +59 -0
- package/server/utils/__tests__/accessDeniedHandover.spec.ts +49 -0
- package/server/utils/__tests__/backendRequestHeaders.spec.ts +115 -0
- package/server/utils/accessDeniedHandover.ts +48 -0
- package/server/utils/backendRequestHeaders.ts +51 -0
- package/server/utils/cacheability.ts +4 -4
- package/shared/__tests__/blokkliState.spec.ts +54 -0
- package/shared/__tests__/csp.spec.ts +53 -0
- package/shared/__tests__/drupalUrl.spec.ts +50 -0
- package/shared/blokkliState.ts +40 -0
- package/shared/csp.ts +47 -0
- package/shared/drupalUrl.ts +30 -0
- package/tailwind.config.ts +0 -5
- package/types/breadcrumb.ts +22 -0
- package/types/global.ts +15 -14
- package/types/langcode.ts +16 -0
- package/types/layerConfig.ts +84 -0
- package/utils/pageCache.ts +95 -0
- package/utils/turnstile.ts +35 -0
- package/composables/useWebformEnrichment.ts +0 -68
- package/server/__tests__/graphqlMiddleware.ts +0 -41
- package/server/multiCache.serverOptions.ts +0 -32
- package/vitest.config.mts +0 -28
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `icms` key every ICMS layer may add to its Nuxt config.
|
|
3
|
+
*
|
|
4
|
+
* It is read by the dynamicParagraphResolver and
|
|
5
|
+
* dynamicEntityComponentResolver modules, which merge the fragment names of
|
|
6
|
+
* all layers into the generated GraphQL fragments.
|
|
7
|
+
*/
|
|
8
|
+
export type IcmsLayerConfig = {
|
|
9
|
+
/**
|
|
10
|
+
* Entity types that get a page component (e.g. `Node`).
|
|
11
|
+
*/
|
|
12
|
+
pageComponentTypes?: string[]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether editors may place blökkli fragments (`defineBlokkliFragment()`).
|
|
16
|
+
*
|
|
17
|
+
* Defaults to `true`: every fragment component in any layer is offered. A
|
|
18
|
+
* project sets `false` to disable the feature; the `blokkliFragments` module
|
|
19
|
+
* then removes the editor's fragments feature.
|
|
20
|
+
*/
|
|
21
|
+
blokkliFragments?: boolean
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Fragment names contributed by the layer's paragraph components.
|
|
25
|
+
*/
|
|
26
|
+
components?: {
|
|
27
|
+
fragments?: string[]
|
|
28
|
+
/**
|
|
29
|
+
* Fragments of wrapper paragraphs that embed another paragraph (e.g.
|
|
30
|
+
* `paragraphFromLibrary`). They are spread into `paragraph` only — the
|
|
31
|
+
* embedded paragraph uses `paragraphInLibrary`, which leaves them out so
|
|
32
|
+
* the fragments do not spread themselves.
|
|
33
|
+
*/
|
|
34
|
+
wrapperFragments?: string[]
|
|
35
|
+
teaserFragments?: string[]
|
|
36
|
+
searchResultFragments?: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Additional fragments spread into the generated entity fragments.
|
|
41
|
+
*/
|
|
42
|
+
extendFragments?: {
|
|
43
|
+
entity?: string[]
|
|
44
|
+
teaser?: string[]
|
|
45
|
+
searchResult?: string[]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare module '@nuxt/schema' {
|
|
50
|
+
interface NuxtConfig {
|
|
51
|
+
icms?: IcmsLayerConfig
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface NuxtOptions {
|
|
55
|
+
icms?: IcmsLayerConfig
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Re-declared because nuxt-graphql-middleware augments `nuxt/schema` instead
|
|
60
|
+
* of `@nuxt/schema`, which does not merge into the hook types used by
|
|
61
|
+
* `nuxt.hooks.hookOnce()`.
|
|
62
|
+
*
|
|
63
|
+
* The `blokkli:alter-*` hooks are declared here because @blokkli/editor ships
|
|
64
|
+
* them without types; only the members the ICMS modules use are described.
|
|
65
|
+
*/
|
|
66
|
+
interface NuxtHooks {
|
|
67
|
+
'nuxt-graphql-middleware:init': (context: {
|
|
68
|
+
addDocument: (name: string, source: string) => void
|
|
69
|
+
schemaHasType: (name: string) => boolean
|
|
70
|
+
}) => void | Promise<void>
|
|
71
|
+
'blokkli:alter-features': (context: {
|
|
72
|
+
features: {
|
|
73
|
+
getDefinition: () => { id: string } | null | undefined
|
|
74
|
+
disable: () => void
|
|
75
|
+
}[]
|
|
76
|
+
}) => void | Promise<void>
|
|
77
|
+
'blokkli:alter-blocks': (context: {
|
|
78
|
+
blocks: {
|
|
79
|
+
type: 'main' | 'context' | 'fragment' | null
|
|
80
|
+
definition?: { name?: string } | null
|
|
81
|
+
}[]
|
|
82
|
+
}) => void | Promise<void>
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page cache configuration (ICMS-268), shared by the Nitro server options and
|
|
3
|
+
* plugin and by the usePageCache() composable. Kept outside `server/` so the
|
|
4
|
+
* app can import it too.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* How the SSR responses are cached (ICMS-268).
|
|
9
|
+
*
|
|
10
|
+
* - `default`: no shared cache in front of Nuxt. Anonymous HTML is cached in
|
|
11
|
+
* the bounded in-memory route cache of nuxt-multi-cache and sent with a
|
|
12
|
+
* short browser-only Cache-Control; no s-maxage and no Cache-Tag header.
|
|
13
|
+
* - `varnish`: a shared cache (Varnish) sits in front of Nuxt. Responses carry
|
|
14
|
+
* s-maxage and Cache-Tag for it, and the in-memory route cache stays off so
|
|
15
|
+
* that Varnish never re-caches a page Nuxt has not purged yet.
|
|
16
|
+
*/
|
|
17
|
+
export type PageCacheMode = 'default' | 'varnish'
|
|
18
|
+
|
|
19
|
+
export type PageCacheConfig = {
|
|
20
|
+
mode: PageCacheMode
|
|
21
|
+
/** Browser max-age (seconds) for cacheable anonymous responses. */
|
|
22
|
+
browserMaxAge: number
|
|
23
|
+
/**
|
|
24
|
+
* Byte budget of the in-memory route (HTML) cache. 0 disables it, `auto`
|
|
25
|
+
* derives it from the memory limit of the container.
|
|
26
|
+
*/
|
|
27
|
+
routeMaxSize: number | 'auto'
|
|
28
|
+
/** Store route cache entries gzipped (about 8× smaller). */
|
|
29
|
+
routeCompression: boolean
|
|
30
|
+
/** Upper bound (seconds) for how long a page stays in the route cache. */
|
|
31
|
+
routeMaxAge: number
|
|
32
|
+
/** Byte budget of the in-memory data cache. */
|
|
33
|
+
dataMaxSize: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const MB = 1024 * 1024
|
|
37
|
+
|
|
38
|
+
export const PAGE_CACHE_DEFAULTS: PageCacheConfig = Object.freeze({
|
|
39
|
+
mode: 'default',
|
|
40
|
+
browserMaxAge: 300,
|
|
41
|
+
routeMaxSize: 'auto',
|
|
42
|
+
routeCompression: true,
|
|
43
|
+
routeMaxAge: 7 * 86_400,
|
|
44
|
+
dataMaxSize: 8 * MB,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The runtime config defaults. In development (`nuxi dev`) the route cache is
|
|
49
|
+
* off: nothing purges it locally, so anonymous pages would stay stale until
|
|
50
|
+
* the dev server restarts. NUXT_PAGE_CACHE_ROUTE_MAX_SIZE=auto turns it on.
|
|
51
|
+
*/
|
|
52
|
+
export function pageCacheDefaults(dev: boolean): PageCacheConfig {
|
|
53
|
+
return { ...PAGE_CACHE_DEFAULTS, ...(dev ? { routeMaxSize: 0 } : {}) }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toBoolean(value: unknown, fallback: boolean): boolean {
|
|
57
|
+
if (typeof value === 'boolean') {
|
|
58
|
+
return value
|
|
59
|
+
}
|
|
60
|
+
if (value === 'true' || value === '1') {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
if (value === 'false' || value === '0') {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
return fallback
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function toNonNegativeNumber<T>(value: unknown, fallback: T): number | T {
|
|
70
|
+
const number = typeof value === 'string' ? Number(value) : value
|
|
71
|
+
return typeof number === 'number' && Number.isFinite(number) && number >= 0
|
|
72
|
+
? Math.floor(number)
|
|
73
|
+
: fallback
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalize the `pageCache` runtime config; values may arrive as strings from
|
|
78
|
+
* NUXT_PAGE_CACHE_* environment variables.
|
|
79
|
+
*/
|
|
80
|
+
export function resolvePageCacheConfig(
|
|
81
|
+
input?: Partial<Record<keyof PageCacheConfig, unknown>> | null,
|
|
82
|
+
): PageCacheConfig {
|
|
83
|
+
const d = PAGE_CACHE_DEFAULTS
|
|
84
|
+
return {
|
|
85
|
+
mode: input?.mode === 'varnish' ? 'varnish' : 'default',
|
|
86
|
+
browserMaxAge: toNonNegativeNumber(input?.browserMaxAge, d.browserMaxAge),
|
|
87
|
+
routeMaxSize:
|
|
88
|
+
input?.routeMaxSize === 'auto'
|
|
89
|
+
? 'auto'
|
|
90
|
+
: toNonNegativeNumber(input?.routeMaxSize, d.routeMaxSize),
|
|
91
|
+
routeCompression: toBoolean(input?.routeCompression, d.routeCompression),
|
|
92
|
+
routeMaxAge: toNonNegativeNumber(input?.routeMaxAge, d.routeMaxAge),
|
|
93
|
+
dataMaxSize: toNonNegativeNumber(input?.dataMaxSize, d.dataMaxSize),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and helpers for the Turnstile spam protection.
|
|
3
|
+
*
|
|
4
|
+
* Kept free of Nitro auto-imports and outside `server/` so both the middleware and the
|
|
5
|
+
* frontend component can use them, and so they are unit testable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Header carrying the Turnstile token from the browser to the Nuxt server.
|
|
10
|
+
*
|
|
11
|
+
* The token cannot travel as a GraphQL variable: `submitWebform` forwards its
|
|
12
|
+
* variables to Drupal, which knows nothing about Turnstile.
|
|
13
|
+
*/
|
|
14
|
+
export const TURNSTILE_TOKEN_HEADER = 'x-turnstile-token'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Marks a rejected submission, so the form can show the right message.
|
|
18
|
+
*/
|
|
19
|
+
export const TURNSTILE_ERROR_CODE = 'TURNSTILE_VERIFICATION_FAILED'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether a request is a webform submission and therefore has to be verified.
|
|
23
|
+
*
|
|
24
|
+
* Matched on the operation suffix rather than the full route so a project that
|
|
25
|
+
* changes `graphqlMiddleware.serverApiPrefix` stays protected.
|
|
26
|
+
*/
|
|
27
|
+
export function isWebformSubmissionRequest(path?: string | null): boolean {
|
|
28
|
+
if (!path) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pathname = path.split('?')[0] ?? ''
|
|
33
|
+
|
|
34
|
+
return pathname.endsWith('/mutation/submitWebform')
|
|
35
|
+
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
// Composable for webform field enrichment system
|
|
2
|
-
// Allows bundles to register custom field processors
|
|
3
|
-
|
|
4
|
-
interface FormElement {
|
|
5
|
-
metadata?: {
|
|
6
|
-
key?: string
|
|
7
|
-
type?: string
|
|
8
|
-
[key: string]: any
|
|
9
|
-
}
|
|
10
|
-
[key: string]: any
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface EnrichmentContext {
|
|
14
|
-
formElement: FormElement
|
|
15
|
-
props: Record<string, any>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface SubmissionContext {
|
|
19
|
-
data: Record<string, any>
|
|
20
|
-
elements: Array<{ element: string; value: any }>
|
|
21
|
-
props: Record<string, any>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type EnrichmentFunction = (context: EnrichmentContext) => FormElement | null
|
|
25
|
-
type SubmissionProcessor = (context: SubmissionContext) => void
|
|
26
|
-
|
|
27
|
-
const enrichmentFunctions: EnrichmentFunction[] = []
|
|
28
|
-
const submissionProcessors: SubmissionProcessor[] = []
|
|
29
|
-
|
|
30
|
-
export function useWebformEnrichment() {
|
|
31
|
-
const registerEnrichment = (fn: EnrichmentFunction) => {
|
|
32
|
-
if (!enrichmentFunctions.includes(fn)) {
|
|
33
|
-
enrichmentFunctions.push(fn)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const registerSubmissionProcessor = (fn: SubmissionProcessor) => {
|
|
38
|
-
if (!submissionProcessors.includes(fn)) {
|
|
39
|
-
submissionProcessors.push(fn)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const enrichFormElement = (formElement: FormElement, props: Record<string, any>): FormElement => {
|
|
44
|
-
// Try each registered enrichment function
|
|
45
|
-
for (const fn of enrichmentFunctions) {
|
|
46
|
-
const result = fn({ formElement, props })
|
|
47
|
-
if (result) {
|
|
48
|
-
return result
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Return original if no enrichment applied
|
|
52
|
-
return formElement
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const processSubmission = (context: SubmissionContext) => {
|
|
56
|
-
// Run all submission processors
|
|
57
|
-
for (const fn of submissionProcessors) {
|
|
58
|
-
fn(context)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
registerEnrichment,
|
|
64
|
-
registerSubmissionProcessor,
|
|
65
|
-
enrichFormElement,
|
|
66
|
-
processSubmission,
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
-
import graphqlMiddlewareConfig from './../graphqlMiddleware.serverOptions'
|
|
3
|
-
import type { H3Event } from 'h3'
|
|
4
|
-
|
|
5
|
-
vi.mock('#graphql-server-options', () => {
|
|
6
|
-
return {
|
|
7
|
-
defineGraphqlServerOptions(v: unknown) {
|
|
8
|
-
return v
|
|
9
|
-
},
|
|
10
|
-
}
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const metaMock = vi.hoisted(() => ({
|
|
14
|
-
importMetaClient: false,
|
|
15
|
-
}))
|
|
16
|
-
|
|
17
|
-
vi.mock('~~/shared/importMeta', () => metaMock)
|
|
18
|
-
|
|
19
|
-
describe('The nuxt-graphql-middleware config', () => {
|
|
20
|
-
test('Passes appropriate incoming headers', async () => {
|
|
21
|
-
metaMock.importMetaClient = false
|
|
22
|
-
expect(graphqlMiddlewareConfig.serverFetchOptions).toBeDefined()
|
|
23
|
-
|
|
24
|
-
const event = {
|
|
25
|
-
node: {
|
|
26
|
-
req: {
|
|
27
|
-
headers: {
|
|
28
|
-
foobar: 'test',
|
|
29
|
-
cookie: 'my_cookie',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
} as unknown as H3Event
|
|
34
|
-
|
|
35
|
-
const result = await graphqlMiddlewareConfig!.serverFetchOptions!(event)
|
|
36
|
-
// @ts-ignore
|
|
37
|
-
expect(result.headers?.foobar).toBeUndefined()
|
|
38
|
-
// @ts-ignore
|
|
39
|
-
expect(result.headers?.cookie).toEqual('my_cookie')
|
|
40
|
-
})
|
|
41
|
-
})
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { defineMultiCacheOptions } from 'nuxt-multi-cache/server-options'
|
|
2
|
-
import { getHeaders } from 'h3'
|
|
3
|
-
import type { H3Event } from 'h3'
|
|
4
|
-
import lruCacheDriver from 'unstorage/drivers/lru-cache'
|
|
5
|
-
|
|
6
|
-
export default defineMultiCacheOptions(() => {
|
|
7
|
-
return {
|
|
8
|
-
api: {},
|
|
9
|
-
data: {},
|
|
10
|
-
enabledForRequest: (event: H3Event) => {
|
|
11
|
-
const hasSession = (event.node.req.headers.cookie || '').includes('SSESS')
|
|
12
|
-
return Promise.resolve(!hasSession)
|
|
13
|
-
},
|
|
14
|
-
route: {
|
|
15
|
-
storage: {
|
|
16
|
-
driver: lruCacheDriver({
|
|
17
|
-
max: 10000,
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
buildCacheKey: (event: H3Event) => {
|
|
21
|
-
const path = (event.path || '')
|
|
22
|
-
.replaceAll('/', '__')
|
|
23
|
-
.replaceAll('?', '__')
|
|
24
|
-
.replaceAll('&', '__')
|
|
25
|
-
|
|
26
|
-
const headers = getHeaders(event)
|
|
27
|
-
const cookie = headers.cookie || 'anonymous'
|
|
28
|
-
return path + cookie
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
})
|
package/vitest.config.mts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { defineVitestConfig } from '@nuxt/test-utils/config'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
|
|
4
|
-
export default defineVitestConfig({
|
|
5
|
-
test: {
|
|
6
|
-
globals: true,
|
|
7
|
-
environment: 'nuxt',
|
|
8
|
-
alias: {
|
|
9
|
-
'~': path.resolve(__dirname, './app'),
|
|
10
|
-
'~~': path.resolve(__dirname, './'),
|
|
11
|
-
},
|
|
12
|
-
coverage: {
|
|
13
|
-
all: true,
|
|
14
|
-
provider: 'v8',
|
|
15
|
-
reporter: ['text', 'json', 'html'],
|
|
16
|
-
include: [
|
|
17
|
-
'app/**/*.*',
|
|
18
|
-
'components/**/*.*',
|
|
19
|
-
'composables/**/*.*',
|
|
20
|
-
'config/**/*.*',
|
|
21
|
-
'layouts/**/*.*',
|
|
22
|
-
'middleware/**/*.*',
|
|
23
|
-
'plugins/**/*.*',
|
|
24
|
-
'/**/*.*',
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
})
|