juxscript 1.1.47 → 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,CAAmC;IAExD;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE;IAYzE,OAAO,CAAC,MAAM,CAAC,UAAU;IA6FzB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAcpC;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,IAAI;CAG5B;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,220 +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
- const cacheKey = `${language}:${code}`;
45
- if (this._cache.has(cacheKey)) {
46
- return this._cache.get(cacheKey);
47
- }
48
- const result = this._parseCode(code, language);
49
- this._cache.set(cacheKey, result);
50
- return result;
51
- }
52
- static _parseCode(code, language) {
53
- const lines = [];
54
- const codeLines = code.split('\n');
55
- // ✅ Skip parsing for non-JS languages
56
- if (!['javascript', 'typescript', 'js', 'ts', 'jsx', 'tsx'].includes(language.toLowerCase())) {
57
- codeLines.forEach((lineText, index) => {
58
- lines.push({
59
- lineNumber: index + 1,
60
- tokens: [],
61
- raw: lineText
62
- });
63
- });
64
- return lines;
65
- }
66
- // ✅ Strip TypeScript types for parsing
67
- const cleanedCode = language.toLowerCase().includes('typescript') || language.toLowerCase().includes('ts')
68
- ? this._stripTypeScriptTypes(code)
69
- : code;
70
- try {
71
- // Parse with Acorn (supports ES2020+)
72
- const tokens = [];
73
- acorn.parse(cleanedCode, {
74
- ecmaVersion: 2022,
75
- sourceType: 'module',
76
- locations: true,
77
- ranges: true,
78
- onToken: tokens,
79
- onComment: (block, text, start, end, startLoc, endLoc) => {
80
- tokens.push({
81
- type: block ? 'BlockComment' : 'LineComment',
82
- value: text,
83
- start,
84
- end,
85
- loc: { start: startLoc, end: endLoc }
86
- });
87
- }
88
- });
89
- // Sort tokens by position
90
- tokens.sort((a, b) => a.start - b.start);
91
- // Group tokens by line
92
- const tokensByLine = new Map();
93
- tokens.forEach(token => {
94
- const line = token.loc.start.line;
95
- const parsedToken = {
96
- type: token.type.label || token.type,
97
- value: token.value !== undefined ? String(token.value) : cleanedCode.substring(token.start, token.end),
98
- start: token.start,
99
- end: token.end,
100
- line: token.loc.start.line,
101
- column: token.loc.start.column,
102
- className: getTokenClass(token)
103
- };
104
- if (!tokensByLine.has(line)) {
105
- tokensByLine.set(line, []);
106
- }
107
- tokensByLine.get(line).push(parsedToken);
108
- });
109
- // ✅ Build lines with tokens (use ORIGINAL code, not cleaned)
110
- codeLines.forEach((lineText, index) => {
111
- const lineNumber = index + 1;
112
- const lineTokens = tokensByLine.get(lineNumber) || [];
113
- lines.push({
114
- lineNumber,
115
- tokens: lineTokens,
116
- raw: lineText // Use original line with types
117
- });
118
- });
119
- }
120
- catch (error) {
121
- // Fallback: if parsing fails, return plain lines
122
- console.warn('[CodeParser] Parse failed, using plain text:', error);
123
- codeLines.forEach((lineText, index) => {
124
- lines.push({
125
- lineNumber: index + 1,
126
- tokens: [],
127
- raw: lineText
128
- });
129
- });
130
- }
131
- return lines;
132
- }
133
- /**
134
- * ✅ NEW: Strip TypeScript type annotations for parsing
135
- * This is a simple regex-based approach - not perfect but works for most cases
136
- */
137
- static _stripTypeScriptTypes(code) {
138
- return code
139
- // Remove type annotations from parameters: (name: string) -> (name)
140
- .replace(/(\w+)\s*:\s*[\w<>\[\]|&]+/g, '$1')
141
- // Remove return type annotations: ): string -> )
142
- .replace(/\)\s*:\s*[\w<>\[\]|&]+/g, ')')
143
- // Remove interface/type declarations (keep as comments to preserve line numbers)
144
- .replace(/^(\s*)(interface|type)\s+\w+.*$/gm, '$1// $2 declaration')
145
- // Remove as type assertions: value as string -> value
146
- .replace(/\s+as\s+[\w<>\[\]|&]+/g, '')
147
- // Remove angle bracket generics: Array<string> -> Array
148
- .replace(/<[\w<>\[\]|&,\s]+>/g, '');
149
- }
150
- /**
151
- * Clear cache (useful for development)
152
- */
153
- static clearCache() {
154
- this._cache.clear();
155
- }
156
- }
157
- CodeParser._cache = new Map();
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
+ ]);
158
12
  /**
159
- * Get CSS class for a token
13
+ * Parse code into lines with simple syntax highlighting
160
14
  */
