ink-markdown-es 1.2.0 → 1.4.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** biome-ignore-all lint/suspicious/noArrayIndexKey: <empty> */
2
2
  import { type Token, type Tokens } from 'marked';
3
3
  import type { MarkdownProps } from './types';
4
- declare function MarkdownComponent({ children, id, styles, renderers, showSharp, highlight, theme, }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
4
+ declare function MarkdownComponent({ children, id, styles, renderers, showSharp, }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
5
5
  declare const Markdown: import("react").MemoExoticComponent<typeof MarkdownComponent>;
6
6
  export default Markdown;
7
7
  export type { Token, Tokens };
package/dist/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { marked } from "marked";
4
- import { memo, useEffect, useId, useMemo, useState } from "react";
5
- import { codeToTokens } from "shiki";
4
+ import { memo, useId, useMemo } from "react";
6
5
  const DEFAULT_STYLES = {
7
6
  h1: {
8
7
  bold: true,
@@ -42,7 +41,8 @@ const DEFAULT_STYLES = {
42
41
  borderDimColor: true
43
42
  },
44
43
  code: {
45
- marginTop: 1
44
+ marginTop: 1,
45
+ paddingX: 2
46
46
  },
47
47
  codespan: {
48
48
  dimColor: true
@@ -121,119 +121,6 @@ const BOX_STYLE_KEYS = [
121
121
  'borderLeftDimColor',
122
122
  'borderRightDimColor'
123
123
  ];
124
- const TERMINAL_COLORS = {
125
- comment: 'gray',
126
- 'comment.block': 'gray',
127
- 'comment.line': 'gray',
128
- keyword: 'magenta',
129
- 'keyword.control': 'magenta',
130
- 'keyword.other': 'magenta',
131
- string: 'green',
132
- 'string.quoted': 'green',
133
- 'string.regexp': 'red',
134
- 'entity.name.function': 'blue',
135
- 'support.function': 'blueBright',
136
- 'meta.function-call': 'blueBright',
137
- variable: 'white',
138
- 'variable.parameter': 'yellowBright',
139
- 'variable.other': 'white',
140
- 'entity.name.type': 'cyan',
141
- 'entity.name.class': 'yellowBright',
142
- 'support.type': 'cyanBright',
143
- 'support.class': 'yellowBright',
144
- 'constant.numeric': 'yellow',
145
- 'constant.language': 'yellow',
146
- operator: 'cyan',
147
- punctuation: 'white',
148
- 'variable.other.property': 'blue',
149
- 'entity.other.attribute-name': 'blue',
150
- 'entity.name.tag': 'red',
151
- 'meta.tag': 'red',
152
- 'constant.language.boolean': 'yellow',
153
- 'constant.language.null': 'yellow',
154
- meta: 'gray',
155
- storage: 'magenta',
156
- 'storage.type': 'magenta'
157
- };
158
- function hexToTerminalColor(hex) {
159
- const colorMap = {
160
- '#6a737d': 'gray',
161
- '#8b949e': 'gray',
162
- '#6e7681': 'gray',
163
- '#d73a49': 'magenta',
164
- '#f97583': 'magenta',
165
- '#ff7b72': 'red',
166
- '#032f62': 'green',
167
- '#22863a': 'green',
168
- '#7ee787': 'green',
169
- '#005cc5': 'blue',
170
- '#0366d6': 'blue',
171
- '#79c0ff': 'cyan',
172
- '#58a6ff': 'blueBright',
173
- '#a5d6ff': 'cyan',
174
- '#e36209': 'yellow',
175
- '#d29922': 'yellow',
176
- '#ffa657': 'yellow',
177
- '#0550ae': 'cyan'
178
- };
179
- const normalized = hex.toLowerCase();
180
- if (colorMap[normalized]) return colorMap[normalized];
181
- const r = Number.parseInt(normalized.slice(1, 3), 16);
182
- const g = Number.parseInt(normalized.slice(3, 5), 16);
183
- const b = Number.parseInt(normalized.slice(5, 7), 16);
184
- const brightness = (299 * r + 587 * g + 114 * b) / 1000;
185
- if (brightness < 80) return 'gray';
186
- if (r > g && r > b) return 'red';
187
- if (g > r && g > b) return 'green';
188
- if (b > r && b > g) return 'blue';
189
- if (r > 150 && g > 150) return 'yellow';
190
- if (g > 150 && b > 150) return 'cyan';
191
- if (r > 150 && b > 150) return 'magenta';
192
- }
193
- function tokenScopeToColor(scopes) {
194
- for (const scope of scopes){
195
- if (TERMINAL_COLORS[scope]) return TERMINAL_COLORS[scope];
196
- const parts = scope.split('.');
197
- for(let i = parts.length; i > 0; i--){
198
- const key = parts.slice(0, i).join('.');
199
- if (TERMINAL_COLORS[key]) return TERMINAL_COLORS[key];
200
- }
201
- }
202
- }
203
- async function highlightCodeAsync(code, language, theme = 'github-dark') {
204
- try {
205
- const result = await codeToTokens(code, {
206
- lang: language || 'text',
207
- theme: theme
208
- });
209
- const nodes = [];
210
- let index = 0;
211
- for(let lineIndex = 0; lineIndex < result.tokens.length; lineIndex++){
212
- const line = result.tokens[lineIndex];
213
- if (line) {
214
- for (const token of line){
215
- let color;
216
- if (token.color) color = hexToTerminalColor(token.color);
217
- if (!color && token.explanation) {
218
- const scopes = token.explanation.map((e)=>e.scopes?.[0]?.scopeName || '');
219
- color = tokenScopeToColor(scopes);
220
- }
221
- nodes.push(/*#__PURE__*/ jsx(Text, {
222
- color: color,
223
- children: token.content
224
- }, `token-${index}`));
225
- index++;
226
- }
227
- if (lineIndex < result.tokens.length - 1) nodes.push(/*#__PURE__*/ jsx(Text, {
228
- children: '\n'
229
- }, `nl-${lineIndex}`));
230
- }
231
- }
232
- return nodes;
233
- } catch {
234
- return null;
235
- }
236
- }
237
124
  function extractTextProps(style) {
238
125
  if (!style) return {};
239
126
  const result = {};
@@ -255,51 +142,6 @@ function mergeStyles(defaultStyle, userStyle) {
255
142
  ...userStyle
256
143
  };
257
144
  }
258
- function CodeBlock({ code, language, style, theme }) {
259
- const [highlightedCode, setHighlightedCode] = useState(null);
260
- const [isLoading, setIsLoading] = useState(true);
261
- useEffect(()=>{
262
- let cancelled = false;
263
- async function highlight() {
264
- try {
265
- const result = await highlightCodeAsync(code, language, theme);
266
- if (!cancelled) {
267
- setHighlightedCode(result);
268
- setIsLoading(false);
269
- }
270
- } catch {
271
- if (!cancelled) {
272
- setHighlightedCode(null);
273
- setIsLoading(false);
274
- }
275
- }
276
- }
277
- highlight();
278
- return ()=>{
279
- cancelled = true;
280
- };
281
- }, [
282
- code,
283
- language,
284
- theme
285
- ]);
286
- if (isLoading || !highlightedCode) return /*#__PURE__*/ jsx(Box, {
287
- ...extractBoxProps(style),
288
- children: /*#__PURE__*/ jsx(Text, {
289
- ...extractTextProps(style),
290
- children: code
291
- })
292
- });
293
- return /*#__PURE__*/ jsx(Box, {
294
- paddingX: 2,
295
- paddingY: 1,
296
- ...extractBoxProps(style),
297
- children: /*#__PURE__*/ jsx(Text, {
298
- ...extractTextProps(style),
299
- children: highlightedCode
300
- })
301
- });
302
- }
303
145
  function renderInlineTokens(tokens, styles, renderers) {
304
146
  if (!tokens || 0 === tokens.length) return null;
305
147
  return tokens.map((token, index)=>{
@@ -407,7 +249,7 @@ function renderInlineTokens(tokens, styles, renderers) {
407
249
  }
408
250
  });
409
251
  }
410
- function renderBlockToken(token, styles, renderers, showSharp, highlight, theme = 'github-dark') {
252
+ function renderBlockToken(token, styles, renderers, showSharp) {
411
253
  switch(token.type){
412
254
  case 'heading':
413
255
  {
@@ -448,12 +290,6 @@ function renderBlockToken(token, styles, renderers, showSharp, highlight, theme
448
290
  const codeToken = token;
449
291
  const codeStyle = mergeStyles(DEFAULT_STYLES.code, styles.code);
450
292
  if (renderers.code) return renderers.code(codeToken.text, codeToken.lang, codeToken);
451
- if (highlight) return /*#__PURE__*/ jsx(CodeBlock, {
452
- code: codeToken.text,
453
- language: codeToken.lang,
454
- style: codeStyle,
455
- theme: theme
456
- });
457
293
  return /*#__PURE__*/ jsx(Box, {
458
294
  ...extractBoxProps(codeStyle),
459
295
  children: /*#__PURE__*/ jsx(Text, {
@@ -466,22 +302,20 @@ function renderBlockToken(token, styles, renderers, showSharp, highlight, theme
466
302
  {
467
303
  const blockquoteToken = token;
468
304
  const blockquoteStyle = mergeStyles(DEFAULT_STYLES.blockquote, styles.blockquote);
469
- const content = blockquoteToken.tokens.map((t, i)=>/*#__PURE__*/ jsx(Box, {
470
- children: renderBlockToken(t, styles, renderers, showSharp, highlight, theme)
471
- }, `bq-${i}`));
472
- if (renderers.blockquote) return renderers.blockquote(content, blockquoteToken);
305
+ if (renderers.blockquote) {
306
+ const content = blockquoteToken.tokens.map((t, i)=>/*#__PURE__*/ jsx(Box, {
307
+ children: renderBlockToken(t, styles, renderers, showSharp)
308
+ }, `bq-${i}`));
309
+ return renderers.blockquote(content, blockquoteToken);
310
+ }
473
311
  return /*#__PURE__*/ jsx(Box, {
474
312
  ...extractBoxProps(blockquoteStyle),
475
313
  children: /*#__PURE__*/ jsx(Box, {
476
314
  flexDirection: "column",
477
- children: blockquoteToken.tokens.map((t, i)=>{
478
- const rendered = renderBlockToken(t, styles, renderers, showSharp, highlight, theme);
479
- if (rendered) return /*#__PURE__*/ jsx(Text, {
315
+ children: blockquoteToken.tokens.map((t, i)=>/*#__PURE__*/ jsx(Text, {
480
316
  ...extractTextProps(blockquoteStyle),
481
317
  children: 'paragraph' === t.type ? renderInlineTokens(t.tokens, styles, renderers) : t.text || ''
482
- }, `bq-text-${i}`);
483
- return null;
484
- })
318
+ }, `bq-text-${i}`))
485
319
  })
486
320
  });
487
321
  }
@@ -579,7 +413,8 @@ function renderBlockToken(token, styles, renderers, showSharp, highlight, theme
579
413
  /*#__PURE__*/ jsxs(Text, {
580
414
  dimColor: true,
581
415
  children: [
582
- " │",
416
+ ' ',
417
+ "│",
583
418
  i < tableToken.header.length - 1 ? ' ' : ''
584
419
  ]
585
420
  })
@@ -670,13 +505,13 @@ function renderBlockToken(token, styles, renderers, showSharp, highlight, theme
670
505
  return null;
671
506
  }
672
507
  }
673
- const src_MemoizedBlock = /*#__PURE__*/ memo(function({ token, styles, renderers, showSharp, highlight, theme }) {
508
+ const src_MemoizedBlock = /*#__PURE__*/ memo(function({ token, styles, renderers, showSharp }) {
674
509
  return /*#__PURE__*/ jsx(Fragment, {
675
- children: renderBlockToken(token, styles, renderers, showSharp, highlight, theme)
510
+ children: renderBlockToken(token, styles, renderers, showSharp)
676
511
  });
677
512
  }, (prevProps, nextProps)=>prevProps.token === nextProps.token);
678
513
  src_MemoizedBlock.displayName = 'MemoizedBlock';
679
- function MarkdownComponent({ children, id, styles = {}, renderers = {}, showSharp = false, highlight = true, theme = 'github-dark' }) {
514
+ function MarkdownComponent({ children, id, styles = {}, renderers = {}, showSharp = false }) {
680
515
  const generatedId = useId();
681
516
  const key = id || generatedId;
682
517
  const tokens = useMemo(()=>marked.lexer(children, {
@@ -691,9 +526,7 @@ function MarkdownComponent({ children, id, styles = {}, renderers = {}, showShar
691
526
  token: token,
692
527
  styles: styles,
693
528
  renderers: renderers,
694
- showSharp: showSharp,
695
- highlight: highlight,
696
- theme: theme
529
+ showSharp: showSharp
697
530
  }, `${key}-block-${index}`))
698
531
  });
699
532
  }
package/dist/types.d.ts CHANGED
@@ -62,24 +62,10 @@ export type MarkdownProps = {
62
62
  styles?: BlockStyles;
63
63
  renderers?: BlockRenderers;
64
64
  showSharp?: boolean;
65
- /**
66
- * Enable syntax highlighting for code blocks
67
- * @default true
68
- */
69
- highlight?: boolean;
70
- /**
71
- * Shiki theme name for syntax highlighting
72
- * @default 'github-dark'
73
- * @example 'github-dark', 'github-light', 'nord', 'dracula', etc.
74
- * @see https://shiki.style/themes
75
- */
76
- theme?: string;
77
65
  };
78
66
  export type MemoizedBlockProps = {
79
67
  token: Token;
80
68
  styles: BlockStyles;
81
69
  renderers: BlockRenderers;
82
70
  showSharp: boolean;
83
- highlight: boolean;
84
- theme: string;
85
71
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ink-markdown-es",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "A modern performance markdown renderer for ink",
5
5
  "keywords": [
6
6
  "markdown",
@@ -28,9 +28,9 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "build": "rslib build",
31
- "check": "biome check --write",
31
+ "check": "biome check --write .",
32
32
  "dev": "bun --watch run examples/index.tsx",
33
- "format": "biome format --write",
33
+ "format": "biome format --write .",
34
34
  "prepublishOnly": "bun run build"
35
35
  },
36
36
  "devDependencies": {
@@ -40,6 +40,7 @@
40
40
  "@types/react": "^19",
41
41
  "@biomejs/biome": "2.3.8",
42
42
  "ink": "^6",
43
+ "ink-shiki-code": "workspace:*",
43
44
  "react": "^19",
44
45
  "react-devtools-core": "^6",
45
46
  "typescript": "^5.9.3",
@@ -51,7 +52,6 @@
51
52
  },
52
53
  "dependencies": {
53
54
  "dedent": "^1.7.1",
54
- "marked": "^16.4.2",
55
- "shiki": "^3.22.0"
55
+ "marked": "^16.4.2"
56
56
  }
57
57
  }
package/README.md DELETED
@@ -1,123 +0,0 @@
1
- # ink-markdown-es
2
-
3
- A modern performance markdown renderer for [ink](https://github.com/vadimdemedes/ink) using [marked](https://github.com/markedjs/marked).
4
-
5
- Inspired by [ink-markdown](https://github.com/vadimdemedes/ink-markdown) and [prompt-kit](https://github.com/ibelick/prompt-kit).
6
-
7
- Compare with [ink-markdown](https://github.com/vadimdemedes/ink-markdown):
8
-
9
- - **ES module** support & only
10
- - Use memo & useMemo to improve performance
11
- - More flexible configuration (`renderers` prop)
12
-
13
- ## Quick Start
14
-
15
- ```bash
16
- npm install ink-markdown-es # npm
17
-
18
- pnpm add ink-markdown-es # pnpm
19
-
20
- bun add ink-markdown-es # bun
21
- ```
22
-
23
- ```tsx
24
- const text = `# Hello World
25
-
26
- This is a show case.
27
- It's very fast!
28
-
29
- ## Features
30
- - Render markdown in ink
31
- - Support custom renderers
32
- - **Bold text** and *italic text*
33
- - Inline \`code\` support
34
- - **Syntax highlighting** for code blocks powered by highlight.js
35
-
36
- ### Code Block with Syntax Highlighting
37
-
38
- \`\`\`typescript
39
- interface User {
40
- id: number;
41
- name: string;
42
- email: string;
43
- }
44
-
45
- const user: User = {
46
- id: 1,
47
- name: "Alice",
48
- email: "alice@example.com"
49
- };
50
-
51
- async function fetchUser(id: number): Promise<User> {
52
- const response = await fetch(\`/api/users/\${id}\`);
53
- return response.json();
54
- }
55
- \`\`\`
56
-
57
- > This is a blockquote
58
- > with multiple lines
59
-
60
- ---
61
-
62
- Check out [this link](https://example.com) for more info.
63
-
64
- 1. First item
65
- 2. Second item
66
- 3. Third item
67
-
68
- | Name | Age |
69
- |------|-----|
70
- | Alice | 25 |
71
- | Bob | 30 |
72
- `;
73
-
74
- const TestApp = () => {
75
- useInput(() => {});
76
-
77
- return (
78
- <Markdown
79
- showSharp
80
- theme="dracula"
81
- renderers={{
82
- h1: (text) => (
83
- <Box padding={1} borderStyle="round" borderDimColor>
84
- <Text bold color="greenBright">
85
- {text}
86
- </Text>
87
- </Box>
88
- ),
89
- }}
90
- >
91
- {text}
92
- </Markdown>
93
- );
94
- };
95
-
96
- render(<TestApp />);
97
-
98
- ```
99
-
100
- <img width="1904" height="964" alt="image" src="https://github.com/user-attachments/assets/bed0c942-6d08-4f24-a42a-4999bdf1fc85" />
101
-
102
- ## Props
103
-
104
- - `children` (string): The markdown content to render.
105
- - `id` (string, optional): A unique identifier for the component. In AI scene, it's useful to identify the component in the DOM tree, you can use AI message id.
106
- - `styles` (BlockStyles, optional): Custom styles for markdown blocks.
107
- - `renderers` (BlockRenderers, optional): Custom renderers for markdown blocks.
108
- - `showSharp` (boolean, optional): Whether to show sharp signs for headings. Default is `false`.
109
- - `theme` (string, optional): The theme for syntax highlighting. Default is `github-dark`. Check out [shiki](https://shiki.style/themes) for more themes.
110
-
111
- ## Contributing
112
-
113
- To install dependencies:
114
-
115
- ```bash
116
- bun install
117
- ```
118
-
119
- To run:
120
-
121
- ```bash
122
- bun run dev
123
- ```
@@ -1,2 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- export declare function highlightCodeAsync(code: string, language?: string, theme?: string): Promise<ReactNode[] | null>;