juxscript 1.1.43 → 1.1.44
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/lib/components/code.d.ts +26 -0
- package/lib/components/code.d.ts.map +1 -1
- package/lib/components/code.js +126 -19
- package/lib/components/code.ts +153 -21
- package/package.json +1 -1
package/lib/components/code.d.ts
CHANGED
|
@@ -2,22 +2,48 @@ import { BaseComponent } from './base/BaseComponent.js';
|
|
|
2
2
|
export interface CodeOptions {
|
|
3
3
|
content?: string;
|
|
4
4
|
language?: string;
|
|
5
|
+
showLineNumbers?: boolean;
|
|
6
|
+
startLine?: number;
|
|
7
|
+
highlightLines?: number[];
|
|
5
8
|
style?: string;
|
|
6
9
|
class?: string;
|
|
7
10
|
}
|
|
8
11
|
type CodeState = {
|
|
9
12
|
content: string;
|
|
10
13
|
language: string;
|
|
14
|
+
showLineNumbers: boolean;
|
|
15
|
+
startLine: number;
|
|
16
|
+
highlightLines: number[];
|
|
11
17
|
style: string;
|
|
12
18
|
class: string;
|
|
13
19
|
};
|
|
14
20
|
export declare class Code extends BaseComponent<CodeState> {
|
|
21
|
+
private _codeContainer;
|
|
15
22
|
constructor(id: string, options?: CodeOptions);
|
|
16
23
|
protected getTriggerEvents(): readonly string[];
|
|
17
24
|
protected getCallbackEvents(): readonly string[];
|
|
18
25
|
update(prop: string, value: any): void;
|
|
19
26
|
content(value: string): this;
|
|
20
27
|
language(value: string): this;
|
|
28
|
+
showLineNumbers(value: boolean): this;
|
|
29
|
+
startLine(value: number): this;
|
|
30
|
+
highlightLines(lines: number[]): this;
|
|
31
|
+
/**
|
|
32
|
+
* Parse code content into individual lines
|
|
33
|
+
*/
|
|
34
|
+
private _parseLines;
|
|
35
|
+
/**
|
|
36
|
+
* Rebuild all line elements
|
|
37
|
+
*/
|
|
38
|
+
private _rebuildLines;
|
|
39
|
+
/**
|
|
40
|
+
* Create a single line element
|
|
41
|
+
*/
|
|
42
|
+
private _createLineElement;
|
|
43
|
+
/**
|
|
44
|
+
* Update highlighted lines without full rebuild
|
|
45
|
+
*/
|
|
46
|
+
private _updateHighlights;
|
|
21
47
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this;
|
|
22
48
|
}
|
|
23
49
|
export declare function code(id: string, options?: CodeOptions): Code;
|
|
@@ -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,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,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAa,IAAK,SAAQ,aAAa,CAAC,SAAS,CAAC;
|
|
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"}
|
package/lib/components/code.js
CHANGED
|
@@ -7,9 +7,13 @@ export class Code extends BaseComponent {
|
|
|
7
7
|
super(id, {
|
|
8
8
|
content: options.content ?? '',
|
|
9
9
|
language: options.language ?? 'javascript',
|
|
10
|
+
showLineNumbers: options.showLineNumbers ?? true,
|
|
11
|
+
startLine: options.startLine ?? 1,
|
|
12
|
+
highlightLines: options.highlightLines ?? [],
|
|
10
13
|
style: options.style ?? '',
|
|
11
14
|
class: options.class ?? ''
|
|
12
15
|
});
|
|
16
|
+
this._codeContainer = null;
|
|
13
17
|
}
|
|
14
18
|
getTriggerEvents() {
|
|
15
19
|
return TRIGGER_EVENTS;
|
|
@@ -18,7 +22,20 @@ export class Code extends BaseComponent {
|
|
|
18
22
|
return CALLBACK_EVENTS;
|
|
19
23
|
}
|
|
20
24
|
update(prop, value) {
|
|
21
|
-
|
|
25
|
+
super.update(prop, value);
|
|
26
|
+
if (!this._codeContainer)
|
|
27
|
+
return;
|
|
28
|
+
switch (prop) {
|
|
29
|
+
case 'content':
|
|
30
|
+
this._rebuildLines();
|
|
31
|
+
break;
|
|
32
|
+
case 'showLineNumbers':
|
|
33
|
+
this._codeContainer.classList.toggle('jux-code-no-numbers', !value);
|
|
34
|
+
break;
|
|
35
|
+
case 'highlightLines':
|
|
36
|
+
this._updateHighlights();
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
22
39
|
}
|
|
23
40
|
/* ═════════════════════════════════════════════════════════════════
|
|
24
41
|
* FLUENT API
|
|
@@ -31,23 +48,115 @@ export class Code extends BaseComponent {
|
|
|
31
48
|
this.state.language = value;
|
|
32
49
|
return this;
|
|
33
50
|
}
|
|
51
|
+
showLineNumbers(value) {
|
|
52
|
+
this.state.showLineNumbers = value;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
startLine(value) {
|
|
56
|
+
this.state.startLine = value;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
highlightLines(lines) {
|
|
60
|
+
this.state.highlightLines = lines;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
64
|
+
* PARSING & RENDERING
|
|
65
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
66
|
+
/**
|
|
67
|
+
* Parse code content into individual lines
|
|
68
|
+
*/
|
|
69
|
+
_parseLines() {
|
|
70
|
+
return this.state.content.split('\n');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Rebuild all line elements
|
|
74
|
+
*/
|
|
75
|
+
_rebuildLines() {
|
|
76
|
+
if (!this._codeContainer)
|
|
77
|
+
return;
|
|
78
|
+
const codeEl = this._codeContainer.querySelector('.jux-code-lines');
|
|
79
|
+
if (!codeEl)
|
|
80
|
+
return;
|
|
81
|
+
codeEl.innerHTML = '';
|
|
82
|
+
const lines = this._parseLines();
|
|
83
|
+
const { startLine, highlightLines, showLineNumbers } = this.state;
|
|
84
|
+
lines.forEach((lineContent, index) => {
|
|
85
|
+
const lineNumber = startLine + index;
|
|
86
|
+
const isHighlighted = highlightLines.includes(lineNumber);
|
|
87
|
+
const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
|
|
88
|
+
codeEl.appendChild(lineEl);
|
|
89
|
+
});
|
|
90
|
+
// Re-run Prism if available
|
|
91
|
+
if (window.Prism && this._codeContainer) {
|
|
92
|
+
window.Prism.highlightAllUnder(this._codeContainer);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a single line element
|
|
97
|
+
*/
|
|
98
|
+
_createLineElement(content, lineNumber, highlighted) {
|
|
99
|
+
const lineEl = document.createElement('div');
|
|
100
|
+
lineEl.className = 'jux-code-line';
|
|
101
|
+
lineEl.setAttribute('data-line', String(lineNumber));
|
|
102
|
+
if (highlighted) {
|
|
103
|
+
lineEl.classList.add('jux-code-line-highlight');
|
|
104
|
+
}
|
|
105
|
+
// Line number
|
|
106
|
+
if (this.state.showLineNumbers) {
|
|
107
|
+
const lineNum = document.createElement('span');
|
|
108
|
+
lineNum.className = 'jux-code-line-number';
|
|
109
|
+
lineNum.textContent = String(lineNumber);
|
|
110
|
+
lineEl.appendChild(lineNum);
|
|
111
|
+
}
|
|
112
|
+
// Line content
|
|
113
|
+
const lineCode = document.createElement('span');
|
|
114
|
+
lineCode.className = `jux-code-line-content language-${this.state.language}`;
|
|
115
|
+
lineCode.textContent = content || ' '; // Preserve empty lines
|
|
116
|
+
lineEl.appendChild(lineCode);
|
|
117
|
+
return lineEl;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Update highlighted lines without full rebuild
|
|
121
|
+
*/
|
|
122
|
+
_updateHighlights() {
|
|
123
|
+
if (!this._codeContainer)
|
|
124
|
+
return;
|
|
125
|
+
const lines = this._codeContainer.querySelectorAll('.jux-code-line');
|
|
126
|
+
lines.forEach((line) => {
|
|
127
|
+
const lineNum = parseInt(line.getAttribute('data-line') || '0', 10);
|
|
128
|
+
const isHighlighted = this.state.highlightLines.includes(lineNum);
|
|
129
|
+
line.classList.toggle('jux-code-line-highlight', isHighlighted);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
34
132
|
/* ═════════════════════════════════════════════════════════════════
|
|
35
133
|
* RENDER
|
|
36
134
|
* ═════════════════════════════════════════════════════════════════ */
|
|
37
135
|
render(targetId) {
|
|
38
136
|
const container = this._setupContainer(targetId);
|
|
39
|
-
const {
|
|
137
|
+
const { language, showLineNumbers, style, class: className } = this.state;
|
|
40
138
|
const wrapper = document.createElement('div');
|
|
41
139
|
wrapper.className = 'jux-code';
|
|
42
140
|
wrapper.id = this._id;
|
|
141
|
+
if (!showLineNumbers)
|
|
142
|
+
wrapper.classList.add('jux-code-no-numbers');
|
|
43
143
|
if (className)
|
|
44
144
|
wrapper.className += ` ${className}`;
|
|
45
145
|
if (style)
|
|
46
146
|
wrapper.setAttribute('style', style);
|
|
47
147
|
const pre = document.createElement('pre');
|
|
148
|
+
pre.className = `language-${language}`;
|
|
48
149
|
const codeEl = document.createElement('code');
|
|
49
|
-
codeEl.className =
|
|
50
|
-
|
|
150
|
+
codeEl.className = 'jux-code-lines';
|
|
151
|
+
// Build initial lines
|
|
152
|
+
const lines = this._parseLines();
|
|
153
|
+
const { startLine, highlightLines } = this.state;
|
|
154
|
+
lines.forEach((lineContent, index) => {
|
|
155
|
+
const lineNumber = startLine + index;
|
|
156
|
+
const isHighlighted = highlightLines.includes(lineNumber);
|
|
157
|
+
const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
|
|
158
|
+
codeEl.appendChild(lineEl);
|
|
159
|
+
});
|
|
51
160
|
pre.appendChild(codeEl);
|
|
52
161
|
wrapper.appendChild(pre);
|
|
53
162
|
this._wireStandardEvents(wrapper);
|
|
@@ -57,35 +166,33 @@ export class Code extends BaseComponent {
|
|
|
57
166
|
const transform = toComponent || ((v) => String(v));
|
|
58
167
|
stateObj.subscribe((val) => {
|
|
59
168
|
const transformed = transform(val);
|
|
60
|
-
codeEl.textContent = transformed;
|
|
61
169
|
this.state.content = transformed;
|
|
62
|
-
|
|
63
|
-
window.Prism.highlightElement(codeEl);
|
|
64
|
-
}
|
|
170
|
+
this._rebuildLines();
|
|
65
171
|
});
|
|
66
172
|
}
|
|
67
173
|
else if (property === 'language') {
|
|
68
174
|
const transform = toComponent || ((v) => String(v));
|
|
69
175
|
stateObj.subscribe((val) => {
|
|
70
176
|
const transformed = transform(val);
|
|
71
|
-
codeEl.className = `language-${transformed}`;
|
|
72
177
|
this.state.language = transformed;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
178
|
+
pre.className = `language-${transformed}`;
|
|
179
|
+
this._rebuildLines();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else if (property === 'highlightLines') {
|
|
183
|
+
const transform = toComponent || ((v) => (Array.isArray(v) ? v : []));
|
|
184
|
+
stateObj.subscribe((val) => {
|
|
185
|
+
const transformed = transform(val);
|
|
186
|
+
this.state.highlightLines = transformed;
|
|
187
|
+
this._updateHighlights();
|
|
76
188
|
});
|
|
77
189
|
}
|
|
78
190
|
});
|
|
79
191
|
container.appendChild(wrapper);
|
|
192
|
+
this._codeContainer = wrapper;
|
|
80
193
|
requestAnimationFrame(() => {
|
|
81
194
|
if (window.Prism) {
|
|
82
|
-
window.Prism.
|
|
83
|
-
}
|
|
84
|
-
else if (process.env.NODE_ENV !== 'production') {
|
|
85
|
-
// ✅ Helpful hint in development
|
|
86
|
-
console.log(`💡 Tip: Add Prism.js for syntax highlighting:\n` +
|
|
87
|
-
` <link href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css" rel="stylesheet" />\n` +
|
|
88
|
-
` <script src="https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js"></script>`);
|
|
195
|
+
window.Prism.highlightAllUnder(wrapper);
|
|
89
196
|
}
|
|
90
197
|
});
|
|
91
198
|
return this;
|
package/lib/components/code.ts
CHANGED
|
@@ -7,6 +7,9 @@ const CALLBACK_EVENTS = [] as const;
|
|
|
7
7
|
export interface CodeOptions {
|
|
8
8
|
content?: string;
|
|
9
9
|
language?: string;
|
|
10
|
+
showLineNumbers?: boolean;
|
|
11
|
+
startLine?: number;
|
|
12
|
+
highlightLines?: number[];
|
|
10
13
|
style?: string;
|
|
11
14
|
class?: string;
|
|
12
15
|
}
|
|
@@ -14,15 +17,23 @@ export interface CodeOptions {
|
|
|
14
17
|
type CodeState = {
|
|
15
18
|
content: string;
|
|
16
19
|
language: string;
|
|
20
|
+
showLineNumbers: boolean;
|
|
21
|
+
startLine: number;
|
|
22
|
+
highlightLines: number[];
|
|
17
23
|
style: string;
|
|
18
24
|
class: string;
|
|
19
25
|
};
|
|
20
26
|
|
|
21
27
|
export class Code extends BaseComponent<CodeState> {
|
|
28
|
+
private _codeContainer: HTMLElement | null = null;
|
|
29
|
+
|
|
22
30
|
constructor(id: string, options: CodeOptions = {}) {
|
|
23
31
|
super(id, {
|
|
24
32
|
content: options.content ?? '',
|
|
25
33
|
language: options.language ?? 'javascript',
|
|
34
|
+
showLineNumbers: options.showLineNumbers ?? true,
|
|
35
|
+
startLine: options.startLine ?? 1,
|
|
36
|
+
highlightLines: options.highlightLines ?? [],
|
|
26
37
|
style: options.style ?? '',
|
|
27
38
|
class: options.class ?? ''
|
|
28
39
|
});
|
|
@@ -37,7 +48,21 @@ export class Code extends BaseComponent<CodeState> {
|
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
update(prop: string, value: any): void {
|
|
40
|
-
|
|
51
|
+
super.update(prop, value);
|
|
52
|
+
|
|
53
|
+
if (!this._codeContainer) return;
|
|
54
|
+
|
|
55
|
+
switch (prop) {
|
|
56
|
+
case 'content':
|
|
57
|
+
this._rebuildLines();
|
|
58
|
+
break;
|
|
59
|
+
case 'showLineNumbers':
|
|
60
|
+
this._codeContainer.classList.toggle('jux-code-no-numbers', !value);
|
|
61
|
+
break;
|
|
62
|
+
case 'highlightLines':
|
|
63
|
+
this._updateHighlights();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
41
66
|
}
|
|
42
67
|
|
|
43
68
|
/* ═════════════════════════════════════════════════════════════════
|
|
@@ -54,6 +79,103 @@ export class Code extends BaseComponent<CodeState> {
|
|
|
54
79
|
return this;
|
|
55
80
|
}
|
|
56
81
|
|
|
82
|
+
showLineNumbers(value: boolean): this {
|
|
83
|
+
this.state.showLineNumbers = value;
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
startLine(value: number): this {
|
|
88
|
+
this.state.startLine = value;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
highlightLines(lines: number[]): this {
|
|
93
|
+
this.state.highlightLines = lines;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
98
|
+
* PARSING & RENDERING
|
|
99
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Parse code content into individual lines
|
|
103
|
+
*/
|
|
104
|
+
private _parseLines(): string[] {
|
|
105
|
+
return this.state.content.split('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Rebuild all line elements
|
|
110
|
+
*/
|
|
111
|
+
private _rebuildLines(): void {
|
|
112
|
+
if (!this._codeContainer) return;
|
|
113
|
+
|
|
114
|
+
const codeEl = this._codeContainer.querySelector('.jux-code-lines');
|
|
115
|
+
if (!codeEl) return;
|
|
116
|
+
|
|
117
|
+
codeEl.innerHTML = '';
|
|
118
|
+
|
|
119
|
+
const lines = this._parseLines();
|
|
120
|
+
const { startLine, highlightLines, showLineNumbers } = this.state;
|
|
121
|
+
|
|
122
|
+
lines.forEach((lineContent, index) => {
|
|
123
|
+
const lineNumber = startLine + index;
|
|
124
|
+
const isHighlighted = highlightLines.includes(lineNumber);
|
|
125
|
+
|
|
126
|
+
const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
|
|
127
|
+
codeEl.appendChild(lineEl);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Re-run Prism if available
|
|
131
|
+
if ((window as any).Prism && this._codeContainer) {
|
|
132
|
+
(window as any).Prism.highlightAllUnder(this._codeContainer);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create a single line element
|
|
138
|
+
*/
|
|
139
|
+
private _createLineElement(content: string, lineNumber: number, highlighted: boolean): HTMLElement {
|
|
140
|
+
const lineEl = document.createElement('div');
|
|
141
|
+
lineEl.className = 'jux-code-line';
|
|
142
|
+
lineEl.setAttribute('data-line', String(lineNumber));
|
|
143
|
+
|
|
144
|
+
if (highlighted) {
|
|
145
|
+
lineEl.classList.add('jux-code-line-highlight');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Line number
|
|
149
|
+
if (this.state.showLineNumbers) {
|
|
150
|
+
const lineNum = document.createElement('span');
|
|
151
|
+
lineNum.className = 'jux-code-line-number';
|
|
152
|
+
lineNum.textContent = String(lineNumber);
|
|
153
|
+
lineEl.appendChild(lineNum);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Line content
|
|
157
|
+
const lineCode = document.createElement('span');
|
|
158
|
+
lineCode.className = `jux-code-line-content language-${this.state.language}`;
|
|
159
|
+
lineCode.textContent = content || ' '; // Preserve empty lines
|
|
160
|
+
lineEl.appendChild(lineCode);
|
|
161
|
+
|
|
162
|
+
return lineEl;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Update highlighted lines without full rebuild
|
|
167
|
+
*/
|
|
168
|
+
private _updateHighlights(): void {
|
|
169
|
+
if (!this._codeContainer) return;
|
|
170
|
+
|
|
171
|
+
const lines = this._codeContainer.querySelectorAll('.jux-code-line');
|
|
172
|
+
lines.forEach((line) => {
|
|
173
|
+
const lineNum = parseInt(line.getAttribute('data-line') || '0', 10);
|
|
174
|
+
const isHighlighted = this.state.highlightLines.includes(lineNum);
|
|
175
|
+
line.classList.toggle('jux-code-line-highlight', isHighlighted);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
57
179
|
/* ═════════════════════════════════════════════════════════════════
|
|
58
180
|
* RENDER
|
|
59
181
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -61,18 +183,32 @@ export class Code extends BaseComponent<CodeState> {
|
|
|
61
183
|
render(targetId?: string | HTMLElement | BaseComponent<any>): this {
|
|
62
184
|
const container = this._setupContainer(targetId);
|
|
63
185
|
|
|
64
|
-
const {
|
|
186
|
+
const { language, showLineNumbers, style, class: className } = this.state;
|
|
65
187
|
|
|
66
188
|
const wrapper = document.createElement('div');
|
|
67
189
|
wrapper.className = 'jux-code';
|
|
68
190
|
wrapper.id = this._id;
|
|
191
|
+
if (!showLineNumbers) wrapper.classList.add('jux-code-no-numbers');
|
|
69
192
|
if (className) wrapper.className += ` ${className}`;
|
|
70
193
|
if (style) wrapper.setAttribute('style', style);
|
|
71
194
|
|
|
72
195
|
const pre = document.createElement('pre');
|
|
196
|
+
pre.className = `language-${language}`;
|
|
197
|
+
|
|
73
198
|
const codeEl = document.createElement('code');
|
|
74
|
-
codeEl.className =
|
|
75
|
-
|
|
199
|
+
codeEl.className = 'jux-code-lines';
|
|
200
|
+
|
|
201
|
+
// Build initial lines
|
|
202
|
+
const lines = this._parseLines();
|
|
203
|
+
const { startLine, highlightLines } = this.state;
|
|
204
|
+
|
|
205
|
+
lines.forEach((lineContent, index) => {
|
|
206
|
+
const lineNumber = startLine + index;
|
|
207
|
+
const isHighlighted = highlightLines.includes(lineNumber);
|
|
208
|
+
const lineEl = this._createLineElement(lineContent, lineNumber, isHighlighted);
|
|
209
|
+
codeEl.appendChild(lineEl);
|
|
210
|
+
});
|
|
211
|
+
|
|
76
212
|
pre.appendChild(codeEl);
|
|
77
213
|
wrapper.appendChild(pre);
|
|
78
214
|
|
|
@@ -85,12 +221,8 @@ export class Code extends BaseComponent<CodeState> {
|
|
|
85
221
|
|
|
86
222
|
stateObj.subscribe((val: any) => {
|
|
87
223
|
const transformed = transform(val);
|
|
88
|
-
codeEl.textContent = transformed;
|
|
89
224
|
this.state.content = transformed;
|
|
90
|
-
|
|
91
|
-
if ((window as any).Prism) {
|
|
92
|
-
(window as any).Prism.highlightElement(codeEl);
|
|
93
|
-
}
|
|
225
|
+
this._rebuildLines();
|
|
94
226
|
});
|
|
95
227
|
}
|
|
96
228
|
else if (property === 'language') {
|
|
@@ -98,28 +230,28 @@ export class Code extends BaseComponent<CodeState> {
|
|
|
98
230
|
|
|
99
231
|
stateObj.subscribe((val: any) => {
|
|
100
232
|
const transformed = transform(val);
|
|
101
|
-
codeEl.className = `language-${transformed}`;
|
|
102
233
|
this.state.language = transformed;
|
|
234
|
+
pre.className = `language-${transformed}`;
|
|
235
|
+
this._rebuildLines();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (property === 'highlightLines') {
|
|
239
|
+
const transform = toComponent || ((v: any) => (Array.isArray(v) ? v : []));
|
|
103
240
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
241
|
+
stateObj.subscribe((val: any) => {
|
|
242
|
+
const transformed = transform(val);
|
|
243
|
+
this.state.highlightLines = transformed;
|
|
244
|
+
this._updateHighlights();
|
|
107
245
|
});
|
|
108
246
|
}
|
|
109
247
|
});
|
|
110
248
|
|
|
111
249
|
container.appendChild(wrapper);
|
|
250
|
+
this._codeContainer = wrapper;
|
|
112
251
|
|
|
113
252
|
requestAnimationFrame(() => {
|
|
114
253
|
if ((window as any).Prism) {
|
|
115
|
-
(window as any).Prism.
|
|
116
|
-
} else if (process.env.NODE_ENV !== 'production') {
|
|
117
|
-
// ✅ Helpful hint in development
|
|
118
|
-
console.log(
|
|
119
|
-
`💡 Tip: Add Prism.js for syntax highlighting:\n` +
|
|
120
|
-
` <link href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism.min.css" rel="stylesheet" />\n` +
|
|
121
|
-
` <script src="https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js"></script>`
|
|
122
|
-
);
|
|
254
|
+
(window as any).Prism.highlightAllUnder(wrapper);
|
|
123
255
|
}
|
|
124
256
|
});
|
|
125
257
|
|