@codady/coax 0.0.2 → 0.0.4

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.
Files changed (43) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +331 -166
  3. package/dist/coax.cjs.js +450 -271
  4. package/dist/coax.cjs.min.js +4 -4
  5. package/dist/coax.esm.js +444 -271
  6. package/dist/coax.esm.min.js +4 -4
  7. package/dist/coax.umd.js +466 -266
  8. package/dist/coax.umd.min.js +4 -4
  9. package/examples/.htaccess +0 -0
  10. package/examples/append-highlight.html +82 -0
  11. package/examples/color-selector.html +412 -0
  12. package/examples/deepseek-highlight.html +100 -0
  13. package/examples/js-highlight.html +1 -1
  14. package/examples/md-highlight.html +60 -0
  15. package/examples/nginx.htaccess +0 -0
  16. package/examples/replace-highlight.html +69 -0
  17. package/examples/stream-highlight.html +64 -0
  18. package/examples/theme-highlight.html +69 -0
  19. package/package.json +4 -4
  20. package/rollup.config.js +3 -3
  21. package/script-note.js +2 -2
  22. package/src/Coax.js +25 -414
  23. package/src/Coax.ts +28 -443
  24. package/src/components/Coax.js +528 -0
  25. package/src/components/Coax.ts +556 -0
  26. package/src/modules.js +12 -0
  27. package/src/modules.ts +23 -0
  28. package/src/rules/css.js +11 -0
  29. package/src/rules/css.ts +11 -0
  30. package/src/rules/html.js +13 -0
  31. package/src/rules/html.ts +13 -0
  32. package/src/rules/javascript.js +10 -0
  33. package/src/rules/javascript.ts +10 -0
  34. package/src/rules/markdown.js +29 -0
  35. package/src/rules/markdown.ts +41 -0
  36. package/src/rules/ruleCss - /345/211/257/346/234/254.js" +10 -0
  37. package/src/rules/ruleHTML - /345/211/257/346/234/254.js" +12 -0
  38. package/src/rules/ruleJs - /345/211/257/346/234/254.js" +10 -0
  39. package/src/rules/ruleTs - /345/211/257/346/234/254.js" +12 -0
  40. package/src/rules/typescript.js +12 -0
  41. package/src/rules/typescript.ts +12 -0
  42. package/src/tools/copy.js +26 -0
  43. package/src/tools/copy.ts +29 -0
package/dist/coax.esm.js CHANGED
@@ -1,10 +1,10 @@
1
1
 
2
2
  /*!
3
- * @since Last modified: 2026-1-8 16:55:44
3
+ * @since Last modified: 2026-1-12 14:46:3
4
4
  * @name Coax event management system.
5
- * @version 0.0.2
5
+ * @version 0.0.4
6
6
  * @author AXUI development team <3217728223@qq.com>
7
- * @description Coax is a custom web component that enables syntax highlighting for various programming languages inside your HTML documents.
7
+ * @description Coax is a lightweight web component for elegant code display with syntax highlighting, typewriter effects, and theme switching. Supports JavaScript, HTML, CSS, TypeScript, and Markdown with copy tools and customization.
8
8
  * @see {@link https://coax.axui.cn|Official website}
9
9
  * @see {@link https://github.com/codady/coax/issues|github issues}
10
10
  * @see {@link https://gitee.com/codady/coax/issues|Gitee issues}
@@ -14,6 +14,75 @@
14
14
  * @license MIT license
15
15
  */
16
16
 
