Skip to content

HTML Documents Extension - #628

Open
ScrTwPm wants to merge 22 commits into
PenguinMod:mainfrom
ScrTwPm:main
Open

HTML Documents Extension#628
ScrTwPm wants to merge 22 commits into
PenguinMod:mainfrom
ScrTwPm:main

Conversation

@ScrTwPm

@ScrTwPm ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown

Create HTML webpages and display them on the stage
htmldocs
I'll make a documentation when I know what you think of this

@sakurabuilder

sakurabuilder Bot commented Aug 22, 2026

Copy link
Copy Markdown

✅ Preview ready

Your changes are live at: https://lively-cedar-291d5d94.skr.mubilop.com/

Built from f75c800

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

But you were right about the Python extension it didnt really add anything new. I think this does add more features that that does. I'm checking right now to see if something like this already exists but i'm pretty sure it isn't

@Steve0Greatness

Copy link
Copy Markdown
Contributor

Doesn't appear to scale appropriately for full screened usage

image

Additionally, although the extension still believes the currently displayed page is still the page when changing view modes (ie. into/out of full screen), it does actually get removed.

image

@Steve0Greatness

Steve0Greatness commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Doesn't appear to scale appropriately for full screened usage
image removed

I see you had added a block that helps to scale appropriately (id spw). Second one still applies, though.

@Steve0Greatness

Copy link
Copy Markdown
Contributor

Could you make it so that multiple pages can be displayed at once?

@Steve0Greatness Steve0Greatness left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more of a general concern, but you should put this through Prettier, because this is formatted, in my opinion, a bit badly.

width: "470",
},
"ids": [],
"code":""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably require a bit of a restructure, but there are far safer and more efficient ways to handle HTML code. Storing it as a DOM would actually be a good idea, here.

