@affanhamid/markdown-renderer 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -7
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# @affanhamid/markdown-renderer
|
|
2
2
|
|
|
3
|
-
A React markdown renderer built for AI-generated content.
|
|
3
|
+
> A React markdown renderer built for AI-generated content. One import. Math, code, diagrams, callouts — all handled.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The Problem
|
|
6
6
|
|
|
7
7
|
If you've used `react-markdown` with `remark-math` and `rehype-katex` to render LLM output, you've hit these problems:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
**
|
|
9
|
+
| Problem | Details |
|
|
10
|
+
|---------|---------|
|
|
11
|
+
| **Dollar signs break everything** | `remark-math` uses `$` for math, but `$` is also currency. Their `singleDollarTextMath: false` disables single-dollar math entirely. This package disambiguates intelligently: `$20` → currency, `$x + y$` → math. |
|
|
12
|
+
| **Inconsistent math delimiters** | GPT, Claude, and Gemini variously output `$`, `$$`, `\(…\)`, and `\[…\]`. `remark-math` doesn't support `\(…\)` or `\[…\]`. This package normalizes all four formats automatically. |
|
|
13
|
+
| **Too many moving parts** | The standard setup: `react-markdown` + `remark-math` + `remark-gfm` + `rehype-katex` + KaTeX CSS + syntax highlighter + custom components. This package is **one import**. |
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
@@ -21,6 +21,8 @@ If you've used `react-markdown` with `remark-math` and `rehype-katex` to render
|
|
|
21
21
|
- **Executable code blocks** — optional `onRunCode` callback for running Python, R, etc.
|
|
22
22
|
- **Inline images** — `` works inside paragraphs, not just as standalone blocks
|
|
23
23
|
- **Semantic color tags** — `{color:important}text{/color}` for highlighting (important, definition, example, note, formula)
|
|
24
|
+
- **Callout blocks** — LaTeX-style `\begin{callout}{color}...\end{callout}` with any Tailwind color name
|
|
25
|
+
- **Mermaid diagrams** — fenced `mermaid` code blocks render as diagrams (client-side hydration with dynamic import)
|
|
24
26
|
- **Auto-scaling brackets** — `($x + y$)` automatically uses `\left(` and `\right)` for proper sizing
|
|
25
27
|
- **Prompt appendix** — exported `MATH_MARKDOWN_RULES_APPENDIX` string to append to your LLM system prompt, steering models toward consistent delimiter usage
|
|
26
28
|
|
|
@@ -98,6 +100,36 @@ import { MATH_MARKDOWN_RULES_APPENDIX } from "@affanhamid/markdown-renderer";
|
|
|
98
100
|
const systemPrompt = `You are a helpful assistant.\n\n${MATH_MARKDOWN_RULES_APPENDIX}`;
|
|
99
101
|
```
|
|
100
102
|
|
|
103
|
+
### Callout blocks
|
|
104
|
+
|
|
105
|
+
Use LaTeX-style syntax with any Tailwind color name:
|
|
106
|
+
|
|
107
|
+
```md
|
|
108
|
+
\begin{callout}{amber}
|
|
109
|
+
**Warning:** This operation is irreversible.
|
|
110
|
+
\end{callout}
|
|
111
|
+
|
|
112
|
+
\begin{callout}{green}
|
|
113
|
+
**Tip:** Use `Ctrl+S` to save quickly.
|
|
114
|
+
\end{callout}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Renders as styled callout boxes with `border-{color}-200`, `bg-{color}-50`, and `text-{color}-900` classes.
|
|
118
|
+
|
|
119
|
+
### Mermaid diagrams
|
|
120
|
+
|
|
121
|
+
Fenced code blocks with `mermaid` as the language render as diagrams:
|
|
122
|
+
|
|
123
|
+
````md
|
|
124
|
+
```mermaid
|
|
125
|
+
graph LR
|
|
126
|
+
A[Input] --> B[Process]
|
|
127
|
+
B --> C[Output]
|
|
128
|
+
```
|
|
129
|
+
````
|
|
130
|
+
|
|
131
|
+
On the client, mermaid is dynamically imported and renders after hydration. For server-side rendering (`renderMarkdownToHtml`), a `<pre>` fallback is output inside a `.md-mermaid` container with `data-mermaid-code` for later hydration.
|
|
132
|
+
|
|
101
133
|
## API
|
|
102
134
|
|
|
103
135
|
### `<MarkdownRenderer />` (default export)
|
|
@@ -105,6 +137,7 @@ const systemPrompt = `You are a helpful assistant.\n\n${MATH_MARKDOWN_RULES_APPE
|
|
|
105
137
|
| Prop | Type | Default | Description |
|
|
106
138
|
|------|------|---------|-------------|
|
|
107
139
|
| `markdown` | `string` | required | Markdown content to render |
|
|
140
|
+
| `className` | `string` | `undefined` | CSS class applied to the wrapper `<div>` |
|
|
108
141
|
| `onRunCode` | `(code: string, language: string) => Promise<CodeExecutionResult>` | `undefined` | Callback for executing code blocks |
|
|
109
142
|
| `executableLanguages` | `string[]` | `["python", "r"]` | Languages that get a "Run" button |
|
|
110
143
|
|
|
@@ -130,6 +163,100 @@ Normalizes `\(...\)` to `$...$` and `\[...\]` to `$$...$$`. Converts inline `$$.
|
|
|
130
163
|
|
|
131
164
|
A plain-text string with math formatting rules to append to LLM system prompts.
|
|
132
165
|
|
|
166
|
+
## Theming with Tailwind CSS
|
|
167
|
+
|
|
168
|
+
The `className` prop sets a class on the outer wrapper `<div>`, which you can use as a scoping selector for custom themes.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<MarkdownRenderer markdown={content} className="my-theme" />
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This renders:
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<div class="my-theme">
|
|
178
|
+
<h1>...</h1>
|
|
179
|
+
<p>...</p>
|
|
180
|
+
<div class="md-callout ...">...</div>
|
|
181
|
+
<!-- etc. -->
|
|
182
|
+
</div>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Creating a theme file
|
|
186
|
+
|
|
187
|
+
Create a CSS file (e.g. `markdown-theme.css`) that uses descendant selectors scoped to your theme class. With Tailwind, use `@apply` for utility-based styling:
|
|
188
|
+
|
|
189
|
+
```css
|
|
190
|
+
/* markdown-theme.css */
|
|
191
|
+
|
|
192
|
+
.my-theme h1 {
|
|
193
|
+
@apply text-3xl font-bold mt-8 mb-4 text-gray-900;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.my-theme h2 {
|
|
197
|
+
@apply text-2xl font-semibold mt-6 mb-3 text-gray-800;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.my-theme p {
|
|
201
|
+
@apply text-base leading-7 mb-4 text-gray-700;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.my-theme code {
|
|
205
|
+
@apply bg-gray-100 text-sm px-1.5 py-0.5 rounded font-mono;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.my-theme pre {
|
|
209
|
+
@apply rounded-lg my-4 overflow-x-auto;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.my-theme .md-callout {
|
|
213
|
+
@apply border-l-4 border-blue-400 bg-blue-50 px-4 py-3 my-4 rounded-r-lg;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.my-theme table {
|
|
217
|
+
@apply w-full border-collapse my-4 text-sm;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.my-theme th {
|
|
221
|
+
@apply bg-gray-50 font-semibold text-left px-3 py-2 border border-gray-200;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.my-theme td {
|
|
225
|
+
@apply px-3 py-2 border border-gray-200;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Import the theme file in your app and pass the matching class name:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
import "./markdown-theme.css";
|
|
233
|
+
|
|
234
|
+
<MarkdownRenderer markdown={content} className="my-theme" />
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Semantic CSS classes
|
|
238
|
+
|
|
239
|
+
The renderer outputs these classes that you can target in your theme:
|
|
240
|
+
|
|
241
|
+
| Class | Element |
|
|
242
|
+
|-------|---------|
|
|
243
|
+
| `md-callout` | Callout/admonition blocks (`> [!NOTE]`, etc.) |
|
|
244
|
+
| `md-code-block` | Executable code block wrapper |
|
|
245
|
+
| `md-code-block-header` | Header bar above executable code blocks |
|
|
246
|
+
| `md-code-output` | Code execution output area |
|
|
247
|
+
| `md-code-error` | Code execution error output |
|
|
248
|
+
| `md-mermaid` | Mermaid diagram container |
|
|
249
|
+
| `md-run-btn` | "Run" button on executable code blocks |
|
|
250
|
+
|
|
251
|
+
### Works with server-side rendering
|
|
252
|
+
|
|
253
|
+
`renderMarkdownToHtml()` produces the same HTML structure (wrapped in `<div class="prose max-w-none">`), so the same theme CSS applies to both client and server-rendered output. For server rendering, scope your selectors to the `prose` class or add a wrapper with your theme class:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const html = renderMarkdownToHtml(content);
|
|
257
|
+
const themed = `<div class="my-theme">${html}</div>`;
|
|
258
|
+
```
|
|
259
|
+
|
|
133
260
|
## License
|
|
134
261
|
|
|
135
262
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -922,6 +922,7 @@ function renderMarkdownToHtml(markdown, options) {
|
|
|
922
922
|
}
|
|
923
923
|
var MarkdownRenderer = ({
|
|
924
924
|
markdown,
|
|
925
|
+
className,
|
|
925
926
|
onRunCode,
|
|
926
927
|
executableLanguages = ["python", "r"]
|
|
927
928
|
}) => {
|
|
@@ -1103,7 +1104,7 @@ var MarkdownRenderer = ({
|
|
|
1103
1104
|
alive = false;
|
|
1104
1105
|
};
|
|
1105
1106
|
}, [html]);
|
|
1106
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
|
|
1107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, dangerouslySetInnerHTML: { __html: html } });
|
|
1107
1108
|
};
|
|
1108
1109
|
var markdown_renderer_default = MarkdownRenderer;
|
|
1109
1110
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.d.cts
CHANGED
|
@@ -7,13 +7,14 @@ interface CodeExecutionResult {
|
|
|
7
7
|
}
|
|
8
8
|
interface MarkdownRendererProps {
|
|
9
9
|
markdown: string;
|
|
10
|
+
className?: string;
|
|
10
11
|
onRunCode?: (code: string, language: string) => Promise<CodeExecutionResult>;
|
|
11
12
|
executableLanguages?: string[];
|
|
12
13
|
}
|
|
13
14
|
declare function renderMarkdownToHtml(markdown: string, options?: {
|
|
14
15
|
executableLanguages?: string[];
|
|
15
16
|
}): string;
|
|
16
|
-
declare const MarkdownRenderer: ({ markdown, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare const MarkdownRenderer: ({ markdown, className, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
|
|
17
18
|
|
|
18
19
|
declare function normalizeMathMarkdownDelimiters(markdown: string): string;
|
|
19
20
|
declare const MATH_MARKDOWN_RULES_APPENDIX = "Math formatting rules (must follow):\n- Inline math: use single-dollar delimiters like $...$.\n- Display math: use $$ delimiters on their own lines with nothing else on those lines.\n- Do not use \\(...\\) or \\[...\\] delimiters.\n- Do not place display-math $$...$$ inside bullets or table cells; use inline $...$ there.\n- Escape non-math currency dollars as \\$.";
|
package/dist/index.d.ts
CHANGED
|
@@ -7,13 +7,14 @@ interface CodeExecutionResult {
|
|
|
7
7
|
}
|
|
8
8
|
interface MarkdownRendererProps {
|
|
9
9
|
markdown: string;
|
|
10
|
+
className?: string;
|
|
10
11
|
onRunCode?: (code: string, language: string) => Promise<CodeExecutionResult>;
|
|
11
12
|
executableLanguages?: string[];
|
|
12
13
|
}
|
|
13
14
|
declare function renderMarkdownToHtml(markdown: string, options?: {
|
|
14
15
|
executableLanguages?: string[];
|
|
15
16
|
}): string;
|
|
16
|
-
declare const MarkdownRenderer: ({ markdown, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare const MarkdownRenderer: ({ markdown, className, onRunCode, executableLanguages, }: MarkdownRendererProps) => react_jsx_runtime.JSX.Element;
|
|
17
18
|
|
|
18
19
|
declare function normalizeMathMarkdownDelimiters(markdown: string): string;
|
|
19
20
|
declare const MATH_MARKDOWN_RULES_APPENDIX = "Math formatting rules (must follow):\n- Inline math: use single-dollar delimiters like $...$.\n- Display math: use $$ delimiters on their own lines with nothing else on those lines.\n- Do not use \\(...\\) or \\[...\\] delimiters.\n- Do not place display-math $$...$$ inside bullets or table cells; use inline $...$ there.\n- Escape non-math currency dollars as \\$.";
|
package/dist/index.js
CHANGED
|
@@ -883,6 +883,7 @@ function renderMarkdownToHtml(markdown, options) {
|
|
|
883
883
|
}
|
|
884
884
|
var MarkdownRenderer = ({
|
|
885
885
|
markdown,
|
|
886
|
+
className,
|
|
886
887
|
onRunCode,
|
|
887
888
|
executableLanguages = ["python", "r"]
|
|
888
889
|
}) => {
|
|
@@ -1064,7 +1065,7 @@ var MarkdownRenderer = ({
|
|
|
1064
1065
|
alive = false;
|
|
1065
1066
|
};
|
|
1066
1067
|
}, [html]);
|
|
1067
|
-
return /* @__PURE__ */ jsx("div", { ref: containerRef, dangerouslySetInnerHTML: { __html: html } });
|
|
1068
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, dangerouslySetInnerHTML: { __html: html } });
|
|
1068
1069
|
};
|
|
1069
1070
|
var markdown_renderer_default = MarkdownRenderer;
|
|
1070
1071
|
export {
|