Files
boos e0a5f57793 feat(desktop): embed BoosAPI Desktop client as a subproject
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
2026-06-22 11:32:57 +08:00

6.2 KiB

description
description
Override built-in markstream-vue components like image, code_block, mermaid, and link with scoped, project-safe custom mappings.

Override Built-in Components

Most advanced customization starts with setCustomComponents, not parser hooks.

Use this page when you want to:

  • replace built-in renderers such as image, code_block, mermaid, or link
  • keep overrides scoped to one part of the app
  • understand which override key matches which node type

If you want trusted custom tags such as thinking, go to Custom Tags & Advanced Components. If props and slots are enough, stay with Props & Options.

1. Prefer scoped overrides

Use custom-id on the renderer and register overrides under the same id:

import type { Component } from 'vue'
import { setCustomComponents } from 'markstream-vue'

declare const DocImage: Component

setCustomComponents('docs', {
  image: DocImage,
})
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'

const markdown = '![demo](https://example.com/demo.png)'
</script>

<template>
  <MarkdownRender custom-id="docs" :content="markdown" />
</template>

Why this is the default recommendation:

  • it prevents one project area from changing another
  • it makes testing and cleanup easier
  • it matches how the renderer merges scoped and global mappings

The single-argument form setCustomComponents({ ... }) is still supported, but it is best treated as a legacy global override.

2. The override keys you will actually use

Key What it replaces Notes
image ImageNode Great first override for lightboxes, captions, or lazy-loading wrappers
link LinkNode Useful for analytics, router links, or custom tooltip behavior
code_block Regular fenced code blocks Used as the generic fallback after exact language keys and the built-in mermaid / d2 / infographic routes
mermaid Mermaid fenced blocks Preferred over code_block when only Mermaid needs a custom renderer
d2 D2 and d2lang fenced blocks Same routing idea as Mermaid
infographic Infographic fenced blocks Use when only infographic output changes
inline_code Inline code spans Useful for docs typography or special inline tooling
heading, paragraph, list_item, blockquote Container nodes Powerful, but you must render children yourself

The full list of built-in node types lives in Components API and the exported CustomComponents type.

3. Start with leaf-like nodes

Leaf-like nodes are the easiest place to begin because they do not require you to manually re-render child nodes.

Example: replace image

<script setup lang="ts">
import type { ImageNodeProps } from 'markstream-vue'

const props = defineProps<ImageNodeProps>()
</script>

<template>
  <figure class="docs-image">
    <img :src="props.node.src" :alt="props.node.alt" loading="lazy">
    <figcaption v-if="props.node.title || props.node.alt">
      {{ props.node.title || props.node.alt }}
    </figcaption>
  </figure>
</template>
import type { Component } from 'vue'
import { setCustomComponents } from 'markstream-vue'

declare const DocImage: Component

setCustomComponents('docs', {
  image: DocImage,
})

Example: switch regular code blocks to MarkdownCodeBlockNode

import { MarkdownCodeBlockNode, setCustomComponents } from 'markstream-vue'

setCustomComponents('docs', {
  code_block: MarkdownCodeBlockNode,
})

This only changes regular fenced code blocks. Mermaid, D2, and infographic blocks still route to their specialized renderers unless you override mermaid, d2, or infographic.

Example: target one fenced language directly

import type { Component } from 'vue'
import { setCustomComponents } from 'markstream-vue'

declare const EChartsBlockNode: Component

setCustomComponents('docs', {
  echarts: EChartsBlockNode,
})

This only catches fences whose language is echarts. The routing order for code blocks is:

  • exact language key such as echarts
  • built-in specialized routes such as mermaid, d2, and infographic
  • generic code_block

Example: override Mermaid only

import type { Component } from 'vue'
import { setCustomComponents } from 'markstream-vue'

declare const CustomMermaidBlock: Component

setCustomComponents('docs', {
  mermaid: CustomMermaidBlock,
})

This is the safest pattern when you want custom export controls, branding, or diagram chrome without affecting all code blocks.

4. What props your override receives

Every custom renderer receives the basics:

  • node
  • loading
  • indexKey
  • customId
  • isDark

The renderer also forwards feature-specific props:

  • regular code block overrides receive stream, theme, darkTheme, lightTheme, monacoOptions, themes, minWidth, maxWidth, and anything passed through codeBlockProps (theme is the recommended field; darkTheme / lightTheme remain for compatibility)
  • Mermaid, D2, and infographic overrides receive the corresponding mermaidProps, d2Props, or infographicProps
  • link and list overrides inherit tooltip and typewriter-related bindings from the parent renderer

The exported prop types in markstream-vue are the easiest way to keep your overrides strongly typed.

5. Container nodes need extra care

Overriding heading, paragraph, list_item, or blockquote is absolutely supported, but those nodes carry children. Your component needs to decide how to render them.

Practical advice:

  • start with image, link, code_block, mermaid, or inline_code unless you truly need container-node control
  • if you override a container node, inspect the built-in renderer in src/components/* and mirror the child-rendering pattern you need
  • keep the override scoped with custom-id so you can iterate safely

6. Cleanup for dynamic scopes

If the override only lives for a temporary view, remove it when that scope is no longer needed:

import { removeCustomComponents } from 'markstream-vue'

removeCustomComponents('docs')

This is especially helpful in SPAs, Storybook stories, playgrounds, and test suites.