From 37730968a1feb30794acf3c7cf39b715787c5632 Mon Sep 17 00:00:00 2001 From: bat Date: Mon, 10 Apr 2023 14:02:40 +0000 Subject: [PATCH 1/6] Add file-group-page component --- app.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app.js b/app.js index 78bbbea..012f2f8 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,6 @@ import { Layout } from "/components/layout.js" import { Page } from "/components/page.js" +import { FileGroupPage } from "/components/file-group-page.js" import { Header } from "/components/header.js" import { NavMenu } from "/components/nav-menu.js" import { Dialog } from "/dialog/dialog.js" @@ -8,6 +9,9 @@ import { Dropdown } from "/menu/dropdown.js" customElements.define('m-layout', Layout) customElements.define('m-page', Page) +customElements.define( + 'm-file-group-page', FileGroupPage +) customElements.define('m-header', Header) customElements.define('m-nav-menu', NavMenu) customElements.define('m-dialog', Dialog) From 67664496ef8611c6c59694c4a048ed9bd8720abc Mon Sep 17 00:00:00 2001 From: bat Date: Mon, 10 Apr 2023 17:07:42 +0000 Subject: [PATCH 2/6] Get file group page component showing dummy text --- components/file-group-page.json | 178 ++++++++++++++++++++++++++++++++ components/layout.js | 17 ++- 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 components/file-group-page.json diff --git a/components/file-group-page.json b/components/file-group-page.json new file mode 100644 index 0000000..03bd4e2 --- /dev/null +++ b/components/file-group-page.json @@ -0,0 +1,178 @@ +const frameHtml = ` + + + Frame + + + + ${'<'}script type="module"> + let frame = undefined +addEventListener('message', event => { + let isNew = false + const d = event.data + if (Array.isArray(d) && d[0] === 'srcdoc') { + isNew = frame === undefined + if (isNew) { + frame = document.createElement('iframe') + frame.sandbox = "allow-scripts allow-top-navigation" + } + frame.srcdoc = d[1] + } else if (frame !== undefined) { + frame.postMessage(event.data) + } + if (isNew) { + document.body.appendChild(frame) + } +}) + ${' + +` + +export class FileGroupPage extends HTMLElement { + constructor() { + super() + this.attachShadow({mode: 'open'}) + this.csp = "default-src 'self' 'unsafe-inline' 'unsafe-eval'" + } + + connectedCallback() { + const style = document.createElement('style') + style.textContent = ` + :host { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr; + grid-template-areas: "main"; + flex-direction: column; + align-items: stretch; + } + iframe { + border: none; + margin: 0; + padding: 0; + grid-area: main; + width: 100%; + } + :host(.editing) iframe.view { + display: none; + } + :host(.viewing) iframe.edit { + display: none; + } + ` + this.shadowRoot.append(style) + this.initEditFrame() + this.initViewFrame() + this.editing = this.editing + } + + initEditFrame() { + const frame = document.createElement('iframe') + if (this.csp !== undefined) { + frame.sandbox = "allow-same-origin allow-scripts allow-top-navigation" + const url = new URL( + '/-/frame', location.href + ) + url.searchParams.set('csp', this.csp) + url.searchParams.set('html', frameHtml) + frame.src = url.href + } else { + frame.sandbox = "allow-scripts allow-top-navigation" + frame.srcdoc = frameHtml + } + frame.addEventListener('load', () => { + this.displayEdit() + }) + this.editFrame = frame + this.shadowRoot.append(frame) + } + + initViewFrame() { + const frame = document.createElement('iframe') + if (this.csp !== undefined) { + frame.sandbox = "allow-same-origin allow-scripts allow-top-navigation" + const url = new URL( + '/-/frame', location.href + ) + url.searchParams.set('csp', this.csp) + url.searchParams.set('html', frameHtml) + frame.src = url.href + } else { + frame.sandbox = "allow-scripts allow-top-navigation" + frame.srcdoc = frameHtml + } + frame.addEventListener('load', () => { + this.displayView() + }) + this.viewFrame = frame + this.shadowRoot.append(frame) + } + + displayView() { + let doc = 'view here' + const msg = ['srcdoc', doc] + this.viewFrame.contentWindow.postMessage( + msg, '*' + ) + } + + displayEdit() { + let doc = 'edit here' + const msg = ['srcdoc', doc] + this.viewFrame.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/components/layout.js b/components/layout.js index 40e1cf9..2f37667 100644 --- a/components/layout.js +++ b/components/layout.js @@ -44,7 +44,22 @@ export class Layout extends HTMLElement { load() { const path = this.path const prevPage = this.page - this.page = document.createElement('m-page') + let isGroup = false + const body = localStorage.getItem(path) || '' + if (body.match(/^\s*{/)) { + try { + const bodyData = JSON.parse(body) + isGroup = ( + Array.isArray(bodyData?.files) && + bodyData.type === 'm-file-group' + ) + } catch (err) { + // do nothing, is not file group + } + } + this.page = document.createElement( + isGroup ? 'm-file-group-page' : 'm-page' + ) this.page.csp = this.csp this.page.path = path this.editing = this.editing From 02112e6c8dfebac477c379f0ab36131f9d477cac Mon Sep 17 00:00:00 2001 From: bat Date: Mon, 10 Apr 2023 17:08:59 +0000 Subject: [PATCH 3/6] Fix name --- components/{file-group-page.json => file-group-page.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/{file-group-page.json => file-group-page.js} (100%) diff --git a/components/file-group-page.json b/components/file-group-page.js similarity index 100% rename from components/file-group-page.json rename to components/file-group-page.js From c6b97474a3830a77de9e8e3080275e74c9306750 Mon Sep 17 00:00:00 2001 From: Benjamin Atkin Date: Tue, 11 Apr 2023 12:39:16 -0700 Subject: [PATCH 5/6] update service worker --- sw.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sw.js b/sw.js index 2978397..19a73c4 100644 --- a/sw.js +++ b/sw.js @@ -11,7 +11,12 @@ async function initCache() { '/dialog/dialog.js', '/forms/button-group.js', '/menu/dropdown.js', - ]) //1 + '/loader/builder.js', + '/loader/editor-build.js', + '/editor/file-group.js', + '/editor/file-page.js', + '/editor/text-edit.js', + ]) } self.addEventListener("install", event => { From c9a021057b949a021582051018c651eb6763d88d Mon Sep 17 00:00:00 2001 From: Benjamin Atkin Date: Tue, 11 Apr 2023 12:43:25 -0700 Subject: [PATCH 6/6] integrate editor --- components/file-group-page.js | 88 +++++++++++++++++++++++++++-------- components/header.js | 41 +++++++++++++++- components/layout.js | 14 +++++- components/page.js | 2 +- sw.js | 19 ++++---- 5 files changed, 133 insertions(+), 31 deletions(-) diff --git a/components/file-group-page.js b/components/file-group-page.js index 03bd4e2..dab9dcf 100644 --- a/components/file-group-page.js +++ b/components/file-group-page.js @@ -23,22 +23,29 @@ html { ${'<'}script type="module"> - let frame = undefined +let frame = undefined addEventListener('message', event => { - let isNew = false - const d = event.data - if (Array.isArray(d) && d[0] === 'srcdoc') { - isNew = frame === undefined - if (isNew) { - frame = document.createElement('iframe') - frame.sandbox = "allow-scripts allow-top-navigation" + const isChild = ( + frame !== undefined && event.source == frame.contentWindow + ) + if (isChild) { + parent.postMessage(event.data, '*') + } else { + let isNew = false + const d = event.data + if (Array.isArray(d) && d[0] === 'srcdoc') { + isNew = frame === undefined + if (isNew) { + frame = document.createElement('iframe') + frame.sandbox = "allow-scripts allow-top-navigation" + } + frame.srcdoc = d[1] + if (isNew) { + document.body.appendChild(frame) + } + } else if (frame !== undefined) { + frame.contentWindow.postMessage(event.data, '*') } - frame.srcdoc = d[1] - } else if (frame !== undefined) { - frame.postMessage(event.data) - } - if (isNew) { - document.body.appendChild(frame) } }) ${' @@ -81,10 +88,16 @@ export class FileGroupPage extends HTMLElement { this.initEditFrame() this.initViewFrame() this.editing = this.editing + addEventListener('message', this.handleMessage) + } + + disconnectedCallback() { + removeEventListener('message', this.handleMessage) } initEditFrame() { const frame = document.createElement('iframe') + frame.classList.add('edit') if (this.csp !== undefined) { frame.sandbox = "allow-same-origin allow-scripts allow-top-navigation" const url = new URL( @@ -100,12 +113,16 @@ export class FileGroupPage extends HTMLElement { frame.addEventListener('load', () => { this.displayEdit() }) + frame.addEventListener('message', message => { + this.handleEditMessage(message) + }) this.editFrame = frame this.shadowRoot.append(frame) } initViewFrame() { const frame = document.createElement('iframe') + frame.classList.add('view') if (this.csp !== undefined) { frame.sandbox = "allow-same-origin allow-scripts allow-top-navigation" const url = new URL( @@ -125,22 +142,50 @@ export class FileGroupPage extends HTMLElement { this.shadowRoot.append(frame) } - displayView() { - let doc = 'view here' + displayView(doc) { const msg = ['srcdoc', doc] this.viewFrame.contentWindow.postMessage( msg, '*' ) } - displayEdit() { - let doc = 'edit here' + async displayEdit() { + const doc = await this.editorBuild.build() const msg = ['srcdoc', doc] - this.viewFrame.contentWindow.postMessage( + this.editFrame.contentWindow.postMessage( msg, '*' ) } + handleMessage = event => { + const editWin = this.editFrame?.contentWindow + const viewWin = this.viewFrame?.contentWindow + if (editWin && event.source == editWin) { + this.handleEditMessage(event) + } else if (viewWin && event.source == viewWin) { + this.handleViewMessage(event) + } + } + + async handleViewMessage(event) { + } + + async handleEditMessage(event) { + if (Array.isArray(event.data)) { + if (event.data[0] === 'ready') { + this.editFrame.contentWindow.postMessage( + ['doc', this.body], '*' + ) + } else if (event.data[0] === 'html') { + const html = event.data[1] + this.displayView(html) + } else if (event.data[0] === 'save') { + const doc = event.data[1] + this.body = doc + } + } + } + set body(value) { try { localStorage.setItem(this.path, value) @@ -168,6 +213,11 @@ export class FileGroupPage extends HTMLElement { } else { classes.add('viewing') classes.remove('editing') + if (this.editFrame) { + this.editFrame.contentWindow.postMessage( + ['request-html'], '*' + ) + } } } } diff --git a/components/header.js b/components/header.js index 088a30f..04ff05e 100644 --- a/components/header.js +++ b/components/header.js @@ -28,6 +28,9 @@ export class Header extends HTMLElement { cancel: 'Cancel', alreadyExists: 'There is already a page with that name.', createPage: 'Create Page', + htmlCss: 'HTML/CSS', + singleFile: 'Single File', + newPage: 'New Page', } textEs = { @@ -40,6 +43,9 @@ export class Header extends HTMLElement { cancel: 'Cancelar', alreadyExists: 'Ya existe una página con ese nombre.', createPage: 'Crear Página', + htmlCss: 'HTML/CSS', + singleFile: 'Archivo único', + newPage: 'Nueva Página', } constructor() { @@ -310,6 +316,22 @@ export class Header extends HTMLElement { input.value = '/' input.style.minWidth = '300px' dialog.bodyEl.appendChild(input) + const select = document.createElement('select') + const options = ['htmlCss', 'singleFile'] + select.append(...options.map(value => { + const el = document.createElement('option') + el.value = value + el.innerText = this.text[value] + return el + })) + select.value = 'htmlCss' + input.addEventListener('input', e => { + const ext = e.target.value.match(/\.\w+$/) + select.value = ext ? 'singleFile' : 'htmlCss' + }) + select.style.marginTop = '10px' + select.style.marginBottom = '10px' + dialog.bodyEl.appendChild(select) let errorEl const bGroup = document.createElement( 'm-forms-button-group' @@ -328,7 +350,24 @@ export class Header extends HTMLElement { } return } - localStorage.setItem(newPath, '') + const value = ( + select.value === 'singleFile' ? + '' : + JSON.stringify({ + type: 'm-file-group', + files: [ + { + "name": "index.html", + "data": `

${this.text.newPage}

`, + }, + { + "name": "style.css", + "data": 'h1 { color: dodgerblue; }', + }, + ], + }) + ) + localStorage.setItem(newPath, value) location.hash = newPath dialog.close() this.dispatchEvent(new CustomEvent( diff --git a/components/layout.js b/components/layout.js index 2f37667..4468f06 100644 --- a/components/layout.js +++ b/components/layout.js @@ -1,3 +1,5 @@ +import { EditorBuild } from "/loader/editor-build.js" + export class Layout extends HTMLElement { constructor() { super() @@ -19,7 +21,7 @@ export class Layout extends HTMLElement { overflow-y: hidden; position: relative; } - m-page { + m-page, m-file-group-page { flex-grow: 1; } ` @@ -61,6 +63,9 @@ export class Layout extends HTMLElement { isGroup ? 'm-file-group-page' : 'm-page' ) this.page.csp = this.csp + if (isGroup) { + this.page.editorBuild = this.editorBuild + } this.page.path = path this.editing = this.editing if (prevPage !== undefined) { @@ -99,4 +104,11 @@ export class Layout extends HTMLElement { return false } } + + get editorBuild() { + if (this._editorBuild === undefined) { + this._editorBuild = new EditorBuild() + } + return this._editorBuild + } } \ No newline at end of file diff --git a/components/page.js b/components/page.js index 3e1dd58..d77f271 100644 --- a/components/page.js +++ b/components/page.js @@ -35,7 +35,7 @@ addEventListener('message', event => { } frame.srcdoc = d[1] } else if (frame !== undefined) { - frame.postMessage(event.data) + frame.contentWindow.postMessage(event.data, '*') } if (isNew) { document.body.appendChild(frame) diff --git a/sw.js b/sw.js index 19a73c4..326907c 100644 --- a/sw.js +++ b/sw.js @@ -1,21 +1,22 @@ async function initCache() { const cache = await caches.open('v1') await cache.addAll([ - '/', - '/index.html', '/app.js', - '/components/page.js', - '/components/layout.js', + '/components/file-group-page.js', '/components/header.js', + '/components/layout.js', '/components/nav-menu.js', + '/components/page.js', '/dialog/dialog.js', + '/editor/app.js', + '/editor/file-group.js', + '/editor/file-view.js', + '/editor/text-edit.js', '/forms/button-group.js', - '/menu/dropdown.js', + '/index.html', '/loader/builder.js', '/loader/editor-build.js', - '/editor/file-group.js', - '/editor/file-page.js', - '/editor/text-edit.js', + '/menu/dropdown.js', ]) } @@ -52,4 +53,4 @@ self.addEventListener('fetch', event => { self.addEventListener('activate', event => { event.waitUntil(clients.claim()) -}) \ No newline at end of file +})