@codady/coax 0.0.4 → 0.0.6
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/dist/coax.cjs.js +72 -35
- package/dist/coax.cjs.min.js +3 -3
- package/dist/coax.esm.js +72 -35
- package/dist/coax.esm.min.js +3 -3
- package/dist/coax.umd.js +72 -35
- package/dist/coax.umd.min.js +3 -3
- package/examples/{append-highlight.html → append.html} +1 -5
- package/examples/{css-highlight.html → css.html} +1 -5
- package/examples/{deepseek-highlight.html → deepseek.html} +1 -1
- package/examples/{html-highlight.html → html.html} +2 -13
- package/examples/index.html +214 -0
- package/examples/js.html +41 -0
- package/examples/{md-highlight.html → md.html} +1 -12
- package/examples/{theme-highlight.html → module.html} +1 -5
- package/examples/{plain-highlight.html → plain.html} +1 -5
- package/examples/{replace-highlight.html → replace.html} +1 -6
- package/examples/{stream-highlight.html → steam.html} +10 -16
- package/examples/{color-selector.html → theme.html} +3 -6
- package/examples/{js-highlight.html → tools.html} +2 -10
- package/examples/{ts-highlight.html → ts.html} +1 -20
- package/examples/typewriter.html +41 -0
- package/package.json +2 -2
- package/src/Coax.js +1 -1
- package/src/Coax.ts +1 -1
- package/src/components/Coax.js +54 -34
- package/src/components/Coax.ts +63 -36
- package/src/modules.js +1 -1
- package/src/modules.ts +1 -1
- package/examples/.htaccess +0 -0
- package/examples/nginx.htaccess +0 -0
package/src/components/Coax.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Last modified: 2026/01/
|
|
2
|
+
* Last modified: 2026/01/15 19:21:29
|
|
3
3
|
* Coax - A custom web component for syntax highlighting, code display, and interactive features
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
@@ -9,6 +9,8 @@ import NAMESPACE from "@codady/utils/namespace";
|
|
|
9
9
|
import getEl from "@codady/utils/getEl";
|
|
10
10
|
import createTools from "@codady/utils/createTools";
|
|
11
11
|
import createEl from "@codady/utils/createEl";
|
|
12
|
+
import trimEmptyLines from "@codady/utils/trimEmptyLines";
|
|
13
|
+
import escapeHtmlChars from "@codady/utils/escapeHtmlChars";
|
|
12
14
|
class Coax extends HTMLElement {
|
|
13
15
|
// A static Map to hold the configuration for different languages
|
|
14
16
|
static languages = new Map();
|
|
@@ -19,7 +21,7 @@ class Coax extends HTMLElement {
|
|
|
19
21
|
baseStylesEl; // Element for base styles
|
|
20
22
|
themeStylesEl; // Element for theme styles
|
|
21
23
|
dynamicStylesEl; // Element for dynamic styles
|
|
22
|
-
|
|
24
|
+
highlightEl; // Element that holds the highlighted code
|
|
23
25
|
headerEl; // Header element (for code name, tools, etc.)
|
|
24
26
|
codeNameEl; // Code name element (shows language or alias)
|
|
25
27
|
codeToolsEl; // Code tools element (for interactive tools like copy)
|
|
@@ -30,12 +32,13 @@ class Coax extends HTMLElement {
|
|
|
30
32
|
lastLineString = ''; // The last line's string
|
|
31
33
|
speed = 5; // Speed of the typing effect (higher is slower)
|
|
32
34
|
autoScroll = true; // Flag to enable/disable auto-scrolling
|
|
35
|
+
canListen = true; // Flag to enable/disable auto-scrolling
|
|
33
36
|
constructor() {
|
|
34
37
|
super();
|
|
35
38
|
// Attach a Shadow DOM to the custom element
|
|
36
39
|
this.attachShadow({ mode: 'open' });
|
|
37
40
|
// Remove leading/trailing whitespace from the raw code content
|
|
38
|
-
this.source = this.textContent
|
|
41
|
+
this.source = escapeHtmlChars(trimEmptyLines(this.textContent));
|
|
39
42
|
// Initialize the basic structure of the component
|
|
40
43
|
this.shadowRoot.innerHTML = `
|
|
41
44
|
<style id="base-styles">
|
|
@@ -166,7 +169,7 @@ class Coax extends HTMLElement {
|
|
|
166
169
|
<style id="theme-styles"></style>
|
|
167
170
|
<div id="code-header"><span id="code-name">${this.alias}</span><div id="code-tools"></div></div>
|
|
168
171
|
<div id="code-body">
|
|
169
|
-
<pre><code id="highlight
|
|
172
|
+
<pre><code id="highlight"></code></pre>
|
|
170
173
|
</div>
|
|
171
174
|
`;
|
|
172
175
|
// Cache references to various elements
|
|
@@ -177,7 +180,7 @@ class Coax extends HTMLElement {
|
|
|
177
180
|
this.codeNameEl = getEl('#code-name', this.shadowRoot);
|
|
178
181
|
this.codeToolsEl = getEl('#code-tools', this.shadowRoot);
|
|
179
182
|
this.codeBodyEl = getEl('#code-body', this.shadowRoot);
|
|
180
|
-
this.
|
|
183
|
+
this.highlightEl = getEl('#highlight', this.shadowRoot);
|
|
181
184
|
this.codeBodyEl.addEventListener('scroll', () => {
|
|
182
185
|
let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
|
|
183
186
|
// Check if the user manually scrolled
|
|
@@ -231,7 +234,7 @@ class Coax extends HTMLElement {
|
|
|
231
234
|
* @param newVal - The new value of the attribute.
|
|
232
235
|
*/
|
|
233
236
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
234
|
-
if (oldVal === newVal)
|
|
237
|
+
if (oldVal === newVal || !this.canListen)
|
|
235
238
|
return;
|
|
236
239
|
if (name === 'height' || name === 'max-height') {
|
|
237
240
|
this.updateStyleByRegExp(name, newVal);
|
|
@@ -302,10 +305,10 @@ class Coax extends HTMLElement {
|
|
|
302
305
|
createLineWrap(index, startIndex) {
|
|
303
306
|
let dataIndex = 0;
|
|
304
307
|
if (index == null && startIndex == null) {
|
|
305
|
-
dataIndex = this.
|
|
308
|
+
dataIndex = this.highlightEl.children.length;
|
|
306
309
|
}
|
|
307
310
|
else {
|
|
308
|
-
const start = startIndex || this.
|
|
311
|
+
const start = startIndex || this.highlightEl.children.length;
|
|
309
312
|
dataIndex = start + index;
|
|
310
313
|
}
|
|
311
314
|
return createEl('div', { 'data-index': dataIndex }, '<div></div>');
|
|
@@ -316,7 +319,7 @@ class Coax extends HTMLElement {
|
|
|
316
319
|
* @param line - The line of code to highlight.
|
|
317
320
|
* @param config - The language configuration object.
|
|
318
321
|
*/
|
|
319
|
-
|
|
322
|
+
getLineToFillHighLight(codeWrap, line, config) {
|
|
320
323
|
config = config || Coax.languages.get(this.lang);
|
|
321
324
|
let highlightedLine = this.getHighLightString(line, config);
|
|
322
325
|
// 将高亮后的内容填充到 div 中
|
|
@@ -327,10 +330,10 @@ class Coax extends HTMLElement {
|
|
|
327
330
|
* Highlights new source code and appends it to the code body.
|
|
328
331
|
* @param newCode - The new source code to highlight and append.
|
|
329
332
|
*/
|
|
330
|
-
async highlight(newCode) {
|
|
331
|
-
const config = Coax.languages.get(this.lang), startIndex = this.
|
|
333
|
+
async highlight(newCode = this.source) {
|
|
334
|
+
const config = Coax.languages.get(this.lang), startIndex = this.highlightEl.children.length,
|
|
332
335
|
// 将新源码按行分割
|
|
333
|
-
newCodeLines = newCode ? newCode.split('\n') : [];
|
|
336
|
+
newCodeLines = newCode ? newCode.split('\n') : [], hasSpeedAttr = this.hasAttribute('speed'), hasSanitizedAttr = this.hasAttribute('sanitized');
|
|
334
337
|
//更新别名
|
|
335
338
|
this.updateName(config);
|
|
336
339
|
if (!newCodeLines.length)
|
|
@@ -338,14 +341,14 @@ class Coax extends HTMLElement {
|
|
|
338
341
|
// 如果没有找到配置,则输出原始代码,并不进行高亮处理
|
|
339
342
|
for (let [index, lineString] of newCodeLines.entries()) {
|
|
340
343
|
//如果是空行则跳过
|
|
341
|
-
if (!lineString.trim() &&
|
|
344
|
+
if (!lineString.trim() && hasSanitizedAttr)
|
|
342
345
|
continue;
|
|
343
346
|
// 创建一个 div 包裹每一行
|
|
344
347
|
const lineWrap = this.createLineWrap(index, startIndex), codeWrap = lineWrap.lastElementChild;
|
|
345
348
|
//标记完成
|
|
346
349
|
lineWrap.completed = true;
|
|
347
|
-
if (
|
|
348
|
-
this.
|
|
350
|
+
if (hasSpeedAttr) {
|
|
351
|
+
this.highlightEl.appendChild(lineWrap);
|
|
349
352
|
//流式打字
|
|
350
353
|
await typeWriter(lineString, {
|
|
351
354
|
speed: this.speed,
|
|
@@ -353,13 +356,13 @@ class Coax extends HTMLElement {
|
|
|
353
356
|
codeWrap.innerHTML = fullText;
|
|
354
357
|
}
|
|
355
358
|
});
|
|
356
|
-
this.
|
|
359
|
+
this.getLineToFillHighLight(codeWrap, lineString, config);
|
|
357
360
|
}
|
|
358
361
|
else {
|
|
359
362
|
//直接打出
|
|
360
|
-
this.
|
|
363
|
+
this.getLineToFillHighLight(codeWrap, lineString, config);
|
|
361
364
|
//
|
|
362
|
-
this.
|
|
365
|
+
this.highlightEl.appendChild(lineWrap);
|
|
363
366
|
}
|
|
364
367
|
}
|
|
365
368
|
//滚动到最底部
|
|
@@ -422,20 +425,30 @@ class Coax extends HTMLElement {
|
|
|
422
425
|
this.codeNameEl.innerHTML = this.alias;
|
|
423
426
|
}
|
|
424
427
|
}
|
|
428
|
+
trimLineString(str) {
|
|
429
|
+
// 删除开头的第一个换行符
|
|
430
|
+
if (str.startsWith('\n')) {
|
|
431
|
+
str = str.substring(1);
|
|
432
|
+
}
|
|
433
|
+
// 删除结尾的第一个换行符
|
|
434
|
+
if (str.endsWith('\n')) {
|
|
435
|
+
str = str.slice(0, -1);
|
|
436
|
+
}
|
|
437
|
+
return str;
|
|
438
|
+
}
|
|
425
439
|
/**
|
|
426
440
|
* Renders the highlighted code within the shadow DOM.
|
|
427
441
|
*/
|
|
428
|
-
render(
|
|
442
|
+
render() {
|
|
429
443
|
//同时多次改变属性,只执行一次
|
|
430
444
|
if (this._renderQueued)
|
|
431
445
|
return;
|
|
432
446
|
this._renderQueued = true;
|
|
433
|
-
// 使用 requestAnimationFrame 将渲染推迟到下一帧
|
|
434
447
|
requestAnimationFrame(async () => {
|
|
435
|
-
this.
|
|
448
|
+
this.highlightEl.innerHTML = '';
|
|
436
449
|
this.injectThemeStyles();
|
|
437
450
|
//一次性渲染
|
|
438
|
-
await this.highlight(
|
|
451
|
+
await this.highlight();
|
|
439
452
|
this._renderQueued = false;
|
|
440
453
|
});
|
|
441
454
|
}
|
|
@@ -443,21 +456,27 @@ class Coax extends HTMLElement {
|
|
|
443
456
|
* Clears the current content and resets the state.
|
|
444
457
|
*/
|
|
445
458
|
clear() {
|
|
446
|
-
this.
|
|
459
|
+
this.highlightEl.innerHTML = this.source = this.lineString = '';
|
|
447
460
|
}
|
|
448
461
|
/**
|
|
449
462
|
* Replaces the existing code with new source code and re-renders.
|
|
450
463
|
* @param newCode - The new source code to replace the existing code.
|
|
451
464
|
*/
|
|
452
465
|
async replace(newCode) {
|
|
453
|
-
|
|
454
|
-
|
|
466
|
+
newCode = escapeHtmlChars(newCode);
|
|
467
|
+
this.source = trimEmptyLines(newCode);
|
|
468
|
+
if (this._renderQueued)
|
|
469
|
+
return;
|
|
470
|
+
this.highlightEl.innerHTML = '';
|
|
471
|
+
//一次性渲染
|
|
472
|
+
await this.highlight();
|
|
455
473
|
}
|
|
456
474
|
/**
|
|
457
475
|
* Appends new source code to the current content and highlights only the new portion.
|
|
458
476
|
* @param newCode - The new source code to append and highlight.
|
|
459
477
|
*/
|
|
460
478
|
async append(newCode) {
|
|
479
|
+
newCode = escapeHtmlChars(newCode);
|
|
461
480
|
// 将新的代码追加到现有代码末尾
|
|
462
481
|
this.source += `\n${newCode}`;
|
|
463
482
|
// 高亮新的部分
|
|
@@ -468,7 +487,7 @@ class Coax extends HTMLElement {
|
|
|
468
487
|
* @returns An object containing the line wrapper and code wrapper for the last line.
|
|
469
488
|
*/
|
|
470
489
|
getLastLine() {
|
|
471
|
-
const lastChild = this.
|
|
490
|
+
const lastChild = this.highlightEl.lastElementChild, lastLine = !lastChild || this.highlightEl.lastElementChild?.completed ?
|
|
472
491
|
this.createLineWrap() : lastChild;
|
|
473
492
|
return {
|
|
474
493
|
lineWrap: lastLine,
|
|
@@ -479,13 +498,13 @@ class Coax extends HTMLElement {
|
|
|
479
498
|
* Marks the current line as completed and updates the displayed code.
|
|
480
499
|
*/
|
|
481
500
|
close() {
|
|
482
|
-
const lineWrap = this.
|
|
501
|
+
const lineWrap = this.highlightEl.lastElementChild;
|
|
483
502
|
if (!lineWrap)
|
|
484
503
|
return;
|
|
485
504
|
lineWrap.completed = true;
|
|
486
505
|
//行结束前保存
|
|
487
506
|
this.lastLineString = this.lineString;
|
|
488
|
-
this.
|
|
507
|
+
this.getLineToFillHighLight(lineWrap?.lastElementChild, this.lineString);
|
|
489
508
|
//一行结束,清空临时行文本
|
|
490
509
|
this.lineString = '';
|
|
491
510
|
}
|
|
@@ -493,7 +512,7 @@ class Coax extends HTMLElement {
|
|
|
493
512
|
* Reopens the last closed line and restores its original content.
|
|
494
513
|
*/
|
|
495
514
|
open() {
|
|
496
|
-
const lineWrap = this.
|
|
515
|
+
const lineWrap = this.highlightEl.lastElementChild;
|
|
497
516
|
if (!lineWrap)
|
|
498
517
|
return;
|
|
499
518
|
lineWrap.completed = false;
|
|
@@ -506,20 +525,21 @@ class Coax extends HTMLElement {
|
|
|
506
525
|
* @param forceClose - Forcefully close the line if set to `true`.
|
|
507
526
|
*/
|
|
508
527
|
stream(str, forceClose = false) {
|
|
509
|
-
|
|
510
|
-
this.
|
|
528
|
+
str = escapeHtmlChars(str);
|
|
529
|
+
const { lineWrap, codeWrap } = this.getLastLine(), isLine = str.startsWith('\n') || str.endsWith('\n');
|
|
530
|
+
this.highlightEl.appendChild(lineWrap);
|
|
511
531
|
//汇集
|
|
512
532
|
this.source += str;
|
|
533
|
+
//临时保存行文本
|
|
534
|
+
this.lineString += isLine ? this.trimLineString(str) : str;
|
|
513
535
|
//如果没有遇到换行符号,也可以强制结束
|
|
514
|
-
if (forceClose ||
|
|
536
|
+
if (forceClose || isLine) {
|
|
515
537
|
//标记完成
|
|
516
538
|
this.close();
|
|
517
539
|
}
|
|
518
540
|
else {
|
|
519
541
|
//插入文本
|
|
520
542
|
codeWrap.innerHTML += str;
|
|
521
|
-
//临时保存行文本
|
|
522
|
-
this.lineString += str;
|
|
523
543
|
}
|
|
524
544
|
//滚动到最底部
|
|
525
545
|
this.autoScrollCode();
|
package/src/components/Coax.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Last modified: 2026/01/
|
|
2
|
+
* Last modified: 2026/01/15 19:21:29
|
|
3
3
|
* Coax - A custom web component for syntax highlighting, code display, and interactive features
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
@@ -10,6 +10,8 @@ import NAMESPACE from "@codady/utils/namespace";
|
|
|
10
10
|
import getEl from "@codady/utils/getEl";
|
|
11
11
|
import createTools, { toolsItem } from "@codady/utils/createTools";
|
|
12
12
|
import createEl from "@codady/utils/createEl";
|
|
13
|
+
import trimEmptyLines from "@codady/utils/trimEmptyLines";
|
|
14
|
+
import escapeHtmlChars from "@codady/utils/escapeHtmlChars";
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
// Define the structure for the language configuration
|
|
@@ -39,7 +41,7 @@ class Coax extends HTMLElement {
|
|
|
39
41
|
private baseStylesEl!: HTMLStyleElement; // Element for base styles
|
|
40
42
|
private themeStylesEl!: HTMLStyleElement; // Element for theme styles
|
|
41
43
|
private dynamicStylesEl!: HTMLStyleElement; // Element for dynamic styles
|
|
42
|
-
private
|
|
44
|
+
private highlightEl!: HTMLElement; // Element that holds the highlighted code
|
|
43
45
|
private headerEl!: HTMLElement; // Header element (for code name, tools, etc.)
|
|
44
46
|
private codeNameEl!: HTMLElement; // Code name element (shows language or alias)
|
|
45
47
|
private codeToolsEl!: HTMLElement; // Code tools element (for interactive tools like copy)
|
|
@@ -51,13 +53,14 @@ class Coax extends HTMLElement {
|
|
|
51
53
|
public lastLineString: string = ''; // The last line's string
|
|
52
54
|
public speed: number = 5; // Speed of the typing effect (higher is slower)
|
|
53
55
|
public autoScroll: boolean = true; // Flag to enable/disable auto-scrolling
|
|
56
|
+
public canListen: boolean = true; // Flag to enable/disable auto-scrolling
|
|
54
57
|
|
|
55
58
|
constructor() {
|
|
56
59
|
super();
|
|
57
60
|
// Attach a Shadow DOM to the custom element
|
|
58
61
|
this.attachShadow({ mode: 'open' });
|
|
59
62
|
// Remove leading/trailing whitespace from the raw code content
|
|
60
|
-
this.source = this.textContent
|
|
63
|
+
this.source = escapeHtmlChars(trimEmptyLines(this.textContent));
|
|
61
64
|
// Initialize the basic structure of the component
|
|
62
65
|
(this.shadowRoot as any).innerHTML = `
|
|
63
66
|
<style id="base-styles">
|
|
@@ -188,7 +191,7 @@ class Coax extends HTMLElement {
|
|
|
188
191
|
<style id="theme-styles"></style>
|
|
189
192
|
<div id="code-header"><span id="code-name">${this.alias}</span><div id="code-tools"></div></div>
|
|
190
193
|
<div id="code-body">
|
|
191
|
-
<pre><code id="highlight
|
|
194
|
+
<pre><code id="highlight"></code></pre>
|
|
192
195
|
</div>
|
|
193
196
|
`;
|
|
194
197
|
|
|
@@ -200,7 +203,7 @@ class Coax extends HTMLElement {
|
|
|
200
203
|
this.codeNameEl = getEl('#code-name', this.shadowRoot) as HTMLElement;
|
|
201
204
|
this.codeToolsEl = getEl('#code-tools', this.shadowRoot) as HTMLElement;
|
|
202
205
|
this.codeBodyEl = getEl('#code-body', this.shadowRoot) as HTMLElement;
|
|
203
|
-
this.
|
|
206
|
+
this.highlightEl = getEl('#highlight', this.shadowRoot) as HTMLElement;
|
|
204
207
|
|
|
205
208
|
this.codeBodyEl.addEventListener('scroll', () => {
|
|
206
209
|
let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
|
|
@@ -257,7 +260,7 @@ class Coax extends HTMLElement {
|
|
|
257
260
|
* @param newVal - The new value of the attribute.
|
|
258
261
|
*/
|
|
259
262
|
attributeChangedCallback(name: string, oldVal: string, newVal: string) {
|
|
260
|
-
if (oldVal === newVal) return;
|
|
263
|
+
if (oldVal === newVal || !this.canListen) return;
|
|
261
264
|
if (name === 'height' || name === 'max-height') {
|
|
262
265
|
this.updateStyleByRegExp(name, newVal);
|
|
263
266
|
}
|
|
@@ -328,9 +331,9 @@ class Coax extends HTMLElement {
|
|
|
328
331
|
createLineWrap(index?: number, startIndex?: number) {
|
|
329
332
|
let dataIndex = 0;
|
|
330
333
|
if (index == null && startIndex == null) {
|
|
331
|
-
dataIndex = this.
|
|
334
|
+
dataIndex = this.highlightEl.children.length;
|
|
332
335
|
} else {
|
|
333
|
-
const start = startIndex || this.
|
|
336
|
+
const start = startIndex || this.highlightEl.children.length;
|
|
334
337
|
dataIndex = start + (index as number);
|
|
335
338
|
}
|
|
336
339
|
return createEl('div', { 'data-index': dataIndex }, '<div></div>');
|
|
@@ -341,7 +344,7 @@ class Coax extends HTMLElement {
|
|
|
341
344
|
* @param line - The line of code to highlight.
|
|
342
345
|
* @param config - The language configuration object.
|
|
343
346
|
*/
|
|
344
|
-
|
|
347
|
+
getLineToFillHighLight(codeWrap: Element, line: string, config?: LanguageConfig) {
|
|
345
348
|
config = config || Coax.languages.get(this.lang);
|
|
346
349
|
let highlightedLine = this.getHighLightString(line, config);
|
|
347
350
|
// 将高亮后的内容填充到 div 中
|
|
@@ -351,26 +354,28 @@ class Coax extends HTMLElement {
|
|
|
351
354
|
* Highlights new source code and appends it to the code body.
|
|
352
355
|
* @param newCode - The new source code to highlight and append.
|
|
353
356
|
*/
|
|
354
|
-
async highlight(newCode: string) {
|
|
357
|
+
async highlight(newCode: string = this.source) {
|
|
355
358
|
const config = Coax.languages.get(this.lang),
|
|
356
|
-
startIndex = this.
|
|
359
|
+
startIndex = this.highlightEl.children.length,
|
|
357
360
|
// 将新源码按行分割
|
|
358
|
-
newCodeLines = newCode ? newCode.split('\n') : []
|
|
361
|
+
newCodeLines = newCode ? newCode.split('\n') : [],
|
|
362
|
+
hasSpeedAttr = this.hasAttribute('speed'),
|
|
363
|
+
hasSanitizedAttr = this.hasAttribute('sanitized');
|
|
359
364
|
//更新别名
|
|
360
|
-
this.updateName(config)
|
|
365
|
+
this.updateName(config);
|
|
361
366
|
if (!newCodeLines.length) return true;
|
|
362
367
|
// 如果没有找到配置,则输出原始代码,并不进行高亮处理
|
|
363
368
|
for (let [index, lineString] of newCodeLines.entries()) {
|
|
364
369
|
//如果是空行则跳过
|
|
365
|
-
if (!lineString.trim() &&
|
|
370
|
+
if (!lineString.trim() && hasSanitizedAttr) continue;
|
|
366
371
|
// 创建一个 div 包裹每一行
|
|
367
372
|
const lineWrap = this.createLineWrap(index, startIndex),
|
|
368
373
|
codeWrap = lineWrap.lastElementChild as Element;
|
|
369
374
|
//标记完成
|
|
370
375
|
(lineWrap as any).completed = true;
|
|
371
376
|
|
|
372
|
-
if (
|
|
373
|
-
this.
|
|
377
|
+
if (hasSpeedAttr) {
|
|
378
|
+
this.highlightEl.appendChild(lineWrap);
|
|
374
379
|
//流式打字
|
|
375
380
|
await typeWriter(lineString, {
|
|
376
381
|
speed: this.speed,
|
|
@@ -378,12 +383,12 @@ class Coax extends HTMLElement {
|
|
|
378
383
|
codeWrap.innerHTML = fullText;
|
|
379
384
|
}
|
|
380
385
|
});
|
|
381
|
-
this.
|
|
386
|
+
this.getLineToFillHighLight(codeWrap, lineString, config);
|
|
382
387
|
} else {
|
|
383
388
|
//直接打出
|
|
384
|
-
this.
|
|
389
|
+
this.getLineToFillHighLight(codeWrap, lineString, config);
|
|
385
390
|
//
|
|
386
|
-
this.
|
|
391
|
+
this.highlightEl.appendChild(lineWrap);
|
|
387
392
|
}
|
|
388
393
|
}
|
|
389
394
|
//滚动到最底部
|
|
@@ -451,41 +456,60 @@ class Coax extends HTMLElement {
|
|
|
451
456
|
this.codeNameEl.innerHTML = this.alias;
|
|
452
457
|
}
|
|
453
458
|
}
|
|
459
|
+
|
|
460
|
+
trimLineString(str: string) {
|
|
461
|
+
// 删除开头的第一个换行符
|
|
462
|
+
if (str.startsWith('\n')) {
|
|
463
|
+
str = str.substring(1);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 删除结尾的第一个换行符
|
|
467
|
+
if (str.endsWith('\n')) {
|
|
468
|
+
str = str.slice(0, -1);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return str;
|
|
472
|
+
}
|
|
454
473
|
/**
|
|
455
474
|
* Renders the highlighted code within the shadow DOM.
|
|
456
475
|
*/
|
|
457
|
-
render(
|
|
476
|
+
render() {
|
|
458
477
|
//同时多次改变属性,只执行一次
|
|
459
478
|
if (this._renderQueued) return;
|
|
460
479
|
this._renderQueued = true;
|
|
461
|
-
// 使用 requestAnimationFrame 将渲染推迟到下一帧
|
|
462
480
|
requestAnimationFrame(async () => {
|
|
463
|
-
this.
|
|
481
|
+
this.highlightEl.innerHTML = '';
|
|
464
482
|
this.injectThemeStyles();
|
|
465
483
|
//一次性渲染
|
|
466
|
-
await this.highlight(
|
|
484
|
+
await this.highlight();
|
|
467
485
|
this._renderQueued = false;
|
|
468
486
|
});
|
|
487
|
+
|
|
469
488
|
}
|
|
470
489
|
/**
|
|
471
490
|
* Clears the current content and resets the state.
|
|
472
491
|
*/
|
|
473
492
|
clear() {
|
|
474
|
-
this.
|
|
493
|
+
this.highlightEl.innerHTML = this.source = this.lineString = '';
|
|
475
494
|
}
|
|
476
495
|
/**
|
|
477
496
|
* Replaces the existing code with new source code and re-renders.
|
|
478
497
|
* @param newCode - The new source code to replace the existing code.
|
|
479
498
|
*/
|
|
480
499
|
async replace(newCode: string) {
|
|
481
|
-
|
|
482
|
-
|
|
500
|
+
newCode = escapeHtmlChars(newCode);
|
|
501
|
+
this.source = trimEmptyLines(newCode);
|
|
502
|
+
if (this._renderQueued) return;
|
|
503
|
+
this.highlightEl.innerHTML = '';
|
|
504
|
+
//一次性渲染
|
|
505
|
+
await this.highlight();
|
|
483
506
|
}
|
|
484
507
|
/**
|
|
485
508
|
* Appends new source code to the current content and highlights only the new portion.
|
|
486
509
|
* @param newCode - The new source code to append and highlight.
|
|
487
510
|
*/
|
|
488
511
|
async append(newCode: string) {
|
|
512
|
+
newCode = escapeHtmlChars(newCode);
|
|
489
513
|
// 将新的代码追加到现有代码末尾
|
|
490
514
|
this.source += `\n${newCode}`;
|
|
491
515
|
// 高亮新的部分
|
|
@@ -496,8 +520,8 @@ class Coax extends HTMLElement {
|
|
|
496
520
|
* @returns An object containing the line wrapper and code wrapper for the last line.
|
|
497
521
|
*/
|
|
498
522
|
getLastLine() {
|
|
499
|
-
const lastChild = this.
|
|
500
|
-
lastLine = !lastChild || (this.
|
|
523
|
+
const lastChild = this.highlightEl.lastElementChild,
|
|
524
|
+
lastLine = !lastChild || (this.highlightEl.lastElementChild as any)?.completed ?
|
|
501
525
|
this.createLineWrap() : lastChild;
|
|
502
526
|
return {
|
|
503
527
|
lineWrap: lastLine,
|
|
@@ -508,12 +532,12 @@ class Coax extends HTMLElement {
|
|
|
508
532
|
* Marks the current line as completed and updates the displayed code.
|
|
509
533
|
*/
|
|
510
534
|
close() {
|
|
511
|
-
const lineWrap = this.
|
|
535
|
+
const lineWrap = this.highlightEl.lastElementChild;
|
|
512
536
|
if (!lineWrap) return;
|
|
513
537
|
(lineWrap as any).completed = true;
|
|
514
538
|
//行结束前保存
|
|
515
539
|
this.lastLineString = this.lineString;
|
|
516
|
-
this.
|
|
540
|
+
this.getLineToFillHighLight(lineWrap?.lastElementChild as Element, this.lineString);
|
|
517
541
|
//一行结束,清空临时行文本
|
|
518
542
|
this.lineString = '';
|
|
519
543
|
}
|
|
@@ -521,7 +545,7 @@ class Coax extends HTMLElement {
|
|
|
521
545
|
* Reopens the last closed line and restores its original content.
|
|
522
546
|
*/
|
|
523
547
|
open() {
|
|
524
|
-
const lineWrap = this.
|
|
548
|
+
const lineWrap = this.highlightEl.lastElementChild;
|
|
525
549
|
if (!lineWrap) return;
|
|
526
550
|
(lineWrap as any).completed = false;
|
|
527
551
|
//恢复最后一行字符串
|
|
@@ -533,20 +557,23 @@ class Coax extends HTMLElement {
|
|
|
533
557
|
* @param forceClose - Forcefully close the line if set to `true`.
|
|
534
558
|
*/
|
|
535
559
|
stream(str: string, forceClose: boolean = false) {
|
|
536
|
-
|
|
537
|
-
this.
|
|
560
|
+
str = escapeHtmlChars(str);
|
|
561
|
+
const { lineWrap, codeWrap } = this.getLastLine(),
|
|
562
|
+
isLine = str.startsWith('\n') || str.endsWith('\n');
|
|
563
|
+
this.highlightEl.appendChild(lineWrap);
|
|
538
564
|
//汇集
|
|
539
565
|
this.source += str;
|
|
540
566
|
|
|
567
|
+
//临时保存行文本
|
|
568
|
+
this.lineString += isLine ? this.trimLineString(str) : str;
|
|
569
|
+
|
|
541
570
|
//如果没有遇到换行符号,也可以强制结束
|
|
542
|
-
if (forceClose ||
|
|
571
|
+
if (forceClose || isLine) {
|
|
543
572
|
//标记完成
|
|
544
573
|
this.close();
|
|
545
574
|
} else {
|
|
546
575
|
//插入文本
|
|
547
576
|
codeWrap.innerHTML += str;
|
|
548
|
-
//临时保存行文本
|
|
549
|
-
this.lineString += str;
|
|
550
577
|
}
|
|
551
578
|
//滚动到最底部
|
|
552
579
|
this.autoScrollCode();
|
package/src/modules.js
CHANGED
package/src/modules.ts
CHANGED
package/examples/.htaccess
DELETED
|
File without changes
|
package/examples/nginx.htaccess
DELETED
|
File without changes
|