mirror of https://git.sr.ht/~cadence/bibliogram
219 lines
6.1 KiB
JavaScript
219 lines
6.1 KiB
JavaScript
import {q, ElemJS} from "./elemjs/elemjs.js"
|
|
import {timeline} from "./post_series.js"
|
|
import {quota} from "./quota.js"
|
|
|
|
/** @type {PostOverlay[]} */
|
|
const postOverlays = []
|
|
const titleHistory = []
|
|
const focusHistory = []
|
|
titleHistory.push(document.title)
|
|
const shortcodeDataMap = new Map()
|
|
|
|
window.addEventListener("popstate", event => {
|
|
// console.log(event.state, postOverlays.length)
|
|
if (event.state) {
|
|
if (event.state.view === "post_overlay") {
|
|
console.log(event.state.shortcode, postOverlays.map(o => o.identifier))
|
|
/*if (postOverlays.length >= 2 && postOverlays.slice(-2)[0].identifier === event.state.shortcode) {
|
|
// continue down to actually pop please
|
|
} else {*/
|
|
return loadPostOverlay(event.state.shortcode, "none")
|
|
/*}*/
|
|
}
|
|
}
|
|
// event.state === null which means back to originally loaded page, so pop overlay
|
|
setTimeout(() => { // make sure document is entirely loaded
|
|
if (titleHistory.length === 1) {
|
|
document.title = titleHistory[0]
|
|
} else if (titleHistory.length >= 2) {
|
|
titleHistory.pop()
|
|
document.title = titleHistory.slice(-1)[0]
|
|
}
|
|
if (focusHistory.length) {
|
|
const item = focusHistory.pop()
|
|
item.focus()
|
|
}
|
|
if (postOverlays.length) {
|
|
popOverlay()
|
|
} else {
|
|
window.location.reload()
|
|
}
|
|
})
|
|
})
|
|
|
|
function pushOverlay(overlay) {
|
|
postOverlays.push(overlay)
|
|
focusHistory.push(document.activeElement)
|
|
document.body.style.overflowY = "hidden"
|
|
}
|
|
|
|
function popOverlay() {
|
|
const top = postOverlays.pop()
|
|
if (top) {
|
|
top.pop()
|
|
}
|
|
if (postOverlays.length === 0) document.body.style.overflowY = "auto"
|
|
}
|
|
|
|
class PostOverlay extends ElemJS {
|
|
constructor(identifier) {
|
|
super("div")
|
|
this.identifier = identifier
|
|
this.loaded = false
|
|
this.available = true
|
|
this.keyboardListeners = []
|
|
|
|
this.class("post-overlay")
|
|
this.event("click", event => {
|
|
if (event.target === event.currentTarget) history.back()
|
|
})
|
|
setTimeout(() => {
|
|
if (!this.loaded) {
|
|
this.class("loading")
|
|
this.child(
|
|
new ElemJS("div").class("loading-inner").text("Loading...")
|
|
)
|
|
}
|
|
}, 0)
|
|
}
|
|
|
|
addKeyboardCallback(callback) {
|
|
if (this.available) {
|
|
this.keyboardListeners.push(callback)
|
|
document.addEventListener("keydown", callback)
|
|
}
|
|
}
|
|
|
|
setContent(html) {
|
|
this.html(html)
|
|
this.loaded = true
|
|
this.removeClass("loading")
|
|
setTimeout(() => {
|
|
this.element.focus()
|
|
})
|
|
}
|
|
|
|
showError() {
|
|
this.loaded = true
|
|
this.class("loading")
|
|
this.clearChildren()
|
|
this.child(
|
|
new ElemJS("div").class("loading-inner").text("Request failed.")
|
|
)
|
|
}
|
|
|
|
pop() {
|
|
this.element.remove()
|
|
this.available = false
|
|
while (this.keyboardListeners.length) {
|
|
document.removeEventListener("keydown", this.keyboardListeners.shift())
|
|
}
|
|
}
|
|
}
|
|
|
|
const timelineElement = q("#timeline")
|
|
if (timelineElement) {
|
|
timelineElement.addEventListener("click", event => {
|
|
/** @type {HTMLElement[]} */
|
|
//@ts-ignore
|
|
const path = event.composedPath()
|
|
const postLink = path.find(element => element.classList && element.classList.contains("sized-link") && element.hasAttribute("data-shortcode"))
|
|
if (postLink) {
|
|
event.preventDefault()
|
|
const shortcode = postLink.getAttribute("data-shortcode")
|
|
loadPostOverlay(shortcode, "push")
|
|
}
|
|
})
|
|
}
|
|
|
|
function fetchShortcodeFragment(shortcode) {
|
|
if (shortcodeDataMap.has(shortcode)) return Promise.resolve(shortcodeDataMap.get(shortcode))
|
|
else return fetch(`/fragment/post/${shortcode}`).then(res => res.json())
|
|
}
|
|
|
|
function loadPostOverlay(shortcode, stateChangeType) {
|
|
const overlay = new PostOverlay(shortcode)
|
|
document.body.appendChild(overlay.element)
|
|
pushOverlay(overlay)
|
|
if (stateChangeType === "push") {
|
|
history.pushState({view: "post_overlay", shortcode: shortcode}, "", `/p/${shortcode}`)
|
|
} else if (stateChangeType === "replace") {
|
|
history.replaceState({view: "post_overlay", shortcode: shortcode}, "", `/p/${shortcode}`)
|
|
} else if (stateChangeType !== "none") {
|
|
throw new Error("Unknown stateChangeType: "+stateChangeType)
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const fetcher = fetchShortcodeFragment(shortcode)
|
|
fetcher.then(root => {
|
|
if (root.redirectTo) {
|
|
window.location.assign(root.redirectTo)
|
|
return
|
|
}
|
|
|
|
if (root.quota) {
|
|
quota.set(root.quota)
|
|
delete root.quota // don't apply the old quota next time the post is opened
|
|
}
|
|
|
|
shortcodeDataMap.set(shortcode, root)
|
|
if (overlay.available) {
|
|
const {title, html} = root
|
|
overlay.setContent(html)
|
|
if (overlay.available) {
|
|
document.title = title
|
|
}
|
|
while (postOverlays.length >= 2) postOverlays.shift().pop()
|
|
const entry = timeline.entries.get(shortcode)
|
|
let canInteractWithNavigation = true
|
|
overlay.element.querySelectorAll(".navigate-posts").forEach(button => {
|
|
button.addEventListener("click", async event => {
|
|
/** @type {HTMLButtonElement} */
|
|
// @ts-ignore
|
|
const button = event.currentTarget
|
|
if (button.hasAttribute("data-next")) {
|
|
navigate("next")
|
|
} else if (button.hasAttribute("data-previous")) {
|
|
navigate("previous")
|
|
}
|
|
})
|
|
})
|
|
overlay.addKeyboardCallback(event => {
|
|
if (event.key === "ArrowRight") navigate("next")
|
|
else if (event.key === "ArrowLeft") navigate("previous")
|
|
})
|
|
async function navigate(direction) {
|
|
if (canInteractWithNavigation) {
|
|
/** @type {HTMLButtonElement} */
|
|
//@ts-ignore
|
|
if (direction === "next") {
|
|
canInteractWithNavigation = false
|
|
if (entry.isLastEntry()) await timeline.fetch()
|
|
if (!overlay.available) return
|
|
var futureShortcode = entry.getNextShortcode()
|
|
} else { // "previous"
|
|
if (entry.isFirstEntry()) return
|
|
canInteractWithNavigation = false
|
|
var futureShortcode = entry.getPreviousShortcode()
|
|
}
|
|
if (futureShortcode) {
|
|
await loadPostOverlay(futureShortcode, "replace")
|
|
const newOverlay = postOverlays.slice(-1)[0]
|
|
if (newOverlay === overlay) { // was cancelled
|
|
canInteractWithNavigation = true
|
|
}
|
|
} else {
|
|
canInteractWithNavigation = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resolve()
|
|
})
|
|
fetcher.catch(error => {
|
|
console.error(error)
|
|
overlay.showError()
|
|
reject(error)
|
|
})
|
|
})
|
|
}
|