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