You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
9.0 KiB
JavaScript
316 lines
9.0 KiB
JavaScript
export class Header extends HTMLElement {
|
|
icons = {
|
|
menu: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-list" viewBox="0 0 16 16">
|
|
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
|
|
</svg>`,
|
|
dot: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-three-dots" viewBox="0 0 16 16">
|
|
<path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
|
|
</svg>`,
|
|
edit: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
|
|
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
|
|
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/>
|
|
</svg>`,
|
|
check: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16">
|
|
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"/>
|
|
</svg>`,
|
|
add: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-plus-lg" viewBox="0 0 16 16">
|
|
<path fill-rule="evenodd" d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2Z"/>
|
|
</svg>`,
|
|
}
|
|
|
|
textEn = {
|
|
confirmDelete: f => (
|
|
`Are you sure you want to delete ${f}?`
|
|
),
|
|
cancel: 'Cancel',
|
|
alreadyExists: 'There is already a page with that name.',
|
|
createPage: 'Create Page',
|
|
htmlCss: 'HTML/CSS',
|
|
singleFile: 'Single File',
|
|
newPage: 'New Page',
|
|
}
|
|
|
|
textEs = {
|
|
confirmDelete: f => (
|
|
`¿Desea borrar ${f}?`
|
|
),
|
|
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() {
|
|
super()
|
|
this.language = navigator.language
|
|
this.editing = false
|
|
this.attachShadow({mode: 'open'})
|
|
this.appBar = document.createElement('div')
|
|
this.appBar.classList.add('app-bar')
|
|
this.shadowRoot.appendChild(this.appBar)
|
|
this.addButton(this.icons.menu, 'nav', () => {
|
|
this.menu.pages = this.getPages()
|
|
this.menuPanel.classList.add('open')
|
|
this.overlay.classList.add('open')
|
|
}).classList.add('left-end')
|
|
this.addButton(this.icons.add, 'add', () => {
|
|
this.addPage()
|
|
})
|
|
this.addDivider()
|
|
this.editBtn = this.addButton(this.editIcon, 'edit', () => {
|
|
this.dispatchEvent(new CustomEvent(
|
|
'click-edit', {bubbles: true}
|
|
))
|
|
})
|
|
this.pageButton = this.addButton(
|
|
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)
|
|
}
|
|
)
|
|
this.pageButton.classList.add('right-end')
|
|
this.addMenu()
|
|
}
|
|
|
|
connectedCallback() {
|
|
const style = document.createElement('style')
|
|
style.textContent = `
|
|
:host {
|
|
background: #111;
|
|
color: #ddd;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
div.app-bar {
|
|
display: flex;
|
|
flex-direction: row;
|
|
padding: 0;
|
|
}
|
|
div.divider {
|
|
flex-grow: 1;
|
|
}
|
|
button {
|
|
border: none;
|
|
background: inherit;
|
|
color: inherit;
|
|
font-size: 30px;
|
|
padding: 0 8px;
|
|
}
|
|
button.left-end {
|
|
padding-left: 12px;
|
|
}
|
|
button.right-end {
|
|
padding-right: 12px;
|
|
}
|
|
div.menu {
|
|
position: fixed;
|
|
top: 0;
|
|
left: max(-90vw, -480px);
|
|
height: 100vh;
|
|
width: min(90vw, 480px);
|
|
background-color: #fff;
|
|
transition: left .25s ease-in-out;
|
|
}
|
|
div.menu.open {
|
|
left: 0;
|
|
}
|
|
div.overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: -100vw;
|
|
height: 100vh;
|
|
width: 100vw;
|
|
opacity: 0%;
|
|
transition: opacity .25s ease-in;
|
|
background: #777;
|
|
}
|
|
div.overlay.closing {
|
|
left: 0;
|
|
}
|
|
div.overlay.open {
|
|
left: 0;
|
|
opacity: 15%;
|
|
}
|
|
svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
`
|
|
this.shadowRoot.append(style)
|
|
this.addPageMenu()
|
|
}
|
|
|
|
encodePath(path) {
|
|
return path
|
|
}
|
|
|
|
addButton(html, cls, onClick) {
|
|
const b = document.createElement('button')
|
|
b.innerHTML = html
|
|
b.classList.add(cls)
|
|
this.appBar.appendChild(b)
|
|
if (onClick) {
|
|
b.addEventListener('click', onClick)
|
|
}
|
|
return b
|
|
}
|
|
|
|
addDivider() {
|
|
const d = document.createElement('div')
|
|
d.classList.add('divider')
|
|
this.appBar.appendChild(d)
|
|
}
|
|
|
|
addMenu() {
|
|
this.overlay = document.createElement('div')
|
|
this.overlay.classList.add('overlay')
|
|
this.shadowRoot.appendChild(this.overlay)
|
|
this.menuPanel = document.createElement('div')
|
|
this.menuPanel.classList.add('menu')
|
|
this.menu = document.createElement('m-nav-menu')
|
|
this.menuPanel.appendChild(this.menu)
|
|
this.shadowRoot.appendChild(this.menuPanel)
|
|
this.overlay.addEventListener('click', () => {
|
|
this.close()
|
|
})
|
|
this.menu.addEventListener('close-menu', () => {
|
|
this.close()
|
|
})
|
|
}
|
|
|
|
addPageMenu() {
|
|
this.pageMenu = document.createElement(
|
|
'm-menu-dropdown'
|
|
)
|
|
this.shadowRoot.appendChild(this.pageMenu)
|
|
}
|
|
|
|
close() {
|
|
this.overlay.classList.add('closing')
|
|
this.overlay.classList.remove('open')
|
|
this.menuPanel.classList.remove('open')
|
|
setTimeout(() => {
|
|
this.overlay.classList.remove('closing')
|
|
}, 250)
|
|
}
|
|
|
|
getPages() {
|
|
return (
|
|
Object.keys(localStorage).slice()
|
|
.filter(s => s.startsWith('/'))
|
|
.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
|
|
}
|
|
|
|
addPage() {
|
|
const dialog = document.createElement('m-dialog')
|
|
this.dialogWrap.replaceChildren(dialog)
|
|
const input = document.createElement('input')
|
|
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'
|
|
)
|
|
bGroup.addPrimary(this.text.createPage, () => {
|
|
const newPath = this.encodePath(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
|
|
}
|
|
return
|
|
}
|
|
const value = (
|
|
select.value === 'singleFile' ?
|
|
'' :
|
|
JSON.stringify({
|
|
type: 'm-file-group',
|
|
files: [
|
|
{
|
|
"name": "index.html",
|
|
"data": `<h1>${this.text.newPage}</h1>`,
|
|
},
|
|
{
|
|
"name": "style.css",
|
|
"data": 'h1 { color: dodgerblue; }',
|
|
},
|
|
],
|
|
})
|
|
)
|
|
localStorage.setItem(newPath, value)
|
|
localStorage.setItem(
|
|
'settings/page:' + newPath, '{}'
|
|
)
|
|
location.hash = newPath
|
|
dialog.close()
|
|
this.dispatchEvent(new CustomEvent(
|
|
'create-page', {bubbles: true}
|
|
))
|
|
})
|
|
bGroup.addCancel(this.text.cancel, () => {
|
|
dialog.close()
|
|
})
|
|
dialog.footerEl.appendChild(bGroup)
|
|
dialog.open()
|
|
}
|
|
|
|
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)
|
|
}
|
|
} |