@codady/coax 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Last modified: 2026/01/12 15:11:21
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();
@@ -36,7 +38,7 @@ class Coax extends HTMLElement {
36
38
  // Attach a Shadow DOM to the custom element
37
39
  this.attachShadow({ mode: 'open' });
38
40
  // Remove leading/trailing whitespace from the raw code content
39
- this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
41
+ this.source = escapeHtmlChars(trimEmptyLines(this.textContent));
40
42
  // Initialize the basic structure of the component
41
43
  this.shadowRoot.innerHTML = `
42
44
  <style id="base-styles">
@@ -317,7 +319,7 @@ class Coax extends HTMLElement {
317
319
  * @param line - The line of code to highlight.
318
320
  * @param config - The language configuration object.
319
321
  */
320
- getLineToFill(codeWrap, line, config) {
322
+ getLineToFillHighLight(codeWrap, line, config) {
321
323
  config = config || Coax.languages.get(this.lang);
322
324
  let highlightedLine = this.getHighLightString(line, config);
323
325
  // 将高亮后的内容填充到 div 中
@@ -328,10 +330,10 @@ class Coax extends HTMLElement {
328
330
  * Highlights new source code and appends it to the code body.
329
331
  * @param newCode - The new source code to highlight and append.
330
332
  */
331
- async highlight(newCode) {
333
+ async highlight(newCode = this.source) {
332
334
  const config = Coax.languages.get(this.lang), startIndex = this.highlightEl.children.length,
333
335
  // 将新源码按行分割
334
- newCodeLines = newCode ? newCode.split('\n') : [];
336
+ newCodeLines = newCode ? newCode.split('\n') : [], hasSpeedAttr = this.hasAttribute('speed'), hasSanitizedAttr = this.hasAttribute('sanitized');
335
337
  //更新别名
336
338
  this.updateName(config);
337
339
  if (!newCodeLines.length)
@@ -339,13 +341,13 @@ class Coax extends HTMLElement {
339
341
  // 如果没有找到配置,则输出原始代码,并不进行高亮处理
340
342
  for (let [index, lineString] of newCodeLines.entries()) {
341
343
  //如果是空行则跳过
342
- if (!lineString.trim() && this.hasAttribute('sanitized'))
344
+ if (!lineString.trim() && hasSanitizedAttr)
343
345
  continue;
344
346
  // 创建一个 div 包裹每一行
345
347
  const lineWrap = this.createLineWrap(index, startIndex), codeWrap = lineWrap.lastElementChild;
346
348
  //标记完成
347
349
  lineWrap.completed = true;
348
- if (this.hasAttribute('speed')) {
350
+ if (hasSpeedAttr) {
349
351
  this.highlightEl.appendChild(lineWrap);
350
352
  //流式打字
351
353
  await typeWriter(lineString, {
@@ -354,11 +356,11 @@ class Coax extends HTMLElement {
354
356
  codeWrap.innerHTML = fullText;
355
357
  }
356
358
  });
357
- this.getLineToFill(codeWrap, lineString, config);
359
+ this.getLineToFillHighLight(codeWrap, lineString, config);
358
360
  }
359
361
  else {
360
362
  //直接打出
361
- this.getLineToFill(codeWrap, lineString, config);
363
+ this.getLineToFillHighLight(codeWrap, lineString, config);
362
364
  //
363
365
  this.highlightEl.appendChild(lineWrap);
364
366
  }
@@ -423,19 +425,32 @@ class Coax extends HTMLElement {
423
425
  this.codeNameEl.innerHTML = this.alias;
424
426
  }
425
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
+ }
426
439
  /**
427
440
  * Renders the highlighted code within the shadow DOM.
428
441
  */
429
- render(code = this.source) {
442
+ render() {
430
443
  //同时多次改变属性,只执行一次
431
444
  if (this._renderQueued)
432
445
  return;
433
446
  this._renderQueued = true;
434
- this.clear();
435
- this.injectThemeStyles();
436
- //一次性渲染
437
- this.highlight(code);
438
- this._renderQueued = false;
447
+ requestAnimationFrame(async () => {
448
+ this.highlightEl.innerHTML = '';
449
+ this.injectThemeStyles();
450
+ //一次性渲染
451
+ await this.highlight();
452
+ this._renderQueued = false;
453
+ });
439
454
  }
440
455
  /**
441
456
  * Clears the current content and resets the state.
@@ -448,14 +463,20 @@ class Coax extends HTMLElement {
448
463
  * @param newCode - The new source code to replace the existing code.
449
464
  */
450
465
  async replace(newCode) {
451
- this.clear();
452
- await this.highlight(newCode);
466
+ newCode = escapeHtmlChars(newCode);
467
+ this.source = trimEmptyLines(newCode);
468
+ if (this._renderQueued)
469
+ return;
470
+ this.highlightEl.innerHTML = '';
471
+ //一次性渲染
472
+ await this.highlight();
453
473
  }
454
474
  /**
455
475
  * Appends new source code to the current content and highlights only the new portion.
456
476
  * @param newCode - The new source code to append and highlight.
457
477
  */
458
478
  async append(newCode) {
479
+ newCode = escapeHtmlChars(newCode);
459
480
  // 将新的代码追加到现有代码末尾
460
481
  this.source += `\n${newCode}`;
461
482
  // 高亮新的部分
@@ -483,7 +504,7 @@ class Coax extends HTMLElement {
483
504
  lineWrap.completed = true;
484
505
  //行结束前保存
485
506
  this.lastLineString = this.lineString;
486
- this.getLineToFill(lineWrap?.lastElementChild, this.lineString);
507
+ this.getLineToFillHighLight(lineWrap?.lastElementChild, this.lineString);
487
508
  //一行结束,清空临时行文本
488
509
  this.lineString = '';
489
510
  }
@@ -504,20 +525,21 @@ class Coax extends HTMLElement {
504
525
  * @param forceClose - Forcefully close the line if set to `true`.
505
526
  */
506
527
  stream(str, forceClose = false) {
507
- const { lineWrap, codeWrap } = this.getLastLine();
528
+ str = escapeHtmlChars(str);
529
+ const { lineWrap, codeWrap } = this.getLastLine(), isLine = str.startsWith('\n') || str.endsWith('\n');
508
530
  this.highlightEl.appendChild(lineWrap);
509
531
  //汇集
510
532
  this.source += str;
533
+ //临时保存行文本
534
+ this.lineString += isLine ? this.trimLineString(str) : str;
511
535
  //如果没有遇到换行符号,也可以强制结束
512
- if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
536
+ if (forceClose || isLine) {
513
537
  //标记完成
514
538
  this.close();
515
539
  }
516
540
  else {
517
541
  //插入文本
518
542
  codeWrap.innerHTML += str;
519
- //临时保存行文本
520
- this.lineString += str;
521
543
  }
522
544
  //滚动到最底部
523
545
  this.autoScrollCode();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Last modified: 2026/01/12 15:11:21
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
@@ -58,7 +60,7 @@ class Coax extends HTMLElement {
58
60
  // Attach a Shadow DOM to the custom element
59
61
  this.attachShadow({ mode: 'open' });
60
62
  // Remove leading/trailing whitespace from the raw code content
61
- this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
63
+ this.source = escapeHtmlChars(trimEmptyLines(this.textContent));
62
64
  // Initialize the basic structure of the component
63
65
  (this.shadowRoot as any).innerHTML = `
64
66
  <style id="base-styles">
@@ -342,7 +344,7 @@ class Coax extends HTMLElement {
342
344
  * @param line - The line of code to highlight.
343
345
  * @param config - The language configuration object.
344
346
  */
345
- getLineToFill(codeWrap: Element, line: string, config?: LanguageConfig) {
347
+ getLineToFillHighLight(codeWrap: Element, line: string, config?: LanguageConfig) {
346
348
  config = config || Coax.languages.get(this.lang);
347
349
  let highlightedLine = this.getHighLightString(line, config);
348
350
  // 将高亮后的内容填充到 div 中
@@ -352,25 +354,27 @@ class Coax extends HTMLElement {
352
354
  * Highlights new source code and appends it to the code body.
353
355
  * @param newCode - The new source code to highlight and append.
354
356
  */
355
- async highlight(newCode: string) {
357
+ async highlight(newCode: string = this.source) {
356
358
  const config = Coax.languages.get(this.lang),
357
359
  startIndex = this.highlightEl.children.length,
358
360
  // 将新源码按行分割
359
- newCodeLines = newCode ? newCode.split('\n') : [];
361
+ newCodeLines = newCode ? newCode.split('\n') : [],
362
+ hasSpeedAttr = this.hasAttribute('speed'),
363
+ hasSanitizedAttr = this.hasAttribute('sanitized');
360
364
  //更新别名
361
- this.updateName(config)
365
+ this.updateName(config);
362
366
  if (!newCodeLines.length) return true;
363
367
  // 如果没有找到配置,则输出原始代码,并不进行高亮处理
364
368
  for (let [index, lineString] of newCodeLines.entries()) {
365
369
  //如果是空行则跳过
366
- if (!lineString.trim() && this.hasAttribute('sanitized')) continue;
370
+ if (!lineString.trim() && hasSanitizedAttr) continue;
367
371
  // 创建一个 div 包裹每一行
368
372
  const lineWrap = this.createLineWrap(index, startIndex),
369
373
  codeWrap = lineWrap.lastElementChild as Element;
370
374
  //标记完成
371
375
  (lineWrap as any).completed = true;
372
376
 
373
- if (this.hasAttribute('speed')) {
377
+ if (hasSpeedAttr) {
374
378
  this.highlightEl.appendChild(lineWrap);
375
379
  //流式打字
376
380
  await typeWriter(lineString, {
@@ -379,10 +383,10 @@ class Coax extends HTMLElement {
379
383
  codeWrap.innerHTML = fullText;
380
384
  }
381
385
  });
382
- this.getLineToFill(codeWrap, lineString, config);
386
+ this.getLineToFillHighLight(codeWrap, lineString, config);
383
387
  } else {
384
388
  //直接打出
385
- this.getLineToFill(codeWrap, lineString, config);
389
+ this.getLineToFillHighLight(codeWrap, lineString, config);
386
390
  //
387
391
  this.highlightEl.appendChild(lineWrap);
388
392
  }
@@ -452,18 +456,35 @@ class Coax extends HTMLElement {
452
456
  this.codeNameEl.innerHTML = this.alias;
453
457
  }
454
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
+ }
455
473
  /**
456
474
  * Renders the highlighted code within the shadow DOM.
457
475
  */
458
- render(code = this.source) {
476
+ render() {
459
477
  //同时多次改变属性,只执行一次
460
478
  if (this._renderQueued) return;
461
479
  this._renderQueued = true;
462
- this.clear();
463
- this.injectThemeStyles();
464
- //一次性渲染
465
- this.highlight(code);
466
- this._renderQueued = false;
480
+ requestAnimationFrame(async () => {
481
+ this.highlightEl.innerHTML = '';
482
+ this.injectThemeStyles();
483
+ //一次性渲染
484
+ await this.highlight();
485
+ this._renderQueued = false;
486
+ });
487
+
467
488
  }
468
489
  /**
469
490
  * Clears the current content and resets the state.
@@ -476,14 +497,19 @@ class Coax extends HTMLElement {
476
497
  * @param newCode - The new source code to replace the existing code.
477
498
  */
478
499
  async replace(newCode: string) {
479
- this.clear();
480
- await this.highlight(newCode);
500
+ newCode = escapeHtmlChars(newCode);
501
+ this.source = trimEmptyLines(newCode);
502
+ if (this._renderQueued) return;
503
+ this.highlightEl.innerHTML = '';
504
+ //一次性渲染
505
+ await this.highlight();
481
506
  }
482
507
  /**
483
508
  * Appends new source code to the current content and highlights only the new portion.
484
509
  * @param newCode - The new source code to append and highlight.
485
510
  */
486
511
  async append(newCode: string) {
512
+ newCode = escapeHtmlChars(newCode);
487
513
  // 将新的代码追加到现有代码末尾
488
514
  this.source += `\n${newCode}`;
489
515
  // 高亮新的部分
@@ -511,7 +537,7 @@ class Coax extends HTMLElement {
511
537
  (lineWrap as any).completed = true;
512
538
  //行结束前保存
513
539
  this.lastLineString = this.lineString;
514
- this.getLineToFill(lineWrap?.lastElementChild as Element, this.lineString);
540
+ this.getLineToFillHighLight(lineWrap?.lastElementChild as Element, this.lineString);
515
541
  //一行结束,清空临时行文本
516
542
  this.lineString = '';
517
543
  }
@@ -531,20 +557,23 @@ class Coax extends HTMLElement {
531
557
  * @param forceClose - Forcefully close the line if set to `true`.
532
558
  */
533
559
  stream(str: string, forceClose: boolean = false) {
534
- const { lineWrap, codeWrap } = this.getLastLine();
560
+ str = escapeHtmlChars(str);
561
+ const { lineWrap, codeWrap } = this.getLastLine(),
562
+ isLine = str.startsWith('\n') || str.endsWith('\n');
535
563
  this.highlightEl.appendChild(lineWrap);
536
564
  //汇集
537
565
  this.source += str;
538
566
 
567
+ //临时保存行文本
568
+ this.lineString += isLine ? this.trimLineString(str) : str;
569
+
539
570
  //如果没有遇到换行符号,也可以强制结束
540
- if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
571
+ if (forceClose || isLine) {
541
572
  //标记完成
542
573
  this.close();
543
574
  } else {
544
575
  //插入文本
545
576
  codeWrap.innerHTML += str;
546
- //临时保存行文本
547
- this.lineString += str;
548
577
  }
549
578
  //滚动到最底部
550
579
  this.autoScrollCode();
package/src/modules.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @since Last modified: 2026/01/12 14:10:29
2
+ * @since Last modified: 2026/01/15 19:21:31
3
3
  */
4
4
  'use strict';
5
5
  import Coax from './components/Coax.js';
package/src/modules.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @since Last modified: 2026/01/12 14:10:29
2
+ * @since Last modified: 2026/01/15 19:21:31
3
3
  */
4
4
  'use strict'
5
5