ink-markdown-es 1.0.2 → 1.1.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 +45 -26
- package/dist/highlight.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +121 -12
- package/dist/types.d.ts +8 -2
- package/dist/utils.d.ts +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -21,9 +21,6 @@ bun add ink-markdown-es # bun
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```tsx
|
|
24
|
-
import Markdown from "ink-markdown-es";
|
|
25
|
-
import { render } from "ink";
|
|
26
|
-
|
|
27
24
|
const text = `# Hello World
|
|
28
25
|
|
|
29
26
|
This is a show case.
|
|
@@ -34,12 +31,27 @@ It's very fast!
|
|
|
34
31
|
- Support custom renderers
|
|
35
32
|
- **Bold text** and *italic text*
|
|
36
33
|
- Inline \`code\` support
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
}
|
|
43
55
|
\`\`\`
|
|
44
56
|
|
|
45
57
|
> This is a blockquote
|
|
@@ -59,25 +71,32 @@ Check out [this link](https://example.com) for more info.
|
|
|
59
71
|
| Bob | 30 |
|
|
60
72
|
`;
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
const TestApp = () => {
|
|
75
|
+
useInput(() => {});
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Markdown
|
|
79
|
+
showSharp
|
|
80
|
+
renderers={{
|
|
81
|
+
h1: (text) => (
|
|
82
|
+
<Box padding={1} borderStyle="round" borderDimColor>
|
|
83
|
+
<Text bold color="greenBright">
|
|
84
|
+
{text}
|
|
85
|
+
</Text>
|
|
86
|
+
</Box>
|
|
87
|
+
),
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{text}
|
|
91
|
+
</Markdown>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
render(<TestApp />);
|
|
96
|
+
|
|
78
97
|
```
|
|
79
98
|
|
|
80
|
-
<img width="
|
|
99
|
+
<img width="1904" height="964" alt="image" src="https://github.com/user-attachments/assets/bed0c942-6d08-4f24-a42a-4999bdf1fc85" />
|
|
81
100
|
|
|
82
101
|
## Props
|
|
83
102
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noArrayIndexKey: <empty> */
|
|
1
2
|
import { type Token, type Tokens } from 'marked';
|
|
2
3
|
import type { MarkdownProps } from './types';
|
|
3
|
-
declare function MarkdownComponent({ children, id, styles, renderers, showSharp, }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function MarkdownComponent({ children, id, styles, renderers, showSharp, highlight, }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
|
|
4
5
|
declare const Markdown: import("react").MemoExoticComponent<typeof MarkdownComponent>;
|
|
5
6
|
export default Markdown;
|
|
6
7
|
export type { Token, Tokens };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { memo, useId, useMemo } from "react";
|
|
3
|
-
import { marked } from "marked";
|
|
4
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import { memo, useId, useMemo } from "react";
|
|
5
|
+
import highlight_0 from "highlight.js";
|
|
5
6
|
const DEFAULT_STYLES = {
|
|
6
7
|
h1: {
|
|
7
8
|
bold: true,
|
|
@@ -120,6 +121,102 @@ const BOX_STYLE_KEYS = [
|
|
|
120
121
|
'borderLeftDimColor',
|
|
121
122
|
'borderRightDimColor'
|
|
122
123
|
];
|
|
124
|
+
const TERMINAL_COLORS = {
|
|
125
|
+
keyword: 'magenta',
|
|
126
|
+
'keyword.control': 'magenta',
|
|
127
|
+
'keyword.operator': 'cyan',
|
|
128
|
+
string: 'green',
|
|
129
|
+
'string.special': 'greenBright',
|
|
130
|
+
comment: 'gray',
|
|
131
|
+
function: 'blue',
|
|
132
|
+
'function.call': 'blueBright',
|
|
133
|
+
'function.builtin': 'blueBright',
|
|
134
|
+
variable: 'white',
|
|
135
|
+
'variable.builtin': 'yellow',
|
|
136
|
+
'variable.parameter': 'yellowBright',
|
|
137
|
+
type: 'cyan',
|
|
138
|
+
'type.builtin': 'cyanBright',
|
|
139
|
+
class: 'yellowBright',
|
|
140
|
+
number: 'yellow',
|
|
141
|
+
'number.float': 'yellow',
|
|
142
|
+
constant: 'yellow',
|
|
143
|
+
'constant.builtin': 'yellowBright',
|
|
144
|
+
operator: 'cyan',
|
|
145
|
+
punctuation: 'white',
|
|
146
|
+
property: 'blue',
|
|
147
|
+
attribute: 'blue',
|
|
148
|
+
tag: 'red',
|
|
149
|
+
'tag.builtin': 'redBright',
|
|
150
|
+
boolean: 'yellow',
|
|
151
|
+
null: 'yellow',
|
|
152
|
+
regexp: 'red',
|
|
153
|
+
meta: 'gray',
|
|
154
|
+
title: 'yellowBright',
|
|
155
|
+
section: 'yellowBright'
|
|
156
|
+
};
|
|
157
|
+
function classToColor(className) {
|
|
158
|
+
if (TERMINAL_COLORS[className]) return TERMINAL_COLORS[className];
|
|
159
|
+
const parts = className.replace(/^hljs-/, '').split('.');
|
|
160
|
+
for(let i = parts.length; i > 0; i--){
|
|
161
|
+
const key = parts.slice(0, i).join('.');
|
|
162
|
+
if (TERMINAL_COLORS[key]) return TERMINAL_COLORS[key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function decodeHtmlEntities(text) {
|
|
166
|
+
const entities = {
|
|
167
|
+
'"': '"',
|
|
168
|
+
'"': '"',
|
|
169
|
+
''': "'",
|
|
170
|
+
''': "'",
|
|
171
|
+
'<': '<',
|
|
172
|
+
'<': '<',
|
|
173
|
+
'>': '>',
|
|
174
|
+
'>': '>',
|
|
175
|
+
'&': '&',
|
|
176
|
+
'&': '&',
|
|
177
|
+
' ': ' ',
|
|
178
|
+
' ': ' '
|
|
179
|
+
};
|
|
180
|
+
return text.replace(/&[a-z]+;|&#\d+;/gi, (match)=>entities[match] || match);
|
|
181
|
+
}
|
|
182
|
+
function parseHighlightedCode(html) {
|
|
183
|
+
const result = [];
|
|
184
|
+
const regex = /<span class="([^"]+)">([^<]*)<\/span>|([^<]+)/g;
|
|
185
|
+
let index = 0;
|
|
186
|
+
let match = regex.exec(html);
|
|
187
|
+
while(null !== match){
|
|
188
|
+
if (match[1] && void 0 !== match[2]) {
|
|
189
|
+
const className = match[1];
|
|
190
|
+
const text = decodeHtmlEntities(match[2]);
|
|
191
|
+
const color = classToColor(className);
|
|
192
|
+
result.push(/*#__PURE__*/ jsx(Text, {
|
|
193
|
+
color: color,
|
|
194
|
+
children: text
|
|
195
|
+
}, index));
|
|
196
|
+
} else if (match[3]) result.push(/*#__PURE__*/ jsx(Text, {
|
|
197
|
+
children: decodeHtmlEntities(match[3])
|
|
198
|
+
}, index));
|
|
199
|
+
index++;
|
|
200
|
+
match = regex.exec(html);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function highlightCode(code, language) {
|
|
205
|
+
try {
|
|
206
|
+
let highlighted;
|
|
207
|
+
if (language) try {
|
|
208
|
+
highlighted = highlight_0.highlight(code, {
|
|
209
|
+
language
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
highlighted = highlight_0.highlightAuto(code);
|
|
213
|
+
}
|
|
214
|
+
else highlighted = highlight_0.highlightAuto(code);
|
|
215
|
+
return parseHighlightedCode(highlighted.value);
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
123
220
|
function extractTextProps(style) {
|
|
124
221
|
if (!style) return {};
|
|
125
222
|
const result = {};
|
|
@@ -248,7 +345,7 @@ function renderInlineTokens(tokens, styles, renderers) {
|
|
|
248
345
|
}
|
|
249
346
|
});
|
|
250
347
|
}
|
|
251
|
-
function renderBlockToken(token, styles, renderers, showSharp) {
|
|
348
|
+
function renderBlockToken(token, styles, renderers, showSharp, highlight) {
|
|
252
349
|
switch(token.type){
|
|
253
350
|
case 'heading':
|
|
254
351
|
{
|
|
@@ -289,6 +386,19 @@ function renderBlockToken(token, styles, renderers, showSharp) {
|
|
|
289
386
|
const codeToken = token;
|
|
290
387
|
const codeStyle = mergeStyles(DEFAULT_STYLES.code, styles.code);
|
|
291
388
|
if (renderers.code) return renderers.code(codeToken.text, codeToken.lang, codeToken);
|
|
389
|
+
if (highlight) {
|
|
390
|
+
const highlightedCode = highlightCode(codeToken.text, codeToken.lang);
|
|
391
|
+
if (highlightedCode) return /*#__PURE__*/ jsx(Box, {
|
|
392
|
+
backgroundColor: "gray",
|
|
393
|
+
paddingX: 2,
|
|
394
|
+
paddingY: 1,
|
|
395
|
+
...extractBoxProps(codeStyle),
|
|
396
|
+
children: /*#__PURE__*/ jsx(Text, {
|
|
397
|
+
...extractTextProps(codeStyle),
|
|
398
|
+
children: highlightedCode
|
|
399
|
+
})
|
|
400
|
+
});
|
|
401
|
+
}
|
|
292
402
|
return /*#__PURE__*/ jsx(Box, {
|
|
293
403
|
...extractBoxProps(codeStyle),
|
|
294
404
|
children: /*#__PURE__*/ jsx(Text, {
|
|
@@ -302,17 +412,15 @@ function renderBlockToken(token, styles, renderers, showSharp) {
|
|
|
302
412
|
const blockquoteToken = token;
|
|
303
413
|
const blockquoteStyle = mergeStyles(DEFAULT_STYLES.blockquote, styles.blockquote);
|
|
304
414
|
const content = blockquoteToken.tokens.map((t, i)=>/*#__PURE__*/ jsx(Box, {
|
|
305
|
-
children: renderBlockToken(t, styles, renderers)
|
|
415
|
+
children: renderBlockToken(t, styles, renderers, showSharp, highlight)
|
|
306
416
|
}, `bq-${i}`));
|
|
307
|
-
if (renderers.blockquote) return renderers.blockquote(
|
|
308
|
-
children: content
|
|
309
|
-
}), blockquoteToken);
|
|
417
|
+
if (renderers.blockquote) return renderers.blockquote(content, blockquoteToken);
|
|
310
418
|
return /*#__PURE__*/ jsx(Box, {
|
|
311
419
|
...extractBoxProps(blockquoteStyle),
|
|
312
420
|
children: /*#__PURE__*/ jsx(Box, {
|
|
313
421
|
flexDirection: "column",
|
|
314
422
|
children: blockquoteToken.tokens.map((t, i)=>{
|
|
315
|
-
const rendered = renderBlockToken(t, styles, renderers);
|
|
423
|
+
const rendered = renderBlockToken(t, styles, renderers, showSharp, highlight);
|
|
316
424
|
if (rendered) return /*#__PURE__*/ jsx(Text, {
|
|
317
425
|
...extractTextProps(blockquoteStyle),
|
|
318
426
|
children: 'paragraph' === t.type ? renderInlineTokens(t.tokens, styles, renderers) : t.text || ''
|
|
@@ -428,13 +536,13 @@ function renderBlockToken(token, styles, renderers, showSharp) {
|
|
|
428
536
|
return null;
|
|
429
537
|
}
|
|
430
538
|
}
|
|
431
|
-
const src_MemoizedBlock = /*#__PURE__*/ memo(function({ token, styles, renderers, showSharp }) {
|
|
539
|
+
const src_MemoizedBlock = /*#__PURE__*/ memo(function({ token, styles, renderers, showSharp, highlight }) {
|
|
432
540
|
return /*#__PURE__*/ jsx(Fragment, {
|
|
433
|
-
children: renderBlockToken(token, styles, renderers, showSharp)
|
|
541
|
+
children: renderBlockToken(token, styles, renderers, showSharp, highlight)
|
|
434
542
|
});
|
|
435
543
|
}, (prevProps, nextProps)=>prevProps.token === nextProps.token);
|
|
436
544
|
src_MemoizedBlock.displayName = 'MemoizedBlock';
|
|
437
|
-
function MarkdownComponent({ children, id, styles = {}, renderers = {}, showSharp = false }) {
|
|
545
|
+
function MarkdownComponent({ children, id, styles = {}, renderers = {}, showSharp = false, highlight = true }) {
|
|
438
546
|
const generatedId = useId();
|
|
439
547
|
const key = id || generatedId;
|
|
440
548
|
const tokens = useMemo(()=>marked.lexer(children), [
|
|
@@ -446,7 +554,8 @@ function MarkdownComponent({ children, id, styles = {}, renderers = {}, showShar
|
|
|
446
554
|
token: token,
|
|
447
555
|
styles: styles,
|
|
448
556
|
renderers: renderers,
|
|
449
|
-
showSharp: showSharp
|
|
557
|
+
showSharp: showSharp,
|
|
558
|
+
highlight: highlight
|
|
450
559
|
}, `${key}-block-${index}`))
|
|
451
560
|
});
|
|
452
561
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Box, Text } from 'ink';
|
|
2
2
|
import type { Token, Tokens } from 'marked';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
4
4
|
export type TextStyleProps = Pick<ComponentProps<typeof Text>, 'color' | 'backgroundColor' | 'dimColor' | 'bold' | 'italic' | 'underline' | 'strikethrough' | 'inverse' | 'wrap'>;
|
|
5
5
|
export type BoxStyleProps = ComponentProps<typeof Box>;
|
|
6
6
|
export type HeadingStyleProps = TextStyleProps & BoxStyleProps & {
|
|
@@ -62,10 +62,16 @@ 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;
|
|
65
70
|
};
|
|
66
71
|
export type MemoizedBlockProps = {
|
|
67
72
|
token: Token;
|
|
68
73
|
styles: BlockStyles;
|
|
69
74
|
renderers: BlockRenderers;
|
|
70
75
|
showSharp: boolean;
|
|
76
|
+
highlight: boolean;
|
|
71
77
|
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { BoxStyleProps, TextStyleProps } from './types';
|
|
2
2
|
export declare function extractTextProps(style: (TextStyleProps & BoxStyleProps) | undefined): TextStyleProps;
|
|
3
3
|
export declare function extractBoxProps(style: (TextStyleProps & BoxStyleProps) | undefined): BoxStyleProps;
|
|
4
4
|
export declare function mergeStyles<T>(defaultStyle: T | undefined, userStyle: T | undefined): T;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ink-markdown-es",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A modern performance markdown renderer for ink",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"dedent": "^1.7.1",
|
|
54
|
+
"highlight.js": "^11.11.1",
|
|
54
55
|
"marked": "^16.4.2"
|
|
55
56
|
}
|
|
56
57
|
}
|