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(document-driven): add document-driven as a @nuxt/content feature #1279

Merged
merged 17 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 3 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"root": true,
"extends": [
"@nuxtjs/eslint-config-typescript"
],
"extends": ["@nuxtjs/eslint-config-typescript"],
"rules": {
"vue/multi-word-component-names": "off",
"vue/no-multiple-template-root": "off",
"no-redeclare": "off"
"no-redeclare": "off",
"import/named": "off"
}
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- run: yarn dev:prepare
- run: yarn lint
- run: yarn build
- run: yarn test:unit
- run: yarn test
- name: Release Edge
if: |
github.event_name == 'push' &&
Expand Down
139 changes: 139 additions & 0 deletions docs/content/3.guide/1.writing/7.document-driven.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Document-driven
description: '@nuxt/content allows a new way of writing your website pages called Document-driven.'
---

Document-driven development gives a lot more power to Markdown-based pages.

This mode creates a direct binding between your `content/` directory and your pages.

It also offers `page`, `navigation`, `surround` and `globals` global variables.

Let's dive into what this new @nuxt/content feature has to offer.

## Global `page`, `surround` and `navigation`

With this mode enabled, queries will be made from [route middlewares](https://v3.nuxtjs.org/guide/directory-structure/middleware#middleware-directory) and will be resolve before your page renders.

That gives access to the [`useContent()`](/api/composables/use-document-driven) composable anywhere in your app.

```ts
<script setup lang="ts">
const { navigation, page, surround, globals } = useContent()

console.log(page.value)
</script>
```

---

## Layout binding

Most of the content context of your project becomes globally available with Document-driven mode.

This unlocks the capability of configuring your page layout from the front-matter.

To do so, you only have to specify an available layout from your page front-matter:

```md [my-page.md]
---
layout: reversed
---

This page will use the reversed layout!
```

By default, the app will search for `default` layout if none is specified.

If you want to use another layout, you might want to use `layoutFallbacks` key from the configuration:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
content: {
documentDriven: {
layoutFallbacks: ['theme'],
}
}
})
```

This configuration will search for a `theme` key in `globals`, then search for a `layout` key inside that global object.

If it is found, that layout key will be used as a fallback.

---

## Globally available queries

This mode gives you a convenient way to access some files data globally in your app.

To do so, you have to specify a query in your `documentDriven.globals` key of your module configuration:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
content: {
documentDriven: {
globals: {
theme: {
where: [
{
_id: 'content:_theme.yml'
}
],
without: ['_']
}
}
}
}
})
```

This configuration will search for a `_theme.yml` file in the `content/` directory.

If it is found, it'll add it as `globals.theme` key, accessible via `useContent().globals`.

Any change to these files will be automatically reflected in the app, as any other file from @nuxt/content.

---

## Catch-all page injection

The Document-driven mode also ships with a pre-configured catch-all page.

That bundled page is useful to keep your project structure ultra minimal when using Document-driven mode.

Here is what your project can look like with this enabled:

```
my-project/
content/
nuxt.config.ts
package.json
tsconfig.json
```

It comes with a direct binding between your pages `title` and `description` to your OpenGraph tags.

Also, it allows you to use `cover` key from front-matter to set the OpenGraph `og:image` and `twitter:image` tags.

```md [my-page.md]
---
cover:
src: /path/to/image.jpg
alt: "My Cover Image"
---
```

### Page slots

That page comes with 2 slot components, that you can replace at your project level.

To do so, you only have to create a component with the same name in your project `components/` directory.

#### `<DocumentDrivenEmpty />`

This component will be shown when there is no content for the current page, but the page exists.

#### `<DocumentDrivenNotFound />`

This component will be shown when no page has been found for the current URL.
84 changes: 84 additions & 0 deletions docs/content/4.api/2.composables/4.use-document-driven.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: useContent()
---

::alert{type="warning"}
This composable will only be enabled if you toggle the [Document-driven](/guide/writing/document-driven) feature!
::

## Usage

`useContent()`{lang="ts"} is available everywhere in your app and gives access to a list of refs based on your content.

```ts
<script setup lang="ts">
const {
// Global references
globals,
navigation,
surround,
page,
// Computed properties from `page` key
excerpt,
toc,
type,
layout,
// Computed properties from `surround` key
next,
prev
} = useContent()
</script>
```

## Examples

### Rendering the page

Rendering the current page becomes a bliss with this composable:

```vue [pages/[...slug].vue]
<script setup lang="ts">
const { page } = useContent()
</script>

<template>
<ContentRenderer :key="page._id" :value="page">
</template>
```

### Creating a previous/next page component

```vue [PagePrevNext.vue]
<script setup lang="ts">
const { prev, next } = useContent()
</script>

<template>
<div>
<NuxtLink v-if="prev" :to="prev._path">{{ prev.title }}</NuxtLink>
<NuxtLink v-if="next" :to="next._path">{{ next.title }}</NuxtLink>
</div>
</template>
```

### Creating a Table of Contents component

```vue [PageToc.vue]
<script setup lang="ts">
const { toc } = useContent()
</script>

<template>
<div>
<ul v-if="toc && toc.links">
<li v-for="link in toc.links" :key="link.text">
<a
:href="`#${link.id}`"
>
{{ link.text }}
</a>
</li>
</ul>
</div>
</template>
```
80 changes: 80 additions & 0 deletions docs/content/4.api/2.composables/5.use-content-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: useContentHelpers()
---

## Usage

`useContentHelpers()`{lang="ts"} is available everywhere in your app and gives access to helpers to interact with the navigation object.

```ts
<script setup lang="ts">
const {
navBottomLink,
navDirFromPath,
navPageFromPath,
navKeyFromPath
} = useContentHelpers()
</script>
```

### `navBottomLink()`

This function will take a navigation node and will resolve the first available path from that node.

It can be useful to build nested navigations systems.

**Example:**

```ts
const { navigation } = useContent()

const path = navBottomLink(navigation.value)
```

### `navDirFromPath()`

This function will take a path and will resolve the first available navigation node from that path.

It can be useful to find the current directory of a navigation node.

**Example:**

```ts
const route = useRoute()

const { navigation } = useContent()

const dir = navDirFromPath(route.path, navigation.value)
```

### `navPageFromPath()`

This function will take a path and will resolve the first available navigation page from that path.

It can be useful to find the current navigation node the page you're browsing.

**Example:**

```ts
const route = useRoute()

const { navigation } = useContent()

const page = navPageFromPath(route.path, navigation.value)
```

### `navKeyFromPath()`

This function will take a path and will resolve a specific key from that path.

It can be useful when you want to add a fallback on the `_dir.yml` value of a key in a page.

**Example:**

```ts
const route = useRoute()

const { navigation } = useContent()

const layout = navKeyFromPath(route.path, 'layout', navigation.value)
```