convert <!> tag to boilerplate

Hunter
Wednesday, December 24th, 2025 at 11:27 PM

README.md+16 
main.js+22 

README.md
19  ### Stock Images
20  A library of stock images is included for your convenience. To browse them, type `<img src="?">` anywhere in the editor. You can use them in your pages like so: `<img src="coffee.gif">`.
21  
22 +### Boilerplate
23 +Type `<!>` to insert the following starter HTML:
24 +
25 +```html
26 +<html>
27 +	<head>
28 +		<style>
29 +
30 +		</style>
31 +	</head>
32 +	<body>
33 +
34 +	</body>
35 +</html>
36 +```
37 +
38  <br>
39  <img src="images/hint.gif" width=60px>
40  <i>
main.js
394  				]),
395  				html(),
396  				// Auto-close <style> and <script> tags (not handled by default html() extension)
397 +				// Also expand <!> into HTML boilerplate
398  				EditorView.inputHandler.of((view, from, to, text) => {
399  					if (text !== '>') return false;
400  					const before = view.state.doc.sliceString(Math.max(0, from - 20), from);
401 +					// Check for <!> boilerplate trigger
402 +					if (before.endsWith('<!')) {
403 +						const boilerplate = `<html>
404 +\t<head>
405 +\t\t<style>
406 +\t\t\t
407 +\t\t</style>
408 +\t</head>
409 +\t<body>
410 +\t\t
411 +\t</body>
412 +</html>`;
413 +						const startPos = from - 2;
414 +						const cursorPos = startPos + boilerplate.indexOf('<body>') + 9;
415 +						view.dispatch({
416 +							changes: { from: startPos, to: from, insert: boilerplate },
417 +							selection: { anchor: cursorPos }
418 +						});
419 +						return true;
420 +					}
421 +					// Check for <style> or <script>
422  					const match = before.match(/<(style|script)(\s[^>]*)?$/i);
423  					if (!match) return false;
424  					const tagName = match[1].toLowerCase();