defter 0.3.5

Self-hosted Zotero sync and file storage server, with metadata in SQLite and attachments in S3.
// Appended to htmx (vendor/) by build.rs; both run as one deferred script.

// The theme toggle. The button's label switches by CSS (dark:hidden), and an
// inline script in the page head applies the stored theme before first paint.
document.addEventListener('click', (event) => {
  if (!event.target.closest('[data-theme-toggle]')) return
  const dark = document.documentElement.classList.toggle('dark')
  localStorage.setItem('defter-dark', String(dark))
})

// The public profile's facets, which are links and <details> and work without
// any of this: open or close them all, and keep them as they were when htmx
// replaces the list of works.
function syncFacetsToggle() {
  const toggle = document.querySelector('[data-facets-toggle]')
  if (!toggle) return
  const open = [...document.querySelectorAll('details[data-facet]')].every((f) => f.open)
  toggle.textContent = open ? toggle.dataset.closeLabel : toggle.dataset.openLabel
}

document.addEventListener('click', (event) => {
  if (!event.target.closest('[data-facets-toggle]')) return
  const facets = [...document.querySelectorAll('details[data-facet]')]
  const open = !facets.every((f) => f.open)
  facets.forEach((f) => { f.open = open })
})

// `toggle` does not bubble, so this listens on the way down.
document.addEventListener('toggle', syncFacetsToggle, true)

let openFacets = new Map()

document.addEventListener('htmx:beforeSwap', () => {
  openFacets = new Map(
    [...document.querySelectorAll('details[data-facet]')].map((f) => [f.dataset.facet, f.open])
  )
})

document.addEventListener('htmx:afterSwap', () => {
  document.querySelectorAll('details[data-facet]').forEach((f) => {
    f.open = openFacets.get(f.dataset.facet) ?? f.open
  })
  syncFacetsToggle()
})

// Copy what a button holds, and show a tick for a moment.
document.addEventListener('click', async (event) => {
  const button = event.target.closest('[data-copy]')
  if (!button) return
  await navigator.clipboard.writeText(button.dataset.copy)
  const [copy, copied] = button.querySelectorAll('svg')
  copy.classList.add('hidden')
  copied.classList.remove('hidden')
  setTimeout(() => {
    copy.classList.remove('hidden')
    copied.classList.add('hidden')
  }, 2000)
})

// The language switcher: the same page in another language. The URL has
// whatever htmx has made of the list since the page was rendered.
document.addEventListener('change', (event) => {
  if (!event.target.matches('[data-language]')) return
  const url = new URL(window.location.href)
  url.searchParams.set('lang', event.target.value)
  window.location.href = url.href
})