├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.json ├── .yarnrc ├── LICENSE ├── README.md ├── docs └── 拓展开发教程.md ├── extensions ├── Arkos │ ├── Archivecode.js │ ├── Archivecode新版.js │ ├── Archivecode旧版.js │ ├── advancedControls.js │ ├── assets │ │ ├── advanced_control_cover.png │ │ ├── advanced_control_icon.svg │ │ ├── cover1.png │ │ ├── cover2.png │ │ ├── icon1.svg │ │ ├── icon2.svg │ │ ├── moreDataTypes_cover.png │ │ └── moreDataTypes_icon.svg │ ├── moreDataTypes │ │ ├── SafeObject.js │ │ ├── l10n.js │ │ └── moreDataTypes.js │ ├── moreSensing.js │ ├── project.js │ ├── projectWith30.js │ └── test.js ├── DilemmaGX │ ├── 3DMath │ │ └── Three.js │ ├── Animator │ │ └── Animator.js │ └── rxfs │ │ └── rxFS.js ├── DollyPro │ ├── assets │ │ ├── cover.png │ │ └── icon.svg │ └── dollytest.js ├── Fath │ ├── CocreaFetch.js │ ├── ScratchHub │ ├── Turbowarp │ │ ├── BigInt.js │ │ ├── assets │ │ │ ├── bigint.png │ │ │ └── lilyComment.png │ │ └── comment blocks.js │ ├── assets │ │ ├── CocreaFetch.png │ │ └── lilyComment.png │ ├── banners │ │ └── CommentBlocks.png │ └── comment blocks.js ├── FurryR │ ├── README.md │ ├── lpp.js │ └── not.js.js ├── Nights │ ├── stats.js │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public │ │ │ ├── assets │ │ │ │ ├── cover.png │ │ │ │ └── icon.png │ │ │ └── info.json │ │ ├── src │ │ │ ├── block-info.js │ │ │ ├── lang.js │ │ │ ├── main.js │ │ │ └── stats.js │ │ └── vite.config.js │ └── tilemap │ │ ├── README.md │ │ ├── cover.png │ │ ├── demo.sb3 │ │ ├── icon.png │ │ ├── info.json │ │ └── tilemap.js ├── PortFromTurboWarp │ ├── NOname-awa │ │ ├── Morecomparison.js │ │ └── assets │ │ │ └── morecomparisons.png │ ├── README.md │ ├── Vadik1 │ │ └── clippingblending.js │ ├── licenses │ │ ├── GPL-3.0.txt │ │ ├── LGPL-3.0.txt │ │ └── MIT.txt │ └── stretch.js ├── Project │ ├── Project.js │ └── Project.svg ├── QuakeStudio │ ├── BetterQuake │ │ ├── BetterQuake.js │ │ ├── LICENSE.txt │ │ └── README.md │ ├── README.md │ └── assets │ │ ├── BetterQuake.png │ │ ├── QuakeStudio.png │ │ └── banner.png ├── SimonShiki │ ├── assets │ │ ├── cover.png │ │ └── icon.png │ └── scopeVar.js ├── YUEN │ └── FeishuExtension │ │ ├── .eslintrc.js │ │ ├── package.json │ │ ├── src │ │ ├── assets │ │ │ ├── cover.png │ │ │ └── icon.png │ │ ├── constants.js │ │ ├── extension.js │ │ └── index.js │ │ └── webpack.config.js ├── dannydev │ └── scCOM.js ├── example │ ├── assets │ │ ├── cover.png │ │ └── icon.svg │ ├── ccw-approved-ext.js │ └── normal-ext.js ├── kukemc │ ├── I18n │ │ ├── assets │ │ │ └── index.js │ │ ├── extension.js │ │ ├── index.js │ │ ├── package.json │ │ └── webpack.config.js │ └── WebHook │ │ ├── assets │ │ └── index.js │ │ ├── extension.js │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── webpack.config.js ├── qxsck │ ├── data-analysis.js │ ├── easy-struct.js │ ├── matrix.js │ ├── str-mani.js │ └── var-and-list.js ├── sipc.ink │ ├── CloudMusic │ │ ├── CloudMusic.js │ │ └── assets │ │ │ └── cover.svg │ ├── Consoles │ │ ├── Consoles.js │ │ └── assets │ │ │ └── cover.svg │ └── Recording │ │ ├── Recording.js │ │ └── assets │ │ └── cover.svg ├── six-6 │ ├── DateTime.svg │ ├── DateTimePost.svg │ ├── datetime.js │ └── regexp.js ├── wit_cat │ ├── BBcode │ │ ├── MarkDown.js │ │ ├── assets │ │ │ └── index.js │ │ ├── bbcode.js │ │ ├── htmlToBBCode.js │ │ ├── index.js │ │ └── prism.js │ ├── FPS.js │ ├── File_Helper │ │ ├── File_Helper.js │ │ └── base64.js │ ├── GamePad.js │ ├── IndexedDB.js │ ├── Input.js │ ├── Interpreter │ │ ├── Interpreter.js │ │ └── math.js │ ├── MarkDown │ │ ├── MarkDown.js │ │ ├── assets │ │ │ └── index.js │ │ ├── index.js │ │ └── prism.js │ ├── More_Mouse.js │ ├── README.md │ ├── Sensory_Control.js │ └── Zip │ │ ├── assets │ │ └── index.js │ │ ├── extension.js │ │ ├── index.js │ │ ├── package.json │ │ └── webpack.config.js ├── wtkzq │ └── complex │ │ ├── Complex.js │ │ └── assets │ │ └── index.js └── x10clkz │ └── Biter.js ├── index.html ├── package.json ├── useEslint.md ├── utils ├── assets │ ├── arrow_left.svg │ └── arrow_right.svg ├── cast.js ├── color.js ├── extendable_example.js └── use-expandable-blocks.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | tab_width = 2 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | 16 | [Makefile] 17 | indent_style = tab -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | env: { 4 | es2021: true, 5 | node: true, 6 | browser: true, 7 | }, 8 | extends: "eslint:recommended", 9 | overrides: [], 10 | parserOptions: { 11 | ecmaVersion: "latest", 12 | sourceType: "module", 13 | }, 14 | globals: { 15 | Blockly: "readonly", 16 | Scratch: "readonly", 17 | }, 18 | rules: { 19 | // Unused variables commonly indicate logic errors 20 | "no-unused-vars": [ 21 | "error", 22 | { 23 | // Unused arguments are useful, eg. it can be nice for blocks to accept `args` even if they don't use it 24 | args: "none", 25 | // Allow silently eating try { } catch { } 26 | caughtErrors: "none", 27 | // Variables starting with _ are intentionally unused 28 | argsIgnorePattern: "^_", 29 | varsIgnorePattern: "^_", 30 | }, 31 | ], 32 | // Allow while (true) { } 33 | "no-constant-condition": [ 34 | "error", 35 | { 36 | checkLoops: false, 37 | }, 38 | ], 39 | // Allow empty catch {} blocks 40 | "no-empty": [ 41 | "error", 42 | { 43 | allowEmptyCatch: true, 44 | }, 45 | ], 46 | // Returning a value from a constructor() implies a mistake 47 | "no-constructor-return": "error", 48 | // new Promise(async () => {}) implies a mistake 49 | "no-async-promise-executor": "warn", 50 | // x === x implies a mistake 51 | "no-self-compare": "error", 52 | // Using ${...} in a non-template-string implies a mistake 53 | "no-template-curly-in-string": "error", 54 | // Loops that only iterate once imply a mistake 55 | "no-unreachable-loop": "error", 56 | // Detect some untrusted code execution 57 | "no-eval": "error", 58 | "no-implied-eval": "error", 59 | "no-new-func": "error", 60 | "no-script-url": "error", 61 | // Combinations of || and && are unreadable and may not do what you expect 62 | "no-mixed-operators": [ 63 | "error", 64 | { 65 | groups: [["&&", "||"]], 66 | }, 67 | ], 68 | // Disallow async functions that don't need to be. This is important as a Promise and non-Promise return value 69 | // significantly impacts the behavior of projects. 70 | "require-await": "error", 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | dist/ 11 | 12 | 13 | /types 14 | 15 | # misc 16 | .DS_Store 17 | .idea 18 | .vscode 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | *.scss.d.ts 25 | 26 | *.local 27 | 28 | .cache 29 | !.gitkeep 30 | 31 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn lint-staged 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "endOfLine": "auto", 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "printWidth": 120, 7 | "bracketSpacing": true, 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry="https://registry.npmmirror.com" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ccw-user-extensions 2 | 3 | - [ccw-user-extensions](#ccw-user-extensions) 4 | - [What for](#what-for) 5 | - [About license](#about-license) 6 | - [How to jump in](#how-to-jump-in) 7 | - [Normal remote extension (for anyone)](#normal-remote-extension-for-anyone) 8 | - [CCW approved extension (for CCW collaborator)](#ccw-approved-extension-for-ccw-collaborator) 9 | - [Apply for CCW collaborator](#apply-for-ccw-collaborator) 10 | - [How to develop](#how-to-develop) 11 | - [Define a extension](#define-a-extension) 12 | - [normal remote extension](#normal-remote-extension) 13 | - [CCW approved extension](#ccw-approved-extension) 14 | - [Test your extension](#test-your-extension) 15 | - [normal remote extension](#normal-remote-extension-1) 16 | - [CCW approved extension](#ccw-approved-extension-1) 17 | - [Asset standard](#asset-standard) 18 | 19 | Table of contents generated with markdown-toc 20 | 21 | ## What for 22 | This repo is for Gandi Developer who wants make and test their own extensions. 23 | ## About license 24 | Extensions in this repository are licensed under [LGPL-2.1](./LICENSE) by default, but certain extensions may have different licenses (such as extensions ported from TurboWarp for which we are grateful for their contributions). 25 | ## How to jump in 26 | There are two ways to develop and test your extensions. 27 | ### Normal remote extension (for anyone) 28 | You can build your blocks and test them. 29 | 30 | You can use all javascript capacity to do what you want. 31 | 32 | There only have one limit: normal remote extension will be running in a sandbox and unable to access vm or runtime utilities. 33 | ### CCW approved extension (for CCW collaborator) 34 | 35 | As a CCW collaborator, your can push code into this repo. 36 | 37 | Extensions in this repo will have full access to vm and runtime utilities allows you build advanced block for all CCW Creator. 38 | 39 | When your complete testing,you can apply for publish extensions to CCW Extension Library. All CCW Creator can use it in their project. 40 | ### Apply for CCW collaborator 41 | - fork this repo and submit your PR。 42 | ## How to develop 43 | 44 | ### Define a extension 45 | #### normal remote extension 46 | 47 | [.extensions/example/normal-ext.js](https://github.com/Gandi-IDE/custom-extension/blob/main/extensions/example/normal-ext.js) 48 | 49 | #### CCW approved extension 50 | 51 | [.extensions/example/ccw-approved-ext.js](https://github.com/Gandi-IDE/custom-extension/blob/main/extensions/example/ccw-approved-ext.js) 52 | ### Test your extension 53 | #### normal remote extension 54 | if your work on a normal remote extension, you can upload your extension js file to any http server and make sure it can be accessed in open network. 55 | 56 | when complete above moves, use below url in browser(recommend Chrome) 57 | ``` 58 | https://www.ccw.site/gandi?gext=${your_file_url} 59 | ``` 60 | or 61 | ``` 62 | https://cocrea.world/gandi?gext=${your_file_url} 63 | ``` 64 | 65 | For example 66 | ``` 67 | https://www.ccw.site/gandi?gext=https://ccw-user-extension.ccw.site/extensions/example/normal-ext.js 68 | ``` 69 | Extensions will be added to the bottom of block menu if there is no error in your code. 70 | 71 | #### CCW approved extension 72 | Push code and a bot will deploy your commit to a http server automatically. The URL is like below. 73 | 74 | The automatic deploy url format is like below 75 | ``` 76 | https://ccw-user-extension.ccw.site/extensions/${your_folder}/${jsFile} 77 | ``` 78 | 79 | When completing the above moves, use the URL below in your browser(recommend Chrome). 80 | ``` 81 | https://www.ccw.site/gandi?gext=https://ccw-user-extension.ccw.site/extensions/${your_folder}/${jsFile} 82 | ``` 83 | For example 84 | ``` 85 | https://www.ccw.site/gandi?gext=https://ccw-user-extension.ccw.site/extensions/example/ccw-approved-ext.js 86 | ``` 87 | 88 | Extensions will be registed in Extension Library. You must add it by hand before you use it. 89 | 90 | ### Asset standard 91 | 92 | Extension cover 93 | type: png/jpg/svg 94 | size: 600 × 372 px 95 | 96 | Extension block icon 97 | type: svg 98 | size: 80 x 80 px 99 | 100 | Extension menu icon 101 | type: svg 102 | size: 80 x 80 px 103 | 104 | -------------------------------------------------------------------------------- /extensions/Arkos/assets/advanced_control_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Arkos/assets/advanced_control_cover.png -------------------------------------------------------------------------------- /extensions/Arkos/assets/advanced_control_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/Arkos/assets/cover1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Arkos/assets/cover1.png -------------------------------------------------------------------------------- /extensions/Arkos/assets/cover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Arkos/assets/cover2.png -------------------------------------------------------------------------------- /extensions/Arkos/assets/icon1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/Arkos/assets/icon2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/Arkos/assets/moreDataTypes_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Arkos/assets/moreDataTypes_cover.png -------------------------------------------------------------------------------- /extensions/Arkos/assets/moreDataTypes_icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/Arkos/moreDataTypes/SafeObject.js: -------------------------------------------------------------------------------- 1 | const OLD_PREFIX = ' '; 2 | 3 | const OBJ_ID_KEY = '@id🗄️'; 4 | const OBJ_REF = '@ref🗄️'; 5 | 6 | /** 7 | * 更适合Scratch体质的Object 8 | * - 继承String,避免object保存在作品中时出错(Inspired by Nights) 9 | * - 对于object,原型设为 null,避免原型污染 10 | */ 11 | class SafeObject extends String { 12 | /** 13 | * 以 obj 作为值初始化 SafeObject 14 | * @param {object} obj 对象或数组 15 | */ 16 | constructor(obj = Object.create(null)) { 17 | super(''); 18 | this.assign(typeof obj === 'object' ? obj : Object.create(null)); 19 | } 20 | 21 | /** 22 | * 以 value 作为值赋给 SafeObject 23 | * @param {any} value 值 24 | */ 25 | assign(value) { 26 | if (typeof value !== 'object') { 27 | throw new Error('Invalid object to assign for SafeObject'); 28 | } 29 | this.value = SafeObject.getActualObject(value); 30 | // 不是数组,且原型链的终点是Object,则原型改为 null,避免污染 31 | if (!Array.isArray(this.value) && this.value instanceof Object) { 32 | // 以 null 为原型,避免原型污染 33 | Object.setPrototypeOf(this.value, null); 34 | } 35 | } 36 | 37 | /** 38 | * 将字符串解析为 SafeObject 39 | * @param {string} string 字符串 40 | * @returns {SafeObject} SafeObject 41 | */ 42 | static parse(string) { 43 | return JSON.parse(string, (key, value) => SafeObject.toSafeObject(value)); 44 | } 45 | 46 | static simpleParse(string) { 47 | return SafeObject.toSafeObject(JSON.parse(string)); // reviver疑似性能开销较大,故取消 48 | } 49 | 50 | /** 51 | * 检查是否是类SafeObject的对象(继承String且含属性value) 52 | * @param {*} obj 53 | * @returns {boolean} 54 | */ 55 | static isSafeObjectLike(obj) { 56 | return obj instanceof String && 'value' in obj; 57 | } 58 | 59 | /** 60 | * 将 SafeObject 转换为字符串 61 | * 遇到循环引用报错时,用''代替循环引用对象 62 | * @param {SafeObject} obj SafeObject 63 | * @returns {string} 字符串 64 | */ 65 | static stringify(obj) { 66 | try { 67 | // 没有循环引用,直接序列化 68 | return JSON.stringify(obj, (key, v) => SafeObject.getActualObject(v)); 69 | } catch (e) { 70 | // 遇到循环引用 71 | try { 72 | const parents = new Map(); 73 | return JSON.stringify(obj, function (key, v) { 74 | const value = SafeObject.getActualObject(v); 75 | // 原始值直接返回 76 | if (typeof value !== 'object' || value === null) return value; 77 | // 检查对象是否出现过 78 | if (parents.has(value)) { 79 | // 检查父亲链,是否包含自己 80 | for (let p = this; p; p = parents.get(p)) { 81 | if (p === value) return ''; 82 | } 83 | } else { 84 | // 记录父子关系 85 | parents.set(value, this); 86 | } 87 | return value; 88 | }); 89 | } catch (e2) { 90 | return `error: ${e2.message}`; 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * 如果是 SafeObject,取出其实际对象。否则返回原值 97 | * @param {object} obj The object to check. 98 | * @returns {object} The actual object. 99 | */ 100 | static getActualObject(obj) { 101 | if (SafeObject.isSafeObjectLike(obj)) { 102 | return obj.value; 103 | } 104 | return obj; 105 | } 106 | 107 | /** 108 | * 如果是对象,套一层 SafeObject 109 | * @param {any} value 值 110 | * @returns {SafeObject} SafeObject 111 | */ 112 | static toSafeObject(obj) { 113 | if (typeof obj === 'object' && obj !== null && !SafeObject.isSafeObjectLike(obj)) { 114 | return new SafeObject(obj); 115 | } 116 | return obj; 117 | } 118 | 119 | /** 120 | * 深拷贝,支持处理循环引用 121 | * @param {*} obj 122 | * @param {*} cache 123 | * @returns 124 | */ 125 | static deepCopy(OBJ, cache = new Map()) { 126 | // 检测循环引用 127 | if (cache.has(OBJ)) { 128 | return cache.get(OBJ); 129 | } 130 | const obj = SafeObject.getActualObject(OBJ); 131 | // 处理基本数据类型 132 | if (obj === null || typeof obj !== 'object') { 133 | return obj; 134 | } 135 | const safeObj = new SafeObject(); 136 | // 在缓存中记录 137 | cache.set(OBJ, safeObj); 138 | let copyObj; 139 | // 处理数组 140 | if (Array.isArray(obj)) { 141 | copyObj = []; 142 | safeObj.assign(copyObj); 143 | // 递归复制数组元素 144 | obj.forEach((item, index) => { 145 | copyObj[index] = SafeObject.deepCopy(item, cache); 146 | }); 147 | return safeObj; 148 | } 149 | // 处理对象 150 | copyObj = {}; 151 | safeObj.assign(copyObj); 152 | // 递归复制对象属性 153 | Object.keys(obj).forEach((key) => { 154 | copyObj[key] = SafeObject.deepCopy(obj[key], cache); 155 | }); 156 | return safeObj; 157 | } 158 | 159 | /** 160 | * 返回 SafeObject 字符串表示(例如:" [1,2,3]") 161 | * @returns {string} 字符串表示 162 | */ 163 | toString() { 164 | // return `${ 165 | // Array.isArray(this.value) ? LIST_PREFIX : OBJ_PREFIX 166 | // }${SafeObject.stringify(this.value)}`; 167 | // return ` ${SafeObject.stringify(this.value)}`; 168 | return SafeObject.stringify(this.value); 169 | } 170 | 171 | valueOf() { 172 | return this.toString(); 173 | } 174 | 175 | /** 176 | * stringify,保留引用(用于保存到作品) 177 | * - 对于首次对象,转为{@id🗄️: 0, value: {...}}的形式,记录id 178 | * - 对于第二次出现的对象,使用'@ref🗄️id'的形式,通过id引用对象 179 | * @param {Object} obj 要序列化的对象 180 | * @param {{nextId: number, seen: Map}} info 记录ID,对象对应ID 181 | * @returns {string} 序列化结果 182 | */ 183 | static stringifyWithRef(obj, info = { nextId: 0, seen: new Map() }) { 184 | // const parents = new Map(); 185 | return JSON.stringify(obj, function (key, v) { 186 | const value = SafeObject.getActualObject(v); 187 | // 非对象直接返回 188 | if (typeof value !== 'object' || value === null) return value; 189 | if (OBJ_ID_KEY in this && key === 'value') { 190 | return value; 191 | } 192 | const id = info.seen.get(value); 193 | // 对象之前出现过,存引用 194 | if (id !== undefined) { 195 | return `${OBJ_REF}${id}`; 196 | } 197 | // 第一次出现,存{id, value},记录ID 198 | const warp = { [OBJ_ID_KEY]: info.nextId, value }; 199 | info.seen.set(value, info.nextId); 200 | info.nextId += 1; 201 | return warp; 202 | }); 203 | } 204 | 205 | /** 206 | * 解析字符串为对象。可解析出对象引用关系 207 | * - 例如,一个含循环引用的对象的字符串表示:{@id🗄️: 0, value: {a:1, b: '@ref🗄️0'}} 208 | * @param {string} string 待解析字符串 209 | * @param {Map} map id到对象的映射 210 | * @returns {Object} 解析结果 211 | */ 212 | static parseWithRef(string, info = { map: new Map(), refsToResolve: [] }) { 213 | const res = JSON.parse(string, function (key, value) { 214 | // 如果是对象引用('@ref🗄️123') 215 | if (typeof value === 'string' && value.startsWith(OBJ_REF)) { 216 | const id = parseInt(value.slice(OBJ_REF.length), 10); 217 | // 根据id查找对象 218 | const obj = info.map.get(id); 219 | if (obj === undefined) { 220 | // 未找到对象,标记为待解决 221 | info.refsToResolve.push([this, key, id]); 222 | return 'not found'; 223 | } 224 | return obj; 225 | } 226 | if (typeof value !== 'object' || value === null) return value; 227 | // 是{@id🗄️: 0, value:{...}}的对象,记录对象id,返回value 228 | if (OBJ_ID_KEY in value) { 229 | // 记录ID 230 | info.map.set(value[OBJ_ID_KEY], value.value); 231 | return SafeObject.toSafeObject(value.value); 232 | } 233 | // 普通对象 234 | return SafeObject.toSafeObject(value); 235 | }); 236 | return res; 237 | } 238 | 239 | /** 240 | * 处理调用parseWithRef后未解决的引用 241 | * @param {*} info 242 | * @private 243 | */ 244 | static resolveRef(info = { map: new Map(), refsToResolve: [] }) { 245 | const remains = []; 246 | info.refsToResolve.forEach((item) => { 247 | const [obj, key, id] = item; 248 | // 补充未解决的引用 249 | const value = info.map.get(id); 250 | if (value === undefined) { 251 | obj[key] = 'not found'; 252 | remains.push(item); 253 | } else { 254 | obj[key] = value; 255 | } 256 | }); 257 | info.refsToResolve = remains; 258 | } 259 | 260 | /** 261 | * 尝试匹配形如 {"a": 1, "b": 2} 的字符串,转为SafeObject对象 262 | * @param {string} string 要转换的字符串 263 | * @returns {string | SafeObject} 转换结果(如果失败,返回原内容) 264 | */ 265 | static tryParseSafeObjectString(string, info) { 266 | // 检测 开头的字符串 267 | if (!string.startsWith(OLD_PREFIX)) return string; 268 | const jsonString = string.slice(OLD_PREFIX.length); 269 | 270 | try { 271 | // 尝试解析 JSON 字符串为对象 272 | const obj = SafeObject.parseWithRef(jsonString, info); 273 | if (typeof obj !== 'object' || obj === null) return string; 274 | return obj; 275 | } catch (error) { 276 | console.error('Error parsing SafeObject:', error); 277 | return string; 278 | } 279 | } 280 | 281 | /** 282 | * 将作品里的存放形如 {...}字符串的变量、列表转为SafeObject 283 | * 可以解析出对象间的相互引用 284 | * @param {*} runtime runtime 对象 285 | */ 286 | static parseAllVarInProject(runtime) { 287 | const info = { map: new Map(), refsToResolve: [] }; 288 | runtime.targets.forEach(({ variables }) => { 289 | Object.values(variables).forEach((variable) => { 290 | if (variable.type === '') { 291 | // 变量 292 | if (typeof variable.value === 'string') { 293 | variable.value = SafeObject.tryParseSafeObjectString(variable.value, info); 294 | } 295 | } else if (variable.type === 'list') { 296 | // 列表 297 | const list = variable.value; 298 | for (let i = 0; i < list.length; i += 1) { 299 | if (typeof list[i] === 'string') { 300 | list[i] = SafeObject.tryParseSafeObjectString(list[i], info); 301 | } 302 | } 303 | } 304 | }); 305 | }); 306 | // 解决剩下的引用 307 | SafeObject.resolveRef(info); 308 | } 309 | 310 | // toJSON() { 311 | // return ''; 312 | // } 313 | } 314 | 315 | export default SafeObject; 316 | -------------------------------------------------------------------------------- /extensions/Arkos/test.js: -------------------------------------------------------------------------------- 1 | import Cast from '../utils/cast.js' 2 | 3 | class StrictEqualityExtension { 4 | constructor(runtime) { 5 | this.runtime = runtime 6 | } 7 | getInfo() { 8 | return { 9 | id: 'strictequalityexample', // change this if you make an actual extension! 10 | name: 'Strict Equality', 11 | blocks: [ 12 | { 13 | opcode: 'strictlyEquals', 14 | blockType: 'Boolean', 15 | text: 'dfdfd [ONE] strictly equals [TWO]', 16 | arguments: { 17 | ONE: { 18 | type: 'string', 19 | menu: 'dynamicMenu', 20 | defaultValue: 'Second value' 21 | }, 22 | TWO: { 23 | type: 'string', 24 | defaultValue: 'Second value' 25 | } 26 | } 27 | }, 28 | { 29 | opcode: 'getType', 30 | blockType: 'reporter', 31 | text: '获得[n]的类型', 32 | arguments: { 33 | n: { 34 | type: 'string', 35 | defaultValue: '1' 36 | } 37 | } 38 | } 39 | ], 40 | menus: { 41 | staticMenu: { 42 | items: ['dd 1', '鼠标 2', '33w 3'] 43 | }, 44 | dynamicMenu: { 45 | items: 'getDynamicMenuItems'//['static 1', 'static 2', 'static 3']// 46 | } 47 | } 48 | }; 49 | } 50 | strictlyEquals(args) { 51 | // Note strict equality: Inputs must match exactly: in type, case, etc. 52 | return args.ONE === args.TWO; 53 | } 54 | 55 | getDynamicMenuItems() { 56 | return ['ddd 1', '大V 2', 'dd了 3']; 57 | } 58 | 59 | getType(args) { 60 | console.log(Cast.toString(args.n)); 61 | console.log(typeof Cast.toString(args.n)); 62 | return typeof(args.n); 63 | } 64 | 65 | } 66 | 67 | 68 | window.tempExt = { 69 | Extension: StrictEqualityExtension, 70 | info: { 71 | name: 'hcn.extensionName', 72 | description: 'hcn.description', 73 | extensionId: 'strictequalityexample', 74 | // iconURL: icon, 75 | // insetIconURL: cover, 76 | featured: true, 77 | disabled: false, 78 | collaborator: 'only for hcn test', 79 | }, 80 | l10n: { 81 | 'zh-cn': { 82 | 'hcn.extensionName': 'hcn 的测试', 83 | 'hcn.description': 'hcn 的测试', 84 | }, 85 | en: { 86 | 'hcn.extensionName': 'hcn test', 87 | 'hcn.description': 'hcn test', 88 | }, 89 | }, 90 | } 91 | -------------------------------------------------------------------------------- /extensions/DollyPro/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/DollyPro/assets/cover.png -------------------------------------------------------------------------------- /extensions/DollyPro/assets/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/Fath/Turbowarp/assets/bigint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Fath/Turbowarp/assets/bigint.png -------------------------------------------------------------------------------- /extensions/Fath/Turbowarp/assets/lilyComment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Fath/Turbowarp/assets/lilyComment.png -------------------------------------------------------------------------------- /extensions/Fath/assets/CocreaFetch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Fath/assets/CocreaFetch.png -------------------------------------------------------------------------------- /extensions/Fath/assets/lilyComment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Fath/assets/lilyComment.png -------------------------------------------------------------------------------- /extensions/Fath/banners/CommentBlocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Fath/banners/CommentBlocks.png -------------------------------------------------------------------------------- /extensions/FurryR/README.md: -------------------------------------------------------------------------------- 1 | # FurryR 的插件仓库 2 | 3 | 目前包含的插件: 4 | 5 | - not.js (MIT): [GitHub](https://github.com/FurryR/not.js) 6 | - lpp (MIT): [GitHub](https://github.com/FurryR/lpp-scratch) 7 | 8 | --- 9 | 10 | _所有插件均遵循源仓库的开源协议开源。_ 11 | -------------------------------------------------------------------------------- /extensions/Nights/stats.js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /extensions/Nights/stats.js/README.md: -------------------------------------------------------------------------------- 1 | # install 2 | ``` 3 | npm i 4 | ``` 5 | 6 | # build 7 | ``` 8 | npm run build 9 | ``` 10 | 11 | # 扩展文件在dist\stats.js -------------------------------------------------------------------------------- /extensions/Nights/stats.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stats-ext", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "vite", 9 | "build": "vite build" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "vite-plugin-banner": "^0.7.1", 16 | "vite": "^5.0.10" 17 | } 18 | } -------------------------------------------------------------------------------- /extensions/Nights/stats.js/public/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Nights/stats.js/public/assets/cover.png -------------------------------------------------------------------------------- /extensions/Nights/stats.js/public/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Nights/stats.js/public/assets/icon.png -------------------------------------------------------------------------------- /extensions/Nights/stats.js/public/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "Stats.name", 4 | "description": "Stats.descp", 5 | "extensionId": "Stats", 6 | "featured": true, 7 | "disabled": false, 8 | "collaborator": "nights" 9 | }, 10 | "l10n": { 11 | "zh-cn": { 12 | "Stats.name": "性能监控", 13 | "Stats.descp": "监控性能" 14 | }, 15 | "en": { 16 | "Stats.name": "性能监控", 17 | "Stats.descp": "监控性能" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /extensions/Nights/stats.js/src/block-info.js: -------------------------------------------------------------------------------- 1 | import { STYLEENUM } from "./stats" 2 | export default (formatMessage) => { 3 | return { 4 | id: 'stats', 5 | name: formatMessage('nights.stats.extensionName'), 6 | color1: '#4D7EB4', 7 | blocks: [ 8 | { 9 | opcode: 'openStats', 10 | blockType: "command", 11 | text: formatMessage('nights.stats.openStats'), 12 | }, 13 | { 14 | opcode: 'closeStats', 15 | blockType: "command", 16 | text: formatMessage('nights.stats.closeStats'), 17 | }, 18 | { 19 | opcode: 'setStyle', 20 | blockType: "command", 21 | text: formatMessage('nights.stats.setStyle'), 22 | arguments: { 23 | STYLE: { 24 | type: "string", 25 | menu: 'STYLE', 26 | defaultValue: STYLEENUM.ONE 27 | }, 28 | } 29 | } 30 | ], 31 | menus: { 32 | STYLE: { 33 | items: [ 34 | { 35 | text: formatMessage('nights.stats.showOne'), 36 | value: STYLEENUM.ONE.toString() 37 | }, 38 | { 39 | text: formatMessage('nights.stats.showAll'), 40 | value: STYLEENUM.ALL.toString() 41 | } 42 | ] 43 | }, 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /extensions/Nights/stats.js/src/lang.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'zh-cn': { 3 | 'nights.stats.extensionName': '性能监控', 4 | 'nights.stats.openStats': '打开性能监控', 5 | 'nights.stats.closeStats': '关闭性能监控', 6 | 'nights.stats.showAll': '全部显示', 7 | 'nights.stats.showOne': '单个显示', 8 | 'nights.stats.setStyle': '设置样式为[STYLE]' 9 | }, 10 | 11 | en: { 12 | 'nights.stats.extensionName': 'state', 13 | 'nights.stats.openStats': 'open state', 14 | 'nights.stats.closeStats': 'close state', 15 | 'nights.stats.showAll': 'show all', 16 | 'nights.stats.showOne': 'show one', 17 | 'nights.stats.setStyle': 'set style [STYLE]' 18 | }, 19 | } -------------------------------------------------------------------------------- /extensions/Nights/stats.js/src/main.js: -------------------------------------------------------------------------------- 1 | import Stats, { STYLEENUM } from "./stats"; 2 | import lang from "./lang"; 3 | import getInfo from "./block-info" 4 | /** 5 | * @author nights 6 | */ 7 | 8 | const MAXCLONES = 350 9 | const MAXTHREADS = 30 10 | 11 | class StatsExt { 12 | constructor(runtime) { 13 | this.runtime = runtime 14 | this.stats = new Stats() 15 | this.clonePanel = this.stats.addPanel(new Stats.Panel('CLONE', '#faea5c', '#a58409')); 16 | this.threadsPanel = this.stats.addPanel(new Stats.Panel('THREAD', '#62baf4', '#0c3e5f')); 17 | 18 | this.stats.showPanel(0) 19 | 20 | this.renderer = runtime.renderer; 21 | this.renderer.canvas.parentNode.appendChild(this.stats.dom); 22 | 23 | this.originalDraw = this.renderer.draw; 24 | this.renderer.draw = this.draw.bind(this); 25 | this.stats.setStyle(STYLEENUM.ONE) 26 | this.closeStats() 27 | this._formatMessage = runtime.getFormatMessage(lang) 28 | this.getInfo = ()=> getInfo(this.formatMessage.bind(this)) 29 | } 30 | formatMessage(id) { 31 | return this._formatMessage({ 32 | id, 33 | default: id, 34 | description: id, 35 | }) 36 | } 37 | get threadsCount() { 38 | return this.runtime.threads.length 39 | } 40 | get cloneCount() { 41 | return this.runtime._cloneCounter 42 | } 43 | draw() { 44 | this.stats.begin(); 45 | this.originalDraw.call(this.renderer); 46 | this.stats.end(); 47 | 48 | this.clonePanel.update(this.cloneCount, MAXCLONES) 49 | this.threadsPanel.update(this.threadsCount, MAXTHREADS) 50 | } 51 | 52 | openStats() { 53 | this.stats.domElement.style.display = 'block' 54 | } 55 | closeStats() { 56 | this.stats.domElement.style.display = 'none' 57 | } 58 | setStyle(arg) { 59 | this.stats.setStyle(parseInt(arg.STYLE)) 60 | } 61 | } 62 | 63 | window.tempExt = { 64 | Extension: StatsExt, 65 | info: { 66 | name: "nights.stats.name", 67 | description: "nights.stats.descp", 68 | extensionId: "nights.stats.", 69 | //iconURL: _picture, 70 | //insetIconURL: _icon, 71 | featured: true, 72 | disabled: false, 73 | collaborator: "nights" 74 | }, 75 | "l10n": { 76 | "zh-cn": { 77 | "nights.stats.name": "性能监控", 78 | "nights.stats.descp": "监视性能包括FPS,内存占用等" 79 | }, 80 | "en": { 81 | "nights.stats.name": "performance monitor", 82 | "nights.stats.descp": "Monitoring performance including FPS, memory usage, etc" 83 | } 84 | } 85 | }; 86 | 87 | export default StatsExt -------------------------------------------------------------------------------- /extensions/Nights/stats.js/src/stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | const STYLEENUM = { 6 | ONE: 0, 7 | ALL: 1 8 | } 9 | 10 | var Stats = function () { 11 | 12 | var mode = 0; 13 | var showStyle = STYLEENUM.ONE; 14 | var container = document.createElement('div'); 15 | container.style.cssText = 'position:absolute;top:0;cursor:pointer;opacity:0.9;z-index:1'; 16 | container.addEventListener('click', function (event) { 17 | if (showStyle == STYLEENUM.ONE) { 18 | event.preventDefault(); 19 | showPanel(++mode % container.children.length); 20 | } 21 | }, false); 22 | 23 | // 24 | 25 | function addPanel(panel) { 26 | 27 | container.appendChild(panel.dom); 28 | return panel; 29 | 30 | } 31 | 32 | function showPanel(id) { 33 | 34 | for (var i = 0; i < container.children.length; i++) { 35 | 36 | container.children[i].style.display = i === id ? 'block' : 'none'; 37 | 38 | } 39 | 40 | mode = id; 41 | 42 | } 43 | function setStyle(newStyle) { 44 | showStyle = newStyle 45 | if (showStyle == STYLEENUM.ALL) { 46 | for (var i = 0; i < container.children.length; i++) { 47 | container.children[i].style.display = 'block'; 48 | } 49 | } else { 50 | showPanel(0) 51 | } 52 | } 53 | 54 | // 55 | 56 | var beginTime = (performance || Date).now(), prevTime = beginTime, frames = 0; 57 | 58 | var fpsPanel = addPanel(new Stats.Panel('FPS', '#0ff', '#002')); 59 | var msPanel = addPanel(new Stats.Panel('MS', '#0f0', '#020')); 60 | 61 | if (self.performance && self.performance.memory) { 62 | 63 | var memPanel = addPanel(new Stats.Panel('MB', '#f08', '#201')); 64 | 65 | } 66 | 67 | showPanel(0); 68 | 69 | return { 70 | 71 | REVISION: 16, 72 | 73 | dom: container, 74 | 75 | addPanel: addPanel, 76 | showPanel: showPanel, 77 | 78 | begin: function () { 79 | 80 | beginTime = (performance || Date).now(); 81 | 82 | }, 83 | 84 | end: function () { 85 | 86 | frames++; 87 | 88 | var time = (performance || Date).now(); 89 | 90 | msPanel.update(time - beginTime, 200); 91 | 92 | if (time >= prevTime + 1000) { 93 | 94 | fpsPanel.update((frames * 1000) / (time - prevTime), 100); 95 | 96 | prevTime = time; 97 | frames = 0; 98 | 99 | if (memPanel) { 100 | 101 | var memory = performance.memory; 102 | memPanel.update(memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576); 103 | 104 | } 105 | 106 | } 107 | 108 | return time; 109 | 110 | }, 111 | 112 | update: function () { 113 | 114 | beginTime = this.end(); 115 | 116 | }, 117 | 118 | // Backwards Compatibility 119 | 120 | domElement: container, 121 | setMode: showPanel, 122 | setStyle: setStyle 123 | 124 | }; 125 | 126 | }; 127 | 128 | Stats.Panel = function (name, fg, bg) { 129 | 130 | var min = Infinity, max = 0, round = Math.round; 131 | var PR = round(window.devicePixelRatio || 1); 132 | 133 | var WIDTH = 80 * PR, HEIGHT = 48 * PR, 134 | TEXT_X = 3 * PR, TEXT_Y = 2 * PR, 135 | GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, 136 | GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR; 137 | 138 | var canvas = document.createElement('canvas'); 139 | canvas.width = WIDTH; 140 | canvas.height = HEIGHT; 141 | canvas.style.cssText = 'width:80px;height:48px'; 142 | 143 | var context = canvas.getContext('2d'); 144 | context.font = 'bold ' + (9 * PR) + 'px Helvetica,Arial,sans-serif'; 145 | context.textBaseline = 'top'; 146 | 147 | context.fillStyle = bg; 148 | context.fillRect(0, 0, WIDTH, HEIGHT); 149 | 150 | context.fillStyle = fg; 151 | context.fillText(name, TEXT_X, TEXT_Y); 152 | context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 153 | 154 | context.fillStyle = bg; 155 | context.globalAlpha = 0.9; 156 | context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); 157 | 158 | return { 159 | 160 | dom: canvas, 161 | 162 | update: function (value, maxValue) { 163 | 164 | min = Math.min(min, value); 165 | max = Math.max(max, value); 166 | 167 | context.fillStyle = bg; 168 | context.globalAlpha = 1; 169 | context.fillRect(0, 0, WIDTH, GRAPH_Y); 170 | context.fillStyle = fg; 171 | context.fillText(round(value) + ' ' + name + ' (' + round(min) + '-' + round(max) + ')', TEXT_X, TEXT_Y); 172 | 173 | context.drawImage(canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT); 174 | 175 | context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT); 176 | 177 | context.fillStyle = bg; 178 | context.globalAlpha = 0.9; 179 | context.fillRect(GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round((1 - (value / maxValue)) * GRAPH_HEIGHT)); 180 | } 181 | 182 | }; 183 | 184 | }; 185 | export { STYLEENUM } 186 | export default Stats; -------------------------------------------------------------------------------- /extensions/Nights/stats.js/vite.config.js: -------------------------------------------------------------------------------- 1 | import banner from 'vite-plugin-banner' 2 | 3 | export default { 4 | build: { 5 | lib: { 6 | entry: 'src/main.js', 7 | name: 'stats', 8 | fileName: format => `stats.js`, 9 | formats: ['esm'], 10 | }, 11 | minify: false, 12 | }, 13 | server:{ 14 | port: '8000', 15 | open: './dist/stats.js' 16 | }, 17 | plugins: [banner('\nimport _picture from "./assets/cover.png"\nimport _icon from "./assets/icon.png"\n')] 18 | }; 19 | -------------------------------------------------------------------------------- /extensions/Nights/tilemap/README.md: -------------------------------------------------------------------------------- 1 | * timeap.js 扩展文件 2 | * demo.sb3 demo 作品 3 | * icon.png 图标 4 | * cover.png 封面 5 | * info.json 6 | 7 | 我修改了 scratch-render的webglrender的drawthese方法(在src/override.js中) 8 | 。可能需要修改,在src/override.js所有我修改的都标注了by:nights,否则骨骼,图层管理,雷神,可能无法与tilemap一起使用 9 | 10 | 这个是打包好了的,没打包的:[https://github.com/Nightre/tilemap](https://github.com/Nightre/tilemap) -------------------------------------------------------------------------------- /extensions/Nights/tilemap/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Nights/tilemap/cover.png -------------------------------------------------------------------------------- /extensions/Nights/tilemap/demo.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Nights/tilemap/demo.sb3 -------------------------------------------------------------------------------- /extensions/Nights/tilemap/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/Nights/tilemap/icon.png -------------------------------------------------------------------------------- /extensions/Nights/tilemap/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "name": "nights.tilemap.name", 4 | "description": "nights.tilemap.descp", 5 | "extensionId": "nightstilemap", 6 | "featured": true, 7 | "disabled": false, 8 | "collaborator": "nights", 9 | "collaboratorURL": "https://www.ccw.site/student/612ed1533068ae6640de96f0", 10 | }, 11 | "l10n": { 12 | "zh-cn": { 13 | "nights.tilemap.name": "瓦片地图", 14 | "nights.tilemap.descp": "[beta] 高性能的瓦片地图渲染器(不兼容雷神,图层管理,以后会兼容)" 15 | }, 16 | "en": { 17 | "nights.tilemap.name": "Tilemap", 18 | "nights.tilemap.descp": "[beta] High performance tile map renderer (not compatible with quake, layer management, will be compatible in the future)" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /extensions/PortFromTurboWarp/NOname-awa/assets/morecomparisons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/PortFromTurboWarp/NOname-awa/assets/morecomparisons.png -------------------------------------------------------------------------------- /extensions/PortFromTurboWarp/README.md: -------------------------------------------------------------------------------- 1 | Extensions below are ported from [TurboWarp](https://github.com/TurboWarp/extensions), which may have licenses different from other extensions in the repository. Thanks for their contributions! 2 | Images mainly from https://github.com/TurboWarp/extensions/blob/master/images, under [GPLv3](./licenses/GPL-3.0.txt) 3 | 4 | ## [stretch.js](./stretch.js) 5 | - Original by: [GarboMuffin](https://github.com/GarboMuffin), [Star World](https://github.com/TheStarWorld) 6 | - Ported by: [Arkos](https://github.com/Arkos123) 7 | - Code ported from: https://github.com/TurboWarp/extensions/blob/master/extensions/stretch.js 8 | Code under license: [LGPLv3](./licenses/LGPL-3.0.txt.txt) & [MIT](./licenses/MIT.txt) 9 | - Cover from: https://github.com/TurboWarp/extensions/blob/master/images/stretch.svg 10 | Cover under license: [GPLv3](./licenses/GPL-3.0.txt) 11 | 12 | ## [clippingblending.js](../Vadik1/clippingblending.js) 13 | - Original by: [Vadik1](https://scratch.mit.edu/users/Vadik1/) 14 | - Ported & modified by: [Arkos](https://github.com/Arkos123) 15 | - Code ported from: https://github.com/TurboWarp/extensions/blob/master/extensions/Xeltalliv/clippingblending.js 16 | Code under license: [MIT](./licenses/MIT.txt) 17 | - Cover from: https://github.com/TurboWarp/extensions/blob/master/images/Xeltalliv/clippingblending.svg 18 | Cover under license: [GPLv3](./licenses/GPL-3.0.txt) 19 | 20 | ## [comment blocks.js](../Fath/comment-blocks.js) 21 | - Original by: [lilymakesthings](https://scratch.mit.edu/users/lilymakesthings) 22 | - Ported & modified by: [fath11](https://github.com/Fath11) 23 | - Code ported from: https://github.com/TurboWarp/extensions/blob/master/extensions/Lily/CommentBlocks.js 24 | Code under license: [LGPLv3](./licenses/LGPL-3.0.txt.txt) & [MIT](./licenses/MIT.txt) 25 | - Cover from: https://github.com/TurboWarp/extensions/blob/master/images/Lily/CommentBlocks.svg 26 | Cover under license: [GPLv3](./licenses/GPL-3.0.txt) 27 | 28 | ## [bigint.js](../Fath/Turbowarp/BigInt.js) 29 | - Original by: [Skyhigh173](https://github.com/Skyhigh173) 30 | - Ported & modified by: [fath11](https://github.com/Fath11) 31 | - Code ported from: https://github.com/TurboWarp/extensions/blob/master/extensions/Skyhigh173/bigint.js 32 | Code under license: [MIT](./licenses/MIT.txt) 33 | - Cover from: https://github.com/TurboWarp/extensions/blob/master/images/Skyhigh173/bigint.svg 34 | Cover under license: [GPLv3](./licenses/GPL-3.0.txt) 35 | 36 | ## [Morecomparison.js](./NOname-awa/Morecomparison.js) 37 | - Original by: [NOname-awa](https://github.com/NOname-awa) 38 | - Ported & modified by: [fath11](https://github.com/Fath11) 39 | - Code ported from: https://extensions.turbowarp.org/NOname-awa/more-comparisons.js 40 | Code under license: [MIT](./licenses/MIT.txt) 41 | - Cover from: https://github.com/TurboWarp/extensions/blob/master/images/NOname-awa/more-comparisons.svg 42 | Cover under license: [GPLv3](./licenses/GPL-3.0.txt) -------------------------------------------------------------------------------- /extensions/PortFromTurboWarp/licenses/LGPL-3.0.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /extensions/PortFromTurboWarp/licenses/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2024 TurboWarp Extensions Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /extensions/Project/Project.js: -------------------------------------------------------------------------------- 1 | import { icon } from "./Project.svg"; 2 | 3 | (function (Scratch) { 4 | Scratch.translate.setup({ 5 | zh: { 6 | 'Project0832.Project0832': '项目', 7 | 'Project0832.description': '在Gandi中读取项目信息', 8 | 'Project0832.TotalUsedBlocks': '使用的块总数', 9 | 'Project0832.NumberOfBlockTypes': '块种类数', 10 | 'Project0832.NumberOfSegments': '段数', 11 | 'Project0832.NumberOfCostumes': '造型数', 12 | 'Project0832.NumberOfSounds': '声音数' 13 | }, 14 | en: { 15 | 'Project0832.Project0832': 'Project', 16 | 'Project0832.description': 'Read Project infomation in Gandi', 17 | 'Project0832.TotalUsedBlocks': 'Total Used Blocks', 18 | 'Project0832.NumberOfBlockTypes': 'Number Of Block Types', 19 | 'Project0832.NumberOfSegments': 'Number Of Segments', 20 | 'Project0832.NumberOfCostumes': 'Number Of Costumes', 21 | 'Project0832.NumberOfSounds': 'Number Of Sounds' 22 | } 23 | }); 24 | const extensionId = 'Project0832'; 25 | class Project { 26 | constructor(runtime) { 27 | this.runtime = runtime; 28 | this.totalUsedBlocks = 0; 29 | this.numberOfBlockTypes = 0; 30 | this.numberOfSegments = 0; 31 | this.numberOfCostumes = 0; 32 | this.numberOfSounds = 0; 33 | } 34 | calculate() { 35 | const targets = this.runtime.targets; 36 | const blocksUsed = {}; // Record the number of block types 37 | let segmentCount = 0; // Record the number of segments 38 | let costumeCount = 0; // Record the number of costumes 39 | let soundCount = 0; // Record the number of sounds 40 | 41 | for (let i = 0; i < targets.length; i++) { 42 | const target = targets[i]; 43 | if (target.blocks) { 44 | for (const blockId in target.blocks._blocks) { 45 | const block = target.blocks._blocks[blockId]; 46 | const blockType = block.opcode.split('_')[0]; // Get the block type 47 | 48 | if (!blocksUsed[blockType]) { 49 | blocksUsed[blockType] = 1; 50 | } else { 51 | blocksUsed[blockType]++; 52 | } 53 | 54 | // Count the number of segments 55 | if (block.topLevel && block.parent === null) { 56 | segmentCount++; 57 | } 58 | } 59 | } 60 | 61 | // Count the number of costumes 62 | if (target.sprite.costumes) { 63 | costumeCount += target.sprite.costumes.length; 64 | } 65 | 66 | // Count the number of sounds 67 | if (target.sprite.sounds) { 68 | soundCount += target.sprite.sounds.length; 69 | } 70 | } 71 | 72 | this.totalUsedBlocks = targets.reduce((acc, target) => acc + Object.keys(target.blocks._blocks || {}).length, 0); 73 | this.numberOfBlockTypes = Object.keys(blocksUsed).length; 74 | this.numberOfSegments = segmentCount; 75 | this.numberOfCostumes = costumeCount; 76 | this.numberOfSounds = soundCount; 77 | } 78 | getInfo() { 79 | return { 80 | id: extensionId, 81 | name: Scratch.translate({ id: 'Project0832.Project0832', default: 'Project' }), 82 | blocks: [ 83 | { 84 | opcode: 'TotalUsedBlocks', 85 | blockType: Scratch.BlockType.REPORTER, 86 | disableMonitor: true, 87 | text: Scratch.translate({ id: 'Project0832.TotalUsedBlocks', default: 'Total Used Blocks' }) 88 | }, 89 | { 90 | opcode: 'NumberOfBlockTypes', 91 | blockType: Scratch.BlockType.REPORTER, 92 | disableMonitor: true, 93 | text: Scratch.translate({ id: 'Project0832.NumberOfBlockTypes', default: 'Number of Block Types' }) 94 | }, 95 | { 96 | opcode: 'NumberOfSegments', 97 | blockType: Scratch.BlockType.REPORTER, 98 | disableMonitor: true, 99 | text: Scratch.translate({ id: 'Project0832.NumberOfSegments', default: 'Number of Segments' }) 100 | }, 101 | { 102 | opcode: 'NumberOfCostumes', 103 | blockType: Scratch.BlockType.REPORTER, 104 | disableMonitor: true, 105 | text: Scratch.translate({ id: 'Project0832.NumberOfCostumes', default: 'Number of Costumes' }) 106 | }, 107 | { 108 | opcode: 'NumberOfSounds', 109 | blockType: Scratch.BlockType.REPORTER, 110 | disableMonitor: true, 111 | text: Scratch.translate({ id: 'Project0832.NumberOfSounds', default: 'Number of Sounds' }) 112 | }, 113 | ] 114 | }; 115 | } 116 | TotalUsedBlocks() { 117 | this.calculate(); 118 | return this.totalUsedBlocks; 119 | } 120 | 121 | NumberOfBlockTypes() { 122 | this.calculate(); 123 | return this.numberOfBlockTypes; 124 | } 125 | 126 | NumberOfSegments() { 127 | this.calculate(); 128 | return this.numberOfSegments; 129 | } 130 | 131 | NumberOfCostumes() { 132 | this.calculate(); 133 | return this.numberOfCostumes; 134 | } 135 | 136 | NumberOfSounds() { 137 | this.calculate(); 138 | return this.numberOfSounds; 139 | } 140 | } 141 | 142 | const extension = { 143 | Extension: Project, 144 | info: { 145 | name: `${extensionId}.Project0832.Project0832`, 146 | description: `${extensionId}.Project0832.description`, 147 | extensionId, 148 | iconURL: icon, 149 | featured: true, 150 | disabled: false, 151 | collaboratorList: [ 152 | { 153 | collaborator: "0832", 154 | collaboratorURL: "https://github.com/0832k12", 155 | }, 156 | ], 157 | }, 158 | l10n: { 159 | "zh-cn": { 160 | [`${extensionId}.Project0832.Project0832`]: "项目", 161 | [`${extensionId}.Project0832.description`]: "在Gandi中读取项目信息", 162 | }, 163 | en: { 164 | [`${extensionId}.Project0832.Project0832`]: "Project", 165 | [`${extensionId}.Project0832.description`]: "Read Project infomation in Gandi", 166 | }, 167 | }, 168 | }; 169 | window.tempExt = extension; 170 | })(Scratch); 171 | -------------------------------------------------------------------------------- /extensions/Project/Project.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/QuakeStudio/BetterQuake/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![QuakeFragment logo](/extensions/QuakeStudio/assets/BetterQuake.png "BetterQuake Logo") 4 | 5 | # Better Quake 6 | 7 |
8 | 9 | BetterQuake is an extension made for [Gandi IDE](https://getgandi.com/) to fully allow the use of custom shaders. 10 | This extension is built with an [extension scaffholding](https://github.com/FurryR/scratch-ext) by FurryR. Huge thanks to him :3s 11 | 12 | ## For contributers and reviewers 13 | 14 | Please visit https://github.com/QuakeStudio/BetterQuake. The files provided here are NOT meant to be edited nor viewed, thank you. 15 | 16 | --- 17 | 18 |
19 | 20 | _`This project is licensed under the MPL-2.0 license.`_ 21 | 22 | 🐢❤️ 23 | 24 |
25 | -------------------------------------------------------------------------------- /extensions/QuakeStudio/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![QuakeFragment logo](/extensions/QuakeStudio/assets/QuakeStudio.png "QuakeStudio Logo") 4 | 5 | # Quake Studio 6 | 7 |
8 | 9 | Quake Studio is created specifically to make rendering and WebGL related extensions for [Gandi IDE](https://getgandi.com/). 10 | 11 | Visit [our GitHub](https://github.com/QuakeStudio/) for more info. 12 | -------------------------------------------------------------------------------- /extensions/QuakeStudio/assets/BetterQuake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/QuakeStudio/assets/BetterQuake.png -------------------------------------------------------------------------------- /extensions/QuakeStudio/assets/QuakeStudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/QuakeStudio/assets/QuakeStudio.png -------------------------------------------------------------------------------- /extensions/QuakeStudio/assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/QuakeStudio/assets/banner.png -------------------------------------------------------------------------------- /extensions/SimonShiki/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/SimonShiki/assets/cover.png -------------------------------------------------------------------------------- /extensions/SimonShiki/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/SimonShiki/assets/icon.png -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'es2021': true 5 | }, 6 | 'extends': 'eslint:recommended', 7 | 'parserOptions': { 8 | 'ecmaVersion': 12, 9 | 'sourceType': 'module' 10 | }, 11 | 'rules': { 12 | 'indent': [ 13 | 'error', 14 | 2 15 | ], 16 | 'linebreak-style': [ 17 | 'error', 18 | 'unix' 19 | ], 20 | 'quotes': [ 21 | 'error', 22 | 'single' 23 | ], 24 | 'semi': [ 25 | 'error', 26 | 'always' 27 | ] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gandi-extension-webpack-template", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./dist/extension.js", 6 | "browser": "./dist/index.js", 7 | "license": "MIT", 8 | "author": "yuen619 ", 9 | "scripts": { 10 | "build": "npm run clean && webpack --config webpack.config.js", 11 | "build:pre": "npm run clean && cross-env DEPLOY_ENV=pre webpack --config webpack.config.js", 12 | "watch": "npm run clean && webpack --watch --progress", 13 | "watch:pre": "npm run clean && cross-env DEPLOY_ENV=pre webpack --watch --progress", 14 | "lint": "eslint src/**/*.js", 15 | "clean": "rimraf ./dist" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.19.3", 19 | "babel-loader": "^8.2.5", 20 | "copy-webpack-plugin": "^11.0.0", 21 | "eslint": "^7.32.0", 22 | "css-loader": "^6.7.1", 23 | "file-loader": "^6.2.0", 24 | "rimraf": "^5.0.5", 25 | "style-loader": "^3.3.1", 26 | "url-loader": "^4.1.1", 27 | "webpack": "^5.74.0", 28 | "webpack-cli": "^4.10.0", 29 | "webpack-dev-server": "^4.11.1", 30 | "cross-env": "^7.0.3" 31 | }, 32 | "dependencies": { 33 | "axios": "^1.6.8" 34 | } 35 | } -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/src/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/YUEN/FeishuExtension/src/assets/cover.png -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/YUEN/FeishuExtension/src/assets/icon.png -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/src/constants.js: -------------------------------------------------------------------------------- 1 | import Cover from './assets/cover.png'; 2 | import Icon from './assets/icon.png'; 3 | 4 | export { Cover, Icon }; 5 | 6 | export const extensionNS = 'YUEN'; 7 | 8 | // eslint-disable-next-line no-undef 9 | export const extensionId = `${extensionNS}.feishu${DEPLOY_ENV && DEPLOY_ENV === 'pre' ? '1' : ''}`; 10 | -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/src/index.js: -------------------------------------------------------------------------------- 1 | import FeishuExtension from './extension'; 2 | import { Cover, extensionId, Icon } from './constants'; 3 | 4 | window.tempExt = { 5 | Extension: FeishuExtension, 6 | info: { 7 | name: 'YUEN.feishu.extensionName', 8 | description: 'YUEN.feishu.description', 9 | doc: 'YUEN.feishu.doc', 10 | extensionId, 11 | iconURL: Cover, 12 | insetIconURL: Icon, 13 | featured: true, 14 | disabled: false, 15 | collaborator: 'YUEN @ CCW', 16 | collaboratorURL: 'https://www.ccw.site/student/628979aa804a3a2bc801b097', 17 | collaboratorList: [ 18 | { 19 | collaborator: 'YUEN @ CCW', 20 | collaboratorURL: 21 | 'https://www.ccw.site/student/628979aa804a3a2bc801b097', 22 | }, 23 | { 24 | collaborator: '酷可 @ CCW', 25 | collaboratorURL: 26 | 'https://www.ccw.site/student/610b508176415b2f27e0f851', 27 | }, 28 | ], 29 | }, 30 | l10n: { 31 | 'zh-cn': { 32 | 'YUEN.feishu.extensionName': '飞书', 33 | 'YUEN.feishu.description': '✨ 更好的WebHook', 34 | 'YUEN.feishu.doc': 35 | 'https://learn.ccw.site/article/7795eb4b-170b-435b-bca3-8b7d4e0c24f8', 36 | }, 37 | en: { 38 | 'YUEN.feishu.extensionName': 'Feishu', 39 | 'YUEN.feishu.description': '✨ Enhanced WebHook', 40 | 'YUEN.feishu.doc': 41 | 'https://learn.ccw.site/article/e895cb95-cc0c-47a0-9bbb-a5aabf82750d', 42 | }, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /extensions/YUEN/FeishuExtension/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: { 7 | index: './src/index.js', 8 | extension: './src/extension.js', 9 | }, 10 | output: { 11 | filename: '[name].js', 12 | chunkFilename: '[name].[contenthash:8].js', 13 | publicPath: '/', 14 | path: path.resolve('./dist'), 15 | globalObject: 'this', 16 | libraryTarget: 'umd', 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(js)$/, 22 | exclude: /node_modules/, 23 | use: ['babel-loader'], 24 | }, 25 | { 26 | test: /\.css/, 27 | use: ['style-loader', 'css-loader'], 28 | }, 29 | { 30 | test: /\.(woff2?|eot|ttf|otf|bin|png|svg|gif|jpe?g)(\?.*)?$/i, 31 | loader: 'url-loader', 32 | options: { 33 | name: '[name].[hash:8].[ext]', 34 | limit: 250000, 35 | }, 36 | }, 37 | ], 38 | }, 39 | resolve: { 40 | extensions: ['.js', '.png', '.svg', '.gif', '.jpg', '.jpeg', '.css'], 41 | }, 42 | plugins: [ 43 | new webpack.DefinePlugin({ 44 | DEPLOY_ENV: `"${process.env.DEPLOY_ENV || 'prd'}"`, 45 | }), 46 | ], 47 | }; 48 | -------------------------------------------------------------------------------- /extensions/example/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gandi-IDE/custom-extension/a95bb4da72973fdfbc7d826ead75ef70de8e4681/extensions/example/assets/cover.png -------------------------------------------------------------------------------- /extensions/example/assets/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/example/ccw-approved-ext.js: -------------------------------------------------------------------------------- 1 | import Cast from '../utils/cast.js' 2 | // import cover from './assets/icon.svg' 3 | // import icon from './assets/icon.svg' 4 | class ArkosExtensions { 5 | constructor(runtime) { 6 | this.runtime = runtime 7 | this._formatMessage = runtime.getFormatMessage({ 8 | 'zh-cn': { 9 | 'ArkosExt.extensionName': 'Arkosの拓展', 10 | 'ArkosExt.stringEquality': '(区分大小写)[ONE]=[TWO]', 11 | 'ArkosExt.directionFromAtoB': '点x1:[X1]y1:[Y1]朝向点x2:[X2]y2:[Y2]的方向', 12 | 'ArkosExt.differenceBetweenDirections': '由方向1[a]到方向2[b]的角度差', 13 | 'ArkosExt.distance': '点x1:[X1]y1:[Y1]到点x2:[X2]y2:[Y2]的距离', 14 | 'ArkosExt.searchString': '在[str]中查找[substr]的位置(从位置[pos]开始找)', 15 | 'ArkosExt.insertString': '在[str]的第[pos]个字符前插入[substr]', 16 | 'ArkosExt.replaceString': '将[str]中的第[start]个到第[end]个字符,替换为[substr]', 17 | }, 18 | 19 | en: { 20 | 'ArkosExt.extensionName': "Arkos' Extensions", 21 | 'ArkosExt.stringEquality': '(case sensitive)[ONE]=[TWO]', 22 | 'ArkosExt.directionFromAtoB': 'direction from x1:[X1]y1:[Y1]to x2:[X2]y2:[Y2]', 23 | 'ArkosExt.differenceBetweenDirections': 'direction[b] minus direction[a]', 24 | 'ArkosExt.distance': 'distance between x1:[X1]y1:[Y1]and x2:[X2]y2:[Y2]', 25 | 'ArkosExt.searchString': 'position of[substr]in[str],start from[pos]', 26 | 'ArkosExt.insertString': 'insert[substr]at[pos]of[str]', 27 | 'ArkosExt.replaceString': 'replace from[start]to[end]of[str],with[substr]', 28 | }, 29 | }) 30 | } 31 | 32 | formatMessage(id) { 33 | return this._formatMessage({ 34 | id, 35 | default: id, 36 | description: id, 37 | }) 38 | } 39 | 40 | getInfo() { 41 | return { 42 | id: 'hcnTest', // 拓展id 43 | name: this.formatMessage('ArkosExt.extensionName'), // 拓展名 44 | color1: '#FF8383', 45 | // menuIconURI: icon, 46 | // blockIconURI: icon, 47 | blocks: [ 48 | { 49 | // 判断相等(区分大小写) 50 | opcode: 'strictlyEquals', 51 | blockType: 'Boolean', 52 | text: this.formatMessage('ArkosExt.stringEquality'), 53 | arguments: { 54 | ONE: { 55 | type: 'string', 56 | defaultValue: 'A', 57 | }, 58 | TWO: { 59 | type: 'string', 60 | defaultValue: 'a', 61 | }, 62 | }, 63 | }, 64 | { 65 | // 计算点A到点B的方向 66 | opcode: 'getDirFromAToB', 67 | blockType: 'reporter', 68 | text: this.formatMessage('ArkosExt.directionFromAtoB'), 69 | arguments: { 70 | X1: { 71 | type: 'number', 72 | defaultValue: 0, 73 | }, 74 | Y1: { 75 | type: 'number', 76 | defaultValue: 0, 77 | }, 78 | X2: { 79 | type: 'number', 80 | defaultValue: 0, 81 | }, 82 | Y2: { 83 | type: 'number', 84 | defaultValue: 0, 85 | }, 86 | }, 87 | }, 88 | { 89 | // 计算角b-角a的角度差 90 | opcode: 'differenceBetweenDirections', 91 | blockType: 'reporter', 92 | text: this.formatMessage('ArkosExt.differenceBetweenDirections'), 93 | arguments: { 94 | a: { 95 | type: 'number', 96 | defaultValue: 0, 97 | }, 98 | b: { 99 | type: 'number', 100 | defaultValue: 0, 101 | }, 102 | }, 103 | }, 104 | { 105 | // 两点距离 106 | opcode: 'disFromAToB', 107 | blockType: 'reporter', 108 | text: this.formatMessage('ArkosExt.distance'), 109 | arguments: { 110 | X1: { 111 | type: 'number', 112 | defaultValue: 0, 113 | }, 114 | Y1: { 115 | type: 'number', 116 | defaultValue: 0, 117 | }, 118 | X2: { 119 | type: 'number', 120 | defaultValue: 0, 121 | }, 122 | Y2: { 123 | type: 'number', 124 | defaultValue: 0, 125 | }, 126 | }, 127 | }, 128 | { 129 | // 查找子字符串,从pos开始 130 | opcode: 'indexof', 131 | blockType: 'reporter', 132 | text: this.formatMessage('ArkosExt.searchString'), 133 | arguments: { 134 | str: { 135 | type: 'string', 136 | defaultValue: 'banana', 137 | }, 138 | substr: { 139 | type: 'string', 140 | defaultValue: 'na', 141 | }, 142 | pos: { 143 | type: 'number', 144 | defaultValue: 1, 145 | }, 146 | }, 147 | }, 148 | { 149 | // 在字符串中插入子字符串 150 | opcode: 'insertStr', 151 | blockType: 'reporter', 152 | text: this.formatMessage('ArkosExt.insertString'), 153 | arguments: { 154 | str: { 155 | type: 'string', 156 | defaultValue: 'ac', 157 | }, 158 | substr: { 159 | type: 'string', 160 | defaultValue: 'b', 161 | }, 162 | pos: { 163 | type: 'number', 164 | defaultValue: 2, 165 | }, 166 | }, 167 | }, 168 | { 169 | // 替换字符串中的从..到..的字符串 170 | opcode: 'replaceStr', 171 | blockType: 'reporter', 172 | text: this.formatMessage('ArkosExt.replaceString'), 173 | arguments: { 174 | str: { 175 | type: 'string', 176 | defaultValue: 'ABCDEF', 177 | }, 178 | substr: { 179 | type: 'string', 180 | defaultValue: 'XX', 181 | }, 182 | start: { 183 | type: 'number', 184 | defaultValue: 3, 185 | }, 186 | end: { 187 | type: 'number', 188 | defaultValue: 4, 189 | }, 190 | }, 191 | }, 192 | { 193 | //朝..方向旋转..角度 194 | opcode: 'turnDegreesToDir', 195 | blockType: 'command', 196 | text: 'turn[degree]degrees toward direction[dir]', 197 | arguments: { 198 | degree: { 199 | type: 'number', 200 | defaultValue: 0, 201 | }, 202 | dir: { 203 | type: 'angle', 204 | defaultValue: 0, 205 | }, 206 | }, 207 | }, 208 | ], 209 | } 210 | } 211 | 212 | strictlyEquals(args) { 213 | // Note strict equality: Inputs must match exactly: in type, case, etc. 214 | return args.ONE === args.TWO 215 | } 216 | 217 | numGreaterThen(args) { 218 | return args.ONE > args.TWO 219 | } 220 | 221 | getDirFromAToB(args) { 222 | const { X1, X2, Y1, Y2 } = args 223 | let a = (Math.atan((X2 - X1) / (Y2 - Y1)) / Math.PI) * 180.0 224 | if (Y1 < Y2) return a 225 | if (Y1 > Y2) { 226 | a += 180 227 | if (a > 180.0) a -= 360.0 228 | return a 229 | } 230 | if (X2 > X1) return 90 231 | if (X2 < X1) return -90 232 | return NaN 233 | } 234 | 235 | differenceBetweenDirections(args) { 236 | const { a, b } = args 237 | let dif = (b - a) % 360 238 | if (dif > 180) dif -= 360 239 | return dif 240 | } 241 | 242 | disFromAToB(args) { 243 | const { X1, X2, Y1, Y2 } = args 244 | return Math.sqrt((X1 - X2) * (X1 - X2) + (Y1 - Y2) * (Y1 - Y2)) 245 | } 246 | 247 | indexof(args) { 248 | const str = Cast.toString(args.str) 249 | const substr = Cast.toString(args.substr) 250 | const a = str.indexOf(substr, args.pos - 1) 251 | if (a === -1) { 252 | return '' 253 | } 254 | return a + 1 255 | } 256 | 257 | insertStr(args) { 258 | const str = Cast.toString(args.str) 259 | const substr = Cast.toString(args.substr) 260 | let pos = args.pos - 1 261 | if (pos < 0) { 262 | pos = 0 263 | } 264 | return str.slice(0, pos) + substr + str.slice(pos) 265 | } 266 | 267 | replaceStr(args) { 268 | const str = Cast.toString(args.str) 269 | const substr = Cast.toString(args.substr) 270 | let start = Cast.toNumber(args.start) 271 | let end = Cast.toNumber(args.end) 272 | if (start > end) { 273 | const t = end 274 | end = start 275 | start = t 276 | } 277 | if (start < 1) start = 1 278 | return str.slice(0, start - 1) + substr + str.slice(end) 279 | } 280 | 281 | turnDegreesToDir(args, util) { 282 | // 283 | // util.target.setDirection(util.target.direction + degrees); 284 | console.log('---util-------------\n', util) 285 | console.log('---args-------------\n', args) 286 | console.log('---runtime-------------\n', this.runtime) 287 | } 288 | } 289 | 290 | 291 | 292 | window.tempExt = { 293 | Extension: ArkosExtensions, 294 | info: { 295 | name: 'hcn.extensionName', 296 | description: 'hcn.description', 297 | extensionId: 'hcnTest', 298 | // iconURL: icon, 299 | // insetIconURL: cover, 300 | featured: true, 301 | disabled: false, 302 | collaborator: 'only for hcn test', 303 | }, 304 | l10n: { 305 | 'zh-cn': { 306 | 'hcn.extensionName': 'hcn 的测试', 307 | 'hcn.description': 'hcn 的测试', 308 | }, 309 | en: { 310 | 'hcn.extensionName': 'hcn test', 311 | 'hcn.description': 'hcn test', 312 | }, 313 | }, 314 | } 315 | -------------------------------------------------------------------------------- /extensions/example/normal-ext.js: -------------------------------------------------------------------------------- 1 | class ExampleExtension { 2 | getInfo() { 3 | return { 4 | // Required: the machine-readable name of this extension. 5 | // Will be used as the extension's namespace. Must not contain a '.' character. 6 | id: 'someBlocks', 7 | 8 | // Optional: the human-readable name of this extension as string. 9 | // This and any other string to be displayed in the Scratch UI may either be 10 | // a string or a call to `intlDefineMessage`; a plain string will not be 11 | // translated whereas a call to `intlDefineMessage` will connect the string 12 | // to the translation map (see below). The `intlDefineMessage` call is 13 | // similar to `defineMessages` from `react-intl` in form, but will actually 14 | // call some extension support code to do its magic. For example, we will 15 | // internally namespace the messages such that two extensions could have 16 | // messages with the same ID without colliding. 17 | // See also: https://github.com/yahoo/react-intl/wiki/API#definemessages 18 | name: 'someBlocks.name', 19 | // block color 20 | color1: '#4D7EB4', 21 | // Optional: URI for an icon for this extension. Data URI OK. 22 | menuIconURI: 23 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAAAAACyOJm3AAAAFklEQVQYV2P4DwMMEMgAI/+DE' + 24 | 'UIMBgAEWB7i7uidhAAAAABJRU5ErkJggg==', 25 | 26 | blockIconURI: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QA/wD/AP+gvaeTAAAEtklEQVRoge2ZW4hWVRTHf984Kk1jlj2kI2H2UIwRacVot7EiSLtQBFlPRWUvSQTdsJCCyogy6CWSiMoeqrGioKDJLmpWYxcjxyZSJ4hRuznihZnQRj09rP/unO+b852z9/edT3qYPxz2OXuv9V9rX87Za68DYxhDQ1AqmO9sYAEwDzgDaAMmq20/8BuwDegBuoEfC7Y/CuOAl4HdwAfAyRmy44E7gF4gCrw2A7eLoxpagC758jZwXEhHbqww+FIVuYuBvoTcHuBN4DagA5ghR1p036G2Lsk6vT7goio2llf4cndIR96X0tPAQeCIOpfE/cCI5AaAxUBzgI1m4E7pRuK6t0LmioT9JyT3TYANfpHS6SKPgMPAc8DMBGkErAAmhpBXYCLwbILvcezdekydiICngON1P0TAuz0opTY9P0g8+u76B5uForBYnBE2AxFwFFsVJWACNphHyX6nyrBJRFcm6s4BPiLuyJL6fR+FJQn+dcCFibY5qt8RQuiWzocV9d2qf71WTz3wRhXbq1T/YgjZNOy7HwGPqO4SPR8ATqrH0xxMkY1INgHuwpbUIeDMUMKbpRwBn2BT7b5kjcYK2foUeJcClvMiYG+CaASbrUZjOvZiO7vD2Ke6LpxCvG6/rpcsAN/K5jvAqXnCTR6EfwK/6n5t7X4FY53KrXh8qXw6AvbpBfgipe1qYKeMLfTk89HbUGG7EGzDpnl2StsO4rU8EMCZp3eu2rb6kPnOiAvF93jKF4FBlScWSepinpaUtoXY6A5gZxFf5Om1yObBIE9z4JbAsYa3XZ+ldVp9vhSCun3oAP7ARuXLAL2vdCXPJ83A56oP4YmA34HzA/TK0IntqBEWJrQG6G6R3irsKDAdeE11vQE8rcQhyhDVT5FVMYP4TLKS8CTFfOJBSF7DxIGgL5qwiDcC/sJjl3coYUFiBKzBEhG1YA7wHjYgg9jIpu1DPhiX8OljPAf2Bins5dgEiL5oA/Zhvl3vo7BZwvc10Kla8QDm2/d5gudJcB922P+/oZV4VsqWaeU+cq3K1diL6TAN+A74rEEOpmEtFsonl/cQ8Jbur8tSXoP1NpnDmgn0E395phblaQamJuz1U74hLlJ9dxaBi3Lb9TwZ+JnyT+hlhbqcjssrbP5EHLi2kxIVVy6tKSpd5PkMdtjvA15V3axCXU7HWSpfke124lzBbpVZ+ej/ZuQmLDo9gmUuZgG3qu35Ql1OxwuydYtsH5IvC+Rb7jnlYUbvxg+pbTbhMVet6JEtdzpcmuLX0iyCJmAZ1tte4B7iXXQCNjLDhCWrQzEe+Fu2XGq0JF965dsy/A+FqfiB8pFqBFxqdFOIUmivHPkFgXohmKcyd/dOIrQj7v24NENmPbAdeBSYC5yga67qthOnetIwX2XIuSUYLv+7K0PG97dbNeyiPO9bONqId/j1GXLO0ZXYAeuAri2qy+uIyzH3E/+fKQwlLM6KgI3ApAzZPEfz2ifJhkueF4prRLyf/Fir3o4gG+63xlWePnphtUiXe8gW0RGAJyXX5SHrjZ0i7fSQLaojnQSkYX2TCiOE7+bVuEMTfYfx+Pnpu49sDDS+IaMtLaOfhZ5A+TGMoUj8CyaTiB/WbnspAAAAAElFTkSuQmCC', 27 | 28 | // Optional: Link to documentation content for this extension. 29 | // If not present, offer no link. 30 | docsURI: 'https://ccw.site', 31 | 32 | // Required: the list of blocks implemented by this extension, 33 | // in the order intended for display. 34 | // Scratch object is pera 35 | blocks: [ 36 | { 37 | opcode: 'example-noop', 38 | blockType: Scratch.BlockType.COMMAND, 39 | blockAllThreads: false, 40 | text: 'do nothing', 41 | func: 'noop', 42 | }, 43 | { 44 | opcode: 'example-conditional', 45 | blockType: Scratch.BlockType.CONDITIONAL, 46 | branchCount: 4, 47 | isTerminal: true, 48 | blockAllThreads: false, 49 | text: 'choose [BRANCH]', 50 | arguments: { 51 | BRANCH: { 52 | type: Scratch.ArgumentType.NUMBER, 53 | defaultValue: 1, 54 | }, 55 | }, 56 | func: 'noop', 57 | }, 58 | { 59 | // Required: the machine-readable name of this operation. 60 | // This will appear in project JSON. Must not contain a '.' character. 61 | opcode: 'myReporter', // becomes 'someBlocks.myReporter' 62 | 63 | // Required: the kind of block we're defining, from a predefined list: 64 | // 'command' - a normal command block, like "move {} steps" 65 | // 'reporter' - returns a value, like "direction" 66 | // 'Boolean' - same as 'reporter' but returns a Boolean value 67 | // 'hat' - starts a stack if its value is truthy 68 | // 'conditional' - control flow, like "if {}" or "repeat {}" 69 | // A 'conditional' block may return the one-based index of a branch 70 | // to run, or it may return zero/falsy to run no branch. Each time a 71 | // child branch finishes, the block is called again. This is only a 72 | // slight change to the current model for control flow blocks, and is 73 | // also compatible with returning true/false for an "if" or "repeat" 74 | // block. 75 | // TODO: Consider Blockly-like nextStatement, previousStatement, and 76 | // output attributes as an alternative. Those are more flexible, but 77 | // allow bad combinations. 78 | blockType: Scratch.BlockType.REPORTER, 79 | 80 | // Required for conditional blocks, ignored for others: the number of 81 | // child branches this block controls. An "if" or "repeat" block would 82 | // specify a branch count of 1; an "if-else" block would specify a 83 | // branch count of 2. 84 | // TODO: should we support dynamic branch count for "switch"-likes? 85 | branchCount: 0, 86 | 87 | // Optional, default false: whether or not this block ends a stack. 88 | // The "forever" and "stop all" blocks would specify true here. 89 | isTerminal: true, 90 | 91 | // Optional, default false: whether or not to block all threads while 92 | // this block is busy. This is for things like the "touching color" 93 | // block in compatibility mode, and is only needed if the VM runs in a 94 | // worker. We might even consider omitting it from extension docs... 95 | blockAllThreads: false, 96 | 97 | // Required: the human-readable text on this block, including argument 98 | // placeholders. Argument placeholders should be in [MACRO_CASE] and 99 | // must be [ENCLOSED_WITHIN_SQUARE_BRACKETS]. 100 | text: 'letter [LETTER_NUM] of [TEXT]', 101 | 102 | // Required: describe each argument. 103 | // Note that this is an array: the order of arguments will be used 104 | arguments: { 105 | // Required: the ID of the argument, which will be the name in the 106 | // args object passed to the implementation function. 107 | LETTER_NUM: { 108 | // Required: type of the argument / shape of the block input 109 | type: Scratch.ArgumentType.NUMBER, 110 | 111 | // Optional: the default value of the argument 112 | defaultValue: 1, 113 | }, 114 | 115 | // Required: the ID of the argument, which will be the name in the 116 | // args object passed to the implementation function. 117 | TEXT: { 118 | // Required: type of the argument / shape of the block input 119 | type: Scratch.ArgumentType.STRING, 120 | 121 | // Optional: the default value of the argument 122 | defaultValue: 'text', 123 | }, 124 | }, 125 | 126 | // Optional: a string naming the function implementing this block. 127 | // If this is omitted, use the opcode string. 128 | func: 'myReporter', 129 | 130 | // Optional: list of target types for which this block should appear. 131 | // If absent, assume it applies to all builtin targets -- that is: 132 | // ['sprite', 'stage'] 133 | filter: ['someBlocks.wedo2', 'sprite', 'stage'], 134 | }, 135 | { 136 | opcode: 'example-Boolean', 137 | blockType: Scratch.BlockType.BOOLEAN, 138 | text: 'return true', 139 | func: 'returnTrue', 140 | }, 141 | { 142 | opcode: 'example-hat', 143 | blockType: Scratch.BlockType.HAT, 144 | text: 'after forever', 145 | func: 'returnFalse', 146 | }, 147 | { 148 | // Another block... 149 | }, 150 | ], 151 | 152 | // Optional: define extension-specific menus here. 153 | menus: { 154 | // Required: an identifier for this menu, unique within this extension. 155 | menuA: [ 156 | // Static menu: list items which should appear in the menu. 157 | { 158 | // Required: the value of the menu item when it is chosen. 159 | value: 'itemId1', 160 | 161 | // Optional: the human-readable label for this item. 162 | // Use `value` as the text if this is absent. 163 | text: 'Item One', 164 | }, 165 | 166 | // The simplest form of a list item is a string which will be used as 167 | // both value and text. 168 | 'itemId2', 169 | ], 170 | 171 | // Dynamic menu: a string naming a function which returns an array as above. 172 | // Called each time the menu is opened. 173 | menuB: 'getItemsForMenuB', 174 | }, 175 | 176 | // Optional: translations 177 | translation_map: { 178 | 'zh-cn': { 179 | 'someBlocks.name': 'CCW Lab', 180 | 'someBlocks.setValue': '设置[KEY]=[VALUE]', 181 | 'someBlocks.getValue': '获取[KEY]的值', 182 | }, 183 | en: { 184 | 'someBlocks.name': 'CCW Lab', 185 | 'someBlocks.setValue': 'set[KEY]=[VALUE]', 186 | 'someBlocks.getValue': 'get[KEY]', 187 | }, 188 | }, 189 | 190 | // Optional: list new target type(s) provided by this extension. 191 | targetTypes: [ 192 | 'wedo2', // automatically transformed to 'someBlocks.wedo2' 193 | 'speech', // automatically transformed to 'someBlocks.speech' 194 | ], 195 | } 196 | } 197 | noop () {} 198 | returnTrue () { 199 | return true 200 | } 201 | returnFalse () { 202 | return false 203 | } 204 | } 205 | 206 | /** dont forget register your extension to Scratch */ 207 | Scratch.extensions.register(new ExampleExtension()) 208 | -------------------------------------------------------------------------------- /extensions/kukemc/I18n/index.js: -------------------------------------------------------------------------------- 1 | import KukeMCI18n from "./extension.js"; 2 | import { kukemcI18nIcon, kukemcI18nCover, kukemcI18nExtensionId } from "./assets"; 3 | 4 | window.tempExt = { 5 | Extension: KukeMCI18n, 6 | info: { 7 | name: "kukeMCI18n.name", 8 | description: "kukeMCI18n.description", 9 | doc: "https://learn.ccw.site/article/99e0432c-98f2-4394-8a32-e501beee1e27", 10 | extensionId: kukemcI18nExtensionId, 11 | iconURL: kukemcI18nCover, 12 | insetIconURL: kukemcI18nIcon, 13 | featured: true, 14 | disabled: false, 15 | collaborator: "酷可mc @ CCW", 16 | collaboratorURL: "https://www.ccw.site/student/203910367", 17 | collaboratorList: [ 18 | { 19 | collaborator: "酷可mc @ CCW", 20 | collaboratorURL: "https://www.ccw.site/student/203910367", 21 | }, 22 | { 23 | collaborator: "YUEN @ CCW", 24 | collaboratorURL: "https://www.ccw.site/student/236217560", 25 | }, 26 | { 27 | collaborator: "FurryR @ GitHub", 28 | collaboratorURL: "https://github.com/FurryR", 29 | }, 30 | ], 31 | }, 32 | l10n: { 33 | "zh-cn": { 34 | "kukeMCI18n.name": "I18n", 35 | "kukeMCI18n.description": "你的首款游戏多语言扩展,助你轻松实现游戏内容的国际化支持。", 36 | }, 37 | en: { 38 | "kukeMCI18n.name": "I18n", 39 | "kukeMCI18n.description": 40 | "Your first game's multilingual extension, helping you effortlessly implement internationalization support for your game content.", 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /extensions/kukemc/I18n/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "franc-min": "^6.2.0", 4 | "moment-timezone": "^0.5.45" 5 | }, 6 | "devDependencies": { 7 | "@babel/core": "^7.24.0", 8 | "@babel/preset-env": "^7.24.0", 9 | "babel-loader": "^9.1.3", 10 | "path-browserify": "^1.0.1", 11 | "webpack": "^5.90.3", 12 | "webpack-cli": "^5.1.4", 13 | "webpack-dev-server": "^5.0.3" 14 | }, 15 | "name": "KukeMCI18n", 16 | "version": "1.0.0", 17 | "description": "I18n extension", 18 | "main": "index.js", 19 | "scripts": { 20 | "production": "npx webpack --mode production" 21 | }, 22 | "keywords": [ 23 | "extension" 24 | ], 25 | "author": "kukemc", 26 | "license": "ISC" 27 | } -------------------------------------------------------------------------------- /extensions/kukemc/I18n/webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: { 7 | I18n_dev : './index.js', //开发调试用 8 | I18n_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 | fallback: { // 对于不支持动态import的环境提供polyfill 33 | "path": require.resolve("path-browserify"), 34 | }, 35 | }, 36 | }; -------------------------------------------------------------------------------- /extensions/kukemc/WebHook/extension.js: -------------------------------------------------------------------------------- 1 | import { extensionId, Icon } from "./assets"; 2 | 3 | export default class kukemcWebhook { 4 | constructor(runtime) { 5 | this.runtime = runtime; 6 | 7 | this._formatMessage = runtime.getFormatMessage({ 8 | "zh-cn": { 9 | "kukemcWebhook.div.1": "✨ WebHook 参数", 10 | "kukemcWebhook.div.2": "🌍 WebHook 请求", 11 | "kukemcWebhook.block.webHookRequest": "请求Webhook [URL]", 12 | "kukemcWebhook.block.webHookRequestCompleted": "当Webhook请求完成时 状态码=[STATUS]", 13 | "kukemcWebhook.block.setHeader": "设置请求头 [KEY] 值 [VALUE]", 14 | "kukemcWebhook.block.setMethod": "设置请求方法 [METHOD]", 15 | "kukemcWebhook.block.setBody": "设置请求参数为 [BODY]", 16 | "kukemcWebhook.block.checkStatus": "检查状态码是否为 [CODE]", 17 | "kukemcWebhook.block.cancelRequest": "取消当前请求", 18 | "kukemcWebhook.block.getRemainingPoints": "剩余请求点数", 19 | "kukemcWebhook.tip.rateLimit": "请求过于频繁!请等待数秒再试。", 20 | }, 21 | en: { 22 | "kukemcWebhook.div.1": "✨ WebHook Parameters", 23 | "kukemcWebhook.div.2": "🌍 WebHook Request", 24 | "kukemcWebhook.block.webHookRequest": "Request Webhook [URL]", 25 | "kukemcWebhook.block.webHookRequestCompleted": "When Webhook request completes with status [STATUS]", 26 | "kukemcWebhook.block.setHeader": "Set request header [KEY] to [VALUE]", 27 | "kukemcWebhook.block.setMethod": "Set request method to [METHOD]", 28 | "kukemcWebhook.block.setBody": "Set request body to [BODY]", 29 | "kukemcWebhook.block.checkStatus": "Check if status code is [CODE]", 30 | "kukemcWebhook.block.cancelRequest": "Cancel current request", 31 | "kukemcWebhook.block.getRemainingPoints": "Remaining Request Points", 32 | "kukemcWebhook.tip.rateLimit": "Rate limit exceeded! Try again in a few seconds.", 33 | }, 34 | uk: { 35 | "kukemcWebhook.div.1": "✨ Параметри вебхуку", 36 | "kukemcWebhook.div.2": "🌍 Запит вебхуку", 37 | "kukemcWebhook.block.webHookRequest": "Запит вебхуку [URL]", 38 | "kukemcWebhook.block.webHookRequestCompleted": "Коли запит вебхуку завершується статусом [STATUS]", 39 | "kukemcWebhook.block.setHeader": "Надати заголовок запиту [KEY] до [VALUE]", 40 | "kukemcWebhook.block.setMethod": "Надати метод запиту в [METHOD]", 41 | "kukemcWebhook.block.setBody": "Надати тіло запиту в [BODY]", 42 | "kukemcWebhook.block.checkStatus": "Перевірити чи є код статусу [CODE]", 43 | "kukemcWebhook.block.cancelRequest": "Скасувати поточний запит", 44 | "kukemcWebhook.block.getRemainingPoints": "Залишок балів запиту", 45 | "kukemcWebhook.tip.rateLimit": "Ліміт швидкості перевищено! Повторіть спробу за кілька секунд.", 46 | }, 47 | }); 48 | 49 | this._lastStatusCode = 0; 50 | this._headers = {}; 51 | this._method = "GET"; 52 | this._body = "{}"; 53 | this._controller = null; 54 | this._points = 3; 55 | this._rateLimitMessage = this.formatMessage("kukemcWebhook.tip.rateLimit"); 56 | 57 | this._intervalId = setInterval(() => { 58 | if (this._points < 3) { 59 | this._points++; 60 | } 61 | }, 1000); // 每秒恢复一点,最大为三点 62 | } 63 | 64 | formatMessage(id) { 65 | return this._formatMessage({ 66 | id, 67 | default: id, 68 | description: id, 69 | }); 70 | } 71 | 72 | getInfo() { 73 | const webHookRequest = { 74 | opcode: "webHookRequest", 75 | blockType: Scratch.BlockType.COMMAND, 76 | text: this.formatMessage("kukemcWebhook.block.webHookRequest"), 77 | arguments: { 78 | URL: { 79 | type: Scratch.ArgumentType.STRING, 80 | defaultValue: "https://example.com", 81 | }, 82 | }, 83 | }; 84 | 85 | const webHookRequestCompleted = { 86 | opcode: "webHookRequestCompleted", 87 | blockType: Scratch.BlockType.HAT, 88 | isEdgeActivated: false, 89 | text: this.formatMessage("kukemcWebhook.block.webHookRequestCompleted"), 90 | arguments: { 91 | STATUS: { 92 | type: 'ccw_hat_parameter', 93 | }, 94 | }, 95 | }; 96 | 97 | const setHeader = { 98 | opcode: "setHeader", 99 | blockType: Scratch.BlockType.COMMAND, 100 | text: this.formatMessage("kukemcWebhook.block.setHeader"), 101 | arguments: { 102 | KEY: { 103 | type: Scratch.ArgumentType.STRING, 104 | defaultValue: "Authorization", 105 | }, 106 | VALUE: { 107 | type: Scratch.ArgumentType.STRING, 108 | defaultValue: "Bearer token", 109 | }, 110 | }, 111 | }; 112 | 113 | const setMethod = { 114 | opcode: "setMethod", 115 | blockType: Scratch.BlockType.COMMAND, 116 | text: this.formatMessage("kukemcWebhook.block.setMethod"), 117 | arguments: { 118 | METHOD: { 119 | type: Scratch.ArgumentType.STRING, 120 | menu: "METHOD", 121 | defaultValue: "GET", 122 | }, 123 | }, 124 | }; 125 | 126 | const setBody = { 127 | opcode: "setBody", 128 | blockType: Scratch.BlockType.COMMAND, 129 | text: this.formatMessage("kukemcWebhook.block.setBody"), 130 | arguments: { 131 | BODY: { 132 | type: Scratch.ArgumentType.STRING, 133 | defaultValue: "{}", 134 | }, 135 | }, 136 | }; 137 | 138 | const checkStatus = { 139 | opcode: "checkStatus", 140 | blockType: Scratch.BlockType.BOOLEAN, 141 | text: this.formatMessage("kukemcWebhook.block.checkStatus"), 142 | arguments: { 143 | CODE: { 144 | type: Scratch.ArgumentType.NUMBER, 145 | defaultValue: 200, 146 | }, 147 | }, 148 | }; 149 | 150 | const cancelRequest = { 151 | opcode: "cancelRequest", 152 | blockType: Scratch.BlockType.COMMAND, 153 | text: this.formatMessage("kukemcWebhook.block.cancelRequest"), 154 | }; 155 | 156 | const getRemainingPoints = { 157 | opcode: "getRemainingPoints", 158 | blockType: Scratch.BlockType.REPORTER, 159 | text: this.formatMessage("kukemcWebhook.block.getRemainingPoints"), 160 | }; 161 | 162 | return { 163 | id: extensionId, 164 | name: "WebHook", 165 | blockIconURI: Icon, 166 | menuIconURI: Icon, 167 | color1: "#13003a", 168 | color2: "#0a001e", 169 | docsURI: "https://learn.ccw.site/article/99e0432c-98f2-4394-8a32-e501beee1e27", 170 | blocks: [ 171 | "---" + this.formatMessage("kukemcWebhook.div.1"), 172 | setMethod, 173 | setHeader, 174 | setBody, 175 | "---" + this.formatMessage("kukemcWebhook.div.2"), 176 | webHookRequest, 177 | webHookRequestCompleted, 178 | checkStatus, 179 | cancelRequest, 180 | getRemainingPoints, 181 | ], 182 | menus: { 183 | METHOD: ["GET", "POST", "PUT", "DELETE"], 184 | }, 185 | }; 186 | } 187 | 188 | async webHookRequest({ URL }) { 189 | this._points--; 190 | if (this._points > 0) { 191 | this._controller = new AbortController(); 192 | const options = { 193 | method: this._method, 194 | headers: { "Content-Type": "application/json", ...this._headers }, 195 | body: this._method === "GET" ? undefined : this._body, 196 | signal: this._controller.signal, 197 | credentials: "omit", 198 | }; 199 | 200 | try { 201 | const response = await fetch(URL, options); 202 | this._lastStatusCode = response.status; 203 | this.runtime.startHatsWithParams(`${extensionId}_webHookRequestCompleted`, { parameters: { STATUS: response.status } }); 204 | } catch (error) { 205 | if (error.name === "AbortError") { 206 | // 请求被取消 207 | } else { 208 | // 其他错误 209 | } 210 | } 211 | } else { 212 | this.runtime.scratchBlocks.utils?.toast(this._rateLimitMessage); 213 | } 214 | } 215 | 216 | setHeader({ KEY, VALUE }) { 217 | this._headers[KEY] = VALUE; 218 | } 219 | 220 | setMethod({ METHOD }) { 221 | this._method = METHOD; 222 | } 223 | 224 | setBody({ BODY }) { 225 | this._body = BODY; 226 | } 227 | 228 | checkStatus({ CODE }) { 229 | return this._lastStatusCode === CODE; 230 | } 231 | 232 | cancelRequest() { 233 | if (this._controller) { 234 | this._controller.abort(); 235 | this._controller = null; 236 | } 237 | } 238 | 239 | getRemainingPoints() { 240 | return this._points; 241 | } 242 | 243 | webHookRequestCompleted() { 244 | return true; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /extensions/kukemc/WebHook/index.js: -------------------------------------------------------------------------------- 1 | import kukemcWebhook from "./extension.js"; 2 | import { Icon, Cover, extensionId } from "./assets"; 3 | 4 | window.tempExt = { 5 | Extension: kukemcWebhook, 6 | info: { 7 | name: "kukemcWebhook.name", 8 | description: "kukemcWebhook.description", 9 | doc: "https://learn.ccw.site/article/99e0432c-98f2-4394-8a32-e501beee1e27", 10 | extensionId: extensionId, 11 | iconURL: Cover, 12 | insetIconURL: Icon, 13 | featured: true, 14 | disabled: false, 15 | collaborator: "酷可mc @ CCW", 16 | collaboratorURL: "https://www.ccw.site/student/203910367", 17 | collaboratorList: [ 18 | { 19 | collaborator: "酷可mc @ CCW", 20 | collaboratorURL: "https://www.ccw.site/student/203910367", 21 | }, 22 | { 23 | ccollaborator: 'Arkos @ CCW', 24 | collaboratorURL: 'https://www.ccw.site/student/6107c5323e593a0c25f850f8', 25 | }, 26 | ], 27 | }, 28 | l10n: { 29 | "zh-cn": { 30 | "kukemcWebhook.name": "WebHook", 31 | "kukemcWebhook.description": 32 | "✨更好的WebHook", 33 | }, 34 | en: { 35 | "kukemcWebhook.name": "WebHook", 36 | "kukemcWebhook.description": 37 | "✨Better WebHook", 38 | }, 39 | uk: { 40 | "kukemcWebhook.name": "Вебхук", 41 | "kukemcWebhook.description": 42 | "✨Найкращий вебхук", 43 | }, 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /extensions/kukemc/WebHook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "franc-min": "^6.2.0", 4 | "moment-timezone": "^0.5.45" 5 | }, 6 | "devDependencies": { 7 | "@babel/core": "^7.24.0", 8 | "@babel/preset-env": "^7.24.0", 9 | "babel-loader": "^9.1.3", 10 | "path-browserify": "^1.0.1", 11 | "webpack": "^5.90.3", 12 | "webpack-cli": "^5.1.4", 13 | "webpack-dev-server": "^5.0.3" 14 | }, 15 | "name": "kukemcWebhook", 16 | "version": "1.0.0", 17 | "description": "I18n extension", 18 | "main": "index.js", 19 | "scripts": { 20 | "production": "npx webpack --mode production" 21 | }, 22 | "keywords": [ 23 | "extension" 24 | ], 25 | "author": "kukemc", 26 | "license": "ISC" 27 | } -------------------------------------------------------------------------------- /extensions/kukemc/WebHook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: { 7 | Webhook_dev : './index.js', //开发调试用 8 | Webhook_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 | fallback: { // 对于不支持动态import的环境提供polyfill 33 | "path": require.resolve("path-browserify"), 34 | }, 35 | }, 36 | }; -------------------------------------------------------------------------------- /extensions/sipc.ink/CloudMusic/assets/cover.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/sipc.ink/Consoles/Consoles.js: -------------------------------------------------------------------------------- 1 | (function (Scratch) { 2 | "use strict"; 3 | const icon = 4 | "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI4MS41NTIwNyIgaGVpZ2h0PSI4MC42MDMwOCIgdmlld0JveD0iMCwwLDgxLjU1MjA3LDgwLjYwMzA4Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTk5LjIyMzk3LC0xNDAuNjk4NDYpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2UtbGluZWNhcD0iYnV0dCIgc3Ryb2tlLWxpbmVqb2luPSJtaXRlciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTI4MC43NzYwMywxODFjMCwyMi4yNTc5MiAtMTguMjU2MDUsNDAuMzAxNTQgLTQwLjc3NjAzLDQwLjMwMTU0Yy0yMi41MTk5OCwwIC00MC43NzYwMywtMTguMDQzNjEgLTQwLjc3NjAzLC00MC4zMDE1NGMwLC0yMi4yNTc5MiAxOC4yNTYwNSwtNDAuMzAxNTQgNDAuNzc2MDMsLTQwLjMwMTU0YzIyLjUxOTk4LDAgNDAuNzc2MDMsMTguMDQzNjEgNDAuNzc2MDMsNDAuMzAxNTR6IiBmaWxsPSIjODA4MDgwIiBzdHJva2Utd2lkdGg9IjAiLz48cGF0aCBkPSJNMjY2LjE2NTgzLDE2Mi4xOTM1NnYyOS4yMDMwMWMwLDIuMjU4MzMgLTEuODMwNTksNC4wODg0MSAtNC4wODg0MSw0LjA4ODQxaC00NC4xNTQ4NWMtMi4yNTgzMywwIC00LjA4ODQxLC0xLjgzMDA3IC00LjA4ODQxLC00LjA4ODQxdi0yOS4yMDMwMWMwLC0yLjI1ODMzIDEuODMwMDcsLTQuMDg4NDEgNC4wODg0MSwtNC4wODg0MWg0NC4xNTQ4NWMyLjI1NzgzLDAgNC4wODg0MSwxLjgzMDA3IDQuMDg4NDEsNC4wODg0MXpNMjYyLjM1NTk1LDE2NC42NDUwOGMwLC0xLjM0MjAyIC0xLjA4ODAzLC0yLjQzMDA1IC0yLjQzMDA1LC0yLjQzMDA1aC0zOS44NTEyOGMtMS4zNDIwMiwwIC0yLjQzMDA1LDEuMDg4MDMgLTIuNDMwMDUsMi40MzAwNXYyNC4yOTk0N2MwLDEuMzQyMDIgMS4wODgwMywyLjQzMDA1IDIuNDMwMDUsMi40MzAwNWg3Ljc3NDYzdi0xMC4yMDU2OWMwLC0xLjM0MjU0IDEuMDg4MDMsLTIuNDMwMDUgMi40MzAwNSwtMi40MzAwNWMxLjM0MjU0LDAgMi40MzAwNSwxLjA4ODAzIDIuNDMwMDUsMi40MzAwNXYxMC4yMDU2OWg0Ljg2MDF2LTE4Ljk1Mzg4YzAsLTEuMzQyMDIgMS4wODgwMywtMi40MzAwNSAyLjQzMDA1LC0yLjQzMDA1YzEuMzQyNTQsMCAyLjQzMDA1LDEuMDg4MDMgMi40MzAwNSwyLjQzMDA1djE4Ljk1Mzg4aDQuODYwMXYtMTQuMDkzNzhjMCwtMS4zNDIwMiAxLjA4ODAzLC0yLjQzMDA1IDIuNDMwMDUsLTIuNDMwMDVjMS4zNDI1NCwwIDIuNDMwMDUsMS4wODgwMyAyLjQzMDA1LDIuNDMwMDV2MTQuMDkzNzhoNy43NzYxNmMxLjM0MjAyLDAgMi40MzAwNSwtMS4wODgwMyAyLjQzMDA1LC0yLjQzMDA1ek0yNTMuMDgyOTIsMjAxLjU1ODgzYzAsMS4yOTA0MSAtMS4wNDYxMiwyLjMzNjAyIC0yLjMzNjAyLDIuMzM2MDJoLTIxLjQ5MzhjLTEuMjg5ODksMCAtMi4zMzYwMiwtMS4wNDU2MSAtMi4zMzYwMiwtMi4zMzYwMmMwLC0xLjI4OTg5IDEuMDQ1NjEsLTIuMzM2MDIgMi4zMzYwMiwtMi4zMzYwMmgyMS40OTM4YzEuMjg5ODksMCAyLjMzNjAyLDEuMDQ2MTMgMi4zMzYwMiwyLjMzNjAyek0yNTAuNzQ2OSwxOTkuMjIyODEiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMSIvPjwvZz48L2c+PC9zdmc+PCEtLXJvdGF0aW9uQ2VudGVyOjQwLjc3NjAzMzMzMzMzMzQ6MzkuMzAxNTM5OTk5OTk5OTYtLT4="; 5 | const icon2 = 6 | "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjczNzk0NjIzOTU3IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjQ0NDAiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTk0NC4zOCA3MC4xOWgtODY0Yy00NC4xOSAwLTgwIDM1LjgxLTgwIDgwdjU3MS40M2MwIDQ0LjE5IDM1LjgxIDgwIDgwIDgwaDg2NGM0NC4xOCAwIDgwLTM1LjgxIDgwLTgwVjE1MC4xOWMwLTQ0LjE5LTM1LjgyLTgwLTgwLTgweiBtNS40NSA2MDMuNDVjMCAyNi4yNi0yMS4yOSA0Ny41NS00Ny41NSA0Ny41NUg3NTAuMTJWNDQ1LjQxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYyNzUuNzhoLTk1LjFWMzUwLjMxYzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOS00Ny41NSA0Ny41NXYzNzAuODhoLTk1LjF2LTE5OS43YzAtMjYuMjYtMjEuMjgtNDcuNTUtNDcuNTUtNDcuNTUtMjYuMjYgMC00Ny41NSAyMS4yOC00Ny41NSA0Ny41NXYxOTkuN0gxMjIuNDljLTI2LjI2IDAtNDcuNTUtMjEuMjktNDcuNTUtNDcuNTVWMTk4LjE2YzAtMjYuMjYgMjEuMjktNDcuNTUgNDcuNTUtNDcuNTVoNzc5Ljc5YzI2LjI2IDAgNDcuNTUgMjEuMjkgNDcuNTUgNDcuNTV2NDc1LjQ4ek03MjIuNjcgODc0Ljc2SDMwMi4wOWMtMjUuMjUgMC00NS43MSAyMC40Ny00NS43MSA0NS43MSAwIDI1LjI1IDIwLjQ3IDQ1LjcxIDQ1LjcxIDQ1LjcxaDQyMC41OGMyNS4yNCAwIDQ1LjcxLTIwLjQ2IDQ1LjcxLTQ1LjcxIDAtMjUuMjQtMjAuNDctNDUuNzEtNDUuNzEtNDUuNzF6IG0wIDAiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjQ0NDEiPjwvcGF0aD48L3N2Zz4="; 7 | Scratch.translate.setup({ 8 | zh: { 9 | name: "控制台", 10 | Emptying: "清空控制台", 11 | Information: "信息 [string]", 12 | Journal: "日志 [string]", 13 | Warning: "警告 [string]", 14 | Error: "错误 [string]", 15 | Debug: "调试 [string]", 16 | Group: "创建一个名为 [string] 的分组", 17 | GroupCollapsed: "创建一个名为 [string] 的折叠分组", 18 | GroupEnd: "退出当前分组", 19 | Timeron: "启动一个名为 [string] 的计时器", 20 | Timerlog: "打印名为 [string] 的计时器所运行的时间", 21 | Timeroff: "结束名为 [string] 的计时器并以打印从开始到结束所用的时间", 22 | }, 23 | }); 24 | class Consoles { 25 | constructor() {} 26 | getInfo() { 27 | return { 28 | id: "Consoles", 29 | name: Scratch.translate({ id: "name", default: "Consoles" }), 30 | color1: "#808080", 31 | color2: "#8c8c8c", 32 | color3: "#999999", 33 | menuIconURI: icon, 34 | blockIconURI: icon2, 35 | blocks: [ 36 | { 37 | opcode: "Emptying", 38 | blockType: Scratch.BlockType.COMMAND, 39 | text: Scratch.translate({ id: "Emptying", default: "clear console" }), 40 | arguments: {}, 41 | }, 42 | { 43 | opcode: "Information", 44 | blockType: Scratch.BlockType.COMMAND, 45 | text: Scratch.translate({ id: "Information", default: "Information [string]" }), 46 | arguments: { 47 | string: { 48 | type: Scratch.ArgumentType.STRING, 49 | defaultValue: "Information", 50 | }, 51 | }, 52 | }, 53 | { 54 | opcode: "Journal", 55 | blockType: Scratch.BlockType.COMMAND, 56 | text: Scratch.translate({ id: "Journal", default: "Journal [string]" }), 57 | arguments: { 58 | string: { 59 | type: Scratch.ArgumentType.STRING, 60 | defaultValue: "Journal", 61 | }, 62 | }, 63 | }, 64 | { 65 | opcode: "Warning", 66 | blockType: Scratch.BlockType.COMMAND, 67 | text: Scratch.translate({ id: "Warning", default: "Warning [string]" }), 68 | arguments: { 69 | string: { 70 | type: Scratch.ArgumentType.STRING, 71 | defaultValue: "Warning", 72 | }, 73 | }, 74 | }, 75 | { 76 | opcode: "Error", 77 | blockType: Scratch.BlockType.COMMAND, 78 | text: Scratch.translate({ id: "Error", default: "Error [string]" }), 79 | arguments: { 80 | string: { 81 | type: Scratch.ArgumentType.STRING, 82 | defaultValue: "Error", 83 | }, 84 | }, 85 | }, 86 | { 87 | opcode: "debug", 88 | blockType: Scratch.BlockType.COMMAND, 89 | text: Scratch.translate({ id: "Debug", default: "Debug [string]" }), 90 | arguments: { 91 | string: { 92 | type: Scratch.ArgumentType.STRING, 93 | defaultValue: "debug", 94 | }, 95 | }, 96 | }, 97 | "---", 98 | { 99 | opcode: "group", 100 | blockType: Scratch.BlockType.COMMAND, 101 | text: Scratch.translate({ id: "Group", default: "Create a group named [string]" }), 102 | arguments: { 103 | string: { 104 | type: Scratch.ArgumentType.STRING, 105 | defaultValue: "group", 106 | }, 107 | }, 108 | }, 109 | { 110 | opcode: "groupCollapsed", 111 | blockType: Scratch.BlockType.COMMAND, 112 | text: Scratch.translate({ id: "GroupCollapsed", default: "Create a fold group named [string]" }), 113 | arguments: { 114 | string: { 115 | type: Scratch.ArgumentType.STRING, 116 | defaultValue: "group", 117 | }, 118 | }, 119 | }, 120 | { 121 | opcode: "groupEnd", 122 | blockType: Scratch.BlockType.COMMAND, 123 | text: Scratch.translate({ id: "GroupEnd", default: "exit current group" }), 124 | arguments: {}, 125 | }, 126 | "---", 127 | { 128 | opcode: "Timeron", 129 | blockType: Scratch.BlockType.COMMAND, 130 | text: Scratch.translate({ id: "Timeron", default: "Start a timer named [string]" }), 131 | arguments: { 132 | string: { 133 | type: Scratch.ArgumentType.STRING, 134 | defaultValue: "Time", 135 | }, 136 | }, 137 | }, 138 | { 139 | opcode: "Timerlog", 140 | blockType: Scratch.BlockType.COMMAND, 141 | text: Scratch.translate({ id: "Timerlog", default: "print the elapsed time of the timer named [string]" }), 142 | arguments: { 143 | string: { 144 | type: Scratch.ArgumentType.STRING, 145 | defaultValue: "Time", 146 | }, 147 | }, 148 | }, 149 | { 150 | opcode: "Timeroff", 151 | blockType: Scratch.BlockType.COMMAND, 152 | text: Scratch.translate({ 153 | id: "Timeroff", 154 | default: "Ends a timer named [string] and prints the elapsed time from start to end", 155 | }), 156 | arguments: { 157 | string: { 158 | type: Scratch.ArgumentType.STRING, 159 | defaultValue: "Time", 160 | }, 161 | }, 162 | }, 163 | ], 164 | }; 165 | } 166 | Emptying() { 167 | console.clear(); 168 | } 169 | Information({ string }) { 170 | console.info(string); 171 | } 172 | Journal({ string }) { 173 | console.log(string); 174 | } 175 | Warning({ string }) { 176 | console.warn(string); 177 | } 178 | Error({ string }) { 179 | console.error(string); 180 | } 181 | debug({ string }) { 182 | console.debug(string); 183 | } 184 | group({ string }) { 185 | console.group(string); 186 | } 187 | groupCollapsed({ string }) { 188 | console.groupCollapsed(string); 189 | } 190 | groupEnd() { 191 | console.groupEnd(); 192 | } 193 | Timeron({ string }) { 194 | console.time(string); 195 | } 196 | Timerlog({ string }) { 197 | console.timeLog(string); 198 | } 199 | Timeroff({ string }) { 200 | console.timeEnd(string); 201 | } 202 | } 203 | Scratch.extensions.register(new Consoles()); 204 | })(Scratch); 205 | 206 | //BY -SIPC- 502415953 207 | -------------------------------------------------------------------------------- /extensions/sipc.ink/Consoles/assets/cover.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/sipc.ink/Recording/assets/cover.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/six-6/DateTime.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/six-6/DateTimePost.svg: -------------------------------------------------------------------------------- 1 | 2h18星期一182022七月 -------------------------------------------------------------------------------- /extensions/wit_cat/BBcode/assets/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | export const witcat_BBcode_icon = 3 | 'data:@file/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKcAAACWCAMAAACM7mjWAAAAjVBMVEUAAADi4eTi4uTh4OPf3+Pq1eri4eXi4eTj4uTi4eXi4uXh4ePf3+Xi4uTi4eXi4eTj4OTf39+/v7/j4uTj4uXi4eTj4uXj4eTj4OTj4uTf3+Df39/h4eTh4OTg3+Th4OPj4uTh3+Ti4uXh4ePi4eLj4uXj3+Pm5ubi4OTi4uXj4ePf39/f39/j4eXi4eQ4kjUHAAAALnRSTlMAn7+AQAzg+sfDuXQo8eekRwgEls2q69hoYxgQbGMwJINwUjosjUAUv7NcMCCHUysqWQAAA2hJREFUeNrt3dly2jAAheEDxvuCF0gwawJkb8/7P17btNO0km1IR7bkjr4XyD8wCC6iY7Ry87gKPc/nEHzPC6s4d/FJByfwOTw/dA64WrENqE+wLXCNTZxRryze4KL6hvrd1Oj2FtAMQYIOK5+m8Fdo485pkrnbkhnSLGFj6PqWprldQ7IxL/N7qGv8m97y1pv1EfowFw4kmmqFP7yZc26K/Dd8MOVbqEmA33ZscxNEkyFEwQ3b7PDLJmWjqD5gOIc6anmtNvjJYZPlGUM7L9nEwbui6SX3HqHDo0dZVuCHLWUvLvRwI8q2+GFBybKELuWSkgW+Syh5KaFPGVGSALijyHOhk+tRdNd4xj9Cr8ems971KVhCtyUFvoucojN021OUI6ZgCv1CCmJMKLiHfvcUVHJ6Av0SCkKIh0AKE6TiUSl1LmCChdTpG/gxAqbiwQRe1ekeHyYKVKv1v3Xyqs7SSamIP1n31rmZUqHs1FNnOaVS2Zd+OmMqNu2ls0ip2qmPzpzKPfTR6VC5sI/Oisp5fXTeUblFH501lVv20fnkU7XXPjoxoWJe0Uvnl4xq5T19v59SqhT39nspWVCZ7AhlnbLjXMk/DHjRvYv+OrvNKHDQxXbaTttpO22n7bSdttN22k7baTttp+20nbbTdtpO22k7bafttJ2203baTttpO22n7bSdttOQzqKOvIZZhvnRrM7cY4tFYlDnzmer9GRM58lnh2xtSGe5YKeJIZ05u/lPZnRWvODVjM6IFzhDdO4/+1dl1RCda/nisuDFiNcT3qULdg4vyAfpfOBfbiFK2C0rB+lcp/zTEZI5O91jkE68CssPkrXHDlHZe6e8i7Ms0CC57ch0MVQnTgHfZSs0e6p8NkrjEv11yvZxNbnLC7R6qh8mkq9H97rfn6O5xzeWe5FjuWdq5L3dMwUhqpHcg47ldP0a7pXnFO2hVfM9/dHsHiCgaAa9ZhQFI9rlSCiJTNw5GctuDLaURQbu8BQZZd4MOsw6do3gsMn8jKHt52ziXNjdeh50dyupn9t2t8zZMXsOUrbZjW4XDnuDd/b2I9wtHM0O5Gh2NeGOY6d0NLuvBr71oTuOXeJiDDvPaY0OiSnfTEGCbjsjdsh3/82u+2h28t8dnND85w58PMdhOuBzHKadz3H4Bhhs5K+IK1U6AAAAAElFTkSuQmCC'; 4 | export const witcat_BBcode_picture = 5 | 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBmaWxsPSJub25lIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI2NDAiIGhlaWdodD0iMzYwIiB2aWV3Qm94PSIwIDAgNjQwIDM2MCI+PGRlZnM+PGNsaXBQYXRoIGlkPSJtYXN0ZXJfc3ZnMF8xNF8wMDMiPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSI2NDAiIGhlaWdodD0iMzYwIiByeD0iMCIvPjwvY2xpcFBhdGg+PGxpbmVhckdyYWRpZW50IHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIiBpZD0ibWFzdGVyX3N2ZzFfMjFfNTciPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNFMTZDOTYiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI0ZCRTRGRCIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48ZmlsdGVyIGlkPSJtYXN0ZXJfc3ZnMl8xNF8wMDQiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiB4PSItNjQiIHk9Ii02MCIgd2lkdGg9IjM5OCIgaGVpZ2h0PSIzOTgiPjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+PGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiLz48ZmVPZmZzZXQgZHk9IjQiIGR4PSIwIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMTYiLz48ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4zMDE5NjA3OTYxMTc3ODI2IDAiLz48ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvdyIvPjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iZWZmZWN0MV9kcm9wU2hhZG93IiByZXN1bHQ9InNoYXBlIi8+PC9maWx0ZXI+PGZpbHRlciBpZD0ibWFzdGVyX3N2ZzNfMTdfMDYzMSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBjb2xvci1pbnRlcnBvbGF0aW9uLWZpbHRlcnM9InNSR0IiIHg9Ii0yMCIgeT0iLTE2IiB3aWR0aD0iNDY1IiBoZWlnaHQ9IjQ2MCI+PGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz48ZmVDb2xvck1hdHJpeCBpbj0iU291cmNlQWxwaGEiIHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAxMjcgMCIvPjxmZU9mZnNldCBkeT0iNCIgZHg9IjAiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSI1Ii8+PGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMzAxOTYwNzk2MTE3NzgyNiAwIi8+PGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3ciLz48ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvdyIgcmVzdWx0PSJzaGFwZSIvPjwvZmlsdGVyPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjbWFzdGVyX3N2ZzBfMTRfMDAzKSI+PGc+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjY0MCIgaGVpZ2h0PSIzNjAiIHJ4PSIwIiBmaWxsPSJ1cmwoI21hc3Rlcl9zdmcxXzIxXzU3KSIgZmlsbC1vcGFjaXR5PSIxIi8+PC9nPjxnIGZpbHRlcj0idXJsKCNtYXN0ZXJfc3ZnMl8xNF8wMDQpIj48cGF0aCBkPSJNMjQxLDQ1QzI3MC44MjMsNDUsMjk1LDcxLjg2MjksMjk1LDEwNUwyOTUsMjU1QzI5NSwyODguMTM3LDI3MC44MjMsMzE1LDI0MSwzMTVMNzksMzE1QzQ5LjE3NjYsMzE1LDI0Ljk5OTk5NjkxMDA5LDI4OC4xMzcsMjUsMjU1TDI1LDEwNUMyNC45OTk5OTM4MjAxOSw3MS44NjI5LDQ5LjE3NjYsNDUsNzksNDVMMjQxLDQ1Wk0yNDEsNzVMNzksNzVDNjQuODcwNiw3NC45OTUsNTMuMTI3Miw4Ny4wOTQ3OTk5OTk5OTk5OSw1Mi4wNjc0OTk5OTk5OTk5OTUsMTAyLjc1TDUyLDEwNUw1MiwyNTVDNTEuOTk1NSwyNzAuNjk5LDYyLjg4NTQsMjgzLjc0OCw3Ni45NzUsMjg0LjkyNUw3OSwyODVMMjQxLDI4NUMyNTUuMTI5LDI4NS4wMDUsMjY2Ljg3MywyNzIuOTA1LDI2Ny45MzMsMjU3LjI1TDI2OCwyNTVMMjY4LDEwNUMyNjguMDA0LDg5LjMwMDYsMjU3LjExNSw3Ni4yNTI1LDI0My4wMjUsNzUuMDc1TDI0MSw3NVpNMjI3LjUsMjI1QzIzNC45NTEsMjI1LjAwOCwyNDAuOTg3LDIzMS43MjEsMjQwLjk4NywyNDBDMjQwLjk4NywyNDguMjc5LDIzNC45NTEsMjU0Ljk5MiwyMjcuNSwyNTVMMjAwLjUsMjU1QzE5My4wNDQsMjU1LDE4NywyNDguMjg0LDE4NywyNDBDMTg3LDIzMS43MTYsMTkzLjA0NCwyMjUsMjAwLjUsMjI1TDIyNy41LDIyNVpNMjAwLjUsMTA1QzIwNy45NTIsMTA1LjAwNTY5OTk5OTk5OTk5LDIxMy45OTEsMTExLjcxOTgsMjEzLjk5MSwxMjBDMjEzLjk5MSwxMjguMjgwMTk5OTk5OTk5OTgsMjA3Ljk1MiwxMzQuOTk0MywyMDAuNSwxMzVMMTYwLDEzNUwxNjAsMjQwQzE1OS45OTMsMjQ4LjI3OSwxNTMuOTUxLDI1NC45ODUsMTQ2LjUsMjU0Ljk4NUMxMzkuMDQ5LDI1NC45ODUsMTMzLjAwNywyNDguMjc5LDEzMywyNDBMMTMzLDEzNUw5Mi41LDEzNUM4NS4wNDA1MDAwMDAwMDAwMSwxMzUuMDA1Nyw3OC45OTA3LDEyOC4yODgzLDc4Ljk5MDcsMTIwQzc4Ljk5MDcsMTExLjcxMTcsODUuMDQwNTAwMDAwMDAwMDEsMTA0Ljk5NDMwMDAwMDAwMDAxLDkyLjUsMTA1TDIwMC41LDEwNVoiIGZpbGw9IiNGRkZGRkYiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48ZyBmaWx0ZXI9InVybCgjbWFzdGVyX3N2ZzNfMTdfMDYzMSkiPjxwYXRoIGQ9Ik0zNjEuMzQzOCw3OS4yTDM1OS4yNSw5NS45MzQ0TDM1NS4wNzgxLDk4LjY4NDRMMzU4LjUzMTIsMTAxLjUxMjVMMzU2LjQ1MzEsMTE1LjM3MTlRMzU1LjkzNzUsMTE3LjQ2NTYsMzU0LjUsMTE4Ljg0MDZRMzUzLjA2MjUsMTIwLjIsMzUwLjk2ODgsMTIwLjJMMzEyLjIzNDM4LDEyMC4yTDMxNy4wNjI1LDgxLjk1TDMxNC45Njg3NSw3OS4yTDM2MS4zNDM4LDc5LjJaTTMzMC4yMzQ0LDg2LjE2ODdMMzI5LjUxNTYsOTUuMkwzNDUuNDM3NSw5NS4yTDM0Ni43OTY5LDg2LjE2ODdMMzMwLjIzNDQsODYuMTY4N1pNMzI4LjE1NjIsMTAyLjE2ODdMMzI2Ljc4MTIsMTEzLjIzMTJMMzQzLjM0MzgsMTEzLjIzMTJMMzQ0LjcxODgsMTAyLjE2ODdMMzI4LjE1NjIsMTAyLjE2ODdaTTQxNy4zNDQsNzkuMkw0MTUuMjUsOTUuOTM0NEw0MTEuMDc4LDk4LjY4NDRMNDE0LjUzMSwxMDEuNTEyNUw0MTIuNDUzLDExNS4zNzE5UTQxMS45MzgsMTE3LjQ2NTYsNDEwLjUsMTE4Ljg0MDZRNDA5LjA2MiwxMjAuMiw0MDYuOTY4OCwxMjAuMkwzNjguMjM0NCwxMjAuMkwzNzMuMDYyNSw4MS45NUwzNzAuOTY4OCw3OS4yTDQxNy4zNDQsNzkuMlpNMzg2LjIzNDQsODYuMTY4N0wzODUuNTE1Niw5NS4yTDQwMS40Mzc1LDk1LjJMNDAyLjc5NjksODYuMTY4N0wzODYuMjM0NCw4Ni4xNjg3Wk0zODQuMTU2MiwxMDIuMTY4N0wzODIuNzgxMiwxMTMuMjMxMkwzOTkuMzQzOCwxMTMuMjMxMkw0MDAuNzE4OCwxMDIuMTY4N0wzODQuMTU2MiwxMDIuMTY4N1pNNDM3LjI2NTk5OTk5OTk5OTk2LDE5MC4xNjlMNDE1LjE1NiwxOTAuMTY5TDQxMi40MjIsMjA3LjIzMUw0MzEuNzE5LDIwNy4yMzFMNDM1LjE3MiwyMDQuNDE5TDQzMy44MTIsMjE0LjJMMzk3Ljg3NSwyMTQuMkw0MDIuNzAzMSwxODMuMkw0MzcuOTg0MDAwMDAwMDAwMDQsMTgzLjJMNDM3LjI2NTk5OTk5OTk5OTk2LDE5MC4xNjlaTTQ3OC4zNTkwMDAwMDAwMDAwNCwxODMuMlE0ODAuNDM4LDE4My4yLDQ4MS40NjksMTg0LjkwM1E0ODIuNSwxODYuNjA2LDQ4Mi41LDE4OC43MTZMNDc5LjA2MiwyMTQuMkw0NDguMTU2LDIxNC4yUTQ0Ni4wNzgsMjE0LjIsNDQ1LjAzMSwyMTIuODI1UTQ0NCwyMTEuNDUsNDQ0LDIwOS4zNDFMNDQ3LjQzOCwxODMuMkw0NzguMzU5MDAwMDAwMDAwMDQsMTgzLjJaTTQ2MC40NTMsMTkwLjE2OUw0NTcuNzM0MDAwMDAwMDAwMDQsMjA3LjIzMUw0NjUuOTY5LDIwNy4yMzFMNDY4Ljc2NTk5OTk5OTk5OTk2LDE5MC4xNjlMNDYwLjQ1MywxOTAuMTY5Wk01MTUuNjU2LDE2OC4yTDUyOS40ODQsMTY4LjJMNTIzLjI4MSwyMTQuMkw0OTIuMTg4LDIxNC4yUTQ5MC4wOTQsMjE0LjIsNDg5LjA0NywyMTIuODI1UTQ4OCwyMTEuNDUsNDg4LDIwOS4zNDFMNDkxLjQ2OSwxODMuMkw1MTMuNTYyLDE4My4yTDUxNS42NTYsMTY4LjJaTTUwNC4yMDMsMTkwLjE2OUw1MDEuNTQ3LDIwNy4yMzFMNTEwLjMyOCwyMDcuMjMxTDUxMi45MjIsMTkwLjE2OUw1MDQuMjAzLDE5MC4xNjlaTTU3MS42NTYsMjAxLjJMNTQ5LjU0NywyMDEuMkw1NDguODI4LDIwNy4yMzFMNTY4LjIwMywyMDcuMjMxTDU3MS42NTYsMjA0LjI2Mkw1NjkuNTYyLDIxNC4yTDUzOS4xODgsMjE0LjJRNTM3LjA5NCwyMTQuMiw1MzYuMDQ3LDIxMi44MjVRNTM1LDIxMS40NSw1MzUsMjA5LjM0MUw1MzguNDY5LDE4My4yTDU3NC4zOTEwMDAwMDAwMDAxLDE4My4yTDU3MS42NTYsMjAxLjJaTTU1MS41NjIsMTkwLjE2OUw1NTAuOTIyLDE5NC4yMzFMNTU5LjIwMywxOTQuMjMxTDU1OS45MjIsMTkwLjE2OUw1NTEuNTYyLDE5MC4xNjlaIiBmaWxsPSIjRkZGRkZGIiBmaWxsLW9wYWNpdHk9IjEiLz48L2c+PC9nPjwvc3ZnPg=='; 6 | -------------------------------------------------------------------------------- /extensions/wit_cat/BBcode/htmlToBBCode.js: -------------------------------------------------------------------------------- 1 | const htmltobbcode = (htmlInput) => { 2 | // Replace
tags with BBCode line breaks (换行) 3 | let bbcodeText = htmlInput.replace(//gi, '\n'); 4 | 5 | // Replace
tags with BBCode line breaks (换行) 6 | bbcodeText = bbcodeText.replace(//gi, '\n'); 7 | 8 | // Replace tags with BBCode bold ([b]) 9 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/strong>/gi, '[b]$1[/b]'); 10 | 11 | // Replace tags with BBCode italic ([i]) 12 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/em>/gi, '[i]$1[/i]'); 13 | 14 | // Replace tags with BBCode deleteline ([s]) 15 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/strike>/gi, '[s]$1[/s]'); 16 | 17 | // Replace tags with BBCode underline ([u]) 18 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/u>/gi, '[u]$1[/u]'); 19 | 20 | // Replace tags with BBCode URL ([url]) 21 | bbcodeText = bbcodeText.replace(/]*?\s+)?href=(["'])(.*?)\1[^>]*>(.*?)<\/a>/gi, '[url=$2]$3[/url]'); 22 | 23 | // Replace tags with BBCode image ([img]) 24 | bbcodeText = bbcodeText.replace(/]*?\s+)?src=(["'])(.*?)\1[^>]*>/gi, '[img]$2[/img]'); 25 | 26 | // Replace
tags with BBCode quote ([quote]) 27 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/blockquote>/gi, '[quote]$1[/quote]'); 28 | 29 | // Replace tags with BBCode code ([code]) 30 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/code>/gi, '[code]$1[/code]'); 31 | 32 | // Replace

to

tags with BBCode headers ([h1] to [h6]) 33 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/h\1>/gi, '[h$1]$2[/h$1]'); 34 | 35 | // Replace
    and
  1. tags with BBCode ordered list ([list=1] and [*]) 36 | bbcodeText = bbcodeText.replace( 37 | /]*>([\s\S]*?)<\/ol>/gi, 38 | '[list=a]$2[/list]' 39 | ); 40 | bbcodeText = bbcodeText.replace(/]*>([\s\S]*?)<\/ol>/gi, '[list=1]$1[/list]'); 41 | 42 | // Replace
      and
    • tags with BBCode list ([list] and [*]) 43 | bbcodeText = bbcodeText.replace(/]*>([\s\S]*?)<\/ul>/gi, '[list]$1[/list]'); 44 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/li>/gi, '[*]$1'); 45 | 46 | // Replace

      tags with BBCode paragraph ([p]) 47 | bbcodeText = bbcodeText.replace(/]*>(.*?)<\/p>/gi, '[p]$1[/p]'); 48 | 49 | // Replace with BBCode size ([size=X]) 50 | bbcodeText = bbcodeText.replace( 51 | /]*>(.*?)<\/span>/gi, 52 | '[size=$2]$3[/size]' 53 | ); 54 | 55 | // Replace with BBCode color ([color=X]) 56 | bbcodeText = bbcodeText.replace( 57 | /]*>(.*?)<\/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 | 
      
      
      --------------------------------------------------------------------------------