const frameHtml = ` Frame ` export class Page extends HTMLElement { constructor() { super() const shadow = this.attachShadow({mode: 'open'}) this.textArea = document.createElement('textarea') this.textArea.addEventListener('input', e => { localStorage.setItem( this.path, e.target.value ) }) const div = document.createElement('div') div.appendChild(this.textArea) shadow.appendChild(div) } connectedCallback() { this.textArea.value = localStorage.getItem( this.path ) ?? '' const style = document.createElement('style') style.textContent = ` :host { overflow-y: auto; display: flex; flex-direction: column; } textarea { width: 100%; margin: 0 5px; padding: 5px; height: 50vh; } iframe { border: none; width: 100%; height: 90vh; } ` this.shadowRoot.append(style) if (this.path.startsWith('/sandbox/')) { this.initFrame() } } initFrame() { const wrap = document.createElement('div') this.shadowRoot.appendChild(wrap) const tmp = document.createElement('iframe') tmp.sandbox = "allow-same-origin allow-scripts" const url = new URL( '/-/frame', location.href ) url.searchParams.set( 'csp', "default-src 'self' 'unsafe-inline' 'unsafe-eval'" ) url.searchParams.set('html', frameHtml) tmp.src = url.href wrap.insertAdjacentHTML( 'beforeend', tmp.outerHTML ) const frames = wrap.getElementsByTagName('iframe') this.frame = frames[frames.length - 1] this.textArea.addEventListener('blur', e => { const msg = ['srcdoc', e.target.value] this.frame.contentWindow.postMessage(msg) }) } }