Skip to content

Commit 96bceb5

Browse files
committed
feat: breadcrumb, languageSwitchLinks feature
1 parent 592e679 commit 96bceb5

File tree

19 files changed

+378
-931
lines changed

19 files changed

+378
-931
lines changed

package-lock.json

Lines changed: 32 additions & 806 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"happy-dom": "^13.3.8",
6767
"nuxt": "^3.16.2",
6868
"nuxt-graphql-middleware": "^5.0.0-alpha.17",
69-
"nuxt-language-negotiation": "^1.0.1",
69+
"nuxt-language-negotiation": "^1.0.2",
7070
"postcss": "^8.4.35",
7171
"postcss-cli": "^11.0.0",
7272
"postcss-import": "^16.0.1",

playground/nuxt.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ export default defineNuxtConfig({
7272
drupalRoute: {
7373
enabled: true,
7474
},
75+
languageSwitchLinks: {
76+
enabled: true,
77+
},
78+
breadcrumb: {
79+
enabled: true,
80+
},
7581
trustedOrigins: {
7682
enabled: true,
7783
origins: [

src/build/classes/ModuleHelper.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import type { Nuxt, NuxtPlugin, ResolvedNuxtTemplate } from 'nuxt/schema'
1414
import { fileExists, logger } from '../helpers'
1515
import { useGraphqlModuleContext } from 'nuxt-graphql-middleware/utils'
1616
import { FileCache } from './FileCache'
17+
import type { ModuleOptions } from '../types/options'
18+
import { FEATURE_KEYS, type ValidFeature } from '../features'
1719

1820
type GraphqlModuleContext = NonNullable<
1921
ReturnType<typeof useGraphqlModuleContext>
@@ -75,8 +77,11 @@ export class ModuleHelper {
7577

7678
public readonly caches: FileCache<unknown>[] = []
7779

80+
private enabledFeatures = new Set<ValidFeature>()
81+
7882
constructor(
7983
public nuxt: Nuxt,
84+
moduleOptions: ModuleOptions,
8085
moduleUrl: string,
8186
options: { debug?: boolean; isModuleBuild: boolean },
8287
) {
@@ -85,6 +90,12 @@ export class ModuleHelper {
8590

8691
this.graphql = useGraphqlModuleContext()
8792

93+
FEATURE_KEYS.forEach((key) => {
94+
if (moduleOptions[key]?.enabled || this.isModuleBuild) {
95+
this.enabledFeatures.add(key)
96+
}
97+
})
98+
8899
// Gather all aliases for each layer.
89100
const layerAliases = nuxt.options._layers.map((layer) => {
90101
// @see https://nuxt.com/docs/api/nuxt-config#alias
@@ -371,4 +382,8 @@ export class ModuleHelper {
371382
public clearFilePathCaches(filePath: string) {
372383
this.caches.forEach((cache) => cache.clear(filePath))
373384
}
385+
386+
public hasFeatureEnabled(key: ValidFeature): boolean {
387+
return this.enabledFeatures.has(key)
388+
}
374389
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineVuepalFeature } from '../defineFeature'
2+
3+
export default defineVuepalFeature({
4+
name: 'breadcrumb',
5+
description: 'Adds support for breadcrumbs.',
6+
setup(helper) {
7+
helper.assertGraphqlObjectField(
8+
{ extension: 'breadcrumb' },
9+
'EntityUrl',
10+
'breadcrumb',
11+
)
12+
13+
helper.addComposable('useBreadcrumb')
14+
helper.addPlugin('breadcrumb')
15+
helper.addGraphqlFile('fragment.breadcrumb.graphql')
16+
},
17+
})

src/build/features/drupalRoute/index.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,70 @@ export default defineVuepalFeature({
1818

1919
helper.addComposable('useDrupalRoute')
2020
helper.addComposable('buildDrupalMetatags')
21-
helper.addGraphqlFile('fragment.drupalRoute.graphql')
21+
const breadcrumbSpread = helper.hasFeatureEnabled('breadcrumb')
22+
? 'breadcrumb { ...breadcrumb }'
23+
: ''
24+
25+
const languageSwitchLinksSpread = helper.hasFeatureEnabled(
26+
'languageSwitchLinks',
27+
)
28+
? 'languageSwitchLinks { ...languageSwitchLink }'
29+
: ''
30+
31+
helper.graphql.addDocument(
32+
'fragment.drupalRoute.graphql',
33+
`
34+
fragment useDrupalRoute on Query {
35+
route(path: $path) {
36+
__typename
37+
path
38+
39+
... on InternalUrl {
40+
${breadcrumbSpread}
41+
${languageSwitchLinksSpread}
42+
43+
metatags {
44+
...metatag
45+
}
46+
47+
routeName
48+
}
49+
50+
... on EntityUrl {
51+
${breadcrumbSpread}
52+
${languageSwitchLinksSpread}
53+
54+
metatags {
55+
...metatag
56+
}
57+
58+
drupalRouteEntity: entity {
59+
uuid
60+
entityBundle
61+
entityTypeId
62+
id
63+
}
64+
65+
routeName
66+
}
67+
68+
... on RedirectUrl {
69+
redirect {
70+
statusCode
71+
}
72+
}
73+
}
74+
}
75+
76+
fragment metatag on Metatag {
77+
id
78+
tag
79+
attributes {
80+
key
81+
value
82+
}
83+
}
84+
`,
85+
)
2286
},
2387
})

src/build/features/index.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import adminToolbar from './adminToolbar'
2+
import devMode from './devMode'
3+
import drupalRoute from './drupalRoute'
4+
import frontendRouting from './frontendRouting'
5+
import localTasks from './localTasks'
6+
import trustedOrigins from './trustedOrigins'
7+
import breadcrumb from './breadcrumb'
8+
import languageSwitchLinks from './languageSwitchLinks'
9+
10+
export const FEATURES = {
11+
/**
12+
* Provides a component and GraphQL query to display a Drupal Admin Toolbar.
13+
*/
14+
adminToolbar,
15+
16+
/**
17+
*Provides features for local development.
18+
*/
19+
devMode,
20+
21+
/**
22+
*Adds routing related GraphQL queries and composables.
23+
*/
24+
drupalRoute,
25+
26+
/**
27+
* Integration with the drupal/frontend_routing Drupal module.
28+
*/
29+
frontendRouting,
30+
31+
/**
32+
* Provides a component and GraphQL query to display local tasks.
33+
*/
34+
localTasks,
35+
36+
/**
37+
* Provides a plugin and composable to verify the origin in the browser.
38+
*/
39+
trustedOrigins,
40+
41+
/**
42+
* Provides a component and fragment to display Drupal breadcrumbs.
43+
*/
44+
breadcrumb,
45+
46+
/**
47+
* Provides a component and fragment to display language switch links.
48+
*/
49+
languageSwitchLinks,
50+
}
51+
52+
export type ValidFeature = keyof typeof FEATURES
53+
54+
export const FEATURE_KEYS = Object.keys(FEATURES) as ValidFeature[]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineVuepalFeature } from '../defineFeature'
2+
3+
export default defineVuepalFeature({
4+
name: 'languageSwitchLinks',
5+
description: 'Adds support for language links.',
6+
setup(helper) {
7+
helper.assertGraphqlObjectField(
8+
{ extension: 'language_switch_links' },
9+
'EntityUrl',
10+
'languageSwitchLinks',
11+
)
12+
13+
helper.addComposable('useLanguage')
14+
helper.addPlugin('languageSwitchLinks')
15+
helper.addGraphqlFile('fragment.languageSwitchLink.graphql')
16+
},
17+
})

src/build/types/options.ts

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import type { VuepalFeature } from './../features/defineFeature'
2-
import type adminToolbar from './../features/adminToolbar'
3-
import type devMode from './../features/devMode'
4-
import type drupalRoute from './../features/drupalRoute'
5-
import type frontendRouting from './../features/frontendRouting'
6-
import type localTasks from './../features/localTasks'
7-
import type trustedOrigins from './../features/trustedOrigins'
2+
import type { FEATURES } from '../features'
83

94
export const COMPOSABLES = [
105
'useClickTriggerProxy',
@@ -23,43 +18,11 @@ type FeatureOptions<T> =
2318
? O & { enabled: boolean }
2419
: { enabled: boolean }
2520

26-
export type ModuleOptions = {
27-
/**
28-
* Provides a <VuepalAdminToolbar> component to render the Drupal toolbar.
29-
*/
30-
adminToolbar?: FeatureOptions<typeof adminToolbar>
31-
32-
/**
33-
* Provides features for local development.
34-
*/
35-
devMode?: FeatureOptions<typeof devMode>
36-
37-
/**
38-
* Provides the useDrupalRoute() composable to automatically handle
39-
* redirects and metatags.
40-
*/
41-
drupalRoute?: FeatureOptions<typeof drupalRoute>
42-
43-
/**
44-
* Provides a feature to have Nuxt pages be connected to a Node in Drupal.
45-
*
46-
* Enabling the feature requires setting the outputPath option.
47-
* The module will then generate the settings YML file for Drupal that
48-
* contains the aggregated routes where the frontend "dictates" the aliases
49-
* for all languages.
50-
*/
51-
frontendRouting?: FeatureOptions<typeof frontendRouting>
52-
53-
/**
54-
* Provides a component to render Drupal local tasks.
55-
*/
56-
localTasks?: FeatureOptions<typeof localTasks>
57-
58-
/**
59-
* Provides a client plugin to validate the origin.
60-
*/
61-
trustedOrigins?: FeatureOptions<typeof trustedOrigins>
21+
export type ModuleOptionsFeatures = {
22+
[K in keyof typeof FEATURES]?: FeatureOptions<(typeof FEATURES)[K]>
23+
}
6224

25+
export type ModuleOptions = ModuleOptionsFeatures & {
6326
/**
6427
* Disable composables. By default all composables are included.
6528
*/

src/module.ts

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@ import { extname } from 'pathe'
33
import { name, version } from '../package.json'
44
import { defineNuxtModule, hasNuxtModule, installModule } from '@nuxt/kit'
55
import { ModuleHelper } from './build/classes/ModuleHelper'
6-
import adminToolbar from './build/features/adminToolbar'
7-
import devMode from './build/features/devMode'
8-
import drupalRoute from './build/features/drupalRoute'
9-
import frontendRouting from './build/features/frontendRouting'
10-
import localTasks from './build/features/localTasks'
11-
import trustedOrigins from './build/features/trustedOrigins'
6+
import { FEATURE_KEYS, FEATURES } from './build/features'
127
import { logger } from './build/helpers'
138
import {
149
COMPONENTS,
@@ -57,37 +52,21 @@ export default defineNuxtModule<ModuleOptions>({
5752
})
5853
}
5954

60-
const helper = new ModuleHelper(nuxt, import.meta.url, {
55+
const helper = new ModuleHelper(nuxt, options, import.meta.url, {
6156
debug: true,
6257
isModuleBuild,
6358
})
6459

6560
// Each feature can throw an error, for example when types or fields are
6661
// missing from the GraphQL schema.
6762
try {
68-
if (options.adminToolbar?.enabled || helper.isModuleBuild) {
69-
adminToolbar.setup(helper, options.adminToolbar)
70-
}
71-
72-
if (options.devMode?.enabled || helper.isModuleBuild) {
73-
devMode.setup(helper, options.devMode)
74-
}
75-
76-
if (options.drupalRoute?.enabled || helper.isModuleBuild) {
77-
drupalRoute.setup(helper, options.drupalRoute)
78-
}
79-
80-
if (options.frontendRouting?.enabled || helper.isModuleBuild) {
81-
frontendRouting.setup(helper, options.frontendRouting)
82-
}
83-
84-
if (options.localTasks?.enabled || helper.isModuleBuild) {
85-
localTasks.setup(helper, options.localTasks)
86-
}
87-
88-
if (options.trustedOrigins?.enabled || helper.isModuleBuild) {
89-
trustedOrigins.setup(helper, options.trustedOrigins)
90-
}
63+
FEATURE_KEYS.forEach((key) => {
64+
const featureOptions = options[key]
65+
if (featureOptions?.enabled || helper.isModuleBuild) {
66+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
67+
FEATURES[key].setup(helper, featureOptions as any)
68+
}
69+
})
9170
} catch (e) {
9271
if (e instanceof Error) {
9372
logger.box(e.message)

0 commit comments

Comments
 (0)