161
- export function getTokenClass(token) {
162
- const type = token.type.label || token.type;
163
- // Check if it's a keyword
164
- if (type === 'name' && token.value && isKeyword(token.value)) {
165
- return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
166
- }
167
- // Map type to class
168
- 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
+ }));
169
22
  }
170
23
  /**
171
- * Check if a word is a JavaScript keyword
24
+ * Highlight a single line of code
172
25
  */
173
- export function isKeyword(word) {
174
- const keywords = [
175
- 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
176
- 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
177
- 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
178
- 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
179
- 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
180
- ];
181
- 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 || ' ';
182
49
  }
183
50
  /**
184
- * Render a parsed line as HTML with spans
51
+ * Render a parsed line (just returns the pre-generated HTML)
185
52
  */
186
53
  export function renderLineWithTokens(parsedLine) {
187
- if (parsedLine.tokens.length === 0) {
188
- // No tokens, return plain text (escaped)
189
- return escapeHtml(parsedLine.raw) || ' ';
190
- }
191
- const { raw, tokens } = parsedLine;
192
- let html = '';
193
- let lastIndex = 0;
194
- // Calculate line start position in original code
195
- const lineStartPos = tokens[0]?.start - tokens[0]?.column;
196
- tokens.forEach(token => {
197
- // Add any whitespace/text between tokens
198
- const localStart = token.start - lineStartPos;
199
- if (localStart > lastIndex) {
200
- html += escapeHtml(raw.substring(lastIndex, localStart));
201
- }
202
- // Add token with span
203
- const localEnd = token.end - lineStartPos;
204
- const tokenText = raw.substring(localStart, localEnd);
205
- html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
206
- lastIndex = localEnd;
207
- });
208
- // Add any remaining text
209
- if (lastIndex < raw.length) {
210
- html += escapeHtml(raw.substring(lastIndex));
211
- }
212
- return html || ' ';
54
+ return parsedLine.html;
213
55
  }
214
56
  /**
215
57
  * Escape HTML entities
216
58
  */
