mirror of
https://git.sr.ht/~cadence/bibliogram
synced 2024-11-22 16:17:29 +00:00
parent
becd2c2ec7
commit
d32a12a134
1
src/site/html/static/img/arrow-circled.svg
Normal file
1
src/site/html/static/img/arrow-circled.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 6.35 6.35"><g transform="translate(0 -290.65)" paint-order="fill markers stroke"><circle cx="3.175" cy="293.825" r="3.175" fill="#505050"/><path d="M2.706 292.277l1.51 1.548-1.51 1.548" fill="none" stroke="#ddd" stroke-width=".529" stroke-linecap="round"/></g></svg>
|
After Width: | Height: | Size: 342 B |
@ -18,6 +18,7 @@ const intersectionThreshold = 0
|
||||
class NextPageController {
|
||||
constructor() {
|
||||
this.instance = null
|
||||
this.activatedCallbacks = []
|
||||
}
|
||||
|
||||
add() {
|
||||
@ -27,10 +28,15 @@ class NextPageController {
|
||||
} else {
|
||||
this.instance = null
|
||||
}
|
||||
this.activatedCallbacks.forEach(c => c())
|
||||
}
|
||||
|
||||
activate() {
|
||||
if (this.instance) this.instance.activate()
|
||||
addActivatedCallback(callback) {
|
||||
this.activatedCallbacks.push(callback)
|
||||
}
|
||||
|
||||
async activate() {
|
||||
if (this.instance) await this.instance.activate()
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +76,7 @@ class NextPage extends FreezeWidth {
|
||||
this.fetching = true
|
||||
this.freeze("Loading...")
|
||||
|
||||
fetch(`/fragment/user/${this.element.getAttribute("data-username")}/${this.nextPageNumber}`).then(res => res.text()).then(text => {
|
||||
return fetch(`/fragment/user/${this.element.getAttribute("data-username")}/${this.nextPageNumber}`).then(res => res.text()).then(text => {
|
||||
q("#next-page-container").remove()
|
||||
this.observer.disconnect()
|
||||
q("#timeline").insertAdjacentHTML("beforeend", text)
|
||||
@ -81,7 +87,7 @@ class NextPage extends FreezeWidth {
|
||||
activate() {
|
||||
if (this.fetching) return
|
||||
this.class("disabled")
|
||||
this.fetch()
|
||||
return this.fetch()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {q, ElemJS} from "./elemjs/elemjs.js"
|
||||
import {timeline} from "./post_series.js"
|
||||
|
||||
/** @type {PostOverlay[]} */
|
||||
const postOverlays = []
|
||||
@ -10,23 +11,28 @@ window.addEventListener("popstate", event => {
|
||||
// console.log(event.state, postOverlays.length)
|
||||
if (event.state) {
|
||||
if (event.state.view === "post_overlay") {
|
||||
loadPostOverlay(event.state.shortcode, false)
|
||||
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")
|
||||
/*}*/
|
||||
}
|
||||
} else { // 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 (postOverlays.length) {
|
||||
popOverlay()
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 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 (postOverlays.length) {
|
||||
popOverlay()
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function pushOverlay(overlay) {
|
||||
@ -43,14 +49,17 @@ function popOverlay() {
|
||||
}
|
||||
|
||||
class PostOverlay extends ElemJS {
|
||||
constructor() {
|
||||
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()
|
||||
})
|
||||
this.loaded = false
|
||||
this.available = true
|
||||
setTimeout(() => {
|
||||
if (!this.loaded) {
|
||||
this.class("loading")
|
||||
@ -61,6 +70,13 @@ class PostOverlay extends ElemJS {
|
||||
}, 0)
|
||||
}
|
||||
|
||||
addKeyboardCallback(callback) {
|
||||
if (this.available) {
|
||||
this.keyboardListeners.push(callback)
|
||||
document.addEventListener("keydown", callback)
|
||||
}
|
||||
}
|
||||
|
||||
setContent(html) {
|
||||
this.html(html)
|
||||
this.loaded = true
|
||||
@ -79,12 +95,15 @@ class PostOverlay extends ElemJS {
|
||||
pop() {
|
||||
this.element.remove()
|
||||
this.available = false
|
||||
while (this.keyboardListeners.length) {
|
||||
document.removeEventListener("keydown", this.keyboardListeners.shift())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const timeline = q("#timeline")
|
||||
if (timeline) {
|
||||
timeline.addEventListener("click", event => {
|
||||
const timelineElement = q("#timeline")
|
||||
if (timelineElement) {
|
||||
timelineElement.addEventListener("click", event => {
|
||||
/** @type {HTMLElement[]} */
|
||||
//@ts-ignore
|
||||
const path = event.composedPath()
|
||||
@ -92,7 +111,7 @@ if (timeline) {
|
||||
if (postLink) {
|
||||
event.preventDefault()
|
||||
const shortcode = postLink.getAttribute("data-shortcode")
|
||||
loadPostOverlay(shortcode, true)
|
||||
loadPostOverlay(shortcode, "push")
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -102,24 +121,74 @@ function fetchShortcodeFragment(shortcode) {
|
||||
else return fetch(`/fragment/post/${shortcode}`).then(res => res.json())
|
||||
}
|
||||
|
||||
function loadPostOverlay(shortcode, shouldPushState) {
|
||||
const overlay = new PostOverlay()
|
||||
function loadPostOverlay(shortcode, stateChangeType) {
|
||||
const overlay = new PostOverlay(shortcode)
|
||||
document.body.appendChild(overlay.element)
|
||||
pushOverlay(overlay)
|
||||
if (shouldPushState) history.pushState({view: "post_overlay", shortcode: shortcode}, "", `/p/${shortcode}`)
|
||||
const fetcher = fetchShortcodeFragment(shortcode)
|
||||
fetcher.then(root => {
|
||||
shortcodeDataMap.set(shortcode, root)
|
||||
if (overlay.available) {
|
||||
const {title, html} = root
|
||||
overlay.setContent(html)
|
||||
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 => {
|
||||
shortcodeDataMap.set(shortcode, root)
|
||||
if (overlay.available) {
|
||||
document.title = title
|
||||
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.classList.contains("next")) {
|
||||
navigate("next")
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
await loadPostOverlay(futureShortcode, "replace")
|
||||
const newOverlay = postOverlays.slice(-1)[0]
|
||||
if (newOverlay === overlay) { // was cancelled
|
||||
canInteractWithNavigation = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
fetcher.catch(error => {
|
||||
console.error(error)
|
||||
overlay.showError()
|
||||
resolve()
|
||||
})
|
||||
fetcher.catch(error => {
|
||||
console.error(error)
|
||||
overlay.showError()
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
56
src/site/html/static/js/post_series.js
Normal file
56
src/site/html/static/js/post_series.js
Normal file
@ -0,0 +1,56 @@
|
||||
import {controller} from "./pagination.js"
|
||||
|
||||
class Timeline {
|
||||
constructor() {
|
||||
this.shortcodes = []
|
||||
/** @type {Map<string, TimelineEntry>} */
|
||||
this.entries = new Map()
|
||||
controller.addActivatedCallback(() => this.update())
|
||||
this.update()
|
||||
}
|
||||
|
||||
update() {
|
||||
this.shortcodes = []
|
||||
document.querySelectorAll("#timeline .sized-link").forEach(element => {
|
||||
const shortcode = element.getAttribute("data-shortcode")
|
||||
this.shortcodes.push(shortcode)
|
||||
this.entries.set(shortcode, new TimelineEntry(this, shortcode))
|
||||
})
|
||||
console.log(this.shortcodes)
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return controller.activate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Timeline} timeline
|
||||
* @param {string} shortcode
|
||||
*/
|
||||
class TimelineEntry {
|
||||
constructor(timeline, shortcode) {
|
||||
this.timeline = timeline
|
||||
this.shortcode = shortcode
|
||||
}
|
||||
|
||||
getNextShortcode() {
|
||||
return this.timeline.shortcodes[this.timeline.shortcodes.indexOf(this.shortcode)+1]
|
||||
}
|
||||
|
||||
getPreviousShortcode() {
|
||||
return this.timeline.shortcodes[this.timeline.shortcodes.indexOf(this.shortcode)-1]
|
||||
}
|
||||
|
||||
isFirstEntry() {
|
||||
return this.timeline.shortcodes.indexOf(this.shortcode) === 0
|
||||
}
|
||||
|
||||
isLastEntry() {
|
||||
return this.timeline.shortcodes.indexOf(this.shortcode) === this.timeline.shortcodes.length-1
|
||||
}
|
||||
}
|
||||
|
||||
const timeline = new Timeline()
|
||||
|
||||
export {timeline}
|
@ -2,4 +2,4 @@
|
||||
|
||||
include ../includes/post.pug
|
||||
|
||||
+post(post)
|
||||
+post(post, true)
|
||||
|
@ -1,11 +1,17 @@
|
||||
include ./display_structured
|
||||
|
||||
mixin post(post)
|
||||
mixin post(post, headerWithNavigation)
|
||||
.post-page-divider
|
||||
section.description-section
|
||||
header.user-header
|
||||
img(src=post.ownerPfpCacheP width=150 height=150 alt="").pfp
|
||||
a.name(href=`/u/${post.getBasicOwner().username}`)= `${post.data.owner.full_name} (@${post.getBasicOwner().username})`
|
||||
.user-header
|
||||
header.user-header-inner
|
||||
img(src=post.ownerPfpCacheP width=150 height=150 alt="").pfp
|
||||
a.name(href=`/u/${post.getBasicOwner().username}`)= `${post.data.owner.full_name} (@${post.getBasicOwner().username})`
|
||||
if headerWithNavigation
|
||||
button.navigate-posts.previous
|
||||
img(src="/static/img/arrow-circled.svg" alt="Previous post." style="transform: rotate(180deg)").icon
|
||||
button.navigate-posts.next
|
||||
img(src="/static/img/arrow-circled.svg" alt="Next post.").icon
|
||||
if post.getCaption()
|
||||
p.structured-text.description
|
||||
+display_structured(post.getStructuredCaption())
|
||||
|
@ -13,4 +13,4 @@ html
|
||||
script(type="module" src="/static/js/post_overlay.js")
|
||||
body.post-page
|
||||
main
|
||||
+post(post)
|
||||
+post(post, false)
|
||||
|
@ -248,27 +248,47 @@ body
|
||||
height: inherit
|
||||
|
||||
.user-header
|
||||
display: flex
|
||||
display: grid
|
||||
align-items: center
|
||||
grid-template-columns: auto 1fr auto
|
||||
justify-content: center
|
||||
background-color: #b3b3b3
|
||||
padding: 10px
|
||||
|
||||
.pfp
|
||||
$size: 40px
|
||||
.navigate-posts
|
||||
-webkit-appearance: none
|
||||
-moz-appearance: none
|
||||
border: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
cursor: pointer
|
||||
background: none
|
||||
|
||||
width: $size
|
||||
height: $size
|
||||
margin-right: 10px
|
||||
background-color: rgba(40, 40, 40, 0.25)
|
||||
.icon
|
||||
display: block
|
||||
|
||||
.name
|
||||
font-size: 20px
|
||||
color: black
|
||||
text-decoration: none
|
||||
.user-header-inner
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
grid-row: 1
|
||||
grid-column: 2
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
.pfp
|
||||
$size: 40px
|
||||
|
||||
width: $size
|
||||
height: $size
|
||||
margin-right: 10px
|
||||
background-color: rgba(40, 40, 40, 0.25)
|
||||
|
||||
.name
|
||||
font-size: 20px
|
||||
color: black
|
||||
text-decoration: none
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
.description
|
||||
margin: 12px
|
||||
@ -402,7 +422,7 @@ body
|
||||
display: flex
|
||||
|
||||
.text, .button
|
||||
appearance: none
|
||||
-webkit-appearance: none
|
||||
-moz-appearance: none
|
||||
display: flex
|
||||
padding: 9px 8px 7px
|
||||
|
Loading…
Reference in New Issue
Block a user