Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(navigation): allow passing QueryBuilder or QueryBuilderParams in fetchNavigation or <ContentNavigation> #1206

Merged
merged 7 commits into from
Jun 3, 2022
Merged
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>