# Markdown 内容 · Markdown

- 页面: https://jiguang132.com/zh/resources/astryx/docs/markdown
- 资源: Astryx (https://jiguang132.com/zh/resources/astryx.md)
- 全部指南: https://jiguang132.com/zh/resources/astryx/docs.md

渲染格式化文本，使用示例内容演示不同排版。

## 安装与前置条件

```bash
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
```

需要 React 19+，按顺序导入基础 CSS 和主题，并设置 Theme Provider。参见[开始使用](/resources/astryx/docs/getting-started)。

## 示例与使用方式

- **Markdown 内容 · 综合展示 1**：首先体验这个示例，再查看代码理解组件的组合方式。
- **Markdown 内容 · 交互示例 2**：可交互查看这一用法的行为，复制代码时保留它实际使用的属性与导入。
- **Markdown 内容 · 交互示例 3**：可交互查看这一用法的行为，复制代码时保留它实际使用的属性与导入。
- **Markdown 内容 · 交互示例 4**：可交互查看这一用法的行为，复制代码时保留它实际使用的属性与导入。
- **Markdown 内容 · 交互示例 5**：可交互查看这一用法的行为，复制代码时保留它实际使用的属性与导入。

## API 与上游详细参考

优先使用 0.7.1 官方中文说明；上游尚未提供中文的条目保留英文。属性名、类型及默认值保持原生 API，不把示例状态名称当成组件属性。

## Markdown

Renders a markdown string as Astryx-styled components. Use Markdown for user-generated content, AI responses, and documentation; it handles headings, lists, tables, code blocks, and citations with consistent styling.

| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children * | string |  | 要渲染的 Markdown 字符串。 |
| display | 'block' \| 'inline' | 'block' | 显示类型。Markdown 默认为 block。使用 'inline' 可在文本内嵌入 Markdown 片段。 |
| density | 'default' \| 'compact' | 'default' | 控制块级元素之间的间距。 |
| headingLevelStart | 1 \| 2 \| 3 \| 4 \| 5 \| 6 | 1 | Markdown # 映射到的 HTML 标题级别。将所有标题级别向下偏移以适应页面层次结构。超过 h6 的级别将被限制为 h6。 |
| isStreaming | boolean | false | 启用流式模式，使用增量解析和淡入动画处理分块文本。 |
| onLinkClick | (href: string, event: MouseEvent) => void \| false |  | 链接点击处理器。返回 false 可阻止默认导航行为。 |
| sources | Record<string, MarkdownSource> |  | 按 ID 索引的引用来源。提供后，Markdown 中匹配的 [id] 和 【id】 标记将渲染为引用标签。 |
| citationStyle | 'label' \| 'number' | 'label' | 引用的内联显示方式。'label' 显示带标题、图标和边框的标签。'number' 显示紧凑编号徽章。 |
| contentWidth | number \| string | 680 | 正文内容的最大宽度（段落、标题、列表、引用块）。表格和代码块不受限制，可扩展到完整容器宽度。用于在宽布局中保持可读行长。 |
| contentAlign | 'start' \| 'center' | 'start' | 当 contentWidth 小于可用空间时，正文内容在容器内的对齐方式。 |
| plugins | readonly MarkdownPluginEntry[] |  | 由 createMarkdownPlugin() 创建的有序扩展。插件可添加有界语法、不可变的类型化 AST 转换和类型化扩展渲染器。使用 isMarkdownExtensionNode() 缩小从其他插件观察到的扩展数据类型。渲染回调必须是纯函数；需要 Hook 时请返回子组件。省略或传入空列表时保持已发布的 Markdown 行为。 |
| inlinePlugins | MarkdownInlinePlugin[] |  | 将已解析文本节点中的正则匹配转换为自定义内联 React 元素。适用于带前缀的标识符、用户提及等简写模式。内联代码、围栏代码块和数学表达式不受影响。 |
| autolink | 'gfm' |  | 可选的裸 URL 和电子邮箱自动链接。设为 'gfm' 启用 GitHub Flavored Markdown 自动链接规则：裸 https?://、www.、<scheme:url>、<email> 以及 user@host 都会变成链接。末尾句末标点和不平衡的末尾右括号会被排除；代码块、现有链接和图片替代文本内部的匹配会被跳过。默认为关闭。 |
| components | MarkdownComponents |  | 用于覆盖 Markdown 渲染元素的自定义 React 组件（code、inlineCode、math、link、heading、paragraph、image、blockquote、hr、citation）。提供 math 会启用 `$…$` 行内数学和 `$$…$$` 块级数学解析，并接收 `{value, display}`；不提供时美元符号保持原样。 |
| xstyle | StyleXStyles |  | 用于布局自定义的 StyleX 样式。必须是 stylex.create() 的值，而非内联样式对象。 |
| className | string |  | 根元素的 CSS 类名。建议使用 xstyle，className 适用于非 StyleX 系统集成。 |
| style | CSSProperties |  | 根元素的内联样式。建议使用 xstyle，内联样式会绕过 StyleX 优化。 |
| data-testid | string |  | 用于自动化测试框架的测试选择器。 |

### 使用建议

- Set headingLevelStart to match the page hierarchy, e.g. start at 3 if the markdown sits inside an h2 section.
- Use contentWidth to keep prose at a readable line length in wide layouts.
- Emphasis and strong pair as CommonMark specifies, so *see **bold** more* puts the bold inside the emphasis. ***text*** renders as strong around emphasis, the order RichText draws, and is announced as both. Emphasis nested more than 100 levels deep stays as written text.
- Use plugins created by createMarkdownPlugin for reusable syntax, immutable AST transforms, and typed extension rendering. Keep the ordered list stable while its syntax configuration is unchanged.
- To render a plugin node outside Markdown, as another surface showing the same document does, read what the plugin declares with getMarkdownPluginCapabilities (syntax and transform, nothing else), and render each parsed extension node with MarkdownPluginNodeRenderer from the client-only @astryxdesign/core/Markdown/plugin-renderer subpath. It presents the node exactly as Markdown does, fallbacks and failure reports included, with no element of its own.
- Add markdownSoftBreaksPlugin when single line endings are meaningful. It matches remark-breaks for supported Markdown, including multiline link labels, while code and other opaque content stay unchanged.
- Add markdownSourceLinesPlugin when a feature maps the rendered document back to Markdown source lines (find, highlights, review comments). Every block Markdown renders, nested blocks included, then carries data-source-line and data-source-line-end (1-based, inclusive); components block renderers receive sourceLines to place on their own element, and plugin renderers read node.position lines. Inline elements, table rows, and cells carry none. Without the plugin no line work runs.
- Use createMarkdownTextTransform for prose matching; it preserves code, links, images, citations, math, and accepted extension syntax as protected contexts. Provide requiredSubstrings only when they conservatively cover every possible match.
- Use createMarkdownFenceTransform for declared code-fence languages with semantic data. createNode returns an owned block extension node; its standard plugin renderer and toText own presentation. A claimed fence renders through that plugin even when components.code is supplied; components.code renders every unclaimed fence and is the fallback when the plugin renderer declines, throws, or suspends, and without it the accessible, copyable CodeBlock is that fallback.
- Use createMarkdownSourceDecoration to attach non-visual metadata — search hits, review annotations — to the blocks a source range touches, and getMarkdownSourceDecorations to read it back in a later plugin. Decorations appear on the settled document rather than on partial streaming chunks, and never change rendering, copyable text, accessible names, ids, focus order, or navigation.
- Import parseMarkdownAst or parseInlineAst from @astryxdesign/core/Markdown/parser when server or React Server Component code needs to run plugins against the canonical readonly tree. The parser and plugin subpaths have no use-client boundary. The Markdown component remains client-owned, so function-bearing plugin entries must not be passed across an RSC serialization boundary.
- Use createMarkdownFrontmatter for typed document metadata. Its parse() method gives the host metadata directly; its plugin removes a complete leading block before rendering and withholds an unfinished block during streaming.
- Import createMarkdownRemarkTransform from '@astryxdesign/core/Markdown/remark' only to reuse an existing synchronous transform-only Remark plugin; it stays out of every other bundle. Prove each plugin with fixtures: anything outside the supported MDAST subset — async work, parser or compiler plugins, processor state, raw HTML, unsupported nodes, forged positions, or metadata Astryx cannot represent — keeps the last valid document and reports one diagnostic.
- Use inlinePlugins for prefixed identifiers, mentions, and other prose-only shorthand instead of preprocessing the markdown string.
- Provide components.math only for documents that use dollar-delimited math. The renderer owns typesetting and accessible output; Astryx passes the expression as text and never executes raw HTML.
- For direct parsing, use MathParseOptions and handle InlineNodeWithMath or BlockNodeWithMath. Incremental math parsing also uses createIncrementalState<true>() and IncrementalParseState<true>; default calls and ParseOptions annotations keep the legacy unions.
- Install createMarkdownHeadingLinks() through plugins when a document needs copyable h1–h6 permalinks. The returned entry carries its validated namespace and URL base across compatible Core package copies. Each built-in heading keeps its incoming fragment ID and gains an inline trailing # copy button: it is hidden at fine-pointer rest through useContainerReveal, reveals on heading-row hover or keyboard focus, and stays visible under the hook’s coarse/touch semantics. Unmodified tap, click, Enter, or Space copies the canonical URL without navigation, hash mutation, or scrolling; a check confirms success for 1.5 seconds. The no-plugin path stays unchanged, and nested headings use the same depth-first identity projection as Markdown-derived Outline.
- For multiple documents, create one heading-links plugin per document with headingIdPrefix and pass that same plugin to Markdown and useOutlineFromMarkdown. Use the same stable value for Markdown id when the root also needs a DOM id; permalinkBaseUrl may supply a safe caller-owned URL before the fragment.
- 避免：Use Markdown for hand-authored layouts; use Text and Heading directly when you control the content.

### Inline display

```tsx
import {Text} from '@astryxdesign/core/Text';

<Text>
  This description includes{' '}
  <Markdown display="inline">{'`inline code` and **bold text**'}</Markdown>
  .
</Text>;
```

### GFM autolinks

```tsx
<Markdown autolink="gfm">
  {'Visit https://example.com or email contact@example.com. ' +
    'You can also bracket links: <https://docs.example.com>.'}
</Markdown>;
```

### Linkable headings

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {createMarkdownHeadingLinks} from '@astryxdesign/core/Markdown/plugins';
import {Outline, useOutlineFromMarkdown} from '@astryxdesign/core/Outline';

const documentId = 'guide';
const headingLinks = createMarkdownHeadingLinks({
  headingIdPrefix: documentId,
});
const plugins = [headingLinks];

function Guide({source}) {
  const items = useOutlineFromMarkdown(source, {plugins});
  return (
    <>
      <Markdown id={documentId} plugins={plugins}>{source}</Markdown>
      <Outline items={items} />
    </>
  );
}
```

### First-party soft breaks

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {markdownSoftBreaksPlugin} from '@astryxdesign/core/Markdown/plugins';

<Markdown plugins={[markdownSoftBreaksPlugin]}>
  {'First line\nSecond line'}
</Markdown>;
```

### Source-line stamps

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {markdownSourceLinesPlugin} from '@astryxdesign/core/Markdown/plugins';

// Each rendered block carries data-source-line / data-source-line-end.
<Markdown plugins={[markdownSourceLinesPlugin]}>
  {'# Title\n\n- one\n- two'}
</Markdown>;
```

### Text transform helper

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {
  createMarkdownPlugin,
  createMarkdownTextTransform,
} from '@astryxdesign/core/Markdown/plugins';

const finalLabels = createMarkdownPlugin({
  name: 'final-labels',
  apiVersion: 1,
  transform: createMarkdownTextTransform({
    pattern: /\bDraft\b/g,
    requiredSubstrings: ['Draft'],
    replace: () => ({type: 'text', value: 'Final'}),
  }),
});

<Markdown plugins={[finalLabels]}># Draft</Markdown>;
```

### Semantic fence helper

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {
  createMarkdownFenceTransform,
  createMarkdownPlugin,
  type MarkdownExtensionNode,
} from '@astryxdesign/core/Markdown/plugins';

type DiagramNode = MarkdownExtensionNode<
  'diagrams',
  'diagram',
  {readonly code: string; readonly label?: string},
  'block'
>;

const diagrams = createMarkdownPlugin<'diagrams', DiagramNode>({
  name: 'diagrams',
  apiVersion: 1,
  transform: createMarkdownFenceTransform({
    languages: ['mermaid'],
    createNode: ({code, meta}) => ({
      type: 'extension',
      plugin: 'diagrams',
      name: 'diagram',
      display: 'block',
      data: {code, ...(meta == null ? {} : {label: meta})},
    }),
  }),
  renderers: {
    diagram: {
      render: ({node}) => (
        <Diagram source={node.data.code} label={node.data.label} />
      ),
      toText: node => node.data.code,
    },
  },
});

<Markdown plugins={[diagrams]}>
  {'```mermaid Checkout flow\ngraph LR; A-->B\n```'}
</Markdown>;
```

### Source decoration helper

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {
  createMarkdownPlugin,
  createMarkdownSourceDecoration,
  getMarkdownSourceDecorations,
} from '@astryxdesign/core/Markdown/plugins';

// Ranges are UTF-16 offsets into the same string Markdown renders. Every
// block a range touches is annotated; rendering, copyable text, and ids
// never change.
const searchHits = createMarkdownPlugin({
  name: 'search-hits',
  apiVersion: 1,
  transform: createMarkdownSourceDecoration({
    name: 'search-hit',
    ranges: [{start: 0, end: 15, data: {query: 'release'}}],
  }),
});

const readDecorations = createMarkdownPlugin({
  name: 'read-decorations',
  apiVersion: 1,
  transform: root => {
    report(root.children.map(getMarkdownSourceDecorations));
    return root;
  },
});

<Markdown plugins={[searchHits, readDecorations]}>{source}</Markdown>;
```

### Native frontmatter

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {createMarkdownFrontmatter} from '@astryxdesign/core/Markdown/plugins';

const frontmatter = createMarkdownFrontmatter({
  name: 'document-metadata',
  parse: fields => ({
    title: fields.title ?? 'Untitled',
    draft: fields.draft === 'true',
  }),
});

const source = '---\ntitle: Release notes\ndraft: true\n---\n# Shipped';
const result = frontmatter.parse(source);

<Markdown plugins={[frontmatter.plugin]}>{source}</Markdown>;
```

### Compatible Remark plugin

```tsx
import {Markdown} from '@astryxdesign/core/Markdown';
import {createMarkdownPlugin} from '@astryxdesign/core/Markdown/plugins';
import {createMarkdownRemarkTransform} from '@astryxdesign/core/Markdown/remark';

// A synchronous transform-only Remark plugin in the usual attacher shape.
const remarkRename =
  ({from, to}) =>
  tree => {
    const rename = node => {
      if (node.type === 'text') {
        node.value = node.value.split(from).join(to);
      }
      node.children?.forEach(rename);
    };
    rename(tree);
  };

const productName = createMarkdownPlugin({
  name: 'product-name',
  apiVersion: 1,
  transform: createMarkdownRemarkTransform(remarkRename, {
    from: 'Astryx',
    to: 'Astryx Design',
  }),
});

<Markdown plugins={[productName]}># Astryx release notes</Markdown>;
```

### Entity links

```tsx
import {Link} from '@astryxdesign/core/Link';

const entityPlugins = [
  {
    pattern: /\b([A-Z][A-Z0-9]+-\d+)\b/g,
    render: (match, key) => (
      <Link key={key} href={`/entities/${match[1]}`}>
        {match[0]}
      </Link>
    ),
  },
];

<Markdown inlinePlugins={entityPlugins}>
  {'See DOC-2048. Inline code stays plain: `DOC-9999`.'}
</Markdown>;
```

### Math renderer

```tsx
import {BlockMath, InlineMath} from 'react-katex';

function MathExpression({value, display}) {
  const Component = display === 'block' ? BlockMath : InlineMath;
  return <Component math={value} />;
}

<Markdown components={{math: MathExpression}}>
  {'Inline $x_1 + y$ and display math:\n\n$$\n\\sum_i x_i\n$$'}
</Markdown>;
```

### 主题 API

```json
{
  "targets": [
    {
      "className": "astryx-markdown",
      "visualProps": [
        "density"
      ]
    },
    {
      "className": "astryx-markdown-heading",
      "visualProps": [
        "density",
        "level"
      ],
      "description": "每个渲染的标题块（h1–h6）。覆盖 marginBlockStart/marginBlockEnd 可调整标题周围的间距；反映 data-density 和 data-level，因此主题可按密度和标题层级设置间距。仅适用于默认标题渲染——自定义的 components.heading 拥有自己的样式。"
    },
    {
      "className": "astryx-markdown-paragraph",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的段落块。覆盖 marginBlockStart/marginBlockEnd 可调整段落之间的间距。反映 data-density，因此主题可以为不同密度设置不同的间距。"
    },
    {
      "className": "astryx-markdown-list",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的列表块（有序、无序和任务列表）。覆盖 marginBlockStart/marginBlockEnd 可调整列表周围的间距；反映 data-density。"
    },
    {
      "className": "astryx-markdown-codeblock",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的代码块外层容器。覆盖 marginBlockStart/marginBlockEnd 可调整代码块周围的间距；反映 data-density。仅适用于默认渲染——自定义的 components.code 拥有自己的样式。"
    },
    {
      "className": "astryx-markdown-blockquote",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的引用块（与 astryx-blockquote 目标共用同一元素）。覆盖 marginBlockStart/marginBlockEnd 可调整引用块周围的间距；反映 data-density。仅适用于默认渲染——自定义的 components.blockquote 拥有自己的样式。"
    },
    {
      "className": "astryx-markdown-table",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的表格外层容器。覆盖 marginBlockStart/marginBlockEnd 可调整表格周围的间距；反映 data-density。"
    },
    {
      "className": "astryx-markdown-hr",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的水平分隔线。覆盖 marginBlockStart/marginBlockEnd 可调整分隔线周围的间距；反映 data-density。仅适用于默认渲染——自定义的 components.hr 拥有自己的样式。"
    },
    {
      "className": "astryx-markdown-image",
      "visualProps": [
        "density"
      ],
      "description": "每个渲染的块级图片外层容器（以及损坏图片的占位符）。覆盖 marginBlockStart/marginBlockEnd 可调整图片周围的间距；反映 data-density。仅适用于默认渲染——自定义的 components.image 拥有自己的样式。"
    }
  ]
}
```

### 组件结构

- **Document**: Root container for block or inline Markdown content.
- **Heading**: Rendered semantic heading block; a custom heading renderer replaces the built-in part.
- **Paragraph**: Rendered paragraph block; a custom paragraph renderer replaces the default part.
- **List**: Ordered, unordered, or task-list block rendered from Markdown items.
- **Code block**: Fenced code block; long lines wrap rather than scroll sideways. A custom code renderer replaces the default part.
- **Blockquote**: Quoted block; a custom blockquote renderer replaces the default part.
- **Table**: Table block rendered from Markdown rows and columns; its Table child owns horizontal scrolling.
- **Divider**: Horizontal rule block; a custom hr renderer replaces the default part.
- **Image**: Block image or unsafe-URL fallback; a custom image renderer replaces a safe default image.

---

JGuang 全部资源索引: https://jiguang132.com/llms.txt
