@haklex/rich-renderer-codeblock 0.0.3 → 0.0.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockCard.d.ts","sourceRoot":"","sources":["../src/CodeBlockCard.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AAGrB,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAcrD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,kBAAkB,2CAwFpB"}
1
+ {"version":3,"file":"CodeBlockCard.d.ts","sourceRoot":"","sources":["../src/CodeBlockCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAerD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,kBAAkB,2CAuGpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockEditRenderer.d.ts","sourceRoot":"","sources":["../src/CodeBlockEditRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAO1C,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,sBAAsB,CA4KvE,CAAA"}
1
+ {"version":3,"file":"CodeBlockEditRenderer.d.ts","sourceRoot":"","sources":["../src/CodeBlockEditRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAQ1C,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,sBAAsB,CAmLvE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"CodeBlockRenderer.d.ts","sourceRoot":"","sources":["../src/CodeBlockRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAO1C,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,sBAAsB,CA8DnE,CAAA;AAED,eAAe,iBAAiB,CAAA"}
1
+ {"version":3,"file":"CodeBlockRenderer.d.ts","sourceRoot":"","sources":["../src/CodeBlockRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAQ1C,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,sBAAsB,CAgEnE,CAAA;AAED,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,76 @@
1
+ const languageToIconMap = {
2
+ javascript: "JS",
3
+ js: "JS",
4
+ typescript: "TS",
5
+ ts: "TS",
6
+ jsx: "JSX",
7
+ tsx: "TSX",
8
+ html: "HTML",
9
+ css: "CSS",
10
+ scss: "SCSS",
11
+ json: "JSON",
12
+ markdown: "MD",
13
+ md: "MD",
14
+ bash: "SH",
15
+ sh: "SH",
16
+ shell: "SH",
17
+ zsh: "SH",
18
+ python: "PY",
19
+ py: "PY",
20
+ rust: "RS",
21
+ go: "GO",
22
+ java: "JAVA",
23
+ c: "C",
24
+ cpp: "C++",
25
+ "c++": "C++",
26
+ swift: "SW",
27
+ kotlin: "KT",
28
+ yaml: "YAML",
29
+ yml: "YAML",
30
+ sql: "SQL"
31
+ };
32
+ const languageToColorMap = {
33
+ javascript: "#f7df1e",
34
+ js: "#f7df1e",
35
+ typescript: "#3178c6",
36
+ ts: "#3178c6",
37
+ jsx: "#61dafb",
38
+ tsx: "#61dafb",
39
+ html: "#e34f26",
40
+ css: "#1572b6",
41
+ scss: "#cc6699",
42
+ json: "#f59e0b",
43
+ markdown: "#737373",
44
+ md: "#737373",
45
+ bash: "#4eaa25",
46
+ sh: "#4eaa25",
47
+ shell: "#4eaa25",
48
+ zsh: "#4eaa25",
49
+ python: "#3776ab",
50
+ py: "#3776ab",
51
+ rust: "#dea584",
52
+ go: "#00add8",
53
+ java: "#b07219",
54
+ c: "#a8b9cc",
55
+ cpp: "#00599c",
56
+ "c++": "#00599c",
57
+ swift: "#fa7343",
58
+ kotlin: "#7f52ff",
59
+ yaml: "#cb171e",
60
+ yml: "#cb171e",
61
+ sql: "#e38c00"
62
+ };
63
+ function normalizeLanguage(lang) {
64
+ if (!lang) return "text";
65
+ return lang.trim().toLowerCase() || "text";
66
+ }
67
+ function getLanguageDisplayName(lang) {
68
+ if (lang === "text") return "TEXT";
69
+ return lang.toUpperCase();
70
+ }
71
+ export {
72
+ getLanguageDisplayName,
73
+ languageToColorMap,
74
+ languageToIconMap,
75
+ normalizeLanguage
76
+ };
@@ -0,0 +1,13 @@
1
+ import { FC, ReactNode } from 'react';
2
+ /** Maps file extension to material-icon-theme icon name. */
3
+ export declare const EXT_TO_ICON: Record<string, string>;
4
+ export interface FileIconProps {
5
+ filename: string;
6
+ size?: number;
7
+ /** Optional class for the wrapper (e.g. consumer styles). */
8
+ className?: string;
9
+ /** Fallback when no icon is found. Default "F". */
10
+ fallback?: ReactNode;
11
+ }
12
+ export declare const FileIcon: FC<FileIconProps>;
13
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/icons/file.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAK1C,4DAA4D;AAC5D,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkD9C,CAAA;AAQD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC,aAAa,CAiCtC,CAAA"}
@@ -0,0 +1,5 @@
1
+ export type { FileIconProps } from './file';
2
+ export { FileIcon } from './file';
3
+ export { hasLanguageIcon, LANG_TO_ICON, LanguageIcon } from './language';
4
+ export { getMaterialIconSvg } from './material-icon';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/icons/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AACjC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAC,YAAY,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { FC } from 'react';
2
+ /** Maps normalized language to material-icon-theme icon name. */
3
+ export declare const LANG_TO_ICON: Record<string, string>;
4
+ /** Returns whether a Material icon is available for the given language. */
5
+ export declare function hasLanguageIcon(lang: string): boolean;
6
+ export declare const LanguageIcon: FC<{
7
+ language: string;
8
+ size?: number;
9
+ className?: string;
10
+ }>;
11
+ //# sourceMappingURL=language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../../src/icons/language.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAA;AAM/B,iEAAiE;AACjE,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA8C/C,CAAA;AAOD,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAiCA,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** Get SVG markup for a material-icon-theme icon by name. Returns null if not found. */
2
+ export declare function getMaterialIconSvg(iconName: string): string | null;
3
+ //# sourceMappingURL=material-icon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-icon.d.ts","sourceRoot":"","sources":["../../src/icons/material-icon.ts"],"names":[],"mappings":"AAGA,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKlE"}
package/dist/icons.mjs ADDED
@@ -0,0 +1,96 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { n as getMaterialIconSvg } from "./language-AfAZN4lZ.js";
4
+ import { o, L, h } from "./language-AfAZN4lZ.js";
5
+ const EXT_TO_ICON = {
6
+ ts: "typescript",
7
+ tsx: "react-ts",
8
+ // TSX → React icon (same as JSX)
9
+ mts: "typescript",
10
+ cts: "typescript",
11
+ js: "javascript",
12
+ jsx: "react",
13
+ mjs: "javascript",
14
+ cjs: "javascript",
15
+ py: "python",
16
+ rs: "rust",
17
+ go: "go",
18
+ java: "java",
19
+ html: "html",
20
+ htm: "html",
21
+ css: "css",
22
+ scss: "sass",
23
+ sass: "sass",
24
+ less: "sass",
25
+ json: "json",
26
+ jsonc: "json",
27
+ json5: "json",
28
+ md: "markdown",
29
+ mdx: "markdown",
30
+ sh: "console",
31
+ bash: "console",
32
+ zsh: "console",
33
+ yml: "yaml",
34
+ yaml: "yaml",
35
+ sql: "database",
36
+ c: "c",
37
+ h: "c",
38
+ cpp: "cpp",
39
+ cc: "cpp",
40
+ cxx: "cpp",
41
+ hpp: "cpp",
42
+ swift: "swift",
43
+ kt: "kotlin",
44
+ kts: "kotlin",
45
+ rb: "ruby",
46
+ php: "php",
47
+ vue: "vue",
48
+ svelte: "svelte",
49
+ toml: "toml",
50
+ xml: "xml",
51
+ graphql: "graphql",
52
+ gql: "graphql",
53
+ dockerfile: "docker",
54
+ lua: "lua",
55
+ zig: "zig"
56
+ };
57
+ function getFileIconSvg(filename) {
58
+ const ext = filename.split(".").pop()?.toLowerCase() ?? "";
59
+ const iconName = EXT_TO_ICON[ext] ?? "file";
60
+ return getMaterialIconSvg(iconName);
61
+ }
62
+ const FileIcon = ({
63
+ filename,
64
+ size = 16,
65
+ className,
66
+ fallback = "F"
67
+ }) => {
68
+ const html = useMemo(() => getFileIconSvg(filename), [filename]);
69
+ const wrapperStyle = {
70
+ width: size,
71
+ height: size,
72
+ display: "inline-flex",
73
+ alignItems: "center",
74
+ justifyContent: "center",
75
+ opacity: html ? 1 : 0.5,
76
+ fontSize: size * 0.6
77
+ };
78
+ if (!html) {
79
+ return /* @__PURE__ */ jsx("span", { className, style: wrapperStyle, children: fallback });
80
+ }
81
+ return /* @__PURE__ */ jsx(
82
+ "span",
83
+ {
84
+ className,
85
+ style: { width: size, height: size },
86
+ dangerouslySetInnerHTML: { __html: html }
87
+ }
88
+ );
89
+ };
90
+ export {
91
+ FileIcon,
92
+ o as LANG_TO_ICON,
93
+ L as LanguageIcon,
94
+ getMaterialIconSvg,
95
+ h as hasLanguageIcon
96
+ };
package/dist/index.mjs CHANGED
@@ -2,76 +2,11 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useColorScheme } from "@haklex/rich-editor";
3
3
  import { useState, useRef, useEffect, useCallback, useMemo } from "react";
4
4
  import { Check, Copy, ChevronDown } from "lucide-react";
5
- const languageToIconMap = {
6
- javascript: "JS",
7
- js: "JS",
8
- typescript: "TS",
9
- ts: "TS",
10
- jsx: "JSX",
11
- tsx: "TSX",
12
- html: "HTML",
13
- css: "CSS",
14
- scss: "SCSS",
15
- json: "JSON",
16
- markdown: "MD",
17
- md: "MD",
18
- bash: "SH",
19
- sh: "SH",
20
- shell: "SH",
21
- zsh: "SH",
22
- python: "PY",
23
- py: "PY",
24
- rust: "RS",
25
- go: "GO",
26
- java: "JAVA",
27
- c: "C",
28
- cpp: "C++",
29
- "c++": "C++",
30
- swift: "SW",
31
- kotlin: "KT",
32
- yaml: "YAML",
33
- yml: "YAML",
34
- sql: "SQL"
35
- };
36
- const languageToColorMap = {
37
- javascript: "#f7df1e",
38
- js: "#f7df1e",
39
- typescript: "#3178c6",
40
- ts: "#3178c6",
41
- jsx: "#61dafb",
42
- tsx: "#61dafb",
43
- html: "#e34f26",
44
- css: "#1572b6",
45
- scss: "#cc6699",
46
- json: "#f59e0b",
47
- markdown: "#737373",
48
- md: "#737373",
49
- bash: "#4eaa25",
50
- sh: "#4eaa25",
51
- shell: "#4eaa25",
52
- zsh: "#4eaa25",
53
- python: "#3776ab",
54
- py: "#3776ab",
55
- rust: "#dea584",
56
- go: "#00add8",
57
- java: "#b07219",
58
- c: "#a8b9cc",
59
- cpp: "#00599c",
60
- "c++": "#00599c",
61
- swift: "#fa7343",
62
- kotlin: "#7f52ff",
63
- yaml: "#cb171e",
64
- yml: "#cb171e",
65
- sql: "#e38c00"
66
- };
67
- function normalizeLanguage(lang) {
68
- if (!lang) return "text";
69
- return lang.trim().toLowerCase() || "text";
70
- }
71
- function getLanguageDisplayName(lang) {
72
- if (lang === "text") return "TEXT";
73
- return lang.toUpperCase();
74
- }
5
+ import { normalizeLanguage, getLanguageDisplayName, languageToColorMap } from "./constants.mjs";
6
+ import "@iconify/utils";
7
+ import "@iconify-json/material-icon-theme";
8
+ import { c as card, s as semanticClassNames, l as lang, h as hasLanguageIcon, L as LanguageIcon, a as copyButton, b as bodyBackground, e as expandWrap, d as expandButton, f as scroll, g as scrollCollapsed, i as lined, j as linedWithNumbers, k as body, m as bodyReadonly } from "./language-AfAZN4lZ.js";
9
+ import { getHighlighterWithLang, SHIKI_THEMES } from "./shiki.mjs";
75
10
  const CopyIcon = /* @__PURE__ */ jsx(Copy, { size: 16 });
76
11
  const CheckIcon = /* @__PURE__ */ jsx(Check, { size: 16 });
77
12
  const ExpandIcon = /* @__PURE__ */ jsx(ChevronDown, { size: 14 });
@@ -107,7 +42,6 @@ function CodeBlockCard({
107
42
  }).catch(() => {
108
43
  });
109
44
  }, [code]);
110
- const languageIcon = languageToIconMap[normalizedLanguage] || "";
111
45
  const languageLabel = getLanguageDisplayName(normalizedLanguage);
112
46
  const accent = languageToColorMap[normalizedLanguage] || "#737373";
113
47
  const cardStyle = useMemo(
@@ -115,67 +49,66 @@ function CodeBlockCard({
115
49
  [accent]
116
50
  );
117
51
  const scrollClassName = [
118
- "rr-code-scroll",
119
- isCollapsed && isOverflow && "rr-code-scroll-collapsed"
52
+ scroll,
53
+ semanticClassNames.scroll,
54
+ isCollapsed && isOverflow && scrollCollapsed,
55
+ isCollapsed && isOverflow && semanticClassNames.scrollCollapsed
120
56
  ].filter(Boolean).join(" ");
121
- return /* @__PURE__ */ jsxs("div", { className: "rr-code-card", style: cardStyle, children: [
122
- normalizedLanguage !== "text" && /* @__PURE__ */ jsx("div", { className: "rr-code-lang", "aria-hidden": true, children: languageIcon || languageLabel }),
123
- /* @__PURE__ */ jsx(
124
- "button",
125
- {
126
- type: "button",
127
- className: "rr-code-copy",
128
- onClick: handleCopy,
129
- "aria-label": copied ? "Copied" : "Copy code",
130
- children: copied ? CheckIcon : CopyIcon
131
- }
132
- ),
133
- /* @__PURE__ */ jsxs("div", { className: "rr-code-bg", children: [
134
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: scrollClassName, children }),
135
- isOverflow && isCollapsed && /* @__PURE__ */ jsx("div", { className: "rr-code-expand-wrap", children: /* @__PURE__ */ jsxs(
136
- "button",
137
- {
138
- type: "button",
139
- className: "rr-code-expand",
140
- onClick: () => setIsCollapsed(false),
141
- children: [
142
- ExpandIcon,
143
- /* @__PURE__ */ jsx("span", { children: "展开" })
144
- ]
145
- }
146
- ) })
147
- ] })
148
- ] });
149
- }
150
- let highlighterPromise = null;
151
- function getHighlighter() {
152
- if (!highlighterPromise) {
153
- highlighterPromise = import("shiki/bundle/web").then(
154
- (mod) => mod.createHighlighter({
155
- langs: [],
156
- themes: ["github-light", "github-dark"]
157
- })
158
- );
159
- }
160
- return highlighterPromise;
161
- }
162
- async function getHighlighterWithLang(language) {
163
- const highlighter = await getHighlighter();
164
- if (language && language !== "text" && language !== "plaintext") {
165
- const loaded = highlighter.getLoadedLanguages();
166
- if (!loaded.includes(language)) {
167
- try {
168
- await highlighter.loadLanguage(language);
169
- } catch {
170
- }
57
+ return /* @__PURE__ */ jsxs(
58
+ "div",
59
+ {
60
+ className: `${card} ${semanticClassNames.card}`,
61
+ style: cardStyle,
62
+ children: [
63
+ normalizedLanguage !== "text" && /* @__PURE__ */ jsx(
64
+ "div",
65
+ {
66
+ className: `${lang} ${semanticClassNames.lang}`,
67
+ "aria-hidden": true,
68
+ children: hasLanguageIcon(normalizedLanguage) ? /* @__PURE__ */ jsx(LanguageIcon, { language: normalizedLanguage, size: 14 }) : /* @__PURE__ */ jsx("span", { children: languageLabel })
69
+ }
70
+ ),
71
+ /* @__PURE__ */ jsx(
72
+ "button",
73
+ {
74
+ type: "button",
75
+ className: `${copyButton} ${semanticClassNames.copyButton}`,
76
+ onClick: handleCopy,
77
+ "aria-label": copied ? "Copied" : "Copy code",
78
+ children: copied ? CheckIcon : CopyIcon
79
+ }
80
+ ),
81
+ /* @__PURE__ */ jsxs(
82
+ "div",
83
+ {
84
+ className: `${bodyBackground} ${semanticClassNames.bodyBackground}`,
85
+ children: [
86
+ /* @__PURE__ */ jsx("div", { ref: scrollRef, className: scrollClassName, children }),
87
+ isOverflow && isCollapsed && /* @__PURE__ */ jsx(
88
+ "div",
89
+ {
90
+ className: `${expandWrap} ${semanticClassNames.expandWrap}`,
91
+ children: /* @__PURE__ */ jsxs(
92
+ "button",
93
+ {
94
+ type: "button",
95
+ className: `${expandButton} ${semanticClassNames.expandButton}`,
96
+ onClick: () => setIsCollapsed(false),
97
+ children: [
98
+ ExpandIcon,
99
+ /* @__PURE__ */ jsx("span", { children: "展开" })
100
+ ]
101
+ }
102
+ )
103
+ }
104
+ )
105
+ ]
106
+ }
107
+ )
108
+ ]
171
109
  }
172
- }
173
- return highlighter;
110
+ );
174
111
  }
175
- const SHIKI_THEMES = {
176
- light: "github-light",
177
- dark: "github-dark"
178
- };
179
112
  const CodeBlockEditRenderer = ({
180
113
  code,
181
114
  language,
@@ -218,13 +151,13 @@ const CodeBlockEditRenderer = ({
218
151
  if (disposed) return;
219
152
  const theme = SHIKI_THEMES[props.colorScheme];
220
153
  const loaded2 = highlighter.getLoadedLanguages();
221
- const lang = loaded2.includes(props.normalizedLanguage) ? props.normalizedLanguage : "text";
154
+ const lang2 = loaded2.includes(props.normalizedLanguage) ? props.normalizedLanguage : "text";
222
155
  const editor = shikiCode().withOptions({
223
156
  readOnly: !props.editable,
224
157
  lineNumbers: props.showLineNumbers ? "on" : "off"
225
158
  }).create(container, highlighter, {
226
159
  value: props.code,
227
- language: lang,
160
+ language: lang2,
228
161
  theme
229
162
  });
230
163
  if (!props.editable) {
@@ -277,10 +210,17 @@ const CodeBlockEditRenderer = ({
277
210
  }, [code, normalizedLanguage, colorScheme, showLineNumbers, editable]);
278
211
  const fallbackLines = useMemo(() => code.split("\n"), [code]);
279
212
  const fallbackClassName = [
280
- "rr-code-lined",
281
- showLineNumbers && "rr-code-lined-ln"
213
+ lined,
214
+ semanticClassNames.lined,
215
+ showLineNumbers && linedWithNumbers,
216
+ showLineNumbers && semanticClassNames.linedWithNumbers
217
+ ].filter(Boolean).join(" ");
218
+ const bodyClassName = [
219
+ body,
220
+ semanticClassNames.body,
221
+ !editable && bodyReadonly,
222
+ !editable && semanticClassNames.bodyReadonly
282
223
  ].filter(Boolean).join(" ");
283
- const bodyClassName = ["rr-code-body", !editable && "rr-code-readonly"].filter(Boolean).join(" ");
284
224
  return /* @__PURE__ */ jsxs(CodeBlockCard, { code, language, children: [
285
225
  !loaded && /* @__PURE__ */ jsx("pre", { className: fallbackClassName, children: /* @__PURE__ */ jsx("code", { children: fallbackLines.map((line, i) => /* @__PURE__ */ jsx("span", { className: "line", children: line }, i)) }) }),
286
226
  /* @__PURE__ */ jsx(
@@ -307,9 +247,9 @@ const CodeBlockRenderer = ({
307
247
  const highlighter = await getHighlighterWithLang(normalizedLanguage);
308
248
  if (cancelled) return;
309
249
  const loaded = highlighter.getLoadedLanguages();
310
- const lang = loaded.includes(normalizedLanguage) ? normalizedLanguage : "text";
250
+ const lang2 = loaded.includes(normalizedLanguage) ? normalizedLanguage : "text";
311
251
  const result = highlighter.codeToHtml(code, {
312
- lang,
252
+ lang: lang2,
313
253
  theme: SHIKI_THEMES[colorScheme]
314
254
  });
315
255
  if (!cancelled) setHtml(result);
@@ -320,8 +260,10 @@ const CodeBlockRenderer = ({
320
260
  }, [code, normalizedLanguage, colorScheme]);
321
261
  const fallbackLines = useMemo(() => code.split("\n"), [code]);
322
262
  const linedClassName = [
323
- "rr-code-lined",
324
- showLineNumbers && "rr-code-lined-ln"
263
+ lined,
264
+ semanticClassNames.lined,
265
+ showLineNumbers && linedWithNumbers,
266
+ showLineNumbers && semanticClassNames.linedWithNumbers
325
267
  ].filter(Boolean).join(" ");
326
268
  return /* @__PURE__ */ jsx(CodeBlockCard, { code, language, children: html ? /* @__PURE__ */ jsx(
327
269
  "div",
@@ -0,0 +1,131 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { getIconData, iconToSVG, iconToHTML } from "@iconify/utils";
4
+ import { icons } from "@iconify-json/material-icon-theme";
5
+ function getMaterialIconSvg(iconName) {
6
+ const data = getIconData(icons, iconName);
7
+ if (!data) return null;
8
+ const svg = iconToSVG(data);
9
+ return iconToHTML(svg.body, svg.attributes);
10
+ }
11
+ var semanticClassNames = { card: "rr-code-card", lang: "rr-code-lang", langIcon: "rr-code-lang-icon", copyButton: "rr-code-copy", bodyBackground: "rr-code-bg", scroll: "rr-code-scroll", scrollCollapsed: "rr-code-scroll-collapsed", body: "rr-code-body", bodyReadonly: "rr-code-readonly", expandWrap: "rr-code-expand-wrap", expandButton: "rr-code-expand", lined: "rr-code-lined", linedWithNumbers: "rr-code-lined-ln" };
12
+ var card = "_1pn9r4q0";
13
+ var lang = "_1pn9r4q1";
14
+ var langIcon = "_1pn9r4q2";
15
+ var copyButton = "_1pn9r4q3";
16
+ var bodyBackground = "_1pn9r4q4";
17
+ var scroll = "_1pn9r4q5";
18
+ var scrollCollapsed = "_1pn9r4q6";
19
+ var body = "_1pn9r4q7";
20
+ var bodyReadonly = "_1pn9r4q8";
21
+ var expandWrap = "_1pn9r4q9";
22
+ var expandButton = "_1pn9r4qa";
23
+ var lined = "_1pn9r4qb";
24
+ var linedWithNumbers = "_1pn9r4qc";
25
+ const LANG_TO_ICON = {
26
+ javascript: "javascript",
27
+ js: "javascript",
28
+ typescript: "typescript",
29
+ ts: "typescript",
30
+ jsx: "react",
31
+ tsx: "react",
32
+ // TSX → React icon (same as JSX)
33
+ html: "html",
34
+ css: "css",
35
+ scss: "sass",
36
+ sass: "sass",
37
+ less: "sass",
38
+ json: "json",
39
+ jsonc: "json",
40
+ markdown: "markdown",
41
+ md: "markdown",
42
+ mdx: "markdown",
43
+ bash: "console",
44
+ sh: "console",
45
+ shell: "console",
46
+ zsh: "console",
47
+ python: "python",
48
+ py: "python",
49
+ rust: "rust",
50
+ go: "go",
51
+ java: "java",
52
+ c: "c",
53
+ cpp: "cpp",
54
+ "c++": "cpp",
55
+ swift: "swift",
56
+ kotlin: "kotlin",
57
+ kt: "kotlin",
58
+ yaml: "yaml",
59
+ yml: "yaml",
60
+ sql: "database",
61
+ xml: "xml",
62
+ vue: "vue",
63
+ svelte: "svelte",
64
+ php: "php",
65
+ rb: "ruby",
66
+ lua: "lua",
67
+ zig: "zig",
68
+ toml: "toml",
69
+ graphql: "graphql",
70
+ gql: "graphql",
71
+ dockerfile: "docker"
72
+ };
73
+ function getLanguageIconSvg(lang2) {
74
+ const iconName = LANG_TO_ICON[lang2] ?? "code";
75
+ return getMaterialIconSvg(iconName);
76
+ }
77
+ function hasLanguageIcon(lang2) {
78
+ return getLanguageIconSvg(lang2) !== null;
79
+ }
80
+ const LanguageIcon = ({
81
+ language,
82
+ size = 14,
83
+ className = `${langIcon} ${semanticClassNames.langIcon}`
84
+ }) => {
85
+ const html = useMemo(() => getLanguageIconSvg(language), [language]);
86
+ if (!html) {
87
+ return /* @__PURE__ */ jsx(
88
+ "span",
89
+ {
90
+ className,
91
+ style: {
92
+ width: size,
93
+ height: size,
94
+ display: "inline-flex",
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ opacity: 0.6,
98
+ fontSize: size * 0.6
99
+ },
100
+ children: "•"
101
+ }
102
+ );
103
+ }
104
+ return /* @__PURE__ */ jsx(
105
+ "span",
106
+ {
107
+ className,
108
+ style: { width: size, height: size },
109
+ dangerouslySetInnerHTML: { __html: html }
110
+ }
111
+ );
112
+ };
113
+ export {
114
+ LanguageIcon as L,
115
+ copyButton as a,
116
+ bodyBackground as b,
117
+ card as c,
118
+ expandButton as d,
119
+ expandWrap as e,
120
+ scroll as f,
121
+ scrollCollapsed as g,
122
+ hasLanguageIcon as h,
123
+ lined as i,
124
+ linedWithNumbers as j,
125
+ body as k,
126
+ lang as l,
127
+ bodyReadonly as m,
128
+ getMaterialIconSvg as n,
129
+ LANG_TO_ICON as o,
130
+ semanticClassNames as s
131
+ };
@@ -1,4 +1,4 @@
1
- .rr-code-card {
1
+ ._1pn9r4q0 {
2
2
  --rr-code-accent: #737373;
3
3
  position: relative;
4
4
  margin: 1.5rem 0;
@@ -6,16 +6,27 @@
6
6
  overflow: hidden;
7
7
  font-size: 14px;
8
8
  }
9
- .rr-code-lang {
9
+ ._1pn9r4q1 {
10
10
  position: absolute;
11
11
  bottom: 0.75rem;
12
12
  right: 0.75rem;
13
13
  z-index: 2;
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 0.375rem;
14
17
  font-size: 0.875rem;
15
18
  opacity: 0.6;
16
19
  pointer-events: none;
17
20
  }
18
- .rr-code-copy {
21
+ ._1pn9r4q2 {
22
+ display: inline-flex;
23
+ flex-shrink: 0;
24
+ }
25
+ ._1pn9r4q2 svg {
26
+ width: 100%;
27
+ height: 100%;
28
+ }
29
+ ._1pn9r4q3 {
19
30
  appearance: none;
20
31
  position: absolute;
21
32
  right: 0.5rem;
@@ -34,36 +45,36 @@
34
45
  transition: opacity 0.2s ease;
35
46
  backdrop-filter: blur(8px);
36
47
  }
37
- .rr-code-card:hover .rr-code-copy {
48
+ ._1pn9r4q0:hover ._1pn9r4q3 {
38
49
  opacity: 1;
39
50
  }
40
- .rr-code-bg {
51
+ ._1pn9r4q4 {
41
52
  position: relative;
42
53
  background: color-mix(in srgb, var(--rr-code-accent) 5%, transparent);
43
54
  padding: 1rem 0;
44
55
  }
45
- .rr-code-scroll {
56
+ ._1pn9r4q5 {
46
57
  position: relative;
47
58
  width: 100%;
48
59
  overflow: auto;
49
60
  }
50
- .rr-code-scroll-collapsed {
61
+ ._1pn9r4q6 {
51
62
  max-height: 50vh;
52
63
  overflow: hidden;
53
64
  }
54
- .rr-code-body {
65
+ ._1pn9r4q7 {
55
66
  position: relative;
56
67
  overflow: auto;
57
68
  background-color: transparent !important;
58
69
  }
59
- .rr-code-body > .shikicode.output {
70
+ ._1pn9r4q7 > .shikicode.output {
60
71
  position: static !important;
61
72
  inset: auto !important;
62
73
  }
63
- .rr-code-readonly > textarea.shikicode {
74
+ ._1pn9r4q8 > textarea.shikicode {
64
75
  display: none;
65
76
  }
66
- .rr-code-expand-wrap {
77
+ ._1pn9r4q9 {
67
78
  position: absolute;
68
79
  bottom: 0;
69
80
  left: 0;
@@ -74,7 +85,7 @@
74
85
  background: linear-gradient(to bottom, transparent 0%, var(--bg, var(--rc-bg-secondary)) 80%);
75
86
  pointer-events: none;
76
87
  }
77
- .rr-code-expand {
88
+ ._1pn9r4qa {
78
89
  appearance: none;
79
90
  border: none;
80
91
  background: none;
@@ -87,46 +98,47 @@
87
98
  opacity: 0.7;
88
99
  pointer-events: auto;
89
100
  }
90
- .rr-code-expand:hover {
101
+ ._1pn9r4qa:hover {
91
102
  opacity: 1;
92
103
  }
93
- .rr-code-card pre {
104
+ ._1pn9r4q0 pre {
94
105
  margin: 0 !important;
95
106
  padding: 0 !important;
96
107
  border-radius: 0;
97
108
  font-size: min(1em, 16px);
98
109
  }
99
- .rr-code-card pre code {
110
+ ._1pn9r4q0 pre code {
100
111
  display: flex;
101
112
  flex-direction: column;
102
113
  }
103
- .rr-code-card .shiki, .rr-code-card code, .rr-code-card pre {
114
+ ._1pn9r4q0 .shiki, ._1pn9r4q0 code, ._1pn9r4q0 pre {
104
115
  background: transparent !important;
105
116
  }
106
- .rr-code-card .shikicode.output .line::before {
117
+ ._1pn9r4q0 .shikicode.output .line::before {
107
118
  background-color: transparent !important;
119
+ color: color-mix(in srgb, var(--rc-text-secondary) 40%, transparent) !important;
108
120
  }
109
- .rr-code-card .line {
121
+ ._1pn9r4q0 .line {
110
122
  display: block;
111
123
  padding: 0 1.25rem;
112
124
  }
113
- .rr-code-card .shikicode.input.line-numbers {
125
+ ._1pn9r4q0 .shikicode.input.line-numbers {
114
126
  padding-left: calc(5em + 1.25rem);
115
127
  }
116
- .rr-code-card .shikicode.input:not(.line-numbers) {
128
+ ._1pn9r4q0 .shikicode.input:not(.line-numbers) {
117
129
  padding-left: 1.25rem;
118
130
  }
119
- .rr-code-card .line > span:last-child {
131
+ ._1pn9r4q0 .line > span:last-child {
120
132
  margin-right: 1.25rem;
121
133
  }
122
- .rr-code-card .line::after {
134
+ ._1pn9r4q0 .line::after {
123
135
  content: ' ';
124
136
  }
125
- .rr-code-card .highlighted, .rr-code-card .diff {
137
+ ._1pn9r4q0 .highlighted, ._1pn9r4q0 .diff {
126
138
  position: relative;
127
139
  overflow-wrap: break-word;
128
140
  }
129
- .rr-code-card .highlighted::before, .rr-code-card .diff::before {
141
+ ._1pn9r4q0 .highlighted::before, ._1pn9r4q0 .diff::before {
130
142
  content: '';
131
143
  position: absolute;
132
144
  left: 0;
@@ -134,43 +146,43 @@
134
146
  height: 100%;
135
147
  width: 2px;
136
148
  }
137
- .rr-code-card .highlighted {
149
+ ._1pn9r4q0 .highlighted {
138
150
  background: color-mix(in srgb, var(--rr-code-accent) 20%, transparent);
139
151
  }
140
- .rr-code-card .highlighted::before {
152
+ ._1pn9r4q0 .highlighted::before {
141
153
  background: var(--rr-code-accent);
142
154
  }
143
- .rr-code-card .diff.add {
155
+ ._1pn9r4q0 .diff.add {
144
156
  background: rgba(34, 197, 94, 0.15);
145
157
  }
146
- .rr-code-card .diff.add::before {
158
+ ._1pn9r4q0 .diff.add::before {
147
159
  background: #22c55e;
148
160
  }
149
- .rr-code-card .diff.add::after {
161
+ ._1pn9r4q0 .diff.add::after {
150
162
  content: ' +';
151
163
  position: absolute;
152
164
  left: 0;
153
165
  color: #22c55e;
154
166
  }
155
- .rr-code-card .diff.remove {
167
+ ._1pn9r4q0 .diff.remove {
156
168
  background: rgba(239, 68, 68, 0.15);
157
169
  }
158
- .rr-code-card .diff.remove::before {
170
+ ._1pn9r4q0 .diff.remove::before {
159
171
  background: #ef4444;
160
172
  }
161
- .rr-code-card .diff.remove::after {
173
+ ._1pn9r4q0 .diff.remove::after {
162
174
  content: ' -';
163
175
  position: absolute;
164
176
  left: 0;
165
177
  color: #ef4444;
166
178
  }
167
- .rr-code-lined {
179
+ ._1pn9r4qb {
168
180
  counter-reset: shiki-line 0;
169
181
  }
170
- .rr-code-lined .line {
182
+ ._1pn9r4qb .line {
171
183
  counter-increment: shiki-line 1;
172
184
  }
173
- .rr-code-lined .line::before {
185
+ ._1pn9r4qb .line::before {
174
186
  content: counter(shiki-line);
175
187
  color: transparent;
176
188
  text-align: right;
@@ -180,19 +192,57 @@
180
192
  position: sticky;
181
193
  left: 0;
182
194
  }
183
- .rr-code-lined-ln .line::before {
195
+ ._1pn9r4qc .line::before {
184
196
  color: inherit;
185
197
  opacity: 0.4;
186
198
  width: 5em;
187
199
  padding-right: 2em;
188
200
  }
189
- .rr-code-scroll pre::-webkit-scrollbar-track {
201
+ ._1pn9r4q5 pre::-webkit-scrollbar-track {
190
202
  margin-left: 1rem;
191
203
  margin-right: var(--sr-margin, 0);
192
204
  }
193
- .rr-code-scroll pre::-webkit-scrollbar {
205
+ ._1pn9r4q5 pre::-webkit-scrollbar {
194
206
  background-color: transparent !important;
195
207
  }
208
+ ._1pn9r4qd {
209
+ font-family: var(--rc-font-mono);
210
+ font-size: var(--rc-font-size-small);
211
+ background-color: var(--rc-code-bg);
212
+ border-radius: var(--rc-radius-md);
213
+ overflow: hidden;
214
+ margin: var(--rc-space-md) 0;
215
+ border: 1px solid var(--rc-border);
216
+ }
217
+ ._1pn9r4qe {
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: space-between;
221
+ padding: var(--rc-space-sm) var(--rc-space-md);
222
+ border-bottom: 1px solid var(--rc-border);
223
+ font-size: var(--rc-font-size-small);
224
+ color: var(--rc-text-secondary);
225
+ user-select: none;
226
+ }
227
+ ._1pn9r4qf {
228
+ font-family: var(--rc-font-mono);
229
+ font-size: 0.85em;
230
+ text-transform: uppercase;
231
+ letter-spacing: 0.05em;
232
+ }
233
+ ._1pn9r4qg {
234
+ appearance: none;
235
+ border: none;
236
+ background: none;
237
+ color: var(--rc-text-secondary);
238
+ cursor: pointer;
239
+ padding: var(--rc-space-xs) var(--rc-space-sm);
240
+ border-radius: var(--rc-radius-sm);
241
+ font-family: var(--rc-font-family);
242
+ font-size: var(--rc-font-size-small);
243
+ line-height: 1;
244
+ transition: color 0.15s ease, background-color 0.15s ease;
245
+ }
196
246
  .rich-code-block {
197
247
  font-family: var(--rc-font-mono);
198
248
  font-size: var(--rc-font-size-small);
@@ -260,7 +310,7 @@
260
310
  width: 2.5em;
261
311
  margin-right: var(--rc-space-md);
262
312
  text-align: right;
263
- color: color-mix(in srgb, var(--rc-text-secondary) 40%, transparent);
313
+ color: color-mix(in srgb, var(--rc-text-secondary) 40%, transparent) !important;
264
314
  opacity: 0.4;
265
315
  user-select: none;
266
316
  font-size: var(--rc-font-size-small);
package/dist/shiki.mjs ADDED
@@ -0,0 +1,33 @@
1
+ let highlighterPromise = null;
2
+ function getHighlighter() {
3
+ if (!highlighterPromise) {
4
+ highlighterPromise = import("shiki/bundle/web").then(
5
+ (mod) => mod.createHighlighter({
6
+ langs: [],
7
+ themes: ["github-light", "github-dark"]
8
+ })
9
+ );
10
+ }
11
+ return highlighterPromise;
12
+ }
13
+ async function getHighlighterWithLang(language) {
14
+ const highlighter = await getHighlighter();
15
+ if (language && language !== "text" && language !== "plaintext") {
16
+ const loaded = highlighter.getLoadedLanguages();
17
+ if (!loaded.includes(language)) {
18
+ try {
19
+ await highlighter.loadLanguage(language);
20
+ } catch {
21
+ }
22
+ }
23
+ }
24
+ return highlighter;
25
+ }
26
+ const SHIKI_THEMES = {
27
+ light: "github-light",
28
+ dark: "github-dark"
29
+ };
30
+ export {
31
+ SHIKI_THEMES,
32
+ getHighlighterWithLang
33
+ };
@@ -1,2 +1,40 @@
1
- export {};
1
+ export declare const semanticClassNames: {
2
+ readonly card: "rr-code-card";
3
+ readonly lang: "rr-code-lang";
4
+ readonly langIcon: "rr-code-lang-icon";
5
+ readonly copyButton: "rr-code-copy";
6
+ readonly bodyBackground: "rr-code-bg";
7
+ readonly scroll: "rr-code-scroll";
8
+ readonly scrollCollapsed: "rr-code-scroll-collapsed";
9
+ readonly body: "rr-code-body";
10
+ readonly bodyReadonly: "rr-code-readonly";
11
+ readonly expandWrap: "rr-code-expand-wrap";
12
+ readonly expandButton: "rr-code-expand";
13
+ readonly lined: "rr-code-lined";
14
+ readonly linedWithNumbers: "rr-code-lined-ln";
15
+ };
16
+ export declare const card: string;
17
+ export declare const lang: string;
18
+ export declare const langIcon: string;
19
+ export declare const copyButton: string;
20
+ export declare const bodyBackground: string;
21
+ export declare const scroll: string;
22
+ export declare const scrollCollapsed: string;
23
+ export declare const body: string;
24
+ export declare const bodyReadonly: string;
25
+ export declare const expandWrap: string;
26
+ export declare const expandButton: string;
27
+ export declare const lined: string;
28
+ export declare const linedWithNumbers: string;
29
+ export declare const contentSemanticClassNames: {
30
+ readonly root: "rich-code-block";
31
+ readonly header: "rich-code-block-header";
32
+ readonly lang: "rich-code-block-lang";
33
+ readonly copyButton: "rich-code-block-copy";
34
+ readonly numbered: "rich-code-block-numbered";
35
+ };
36
+ export declare const contentRoot: string;
37
+ export declare const contentHeader: string;
38
+ export declare const contentLang: string;
39
+ export declare const contentCopyButton: string;
2
40
  //# sourceMappingURL=styles.css.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.css.d.ts","sourceRoot":"","sources":["../src/styles.css.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"styles.css.d.ts","sourceRoot":"","sources":["../src/styles.css.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;CAcrB,CAAA;AAGV,eAAO,MAAM,IAAI,QAOf,CAAA;AAGF,eAAO,MAAM,IAAI,QAWf,CAAA;AAEF,eAAO,MAAM,QAAQ,QAGnB,CAAA;AAQF,eAAO,MAAM,UAAU,QAuBrB,CAAA;AAGF,eAAO,MAAM,cAAc,QAIzB,CAAA;AAGF,eAAO,MAAM,MAAM,QAIjB,CAAA;AAEF,eAAO,MAAM,eAAe,QAG1B,CAAA;AAGF,eAAO,MAAM,IAAI,QAIf,CAAA;AAEF,eAAO,MAAM,YAAY,QAAY,CAAA;AAYrC,eAAO,MAAM,UAAU,QAUrB,CAAA;AAEF,eAAO,MAAM,YAAY,QAiBvB,CAAA;AA8FF,eAAO,MAAM,KAAK,QAEhB,CAAA;AAiBF,eAAO,MAAM,gBAAgB,QAAY,CAAA;AAmBzC,eAAO,MAAM,yBAAyB;;;;;;CAM5B,CAAA;AAiFV,eAAO,MAAM,WAAW,QAA2B,CAAA;AACnD,eAAO,MAAM,aAAa,QAA6B,CAAA;AACvD,eAAO,MAAM,WAAW,QAA2B,CAAA;AACnD,eAAO,MAAM,iBAAiB,QAAiC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haklex/rich-renderer-codeblock",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Code block renderer with Shiki syntax highlighting",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,18 +17,24 @@
17
17
  "import": "./dist/constants.mjs",
18
18
  "types": "./dist/constants.d.ts"
19
19
  },
20
- "./style.css": "./dist/rich-renderer-codeblock.css"
20
+ "./style.css": "./dist/rich-renderer-codeblock.css",
21
+ "./icons": {
22
+ "import": "./dist/icons.mjs",
23
+ "types": "./dist/icons.d.ts"
24
+ }
21
25
  },
22
26
  "main": "./dist/index.mjs",
23
27
  "files": [
24
28
  "dist"
25
29
  ],
26
30
  "dependencies": {
31
+ "@iconify-json/material-icon-theme": "^1.2.22",
32
+ "@iconify/utils": "^2.3.0",
27
33
  "lucide-react": "^0.574.0",
28
34
  "shiki": "^3.21.0",
29
35
  "shikicode": "*",
30
- "@haklex/rich-style-token": "0.0.3",
31
- "@haklex/rich-editor": "0.0.3"
36
+ "@haklex/rich-editor": "0.0.5",
37
+ "@haklex/rich-style-token": "0.0.5"
32
38
  },
33
39
  "devDependencies": {
34
40
  "@types/react": "^19.0.0",