diff --git a/src/site/html/static/img/arrow-circled.svg b/src/site/html/static/img/arrow-circled.svg
new file mode 100644
index 0000000..a54833b
--- /dev/null
+++ b/src/site/html/static/img/arrow-circled.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/site/html/static/js/pagination.js b/src/site/html/static/js/pagination.js
index d942170..ce3caf8 100644
--- a/src/site/html/static/js/pagination.js
+++ b/src/site/html/static/js/pagination.js
@@ -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()
}
}
diff --git a/src/site/html/static/js/post_overlay.js b/src/site/html/static/js/post_overlay.js
index 99299e8..a8f994f 100644
--- a/src/site/html/static/js/post_overlay.js
+++ b/src/site/html/static/js/post_overlay.js
@@ -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)
+ })
})
}
diff --git a/src/site/html/static/js/post_series.js b/src/site/html/static/js/post_series.js
new file mode 100644
index 0000000..a7048c5
--- /dev/null
+++ b/src/site/html/static/js/post_series.js
@@ -0,0 +1,56 @@
+import {controller} from "./pagination.js"
+
+class Timeline {
+ constructor() {
+ this.shortcodes = []
+ /** @type {Map} */
+ 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}
diff --git a/src/site/pug/fragments/post.pug b/src/site/pug/fragments/post.pug
index 2c2799c..2fbb353 100644
--- a/src/site/pug/fragments/post.pug
+++ b/src/site/pug/fragments/post.pug
@@ -2,4 +2,4 @@
include ../includes/post.pug
-+post(post)
++post(post, true)
diff --git a/src/site/pug/includes/post.pug b/src/site/pug/includes/post.pug
index 371ef97..d564c67 100644
--- a/src/site/pug/includes/post.pug
+++ b/src/site/pug/includes/post.pug
@@ -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())
diff --git a/src/site/pug/post.pug b/src/site/pug/post.pug
index b106e93..3169d6d 100644
--- a/src/site/pug/post.pug
+++ b/src/site/pug/post.pug
@@ -13,4 +13,4 @@ html
script(type="module" src="/static/js/post_overlay.js")
body.post-page
main
- +post(post)
+ +post(post, false)
diff --git a/src/site/sass/main.sass b/src/site/sass/main.sass
index 29c0b43..c0a7b7c 100644
--- a/src/site/sass/main.sass
+++ b/src/site/sass/main.sass
@@ -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