Manipulating mdast / file.body during afterParse hook?
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
# 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
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
:negative_squared_cross_mark: Unable to change node type to anything else:
Code
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:
export default defineNuxtConfig({
modules: [
'@nuxt/content'
],
runtimeConfig: {
stripBrackets: true,
parse_before: false,
parse_after: true
},
content: {
documentDriven: true,
markdown: {
// anchorLinks: false
}
}
})
```</div>
Thanks for reporting the issue? Do you mind providing a reproduction? You can use Nuxt Starter if you want to
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
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
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? :)
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
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.
This issue was closed because it has been stalled for 30 days with no activity.