diff --git a/docs/content/3.guide/2.displaying/3.navigation.md b/docs/content/3.guide/2.displaying/3.navigation.md
index 5b8b38553..ad4926512 100644
--- a/docs/content/3.guide/2.displaying/3.navigation.md
+++ b/docs/content/3.guide/2.displaying/3.navigation.md
@@ -1,11 +1,9 @@
---
title: Navigation
-description: 'The fetchContentNavigation utility returns a tree of items based on the content/ directory structure and files.'
+description: 'Nuxt Content provides a component and composable to display a navigation based on the content/ directory structure and files.'
---
-Navigation generation is based on your content sources directory structure.
-
-Based on the generated `_id` and `_path` keys, we generate a whole navigation structure for your content.
+Based on the generated `_id` and `_path` keys, Nuxt Content generates a whole navigation structure for your content.
It allows you to create advanced navigation components without having to maintain any querying logic related to it.
@@ -59,13 +57,64 @@ content/
::
-## Configuration
+## Custom keys
+
+You can use the `navigation` property in the front-matter of your content files to add keys to the navigation object.
+
+::code-group
+```md [index.md]
+---
+navigation:
+ title: 'Home'
+ icon: '🏡'
+---
+
+# Welcome
+```
+
+```json [Navigation]
+[
+ {
+ "title": "Home",
+ "icon": "🏡",
+ "_path": "/",
+ }
+]
+```
+::
+
+
+Alternatively, the navigation also allows you to configure directory nodes via `_dir.yml` files.
+
+It allows you to overwrite the `title` and custom properties to directory nodes in navigation.
+
+::code-group
+
+``` [Directory structure]
+content/
+ my-directory/
+ _dir.yml
+ page.md
+```
-### Module
+```yaml [_dir.yml]
+title: 'My awesome directory'
+navigation.icon: '📁'
+```
-The only key available in [module configuration](/api/configuration#navigation) about navigation is `fields`.
+```json [Navigation]
+{
+ "title": "My awesome directory",
+ "icon": "📁",
+ "_path": "/my-directory",
+ "children": [
+ ...
+ ]
+}
+```
+::
-`fields` allows you to define the properties that will be included in the navigation items.
+If you want to use top-level keys in the front-matter to be included in the navigation object, use the [`content.navigation.fields`](/api/configuration#navigation) property in the `nuxt.config`:
::code-group
@@ -99,15 +148,10 @@ publishedAt: '15-06-2022'
::
-### Per-directory
-
-Alternatively, the navigation also allows you to configure directory nodes via `_dir.yml` files.
-
-It allows you to overwrite directory names, and add custom properties to directory nodes in navigation.
## Excluding
-Set `navigation: false` in the [front-matter](/guide/writing/markdown) of a page to filter it out of navigation.
+Set `navigation: false` in the [front-matter](/guide/writing/markdown) of a page to filter it out.
```md [page.md]
---
@@ -174,6 +218,6 @@ const query = queryContent({
::alert
Go deeper in the **API** section:
-- [fetchContentNavigation()](/api/composables/fetch-content-navigation) composable to fetch navigation in `
-
- {{ navigation }}
-
+ {{ navigation }}
```
@@ -40,13 +38,13 @@ const { data: navigation } = await useAsyncData('navigation', fetchContentNaviga
]
},
{
- "title": "Sub Directory",
- "_path": "/sub-directory",
+ "title": "Sub Folder",
+ "_path": "/sub-folder",
"children": [
{
"title": "About Content V2",
- "_id": "content:sub-directory:about.md",
- "_path": "/sub-directory/about"
+ "_id": "content:sub-folder:about.md",
+ "_path": "/sub-folder/about"
}
]
}
@@ -55,6 +53,8 @@ const { data: navigation } = await useAsyncData('navigation', fetchContentNaviga
::
+:ReadMore{link="/examples/navigation/fetch-content-navigation"}
+
## Arguments
- `queryBuilder`{lang=ts}
@@ -62,5 +62,3 @@ const { data: navigation } = await useAsyncData('navigation', fetchContentNaviga
- Definition: Any query built via `queryContent()`{lang=ts}
- Default: `/`{lang=ts}
-::ReadMore{link="/examples/navigation/fetch-content-navigation"}
-::
diff --git a/playground/navigation/app.vue b/playground/navigation/app.vue
new file mode 100644
index 000000000..31b7fbe95
--- /dev/null
+++ b/playground/navigation/app.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/playground/navigation/content/about.md b/playground/navigation/content/about.md
new file mode 100644
index 000000000..449429d43
--- /dev/null
+++ b/playground/navigation/content/about.md
@@ -0,0 +1 @@
+# About
diff --git a/playground/navigation/content/index.md b/playground/navigation/content/index.md
new file mode 100644
index 000000000..1a7557e04
--- /dev/null
+++ b/playground/navigation/content/index.md
@@ -0,0 +1,5 @@
+---
+title: Home
+---
+
+# Hello World
diff --git a/playground/navigation/content/resources/_dir.yml b/playground/navigation/content/resources/_dir.yml
new file mode 100644
index 000000000..9e65ca286
--- /dev/null
+++ b/playground/navigation/content/resources/_dir.yml
@@ -0,0 +1,2 @@
+navigation:
+ hello: true
diff --git a/playground/navigation/content/resources/case-studies.md b/playground/navigation/content/resources/case-studies.md
new file mode 100644
index 000000000..6deb35c71
--- /dev/null
+++ b/playground/navigation/content/resources/case-studies.md
@@ -0,0 +1,2 @@
+# Case studies
+
diff --git a/playground/navigation/content/resources/index.md b/playground/navigation/content/resources/index.md
new file mode 100644
index 000000000..f1804eaee
--- /dev/null
+++ b/playground/navigation/content/resources/index.md
@@ -0,0 +1,5 @@
+---
+navigation: false
+---
+
+# Resources
diff --git a/playground/navigation/nuxt.config.ts b/playground/navigation/nuxt.config.ts
new file mode 100644
index 000000000..06c5155d3
--- /dev/null
+++ b/playground/navigation/nuxt.config.ts
@@ -0,0 +1,9 @@
+import { defineNuxtConfig } from 'nuxt'
+import contentModule from '../../src/module' // eslint-disable-line
+
+export default defineNuxtConfig({
+ modules: [contentModule],
+ content: {
+ documentDriven: true
+ }
+})
diff --git a/src/runtime/components/ContentNavigation.ts b/src/runtime/components/ContentNavigation.ts
index b115dc3f0..b08c9bdf4 100644
--- a/src/runtime/components/ContentNavigation.ts
+++ b/src/runtime/components/ContentNavigation.ts
@@ -2,7 +2,8 @@ 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'
+import { useAsyncData, fetchContentNavigation, useState, useContent } from '#imports'
+import { NuxtLink } from '#components'
export default defineComponent({
name: 'ContentNavigation',
@@ -33,15 +34,17 @@ export default defineComponent({
return query.value
})
- const { data, refresh } = await useAsyncData(
+ // If doc driven mode and no query given, re-use the fetched navigation
+ if (!queryBuilder.value && useState('dd-navigation').value) {
+ const { navigation } = useContent()
+
+ return { navigation }
+ }
+ const { data: navigation } = await useAsyncData(
`content-navigation-${hash(queryBuilder.value)}`,
() => fetchContentNavigation(queryBuilder.value)
)
-
- return {
- data,
- refresh
- }
+ return { navigation }
},
/**
@@ -51,22 +54,24 @@ export default defineComponent({
render (ctx) {
const slots = useSlots()
- const {
- query,
- data,
- refresh
- } = ctx
-
- const emptyNode = (slot: string, data: any) => h('pre', null, JSON.stringify({ message: 'You should use slots with ', slot, data }, null, 2))
-
- // Render empty data object
- if (slots?.empty && (!data || !data?.length)) {
- return slots?.empty?.({ query, ...this.$attrs }) || emptyNode('empty', { query, data })
- }
+ const { navigation } = ctx
+ const renderLink = (link: NavItem) => h(NuxtLink, { to: link._path }, () => link.title)
+ const renderLinks = (data: NavItem[], level: number) =>
+ h(
+ 'ul',
+ level ? { 'data-level': level } : null,
+ data.map((link) => {
+ if (link.children) {
+ return h('li', null, [renderLink(link), renderLinks(link.children, level + 1)])
+ }
+ return h('li', null, renderLink(link))
+ })
+ )
+ const defaultNode = (data: NavItem[]) => renderLinks(data, 0)
// Render default slot with navigation as `data`
return slots?.default
- ? slots.default({ navigation: data, refresh, ...this.$attrs })
- : emptyNode('default', data)
+ ? slots.default({ navigation, ...this.$attrs })
+ : defaultNode(navigation)
}
})
diff --git a/src/runtime/composables/content.ts b/src/runtime/composables/content.ts
index c8ac97f30..f28d4a2c5 100644
--- a/src/runtime/composables/content.ts
+++ b/src/runtime/composables/content.ts
@@ -5,24 +5,24 @@ export const useContentState = () => {
/**
* Current page complete data.
*/
- const page = useState('content-document-driven-page')
+ const page = useState('dd-page')
/**
* Navigation tree from root of app.
*/
- const navigation = useState('content-document-driven-navigation')
+ const navigation = useState('dd-navigation')
/**
* Previous and next page data.
* Format: [prev, next]
*/
- const surround = useState[]>('content-document-driven-page-surround')
+ const surround = useState[]>('dd-surround')
/**
* Globally loaded content files.
* Format: { [key: string]: ParsedContent }
*/
- const globals = useState>('content-document-driven-globals', () => ({}))
+ const globals = useState>('dd-globals', () => ({}))
return {
page,
diff --git a/src/runtime/markdown-parser/content.ts b/src/runtime/markdown-parser/content.ts
index c8b6580cf..28549a2a9 100644
--- a/src/runtime/markdown-parser/content.ts
+++ b/src/runtime/markdown-parser/content.ts
@@ -72,8 +72,8 @@ export function contentHeading (body: MarkdownRoot) {
let title = ''
let description = ''
const children = body.children
- // top level `text` can be ignored
- .filter(node => node.type !== 'text')
+ // top level `text` and `hr` can be ignored
+ .filter(node => node.type !== 'text' && node.tag !== 'hr')
if (children.length && children[0].tag === 'h1') {
/**
diff --git a/src/runtime/server/navigation.ts b/src/runtime/server/navigation.ts
index 52dcfd544..31c897428 100644
--- a/src/runtime/server/navigation.ts
+++ b/src/runtime/server/navigation.ts
@@ -12,7 +12,10 @@ export function createNav (contents: ParsedContentMeta[], configs: Record ({
+ ...pick(['title', ...navigation.fields])(content),
+ ...(isObject(content?.navigation) ? content.navigation : {})
+ })
// Create navigation object
const nav = contents
@@ -142,3 +145,7 @@ function pick (keys?: string[]) {
return obj
}
}
+
+function isObject (obj: any) {
+ return Object.prototype.toString.call(obj) === '[object Object]'
+}
diff --git a/test/features/navigation.ts b/test/features/navigation.ts
index 3df3f6749..33bd4a4c3 100644
--- a/test/features/navigation.ts
+++ b/test/features/navigation.ts
@@ -12,8 +12,16 @@ export const testNavigation = () => {
_params: jsonStringify(query)
}
})
+ console.log(JSON.stringify(list, null, 2))
+
expect(list.find(item => item._path === '/')).toBeTruthy()
expect(list.find(item => item._path === '/').children).toBeUndefined()
+
+ // Check navigation data in front-matter
+ const testNavigation = list.find(item => item._path === '/test-navigation')
+ expect(testNavigation['custom-field']).toEqual('_dir file')
+ expect(testNavigation.children[0]['custom-field']).toEqual('index file')
+ expect(testNavigation.children[1]['custom-field']).toEqual('page file')
})
test('Get cats navigation', async () => {
diff --git a/test/fixtures/basic/content/test-navigation/1.index.md b/test/fixtures/basic/content/test-navigation/1.index.md
index e2f4b3bf0..a01752011 100644
--- a/test/fixtures/basic/content/test-navigation/1.index.md
+++ b/test/fixtures/basic/content/test-navigation/1.index.md
@@ -1 +1,4 @@
+---
+navigation.custom-field: index file
+---
# /test-navigation
diff --git a/test/fixtures/basic/content/test-navigation/2.page.md b/test/fixtures/basic/content/test-navigation/2.page.md
index fe3f2ce6d..aee5d0255 100644
--- a/test/fixtures/basic/content/test-navigation/2.page.md
+++ b/test/fixtures/basic/content/test-navigation/2.page.md
@@ -1 +1,4 @@
+---
+navigation.custom-field: page file
+---
# /test-navigation/page
diff --git a/test/fixtures/basic/content/test-navigation/_dir.yml b/test/fixtures/basic/content/test-navigation/_dir.yml
index a6a94f243..b4ff2a77f 100644
--- a/test/fixtures/basic/content/test-navigation/_dir.yml
+++ b/test/fixtures/basic/content/test-navigation/_dir.yml
@@ -1 +1,3 @@
title: Test Navigation
+navigation:
+ custom-field: _dir file