class HTMLtoCanvas {
constructor(runtime) {
// Initialize an array holding your default dropdown menu options
this.pages = {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll probably want to use a Map instead of just an object, since maps are more optimized to change frequently.

Comment on lines +4 to +13
if (Scratch.gui) {
Scratch.gui.getBlockly().then(ScratchBlocks => {
ScratchBlocks.BlockSvg.registerCustomNotch("htmldocuments-coolshape",
"c 2 0 3 1 4 2 l 4 4 c 1 1 4 -7 4 2 h 3 c 1 -1 2 -2 3 -3 c 1 1 2 2 3 3 l 3 0 c 0 -9 3 -1 4 -2 l 4 -4 c 1 -1 2 -2 4 -2"
)
ScratchBlocks.BlockSvg.registerCustomNotch("htmldocuments-coolshape2",
"c 0 2 0 8 4 8 c 6 0 10 -7 14 -5 c 4 -2 8 5 14 5 c 4 0 4 -6 4 -8"
)
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These currently go unused.

Comment on lines +661 to +662
const el = document.createElement("div");
el.innerHTML = this.pages[args.PAGE].code;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of sandboxing, please use iframe instead of a div. You can include HTML inside an iframe using the srcdoc attribute.

]
},
attr: {
acceptReporters: false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be set to true, instead of false. This would necessitate the use of a DOM, however, as the way you're currently setting attributes by their name is unsafe if you allow direct input.

{
opcode: 'eve',
blockType: Scratch.BlockType.HAT,
text: 'When listener for [ID] activated in [PAGE]',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency, this should start with a lowercase letter.

Comment on lines +855 to +857
this.pages[args.PAGE].code = `${this.pages[args.PAGE].code}<!--begin my style--><style>`
this.pages[page].code = `${this.pages[page].code}.htmldocumentelement${args.TYPE}element${page}${args.NAME}{${args.PROPERTY}:${value}};`
this.pages[args.PAGE].code = `${this.pages[args.PAGE].code}</style><!--end my style-->`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In cases like these, it's easier to read if you use += `...` instead of = `${...}...`

this.pages[args.PAGE].data.x = 5
this.pages[args.PAGE].data.y = 5
console.log(this.viewing)
console.log(document.querySelector("#htmlpage"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few spare console logs here and there in this extension, I'd recommend removing them before this gets merged.

Comment on lines +1023 to +1060
vm.runtime.targets.forEach(target => {
const blocks = target.blocks;
const scripts = blocks.getScripts();

scripts.forEach(rootBlockId => {
const block = blocks.getBlock(rootBlockId);

if (block && block.opcode === targetOpcode) {
let hatValue = '';
let hatValuea = '';

// 1. Check if it's a Field (dropdown/fixed text)
if (block.fields && block.fields.ID && block.fields.PAGE) {
console.log(block.fields.ID.value)
hatValue = block.fields.ID.value;
hatValuea = block.fields.PAGE.value;
}
// 2. Check if it's an Input (text bubble)
else if (block.inputs && block.inputs.ID) {
const input = block.inputs.ID;
const inputa = block.inputs.PAGE;
// Dig into the 'shadow' block which holds the text value
const shadowBlock = blocks.getBlock(input.shadow);
const shadowBlocka = blocks.getBlock(inputa.shadow);
if (shadowBlock && shadowBlock.fields && shadowBlock.fields.TEXT && shadowBlocka && shadowBlocka.fields && shadowBlocka.fields.TEXT) {
hatValue = shadowBlock.fields.TEXT.value;
hatValuea = shadowBlocka.fields.TEXT.value;
}
}

// Compare and trigger
if (hatValue === triggerText && hatValuea === triggerTexta) {
vm.runtime._pushThread(rootBlockId, target);
}
}
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runtime.startHats has the functionality to start hat blocks dependent on their values, already.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

thanks for the feedback! i'll get to it.

@Steve0Greatness

Copy link
Copy Markdown
Contributor

But you were right about the Python extension it didnt really add anything new. I think this does add more features that that does. I'm checking right now to see if something like this already exists but i'm pretty sure it isn't

Well, there's Project Interfaces, which is a similar idea, but it doesn't allow for the creation of documents made up of HTML.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

i was thinking about that exact thing while i was making this. I wanted to give users more control and the ability to display and build their own webpage documents.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

or i can use this function

function prettierInText(html) {
  // Clean up existing whitespace and collapse it
  let clean = html.replace(/\s*([<>])\s*/g, '$1').replace(/\s+/g, ' ');
  let reg = /(<[^>]+>)/g;
  let matches = clean.split(reg).filter(Boolean);
  
  let formatted = '';
  let pad = 0;

  matches.forEach((token) => {
    // Check if the token is a closing tag
    if (token.match( /<\/\w+/ )) {
      pad--;
    }
    
    // Add current line indentation
    formatted += '  '.repeat(Math.max(0, pad)) + token + '\n';
    
    // Check if the token is an opening tag (and not self-closing)
    if (token.match( /<[^\/][^>]*[^>\/]>/ ) && !token.match(/<(input|img|br|hr|meta|link)/)) {
      pad++;
    }
  });

  return formatted.trim();
}

// Example Usage
const raw = '<div><h1>Title</h1><p>Text</p><img src="img.jpg"/></div>';
console.log(prettierInText(raw));

@Gen1xLol

Copy link
Copy Markdown
Contributor

or i can use this function

function prettierInText(html) {
  // Clean up existing whitespace and collapse it
  let clean = html.replace(/\s*([<>])\s*/g, '$1').replace(/\s+/g, ' ');
  let reg = /(<[^>]+>)/g;
  let matches = clean.split(reg).filter(Boolean);
  
  let formatted = '';
  let pad = 0;

  matches.forEach((token) => {
    // Check if the token is a closing tag
    if (token.match( /<\/\w+/ )) {
      pad--;
    }
    
    // Add current line indentation
    formatted += '  '.repeat(Math.max(0, pad)) + token + '\n';
    
    // Check if the token is an opening tag (and not self-closing)
    if (token.match( /<[^\/][^>]*[^>\/]>/ ) && !token.match(/<(input|img|br|hr|meta|link)/)) {
      pad++;
    }
  });

  return formatted.trim();
}

// Example Usage
const raw = '<div><h1>Title</h1><p>Text</p><img src="img.jpg"/></div>';
console.log(prettierInText(raw));

This seems to be very clearly AI generated. I don't know if you vibecoded this extension but if you did that's a biiiig no-no

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

I typed this myself. But the function i just pasted was AI generated because I wanted to see if you would or would not want me to import the prettier.
I typed the full extension by hand. I've been working on it since May.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

I don't paln on using the script I sent you but I want to verify if i'm ok to import perttier from https://unpkg.com/html-dom-parser@latest/dist/html-dom-parser.min.js'

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

This seems to be very clearly AI generated. I don't know if you vibecoded this extension but if you did that's a biiiig no-no

I built this off of my previous html to canvas extension that I made last year.
It is very unsafe and uses cst1229's extendable blocks (i never made a pr for it)

htmltocanvasold.js

@Steve0Greatness

Copy link
Copy Markdown
Contributor

I don't paln on using the script I sent you but I want to verify if i'm ok to import perttier from https://unpkg.com/html-dom-parser@latest/dist/html-dom-parser.min.js'

Uh, I don't think that's Prettier, that's a DOM parser. Prettier is a code formatter, I was requesting that you reformat your code.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

OH. ok. it does have a html parser. You wnated me to format my code

@Steve0Greatness

Copy link
Copy Markdown
Contributor

OH. ok. it does have a html parser. You wnated me to format my code

Yes. The JavaScript of the extension, that's what I want you to format.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

I'll do that when i post my new revided version that replies to your suggestions

(I'm a good coder but a horrible formatter. I'm downloading prettier on vscode right now)

@Steve0Greatness

Copy link
Copy Markdown
Contributor

I'll do that when i post my new revided version that replies to your suggestions

(I'm a good coder but a horrible formatter. I'm downloading prettier on vscode right now)

Alright, you actually don't need to open a new PR, just push to the same branch that this PR is pulling from (main).

@Steve0Greatness

Copy link
Copy Markdown
Contributor

Additionally, you can just use the builtin DOMParser provided by the browser, rather than needing an external package..

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

I got you.

@ScrTwPm

ScrTwPm commented Aug 22, 2026

Copy link
Copy Markdown
Author

should i use that to insert elements into my iframe or change the entire

this.pages[page].code

to a HTML DOM

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

Used a map for pages and return [] on empty "current page displayed". Added support for non-latin languages by only rejecting empty names for pages.

@RedMan13

RedMan13 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY a DOM????????????????????????????????????????????????????????????????????????/

The Document Object Model (DOM) is the system used to represent the contents of a Document as an Object. In the web, a document is normally some flavor of Standard Generalized Markup Language (SGML). The current standard for representing such documents as Objects, is to make each "Element" into one object, that object would then house each property of that Element, such as attributes and child Elements. Normally, this structure is installed in languages so that the Document can be controlled by, and better understood by, the developers of a program, and the program itself. There have been, in the past, propositions to standardize syntax for interacting with SGML inside of languages that often need to, such as JavaScript. Though JavaScript did not end up accepting this proposed standard.

@Gen1xLol

Copy link
Copy Markdown
Contributor

WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY a DOM????????????????????????????????????????????????????????????????????????/

I know this is unrelated to the issue and will most likely be marked as off topic but Why are you Like This

@Steve0Greatness

Copy link
Copy Markdown
Contributor

WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY a DOM????????????????????????????????????????????????????????????????????????/

Because a DOM is safer than your current method.

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

Can i just change ```this.pages...code```` to a DOM after I perform the same string operations to it?
Or do I have to change my operations to edit the DOM rather than on the string

like using

// a function to add elements
docVar.createElement("div")

or can I just use

const toAString = new XMLSerializer()
const dom = new DOMParser()
// getinfo...
// a function to add elements
string = toAString.serializeToString(this.pages...code) //obviously this is a map now so different ops like get()
string += "<div>some stuff...</div>"
this.pages...code = dom.parseFromString(string, 'text/html') // set() now

Did you see my comment about how the nesting elements works. If this can be solved I can completely move to operations on the dom rather than on strings

Screenshot 2026-08-25 134023

thats why i asked

WHYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY a DOM????????????????????????????????????????????????????????????????????????/

@jwklong

jwklong commented Aug 25, 2026

Copy link
Copy Markdown
Member

you can run unsandboxed javascript code with this extension
{0CFB0602-D871-4682-B118-2CA899034480}

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

I'll disallow using <script> in the add text block

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

@jwklong do you have a resolution for the comment I sent before your find?

@jwklong

jwklong commented Aug 25, 2026

Copy link
Copy Markdown
Member

you also cant make html code in parallel
{6F8E2FAC-ED08-400A-AB88-30BE0FD1E10E}

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

uhh what should happen when that code is built? It does what I coded it to do.
The loop at the bottom begins first and appends <div id="...new-el">
Then before it is done the new thread begins and appends <div id ="...new-el2> and </div>
After the 2nd second, </div> is added from the loop at the bottom.
I don't know why anyone would need to build that script.
Screenshot 2026-08-25 134023

@ScrTwPm

ScrTwPm commented Aug 25, 2026

Copy link
Copy Markdown
Author

I will make it do whatever you think it should do if someone does that. I dont know what a user would expect if they did that.

@Steve0Greatness

Copy link
Copy Markdown
Contributor

I'm not sure how I will change this just using a DOM. otherwise the idea is fine. Working on a solution right now

Actually, I'd argue, in part related to Jwk's argument, that this is actually a place where a DOM is better. You can set values on the thread (util.thread) that will only be on the current thread, meaning multiple threads can create different elements at the same time. You could set a value that is an array of all ids in order of last created. New elements get the latest id, and when the element closes it pops the current id.

Speaking of which, there should actually be a way to edit the child elements and text of an element after its creation.

@ScrTwPm

ScrTwPm commented Aug 26, 2026

Copy link
Copy Markdown
Author

so the there is a thread var (an array) that contains elements to be created. i'm changing it right now to a dom

@ScrTwPm

ScrTwPm commented Aug 26, 2026

Copy link
Copy Markdown
Author

changed to a DOM

Refactor HTMLDocuments class to remove unused 'ids' property and update related methods to use 'code' for element queries.
@ScrTwPm

ScrTwPm commented Aug 26, 2026

Copy link
Copy Markdown
Author

Added new blocks and got rid of unessecary tracking of ids

@ScrTwPm ScrTwPm closed this Aug 26, 2026
@ScrTwPm ScrTwPm reopened this Aug 26, 2026
@ScrTwPm

ScrTwPm commented Aug 26, 2026

Copy link
Copy Markdown
Author

Didn't mean to close :slappingmyheademoji:

I just made a big change in how this works so there are a lot of things to fix
IM NOT LYING THERE WERE LITERALLY 67 CONSOLE LOG STATEMENTS IN MY CODE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants