Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

renderer.* to vue3 component #2915

Closed
doroved opened this issue Aug 3, 2023 · 7 comments
Closed

renderer.* to vue3 component #2915

doroved opened this issue Aug 3, 2023 · 7 comments

Comments

@doroved
Copy link

doroved commented Aug 3, 2023

Describe the feature
Make it possible to render tokens using vue 3 components.

App.vue

<script setup lang="ts">
import { ref, h } from 'vue'
import { marked } from 'marked'

const content = ref(`
1. [Apple](https://apple.com/)
2. [Google](https://google.com/)
3. [Amazon](https://amazon.com/)
`)

const renderer = new marked.Renderer()
renderer.link = (href, title, text) => {
  return h('CustomLink', { href, text }) // Return a component instead of an HTML string
}

marked.use({ renderer })
</script>

<template>
<div v-html="marked.parse(content)"></div>
</template>

CustomLink.vue

<script setup lang="ts">
const props = defineProps({
  href: {
    type: String,
    required: true,
    default: () => ''
  },
  text: {
    type: String,
    required: true,
    default: () => ''
  }
})
</script>

<template>
  <a :href="props.href" target="_blank" class="text-red-500">{{ props.text }}</a>
</template>

Why is this feature necessary?
In vue 3 you need to render code,link etc from a component so that you can set @click events etc for elements inside the component.

Describe alternatives you've considered
I used this code, it works, but the registered events inside the component will not work as we are only returning the HTML code of the component, not the component itself.

<script setup lang="ts">
import { ref, h } from 'vue'
import { marked } from 'marked'

const content = ref(`
1. [Apple](https://apple.com/)
2. [Google](https://google.com/)
3. [Amazon](https://amazon.com/)
`)

const renderer = new marked.Renderer()
renderer.link = (href, title, text) => {
  // Create vnode
  const vnode = h(CustomLink, {
    href: href!,
    text: text!
  })

  // Create a container for rendering on the client
  const container = document.createElement('div')

  // Convert vnode to a string
  render(vnode, container)
  return container.innerHTML
}

marked.use({ renderer })
</script>

<template>
<div v-html="marked.parse(content)"></div>
</template>
@UziTech
Copy link
Member

UziTech commented Aug 3, 2023

You will have to create your own Parser. The marked parser concats the output of the renderers as strings.

@doroved
Copy link
Author

doroved commented Aug 3, 2023

You will have to create your own Parser. The marked parser concats the output of the renderers as strings.

Can you tell me how to do it? Are there any examples?
I need to render code to the component, not HTML string, so that all registered events and functions inside the component work.

renderer.code = (code: string, lang: string) => {
  const language = hljs.getLanguage(lang) ? lang : 'plaintext'
  const result = hljs.highlight(code, { language }).value

  return h('CodeBlock', { code, lang, result })
}

From your answer I understood that we still get HTML code on the output, but most likely functions and events inside the component will not work in this case.

@UziTech
Copy link
Member

UziTech commented Aug 3, 2023

You can copy the marked Parser and run it on the lexer tokens.

marked.use({renderer});
const tokens = marked.lexer(markdown);
const vueComponent = myParser(tokens);

@doroved
Copy link
Author

doroved commented Aug 3, 2023

You can copy the marked Parser and run it on the lexer tokens.

marked.use({renderer});
const tokens = marked.lexer(markdown);
const vueComponent = myParser(tokens);

Yeah, I gather it's not exactly an easy task, too bad.

@nanmu42
Copy link

nanmu42 commented Jan 10, 2024

Following hints from #2915 (comment), I managed to implement a markdown-to-vue-component parser. Here are some gists:

Take the source code of Marked and put them into your project - It's well structured and has no external dependency.

Start with parseInline() and change its signature:

  /**
   * Parse Inline Tokens
   */
  parseInline(tokens: Token[], renderer?: _Renderer): (VNode | string)[] {
    renderer = renderer || this.renderer
    const out: (VNode | string)[] = []

where VNode is from Vue.

Now follow the type error to amend the Renderer.ts, e.g.:

  list(body: (VNode | string)[], ordered: boolean, start: number | ''): VNode {
    const type = ordered ? 'ol' : 'ul'
    const startatt = ordered && start !== 1 ? { start: start } : {}
    return h(type, startatt, body)
  }

  listitem(text: (VNode | string)[]): VNode {
    return h('li', {}, text)
  }

  checkbox(checked: boolean): VNode {
    interface Props {
      type: string
      disabled: boolean
      checked?: ''
    }

    const props: Props = {
      type: 'checkbox',
      disabled: true,
    }
    if (checked) {
      props['checked'] = ''
    }

    return h('input', props)
  }

You may import and use your own tailored Vue components in the h() function.

Repeat the process for parse():

  /**
   * Parse Loop
   */
  parse(tokens: Token[], top = true): (VNode | string)[] {
    const out: (VNode | string)[] = []

Follow the type errors to modify marked.ts, Instance.ts, etc.

Also usages of escape() functions should be removed to avoid unwanted HTML escape.

Finally, a ​functional component is going to consume marked.parse.

Good luck.


Many thanks to the authors of Marked, the project is compact and well-structured. I really enjoy the code reading and hacking process. Cheers! 🍻

@doroved
Copy link
Author

doroved commented Jan 10, 2024

@nanmu42

Hi, thanks for the info)
Could you please create a repository with an example, so it would be clear to see how it all works. Because in the node_modules folder I only see these files.
image
+ I'm interested to see how the code() render function would look like

@L422Y
Copy link

L422Y commented Jan 18, 2024

I'm working on this, might be of help, still very much a WIP and I honestly am not incredibly sure I have a great handle on everything yet, but it mostly works:

https://github.com/L422Y/nuxt-content-lite/tree/main/src/runtime/src/vueMarked

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants