@wsxjs/wsx-marked-components 0.0.22 → 0.0.24
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.cjs +9 -1
- package/dist/index.js +3828 -935
- package/package.json +7 -5
- package/src/Code.css +529 -16
- package/src/Code.wsx +190 -3
- package/src/Markdown.wsx +11 -1
- package/src/index.ts +3 -0
- package/src/prism-wsx.ts +65 -0
package/src/Code.wsx
CHANGED
|
@@ -1,16 +1,82 @@
|
|
|
1
1
|
/** @jsxImportSource @wsxjs/wsx-core */
|
|
2
2
|
/**
|
|
3
3
|
* WSX Code Component
|
|
4
|
-
* Custom code block component for markdown rendering
|
|
4
|
+
* Custom code block component for markdown rendering with syntax highlighting.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Displays code as raw text (not rendered as HTML)
|
|
8
|
+
* - Syntax highlighting via Prism.js
|
|
9
|
+
* - Support for WSX language highlighting
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { LightComponent, autoRegister } from "@wsxjs/wsx-core";
|
|
8
13
|
import styles from "./Code.css?inline";
|
|
9
14
|
|
|
15
|
+
// Import Prism.js core
|
|
16
|
+
import Prism from "prismjs";
|
|
17
|
+
// Note: We don't import Prism's default theme here.
|
|
18
|
+
// Instead, we use custom CSS in Code.css that supports both light and dark modes.
|
|
19
|
+
|
|
20
|
+
// Import language components (in dependency order)
|
|
21
|
+
// Base languages (no dependencies)
|
|
22
|
+
import "prismjs/components/prism-markup";
|
|
23
|
+
import "prismjs/components/prism-css";
|
|
24
|
+
import "prismjs/components/prism-javascript";
|
|
25
|
+
import "prismjs/components/prism-json";
|
|
26
|
+
import "prismjs/components/prism-bash";
|
|
27
|
+
import "prismjs/components/prism-sql";
|
|
28
|
+
import "prismjs/components/prism-yaml";
|
|
29
|
+
import "prismjs/components/prism-c";
|
|
30
|
+
import "prismjs/components/prism-markdown";
|
|
31
|
+
import "prismjs/components/prism-python";
|
|
32
|
+
import "prismjs/components/prism-java";
|
|
33
|
+
import "prismjs/components/prism-rust";
|
|
34
|
+
import "prismjs/components/prism-go";
|
|
35
|
+
|
|
36
|
+
// Languages depending on base languages
|
|
37
|
+
import "prismjs/components/prism-typescript"; // depends on javascript
|
|
38
|
+
import "prismjs/components/prism-jsx"; // depends on javascript
|
|
39
|
+
import "prismjs/components/prism-cpp"; // depends on c
|
|
40
|
+
|
|
41
|
+
// Languages depending on other languages (must be after dependencies)
|
|
42
|
+
import "prismjs/components/prism-tsx"; // depends on typescript and jsx
|
|
43
|
+
|
|
44
|
+
// Import WSX language definition (depends on tsx)
|
|
45
|
+
import "./prism-wsx";
|
|
46
|
+
|
|
47
|
+
/** Language alias mapping for common language names */
|
|
48
|
+
const LANGUAGE_ALIASES: Record<string, string> = {
|
|
49
|
+
js: "javascript",
|
|
50
|
+
ts: "typescript",
|
|
51
|
+
py: "python",
|
|
52
|
+
rb: "ruby",
|
|
53
|
+
sh: "bash",
|
|
54
|
+
shell: "bash",
|
|
55
|
+
yml: "yaml",
|
|
56
|
+
html: "markup",
|
|
57
|
+
xml: "markup",
|
|
58
|
+
svg: "markup",
|
|
59
|
+
vue: "markup",
|
|
60
|
+
md: "markdown",
|
|
61
|
+
plaintext: "text",
|
|
62
|
+
plain: "text",
|
|
63
|
+
txt: "text",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Normalize language name to Prism-compatible format
|
|
68
|
+
*/
|
|
69
|
+
function normalizeLanguage(lang: string): string {
|
|
70
|
+
const normalized = lang.toLowerCase().trim();
|
|
71
|
+
return LANGUAGE_ALIASES[normalized] || normalized;
|
|
72
|
+
}
|
|
73
|
+
|
|
10
74
|
@autoRegister({ tagName: "wsx-marked-code" })
|
|
11
75
|
export default class Code extends LightComponent {
|
|
12
76
|
private code: string = "";
|
|
13
77
|
private language: string = "";
|
|
78
|
+
private codeElement: HTMLElement | null = null;
|
|
79
|
+
private isHighlighting: boolean = false;
|
|
14
80
|
|
|
15
81
|
constructor() {
|
|
16
82
|
super({
|
|
@@ -32,14 +98,135 @@ export default class Code extends LightComponent {
|
|
|
32
98
|
this.rerender();
|
|
33
99
|
}
|
|
34
100
|
|
|
101
|
+
protected onRendered() {
|
|
102
|
+
super.onRendered?.();
|
|
103
|
+
// Apply syntax highlighting after render
|
|
104
|
+
if (!this.isHighlighting && this.codeElement) {
|
|
105
|
+
this.highlightCode();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
35
109
|
render() {
|
|
110
|
+
const normalizedLang = normalizeLanguage(this.language);
|
|
111
|
+
|
|
36
112
|
return (
|
|
37
|
-
<div>
|
|
113
|
+
<div class="code-container">
|
|
38
114
|
{this.language && <span class="language-label">{this.language}</span>}
|
|
39
115
|
<pre>
|
|
40
|
-
<code
|
|
116
|
+
<code
|
|
117
|
+
class={normalizedLang ? `language-${normalizedLang}` : ""}
|
|
118
|
+
ref={(el) => {
|
|
119
|
+
this.codeElement = el as HTMLElement | null;
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{/* Code will be set via textContent in highlightCode() */}
|
|
123
|
+
</code>
|
|
41
124
|
</pre>
|
|
42
125
|
</div>
|
|
43
126
|
);
|
|
44
127
|
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Apply syntax highlighting to the code element
|
|
131
|
+
* Uses textContent to ensure code is displayed as raw text
|
|
132
|
+
*/
|
|
133
|
+
private highlightCode(): void {
|
|
134
|
+
// Prevent concurrent highlighting
|
|
135
|
+
if (this.isHighlighting) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.isHighlighting = true;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Use requestAnimationFrame to ensure DOM is fully updated
|
|
143
|
+
requestAnimationFrame(() => {
|
|
144
|
+
try {
|
|
145
|
+
if (!this.codeElement) {
|
|
146
|
+
this.isHighlighting = false;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Skip if already highlighted with same content
|
|
151
|
+
if (
|
|
152
|
+
this.codeElement.hasAttribute("data-prism-highlighted") &&
|
|
153
|
+
this.codeElement.getAttribute("data-code-hash") === this.hashCode(this.code)
|
|
154
|
+
) {
|
|
155
|
+
this.isHighlighting = false;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Set code as raw text (prevents HTML rendering)
|
|
160
|
+
this.codeElement.textContent = this.code;
|
|
161
|
+
|
|
162
|
+
// Get normalized language
|
|
163
|
+
const normalizedLang = normalizeLanguage(this.language);
|
|
164
|
+
|
|
165
|
+
// Apply syntax highlighting if language is supported
|
|
166
|
+
if (normalizedLang && Prism.languages[normalizedLang]) {
|
|
167
|
+
this.codeElement.className = `language-${normalizedLang}`;
|
|
168
|
+
Prism.highlightElement(this.codeElement);
|
|
169
|
+
} else if (normalizedLang && normalizedLang !== "text") {
|
|
170
|
+
// Language not supported, try to fall back to similar language
|
|
171
|
+
const fallback = this.getFallbackLanguage(normalizedLang);
|
|
172
|
+
if (fallback && Prism.languages[fallback]) {
|
|
173
|
+
this.codeElement.className = `language-${fallback}`;
|
|
174
|
+
Prism.highlightElement(this.codeElement);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Mark as highlighted
|
|
179
|
+
this.codeElement.setAttribute("data-prism-highlighted", "true");
|
|
180
|
+
this.codeElement.setAttribute("data-code-hash", this.hashCode(this.code));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error("[wsx-marked-code] Failed to highlight code:", error);
|
|
183
|
+
} finally {
|
|
184
|
+
this.isHighlighting = false;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error("[wsx-marked-code] Failed to schedule highlight:", error);
|
|
189
|
+
this.isHighlighting = false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get fallback language for unsupported languages
|
|
195
|
+
*/
|
|
196
|
+
private getFallbackLanguage(lang: string): string | null {
|
|
197
|
+
const fallbacks: Record<string, string> = {
|
|
198
|
+
vue: "markup",
|
|
199
|
+
svelte: "markup",
|
|
200
|
+
astro: "markup",
|
|
201
|
+
scss: "css",
|
|
202
|
+
sass: "css",
|
|
203
|
+
less: "css",
|
|
204
|
+
stylus: "css",
|
|
205
|
+
coffee: "javascript",
|
|
206
|
+
coffeescript: "javascript",
|
|
207
|
+
kotlin: "java",
|
|
208
|
+
scala: "java",
|
|
209
|
+
groovy: "java",
|
|
210
|
+
swift: "typescript",
|
|
211
|
+
dart: "typescript",
|
|
212
|
+
csharp: "typescript",
|
|
213
|
+
"c#": "typescript",
|
|
214
|
+
fsharp: "typescript",
|
|
215
|
+
"f#": "typescript",
|
|
216
|
+
};
|
|
217
|
+
return fallbacks[lang] || null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Simple hash function for content comparison
|
|
222
|
+
*/
|
|
223
|
+
private hashCode(str: string): string {
|
|
224
|
+
let hash = 0;
|
|
225
|
+
for (let i = 0; i < str.length; i++) {
|
|
226
|
+
const char = str.charCodeAt(i);
|
|
227
|
+
hash = (hash << 5) - hash + char;
|
|
228
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
229
|
+
}
|
|
230
|
+
return hash.toString(36);
|
|
231
|
+
}
|
|
45
232
|
}
|
package/src/Markdown.wsx
CHANGED
|
@@ -79,14 +79,24 @@ export default class Markdown extends LightComponent {
|
|
|
79
79
|
return { ...this.customRenderers };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* 移除 markdown 中的 frontmatter(YAML 元数据块)
|
|
84
|
+
* frontmatter 格式:文件开头的 --- 包围的 YAML 块
|
|
85
|
+
*/
|
|
86
|
+
private stripFrontmatter(markdown: string): string {
|
|
87
|
+
return markdown.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
render() {
|
|
83
91
|
if (!this.markdown) {
|
|
84
92
|
return <div class="marked-content"></div>;
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
try {
|
|
96
|
+
// 移除 frontmatter 后再解析
|
|
97
|
+
const cleanMarkdown = this.stripFrontmatter(this.markdown);
|
|
88
98
|
// Use marked.lexer to get tokens, then render with WSX JSX
|
|
89
|
-
const tokens = marked.lexer(
|
|
99
|
+
const tokens = marked.lexer(cleanMarkdown);
|
|
90
100
|
return <div class="marked-content">{this.renderTokens(tokens)}</div>;
|
|
91
101
|
} catch (error) {
|
|
92
102
|
logger.error("Failed to render markdown", error);
|
package/src/index.ts
CHANGED
|
@@ -12,5 +12,8 @@ export { default as Markdown } from "./Markdown.wsx";
|
|
|
12
12
|
// Export utilities
|
|
13
13
|
export * from "./marked-utils";
|
|
14
14
|
|
|
15
|
+
// Export Prism WSX language registration
|
|
16
|
+
export { registerWsxLanguage } from "./prism-wsx";
|
|
17
|
+
|
|
15
18
|
// Export types
|
|
16
19
|
export type { TokenRenderer, CustomRenderers, MarkdownOptions } from "./types";
|
package/src/prism-wsx.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prism.js WSX Language Definition
|
|
3
|
+
*
|
|
4
|
+
* WSX is a JSX-like syntax for Web Components.
|
|
5
|
+
* This definition extends TSX with WSX-specific patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Prism from "prismjs";
|
|
9
|
+
|
|
10
|
+
// Ensure JSX is loaded first (TSX depends on it)
|
|
11
|
+
import "prismjs/components/prism-jsx";
|
|
12
|
+
// Ensure TypeScript is loaded
|
|
13
|
+
import "prismjs/components/prism-typescript";
|
|
14
|
+
// Ensure TSX is loaded
|
|
15
|
+
import "prismjs/components/prism-tsx";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register WSX language with Prism
|
|
19
|
+
* WSX is essentially TSX with Web Components conventions
|
|
20
|
+
*/
|
|
21
|
+
export function registerWsxLanguage(): void {
|
|
22
|
+
// Check if already registered
|
|
23
|
+
if (Prism.languages.wsx) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Clone TSX as base for WSX
|
|
28
|
+
Prism.languages.wsx = Prism.languages.extend("tsx", {
|
|
29
|
+
// WSX-specific decorators (e.g., @autoRegister, @state)
|
|
30
|
+
decorator: {
|
|
31
|
+
pattern: /@[\w]+(?:\([^)]*\))?/,
|
|
32
|
+
inside: {
|
|
33
|
+
"decorator-name": {
|
|
34
|
+
pattern: /@[\w]+/,
|
|
35
|
+
alias: "function",
|
|
36
|
+
},
|
|
37
|
+
punctuation: /[(),]/,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
// Web Component tag names (custom elements with hyphens)
|
|
41
|
+
"web-component": {
|
|
42
|
+
pattern: /<\/?[a-z][\w-]*(?:-[\w-]+)+(?:\s|\/?>)/i,
|
|
43
|
+
inside: {
|
|
44
|
+
punctuation: /^<\/?|\/?>$/,
|
|
45
|
+
"tag-name": {
|
|
46
|
+
pattern: /[a-z][\w-]*(?:-[\w-]+)+/i,
|
|
47
|
+
alias: "class-name",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
// WSX specific imports
|
|
52
|
+
"wsx-import": {
|
|
53
|
+
pattern: /(?:from\s+)["']@wsxjs\/[\w-]+["']/,
|
|
54
|
+
inside: {
|
|
55
|
+
string: /["']@wsxjs\/[\w-]+["']/,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Also register as alias
|
|
61
|
+
Prism.languages.WSX = Prism.languages.wsx;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Auto-register when this module is imported
|
|
65
|
+
registerWsxLanguage();
|