FTML to HTML
<template> <div> <h1>FTML (type: page) to HTML</h1> <a href="https://github.com/scpwiki/wikijump/blob/develop/ftml/docs/Blocks.md" target="_blank" > @wikijump/ftml/docs/Blocks.md </a> <hr /> <h3>Wiki Syntax</h3> <textarea ref="ftmlarea" v-model="ftml" @input="postMessage($event.target.value)"></textarea> <hr /> <h3>Result</h3> <pre ref="result"></pre> </div> </template> <script> const CN_ParentElementNode = class { constructor() { this.name = '_parent'; this.init(); } init() { this._content = document.createElement('div'); this._content.classList.add('check_node_block'); } get object() { return this._content; } append(cnnode) { this._content.appendChild(cnnode.object); } }; const CN_ElementNode = class { constructor(name, ...attr) { this.name = name; this.attr = attr.map(a => `<span class="check_node_attribute"><b>${a.nodeName}</b>=<i>"${a.nodeValue}"</i></span>` ); this.init(); } init() { this._wrap = document.createElement('div'); this._label_open = document.createElement('a'); this._label_open.classList.add('check_node_element_open'); this._label_open.innerHTML = this.name == 'wj-body' ? '' : `<${['<span class="check_node_tagname">' + this.name + '</span>', ...this.attr].join(' ')}>`; this._dummy = document.createElement('div'); this._content = document.createElement('label'); this.name == 'wj-body' || this._content.classList.add('check_node_block'); const check = document.createElement('input'); check.type = 'checkbox'; this.name == 'wj-body' || check.classList.add('check_node_hidden'); this._content.appendChild(check); this._label_close = document.createElement('a'); this._label_close.classList.add('check_node_element_close'); this._label_close.innerHTML = this.name == 'wj-body' ? '' : `</<span class="check_node_tagname">${this.name}</span>>`; for(const e of [this._label_open, this._dummy, this._content, this._label_close]) this._wrap.appendChild(e); } get object() { return this._wrap; } append(cnnode) { this._content.appendChild(cnnode.object); } }; // <br>, <hr>, <img>, <input>, <link>, <base>, <meta>, <param>, <area>, <embed>, <col>, <track>, <source> const CN_VoidElementNode = class { constructor(name, ...attr) { this.name = name; this.attr = attr.map(a => `<span class="check_node_attribute"><b>${a.nodeName}</b>=<i>"${a.nodeValue}"</i></span>` ); this.init(); } init() { this._span = document.createElement('a'); this._span.classList.add('check_node_element_void', 'check_node_inline'); this._span.innerHTML= `<${['<span class="check_node_tagname">' + this.name + '</span>', ...this.attr, '/'].join(' ')}>`; } get object() { return this._span; } }; const CN_TextNode = class { constructor(name, value) { this.name = name; this.value = value; this.init(); } init() { this._span = document.createElement('span'); this._span.classList.add('check_node_inline'); this._span.innerText = this.value; this._span.dataset.type = this.name; } get object() { return this._span; } }; const CheckNode = (node, cnparent) => { const type = node.nodeName.toLowerCase(); const isVoid = ['br', 'hr', 'img', 'input', 'link', 'base', 'meta', 'param', 'area', 'embed', 'col', 'track', 'source'] .includes(type); if(type == '#text') { const cnnode = new CN_TextNode(type, node.nodeValue); cnparent.append(cnnode); } else if(type == '#comment') { const cnnode = new CN_TextNode(type, `<!-- ${node.nodeValue} -->`); cnparent.append(cnnode); } else if(isVoid) { const cnnode = new CN_VoidElementNode(type, ...node.attributes); cnparent.append(cnnode); } else { const cnnode = new CN_ElementNode(type, ...node.attributes); cnparent.append(cnnode); for(const child of node.childNodes) CheckNode(child, cnnode); } return cnparent.object; }; module.exports = { data: function() { return { worker: null, ftml: '', result: '' } }, methods: { createWorker: async function() { this.$refs.ftmlarea.disabled = true; if(this.worker) this.worker.terminate(); const raw = await fetch('/local--files/7happy7-warehouse/ftml-base64.txt'); const base64 = await raw.text(); this.worker = new Worker(base64, {type: 'module'}); this.worker.onmessage = e => { const { html, styles } = e.data; const dom = this.beautifyHTML(html, styles); if(dom) { this.$refs.result.innerHTML = ''; this.$refs.result.appendChild(dom); } }; this.$refs.ftmlarea.disabled = false; }, postMessage: function(value) { this.worker.postMessage({ type: 'page', value }); }, beautifyHTML: function(html, styles) { try { const dom = new DOMParser().parseFromString(html, 'text/html'); const body = dom.querySelector('wj-body'); for(const s of styles) { const e = dom.createElement('style'); e.innerHTML = s; body.appendChild(e); } return CheckNode(body, new CN_ParentElementNode()); }catch(e) { console.log(e); return html; } }/*, format: function(node, level) { const bef = ' '.repeat(level++ + 1); const aft = ' '.repeat(level - 1); let textNode; for(const child of node.children) { textNode = document.createTextNode(`\n${bef}`); node.insertBefore(textNode, child); this.format(child, level); if(node.lastElementChild == child) { textNode = document.createTextNode(`\n${aft}`); node.appendChild(textNode); } } return node; }*/ }, mounted: async function() { document.title = '7happy7\'s Warehouse - FTML to HTML'; await this.createWorker(); } } </script> <style scoped> textarea { border: 1px solid rgb(170, 170, 170); border-radius: 5px; font-family: "Ubuntu Mono", monospace; font-size: 20px; height: 250px; margin: 0 auto; position: relative; resize: vertical; transform: scale(0.65); width: 100%; } textarea:focus { outline: none; } pre { box-shadow: 1px 1px 3px #aaa; font-family: 'Ubuntu Mono', monospace; font-size: .8em; overflow-x: scroll; padding: 1em; position: relative; text-align: left; margin: 0 auto; max-width: calc(800px - 2em); width: calc(80% - 2em); } .check_node_block { display: block; padding: 0.5em 0 0.5em 0.5em; transition: all .2s; } .check_node_block:hover { box-shadow: 1px 1px 3px #aaa; } .check_node_block input { display: none; } .check_node_hidden { display: none; } [class^="check_node_element"] { color: #502; font-weight: normal; } .check_node_inline { display: block; } .check_node_inline[data-type="#comment"] { color: #589e58; font-style: italic; } .check_node_tagname { color: #568; font-weight: bold; } .check_node_attribute:hover { text-decoration: underline; } .check_node_attribute > b { color: #586; } .check_node_attribute > i { color: #c95; font-weight: bold; } .check_node_hidden:checked ~ * { display: inline-flex; height: 0.8em; visibility: hidden; } .check_node_hidden:checked + *::before { content: '...'; visibility: visible; } .check_node_hidden:checked ~ * .check_node_hidden:checked + *::before { visibility: hidden; } </style>
page revision: 38, last edited: 31 Aug 2022 16:41






