@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/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>{this.code}</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(this.markdown);
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";
@@ -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();