@kissmybutton/mc-code 0.0.1
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/.babelrc +14 -0
- package/.eslintignore +2 -0
- package/.eslintrc +69 -0
- package/README.md +121 -0
- package/dist/bundle.cjs.js +365 -0
- package/dist/bundle.esm.js +363 -0
- package/dist/bundle.umd.js +1 -0
- package/package.json +87 -0
- package/renovate.json +10 -0
- package/rollup.config.js +67 -0
- package/src/Incidents/CodeClip.js +237 -0
- package/src/Incidents/CodeHighlight.js +53 -0
- package/src/Incidents/WriteCode.js +44 -0
- package/src/index.js +22 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { BrowserClip, Effect } from '@donkeyclip/motorcortex';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CodeClip — a BrowserClip that renders syntax-highlighted code.
|
|
5
|
+
*
|
|
6
|
+
* Each line gets id: line_0, line_1, ... for targeting.
|
|
7
|
+
* Registers custom entity "code" for Effects via !#code.
|
|
8
|
+
*
|
|
9
|
+
* Attrs:
|
|
10
|
+
* code: string — the source code to display
|
|
11
|
+
* language: string (default: "javascript")
|
|
12
|
+
* theme: "dark" | "light" (default: "dark")
|
|
13
|
+
* fontSize: number (default: 14)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const THEMES = {
|
|
17
|
+
dark: {
|
|
18
|
+
bg: "#1e1e2e",
|
|
19
|
+
fg: "#cdd6f4",
|
|
20
|
+
lineNum: "#585b70",
|
|
21
|
+
keyword: "#cba6f7",
|
|
22
|
+
string: "#a6e3a1",
|
|
23
|
+
comment: "#6c7086",
|
|
24
|
+
fn: "#89b4fa",
|
|
25
|
+
number: "#fab387",
|
|
26
|
+
punct: "#94e2d5",
|
|
27
|
+
type: "#f9e2af"
|
|
28
|
+
},
|
|
29
|
+
light: {
|
|
30
|
+
bg: "#fafafa",
|
|
31
|
+
fg: "#383a42",
|
|
32
|
+
lineNum: "#9ca0b0",
|
|
33
|
+
keyword: "#a626a4",
|
|
34
|
+
string: "#50a14f",
|
|
35
|
+
comment: "#a0a1a7",
|
|
36
|
+
fn: "#4078f2",
|
|
37
|
+
number: "#986801",
|
|
38
|
+
punct: "#0184bc",
|
|
39
|
+
type: "#c18401"
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const LANG_KEYWORDS = {
|
|
43
|
+
javascript: new Set(["const", "let", "var", "function", "return", "if", "else", "for", "while", "do", "switch", "case", "break", "continue", "new", "this", "class", "extends", "import", "export", "default", "from", "of", "in", "typeof", "instanceof", "try", "catch", "finally", "throw", "async", "await", "yield", "null", "undefined", "true", "false"]),
|
|
44
|
+
typescript: new Set(["const", "let", "var", "function", "return", "if", "else", "for", "while", "do", "switch", "case", "break", "continue", "new", "this", "class", "extends", "import", "export", "default", "from", "of", "in", "typeof", "instanceof", "try", "catch", "finally", "throw", "async", "await", "yield", "null", "undefined", "true", "false", "type", "interface", "enum", "implements", "declare", "readonly", "as", "is", "keyof", "never", "unknown", "any", "void", "abstract", "override", "satisfies", "infer"]),
|
|
45
|
+
python: new Set(["def", "return", "if", "elif", "else", "for", "while", "in", "not", "and", "or", "is", "class", "import", "from", "as", "with", "try", "except", "finally", "raise", "pass", "break", "continue", "yield", "lambda", "None", "True", "False", "self", "print", "range", "len", "dict", "list", "set", "tuple", "int", "str", "float", "bool", "async", "await"]),
|
|
46
|
+
bash: new Set(["if", "then", "else", "elif", "fi", "for", "do", "done", "while", "until", "case", "esac", "function", "return", "in", "echo", "exit", "export", "source", "local", "readonly", "declare", "set", "unset", "shift", "cd", "pwd", "ls", "grep", "sed", "awk", "cat", "mkdir", "rm", "cp", "mv", "chmod", "chown", "sudo", "apt", "yum", "npm", "pip", "git", "docker", "kubectl"]),
|
|
47
|
+
html: new Set(["div", "span", "p", "a", "img", "ul", "ol", "li", "h1", "h2", "h3", "h4", "h5", "h6", "table", "tr", "td", "th", "form", "input", "button", "select", "option", "textarea", "label", "header", "footer", "nav", "main", "section", "article", "aside", "script", "style", "link", "meta", "head", "body", "html"]),
|
|
48
|
+
css: new Set(["color", "background", "border", "margin", "padding", "display", "position", "width", "height", "font", "text", "flex", "grid", "align", "justify", "overflow", "opacity", "transform", "transition", "animation", "z-index", "top", "left", "right", "bottom", "none", "auto", "inherit", "initial", "important"])
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// JSON/YAML don't have keywords in the traditional sense — handled by tokenizer rules
|
|
52
|
+
const COMMENT_STYLES = {
|
|
53
|
+
javascript: "//",
|
|
54
|
+
typescript: "//",
|
|
55
|
+
python: "#",
|
|
56
|
+
bash: "#",
|
|
57
|
+
yaml: "#",
|
|
58
|
+
css: null,
|
|
59
|
+
json: null,
|
|
60
|
+
html: null
|
|
61
|
+
};
|
|
62
|
+
function escapeHtml(s) {
|
|
63
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
64
|
+
}
|
|
65
|
+
function tokenizeLine(line, theme, language) {
|
|
66
|
+
const t = theme;
|
|
67
|
+
const keywords = LANG_KEYWORDS[language] || LANG_KEYWORDS.javascript;
|
|
68
|
+
const commentChar = COMMENT_STYLES[language] ?? "//";
|
|
69
|
+
let result = "";
|
|
70
|
+
let i = 0;
|
|
71
|
+
|
|
72
|
+
// YAML/JSON: color keys specially
|
|
73
|
+
if (language === "yaml") {
|
|
74
|
+
// YAML line: "key: value" or "- item"
|
|
75
|
+
const yamlKeyMatch = line.match(/^(\s*)([\w.\-/]+)(\s*:\s*)(.*)/);
|
|
76
|
+
if (yamlKeyMatch) {
|
|
77
|
+
const [, indent, key, colon, value] = yamlKeyMatch;
|
|
78
|
+
result += escapeHtml(indent);
|
|
79
|
+
result += `<span style="color:${t.fn}">${escapeHtml(key)}</span>`;
|
|
80
|
+
result += `<span style="color:${t.punct}">${escapeHtml(colon)}</span>`;
|
|
81
|
+
// Value coloring
|
|
82
|
+
if (/^(true|false|null|~)$/i.test(value.trim())) {
|
|
83
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(value)}</span>`;
|
|
84
|
+
} else if (/^\d+(\.\d+)?$/.test(value.trim())) {
|
|
85
|
+
result += `<span style="color:${t.number}">${escapeHtml(value)}</span>`;
|
|
86
|
+
} else if (/^["']/.test(value.trim())) {
|
|
87
|
+
result += `<span style="color:${t.string}">${escapeHtml(value)}</span>`;
|
|
88
|
+
} else {
|
|
89
|
+
result += `<span style="color:${t.fg}">${escapeHtml(value)}</span>`;
|
|
90
|
+
}
|
|
91
|
+
return result || " ";
|
|
92
|
+
}
|
|
93
|
+
const yamlDash = line.match(/^(\s*)(- )(.*)/);
|
|
94
|
+
if (yamlDash) {
|
|
95
|
+
result += escapeHtml(yamlDash[1]);
|
|
96
|
+
result += `<span style="color:${t.punct}">${escapeHtml(yamlDash[2])}</span>`;
|
|
97
|
+
result += `<span style="color:${t.fg}">${escapeHtml(yamlDash[3])}</span>`;
|
|
98
|
+
return result || " ";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (language === "json") {
|
|
102
|
+
// JSON: color keys in quotes before colon
|
|
103
|
+
const jsonKeyMatch = line.match(/^(\s*)("(?:[^"\\]|\\.)*")(\s*:\s*)(.*)/);
|
|
104
|
+
if (jsonKeyMatch) {
|
|
105
|
+
const [, indent, key, colon, value] = jsonKeyMatch;
|
|
106
|
+
result += escapeHtml(indent);
|
|
107
|
+
result += `<span style="color:${t.fn}">${escapeHtml(key)}</span>`;
|
|
108
|
+
result += `<span style="color:${t.punct}">${escapeHtml(colon)}</span>`;
|
|
109
|
+
if (/^(true|false|null)/.test(value.trim())) {
|
|
110
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(value)}</span>`;
|
|
111
|
+
} else if (/^\d/.test(value.trim())) {
|
|
112
|
+
result += `<span style="color:${t.number}">${escapeHtml(value)}</span>`;
|
|
113
|
+
} else if (/^"/.test(value.trim())) {
|
|
114
|
+
result += `<span style="color:${t.string}">${escapeHtml(value)}</span>`;
|
|
115
|
+
} else {
|
|
116
|
+
result += `<span style="color:${t.fg}">${escapeHtml(value)}</span>`;
|
|
117
|
+
}
|
|
118
|
+
return result || " ";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
while (i < line.length) {
|
|
122
|
+
// Comments: // or # to end of line
|
|
123
|
+
if (commentChar && line.startsWith(commentChar, i) && (i === 0 || /\s/.test(line[i - 1]) || i === 0)) {
|
|
124
|
+
result += `<span style="color:${t.comment};font-style:italic">${escapeHtml(line.slice(i))}</span>`;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Strings: single or double quoted
|
|
129
|
+
if (line[i] === '"' || line[i] === "'" || line[i] === "`") {
|
|
130
|
+
const q = line[i];
|
|
131
|
+
let j = i + 1;
|
|
132
|
+
while (j < line.length && line[j] !== q) {
|
|
133
|
+
if (line[j] === "\\") j++;
|
|
134
|
+
j++;
|
|
135
|
+
}
|
|
136
|
+
j = Math.min(j + 1, line.length);
|
|
137
|
+
result += `<span style="color:${t.string}">${escapeHtml(line.slice(i, j))}</span>`;
|
|
138
|
+
i = j;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Numbers
|
|
143
|
+
if (/\d/.test(line[i]) && (i === 0 || /[\s,;(=+\-*/<>!&|]/.test(line[i - 1]))) {
|
|
144
|
+
let j = i;
|
|
145
|
+
while (j < line.length && /[\d.]/.test(line[j])) j++;
|
|
146
|
+
result += `<span style="color:${t.number}">${escapeHtml(line.slice(i, j))}</span>`;
|
|
147
|
+
i = j;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Words (keywords, identifiers)
|
|
152
|
+
if (/[a-zA-Z_$]/.test(line[i])) {
|
|
153
|
+
let j = i;
|
|
154
|
+
while (j < line.length && /[a-zA-Z0-9_$]/.test(line[j])) j++;
|
|
155
|
+
const word = line.slice(i, j);
|
|
156
|
+
if (keywords.has(word)) {
|
|
157
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(word)}</span>`;
|
|
158
|
+
} else if (j < line.length && line[j] === "(") {
|
|
159
|
+
result += `<span style="color:${t.fn}">${escapeHtml(word)}</span>`;
|
|
160
|
+
} else {
|
|
161
|
+
result += `<span style="color:${t.fg}">${escapeHtml(word)}</span>`;
|
|
162
|
+
}
|
|
163
|
+
i = j;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Punctuation / operators
|
|
168
|
+
if (/[{}()\[\];:.,=+\-*/<>!&|?%^~@]/.test(line[i])) {
|
|
169
|
+
result += `<span style="color:${t.punct}">${escapeHtml(line[i])}</span>`;
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Whitespace and anything else
|
|
175
|
+
result += escapeHtml(line[i]);
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
return result || " ";
|
|
179
|
+
}
|
|
180
|
+
function renderLines(code, theme, language) {
|
|
181
|
+
const lines = code.split("\n");
|
|
182
|
+
return lines.map((line, i) => `<div class="shiki-line" id="line_${i}"><span class="line-num">${i + 1}</span><span class="line-code">${tokenizeLine(line, theme, language)}</span></div>`).join("");
|
|
183
|
+
}
|
|
184
|
+
class CodeClip extends BrowserClip {
|
|
185
|
+
get html() {
|
|
186
|
+
const code = this.attrs.code || "";
|
|
187
|
+
const language = this.attrs.language || "javascript";
|
|
188
|
+
const t = THEMES[this.attrs.theme] || THEMES.dark;
|
|
189
|
+
const linesHtml = renderLines(code, t, language);
|
|
190
|
+
return `<div id="code-container"><pre><code>${linesHtml}</code></pre></div>`;
|
|
191
|
+
}
|
|
192
|
+
get css() {
|
|
193
|
+
const fs = this.attrs.fontSize || 14;
|
|
194
|
+
const t = THEMES[this.attrs.theme] || THEMES.dark;
|
|
195
|
+
return `
|
|
196
|
+
#code-container {
|
|
197
|
+
width: 100%;
|
|
198
|
+
height: 100%;
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
padding: 24px;
|
|
203
|
+
box-sizing: border-box;
|
|
204
|
+
background: ${t.bg};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
pre {
|
|
208
|
+
margin: 0;
|
|
209
|
+
padding: 20px 24px;
|
|
210
|
+
border-radius: 10px;
|
|
211
|
+
background: ${t.bg};
|
|
212
|
+
font-size: ${fs}px;
|
|
213
|
+
line-height: 1.7;
|
|
214
|
+
overflow-x: auto;
|
|
215
|
+
width: 100%;
|
|
216
|
+
max-height: 100%;
|
|
217
|
+
box-sizing: border-box;
|
|
218
|
+
font-family: "Fira Code", "Cascadia Code", "JetBrains Mono", "Consolas", monospace;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
code {
|
|
222
|
+
color: ${t.fg};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.shiki-line {
|
|
226
|
+
display: flex;
|
|
227
|
+
padding: 1px 8px;
|
|
228
|
+
border-radius: 4px;
|
|
229
|
+
gap: 16px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.line-num {
|
|
233
|
+
color: ${t.lineNum};
|
|
234
|
+
user-select: none;
|
|
235
|
+
min-width: 2em;
|
|
236
|
+
text-align: right;
|
|
237
|
+
flex-shrink: 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.line-code {
|
|
241
|
+
white-space: pre;
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
onAfterRender() {
|
|
246
|
+
this.attrs.code || "";
|
|
247
|
+
const container = this.context.getElements("#code-container")[0];
|
|
248
|
+
const lines = container.querySelectorAll(".shiki-line");
|
|
249
|
+
this.setCustomEntity("code", {
|
|
250
|
+
container,
|
|
251
|
+
lines,
|
|
252
|
+
lineCount: lines.length
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* CodeHighlight — Effect that pulses a line's background color.
|
|
259
|
+
*
|
|
260
|
+
* Selector: !#code
|
|
261
|
+
* animatedAttrs: { highlightLine_N: 1 } — where N is the line index (0-based)
|
|
262
|
+
* The line index is parsed from the attr key.
|
|
263
|
+
*
|
|
264
|
+
* Attrs:
|
|
265
|
+
* color: string (default: "rgba(99,102,241,0.25)") — highlight background
|
|
266
|
+
*/
|
|
267
|
+
class CodeHighlight extends Effect {
|
|
268
|
+
getScratchValue() {
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
onGetContext() {
|
|
272
|
+
this._lines = this.element?.entity?.lines;
|
|
273
|
+
this._color = this.attrs.color || "rgba(99,102,241,0.25)";
|
|
274
|
+
// Parse line index from attributeKey: "highlightLine_4" → 4
|
|
275
|
+
const key = this.attributeKey || "";
|
|
276
|
+
this._lineIndex = parseInt(key.split("_").pop(), 10);
|
|
277
|
+
}
|
|
278
|
+
onProgress(millisecond) {
|
|
279
|
+
if (!this._lines || isNaN(this._lineIndex) || this._lineIndex < 0 || this._lineIndex >= this._lines.length) return;
|
|
280
|
+
const fraction = this.getFraction(millisecond);
|
|
281
|
+
const line = this._lines[this._lineIndex];
|
|
282
|
+
|
|
283
|
+
// Double pump: 0→0.25 up, 0.25→0.5 down, 0.5→0.75 up, 0.75→1 down
|
|
284
|
+
let intensity;
|
|
285
|
+
if (fraction < 0.25) {
|
|
286
|
+
intensity = fraction / 0.25;
|
|
287
|
+
} else if (fraction < 0.5) {
|
|
288
|
+
intensity = 1 - (fraction - 0.25) / 0.25;
|
|
289
|
+
} else if (fraction < 0.75) {
|
|
290
|
+
intensity = (fraction - 0.5) / 0.25;
|
|
291
|
+
} else {
|
|
292
|
+
intensity = 1 - (fraction - 0.75) / 0.25;
|
|
293
|
+
}
|
|
294
|
+
if (intensity > 0.01) {
|
|
295
|
+
const alpha = intensity * 0.35;
|
|
296
|
+
const baseRgb = this._color.match(/\d+/g);
|
|
297
|
+
if (baseRgb && baseRgb.length >= 3) {
|
|
298
|
+
line.style.backgroundColor = `rgba(${baseRgb[0]},${baseRgb[1]},${baseRgb[2]},${alpha})`;
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
line.style.backgroundColor = "";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* WriteCode — Effect that progressively reveals code lines.
|
|
308
|
+
*
|
|
309
|
+
* Selector: !#code
|
|
310
|
+
* animatedAttrs: { writeCode: 1 } — fraction 0→1 reveals all lines
|
|
311
|
+
*/
|
|
312
|
+
class WriteCode extends Effect {
|
|
313
|
+
getScratchValue() {
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
onGetContext() {
|
|
317
|
+
this._lines = this.element?.entity?.lines;
|
|
318
|
+
if (this._lines) {
|
|
319
|
+
// Hide all lines initially
|
|
320
|
+
for (const line of this._lines) {
|
|
321
|
+
line.style.opacity = "0";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
onProgress(millisecond) {
|
|
326
|
+
if (!this._lines) return;
|
|
327
|
+
const fraction = this.getFraction(millisecond);
|
|
328
|
+
const initial = this.initialValue ?? 0;
|
|
329
|
+
const target = this.targetValue;
|
|
330
|
+
const current = initial + (target - initial) * fraction;
|
|
331
|
+
const total = this._lines.length;
|
|
332
|
+
const revealUpTo = current * total;
|
|
333
|
+
for (let i = 0; i < total; i++) {
|
|
334
|
+
if (i < Math.floor(revealUpTo)) {
|
|
335
|
+
this._lines[i].style.opacity = "1";
|
|
336
|
+
} else if (i < revealUpTo) {
|
|
337
|
+
this._lines[i].style.opacity = String(revealUpTo - i);
|
|
338
|
+
} else {
|
|
339
|
+
this._lines[i].style.opacity = "0";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
var name = "@kissmybutton/mc-code";
|
|
346
|
+
var version = "0.0.1";
|
|
347
|
+
|
|
348
|
+
var index = {
|
|
349
|
+
npm_name: name,
|
|
350
|
+
version: version,
|
|
351
|
+
incidents: [{
|
|
352
|
+
exportable: CodeHighlight,
|
|
353
|
+
name: "CodeHighlight"
|
|
354
|
+
}, {
|
|
355
|
+
exportable: WriteCode,
|
|
356
|
+
name: "WriteCode"
|
|
357
|
+
}],
|
|
358
|
+
Clip: {
|
|
359
|
+
exportable: CodeClip
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export { index as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@donkeyclip/motorcortex")):"function"==typeof define&&define.amd?define(["@donkeyclip/motorcortex"],t):(e="undefined"!=typeof globalThis?globalThis:e||self)["@kissmybutton/mc-code"]=t(e.MotorCortex)}(this,(function(e){"use strict";const t={dark:{bg:"#1e1e2e",fg:"#cdd6f4",lineNum:"#585b70",keyword:"#cba6f7",string:"#a6e3a1",comment:"#6c7086",fn:"#89b4fa",number:"#fab387",punct:"#94e2d5",type:"#f9e2af"},light:{bg:"#fafafa",fg:"#383a42",lineNum:"#9ca0b0",keyword:"#a626a4",string:"#50a14f",comment:"#a0a1a7",fn:"#4078f2",number:"#986801",punct:"#0184bc",type:"#c18401"}},n={javascript:new Set(["const","let","var","function","return","if","else","for","while","do","switch","case","break","continue","new","this","class","extends","import","export","default","from","of","in","typeof","instanceof","try","catch","finally","throw","async","await","yield","null","undefined","true","false"]),typescript:new Set(["const","let","var","function","return","if","else","for","while","do","switch","case","break","continue","new","this","class","extends","import","export","default","from","of","in","typeof","instanceof","try","catch","finally","throw","async","await","yield","null","undefined","true","false","type","interface","enum","implements","declare","readonly","as","is","keyof","never","unknown","any","void","abstract","override","satisfies","infer"]),python:new Set(["def","return","if","elif","else","for","while","in","not","and","or","is","class","import","from","as","with","try","except","finally","raise","pass","break","continue","yield","lambda","None","True","False","self","print","range","len","dict","list","set","tuple","int","str","float","bool","async","await"]),bash:new Set(["if","then","else","elif","fi","for","do","done","while","until","case","esac","function","return","in","echo","exit","export","source","local","readonly","declare","set","unset","shift","cd","pwd","ls","grep","sed","awk","cat","mkdir","rm","cp","mv","chmod","chown","sudo","apt","yum","npm","pip","git","docker","kubectl"]),html:new Set(["div","span","p","a","img","ul","ol","li","h1","h2","h3","h4","h5","h6","table","tr","td","th","form","input","button","select","option","textarea","label","header","footer","nav","main","section","article","aside","script","style","link","meta","head","body","html"]),css:new Set(["color","background","border","margin","padding","display","position","width","height","font","text","flex","grid","align","justify","overflow","opacity","transform","transition","animation","z-index","top","left","right","bottom","none","auto","inherit","initial","important"])},s={javascript:"//",typescript:"//",python:"#",bash:"#",yaml:"#",css:null,json:null,html:null};function i(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function o(e,t,o){return e.split("\n").map(((e,a)=>`<div class="shiki-line" id="line_${a}"><span class="line-num">${a+1}</span><span class="line-code">${function(e,t,o){const a=t,l=n[o]||n.javascript,r=s[o]??"//";let c="",p=0;if("yaml"===o){const t=e.match(/^(\s*)([\w.\-/]+)(\s*:\s*)(.*)/);if(t){const[,e,n,s,o]=t;return c+=i(e),c+=`<span style="color:${a.fn}">${i(n)}</span>`,c+=`<span style="color:${a.punct}">${i(s)}</span>`,/^(true|false|null|~)$/i.test(o.trim())?c+=`<span style="color:${a.keyword};font-weight:600">${i(o)}</span>`:/^\d+(\.\d+)?$/.test(o.trim())?c+=`<span style="color:${a.number}">${i(o)}</span>`:/^["']/.test(o.trim())?c+=`<span style="color:${a.string}">${i(o)}</span>`:c+=`<span style="color:${a.fg}">${i(o)}</span>`,c||" "}const n=e.match(/^(\s*)(- )(.*)/);if(n)return c+=i(n[1]),c+=`<span style="color:${a.punct}">${i(n[2])}</span>`,c+=`<span style="color:${a.fg}">${i(n[3])}</span>`,c||" "}if("json"===o){const t=e.match(/^(\s*)("(?:[^"\\]|\\.)*")(\s*:\s*)(.*)/);if(t){const[,e,n,s,o]=t;return c+=i(e),c+=`<span style="color:${a.fn}">${i(n)}</span>`,c+=`<span style="color:${a.punct}">${i(s)}</span>`,/^(true|false|null)/.test(o.trim())?c+=`<span style="color:${a.keyword};font-weight:600">${i(o)}</span>`:/^\d/.test(o.trim())?c+=`<span style="color:${a.number}">${i(o)}</span>`:/^"/.test(o.trim())?c+=`<span style="color:${a.string}">${i(o)}</span>`:c+=`<span style="color:${a.fg}">${i(o)}</span>`,c||" "}}for(;p<e.length;){if(r&&e.startsWith(r,p)&&(0===p||/\s/.test(e[p-1])||0===p)){c+=`<span style="color:${a.comment};font-style:italic">${i(e.slice(p))}</span>`;break}if('"'!==e[p]&&"'"!==e[p]&&"`"!==e[p])if(!/\d/.test(e[p])||0!==p&&!/[\s,;(=+\-*/<>!&|]/.test(e[p-1]))if(/[a-zA-Z_$]/.test(e[p])){let t=p;for(;t<e.length&&/[a-zA-Z0-9_$]/.test(e[t]);)t++;const n=e.slice(p,t);l.has(n)?c+=`<span style="color:${a.keyword};font-weight:600">${i(n)}</span>`:t<e.length&&"("===e[t]?c+=`<span style="color:${a.fn}">${i(n)}</span>`:c+=`<span style="color:${a.fg}">${i(n)}</span>`,p=t}else/[{}()\[\];:.,=+\-*/<>!&|?%^~@]/.test(e[p])?(c+=`<span style="color:${a.punct}">${i(e[p])}</span>`,p++):(c+=i(e[p]),p++);else{let t=p;for(;t<e.length&&/[\d.]/.test(e[t]);)t++;c+=`<span style="color:${a.number}">${i(e.slice(p,t))}</span>`,p=t}else{const t=e[p];let n=p+1;for(;n<e.length&&e[n]!==t;)"\\"===e[n]&&n++,n++;n=Math.min(n+1,e.length),c+=`<span style="color:${a.string}">${i(e.slice(p,n))}</span>`,p=n}}return c||" "}(e,t,o)}</span></div>`)).join("")}class a extends e.BrowserClip{get html(){const e=this.attrs.code||"",n=this.attrs.language||"javascript";return`<div id="code-container"><pre><code>${o(e,t[this.attrs.theme]||t.dark,n)}</code></pre></div>`}get css(){const e=this.attrs.fontSize||14,n=t[this.attrs.theme]||t.dark;return`\n #code-container {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n box-sizing: border-box;\n background: ${n.bg};\n }\n\n pre {\n margin: 0;\n padding: 20px 24px;\n border-radius: 10px;\n background: ${n.bg};\n font-size: ${e}px;\n line-height: 1.7;\n overflow-x: auto;\n width: 100%;\n max-height: 100%;\n box-sizing: border-box;\n font-family: "Fira Code", "Cascadia Code", "JetBrains Mono", "Consolas", monospace;\n }\n\n code {\n color: ${n.fg};\n }\n\n .shiki-line {\n display: flex;\n padding: 1px 8px;\n border-radius: 4px;\n gap: 16px;\n }\n\n .line-num {\n color: ${n.lineNum};\n user-select: none;\n min-width: 2em;\n text-align: right;\n flex-shrink: 0;\n }\n\n .line-code {\n white-space: pre;\n }\n `}onAfterRender(){this.attrs.code;const e=this.context.getElements("#code-container")[0],t=e.querySelectorAll(".shiki-line");this.setCustomEntity("code",{container:e,lines:t,lineCount:t.length})}}class l extends e.Effect{getScratchValue(){return 0}onGetContext(){this._lines=this.element?.entity?.lines,this._color=this.attrs.color||"rgba(99,102,241,0.25)";const e=this.attributeKey||"";this._lineIndex=parseInt(e.split("_").pop(),10)}onProgress(e){if(!this._lines||isNaN(this._lineIndex)||this._lineIndex<0||this._lineIndex>=this._lines.length)return;const t=this.getFraction(e),n=this._lines[this._lineIndex];let s;if(s=t<.25?t/.25:t<.5?1-(t-.25)/.25:t<.75?(t-.5)/.25:1-(t-.75)/.25,s>.01){const e=.35*s,t=this._color.match(/\d+/g);t&&t.length>=3&&(n.style.backgroundColor=`rgba(${t[0]},${t[1]},${t[2]},${e})`)}else n.style.backgroundColor=""}}class r extends e.Effect{getScratchValue(){return 0}onGetContext(){if(this._lines=this.element?.entity?.lines,this._lines)for(const e of this._lines)e.style.opacity="0"}onProgress(e){if(!this._lines)return;const t=this.getFraction(e),n=this.initialValue??0,s=n+(this.targetValue-n)*t,i=this._lines.length,o=s*i;for(let e=0;e<i;e++)e<Math.floor(o)?this._lines[e].style.opacity="1":this._lines[e].style.opacity=e<o?String(o-e):"0"}}return{npm_name:"@kissmybutton/mc-code",version:"0.0.1",incidents:[{exportable:l,name:"CodeHighlight"},{exportable:r,name:"WriteCode"}],Clip:{exportable:a}}}));
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kissmybutton/mc-code",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "MotorCortex code display plugin — Shiki syntax highlighting with line-level animation",
|
|
5
|
+
"main": "dist/bundle.cjs.js",
|
|
6
|
+
"module": "dist/bundle.esm.js",
|
|
7
|
+
"browser": "dist/bundle.umd.js",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"author": "Donkeyclip",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/kissmybutton/mc-code.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=16"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"lint": "eslint -c .eslintrc src/**/*.js",
|
|
20
|
+
"lint:fix": "npm run lint -- --fix",
|
|
21
|
+
"build": "npm run build:lib && npm run build:demo",
|
|
22
|
+
"build:lib": "rollup -c",
|
|
23
|
+
"start": "npm run build:lib && concurrently -c \"cyan.bold,magenta.bold\" \"npm:build:lib -- -w\" \"npm:start:demo\" ",
|
|
24
|
+
"start:demo": "webpack serve --mode=development --config ./demo/webpack.config.cjs",
|
|
25
|
+
"build:demo": "webpack --mode=production --config ./demo/webpack.config.cjs",
|
|
26
|
+
"test": "echo \"No tests yet\"",
|
|
27
|
+
"test:prod": "npm run lint",
|
|
28
|
+
"prepare": "husky install"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"motorcortex",
|
|
32
|
+
"code",
|
|
33
|
+
"shiki",
|
|
34
|
+
"syntax-highlighting"
|
|
35
|
+
],
|
|
36
|
+
"lint-staged": {
|
|
37
|
+
"*.{json,md,yml,yaml,css}": [
|
|
38
|
+
"prettier --write"
|
|
39
|
+
],
|
|
40
|
+
"*.{js,jsx}": [
|
|
41
|
+
"prettier --write",
|
|
42
|
+
"eslint --fix"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@donkeyclip/motorcortex": ">=9.24.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@babel/cli": "^7.23.4",
|
|
50
|
+
"@babel/core": "^7.23.3",
|
|
51
|
+
"@babel/eslint-parser": "^7.23.3",
|
|
52
|
+
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
|
53
|
+
"@babel/plugin-syntax-jsx": "^7.23.3",
|
|
54
|
+
"@babel/plugin-transform-react-jsx": "^7.22.15",
|
|
55
|
+
"@babel/preset-env": "^7.23.3",
|
|
56
|
+
"@babel/preset-react": "^7.23.3",
|
|
57
|
+
"@donkeyclip/motorcortex": "^9.24.0",
|
|
58
|
+
"@donkeyclip/motorcortex-player": "^2.15.18",
|
|
59
|
+
"@rollup/plugin-babel": "6.0.4",
|
|
60
|
+
"@rollup/plugin-commonjs": "25.0.8",
|
|
61
|
+
"@rollup/plugin-json": "6.1.0",
|
|
62
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
63
|
+
"babel-loader": "9.2.1",
|
|
64
|
+
"concurrently": "^9.0.0",
|
|
65
|
+
"core-js": "^3.33.3",
|
|
66
|
+
"css-loader": "6.11.0",
|
|
67
|
+
"eslint": "^8.54.0",
|
|
68
|
+
"eslint-config-prettier": "9.1.0",
|
|
69
|
+
"eslint-plugin-babel": "5.3.1",
|
|
70
|
+
"eslint-plugin-import": "^2.29.0",
|
|
71
|
+
"eslint-plugin-node": "11.1.0",
|
|
72
|
+
"eslint-plugin-prettier": "5.2.1",
|
|
73
|
+
"eslint-plugin-promise": "6.6.0",
|
|
74
|
+
"eslint-plugin-react": "^7.33.2",
|
|
75
|
+
"husky": "8.0.3",
|
|
76
|
+
"prettier": "3.4.2",
|
|
77
|
+
"rimraf": "5.0.10",
|
|
78
|
+
"rollup": "4.28.1",
|
|
79
|
+
"webpack": "^5.89.0",
|
|
80
|
+
"webpack-cli": "5.1.4",
|
|
81
|
+
"webpack-dev-server": "4.15.2"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
85
|
+
"shiki": "^3.0.0"
|
|
86
|
+
}
|
|
87
|
+
}
|
package/renovate.json
ADDED
package/rollup.config.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import babel from "@rollup/plugin-babel";
|
|
2
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
3
|
+
import json from "@rollup/plugin-json";
|
|
4
|
+
import resolve from "@rollup/plugin-node-resolve";
|
|
5
|
+
import terser from "@rollup/plugin-terser";
|
|
6
|
+
import pkg from "./package.json" with { type: "json" };
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
{
|
|
10
|
+
input: "src/index.js",
|
|
11
|
+
external: ["@donkeyclip/motorcortex"],
|
|
12
|
+
output: [
|
|
13
|
+
{ file: pkg.main, format: "cjs", inlineDynamicImports: true },
|
|
14
|
+
{ file: pkg.module, format: "es", inlineDynamicImports: true },
|
|
15
|
+
],
|
|
16
|
+
plugins: [
|
|
17
|
+
resolve(),
|
|
18
|
+
babel({
|
|
19
|
+
plugins: [
|
|
20
|
+
"@babel/plugin-syntax-jsx",
|
|
21
|
+
[
|
|
22
|
+
"@babel/plugin-transform-react-jsx",
|
|
23
|
+
{
|
|
24
|
+
pragma: "JSX",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
],
|
|
28
|
+
extensions: [".js", ".jsx"],
|
|
29
|
+
}),
|
|
30
|
+
commonjs(),
|
|
31
|
+
json(),
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
input: "src/index.js",
|
|
36
|
+
external: ["@donkeyclip/motorcortex"],
|
|
37
|
+
output: [
|
|
38
|
+
{
|
|
39
|
+
globals: {
|
|
40
|
+
"@donkeyclip/motorcortex": "MotorCortex",
|
|
41
|
+
},
|
|
42
|
+
name: pkg.name,
|
|
43
|
+
file: pkg.browser,
|
|
44
|
+
format: "umd",
|
|
45
|
+
inlineDynamicImports: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
plugins: [
|
|
49
|
+
json(),
|
|
50
|
+
resolve({ mainFields: ["module", "main", "browser"] }),
|
|
51
|
+
babel({
|
|
52
|
+
plugins: [
|
|
53
|
+
"@babel/plugin-syntax-jsx",
|
|
54
|
+
[
|
|
55
|
+
"@babel/plugin-transform-react-jsx",
|
|
56
|
+
{
|
|
57
|
+
pragma: "JSX",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
],
|
|
61
|
+
extensions: [".js", ".jsx"],
|
|
62
|
+
}),
|
|
63
|
+
commonjs(),
|
|
64
|
+
terser(),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|