@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.
- package/LICENSE +1 -1
- package/README.md +331 -166
- package/dist/coax.cjs.js +450 -271
- package/dist/coax.cjs.min.js +4 -4
- package/dist/coax.esm.js +444 -271
- package/dist/coax.esm.min.js +4 -4
- package/dist/coax.umd.js +466 -266
- package/dist/coax.umd.min.js +4 -4
- package/examples/.htaccess +0 -0
- package/examples/append-highlight.html +82 -0
- package/examples/color-selector.html +412 -0
- package/examples/deepseek-highlight.html +100 -0
- package/examples/js-highlight.html +1 -1
- package/examples/md-highlight.html +60 -0
- package/examples/nginx.htaccess +0 -0
- package/examples/replace-highlight.html +69 -0
- package/examples/stream-highlight.html +64 -0
- package/examples/theme-highlight.html +69 -0
- package/package.json +4 -4
- package/rollup.config.js +3 -3
- package/script-note.js +2 -2
- package/src/Coax.js +25 -414
- package/src/Coax.ts +28 -443
- package/src/components/Coax.js +528 -0
- package/src/components/Coax.ts +556 -0
- package/src/modules.js +12 -0
- package/src/modules.ts +23 -0
- package/src/rules/css.js +11 -0
- package/src/rules/css.ts +11 -0
- package/src/rules/html.js +13 -0
- package/src/rules/html.ts +13 -0
- package/src/rules/javascript.js +10 -0
- package/src/rules/javascript.ts +10 -0
- package/src/rules/markdown.js +29 -0
- package/src/rules/markdown.ts +41 -0
- package/src/rules/ruleCss - /345/211/257/346/234/254.js" +10 -0
- package/src/rules/ruleHTML - /345/211/257/346/234/254.js" +12 -0
- package/src/rules/ruleJs - /345/211/257/346/234/254.js" +10 -0
- package/src/rules/ruleTs - /345/211/257/346/234/254.js" +12 -0
- package/src/rules/typescript.js +12 -0
- package/src/rules/typescript.ts +12 -0
- package/src/tools/copy.js +26 -0
- package/src/tools/copy.ts +29 -0
package/dist/coax.umd.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
/*!
|
|
3
|
-
* @since Last modified: 2026-1-
|
|
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}
|
|
@@ -14,10 +14,244 @@
|
|
|
14
14
|
* @license MIT license
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
(function (factory) {
|
|
17
|
+
(function (global, factory) {
|
|
18
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
18
19
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
19
|
-
factory();
|
|
20
|
-
})((function () { 'use strict';
|
|
20
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.coax = factory());
|
|
21
|
+
})(this, (function () { 'use strict';
|
|
22
|
+
|
|
23
|
+
const NAMESPACE = 'ax';
|
|
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
|
+
|
|
189
|
+
const typeWriter = (text, options) => {
|
|
190
|
+
const speed = options.speed || 100; // Set typing speed (default to 100ms per character)
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
// Callback before typing starts
|
|
193
|
+
options?.onBeforeType?.(text);
|
|
194
|
+
let index = 0;
|
|
195
|
+
// Timer to type the text character by character at the given speed
|
|
196
|
+
const timer = setInterval(() => {
|
|
197
|
+
if (index < text.length) {
|
|
198
|
+
const char = text.charAt(index); // Get the character at the current index
|
|
199
|
+
const typedText = text.substring(0, index + 1); // The text typed so far
|
|
200
|
+
// Callback during typing each character
|
|
201
|
+
options?.onDuringType?.(char, typedText);
|
|
202
|
+
index++;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// Clear the timer once typing is complete
|
|
206
|
+
clearInterval(timer);
|
|
207
|
+
// Resolve the Promise when typing is complete
|
|
208
|
+
resolve(text);
|
|
209
|
+
// Callback after typing is finished
|
|
210
|
+
options?.onAfterType?.(text);
|
|
211
|
+
}
|
|
212
|
+
}, speed);
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const COMMA = ',';
|
|
217
|
+
|
|
218
|
+
const SPACE = ' ';
|
|
219
|
+
|
|
220
|
+
const trim = (str, placement = 'compress') => {
|
|
221
|
+
if (typeof str !== 'string') {
|
|
222
|
+
console.warn('Expected a string input');
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
switch (placement) {
|
|
226
|
+
case 'start':
|
|
227
|
+
return str.trimStart();
|
|
228
|
+
case 'end':
|
|
229
|
+
return str.trimEnd();
|
|
230
|
+
case 'both':
|
|
231
|
+
return str.trim();
|
|
232
|
+
case 'global':
|
|
233
|
+
return str.replace(/[\s\r\n]+/g, '');
|
|
234
|
+
default:
|
|
235
|
+
return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const parseClasses = (data) => {
|
|
240
|
+
let separator, result = [];
|
|
241
|
+
if (Array.isArray(data)) {
|
|
242
|
+
// If data is already an array, filter out invalid values
|
|
243
|
+
result = data.filter((k) => k && typeof k === 'string');
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Trim the input string and handle multiple spaces
|
|
247
|
+
data = trim(data);
|
|
248
|
+
// Use comma as the separator if present, otherwise use space
|
|
249
|
+
separator = data.includes(COMMA) ? COMMA : SPACE;
|
|
250
|
+
result = data.split(separator);
|
|
251
|
+
}
|
|
252
|
+
// Trim each item globally and filter out any empty strings
|
|
253
|
+
return result.map((k) => trim(k, 'global')).filter(Boolean);
|
|
254
|
+
};
|
|
21
255
|
|
|
22
256
|
const getDataType = (obj) => {
|
|
23
257
|
let tmp = Object.prototype.toString.call(obj).slice(8, -1), result;
|
|
@@ -161,50 +395,8 @@
|
|
|
161
395
|
|
|
162
396
|
const ALIAS = 'rep';
|
|
163
397
|
|
|
164
|
-
const NAMESPACE = 'ax';
|
|
165
|
-
|
|
166
|
-
const COMMA$1 = ',';
|
|
167
|
-
|
|
168
|
-
const SPACE$1 = ' ';
|
|
169
|
-
|
|
170
|
-
const trim$1 = (str, placement = '') => {
|
|
171
|
-
if (typeof str !== 'string') {
|
|
172
|
-
console.warn('Expected a string input');
|
|
173
|
-
return '';
|
|
174
|
-
}
|
|
175
|
-
switch (placement) {
|
|
176
|
-
case 'start':
|
|
177
|
-
return str.trimStart();
|
|
178
|
-
case 'end':
|
|
179
|
-
return str.trimEnd();
|
|
180
|
-
case 'both':
|
|
181
|
-
return str.trim();
|
|
182
|
-
case 'global':
|
|
183
|
-
return str.replace(/[\s\r\n]+/g, '');
|
|
184
|
-
default:
|
|
185
|
-
return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const parseClasses$1 = (data) => {
|
|
190
|
-
let separator, result = [];
|
|
191
|
-
if (Array.isArray(data)) {
|
|
192
|
-
// If data is already an array, filter out invalid values
|
|
193
|
-
result = data.filter((k) => k && typeof k === 'string');
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// Trim the input string and handle multiple spaces
|
|
197
|
-
data = trim$1(data);
|
|
198
|
-
// Use comma as the separator if present, otherwise use space
|
|
199
|
-
separator = data.includes(COMMA$1) ? COMMA$1 : SPACE$1;
|
|
200
|
-
result = data.split(separator);
|
|
201
|
-
}
|
|
202
|
-
// Trim each item globally and filter out any empty strings
|
|
203
|
-
return result.map((k) => trim$1(k, 'global')).filter(Boolean);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
398
|
const addClasses = (target, classes, intercept) => {
|
|
207
|
-
const el = getEl(target), arr = parseClasses
|
|
399
|
+
const el = getEl(target), arr = parseClasses(classes);
|
|
208
400
|
if (!el || arr.length === 0) {
|
|
209
401
|
return;
|
|
210
402
|
}
|
|
@@ -244,94 +436,34 @@
|
|
|
244
436
|
return toolsEl;
|
|
245
437
|
};
|
|
246
438
|
|
|
247
|
-
const COMMA = ',';
|
|
248
|
-
|
|
249
|
-
const SPACE = ' ';
|
|
250
|
-
|
|
251
|
-
const trim = (str, placement = '') => {
|
|
252
|
-
if (typeof str !== 'string') {
|
|
253
|
-
console.warn('Expected a string input');
|
|
254
|
-
return '';
|
|
255
|
-
}
|
|
256
|
-
switch (placement) {
|
|
257
|
-
case 'start':
|
|
258
|
-
return str.trimStart();
|
|
259
|
-
case 'end':
|
|
260
|
-
return str.trimEnd();
|
|
261
|
-
case 'both':
|
|
262
|
-
return str.trim();
|
|
263
|
-
case 'global':
|
|
264
|
-
return str.replace(/[\s\r\n]+/g, '');
|
|
265
|
-
default:
|
|
266
|
-
return str.trim().replace(/[\s\r\n]+/g, ' '); // Default behavior, trims both ends and replaces inner spaces
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
const parseClasses = (data) => {
|
|
271
|
-
let separator, result = [];
|
|
272
|
-
if (Array.isArray(data)) {
|
|
273
|
-
// If data is already an array, filter out invalid values
|
|
274
|
-
result = data.filter((k) => k && typeof k === 'string');
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
// Trim the input string and handle multiple spaces
|
|
278
|
-
data = trim(data);
|
|
279
|
-
// Use comma as the separator if present, otherwise use space
|
|
280
|
-
separator = data.includes(COMMA) ? COMMA : SPACE;
|
|
281
|
-
result = data.split(separator);
|
|
282
|
-
}
|
|
283
|
-
// Trim each item globally and filter out any empty strings
|
|
284
|
-
return result.map((k) => trim(k, 'global')).filter(Boolean);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const rtlStyle = (name = '') => `
|
|
288
|
-
<style>
|
|
289
|
-
:where([dir="rtl"]) .icax-${name},
|
|
290
|
-
:where(:dir(rtl)) .icax-${name} {
|
|
291
|
-
transform: scaleX(-1);
|
|
292
|
-
transform-origin: center;
|
|
293
|
-
}
|
|
294
|
-
</style>
|
|
295
|
-
`;
|
|
296
|
-
|
|
297
|
-
const wrap = (content, fun, isRtl = false, options) => {
|
|
298
|
-
const size = options?.size || '1em', color = options?.color || 'currentColor', thickness = options?.thickness || 2, classes = options?.classes ? parseClasses(options.classes).join(' ') : '',
|
|
299
|
-
// 得到 "icax-left"
|
|
300
|
-
origName = fun.name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
301
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}"
|
|
302
|
-
stroke-width="${thickness}" stroke-linecap="round" stroke-linejoin="round" class="${origName} ${classes}">
|
|
303
|
-
${isRtl ? rtlStyle(origName.split('-')[1]) : ''}
|
|
304
|
-
${content}
|
|
305
|
-
</svg>`;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
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);
|
|
309
|
-
|
|
310
|
-
const icaxCheck = (options) => wrap(`<polyline points="20 6 9 17 4 12"></polyline>`, icaxCheck, false, options);
|
|
311
|
-
|
|
312
439
|
class Coax extends HTMLElement {
|
|
313
440
|
// A static Map to hold the configuration for different languages
|
|
314
441
|
static languages = new Map();
|
|
442
|
+
// A static array to hold the tools registered with the component
|
|
315
443
|
static tools = [];
|
|
316
|
-
source;
|
|
317
|
-
_renderQueued = false;
|
|
318
|
-
baseStylesEl;
|
|
319
|
-
themeStylesEl;
|
|
320
|
-
dynamicStylesEl;
|
|
321
|
-
highlightedCodeEl;
|
|
322
|
-
headerEl;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
326
460
|
constructor() {
|
|
327
461
|
super();
|
|
328
462
|
// Attach a Shadow DOM to the custom element
|
|
329
463
|
this.attachShadow({ mode: 'open' });
|
|
330
464
|
// Remove leading/trailing whitespace from the raw code content
|
|
331
465
|
this.source = this.textContent?.replace(/^\s*\n|\n\s*$/g, '') || '';
|
|
332
|
-
|
|
333
|
-
this.alias = 'Plain Text';
|
|
334
|
-
// 1. 初始化基础骨架
|
|
466
|
+
// Initialize the basic structure of the component
|
|
335
467
|
this.shadowRoot.innerHTML = `
|
|
336
468
|
<style id="base-styles">
|
|
337
469
|
:host {
|
|
@@ -348,16 +480,16 @@
|
|
|
348
480
|
--border-color:rgb(224, 224, 224);
|
|
349
481
|
--color-code:rgb(51, 51, 51);
|
|
350
482
|
--color-index:rgb(153, 153, 153);
|
|
351
|
-
--color-stripe:
|
|
352
|
-
--color-hover:
|
|
483
|
+
--color-stripe:rgba(0,0,0,0.04);
|
|
484
|
+
--color-hover:rgba(0,0,0,0.06);
|
|
353
485
|
}
|
|
354
486
|
:host([scheme="dark"]){
|
|
355
487
|
--background: #282c34;
|
|
356
488
|
--border-color: transparent;
|
|
357
489
|
--color-code: #abb2bf;
|
|
358
490
|
--color-index:rgb(153, 153, 153);
|
|
359
|
-
--color-stripe:
|
|
360
|
-
--color-hover:
|
|
491
|
+
--color-stripe:rgba(255,255,255,0.04);
|
|
492
|
+
--color-hover:rgba(255,255,255,0.06);
|
|
361
493
|
}
|
|
362
494
|
@media (prefers-color-scheme: dark) {
|
|
363
495
|
:host{
|
|
@@ -365,8 +497,8 @@
|
|
|
365
497
|
--border-color: transparent;
|
|
366
498
|
--color-code: #abb2bf;
|
|
367
499
|
--color-index:rgb(153, 153, 153);
|
|
368
|
-
--color-stripe:
|
|
369
|
-
--color-hover:
|
|
500
|
+
--color-stripe:rgba(255,255,255,0.04);
|
|
501
|
+
--color-hover:rgba(255,255,255,0.06);
|
|
370
502
|
}
|
|
371
503
|
}
|
|
372
504
|
:host {
|
|
@@ -376,7 +508,6 @@
|
|
|
376
508
|
background:var(--${NAMESPACE}-code-background-color,var(--background));
|
|
377
509
|
color:var(--${NAMESPACE}-code-color,var(--color-code));
|
|
378
510
|
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));
|
|
379
|
-
overflow:auto;
|
|
380
511
|
transition: border-color 0.3s ease,color 0.3s ease;
|
|
381
512
|
border-radius: var(--${NAMESPACE}-code-radius,var(--radius));
|
|
382
513
|
}
|
|
@@ -393,6 +524,7 @@
|
|
|
393
524
|
padding: var(--${NAMESPACE}-code-padding,var(--padding)) 0;
|
|
394
525
|
height:var(--${NAMESPACE}-code-height,var(--height));
|
|
395
526
|
max-height:var(--${NAMESPACE}-code-max-height,var(--max-height));
|
|
527
|
+
overflow:auto;
|
|
396
528
|
}
|
|
397
529
|
pre,code{
|
|
398
530
|
font-family:"Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", "monospace";
|
|
@@ -408,6 +540,9 @@
|
|
|
408
540
|
flex:auto;
|
|
409
541
|
}
|
|
410
542
|
}
|
|
543
|
+
code>div>div:empty:before{
|
|
544
|
+
content:' ';
|
|
545
|
+
}
|
|
411
546
|
:host([indexed]) code>div:before{
|
|
412
547
|
display:inline-flex;
|
|
413
548
|
color:var(--color-index);
|
|
@@ -415,13 +550,13 @@
|
|
|
415
550
|
min-width:var(--${NAMESPACE}-code-index-width,2em);
|
|
416
551
|
margin-inline-end:var(--${NAMESPACE}-code-padding,8px);
|
|
417
552
|
}
|
|
418
|
-
:host([hoverable]) code>div:hover{
|
|
419
|
-
background-color:var(--color-hover);
|
|
420
|
-
}
|
|
421
553
|
:host([striped]) code>div:nth-child(odd){
|
|
422
554
|
background-color:var(--color-stripe);
|
|
423
555
|
}
|
|
424
|
-
:host([
|
|
556
|
+
:host([hoverable]) code>div:hover{
|
|
557
|
+
background-color:var(--color-hover);
|
|
558
|
+
}
|
|
559
|
+
:host([wrapped]) code>div>div{
|
|
425
560
|
white-space: pre-wrap;
|
|
426
561
|
}
|
|
427
562
|
:host([unnamed]) #code-name{
|
|
@@ -461,41 +596,47 @@
|
|
|
461
596
|
<pre><code id="highlight-code"></code></pre>
|
|
462
597
|
</div>
|
|
463
598
|
`;
|
|
464
|
-
//
|
|
599
|
+
// Cache references to various elements
|
|
465
600
|
this.baseStylesEl = getEl('#base-styles', this.shadowRoot);
|
|
466
601
|
this.themeStylesEl = getEl('#theme-styles', this.shadowRoot);
|
|
467
602
|
this.dynamicStylesEl = getEl('#dynamic-styles', this.shadowRoot);
|
|
468
603
|
this.headerEl = getEl('#code-header', this.shadowRoot);
|
|
469
|
-
this.
|
|
470
|
-
this.
|
|
604
|
+
this.codeNameEl = getEl('#code-name', this.shadowRoot);
|
|
605
|
+
this.codeToolsEl = getEl('#code-tools', this.shadowRoot);
|
|
606
|
+
this.codeBodyEl = getEl('#code-body', this.shadowRoot);
|
|
471
607
|
this.highlightedCodeEl = getEl('#highlight-code', this.shadowRoot);
|
|
608
|
+
this.codeBodyEl.addEventListener('scroll', () => {
|
|
609
|
+
let flag = this.codeBodyEl.scrollTop + this.codeBodyEl.clientHeight < this.codeBodyEl.scrollHeight;
|
|
610
|
+
// Check if the user manually scrolled
|
|
611
|
+
this.autoScroll = !flag;
|
|
612
|
+
});
|
|
472
613
|
}
|
|
473
614
|
|
|
474
615
|
static register(name, config) {
|
|
475
616
|
// Store the language configuration in the static map
|
|
476
617
|
this.languages.set(name, { ...config });
|
|
477
618
|
}
|
|
478
|
-
|
|
619
|
+
|
|
479
620
|
static addTools(items) {
|
|
480
621
|
Coax.tools = items;
|
|
481
622
|
}
|
|
482
|
-
|
|
623
|
+
|
|
483
624
|
mountTools(toolItems) {
|
|
484
625
|
requestAnimationFrame(() => {
|
|
485
|
-
this.
|
|
626
|
+
this.codeToolsEl.innerHTML = '';
|
|
486
627
|
let items = toolItems.map(k => {
|
|
487
628
|
k.action = k.action.bind(this);
|
|
488
629
|
return k;
|
|
489
630
|
});
|
|
490
|
-
this.
|
|
631
|
+
this.codeToolsEl.appendChild(createTools(items));
|
|
491
632
|
});
|
|
492
633
|
}
|
|
493
|
-
|
|
634
|
+
|
|
494
635
|
connectedCallback() {
|
|
495
636
|
this.render();
|
|
496
637
|
}
|
|
497
|
-
|
|
498
|
-
static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools']; }
|
|
638
|
+
|
|
639
|
+
static get observedAttributes() { return ['lang', 'height', 'max-height', 'tools', 'speed']; }
|
|
499
640
|
|
|
500
641
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
501
642
|
if (oldVal === newVal)
|
|
@@ -503,19 +644,19 @@
|
|
|
503
644
|
if (name === 'height' || name === 'max-height') {
|
|
504
645
|
this.updateStyleByRegExp(name, newVal);
|
|
505
646
|
}
|
|
506
|
-
|
|
507
|
-
|
|
647
|
+
if (name === 'speed') {
|
|
648
|
+
// Convert to integer (0 or 1)
|
|
649
|
+
this.speed = ~~(!!newVal);
|
|
650
|
+
}
|
|
651
|
+
if (name === 'lang') {
|
|
652
|
+
// Update the language and re-render
|
|
508
653
|
this.lang = newVal;
|
|
509
654
|
this.render();
|
|
510
655
|
}
|
|
511
|
-
|
|
512
|
-
this.nameEl.innerHTML = newVal === null ? (this.alias || this.lang) : '';
|
|
513
|
-
this.nameEl.innerHTML = '';
|
|
514
|
-
}
|
|
515
|
-
else if (name === 'tools') {
|
|
656
|
+
if (name === 'tools') {
|
|
516
657
|
if (!newVal)
|
|
517
|
-
this.
|
|
518
|
-
const tools = parseClasses
|
|
658
|
+
this.codeToolsEl.innerHTML = '';
|
|
659
|
+
const tools = parseClasses(newVal), toolItems = Coax.tools.filter(k => tools.includes(k.name));
|
|
519
660
|
if (!toolItems.length)
|
|
520
661
|
return;
|
|
521
662
|
this.mountTools(toolItems);
|
|
@@ -529,37 +670,91 @@
|
|
|
529
670
|
// 替换为新的属性值
|
|
530
671
|
this.baseStylesEl.textContent = this.baseStylesEl.textContent.replace(regex, `;\n${prop}: ${value};`);
|
|
531
672
|
}
|
|
673
|
+
|
|
532
674
|
getCssPrefix(config) {
|
|
533
675
|
return (config?.cssPrefix || this.lang).replace(/[^a-zA-Z0-9_-]/g, '\\$&');
|
|
534
676
|
}
|
|
535
677
|
|
|
536
|
-
|
|
678
|
+
getHighLightString(string, config) {
|
|
679
|
+
config = config || Coax.languages.get(this.lang);
|
|
680
|
+
if (!config)
|
|
681
|
+
return string;
|
|
682
|
+
// 如果找到了配置,则进行高亮处理
|
|
683
|
+
const cssPrefix = this.getCssPrefix(config),
|
|
684
|
+
// 获取用于语法高亮的正则表达式
|
|
685
|
+
combinedRegex = new RegExp(config.rules.map((r) => `(${r.pattern.source})`).join('|'), 'g');
|
|
686
|
+
return string.replace(combinedRegex, (match, ...args) => {
|
|
687
|
+
const i = args.findIndex(val => val !== undefined);
|
|
688
|
+
return i !== -1 && config.rules[i] ? `<span class="${NAMESPACE}-${cssPrefix}-${config.rules[i].token}">${match}</span>` : match;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
createLineWrap(index, startIndex) {
|
|
693
|
+
let dataIndex = 0;
|
|
694
|
+
if (index == null && startIndex == null) {
|
|
695
|
+
dataIndex = this.highlightedCodeEl.children.length;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
const start = startIndex || this.highlightedCodeEl.children.length;
|
|
699
|
+
dataIndex = start + index;
|
|
700
|
+
}
|
|
701
|
+
return createEl('div', { 'data-index': dataIndex }, '<div></div>');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
getLineToFill(codeWrap, line, config) {
|
|
705
|
+
config = config || Coax.languages.get(this.lang);
|
|
706
|
+
let highlightedLine = this.getHighLightString(line, config);
|
|
707
|
+
// 将高亮后的内容填充到 div 中
|
|
708
|
+
codeWrap.innerHTML = highlightedLine;
|
|
709
|
+
}
|
|
710
|
+
;
|
|
711
|
+
|
|
712
|
+
async highlight(newCode) {
|
|
537
713
|
const config = Coax.languages.get(this.lang), startIndex = this.highlightedCodeEl.children.length,
|
|
538
714
|
// 将新源码按行分割
|
|
539
|
-
newCodeLines = newCode.split('\n')
|
|
715
|
+
newCodeLines = newCode ? newCode.split('\n') : [];
|
|
716
|
+
//更新别名
|
|
717
|
+
this.updateName(config);
|
|
718
|
+
if (!newCodeLines.length)
|
|
719
|
+
return true;
|
|
540
720
|
// 如果没有找到配置,则输出原始代码,并不进行高亮处理
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
721
|
+
for (let [index, lineString] of newCodeLines.entries()) {
|
|
722
|
+
//如果是空行则跳过
|
|
723
|
+
if (!lineString.trim() && this.hasAttribute('sanitized'))
|
|
724
|
+
continue;
|
|
725
|
+
// 创建一个 div 包裹每一行
|
|
726
|
+
const lineWrap = this.createLineWrap(index, startIndex), codeWrap = lineWrap.lastElementChild;
|
|
727
|
+
//标记完成
|
|
728
|
+
lineWrap.completed = true;
|
|
729
|
+
if (this.hasAttribute('speed')) {
|
|
730
|
+
this.highlightedCodeEl.appendChild(lineWrap);
|
|
731
|
+
//流式打字
|
|
732
|
+
await typeWriter(lineString, {
|
|
733
|
+
speed: this.speed,
|
|
734
|
+
onDuringType: (char, fullText) => {
|
|
735
|
+
codeWrap.innerHTML = fullText;
|
|
736
|
+
}
|
|
552
737
|
});
|
|
738
|
+
this.getLineToFill(codeWrap, lineString, config);
|
|
553
739
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
740
|
+
else {
|
|
741
|
+
//直接打出
|
|
742
|
+
this.getLineToFill(codeWrap, lineString, config);
|
|
743
|
+
//
|
|
744
|
+
this.highlightedCodeEl.appendChild(lineWrap);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
//滚动到最底部
|
|
748
|
+
this.autoScrollCode();
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
autoScrollCode() {
|
|
753
|
+
if (this.autoScroll) {
|
|
754
|
+
this.codeBodyEl.scrollTop = this.codeBodyEl.scrollHeight;
|
|
755
|
+
}
|
|
562
756
|
}
|
|
757
|
+
|
|
563
758
|
injectThemeStyles() {
|
|
564
759
|
const config = Coax.languages.get(this.lang);
|
|
565
760
|
if (!config)
|
|
@@ -569,143 +764,148 @@
|
|
|
569
764
|
//Generate dynamic CSS classes for each syntax rule
|
|
570
765
|
// 为 rules 中的每一个 name 生成类似 .hl-name { color: var(--ax-code-name); }
|
|
571
766
|
lightStyles = config.rules.map((rule) => `
|
|
572
|
-
.${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token}${rule.
|
|
767
|
+
.${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token}${rule.light ? ',' + rule.light : ''});}`).join('\n'), darkStyles = '', schemeStyles = '';
|
|
573
768
|
darkStyles += `:host([scheme="dark"]){`;
|
|
574
769
|
darkStyles += config.rules.map((rule) => `
|
|
575
|
-
${rule.
|
|
770
|
+
${rule.light ? `
|
|
576
771
|
.${NAMESPACE}-${cssPrefix}-${rule.token} {color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark});}` : ``}`).join('\n');
|
|
577
772
|
darkStyles += `}`;
|
|
578
773
|
schemeStyles = `@media (prefers-color-scheme: dark){
|
|
579
774
|
:host{
|
|
580
775
|
`;
|
|
581
776
|
schemeStyles += config.rules.map((rule) => `
|
|
582
|
-
${rule.
|
|
777
|
+
${rule.light ? `
|
|
583
778
|
.${NAMESPACE}-${cssPrefix}-${rule.token} { color: var(--${NAMESPACE}-${cssPrefix}-${rule.token},${rule.dark}); }` : ``} `).join('\n');
|
|
584
779
|
schemeStyles += `}`;
|
|
585
780
|
// Set the inner HTML of the shadow root, including styles and highlighted code
|
|
586
|
-
// 2. 精确更新 DOM 节点而非重写 ShadowRoot
|
|
587
781
|
this.dynamicStylesEl.textContent = lightStyles + darkStyles + schemeStyles;
|
|
588
782
|
//附加主题样式
|
|
589
783
|
if (config?.themeStyles) {
|
|
590
784
|
this.themeStylesEl.textContent = config.themeStyles;
|
|
591
785
|
}
|
|
592
|
-
//更新别名
|
|
593
|
-
this.updateName(config);
|
|
594
786
|
}
|
|
787
|
+
|
|
595
788
|
updateName(config) {
|
|
596
789
|
if (this.hasAttribute('unnamed'))
|
|
597
790
|
return;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
791
|
+
if (!config) {
|
|
792
|
+
this.codeNameEl.innerHTML = 'Plain Text';
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
//更新别名
|
|
796
|
+
this.alias = config.alias || this.lang;
|
|
797
|
+
this.codeNameEl.innerHTML = this.alias;
|
|
798
|
+
}
|
|
601
799
|
}
|
|
602
800
|
|
|
603
|
-
render() {
|
|
801
|
+
render(code = this.source) {
|
|
604
802
|
//同时多次改变属性,只执行一次
|
|
605
|
-
// 如果已经在队列中,则直接返回
|
|
606
803
|
if (this._renderQueued)
|
|
607
804
|
return;
|
|
608
805
|
this._renderQueued = true;
|
|
609
806
|
// 使用 requestAnimationFrame 将渲染推迟到下一帧
|
|
610
|
-
requestAnimationFrame(() => {
|
|
611
|
-
this.
|
|
612
|
-
//一次性渲染
|
|
613
|
-
this.highlight(this.source);
|
|
807
|
+
requestAnimationFrame(async () => {
|
|
808
|
+
this.clear();
|
|
614
809
|
this.injectThemeStyles();
|
|
810
|
+
//一次性渲染
|
|
811
|
+
await this.highlight(code);
|
|
615
812
|
this._renderQueued = false;
|
|
616
813
|
});
|
|
617
814
|
}
|
|
618
815
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
this.
|
|
816
|
+
clear() {
|
|
817
|
+
this.highlightedCodeEl.innerHTML = this.source = this.lineString = '';
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async replace(newCode) {
|
|
821
|
+
this.clear();
|
|
822
|
+
await this.highlight(newCode);
|
|
625
823
|
}
|
|
626
824
|
|
|
627
|
-
|
|
825
|
+
async append(newCode) {
|
|
628
826
|
// 将新的代码追加到现有代码末尾
|
|
629
827
|
this.source += `\n${newCode}`;
|
|
630
828
|
// 高亮新的部分
|
|
631
|
-
this.highlight(newCode);
|
|
829
|
+
await this.highlight(newCode);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
getLastLine() {
|
|
833
|
+
const lastChild = this.highlightedCodeEl.lastElementChild, lastLine = !lastChild || this.highlightedCodeEl.lastElementChild?.completed ?
|
|
834
|
+
this.createLineWrap() : lastChild;
|
|
835
|
+
return {
|
|
836
|
+
lineWrap: lastLine,
|
|
837
|
+
codeWrap: lastLine.lastElementChild
|
|
838
|
+
};
|
|
839
|
+
}
|
|
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 = '';
|
|
851
|
+
}
|
|
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;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
stream(str, forceClose = false) {
|
|
863
|
+
const { lineWrap, codeWrap } = this.getLastLine();
|
|
864
|
+
this.highlightedCodeEl.appendChild(lineWrap);
|
|
865
|
+
//汇集
|
|
866
|
+
this.source += str;
|
|
867
|
+
//如果没有遇到换行符号,也可以强制结束
|
|
868
|
+
if (forceClose || (str.startsWith('\n') || str.endsWith('\n'))) {
|
|
869
|
+
//标记完成
|
|
870
|
+
this.close();
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
//插入文本
|
|
874
|
+
codeWrap.innerHTML += str;
|
|
875
|
+
//临时保存行文本
|
|
876
|
+
this.lineString += str;
|
|
877
|
+
}
|
|
878
|
+
//滚动到最底部
|
|
879
|
+
this.autoScrollCode();
|
|
632
880
|
}
|
|
633
881
|
}
|
|
882
|
+
|
|
883
|
+
//注册语言类型
|
|
634
884
|
Coax.register('css', {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
{ token: 'value', pattern: /(?:'|")(?:\\.|[^\\'"\b])*?(?:'|")/, color: '#61afef', dark: '#a5d6ff' },
|
|
638
|
-
{ token: 'func', pattern: /[a-z-]+\(?=/, color: '#e36209', dark: '#ffa657' },
|
|
639
|
-
{ token: 'property', pattern: /[a-z-]+(?=\s*:)/, color: '#005cc5', dark: '#79c0ff' },
|
|
640
|
-
{ token: 'selector', pattern: /[.#a-z0-9, \n\t>:+()_-]+(?=\s*\{)/i, color: '#6f42c1', dark: '#d2a8ff' },
|
|
641
|
-
{ token: 'unit', pattern: /(?<=\d)(px|em|rem|%|vh|vw|ms|s|deg)/, color: '#d73a49', dark: '#ff7b72' },
|
|
642
|
-
{ token: 'number', pattern: /\b\d+(\.\d+)?\b/, color: '#005cc5', dark: '#79c0ff' },
|
|
643
|
-
{ token: 'punct', pattern: /[{}();:]/, color: '#24292e', dark: '#c9d1d9' }
|
|
644
|
-
],
|
|
885
|
+
alias: 'CSS',
|
|
886
|
+
rules: css,
|
|
645
887
|
|
|
646
888
|
});
|
|
647
889
|
Coax.register('html', {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
{ token: 'doctype', pattern: /<!DOCTYPE[\s\S]*?>/i, color: '#005cc5', dark: '#56b6c2' },
|
|
651
|
-
// 匹配标签名: <div, </div
|
|
652
|
-
{ token: 'tag', pattern: /<\/?[a-zA-Z0-9]+/, color: '#22863a', dark: '#abb2bf' },
|
|
653
|
-
// 匹配属性名: class=
|
|
654
|
-
{ token: 'attr', pattern: /[a-zA-Z-]+(?=\s*=\s*)/, color: '#6f42c1', dark: '#e06c75' },
|
|
655
|
-
// 匹配属性值: "value"
|
|
656
|
-
{ token: 'string', pattern: /(['"])(?:\\.|[^\\])*?\1/, color: '#032f62', dark: '#f39c12' },
|
|
657
|
-
// 匹配标签闭合: >, />
|
|
658
|
-
{ token: 'bracket', pattern: /\/?>/, color: '#24292e', dark: '#f1f1f1' }
|
|
659
|
-
],
|
|
890
|
+
alias: 'HTML',
|
|
891
|
+
rules: html,
|
|
660
892
|
});
|
|
661
893
|
Coax.register('js', {
|
|
662
894
|
alias: 'Javascript',
|
|
663
|
-
rules:
|
|
664
|
-
{ token: 'comment', pattern: /\/\/[^\n]*|\/\*[\s\S]*?\*\//, color: '#6a737d', dark: '#8b949e' },
|
|
665
|
-
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, color: '#032f62', dark: '#61afef' },
|
|
666
|
-
{ 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' },
|
|
667
|
-
{ token: 'builtin', pattern: /\b(console|window|document|Math|JSON|true|false|null|undefined|Object|Array|Promise)\b/, color: '#56b6c2', dark: '#61afef' },
|
|
668
|
-
{ token: 'number', pattern: /\b\d+\b/, color: '#61afef', dark: '#d19a66' },
|
|
669
|
-
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, color: '#e5c07b', dark: '#98c379' },
|
|
670
|
-
{ token: 'op', pattern: /[+\-*/%=<>!&|^~]+/, color: '#d73a49', dark: '#e06c75' }
|
|
671
|
-
],
|
|
895
|
+
rules: javascript,
|
|
672
896
|
});
|
|
673
897
|
Coax.register('ts', {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
{ token: 'string', pattern: /(?:'|"|`)(?:\\.|[^\\'"\b])*?(?:'|"|`)/, color: '#032f62', dark: '#61afef' },
|
|
677
|
-
{ token: 'decorator', pattern: /@[a-zA-Z_]\w*/, color: '#d19a66', dark: '#e5c07b' },
|
|
678
|
-
{ 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' },
|
|
679
|
-
{ 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' },
|
|
680
|
-
{ token: 'type', pattern: /\b[A-Z]\w*\b/, color: '#22863a', dark: '#8b949e' },
|
|
681
|
-
{ token: 'number', pattern: /\b\d+\b/, color: '#61afef', dark: '#d19a66' },
|
|
682
|
-
{ token: 'func', pattern: /\b[a-zA-Z_]\w*(?=\s*\()/, color: '#e5c07b', dark: '#98c379' },
|
|
683
|
-
{ token: 'op', pattern: /(\?\.|![:\.]|[+\-*/%=<>!&|^~]+)/, color: '#d73a49', dark: '#e06c75' }
|
|
684
|
-
],
|
|
898
|
+
alias: 'Typescript',
|
|
899
|
+
rules: typescript,
|
|
685
900
|
});
|
|
686
|
-
Coax.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
navigator.clipboard.writeText(this.source)
|
|
693
|
-
.then(() => {
|
|
694
|
-
console.log('Text successfully copied to clipboard');
|
|
695
|
-
arg.iconEl.innerHTML = icaxCheck();
|
|
696
|
-
arg.iconEl.toggleAttribute('disabled', true);
|
|
697
|
-
setTimeout(() => {
|
|
698
|
-
//恢复
|
|
699
|
-
arg.iconEl.removeAttribute('disabled');
|
|
700
|
-
arg.iconEl.innerHTML = icaxCopy();
|
|
701
|
-
}, 2000);
|
|
702
|
-
})
|
|
703
|
-
.catch(err => {
|
|
704
|
-
console.error('Error copying text to clipboard: ', err);
|
|
705
|
-
});
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
}]);
|
|
901
|
+
Coax.register('md', {
|
|
902
|
+
alias: 'Markdown',
|
|
903
|
+
rules: markdown
|
|
904
|
+
});
|
|
905
|
+
//注册工具箱
|
|
906
|
+
Coax.addTools([copy]);
|
|
709
907
|
customElements.define(`${NAMESPACE}-code`, Coax);
|
|
710
908
|
|
|
909
|
+
return Coax;
|
|
910
|
+
|
|
711
911
|
}));
|