217
- export function escapeHtml(text) {
59
+ function escapeHtml(text) {
218
60
  return text
219
61
  .replace(/&/g, '&amp;')
220
62
  .replace(/</g, '&lt;')
@@ -227,34 +69,81 @@ export function escapeHtml(text) {
227
69
  */
228
70
  export function getSyntaxHighlightCSS() {
229
71
  return `
230
- /* 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 */
231
133
  .token-keyword { color: #c678dd; font-weight: 600; }
232
134
  .token-string { color: #98c379; }
233
135
  .token-number { color: #d19a66; }
234
- .token-boolean { color: #d19a66; }
235
- .token-null { color: #d19a66; }
236
- .token-regex { color: #e06c75; }
237
- .token-identifier { color: #e5c07b; }
238
- .token-punctuation { color: #abb2bf; }
239
136
  .token-comment { color: #5c6370; font-style: italic; }
240
- .token-default { color: #abb2bf; }
241
-
242
- /* Function calls */
243
- .token-identifier + .token-punctuation:has(+ .token-punctuation) {
244
- color: #61afef; /* Function names in blue */
245
- }
137
+ .token-punctuation { color: #abb2bf; }
138
+ .token-operator { color: #56b6c2; }
246
139
  `;
247
140
  }
248
141
  /**
249
- * Main parser export with utilities
142
+ * Main parser export
250
143
  */
251
- export const codeParser = {
252
- parse: CodeParser.parse,
144
+ export default {
145
+ parse: parseCode,
253
146
  renderLine: renderLineWithTokens,
254
147
  getCSS: getSyntaxHighlightCSS,
255
- getTokenClass,
256
- isKeyword,
257
- escapeHtml,
258
- TOKEN_CLASS_MAP
148
+ escapeHtml
259
149
  };
260
- export default CodeParser;
@@ -1,271 +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 = new Map<string, ParsedLine[]>();
63
-
64
- /**
65
- * Parse code with caching
66
- */
67
- static parse(code: string, language: string = 'javascript'): ParsedLine[] {
68
- const cacheKey = `${language}:${code}`;
69
-
70
- if (this._cache.has(cacheKey)) {
71
- return this._cache.get(cacheKey)!;
72
- }
73
-
74
- const result = this._parseCode(code, language);
75
- this._cache.set(cacheKey, result);
76
- return result;
77
- }
78
-
79
- private static _parseCode(code: string, language: string): ParsedLine[] {
80
- const lines: ParsedLine[] = [];
81
- const codeLines = code.split('\n');
82
-
83
- // ✅ Skip parsing for non-JS languages
84
- if (!['javascript', 'typescript', 'js', 'ts', 'jsx', 'tsx'].includes(language.toLowerCase())) {
85
- codeLines.forEach((lineText, index) => {
86
- lines.push({
87
- lineNumber: index + 1,
88
- tokens: [],
89
- raw: lineText
90
- });
91
- });
92
- return lines;
93
- }
94
-
95
- // ✅ Strip TypeScript types for parsing
96
- const cleanedCode = language.toLowerCase().includes('typescript') || language.toLowerCase().includes('ts')
97
- ? this._stripTypeScriptTypes(code)
98
- : code;
99
-
100
- try {
101
- // Parse with Acorn (supports ES2020+)
102
- const tokens: any[] = [];
103
-
104
- acorn.parse(cleanedCode, {
105
- ecmaVersion: 2022,
106
- sourceType: 'module',
107
- locations: true,
108
- ranges: true,
109
- onToken: tokens,
110
- onComment: (block, text, start, end, startLoc, endLoc) => {
111
- tokens.push({
112
- type: block ? 'BlockComment' : 'LineComment',
113
- value: text,
114
- start,
115
- end,
116
- loc: { start: startLoc, end: endLoc }
117
- });
118
- }
119
- });
120
-
121
- // Sort tokens by position
122
- tokens.sort((a, b) => a.start - b.start);
123
-
124
- // Group tokens by line
125
- const tokensByLine: Map<number, ParsedToken[]> = new Map();
126
-
127
- tokens.forEach(token => {
128
- const line = token.loc.start.line;
129
- const parsedToken: ParsedToken = {
130
- type: token.type.label || token.type,
131
- value: token.value !== undefined ? String(token.value) : cleanedCode.substring(token.start, token.end),
132
- start: token.start,
133
- end: token.end,
134
- line: token.loc.start.line,
135
- column: token.loc.start.column,
136
- className: getTokenClass(token)
137
- };
138
-
139
- if (!tokensByLine.has(line)) {
140
- tokensByLine.set(line, []);
141
- }
142
- tokensByLine.get(line)!.push(parsedToken);
143
- });
144
-
145
- // ✅ Build lines with tokens (use ORIGINAL code, not cleaned)
146
- codeLines.forEach((lineText, index) => {
147
- const lineNumber = index + 1;
148
- const lineTokens = tokensByLine.get(lineNumber) || [];
149
-
150
- lines.push({
151
- lineNumber,
152
- tokens: lineTokens,
153
- raw: lineText // Use original line with types
154
- });
155
- });
156
-
157
- } catch (error) {
158
- // Fallback: if parsing fails, return plain lines
159
- console.warn('[CodeParser] Parse failed, using plain text:', error);
160
- codeLines.forEach((lineText, index) => {
161
- lines.push({
162
- lineNumber: index + 1,
163
- tokens: [],
164
- raw: lineText
165
- });
166
- });
167
- }
168
-
169
- return lines;
170
- }
171
-
172
- /**
173
- * ✅ NEW: Strip TypeScript type annotations for parsing
174
- * This is a simple regex-based approach - not perfect but works for most cases
175
- */
176
- private static _stripTypeScriptTypes(code: string): string {
177
- return code
178
- // Remove type annotations from parameters: (name: string) -> (name)
179
- .replace(/(\w+)\s*:\s*[\w<>\[\]|&]+/g, '$1')
180
- // Remove return type annotations: ): string -> )
181
- .replace(/\)\s*:\s*[\w<>\[\]|&]+/g, ')')
182
- // Remove interface/type declarations (keep as comments to preserve line numbers)
183
- .replace(/^(\s*)(interface|type)\s+\w+.*$/gm, '$1// $2 declaration')
184
- // Remove as type assertions: value as string -> value
185
- .replace(/\s+as\s+[\w<>\[\]|&]+/g, '')
186
- // Remove angle bracket generics: Array<string> -> Array
187
- .replace(/<[\w<>\[\]|&,\s]+>/g, '');
188
- }
189
-
190
- /**
191
- * Clear cache (useful for development)
192
- */
193
- static clearCache(): void {
194
- this._cache.clear();
195
- }
196
- }
197
-
198
19
  /**
199
- * Get CSS class for a token
20
+ * Parse code into lines with simple syntax highlighting
200
21
  */
201
- export function getTokenClass(token: any): string {
202
- const type = token.type.label || token.type;
203
-
204
- // Check if it's a keyword
205
- if (type === 'name' && token.value && isKeyword(token.value)) {
206
- return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
207
- }
208
-
209
- // Map type to class
210
- 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
+ }));
211
30
  }
