Code Monkey home page Code Monkey logo

nuxt-dynamic-markdown's Introduction

nuxt-dynamic-markdown

Markdown + frontmatter -> dynamically generated vuex stores + more.

Table of Contents


Disclaimer

This project is pre-alpha and you should 100% not use it in a production environment!

  • Some parts of the code don't have proper error checking.
  • There are no tests. Zero. Nada. Keine. Zilch.
  • I haven't tried running any of this in other browsers; on other OSes; with different versions of Nuxt, Vue etc. nor can I guarantee that it works in parallel dimensions, different universes or alternate timelines.
  • It currently relies on fs.promises (experimental API, requires node > 8 I believe).
  • This README presumes a decent understanding of both Nuxt and Vue.
  • Just because the README mentions a feature, that doesn't mean that said feature is ready, or that it has even been started, or that it will ever come, or that it's possible, or that you'd like it if any of those things were true.
  • While there is a basic guide/walkthrough there is no actual documentation (API or otherwise) at this early stage.
  • I'm really new to Nuxt and I'm learning as I go so this may well be the worst Nuxt module of all time!

Finally, you should check out NuxtPress because it does kind of a similar-ish (but not really) thing but probably in a far far better way.

Back to top


Rationale / Origin

I chose to use Nuxt for my personal site and at some point decided that I wanted some kind of custom blog system.

It needed to allow one to:

  • "define" a category by creating a directory
  • create a post by creating a markdown file
  • be able to add things like tags etc. to YAML frontmatter
  • a way to use arbitrary components within my posts
  • and so on...

So I worked on that for a little while and got it to a stage where I was vaguely happy with it (at least functionality-wise, as a proof of concept or early prototype).

Evolution

At some point, I thought to myself - wouldn't it be cool if I could use a similar system for the projects section of my site.

So I created a branch on my website repository and started playing with that idea. Then I figured, what the heck - why not just try to make it into a module/plugin? And here we are.

Back to top


What is it?

It basically takes directories that contain markdown files that contain YAML front matter and builds "entities" and the relationships (e.g. hasMany) between them.

