export class PageActions extends HTMLElement { textEn = { download: 'Download', rename: 'Move/Rename', duplicate: 'Duplicate', delete_: 'Delete', settings: 'Settings', confirmDelete: f => ( `Are you sure you want to delete ${f}?` ), cancel: 'Cancel', alreadyExists: 'There is already a page with that name.', save: 'Guardar', close: 'Close', } textEs = { download: 'Descargar', rename: 'Mover/Renombrar', duplicate: 'Duplicar', delete_: 'Borrar', settings: 'Configuración', confirmDelete: f => ( `¿Desea borrar ${f}?` ), cancel: 'Cancelar', alreadyExists: 'Ya existe una página con ese nombre.', save: 'Guardar', close: 'Cerrar', } constructor() { super() this.attachShadow({mode: 'open'}) this.language = navigator.language this.dialogWrap = document.createElement('div') this.shadowRoot.append(this.dialogWrap) } 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), }, { text: this.text.rename, click: this.rename.bind(this), }, { text: this.text.duplicate, click: this.duplicate.bind(this), }, { text: this.text.delete_, click: this.delete_.bind(this), }, ] if (this.page.isGroup) { return [ ...baseActions, { text: this.text.settings, click: this.settings.bind(this), }, ] } else { return baseActions } } download() { const text = this.storage.getItem(this.path) const sp = this.path.split('/') const filename = sp[sp.length - 1] const el = document.createElement('a') el.setAttribute( 'href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text) ) el.setAttribute('download', filename) el.style.display = 'none' this.shadowRoot.appendChild(el) el.click() this.shadowRoot.removeChild(el) } rename() { const dialog = document.createElement( '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 bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.rename, () => { const newPath = input.value const v = this.storage.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 } const sKeyOld = 'settings/page:' + this.path const sKeyNew = 'settings/page:' + newPath const settingsJson = this.storage.getItem(sKeyOld) if (settingsJson ?? true === true) { this.storage.setItem(sKeyNew, settingsJson) this.storage.removeItem(sKeyOld) let settingsData try { settingsData = JSON.parse(settingsJson) } catch (err) { settingsData = {} } if (settingsData?.connections) { for (const dir of ['outbound', 'inbound']) { const otherDir = ( dir === 'outbound' ? 'inbound' : 'outbound' ) this.applyInverseRename( settingsData, dir, otherDir, this.path, newPath ) } } } else { this.storage.removeItem(sKeyNew) } this.storage.setItem( newPath, this.storage.getItem(this.path) ) this.storage.removeItem(this.path) dialog.close() location.hash = newPath }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() } duplicate() { const dialog = document.createElement( '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 bGroup = document.createElement( 'm-forms-button-group' ) const btnText = this.text.duplicate bGroup.addPrimary(btnText, () => { const newPath = input.value const v = this.storage.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 } this.storage.setItem( newPath, this.storage.getItem(this.path) ) this.storage.setItem( 'settings/page:' + newPath, this.storage.getItem(this.path) ?? '{}' ) dialog.close() location.hash = newPath }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() } delete_() { const dialog = document.createElement( 'm-dialog' ) this.dialogWrap.replaceChildren(dialog) const p = document.createElement('p') p.innerText = this.text.confirmDelete( JSON.stringify(this.path) ) dialog.bodyEl.appendChild(p) const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.delete_, () => { this.storage.removeItem(this.path) this.storage.removeItem( 'settings/page:' + this.path ) location.hash = '/' dialog.close() }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() } settings() { const dialog = document.createElement( 'm-dialog' ) this.dialogWrap.replaceChildren(dialog) const settingsEl = document.createElement( 'm-settings-page-settings' ) settingsEl.cspProfiles = this.cspProfiles settingsEl.path = this.path settingsEl.checkExists = path => ( this.storage.getItem(path) !== null ) settingsEl.data = this.page.settings const h = document.createElement('h2') h.innerText = this.text.settings dialog.headerEl.append(h) dialog.bodyEl.append(settingsEl) const bGroup = document.createElement( 'm-forms-button-group' ) bGroup.addPrimary(this.text.save, () => { const settingsData = settingsEl.data this.page.settings = settingsData for (const dir of ['outbound', 'inbound']) { const otherDir = ( dir === 'outbound' ? 'inbound' : 'outbound' ) this.applyInverseSettings( settingsData, dir, otherDir ) } dialog.close() this.dispatchEvent(new CustomEvent( 'settings-change', {bubbles: true, composed: true} )) }) bGroup.addCancel(this.text.cancel, () => { dialog.close() }) dialog.footerEl.appendChild(bGroup) dialog.open() } applyInverseSettings(settingsData, dir, otherDir) { const selfEntries = Object.entries( settingsData.connections[dir] ) for (const [path, access] of selfEntries) { const key = 'settings/page:' + path let val = this.storage.getItem(key) try { if (val !== null) { val = JSON.parse(val) } } catch (err) { // ignore } const data = val ?? {} data.connections = data.connections ?? {} data.connections[otherDir] = ( data.connections[otherDir] ?? {} ) data.connections[otherDir][this.path] = access this.storage.setItem( key, JSON.stringify(data) ) } } applyInverseRename( settingsData, dir, otherDir, oldPath, newPath ) { const selfEntries = Object.entries( settingsData.connections[dir] ?? {} ) for (const [path, access] of selfEntries) { const key = 'settings/page:' + path let val = this.storage.getItem(key) try { if (val !== null) { val = JSON.parse(val) } } catch (err) { // ignore } const data = val ?? {} data.connections = data.connections ?? {} data.connections[otherDir] = ( data.connections[otherDir] ?? {} ) const accessValue = data.connections[otherDir][oldPath] data.connections[otherDir][newPath] = accessValue data.connections[otherDir][oldPath] = undefined this.storage.setItem( key, JSON.stringify(data) ) } } get language() { return this._language } set language(language) { this._language = language this.text = this.langEs ? this.textEs : this.textEn } get langEs() { return /^es\b/.test(this.language) } }