]*>(.*?)<\/color>/gi,
58 | '[color=$2]$3[/color]'
59 | );
60 |
61 | // Replace tags with BBCode line breaks (换行)
62 | bbcodeText = bbcodeText.replace(//gi, '\n');
63 |
64 | // Remove all other HTML tags
65 | bbcodeText = bbcodeText.replace(/<\/?[^>]+(>|$)/g, '');
66 |
67 | return bbcodeText;
68 | };
69 |
70 | export default htmltobbcode;
71 |
--------------------------------------------------------------------------------
/extensions/wit_cat/File_Helper/base64.js:
--------------------------------------------------------------------------------
1 | // 此处代码来自 https://github.com/dankogai/js-base64/blob/main/base64.mjs
2 | // 使用 BSD 3-Clause 协议
3 | // 协议地址: https://github.com/dankogai/js-base64/blob/main/LICENSE.md
4 | /* eslint-disable */
5 |
6 | /**
7 | * base64.ts
8 | *
9 | * Licensed under the BSD 3-Clause License.
10 | * http://opensource.org/licenses/BSD-3-Clause
11 | *
12 | * References:
13 | * http://en.wikipedia.org/wiki/Base64
14 | *
15 | * @author Dan Kogai (https://github.com/dankogai)
16 | */
17 | const version = "3.7.5";
18 | /**
19 | * @deprecated use lowercase `version`.
20 | */
21 | const VERSION = version;
22 | const _hasatob = typeof atob === "function";
23 | const _hasbtoa = typeof btoa === "function";
24 | const _hasBuffer = typeof Buffer === "function";
25 | const _TD = typeof TextDecoder === "function" ? new TextDecoder() : undefined;
26 | const _TE = typeof TextEncoder === "function" ? new TextEncoder() : undefined;
27 | const b64ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
28 | const b64chs = Array.prototype.slice.call(b64ch);
29 | const b64tab = ((a) => {
30 | let tab = {};
31 | a.forEach((c, i) => (tab[c] = i));
32 | return tab;
33 | })(b64chs);
34 | const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
35 | const _fromCC = String.fromCharCode.bind(String);
36 | const _U8Afrom =
37 | typeof Uint8Array.from === "function"
38 | ? Uint8Array.from.bind(Uint8Array)
39 | : (it) => new Uint8Array(Array.prototype.slice.call(it, 0));
40 | const _mkUriSafe = (src) => src.replace(/=/g, "").replace(/[+\/]/g, (m0) => (m0 == "+" ? "-" : "_"));
41 | const _tidyB64 = (s) => s.replace(/[^A-Za-z0-9\+\/]/g, "");
42 | /**
43 | * polyfill version of `btoa`
44 | */
45 | const btoaPolyfill = (bin) => {
46 | // console.log('polyfilled');
47 | let u32,
48 | c0,
49 | c1,
50 | c2,
51 | asc = "";
52 | const pad = bin.length % 3;
53 | for (let i = 0; i < bin.length;) {
54 | if ((c0 = bin.charCodeAt(i++)) > 255 || (c1 = bin.charCodeAt(i++)) > 255 || (c2 = bin.charCodeAt(i++)) > 255)
55 | throw new TypeError("invalid character found");
56 | u32 = (c0 << 16) | (c1 << 8) | c2;
57 | asc += b64chs[(u32 >> 18) & 63] + b64chs[(u32 >> 12) & 63] + b64chs[(u32 >> 6) & 63] + b64chs[u32 & 63];
58 | }
59 | return pad ? asc.slice(0, pad - 3) + "===".substring(pad) : asc;
60 | };
61 | /**
62 | * does what `window.btoa` of web browsers do.
63 | * @param {String} bin binary string
64 | * @returns {string} Base64-encoded string
65 | */
66 | const _btoa = _hasbtoa
67 | ? (bin) => btoa(bin)
68 | : _hasBuffer
69 | ? (bin) => Buffer.from(bin, "binary").toString("base64")
70 | : btoaPolyfill;
71 | const _fromUint8Array = _hasBuffer
72 | ? (u8a) => Buffer.from(u8a).toString("base64")
73 | : (u8a) => {
74 | // cf. https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string/12713326#12713326
75 | const maxargs = 0x1000;
76 | let strs = [];
77 | for (let i = 0, l = u8a.length; i < l; i += maxargs) {
78 | strs.push(_fromCC.apply(null, u8a.subarray(i, i + maxargs)));
79 | }
80 | return _btoa(strs.join(""));
81 | };
82 | /**
83 | * converts a Uint8Array to a Base64 string.
84 | * @param {boolean} [urlsafe] URL-and-filename-safe a la RFC4648 §5
85 | * @returns {string} Base64 string
86 | */
87 | const fromUint8Array = (u8a, urlsafe = false) => (urlsafe ? _mkUriSafe(_fromUint8Array(u8a)) : _fromUint8Array(u8a));
88 | // This trick is found broken https://github.com/dankogai/js-base64/issues/130
89 | // const utob = (src: string) => unescape(encodeURIComponent(src));
90 | // reverting good old fationed regexp
91 | const cb_utob = (c) => {
92 | if (c.length < 2) {
93 | var cc = c.charCodeAt(0);
94 | return cc < 0x80
95 | ? c
96 | : cc < 0x800
97 | ? _fromCC(0xc0 | (cc >>> 6)) + _fromCC(0x80 | (cc & 0x3f))
98 | : _fromCC(0xe0 | ((cc >>> 12) & 0x0f)) + _fromCC(0x80 | ((cc >>> 6) & 0x3f)) + _fromCC(0x80 | (cc & 0x3f));
99 | } else {
100 | var cc = 0x10000 + (c.charCodeAt(0) - 0xd800) * 0x400 + (c.charCodeAt(1) - 0xdc00);
101 | return (
102 | _fromCC(0xf0 | ((cc >>> 18) & 0x07)) +
103 | _fromCC(0x80 | ((cc >>> 12) & 0x3f)) +
104 | _fromCC(0x80 | ((cc >>> 6) & 0x3f)) +
105 | _fromCC(0x80 | (cc & 0x3f))
106 | );
107 | }
108 | };
109 | const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
110 | /**
111 | * @deprecated should have been internal use only.
112 | * @param {string} src UTF-8 string
113 | * @returns {string} UTF-16 string
114 | */
115 | const utob = (u) => u.replace(re_utob, cb_utob);
116 | //
117 | const _encode = _hasBuffer
118 | ? (s) => Buffer.from(s, "utf8").toString("base64")
119 | : _TE
120 | ? (s) => _fromUint8Array(_TE.encode(s))
121 | : (s) => _btoa(utob(s));
122 | /**
123 | * converts a UTF-8-encoded string to a Base64 string.
124 | * @param {boolean} [urlsafe] if `true` make the result URL-safe
125 | * @returns {string} Base64 string
126 | */
127 | const encode = (src, urlsafe = false) => (urlsafe ? _mkUriSafe(_encode(src)) : _encode(src));
128 | /**
129 | * converts a UTF-8-encoded string to URL-safe Base64 RFC4648 §5.
130 | * @returns {string} Base64 string
131 | */
132 | const encodeURI = (src) => encode(src, true);
133 | // This trick is found broken https://github.com/dankogai/js-base64/issues/130
134 | // const btou = (src: string) => decodeURIComponent(escape(src));
135 | // reverting good old fationed regexp
136 | const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
137 | const cb_btou = (cccc) => {
138 | switch (cccc.length) {
139 | case 4:
140 | var cp =
141 | ((0x07 & cccc.charCodeAt(0)) << 18) |
142 | ((0x3f & cccc.charCodeAt(1)) << 12) |
143 | ((0x3f & cccc.charCodeAt(2)) << 6) |
144 | (0x3f & cccc.charCodeAt(3)),
145 | offset = cp - 0x10000;
146 | return _fromCC((offset >>> 10) + 0xd800) + _fromCC((offset & 0x3ff) + 0xdc00);
147 | case 3:
148 | return _fromCC(
149 | ((0x0f & cccc.charCodeAt(0)) << 12) | ((0x3f & cccc.charCodeAt(1)) << 6) | (0x3f & cccc.charCodeAt(2))
150 | );
151 | default:
152 | return _fromCC(((0x1f & cccc.charCodeAt(0)) << 6) | (0x3f & cccc.charCodeAt(1)));
153 | }
154 | };
155 | /**
156 | * @deprecated should have been internal use only.
157 | * @param {string} src UTF-16 string
158 | * @returns {string} UTF-8 string
159 | */
160 | const btou = (b) => b.replace(re_btou, cb_btou);
161 | /**
162 | * polyfill version of `atob`
163 | */
164 | const atobPolyfill = (asc) => {
165 | // console.log('polyfilled');
166 | asc = asc.replace(/\s+/g, "");
167 | if (!b64re.test(asc)) throw new TypeError("malformed base64.");
168 | asc += "==".slice(2 - (asc.length & 3));
169 | let u24,
170 | bin = "",
171 | r1,
172 | r2;
173 | for (let i = 0; i < asc.length;) {
174 | u24 =
175 | (b64tab[asc.charAt(i++)] << 18) |
176 | (b64tab[asc.charAt(i++)] << 12) |
177 | ((r1 = b64tab[asc.charAt(i++)]) << 6) |
178 | (r2 = b64tab[asc.charAt(i++)]);
179 | bin +=
180 | r1 === 64
181 | ? _fromCC((u24 >> 16) & 255)
182 | : r2 === 64
183 | ? _fromCC((u24 >> 16) & 255, (u24 >> 8) & 255)
184 | : _fromCC((u24 >> 16) & 255, (u24 >> 8) & 255, u24 & 255);
185 | }
186 | return bin;
187 | };
188 | /**
189 | * does what `window.atob` of web browsers do.
190 | * @param {String} asc Base64-encoded string
191 | * @returns {string} binary string
192 | */
193 | const _atob = _hasatob
194 | ? (asc) => atob(_tidyB64(asc))
195 | : _hasBuffer
196 | ? (asc) => Buffer.from(asc, "base64").toString("binary")
197 | : atobPolyfill;
198 | //
199 | const _toUint8Array = _hasBuffer
200 | ? (a) => _U8Afrom(Buffer.from(a, "base64"))
201 | : (a) =>
202 | _U8Afrom(
203 | _atob(a)
204 | .split("")
205 | .map((c) => c.charCodeAt(0))
206 | );
207 | /**
208 | * converts a Base64 string to a Uint8Array.
209 | */
210 | const toUint8Array = (a) => _toUint8Array(_unURI(a));
211 | //
212 | const _decode = _hasBuffer
213 | ? (a) => Buffer.from(a, "base64").toString("utf8")
214 | : _TD
215 | ? (a) => _TD.decode(_toUint8Array(a))
216 | : (a) => btou(_atob(a));
217 | const _unURI = (a) => _tidyB64(a.replace(/[-_]/g, (m0) => (m0 == "-" ? "+" : "/")));
218 | /**
219 | * converts a Base64 string to a UTF-8 string.
220 | * @param {String} src Base64 string. Both normal and URL-safe are supported
221 | * @returns {string} UTF-8 string
222 | */
223 | const decode = (src) => _decode(_unURI(src));
224 | /**
225 | * check if a value is a valid Base64 string
226 | * @param {String} src a value to check
227 | */
228 | const isValid = (src) => {
229 | if (typeof src !== "string") return false;
230 | const s = src.replace(/\s+/g, "").replace(/={0,2}$/, "");
231 | return !/[^\s0-9a-zA-Z\+/]/.test(s) || !/[^\s0-9a-zA-Z\-_]/.test(s);
232 | };
233 | //
234 | const _noEnum = (v) => {
235 | return {
236 | value: v,
237 | enumerable: false,
238 | writable: true,
239 | configurable: true,
240 | };
241 | };
242 | /**
243 | * extend String.prototype with relevant methods
244 | */
245 | const extendString = function () {
246 | const _add = (name, body) => Object.defineProperty(String.prototype, name, _noEnum(body));
247 | _add("fromBase64", function () {
248 | return decode(this);
249 | });
250 | _add("toBase64", function (urlsafe) {
251 | return encode(this, urlsafe);
252 | });
253 | _add("toBase64URI", function () {
254 | return encode(this, true);
255 | });
256 | _add("toBase64URL", function () {
257 | return encode(this, true);
258 | });
259 | _add("toUint8Array", function () {
260 | return toUint8Array(this);
261 | });
262 | };
263 | /**
264 | * extend Uint8Array.prototype with relevant methods
265 | */
266 | const extendUint8Array = function () {
267 | const _add = (name, body) => Object.defineProperty(Uint8Array.prototype, name, _noEnum(body));
268 | _add("toBase64", function (urlsafe) {
269 | return fromUint8Array(this, urlsafe);
270 | });
271 | _add("toBase64URI", function () {
272 | return fromUint8Array(this, true);
273 | });
274 | _add("toBase64URL", function () {
275 | return fromUint8Array(this, true);
276 | });
277 | };
278 | /**
279 | * extend Builtin prototypes with relevant methods
280 | */
281 | const extendBuiltins = () => {
282 | extendString();
283 | extendUint8Array();
284 | };
285 | const gBase64 = {
286 | version: version,
287 | VERSION: VERSION,
288 | atob: _atob,
289 | atobPolyfill: atobPolyfill,
290 | btoa: _btoa,
291 | btoaPolyfill: btoaPolyfill,
292 | fromBase64: decode,
293 | toBase64: encode,
294 | encode: encode,
295 | encodeURI: encodeURI,
296 | encodeURL: encodeURI,
297 | utob: utob,
298 | btou: btou,
299 | decode: decode,
300 | isValid: isValid,
301 | fromUint8Array: fromUint8Array,
302 | toUint8Array: toUint8Array,
303 | extendString: extendString,
304 | extendUint8Array: extendUint8Array,
305 | extendBuiltins: extendBuiltins,
306 | };
307 | // makecjs:CUT //
308 | export { version };
309 | export { VERSION };
310 | export { _atob as atob };
311 | export { atobPolyfill };
312 | export { _btoa as btoa };
313 | export { btoaPolyfill };
314 | export { decode as fromBase64 };
315 | export { encode as toBase64 };
316 | export { utob };
317 | export { encode };
318 | export { encodeURI };
319 | export { encodeURI as encodeURL };
320 | export { btou };
321 | export { decode };
322 | export { isValid };
323 | export { fromUint8Array };
324 | export { toUint8Array };
325 | export { extendString };
326 | export { extendUint8Array };
327 | export { extendBuiltins };
328 | // and finally,
329 | export { gBase64 as Base64 };
330 |
--------------------------------------------------------------------------------
/extensions/wit_cat/MarkDown/assets/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 | export const witcat_markdown_icon =
3 | 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSJub25lIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgdmlld0JveD0iMCAwIDEwIDEwIj48ZGVmcz48Y2xpcFBhdGggaWQ9Im1hc3Rlcl9zdmcwXzEwXzEiPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgcng9IjAiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjbWFzdGVyX3N2ZzBfMTBfMSkiPjxnPjxnPjxwYXRoIGQ9Ik05LjI4Njk3LDIuMDAwMDA5MTU1MjdDOS42ODA5NywyLjAwMDAwOTE1NTI3LDEwLjAwNjIsMi4zMTIwMTIsOS45OTk5MSwyLjY5MDAwNkw5Ljk5OTkxLDcuMzFDOS45OTk5MSw3LjY4ODAxLDkuNjgwOTcsOCw5LjI4MDcyLDhMMC43MTkxOSw4QzAuMzI1MTk0LDgsMCw3LjY4OCwwLDcuMzA0TDAsMi42ODk5OTZDMCwyLjMxMTk5MywwLjMyNTIwMywyLDAuNzE5MTksMkw5LjI4Njk3LDIuMDAwMDA5MTU1MjdaTTUuNjI4NDYsNi44MDAwMUw1LjYyODQ2LDMuMjAwMDFMNC4zNzc2OSwzLjIwMDAxTDMuNDM5NjEsNC40MDAwMUwyLjUwMTUzLDMuMjAwMDFMMS4yNTA3NiwzLjIwMDAxTDEuMjUwNzYsNi44MDAwMUwyLjUwMTUzLDYuODAwMDFMMi41MDE1Myw1LjAwMDAxTDMuNDM5NjEsNi4xNTIwMUw0LjM3NzY5LDUuMDAwMDFMNC4zNzc2OSw2LjgwMDAxTDUuNjI4NDYsNi44MDAwMVpNNy40OTgzNyw3LjEwMDAxTDkuMDY4MDksNS4wMDAwMUw4LjEzMDAxLDUuMDAwMDFMOC4xMzAwMSwzLjIwMDAxTDYuODc5MjQsMy4yMDAwMUw2Ljg3OTI0LDUuMDAwMDFMNS45NDExNiw1LjAwMDAxTDcuNDk4MzcsNy4xMDAwMVoiIGZpbGw9IiNGRkZGRkYiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L2c+PC9nPjwvc3ZnPg==';
4 | export const witcat_markdown_picture =
5 | 'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIiA/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIgd2lkdGg9IjYwMCIgaGVpZ2h0PSIzNzIiIHZpZXdCb3g9IjAgMCA2MDAgMzcyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPGRlc2M+Q3JlYXRlZCB3aXRoIEZhYnJpYy5qcyAzLjYuNjwvZGVzYz4KPGRlZnM+CjwvZGVmcz4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0icmdiYSgyNTUsIDI1NSwgMjU1LCAxKSI+PC9yZWN0Pgo8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxNi4xMiAwIDAgMTYuMTIgMjk5Ljk4IDE4NS42OSkiICA+CjxwYXRoIHN0eWxlPSJzdHJva2U6IG5vbmU7IHN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogbm9uZTsgc3Ryb2tlLWxpbmVjYXA6IGJ1dHQ7IHN0cm9rZS1kYXNob2Zmc2V0OiAwOyBzdHJva2UtbGluZWpvaW46IG1pdGVyOyBzdHJva2UtbWl0ZXJsaW1pdDogNDsgZmlsbDogcmdiKDAsMCwwKTsgZmlsbC1ydWxlOiBub256ZXJvOyBvcGFjaXR5OiAxOyIgIHRyYW5zZm9ybT0iIHRyYW5zbGF0ZSgtOCwgLTgpIiBkPSJNIDE0Ljg1IDMgYyAwLjYzIDAgMS4xNSAwLjUyIDEuMTQgMS4xNSB2IDcuNyBjIDAgMC42MyAtMC41MSAxLjE1IC0xLjE1IDEuMTUgSCAxLjE1IEMgMC41MiAxMyAwIDEyLjQ4IDAgMTEuODQgViA0LjE1IEMgMCAzLjUyIDAuNTIgMyAxLjE1IDMgWiBNIDkgMTEgViA1IEggNyBMIDUuNSA3IEwgNCA1IEggMiB2IDYgaCAyIFYgOCBsIDEuNSAxLjkyIEwgNyA4IHYgMyBaIG0gMi45OSAwLjUgTCAxNC41IDggSCAxMyBWIDUgaCAtMiB2IDMgSCA5LjUgWiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiAvPgo8L2c+Cjwvc3ZnPg==';
6 |
--------------------------------------------------------------------------------
/extensions/wit_cat/README.md:
--------------------------------------------------------------------------------
1 | 文件助手扩展使用了来自 [js-base64](https://github.com/dankogai/js-base64) 的代码 (js-base64 使用 [BSD 3-Clause 协议](https://github.com/dankogai/js-base64/blob/main/LICENSE.md))
2 |
--------------------------------------------------------------------------------
/extensions/wit_cat/Zip/index.js:
--------------------------------------------------------------------------------
1 |
2 | import WitCatZip from "./extension.js";
3 | import { witcat_Zip_icon, witcat_Zip_picture, witcat_Zip_extensionId } from "./assets/index.js";
4 |
5 | window.tempExt = {
6 | Extension: WitCatZip,
7 | info: {
8 | name: "WitCatZip.name",
9 | description: "WitCatZip.descp",
10 | extensionId: witcat_Zip_extensionId,
11 | iconURL: witcat_Zip_picture,
12 | insetIconURL: witcat_Zip_icon,
13 | featured: true,
14 | disabled: false,
15 | collaborator: "白猫 @ CCW"
16 | },
17 | l10n: {
18 | "zh-cn": {
19 | "WitCatZip.name": "白猫的压缩文件 V1.0",
20 | "WitCatZip.descp": "处理压缩文件"
21 | },
22 | en: {
23 | "WitCatZip.name": "WitCat’s Zip V1.0",
24 | "WitCatZip.descp": "Processing compressed files"
25 | }
26 | }
27 | };
--------------------------------------------------------------------------------
/extensions/wit_cat/Zip/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "jszip": "^3.10.1",
4 | "schema-utils": "^4.2.0"
5 | },
6 | "devDependencies": {
7 | "@babel/core": "^7.24.3",
8 | "@babel/preset-env": "^7.24.3",
9 | "@types/crypto-js": "^4.2.2",
10 | "babel-loader": "^9.1.3",
11 | "webpack-cli": "^5.1.4"
12 | },
13 | "name": "zip",
14 | "version": "1.0.0",
15 | "main": "index.js",
16 | "scripts": {
17 | "production": "npx webpack --mode production"
18 | },
19 | "author": "白猫 @ CCW",
20 | "license": "ISC",
21 | "description": "zip file handling",
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/little-starts/custom-extension.git#Zip"
25 | },
26 | "keywords": [
27 | "Zip",
28 | "wit_cat"
29 | ],
30 | "bugs": {
31 | "url": "https://github.com/little-starts/custom-extension/issues"
32 | },
33 | "homepage": "https://github.com/little-starts/custom-extension/tree/Zip#readme"
34 | }
35 |
--------------------------------------------------------------------------------
/extensions/wit_cat/Zip/webpack.config.js:
--------------------------------------------------------------------------------
1 | // webpack.config.js
2 |
3 | const path = require('path');
4 |
5 | module.exports = {
6 | entry: {
7 | Zip_dev: './index.js', //开发调试用
8 | Zip_prd: './extension.js',//发布用
9 | }, // 指定项目的入口文件
10 | output: {
11 | filename: '[name].js', // 打包后的输出文件名
12 | path: path.resolve(__dirname, 'dist'), // 输出路径
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | exclude: /node_modules/, // 排除对node_modules中模块的处理
19 | use: {
20 | loader: 'babel-loader',
21 | options: {
22 | presets: ['@babel/preset-env'], // 使用Babel将ES6+语法转为浏览器兼容的语法
23 | },
24 | },
25 | },
26 | ],
27 | },
28 | resolve: {
29 | alias: {
30 | moment$: path.resolve('./node_modules/moment/moment.js'), // 如果需要解决moment库的别名引用问题
31 | },
32 | },
33 | };
--------------------------------------------------------------------------------
/extensions/wtkzq/complex/Complex.js:
--------------------------------------------------------------------------------
1 | // 作者: 微调控制器@CCW
2 | import { wtkzq_complex_icon, wtkzq_complex_pic } from './assets/index.js'
3 | const extensionId = 'wtkzq.complex';
4 |
5 | class Complex {
6 | constructor(runtime) {
7 | this.runtime = runtime;
8 | this._formatMessage = runtime.getFormatMessage({
9 | 'zh-cn': {
10 | 'complex.name': '复数',
11 | 'complex.real': '[A]的实部',
12 | 'complex.imag': '[A]的虚部',
13 | 'complex.round': '四舍五入[A]',
14 | 'complex.func': '[FUNC] [A]',
15 | 'complex.abs': '绝对值',
16 | 'complex.floor': '向下取整',
17 | 'complex.ceiling': '向上取整',
18 | 'complex.sqrt': '平方根',
19 | 'complex.convertAbj': '将[A]转换为a+bj形式',
20 | },
21 |
22 | en: {
23 | 'complex.name': 'Complex',
24 | 'complex.real': 'the real part of [A]',
25 | 'complex.imag': 'the imag part of [A]',
26 | 'complex.round': 'round [A]',
27 | 'complex.func': '[FUNC] of [A]',
28 | 'complex.abs': 'abs',
29 | 'complex.floor': 'floor',
30 | 'complex.ceiling': 'ceiling',
31 | 'complex.sqrt': 'sqrt',
32 | 'complex.convertAbj': 'convert [A] to a+bj',
33 | },
34 | });
35 | }
36 |
37 | formatMessage(id) {
38 | return this._formatMessage({
39 | id,
40 | default: id,
41 | description: id,
42 | });
43 | }
44 |
45 | getInfo() {
46 | return {
47 | id: extensionId,
48 | name: this.formatMessage('complex.name'),
49 | blockIconURI: wtkzq_complex_icon,
50 | menuIconURI: wtkzq_complex_icon,
51 | color1: '#565656',
52 | color2: '#404040',
53 | color3: '#393939',
54 | docsURI: 'https://learn.ccw.site/article/d0752e24-de42-4583-a357-59e36973c0a9',
55 | blocks: [
56 | {
57 | opcode: 'j',
58 | blockType: Scratch.BlockType.REPORTER,
59 | text: '[NUM]j',
60 | arguments: {
61 | NUM: {
62 | type: Scratch.ArgumentType.NUMBER,
63 | },
64 | },
65 | },
66 | {
67 | opcode: 'addJ',
68 | blockType: Scratch.BlockType.REPORTER,
69 | text: '[REAL]+[IMAG]j',
70 | arguments: {
71 | REAL: {
72 | type: Scratch.ArgumentType.NUMBER,
73 | },
74 | IMAG: {
75 | type: Scratch.ArgumentType.NUMBER,
76 | },
77 | },
78 | },
79 | {
80 | opcode: 'real',
81 | blockType: Scratch.BlockType.REPORTER,
82 | text: this.formatMessage('complex.real'),
83 | arguments: {
84 | A: {
85 | type: Scratch.ArgumentType.STRING,
86 | },
87 | },
88 | },
89 | {
90 | opcode: 'imag',
91 | blockType: Scratch.BlockType.REPORTER,
92 | text: this.formatMessage('complex.imag'),
93 | arguments: {
94 | A: {
95 | type: Scratch.ArgumentType.STRING,
96 | },
97 | },
98 | },
99 | {
100 | opcode: 'add',
101 | blockType: Scratch.BlockType.REPORTER,
102 | text: '[A]+[B]',
103 | arguments: {
104 | A: {
105 | type: Scratch.ArgumentType.STRING,
106 | },
107 | B: {
108 | type: Scratch.ArgumentType.STRING,
109 | },
110 | },
111 | },
112 | {
113 | opcode: 'sub',
114 | blockType: Scratch.BlockType.REPORTER,
115 | text: '[A]-[B]',
116 | arguments: {
117 | A: {
118 | type: Scratch.ArgumentType.STRING,
119 | },
120 | B: {
121 | type: Scratch.ArgumentType.STRING,
122 | },
123 | },
124 | },
125 | {
126 | opcode: 'mul',
127 | blockType: Scratch.BlockType.REPORTER,
128 | text: '[A]*[B]',
129 | arguments: {
130 | A: {
131 | type: Scratch.ArgumentType.STRING,
132 | },
133 | B: {
134 | type: Scratch.ArgumentType.STRING,
135 | },
136 | },
137 | },
138 | {
139 | opcode: 'div',
140 | blockType: Scratch.BlockType.REPORTER,
141 | text: '[A]/[B]',
142 | arguments: {
143 | A: {
144 | type: Scratch.ArgumentType.STRING,
145 | },
146 | B: {
147 | type: Scratch.ArgumentType.STRING,
148 | },
149 | },
150 | },
151 | {
152 | opcode: 'round',
153 | blockType: Scratch.BlockType.REPORTER,
154 | text: this.formatMessage('complex.round'),
155 | arguments: {
156 | A: {
157 | type: Scratch.ArgumentType.STRING,
158 | },
159 | },
160 | },
161 | {
162 | opcode: 'func',
163 | blockType: Scratch.BlockType.REPORTER,
164 | text: this.formatMessage('complex.func'),
165 | arguments: {
166 | FUNC: {
167 | type: Scratch.ArgumentType.STRING,
168 | menu: 'FUNC',
169 | },
170 | A: {
171 | type: Scratch.ArgumentType.STRING,
172 | },
173 | },
174 | },
175 | {
176 | opcode: 'convertAbj',
177 | blockType: Scratch.BlockType.REPORTER,
178 | text: this.formatMessage('complex.convertAbj'),
179 | arguments: {
180 | A: {
181 | type: Scratch.ArgumentType.STRING,
182 | },
183 | },
184 | },
185 | ],
186 | menus: {
187 | FUNC: {
188 | items: [
189 | {
190 | text: this.formatMessage('complex.abs'),
191 | value: 'abs',
192 | },
193 | {
194 | text: this.formatMessage('complex.floor'),
195 | value: 'floor',
196 | },
197 | {
198 | text: this.formatMessage('complex.ceiling'),
199 | value: 'ceiling',
200 | },
201 | {
202 | text: this.formatMessage('complex.sqrt'),
203 | value: 'sqrt',
204 | },
205 | ],
206 | acceptReporters: false,
207 | },
208 | },
209 | };
210 | }
211 |
212 | j({ NUM }) {
213 | NUM = Scratch.Cast.toNumber(NUM);
214 | return `0,${NUM}`;
215 | }
216 |
217 | addJ({ REAL, IMAG }) {
218 | REAL = Scratch.Cast.toNumber(REAL);
219 | IMAG = Scratch.Cast.toNumber(IMAG);
220 | return `${REAL},${IMAG}`;
221 | }
222 |
223 | real({ A }) {
224 | A = Scratch.Cast.toString(A);
225 | return this._to_complex(A)[0];
226 | }
227 |
228 | imag({ A }) {
229 | A = Scratch.Cast.toString(A);
230 | return this._to_complex(A)[1];
231 | }
232 |
233 | add({ A, B }) {
234 | A = Scratch.Cast.toString(A);
235 | B = Scratch.Cast.toString(B);
236 | A = this._to_complex(A);
237 | B = this._to_complex(B);
238 | return this._to_comma([A[0] + B[0], A[1] + B[1]]);
239 | }
240 |
241 | sub({ A, B }) {
242 | A = Scratch.Cast.toString(A);
243 | B = Scratch.Cast.toString(B);
244 | A = this._to_complex(A);
245 | B = this._to_complex(B);
246 | return this._to_comma([A[0] - B[0], A[1] - B[1]]);
247 | }
248 |
249 | _to_complex(A) {
250 | if (A.includes(',')) {
251 | return A.split(',').map(Number);
252 | }
253 | return [Number(A), 0];
254 | }
255 |
256 | _to_comma(A) {
257 | return `${A[0]},${A[1]}`;
258 | }
259 |
260 | mul({ A, B }) {
261 | A = Scratch.Cast.toString(A);
262 | B = Scratch.Cast.toString(B);
263 | A = this._to_complex(A);
264 | B = this._to_complex(B);
265 | return this._to_comma([A[0] * B[0] - A[1] * B[1], A[1] * B[0] + A[0] * B[1]]);
266 | }
267 |
268 | div({ A, B }) {
269 | A = Scratch.Cast.toString(A);
270 | B = Scratch.Cast.toString(B);
271 | A = this._to_complex(A);
272 | B = this._to_complex(B);
273 | return this._to_comma([
274 | (A[0] * B[0] + A[1] * B[1]) / (B[0] * B[0] + B[1] * B[1]),
275 | (A[1] * B[0] - A[0] * B[1]) / (B[0] * B[0] + B[1] * B[1]),
276 | ]);
277 | }
278 |
279 | func({ FUNC, A }) {
280 | A = Scratch.Cast.toString(A);
281 | A = this._to_complex(A);
282 | if (FUNC === 'abs') return Math.sqrt(A[0] * A[0] + A[1] * A[1]);
283 | if (FUNC === 'floor') return this._to_comma(A.map(Math.floor));
284 | if (FUNC === 'ceiling') return this._to_comma(A.map(Math.ceil));
285 | if (FUNC === 'sqrt')
286 | if (A[1] == 0)
287 | if (A[0] >= 0) return this._to_comma([Math.sqrt(A[0]), 0]);
288 | else return this._to_comma([0, Math.sqrt(-A[0])]);
289 | else return NaN;
290 | }
291 |
292 | round({ A }) {
293 | A = Scratch.Cast.toString(A);
294 | return this._to_comma(this._to_complex(A).map(Math.round));
295 | }
296 |
297 | convertAbj({ A }) {
298 | A = Scratch.Cast.toString(A);
299 | A = this._to_complex(A);
300 | if (A[1] < 0) return `${A[0]}${A[1]}j`;
301 | return `${A[0]}+${A[1]}j`;
302 | }
303 | }
304 |
305 | window.tempExt = {
306 | Extension: Complex,
307 | info: {
308 | name: 'complex.name',
309 | description: 'complex.description',
310 | extensionId,
311 | iconURL: wtkzq_complex_pic,
312 | insetIconURL: wtkzq_complex_icon,
313 | featured: true,
314 | disabled: false,
315 | docsURI: 'https://learn.ccw.site/article/d0752e24-de42-4583-a357-59e36973c0a9',
316 | collaborator: '微调控制器@CCW',
317 | collaboratorList: [
318 | {
319 | collaborator: '微调控制器@CCW',
320 | collaboratorURL:
321 | 'https://www.ccw.site/student/6231db9f76f82e3c2a3d428e',
322 | },
323 | ]
324 | },
325 | l10n: {
326 | 'zh-cn': {
327 | 'complex.name': '复数',
328 | 'complex.description': '复数运算',
329 | },
330 | en: {
331 | 'complex.name': 'Complex',
332 | 'complex.description': 'Complex operation',
333 | },
334 | },
335 | }
336 |
--------------------------------------------------------------------------------
/extensions/wtkzq/complex/assets/index.js:
--------------------------------------------------------------------------------
1 | export const wtkzq_complex_pic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAF0CAYAAAD/4EcMAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiUAABYlAUlSJPAAABgjSURBVHhe7d17zJd1/fjxNyIgJ/UWVFJIEQWmIsqalTmnLu20mWZhOcuGmXnAQ5gddB7zj2yzYeV0zY6uZjnLLJubNUpnNvOECAQKHlAQVEQOAnL4/t6f3+u76qv3h8998+a+r+tzPx6b4/W6dFQa8tznc13vq9/UqVO3JgAAitkpfgQAoBCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAKE1gAAIUJLACAwgQWAEBhAgsAoDCBBQBQmMACAChMYAEAFCawAAAK6zd16tStMQNt7tRTT40JeteiRYvSwoUL05o1a+IKtBeBBX3EsGHD0i233JIGDBgQV6B3bdy4Mc2aNSvddtttcQXah68IoY8YNWqUuKJSBg4cmE488cR00UUXxRVoHwIL+oj3vOc9MUG1HHXUUemYY46JDdqDwII+YsKECTFB9UyZMiUmaA8CC/qI0aNHxwTVs9tuu8UE7UFgQR/Qr18/gUWlrV27NiZoDwIL+oAxY8ak4cOHxwbVk49sgHYisKAPyIEFVfX444+n3//+97FBexBY0AeMHz8+JqiWfA7W97///bR1qyMZaS8OGoU+4IorrkiTJk2KrXP3339/evXVV2ODHeeFF15IK1asaPwI7UhgQR+QPyHYa6+9YuvcjBkz0pIlS2IDoLt8RQhtbuTIkWnEiBGxdW79+vWNTxQA2H4CC9rcAQcckPr37x9b55599tm0YcOG2ADYHgIL2ty4ceNiau6ZZ56JCYDtJbCgzR100EExNTd//vyYANheAgva3B577BFTc8uXL48JgO0lsKCNDRgwIHV0dMTWuXwG0apVq2IDYHsJLGhj+YDRXXbZJbbO5aMZVq9eHRsA20tgQRubMGFCTM25/wqgLIEFbezQQw+Nqbl58+bFBEAJAgvaWCsHjGaLFi2KCYASBBa0sWHDhsXU3Nq1a2MCoASBBW1qzJgxLQXWunXrBBZAYQIL2lSr91899dRTafPmzbEBUILAgjY1adKkmJqbPXt2TACUIrCgTe2///4xNSewAMoTWNCmWjlgNFuzZk1MAJQisKANDRkypOXA2rhxY0zU2cknnxwTUAUCC9rQ4Ycfnvr37x9b55555pm0adOm2KirffbZJ5122mmxAVUgsKANHXHEETE19+ijj8ZEXeVPKq+//vq0007+dQ5V4lcktKHJkyfH1LmtW7emxx57LDbqaOedd05XXnll4ythoFoEFrSZ/EnG4MGDY+tcPvvq+eefj406+tjHPpbGjRsXG1AlAgvaTL73qpX7r9x7VW+HHXZYOuOMM2IDqkZgQZsZP358S4H15ptvNr4mpH46OjrSBRdcEBtQRQIL2syUKVNiau7xxx+PiToZMGBAmj59etptt93iClBFAgvazPve976YmnvyySdjok5OOeWUdMghh8QGVJXAgjYycODANGrUqNg69/bbbwusGsqfTp566qmxAVUmsKCNtHp6+1tvveUm95rJT4a67wrqQ2BBGxk5cmRMzeXAol6uuOKKNHTo0NiAqhNY0EYmTZoUU3MLFy6MiTo455xz0oEHHhgbUAcCC9pIKye4Z08//XRMVF2+7+qYY46JDagLgQVtIt/gPmHChNg6t2XLFoFVE8OHD08XX3xx45U4QL0ILGgTw4YNa+k34jVr1qRXXnklNqoqHxZ7zTXXpEGDBsUVoE4EFrSJVg+ezCe4U239+vVL06ZNS/vuu29cAepGYEGbyK/IacWrr74aE1V16KGHpg9/+MOxAXUksKBNTJw4Mabm5syZExNVtM8++zSOZADqTWBBG8hfKbX6+pQFCxbERNXke+hmzJgRG1BnAgvaQEdHR0v3YOUDRp2BVV1nnXVWGj16dGxAnQksaAM5sFqR77/KxzRQPUcffXQ67rjjYgPqTmBBG9hvv/1iau61116LiSrJ//zOPvvsxle9QHsQWNAGxo0bF1Nzvh6snnzf1YUXXtjyi7qBehBY0AZaPaJh0aJFMVEV5513nvuuoA0JLKi5fIL7mDFjYutcvvdq8eLFsVEFH/3oR9OHPvSh2IB2IrCg5vbee++W7t1Zvnx5WrlyZWz0tnxK+2c/+9nYgHYjsKDmWn2dyrJly2Kit+UXc1999dVp8ODBcQVoNwILaq7VG9yXLFkSE73tkksuSbvuumtsQDsSWFBz733ve2NqzhOE1XDCCSekKVOmxLZtzi2DehJYUGP9+/f3CVaN7LHHHulLX/pSbK25+eabYwLqRGBBjeX7rwYNGhRb51avXi2well+2vOmm26KrTVPPPFEeuCBB2ID6kRgQY21cjxD9vzzz8dEbzn//PPTgAEDYtu2/N7IG264ITagbgQW1NiBBx4YU3MvvvhiTPSGT37yk12672rDhg1pxowZafPmzXEFqBuBBTXW6hENbnDvPRMnTkxTp06NrTU/+9nPvDcSak5gQY21+oqVpUuXxkRPyje1T58+vfG+wVY99NBD6c9//nNsQF0JLKipPffcM40YMSK2zm3atCm98sorsdFT8un6X/nKV9LIkSPjyrblBxFmzpwZG1BnAgtqauzYsTE198ILL6S1a9fGRk859thj0+TJk2PbtnXr1nX5KUOgugQW1NT48eNjam7RokUx0ZPyi5y74rbbbvO0J7QRgQU11eoJ7s8880xM9KT8FW6r7rvvvvTggw/GBrQDgQU1tddee8XUXP6KkJ7XygGw2fLly9OPf/zj2IB2IbCghvJv3q0G1sqVK2OiJy1evDim5q688sqYgHYisKCGDjrooMZ7CLclx5XA6h0PP/xwTJ277rrr/POBNiWwoIYOPvjgmJqbO3du2rp1a2z0pHvvvTf94x//iO2dclzNmTMnNqDdCCyooQkTJsTU3Pz582Oip23ZsiXdfPPNjfcJPv3002nVqlVp48aN6e67724cPiquoL0JLKihjo6OmJrzipzetX79+vToo4+ma6+9Nn35y19On//859Mvf/nLxo3tQHsTWFBDrQbWm2++GRMAPUlgQc3k86+GDBkSW+fyK3JWr14dGwA9SWBBzUyaNCmm5v71r3817vkBoOcJLKiZww8/PKbmZs+eHRMAPU1gQc3svffeMTXnKTWA3iOwoGaGDh0aU3MrVqyICYCeJrCgRoYPH95yYOUjAgDoHQILamTy5MmpX79+sXUuf3q1YcOG2ADoaQILauT9739/TM098sgjMQHQGwQW1MjEiRNjau6xxx6LCYDeILCgRgYMGBBT5/I78BYsWBAbAL2h39SpU71qH2pgp512Srfffnvq379/XHl3+QT3M888s/FjVeX/LUcffXQ6/vjj06hRo1q6r6w3rFu3Lj3xxBPp3nvv7dWnMu+4446YmjvttNNiAnqbwIKaOPjgg9NVV10VW+dyFEybNi1t3VrNX9oDBw5Ml19+ectfd1ZF/ns/f/782HqWwIL68RUh1MRRRx0VU3NPPfVUZeMqO+uss2oXV9k111yTdtttt9gAmhNYUBMf/OAHY2quyk8Qjhw5Mh177LGx1U+r/wwABBZ00+mnn56GDBkS246Vb24fNmxYbJ3Ln1xV+QnCPffcM6Z6yueQAbRCYEE3XHjhhemkk05KJ554YlzZsfJ9S63Ip7evXbs2turJN7fX2c477xwTQHMCC7oo3+yc74fKT7596lOfSh0dHfFndpxWP/mp+utxVq5cGVM99dZN7kD9CCxoUf705brrrms8zfe/xwoMGjSo8UnWjjZlypSYmlu2bFlM1bR06dI0d+7c2OrnoYceigmgOYEFLcghdfXVV6fx48fHlX877rjj0t577x3bjnHkkUfG1FzVT3DP94h973vfS6tWrYor9TFz5sxGIAK0QmDBNgwfPrzxteCECRPiyn8bPHhw46vCHSXffzV27NjYmps9e3ZM1fXmm2+miy++ON1///2Vvl8sy0GYP3H79re/7dMroEscNApN7LHHHo1DMUePHh1X3t2GDRvSN7/5zfTSSy/FlXLyPV633HJLbJ1766230he/+MXY6iMHbFVPcs9/T99+++3Yeo+DRqF+fIIFndhrr70an1xsK66y/BXiF77whdjKavVwy9WrV8dUL/m/d/5Uq4p/VCGugHoSWPAu8j1VN9xwQxoxYkRc2bbDDjssHXTQQbGV0+rPWcf7mgDalcCC/2PfffdNN910U+Peqq7ITxmed955sZVzxBFHxNTcvHnzYgKgtwks+A/jxo1LN954Y2xdt88++7QcRK3I0dbq6eFz5syJCYDeJrAg5LOm8j1X2+vcc8+NafvtvvvuLZ0evnnz5rRgwYLYAOhtAgv+nxNOOCFdeumlRV7lsuuuu6ajjz46tu3T6inxr732WuOJNwCqQWDR5+VH288666zUv3//uLJ98pED+ecr8d66Vp5gzF5//fWYAKgCgUWfNm3atMYhoaXPYco3yB9//PGxdV9+LU8rlixZEhMAVSCw6LPOP//89JGPfCS2snKwnXnmmWno0KFxpXsOOeSQmJrzEmKAahFY9Dn5q7tLLrkkHXPMMXGldStWrEhvvPFGbM3l/5yPf/zjsXVdvpdrzz33jK25hQsXxgRAFQgs+pT81V2+mf0DH/hAXGndiy++mKZPn954WfGWLVvianOf/vSnG6/b6Y58knwr8gGjy5Ytiw2AKhBY9BnDhg1L3/rWt7p1TtWzzz7bCLP88t/8ddxzzz0Xf2bbuvsi6HymViuWLl0aEwBVIbDoE/K7Aq+99to0fvz4uNK6HFQ5zP5TfvlyPnuqFfkIiFafBvxP+dDTVggsgOoRWPQJM2bMaLwCp6ueeuqpdNVVV8X2b88//3x6+umnY9u2qVOnxtS6Vt9BmD9dA6BaBBZ9QqtP4/2nhx9+uOnJ7j/84Q9j2rYjjzwyTZw4MbZtGzBgQDrggANia27x4sUxAVAVAou2l++56uqhn3/84x/TzJkzY3t3+WnCBx98MLbm8rENXfkUK3+l2MrZXG+//XZ64YUXYgOgKgQWbS9HSFfcfvvtjT9aeVLwtttua/nnz5+i5fcdtmLMmDExNZcPGN24cWNsAFSFwKLtdeWJv5tvvjn94Q9/aPkYhnXr1qV77rkntm37zGc+E1Nz+++/f0zN5aMjAKgegUXbW7NmTZo7d25sncvnW/31r39tHMXQFTnIVq9eHVtz+b6qVg44bTWw3H8FUE0Ciz7hBz/4QXrppZdi+29vvfVW+s53vtO4qb071q5dm+68887Ytu2UU06JqXP77bdfTM3lpxkBqB6BRZ/w2muvNQ4Kveuuuxo3hef7pvKnP3/5y1/SBRdckB577LH4K7tn1qxZ6fXXX4+tuXyAaLNX6IwaNapxKGorXn755ZgAqBKBRZ+R76u644470te+9rV0xhlnpG984xvp1ltvbXyFuL3Wr1+ffvrTn8a2bSeddFJM79TqAaPLly9PK1eujA2AKhFYUMgjjzzSeC9gKzo6OtLnPve52P5bq+dfOWAUoLoEFhSSPyG7/vrrY9u2E088Me200zt/CbZ6RIPAAqgugQUF5ZvOly1bFltzQ4YMSeeee25s/9bqS57d4A5QXQILCssvlW7VUUcd9V+nzA8dOjSNHDkytubyPVgAVJPAgsLyE4vz58+PrbkcV5dddllsqfG+wlZekbNhw4a0YsWK2ACoGoEFO8B3v/vdtGnTptiamzx5ctp9990bc6svhF6wYEHavHlzbABUjcCCHSAf/fDPf/4ztm376le/2vhx7NixjR+3Zd68eTEBUEUCC3aQfHp8PiW+FRMmTEijR49u+f6rhQsXxgRAFQks2EHyafH53YatOu+889KIESNia+7VV1+NCYAqEliwA/3kJz9p+fDRfIL7wIEDY2vujTfeiAmAKhJYsIPde++9MZWxZMmStG7dutgAqCKBBTvY7373u/TKK6/Etv1mz54dEwBVJbCgB9x1110xbT+BBVB9Agt6wKxZs9KiRYti2z4vv/xyTABUlcCCHnLnnXfGtH3Wrl0bEwBVJbCghzz66KNpzpw5sXVPPldLYAFUn8CCHnTHHXfE1D1PPPFE2rp1a2wAVJXAgh6U3yH497//Pbau68rrdwDoPQILethvfvObmLomf3I1d+7c2ACoMoEFPeyll15K9913X2xd44BRgHoQWNALfvvb33brXqr8fkMAqk9gQS9YuXJl+vWvfx1ba/I5Wps3b44NgCoTWNBL/vSnP3UpmB5++OGYAKg6gQW9JJ9p9aMf/Si2bduepw8B6FkCC3rR3/72t5buq8p/zYoVK2IDoOr6TZ061amF0IumTJmSvv71r8f27vLp7dOmTYuNquvo6Egnn3xyGj9+fOrXr19c7b6xY8fG1NzixYtj6pqNGzemRx55JN1zzz1xBdheAgsq4JZbbmn8ptyZZcuWpYsuuig2quwTn/hEOu2009KgQYPiSn3khy9mzpyZ5s2bF1eA7vIVIVTAjTfe2PTYhscffzwmquzwww9Pp59+ei3jKsuRf9lll6VddtklrgDdJbCgAvIrdJ599tnY3im/KJrqmz59etp5551jq6chQ4akc845JzaguwQWVER+onDTpk2x/Vu+5hU59dAun/xMmjQpJqC7BBZUxHPPPZeefPLJ2P4t3+DugNF6KHFDexXstJPfGmB7+VUEFfLzn//8Hcc2rFmzJiaq7t0+gayj9evXxwR0l8CCCslPCz700EOx/X/dffSenvfiiy/GVF/5YYuuvsYJeCeBBRXzi1/84r+eKJw9e3ZMVN3ll1+eVq9eHVv95P/fLV26NM2aNSuuAN0lsKBi8m/Qd999d2wCq27OP//82n7qeN9996VLL700NmB7OGgUKiifo3Trrbc27sc6++yz4yp1ks+UGjx4cC1ufN+yZUsj7N3vB+UILKioE044ofFHPvgRgHrxFSFU1AMPPJAWLVoUGwB1IrCgovKj8r/61a9iA6BOBBZU2KpVq2ICoE4EFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgMIEFgBAYQILAKAwgQUAUJjAAgAoTGABABQmsAAAChNYAACFCSwAgKJS+h84aOj0OxrJYgAAAABJRU5ErkJggg=='
2 | export const wtkzq_complex_icon = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI3OS43NzYyNiIgaGVpZ2h0PSI3OS43NzYyNiIgdmlld0JveD0iMCwwLDc5Ljc3NjI2LDc5Ljc3NjI2Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjgwLjExMTg3LC0xNDAuMTExODcpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC4xMTE4NywxODBjMCwtMjIuMDI5NjEgMTcuODU4NTIsLTM5Ljg4ODEzIDM5Ljg4ODEzLC0zOS44ODgxM2MyMi4wMjk2MSwwIDM5Ljg4ODEzLDE3Ljg1ODUyIDM5Ljg4ODEzLDM5Ljg4ODEzYzAsMjIuMDI5NjEgLTE3Ljg1ODUyLDM5Ljg4ODEzIC0zOS44ODgxMywzOS44ODgxM2MtMjIuMDI5NjEsMCAtMzkuODg4MTMsLTE3Ljg1ODUyIC0zOS44ODgxMywtMzkuODg4MTN6IiBmaWxsPSIjNTY1NjU2IiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9ImJ1dHQiLz48cGF0aCBkPSJNMjg3Ljg3NjQ1LDE4OS43MTE5OGw0LjIzMDU2LC0zLjkyODM4bDYuMzQ1ODQsMTEuNzg1MTNsNi45NTAyLC0zNi4yNjE5NGg0Mi4zMDU1OSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zMTQuMzEzMTMsMTgzLjg1ODI3aDEwLjE3MDYxIiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTMyOS44MzY2OSwxNzcuOTcwMDNsNS4wODUzMSwtNi42OTExOXYyNy44MzUzNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zNDAuMDA3MywxOTkuMzgxODRoLTEwLjk3MzU1IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz4='
3 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Getting Started
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-extension",
3 | "version": "1.0.0",
4 | "description": "Custom extension repository for Gandi IDE.",
5 | "main": "index.js",
6 | "directories": {
7 | "example": "example"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "check-format": "prettier . --check",
12 | "format": "prettier . --write",
13 | "lint": "eslint . --max-warnings=0",
14 | "prepare": "husky",
15 | "lint:fix":"eslint . --fix"
16 | },
17 | "lint-staged": {
18 | "*.{js,jsx,ts,tsx}": [
19 | "prettier --write",
20 | "eslint --ext .js,.jsx,.ts,.tsx --fix",
21 | "git add",
22 | "eslint --ext .js,.jsx,.ts,.tsx"
23 | ],
24 | "*.{css,scss}": [
25 | "prettier --write",
26 | "stylelint --fix",
27 | "git add"
28 | ],
29 | "*.{html,json,md}": [
30 | "prettier --write",
31 | "git add"
32 | ]
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git+https://github.com/Gandi-IDE/custom-extension.git"
37 | },
38 | "keywords": [
39 | "scratch",
40 | "extension",
41 | "turbowarp",
42 | "gandi",
43 | "cocrea"
44 | ],
45 | "author": "all contributors",
46 | "license": "LGPL-3.0-only",
47 | "bugs": {
48 | "url": "https://github.com/Gandi-IDE/custom-extension/issues"
49 | },
50 | "homepage": "https://github.com/Gandi-IDE/custom-extension#readme",
51 | "devDependencies": {
52 | "eslint": "^8.57.0",
53 | "husky": "^9.0.11",
54 | "lint-staged": "^15.2.7",
55 | "prettier": "^3.2.5",
56 | "stylelint": "^16.6.1"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/useEslint.md:
--------------------------------------------------------------------------------
1 |
2 | # How to use eslint for code style control of your extension
3 | ## 1. Install yarn
4 | ```bash
5 | npm install -g yarn
6 | ```
7 | Note: Here we use global installation, if you have already installed, you can ignore it.
8 | ## 2. Install related dependencies
9 | ```bash
10 | yarn
11 | ```
12 | ## Start eslint detection
13 | ```bash
14 | yarn lint
15 | ```
16 | ## Automatically fix code formatting
17 | ```bash
18 | yarn lint:fix
19 | ```
20 |
21 | `
22 |
23 |
24 |
25 |
26 | # 如何使用eslint对自己的拓展码风进行控制
27 | ## 1. 安装yarn
28 | ```bash
29 | npm install -g yarn
30 | ```
31 | 注:这里使用全局安装,如果你已经安装可以忽略。
32 | ## 2. 安装相关依赖
33 | ```bash
34 | yarn
35 | ```
36 | ## 启动eslint检测
37 | ```bash
38 | yarn lint
39 | ```
40 | ## 对代码格式进行自动修复
41 | ```bash
42 | yarn lint:fix
43 | ```
--------------------------------------------------------------------------------
/utils/assets/arrow_left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/assets/arrow_right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utils/cast.js:
--------------------------------------------------------------------------------
1 | import Color from './color.js'
2 | /**
3 | * @fileoverview
4 | * Utilities for casting and comparing Scratch data-types.
5 | * Scratch behaves slightly differently from JavaScript in many respects,
6 | * and these differences should be encapsulated below.
7 | * For example, in Scratch, add(1, join("hello", world")) -> 1.
8 | * This is because "hello world" is cast to 0.
9 | * In JavaScript, 1 + Number("hello" + "world") would give you NaN.
10 | * Use when coercing a value before computation.
11 | */
12 |
13 | class Cast {
14 | /**
15 | * Scratch cast to number.
16 | * Treats NaN as 0.
17 | * In Scratch 2.0, this is captured by `interp.numArg.`
18 | * @param {*} value Value to cast to number.
19 | * @return {number} The Scratch-casted number value.
20 | */
21 | static toNumber (value) {
22 | // If value is already a number we don't need to coerce it with
23 | // Number().
24 | if (typeof value === 'number') {
25 | // Scratch treats NaN as 0, when needed as a number.
26 | // E.g., 0 + NaN -> 0.
27 | if (Number.isNaN(value)) {
28 | return 0;
29 | }
30 | return value;
31 | }
32 | const n = Number(value);
33 | if (Number.isNaN(n)) {
34 | // Scratch treats NaN as 0, when needed as a number.
35 | // E.g., 0 + NaN -> 0.
36 | return 0;
37 | }
38 | return n;
39 | }
40 |
41 | /**
42 | * Scratch cast to boolean.
43 | * In Scratch 2.0, this is captured by `interp.boolArg.`
44 | * Treats some string values differently from JavaScript.
45 | * @param {*} value Value to cast to boolean.
46 | * @return {boolean} The Scratch-casted boolean value.
47 | */
48 | static toBoolean (value) {
49 | // Already a boolean?
50 | if (typeof value === 'boolean') {
51 | return value;
52 | }
53 | if (typeof value === 'string') {
54 | // These specific strings are treated as false in Scratch.
55 | if ((value === '') ||
56 | (value === '0') ||
57 | (value.toLowerCase() === 'false')) {
58 | return false;
59 | }
60 | // All other strings treated as true.
61 | return true;
62 | }
63 | // Coerce other values and numbers.
64 | return Boolean(value);
65 | }
66 |
67 | /**
68 | * Scratch cast to string.
69 | * @param {*} value Value to cast to string.
70 | * @return {string} The Scratch-casted string value.
71 | */
72 | static toString (value) {
73 | return String(value);
74 | }
75 |
76 | /**
77 | * Cast any Scratch argument to an RGB color array to be used for the renderer.
78 | * @param {*} value Value to convert to RGB color array.
79 | * @return {Array.} [r,g,b], values between 0-255.
80 | */
81 | static toRgbColorList (value) {
82 | const color = Cast.toRgbColorObject(value);
83 | return [color.r, color.g, color.b];
84 | }
85 |
86 | /**
87 | * Cast any Scratch argument to an RGB color object to be used for the renderer.
88 | * @param {*} value Value to convert to RGB color object.
89 | * @return {RGBOject} [r,g,b], values between 0-255.
90 | */
91 | static toRgbColorObject (value) {
92 | let color;
93 | if (typeof value === 'string' && value.substring(0, 1) === '#') {
94 | color = Color.hexToRgb(value);
95 |
96 | // If the color wasn't *actually* a hex color, cast to black
97 | if (!color) color = {r: 0, g: 0, b: 0, a: 255};
98 | } else {
99 | color = Color.decimalToRgb(Cast.toNumber(value));
100 | }
101 | return color;
102 | }
103 |
104 | /**
105 | * Determine if a Scratch argument is a white space string (or null / empty).
106 | * @param {*} val value to check.
107 | * @return {boolean} True if the argument is all white spaces or null / empty.
108 | */
109 | static isWhiteSpace (val) {
110 | return val === null || (typeof val === 'string' && val.trim().length === 0);
111 | }
112 |
113 | /**
114 | * Compare two values, using Scratch cast, case-insensitive string compare, etc.
115 | * In Scratch 2.0, this is captured by `interp.compare.`
116 | * @param {*} v1 First value to compare.
117 | * @param {*} v2 Second value to compare.
118 | * @returns {number} Negative number if v1 < v2; 0 if equal; positive otherwise.
119 | */
120 | static compare (v1, v2) {
121 | let n1 = Number(v1);
122 | let n2 = Number(v2);
123 | if (n1 === 0 && Cast.isWhiteSpace(v1)) {
124 | n1 = NaN;
125 | } else if (n2 === 0 && Cast.isWhiteSpace(v2)) {
126 | n2 = NaN;
127 | }
128 | if (isNaN(n1) || isNaN(n2)) {
129 | // At least one argument can't be converted to a number.
130 | // Scratch compares strings as case insensitive.
131 | const s1 = String(v1).toLowerCase();
132 | const s2 = String(v2).toLowerCase();
133 | if (s1 < s2) {
134 | return -1;
135 | } else if (s1 > s2) {
136 | return 1;
137 | }
138 | return 0;
139 | }
140 | // Handle the special case of Infinity
141 | if (
142 | (n1 === Infinity && n2 === Infinity) ||
143 | (n1 === -Infinity && n2 === -Infinity)
144 | ) {
145 | return 0;
146 | }
147 | // Compare as numbers.
148 | return n1 - n2;
149 | }
150 |
151 | /**
152 | * Determine if a Scratch argument number represents a round integer.
153 | * @param {*} val Value to check.
154 | * @return {boolean} True if number looks like an integer.
155 | */
156 | static isInt (val) {
157 | // Values that are already numbers.
158 | if (typeof val === 'number') {
159 | if (isNaN(val)) { // NaN is considered an integer.
160 | return true;
161 | }
162 | // True if it's "round" (e.g., 2.0 and 2).
163 | return val === parseInt(val, 10);
164 | } else if (typeof val === 'boolean') {
165 | // `True` and `false` always represent integer after Scratch cast.
166 | return true;
167 | } else if (typeof val === 'string') {
168 | // If it contains a decimal point, don't consider it an int.
169 | return val.indexOf('.') < 0;
170 | }
171 | return false;
172 | }
173 |
174 | static get LIST_INVALID () {
175 | return 'INVALID';
176 | }
177 |
178 | static get LIST_ALL () {
179 | return 'ALL';
180 | }
181 |
182 | /**
183 | * Compute a 1-based index into a list, based on a Scratch argument.
184 | * Two special cases may be returned:
185 | * LIST_ALL: if the block is referring to all of the items in the list.
186 | * LIST_INVALID: if the index was invalid in any way.
187 | * @param {*} index Scratch arg, including 1-based numbers or special cases.
188 | * @param {number} length Length of the list.
189 | * @param {boolean} acceptAll Whether it should accept "all" or not.
190 | * @return {(number|string)} 1-based index for list, LIST_ALL, or LIST_INVALID.
191 | */
192 | static toListIndex (index, length, acceptAll) {
193 | if (typeof index !== 'number') {
194 | if (index === 'all') {
195 | return acceptAll ? Cast.LIST_ALL : Cast.LIST_INVALID;
196 | }
197 | if (index === 'last') {
198 | if (length > 0) {
199 | return length;
200 | }
201 | return Cast.LIST_INVALID;
202 | } else if (index === 'random' || index === 'any') {
203 | if (length > 0) {
204 | return 1 + Math.floor(Math.random() * length);
205 | }
206 | return Cast.LIST_INVALID;
207 | }
208 | }
209 | index = Math.floor(Cast.toNumber(index));
210 | if (index < 1 || index > length) {
211 | return Cast.LIST_INVALID;
212 | }
213 | return index;
214 | }
215 | }
216 |
217 | // module.exports = Cast;
218 | export default Cast;
219 |
--------------------------------------------------------------------------------
/utils/color.js:
--------------------------------------------------------------------------------
1 | class Color {
2 | /**
3 | * @typedef {object} RGBObject - An object representing a color in RGB format.
4 | * @property {number} r - the red component, in the range [0, 255].
5 | * @property {number} g - the green component, in the range [0, 255].
6 | * @property {number} b - the blue component, in the range [0, 255].
7 | */
8 |
9 | /**
10 | * @typedef {object} HSVObject - An object representing a color in HSV format.
11 | * @property {number} h - hue, in the range [0-359).
12 | * @property {number} s - saturation, in the range [0,1].
13 | * @property {number} v - value, in the range [0,1].
14 | */
15 |
16 | /** @type {RGBObject} */
17 | static get RGB_BLACK () {
18 | return {r: 0, g: 0, b: 0};
19 | }
20 |
21 | /** @type {RGBObject} */
22 | static get RGB_WHITE () {
23 | return {r: 255, g: 255, b: 255};
24 | }
25 |
26 | /**
27 | * Convert a Scratch decimal color to a hex string, #RRGGBB.
28 | * @param {number} decimal RGB color as a decimal.
29 | * @return {string} RGB color as #RRGGBB hex string.
30 | */
31 | static decimalToHex (decimal) {
32 | if (decimal < 0) {
33 | decimal += 0xFFFFFF + 1;
34 | }
35 | let hex = Number(decimal).toString(16);
36 | hex = `#${'000000'.substr(0, 6 - hex.length)}${hex}`;
37 | return hex;
38 | }
39 |
40 | /**
41 | * Convert a Scratch decimal color to an RGB color object.
42 | * @param {number} decimal RGB color as decimal.
43 | * @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
44 | */
45 | static decimalToRgb (decimal) {
46 | const a = (decimal >> 24) & 0xFF;
47 | const r = (decimal >> 16) & 0xFF;
48 | const g = (decimal >> 8) & 0xFF;
49 | const b = decimal & 0xFF;
50 | return {r: r, g: g, b: b, a: a > 0 ? a : 255};
51 | }
52 |
53 | /**
54 | * Convert a hex color (e.g., F00, #03F, #0033FF) to an RGB color object.
55 | * CC-BY-SA Tim Down:
56 | * https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
57 | * @param {!string} hex Hex representation of the color.
58 | * @return {RGBObject} null on failure, or rgb: {r: red [0,255], g: green [0,255], b: blue [0,255]}.
59 | */
60 | static hexToRgb (hex) {
61 | const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
62 | hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
63 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
64 | return result ? {
65 | r: parseInt(result[1], 16),
66 | g: parseInt(result[2], 16),
67 | b: parseInt(result[3], 16)
68 | } : null;
69 | }
70 |
71 | /**
72 | * Convert an RGB color object to a hex color.
73 | * @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
74 | * @return {!string} Hex representation of the color.
75 | */
76 | static rgbToHex (rgb) {
77 | return Color.decimalToHex(Color.rgbToDecimal(rgb));
78 | }
79 |
80 | /**
81 | * Convert an RGB color object to a Scratch decimal color.
82 | * @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
83 | * @return {!number} Number representing the color.
84 | */
85 | static rgbToDecimal (rgb) {
86 | return (rgb.r << 16) + (rgb.g << 8) + rgb.b;
87 | }
88 |
89 | /**
90 | * Convert a hex color (e.g., F00, #03F, #0033FF) to a decimal color number.
91 | * @param {!string} hex Hex representation of the color.
92 | * @return {!number} Number representing the color.
93 | */
94 | static hexToDecimal (hex) {
95 | return Color.rgbToDecimal(Color.hexToRgb(hex));
96 | }
97 |
98 | /**
99 | * Convert an HSV color to RGB format.
100 | * @param {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
101 | * @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
102 | */
103 | static hsvToRgb (hsv) {
104 | let h = hsv.h % 360;
105 | if (h < 0) h += 360;
106 | const s = Math.max(0, Math.min(hsv.s, 1));
107 | const v = Math.max(0, Math.min(hsv.v, 1));
108 |
109 | const i = Math.floor(h / 60);
110 | const f = (h / 60) - i;
111 | const p = v * (1 - s);
112 | const q = v * (1 - (s * f));
113 | const t = v * (1 - (s * (1 - f)));
114 |
115 | let r;
116 | let g;
117 | let b;
118 |
119 | switch (i) {
120 | default:
121 | case 0:
122 | r = v;
123 | g = t;
124 | b = p;
125 | break;
126 | case 1:
127 | r = q;
128 | g = v;
129 | b = p;
130 | break;
131 | case 2:
132 | r = p;
133 | g = v;
134 | b = t;
135 | break;
136 | case 3:
137 | r = p;
138 | g = q;
139 | b = v;
140 | break;
141 | case 4:
142 | r = t;
143 | g = p;
144 | b = v;
145 | break;
146 | case 5:
147 | r = v;
148 | g = p;
149 | b = q;
150 | break;
151 | }
152 |
153 | return {
154 | r: Math.floor(r * 255),
155 | g: Math.floor(g * 255),
156 | b: Math.floor(b * 255)
157 | };
158 | }
159 |
160 | /**
161 | * Convert an RGB color to HSV format.
162 | * @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
163 | * @return {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
164 | */
165 | static rgbToHsv (rgb) {
166 | const r = rgb.r / 255;
167 | const g = rgb.g / 255;
168 | const b = rgb.b / 255;
169 | const x = Math.min(Math.min(r, g), b);
170 | const v = Math.max(Math.max(r, g), b);
171 |
172 | // For grays, hue will be arbitrarily reported as zero. Otherwise, calculate
173 | let h = 0;
174 | let s = 0;
175 | if (x !== v) {
176 | const f = (r === x) ? g - b : ((g === x) ? b - r : r - g);
177 | const i = (r === x) ? 3 : ((g === x) ? 5 : 1);
178 | h = ((i - (f / (v - x))) * 60) % 360;
179 | s = (v - x) / v;
180 | }
181 |
182 | return {h: h, s: s, v: v};
183 | }
184 |
185 | /**
186 | * Linear interpolation between rgb0 and rgb1.
187 | * @param {RGBObject} rgb0 - the color corresponding to fraction1 <= 0.
188 | * @param {RGBObject} rgb1 - the color corresponding to fraction1 >= 1.
189 | * @param {number} fraction1 - the interpolation parameter. If this is 0.5, for example, mix the two colors equally.
190 | * @return {RGBObject} the interpolated color.
191 | */
192 | static mixRgb (rgb0, rgb1, fraction1) {
193 | if (fraction1 <= 0) return rgb0;
194 | if (fraction1 >= 1) return rgb1;
195 | const fraction0 = 1 - fraction1;
196 | return {
197 | r: (fraction0 * rgb0.r) + (fraction1 * rgb1.r),
198 | g: (fraction0 * rgb0.g) + (fraction1 * rgb1.g),
199 | b: (fraction0 * rgb0.b) + (fraction1 * rgb1.b)
200 | };
201 | }
202 | }
203 |
204 | // module.exports = Color;
205 | export default Color;
206 |
--------------------------------------------------------------------------------
/utils/extendable_example.js:
--------------------------------------------------------------------------------
1 | import { initExpandableBlocks, getDynamicArgs } from "./use-expandable-blocks.js";
2 |
3 | const { ArgumentType } = Scratch;
4 |
5 | class ExtendableExample {
6 | constructor(runtime) {
7 | this.runtime = runtime;
8 |
9 | this._formatMessage = runtime.getFormatMessage({
10 | "zh-cn": {
11 | extensionName: "可扩展积木例",
12 | func: "执行函数[func]",
13 | join: "连接[A][B]",
14 | param: "参数",
15 | clone: "克隆[SPRITE]",
16 | },
17 | en: {
18 | extensionName: "Extendable Example",
19 | func: "execute func[func]",
20 | join: "join[A][B]",
21 | param: "param",
22 | clone: "clone[SPRITE]",
23 | },
24 | });
25 | // 注册当前扩展的可扩展积木
26 | initExpandableBlocks(this);
27 | // initExpandableBlocks(this, '+', '-');
28 | }
29 |
30 | /**
31 | * 翻译
32 | * @param {string} id
33 | * @return {string}
34 | */
35 | formatMessage(id) {
36 | return this._formatMessage({
37 | id,
38 | default: id,
39 | description: id,
40 | });
41 | }
42 |
43 | getInfo() {
44 | return {
45 | id: "extendableTest", // 拓展id
46 | name: this.formatMessage("extensionName"), // 拓展名
47 | color: "#FF8C1A", // 拓展颜色
48 | blocks: [
49 | {
50 | opcode: "join",
51 | blockType: "reporter",
52 | text: this.formatMessage("join"),
53 | arguments: {
54 | A: {
55 | type: ArgumentType.STRING,
56 | defaultValue: "A",
57 | },
58 | B: {
59 | type: ArgumentType.STRING,
60 | defaultValue: "B",
61 | },
62 | },
63 | // 设置动态参数信息
64 | dynamicArgsInfo: {
65 | // 各参数的默认值。
66 | defaultValues: ["C", "D", "E", "F", "G"],
67 | // 也可以是函数
68 | // defaultValues: (i) => 'CDEFGHIJK'[i],
69 | // 也可以是单个字符串
70 | // defaultValues: '默认值',
71 | },
72 | },
73 | {
74 | opcode: "plus",
75 | blockType: "reporter",
76 | text: "[A]+[B]",
77 | arguments: {
78 | A: {
79 | type: ArgumentType.NUMBER,
80 | defaultValue: "",
81 | },
82 | B: {
83 | type: ArgumentType.NUMBER,
84 | defaultValue: "",
85 | },
86 | },
87 | // 设置动态参数信息
88 | dynamicArgsInfo: {
89 | defaultValues: "",
90 | afterArg: "B",
91 | joinCh: "+",
92 | // 动态参数类型:
93 | // n: 数字
94 | // s: 字符串(默认)
95 | // b: 布尔
96 | dynamicArgTypes: ["n"],
97 | },
98 | },
99 | {
100 | opcode: "list",
101 | blockType: "reporter",
102 | disableMonitor: true,
103 | text: "-",
104 | // 设置动态参数信息
105 | dynamicArgsInfo: {
106 | // 第一个动态参数前的文本
107 | preText: (n) => (n === 0 ? "empty list" : "list:["),
108 | // 最后一个动态参数后的文本
109 | endText: (n) => (n === 0 ? "" : "]"),
110 | // 各参数的默认值。
111 | defaultValues: ["apple", "banana", "item"],
112 | },
113 | },
114 | {
115 | opcode: "object",
116 | blockType: "reporter",
117 | disableMonitor: true,
118 | text: "-",
119 | // 设置动态参数信息
120 | dynamicArgsInfo: {
121 | // 第一个动态参数前的文本
122 | preText: "{",
123 | // 最后一个动态参数后的文本
124 | endText: "}",
125 | // 连接符
126 | joinCh: (i) => (i % 2 === 1 ? ":" : ","),
127 | paramsIncrement: 2, // 每次增加的参数数量
128 | // 各参数的默认值。
129 | defaultValues: (i) => {
130 | const idx = Math.floor(i / 2);
131 | if (i % 2 === 1) return "key" + idx;
132 | return "value" + idx;
133 | },
134 | },
135 | },
136 | {
137 | opcode: "print",
138 | blockType: "command",
139 | text: this.formatMessage("func"),
140 | arguments: {
141 | func: {
142 | type: "string",
143 | defaultValue: "func",
144 | },
145 | },
146 | // 设置动态参数信息
147 | dynamicArgsInfo: {
148 | // 在哪个参数后面插入动态参数
149 | afterArg: "func",
150 | // endText: 动态参数末尾的文本,可以是字符串或函数。n:动态参数的数量
151 | endText: (n) => (n === 0 ? "" : ")"),
152 | // joinCh: 动态参数之间的连接字符,可以是字符串或函数。i:第 i 个参数前的连接字符
153 | joinCh: (i) => (i === 0 ? "(" : ","),
154 | // defaultValues: 动态参数的默认值,可以是字符串或函数。
155 | defaultValues: [this.formatMessage("param")],
156 | },
157 | },
158 | {
159 | opcode: "set",
160 | blockType: "command",
161 | text: "set[K]to[V]",
162 | arguments: {
163 | K: {
164 | type: "string",
165 | defaultValue: "a",
166 | },
167 | V: {
168 | type: "string",
169 | defaultValue: "b",
170 | },
171 | },
172 | dynamicArgsInfo: {
173 | // (可选)在哪个参数后面插入动态参数
174 | afterArg: "K",
175 | joinCh: ".",
176 | // 默认值可以函数指定
177 | defaultValues: "a",
178 | },
179 | },
180 | {
181 | opcode: "clone",
182 | blockType: "command",
183 | text: "clone[SPRITE]",
184 | arguments: {
185 | SPRITE: {
186 | type: "string",
187 | defaultValue: "sprite1",
188 | },
189 | },
190 | dynamicArgsInfo: {
191 | // (可选)在哪个参数后面插入动态参数
192 | afterArg: "SPRITE",
193 | // 添加参数时,每次添加的数量。i为第i次点击时(从0开始)
194 | // paramsIncrement: (i) => (i === 0 ? 1 : 2), // 第一次增加1个参数,之后增加2个参数
195 | paramsIncrement: [1, 2, 2], //也可以是数组
196 | // 连接符可以函数指定
197 | joinCh: (i) => {
198 | if (i === 0) return "with ID";
199 | if (i === 1) return "data:";
200 | if (i % 2 === 1) return ",";
201 | return "=";
202 | },
203 | // 默认值可以函数指定
204 | defaultValues: (i) => {
205 | if (i === 0) return "ID";
206 | if (i % 2 === 1) return "key";
207 | return "value";
208 | },
209 | },
210 | },
211 | ],
212 | };
213 | }
214 |
215 | join(args) {
216 | console.log(args);
217 | // 读取动态参数(数组)
218 | const dynamicArgs = getDynamicArgs(args);
219 | return String(args.A) + String(args.B) + dynamicArgs.join("");
220 | }
221 |
222 | clone(args) {
223 | const dynamicArgs = getDynamicArgs(args);
224 | console.log(dynamicArgs);
225 | }
226 |
227 | plus(args) {
228 | const dynamicArgs = getDynamicArgs(args);
229 | return Number(args.A) + Number(args.B) + dynamicArgs.reduce((a, b) => a + Number(b), 0);
230 | }
231 | }
232 | Scratch.extensions.register(new ExtendableExample(Scratch.vm.runtime));
233 |
--------------------------------------------------------------------------------