Skip to content

HTML Documents Extension - #628

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

HTML Documents Extension#628
ScrTwPm wants to merge 26 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 987dab1

@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

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:

@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.

I'll probably do a somewhat more in-depth review at some point, but I noticed you added a menu icon. It's currently 374x374px, which is far to large (note they're always displayed at very small resolutions). My personal recommendation is to scale it to 32x32.

@ScrTwPm

ScrTwPm commented Aug 27, 2026

Copy link
Copy Markdown
Author

yeah i´ll do that.

and some other stuff.
resolved nesting issues
@ScrTwPm

ScrTwPm commented Aug 27, 2026

Copy link
Copy Markdown
Author
image

@ScrTwPm

ScrTwPm commented Aug 27, 2026

Copy link
Copy Markdown
Author

runtime.startHats finally worked!

@ScrTwPm

ScrTwPm commented Aug 28, 2026

Copy link
Copy Markdown
Author

I'll be back on Monday.

@ScrTwPm

ScrTwPm commented Aug 29, 2026

Copy link
Copy Markdown
Author

back

@Steve0Greatness

Copy link
Copy Markdown
Contributor

There should probably be a way to change text content of an element dynamically.

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