nuxt / content

The file-based CMS for your Nuxt application, powered by Markdown and Vue components.
https://content.nuxt.com
MIT License
3.15k stars 630 forks source link

Section splitting in Markdown files #678

Open rbndelrio opened 3 years ago

rbndelrio commented 3 years ago

Is your feature request related to a problem? Please describe.

The nuxt/content module works really well with article- and editorial-style content via markdown and managing page metadata via yaml/json, but I believe there's a gap in use cases between the two: copy-centric pages with multiple sections.

My personal use case is a website where every page has multiple <section>s with a one-off component and body copy per section. The layout can be distilled to a traditional two-column layout:

~/pages/about.vue
<template>
 <div>
   <h1>About Us</h1>
   <p>Lorem ipsum</p>
 </div>
  <section class="bg-black text-white">
     <img class="left" src="~/asset.png">
     <div class="prose">
       <h2>First section content</h2>
     </div>
  </section>
  <section class="bg-white text-black">
     <img class="right" src="~/asset.png">
     <div class="prose">
       <h2>Second section content</h2>
     </div>
  </section>
</template>

Describe the solution you'd like

I'd like to be able to manage this content from within the same markdown file. On the layout component end, an ideal payload would be an array of objects containing each section's parsed content and its optional frontmatter:

const { title, body, sections } = await $content('about').fetch()
console.log(sections[0]) // { key: 'string', image: {...}, content: {...} }

gray-matter has this feature already, albeit undocumented outside of this example. See additional context.

Describe alternatives you've considered

To my knowledge, these are the existing methods to reach a similar result:

Use Vue Components within Markdown

Configure it with JSON/YAML/etc. through frontmatter or a separate file (~/content/about/sections.yaml)

Split content into multiple files (~/content/about/section_1.md)

Create a remark/rehype plugin Going this route is more modular, but I'd argue:

Roll your own configuration with extendParser This is actually what I'm doing now but I'm not a fan of how much extra heft and excess processing it's adding to my config. It also results in a substandard file naming convention.

Additional context

Syntax-wise, my example above template's markdown could be written like:

---
title: About Us
---
# About Us
Lorem ipsum

---sectionKey
image:
  src: '~/asset.png'
  align: left
---
## First section content

---callToAction
image:
  src: '~/asset.png'
  align: right
---
## Second section content

Implementation of gray-matter's feature within the parser looks relatively frictionless and could give the user a choice between convention (enabled by default) and configuration (disable or provide a custom parsing function). If we utilize the existing api's return format, the resulting JSON payload could retain the body property while adding a sections array. Each section in that array would have the data from its frontmatter and the generated JSON AST:

{
  body: {/*...*/}, /* JSON AST */
  sections: [
    {
      key: 'sectionKey',
      image: { src: '~/asset.png', align: 'left' },
      content: {/*...*/}, /* JSON AST */
    },
  ],
  ...etc
}

I don't get the impression this would affect the content-fetching methods. However, I haven't thought through how (or whether) these sections can be rendered by the <nuxt-content> component.

If this sounds like a good feature, I can put together a PR before year's end!

rbndelrio commented 3 years ago

After some consideration, I got a few more ideas on how this could be fleshed out:

Frontmatter parsing

gray-matter's section parser simply passes the frontmatter as a string by default. For our purposes, a smart default would be utilizing the package's included YAML parser (matter.engines.yaml). After that, perhaps give users a hook to shape the output (JSON.parse or otherwise)?

Config option to include section data in table of contents generation

In general, most of my personal use cases for sections would also benefit from having sections included in the AST payload for generateToc. I can imagine use cases where the section syntax would be used for nonhierarchical content, though.

Shape the section object's output to conform to <nuxt-component />'s document prop

My original idea for a payload was pretty minimal, but a better implementation would have a type signature resembling:

interface IMarkdownSection { key: string, body: JsonAst, text: string, [string]: any }

That should be enough to implement a path to render the section with the existing component. Still unclear how editing content fits in with the idea, though.

nimonian commented 3 years ago

@rbndelrio I wonder if you ever implemented this? I'd be pretty interested in using it. I'm doing something a little similar right now but it is a quite hacky. If not, I could clean up my implementation and maybe contribute it.

danielfazlijevic commented 3 years ago

Are there any updates regarding this? I'm trying to make some tabbed content (with each tab showing different content from md) but haven't been able to find a solution I'm happy with.