All of this gets dumped into JSON which in turn is pulled into Vuex. Also, getters/setters are dynamically generated for us to help us with our relationships (please note that it doesn't help you with your real life relationships).

Finally, the DynamicMarkdown component helps us to quickly/easily render our strange Frankensteinesque creation.

If that didn't make any sense then try the walkthrough!

Back to top


What does it look like?

You can check out the live demo if you're so inclined.

Example project

The live demo is simply this example project deployed to Netlify.


How do I install it?

Create nuxt project

If you don't already have a nuxt project then create one:

IMPORTANT: Make sure to enable Vuex when prompted.

npx create-nuxt-app my-project

Install the module

With either npm or yarn:

npm install --save nuxt-dynamic-markdown
# or
yarn add nuxt-dynamic-markdown

Register the module

In nuxt.config.js:

import NuxtDynamicMarkdown from "nuxt-dynamic-markdown";

export default {
  modules: [
    [
      NuxtDynamicMarkdown,
      {
        /*
          Sources are how you tell NDM what to load, 
          where from and how you define your relationships.
          
          IMPORTANT: This data-source is presumed to be 
          present for the duration of this README.
        */
        sources: [
          {
            nested: false,
            name: "projects",
            directory: "projects",
            relationships: ["keywords"]
          }
        ]
      }
    ]
  ]
};

Installation complete. Well done, you made it this far!

Back to top


Walkthrough / guide

Directory structure

Okay. Presume we have a directory called contents in our Nuxt project with the following structure:

contents/
  projects/
    my-first-project/
      content.md

Defining entities

And the following content.md file:

---
title: Foo Project
keywords: [foo, bar, baz]
description: I am foo project!
components: [Bar]
---

# Foo

I am foo project! The one! The only!

## Attributes (frontmatter)

My keywords are: {{ keywords.join(", ") }}.

### Custom attributes

This is a custom attribute:

{{ custom }}

## Components

This is a component:

<Bar />

Isn't it great?

There's a lot going on there so let's break it down:

YAML front matter

  • You can attach arbitrary data to an entity using the YFM block.
  • All attributes can be accessed within the markdown.
  • You can pass custom (non-YFM) attributes.
  • By convention, keywords and description populate <meta>.

NOTE: Currently you must mix in the getMeta and setHead mixins in order for <meta> to be populated.

Registering components

The special attribute components allows you to make components available within the markdown file.

Markdown content

The markdown can be anything you want, there are no real restrictions.

Back to top


Creating pages

The view project page

Next, we'll create a projects folder in pages and within it we'll create two files.

The first file will be called _project.vue and will display an individual project:

<template>
  <section>
    <DynamicMarkdown
      v-bind="project"
      :custom-attributes="{ custom: 'Hello!' }"
    />
  </section>
</template>

<script>
  import { getMeta, setHead, asyncData } from "nuxt-dynamic-markdown/lib/mixin";

  export default {
    mixins: [getMeta, setHead],
    asyncData
  };
</script>
Accessing the entity

Since the route parameter for a Nuxt page called _project.vue is project then the entity will be available via this.project.

So we simply pass this in to the DynamicMarkdown.vue component, which renders it for us.

NOTE: It's important that you pass it in exactly as above - v-bind="entity".

Passing custom attributes

As you can see, this is also where we pass in the custom attributes mentioned earlier.

Mixins and helpers (entity)

For now it is required that you, at a minimum, import the asyncData helper and pass it to your component.

The getMeta and setHead mixins on the other hand are not required to be used.

A note on boilerplate

Ideally none of this boilerplate would be necessary but for the time being it is, unfortunately.

I intend to look into ways to reduce and/or remove it.

The project listing page

The second file will be called index.vue and will list all projects:

<template>
  <section>
    <h1>My Projects</h1>

    <ul>
      <li v-for="project in projects">
        <nuxt-link
          :to="{
            name: 'projects-project',
            params: { project: projectName }
          }"
        >
          {{ project }}
        </nuxt-link>
      </li>
    </ul>
  </section>
</template>

<script>
  import { getEntities } from "nuxt-dynamic-markdown/lib/mixin";

  export default {
    asyncData: getEntities("projects")
  };
</script>
Mixins and helpers (index)

For this case (index/listing pages), it is not required that you use the getEntities helper.

If you wanted to, you could just as well create a computed property that simply accesses the Vuex store - it's roughly the same amount of code, to be honest.

Back to top


Creating the Bar component

Finally we'll create a file called Bar.vue in components since we referenced it in our content.md file earlier.

<template>
  <div>
    <p>I am bar component.</p>
  </div>
</template>

Back to top


Result

If the stars were aligned properly and you uttered the incantations with the correct stress and prosody then you should see something like this:

Project listing.

Individual project.

NOTE: You may see something slightly more simple-looking presuming you didn't use our example project (which uses Bulma).

Back to top


nuxt-dynamic-markdown's People

Contributors

sustained avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

nuxt-dynamic-markdown's Issues

Localisation support for relationships

This one is a bit of a pain point and for now I'm avoiding it completely because it severely complicates things.

Right now with the WIP addition of i18n support, relationships are not localised/localiseable.

If you have tags then the tags are going to be in one language and one language only, even if your e.g. blog article has translations into 2 other languages.

Reasons for supporting it:

  1. For some relationships it doesn't make sense anyway, consider the "projects" source for my website which has relationships for "technologies" and "languages". I'm obviously never going to translate "javascript" or "vue" even if I do translate my projects. It doesn't make sense.
  2. Right now we are basically doing something like this: { "keyword": [ "entity-one", "entity-two" ] } to relate the relationships to the entities and vice versa. This becomes more difficult if we support localisation because words are not necessarily unique anymore (i.e. a word in language A can also be a word in language B but both must be distinct tags). This severely complicates the entire system.

Reasons for supporting it:

???

Helper-related issues.

There are some issues relating to the helpers (e.g. asyncData, getEntities etc.).