17
+ const typeWriter = (text, options) => {
18
+ const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
19
+ return new Promise((resolve) => {
20
+ // Callback before typing starts
21
+ options?.onBeforeType?.(text);
22
+ let index = 0;
23
+ // Timer to type the text character by character at the given speed
24
+ const timer = setInterval(() => {
25
+ if (index < text.length) {
26
+ const char = text.charAt(index); // Get the character at the current index
27
+ const typedText = text.substring(0, index + 1); // The text typed so far
28
+ // Callback during typing each character
29
+ options?.onDuringType?.(char, typedText);
30
+ index++;
31
+ }
32
+ else {
33
+ // Clear the timer once typing is complete
34
+ clearInterval(timer);
35
+ // Resolve the Promise when typing is complete
36
+ resolve(text);
37
+ // Callback after typing is finished
38
+ options?.onAfterType?.(text);
39
+ }
40
+ }, speed);
41
+ });
42
+ };
43
+
44
+ const COMMA$1 = ',';
45
+
46
+ const SPACE$1 = ' ';
47
+
48
+ const trim$1 = (str, placement = 'compress') => {
49
+ if (typeof str !== 'string') {
50
+ console.warn('Expected a string input');
51
+ return '';
52
+ }
53
+ switch (placement) {
54
+ case 'start':
55
+ return str.trimStart();
56
+ case 'end':
57
+ return str.trimEnd();
58
+ case 'both':
59
+ return str.trim();
60
+ case 'global':
61
+ return str.replace(/[\s\r\n]+/g, '');
62
+ default:
63
+ return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
64
+ }
65
+ };
66
+
67
+ const parseClasses$1 = (data) => {
68
+ let separator, result = [];
69
+ if (Array.isArray(data)) {
70
+ // If data is already an array, filter out invalid values
71
+ result = data.filter((k) => k && typeof k === 'string');
72
+ }
73
+ else {
74
+ // Trim the input string and handle multiple spaces
75
+ data = trim$1(data);
76
+ // Use comma as the separator if present, otherwise use space
77
+ separator = data.includes(COMMA$1) ? COMMA$1 : SPACE$1;
78
+ result = data.split(separator);
79
+ }
80
+ // Trim each item globally and filter out any empty strings
81
+ return result.map((k) => trim$1(k, 'global')).filter(Boolean);
82
+ };
83
+
84
+ const NAMESPACE = 'ax';
85
+
17
86
  const getDataType = (obj) => {
18
87
  let tmp = Object.prototype.toString.call(obj).slice(8, -1), result;
19
88
  if (tmp === 'Function' && /^\s*class\s+/.test(obj.toString())) {
@@ -156,48 +225,6 @@ const isEmpty = (data) => {
156
225
 
157
226
  const ALIAS = 'rep';
158
227
 
159
- const NAMESPACE = 'ax';
160
-
161
- const COMMA$1 = ',';
162
-
163
- const SPACE$1 = ' ';
164
-
165
- const trim$1 = (str, placement = '') => {
166
- if (typeof str !== 'string') {
167
- console.warn('Expected a string input');
168
- return '';
169
- }
170
- switch (placement) {
171
- case 'start':
172
- return str.trimStart();
173
- case 'end':
174
- return str.trimEnd();
175
- case 'both':
176
- return str.trim();
177
- case 'global':
178
- return str.replace(/[\s\r\n]+/g, '');
179
- default:
180
- return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
181
- }
182
- };
183
-
184
- const parseClasses$1 = (data) => {
185
- let separator, result = [];
186
- if (Array.isArray(data)) {
187
- // If data is already an array, filter out invalid values
188
- result = data.filter((k) => k && typeof k === 'string');
189
- }
190
- else {
191
- // Trim the input string and handle multiple spaces
192
- data = trim$1(data);
193
- // Use comma as the separator if present, otherwise use space
194
- separator = data.includes(COMMA$1) ? COMMA$1 : SPACE$1;
195
- result = data.split(separator);
196
- }
197
- // Trim each item globally and filter out any empty strings
198
- return result.map((k) => trim$1(k, 'global')).filter(Boolean);
199
- };
200
-
201
228
  const addClasses = (target, classes, intercept) => {
202
229
  const el = getEl(target), arr = parseClasses$1(classes);
203
230
  if (!el || arr.length === 0) {
@@ -239,94 +266,34 @@ const createTools = (data) => {
239
266
  return toolsEl;
240
267
  };
241
268
 
242
- const COMMA = ',';
243
-
244
- const SPACE = ' ';
245
-
246
- const trim = (str, placement = '') => {
247
- if (typeof str !== 'string') {
248
- console.warn('Expected a string input');
249
- return '';
250
- }
251
- switch (placement) {
252
- case 'start':
253
- return str.trimStart();
254
- case 'end':
255
- return str.trimEnd();
256
- case 'both':
257
- return str.trim();
258
- case 'global':
259
- return str.replace(/[\s\r\n]+/g, '');
260
- default:
261
- return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
262
- }
263
- };
264
-
265
- const parseClasses = (data) => {
266
- let separator, result = [];
267
- if (Array.isArray(data)) {
268
- // If data is already an array, filter out invalid values
269
- result = data.filter((k) => k && typeof k === 'string');
270
- }
271
- else {
272
- // Trim the input string and handle multiple spaces
273
- data = trim(data);
274
- // Use comma as the separator if present, otherwise use space
275
- separator = data.includes(COMMA) ? COMMA : SPACE;
276
- result = data.split(separator);
277
- }
278
- // Trim each item globally and filter out any empty strings
279
- return result.map((k) => trim(k, 'global')).filter(Boolean);
280
- };
281
-
282
- const rtlStyle = (name = '') => `
283
- <style>
284
- :where([dir="rtl"]) .icax-${name},
285
- :where(:dir(rtl)) .icax-${name} {
286
- transform: scaleX(-1);
287
- transform-origin: center;
288
- }
289
- </style>
290
- `;
291
-
292
- const wrap = (content, fun, isRtl = false, options) => {
293
- const size = options?.size || '1em', color = options?.color || 'currentColor', thickness = options?.thickness || 2, classes = options?.classes ? parseClasses(options.classes).join(' ') : '',
294
- // 得到 "icax-left"
295
- origName = fun.name.replace(/([A-Z])/g, "-$1").toLowerCase();
296
- return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}"
297
- stroke-width="${thickness}" stroke-linecap="round" stroke-linejoin="round" class="${origName} ${classes}">
298
- ${isRtl ? rtlStyle(origName.split('-')[1]) : ''}
299
- ${content}
300
- </svg>`;
301
- };
302
-
303
- const icaxCopy = (options) => wrap(`<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>`, icaxCopy, false, options);
304
-
305
- const icaxCheck = (options) => wrap(`<polyline points="20 6 9 17 4 12"></polyline>`, icaxCheck, false, options);
306
-
307
269
  class Coax extends HTMLElement {
308
270
  // A static Map to hold the configuration for different languages
309
271
  static languages = new Map();
272
+ // A static array to hold the tools registered with the component
310
273
  static tools = [];
311
- source;
312
- _renderQueued = false;
313
- baseStylesEl;
314
- themeStylesEl;
315
- dynamicStylesEl;
316
- highlightedCodeEl;
317
- headerEl;
318
- nameEl;
319
- toolsEl;
320
- alias;
274
+ source; // Source code content to be highlighted
275
+ _renderQueued = false; // Flag to prevent multiple render requests
276
+ baseStylesEl; // Element for base styles
277
+ themeStylesEl; // Element for theme styles
278
+ dynamicStylesEl; // Element for dynamic styles
279
+ highlightedCodeEl; // Element that holds the highlighted code
280
+ headerEl; // Header element (for code name, tools, etc.)
281
+ codeNameEl; // Code name element (shows language or alias)
282
+ codeToolsEl; // Code tools element (for interactive tools like copy)
283
+ codeBodyEl; // Code body element (the container for the code)
284
+ lang = 'plain'; // Language of the code (default is plain text)
285
+ alias = 'Plain Text'; // Alias name for the language (default is 'Plain Text')
286
+ lineString = ''; // The current line's string being typed
287
+ lastLineString = ''; // The last line's string
288
+ speed = 5; // Speed of the typing effect (higher is slower)
289
+ autoScroll = true; // Flag to enable/disable auto-scrolling
321
290
  constructor() {
322
291
  super();
323
292
  // Attach a Shadow DOM to the custom element
324
293
  this.attachShadow({ mode: 'open' });
325
294
  // Remove leading/trailing whitespace from the raw code content
326
295
  this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
327
- this.lang = 'plain';
328
- this.alias = 'Plain Text';
329
- // 1. 初始化基础骨架
296
+ // Initialize the basic structure of the component
330
297
  this.shadowRoot.innerHTML = `
331
298
  <style id="base-styles">
332
299
  :host {
@@ -343,16 +310,16 @@ class Coax extends HTMLElement {
343
310
  --border-color:rgb(224, 224, 224);
344
311
  --color-code:rgb(51, 51, 51);
345
312
  --color-index:rgb(153, 153, 153);
346
- --color-stripe:rgb(224, 224, 224);
347
- --color-hover:rgb(224, 224, 224);
313
+ --color-stripe:rgba(0,0,0,0.04);
314
+ --color-hover:rgba(0,0,0,0.06);
348
315
  }
349
316
  :host([scheme="dark"]){
350
317
  --background: #282c34;
351
318
  --border-color: transparent;
352
319
  --color-code: #abb2bf;
353
320
  --color-index:rgb(153, 153, 153);
354
- --color-stripe:rgb(66, 66, 66);
355
- --color-hover:rgb(66, 66, 66);
321
+ --color-stripe:rgba(255,255,255,0.04);
322
+ --color-hover:rgba(255,255,255,0.06);
356
323
  }
357
324
  @media (prefers-color-scheme: dark) {
358
325
  :host{
@@ -360,8 +327,8 @@ class Coax extends HTMLElement {
360
327
  --border-color: transparent;
361
328
  --color-code: #abb2bf;
362
329
  --color-index:rgb(153, 153, 153);
363
- --color-stripe:rgb(66, 66, 66);
364
- --color-hover:rgb(66, 66, 66);
330
+ --color-stripe:rgba(255,255,255,0.04);
331
+ --color-hover:rgba(255,255,255,0.06);
365
332
  }
366
333
  }
367
334
  :host {
@@ -371,7 +338,6 @@ class Coax extends HTMLElement {
371
338
  background:var(--${NAMESPACE}-code-background-color,var(--background));
372
339
  color:var(--${NAMESPACE}-code-color,var(--color-code));
373
340
  border:var(--${NAMESPACE}-code-border-width,var(--border-width)) var(--${NAMESPACE}-code-border-style,var(--border-style)) var(--${NAMESPACE}-code-border-color,var(--border-color));
374
- overflow:auto;
375
341
  transition: border-color 0.3s ease,color 0.3s ease;
376
342
  border-radius: var(--${NAMESPACE}-code-radius,var(--radius));
377
343
  }
@@ -388,6 +354,7 @@ class Coax extends HTMLElement {
388
354
  padding: var(--${NAMESPACE}-code-padding,var(--padding)) 0;
389
355
  height:var(--${NAMESPACE}-code-height,var(--height));
390
356
  max-height:var(--${NAMESPACE}-code-max-height,var(--max-height));
357
+ overflow:auto;
391
358
  }
392
359
  pre,code{
393
360
  font-family:"Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", "monospace";
@@ -403,6 +370,9 @@ class Coax extends HTMLElement {
403
370
  flex:auto;
404
371
  }
405
372
  }
373
+ code>div>div:empty:before{
374
+ content:' ';
375
+ }
406
376
  :host([indexed]) code>div:before{
407
377
  display:inline-flex;
408
378
  color:var(--color-index);
@@ -410,13 +380,13 @@ class Coax extends HTMLElement {
410
380
  min-width:var(--${NAMESPACE}-code-index-width,2em);
411
381
  margin-inline-end:var(--${NAMESPACE}-code-padding,8px);
412
382
  }
413
- :host([hoverable]) code>div:hover{
414
- background-color:var(--color-hover);
415
- }
416
383
  :host([striped]) code>div:nth-child(odd){
417
384
  background-color:var(--color-stripe);
418
385
  }
419
- :host([wrapped]) code>div{
386
+ :host([hoverable]) code>div:hover{
387
+ background-color:var(--color-hover);
388
+ }
389
+ :host([wrapped]) code>div>div{
420
390
  white-space: pre-wrap;
421
391
  }
422
392
  :host([unnamed]) #code-name{
@@ -456,41 +426,47 @@ class Coax extends HTMLElement {
456
426
  <pre><code id="highlight-code"></code></pre>
457
427
  </div>
458
428
  `;
459
- // 缓存引用
429
+ // Cache references to various elements
460
430
  this.baseStylesEl = getEl('#base-styles', this.shadowRoot);
461
431
  this.themeStylesEl = getEl('#theme-styles', this.shadowRoot);
462
432
  this.dynamicStylesEl = getEl('#dynamic-styles', this.shadowRoot);
463
433
  this.headerEl = getEl('#code-header', this.shadowRoot);
464
- this.nameEl = getEl('#code-name', this.shadowRoot);
465
- this.toolsEl = getEl('#code-tools', this.shadowRoot);
434
+ this.codeNameEl = getEl('#code-name', this.shadowRoot);
435
+ this.codeToolsEl = getEl('#code-tools', this.shadowRoot);
436
+ this.codeBodyEl = getEl('#code-body', this.shadowRoot);
466
437
  this.highlightedCodeEl = getEl('#highlight-code', this.shadowRoot);
438
+ this.codeBodyEl.addEventListener('scroll', () => {
439
+ let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
440
+ // Check if the user manually scrolled
441
+ this.autoScroll = !flag;
442
+ });
467
443
  }
468
444
 
469
445
  static register(name, config) {
470
446
  // Store the language configuration in the static map
471
447
  this.languages.set(name, { ...config });
472
448
  }
473
- //注册工具箱
449
+
474
450
  static addTools(items) {
475
451
  Coax.tools = items;
476
452
  }
477
- //加入工具箱
453
+
478
454
  mountTools(toolItems) {
479
455
  requestAnimationFrame(() => {
480
- this.toolsEl.innerHTML = '';
456
+ this.codeToolsEl.innerHTML = '';
481
457
  let items = toolItems.map(k => {
482
458
  k.action = k.action.bind(this);
483
459
  return k;
484
460
  });
485
- this.toolsEl.appendChild(createTools(items));
461
+ this.codeToolsEl.appendChild(createTools(items));
486
462
  });
487
463
  }
488
- // Called when the element is connected to the DOM
464
+
489
465
  connectedCallback() {
490
466
  this.render();
491
467
  }
492
- // Observed attributes for changes
493
- static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools']; }
468
+
469
+ static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools', 'speed']; }
494
470
 
495
471
  attributeChangedCallback(name, oldVal, newVal) {
496
472
  if (oldVal === newVal)
@@ -498,18 +474,18 @@ class Coax extends HTMLElement {
498
474
  if (name === 'height' || name === 'max-height') {
499
475
  this.updateStyleByRegExp(name, newVal);
500
476
  }
501
- else if (name === 'lang') {
502
- //更新当前语言
477
+ if (name === 'speed') {
478
+ // Convert to integer (0 or 1)
479
+ this.speed = ~~(!!newVal);
480
+ }
481
+ if (name === 'lang') {
482
+ // Update the language and re-render
503
483
  this.lang = newVal;
504
484
  this.render();
505
485
  }
506
- else if (name === 'unnamed') {
507
- this.nameEl.innerHTML = newVal === null ? (this.alias || this.lang) : '';
508
- this.nameEl.innerHTML = '';
509
- }
510
- else if (name === 'tools') {
486
+ if (name === 'tools') {
511
487
  if (!newVal)
512
- this.toolsEl.innerHTML = '';
488
+ this.codeToolsEl.innerHTML = '';
513
489
  const tools = parseClasses$1(newVal), toolItems = Coax.tools.filter(k => tools.includes(k.name));
514
490
  if (!toolItems.length)
515
491
  return;
@@ -524,37 +500,91 @@ class Coax extends HTMLElement {
524
500
  // 替换为新的属性值
525
501
  this.baseStylesEl.textContent = this.baseStylesEl.textContent.replace(regex, `;\n${prop}: ${value};`);
526
502
  }
503
+
527
504
  getCssPrefix(config) {
528
505
  return (config?.cssPrefix || this.lang).replace(/[^a-zA-Z0-9_-]/g, '\\$&');
529
506
  }
530
507
 
531
- highlight(newCode) {
508
+ getHighLightString(string, config) {
509
+ config = config || Coax.languages.get(this.lang);
510
+ if (!config)
511
+ return string;
512
+ // 如果找到了配置,则进行高亮处理
513
+ const cssPrefix = this.getCssPrefix(config),
514
+ // 获取用于语法高亮的正则表达式
515
+ combinedRegex = new RegExp(config.rules.map((r) => `(${r.pattern.source})`).join('|'), 'g');
516
+ return string.replace(combinedRegex, (match, ...args) => {
517
+ const i = args.findIndex(val => val !== undefined);
518
+ return i !== -1 && config.rules[i] ? `<span class="${NAMESPACE}-${cssPrefix}-${config.rules[i].token}">${match}</span>` : match;
519
+ });
520
+ }
521
+
522
+ createLineWrap(index, startIndex) {
523
+ let dataIndex = 0;
524
+ if (index == null && startIndex == null) {
525
+ dataIndex = this.highlightedCodeEl.children.length;
526
+ }
527
+ else {
528
+ const start = startIndex || this.highlightedCodeEl.children.length;
529
+ dataIndex = start + index;
530
+ }
531
+ return createEl('div', { 'data-index': dataIndex }, '<div></div>');
532
+ }
533
+
534
+ getLineToFill(codeWrap, line, config) {
535
+ config = config || Coax.languages.get(this.lang);
536
+ let highlightedLine = this.getHighLightString(line, config);
537
+ // 将高亮后的内容填充到 div 中
538
+ codeWrap.innerHTML = highlightedLine;
539
+ }
540
+ ;
541
+
542
+ async highlight(newCode) {
532
543
  const config = Coax.languages.get(this.lang), startIndex = this.highlightedCodeEl.children.length,
533
544
  // 将新源码按行分割
534
- newCodeLines = newCode.split('\n'),
545
+ newCodeLines = newCode ? newCode.split('\n') : [];
546
+ //更新别名
547
+ this.updateName(config);
548
+ if (!newCodeLines.length)
549
+ return true;
535
550
  // 如果没有找到配置,则输出原始代码,并不进行高亮处理
536
- highlightedLines = newCodeLines.map((line, index) => {
537
- let highlightedLine = line;
538
- // 如果找到了配置,则进行高亮处理
539
- if (config) {
540
- const cssPrefix = this.getCssPrefix(config);
541
- // 获取用于语法高亮的正则表达式
542
- const combinedRegex = new RegExp(config.rules.map((r) => `(${r.pattern.source})`).join('|'), 'g');
543
- // 进行高亮替换
544
- highlightedLine = line.replace(combinedRegex, (match, ...args) => {
545
- const i = args.findIndex(val => val !== undefined);
546
- return i !== -1 && config.rules[i] ? `<span class="${NAMESPACE}-${cssPrefix}-${config.rules[i].token}">${match}</span>` : match;
551
+ for (let [index, lineString] of newCodeLines.entries()) {
552
+ //如果是空行则跳过
553
+ if (!lineString.trim() && this.hasAttribute('sanitized'))
554
+ continue;
555
+ // 创建一个 div 包裹每一行
556
+ const lineWrap = this.createLineWrap(index, startIndex), codeWrap = lineWrap.lastElementChild;
557
+ //标记完成
558
+ lineWrap.completed = true;
559
+ if (this.hasAttribute('speed')) {
560
+ this.highlightedCodeEl.appendChild(lineWrap);
561
+ //流式打字
562
+ await typeWriter(lineString, {
563
+ speed: this.speed,
564
+ onDuringType: (char, fullText) => {
565
+ codeWrap.innerHTML = fullText;
566
+ }
547
567
  });
568
+ this.getLineToFill(codeWrap, lineString, config);
548
569
  }
549
- // 创建一个 div 包裹每一行
550
- const lineDiv = createEl('div', { 'data-index': startIndex + index });
551
- lineDiv.innerHTML = `<div>${highlightedLine}</div>`; // 将高亮后的内容填充到 div 中
552
- return lineDiv;
553
- });
554
- //
555
- // 将新生成的行节点追加到 highlightedCodeEl 节点中
556
- this.highlightedCodeEl.append(...highlightedLines);
570
+ else {
571
+ //直接打出
572
+ this.getLineToFill(codeWrap, lineString, config);
573
+ //
574
+ this.highlightedCodeEl.appendChild(lineWrap);
575
+ }
576
+ }
577
+ //滚动到最底部
578
+ this.autoScrollCode();
579
+ return true;
557
580
  }
581
+
582
+ autoScrollCode() {
583
+ if (this.autoScroll) {
584
+ this.codeBodyEl.scrollTop = this.codeBodyEl.scrollHeight;
585
+ }
586
+ }
587
+
558
588
  injectThemeStyles() {
559
589
  const config = Coax.languages.get(this.lang);
560
590
  if (!config)
@@ -564,141 +594,284 @@ class Coax extends HTMLElement {
564
594
  //Generate dynamic CSS classes for each syntax rule
565
595
  // 为 rules 中的每一个 name 生成类似 .hl-name { color: var(--ax-code-name); }
566
596
  lightStyles = config.rules.map((rule) => `
567
- .${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token}${rule.color ? ',' + rule.color : ''});}`).join('\n'), darkStyles = '', schemeStyles = '';
597
+ .${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token}${rule.light ? ',' + rule.light : ''});}`).join('\n'), darkStyles = '', schemeStyles = '';
568
598
  darkStyles += `:host([scheme="dark"]){`;
569
599
  darkStyles += config.rules.map((rule) => `
570
- ${rule.color ? `
600
+ ${rule.light ? `
571
601
  .${NAMESPACE}-${cssPrefix}-${rule.token} {color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark});}` : ``}`).join('\n');
572
602
  darkStyles += `}`;
573
603
  schemeStyles = `@media (prefers-color-scheme: dark){
574
604
  :host{
575
605
  `;
576
606
  schemeStyles += config.rules.map((rule) => `
577
- ${rule.color ? `
607
+ ${rule.light ? `
578
608
  .${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark}); }` : ``} `).join('\n');
579
609
  schemeStyles += `}`;
580
610
  // Set the inner HTML of the shadow root, including styles and highlighted code
581
- // 2. 精确更新 DOM 节点而非重写 ShadowRoot
582
611
  this.dynamicStylesEl.textContent = lightStyles + darkStyles + schemeStyles;
583
612
  //附加主题样式
584
613
  if (config?.themeStyles) {
585
614
  this.themeStylesEl.textContent = config.themeStyles;
586
615
  }
587
- //更新别名
588
- this.updateName(config);
589
616
  }
617
+
590
618
  updateName(config) {
591
619
  if (this.hasAttribute('unnamed'))
592
620
  return;
593
- //更新别名
594
- this.alias = config.alias || this.lang;
595
- this.nameEl.innerHTML = this.alias;
621
+ if (!config) {
622
+ this.codeNameEl.innerHTML = 'Plain Text';
623
+ }
624
+ else {
625
+ //更新别名
626
+ this.alias = config.alias || this.lang;
627
+ this.codeNameEl.innerHTML = this.alias;
628
+ }
596
629
  }
597
630
 
598
- render() {
631
+ render(code = this.source) {
599
632
  //同时多次改变属性,只执行一次
600
- // 如果已经在队列中,则直接返回
601
633
  if (this._renderQueued)
602
634
  return;
603
635
  this._renderQueued = true;
604
636
  // 使用 requestAnimationFrame 将渲染推迟到下一帧
605
- requestAnimationFrame(() => {
606
- this.highlightedCodeEl.innerHTML = '';
607
- //一次性渲染
608
- this.highlight(this.source);
637
+ requestAnimationFrame(async () => {
638
+ this.clear();
609
639
  this.injectThemeStyles();
640
+ //一次性渲染
641
+ await this.highlight(code);
610
642
  this._renderQueued = false;
611
643
  });
612
644
  }
613
645
 
614
- replaceCode(newCode) {
615
- // 更新原始代码为新代码
616
- this.source = newCode;
617
- //清空
618
- this.highlightedCodeEl.innerHTML = '';
619
- this.highlight(newCode);
646
+ clear() {
647
+ this.highlightedCodeEl.innerHTML = this.source = this.lineString = '';
620
648
  }
621
649
 
622
- appendCode(newCode) {
650
+ async replace(newCode) {
651
+ this.clear();
652
+ await this.highlight(newCode);
653
+ }
654
+
655
+ async append(newCode) {
623
656
  // 将新的代码追加到现有代码末尾
624
657
  this.source += `\n${newCode}`;
625
658
  // 高亮新的部分
626
- this.highlight(newCode);
659
+ await this.highlight(newCode);
627
660
  }
628
- }
629
- Coax.register('css', {
630
- rules: [
631
- { token: 'comment', pattern: /\/\*[\s\S]*?\*\//, color: '#6a737d', dark: '#8b949e' },
632
- { token: 'value', pattern: /(?:'|")(?:\\.|[^\\'"\b])*?(?:'|")/, color: '#61afef', dark: '#a5d6ff' },
633
- { token: 'func', pattern: /[a-z-]+\(?=/, color: '#e36209', dark: '#ffa657' },
634
- { token: 'property', pattern: /[a-z-]+(?=\s*:)/, color: '#005cc5', dark: '#79c0ff' },
635
- { token: 'selector', pattern: /[.#a-z0-9, \n\t>:+()_-]+(?=\s*\{)/i, color: '#6f42c1', dark: '#d2a8ff' },
636
- { token: 'unit', pattern: /(?<=\d)(px|em|rem|%|vh|vw|ms|s|deg)/, color: '#d73a49', dark: '#ff7b72' },
637
- { token: 'number', pattern: /\b\d+(\.\d+)?\b/, color: '#005cc5', dark: '#79c0ff' },
638
- { token: 'punct', pattern: /[{}();:]/, color: '#24292e', dark: '#c9d1d9' }
639
- ],
640
661
 
641
- });
642
- Coax.register('html', {
643
- rules: [
644
- { token: 'comment', pattern: /&lt;!--[\s\S]*?--&gt;/, color: '#6a737d', dark: '#8b949e' },
645
- { token: 'doctype', pattern: /&lt;!DOCTYPE[\s\S]*?&gt;/i, color: '#005cc5', dark: '#56b6c2' },
646
- // 匹配标签名: <div, </div
647
- { token: 'tag', pattern: /&lt;\/?[a-zA-Z0-9]+/, color: '#22863a', dark: '#abb2bf' },
648
- // 匹配属性名: class=
649
- { token: 'attr', pattern: /[a-zA-Z-]+(?=\s*=\s*)/, color: '#6f42c1', dark: '#e06c75' },
650
- // 匹配属性值: "value"
651
- { token: 'string', pattern: /(['"])(?:\\.|[^\\])*?\1/, color: '#032f62', dark: '#f39c12' },
652
- // 匹配标签闭合: >, />
653
- { token: 'bracket', pattern: /\/?&gt;/, color: '#24292e', dark: '#f1f1f1' }
654
- ],
655
- });
656
- Coax.register('js', {
657
- alias: 'Javascript',
658
- rules: [
659
- { token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, color: '#6a737d', dark: '#8b949e' },
660
- { token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, color: '#032f62', dark: '#61afef' },
661
- { token: 'keyword', pattern: /\b(async|await|break|case|catch|class|const|continue|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|new|return|super|switch|this|throw|try|typeof|var|while|with|yield|let|static)\b/, color: '#e06c75', dark: '#d73a49' },
662
- { token: 'builtin', pattern: /\b(console|window|document|Math|JSON|true|false|null|undefined|Object|Array|Promise)\b/, color: '#56b6c2', dark: '#61afef' },
663
- { token: 'number', pattern: /\b\d+\b/, color: '#61afef', dark: '#d19a66' },
664
- { token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, color: '#e5c07b', dark: '#98c379' },
665
- { token: 'op', pattern: /[+\-*/%=<>!&|^~]+/, color: '#d73a49', dark: '#e06c75' }
666
- ],
667
- });
668
- Coax.register('ts', {
669
- rules: [
670
- { token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, color: '#6a737d', dark: '#8b949e' },
671
- { token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, color: '#032f62', dark: '#61afef' },
672
- { token: 'decorator', pattern: /@[a-zA-Z_]\w*/, color: '#d19a66', dark: '#e5c07b' },
673
- { token: 'keyword', pattern: /\b(abstract|as|async|await|break|case|catch|class|const|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|package|private|protected|public|readonly|return|set|static|super|switch|this|throw|try|type|typeof|var|while|with|yield)\b/, color: '#e06c75', dark: '#d73a49' },
674
- { token: 'builtin', pattern: /\b(any|boolean|never|number|string|symbol|unknown|void|undefined|null|boolean|true|false|console|window|document)\b/, color: '#56b6c2', dark: '#61afef' },
675
- { token: 'type', pattern: /\b[A-Z]\w*\b/, color: '#22863a', dark: '#8b949e' },
676
- { token: 'number', pattern: /\b\d+\b/, color: '#61afef', dark: '#d19a66' },
677
- { token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, color: '#e5c07b', dark: '#98c379' },
678
- { token: 'op', pattern: /(\?\.|![:\.]|[+\-*/%=<>!&|^~]+)/, color: '#d73a49', dark: '#e06c75' }
679
- ],
680
- });
681
- Coax.addTools([{
682
- name: 'copy',
683
- icon: icaxCopy(),
684
- action: function (arg) {
685
- arg.wrapEl.onclick = () => {
686
- //this只是组件实例
687
- navigator.clipboard.writeText(this.source)
688
- .then(() => {
689
- console.log('Text successfully copied to clipboard');
690
- arg.iconEl.innerHTML = icaxCheck();
691
- arg.iconEl.toggleAttribute('disabled', true);
692
- setTimeout(() => {
693
- //恢复
694
- arg.iconEl.removeAttribute('disabled');
695
- arg.iconEl.innerHTML = icaxCopy();
696
- }, 2000);
697
- })
698
- .catch(err => {
699
- console.error('Error copying text to clipboard: ', err);
700
- });
701
- };
662
+ getLastLine() {
663
+ const lastChild = this.highlightedCodeEl.lastElementChild, lastLine = !lastChild || this.highlightedCodeEl.lastElementChild?.completed ?
664
+ this.createLineWrap() : lastChild;
665
+ return {
666
+ lineWrap: lastLine,
667
+ codeWrap: lastLine.lastElementChild
668
+ };
669
+ }
670
+
671
+ close() {
672
+ const lineWrap = this.highlightedCodeEl.lastElementChild;
673
+ if (!lineWrap)
674
+ return;
675
+ lineWrap.completed = true;
676
+ //行结束前保存
677
+ this.lastLineString = this.lineString;
678
+ this.getLineToFill(lineWrap?.lastElementChild, this.lineString);
679
+ //一行结束,清空临时行文本
680
+ this.lineString = '';
681
+ }
682
+
683
+ open() {
684
+ const lineWrap = this.highlightedCodeEl.lastElementChild;
685
+ if (!lineWrap)
686
+ return;
687
+ lineWrap.completed = false;
688
+ //恢复最后一行字符串
689
+ (lineWrap?.lastElementChild).textContent = this.lineString = this.lastLineString;
690
+ }
691
+
692
+ stream(str, forceClose = false) {
693
+ const { lineWrap, codeWrap } = this.getLastLine();
694
+ this.highlightedCodeEl.appendChild(lineWrap);
695
+ //汇集
696
+ this.source += str;
697
+ //如果没有遇到换行符号,也可以强制结束
698
+ if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
699
+ //标记完成
700
+ this.close();
701
+ }
702
+ else {
703
+ //插入文本
704
+ codeWrap.innerHTML += str;
705
+ //临时保存行文本
706
+ this.lineString += str;
702
707
  }
703
- }]);
704
- customElements.define(`${NAMESPACE}-code`, Coax);
708
+ //滚动到最底部
709
+ this.autoScrollCode();
710
+ }
711
+ }
712
+
713
+ const css = [
714
+ { token: 'comment', pattern: /\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
715
+ { token: 'value', pattern: /(?:'|")(?:\\.|[^\\'"\b])*?(?:'|")/, light: '#032f62', dark: '#a5d6ff' },
716
+ { token: 'func', pattern: /[a-z-]+\(?=/, light: '#e36209', dark: '#ffa657' },
717
+ { token: 'property', pattern: /[a-z-]+(?=\s*:)/, light: '#005cc5', dark: '#79c0ff' },
718
+ { token: 'selector', pattern: /[.#a-z0-9, \n\t>:+()_-]+(?=\s*\{)/i, light: '#22863a', dark: '#7ee787' },
719
+ { token: 'unit', pattern: /(?<=\d)(px|em|rem|%|vh|vw|ms|s|deg)/, light: '#d73a49', dark: '#ff7b72' },
720
+ { token: 'number', pattern: /\b\d+(\.\d+)?\b/, light: '#005cc5', dark: '#79c0ff' },
721
+ { token: 'punct', pattern: /[{}();:]/, light: '#24292e', dark: '#c9d1d9' }
722
+ ];
723
+
724
+ const html = [
725
+ { token: 'comment', pattern: /&lt;!--[\s\S]*?--&gt;/, light: '#999999', dark: '#6e7681' },
726
+ { token: 'doctype', pattern: /&lt;!DOCTYPE[\s\S]*?&gt;/i, light: '#6a737d', dark: '#8b949e' },
727
+ // 匹配标签名: <div, </div
728
+ { token: 'tag', pattern: /&lt;\/?[a-zA-Z0-9]+/, light: '#22863a', dark: '#7ee787' },
729
+ // 匹配属性名: class=
730
+ { token: 'attr', pattern: /[a-zA-Z-]+(?=\s*=\s*)/, light: '#6f42c1', dark: '#d2a8ff' },
731
+ // 匹配属性值: "value"
732
+ { token: 'string', pattern: /(['"])(?:\\.|[^\\])*?\1/, light: '#032f62', dark: '#a5d6ff' },
733
+ // 匹配标签闭合: >, />
734
+ { token: 'bracket', pattern: /\/?&gt;/, light: '#24292e', dark: '#c9d1d9' }
735
+ ];
736
+
737
+ const javascript = [
738
+ { token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
739
+ { token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
740
+ { token: 'keyword', pattern: /\b(async|await|break|case|catch|class|const|continue|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|new|return|super|switch|this|throw|try|typeof|var|while|with|yield|let|static)\b/, light: '#d73a49', dark: '#ff7b72' },
741
+ { token: 'builtin', pattern: /\b(console|window|document|Math|JSON|true|false|null|undefined|Object|Array|Promise|Number|String|Boolean)\b/, light: '#e36209', dark: '#ffa657' },
742
+ { token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
743
+ { token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
744
+ { token: 'op', pattern: /[+\-*/%=<>!&|^~]+/, light: '#069598', dark: '#56b6c2' }
745
+ ];
746
+
747
+ const markdown = [
748
+ // 注释: 这是 Markdown 中的行内注释
749
+ { token: 'comment', pattern: /<!--[\s\S]*?-->/, light: '#6a737d', dark: '#8b949e' },
750
+ // 标题: 通过 `#` 来定义标题
751
+ { token: 'heading', pattern: /(^|\n)(#{1,6})\s*(.+)/, light: '#e36209', dark: '#ffa657' },
752
+ // 粗体: **text** 或 __text__
753
+ { token: 'bold', pattern: /\*\*([^*]+)\*\*|__([^_]+)__/g, light: '#d73a49', dark: '#ff7b72' },
754
+ // 斜体: *text* 或 _text_
755
+ { token: 'italic', pattern: /\*([^*]+)\*|_([^_]+)_/g, light: '#032f62', dark: '#a5d6ff' },
756
+ // 链接: [text](url)
757
+ { token: 'link', pattern: /\[([^\]]+)\]\(([^)]+)\)/g, light: '#0288d1', dark: '#80c0ff' },
758
+ // 行内代码: `code`
759
+ { token: 'inline-code', pattern: /`([^`]+)`/g, light: '#032f62', dark: '#98c379' },
760
+ // 代码块: ```code```
761
+ { token: 'code-block', pattern: /```([^\n]+)\n([\s\S]*?)```/g, light: '#24292e', dark: '#c9d1d9' },
762
+ // 列表项: - item 或 * item
763
+ { token: 'list-item', pattern: /(^|\n)([-*])\s+(.+)/g, light: '#5c6e7c', dark: '#8b949e' },
764
+ // 引用: > text
765
+ { token: 'quote', pattern: /(^|\n)>[ \t]*(.+)/g, light: '#6f42c1', dark: '#d2a8ff' },
766
+ // 图片: ![alt](url)
767
+ { token: 'image', pattern: /!\[([^\]]+)\]\(([^)]+)\)/g, light: '#d73a49', dark: '#ff7b72' },
768
+ // 分割线: ---
769
+ { token: 'hr', pattern: /^(---|___|\*\*\*)\s*$/gm, light: '#24292e', dark: '#c9d1d9' },
770
+ // 强调和删除: ~~text~~
771
+ { token: 'strikethrough', pattern: /~~([^~]+)~~/g, light: '#e36209', dark: '#ffa657' },
772
+ // 表格: | header1 | header2 |
773
+ { token: 'table', pattern: /\|([^\|]+)\|([^\|]+)\|/g, light: '#5c6e7c', dark: '#8b949e' }
774
+ ];
775
+
776
+ const typescript = [
777
+ { token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
778
+ { token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
779
+ { token: 'decorator', pattern: /@[a-zA-Z_]\w*/, light: '#953800', dark: '#ffa657' },
780
+ { token: 'keyword', pattern: /\b(abstract|as|async|await|break|case|catch|class|const|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|package|private|protected|public|readonly|return|set|static|super|switch|this|throw|try|type|typeof|var|while|with|yield)\b/, light: '#d73a49', dark: '#ff7b72' },
781
+ { token: 'builtin', pattern: /\b(any|boolean|never|number|string|symbol|unknown|void|undefined|null|true|false|console|window|document)\b/, light: '#e36209', dark: '#ffa657' },
782
+ { token: 'type', pattern: /\b[A-Z]\w*\b/, light: '#005cc5', dark: '#79c0ff' },
783
+ { token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
784
+ { token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
785
+ { token: 'op', pattern: /(\?\.|![:\.]|[+\-*/%=<>!&|^~]+)/, light: '#089ba6', dark: '#79c0ff' }
786
+ ];
787
+
788
+ const COMMA = ',';
789
+
790
+ const SPACE = ' ';
791
+
792
+ const trim = (str, placement = '') => {
793
+ if (typeof str !== 'string') {
794
+ console.warn('Expected a string input');
795
+ return '';
796
+ }
797
+ switch (placement) {
798
+ case 'start':
799
+ return str.trimStart();
800
+ case 'end':
801
+ return str.trimEnd();
802
+ case 'both':
803
+ return str.trim();
804
+ case 'global':
805
+ return str.replace(/[\s\r\n]+/g, '');
806
+ default:
807
+ return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
808
+ }
809
+ };
810
+
811
+ const parseClasses = (data) => {
812
+ let separator, result = [];
813
+ if (Array.isArray(data)) {
814
+ // If data is already an array, filter out invalid values
815
+ result = data.filter((k) => k && typeof k === 'string');
816
+ }
817
+ else {
818
+ // Trim the input string and handle multiple spaces
819
+ data = trim(data);
820
+ // Use comma as the separator if present, otherwise use space
821
+ separator = data.includes(COMMA) ? COMMA : SPACE;
822
+ result = data.split(separator);
823
+ }
824
+ // Trim each item globally and filter out any empty strings
825
+ return result.map((k) => trim(k, 'global')).filter(Boolean);
826
+ };
827
+
828
+ const rtlStyle = (name = '') => `
829
+ <style>
830
+ :where([dir="rtl"]) .icax-${name},
831
+ :where(:dir(rtl)) .icax-${name} {
832
+ transform: scaleX(-1);
833
+ transform-origin: center;
834
+ }
835
+ </style>
836
+ `;
837
+
838
+ const wrap = (content, fun, isRtl = false, options) => {
839
+ const size = options?.size || '1em', color = options?.color || 'currentColor', thickness = options?.thickness || 2, classes = options?.classes ? parseClasses(options.classes).join(' ') : '',
840
+ // 得到 "icax-left"
841
+ origName = fun.name.replace(/([A-Z])/g, "-$1").toLowerCase();
842
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}"
843
+ stroke-width="${thickness}" stroke-linecap="round" stroke-linejoin="round" class="${origName} ${classes}">
844
+ ${isRtl ? rtlStyle(origName.split('-')[1]) : ''}
845
+ ${content}
846
+ </svg>`;
847
+ };
848
+
849
+ const icaxCheck = (options) => wrap(`<polyline points="20 6 9 17 4 12"></polyline>`, icaxCheck, false, options);
850
+
851
+ const icaxCopy = (options) => wrap(`<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>`, icaxCopy, false, options);
852
+
853
+ const copy = {
854
+ name: 'copy',
855
+ icon: icaxCopy(),
856
+ action: function (arg) {
857
+ arg.wrapEl.onclick = () => {
858
+ //this只是组件实例
859
+ navigator.clipboard.writeText(this.source)
860
+ .then(() => {
861
+ console.log('Text successfully copied to clipboard');
862
+ arg.iconEl.innerHTML = icaxCheck();
863
+ arg.iconEl.toggleAttribute('disabled', true);
864
+ setTimeout(() => {
865
+ //恢复
866
+ arg.iconEl.removeAttribute('disabled');
867
+ arg.iconEl.innerHTML = icaxCopy();
868
+ }, 2000);
869
+ })
870
+ .catch(err => {
871
+ console.error('Error copying text to clipboard: ', err);
872
+ });
873
+ };
874
+ }
875
+ };
876
+
877
+ export { Coax, copy, css, html, javascript, markdown, typescript };