From 13a156ef00b65d4eb605e5c16203595b0557c4f5 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 4 Jun 2022 01:11:48 +1000 Subject: [PATCH] feat(navigation): allow passing QueryBuilder or QueryBuilderParams in `fetchNavigation` or `` (#1206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Chopin Co-authored-by: Yaël Guilloux --- .../1.components/3.content-navigation.md | 34 ++++++++++- package.json | 1 + src/runtime/components/ContentNavigation.ts | 21 +++++-- src/runtime/composables/navigation.ts | 10 +++- test/basic.test.ts | 2 + test/features/navigation.ts | 39 ++++++++++++ test/fixtures/basic/pages/nav-with-query.vue | 60 +++++++++++++++++++ 7 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/basic/pages/nav-with-query.vue diff --git a/docs/content/4.api/1.components/3.content-navigation.md b/docs/content/4.api/1.components/3.content-navigation.md index 1e47d1763..d7a5eb255 100644 --- a/docs/content/4.api/1.components/3.content-navigation.md +++ b/docs/content/4.api/1.components/3.content-navigation.md @@ -10,7 +10,7 @@ The ``{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 @@ -43,3 +43,35 @@ The `empty`{lang=ts} slot can be used to display a default content before render ``` + +## Query + +By providing the `query` prop you can customise the content used for navigation. + +Here we pass along a `QueryBuilder` instance. + +```vue + + + +``` diff --git a/package.json b/package.json index 3118b13f2..bdb796f01 100644 --- a/package.json +++ b/package.json @@ -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 .", diff --git a/src/runtime/components/ContentNavigation.ts b/src/runtime/components/ContentNavigation.ts index aa7b44a66..ab598ccb1 100644 --- a/src/runtime/components/ContentNavigation.ts +++ b/src/runtime/components/ContentNavigation.ts @@ -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({ @@ -9,7 +10,7 @@ export default defineComponent({ * A query to be passed to `fetchContentNavigation()`. */ query: { - type: Object as PropType, + type: Object as PropType, required: false, default: undefined } @@ -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( - `content-navigation-${hash(query.value)}`, - () => fetchContentNavigation(query.value) + `content-navigation-${hash(queryBuilder.value)}`, + () => fetchContentNavigation(queryBuilder.value) ) return { diff --git a/src/runtime/composables/navigation.ts b/src/runtime/composables/navigation.ts index 933c715d7..1d236e886 100644 --- a/src/runtime/composables/navigation.ts +++ b/src/runtime/composables/navigation.ts @@ -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) { diff --git a/test/basic.test.ts b/test/basic.test.ts index 3be5742f9..2064225f6 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -103,9 +103,11 @@ describe('fixtures:basic', async () => { const html = await $fetch('/_partial/content-(v2)') expect(html).contains('Content (v2)') }) + testNavigation() testMarkdownParser() + testMarkdownParserExcerpt() testYamlParser() diff --git a/test/features/navigation.ts b/test/features/navigation.ts index 647130db0..deb2ac3de 100644 --- a/test/features/navigation.ts +++ b/test/features/navigation.ts @@ -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)) + }) }) } diff --git a/test/fixtures/basic/pages/nav-with-query.vue b/test/fixtures/basic/pages/nav-with-query.vue new file mode 100644 index 000000000..70697a4ab --- /dev/null +++ b/test/fixtures/basic/pages/nav-with-query.vue @@ -0,0 +1,60 @@ + + +