diff --git a/extensions/ShowierData9978/html.js b/extensions/ShowierData9978/html.js new file mode 100644 index 0000000000..6684e57f6e --- /dev/null +++ b/extensions/ShowierData9978/html.js @@ -0,0 +1,260 @@ +// Name: HTWL +// ID: ShowierTWHtml +// Description: Allows for building HTML within scratch. +// By: ShowierData9978 +/* eslint-disable require-await */ +/// + +/** + * @typedef {import("@turbowarp/scratch-vm/")} + + */ + +((Scratch) => { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("HTML Extension must be run unsandboxed"); + } + + /** + * @type {VM} + */ + const vm = Scratch.vm; + + class Html { + constructor() { + /** + * @typedef {Object.>} stack + * @typedef {Object.} html + */ + + /** @type {stack} */ + this.stack = {}; + /** @type {html} */ + this.html = {}; + } + + /** + * + * @returns {Scratch.Info} + */ + getInfo() { + return { + id: "HTWL", + name: "HTML", + color1: "#FF0000", + blocks: [ + { + opcode: "htmlWrap", + blockType: Scratch.BlockType.CONDITIONAL, + text: "<[element] [attributes]>", + arguments: { + element: { + type: Scratch.ArgumentType.STRING, + defaultValue: "div", + }, + attributes: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "htmlCommand", + blockType: Scratch.BlockType.COMMAND, + text: "<[element] [attributes]>[text] ", + arguments: { + element: { + type: Scratch.ArgumentType.STRING, + defaultValue: "div", + }, + attributes: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + text: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "rawInsert", + blockType: Scratch.BlockType.COMMAND, + text: "Insert raw [html]", + arguments: { + html: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + { + opcode: "noInner", + blockType: Scratch.BlockType.COMMAND, + text: "<[element] [attributes] />", + arguments: { + element: { + type: Scratch.ArgumentType.STRING, + defaultValue: "div", + }, + attributes: { + type: Scratch.ArgumentType.STRING, + defaultValue: "", + }, + }, + }, + + "---", + { + opcode: "exit", + blockType: Scratch.BlockType.COMMAND, + text: "", + }, + { + opcode: "html_ret", + blockType: Scratch.BlockType.REPORTER, + text: "html", + }, + { + opcode: "clear", + blockType: Scratch.BlockType.COMMAND, + text: "reset html", + }, + ], + }; + } + + sanitise(text) { + return text.replace(//g, ">"); + } + + /** + * @param {import("scratch-vm").BlockUtility} util + * + */ + pushStack(element, util) { + if (!this.stack[util.target.id]) this.stack[util.target.id] = []; + + this.stack[util.target.id].push(element); + } + + /** + * @param {import("scratch-vm").BlockUtility} util + * @returns {string} + */ + + popStack(util) { + if (!this.stack[util.target.id]) { + this.stack[util.target.id] = []; + return; + } + + return this.stack[util.target.id].pop(); + } + + /** + * @param {import("scratch-vm").BlockUtility} util + */ + getStack(util) { + if (!this.stack[util.target.id]) { + this.stack[util.target.id] = []; + } + + return this.stack[util.target.id]; + } + + /** + * @typedef arg_wrap + * @prop {string} element + * @prop {string} attributes + * @param {arg_wrap} args + * @param {import("scratch-vm").BlockUtility} util + */ + async htmlWrap({ element, attributes }, util) { + this._appendHtml( + `<${this.sanitise(element)} ${this.sanitise(attributes)}>`, + util + ); + this.pushStack(element, util); + return true; + } + + /** + * @param {arg_wrap} args + * @param {import("scratch-vm").BlockUtility} util + */ + async noInner({ element, attributes }, util) { + this._appendHtml( + `<${this.sanitise(element)} ${this.sanitise(attributes)} />`, + util + ); + return true; + } + + /** + * @typedef arg_command + * @prop {string} element + * @prop {string} attributes + * @prop {string} text + * @param {arg_command} args + * @param {import("scratch-vm").BlockUtility} util + */ + async htmlCommand({ element, attributes, text }, util) { + element = this.sanitise(element); + attributes = this.sanitise(attributes); + + this._appendHtml(`<${element} ${attributes}>${text}`, util); + return true; + } + + /** + * @param {string} text + * @param {import("scratch-vm").BlockUtility} util + */ + _appendHtml(text, util) { + let whitespace = " ".repeat( + this.stack.length ? this.getStack(util).length - 1 : 0 + ); + if (this.html[util.target.id] === undefined) { + this.html[util.target.id] = ""; + } + this.html[util.target.id] += whitespace + text + "\n"; + } + + /** + * @param {import("scratch-vm").BlockUtility} util + */ + exit(args, util) { + /* @type {string} */ + const element = this.popStack(util); + if (!element) { + throw "Error: No element to close"; + } + this._appendHtml(``, util); + } + + rawInsert({ html }, util) { + this._appendHtml(html, util); + } + + /** + * @param {import("scratch-vm").BlockUtility} util + * @returns {string} + */ + html_ret(args, util) { + return this.html[util.target.id]; + } + + /** + * @param {import("scratch-vm").BlockUtility} util + */ + clear(args, util) { + this.html[util.target.id] = ""; + } + } + + Scratch.extensions.register(new Html()); + // @ts-ignore +})(Scratch); diff --git a/extensions/extensions.json b/extensions/extensions.json index 06b95e22b9..6b33b69024 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -83,5 +83,6 @@ "itchio", "gamejolt", "obviousAlexC/newgroundsIO", - "Lily/McUtils" + "Lily/McUtils", + "ShowierData9978/html" ] diff --git a/images/ShowierData9978/html.svg b/images/ShowierData9978/html.svg new file mode 100644 index 0000000000..4b5045a0ff --- /dev/null +++ b/images/ShowierData9978/html.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/ShowierData9978/source.fig b/images/ShowierData9978/source.fig new file mode 100644 index 0000000000..3fa310d8c5 Binary files /dev/null and b/images/ShowierData9978/source.fig differ