diff --git a/README.md b/README.md index cf31e04..673f7b2 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ See also: [Invidious, a front-end for YouTube.](https://github.com/omarroth/invi ## Features - [x] View profile and timeline +- [x] Infinite scroll - [x] User memory cache - [ ] Image disk cache - [ ] View post - [ ] Homepage - [ ] Optimised for mobile - [ ] Favicon -- [ ] Infinite scroll - [ ] Settings (e.g. data saving) - [ ] Galleries - [ ] List view diff --git a/src/site/api/routes.js b/src/site/api/routes.js index 6be3c81..a83f4f3 100644 --- a/src/site/api/routes.js +++ b/src/site/api/routes.js @@ -10,5 +10,20 @@ module.exports = [ await user.timeline.fetchUpToPage(page - 1) } return render(200, "pug/user.pug", {url, user}) + }}, + {route: "/fragment/user/([\\w.]+)/(\\d+)", methods: ["GET"], code: async ({url, fill}) => { + const user = await fetchUser(fill[0]) + const pageNumber = +fill[1] + const pageIndex = pageNumber - 1 + await user.timeline.fetchUpToPage(pageIndex) + if (user.timeline.pages[pageIndex]) { + return render(200, "pug/fragments/timeline_page.pug", {page: user.timeline.pages[pageIndex], pageIndex, user, url}) + } else { + return { + statusCode: 400, + contentType: "text/html", + content: "That page does not exist" + } + } }} ] diff --git a/src/site/html/static/js/pagination.js b/src/site/html/static/js/pagination.js new file mode 100644 index 0000000..494687d --- /dev/null +++ b/src/site/html/static/js/pagination.js @@ -0,0 +1,56 @@ +import {ElemJS, q} from "./elemjs/elemjs.js" + +class FreezeWidth extends ElemJS { + freeze(text) { + this.element.style.width = window.getComputedStyle(this.element).width + this.oldText = this.element.textContent + this.text(text) + } + + unfreeze() { + this.element.style.width = "" + this.text(this.oldText) + } +} + +class NextPage extends FreezeWidth { + constructor(container) { + super(container) + this.clicked = false + this.nextPageNumber = +this.element.getAttribute("data-page") + this.attribute("href", "javascript:void(0)") + this.event("click", event => this.onClick(event)) + + this.observer = new IntersectionObserver(entries => this.onIntersect(entries), {rootMargin: "-20px", threshold: 1}) + this.observer.observe(this.element) + } + + onClick(event) { + if (event) event.preventDefault() + if (this.clicked) return + this.clicked = true + this.freeze("Loading...") + + 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) + addNextPageControl() + }) + } + + /** + * @param {IntersectionObserverEntry[]} entries + */ + onIntersect(entries) { + if (entries.some(entry => entry.isIntersecting && entry.intersectionRatio == 1)) this.onClick() + } +} + +function addNextPageControl() { + const nextPage = q("#next-page") + if (nextPage) new NextPage(nextPage) +} + +addNextPageControl() diff --git a/src/site/pug/fragments/timeline_page.pug b/src/site/pug/fragments/timeline_page.pug new file mode 100644 index 0000000..758a46a --- /dev/null +++ b/src/site/pug/fragments/timeline_page.pug @@ -0,0 +1,6 @@ +include ../includes/timeline_page.pug +include ../includes/next_page_button.pug + ++timeline_page(page, pageIndex) + ++next_page_button(user, url) diff --git a/src/site/pug/includes/next_page_button.pug b/src/site/pug/includes/next_page_button.pug new file mode 100644 index 0000000..9981c00 --- /dev/null +++ b/src/site/pug/includes/next_page_button.pug @@ -0,0 +1,10 @@ +mixin next_page_button(user, url) + if user.timeline.hasNextPage() + div.next-page-container#next-page-container + - + const nu = new URL(url) + nu.searchParams.set("page", user.timeline.pages.length+1) + a(href=`${nu.search}#page-${user.timeline.pages.length+1}` data-page=(user.timeline.pages.length+1) data-username=(user.data.username))#next-page.next-page Next page + else + div.page-number.no-more-pages + span.number No more posts. diff --git a/src/site/pug/user.pug b/src/site/pug/user.pug index d592421..d14eeb2 100644 --- a/src/site/pug/user.pug +++ b/src/site/pug/user.pug @@ -1,4 +1,5 @@ include includes/timeline_page.pug +include includes/next_page_button.pug - const numberFormat = new Intl.NumberFormat().format @@ -10,6 +11,7 @@ html title = `${user.data.full_name} (@${user.data.username}) | Bibliogram` link(rel="stylesheet" type="text/css" href="/static/css/main.css") + script(src="/static/js/pagination.js" type="module") body .main-divider header.profile-overview @@ -34,16 +36,8 @@ html | | followed by - main.timeline + main#timeline.timeline each page, pageIndex in user.timeline.pages +timeline_page(page, pageIndex) - if user.timeline.hasNextPage() - div.next-page-container - - - const nu = new URL(url) - nu.searchParams.set("page", user.timeline.pages.length+1) - a(href=`${nu.search}#page-${user.timeline.pages.length+1}` data-cursor=user.timeline.page_info.end_cursor)#next-page.next-page Next page - else - div.page-number.no-more-pages - span.number No more posts. + +next_page_button(user, url) diff --git a/src/site/sass/main.sass b/src/site/sass/main.sass index 7234e18..0802794 100644 --- a/src/site/sass/main.sass +++ b/src/site/sass/main.sass @@ -114,6 +114,7 @@ body .next-page @include link-button font-size: 18px + text-align: center .timeline-inner display: flex