diff --git a/docs/content/en/writing.md b/docs/content/en/writing.md
index 0e50fab16..d86ad9946 100644
--- a/docs/content/en/writing.md
+++ b/docs/content/en/writing.md
@@ -429,6 +429,97 @@ Will be transformed into:
We internally add a `text` key with the markdown body that will be used for [searching](/fetching#searchfield-value) or [extending](/advanced#contentfilebeforeinsert) it.
+## JSON / JSON5
+
+Data defined will be injected into the document.
+
+> No body will be generated.
+
+### Arrays
+
+v0.10.0+
+
+You can now use arrays inside your `.json` files. Objects will be flattened and inserted into the collection. You can [fetch](/fetching) your content in the same way as your used to.
+
+
+
+Since the `slug` is by default taken from the path and missing in this case, you have to define it in your objects for this feature to work properly.
+
+
+
+> Check out our [example](https://github.com/nuxt/content/tree/dev/example) with articles and authors.
+
+### Example
+
+A file `content/home.json`:
+
+```json
+{
+ "title": "Home",
+ "description": "Welcome!"
+}
+```
+
+Will be transformed into:
+
+```json
+{
+ "dir": "/",
+ "slug": "home",
+ "path": "/home",
+ "extension": ".json",
+ "title": "Home",
+ "description": "Welcome!"
+}
+```
+
+A file `content/authors.json`:
+
+```json
+[
+ {
+ "name": "Sébastien Chopin",
+ "slug": "atinux"
+ },
+ {
+ "name": "Krutie Patel",
+ "slug": "krutiepatel"
+ },
+ {
+ "name": "Sergey Bedritsky",
+ "slug": "sergeybedritsky"
+ }
+]
+```
+
+Will be transformed into:
+
+```json
+[
+ {
+ "name": "Sébastien Chopin",
+ "slug": "atinux",
+ "dir": "/authors",
+ "path": "/authors/atinux",
+ "extension": ".json"
+ },
+ {
+ "name": "Krutie Patel",
+ "slug": "krutiepatel",
+ "dir": "/authors",
+ "path": "/authors/krutiepatel",
+ "extension": ".json"
+ },
+ {
+ "name": "Sergey Bedritsky",
+ "slug": "sergeybedritsky",
+ "dir": "/authors",
+ "path": "/authors/sergeybedritsky",
+ "extension": ".json"
+ }
+]
+```
+
## CSV
Rows will be assigned to body variable.
@@ -503,7 +594,6 @@ Will be transformed into:
}
```
-
## YAML / YML
Data defined will be injected into the document.
@@ -530,35 +620,4 @@ Will be transformed into:
"title": "Home",
"description": "Welcome!"
}
-```
-
-## JSON / JSON5
-
-Data defined will be injected into the document.
-
-> No body will be generated.
-
-### Example
-
-A file `content/home.json`:
-
-```json
-{
- "title": "Home",
- "description": "Welcome!"
-}
-
-```
-
-Will be transformed into:
-
-```json
-{
- "dir": "/",
- "slug": "home",
- "path": "/home",
- "extension": ".json",
- "title": "Home",
- "description": "Welcome!"
-}
-```
+```
\ No newline at end of file
diff --git a/example/content/articles/build-dev-to-clone-with-nuxt-new-fetch.md b/example/content/articles/build-dev-to-clone-with-nuxt-new-fetch.md
index a9f18d81a..e88136f67 100644
--- a/example/content/articles/build-dev-to-clone-with-nuxt-new-fetch.md
+++ b/example/content/articles/build-dev-to-clone-with-nuxt-new-fetch.md
@@ -4,12 +4,8 @@ description: Let’s build a blazing fast articles and tutorials app using Nuxt,
imgUrl: blog/build-dev-to-clone-with-nuxt-new-fetch/main.png
date: 2020-04-08
authors:
- - name: Sergey Bedritsky
- avatarUrl: https://pbs.twimg.com/profile_images/1244291720566669315/pGg6Xn-M_400x400.jpg
- link: https://twitter.com/sergeybedritsky
- - name: Sebastien Chopin
- avatarUrl: https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg
- link: https://twitter.com/Atinux
+ - sergeybedritsky
+ - atinux
tags:
- Nuxt
- Fetch
diff --git a/example/content/articles/introducing-smart-prefetching.md b/example/content/articles/introducing-smart-prefetching.md
index 0fb7867fb..a655c7005 100644
--- a/example/content/articles/introducing-smart-prefetching.md
+++ b/example/content/articles/introducing-smart-prefetching.md
@@ -4,9 +4,7 @@ description: "Starting from Nuxt v2.4.0, Nuxt.js will automagically prefetch the
imgUrl: blog/introducing-smart-prefetching/main.png
date: 2019-01-28
authors:
- - name: Sébastien Chopin
- avatarUrl: https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg
- link: https://twitter.com/atinux
+ - atinux
tags:
- framework
- feature
diff --git a/example/content/articles/nuxtjs-from-terminal-to-browser.md b/example/content/articles/nuxtjs-from-terminal-to-browser.md
index 71c5bd5ee..a66e8cc04 100644
--- a/example/content/articles/nuxtjs-from-terminal-to-browser.md
+++ b/example/content/articles/nuxtjs-from-terminal-to-browser.md
@@ -4,9 +4,7 @@ description: How we changed the developer experience to stop switching between t
imgUrl: blog/nuxtjs-from-terminal-to-browser/main.png
date: 2019-06-04
authors:
- - name: Sébastien Chopin
- avatarUrl: https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg
- link: https://twitter.com/atinux
+ - atinux
tags:
- webpack
- DX
diff --git a/example/content/articles/understanding-how-fetch-works-in-nuxt-2-12.md b/example/content/articles/understanding-how-fetch-works-in-nuxt-2-12.md
index 53420050e..56703162f 100644
--- a/example/content/articles/understanding-how-fetch-works-in-nuxt-2-12.md
+++ b/example/content/articles/understanding-how-fetch-works-in-nuxt-2-12.md
@@ -4,9 +4,7 @@ description: Explore different features of the fetch hook and learn a brand new
imgUrl: blog/understanding-how-fetch-works-in-nuxt-2-12/main.png
date: 2020-04-06
authors:
- - name: Krutie Patel
- avatarUrl: https://pbs.twimg.com/profile_images/780651635932434432/YtbSkumD_400x400.jpg
- link: https://twitter.com/KrutiePatel
+ - krutiepatel
tags:
- Nuxt
- Fetch
diff --git a/example/content/authors.json b/example/content/authors.json
new file mode 100644
index 000000000..1dda3b7ea
--- /dev/null
+++ b/example/content/authors.json
@@ -0,0 +1,20 @@
+[
+ {
+ "name": "Sébastien Chopin",
+ "slug": "atinux",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg",
+ "link": "https://twitter.com/atinux"
+ },
+ {
+ "name": "Krutie Patel",
+ "slug": "krutiepatel",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/780651635932434432/YtbSkumD_400x400.jpg",
+ "link": "https://twitter.com/KrutiePatel"
+ },
+ {
+ "name": "Sergey Bedritsky",
+ "slug": "sergeybedritsky",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/1244291720566669315/pGg6Xn-M_400x400.jpg",
+ "link": "https://twitter.com/sergeybedritsky"
+ }
+]
\ No newline at end of file
diff --git a/example/pages/articles/_slug.vue b/example/pages/articles/_slug.vue
index ca6b77970..4c7d379f4 100644
--- a/example/pages/articles/_slug.vue
+++ b/example/pages/articles/_slug.vue
@@ -2,7 +2,10 @@
Articles
{{ article.title }}
-
{{ article.description }}
+
+
![]()
+ {{ author.name }}
+
diff --git a/packages/content/lib/database.js b/packages/content/lib/database.js
index ed2165a8f..6fa0e6e5b 100644
--- a/packages/content/lib/database.js
+++ b/packages/content/lib/database.js
@@ -111,15 +111,22 @@ class Database extends Hookable {
* @param {string} path - The path of the file.
*/
async insertFile (path) {
- const item = await this.parseFile(path)
+ const items = await this.parseFile(path)
- if (!item) {
+ if (!items) {
return
}
- await this.callHook('file:beforeInsert', item)
+ // Assume path is a directory if returning an array
+ if (items.length > 1) {
+ this.dirs.push(this.normalizePath(path))
+ }
+
+ for (const item of items) {
+ await this.callHook('file:beforeInsert', item)
- this.items.insert(item)
+ this.items.insert(item)
+ }
}
/**
@@ -127,22 +134,24 @@ class Database extends Hookable {
* @param {string} path - The path of the file.
*/
async updateFile (path) {
- const item = await this.parseFile(path)
+ const items = await this.parseFile(path)
- if (!item) {
+ if (!items) {
return
}
- await this.callHook('file:beforeInsert', item)
+ for (const item of items) {
+ await this.callHook('file:beforeInsert', item)
- const document = this.items.findOne({ path: item.path })
+ const document = this.items.findOne({ path: item.path })
- logger.info(`Updated ${path.replace(this.cwd, '.')}`)
- if (document) {
- this.items.update({ $loki: document.$loki, meta: document.meta, ...item })
- return
+ logger.info(`Updated ${path.replace(this.cwd, '.')}`)
+ if (document) {
+ this.items.update({ $loki: document.$loki, meta: document.meta, ...item })
+ return
+ }
+ this.items.insert(item)
}
- this.items.insert(item)
}
/**
@@ -187,37 +196,54 @@ class Database extends Hookable {
'.xml': data => this.xml.toJSON(data),
...this.extendParser
})[extension]
+
// Collect data from file
- let data = {}
+ let data = []
try {
data = await parser(file.data)
+ // Force data to be an array
+ data = Array.isArray(data) ? data : [data]
} catch (err) {
logger.warn(`Could not parse ${path.replace(this.cwd, '.')}:`, err.message)
return null
}
+
// Normalize path without dir and ext
const normalizedPath = this.normalizePath(path)
- // Extract dir from path
- const split = normalizedPath.split('/')
- const dir = split.slice(0, split.length - 1).join('/')
-
- // Overrides createdAt & updatedAt if it exists in the document
- const existingCreatedAt = data.createdAt && new Date(data.createdAt)
- const existingUpdatedAt = data.updatedAt && new Date(data.updatedAt)
- // validate the existing dates to avoid wrong date format or typo
+
+ // Validate the existing dates to avoid wrong date format or typo
const isValidDate = (date) => {
return date instanceof Date && !isNaN(date)
}
- return {
- ...data,
- dir: dir || '/',
- path: normalizedPath,
- extension,
- slug: split[split.length - 1],
- createdAt: isValidDate(existingCreatedAt) ? existingCreatedAt : stats.birthtime,
- updatedAt: isValidDate(existingUpdatedAt) ? existingUpdatedAt : stats.mtime
- }
+ return data.map((item) => {
+ const paths = normalizedPath.split('/')
+ // `item.slug` is necessary with JSON arrays since `slug` comes from filename by default
+ if (data.length > 1 && item.slug) {
+ paths.push(item.slug)
+ }
+ // Extract `dir` from paths
+ const dir = paths.slice(0, paths.length - 1).join('/') || '/'
+ // Extract `slug` from paths
+ const slug = paths[paths.length - 1]
+ // Construct full path
+ const path = paths.join('/')
+
+ // Overrides createdAt & updatedAt if it exists in the document
+ const existingCreatedAt = item.createdAt && new Date(item.createdAt)
+ const existingUpdatedAt = item.updatedAt && new Date(item.updatedAt)
+
+ return {
+ slug,
+ // Allow slug override
+ ...item,
+ dir,
+ path,
+ extension,
+ createdAt: isValidDate(existingCreatedAt) ? existingCreatedAt : stats.birthtime,
+ updatedAt: isValidDate(existingUpdatedAt) ? existingUpdatedAt : stats.mtime
+ }
+ })
}
/**
diff --git a/packages/content/test/fixture/content/authors.json b/packages/content/test/fixture/content/authors.json
new file mode 100644
index 000000000..1dda3b7ea
--- /dev/null
+++ b/packages/content/test/fixture/content/authors.json
@@ -0,0 +1,20 @@
+[
+ {
+ "name": "Sébastien Chopin",
+ "slug": "atinux",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg",
+ "link": "https://twitter.com/atinux"
+ },
+ {
+ "name": "Krutie Patel",
+ "slug": "krutiepatel",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/780651635932434432/YtbSkumD_400x400.jpg",
+ "link": "https://twitter.com/KrutiePatel"
+ },
+ {
+ "name": "Sergey Bedritsky",
+ "slug": "sergeybedritsky",
+ "avatarUrl": "https://pbs.twimg.com/profile_images/1244291720566669315/pGg6Xn-M_400x400.jpg",
+ "link": "https://twitter.com/sergeybedritsky"
+ }
+]
\ No newline at end of file
diff --git a/packages/content/test/server.test.js b/packages/content/test/server.test.js
index 11ab1e6d0..a0cbd398a 100644
--- a/packages/content/test/server.test.js
+++ b/packages/content/test/server.test.js
@@ -334,4 +334,31 @@ describe('module', () => {
expect(item).toHaveProperty('text')
})
+
+ test('$content() on json array "directory"', async () => {
+ const items = await $content('authors').fetch()
+
+ expect(items).toEqual(expect.arrayContaining([
+ expect.objectContaining({
+ slug: 'atinux'
+ }),
+ expect.objectContaining({
+ slug: 'krutiepatel'
+ }),
+ expect.objectContaining({
+ slug: 'sergeybedritsky'
+ })
+ ]))
+ })
+
+ test('$content() on json array "file"', async () => {
+ const item = await $content('authors', 'atinux').fetch()
+
+ expect(item).toEqual(expect.objectContaining({
+ name: 'Sébastien Chopin',
+ slug: 'atinux',
+ avatarUrl: 'https://pbs.twimg.com/profile_images/1042510623962275840/1Iw_Mvud_400x400.jpg',
+ link: 'https://twitter.com/atinux'
+ }))
+ })
})