convert <!> tag to boilerplate
Hunter
Wednesday, December 24th, 2025 at 11:27 PM
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>
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();