The store name is not always the entity name

  • For projects we'd have something like store.state.projects.projects.
  • But for the blog it'd be rather store.state.blog.articles.

The issue here is that we don't currently have any way to determine what the store name (blog) is based only on a route parameter (e.g. article).

I see a few potential solutions:

  1. Just require the store name to be passed in to the helpers.
  2. Store some kind of mapping somewhere on the context where asyncData can access it.

The helpers don't cover all the possibilities

We have the asyncData helper which works great for non-nested entities but I'm not so sure it will work fine for nested entities? Needs testing.

And even if it does, it will only handle the inner/child entitiy (e.g. a blog article) and not the outer/parent entity (e.g. a blog category).

Ideally we'd have helpers for all possible cases:

  • Listing inner entities (blog articles)
  • Listing outer entities (blog categories)
  • Listing relationships (blog tags)
  • Viewing a specific entity (blog article, blog category, blog tag).

Nested entities

There are a few points to address relating to nested entities:

  1. The structure right now for a nested and a non-nested entity is identical.
  2. How can we change the structure to make it more logical and easier to parse?
  3. Should there be some limit to the nesting?
  4. Are relationships not technically nested entities?

Structure is identical for nested/non-nested.

Consider the current example from the README:

contents/
  projects/
    my-first-project/
      content.md

This is zero levels of nesting - there are projects and only projects.

And now consider the blog:

contents/
  blog/
    a-blog-category/
      _meta.md
      an-article.md

This is actually one level of nesting - there are categories and within them articles.

But it looks identical, structure-wise, to the example above. And this is an issue.

How can we change the structure?

The my-first-project folder isn't really doing anything for the projects. Heck, I don't even think the directory name is being used. It serves only to make parsing more difficult and to cause confusion.

We could just as well do this:

contents/
  projects/
    my-first-project.md
    my-second-project.md

And actually I think that is probably what we should and will do.

This would mean that directories are only present in the case of nesting, which allows us to remove a source option too.

Should nesting be limited?

So far my own personal use-cases are for entities which:

  • represent my personal projects (no nesting)
  • represent a blog (categories -> articles)
  • represent portfolio entries (no nesting)
  • etc.

I can't even think of a case where more than one level of nesting would be useful or necessary.

Yes, of course we could have something like categories -> articles -> comments but it makes zero sense to be storing comments using this system. The same applies to e.g. a forum (categories -> posts -> replies) - such things make zero sense for this system.

Does there exist a use-case for arbitrary nesting?

If there is, I think that severely complicates this project.

And so for now perhaps we should ask a new question:

Should we impose a limitation of only one level of nesting.

I think for now the answer will have to be yes.

Are relationships not technically nested entities?

Well yes, which complicates this further. Consider: categories -> articles -> tags.

Suddenly we do have two levels of nesting! The difference is that this "entity" does not exist as a directory or a file - only as YAML front matter within the article.

Entity metadata

What if we want or need to attach some metadata the "entities" which exist only in the YAML front matter? Clearly we can't store that in a nested way - there'd be duplication!

We need some kind of convention for this too.

Imagine you want a projects entity and a project can have many technologies and many languages (like on my website). At this stage I'm thinking it would perhaps look something like this:

contents/
  projects/
    _languages.md
    _technologies.md
    my-first-project.md
    my-second-project.md

With a _languages.md like this:

javascript:
  aliases: [js]
  description: JS is da best.

And similarly with a blog with tags:

contents/
  blog/
    _tags.md
    a-blog-category/
      _meta.md
      an-article.md

And _tags.md:

programming:
  description: Articles about programming.
music:
  description: Articles about music.

No vuex store = runtime error.

Cannot read property 'registerModule' of undefined

We should check that the store exists since we currently depend on it!

Inconsistent attribute access.

How attributes are accessed

Bare attributes (e.g. foo)

  • Within markdown files themselves.
  • When iterating over an entity collection (e.g. projects/index.vue).

Namespaced attributes (e.g. attributes.foo)

  • When using asyncData (e.g. projects/_project.vue).

This inconsistency is confusing and this should be changed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.