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' + })) + }) })