juxscript 1.1.44 → 1.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
@@ -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;IAoBrB;;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 { codeParser } from '../utils/codeparser.js'; // ✅ Default import
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 codeParser.parse(this.state.content, this.state.language);
71
72
  }
72
73
  /**
73
74
  * Rebuild all line elements
@@ -79,23 +80,19 @@ 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
- // Re-run Prism if available
91
- if (window.Prism && this._codeContainer) {
92
- window.Prism.highlightAllUnder(this._codeContainer);
93
- }
94
91
  }
95
92
  /**
96
- * Create a single line element
93
+ * Create a single line element with parsed tokens
97
94
  */
98
- _createLineElement(content, lineNumber, highlighted) {
95
+ _createLineElement(parsedLine, lineNumber, highlighted) {
99
96
  const lineEl = document.createElement('div');
100
97
  lineEl.className = 'jux-code-line';
101
98
  lineEl.setAttribute('data-line', String(lineNumber));
@@ -109,10 +106,10 @@ export class Code extends BaseComponent {
109
106
  lineNum.textContent = String(lineNumber);
110
107
  lineEl.appendChild(lineNum);
111
108
  }
112
- // Line content
109
+ // Line content with syntax highlighting
113
110
  const lineCode = document.createElement('span');
114
- lineCode.className = `jux-code-line-content language-${this.state.language}`;
115
- lineCode.textContent = content || ' '; // Preserve empty lines
111
+ lineCode.className = 'jux-code-line-content';
112
+ lineCode.innerHTML = codeParser.renderLine(parsedLine);
116
113
  lineEl.appendChild(lineCode);
117
114
  return lineEl;
118
115
  }
@@ -134,6 +131,14 @@ export class Code extends BaseComponent {
134
131
  * ═════════════════════════════════════════════════════════════════ */
135
132
  render(targetId) {
136
133
  const container = this._setupContainer(targetId);
134
+ // ✅ Inject syntax CSS once globally
135
+ if (!Code._syntaxCSSInjected) {
136
+ const style = document.createElement('style');
137
+ style.id = 'jux-code-syntax-css';
138
+ style.textContent = codeParser.getCSS();
139
+ document.head.appendChild(style);
140
+ Code._syntaxCSSInjected = true;
141
+ }
137
142
  const { language, showLineNumbers, style, class: className } = this.state;
138
143
  const wrapper = document.createElement('div');
139
144
  wrapper.className = 'jux-code';
@@ -148,13 +153,13 @@ export class Code extends BaseComponent {
148
153
  pre.className = `language-${language}`;
149
154
  const codeEl = document.createElement('code');
150
155
  codeEl.className = 'jux-code-lines';
151
- // Build initial lines
152
- const lines = this._parseLines();
156
+ // Build initial lines with parsed tokens
157
+ const parsedLines = this._parseLines();
153
158
  const { startLine, highlightLines } = this.state;
154
- lines.forEach((lineContent, index) => {
159
+ parsedLines.forEach((parsedLine, index) => {
155
160
  const lineNumber = startLine + index;
156
161
  const isHighlighted = highlightLines.includes(lineNumber);
157
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
162
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
158
163
  codeEl.appendChild(lineEl);
159
164
  });
160
165
  pre.appendChild(codeEl);
@@ -190,14 +195,10 @@ export class Code extends BaseComponent {
190
195
  });
191
196
  container.appendChild(wrapper);
192
197
  this._codeContainer = wrapper;
193
- requestAnimationFrame(() => {
194
- if (window.Prism) {
195
- window.Prism.highlightAllUnder(wrapper);
196
- }
197
- });
198
198
  return this;
199
199
  }
200
200
  }
201
+ Code._syntaxCSSInjected = false;
201
202
  export function code(id, options = {}) {
202
203
  return new Code(id, options);
203
204
  }
@@ -1,4 +1,5 @@
1
1
  import { BaseComponent } from './base/BaseComponent.js';
2
+ import { codeParser } from '../utils/codeparser.js'; // ✅ Default import
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 codeParser.parse> {
107
+ return codeParser.parse(this.state.content, this.state.language);
106
108
  }
107
109
 
108
110
  /**
@@ -116,27 +118,22 @@ 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
-
126
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
127
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
127
128
  codeEl.appendChild(lineEl);
128
129
  });
129
130
 
130
- // Re-run Prism if available
131
- if ((window as any).Prism && this._codeContainer) {
132
- (window as any).Prism.highlightAllUnder(this._codeContainer);
133
- }
134
131
  }
135
132
 
136
133
  /**
137
- * Create a single line element
134
+ * Create a single line element with parsed tokens
138
135
  */
139
- private _createLineElement(content: string, lineNumber: number, highlighted: boolean): HTMLElement {
136
+ private _createLineElement(parsedLine: ReturnType<typeof codeParser.parse>[0], lineNumber: number, highlighted: boolean): HTMLElement {
140
137
  const lineEl = document.createElement('div');
141
138
  lineEl.className = 'jux-code-line';
142
139
  lineEl.setAttribute('data-line', String(lineNumber));
@@ -153,10 +150,10 @@ export class Code extends BaseComponent<CodeState> {
153
150
  lineEl.appendChild(lineNum);
154
151
  }
155
152
 
156
- // Line content
153
+ // Line content with syntax highlighting
157
154
  const lineCode = document.createElement('span');
158
- lineCode.className = `jux-code-line-content language-${this.state.language}`;
159
- lineCode.textContent = content || ' '; // Preserve empty lines
155
+ lineCode.className = 'jux-code-line-content';
156
+ lineCode.innerHTML = codeParser.renderLine(parsedLine);
160
157
  lineEl.appendChild(lineCode);
161
158
 
162
159
  return lineEl;
@@ -183,6 +180,15 @@ export class Code extends BaseComponent<CodeState> {
183
180
  render(targetId?: string | HTMLElement | BaseComponent<any>): this {
184
181
  const container = this._setupContainer(targetId);
185
182
 
183
+ // ✅ Inject syntax CSS once globally
184
+ if (!Code._syntaxCSSInjected) {
185
+ const style = document.createElement('style');
186
+ style.id = 'jux-code-syntax-css';
187
+ style.textContent = codeParser.getCSS();
188
+ document.head.appendChild(style);
189
+ Code._syntaxCSSInjected = true;
190
+ }
191
+
186
192
  const { language, showLineNumbers, style, class: className } = this.state;
187
193
 
188
194
  const wrapper = document.createElement('div');
@@ -198,14 +204,14 @@ export class Code extends BaseComponent<CodeState> {
198
204
  const codeEl = document.createElement('code');
199
205
  codeEl.className = 'jux-code-lines';
200
206
 
201
- // Build initial lines
202
- const lines = this._parseLines();
207
+ // Build initial lines with parsed tokens
208
+ const parsedLines = this._parseLines();
203
209
  const { startLine, highlightLines } = this.state;
204
210
 
205
- lines.forEach((lineContent, index) => {
211
+ parsedLines.forEach((parsedLine, index) => {
206
212
  const lineNumber = startLine + index;
207
213
  const isHighlighted = highlightLines.includes(lineNumber);
208
- const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
214
+ const lineEl = this._createLineElement(parsedLine, lineNumber, isHighlighted);
209
215
  codeEl.appendChild(lineEl);
210
216
  });
211
217
 
@@ -249,12 +255,6 @@ export class Code extends BaseComponent<CodeState> {
249
255
  container.appendChild(wrapper);
250
256
  this._codeContainer = wrapper;
251
257
 
252
- requestAnimationFrame(() => {
253
- if ((window as any).Prism) {
254
- (window as any).Prism.highlightAllUnder(wrapper);
255
- }
256
- });
257
-
258
258
  return this;
259
259
  }
260
260
  }
@@ -0,0 +1,60 @@
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
+ export interface ParsedLine {
15
+ lineNumber: number;
16
+ tokens: ParsedToken[];
17
+ raw: string;
18
+ }
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
+ /**
28
+ * Get CSS class for a token
29
+ */
30
+ export declare function getTokenClass(token: any): string;
31
+ /**
32
+ * Check if a word is a JavaScript keyword
33
+ */
34
+ export declare function isKeyword(word: string): boolean;
35
+ /**
36
+ * Render a parsed line as HTML with spans
37
+ */
38
+ export declare function renderLineWithTokens(parsedLine: ParsedLine): string;
39
+ /**
40
+ * Escape HTML entities
41
+ */
42
+ export declare function escapeHtml(text: string): string;
43
+ /**
44
+ * Generate CSS for syntax highlighting
45
+ */
46
+ export declare function getSyntaxHighlightCSS(): string;
47
+ /**
48
+ * Main parser export with utilities
49
+ */
50
+ export declare const codeParser: {
51
+ parse: typeof CodeParser.parse;
52
+ renderLine: typeof renderLineWithTokens;
53
+ getCSS: typeof getSyntaxHighlightCSS;
54
+ getTokenClass: typeof getTokenClass;
55
+ isKeyword: typeof isKeyword;
56
+ escapeHtml: typeof escapeHtml;
57
+ TOKEN_CLASS_MAP: Record<string, string>;
58
+ };
59
+ export default CodeParser;
60
+ //# sourceMappingURL=codeparser.d.ts.map
@@ -0,0 +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;CA2E5B;AACD;;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"}
@@ -0,0 +1,222 @@
1
+ import * as acorn from 'acorn';
2
+ /**
3
+ * Token type to CSS class mapping
4
+ */
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
+ try {
56
+ // Parse with Acorn (supports ES2020+)
57
+ const tokens = [];
58
+ acorn.parse(code, {
59
+ ecmaVersion: 2022,
60
+ sourceType: 'module',
61
+ locations: true,
62
+ ranges: true,
63
+ onToken: tokens,
64
+ onComment: (block, text, start, end, startLoc, endLoc) => {
65
+ tokens.push({
66
+ type: block ? 'BlockComment' : 'LineComment',
67
+ value: text,
68
+ start,
69
+ end,
70
+ loc: { start: startLoc, end: endLoc }
71
+ });
72
+ }
73
+ });
74
+ // Sort tokens by position
75
+ tokens.sort((a, b) => a.start - b.start);
76
+ // Group tokens by line
77
+ const tokensByLine = new Map();
78
+ tokens.forEach(token => {
79
+ const line = token.loc.start.line;
80
+ const parsedToken = {
81
+ type: token.type.label || token.type,
82
+ value: token.value !== undefined ? String(token.value) : code.substring(token.start, token.end),
83
+ start: token.start,
84
+ end: token.end,
85
+ line: token.loc.start.line,
86
+ column: token.loc.start.column,
87
+ className: getTokenClass(token)
88
+ };
89
+ if (!tokensByLine.has(line)) {
90
+ tokensByLine.set(line, []);
91
+ }
92
+ tokensByLine.get(line).push(parsedToken);
93
+ });
94
+ // Build lines with tokens
95
+ codeLines.forEach((lineText, index) => {
96
+ const lineNumber = index + 1;
97
+ const lineTokens = tokensByLine.get(lineNumber) || [];
98
+ lines.push({
99
+ lineNumber,
100
+ tokens: lineTokens,
101
+ raw: lineText
102
+ });
103
+ });
104
+ }
105
+ catch (error) {
106
+ // Fallback: if parsing fails, return plain lines
107
+ console.warn('[CodeParser] Parse failed, using plain text:', error);
108
+ codeLines.forEach((lineText, index) => {
109
+ lines.push({
110
+ lineNumber: index + 1,
111
+ tokens: [],
112
+ raw: lineText
113
+ });
114
+ });
115
+ }
116
+ return lines;
117
+ }
118
+ }
119
+ CodeParser._cache = new Map();
120
+ /**
121
+ * Get CSS class for a token
122
+ */
123
+ export function getTokenClass(token) {
124
+ const type = token.type.label || token.type;
125
+ // Check if it's a keyword
126
+ if (type === 'name' && token.value && isKeyword(token.value)) {
127
+ return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
128
+ }
129
+ // Map type to class
130
+ return TOKEN_CLASS_MAP[type] || 'token-default';
131
+ }
132
+ /**
133
+ * Check if a word is a JavaScript keyword
134
+ */
135
+ export function isKeyword(word) {
136
+ const keywords = [
137
+ 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
138
+ 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for',
139
+ 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'return',
140
+ 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void',
141
+ 'while', 'with', 'yield', 'async', 'of', 'static', 'get', 'set'
142
+ ];
143
+ return keywords.includes(word);
144
+ }
145
+ /**
146
+ * Render a parsed line as HTML with spans
147
+ */
148
+ export function renderLineWithTokens(parsedLine) {
149
+ if (parsedLine.tokens.length === 0) {
150
+ // No tokens, return plain text (escaped)
151
+ return escapeHtml(parsedLine.raw) || ' ';
152
+ }
153
+ const { raw, tokens } = parsedLine;
154
+ let html = '';
155
+ let lastIndex = 0;
156
+ // Calculate line start position in original code
157
+ const lineStartPos = tokens[0]?.start - tokens[0]?.column;
158
+ tokens.forEach(token => {
159
+ // Add any whitespace/text between tokens
160
+ const localStart = token.start - lineStartPos;
161
+ if (localStart > lastIndex) {
162
+ html += escapeHtml(raw.substring(lastIndex, localStart));
163
+ }
164
+ // Add token with span
165
+ const localEnd = token.end - lineStartPos;
166
+ const tokenText = raw.substring(localStart, localEnd);
167
+ html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
168
+ lastIndex = localEnd;
169
+ });
170
+ // Add any remaining text
171
+ if (lastIndex < raw.length) {
172
+ html += escapeHtml(raw.substring(lastIndex));
173
+ }
174
+ return html || ' ';
175
+ }
176
+ /**
177
+ * Escape HTML entities
178
+ */
179
+ export function escapeHtml(text) {
180
+ return text
181
+ .replace(/&/g, '&amp;')
182
+ .replace(/</g, '&lt;')
183
+ .replace(/>/g, '&gt;')
184
+ .replace(/"/g, '&quot;')
185
+ .replace(/'/g, '&#39;');
186
+ }
187
+ /**
188
+ * Generate CSS for syntax highlighting
189
+ */
190
+ export function getSyntaxHighlightCSS() {
191
+ return `
192
+ /* Code Parser Syntax Highlighting */
193
+ .token-keyword { color: #c678dd; font-weight: 600; }
194
+ .token-string { color: #98c379; }
195
+ .token-number { color: #d19a66; }
196
+ .token-boolean { color: #d19a66; }
197
+ .token-null { color: #d19a66; }
198
+ .token-regex { color: #e06c75; }
199
+ .token-identifier { color: #e5c07b; }
200
+ .token-punctuation { color: #abb2bf; }
201
+ .token-comment { color: #5c6370; font-style: italic; }
202
+ .token-default { color: #abb2bf; }
203
+
204
+ /* Function calls */
205
+ .token-identifier + .token-punctuation:has(+ .token-punctuation) {
206
+ color: #61afef; /* Function names in blue */
207
+ }
208
+ `;
209
+ }
210
+ /**
211
+ * Main parser export with utilities
212
+ */
213
+ export const codeParser = {
214
+ parse: CodeParser.parse,
215
+ renderLine: renderLineWithTokens,
216
+ getCSS: getSyntaxHighlightCSS,
217
+ getTokenClass,
218
+ isKeyword,
219
+ escapeHtml,
220
+ TOKEN_CLASS_MAP
221
+ };
222
+ export default CodeParser;
@@ -0,0 +1,273 @@
1
+ import * as acorn from 'acorn';
2
+
3
+ /**
4
+ * Token type to CSS class mapping
5
+ */
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
+ }
54
+
55
+ export interface ParsedLine {
56
+ lineNumber: number;
57
+ tokens: ParsedToken[];
58
+ raw: string;
59
+ }
60
+
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
+ try {
84
+ // Parse with Acorn (supports ES2020+)
85
+ const tokens: any[] = [];
86
+
87
+ acorn.parse(code, {
88
+ ecmaVersion: 2022,
89
+ sourceType: 'module',
90
+ locations: true,
91
+ ranges: true,
92
+ onToken: tokens,
93
+ onComment: (block, text, start, end, startLoc, endLoc) => {
94
+ tokens.push({
95
+ type: block ? 'BlockComment' : 'LineComment',
96
+ value: text,
97
+ start,
98
+ end,
99
+ loc: { start: startLoc, end: endLoc }
100
+ });
101
+ }
102
+ });
103
+
104
+ // Sort tokens by position
105
+ tokens.sort((a, b) => a.start - b.start);
106
+
107
+ // Group tokens by line
108
+ const tokensByLine: Map<number, ParsedToken[]> = new Map();
109
+
110
+ tokens.forEach(token => {
111
+ const line = token.loc.start.line;
112
+ const parsedToken: ParsedToken = {
113
+ type: token.type.label || token.type,
114
+ value: token.value !== undefined ? String(token.value) : code.substring(token.start, token.end),
115
+ start: token.start,
116
+ end: token.end,
117
+ line: token.loc.start.line,
118
+ column: token.loc.start.column,
119
+ className: getTokenClass(token)
120
+ };
121
+
122
+ if (!tokensByLine.has(line)) {
123
+ tokensByLine.set(line, []);
124
+ }
125
+ tokensByLine.get(line)!.push(parsedToken);
126
+ });
127
+
128
+ // Build lines with tokens
129
+ codeLines.forEach((lineText, index) => {
130
+ const lineNumber = index + 1;
131
+ const lineTokens = tokensByLine.get(lineNumber) || [];
132
+
133
+ lines.push({
134
+ lineNumber,
135
+ tokens: lineTokens,
136
+ raw: lineText
137
+ });
138
+ });
139
+
140
+ } catch (error) {
141
+ // Fallback: if parsing fails, return plain lines
142
+ console.warn('[CodeParser] Parse failed, using plain text:', error);
143
+ codeLines.forEach((lineText, index) => {
144
+ lines.push({
145
+ lineNumber: index + 1,
146
+ tokens: [],
147
+ raw: lineText
148
+ });
149
+ });
150
+ }
151
+
152
+ return lines;
153
+ }
154
+ }
155
+ /**
156
+ * Get CSS class for a token
157
+ */
158
+ export function getTokenClass(token: any): string {
159
+ const type = token.type.label || token.type;
160
+
161
+ // Check if it's a keyword
162
+ if (type === 'name' && token.value && isKeyword(token.value)) {
163
+ return TOKEN_CLASS_MAP[token.value] || TOKEN_CLASS_MAP['Keyword'];
164
+ }
165
+
166
+ // Map type to class
167
+ return TOKEN_CLASS_MAP[type] || 'token-default';
168
+ }
169
+
170
+ /**
171
+ * Check if a word is a JavaScript keyword
172
+ */
173
+ export function isKeyword(word: string): boolean {
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);
182
+ }
183
+
184
+ /**
185
+ * Render a parsed line as HTML with spans
186
+ */
187
+ export function renderLineWithTokens(parsedLine: ParsedLine): string {
188
+ if (parsedLine.tokens.length === 0) {
189
+ // No tokens, return plain text (escaped)
190
+ return escapeHtml(parsedLine.raw) || ' ';
191
+ }
192
+
193
+ const { raw, tokens } = parsedLine;
194
+ let html = '';
195
+ let lastIndex = 0;
196
+
197
+ // Calculate line start position in original code
198
+ const lineStartPos = tokens[0]?.start - tokens[0]?.column;
199
+
200
+ tokens.forEach(token => {
201
+ // Add any whitespace/text between tokens
202
+ const localStart = token.start - lineStartPos;
203
+ if (localStart > lastIndex) {
204
+ html += escapeHtml(raw.substring(lastIndex, localStart));
205
+ }
206
+
207
+ // Add token with span
208
+ const localEnd = token.end - lineStartPos;
209
+ const tokenText = raw.substring(localStart, localEnd);
210
+ html += `<span class="${token.className}">${escapeHtml(tokenText)}</span>`;
211
+ lastIndex = localEnd;
212
+ });
213
+
214
+ // Add any remaining text
215
+ if (lastIndex < raw.length) {
216
+ html += escapeHtml(raw.substring(lastIndex));
217
+ }
218
+
219
+ return html || ' ';
220
+ }
221
+
222
+ /**
223
+ * Escape HTML entities
224
+ */
225
+ export function escapeHtml(text: string): string {
226
+ return text
227
+ .replace(/&/g, '&amp;')
228
+ .replace(/</g, '&lt;')
229
+ .replace(/>/g, '&gt;')
230
+ .replace(/"/g, '&quot;')
231
+ .replace(/'/g, '&#39;');
232
+ }
233
+
234
+ /**
235
+ * Generate CSS for syntax highlighting
236
+ */
237
+ export function getSyntaxHighlightCSS(): string {
238
+ return `
239
+ /* Code Parser Syntax Highlighting */
240
+ .token-keyword { color: #c678dd; font-weight: 600; }
241
+ .token-string { color: #98c379; }
242
+ .token-number { color: #d19a66; }
243
+ .token-boolean { color: #d19a66; }
244
+ .token-null { color: #d19a66; }
245
+ .token-regex { color: #e06c75; }
246
+ .token-identifier { color: #e5c07b; }
247
+ .token-punctuation { color: #abb2bf; }
248
+ .token-comment { color: #5c6370; font-style: italic; }
249
+ .token-default { color: #abb2bf; }
250
+
251
+ /* Function calls */
252
+ .token-identifier + .token-punctuation:has(+ .token-punctuation) {
253
+ color: #61afef; /* Function names in blue */
254
+ }
255
+ `;
256
+ }
257
+
258
+
259
+
260
+ /**
261
+ * Main parser export with utilities
262
+ */
263
+ export const codeParser = {
264
+ parse: CodeParser.parse,
265
+ renderLine: renderLineWithTokens,
266
+ getCSS: getSyntaxHighlightCSS,
267
+ getTokenClass,
268
+ isKeyword,
269
+ escapeHtml,
270
+ TOKEN_CLASS_MAP
271
+ };
272
+
273
+ export default CodeParser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.44",
3
+ "version": "1.1.46",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "index.js",