From 9e90b8a3b8e95ab6f3907efd0ee5708fa9f4b1fc Mon Sep 17 00:00:00 2001 From: Benjamin Atkin Date: Mon, 17 Apr 2023 02:08:57 -0700 Subject: [PATCH] add csp setting --- app.js | 4 ++ components/file-group-page.js | 57 +++++++++++++++++++--- components/header.js | 12 +++-- components/layout.js | 18 ++++++- components/page-actions.js | 92 ++++++++++++++++++++--------------- components/page.js | 6 +-- sw.js | 3 +- 7 files changed, 137 insertions(+), 55 deletions(-) diff --git a/app.js b/app.js index c1ad4d6..5b51a7e 100644 --- a/app.js +++ b/app.js @@ -7,10 +7,14 @@ import { NavMenu } from "/components/nav-menu.js" import { Dialog } from "/dialog/dialog.js" import { ButtonGroup } from "/forms/button-group.js" import { Dropdown } from "/menu/dropdown.js" +import { PageSettings } from "/settings/page-settings.js" customElements.define('m-layout', Layout) customElements.define('m-page', Page) customElements.define('m-page-actions', PageActions) +customElements.define( + 'm-settings-page-settings', PageSettings +) customElements.define( 'm-file-group-page', FileGroupPage ) diff --git a/components/file-group-page.js b/components/file-group-page.js index d252d2f..f5a3f54 100644 --- a/components/file-group-page.js +++ b/components/file-group-page.js @@ -56,9 +56,9 @@ export class FileGroupPage extends HTMLElement { constructor() { super() this.attachShadow({mode: 'open'}) - this.csp = "default-src 'self' 'unsafe-inline' 'unsafe-eval'" this.viewLoaded = false this.onceOnLoaded = undefined + this.isGroup = true } connectedCallback() { @@ -80,10 +80,10 @@ export class FileGroupPage extends HTMLElement { width: 100%; } :host(.editing) iframe.view { - display: none; + visibility: hidden; } :host(.viewing) iframe.edit { - display: none; + visibility: hidden; } ` this.shadowRoot.append(style) @@ -100,12 +100,13 @@ export class FileGroupPage extends HTMLElement { initEditFrame() { const frame = document.createElement('iframe') frame.classList.add('edit') - if (this.csp !== undefined) { + const csp = this.csp + if (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('csp', csp) url.searchParams.set('html', frameHtml) frame.src = url.href } else { @@ -125,12 +126,13 @@ export class FileGroupPage extends HTMLElement { initViewFrame() { const frame = document.createElement('iframe') frame.classList.add('view') - if (this.csp !== undefined) { + const csp = this.csp + if (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('csp', csp) url.searchParams.set('html', frameHtml) frame.src = url.href } else { @@ -217,6 +219,47 @@ export class FileGroupPage extends HTMLElement { } } + set settings(value) { + try { + localStorage.setItem( + 'settings/page:' + this.path, + JSON.stringify(value) + ) + } catch (err) { + console.error(err) + } + } + + get settings() { + let data + try { + data = localStorage.getItem( + 'settings/page:' + this.path + ) + if (data === null || data === undefined) { + return {} + } + } catch (err) { + console.error(err) + return {} + } + try { + return JSON.parse(data) + } catch (err) { + return {} + } + } + + get csp() { + if (this.cspOff) { + return undefined + } else { + return this.cspProfiles[ + this.settings.networkAccess + ] ?? this.cspProfiles.local + } + } + set editing(value) { this._editing = value if (this.shadowRoot.host) { diff --git a/components/header.js b/components/header.js index b860e3c..96ae346 100644 --- a/components/header.js +++ b/components/header.js @@ -68,6 +68,11 @@ export class Header extends HTMLElement { this.icons.dot, 'page', () => { + const actions = this.pageActions.menuActions + this.pageMenu.clear() + for (const {text, click} of actions) { + this.pageMenu.add(text, click) + } this.pageMenu.open(this.pageButton) } ) @@ -186,10 +191,6 @@ export class Header extends HTMLElement { this.pageMenu = document.createElement( 'm-menu-dropdown' ) - const actions = this.pageActions.menuActions - for (const {text, click} of actions) { - this.pageMenu.add(text, click) - } this.shadowRoot.appendChild(this.pageMenu) } @@ -280,6 +281,9 @@ export class Header extends HTMLElement { }) ) localStorage.setItem(newPath, value) + localStorage.setItem( + 'settings/page:' + newPath, '{}' + ) location.hash = newPath dialog.close() this.dispatchEvent(new CustomEvent( diff --git a/components/layout.js b/components/layout.js index e64ce1d..fbc44d5 100644 --- a/components/layout.js +++ b/components/layout.js @@ -1,6 +1,12 @@ import { EditorBuild } from "/loader/editor-build.js" export class Layout extends HTMLElement { + cspProfiles = { + local: "default-src 'self' 'unsafe-inline' 'unsafe-eval'", + jsCdns: "default-src cdn.jsdelivr.com data.jsdelivr.com unpkg.com 'self' 'unsafe-inline' 'unsafe-eval'", + open: undefined, + } + constructor() { super() this.attachShadow({mode: 'open'}) @@ -11,6 +17,7 @@ export class Layout extends HTMLElement { this.pageActions = document.createElement( 'm-page-actions' ) + this.pageActions.cspProfiles = this.cspProfiles this.header.pageActions = this.pageActions this.shadowRoot.append( this.header, @@ -54,6 +61,9 @@ export class Layout extends HTMLElement { this.addEventListener('hash-change', () => { this.load() }) + this.addEventListener('settings-change', () => { + this.load() + }) } load() { @@ -75,7 +85,12 @@ export class Layout extends HTMLElement { this.page = document.createElement( isGroup ? 'm-file-group-page' : 'm-page' ) - this.page.csp = this.csp + if (isGroup) { + this.page.cspOff = this.csp === undefined + this.page.cspProfiles = this.cspProfiles + } else { + this.page.csp = this.csp + } if (isGroup) { this.page.editorBuild = this.editorBuild } @@ -87,6 +102,7 @@ export class Layout extends HTMLElement { this.shadowRoot.appendChild(this.page) this.header.path = path this.pageActions.path = path + this.pageActions.page = this.page } get path() { diff --git a/components/page-actions.js b/components/page-actions.js index 8f694f8..20f6ce4 100644 --- a/components/page-actions.js +++ b/components/page-actions.js @@ -10,6 +10,8 @@ export class PageActions extends HTMLElement { ), cancel: 'Cancel', alreadyExists: 'There is already a page with that name.', + save: 'Guardar', + close: 'Close', } textEs = { @@ -23,6 +25,8 @@ export class PageActions extends HTMLElement { ), cancel: 'Cancelar', alreadyExists: 'Ya existe una página con ese nombre.', + save: 'Guardar', + close: 'Cerrar', } constructor() { @@ -31,7 +35,20 @@ export class PageActions extends HTMLElement { this.language = navigator.language this.dialogWrap = document.createElement('div') this.shadowRoot.append(this.dialogWrap) - this.menuActions = [ + } + + connectedCallback() { + const style = document.createElement('style') + style.textContent = ` + m-dialog::part(footer) { + padding-top: 15px; + } + ` + this.shadowRoot.appendChild(style) + } + + get menuActions() { + const baseActions = [ { text: this.text.download, click: this.download.bind(this), @@ -48,20 +65,18 @@ export class PageActions extends HTMLElement { text: this.text.delete_, click: this.delete_.bind(this), }, - //{ - // text: this.text.settings, - // click: this.settings.bind(this), - //}, ] - } - connectedCallback() { - const style = document.createElement('style') - style.textContent = ` - m-dialog::part(footer) { - padding-top: 15px; - } - ` - this.shadowRoot.appendChild(style) + if (this.page.isGroup) { + return [ + ...baseActions, + { + text: this.text.settings, + click: this.settings.bind(this), + }, + ] + } else { + return baseActions + } } download() { @@ -111,7 +126,14 @@ export class PageActions extends HTMLElement { newPath, localStorage.getItem(this.path) ) + localStorage.setItem( + 'settings/page:' + newPath, + localStorage.getItem(this.path) + ) localStorage.removeItem(this.path) + localStorage.removeItem( + 'settings/page:' + newPath, + ) dialog.close() location.hash = newPath }) @@ -153,6 +175,10 @@ export class PageActions extends HTMLElement { newPath, localStorage.getItem(this.path) ) + localStorage.setItem( + 'settings/page:' + newPath, + localStorage.getItem(this.path) ?? '{}' + ) dialog.close() location.hash = newPath }) @@ -179,6 +205,9 @@ export class PageActions extends HTMLElement { ) bGroup.addPrimary(this.text.delete_, () => { localStorage.removeItem(this.path) + localStorage.removeItem( + 'settings/page:' + this.path + ) location.hash = '/' dialog.close() }) @@ -194,34 +223,21 @@ export class PageActions extends HTMLElement { 'm-dialog' ) this.dialogWrap.replaceChildren(dialog) - const input = document.createElement('input') - input.value = this.path - input.style.minWidth = '300px' - dialog.bodyEl.appendChild(input) - let errorEl + const settingsEl = document.createElement( + 'm-settings-page-settings' + ) + settingsEl.cspProfiles = this.cspProfiles + settingsEl.data = this.page.settings + dialog.bodyEl.appendChild(settingsEl) const bGroup = document.createElement( 'm-forms-button-group' ) - bGroup.addPrimary(this.text.rename, () => { - const newPath = input.value - const v = localStorage.getItem(newPath) - if (v !== null || newPath === this.path) { - if (!errorEl) { - errorEl = document.createElement('p') - errorEl.style.color = 'red' - const errText = this.text.alreadyExists - errorEl.innerText = errText - dialog.bodyEl.appendChild(errorEl) - } - return - } - localStorage.setItem( - newPath, - localStorage.getItem(this.path) - ) - localStorage.removeItem(this.path) + bGroup.addPrimary(this.text.save, () => { + this.page.settings = settingsEl.data dialog.close() - location.hash = newPath + this.dispatchEvent(new CustomEvent( + 'settings-change', {bubbles: true, composed: true} + )) }) bGroup.addCancel(this.text.cancel, () => { dialog.close() diff --git a/components/page.js b/components/page.js index d77f271..b965301 100644 --- a/components/page.js +++ b/components/page.js @@ -58,6 +58,7 @@ export class Page extends HTMLElement { div.classList.add('twrap') div.appendChild(this.textArea) this.shadowRoot.appendChild(div) + this.isGroup = false } connectedCallback() { @@ -73,7 +74,7 @@ export class Page extends HTMLElement { align-items: stretch; } div.twrap { - padding: 10px 10px; + padding: 10px; display: flex; align-items: stretch; flex-direction: column; @@ -84,9 +85,6 @@ export class Page extends HTMLElement { font-size: 0.90em; height: 100%; } - textarea:focus { - height: 45vh; - } iframe { border: none; margin: 0; diff --git a/sw.js b/sw.js index 7121d69..8b7ec33 100644 --- a/sw.js +++ b/sw.js @@ -18,7 +18,8 @@ async function initCache() { '/loader/builder.js', '/loader/editor-build.js', '/menu/dropdown.js', - ]) //2 + '/settings/page-settings.js', + ]) } self.addEventListener("install", event => {