const isTag = ele => ele.classList.contains('tag')
const links = [...document.querySelectorAll('a')]
// Set statistics {{{
const counter = document.querySelector('#counter')
counter.textContent = links.length
// }}}
// Modal for links {{{
// click links to open modal
links.forEach(a => a.onclick = e => {
e.preventDefault()
modalForLink(a)
})
// create modal for display details of links
const modal = document.createElement('dialog')
modal.id = 'modal'
document.body.append(modal)
modal.onclick = event => event.target == modal && modal.close()
modal.innerHTML = `
`
const heading = modal.querySelector('#heading')
const qrcode = modal.querySelector('#qrcode')
const copy = modal.querySelector('#copy')
const link = modal.querySelector('#link a')
link.onclick = e => e.stopPropagation()
function modalForLink (a) {
const url = a.href
qrcode.innerHTML = ''
new QRCode(qrcode, url)
link.href = url
link.textContent = url
heading.textContent = a.textContent
copy.textContent = 'đź“‹COPY'
modal.showModal()
}
const copyLink = (ele) => {
navigator.clipboard.writeText(link.href)
ele.textContent = '👌COPIED'
}
// }}}
// Filter by text {{{
const searchInput = document.querySelector('#searchInput')
searchInput.value = ''
// get only text from inline children
function inlineText (el) {
let result = ''
for (const child of el.childNodes) {
if (child.nodeType === Node.TEXT_NODE) {
result += child.textContent
} else if (child.nodeType === Node.ELEMENT_NODE) {
const style = window.getComputedStyle(child)
if (child.tagName === 'SUMMARY' || style.display === 'inline' || style.display === 'inline-block') {
result += inlineText(child)
}
}
}
return result
}
function search (ele, text) {
ele.style.display = 'none'
// TODO consider elements other than
const liChildren = ele.querySelectorAll(':scope > ul > li')
if (inlineText(ele).toLowerCase().includes(text)) {
[ele, ...liChildren].forEach(e => e.style.display = '')
} else if (ele.textContent.toLowerCase().includes(text)) {
ele.style.display = ''
liChildren.forEach(li => search(li, text))
}
}
const onSearchInput = inputEle => {
function search (ele, text) {
ele.style.display = 'none'
const disabled = ele.classList.contains('disabled')
// TODO consider elements other than
const liChildren = ele.querySelectorAll(':scope > ul > li')
if (inlineText(ele).toLowerCase().includes(text)) {
if (disabled) return
[ele, ...liChildren].forEach(e => e.style.display = '')
} else if (ele.textContent.toLowerCase().includes(text)) {
ele.style.display = ''
liChildren.forEach(li => search(li, text))
}
}
// FIXME allow search text separated by spaces later
const text = inputEle.value.includes(' ')
? inputEle.value.replaceAll(' ', '')
: inputEle.value
inputEle.value = text
console.log('Search value changed to:', text)
document.querySelectorAll('details')
.forEach(details => search(details, text.toLowerCase()))
}
document.querySelector('#clear').onclick = () => {
searchInput.value = ''
searchInput.oninput()
}
// }}}
// Filter by tags {{{
document.querySelectorAll('#filter > p:has(.tag)').forEach(cat => {
cat.querySelector('*:first-child').onclick = () => {
const tags = cat.querySelectorAll('.tag')
const toggleDisable = tags[0].classList.contains('disabled')
? ele => ele.classList.remove('disabled')
: ele => ele.classList.add('disabled')
tags.forEach(toggleDisable)
}
})
// hide - if necessary when toggling tag
const tagToggleObserver = target => () => {
const selector = '.' + [...target.classList].join('.').replace('.disabled', '')
const allTags = document.querySelectorAll('details ' + selector)
allTags.forEach(t => {
t.className = target.className
const parent = t.parentElement
// Hide
- if all child tags are disabled
parent.hidden = [...parent.querySelectorAll(':scope .tag')]
.every(child => child.classList.contains('disabled'))
})
}
document.querySelectorAll('#filter .tag').forEach(tag => {
tag.onclick = () => tag.classList.toggle('disabled')
new window.MutationObserver(tagToggleObserver(tag)).observe(tag, {
attributes: true,
attributeFilter: ['class'],
})
})
// hide parent if necessary when
- is hidden
document.querySelectorAll('li').forEach(li => {
new window.MutationObserver(() => {
const parent = li.closest('ul').parentElement
parent.hidden = [...parent.querySelectorAll(':scope > ul > li')]
.every(li => window.getComputedStyle(li).display == 'none')
}).observe(li, {
attributes: true,
attributeFilter: ['hidden'],
})
})
// }}}
// Fold/Unfold
if necessary {{{
const detailsList = document.querySelectorAll('details')
detailsList.forEach(details => {
new window.MutationObserver((ms) => {
const visibleLiList = [...details.querySelectorAll('li')]
.filter(li => {
const visible = window.getComputedStyle(li).display != 'none'
const tagged = [...li.children].some(isTag)
return visible && tagged
})
details.querySelector('summary').setAttribute('count', visibleLiList.length)
details.open = visibleLiList.length <= 5
}).observe(details, {
attributes: true,
attributeFilter: ['style', 'hidden', 'focus'],
subtree: true,
})
details.setAttribute('style', '')
})
// }}}
// Scroll to content by window.location {{{
new window.MutationObserver(ms => {
const target = ms[0].target
const details = target.closest('details')
if (details) {
details.ontoggle = () => {
target.scrollIntoView({ behavior: 'smooth' })
details.ontoggle = null
}
details.open = true
} else {
target.scrollIntoView({ behavior: 'smooth' })
}
if (target.tagName === 'A') {
modalForLink(target)
}
}).observe(document.body, {
attributes: true,
attributeFilter: ['focus'],
subtree: true,
})
const path = decodeURIComponent(window.location.pathname.split('/')[1])
const subject = path
? [
...document.querySelectorAll('details'),
...document.querySelectorAll('li'),
...document.querySelectorAll('a'),
]
.find(e => inlineText(e).includes(path))
: null
const hash = decodeURIComponent(window.location.hash.replace(/^#/, ''))
let moveTo
moveTo = hash
? [
...(subject ?? document.body).querySelectorAll('a'),
...(subject ?? document.body).querySelectorAll('li'),
...(subject ?? document.body).querySelectorAll('details'),
]
.find(e => inlineText(e).includes(hash) || e.title.includes(hash))
: subject
moveTo?.setAttribute('focus', true)
// }}}
// vim:sw=2 ts=2 sts=2 expandtab foldmethod=marker foldtext&vim