@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
package/.babelrc
ADDED
package/.eslintignore
ADDED
package/.eslintrc
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"parser": "@babel/eslint-parser",
|
|
3
|
+
"parserOptions": {
|
|
4
|
+
"babelOptions": {
|
|
5
|
+
"presets": ["@babel/preset-react"]
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
"extends": [
|
|
9
|
+
"eslint:recommended",
|
|
10
|
+
"plugin:react/recommended",
|
|
11
|
+
"prettier"
|
|
12
|
+
],
|
|
13
|
+
"plugins": [
|
|
14
|
+
"babel",
|
|
15
|
+
"prettier",
|
|
16
|
+
"react"
|
|
17
|
+
],
|
|
18
|
+
"env": {
|
|
19
|
+
"browser": true,
|
|
20
|
+
"node": true
|
|
21
|
+
},
|
|
22
|
+
"globals": {
|
|
23
|
+
"document": false,
|
|
24
|
+
"escape": false,
|
|
25
|
+
"navigator": false,
|
|
26
|
+
"unescape": false,
|
|
27
|
+
"window": false,
|
|
28
|
+
"describe": true,
|
|
29
|
+
"before": true,
|
|
30
|
+
"it": true,
|
|
31
|
+
"expect": true,
|
|
32
|
+
"sinon": true
|
|
33
|
+
},
|
|
34
|
+
"rules": {
|
|
35
|
+
"prettier/prettier": [
|
|
36
|
+
"error"
|
|
37
|
+
],
|
|
38
|
+
"linebreak-style": [
|
|
39
|
+
"error",
|
|
40
|
+
"unix"
|
|
41
|
+
],
|
|
42
|
+
"semi": [
|
|
43
|
+
"error",
|
|
44
|
+
"always"
|
|
45
|
+
],
|
|
46
|
+
"no-console": [
|
|
47
|
+
"error",
|
|
48
|
+
{
|
|
49
|
+
"allow": [
|
|
50
|
+
"warn",
|
|
51
|
+
"error",
|
|
52
|
+
"info"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"prefer-promise-reject-errors": "error",
|
|
57
|
+
"prefer-const": [
|
|
58
|
+
"error",
|
|
59
|
+
{
|
|
60
|
+
"destructuring": "any",
|
|
61
|
+
"ignoreReadBeforeAssign": false
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"no-var": "error",
|
|
65
|
+
"no-unused-vars": "error",
|
|
66
|
+
"react/react-in-jsx-scope": "off",
|
|
67
|
+
"react/jsx-uses-react": "off"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @kissmybutton/mc-code
|
|
2
|
+
|
|
3
|
+
MotorCortex plugin for displaying syntax-highlighted code with line-level animation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @kissmybutton/mc-code
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependency: `@donkeyclip/motorcortex >= 9.24.0`
|
|
12
|
+
|
|
13
|
+
## Supported languages
|
|
14
|
+
|
|
15
|
+
JavaScript, TypeScript, Python, Bash, JSON, YAML, HTML, CSS. Each with language-specific keyword coloring, comment styles, and structural highlighting.
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { HTMLClip, loadPlugin } from "@donkeyclip/motorcortex";
|
|
21
|
+
import Player from "@donkeyclip/motorcortex-player";
|
|
22
|
+
import codeDef from "@kissmybutton/mc-code";
|
|
23
|
+
|
|
24
|
+
const McCode = loadPlugin(codeDef);
|
|
25
|
+
|
|
26
|
+
const clip = new HTMLClip({
|
|
27
|
+
html: '<div id="scene" style="width:100%;height:100%;"></div>',
|
|
28
|
+
host: document.getElementById("clip"),
|
|
29
|
+
containerParams: { width: "900px", height: "600px" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const codeClip = new McCode.Clip(
|
|
33
|
+
{
|
|
34
|
+
code: 'function hello() {\n return "world";\n}',
|
|
35
|
+
language: "javascript",
|
|
36
|
+
theme: "dark",
|
|
37
|
+
fontSize: 15,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
selector: "#scene",
|
|
41
|
+
containerParams: { width: "900px", height: "600px" },
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Progressive write-in
|
|
46
|
+
codeClip.addIncident(
|
|
47
|
+
new McCode.WriteCode(
|
|
48
|
+
{ animatedAttrs: { writeCode: 1 } },
|
|
49
|
+
{ selector: "!#code", duration: 2000 },
|
|
50
|
+
),
|
|
51
|
+
500,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Highlight line 1
|
|
55
|
+
codeClip.addIncident(
|
|
56
|
+
new McCode.CodeHighlight(
|
|
57
|
+
{ animatedAttrs: { highlightLine_1: 1 }, color: "rgba(99,102,241,0.35)" },
|
|
58
|
+
{ selector: "!#code", duration: 800 },
|
|
59
|
+
),
|
|
60
|
+
3000,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
clip.addIncident(codeClip, 0);
|
|
64
|
+
new Player({ clip });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Clip attrs
|
|
68
|
+
|
|
69
|
+
| Attr | Type | Default | Description |
|
|
70
|
+
| ---------- | ------ | -------------- | ------------------------------------ |
|
|
71
|
+
| `code` | string | `""` | The source code to display |
|
|
72
|
+
| `language` | string | `"javascript"` | Language for syntax coloring |
|
|
73
|
+
| `theme` | string | `"dark"` | Color theme: `"dark"` or `"light"` |
|
|
74
|
+
| `fontSize` | number | `14` | Font size in pixels |
|
|
75
|
+
|
|
76
|
+
## Incidents
|
|
77
|
+
|
|
78
|
+
### WriteCode (Effect)
|
|
79
|
+
|
|
80
|
+
Progressively reveals code lines with a typewriter-style animation.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
new McCode.WriteCode(
|
|
84
|
+
{ animatedAttrs: { writeCode: 1 } }, // 0 = hidden, 1 = fully visible
|
|
85
|
+
{ selector: "!#code", duration: 3000 },
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CodeHighlight (Effect)
|
|
90
|
+
|
|
91
|
+
Pulses a line's background color with a double-pump effect. Each line needs a unique attr key `highlightLine_N` where N is the 0-based line index.
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
// Highlight line 5 (green)
|
|
95
|
+
new McCode.CodeHighlight(
|
|
96
|
+
{ animatedAttrs: { highlightLine_5: 1 }, color: "rgba(46,160,67,0.3)" },
|
|
97
|
+
{ selector: "!#code", duration: 800 },
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Multiple lines don't conflict because each has a unique key
|
|
101
|
+
new McCode.CodeHighlight(
|
|
102
|
+
{ animatedAttrs: { highlightLine_8: 1 }, color: "rgba(231,111,81,0.3)" },
|
|
103
|
+
{ selector: "!#code", duration: 800 },
|
|
104
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Line IDs
|
|
108
|
+
|
|
109
|
+
Each line in the rendered code gets an id: `line_0`, `line_1`, `line_2`, etc. (0-based).
|
|
110
|
+
|
|
111
|
+
## Themes
|
|
112
|
+
|
|
113
|
+
### Dark (default)
|
|
114
|
+
Dark background (#1e1e2e) with Catppuccin-inspired token colors.
|
|
115
|
+
|
|
116
|
+
### Light
|
|
117
|
+
Light background (#fafafa) with One Light-inspired token colors.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var motorcortex = require('@donkeyclip/motorcortex');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CodeClip — a BrowserClip that renders syntax-highlighted code.
|
|
7
|
+
*
|
|
8
|
+
* Each line gets id: line_0, line_1, ... for targeting.
|
|
9
|
+
* Registers custom entity "code" for Effects via !#code.
|
|
10
|
+
*
|
|
11
|
+
* Attrs:
|
|
12
|
+
* code: string — the source code to display
|
|
13
|
+
* language: string (default: "javascript")
|
|
14
|
+
* theme: "dark" | "light" (default: "dark")
|
|
15
|
+
* fontSize: number (default: 14)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const THEMES = {
|
|
19
|
+
dark: {
|
|
20
|
+
bg: "#1e1e2e",
|
|
21
|
+
fg: "#cdd6f4",
|
|
22
|
+
lineNum: "#585b70",
|
|
23
|
+
keyword: "#cba6f7",
|
|
24
|
+
string: "#a6e3a1",
|
|
25
|
+
comment: "#6c7086",
|
|
26
|
+
fn: "#89b4fa",
|
|
27
|
+
number: "#fab387",
|
|
28
|
+
punct: "#94e2d5",
|
|
29
|
+
type: "#f9e2af"
|
|
30
|
+
},
|
|
31
|
+
light: {
|
|
32
|
+
bg: "#fafafa",
|
|
33
|
+
fg: "#383a42",
|
|
34
|
+
lineNum: "#9ca0b0",
|
|
35
|
+
keyword: "#a626a4",
|
|
36
|
+
string: "#50a14f",
|
|
37
|
+
comment: "#a0a1a7",
|
|
38
|
+
fn: "#4078f2",
|
|
39
|
+
number: "#986801",
|
|
40
|
+
punct: "#0184bc",
|
|
41
|
+
type: "#c18401"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const LANG_KEYWORDS = {
|
|
45
|
+
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"]),
|
|
46
|
+
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"]),
|
|
47
|
+
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"]),
|
|
48
|
+
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"]),
|
|
49
|
+
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"]),
|
|
50
|
+
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"])
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// JSON/YAML don't have keywords in the traditional sense — handled by tokenizer rules
|
|
54
|
+
const COMMENT_STYLES = {
|
|
55
|
+
javascript: "//",
|
|
56
|
+
typescript: "//",
|
|
57
|
+
python: "#",
|
|
58
|
+
bash: "#",
|
|
59
|
+
yaml: "#",
|
|
60
|
+
css: null,
|
|
61
|
+
json: null,
|
|
62
|
+
html: null
|
|
63
|
+
};
|
|
64
|
+
function escapeHtml(s) {
|
|
65
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
66
|
+
}
|
|
67
|
+
function tokenizeLine(line, theme, language) {
|
|
68
|
+
const t = theme;
|
|
69
|
+
const keywords = LANG_KEYWORDS[language] || LANG_KEYWORDS.javascript;
|
|
70
|
+
const commentChar = COMMENT_STYLES[language] ?? "//";
|
|
71
|
+
let result = "";
|
|
72
|
+
let i = 0;
|
|
73
|
+
|
|
74
|
+
// YAML/JSON: color keys specially
|
|
75
|
+
if (language === "yaml") {
|
|
76
|
+
// YAML line: "key: value" or "- item"
|
|
77
|
+
const yamlKeyMatch = line.match(/^(\s*)([\w.\-/]+)(\s*:\s*)(.*)/);
|
|
78
|
+
if (yamlKeyMatch) {
|
|
79
|
+
const [, indent, key, colon, value] = yamlKeyMatch;
|
|
80
|
+
result += escapeHtml(indent);
|
|
81
|
+
result += `<span style="color:${t.fn}">${escapeHtml(key)}</span>`;
|
|
82
|
+
result += `<span style="color:${t.punct}">${escapeHtml(colon)}</span>`;
|
|
83
|
+
// Value coloring
|
|
84
|
+
if (/^(true|false|null|~)$/i.test(value.trim())) {
|
|
85
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(value)}</span>`;
|
|
86
|
+
} else if (/^\d+(\.\d+)?$/.test(value.trim())) {
|
|
87
|
+
result += `<span style="color:${t.number}">${escapeHtml(value)}</span>`;
|
|
88
|
+
} else if (/^["']/.test(value.trim())) {
|
|
89
|
+
result += `<span style="color:${t.string}">${escapeHtml(value)}</span>`;
|
|
90
|
+
} else {
|
|
91
|
+
result += `<span style="color:${t.fg}">${escapeHtml(value)}</span>`;
|
|
92
|
+
}
|
|
93
|
+
return result || " ";
|
|
94
|
+
}
|
|
95
|
+
const yamlDash = line.match(/^(\s*)(- )(.*)/);
|
|
96
|
+
if (yamlDash) {
|
|
97
|
+
result += escapeHtml(yamlDash[1]);
|
|
98
|
+
result += `<span style="color:${t.punct}">${escapeHtml(yamlDash[2])}</span>`;
|
|
99
|
+
result += `<span style="color:${t.fg}">${escapeHtml(yamlDash[3])}</span>`;
|
|
100
|
+
return result || " ";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (language === "json") {
|
|
104
|
+
// JSON: color keys in quotes before colon
|
|
105
|
+
const jsonKeyMatch = line.match(/^(\s*)("(?:[^"\\]|\\.)*")(\s*:\s*)(.*)/);
|
|
106
|
+
if (jsonKeyMatch) {
|
|
107
|
+
const [, indent, key, colon, value] = jsonKeyMatch;
|
|
108
|
+
result += escapeHtml(indent);
|
|
109
|
+
result += `<span style="color:${t.fn}">${escapeHtml(key)}</span>`;
|
|
110
|
+
result += `<span style="color:${t.punct}">${escapeHtml(colon)}</span>`;
|
|
111
|
+
if (/^(true|false|null)/.test(value.trim())) {
|
|
112
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(value)}</span>`;
|
|
113
|
+
} else if (/^\d/.test(value.trim())) {
|
|
114
|
+
result += `<span style="color:${t.number}">${escapeHtml(value)}</span>`;
|
|
115
|
+
} else if (/^"/.test(value.trim())) {
|
|
116
|
+
result += `<span style="color:${t.string}">${escapeHtml(value)}</span>`;
|
|
117
|
+
} else {
|
|
118
|
+
result += `<span style="color:${t.fg}">${escapeHtml(value)}</span>`;
|
|
119
|
+
}
|
|
120
|
+
return result || " ";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
while (i < line.length) {
|
|
124
|
+
// Comments: // or # to end of line
|
|
125
|
+
if (commentChar && line.startsWith(commentChar, i) && (i === 0 || /\s/.test(line[i - 1]) || i === 0)) {
|
|
126
|
+
result += `<span style="color:${t.comment};font-style:italic">${escapeHtml(line.slice(i))}</span>`;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Strings: single or double quoted
|
|
131
|
+
if (line[i] === '"' || line[i] === "'" || line[i] === "`") {
|
|
132
|
+
const q = line[i];
|
|
133
|
+
let j = i + 1;
|
|
134
|
+
while (j < line.length && line[j] !== q) {
|
|
135
|
+
if (line[j] === "\\") j++;
|
|
136
|
+
j++;
|
|
137
|
+
}
|
|
138
|
+
j = Math.min(j + 1, line.length);
|
|
139
|
+
result += `<span style="color:${t.string}">${escapeHtml(line.slice(i, j))}</span>`;
|
|
140
|
+
i = j;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Numbers
|
|
145
|
+
if (/\d/.test(line[i]) && (i === 0 || /[\s,;(=+\-*/<>!&|]/.test(line[i - 1]))) {
|
|
146
|
+
let j = i;
|
|
147
|
+
while (j < line.length && /[\d.]/.test(line[j])) j++;
|
|
148
|
+
result += `<span style="color:${t.number}">${escapeHtml(line.slice(i, j))}</span>`;
|
|
149
|
+
i = j;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Words (keywords, identifiers)
|
|
154
|
+
if (/[a-zA-Z_$]/.test(line[i])) {
|
|
155
|
+
let j = i;
|
|
156
|
+
while (j < line.length && /[a-zA-Z0-9_$]/.test(line[j])) j++;
|
|
157
|
+
const word = line.slice(i, j);
|
|
158
|
+
if (keywords.has(word)) {
|
|
159
|
+
result += `<span style="color:${t.keyword};font-weight:600">${escapeHtml(word)}</span>`;
|
|
160
|
+
} else if (j < line.length && line[j] === "(") {
|
|
161
|
+
result += `<span style="color:${t.fn}">${escapeHtml(word)}</span>`;
|
|
162
|
+
} else {
|
|
163
|
+
result += `<span style="color:${t.fg}">${escapeHtml(word)}</span>`;
|
|
164
|
+
}
|
|
165
|
+
i = j;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Punctuation / operators
|
|
170
|
+
if (/[{}()\[\];:.,=+\-*/<>!&|?%^~@]/.test(line[i])) {
|
|
171
|
+
result += `<span style="color:${t.punct}">${escapeHtml(line[i])}</span>`;
|
|
172
|
+
i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Whitespace and anything else
|
|
177
|
+
result += escapeHtml(line[i]);
|
|
178
|
+
i++;
|
|
179
|
+
}
|
|
180
|
+
return result || " ";
|
|
181
|
+
}
|
|
182
|
+
function renderLines(code, theme, language) {
|
|
183
|
+
const lines = code.split("\n");
|
|
184
|
+
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("");
|
|
185
|
+
}
|
|
186
|
+
class CodeClip extends motorcortex.BrowserClip {
|
|
187
|
+
get html() {
|
|
188
|
+
const code = this.attrs.code || "";
|
|
189
|
+
const language = this.attrs.language || "javascript";
|
|
190
|
+
const t = THEMES[this.attrs.theme] || THEMES.dark;
|
|
191
|
+
const linesHtml = renderLines(code, t, language);
|
|
192
|
+
return `<div id="code-container"><pre><code>${linesHtml}</code></pre></div>`;
|
|
193
|
+
}
|
|
194
|
+
get css() {
|
|
195
|
+
const fs = this.attrs.fontSize || 14;
|
|
196
|
+
const t = THEMES[this.attrs.theme] || THEMES.dark;
|
|
197
|
+
return `
|
|
198
|
+
#code-container {
|
|
199
|
+
width: 100%;
|
|
200
|
+
height: 100%;
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
padding: 24px;
|
|
205
|
+
box-sizing: border-box;
|
|
206
|
+
background: ${t.bg};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
pre {
|
|
210
|
+
margin: 0;
|
|
211
|
+
padding: 20px 24px;
|
|
212
|
+
border-radius: 10px;
|
|
213
|
+
background: ${t.bg};
|
|
214
|
+
font-size: ${fs}px;
|
|
215
|
+
line-height: 1.7;
|
|
216
|
+
overflow-x: auto;
|
|
217
|
+
width: 100%;
|
|
218
|
+
max-height: 100%;
|
|
219
|
+
box-sizing: border-box;
|
|
220
|
+
font-family: "Fira Code", "Cascadia Code", "JetBrains Mono", "Consolas", monospace;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
code {
|
|
224
|
+
color: ${t.fg};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.shiki-line {
|
|
228
|
+
display: flex;
|
|
229
|
+
padding: 1px 8px;
|
|
230
|
+
border-radius: 4px;
|
|
231
|
+
gap: 16px;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.line-num {
|
|
235
|
+
color: ${t.lineNum};
|
|
236
|
+
user-select: none;
|
|
237
|
+
min-width: 2em;
|
|
238
|
+
text-align: right;
|
|
239
|
+
flex-shrink: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.line-code {
|
|
243
|
+
white-space: pre;
|
|
244
|
+
}
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
onAfterRender() {
|
|
248
|
+
this.attrs.code || "";
|
|
249
|
+
const container = this.context.getElements("#code-container")[0];
|
|
250
|
+
const lines = container.querySelectorAll(".shiki-line");
|
|
251
|
+
this.setCustomEntity("code", {
|
|
252
|
+
container,
|
|
253
|
+
lines,
|
|
254
|
+
lineCount: lines.length
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* CodeHighlight — Effect that pulses a line's background color.
|
|
261
|
+
*
|
|
262
|
+
* Selector: !#code
|
|
263
|
+
* animatedAttrs: { highlightLine_N: 1 } — where N is the line index (0-based)
|
|
264
|
+
* The line index is parsed from the attr key.
|
|
265
|
+
*
|
|
266
|
+
* Attrs:
|
|
267
|
+
* color: string (default: "rgba(99,102,241,0.25)") — highlight background
|
|
268
|
+
*/
|
|
269
|
+
class CodeHighlight extends motorcortex.Effect {
|
|
270
|
+
getScratchValue() {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
onGetContext() {
|
|
274
|
+
this._lines = this.element?.entity?.lines;
|
|
275
|
+
this._color = this.attrs.color || "rgba(99,102,241,0.25)";
|
|
276
|
+
// Parse line index from attributeKey: "highlightLine_4" → 4
|
|
277
|
+
const key = this.attributeKey || "";
|
|
278
|
+
this._lineIndex = parseInt(key.split("_").pop(), 10);
|
|
279
|
+
}
|
|
280
|
+
onProgress(millisecond) {
|
|
281
|
+
if (!this._lines || isNaN(this._lineIndex) || this._lineIndex < 0 || this._lineIndex >= this._lines.length) return;
|
|
282
|
+
const fraction = this.getFraction(millisecond);
|
|
283
|
+
const line = this._lines[this._lineIndex];
|
|
284
|
+
|
|
285
|
+
// Double pump: 0→0.25 up, 0.25→0.5 down, 0.5→0.75 up, 0.75→1 down
|
|
286
|
+
let intensity;
|
|
287
|
+
if (fraction < 0.25) {
|
|
288
|
+
intensity = fraction / 0.25;
|
|
289
|
+
} else if (fraction < 0.5) {
|
|
290
|
+
intensity = 1 - (fraction - 0.25) / 0.25;
|
|
291
|
+
} else if (fraction < 0.75) {
|
|
292
|
+
intensity = (fraction - 0.5) / 0.25;
|
|
293
|
+
} else {
|
|
294
|
+
intensity = 1 - (fraction - 0.75) / 0.25;
|
|
295
|
+
}
|
|
296
|
+
if (intensity > 0.01) {
|
|
297
|
+
const alpha = intensity * 0.35;
|
|
298
|
+
const baseRgb = this._color.match(/\d+/g);
|
|
299
|
+
if (baseRgb && baseRgb.length >= 3) {
|
|
300
|
+
line.style.backgroundColor = `rgba(${baseRgb[0]},${baseRgb[1]},${baseRgb[2]},${alpha})`;
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
line.style.backgroundColor = "";
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* WriteCode — Effect that progressively reveals code lines.
|
|
310
|
+
*
|
|
311
|
+
* Selector: !#code
|
|
312
|
+
* animatedAttrs: { writeCode: 1 } — fraction 0→1 reveals all lines
|
|
313
|
+
*/
|
|
314
|
+
class WriteCode extends motorcortex.Effect {
|
|
315
|
+
getScratchValue() {
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
318
|
+
onGetContext() {
|
|
319
|
+
this._lines = this.element?.entity?.lines;
|
|
320
|
+
if (this._lines) {
|
|
321
|
+
// Hide all lines initially
|
|
322
|
+
for (const line of this._lines) {
|
|
323
|
+
line.style.opacity = "0";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
onProgress(millisecond) {
|
|
328
|
+
if (!this._lines) return;
|
|
329
|
+
const fraction = this.getFraction(millisecond);
|
|
330
|
+
const initial = this.initialValue ?? 0;
|
|
331
|
+
const target = this.targetValue;
|
|
332
|
+
const current = initial + (target - initial) * fraction;
|
|
333
|
+
const total = this._lines.length;
|
|
334
|
+
const revealUpTo = current * total;
|
|
335
|
+
for (let i = 0; i < total; i++) {
|
|
336
|
+
if (i < Math.floor(revealUpTo)) {
|
|
337
|
+
this._lines[i].style.opacity = "1";
|
|
338
|
+
} else if (i < revealUpTo) {
|
|
339
|
+
this._lines[i].style.opacity = String(revealUpTo - i);
|
|
340
|
+
} else {
|
|
341
|
+
this._lines[i].style.opacity = "0";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
var name = "@kissmybutton/mc-code";
|
|
348
|
+
var version = "0.0.1";
|
|
349
|
+
|
|
350
|
+
var index = {
|
|
351
|
+
npm_name: name,
|
|
352
|
+
version: version,
|
|
353
|
+
incidents: [{
|
|
354
|
+
exportable: CodeHighlight,
|
|
355
|
+
name: "CodeHighlight"
|
|
356
|
+
}, {
|
|
357
|
+
exportable: WriteCode,
|
|
358
|
+
name: "WriteCode"
|
|
359
|
+
}],
|
|
360
|
+
Clip: {
|
|
361
|
+
exportable: CodeClip
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
module.exports = index;
|