@codady/coax 0.0.3 → 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.
- package/LICENSE +1 -1
- package/README.md +331 -166
- package/dist/coax.cjs.js +72 -57
- package/dist/coax.cjs.min.js +4 -4
- package/dist/coax.esm.js +72 -57
- package/dist/coax.esm.min.js +4 -4
- package/dist/coax.umd.js +243 -229
- package/dist/coax.umd.min.js +4 -4
- package/examples/deepseek-highlight.html +19 -10
- package/package.json +2 -2
- package/script-note.js +2 -2
- package/src/Coax.js +2 -3
- package/src/Coax.ts +2 -3
- package/src/components/{CoaxElem.js → Coax.js} +143 -72
- package/src/components/{CoaxElem.ts → Coax.ts} +151 -83
- package/src/modules.js +3 -3
- package/src/modules.ts +3 -3
package/dist/coax.umd.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
/*!
|
|
3
|
-
* @since Last modified: 2026-1-12
|
|
3
|
+
* @since Last modified: 2026-1-12 14:46:3
|
|
4
4
|
* @name Coax event management system.
|
|
5
|
-
* @version 0.0.
|
|
5
|
+
* @version 0.0.4
|
|
6
6
|
* @author AXUI development team <3217728223@qq.com>
|
|
7
|
-
* @description Coax is a
|
|
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}
|
|
@@ -22,6 +22,170 @@
|
|
|
22
22
|
|
|
23
23
|
const NAMESPACE = 'ax';
|
|
24
24
|
|
|
25
|
+
const html = [
|
|
26
|
+
{ token: 'comment', pattern: /<!--[\s\S]*?-->/, light: '#999999', dark: '#6e7681' },
|
|
27
|
+
{ token: 'doctype', pattern: /<!DOCTYPE[\s\S]*?>/i, light: '#6a737d', dark: '#8b949e' },
|
|
28
|
+
// 匹配标签名: <div, </div
|
|
29
|
+
{ token: 'tag', pattern: /<\/?[a-zA-Z0-9]+/, light: '#22863a', dark: '#7ee787' },
|
|
30
|
+
// 匹配属性名: class=
|
|
31
|
+
{ token: 'attr', pattern: /[a-zA-Z-]+(?=\s*=\s*)/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
32
|
+
// 匹配属性值: "value"
|
|
33
|
+
{ token: 'string', pattern: /(['"])(?:\\.|[^\\])*?\1/, light: '#032f62', dark: '#a5d6ff' },
|
|
34
|
+
// 匹配标签闭合: >, />
|
|
35
|
+
{ token: 'bracket', pattern: /\/?>/, light: '#24292e', dark: '#c9d1d9' }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const javascript = [
|
|
39
|
+
{ token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
40
|
+
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
|
|
41
|
+
{ 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' },
|
|
42
|
+
{ token: 'builtin', pattern: /\b(console|window|document|Math|JSON|true|false|null|undefined|Object|Array|Promise|Number|String|Boolean)\b/, light: '#e36209', dark: '#ffa657' },
|
|
43
|
+
{ token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
44
|
+
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
45
|
+
{ token: 'op', pattern: /[+\-*/%=<>!&|^~]+/, light: '#069598', dark: '#56b6c2' }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const markdown = [
|
|
49
|
+
// 注释: 这是 Markdown 中的行内注释
|
|
50
|
+
{ token: 'comment', pattern: /<!--[\s\S]*?-->/, light: '#6a737d', dark: '#8b949e' },
|
|
51
|
+
// 标题: 通过 `#` 来定义标题
|
|
52
|
+
{ token: 'heading', pattern: /(^|\n)(#{1,6})\s*(.+)/, light: '#e36209', dark: '#ffa657' },
|
|
53
|
+
// 粗体: **text** 或 __text__
|
|
54
|
+
{ token: 'bold', pattern: /\*\*([^*]+)\*\*|__([^_]+)__/g, light: '#d73a49', dark: '#ff7b72' },
|
|
55
|
+
// 斜体: *text* 或 _text_
|
|
56
|
+
{ token: 'italic', pattern: /\*([^*]+)\*|_([^_]+)_/g, light: '#032f62', dark: '#a5d6ff' },
|
|
57
|
+
// 链接: [text](url)
|
|
58
|
+
{ token: 'link', pattern: /\[([^\]]+)\]\(([^)]+)\)/g, light: '#0288d1', dark: '#80c0ff' },
|
|
59
|
+
// 行内代码: `code`
|
|
60
|
+
{ token: 'inline-code', pattern: /`([^`]+)`/g, light: '#032f62', dark: '#98c379' },
|
|
61
|
+
// 代码块: ```code```
|
|
62
|
+
{ token: 'code-block', pattern: /```([^\n]+)\n([\s\S]*?)```/g, light: '#24292e', dark: '#c9d1d9' },
|
|
63
|
+
// 列表项: - item 或 * item
|
|
64
|
+
{ token: 'list-item', pattern: /(^|\n)([-*])\s+(.+)/g, light: '#5c6e7c', dark: '#8b949e' },
|
|
65
|
+
// 引用: > text
|
|
66
|
+
{ token: 'quote', pattern: /(^|\n)>[ \t]*(.+)/g, light: '#6f42c1', dark: '#d2a8ff' },
|
|
67
|
+
// 图片: 
|
|
68
|
+
{ token: 'image', pattern: /!\[([^\]]+)\]\(([^)]+)\)/g, light: '#d73a49', dark: '#ff7b72' },
|
|
69
|
+
// 分割线: ---
|
|
70
|
+
{ token: 'hr', pattern: /^(---|___|\*\*\*)\s*$/gm, light: '#24292e', dark: '#c9d1d9' },
|
|
71
|
+
// 强调和删除: ~~text~~
|
|
72
|
+
{ token: 'strikethrough', pattern: /~~([^~]+)~~/g, light: '#e36209', dark: '#ffa657' },
|
|
73
|
+
// 表格: | header1 | header2 |
|
|
74
|
+
{ token: 'table', pattern: /\|([^\|]+)\|([^\|]+)\|/g, light: '#5c6e7c', dark: '#8b949e' }
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const typescript = [
|
|
78
|
+
{ token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
79
|
+
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
|
|
80
|
+
{ token: 'decorator', pattern: /@[a-zA-Z_]\w*/, light: '#953800', dark: '#ffa657' },
|
|
81
|
+
{ 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' },
|
|
82
|
+
{ token: 'builtin', pattern: /\b(any|boolean|never|number|string|symbol|unknown|void|undefined|null|true|false|console|window|document)\b/, light: '#e36209', dark: '#ffa657' },
|
|
83
|
+
{ token: 'type', pattern: /\b[A-Z]\w*\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
84
|
+
{ token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
85
|
+
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
86
|
+
{ token: 'op', pattern: /(\?\.|![:\.]|[+\-*/%=<>!&|^~]+)/, light: '#089ba6', dark: '#79c0ff' }
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const COMMA$1 = ',';
|
|
90
|
+
|
|
91
|
+
const SPACE$1 = ' ';
|
|
92
|
+
|
|
93
|
+
const trim$1 = (str, placement = '') => {
|
|
94
|
+
if (typeof str !== 'string') {
|
|
95
|
+
console.warn('Expected a string input');
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
switch (placement) {
|
|
99
|
+
case 'start':
|
|
100
|
+
return str.trimStart();
|
|
101
|
+
case 'end':
|
|
102
|
+
return str.trimEnd();
|
|
103
|
+
case 'both':
|
|
104
|
+
return str.trim();
|
|
105
|
+
case 'global':
|
|
106
|
+
return str.replace(/[\s\r\n]+/g, '');
|
|
107
|
+
default:
|
|
108
|
+
return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const parseClasses$1 = (data) => {
|
|
113
|
+
let separator, result = [];
|
|
114
|
+
if (Array.isArray(data)) {
|
|
115
|
+
// If data is already an array, filter out invalid values
|
|
116
|
+
result = data.filter((k) => k && typeof k === 'string');
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Trim the input string and handle multiple spaces
|
|
120
|
+
data = trim$1(data);
|
|
121
|
+
// Use comma as the separator if present, otherwise use space
|
|
122
|
+
separator = data.includes(COMMA$1) ? COMMA$1 : SPACE$1;
|
|
123
|
+
result = data.split(separator);
|
|
124
|
+
}
|
|
125
|
+
// Trim each item globally and filter out any empty strings
|
|
126
|
+
return result.map((k) => trim$1(k, 'global')).filter(Boolean);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const rtlStyle = (name = '') => `
|
|
130
|
+
<style>
|
|
131
|
+
:where([dir="rtl"]) .icax-${name},
|
|
132
|
+
:where(:dir(rtl)) .icax-${name} {
|
|
133
|
+
transform: scaleX(-1);
|
|
134
|
+
transform-origin: center;
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
const wrap = (content, fun, isRtl = false, options) => {
|
|
140
|
+
const size = options?.size || '1em', color = options?.color || 'currentColor', thickness = options?.thickness || 2, classes = options?.classes ? parseClasses$1(options.classes).join(' ') : '',
|
|
141
|
+
// 得到 "icax-left"
|
|
142
|
+
origName = fun.name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
143
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}"
|
|
144
|
+
stroke-width="${thickness}" stroke-linecap="round" stroke-linejoin="round" class="${origName} ${classes}">
|
|
145
|
+
${isRtl ? rtlStyle(origName.split('-')[1]) : ''}
|
|
146
|
+
${content}
|
|
147
|
+
</svg>`;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const icaxCheck = (options) => wrap(`<polyline points="20 6 9 17 4 12"></polyline>`, icaxCheck, false, options);
|
|
151
|
+
|
|
152
|
+
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);
|
|
153
|
+
|
|
154
|
+
const copy = {
|
|
155
|
+
name: 'copy',
|
|
156
|
+
icon: icaxCopy(),
|
|
157
|
+
action: function (arg) {
|
|
158
|
+
arg.wrapEl.onclick = () => {
|
|
159
|
+
//this只是组件实例
|
|
160
|
+
navigator.clipboard.writeText(this.source)
|
|
161
|
+
.then(() => {
|
|
162
|
+
console.log('Text successfully copied to clipboard');
|
|
163
|
+
arg.iconEl.innerHTML = icaxCheck();
|
|
164
|
+
arg.iconEl.toggleAttribute('disabled', true);
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
//恢复
|
|
167
|
+
arg.iconEl.removeAttribute('disabled');
|
|
168
|
+
arg.iconEl.innerHTML = icaxCopy();
|
|
169
|
+
}, 2000);
|
|
170
|
+
})
|
|
171
|
+
.catch(err => {
|
|
172
|
+
console.error('Error copying text to clipboard: ', err);
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const css = [
|
|
179
|
+
{ token: 'comment', pattern: /\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
180
|
+
{ token: 'value', pattern: /(?:'|")(?:\\.|[^\\'"\b])*?(?:'|")/, light: '#032f62', dark: '#a5d6ff' },
|
|
181
|
+
{ token: 'func', pattern: /[a-z-]+\(?=/, light: '#e36209', dark: '#ffa657' },
|
|
182
|
+
{ token: 'property', pattern: /[a-z-]+(?=\s*:)/, light: '#005cc5', dark: '#79c0ff' },
|
|
183
|
+
{ token: 'selector', pattern: /[.#a-z0-9, \n\t>:+()_-]+(?=\s*\{)/i, light: '#22863a', dark: '#7ee787' },
|
|
184
|
+
{ token: 'unit', pattern: /(?<=\d)(px|em|rem|%|vh|vw|ms|s|deg)/, light: '#d73a49', dark: '#ff7b72' },
|
|
185
|
+
{ token: 'number', pattern: /\b\d+(\.\d+)?\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
186
|
+
{ token: 'punct', pattern: /[{}();:]/, light: '#24292e', dark: '#c9d1d9' }
|
|
187
|
+
];
|
|
188
|
+
|
|
25
189
|
const typeWriter = (text, options) => {
|
|
26
190
|
const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
|
|
27
191
|
return new Promise((resolve) => {
|
|
@@ -49,11 +213,11 @@
|
|
|
49
213
|
});
|
|
50
214
|
};
|
|
51
215
|
|
|
52
|
-
const COMMA
|
|
216
|
+
const COMMA = ',';
|
|
53
217
|
|
|
54
|
-
const SPACE
|
|
218
|
+
const SPACE = ' ';
|
|
55
219
|
|
|
56
|
-
const trim
|
|
220
|
+
const trim = (str, placement = 'compress') => {
|
|
57
221
|
if (typeof str !== 'string') {
|
|
58
222
|
console.warn('Expected a string input');
|
|
59
223
|
return '';
|
|
@@ -72,7 +236,7 @@
|
|
|
72
236
|
}
|
|
73
237
|
};
|
|
74
238
|
|
|
75
|
-
const parseClasses
|
|
239
|
+
const parseClasses = (data) => {
|
|
76
240
|
let separator, result = [];
|
|
77
241
|
if (Array.isArray(data)) {
|
|
78
242
|
// If data is already an array, filter out invalid values
|
|
@@ -80,13 +244,13 @@
|
|
|
80
244
|
}
|
|
81
245
|
else {
|
|
82
246
|
// Trim the input string and handle multiple spaces
|
|
83
|
-
data = trim
|
|
247
|
+
data = trim(data);
|
|
84
248
|
// Use comma as the separator if present, otherwise use space
|
|
85
|
-
separator = data.includes(COMMA
|
|
249
|
+
separator = data.includes(COMMA) ? COMMA : SPACE;
|
|
86
250
|
result = data.split(separator);
|
|
87
251
|
}
|
|
88
252
|
// Trim each item globally and filter out any empty strings
|
|
89
|
-
return result.map((k) => trim
|
|
253
|
+
return result.map((k) => trim(k, 'global')).filter(Boolean);
|
|
90
254
|
};
|
|
91
255
|
|
|
92
256
|
const getDataType = (obj) => {
|
|
@@ -232,7 +396,7 @@
|
|
|
232
396
|
const ALIAS = 'rep';
|
|
233
397
|
|
|
234
398
|
const addClasses = (target, classes, intercept) => {
|
|
235
|
-
const el = getEl(target), arr = parseClasses
|
|
399
|
+
const el = getEl(target), arr = parseClasses(classes);
|
|
236
400
|
if (!el || arr.length === 0) {
|
|
237
401
|
return;
|
|
238
402
|
}
|
|
@@ -272,36 +436,34 @@
|
|
|
272
436
|
return toolsEl;
|
|
273
437
|
};
|
|
274
438
|
|
|
275
|
-
class
|
|
439
|
+
class Coax extends HTMLElement {
|
|
276
440
|
// A static Map to hold the configuration for different languages
|
|
277
441
|
static languages = new Map();
|
|
442
|
+
// A static array to hold the tools registered with the component
|
|
278
443
|
static tools = [];
|
|
279
|
-
source;
|
|
280
|
-
_renderQueued = false;
|
|
281
|
-
baseStylesEl;
|
|
282
|
-
themeStylesEl;
|
|
283
|
-
dynamicStylesEl;
|
|
284
|
-
highlightedCodeEl;
|
|
285
|
-
headerEl;
|
|
286
|
-
codeNameEl;
|
|
287
|
-
codeToolsEl;
|
|
288
|
-
codeBodyEl;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
444
|
+
source; // Source code content to be highlighted
|
|
445
|
+
_renderQueued = false; // Flag to prevent multiple render requests
|
|
446
|
+
baseStylesEl; // Element for base styles
|
|
447
|
+
themeStylesEl; // Element for theme styles
|
|
448
|
+
dynamicStylesEl; // Element for dynamic styles
|
|
449
|
+
highlightedCodeEl; // Element that holds the highlighted code
|
|
450
|
+
headerEl; // Header element (for code name, tools, etc.)
|
|
451
|
+
codeNameEl; // Code name element (shows language or alias)
|
|
452
|
+
codeToolsEl; // Code tools element (for interactive tools like copy)
|
|
453
|
+
codeBodyEl; // Code body element (the container for the code)
|
|
454
|
+
lang = 'plain'; // Language of the code (default is plain text)
|
|
455
|
+
alias = 'Plain Text'; // Alias name for the language (default is 'Plain Text')
|
|
456
|
+
lineString = ''; // The current line's string being typed
|
|
457
|
+
lastLineString = ''; // The last line's string
|
|
458
|
+
speed = 5; // Speed of the typing effect (higher is slower)
|
|
459
|
+
autoScroll = true; // Flag to enable/disable auto-scrolling
|
|
293
460
|
constructor() {
|
|
294
461
|
super();
|
|
295
462
|
// Attach a Shadow DOM to the custom element
|
|
296
463
|
this.attachShadow({ mode: 'open' });
|
|
297
464
|
// Remove leading/trailing whitespace from the raw code content
|
|
298
465
|
this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
|
|
299
|
-
|
|
300
|
-
this.alias = 'Plain Text';
|
|
301
|
-
this.lineString = '';
|
|
302
|
-
this.speed = 5;
|
|
303
|
-
this.autoScroll = true;
|
|
304
|
-
// 1. 初始化基础骨架
|
|
466
|
+
// Initialize the basic structure of the component
|
|
305
467
|
this.shadowRoot.innerHTML = `
|
|
306
468
|
<style id="base-styles">
|
|
307
469
|
:host {
|
|
@@ -434,7 +596,7 @@
|
|
|
434
596
|
<pre><code id="highlight-code"></code></pre>
|
|
435
597
|
</div>
|
|
436
598
|
`;
|
|
437
|
-
//
|
|
599
|
+
// Cache references to various elements
|
|
438
600
|
this.baseStylesEl = getEl('#base-styles', this.shadowRoot);
|
|
439
601
|
this.themeStylesEl = getEl('#theme-styles', this.shadowRoot);
|
|
440
602
|
this.dynamicStylesEl = getEl('#dynamic-styles', this.shadowRoot);
|
|
@@ -445,7 +607,7 @@
|
|
|
445
607
|
this.highlightedCodeEl = getEl('#highlight-code', this.shadowRoot);
|
|
446
608
|
this.codeBodyEl.addEventListener('scroll', () => {
|
|
447
609
|
let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
|
|
448
|
-
//
|
|
610
|
+
// Check if the user manually scrolled
|
|
449
611
|
this.autoScroll = !flag;
|
|
450
612
|
});
|
|
451
613
|
}
|
|
@@ -454,11 +616,11 @@
|
|
|
454
616
|
// Store the language configuration in the static map
|
|
455
617
|
this.languages.set(name, { ...config });
|
|
456
618
|
}
|
|
457
|
-
|
|
619
|
+
|
|
458
620
|
static addTools(items) {
|
|
459
|
-
|
|
621
|
+
Coax.tools = items;
|
|
460
622
|
}
|
|
461
|
-
|
|
623
|
+
|
|
462
624
|
mountTools(toolItems) {
|
|
463
625
|
requestAnimationFrame(() => {
|
|
464
626
|
this.codeToolsEl.innerHTML = '';
|
|
@@ -469,11 +631,11 @@
|
|
|
469
631
|
this.codeToolsEl.appendChild(createTools(items));
|
|
470
632
|
});
|
|
471
633
|
}
|
|
472
|
-
|
|
634
|
+
|
|
473
635
|
connectedCallback() {
|
|
474
636
|
this.render();
|
|
475
637
|
}
|
|
476
|
-
|
|
638
|
+
|
|
477
639
|
static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools', 'speed']; }
|
|
478
640
|
|
|
479
641
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
@@ -483,17 +645,18 @@
|
|
|
483
645
|
this.updateStyleByRegExp(name, newVal);
|
|
484
646
|
}
|
|
485
647
|
if (name === 'speed') {
|
|
648
|
+
// Convert to integer (0 or 1)
|
|
486
649
|
this.speed = ~~(!!newVal);
|
|
487
650
|
}
|
|
488
651
|
if (name === 'lang') {
|
|
489
|
-
|
|
652
|
+
// Update the language and re-render
|
|
490
653
|
this.lang = newVal;
|
|
491
654
|
this.render();
|
|
492
655
|
}
|
|
493
656
|
if (name === 'tools') {
|
|
494
657
|
if (!newVal)
|
|
495
658
|
this.codeToolsEl.innerHTML = '';
|
|
496
|
-
const tools = parseClasses
|
|
659
|
+
const tools = parseClasses(newVal), toolItems = Coax.tools.filter(k => tools.includes(k.name));
|
|
497
660
|
if (!toolItems.length)
|
|
498
661
|
return;
|
|
499
662
|
this.mountTools(toolItems);
|
|
@@ -507,11 +670,13 @@
|
|
|
507
670
|
// 替换为新的属性值
|
|
508
671
|
this.baseStylesEl.textContent = this.baseStylesEl.textContent.replace(regex, `;\n${prop}: ${value};`);
|
|
509
672
|
}
|
|
673
|
+
|
|
510
674
|
getCssPrefix(config) {
|
|
511
675
|
return (config?.cssPrefix || this.lang).replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
512
676
|
}
|
|
677
|
+
|
|
513
678
|
getHighLightString(string, config) {
|
|
514
|
-
config = config ||
|
|
679
|
+
config = config || Coax.languages.get(this.lang);
|
|
515
680
|
if (!config)
|
|
516
681
|
return string;
|
|
517
682
|
// 如果找到了配置,则进行高亮处理
|
|
@@ -523,6 +688,7 @@
|
|
|
523
688
|
return i !== -1 && config.rules[i] ? `<span class="${NAMESPACE}-${cssPrefix}-${config.rules[i].token}">${match}</span>` : match;
|
|
524
689
|
});
|
|
525
690
|
}
|
|
691
|
+
|
|
526
692
|
createLineWrap(index, startIndex) {
|
|
527
693
|
let dataIndex = 0;
|
|
528
694
|
if (index == null && startIndex == null) {
|
|
@@ -534,8 +700,9 @@
|
|
|
534
700
|
}
|
|
535
701
|
return createEl('div', { 'data-index': dataIndex }, '<div></div>');
|
|
536
702
|
}
|
|
703
|
+
|
|
537
704
|
getLineToFill(codeWrap, line, config) {
|
|
538
|
-
config = config ||
|
|
705
|
+
config = config || Coax.languages.get(this.lang);
|
|
539
706
|
let highlightedLine = this.getHighLightString(line, config);
|
|
540
707
|
// 将高亮后的内容填充到 div 中
|
|
541
708
|
codeWrap.innerHTML = highlightedLine;
|
|
@@ -543,7 +710,7 @@
|
|
|
543
710
|
;
|
|
544
711
|
|
|
545
712
|
async highlight(newCode) {
|
|
546
|
-
const config =
|
|
713
|
+
const config = Coax.languages.get(this.lang), startIndex = this.highlightedCodeEl.children.length,
|
|
547
714
|
// 将新源码按行分割
|
|
548
715
|
newCodeLines = newCode ? newCode.split('\n') : [];
|
|
549
716
|
//更新别名
|
|
@@ -558,7 +725,7 @@
|
|
|
558
725
|
// 创建一个 div 包裹每一行
|
|
559
726
|
const lineWrap = this.createLineWrap(index, startIndex), codeWrap = lineWrap.lastElementChild;
|
|
560
727
|
//标记完成
|
|
561
|
-
|
|
728
|
+
lineWrap.completed = true;
|
|
562
729
|
if (this.hasAttribute('speed')) {
|
|
563
730
|
this.highlightedCodeEl.appendChild(lineWrap);
|
|
564
731
|
//流式打字
|
|
@@ -581,13 +748,15 @@
|
|
|
581
748
|
this.autoScrollCode();
|
|
582
749
|
return true;
|
|
583
750
|
}
|
|
751
|
+
|
|
584
752
|
autoScrollCode() {
|
|
585
753
|
if (this.autoScroll) {
|
|
586
754
|
this.codeBodyEl.scrollTop = this.codeBodyEl.scrollHeight;
|
|
587
755
|
}
|
|
588
756
|
}
|
|
757
|
+
|
|
589
758
|
injectThemeStyles() {
|
|
590
|
-
const config =
|
|
759
|
+
const config = Coax.languages.get(this.lang);
|
|
591
760
|
if (!config)
|
|
592
761
|
return;
|
|
593
762
|
// Get language name, fallback to 'js' if not provided
|
|
@@ -609,13 +778,13 @@
|
|
|
609
778
|
.${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark}); }` : ``} `).join('\n');
|
|
610
779
|
schemeStyles += `}`;
|
|
611
780
|
// Set the inner HTML of the shadow root, including styles and highlighted code
|
|
612
|
-
// 2. 精确更新 DOM 节点而非重写 ShadowRoot
|
|
613
781
|
this.dynamicStylesEl.textContent = lightStyles + darkStyles + schemeStyles;
|
|
614
782
|
//附加主题样式
|
|
615
783
|
if (config?.themeStyles) {
|
|
616
784
|
this.themeStylesEl.textContent = config.themeStyles;
|
|
617
785
|
}
|
|
618
786
|
}
|
|
787
|
+
|
|
619
788
|
updateName(config) {
|
|
620
789
|
if (this.hasAttribute('unnamed'))
|
|
621
790
|
return;
|
|
@@ -631,7 +800,6 @@
|
|
|
631
800
|
|
|
632
801
|
render(code = this.source) {
|
|
633
802
|
//同时多次改变属性,只执行一次
|
|
634
|
-
// 如果已经在队列中,则直接返回
|
|
635
803
|
if (this._renderQueued)
|
|
636
804
|
return;
|
|
637
805
|
this._renderQueued = true;
|
|
@@ -644,6 +812,7 @@
|
|
|
644
812
|
this._renderQueued = false;
|
|
645
813
|
});
|
|
646
814
|
}
|
|
815
|
+
|
|
647
816
|
clear() {
|
|
648
817
|
this.highlightedCodeEl.innerHTML = this.source = this.lineString = '';
|
|
649
818
|
}
|
|
@@ -659,7 +828,8 @@
|
|
|
659
828
|
// 高亮新的部分
|
|
660
829
|
await this.highlight(newCode);
|
|
661
830
|
}
|
|
662
|
-
|
|
831
|
+
|
|
832
|
+
getLastLine() {
|
|
663
833
|
const lastChild = this.highlightedCodeEl.lastElementChild, lastLine = !lastChild || this.highlightedCodeEl.lastElementChild?.completed ?
|
|
664
834
|
this.createLineWrap() : lastChild;
|
|
665
835
|
return {
|
|
@@ -667,28 +837,37 @@
|
|
|
667
837
|
codeWrap: lastLine.lastElementChild
|
|
668
838
|
};
|
|
669
839
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
840
|
+
|
|
841
|
+
close() {
|
|
842
|
+
const lineWrap = this.highlightedCodeEl.lastElementChild;
|
|
843
|
+
if (!lineWrap)
|
|
844
|
+
return;
|
|
845
|
+
lineWrap.completed = true;
|
|
846
|
+
//行结束前保存
|
|
847
|
+
this.lastLineString = this.lineString;
|
|
848
|
+
this.getLineToFill(lineWrap?.lastElementChild, this.lineString);
|
|
849
|
+
//一行结束,清空临时行文本
|
|
850
|
+
this.lineString = '';
|
|
673
851
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
852
|
+
|
|
853
|
+
open() {
|
|
854
|
+
const lineWrap = this.highlightedCodeEl.lastElementChild;
|
|
855
|
+
if (!lineWrap)
|
|
856
|
+
return;
|
|
857
|
+
lineWrap.completed = false;
|
|
858
|
+
//恢复最后一行字符串
|
|
859
|
+
(lineWrap?.lastElementChild).textContent = this.lineString = this.lastLineString;
|
|
677
860
|
}
|
|
678
|
-
|
|
679
|
-
|
|
861
|
+
|
|
862
|
+
stream(str, forceClose = false) {
|
|
863
|
+
const { lineWrap, codeWrap } = this.getLastLine();
|
|
680
864
|
this.highlightedCodeEl.appendChild(lineWrap);
|
|
681
865
|
//汇集
|
|
682
866
|
this.source += str;
|
|
683
867
|
//如果没有遇到换行符号,也可以强制结束
|
|
684
|
-
|
|
685
|
-
if (str.startsWith('\n') || str.startsWith('\r')) {
|
|
868
|
+
if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
|
|
686
869
|
//标记完成
|
|
687
|
-
this.
|
|
688
|
-
//渲染
|
|
689
|
-
this.getLineToFill(codeWrap, this.lineString);
|
|
690
|
-
//一行结束,清空临时行文本
|
|
691
|
-
this.lineString = '';
|
|
870
|
+
this.close();
|
|
692
871
|
}
|
|
693
872
|
else {
|
|
694
873
|
//插入文本
|
|
@@ -701,171 +880,6 @@
|
|
|
701
880
|
}
|
|
702
881
|
}
|
|
703
882
|
|
|
704
|
-
const html = [
|
|
705
|
-
{ token: 'comment', pattern: /<!--[\s\S]*?-->/, light: '#999999', dark: '#6e7681' },
|
|
706
|
-
{ token: 'doctype', pattern: /<!DOCTYPE[\s\S]*?>/i, light: '#6a737d', dark: '#8b949e' },
|
|
707
|
-
// 匹配标签名: <div, </div
|
|
708
|
-
{ token: 'tag', pattern: /<\/?[a-zA-Z0-9]+/, light: '#22863a', dark: '#7ee787' },
|
|
709
|
-
// 匹配属性名: class=
|
|
710
|
-
{ token: 'attr', pattern: /[a-zA-Z-]+(?=\s*=\s*)/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
711
|
-
// 匹配属性值: "value"
|
|
712
|
-
{ token: 'string', pattern: /(['"])(?:\\.|[^\\])*?\1/, light: '#032f62', dark: '#a5d6ff' },
|
|
713
|
-
// 匹配标签闭合: >, />
|
|
714
|
-
{ token: 'bracket', pattern: /\/?>/, light: '#24292e', dark: '#c9d1d9' }
|
|
715
|
-
];
|
|
716
|
-
|
|
717
|
-
const javascript = [
|
|
718
|
-
{ token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
719
|
-
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
|
|
720
|
-
{ 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' },
|
|
721
|
-
{ token: 'builtin', pattern: /\b(console|window|document|Math|JSON|true|false|null|undefined|Object|Array|Promise|Number|String|Boolean)\b/, light: '#e36209', dark: '#ffa657' },
|
|
722
|
-
{ token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
723
|
-
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
724
|
-
{ token: 'op', pattern: /[+\-*/%=<>!&|^~]+/, light: '#069598', dark: '#56b6c2' }
|
|
725
|
-
];
|
|
726
|
-
|
|
727
|
-
const markdown = [
|
|
728
|
-
// 注释: 这是 Markdown 中的行内注释
|
|
729
|
-
{ token: 'comment', pattern: /<!--[\s\S]*?-->/, light: '#6a737d', dark: '#8b949e' },
|
|
730
|
-
// 标题: 通过 `#` 来定义标题
|
|
731
|
-
{ token: 'heading', pattern: /(^|\n)(#{1,6})\s*(.+)/, light: '#e36209', dark: '#ffa657' },
|
|
732
|
-
// 粗体: **text** 或 __text__
|
|
733
|
-
{ token: 'bold', pattern: /\*\*([^*]+)\*\*|__([^_]+)__/g, light: '#d73a49', dark: '#ff7b72' },
|
|
734
|
-
// 斜体: *text* 或 _text_
|
|
735
|
-
{ token: 'italic', pattern: /\*([^*]+)\*|_([^_]+)_/g, light: '#032f62', dark: '#a5d6ff' },
|
|
736
|
-
// 链接: [text](url)
|
|
737
|
-
{ token: 'link', pattern: /\[([^\]]+)\]\(([^)]+)\)/g, light: '#0288d1', dark: '#80c0ff' },
|
|
738
|
-
// 行内代码: `code`
|
|
739
|
-
{ token: 'inline-code', pattern: /`([^`]+)`/g, light: '#032f62', dark: '#98c379' },
|
|
740
|
-
// 代码块: ```code```
|
|
741
|
-
{ token: 'code-block', pattern: /```([^\n]+)\n([\s\S]*?)```/g, light: '#24292e', dark: '#c9d1d9' },
|
|
742
|
-
// 列表项: - item 或 * item
|
|
743
|
-
{ token: 'list-item', pattern: /(^|\n)([-*])\s+(.+)/g, light: '#5c6e7c', dark: '#8b949e' },
|
|
744
|
-
// 引用: > text
|
|
745
|
-
{ token: 'quote', pattern: /(^|\n)>[ \t]*(.+)/g, light: '#6f42c1', dark: '#d2a8ff' },
|
|
746
|
-
// 图片: 
|
|
747
|
-
{ token: 'image', pattern: /!\[([^\]]+)\]\(([^)]+)\)/g, light: '#d73a49', dark: '#ff7b72' },
|
|
748
|
-
// 分割线: ---
|
|
749
|
-
{ token: 'hr', pattern: /^(---|___|\*\*\*)\s*$/gm, light: '#24292e', dark: '#c9d1d9' },
|
|
750
|
-
// 强调和删除: ~~text~~
|
|
751
|
-
{ token: 'strikethrough', pattern: /~~([^~]+)~~/g, light: '#e36209', dark: '#ffa657' },
|
|
752
|
-
// 表格: | header1 | header2 |
|
|
753
|
-
{ token: 'table', pattern: /\|([^\|]+)\|([^\|]+)\|/g, light: '#5c6e7c', dark: '#8b949e' }
|
|
754
|
-
];
|
|
755
|
-
|
|
756
|
-
const typescript = [
|
|
757
|
-
{ token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
758
|
-
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, light: '#032f62', dark: '#98c379' },
|
|
759
|
-
{ token: 'decorator', pattern: /@[a-zA-Z_]\w*/, light: '#953800', dark: '#ffa657' },
|
|
760
|
-
{ 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' },
|
|
761
|
-
{ token: 'builtin', pattern: /\b(any|boolean|never|number|string|symbol|unknown|void|undefined|null|true|false|console|window|document)\b/, light: '#e36209', dark: '#ffa657' },
|
|
762
|
-
{ token: 'type', pattern: /\b[A-Z]\w*\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
763
|
-
{ token: 'number', pattern: /\b(0x[\da-fA-F]+|0b[01]+|\d+(\.\d+)?)\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
764
|
-
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, light: '#6f42c1', dark: '#d2a8ff' },
|
|
765
|
-
{ token: 'op', pattern: /(\?\.|![:\.]|[+\-*/%=<>!&|^~]+)/, light: '#089ba6', dark: '#79c0ff' }
|
|
766
|
-
];
|
|
767
|
-
|
|
768
|
-
const COMMA = ',';
|
|
769
|
-
|
|
770
|
-
const SPACE = ' ';
|
|
771
|
-
|
|
772
|
-
const trim = (str, placement = '') => {
|
|
773
|
-
if (typeof str !== 'string') {
|
|
774
|
-
console.warn('Expected a string input');
|
|
775
|
-
return '';
|
|
776
|
-
}
|
|
777
|
-
switch (placement) {
|
|
778
|
-
case 'start':
|
|
779
|
-
return str.trimStart();
|
|
780
|
-
case 'end':
|
|
781
|
-
return str.trimEnd();
|
|
782
|
-
case 'both':
|
|
783
|
-
return str.trim();
|
|
784
|
-
case 'global':
|
|
785
|
-
return str.replace(/[\s\r\n]+/g, '');
|
|
786
|
-
default:
|
|
787
|
-
return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
const parseClasses = (data) => {
|
|
792
|
-
let separator, result = [];
|
|
793
|
-
if (Array.isArray(data)) {
|
|
794
|
-
// If data is already an array, filter out invalid values
|
|
795
|
-
result = data.filter((k) => k && typeof k === 'string');
|
|
796
|
-
}
|
|
797
|
-
else {
|
|
798
|
-
// Trim the input string and handle multiple spaces
|
|
799
|
-
data = trim(data);
|
|
800
|
-
// Use comma as the separator if present, otherwise use space
|
|
801
|
-
separator = data.includes(COMMA) ? COMMA : SPACE;
|
|
802
|
-
result = data.split(separator);
|
|
803
|
-
}
|
|
804
|
-
// Trim each item globally and filter out any empty strings
|
|
805
|
-
return result.map((k) => trim(k, 'global')).filter(Boolean);
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
const rtlStyle = (name = '') => `
|
|
809
|
-
<style>
|
|
810
|
-
:where([dir="rtl"]) .icax-${name},
|
|
811
|
-
:where(:dir(rtl)) .icax-${name} {
|
|
812
|
-
transform: scaleX(-1);
|
|
813
|
-
transform-origin: center;
|
|
814
|
-
}
|
|
815
|
-
</style>
|
|
816
|
-
`;
|
|
817
|
-
|
|
818
|
-
const wrap = (content, fun, isRtl = false, options) => {
|
|
819
|
-
const size = options?.size || '1em', color = options?.color || 'currentColor', thickness = options?.thickness || 2, classes = options?.classes ? parseClasses(options.classes).join(' ') : '',
|
|
820
|
-
// 得到 "icax-left"
|
|
821
|
-
origName = fun.name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
822
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}"
|
|
823
|
-
stroke-width="${thickness}" stroke-linecap="round" stroke-linejoin="round" class="${origName} ${classes}">
|
|
824
|
-
${isRtl ? rtlStyle(origName.split('-')[1]) : ''}
|
|
825
|
-
${content}
|
|
826
|
-
</svg>`;
|
|
827
|
-
};
|
|
828
|
-
|
|
829
|
-
const icaxCheck = (options) => wrap(`<polyline points="20 6 9 17 4 12"></polyline>`, icaxCheck, false, options);
|
|
830
|
-
|
|
831
|
-
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);
|
|
832
|
-
|
|
833
|
-
const copy = {
|
|
834
|
-
name: 'copy',
|
|
835
|
-
icon: icaxCopy(),
|
|
836
|
-
action: function (arg) {
|
|
837
|
-
arg.wrapEl.onclick = () => {
|
|
838
|
-
//this只是组件实例
|
|
839
|
-
navigator.clipboard.writeText(this.source)
|
|
840
|
-
.then(() => {
|
|
841
|
-
console.log('Text successfully copied to clipboard');
|
|
842
|
-
arg.iconEl.innerHTML = icaxCheck();
|
|
843
|
-
arg.iconEl.toggleAttribute('disabled', true);
|
|
844
|
-
setTimeout(() => {
|
|
845
|
-
//恢复
|
|
846
|
-
arg.iconEl.removeAttribute('disabled');
|
|
847
|
-
arg.iconEl.innerHTML = icaxCopy();
|
|
848
|
-
}, 2000);
|
|
849
|
-
})
|
|
850
|
-
.catch(err => {
|
|
851
|
-
console.error('Error copying text to clipboard: ', err);
|
|
852
|
-
});
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
const css = [
|
|
858
|
-
{ token: 'comment', pattern: /\/\*[\s\S]*?\*\//, light: '#6a737d', dark: '#8b949e' },
|
|
859
|
-
{ token: 'value', pattern: /(?:'|")(?:\\.|[^\\'"\b])*?(?:'|")/, light: '#032f62', dark: '#a5d6ff' },
|
|
860
|
-
{ token: 'func', pattern: /[a-z-]+\(?=/, light: '#e36209', dark: '#ffa657' },
|
|
861
|
-
{ token: 'property', pattern: /[a-z-]+(?=\s*:)/, light: '#005cc5', dark: '#79c0ff' },
|
|
862
|
-
{ token: 'selector', pattern: /[.#a-z0-9, \n\t>:+()_-]+(?=\s*\{)/i, light: '#22863a', dark: '#7ee787' },
|
|
863
|
-
{ token: 'unit', pattern: /(?<=\d)(px|em|rem|%|vh|vw|ms|s|deg)/, light: '#d73a49', dark: '#ff7b72' },
|
|
864
|
-
{ token: 'number', pattern: /\b\d+(\.\d+)?\b/, light: '#005cc5', dark: '#79c0ff' },
|
|
865
|
-
{ token: 'punct', pattern: /[{}();:]/, light: '#24292e', dark: '#c9d1d9' }
|
|
866
|
-
];
|
|
867
|
-
|
|
868
|
-
const Coax = CoaxElem;
|
|
869
883
|
//注册语言类型
|
|
870
884
|
Coax.register('css', {
|
|
871
885
|
alias: 'CSS',
|