diff --git a/app.js b/app.js index f0fbb32..4de2874 100644 --- a/app.js +++ b/app.js @@ -12,8 +12,9 @@ customElements.define('m-page-menu', PageMenu) class Setup { async run() { - navigator.serviceWorker - .addEventListener('controllerchange', () => { + const sw = navigator.serviceWorker + + sw.addEventListener('controllerchange', () => { if (this.registration.active) { window.location.reload(true) } diff --git a/components/header.js b/components/header.js index 06f9be1..306a064 100644 --- a/components/header.js +++ b/components/header.js @@ -5,11 +5,19 @@ export class Header extends HTMLElement { `, dot: ` +`, + edit: ` + + +`, + check: ` + `, } constructor() { super() + this.editing = false this.attachShadow({mode: 'open'}) this.addButton(this.icons.menu, 'nav', () => { this.menu.pages = this.getPages() @@ -17,6 +25,11 @@ export class Header extends HTMLElement { this.overlay.classList.add('open') }) this.addDivider() + this.editBtn = this.addButton(this.editIcon, 'edit', () => { + this.dispatchEvent(new CustomEvent( + 'click-edit', {bubbles: true} + )) + }) this.addButton(this.icons.dot, 'page', () => { this.pageMenuPanel.classList.add('open') this.overlay.classList.add('open') @@ -33,6 +46,7 @@ export class Header extends HTMLElement { if (onClick) { b.addEventListener('click', onClick) } + return b } addDivider() { @@ -53,7 +67,7 @@ export class Header extends HTMLElement { this.overlay.addEventListener('click', () => { this.close() }) - this.menu.addEventListener('close', () => { + this.menu.addEventListener('close-menu', () => { this.close() }) } @@ -76,7 +90,7 @@ export class Header extends HTMLElement { el.click() this.shadowRoot.removeChild(el) }) - this.pageMenu.addEventListener('close', () => { + this.pageMenu.addEventListener('close-menu', () => { this.close() }) this.pageMenuPanel = document.createElement('div') @@ -166,4 +180,19 @@ export class Header extends HTMLElement { getPages() { return Object.keys(localStorage).slice().sort() } + + set editing(value) { + this._editing = value + if (this.editBtn) { + this.editBtn.innerHTML = this.editIcon + } + } + + get editing() { + return this._editing + } + + get editIcon() { + return this.editing ? this.icons.check : this.icons.edit + } } \ No newline at end of file diff --git a/components/layout.js b/components/layout.js index 4665841..bd22996 100644 --- a/components/layout.js +++ b/components/layout.js @@ -2,12 +2,9 @@ export class Layout extends HTMLElement { constructor() { super() this.attachShadow({mode: 'open'}) + this.csp = "default-src 'self' 'unsafe-inline' 'unsafe-eval'" this.header = document.createElement('m-header') - this.shadowRoot.appendChild(this.header) - this.load() - addEventListener('hashchange', () => { - this.load() - }) + this.editing = false } connectedCallback() { @@ -24,13 +21,24 @@ export class Layout extends HTMLElement { } ` this.shadowRoot.appendChild(style) + this.header.editing = this.editing + this.header.addEventListener('click-edit', () => { + this.editing = !this.editing + }) + this.shadowRoot.appendChild(this.header) + this.load() + addEventListener('hashchange', () => { + this.load() + }) } load() { const path = this.path const prevPage = this.page this.page = document.createElement('m-page') + this.page.csp = this.csp this.page.path = path + this.editing = this.editing if (prevPage !== undefined) { prevPage.remove() } @@ -39,9 +47,18 @@ export class Layout extends HTMLElement { } get path() { - return new URL( - window.location.hash.slice(1) || '/', - window.location - ).pathname + return '/hello' + } + + set editing(value) { + this._editing = value + this.header.editing = this.editing + if (this.page) { + this.page.editing = this.editing + } + } + + get editing() { + return this._editing } } \ No newline at end of file diff --git a/components/nav-menu.js b/components/nav-menu.js index 53919e6..f260642 100644 --- a/components/nav-menu.js +++ b/components/nav-menu.js @@ -35,7 +35,7 @@ export class NavMenu extends HTMLElement { this.shadowRoot.addEventListener('click', e => { if (e.target.classList.contains('page')) { this.dispatchEvent(new CustomEvent( - 'close', {bubbles: true} + 'close-menu', {bubbles: true} )) } }) diff --git a/components/page-menu.js b/components/page-menu.js index 61fd0da..a433bda 100644 --- a/components/page-menu.js +++ b/components/page-menu.js @@ -45,7 +45,7 @@ export class PageMenu extends HTMLElement { this.shadowRoot.appendChild(btn) btn.addEventListener('click', () => { this.dispatchEvent(new CustomEvent( - 'close', {bubbles: true} + 'close-menu', {bubbles: true} )) handler() }) diff --git a/components/page.js b/components/page.js index d5d429a..a9bac7a 100644 --- a/components/page.js +++ b/components/page.js @@ -17,7 +17,7 @@ const frameHtml = ` - + ${' ` @@ -35,12 +35,11 @@ export class Page extends HTMLElement { constructor() { super() const shadow = this.attachShadow({mode: 'open'}) + this.csp = "default-src 'self' 'unsafe-inline' 'unsafe-eval'" + //this.editing = false this.textArea = document.createElement('textarea') this.textArea.addEventListener('input', e => { - localStorage.setItem( - this.path, - e.target.value - ) + this.body = e.target.value }) const div = document.createElement('div') div.classList.add('twrap') @@ -49,9 +48,7 @@ export class Page extends HTMLElement { } connectedCallback() { - this.textArea.value = localStorage.getItem( - this.path - ) ?? '' + this.textArea.value = this.body const style = document.createElement('style') style.textContent = ` :host { @@ -76,35 +73,90 @@ export class Page extends HTMLElement { width: 100%; height: 90vh; } + :host(.editing) iframe { + display: none; + } + :host(.viewing) textarea { + display: none; + } ` this.shadowRoot.append(style) - if (this.path.startsWith('/sandbox/')) { - this.initFrame() - } + this.initFrame() + this.editing = this.editing } 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 + if (this.csp !== undefined) { + tmp.sandbox = "allow-same-origin allow-scripts" + const url = new URL( + '/-/frame', location.href + ) + url.searchParams.set('csp', this.csp) + url.searchParams.set('html', frameHtml) + tmp.src = url.href + } else { + tmp.sandbox = "allow-scripts" + tmp.srcdoc = frameHtml + } 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) + this.display(e.target.value) + }) + this.frame.addEventListener('load', () => { + this.display(this.body) }) } + + display(value) { + let doc = value + if (!(/<\w/).test(doc.substring(0, 30))) { + doc = `
` +
+        doc.replace("<", "<").replace(">", ">") +
+      `
` + } + const msg = ['srcdoc', doc] + this.frame.contentWindow.postMessage(msg, '*') + } + + set body(value) { + try { + localStorage.setItem(this.path, value) + } catch (err) { + console.error(err) + } + } + + get body() { + try { + return localStorage.getItem(this.path) + } catch (err) { + console.error(err) + return '' + } + } + + set editing(value) { + this._editing = value + if (this.shadowRoot.host) { + const classes = this.shadowRoot.host.classList + if (this.editing) { + classes.add('editing') + classes.remove('viewing') + } else { + classes.add('viewing') + classes.remove('editing') + } + } + } + + get editing() { + return this._editing + } } \ No newline at end of file diff --git a/sw.js b/sw.js index b8de43e..6c8bede 100644 --- a/sw.js +++ b/sw.js @@ -13,6 +13,7 @@ async function initCache() { } self.addEventListener("install", event => { + self.skipWaiting() event.waitUntil(initCache()) })