first version of infinite scrolling
This commit is contained in:
@@ -1197,7 +1197,9 @@ body {
|
|||||||
overscroll-behavior-y: contain;
|
overscroll-behavior-y: contain;
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: /* 100%; */auto;
|
height:
|
||||||
|
/* 100%; */
|
||||||
|
auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
@@ -1318,6 +1320,13 @@ div.posts>a:hover::after {
|
|||||||
grid-template-columns: auto 1fr 0fr;
|
grid-template-columns: auto 1fr 0fr;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
border-bottom: 1px solid var(--nav-border-color);
|
border-bottom: 1px solid var(--nav-border-color);
|
||||||
|
background: var(--nav-bg);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar.scrolled {
|
||||||
|
background: #000 !important;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
@@ -1424,7 +1433,7 @@ ul.navbar-nav li.nav-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-link[data-toggle="dropdown"].ddcontent::after {
|
.nav-link[data-toggle="dropdown"].ddcontent::after {
|
||||||
content: "\00a0("attr(content) ")\00a0\25bc";
|
content: "\00a0(" attr(content) ")\00a0\25bc";
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link[data-toggle="dropdown"]:not(.ddcontent)::after {
|
.nav-link[data-toggle="dropdown"]:not(.ddcontent)::after {
|
||||||
@@ -1608,7 +1617,7 @@ span.placeholder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1325px) {
|
@media (max-width: 1325px) {
|
||||||
/* ranking page - idea */
|
/* ranking page - idea */
|
||||||
/* .ranking {
|
/* .ranking {
|
||||||
grid-template-columns: 1fr 1fr !important;
|
grid-template-columns: 1fr 1fr !important;
|
||||||
} */
|
} */
|
||||||
@@ -1783,8 +1792,9 @@ span.placeholder {
|
|||||||
.index-container {
|
.index-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
/* background-color: var(--navigation-links-bg);
|
/* background-color: var(--navigation-links-bg);
|
||||||
*/}
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 361px) {
|
@media (min-width: 361px) {
|
||||||
.embed-responsive-image {
|
.embed-responsive-image {
|
||||||
@@ -2779,7 +2789,9 @@ ul.navbar-nav-guests li.nav-item {
|
|||||||
margin-right: .5rem;
|
margin-right: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-expand-lg .navbar-nav-guests .nav-link, .pagination > a, .pagination > span {
|
.navbar-expand-lg .navbar-nav-guests .nav-link,
|
||||||
|
.pagination>a,
|
||||||
|
.pagination>span {
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
}
|
}
|
||||||
@@ -2792,7 +2804,7 @@ ul.navbar-nav-guests li.nav-item {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.navbar-nav-guests li.nav-item {
|
ul.navbar-nav-guests li.nav-item {
|
||||||
margin-right: unset;
|
margin-right: unset;
|
||||||
}
|
}
|
||||||
@@ -2812,16 +2824,20 @@ ul.navbar-nav-guests li.nav-item {
|
|||||||
|
|
||||||
/* Pagination Responsiveness */
|
/* Pagination Responsiveness */
|
||||||
@media (max-width: 799px) {
|
@media (max-width: 799px) {
|
||||||
.navbar-expand-lg .navbar-nav-guests .nav-link, .pagination > a, .pagination > span {
|
|
||||||
|
.navbar-expand-lg .navbar-nav-guests .nav-link,
|
||||||
|
.pagination>a,
|
||||||
|
.pagination>span {
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination > a, .pagination > span {
|
.pagination>a,
|
||||||
|
.pagination>span {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
}
|
}
|
||||||
@@ -2830,15 +2846,19 @@ ul.navbar-nav-guests li.nav-item {
|
|||||||
/* fadeIn effect */
|
/* fadeIn effect */
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img#f0ck-image, div.imageDoor, div.posts a, video {
|
img#f0ck-image,
|
||||||
animation: 1s ease-out 0s 1 fadeInFX;
|
div.imageDoor,
|
||||||
|
div.posts a,
|
||||||
|
video {
|
||||||
|
animation: 1s ease-out 0s 1 fadeInFX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* f0ckgle */
|
/* f0ckgle */
|
||||||
@@ -2867,7 +2887,7 @@ img#f0ck-image, div.imageDoor, div.posts a, video {
|
|||||||
background: var(--nav-bg);
|
background: var(--nav-bg);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile_head_avatar {
|
.profile_head_avatar {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -2892,11 +2912,13 @@ img#f0ck-image, div.imageDoor, div.posts a, video {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.f0cks h5, .favs h5 {
|
.f0cks h5,
|
||||||
|
.favs h5 {
|
||||||
background: var(--dropdown-bg);
|
background: var(--dropdown-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.f0cks-header, .favs-header {
|
.f0cks-header,
|
||||||
|
.favs-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
background: var(--img-border-color);
|
background: var(--img-border-color);
|
||||||
@@ -2940,46 +2962,50 @@ button#togglebg {
|
|||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
100% {
|
|
||||||
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeOutFX {
|
@keyframes fadeOutFX {
|
||||||
0% {
|
0% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
|
||||||
|
100% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeOut {
|
@keyframes fadeOut {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
100% {
|
|
||||||
opacity: 0;
|
100% {
|
||||||
}
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fader-in {
|
.fader-in {
|
||||||
animation: fadeIn .8s steps(100) forwards;
|
animation: fadeIn .8s steps(100) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fader-out {
|
.fader-out {
|
||||||
animation: fadeOut .8s steps(100) forwards
|
animation: fadeOut .8s steps(100) forwards
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings {
|
.settings {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -3004,6 +3030,7 @@ input#s_avatar {
|
|||||||
#s_avatar:hover {
|
#s_avatar:hover {
|
||||||
background: #ffffff0f;
|
background: #ffffff0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theforceofthree {
|
.theforceofthree {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 0.4fr 1fr 0.4fr;
|
grid-template-columns: 0.4fr 1fr 0.4fr;
|
||||||
@@ -3011,4 +3038,24 @@ input#s_avatar {
|
|||||||
|
|
||||||
.upload {
|
.upload {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Infinite scroll loading indicator */
|
||||||
|
.loading-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--footbar-color);
|
||||||
|
animation: pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +68,18 @@ window.requestAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Navbar scroll effect - make background black when scrolling
|
||||||
|
const navbar = document.querySelector('.navbar');
|
||||||
|
if (navbar) {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (window.scrollY > 50) {
|
||||||
|
navbar.classList.add('scrolled');
|
||||||
|
} else {
|
||||||
|
navbar.classList.remove('scrolled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const loadItemAjax = async (url, inheritContext = true) => {
|
const loadItemAjax = async (url, inheritContext = true) => {
|
||||||
console.log("loadItemAjax called with:", url, "inheritContext:", inheritContext);
|
console.log("loadItemAjax called with:", url, "inheritContext:", inheritContext);
|
||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
@@ -335,15 +347,109 @@ window.requestAnimFrame = (function () {
|
|||||||
}
|
}
|
||||||
// </image-responsive>
|
// </image-responsive>
|
||||||
|
|
||||||
// <scroller>
|
// <infinite-scroll>
|
||||||
let tts = 0;
|
const postsContainer = document.querySelector("div.posts");
|
||||||
const scroll_treshold = 1;
|
if (postsContainer) {
|
||||||
if ([...document.querySelectorAll("div.posts")].length === 1) {
|
// Infinite scroll state
|
||||||
|
let infiniteState = {
|
||||||
|
loading: false,
|
||||||
|
hasMore: true,
|
||||||
|
currentPage: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract current page from URL
|
||||||
|
const pageMatch = window.location.pathname.match(/\/p\/(\d+)/);
|
||||||
|
if (pageMatch) infiniteState.currentPage = parseInt(pageMatch[1]);
|
||||||
|
|
||||||
|
// Extract context (tag/user/mime) from URL
|
||||||
|
const getContext = () => {
|
||||||
|
const ctx = {};
|
||||||
|
const tagMatch = window.location.pathname.match(/\/tag\/([^/]+)/);
|
||||||
|
if (tagMatch) ctx.tag = decodeURIComponent(tagMatch[1]);
|
||||||
|
const userMatch = window.location.pathname.match(/\/user\/([^/]+)/);
|
||||||
|
if (userMatch) ctx.user = decodeURIComponent(userMatch[1]);
|
||||||
|
const mimeMatch = window.location.pathname.match(/\/(image|audio|video)(?:\/|$)/);
|
||||||
|
if (mimeMatch) ctx.mime = mimeMatch[1];
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build URL path for history
|
||||||
|
const buildUrl = (page) => {
|
||||||
|
const ctx = getContext();
|
||||||
|
let path = '/';
|
||||||
|
if (ctx.tag) path += `tag/${ctx.tag}/`;
|
||||||
|
if (ctx.user) path += `user/${ctx.user}/`;
|
||||||
|
if (ctx.mime) path += `${ctx.mime}/`;
|
||||||
|
if (page > 1) path += `p/${page}`;
|
||||||
|
return path.replace(/\/$/, '') || '/';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch and append more items
|
||||||
|
const loadMoreItems = async () => {
|
||||||
|
if (infiniteState.loading || !infiniteState.hasMore) return;
|
||||||
|
|
||||||
|
infiniteState.loading = true;
|
||||||
|
const foot = document.querySelector("div#footbar");
|
||||||
|
if (foot) {
|
||||||
|
foot.innerHTML = '<span class="loading-spinner">Loading...</span>';
|
||||||
|
foot.style.color = 'var(--footbar-color)';
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPage = infiniteState.currentPage + 1;
|
||||||
|
const ctx = getContext();
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', nextPage);
|
||||||
|
if (ctx.tag) params.append('tag', ctx.tag);
|
||||||
|
if (ctx.user) params.append('user', ctx.user);
|
||||||
|
if (ctx.mime) params.append('mime', ctx.mime);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/ajax/items?${params.toString()}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success && data.html) {
|
||||||
|
// Append new thumbnails
|
||||||
|
postsContainer.insertAdjacentHTML('beforeend', data.html);
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
infiniteState.currentPage = data.currentPage;
|
||||||
|
infiniteState.hasMore = data.hasMore;
|
||||||
|
|
||||||
|
// Update URL
|
||||||
|
history.replaceState({}, '', buildUrl(infiniteState.currentPage));
|
||||||
|
|
||||||
|
// Update pagination display if exists
|
||||||
|
const paginationLinks = document.querySelectorAll('.pagination .pagination-int-item, .pagination .btn');
|
||||||
|
paginationLinks.forEach(el => {
|
||||||
|
if (el.textContent.trim() == infiniteState.currentPage) {
|
||||||
|
el.classList.add('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
infiniteState.hasMore = false;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Infinite scroll fetch error:', err);
|
||||||
|
} finally {
|
||||||
|
infiniteState.loading = false;
|
||||||
|
if (foot) {
|
||||||
|
foot.innerHTML = infiniteState.hasMore ? '▼' : '—';
|
||||||
|
foot.style.color = 'transparent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scroll detection
|
||||||
|
let tts = 0;
|
||||||
|
const scroll_treshold = 1;
|
||||||
|
|
||||||
document.addEventListener("wheel", e => {
|
document.addEventListener("wheel", e => {
|
||||||
if (!document.querySelector('#main')) return;
|
if (!document.querySelector('#main')) return;
|
||||||
|
|
||||||
if (Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down
|
if (Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) {
|
||||||
if (elem = document.querySelector(".pagination > .next:not(.disabled)")) {
|
// Scrolling down at bottom
|
||||||
|
if (infiniteState.hasMore && !infiniteState.loading) {
|
||||||
if (tts < scroll_treshold) {
|
if (tts < scroll_treshold) {
|
||||||
const foot = document.querySelector("div#footbar");
|
const foot = document.querySelector("div#footbar");
|
||||||
if (foot) {
|
if (foot) {
|
||||||
@@ -351,43 +457,25 @@ window.requestAnimFrame = (function () {
|
|||||||
foot.style.color = "var(--footbar-color)";
|
foot.style.color = "var(--footbar-color)";
|
||||||
}
|
}
|
||||||
tts++;
|
tts++;
|
||||||
|
} else {
|
||||||
|
loadMoreItems();
|
||||||
|
tts = 0;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
changePage(elem);
|
|
||||||
}
|
}
|
||||||
}
|
} else if (window.scrollY <= 0 && e.deltaY < 0) {
|
||||||
else if (window.scrollY <= 0 && e.deltaY < 0) { // up
|
// Scrolling up at top - could load previous page if needed (optional)
|
||||||
if (elem = document.querySelector(".pagination > .prev:not(.disabled)")) {
|
tts = 0;
|
||||||
if (tts < scroll_treshold) {
|
} else {
|
||||||
const nav = document.querySelector("nav.navbar");
|
|
||||||
if (nav) {
|
|
||||||
nav.style.boxShadow = "0px 2px 0px var(--loading-indicator-color)";
|
|
||||||
nav.style.transition = ".2s ease-in-out";
|
|
||||||
}
|
|
||||||
tts++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
changePage(elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tts = 0;
|
tts = 0;
|
||||||
const foot = document.querySelector("div#footbar");
|
const foot = document.querySelector("div#footbar");
|
||||||
if (foot) {
|
if (foot) {
|
||||||
foot.style.boxShadow = "unset";
|
foot.style.boxShadow = "unset";
|
||||||
foot.style.color = "transparent";
|
foot.style.color = "transparent";
|
||||||
}
|
}
|
||||||
const nav = document.querySelector("nav.navbar");
|
|
||||||
if (nav) nav.style.boxShadow = "unset";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// </infinite-scroll>
|
||||||
const rmatch = /\/p\/(\d+?)/;
|
|
||||||
if (document.referrer.match(rmatch) && document.location.href.match(rmatch))
|
|
||||||
if (document.location.href.match(rmatch) < document.referrer.match(rmatch))
|
|
||||||
document.body.scrollTop = document.body.scrollHeight;
|
|
||||||
// </scroller>
|
|
||||||
|
|
||||||
// <swipe>
|
// <swipe>
|
||||||
const swipeRT = {
|
const swipeRT = {
|
||||||
|
|||||||
@@ -64,5 +64,58 @@ export default (router, tpl) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Infinite scroll endpoint for index thumbnails
|
||||||
|
router.get(/\/ajax\/items/, async (req, res) => {
|
||||||
|
let query = {};
|
||||||
|
if (typeof req.url === 'string') {
|
||||||
|
const parsedUrl = url.parse(req.url, true);
|
||||||
|
query = parsedUrl.query;
|
||||||
|
} else {
|
||||||
|
query = req.url.qs || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = parseInt(query.page) || 1;
|
||||||
|
|
||||||
|
const data = await f0cklib.getf0cks({
|
||||||
|
page: page,
|
||||||
|
tag: query.tag || null,
|
||||||
|
user: query.user || null,
|
||||||
|
mime: query.mime || null,
|
||||||
|
mode: req.session.mode,
|
||||||
|
session: !!req.session,
|
||||||
|
fav: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
return res.reply({
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
html: '',
|
||||||
|
hasMore: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render just the thumbnail items
|
||||||
|
const itemsHtml = tpl.render('snippets/items-grid', {
|
||||||
|
items: data.items,
|
||||||
|
link: data.link
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasMore = data.pagination.next !== null;
|
||||||
|
|
||||||
|
return res.reply({
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
html: itemsHtml,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextPage: data.pagination.next,
|
||||||
|
currentPage: data.pagination.page
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
};
|
};
|
||||||
|
|||||||
7
views/snippets/items-grid.html
Normal file
7
views/snippets/items-grid.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@each(items as item)
|
||||||
|
<a href="{{ link.main }}{{ item.id }}" data-mime="{{ item.mime }}"
|
||||||
|
data-mode="{{ item.tag_id ? ['','sfw','nsfw'][item.tag_id] : 'null' }}"
|
||||||
|
style="background-image: url('/t/{{ item.id }}.webp')">
|
||||||
|
<p></p>
|
||||||
|
</a>
|
||||||
|
@endeach
|
||||||
Reference in New Issue
Block a user