juxscript 1.1.48 → 1.1.49

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,5 +1,5 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
- import { codeParser } from '../utils/codeparser.js'; // ✅ Default import
2
+ import { parseCode, renderLineWithTokens, getSyntaxHighlightCSS } from '../utils/codeparser.js';
3
3
  // Event definitions
4
4
  const TRIGGER_EVENTS = [];
5
5
  const CALLBACK_EVENTS = [];
@@ -68,7 +68,7 @@ export class Code extends BaseComponent {
68
68
  * Parse code content into individual lines with tokens
69
69
  */
70
70
  _parseLines() {
71
- return codeParser.parse(this.state.content, this.state.language);
71
+ return parseCode(this.state.content, this.state.language);
72
72
  }
73
73
  /**
74
74
  * Rebuild all line elements
@@ -81,7 +81,7 @@ export class Code extends BaseComponent {
81
81
  return;
82
82
  codeEl.innerHTML = '';
83
83
  const parsedLines = this._parseLines();
84
- const { startLine, highlightLines, showLineNumbers } = this.state;
84
+ const { startLine, highlightLines } = this.state;
85
85
  parsedLines.forEach((parsedLine, index) => {
86
86
  const lineNumber = startLine + index;
87
87
  const isHighlighted = highlightLines.includes(lineNumber);
@@ -109,7 +109,7 @@ export class Code extends BaseComponent {
109
109
  // Line content with syntax highlighting
110
110
  const lineCode = document.createElement('span');
111
111
  lineCode.className = 'jux-code-line-content';
112
- lineCode.innerHTML = codeParser.renderLine(parsedLine);
112
+ lineCode.innerHTML = renderLineWithTokens(parsedLine);
113
113
  lineEl.appendChild(lineCode);
114
114
  return lineEl;
115
115
  }
@@ -135,7 +135,7 @@ export class Code extends BaseComponent {
135
135
  if (!Code._syntaxCSSInjected) {
136
136
  const style = document.createElement('style');
137
137
  style.id = 'jux-code-syntax-css';
138
- style.textContent = codeParser.getCSS();
138
+ style.textContent = getSyntaxHighlightCSS();
139
139
  document.head.appendChild(style);
140
140
  Code._syntaxCSSInjected = true;
141
141
  }
@@ -1,5 +1,5 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
- import { codeParser } from '../utils/codeparser.js'; // ✅ Default import
2
+ import { parseCode, renderLineWithTokens, getSyntaxHighlightCSS } from '../utils/codeparser.js';
3
3
 
4
4
  // Event definitions
5
5
  const TRIGGER_EVENTS = [] as const;
@@ -103,8 +103,8 @@ export class Code extends BaseComponent<CodeState> {
103
103
  /**
104
104
  * Parse code content into individual lines with tokens
105
105
  */
106
- private _parseLines(): ReturnType<typeof codeParser.parse> {
107
- return codeParser.parse(this.state.content, this.state.language);
106
+ private _parseLines(): ReturnType<typeof parseCode> {
107
+ return parseCode(this.state.content, this.state.language);
108
108
  }
109
109
 
110
110
  /**
@@ -119,7 +119,7 @@ export class Code extends BaseComponent<CodeState> {
119
119
  codeEl.innerHTML = '';
120
120
 
121
121
  const parsedLines = this._parseLines();
122
- const { startLine, highlightLines, showLineNumbers } = this.state;
122
+ const { startLine, highlightLines } = this.state;
123
123
 
124
124
  parsedLines.forEach((parsedLine, index) => {
125
125
  const lineNumber = startLine + index;
@@ -133,7 +133,7 @@ export class Code extends BaseComponent<CodeState> {
133
133
  /**
134
134
  * Create a single line element with parsed tokens
135
135
  */
136
- private _createLineElement(parsedLine: ReturnType<typeof codeParser.parse>[0], lineNumber: number, highlighted: boolean): HTMLElement {
136
+ private _createLineElement(parsedLine: ReturnType<typeof parseCode>[0], lineNumber: number, highlighted: boolean): HTMLElement {
137
137
  const lineEl = document.createElement('div');
138
138
  lineEl.className = 'jux-code-line';
139
139
  lineEl.setAttribute('data-line', String(lineNumber));
@@ -153,7 +153,7 @@ export class Code extends BaseComponent<CodeState> {
153
153
  // Line content with syntax highlighting
154
154
  const lineCode = document.createElement('span');
155
155
  lineCode.className = 'jux-code-line-content';
156
- lineCode.innerHTML = codeParser.renderLine(parsedLine);
156
+ lineCode.innerHTML = renderLineWithTokens(parsedLine);
157
157
  lineEl.appendChild(lineCode);
158
158
 
159
159
  return lineEl;
@@ -184,7 +184,7 @@ export class Code extends BaseComponent<CodeState> {
184
184
  if (!Code._syntaxCSSInjected) {
185
185
  const style = document.createElement('style');
186
186
  style.id = 'jux-code-syntax-css';
187
- style.textContent = codeParser.getCSS();
187
+ style.textContent = getSyntaxHighlightCSS();
188
188
  document.head.appendChild(style);
189
189
  Code._syntaxCSSInjected = true;
190
190
  }
@@ -1,69 +1,32 @@
1
- /**
2
- * Token type to CSS class mapping
3
- */
4
- export declare const TOKEN_CLASS_MAP: Record<string, string>;
5
- export interface ParsedToken {
6
- type: string;
7
- value: string;
8
- start: number;
9
- end: number;
10
- line: number;
11
- column: number;
12
- className: string;
13
- }
14
1
  export interface ParsedLine {
15
2
  lineNumber: number;
16
- tokens: ParsedToken[];
3
+ html: string;
17
4
  raw: string;
18
5
  }
19
- export declare class CodeParser {
20
- private static _cache;
21
- /**
22
- * Parse code with caching
23
- */
24
- static parse(code: string, language?: string): ParsedLine[];
25
- private static _parseCode;
26
- /**
27
- * ✅ NEW: Strip TypeScript type annotations for parsing
28
- * This is a simple regex-based approach - not perfect but works for most cases
29
- */
30
- private static _stripTypeScriptTypes;
31
- /**
32
- * Clear cache (useful for development)
33
- */
34
- static clearCache(): void;
35
- }
36
- /**
37
- * Get CSS class for a token
38
- */
39
- export declare function getTokenClass(token: any): string;
40
6
  /**
41
- * Check if a word is a JavaScript keyword
7
+ * Parse code into lines with simple syntax highlighting
42
8
  */
43
- export declare function isKeyword(word: string): boolean;
9
+ export declare function parseCode(code: string, language?: string): ParsedLine[];
44
10
  /**
45
- * Render a parsed line as HTML with spans
11
+ * Render a parsed line (just returns the pre-generated HTML)
46
12
  */
47
13
  export declare function renderLineWithTokens(parsedLine: ParsedLine): string;
48
14
  /**
49
15
  * Escape HTML entities
50
16
  */
51
- export declare function escapeHtml(text: string): string;
17
+ declare function escapeHtml(text: string): string;
52
18
  /**
53
19
  * Generate CSS for syntax highlighting
54
20
  */
55
21
  export declare function getSyntaxHighlightCSS(): string;
56
22
  /**
57
- * Main parser export with utilities
23
+ * Main parser export
58
24
  */
59
- export declare const codeParser: {
60
- parse: typeof CodeParser.parse;
25
+ declare const _default: {
26
+ parse: typeof parseCode;
61
27
  renderLine: typeof renderLineWithTokens;
62
28
  getCSS: typeof getSyntaxHighlightCSS;
63
- getTokenClass: typeof getTokenClass;
64
- isKeyword: typeof isKeyword;
65
29
  escapeHtml: typeof escapeHtml;
66
- TOKEN_CLASS_MAP: Record<string, string>;
67
30
  };
68
- export default CodeParser;
31
+ export default _default;
69
32
  //# sourceMappingURL=codeparser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAqClD,CAAC;AAEF,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAwC;IAE7D;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE;IAiBzE,OAAO,CAAC,MAAM,CAAC,UAAU;IA6FzB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAcpC;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,IAAI;CAK5B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAUhD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAiCnE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAmB9C;AAID;;GAEG;AACH,eAAO,MAAM,UAAU;;;;;;;;CAQtB,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE,CAQrF;AAqCD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAsE9C;AAED;;GAEG;;;;;;;AACH,wBAKE"}
@@ -1,226 +1,62 @@
1
- import * as acorn from 'acorn';
2
1
  /**
3
- * Token type to CSS class mapping
2
+ * Simple keyword lists for highlighting
4
3
  */
5
- export const TOKEN_CLASS_MAP = {
6
- // Keywords
7
- 'Keyword': 'token-keyword',
8
- 'this': 'token-keyword',
9
- 'const': 'token-keyword',
10
- 'let': 'token-keyword',
11
- 'var': 'token-keyword',
12
- 'function': 'token-keyword',
13
- 'return': 'token-keyword',
14
- 'if': 'token-keyword',
15
- 'else': 'token-keyword',
16
- 'for': 'token-keyword',
17
- 'while': 'token-keyword',
18
- 'class': 'token-keyword',
19
- 'import': 'token-keyword',
20
- 'export': 'token-keyword',
21
- 'from': 'token-keyword',
22
- 'async': 'token-keyword',
23
- 'await': 'token-keyword',
24
- // Literals
25
- 'String': 'token-string',
26
- 'Numeric': 'token-number',
27
- 'Boolean': 'token-boolean',
28
- 'Null': 'token-null',
29
- 'RegExp': 'token-regex',
30
- // Identifiers
31
- 'Identifier': 'token-identifier',
32
- 'PrivateIdentifier': 'token-identifier',
33
- // Operators
34
- 'Punctuator': 'token-punctuation',
35
- // Comments
36
- 'LineComment': 'token-comment',
37
- 'BlockComment': 'token-comment',
38
- };
39
- export class CodeParser {
40
- /**
41
- * Parse code with caching
42
- */
43
- static parse(code, language = 'javascript') {
44
- // ✅ Guard against undefined cache
45
- if (!this._cache) {
46
- this._cache = new Map();
47
- }
48
- const cacheKey = `${language}:${code}`;
49
- if (this._cache.has(cacheKey)) {
50
- return this._cache.get(cacheKey);
51
- }
52
- const result = this._parseCode(code, language);
53
- this._cache.set(cacheKey, result);
54
- return result;
55
- }
56
- static _parseCode(code, language) {
57
- const lines = [];
58
- const codeLines = code.split('\n');
59
- // ✅ Skip parsing for non-JS languages
60
- if (!['javascript', 'typescript', 'js', 'ts', 'jsx', 'tsx'].includes(language.toLowerCase())) {
61
- codeLines.forEach((lineText, index) => {
62
- lines.push({
63
- lineNumber: index + 1,
64
- tokens: [],
65
- raw: lineText
66
- });
67
- });
68
- return lines;
69
- }
70
- // ✅ Strip TypeScript types for parsing
71
- const cleanedCode = language.toLowerCase().includes('typescript') || language.toLowerCase().includes('ts')
72
- ? this._stripTypeScriptTypes(code)
73
- : code;
74
- try {
75
- // Parse with Acorn (supports ES2020+)
76
- const tokens = [];
77
- acorn.parse(cleanedCode, {
78
- ecmaVersion: 2022,
79
- sourceType: 'module',
80
- locations: true,
81
- ranges: true,
82
- onToken: tokens,
83
- onComment: (block, text, start, end, startLoc, endLoc) => {
84
- tokens.push({
85
- type: block ? 'BlockComment' : 'LineComment',
86
- value: text,
87
- start,
88
- end,
89
- loc: { start: startLoc, end: endLoc }
90
- });
91
- }
92
- });
93
- // Sort tokens by position
94
- tokens.sort((a, b) => a.start - b.start);
95
- // Group tokens by line
96
- const tokensByLine = new Map();
97
- tokens.forEach(token => {
98
- const line = token.loc.start.line;
99
- const parsedToken = {
100
- type: token.type.label || token.type,
101
- value: token.value !== undefined ? String(token.value) : cleanedCode.substring(token.start, token.end),
102
- start: token.start,
103
- end: token.end,
104
- line: token.loc.start.line,
105
- column: token.loc.start.column,
106
- className: getTokenClass(token)
107
- };
108
- if (!tokensByLine.has(line)) {
109
- tokensByLine.set(line, []);
110
- }
111
- tokensByLine.get(line).push(parsedToken);
112
- });
113
- // ✅ Build lines with tokens (use ORIGINAL code, not cleaned)
114
- codeLines.forEach((lineText, index) => {
115
- const lineNumber = index + 1;
116
- const lineTokens = tokensByLine.get(lineNumber) || [];
117
- lines.push({
118
- lineNumber,
119
- tokens: lineTokens,
120
- raw: lineText // Use original line with types
121
- });
122
- });
123
- }
124
- catch (error) {
125
- // Fallback: if parsing fails, return plain lines
126
- console.warn('[CodeParser] Parse failed, using plain text:', error);
127
- codeLines.forEach((lineText, index) => {
128
- lines.push({
129
- lineNumber: index + 1,
130
- tokens: [],
131
- raw: lineText
132
- });
133
- });
134
- }
135
- return lines;
136
- }
137
- /**
138
- * ✅ NEW: Strip TypeScript type annotations for parsing
139
- * This is a simple regex-based approach - not perfect but works for most cases
140
- */
141
- static _stripTypeScriptTypes(code) {
142
- return code
143
- // Remove type annotations from parameters: (name: string) -> (name)
144
- .replace(/(\w+)\s*:\s*[\w<>\[\]|&]+/g, '$1')
145
- // Remove return type annotations: ): string -> )
146
- .replace(/\)\s*:\s*[\w<>\[\]|&]+/g, ')')
147
- // Remove interface/type declarations (keep as comments to preserve line numbers)
148
- .replace(/^(\s*)(interface|type)\s+\w+.*$/gm, '$1// $2 declaration')
149
- // Remove as type assertions: value as string -> value
150
- .replace(/\s+as\s+[\w<>\[\]|&]+/g, '')
151
- // Remove angle bracket generics: Array<string> -> Array
152
- .replace(/<[\w<>\[\]|&,\s]+>/g, '');
153
- }
154
- /**
155
- * Clear cache (useful for development)
156
- */
157
- static clearCache() {
158
- if (this._cache) {
159
- this._cache.clear();
160
- }
161
- }
162
- }
163
- CodeParser._cache = new Map(); // ✅ Initialize inline
4
+ const KEYWORDS = new Set([
5
+ 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue',
6
+ 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends',
7
+ 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof',
8
+ 'let', 'new', 'return', 'super', 'switch', 'this', 'throw', 'try',
9
+ 'typeof', 'var', 'void', 'while', 'with', 'yield', 'from', 'of',
10
+ 'static', 'get', 'set', 'as', 'interface', 'type', 'enum', 'namespace'
11
+ ]);
164
12
  /**
165
- * Get CSS class for a token
13
+ * Parse code into lines with simple syntax highlighting
166
14
  */
167
- export function getTokenClass(token) {
168
- const type = token.type.label || token.type;
169
- // Check if it's a keyword
170
- if (type === 'name' && token.value && isKeyword(token.value)) {
171
- return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
172
- }
173
- // Map type to class
174
- return TOKEN_CLASS_MAP[type] || 'token-default';
15
+ export function parseCode(code, language = 'javascript') {
16
+ const lines = code.split('\n');
17
+ return lines.map((line, index) => ({
18
+ lineNumber: index + 1,
19
+ html: highlightLine(line),
20
+ raw: line
21
+ }));
175
22
  }
176
23
  /**
177
- * Check if a word is a JavaScript keyword
24
+ * Highlight a single line of code
178
25
  */
179
- export function isKeyword(word) {
180
- const keywords = [
181
- 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
182
- 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
183
- 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
184
- 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
185
- 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
186
- ];
187
- return keywords.includes(word);
26
+ function highlightLine(line) {
27
+ if (!line.trim())
28
+ return ' ';
29
+ let result = line;
30
+ // 1. Comments (do first to avoid highlighting keywords in comments)
31
+ result = result.replace(/(\/\/.*$)/g, '<span class="token-comment">$1</span>');
32
+ result = result.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="token-comment">$1</span>');
33
+ // 2. Strings (avoid highlighting keywords in strings)
34
+ result = result.replace(/(['"`])(?:(?=(\\?))\2.)*?\1/g, (match) => {
35
+ return `<span class="token-string">${escapeHtml(match)}</span>`;
36
+ });
37
+ // 3. Numbers
38
+ result = result.replace(/\b(\d+\.?\d*)\b/g, '<span class="token-number">$1</span>');
39
+ // 4. Keywords (only whole words)
40
+ KEYWORDS.forEach(keyword => {
41
+ const regex = new RegExp(`\\b(${keyword})\\b`, 'g');
42
+ result = result.replace(regex, '<span class="token-keyword">$1</span>');
43
+ });
44
+ // 5. Braces, brackets, parentheses
45
+ result = result.replace(/([{}[\]()])/g, '<span class="token-punctuation">$1</span>');
46
+ // 6. Operators
47
+ result = result.replace(/([+\-*/%=<>!&|^~?:])/g, '<span class="token-operator">$1</span>');
48
+ return result || ' ';
188
49
  }
189
50
  /**
190
- * Render a parsed line as HTML with spans
51
+ * Render a parsed line (just returns the pre-generated HTML)
191
52
  */
192
53
  export function renderLineWithTokens(parsedLine) {
193
- if (parsedLine.tokens.length === 0) {
194
- // No tokens, return plain text (escaped)
195
- return escapeHtml(parsedLine.raw) || ' ';
196
- }
197
- const { raw, tokens } = parsedLine;
198
- let html = '';
199
- let lastIndex = 0;
200
- // Calculate line start position in original code
201
- const lineStartPos = tokens[0]?.start - tokens[0]?.column;
202
- tokens.forEach(token => {
203
- // Add any whitespace/text between tokens
204
- const localStart = token.start - lineStartPos;
205
- if (localStart > lastIndex) {
206
- html += escapeHtml(raw.substring(lastIndex, localStart));
207
- }
208
- // Add token with span
209
- const localEnd = token.end - lineStartPos;
210
- const tokenText = raw.substring(localStart, localEnd);
211
- html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
212
- lastIndex = localEnd;
213
- });
214
- // Add any remaining text
215
- if (lastIndex < raw.length) {
216
- html += escapeHtml(raw.substring(lastIndex));
217
- }
218
- return html || ' ';
54
+ return parsedLine.html;
219
55
  }
220
56
  /**
221
57
  * Escape HTML entities
222
58
  */
223
- export function escapeHtml(text) {
59
+ function escapeHtml(text) {
224
60
  return text
225
61
  .replace(/&/g, '&amp;')
226
62
  .replace(/</g, '&lt;')
@@ -233,34 +69,81 @@ export function escapeHtml(text) {
233
69
  */
234
70
  export function getSyntaxHighlightCSS() {
235
71
  return `
236
- /* Code Parser Syntax Highlighting */
72
+ /* Simple Syntax Highlighting */
73
+ .jux-code {
74
+ font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
75
+ font-size: 14px;
76
+ line-height: 1.6;
77
+ background: #1e1e1e;
78
+ border-radius: 8px;
79
+ overflow: hidden;
80
+ margin: 1rem 0;
81
+ }
82
+
83
+ .jux-code pre {
84
+ margin: 0;
85
+ padding: 1rem;
86
+ overflow-x: auto;
87
+ background: transparent;
88
+ }
89
+
90
+ .jux-code-lines {
91
+ display: block;
92
+ }
93
+
94
+ .jux-code-line {
95
+ display: flex;
96
+ align-items: flex-start;
97
+ min-height: 1.6em;
98
+ transition: background-color 0.2s;
99
+ }
100
+
101
+ .jux-code-line:hover {
102
+ background-color: rgba(255, 255, 255, 0.05);
103
+ }
104
+
105
+ .jux-code-line-highlight {
106
+ background-color: rgba(255, 215, 0, 0.1);
107
+ border-left: 3px solid #ffd700;
108
+ padding-left: 0.5rem;
109
+ }
110
+
111
+ .jux-code-line-number {
112
+ display: inline-block;
113
+ min-width: 3rem;
114
+ text-align: right;
115
+ padding-right: 1rem;
116
+ color: #858585;
117
+ user-select: none;
118
+ flex-shrink: 0;
119
+ }
120
+
121
+ .jux-code-line-content {
122
+ flex: 1;
123
+ color: #d4d4d4;
124
+ white-space: pre;
125
+ overflow-x: auto;
126
+ }
127
+
128
+ .jux-code-no-numbers .jux-code-line-number {
129
+ display: none;
130
+ }
131
+
132
+ /* Token colors */
237
133
  .token-keyword { color: #c678dd; font-weight: 600; }
238
134
  .token-string { color: #98c379; }
239
135
  .token-number { color: #d19a66; }
240
- .token-boolean { color: #d19a66; }
241
- .token-null { color: #d19a66; }
242
- .token-regex { color: #e06c75; }
243
- .token-identifier { color: #e5c07b; }
244
- .token-punctuation { color: #abb2bf; }
245
136
  .token-comment { color: #5c6370; font-style: italic; }
246
- .token-default { color: #abb2bf; }
247
-
248
- /* Function calls */
249
- .token-identifier + .token-punctuation:has(+ .token-punctuation) {
250
- color: #61afef; /* Function names in blue */
251
- }
137
+ .token-punctuation { color: #abb2bf; }
138
+ .token-operator { color: #56b6c2; }
252
139
  `;
253
140
  }
254
141
  /**
255
- * Main parser export with utilities
142
+ * Main parser export
256
143
  */
257
- export const codeParser = {
258
- parse: CodeParser.parse,
144
+ export default {
145
+ parse: parseCode,
259
146
  renderLine: renderLineWithTokens,
260
147
  getCSS: getSyntaxHighlightCSS,
261
- getTokenClass,
262
- isKeyword,
263
- escapeHtml,
264
- TOKEN_CLASS_MAP
148
+ escapeHtml
265
149
  };
266
- export default CodeParser;
@@ -1,278 +1,80 @@
1
- import * as acorn from 'acorn';
2
-
3
1
  /**
4
- * Token type to CSS class mapping
2
+ * Simple keyword lists for highlighting
5
3
  */
6
- export const TOKEN_CLASS_MAP: Record<string, string> = {
7
- // Keywords
8
- 'Keyword': 'token-keyword',
9
- 'this': 'token-keyword',
10
- 'const': 'token-keyword',
11
- 'let': 'token-keyword',
12
- 'var': 'token-keyword',
13
- 'function': 'token-keyword',
14
- 'return': 'token-keyword',
15
- 'if': 'token-keyword',
16
- 'else': 'token-keyword',
17
- 'for': 'token-keyword',
18
- 'while': 'token-keyword',
19
- 'class': 'token-keyword',
20
- 'import': 'token-keyword',
21
- 'export': 'token-keyword',
22
- 'from': 'token-keyword',
23
- 'async': 'token-keyword',
24
- 'await': 'token-keyword',
25
-
26
- // Literals
27
- 'String': 'token-string',
28
- 'Numeric': 'token-number',
29
- 'Boolean': 'token-boolean',
30
- 'Null': 'token-null',
31
- 'RegExp': 'token-regex',
32
-
33
- // Identifiers
34
- 'Identifier': 'token-identifier',
35
- 'PrivateIdentifier': 'token-identifier',
36
-
37
- // Operators
38
- 'Punctuator': 'token-punctuation',
39
-
40
- // Comments
41
- 'LineComment': 'token-comment',
42
- 'BlockComment': 'token-comment',
43
- };
44
-
45
- export interface ParsedToken {
46
- type: string;
47
- value: string;
48
- start: number;
49
- end: number;
50
- line: number;
51
- column: number;
52
- className: string;
53
- }
4
+ const KEYWORDS = new Set([
5
+ 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue',
6
+ 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends',
7
+ 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof',
8
+ 'let', 'new', 'return', 'super', 'switch', 'this', 'throw', 'try',
9
+ 'typeof', 'var', 'void', 'while', 'with', 'yield', 'from', 'of',
10
+ 'static', 'get', 'set', 'as', 'interface', 'type', 'enum', 'namespace'
11
+ ]);
54
12
 
55
13
  export interface ParsedLine {
56
14
  lineNumber: number;
57
- tokens: ParsedToken[];
15
+ html: string;
58
16
  raw: string;
59
17
  }
60
18
 
61
- export class CodeParser {
62
- private static _cache: Map<string, ParsedLine[]> = new Map(); // ✅ Initialize inline
63
-
64
- /**
65
- * Parse code with caching
66
- */
67
- static parse(code: string, language: string = 'javascript'): ParsedLine[] {
68
- // ✅ Guard against undefined cache
69
- if (!this._cache) {
70
- this._cache = new Map();
71
- }
72
-
73
- const cacheKey = `${language}:${code}`;
74
-
75
- if (this._cache.has(cacheKey)) {
76
- return this._cache.get(cacheKey)!;
77
- }
78
-
79
- const result = this._parseCode(code, language);
80
- this._cache.set(cacheKey, result);
81
- return result;
82
- }
83
-
84
- private static _parseCode(code: string, language: string): ParsedLine[] {
85
- const lines: ParsedLine[] = [];
86
- const codeLines = code.split('\n');
87
-
88
- // ✅ Skip parsing for non-JS languages
89
- if (!['javascript', 'typescript', 'js', 'ts', 'jsx', 'tsx'].includes(language.toLowerCase())) {
90
- codeLines.forEach((lineText, index) => {
91
- lines.push({
92
- lineNumber: index + 1,
93
- tokens: [],
94
- raw: lineText
95
- });
96
- });
97
- return lines;
98
- }
99
-
100
- // ✅ Strip TypeScript types for parsing
101
- const cleanedCode = language.toLowerCase().includes('typescript') || language.toLowerCase().includes('ts')
102
- ? this._stripTypeScriptTypes(code)
103
- : code;
104
-
105
- try {
106
- // Parse with Acorn (supports ES2020+)
107
- const tokens: any[] = [];
108
-
109
- acorn.parse(cleanedCode, {
110
- ecmaVersion: 2022,
111
- sourceType: 'module',
112
- locations: true,
113
- ranges: true,
114
- onToken: tokens,
115
- onComment: (block, text, start, end, startLoc, endLoc) => {
116
- tokens.push({
117
- type: block ? 'BlockComment' : 'LineComment',
118
- value: text,
119
- start,
120
- end,
121
- loc: { start: startLoc, end: endLoc }
122
- });
123
- }
124
- });
125
-
126
- // Sort tokens by position
127
- tokens.sort((a, b) => a.start - b.start);
128
-
129
- // Group tokens by line
130
- const tokensByLine: Map<number, ParsedToken[]> = new Map();
131
-
132
- tokens.forEach(token => {
133
- const line = token.loc.start.line;
134
- const parsedToken: ParsedToken = {
135
- type: token.type.label || token.type,
136
- value: token.value !== undefined ? String(token.value) : cleanedCode.substring(token.start, token.end),
137
- start: token.start,
138
- end: token.end,
139
- line: token.loc.start.line,
140
- column: token.loc.start.column,
141
- className: getTokenClass(token)
142
- };
143
-
144
- if (!tokensByLine.has(line)) {
145
- tokensByLine.set(line, []);
146
- }
147
- tokensByLine.get(line)!.push(parsedToken);
148
- });
149
-
150
- // ✅ Build lines with tokens (use ORIGINAL code, not cleaned)
151
- codeLines.forEach((lineText, index) => {
152
- const lineNumber = index + 1;
153
- const lineTokens = tokensByLine.get(lineNumber) || [];
154
-
155
- lines.push({
156
- lineNumber,
157
- tokens: lineTokens,
158
- raw: lineText // Use original line with types
159
- });
160
- });
161
-
162
- } catch (error) {
163
- // Fallback: if parsing fails, return plain lines
164
- console.warn('[CodeParser] Parse failed, using plain text:', error);
165
- codeLines.forEach((lineText, index) => {
166
- lines.push({
167
- lineNumber: index + 1,
168
- tokens: [],
169
- raw: lineText
170
- });
171
- });
172
- }
173
-
174
- return lines;
175
- }
176
-
177
- /**
178
- * ✅ NEW: Strip TypeScript type annotations for parsing
179
- * This is a simple regex-based approach - not perfect but works for most cases
180
- */
181
- private static _stripTypeScriptTypes(code: string): string {
182
- return code
183
- // Remove type annotations from parameters: (name: string) -> (name)
184
- .replace(/(\w+)\s*:\s*[\w<>\[\]|&]+/g, '$1')
185
- // Remove return type annotations: ): string -> )
186
- .replace(/\)\s*:\s*[\w<>\[\]|&]+/g, ')')
187
- // Remove interface/type declarations (keep as comments to preserve line numbers)
188
- .replace(/^(\s*)(interface|type)\s+\w+.*$/gm, '$1// $2 declaration')
189
- // Remove as type assertions: value as string -> value
190
- .replace(/\s+as\s+[\w<>\[\]|&]+/g, '')
191
- // Remove angle bracket generics: Array<string> -> Array
192
- .replace(/<[\w<>\[\]|&,\s]+>/g, '');
193
- }
194
-
195
- /**
196
- * Clear cache (useful for development)
197
- */
198
- static clearCache(): void {
199
- if (this._cache) {
200
- this._cache.clear();
201
- }
202
- }
203
- }
204
-
205
19
  /**
206
- * Get CSS class for a token
20
+ * Parse code into lines with simple syntax highlighting
207
21
  */
208
- export function getTokenClass(token: any): string {
209
- const type = token.type.label || token.type;
210
-
211
- // Check if it's a keyword
212
- if (type === 'name' && token.value && isKeyword(token.value)) {
213
- return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
214
- }
215
-
216
- // Map type to class
217
- return TOKEN_CLASS_MAP[type] || 'token-default';
22
+ export function parseCode(code: string, language: string = 'javascript'): ParsedLine[] {
23
+ const lines = code.split('\n');
24
+
25
+ return lines.map((line, index) => ({
26
+ lineNumber: index + 1,
27
+ html: highlightLine(line),
28
+ raw: line
29
+ }));
218
30
  }
219
31
 
220
32
  /**
221
- * Check if a word is a JavaScript keyword
33
+ * Highlight a single line of code
222
34
  */
223
- export function isKeyword(word: string): boolean {
224
- const keywords = [
225
- 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
226
- 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
227
- 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
228
- 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
229
- 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
230
- ];
231
- return keywords.includes(word);
232
- }
35
+ function highlightLine(line: string): string {
36
+ if (!line.trim()) return ' ';
233
37
 
234
- /**
235
- * Render a parsed line as HTML with spans
236
- */
237
- export function renderLineWithTokens(parsedLine: ParsedLine): string {
238
- if (parsedLine.tokens.length === 0) {
239
- // No tokens, return plain text (escaped)
240
- return escapeHtml(parsedLine.raw) || ' ';
241
- }
38
+ let result = line;
242
39
 
243
- const { raw, tokens } = parsedLine;
244
- let html = '';
245
- let lastIndex = 0;
40
+ // 1. Comments (do first to avoid highlighting keywords in comments)
41
+ result = result.replace(/(\/\/.*$)/g, '<span class="token-comment">$1</span>');
42
+ result = result.replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="token-comment">$1</span>');
246
43
 
247
- // Calculate line start position in original code
248
- const lineStartPos = tokens[0]?.start - tokens[0]?.column;
44
+ // 2. Strings (avoid highlighting keywords in strings)
45
+ result = result.replace(/(['"`])(?:(?=(\\?))\2.)*?\1/g, (match) => {
46
+ return `<span class="token-string">${escapeHtml(match)}</span>`;
47
+ });
249
48
 
250
- tokens.forEach(token => {
251
- // Add any whitespace/text between tokens
252
- const localStart = token.start - lineStartPos;
253
- if (localStart > lastIndex) {
254
- html += escapeHtml(raw.substring(lastIndex, localStart));
255
- }
49
+ // 3. Numbers
50
+ result = result.replace(/\b(\d+\.?\d*)\b/g, '<span class="token-number">$1</span>');
256
51
 
257
- // Add token with span
258
- const localEnd = token.end - lineStartPos;
259
- const tokenText = raw.substring(localStart, localEnd);
260
- html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
261
- lastIndex = localEnd;
52
+ // 4. Keywords (only whole words)
53
+ KEYWORDS.forEach(keyword => {
54
+ const regex = new RegExp(`\\b(${keyword})\\b`, 'g');
55
+ result = result.replace(regex, '<span class="token-keyword">$1</span>');
262
56
  });
263
57
 
264
- // Add any remaining text
265
- if (lastIndex < raw.length) {
266
- html += escapeHtml(raw.substring(lastIndex));
267
- }
58
+ // 5. Braces, brackets, parentheses
59
+ result = result.replace(/([{}[\]()])/g, '<span class="token-punctuation">$1</span>');
60
+
61
+ // 6. Operators
62
+ result = result.replace(/([+\-*/%=<>!&|^~?:])/g, '<span class="token-operator">$1</span>');
268
63
 
269
- return html || ' ';
64
+ return result || ' ';
65
+ }
66
+
67
+ /**
68
+ * Render a parsed line (just returns the pre-generated HTML)
69
+ */
70
+ export function renderLineWithTokens(parsedLine: ParsedLine): string {
71
+ return parsedLine.html;
270
72
  }
271
73
 
272
74
  /**
273
75
  * Escape HTML entities
274
76
  */
275
- export function escapeHtml(text: string): string {
77
+ function escapeHtml(text: string): string {
276
78
  return text
277
79
  .replace(/&/g, '&amp;')
278
80
  .replace(/</g, '&lt;')
@@ -286,38 +88,82 @@ export function escapeHtml(text: string): string {
286
88
  */
287
89
  export function getSyntaxHighlightCSS(): string {
288
90
  return `
289
- /* Code Parser Syntax Highlighting */
91
+ /* Simple Syntax Highlighting */
92
+ .jux-code {
93
+ font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
94
+ font-size: 14px;
95
+ line-height: 1.6;
96
+ background: #1e1e1e;
97
+ border-radius: 8px;
98
+ overflow: hidden;
99
+ margin: 1rem 0;
100
+ }
101
+
102
+ .jux-code pre {
103
+ margin: 0;
104
+ padding: 1rem;
105
+ overflow-x: auto;
106
+ background: transparent;
107
+ }
108
+
109
+ .jux-code-lines {
110
+ display: block;
111
+ }
112
+
113
+ .jux-code-line {
114
+ display: flex;
115
+ align-items: flex-start;
116
+ min-height: 1.6em;
117
+ transition: background-color 0.2s;
118
+ }
119
+
120
+ .jux-code-line:hover {
121
+ background-color: rgba(255, 255, 255, 0.05);
122
+ }
123
+
124
+ .jux-code-line-highlight {
125
+ background-color: rgba(255, 215, 0, 0.1);
126
+ border-left: 3px solid #ffd700;
127
+ padding-left: 0.5rem;
128
+ }
129
+
130
+ .jux-code-line-number {
131
+ display: inline-block;
132
+ min-width: 3rem;
133
+ text-align: right;
134
+ padding-right: 1rem;
135
+ color: #858585;
136
+ user-select: none;
137
+ flex-shrink: 0;
138
+ }
139
+
140
+ .jux-code-line-content {
141
+ flex: 1;
142
+ color: #d4d4d4;
143
+ white-space: pre;
144
+ overflow-x: auto;
145
+ }
146
+
147
+ .jux-code-no-numbers .jux-code-line-number {
148
+ display: none;
149
+ }
150
+
151
+ /* Token colors */
290
152
  .token-keyword { color: #c678dd; font-weight: 600; }
291
153
  .token-string { color: #98c379; }
292
154
  .token-number { color: #d19a66; }
293
- .token-boolean { color: #d19a66; }
294
- .token-null { color: #d19a66; }
295
- .token-regex { color: #e06c75; }
296
- .token-identifier { color: #e5c07b; }
297
- .token-punctuation { color: #abb2bf; }
298
155
  .token-comment { color: #5c6370; font-style: italic; }
299
- .token-default { color: #abb2bf; }
300
-
301
- /* Function calls */
302
- .token-identifier + .token-punctuation:has(+ .token-punctuation) {
303
- color: #61afef; /* Function names in blue */
304
- }
156
+ .token-punctuation { color: #abb2bf; }
157
+ .token-operator { color: #56b6c2; }
305
158
  `;
306
159
  }
307
160
 
308
-
309
-
310
161
  /**
311
- * Main parser export with utilities
162
+ * Main parser export
312
163
  */
313
- export const codeParser = {
314
- parse: CodeParser.parse,
164
+ export default {
165
+ parse: parseCode,
315
166
  renderLine: renderLineWithTokens,
316
167
  getCSS: getSyntaxHighlightCSS,
317
- getTokenClass,
318
- isKeyword,
319
- escapeHtml,
320
- TOKEN_CLASS_MAP
168
+ escapeHtml
321
169
  };
322
-
323
- export default CodeParser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.48",
3
+ "version": "1.1.49",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",