nuxt / content

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

Manipulating mdast / file.body during afterParse hook? #2502

Closed N0K0 closed 1 month ago

N0K0 commented 8 months ago

Discussed in https://github.com/nuxt/content/discussions/2411

Originally posted by **N0K0** November 5, 2023 Hi! Hitting a wall when it comes to using the afterParse hook. I'm able to change the value of text nodes, but I'm unable to change the node type, like for example from text to inlineCode, which is defined as just swapping type from `text` to `inlineCode` Getting the same error when I try to do the manipulation during a `visit` run, which i assumed was related to manipulation during iteration, and when using `findAndReplace` from `mdast-util-find-and-replace` So I figured it might still work during direct reference without any iterators and the likes, but still hitting the wall. Any help at all would be greatly appreciated! My goal is to make a plugin to support the Foam format, which means supporting Wiki links like [[ this ]] and [[ this|alias]], as well as inline #tags that links to a common page for example :) ## index.md ```md # Index ## Tags #tag1 #tag2 #tag3 #tag_newline #tag1 #tag1 ## Wiki links [[ 1 |Alias for 1]] (Broken) [[2 |Alias for 2]] (Broken) [[3|Alias for 3]] (Valid) [[ 4|Alias for 4]] (Broken) ``` ## :heavy_check_mark: Can change value of a text node ### Code ```js nitroApp.hooks.hook("content:file:afterParse", (file: MarkdownParsedContent ) => { if ( !useRuntimeConfig().parse_after ) return if (!file._id.endsWith(".md")) return if (file.body.children[2]?.children[2]) { const link_node = {type: "inlineCode", value: "test"} // Targeting the #tag_newline node file.body.children[2].children[2].value = "MANIPULATED" } }); ``` ### Output ![image](https://github.com/nuxt/content/assets/13900053/4d0d06ac-4fae-48dd-8587-fd60ce5be78e) ## :negative_squared_cross_mark: Unable to change node type to anything else: ### Code ```js nitroApp.hooks.hook("content:file:afterParse", (file: MarkdownParsedContent ) => { if ( !useRuntimeConfig().parse_after ) return if (!file._id.endsWith(".md")) return if (file.body.children[2]?.children[2]) { const link_node = {type: "inlineCode", value: "test"} console.log(file.body.children[2].children[2]) file.body.children[2].children[2].type = "inlineCode" } }); ``` ### Output ``` { type: 'text', value: '\n#tag3\n#tag_newline\n#tag1' } ``` ### Stacktrace ``` [nuxt] [request error] [unhandled] [500] src.replace is not a function at Object.escapeHtmlComment (./node_modules/@vue/shared/dist/shared.cjs.js:334:14) at renderVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:720:34) at renderVNodeChildren (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:759:5) at ssrRenderSlotInner (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:120:7) at Module.ssrRenderSlot (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:95:3) at _sfc_ssrRender (./node_modules/@nuxtjs/mdc/dist/runtime/components/prose/ProseP.vue:11:25) at renderComponentSubTree (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:683:9) at renderComponentVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:631:12) at renderVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:743:14) at renderComponentSubTree (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:698:7) [nuxt] [request error] [unhandled] [500] src.replace is not a function at Object.escapeHtmlComment (./node_modules/@vue/shared/dist/shared.cjs.js:334:14) at renderVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:720:34) at renderVNodeChildren (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:759:5) at ssrRenderSlotInner (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:120:7) at Module.ssrRenderSlot (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:95:3) at _sfc_ssrRender (./node_modules/@nuxtjs/mdc/dist/runtime/components/prose/ProseP.vue:11:25) at renderComponentSubTree (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:683:9) at renderComponentVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:631:12) at renderVNode (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:743:14) at renderComponentSubTree (./node_modules/@vue/server-renderer/dist/server-renderer.cjs.js:698:7) ``` ## Tsconfig: ```js export default defineNuxtConfig({ modules: [ '@nuxt/content' ], runtimeConfig: { stripBrackets: true, parse_before: false, parse_after: true }, content: { documentDriven: true, markdown: { // anchorLinks: false } } }) ```
farnabaz commented 8 months ago

Thanks for reporting the issue? Do you mind providing a reproduction? You can use Nuxt Starter if you want to

N0K0 commented 8 months ago

Hi @farnabaz, the following is the smallest example I managed to make :) Only touched the index.md file to fill with some alternative content and the plugin https://stackblitz.com/edit/github-njqpgs?file=server%2Fplugins%2Fafter.ts

The log is different from when I last tried in November, but the final error is the same src.replace is not a function

Log:

{
  _path: '/about',
  _dir: '',
  _draft: false,
  _partial: false,
  _locale: '',
  title: 'About Content v2',
  description: 'Back home',
  excerpt: undefined,
  body: {
    type: 'root',
    children: [ [Object], [Object] ],
    toc: { title: '', searchDepth: 2, depth: 2, links: [] }
  },
  _type: 'markdown',
  _id: 'content:about.md',
  _source: 'content',
  _file: 'about.md',
  _extension: 'md'
}
[Vue warn]: Failed to resolve component: 
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
{
  _path: '/',
  _dir: '',
  _draft: false,
  _partial: false,
  _locale: '',
  title: 'Index',
  description: '',
  excerpt: undefined,
  body: {
    type: 'root',
    children: [ [Object], [Object], [Object], [Object] ],
    toc: { title: '', searchDepth: 2, depth: 2, links: [Array] }
  },
  _type: 'markdown',
  _id: 'content:index.md',
  _source: 'content',
  _file: 'index.md',
  _extension: 'md'
}
[Vue warn]: Failed to resolve component: 
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
[Vue warn]: Invalid vnode type when creating vnode: undefined.
[nuxt] [request error] [unhandled] [500] src.replace is not a function

[Vue warn]: Failed to resolve component: 
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
[Vue warn]: Failed to resolve component: 
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
[Vue warn]: Invalid vnode type when creating vnode: undefined.
[nuxt] [request error] [unhandled] [500] src.replace is not a function
farnabaz commented 8 months ago

I see, This happens because the modified tree is invalid. If you want to change a text into a code, you can do this:

if (file.body.children[2]?.children[2]) {
      const link_node = {
        type: 'element',
        tag: 'code',
        children: [
          {
            type: 'text',
            value: 'test updated',
          },
        ],
      };

      file.body.children[2].children[2] = link_node;
    }

I have updated your reproduction: https://stackblitz.com/edit/github-njqpgs-yxn88u?file=server%2Fplugins%2Fafter.ts

N0K0 commented 7 months ago

Ah, interesting! Sorry for the slow response. I've been referring to the mdast spec https://github.com/syntax-tree/mdast?tab=readme-ov-file#code

Any place I can read about the structure Content expects? :)

farnabaz commented 6 months ago

The structure of MDC tree is very similar to HAST structure, except that MDC use tag instead of tagName and props instead of properties. Checkout Hast docs here: https://github.com/syntax-tree/hast

github-actions[bot] commented 2 months ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.

github-actions[bot] commented 1 month ago

This issue was closed because it has been stalled for 30 days with no activity.