c063 1.4.9 → 1.6.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/components/CodeBlock.d.ts +1 -1
- package/dist/components/CodeBlock.js +10 -9
- package/dist/components/CodeLine.d.ts +2 -1
- package/dist/components/CodeLine.js +3 -2
- package/dist/types/index.d.ts +14 -4
- package/dist/utils/index.d.ts +38 -13
- package/dist/utils/index.js +106 -28
- package/package.json +1 -1
- package/src/components/CodeBlock.tsx +29 -22
- package/src/components/CodeLine.tsx +4 -2
- package/src/types/{index.ts → index.tsx} +16 -4
- package/src/utils/index.tsx +115 -27
|
@@ -11,6 +11,6 @@ import { CodeBlockProps } from "../types/index";
|
|
|
11
11
|
* @returns JSX 元素,呈現語法高亮的程式碼區塊
|
|
12
12
|
*/
|
|
13
13
|
export declare const CodeBlock: {
|
|
14
|
-
<T extends React.ElementType = "span">({ tokenLines, showLineNumbers, lineNumberStyle, theme, ...rest }: CodeBlockProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
<T extends React.ElementType = "span">({ tokenLines, showLineNumbers, lineNumberStyle, autoWrap, theme, ...rest }: CodeBlockProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
15
15
|
displayName: string;
|
|
16
16
|
};
|
|
@@ -11,14 +11,15 @@ import { CodeLine } from "./CodeLine";
|
|
|
11
11
|
* @param rest 其他傳遞給 <pre> 的屬性
|
|
12
12
|
* @returns JSX 元素,呈現語法高亮的程式碼區塊
|
|
13
13
|
*/
|
|
14
|
-
export const CodeBlock = ({ tokenLines, showLineNumbers = true, lineNumberStyle, theme, ...rest }) => {
|
|
15
|
-
return (_jsx("pre", { ...rest, children: tokenLines.map((line, index) => (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
export const CodeBlock = ({ tokenLines, showLineNumbers = true, lineNumberStyle, autoWrap, theme, ...rest }) => {
|
|
15
|
+
return (_jsx("pre", { ...rest, style: { margin: 0, padding: 0, overflowX: "auto" }, children: _jsx("table", { style: { borderCollapse: "collapse", width: "100%" }, children: _jsx("tbody", { children: tokenLines.map((line, index) => (_jsxs("tr", { children: [showLineNumbers && (_jsx("td", { style: {
|
|
16
|
+
paddingInline: "0.5rem",
|
|
17
|
+
textAlign: "right",
|
|
18
|
+
whiteSpace: "pre",
|
|
19
|
+
fontVariantNumeric: "tabular-nums",
|
|
20
|
+
color: "#888",
|
|
21
|
+
userSelect: "none",
|
|
22
|
+
...lineNumberStyle,
|
|
23
|
+
}, children: index + 1 })), _jsx("td", { style: { width: "100%" }, children: _jsx(CodeLine, { theme: theme, tokens: line, autoWrap: autoWrap }) })] }, index))) }) }) }));
|
|
23
24
|
};
|
|
24
25
|
CodeBlock.displayName = "CodeBlock";
|
|
@@ -6,10 +6,11 @@ import { CodeLineProps } from "../types/index";
|
|
|
6
6
|
* @param props.tokens 該行所包含的語法 token 陣列
|
|
7
7
|
* @param props.style 自訂樣式,會與 whiteSpace: pre-wrap 合併
|
|
8
8
|
* @param props.theme 主題
|
|
9
|
+
* @param prop.autoWrap 是否自動換行
|
|
9
10
|
* @param rest 其他 HTMLAttributes
|
|
10
11
|
* @returns JSX 元素,呈現語法 token 的單行程式碼
|
|
11
12
|
*/
|
|
12
13
|
export declare const CodeLine: {
|
|
13
|
-
<T extends React.ElementType = "span">({ style, tokens, theme, ...rest }: CodeLineProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
<T extends React.ElementType = "span">({ style, tokens, theme, autoWrap, ...rest }: CodeLineProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
14
15
|
displayName: string;
|
|
15
16
|
};
|
|
@@ -7,12 +7,13 @@ import { CodeToken } from "./CodeToken";
|
|
|
7
7
|
* @param props.tokens 該行所包含的語法 token 陣列
|
|
8
8
|
* @param props.style 自訂樣式,會與 whiteSpace: pre-wrap 合併
|
|
9
9
|
* @param props.theme 主題
|
|
10
|
+
* @param prop.autoWrap 是否自動換行
|
|
10
11
|
* @param rest 其他 HTMLAttributes
|
|
11
12
|
* @returns JSX 元素,呈現語法 token 的單行程式碼
|
|
12
13
|
*/
|
|
13
|
-
export const CodeLine = ({ style, tokens, theme, ...rest }) => {
|
|
14
|
+
export const CodeLine = ({ style, tokens, theme, autoWrap = true, ...rest }) => {
|
|
14
15
|
return (_jsx("code", { ...rest, style: {
|
|
15
|
-
whiteSpace: "pre-wrap",
|
|
16
|
+
whiteSpace: autoWrap ? "pre-wrap" : "nowrap",
|
|
16
17
|
...style,
|
|
17
18
|
}, children: tokens.map((token, index) => (_jsx(CodeToken, { theme: theme, ...token }, index))) }));
|
|
18
19
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { AsComponentProps, OverrideProps } from "./common";
|
|
|
7
7
|
*
|
|
8
8
|
* 類型分為以下幾大類:
|
|
9
9
|
*
|
|
10
|
-
* - `keyword1` / `keyword2`: 關鍵字,如 `const
|
|
10
|
+
* - `keyword1` / `keyword2`: 關鍵字,如 `const`/`return`、`import` 等,分顏色類別。
|
|
11
11
|
* - `string`: 字串常值,如 `'text'`、`"value"`。
|
|
12
12
|
* - `number`: 數字常值,如 `123`、`3.14`。
|
|
13
13
|
* - `comment`: 註解內容,如 `//`。
|
|
@@ -71,6 +71,11 @@ export type CodeLineProps<T extends React.ElementType> = OverrideProps<React.HTM
|
|
|
71
71
|
* @default "vscode-dark"
|
|
72
72
|
*/
|
|
73
73
|
theme?: CodeTheme;
|
|
74
|
+
/**
|
|
75
|
+
* 是否自動換行
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
autoWrap?: boolean;
|
|
74
79
|
}>;
|
|
75
80
|
export type CodeBlockProps<T extends React.ElementType> = OverrideProps<React.HTMLAttributes<HTMLPreElement>, {
|
|
76
81
|
/**
|
|
@@ -80,13 +85,13 @@ export type CodeBlockProps<T extends React.ElementType> = OverrideProps<React.HT
|
|
|
80
85
|
* ```tsx
|
|
81
86
|
* <CodeBlock tokenLines={[
|
|
82
87
|
* [
|
|
83
|
-
* { type: "
|
|
88
|
+
* { type: "keyword1", children: "const" },
|
|
84
89
|
* { type: "variable", children: "x" },
|
|
85
90
|
* { type: "operator", children: "=" },
|
|
86
91
|
* { type: "number", children: "42" },
|
|
87
92
|
* ],
|
|
88
93
|
* [
|
|
89
|
-
* { type: "
|
|
94
|
+
* { type: "keyword2", children: "return" },
|
|
90
95
|
* { type: "variable", children: "x" },
|
|
91
96
|
* ],
|
|
92
97
|
* ]} />
|
|
@@ -109,8 +114,13 @@ export type CodeBlockProps<T extends React.ElementType> = OverrideProps<React.HT
|
|
|
109
114
|
lineNumberStyle?: React.CSSProperties;
|
|
110
115
|
/**
|
|
111
116
|
* 語法主題名稱。
|
|
112
|
-
* @default "
|
|
117
|
+
* @default "default-dark-modern"
|
|
113
118
|
*/
|
|
114
119
|
theme?: CodeTheme;
|
|
120
|
+
/**
|
|
121
|
+
* 是否自動換行
|
|
122
|
+
* @default true
|
|
123
|
+
*/
|
|
124
|
+
autoWrap?: boolean;
|
|
115
125
|
}>;
|
|
116
126
|
export type ParsableLanguage = (typeof parsableLanguages)[number];
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,31 +1,56 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { CodeTokenBuilder, CodeTokenProps, CodeTokenType } from "../types";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* `c063` 是一組語法高亮 token 建構器集合。
|
|
5
|
+
* 每個 key 對應一種語法分類(如 `keyword1`, `string`, `comment` 等),
|
|
6
|
+
* 回傳對應的 `CodeTokenProps` 物件。
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* @example
|
|
8
9
|
* ```tsx
|
|
9
|
-
* c063.keyword1("const")
|
|
10
|
-
* c063.string("'
|
|
10
|
+
* const keyword = c063.keyword1("const");
|
|
11
|
+
* const str = c063.string("'Hello'", { as: "code" });
|
|
11
12
|
* ```
|
|
12
13
|
*
|
|
13
|
-
* @
|
|
14
|
-
* tokens.push(c063.keyword1("const"));
|
|
15
|
-
* tokens.push(c063.string("'Hello'"));
|
|
16
|
-
*
|
|
17
|
-
* @returns 一個以 `CodeTokenType` 為 key 的建構器函式集合
|
|
14
|
+
* @returns 以 `CodeTokenType` 為 key 的建構器函式集合。
|
|
18
15
|
*/
|
|
19
16
|
declare const c063: Record<CodeTokenType, CodeTokenBuilder>;
|
|
17
|
+
export default c063;
|
|
20
18
|
/**
|
|
21
|
-
* 產生指定空白數量的 CodeToken
|
|
19
|
+
* 產生指定空白數量的 CodeToken,用於縮排、空格等用途。
|
|
22
20
|
*
|
|
23
21
|
* @param count 空白字元數,預設為 1
|
|
24
|
-
* @returns
|
|
22
|
+
* @returns type 為 `"default"`、內容為空格的 `CodeTokenProps`
|
|
25
23
|
*
|
|
26
24
|
* @example
|
|
25
|
+
* ```tsx
|
|
27
26
|
* tokens.push(whiteSpace(2)); // -> { type: "default", children: " " }
|
|
27
|
+
* ```
|
|
28
28
|
*/
|
|
29
29
|
export declare const whiteSpace: (count?: number) => CodeTokenProps<"span">;
|
|
30
|
+
/**
|
|
31
|
+
* 抽取單個 `CodeTokenProps` 的純文字內容。
|
|
32
|
+
*
|
|
33
|
+
* @param token 要處理的 token
|
|
34
|
+
* @returns 對應的文字內容
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* extractTokenContent(c063.keyword1("return")); // => "return"
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
30
41
|
export declare const extractTokenContent: <T extends React.ElementType>(token: CodeTokenProps<T>) => string;
|
|
31
|
-
|
|
42
|
+
/**
|
|
43
|
+
* 判斷兩個 token 是否相等(type 與內容皆相同)。
|
|
44
|
+
*
|
|
45
|
+
* @param a 第一個 token
|
|
46
|
+
* @param b 第二個 token
|
|
47
|
+
* @returns 是否相等
|
|
48
|
+
*/
|
|
49
|
+
export declare const isTokenEqual: <T extends React.ElementType>(a: CodeTokenProps<T>, b: CodeTokenProps<T>) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 將 token 列表按語法類型分類。
|
|
52
|
+
*
|
|
53
|
+
* @param lines 二維陣列,每行為一組 token
|
|
54
|
+
* @returns 分組後的 token 映射,key 為 `CodeTokenType`
|
|
55
|
+
*/
|
|
56
|
+
export declare const groupTokensByType: <T extends React.ElementType>(lines: CodeTokenProps<T>[][]) => Record<CodeTokenType, CodeTokenProps<T>[]>;
|
package/dist/utils/index.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* `c063` 是一組語法高亮 token 建構器集合。
|
|
4
|
+
* 每個 key 對應一種語法分類(如 `keyword1`, `string`, `comment` 等),
|
|
5
|
+
* 回傳對應的 `CodeTokenProps` 物件。
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* @example
|
|
7
8
|
* ```tsx
|
|
8
|
-
* c063.keyword1("const")
|
|
9
|
-
* c063.string("'
|
|
9
|
+
* const keyword = c063.keyword1("const");
|
|
10
|
+
* const str = c063.string("'Hello'", { as: "code" });
|
|
10
11
|
* ```
|
|
11
12
|
*
|
|
12
|
-
* @
|
|
13
|
-
* tokens.push(c063.keyword1("const"));
|
|
14
|
-
* tokens.push(c063.string("'Hello'"));
|
|
15
|
-
*
|
|
16
|
-
* @returns 一個以 `CodeTokenType` 為 key 的建構器函式集合
|
|
13
|
+
* @returns 以 `CodeTokenType` 為 key 的建構器函式集合。
|
|
17
14
|
*/
|
|
18
15
|
const c063 = new Proxy({}, {
|
|
19
16
|
get: (_, prop) => {
|
|
17
|
+
/**
|
|
18
|
+
* 建立指定語法類型的 CodeToken。
|
|
19
|
+
*
|
|
20
|
+
* @param children 要包裹的 React 內容或字串
|
|
21
|
+
* @param props 可選的額外屬性,如 `as` 或 `className`
|
|
22
|
+
* @returns 一個 CodeToken 物件
|
|
23
|
+
*/
|
|
20
24
|
const builder = (children, props) => {
|
|
21
25
|
return {
|
|
22
26
|
children,
|
|
@@ -27,39 +31,113 @@ const c063 = new Proxy({}, {
|
|
|
27
31
|
return builder;
|
|
28
32
|
},
|
|
29
33
|
});
|
|
34
|
+
export default c063;
|
|
30
35
|
/**
|
|
31
|
-
* 產生指定空白數量的 CodeToken
|
|
36
|
+
* 產生指定空白數量的 CodeToken,用於縮排、空格等用途。
|
|
32
37
|
*
|
|
33
38
|
* @param count 空白字元數,預設為 1
|
|
34
|
-
* @returns
|
|
39
|
+
* @returns type 為 `"default"`、內容為空格的 `CodeTokenProps`
|
|
35
40
|
*
|
|
36
41
|
* @example
|
|
42
|
+
* ```tsx
|
|
37
43
|
* tokens.push(whiteSpace(2)); // -> { type: "default", children: " " }
|
|
44
|
+
* ```
|
|
38
45
|
*/
|
|
39
46
|
export const whiteSpace = (count = 1) => c063.default(" ".repeat(count));
|
|
47
|
+
/**
|
|
48
|
+
* 遞迴抽取 ReactNode 中的純文字內容。
|
|
49
|
+
*
|
|
50
|
+
* @param children ReactNode,可以是字串、數字、JSX 元素、陣列等
|
|
51
|
+
* @returns 純文字內容字串
|
|
52
|
+
*/
|
|
53
|
+
const _extractReactNode = (children) => {
|
|
54
|
+
if (typeof children === "string")
|
|
55
|
+
return children;
|
|
56
|
+
if (typeof children === "number")
|
|
57
|
+
return children.toString();
|
|
58
|
+
if (Array.isArray(children))
|
|
59
|
+
return children.map(_extractReactNode).join("");
|
|
60
|
+
if (React.isValidElement(children)) {
|
|
61
|
+
return _extractReactNode(children.props.children);
|
|
62
|
+
}
|
|
63
|
+
if (typeof children === "object" && children !== null) {
|
|
64
|
+
return React.Children.toArray(children).map(_extractReactNode).join("");
|
|
65
|
+
}
|
|
66
|
+
return ""; // 如果 children 是 null 或 undefined,則返回空字串
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* 抽取單個 `CodeTokenProps` 的純文字內容。
|
|
70
|
+
*
|
|
71
|
+
* @param token 要處理的 token
|
|
72
|
+
* @returns 對應的文字內容
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* extractTokenContent(c063.keyword1("return")); // => "return"
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
40
79
|
export const extractTokenContent = (token) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
80
|
+
return _extractReactNode(token.children);
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* 判斷兩個 token 是否相等(type 與內容皆相同)。
|
|
84
|
+
*
|
|
85
|
+
* @param a 第一個 token
|
|
86
|
+
* @param b 第二個 token
|
|
87
|
+
* @returns 是否相等
|
|
88
|
+
*/
|
|
89
|
+
export const isTokenEqual = (a, b) => {
|
|
90
|
+
return a.type === b.type && extractTokenContent(a) === extractTokenContent(b);
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* 將 token 列表按語法類型分類。
|
|
94
|
+
*
|
|
95
|
+
* @param lines 二維陣列,每行為一組 token
|
|
96
|
+
* @returns 分組後的 token 映射,key 為 `CodeTokenType`
|
|
97
|
+
*/
|
|
98
|
+
export const groupTokensByType = (lines) => {
|
|
99
|
+
var _a;
|
|
100
|
+
const grouped = {
|
|
101
|
+
keyword1: [],
|
|
102
|
+
keyword2: [],
|
|
103
|
+
function: [],
|
|
104
|
+
string: [],
|
|
105
|
+
number: [],
|
|
106
|
+
comment: [],
|
|
107
|
+
type: [],
|
|
108
|
+
variable: [],
|
|
109
|
+
constant: [],
|
|
110
|
+
brackets1: [],
|
|
111
|
+
brackets2: [],
|
|
112
|
+
brackets3: [],
|
|
113
|
+
operator: [],
|
|
114
|
+
default: [],
|
|
52
115
|
};
|
|
53
|
-
|
|
116
|
+
for (const token of lines.flat()) {
|
|
117
|
+
grouped[(_a = token.type) !== null && _a !== void 0 ? _a : "default"].push(token);
|
|
118
|
+
}
|
|
119
|
+
return grouped;
|
|
54
120
|
};
|
|
55
|
-
export default c063;
|
|
56
121
|
/**
|
|
57
|
-
*
|
|
122
|
+
* 待實作
|
|
123
|
+
* `parseTokens` 是語法解析器的代理集合,用來解析特定語言的程式碼字串。
|
|
124
|
+
*
|
|
125
|
+
* 每個 key 對應一種可解析語言(如 `"javascript"`、`"python"` 等),
|
|
126
|
+
* 傳入原始程式碼字串後,回傳解析後的 token 二維陣列(每行一組 token)。
|
|
127
|
+
*
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const tokens = parseTokens.javascript("const x = 1;");
|
|
132
|
+
* ```
|
|
133
|
+
*
|
|
134
|
+
* @returns 語法高亮用的 `CodeTokenProps` 二維陣列
|
|
58
135
|
*/
|
|
59
|
-
const
|
|
136
|
+
const parseTokens = new Proxy({}, {
|
|
60
137
|
get: (_, prop) => {
|
|
61
138
|
const parser = (content) => {
|
|
62
|
-
|
|
139
|
+
const result = [];
|
|
140
|
+
return result;
|
|
63
141
|
};
|
|
64
142
|
return parser;
|
|
65
143
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "c063",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"description": "A React component for displaying code snippets with syntax highlighting.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CodeBlockProps } from "../types/index";
|
|
2
2
|
import { CodeLine } from "./CodeLine";
|
|
3
|
+
|
|
3
4
|
/**
|
|
4
5
|
* 顯示完整程式碼區塊,支援多行語法 token 與行號顯示。
|
|
5
6
|
*
|
|
@@ -15,32 +16,38 @@ export const CodeBlock = <T extends React.ElementType = "span">({
|
|
|
15
16
|
tokenLines,
|
|
16
17
|
showLineNumbers = true,
|
|
17
18
|
lineNumberStyle,
|
|
19
|
+
autoWrap,
|
|
18
20
|
theme,
|
|
19
21
|
...rest
|
|
20
22
|
}: CodeBlockProps<T>) => {
|
|
21
23
|
return (
|
|
22
|
-
<pre {...rest}>
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
<pre {...rest} style={{ margin: 0, padding: 0, overflowX: "auto" }}>
|
|
25
|
+
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
|
26
|
+
<tbody>
|
|
27
|
+
{tokenLines.map((line, index) => (
|
|
28
|
+
<tr key={index}>
|
|
29
|
+
{showLineNumbers && (
|
|
30
|
+
<td
|
|
31
|
+
style={{
|
|
32
|
+
paddingInline: "0.5rem",
|
|
33
|
+
textAlign: "right",
|
|
34
|
+
whiteSpace: "pre",
|
|
35
|
+
fontVariantNumeric: "tabular-nums",
|
|
36
|
+
color: "#888",
|
|
37
|
+
userSelect: "none",
|
|
38
|
+
...lineNumberStyle,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{index + 1}
|
|
42
|
+
</td>
|
|
43
|
+
)}
|
|
44
|
+
<td style={{ width: "100%" }}>
|
|
45
|
+
<CodeLine theme={theme} tokens={line} autoWrap={autoWrap} />
|
|
46
|
+
</td>
|
|
47
|
+
</tr>
|
|
48
|
+
))}
|
|
49
|
+
</tbody>
|
|
50
|
+
</table>
|
|
44
51
|
</pre>
|
|
45
52
|
);
|
|
46
53
|
};
|
|
@@ -6,7 +6,8 @@ import { CodeToken } from "./CodeToken";
|
|
|
6
6
|
* @template T 元件渲染類型,例如 <code>、<span> 等
|
|
7
7
|
* @param props.tokens 該行所包含的語法 token 陣列
|
|
8
8
|
* @param props.style 自訂樣式,會與 whiteSpace: pre-wrap 合併
|
|
9
|
-
* @param props.theme 主題
|
|
9
|
+
* @param props.theme 主題
|
|
10
|
+
* @param prop.autoWrap 是否自動換行
|
|
10
11
|
* @param rest 其他 HTMLAttributes
|
|
11
12
|
* @returns JSX 元素,呈現語法 token 的單行程式碼
|
|
12
13
|
*/
|
|
@@ -14,13 +15,14 @@ export const CodeLine = <T extends React.ElementType = "span">({
|
|
|
14
15
|
style,
|
|
15
16
|
tokens,
|
|
16
17
|
theme,
|
|
18
|
+
autoWrap = true,
|
|
17
19
|
...rest
|
|
18
20
|
}: CodeLineProps<T>) => {
|
|
19
21
|
return (
|
|
20
22
|
<code
|
|
21
23
|
{...rest}
|
|
22
24
|
style={{
|
|
23
|
-
whiteSpace: "pre-wrap",
|
|
25
|
+
whiteSpace: autoWrap ? "pre-wrap" : "nowrap",
|
|
24
26
|
...style,
|
|
25
27
|
}}
|
|
26
28
|
>
|
|
@@ -7,7 +7,7 @@ import { AsComponentProps, OverrideProps } from "./common";
|
|
|
7
7
|
*
|
|
8
8
|
* 類型分為以下幾大類:
|
|
9
9
|
*
|
|
10
|
-
* - `keyword1` / `keyword2`: 關鍵字,如 `const
|
|
10
|
+
* - `keyword1` / `keyword2`: 關鍵字,如 `const`/`return`、`import` 等,分顏色類別。
|
|
11
11
|
* - `string`: 字串常值,如 `'text'`、`"value"`。
|
|
12
12
|
* - `number`: 數字常值,如 `123`、`3.14`。
|
|
13
13
|
* - `comment`: 註解內容,如 `//`。
|
|
@@ -94,6 +94,12 @@ export type CodeLineProps<T extends React.ElementType> = OverrideProps<
|
|
|
94
94
|
* @default "vscode-dark"
|
|
95
95
|
*/
|
|
96
96
|
theme?: CodeTheme;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 是否自動換行
|
|
100
|
+
* @default true
|
|
101
|
+
*/
|
|
102
|
+
autoWrap?: boolean;
|
|
97
103
|
}
|
|
98
104
|
>;
|
|
99
105
|
|
|
@@ -107,13 +113,13 @@ export type CodeBlockProps<T extends React.ElementType> = OverrideProps<
|
|
|
107
113
|
* ```tsx
|
|
108
114
|
* <CodeBlock tokenLines={[
|
|
109
115
|
* [
|
|
110
|
-
* { type: "
|
|
116
|
+
* { type: "keyword1", children: "const" },
|
|
111
117
|
* { type: "variable", children: "x" },
|
|
112
118
|
* { type: "operator", children: "=" },
|
|
113
119
|
* { type: "number", children: "42" },
|
|
114
120
|
* ],
|
|
115
121
|
* [
|
|
116
|
-
* { type: "
|
|
122
|
+
* { type: "keyword2", children: "return" },
|
|
117
123
|
* { type: "variable", children: "x" },
|
|
118
124
|
* ],
|
|
119
125
|
* ]} />
|
|
@@ -137,9 +143,15 @@ export type CodeBlockProps<T extends React.ElementType> = OverrideProps<
|
|
|
137
143
|
lineNumberStyle?: React.CSSProperties;
|
|
138
144
|
/**
|
|
139
145
|
* 語法主題名稱。
|
|
140
|
-
* @default "
|
|
146
|
+
* @default "default-dark-modern"
|
|
141
147
|
*/
|
|
142
148
|
theme?: CodeTheme;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 是否自動換行
|
|
152
|
+
* @default true
|
|
153
|
+
*/
|
|
154
|
+
autoWrap?: boolean;
|
|
143
155
|
}
|
|
144
156
|
>;
|
|
145
157
|
|
package/src/utils/index.tsx
CHANGED
|
@@ -7,28 +7,32 @@ import {
|
|
|
7
7
|
} from "../types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* `c063` 是一組語法高亮 token 建構器集合。
|
|
11
|
+
* 每個 key 對應一種語法分類(如 `keyword1`, `string`, `comment` 等),
|
|
12
|
+
* 回傳對應的 `CodeTokenProps` 物件。
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
+
* @example
|
|
14
15
|
* ```tsx
|
|
15
|
-
* c063.keyword1("const")
|
|
16
|
-
* c063.string("'
|
|
16
|
+
* const keyword = c063.keyword1("const");
|
|
17
|
+
* const str = c063.string("'Hello'", { as: "code" });
|
|
17
18
|
* ```
|
|
18
19
|
*
|
|
19
|
-
* @
|
|
20
|
-
* tokens.push(c063.keyword1("const"));
|
|
21
|
-
* tokens.push(c063.string("'Hello'"));
|
|
22
|
-
*
|
|
23
|
-
* @returns 一個以 `CodeTokenType` 為 key 的建構器函式集合
|
|
20
|
+
* @returns 以 `CodeTokenType` 為 key 的建構器函式集合。
|
|
24
21
|
*/
|
|
25
22
|
const c063 = new Proxy(
|
|
26
23
|
{},
|
|
27
24
|
{
|
|
28
25
|
get: (_, prop: CodeTokenType) => {
|
|
26
|
+
/**
|
|
27
|
+
* 建立指定語法類型的 CodeToken。
|
|
28
|
+
*
|
|
29
|
+
* @param children 要包裹的 React 內容或字串
|
|
30
|
+
* @param props 可選的額外屬性,如 `as` 或 `className`
|
|
31
|
+
* @returns 一個 CodeToken 物件
|
|
32
|
+
*/
|
|
29
33
|
const builder = <T extends React.ElementType = "span">(
|
|
30
34
|
children: React.ReactNode,
|
|
31
|
-
props
|
|
35
|
+
props?: CodeTokenProps<T>
|
|
32
36
|
) => {
|
|
33
37
|
return {
|
|
34
38
|
children,
|
|
@@ -40,45 +44,129 @@ const c063 = new Proxy(
|
|
|
40
44
|
},
|
|
41
45
|
}
|
|
42
46
|
) as Record<CodeTokenType, CodeTokenBuilder>;
|
|
47
|
+
export default c063;
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
* 產生指定空白數量的 CodeToken
|
|
50
|
+
* 產生指定空白數量的 CodeToken,用於縮排、空格等用途。
|
|
46
51
|
*
|
|
47
52
|
* @param count 空白字元數,預設為 1
|
|
48
|
-
* @returns
|
|
53
|
+
* @returns type 為 `"default"`、內容為空格的 `CodeTokenProps`
|
|
49
54
|
*
|
|
50
55
|
* @example
|
|
56
|
+
* ```tsx
|
|
51
57
|
* tokens.push(whiteSpace(2)); // -> { type: "default", children: " " }
|
|
58
|
+
* ```
|
|
52
59
|
*/
|
|
53
60
|
export const whiteSpace = (count: number = 1): CodeTokenProps<"span"> =>
|
|
54
61
|
c063.default(" ".repeat(count));
|
|
55
62
|
|
|
63
|
+
/**
|
|
64
|
+
* 遞迴抽取 ReactNode 中的純文字內容。
|
|
65
|
+
*
|
|
66
|
+
* @param children ReactNode,可以是字串、數字、JSX 元素、陣列等
|
|
67
|
+
* @returns 純文字內容字串
|
|
68
|
+
*/
|
|
69
|
+
const _extractReactNode = (children: React.ReactNode): string => {
|
|
70
|
+
if (typeof children === "string") return children;
|
|
71
|
+
if (typeof children === "number") return children.toString();
|
|
72
|
+
if (Array.isArray(children)) return children.map(_extractReactNode).join("");
|
|
73
|
+
if (React.isValidElement(children)) {
|
|
74
|
+
return _extractReactNode((children as React.JSX.Element).props.children);
|
|
75
|
+
}
|
|
76
|
+
if (typeof children === "object" && children !== null) {
|
|
77
|
+
return React.Children.toArray(children).map(_extractReactNode).join("");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return ""; // 如果 children 是 null 或 undefined,則返回空字串
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 抽取單個 `CodeTokenProps` 的純文字內容。
|
|
85
|
+
*
|
|
86
|
+
* @param token 要處理的 token
|
|
87
|
+
* @returns 對應的文字內容
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* extractTokenContent(c063.keyword1("return")); // => "return"
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
56
94
|
export const extractTokenContent = <T extends React.ElementType>(
|
|
57
95
|
token: CodeTokenProps<T>
|
|
58
96
|
): string => {
|
|
59
|
-
|
|
60
|
-
if (typeof children === "string") return children;
|
|
61
|
-
if (typeof children === "number") return children.toString();
|
|
62
|
-
if (Array.isArray(children)) return children.map(_extract).join("");
|
|
63
|
-
if (React.isValidElement(children)) {
|
|
64
|
-
return _extract((children as React.JSX.Element).props.children);
|
|
65
|
-
}
|
|
66
|
-
return ""; // 如果 children 是 null 或 undefined,則返回空字串
|
|
67
|
-
};
|
|
68
|
-
return _extract(token.children);
|
|
97
|
+
return _extractReactNode(token.children);
|
|
69
98
|
};
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 判斷兩個 token 是否相等(type 與內容皆相同)。
|
|
102
|
+
*
|
|
103
|
+
* @param a 第一個 token
|
|
104
|
+
* @param b 第二個 token
|
|
105
|
+
* @returns 是否相等
|
|
106
|
+
*/
|
|
107
|
+
export const isTokenEqual = <T extends React.ElementType>(
|
|
108
|
+
a: CodeTokenProps<T>,
|
|
109
|
+
b: CodeTokenProps<T>
|
|
110
|
+
): boolean => {
|
|
111
|
+
return a.type === b.type && extractTokenContent(a) === extractTokenContent(b);
|
|
112
|
+
};
|
|
72
113
|
|
|
73
114
|
/**
|
|
74
|
-
*
|
|
115
|
+
* 將 token 列表按語法類型分類。
|
|
116
|
+
*
|
|
117
|
+
* @param lines 二維陣列,每行為一組 token
|
|
118
|
+
* @returns 分組後的 token 映射,key 為 `CodeTokenType`
|
|
75
119
|
*/
|
|
76
|
-
const
|
|
120
|
+
export const groupTokensByType = <T extends React.ElementType>(
|
|
121
|
+
lines: CodeTokenProps<T>[][]
|
|
122
|
+
): Record<CodeTokenType, CodeTokenProps<T>[]> => {
|
|
123
|
+
const grouped: Record<CodeTokenType, CodeTokenProps<T>[]> = {
|
|
124
|
+
keyword1: [],
|
|
125
|
+
keyword2: [],
|
|
126
|
+
function: [],
|
|
127
|
+
string: [],
|
|
128
|
+
number: [],
|
|
129
|
+
comment: [],
|
|
130
|
+
type: [],
|
|
131
|
+
variable: [],
|
|
132
|
+
constant: [],
|
|
133
|
+
brackets1: [],
|
|
134
|
+
brackets2: [],
|
|
135
|
+
brackets3: [],
|
|
136
|
+
operator: [],
|
|
137
|
+
default: [],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
for (const token of lines.flat()) {
|
|
141
|
+
grouped[token.type ?? "default"].push(token);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return grouped;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 待實作
|
|
149
|
+
* `parseTokens` 是語法解析器的代理集合,用來解析特定語言的程式碼字串。
|
|
150
|
+
*
|
|
151
|
+
* 每個 key 對應一種可解析語言(如 `"javascript"`、`"python"` 等),
|
|
152
|
+
* 傳入原始程式碼字串後,回傳解析後的 token 二維陣列(每行一組 token)。
|
|
153
|
+
*
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* const tokens = parseTokens.javascript("const x = 1;");
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @returns 語法高亮用的 `CodeTokenProps` 二維陣列
|
|
161
|
+
*/
|
|
162
|
+
const parseTokens = new Proxy(
|
|
77
163
|
{},
|
|
78
164
|
{
|
|
79
165
|
get: (_, prop: ParsableLanguage) => {
|
|
80
166
|
const parser = (content: string): CodeTokenProps<"span">[][] => {
|
|
81
|
-
|
|
167
|
+
const result: CodeTokenProps<"span">[][] = [];
|
|
168
|
+
|
|
169
|
+
return result;
|
|
82
170
|
};
|
|
83
171
|
return parser;
|
|
84
172
|
},
|