nuxt / content

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

Allow modify the file object when invoking `beforeParse` hook #2056

Open Kiyo5hi opened 1 year ago

Kiyo5hi commented 1 year ago

Is your feature request related to a problem? Please describe

I tried to do a full-text search among all of markdown files under content directory, and this requires processing the raw text. I tried appending properties to file object:

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('content:file:beforeParse', (file) => {
    if (file._id.endsWith('.md')) {
      file.plainText = file.body
    }
  })
})

However, I noticed that the source simply does not mutate the file object; instead, it just passed properties needed for following procedures.

I wonder if beforeParse can behave the same just like afterParse?

Describe the solution you'd like

Mutate file object when invoking beforeParse hook

Describe alternatives you've considered

For now, I did:

import { Node as UnistNode } from 'unist'

interface Node extends UnistNode {
  value: 'text' | string
  children?: Node[]
  tag?: 'style' | string
}

function toText (root: Node) {
  let text = ''

  function recurse (node: Node) {
    if (node.type === 'text') {
      text += ` ${node.value}`
    }

    if (node.children && node.tag !== 'style') {
      for (const child of node.children) {
        recurse(child)
      }
    }
  }

  recurse(root)
  return text
}

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('content:file:afterParse', (file) => {
    if (file._id.endsWith('.md')) {
      file.plainText = toText(file.body)
    }
  })
})

And it is hecka annoying.

Additional context

Kiyo5hi commented 1 year ago

Using patch to enable this temporarily:

diff --git a/src/runtime/server/storage.ts b/src/runtime/server/storage.ts
index 25cddb13..b607b743 100644
--- a/src/runtime/server/storage.ts
+++ b/src/runtime/server/storage.ts
@@ -182,7 +182,13 @@ export async function parseContent (id: string, content: string, opts: ParseCont
   const file = { _id: id, body: content }
   await nitroApp.hooks.callHook('content:file:beforeParse', file)

-  const result = await transformContent(id, file.body, options)
+  const result: ParsedContent | {[key: string]: any} = await transformContent(id, file.body, options)
+
+  for (const [key, value] of Object.entries(file)) {
+    if (result[key] === undefined) {
+      result[key] = value
+    }
+  }

   // Call hook after parsing the file
   await nitroApp.hooks.callHook('content:file:afterParse', result)
xdeq commented 1 year ago

@Kiyo5hi I found a simple solution (to add "plain text" field), but I don't like it

export default defineNitroPlugin((nitroApp) => {
    let files = {}

    nitroApp.hooks.hook("content:file:beforeParse", (file) => {
        if (file._id.endsWith(".md")) {
            files[file._id] = file.body
        }
    })

    nitroApp.hooks.hook("content:file:afterParse", (file) => {
        if (file._id.endsWith(".md")) {
            file.plainText = files[file._id]
        }
    })
})

Hope this helps!

dword-design commented 1 year ago

Same for my nuxt-content-body-html module :).

N0K0 commented 9 months ago

Just found this issue, my usecase is to support the Foam format https://foambubble.github.io/ Which allows you to inline #tags #like #this

Hoped something as simple as this would have worked:

const tagRegEx = /#(\w+)/g;
if (!file.tags) {
  file.tags = [];
}
// Get all matches of #tag format
const tagMatches = [...file.body.matchAll(tagRegEx)];

tagMatches.forEach((match) => {
  console.log("Tag: " + match)
  // Add tag to frontmatter if it doesn't exist already
  if (!file.tags.includes(match[1])) {
    file.tags.push(match[1]);

    // Replace #tag with a link to the corresponding tag page
    file.body = file.body.replaceAll(match[0], `[#${match[1]}](/tags/${match[1]})`, 'g');
  }
}

but as the others here, I need to do some extra trickery :) Thankfully, the solution from xdeq is smooth and gives me some more ideas for how to fix the support 8)

ManUtopiK commented 3 months ago

There is a simpler way to expose your markdown files ! Even more, it provides a direct URL access and works for all kind of files : md, yaml, csv...

Just expose your content directory as a public assets. In a module, add this :

nuxt.hook('nitro:config', (nitroConfig) => {
  nitroConfig.publicAssets ||= []
  nitroConfig.publicAssets.push({
    dir: `${nitroConfig.rootDir}/content`,
    baseURL: '',
    maxAge: 60 * 60 * 24 * 365, // 1 year
  })
})

Now, add the extension to a route and you get your files. Ex: From the page /url-of-my-awesome-page, you can get the corresponding markdown file with /url-of-my-awesome-page.md.