Skip to content

Commit

Permalink
feat(navigation): allow passing QueryBuilder or QueryBuilderParams in…
Browse files Browse the repository at this point in the history
… `fetchNavigation` or `<ContentNavigation>` (#1206)

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: Yaël Guilloux <yael.guilloux@gmail.com>
  • Loading branch information
3 people committed Jun 3, 2022
1 parent 0329ac9 commit afb791b
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 8 deletions.
34 changes: 33 additions & 1 deletion docs/content/4.api/1.components/3.content-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The `<ContentNavigation>`{lang=html} is a renderless component shortening the ac
## Props

- `query`{lang=ts}: A query to be passed to `fetchContentNavigation()`.
- Type: `QueryBuilderParams`{lang=ts}
- Type: `QueryBuilderParams | QueryBuilder`{lang=ts}
- Default: `undefined`{lang=ts}

## Slots
Expand Down Expand Up @@ -43,3 +43,35 @@ The `empty`{lang=ts} slot can be used to display a default content before render
</nav>
</template>
```

## Query

By providing the `query` prop you can customise the content used for navigation.

Here we pass along a `QueryBuilder` instance.

```vue
<script setup>
const catsQuery = queryContent('cats')
/*
// If you'd prefer to pass along raw `QueryBuilderParams`:
const catsQuery = {
where: [
{ _path: /^\/cats/ },
],
}
*/
</script>
<template>
<ContentNavigation v-slot="{ navigation }" :query="catsQuery">
<NuxtLink
v-for="link of navigation"
:key="link._path"
:to="link._path"
>
{{ link.navTitle || link.title }}
</NuxtLink>
</ContentNavigation>
</template>
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"dev:docs": "(cd docs && nuxi dev)",
"dev:fixtures": "nuxi dev test/fixtures/basic",
"build:docs": "(cd docs && nuxi build)",
"example": "./scripts/example.sh",
"lint": "eslint --ext .js,.ts,.vue .",
Expand Down
21 changes: 17 additions & 4 deletions src/runtime/components/ContentNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PropType, toRefs, defineComponent, h, useSlots } from 'vue'
import { PropType, toRefs, defineComponent, h, useSlots, computed } from 'vue'
import { hash } from 'ohash'
import type { NavItem, QueryBuilderParams } from '../types'
import { QueryBuilder } from '../types'
import { useAsyncData, fetchContentNavigation } from '#imports'

export default defineComponent({
Expand All @@ -9,7 +10,7 @@ export default defineComponent({
* A query to be passed to `fetchContentNavigation()`.
*/
query: {
type: Object as PropType<QueryBuilderParams>,
type: Object as PropType<QueryBuilderParams | QueryBuilder>,
required: false,
default: undefined
}
Expand All @@ -19,9 +20,21 @@ export default defineComponent({
query
} = toRefs(props)

const queryBuilder = computed(() => {
/*
* We need to extract params from a possible QueryBuilder beforehand
* so we don't end up with a duplicate useAsyncData key.
*/
if (typeof query.value?.params === 'function') {
return query.value.params()
}

return query.value
})

const { data, refresh } = await useAsyncData<NavItem[]>(
`content-navigation-${hash(query.value)}`,
() => fetchContentNavigation(query.value)
`content-navigation-${hash(queryBuilder.value)}`,
() => fetchContentNavigation(queryBuilder.value)
)

return {
Expand Down
10 changes: 7 additions & 3 deletions src/runtime/composables/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { hash } from 'ohash'
import { useHead, useCookie } from '#app'
import type { NavItem, QueryBuilder } from '../types'
import type { NavItem, QueryBuilder, QueryBuilderParams } from '../types'
import { jsonStringify } from '../utils/json'
import { withContentBase } from './utils'

export const fetchContentNavigation = (queryBuilder?: QueryBuilder) => {
const params = queryBuilder?.params()
export const fetchContentNavigation = (queryBuilder?: QueryBuilder | QueryBuilderParams) => {
let params = queryBuilder

// When params is an instance of QueryBuilder then we need to pick the params explicitly
if (typeof params?.params === 'function') { params = params.params() }

const apiPath = withContentBase(params ? `/navigation/${hash(params)}` : '/navigation')

if (!process.dev && process.server) {
Expand Down
2 changes: 2 additions & 0 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,11 @@ describe('fixtures:basic', async () => {
const html = await $fetch('/_partial/content-(v2)')
expect(html).contains('Content (v2)')
})

testNavigation()

testMarkdownParser()

testMarkdownParserExcerpt()

testYamlParser()
Expand Down
39 changes: 39 additions & 0 deletions test/features/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,44 @@ export const testNavigation = () => {
const hidden = list.find(i => i._path === '/navigation-disabled')
expect(hidden).toBeUndefined()
})

test('ContentNavigation should work with both QueryBuilder and QueryBuilderParams', async () => {
/* These are local replicas of the queries made in `nav-with-query.vue` */
const catsQuery = {
where: {
_path: /^\/cats/
}
}
const numbersQuery = {
where: {
_path: /^\/numbers/
}
}
const dogsQuery = {
where: { _path: /^\/dogs/ }
}

const queryNav = async (query) => {
const list = await $fetch(`/api/_content/navigation/${hash(query)}`, {
params: {
_params: jsonStringify(query)
}
})

return list
}

const [catsData, numbersData, dogsData] = await Promise.all([
queryNav(catsQuery),
queryNav(numbersQuery),
queryNav(dogsQuery)
])

const html = await $fetch('/nav-with-query')

catsData[0].children.forEach(({ title }) => expect(html).contains(title))
numbersData[0].children.forEach(({ title }) => expect(html).contains(title))
dogsData[0].children.forEach(({ title }) => expect(html).contains(title))
})
})
}
60 changes: 60 additions & 0 deletions test/fixtures/basic/pages/nav-with-query.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script lang="ts" setup>
const catsQuery = queryContent('cats')
const numbersQuery = queryContent('numbers')
const dogsQuery = {
where: [
{ _path: /^\/dogs/ }
]
}
</script>

<template>
<div>
<ContentNavigation v-slot="{ navigation: catsNav }" :query="catsQuery">
<div v-if="catsNav">
{{ JSON.stringify(catsNav) }}

<NuxtLink
v-for="link of catsNav[0].children"
:key="link._path"
:to="link._path"
style="margin-right: 1rem;"
>
{{ link.navTitle || link.title }}
</NuxtLink>
</div>
</ContentNavigation>
<br>
<ContentNavigation v-slot="{ navigation: numbersNav }" :query="numbersQuery">
<div v-if="numbersNav">
{{ JSON.stringify(numbersNav) }}

<NuxtLink
v-for="link of numbersNav[0].children"
:key="link._path"
:to="link._path"
style="margin-right: 1rem;"
>
{{ link.navTitle || link.title }}
</NuxtLink>
</div>
</ContentNavigation>
<br>
<ContentNavigation v-slot="{ navigation: dogsNav }" :query="dogsQuery">
<div v-if="dogsNav">
{{ JSON.stringify(dogsNav) }}

<NuxtLink
v-for="link of dogsNav[0].children"
:key="link._path"
:to="link._path"
style="margin-right: 1rem;"
>
{{ link.navTitle || link.title }}
</NuxtLink>
</div>
</ContentNavigation>
</div>
</template>

0 comments on commit afb791b

Please sign in to comment.