Files
simplex-chat/js/links.js
T
2026-05-21 05:58:27 +00:00

260 lines
8.5 KiB
JavaScript

document.addEventListener("DOMContentLoaded", function () {
var ITEMS_PER_PAGE = 10
var allItems = Array.from(document.querySelectorAll("#links-list .link-item"))
var filteredItems = allItems.slice()
var currentPage = 1
var langSelect = document.getElementById("filter-language")
var pills = Array.from(document.querySelectorAll("#links-filters .filter-chip"))
var pagination = document.getElementById("links-pagination")
var activeFilter = "" // "" | "media" | "cat"
var activeValue = "" // "video" | "audio" | "review" | etc.
function matchesActivePill(el) {
if (!activeFilter) return true
if (activeFilter === "media") return el.getAttribute("data-media") === activeValue
if (activeFilter === "cat") return el.getAttribute("data-category") === activeValue
return true
}
function matchesLang(el) {
var lang = langSelect.value
return !lang || el.getAttribute("data-lang") === lang
}
function getFiltered() {
return allItems.filter(function (el) {
return matchesActivePill(el) && matchesLang(el)
})
}
function updateLanguageOptions() {
var available = {}
allItems.forEach(function (el) {
if (matchesActivePill(el)) {
var lang = el.getAttribute("data-lang")
if (lang) available[lang] = true
}
})
var options = langSelect.options
for (var i = 1; i < options.length; i++) {
var has = !!available[options[i].value]
options[i].disabled = !has
options[i].style.display = has ? "" : "none"
}
if (langSelect.value && !available[langSelect.value]) langSelect.value = ""
}
function updatePillAvailability() {
pills.forEach(function (pill) {
var filter = pill.getAttribute("data-filter")
if (filter === null || filter === "") return
var value = pill.getAttribute("data-value")
var has = allItems.some(function (el) {
if (!matchesLang(el)) return false
if (filter === "media") return el.getAttribute("data-media") === value
if (filter === "cat") return el.getAttribute("data-category") === value
return false
})
pill.style.opacity = has ? "" : "0.3"
})
}
function applyFilters() {
filteredItems = getFiltered()
currentPage = 1
updateLanguageOptions()
updatePillAvailability()
render()
updateHash()
}
function render() {
var totalPages = Math.max(1, Math.ceil(filteredItems.length / ITEMS_PER_PAGE))
if (currentPage > totalPages) currentPage = totalPages
var start = (currentPage - 1) * ITEMS_PER_PAGE
var end = start + ITEMS_PER_PAGE
for (var i = 0; i < allItems.length; i++) allItems[i].style.display = "none"
for (var i = 0; i < filteredItems.length; i++) {
filteredItems[i].style.display = (i >= start && i < end) ? "" : "none"
}
renderPagination(totalPages)
}
function renderPagination(totalPages) {
pagination.innerHTML = ""
if (totalPages <= 1) return
var prevBtn = document.createElement("button")
prevBtn.textContent = "Prev"
prevBtn.className = "text-btn"
prevBtn.disabled = currentPage <= 1
prevBtn.addEventListener("click", function () { goToPage(currentPage - 1) })
pagination.appendChild(prevBtn)
var pages = getPaginationRange(currentPage, totalPages)
for (var i = 0; i < pages.length; i++) {
if (pages[i] === "...") {
var span = document.createElement("span")
span.textContent = "…"
span.style.padding = "8px 4px"
pagination.appendChild(span)
} else {
var btn = document.createElement("button")
btn.textContent = String(pages[i])
if (pages[i] === currentPage) btn.className = "active"
if (Math.abs(pages[i] - currentPage) === 1) btn.classList.add("neighbor")
;(function (p) { btn.addEventListener("click", function () { goToPage(p) }) })(pages[i])
pagination.appendChild(btn)
}
}
var nextBtn = document.createElement("button")
nextBtn.textContent = "Next"
nextBtn.className = "text-btn"
nextBtn.disabled = currentPage >= totalPages
nextBtn.addEventListener("click", function () { goToPage(currentPage + 1) })
pagination.appendChild(nextBtn)
}
function goToPage(page) {
currentPage = page
render()
updateHash()
window.scrollTo({ top: document.getElementById("links-page").offsetTop - 80, behavior: "smooth" })
}
function getPaginationRange(current, total) {
if (total <= 7) {
var r = []
for (var i = 1; i <= total; i++) r.push(i)
return r
}
var pages = [1]
if (current > 3) pages.push("...")
var s = Math.max(2, current - 1), e = Math.min(total - 1, current + 1)
for (var i = s; i <= e; i++) pages.push(i)
if (current < total - 2) pages.push("...")
pages.push(total)
return pages
}
// Hash
function getHashParams() {
var hash = window.location.hash.slice(1)
if (!hash) return {}
var params = {}
hash.split("&").forEach(function (part) {
var kv = part.split("=")
if (kv.length === 2) params[kv[0]] = decodeURIComponent(kv[1])
})
return params
}
function setHash(params) {
var parts = []
if (params.link) parts.push("link=" + encodeURIComponent(params.link))
else if (params.page && params.page > 1) parts.push("page=" + params.page)
if (params.lang) parts.push("lang=" + encodeURIComponent(params.lang))
if (params.media) parts.push("media=" + encodeURIComponent(params.media))
if (params.cat) parts.push("cat=" + encodeURIComponent(params.cat))
var hash = parts.join("&")
history.replaceState(null, "", hash ? "#" + hash : window.location.pathname)
}
function updateHash() {
var params = { page: currentPage, lang: langSelect.value }
if (activeFilter === "media") params.media = activeValue
if (activeFilter === "cat") params.cat = activeValue
setHash(params)
}
function setActivePill(filter, value) {
activeFilter = filter
activeValue = value
pills.forEach(function (p) {
var pf = p.getAttribute("data-filter")
var pv = p.getAttribute("data-value")
p.classList.toggle("active", pf === filter && (pv || "") === (value || ""))
})
}
function readHashAndApply() {
var params = getHashParams()
if (params.lang) langSelect.value = params.lang
if (params.media) setActivePill("media", params.media)
else if (params.cat) setActivePill("cat", params.cat)
else setActivePill("", "")
filteredItems = getFiltered()
updateLanguageOptions()
updatePillAvailability()
if (params.link) {
var target = document.getElementById(params.link)
if (target) {
var idx = filteredItems.indexOf(target)
if (idx === -1) {
setActivePill("", "")
langSelect.value = ""
filteredItems = getFiltered()
updateLanguageOptions()
updatePillAvailability()
idx = filteredItems.indexOf(target)
}
if (idx >= 0) {
currentPage = Math.floor(idx / ITEMS_PER_PAGE) + 1
render()
setTimeout(function () {
target.scrollIntoView({ behavior: "smooth", block: "start" })
target.classList.add("highlighted")
setTimeout(function () { target.classList.remove("highlighted") }, 3000)
}, 100)
return
}
}
}
if (params.page) currentPage = parseInt(params.page) || 1
render()
}
// Share anchor
document.getElementById("links-list").addEventListener("click", function (e) {
var anchor = e.target.closest(".share-anchor")
if (!anchor) return
e.preventDefault()
var id = anchor.getAttribute("href").split("=")[1]
var params = { link: id, lang: langSelect.value }
if (activeFilter === "media") params.media = activeValue
if (activeFilter === "cat") params.cat = activeValue
setHash(params)
if (navigator.clipboard) navigator.clipboard.writeText(window.location.href)
})
langSelect.addEventListener("change", applyFilters)
pills.forEach(function (pill) {
pill.addEventListener("click", function () {
var filter = pill.getAttribute("data-filter")
var value = pill.getAttribute("data-value") || ""
if (activeFilter === filter && activeValue === value) {
setActivePill("", "")
} else {
// If pill is greyed out (no items with current language), reset language first
if (pill.style.opacity === "0.3") {
langSelect.value = ""
}
setActivePill(filter, value)
}
applyFilters()
})
})
window.addEventListener("hashchange", readHashAndApply)
readHashAndApply()
})