212
31
 
213
32
  /**
214
- * Check if a word is a JavaScript keyword
33
+ * Highlight a single line of code
215
34
  */
216
- export function isKeyword(word: string): boolean {
217
- const keywords = [
218
- 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
219
- 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
220
- 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
221
- 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
222
- 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
223
- ];
224
- return keywords.includes(word);
225
- }
35
+ function highlightLine(line: string): string {
36
+ if (!line.trim()) return ' ';
226
37
 
227
- /**
228
- * Render a parsed line as HTML with spans
229
- */
230
- export function renderLineWithTokens(parsedLine: ParsedLine): string {
231
- if (parsedLine.tokens.length === 0) {
232
- // No tokens, return plain text (escaped)
233
- return escapeHtml(parsedLine.raw) || ' ';
234
- }
38
+ let result = line;
235
39
 
236
- const { raw, tokens } = parsedLine;
237
- let html = '';
238
- 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>');
239
43
 
240
- // Calculate line start position in original code
241
- 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
+ });
242
48
 
243
- tokens.forEach(token => {
244
- // Add any whitespace/text between tokens
245
- const localStart = token.start - lineStartPos;
246
- if (localStart > lastIndex) {
247
- html += escapeHtml(raw.substring(lastIndex, localStart));
248
- }
49
+ // 3. Numbers
50
+ result = result.replace(/\b(\d+\.?\d*)\b/g, '<span class="token-number">$1</span>');
249
51
 
250
- // Add token with span
251
- const localEnd = token.end - lineStartPos;
252
- const tokenText = raw.substring(localStart, localEnd);
253
- html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
254
- 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>');
255
56
  });
256
57
 
257
- // Add any remaining text
258
- if (lastIndex < raw.length) {
259
- html += escapeHtml(raw.substring(lastIndex));
260
- }
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>');
261
63
 
262
- 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;
263
72
  }
264
73
 
265
74
  /**
266
75
  * Escape HTML entities
267
76
  */
268
- export function escapeHtml(text: string): string {
77
+ function escapeHtml(text: string): string {
269
78
  return text
270
79
  .replace(/&/g, '&amp;')
271
80
  .replace(/</g, '&lt;')
@@ -279,38 +88,82 @@ export function escapeHtml(text: string): string {
279
88
  */
280
89
  export function getSyntaxHighlightCSS(): string {
281
90
  return `
282
- /* 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 */
283
152
  .token-keyword { color: #c678dd; font-weight: 600; }
284
153
  .token-string { color: #98c379; }
285
154
  .token-number { color: #d19a66; }
286
- .token-boolean { color: #d19a66; }
287
- .token-null { color: #d19a66; }
288
- .token-regex { color: #e06c75; }
289
- .token-identifier { color: #e5c07b; }
290
- .token-punctuation { color: #abb2bf; }
291
155
  .token-comment { color: #5c6370; font-style: italic; }
292
- .token-default { color: #abb2bf; }
293
-
294
- /* Function calls */
295
- .token-identifier + .token-punctuation:has(+ .token-punctuation) {
296
- color: #61afef; /* Function names in blue */
297
- }
156
+ .token-punctuation { color: #abb2bf; }
157
+ .token-operator { color: #56b6c2; }
298
158
  `;
299
159
  }
300
160
 
301
-
302
-
303
161
  /**
304
- * Main parser export with utilities
162
+ * Main parser export
305
163
  */
306
- export const codeParser = {
307
- parse: CodeParser.parse,
164
+ export default {
165
+ parse: parseCode,
308
166
  renderLine: renderLineWithTokens,
309
167
  getCSS: getSyntaxHighlightCSS,
310
- getTokenClass,
311
- isKeyword,
312
- escapeHtml,
313
- TOKEN_CLASS_MAP
168
+ escapeHtml
314
169
  };
315
-
316
- export default CodeParser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",