juxscript 1.1.44 → 1.1.45

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.
@@ -19,6 +19,7 @@ type CodeState = {
19
19
  };
20
20
  export declare class Code extends BaseComponent<CodeState> {
21
21
  private _codeContainer;
22
+ private static _syntaxCSSInjected;
22
23
  constructor(id: string, options?: CodeOptions);
23
24
  protected getTriggerEvents(): readonly string[];
24
25
  protected getCallbackEvents(): readonly string[];
@@ -29,7 +30,7 @@ export declare class Code extends BaseComponent<CodeState> {
29
30
  startLine(value: number): this;
30
31
  highlightLines(lines: number[]): this;
31
32
  /**
32
- * Parse code content into individual lines
33
+ * Parse code content into individual lines with tokens
33
34
  */
34
35
  private _parseLines;
35
36
  /**
@@ -37,7 +38,7 @@ export declare class Code extends BaseComponent<CodeState> {
37
38
  */
38
39
  private _rebuildLines;
39
40
  /**
40
- * Create a single line element
41
+ * Create a single line element with parsed tokens
41
42
  */
42
43
  private _createLineElement;
43
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAMxD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GAAG;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,IAAK,SAAQ,aAAa,CAAC,SAAS,CAAC;IAChD,OAAO,CAAC,cAAc,CAA4B;gBAEtC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IAYjD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAsBtC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAKrC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IASrC;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CA6EnE;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAEhE"}
1
+ {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAOxD,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GAAG;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,IAAK,SAAQ,aAAa,CAAC,SAAS,CAAC;IAChD,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAS;gBAE9B,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB;IAYjD,SAAS,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAI/C,SAAS,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAIhD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAsBtC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAKrC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IASrC;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAezB,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,IAAI;CAgFnE;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI,CAEhE"}
@@ -1,4 +1,5 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
+ import { parseCode, renderLineWithTokens, getSyntaxHighlightCSS } from '../utils/codeparser.js';
2
3
  // Event definitions
3
4
  const TRIGGER_EVENTS = [];
4
5
  const CALLBACK_EVENTS = [];
@@ -64,10 +65,10 @@ export class Code extends BaseComponent {
64
65
  * PARSING & RENDERING
65
66
  * ═════════════════════════════════════════════════════════════════ */
66
67
  /**
67
- * Parse code content into individual lines
68
+ * Parse code content into individual lines with tokens
68
69
  */
69
70
  _parseLines() {
70
- return this.state.content.split('\n');
71
+ return parseCode(this.state.content, this.state.language);
71
72
  }
72
73
  /**
73
74
  * Rebuild all line elements
@@ -79,12 +80,12 @@ export class Code extends BaseComponent {
79
80
  if (!codeEl)
80
81
  return;
81
82
  codeEl.innerHTML = '';
82
- const lines = this._parseLines();
83
+ const parsedLines = this._parseLines();
83
84
  const { startLine, highlightLines, showLineNumbers } = this.state;
84
- lines.forEach((lineContent, index) => {
85
+ parsedLines.forEach((parsedLine, index) => {
85
86
  const lineNumber = startLine + index;
86
87
  const isHighlighted = highlightLines.includes(lineNumber);
87
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
88
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
88
89
  codeEl.appendChild(lineEl);
89
90
  });
90
91
  // Re-run Prism if available
@@ -93,9 +94,9 @@ export class Code extends BaseComponent {
93
94
  }
94
95
  }
95
96
  /**
96
- * Create a single line element
97
+ * Create a single line element with parsed tokens
97
98
  */
98
- _createLineElement(content, lineNumber, highlighted) {
99
+ _createLineElement(parsedLine, lineNumber, highlighted) {
99
100
  const lineEl = document.createElement('div');
100
101
  lineEl.className = 'jux-code-line';
101
102
  lineEl.setAttribute('data-line', String(lineNumber));
@@ -109,10 +110,10 @@ export class Code extends BaseComponent {
109
110
  lineNum.textContent = String(lineNumber);
110
111
  lineEl.appendChild(lineNum);
111
112
  }
112
- // Line content
113
+ // Line content with syntax highlighting
113
114
  const lineCode = document.createElement('span');
114
- lineCode.className = `jux-code-line-content language-${this.state.language}`;
115
- lineCode.textContent = content || ' '; // Preserve empty lines
115
+ lineCode.className = 'jux-code-line-content';
116
+ lineCode.innerHTML = renderLineWithTokens(parsedLine);
116
117
  lineEl.appendChild(lineCode);
117
118
  return lineEl;
118
119
  }
@@ -134,6 +135,14 @@ export class Code extends BaseComponent {
134
135
  * ═════════════════════════════════════════════════════════════════ */
135
136
  render(targetId) {
136
137
  const container = this._setupContainer(targetId);
138
+ // ✅ Inject syntax CSS once globally
139
+ if (!Code._syntaxCSSInjected) {
140
+ const style = document.createElement('style');
141
+ style.id = 'jux-code-syntax-css';
142
+ style.textContent = getSyntaxHighlightCSS();
143
+ document.head.appendChild(style);
144
+ Code._syntaxCSSInjected = true;
145
+ }
137
146
  const { language, showLineNumbers, style, class: className } = this.state;
138
147
  const wrapper = document.createElement('div');
139
148
  wrapper.className = 'jux-code';
@@ -148,13 +157,13 @@ export class Code extends BaseComponent {
148
157
  pre.className = `language-${language}`;
149
158
  const codeEl = document.createElement('code');
150
159
  codeEl.className = 'jux-code-lines';
151
- // Build initial lines
152
- const lines = this._parseLines();
160
+ // Build initial lines with parsed tokens
161
+ const parsedLines = this._parseLines();
153
162
  const { startLine, highlightLines } = this.state;
154
- lines.forEach((lineContent, index) => {
163
+ parsedLines.forEach((parsedLine, index) => {
155
164
  const lineNumber = startLine + index;
156
165
  const isHighlighted = highlightLines.includes(lineNumber);
157
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
166
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
158
167
  codeEl.appendChild(lineEl);
159
168
  });
160
169
  pre.appendChild(codeEl);
@@ -190,14 +199,10 @@ export class Code extends BaseComponent {
190
199
  });
191
200
  container.appendChild(wrapper);
192
201
  this._codeContainer = wrapper;
193
- requestAnimationFrame(() => {
194
- if (window.Prism) {
195
- window.Prism.highlightAllUnder(wrapper);
196
- }
197
- });
198
202
  return this;
199
203
  }
200
204
  }
205
+ Code._syntaxCSSInjected = false;
201
206
  export function code(id, options = {}) {
202
207
  return new Code(id, options);
203
208
  }
@@ -1,4 +1,5 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
+ import { parseCode, renderLineWithTokens, getSyntaxHighlightCSS } from '../utils/codeparser.js';
2
3
 
3
4
  // Event definitions
4
5
  const TRIGGER_EVENTS = [] as const;
@@ -26,6 +27,7 @@ type CodeState = {
26
27
 
27
28
  export class Code extends BaseComponent<CodeState> {
28
29
  private _codeContainer: HTMLElement | null = null;
30
+ private static _syntaxCSSInjected = false;
29
31
 
30
32
  constructor(id: string, options: CodeOptions = {}) {
31
33
  super(id, {
@@ -99,10 +101,10 @@ export class Code extends BaseComponent<CodeState> {
99
101
  * ═════════════════════════════════════════════════════════════════ */
100
102
 
101
103
  /**
102
- * Parse code content into individual lines
104
+ * Parse code content into individual lines with tokens
103
105
  */
104
- private _parseLines(): string[] {
105
- return this.state.content.split('\n');
106
+ private _parseLines(): ReturnType<typeof parseCode> {
107
+ return parseCode(this.state.content, this.state.language);
106
108
  }
107
109
 
108
110
  /**
@@ -116,14 +118,14 @@ export class Code extends BaseComponent<CodeState> {
116
118
 
117
119
  codeEl.innerHTML = '';
118
120
 
119
- const lines = this._parseLines();
121
+ const parsedLines = this._parseLines();
120
122
  const { startLine, highlightLines, showLineNumbers } = this.state;
121
123
 
122
- lines.forEach((lineContent, index) => {
124
+ parsedLines.forEach((parsedLine, index) => {
123
125
  const lineNumber = startLine + index;
124
126
  const isHighlighted = highlightLines.includes(lineNumber);
125
127
 
126
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
128
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
127
129
  codeEl.appendChild(lineEl);
128
130
  });
129
131
 
@@ -134,9 +136,9 @@ export class Code extends BaseComponent<CodeState> {
134
136
  }
135
137
 
136
138
  /**
137
- * Create a single line element
139
+ * Create a single line element with parsed tokens
138
140
  */
139
- private _createLineElement(content: string, lineNumber: number, highlighted: boolean): HTMLElement {
141
+ private _createLineElement(parsedLine: ReturnType<typeof parseCode>[0], lineNumber: number, highlighted: boolean): HTMLElement {
140
142
  const lineEl = document.createElement('div');
141
143
  lineEl.className = 'jux-code-line';
142
144
  lineEl.setAttribute('data-line', String(lineNumber));
@@ -153,10 +155,10 @@ export class Code extends BaseComponent<CodeState> {
153
155
  lineEl.appendChild(lineNum);
154
156
  }
155
157
 
156
- // Line content
158
+ // Line content with syntax highlighting
157
159
  const lineCode = document.createElement('span');
158
- lineCode.className = `jux-code-line-content language-${this.state.language}`;
159
- lineCode.textContent = content || ' '; // Preserve empty lines
160
+ lineCode.className = 'jux-code-line-content';
161
+ lineCode.innerHTML = renderLineWithTokens(parsedLine);
160
162
  lineEl.appendChild(lineCode);
161
163
 
162
164
  return lineEl;
@@ -183,6 +185,15 @@ export class Code extends BaseComponent<CodeState> {
183
185
  render(targetId?: string | HTMLElement | BaseComponent<any>): this {
184
186
  const container = this._setupContainer(targetId);
185
187
 
188
+ // ✅ Inject syntax CSS once globally
189
+ if (!Code._syntaxCSSInjected) {
190
+ const style = document.createElement('style');
191
+ style.id = 'jux-code-syntax-css';
192
+ style.textContent = getSyntaxHighlightCSS();
193
+ document.head.appendChild(style);
194
+ Code._syntaxCSSInjected = true;
195
+ }
196
+
186
197
  const { language, showLineNumbers, style, class: className } = this.state;
187
198
 
188
199
  const wrapper = document.createElement('div');
@@ -198,14 +209,14 @@ export class Code extends BaseComponent<CodeState> {
198
209
  const codeEl = document.createElement('code');
199
210
  codeEl.className = 'jux-code-lines';
200
211
 
201
- // Build initial lines
202
- const lines = this._parseLines();
212
+ // Build initial lines with parsed tokens
213
+ const parsedLines = this._parseLines();
203
214
  const { startLine, highlightLines } = this.state;
204
215
 
205
- lines.forEach((lineContent, index) => {
216
+ parsedLines.forEach((parsedLine, index) => {
206
217
  const lineNumber = startLine + index;
207
218
  const isHighlighted = highlightLines.includes(lineNumber);
208
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
219
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
209
220
  codeEl.appendChild(lineEl);
210
221
  });
211
222
 
@@ -249,12 +260,6 @@ export class Code extends BaseComponent<CodeState> {
249
260
  container.appendChild(wrapper);
250
261
  this._codeContainer = wrapper;
251
262
 
252
- requestAnimationFrame(() => {
253
- if ((window as any).Prism) {
254
- (window as any).Prism.highlightAllUnder(wrapper);
255
- }
256
- });
257
-
258
263
  return this;
259
264
  }
260
265
  }
@@ -0,0 +1,28 @@
1
+ export interface ParsedToken {
2
+ type: string;
3
+ value: string;
4
+ start: number;
5
+ end: number;
6
+ line: number;
7
+ column: number;
8
+ className: string;
9
+ }
10
+ export interface ParsedLine {
11
+ lineNumber: number;
12
+ tokens: ParsedToken[];
13
+ raw: string;
14
+ }
15
+ /**
16
+ * Parse JavaScript/TypeScript code using Acorn
17
+ * Returns token-level information for syntax highlighting
18
+ */
19
+ export declare function parseCode(code: string, language?: string): ParsedLine[];
20
+ /**
21
+ * Render a parsed line as HTML with spans
22
+ */
23
+ export declare function renderLineWithTokens(parsedLine: ParsedLine): string;
24
+ /**
25
+ * Generate CSS for syntax highlighting
26
+ */
27
+ export declare function getSyntaxHighlightCSS(): string;
28
+ //# sourceMappingURL=codeparser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"AA4CA,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;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE,CA0ErF;AA+BD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAiCnE;AAcD;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAmB9C"}
@@ -0,0 +1,198 @@
1
+ import * as acorn from 'acorn';
2
+ /**
3
+ * Token type to CSS class mapping
4
+ */
5
+ 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
+ /**
40
+ * Parse JavaScript/TypeScript code using Acorn
41
+ * Returns token-level information for syntax highlighting
42
+ */
43
+ export function parseCode(code, language = 'javascript') {
44
+ const lines = [];
45
+ const codeLines = code.split('\n');
46
+ try {
47
+ // Parse with Acorn (supports ES2020+)
48
+ const tokens = [];
49
+ acorn.parse(code, {
50
+ ecmaVersion: 2022,
51
+ sourceType: 'module',
52
+ locations: true,
53
+ ranges: true,
54
+ onToken: tokens,
55
+ onComment: (block, text, start, end, startLoc, endLoc) => {
56
+ tokens.push({
57
+ type: block ? 'BlockComment' : 'LineComment',
58
+ value: text,
59
+ start,
60
+ end,
61
+ loc: { start: startLoc, end: endLoc }
62
+ });
63
+ }
64
+ });
65
+ // Sort tokens by position
66
+ tokens.sort((a, b) => a.start - b.start);
67
+ // Group tokens by line
68
+ const tokensByLine = new Map();
69
+ tokens.forEach(token => {
70
+ const line = token.loc.start.line;
71
+ const parsedToken = {
72
+ type: token.type.label || token.type,
73
+ value: token.value !== undefined ? String(token.value) : code.substring(token.start, token.end),
74
+ start: token.start,
75
+ end: token.end,
76
+ line: token.loc.start.line,
77
+ column: token.loc.start.column,
78
+ className: getTokenClass(token)
79
+ };
80
+ if (!tokensByLine.has(line)) {
81
+ tokensByLine.set(line, []);
82
+ }
83
+ tokensByLine.get(line).push(parsedToken);
84
+ });
85
+ // Build lines with tokens
86
+ codeLines.forEach((lineText, index) => {
87
+ const lineNumber = index + 1;
88
+ const lineTokens = tokensByLine.get(lineNumber) || [];
89
+ lines.push({
90
+ lineNumber,
91
+ tokens: lineTokens,
92
+ raw: lineText
93
+ });
94
+ });
95
+ }
96
+ catch (error) {
97
+ // Fallback: if parsing fails, return plain lines
98
+ console.warn('[CodeParser] Parse failed, using plain text:', error);
99
+ codeLines.forEach((lineText, index) => {
100
+ lines.push({
101
+ lineNumber: index + 1,
102
+ tokens: [],
103
+ raw: lineText
104
+ });
105
+ });
106
+ }
107
+ return lines;
108
+ }
109
+ /**
110
+ * Get CSS class for a token
111
+ */
112
+ function getTokenClass(token) {
113
+ const type = token.type.label || token.type;
114
+ // Check if it's a keyword
115
+ if (type === 'name' && token.value && isKeyword(token.value)) {
116
+ return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
117
+ }
118
+ // Map type to class
119
+ return TOKEN_CLASS_MAP[type] || 'token-default';
120
+ }
121
+ /**
122
+ * Check if a word is a JavaScript keyword
123
+ */
124
+ function isKeyword(word) {
125
+ const keywords = [
126
+ 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
127
+ 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
128
+ 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
129
+ 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
130
+ 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
131
+ ];
132
+ return keywords.includes(word);
133
+ }
134
+ /**
135
+ * Render a parsed line as HTML with spans
136
+ */
137
+ export function renderLineWithTokens(parsedLine) {
138
+ if (parsedLine.tokens.length === 0) {
139
+ // No tokens, return plain text (escaped)
140
+ return escapeHtml(parsedLine.raw) || ' ';
141
+ }
142
+ const { raw, tokens } = parsedLine;
143
+ let html = '';
144
+ let lastIndex = 0;
145
+ // Calculate line start position in original code
146
+ const lineStartPos = tokens[0]?.start - tokens[0]?.column;
147
+ tokens.forEach(token => {
148
+ // Add any whitespace/text between tokens
149
+ const localStart = token.start - lineStartPos;
150
+ if (localStart > lastIndex) {
151
+ html += escapeHtml(raw.substring(lastIndex, localStart));
152
+ }
153
+ // Add token with span
154
+ const localEnd = token.end - lineStartPos;
155
+ const tokenText = raw.substring(localStart, localEnd);
156
+ html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
157
+ lastIndex = localEnd;
158
+ });
159
+ // Add any remaining text
160
+ if (lastIndex < raw.length) {
161
+ html += escapeHtml(raw.substring(lastIndex));
162
+ }
163
+ return html || ' ';
164
+ }
165
+ /**
166
+ * Escape HTML entities
167
+ */
168
+ function escapeHtml(text) {
169
+ return text
170
+ .replace(/&/g, '&amp;')
171
+ .replace(/</g, '&lt;')
172
+ .replace(/>/g, '&gt;')
173
+ .replace(/"/g, '&quot;')
174
+ .replace(/'/g, '&#39;');
175
+ }
176
+ /**
177
+ * Generate CSS for syntax highlighting
178
+ */
179
+ export function getSyntaxHighlightCSS() {
180
+ return `
181
+ /* Code Parser Syntax Highlighting */
182
+ .token-keyword { color: #c678dd; font-weight: 600; }
183
+ .token-string { color: #98c379; }
184
+ .token-number { color: #d19a66; }
185
+ .token-boolean { color: #d19a66; }
186
+ .token-null { color: #d19a66; }
187
+ .token-regex { color: #e06c75; }
188
+ .token-identifier { color: #e5c07b; }
189
+ .token-punctuation { color: #abb2bf; }
190
+ .token-comment { color: #5c6370; font-style: italic; }
191
+ .token-default { color: #abb2bf; }
192
+
193
+ /* Function calls */
194
+ .token-identifier + .token-punctuation:has(+ .token-punctuation) {
195
+ color: #61afef; /* Function names in blue */
196
+ }
197
+ `;
198
+ }
@@ -0,0 +1,242 @@
1
+ import * as acorn from 'acorn';
2
+
3
+ /**
4
+ * Token type to CSS class mapping
5
+ */
6
+ 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
+ }
54
+
55
+ export interface ParsedLine {
56
+ lineNumber: number;
57
+ tokens: ParsedToken[];
58
+ raw: string;
59
+ }
60
+
61
+ /**
62
+ * Parse JavaScript/TypeScript code using Acorn
63
+ * Returns token-level information for syntax highlighting
64
+ */
65
+ export function parseCode(code: string, language: string = 'javascript'): ParsedLine[] {
66
+ const lines: ParsedLine[] = [];
67
+ const codeLines = code.split('\n');
68
+
69
+ try {
70
+ // Parse with Acorn (supports ES2020+)
71
+ const tokens: any[] = [];
72
+
73
+ acorn.parse(code, {
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
+
90
+ // Sort tokens by position
91
+ tokens.sort((a, b) => a.start - b.start);
92
+
93
+ // Group tokens by line
94
+ const tokensByLine: Map<number, ParsedToken[]> = new Map();
95
+
96
+ tokens.forEach(token => {
97
+ const line = token.loc.start.line;
98
+ const parsedToken: ParsedToken = {
99
+ type: token.type.label || token.type,
100
+ value: token.value !== undefined ? String(token.value) : code.substring(token.start, token.end),
101
+ start: token.start,
102
+ end: token.end,
103
+ line: token.loc.start.line,
104
+ column: token.loc.start.column,
105
+ className: getTokenClass(token)
106
+ };
107
+
108
+ if (!tokensByLine.has(line)) {
109
+ tokensByLine.set(line, []);
110
+ }
111
+ tokensByLine.get(line)!.push(parsedToken);
112
+ });
113
+
114
+ // Build lines with tokens
115
+ codeLines.forEach((lineText, index) => {
116
+ const lineNumber = index + 1;
117
+ const lineTokens = tokensByLine.get(lineNumber) || [];
118
+
119
+ lines.push({
120
+ lineNumber,
121
+ tokens: lineTokens,
122
+ raw: lineText
123
+ });
124
+ });
125
+
126
+ } catch (error) {
127
+ // Fallback: if parsing fails, return plain lines
128
+ console.warn('[CodeParser] Parse failed, using plain text:', error);
129
+ codeLines.forEach((lineText, index) => {
130
+ lines.push({
131
+ lineNumber: index + 1,
132
+ tokens: [],
133
+ raw: lineText
134
+ });
135
+ });
136
+ }
137
+
138
+ return lines;
139
+ }
140
+
141
+ /**
142
+ * Get CSS class for a token
143
+ */
144
+ function getTokenClass(token: any): string {
145
+ const type = token.type.label || token.type;
146
+
147
+ // Check if it's a keyword
148
+ if (type === 'name' && token.value && isKeyword(token.value)) {
149
+ return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
150
+ }
151
+
152
+ // Map type to class
153
+ return TOKEN_CLASS_MAP[type] || 'token-default';
154
+ }
155
+
156
+ /**
157
+ * Check if a word is a JavaScript keyword
158
+ */
159
+ function isKeyword(word: string): boolean {
160
+ const keywords = [
161
+ 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
162
+ 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
163
+ 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
164
+ 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
165
+ 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
166
+ ];
167
+ return keywords.includes(word);
168
+ }
169
+
170
+ /**
171
+ * Render a parsed line as HTML with spans
172
+ */
173
+ export function renderLineWithTokens(parsedLine: ParsedLine): string {
174
+ if (parsedLine.tokens.length === 0) {
175
+ // No tokens, return plain text (escaped)
176
+ return escapeHtml(parsedLine.raw) || ' ';
177
+ }
178
+
179
+ const { raw, tokens } = parsedLine;
180
+ let html = '';
181
+ let lastIndex = 0;
182
+
183
+ // Calculate line start position in original code
184
+ const lineStartPos = tokens[0]?.start - tokens[0]?.column;
185
+
186
+ tokens.forEach(token => {
187
+ // Add any whitespace/text between tokens
188
+ const localStart = token.start - lineStartPos;
189
+ if (localStart > lastIndex) {
190
+ html += escapeHtml(raw.substring(lastIndex, localStart));
191
+ }
192
+
193
+ // Add token with span
194
+ const localEnd = token.end - lineStartPos;
195
+ const tokenText = raw.substring(localStart, localEnd);
196
+ html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
197
+ lastIndex = localEnd;
198
+ });
199
+
200
+ // Add any remaining text
201
+ if (lastIndex < raw.length) {
202
+ html += escapeHtml(raw.substring(lastIndex));
203
+ }
204
+
205
+ return html || ' ';
206
+ }
207
+
208
+ /**
209
+ * Escape HTML entities
210
+ */
211
+ function escapeHtml(text: string): string {
212
+ return text
213
+ .replace(/&/g, '&amp;')
214
+ .replace(/</g, '&lt;')
215
+ .replace(/>/g, '&gt;')
216
+ .replace(/"/g, '&quot;')
217
+ .replace(/'/g, '&#39;');
218
+ }
219
+
220
+ /**
221
+ * Generate CSS for syntax highlighting
222
+ */
223
+ export function getSyntaxHighlightCSS(): string {
224
+ return `
225
+ /* Code Parser Syntax Highlighting */
226
+ .token-keyword { color: #c678dd; font-weight: 600; }
227
+ .token-string { color: #98c379; }
228
+ .token-number { color: #d19a66; }
229
+ .token-boolean { color: #d19a66; }
230
+ .token-null { color: #d19a66; }
231
+ .token-regex { color: #e06c75; }
232
+ .token-identifier { color: #e5c07b; }
233
+ .token-punctuation { color: #abb2bf; }
234
+ .token-comment { color: #5c6370; font-style: italic; }
235
+ .token-default { color: #abb2bf; }
236
+
237
+ /* Function calls */
238
+ .token-identifier + .token-punctuation:has(+ .token-punctuation) {
239
+ color: #61afef; /* Function names in blue */
240
+ }
241
+ `;
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.44",
3
+ "version": "1.1.45",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",