added AJAX loading for videos

This commit is contained in:
eins
2026-01-23 13:18:55 +00:00
parent a4f9c48e13
commit b72fcaa426
10 changed files with 1112 additions and 773 deletions

View File

@@ -1,66 +1,259 @@
window.requestAnimFrame = (function(){
window.requestAnimFrame = (function () {
return window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| function(callback) { window.setTimeout(callback, 1000 / 60);};
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| function (callback) { window.setTimeout(callback, 1000 / 60); };
})();
(() => {
let video;
if(elem = document.querySelector("#my-video")) {
if (elem = document.querySelector("#my-video")) {
video = new v0ck(elem);
document.addEventListener("keydown", e => {
if(e.key === " " && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
if (e.key === " " && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
video[video.paused ? 'play' : 'pause']();
document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden');
}
});
document.getElementById('togglebg').addEventListener('click', function (e) {
e.preventDefault();
background = !background;
localStorage.setItem('background', background.toString());
var canvas = document.getElementById('bg');
if (background) {
const toggleBg = document.getElementById('togglebg');
if (toggleBg) {
toggleBg.addEventListener('click', function (e) {
e.preventDefault();
background = !background;
localStorage.setItem('background', background.toString());
var canvas = document.getElementById('bg');
if (background) {
canvas.classList.add('fader-in');
canvas.classList.remove('fader-out');
} else {
} else {
canvas.classList.add('fader-out');
canvas.classList.remove('fader-in');
}
animationLoop();
});
if(elem !== null) {
if(localStorage.getItem('background') == undefined) {
localStorage.setItem('background', 'true');
}
animationLoop();
});
}
var background = localStorage.getItem('background') === 'true';
var canvas = document.getElementById('bg');
var context = canvas.getContext('2d');
var cw = canvas.width = canvas.clientWidth | 0;
var ch = canvas.height = canvas.clientHeight | 0;
function animationLoop() {
if(video.paused || video.ended || !background)
return;
context.drawImage(video, 0, 0, cw, ch);
window.requestAnimFrame(animationLoop);
}
if (elem !== null) {
if (localStorage.getItem('background') == undefined) {
localStorage.setItem('background', 'true');
}
elem.addEventListener('play', animationLoop);
}
var background = localStorage.getItem('background') === 'true';
var canvas = document.getElementById('bg');
if (canvas) {
var context = canvas.getContext('2d');
var cw = canvas.width = canvas.clientWidth | 0;
var ch = canvas.height = canvas.clientHeight | 0;
function animationLoop() {
if (video.paused || video.ended || !background)
return;
context.drawImage(video, 0, 0, cw, ch);
window.requestAnimFrame(animationLoop);
}
elem.addEventListener('play', animationLoop);
}
}
}
let tt = false;
const stimeout = 500;
const changePage = (e, pbwork = true) => {
pbwork && document.querySelector("nav.navbar").classList.add("pbwork");
!tt && (tt = setTimeout(() => e.click(), stimeout));
const setupMedia = () => {
if (elem = document.querySelector("#my-video")) {
video = new v0ck(elem);
}
};
const loadItemAjax = async (url, inheritContext = true) => {
console.log("loadItemAjax called with:", url, "inheritContext:", inheritContext);
// Show loading indicator
const navbar = document.querySelector("nav.navbar");
if (navbar) navbar.classList.add("pbwork");
// Extract item ID from URL. Regex now handles query params, hashes, and trailing slashes.
const match = url.match(/\/(\d+)(?:\/|#|\?|$)/);
if (!match) {
console.warn("loadItemAjax: No ID match found in URL", url);
// fallback for weird/external links
window.location.href = url;
return;
}
const itemid = match[1];
// <context-preservation>
// Extract context from Target URL first
let tag = null, user = null;
const tagMatch = url.match(/\/tag\/([^/]+)/);
if (tagMatch) tag = decodeURIComponent(tagMatch[1]);
const userMatch = url.match(/\/user\/([^/]+)/);
if (userMatch) user = decodeURIComponent(userMatch[1]); // Note: "user" variable shadowed? No, block scope or different name? let user defined above.
// If missing and inheritContext is true, check Window Location
if (inheritContext) {
if (!tag) {
const wTagMatch = window.location.href.match(/\/tag\/([^/]+)/);
if (wTagMatch) tag = decodeURIComponent(wTagMatch[1]);
}
if (!user) {
const wUserMatch = window.location.href.match(/\/user\/([^/]+)/);
if (wUserMatch) user = decodeURIComponent(wUserMatch[1]);
}
}
// </context-preservation>
try {
// Construct AJAX URL
let ajaxUrl = `/ajax/item/${itemid}`;
const params = new URLSearchParams();
if (tag) params.append('tag', tag);
if (user) params.append('user', user);
if ([...params].length > 0) {
ajaxUrl += '?' + params.toString();
}
console.log("Fetching:", ajaxUrl);
const response = await fetch(ajaxUrl);
if (!response.ok) throw new Error(`Network response was not ok: ${response.status}`);
const rawText = await response.text();
let html, paginationHtml;
try {
// Optimistically try to parse as JSON first
const data = JSON.parse(rawText);
if (data && typeof data.html === 'string') {
html = data.html;
paginationHtml = data.pagination;
} else {
html = rawText;
}
} catch (e) {
// If JSON parse fails, assume it's HTML text
html = rawText;
}
let container = document.querySelector('.container');
if (!container && document.querySelector('.index-container')) {
// Transition from Index to Item View
const main = document.getElementById('main');
main.innerHTML = '<div class="container"></div>';
container = main.querySelector('.container');
} else if (container) {
// Already in Item View, clear usage
const oldContent = container.querySelector('.content');
const oldMetadata = container.querySelector('.metadata');
const oldHeader = container.querySelector('._204863');
if (oldHeader) oldHeader.remove();
if (oldContent) oldContent.remove();
if (oldMetadata) oldMetadata.remove();
}
container.insertAdjacentHTML('beforeend', html);
// Update pagination if present
if (paginationHtml) {
const pagWrappers = document.querySelectorAll('.pagination-wrapper');
pagWrappers.forEach(el => el.innerHTML = paginationHtml);
}
// Construct proper History URL (Context Aware)
// If we inherited context, we should reflect it in the URL
let pushUrl = `/${itemid}`;
// Logic from ajax.mjs context reconstruction:
if (user) pushUrl = `/user/${user}/${itemid}`; // User takes precedence usually? Or strictly mutually exclusive in UI
else if (tag) pushUrl = `/tag/${tag}/${itemid}`;
// We overwrite proper URL even if the link clicked was "naked"
history.pushState({}, '', pushUrl);
setupMedia();
// Try to extract ID from response if possible or just use itemid
document.title = `f0bm - ${itemid}`;
if (navbar) navbar.classList.remove("pbwork");
console.log("AJAX load complete");
} catch (err) {
console.error("AJAX load failed:", err);
}
};
const changePage = (e, pbwork = true) => {
if (e.tagName === 'A') {
e.preventDefault();
loadItemAjax(e.href);
} else {
pbwork && document.querySelector("nav.navbar").classList.add("pbwork");
!tt && (tt = setTimeout(() => e.click(), stimeout));
}
};
// Intercept clicks
document.addEventListener('click', (e) => {
// Check for thumbnail links on index page
const thumbnail = e.target.closest('.posts > a');
if (thumbnail && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
e.preventDefault();
// Thumbnails inherit context (e.g. from Tag Index)
loadItemAjax(thumbnail.href, true);
return;
}
const link = e.target.closest('#next, #prev, #random, .id-link, .nav-next, .nav-prev');
if (link && link.href && link.hostname === window.location.hostname && !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey) {
// Special check for random
if (link.id === 'random') {
e.preventDefault();
const nav = document.querySelector("nav.navbar");
if (nav) nav.classList.add("pbwork");
// Extract current context from window location
let randomUrl = '/api/v2/random';
const params = new URLSearchParams();
const wTagMatch = window.location.href.match(/\/tag\/([^/]+)/);
if (wTagMatch) params.append('tag', decodeURIComponent(wTagMatch[1]));
const wUserMatch = window.location.href.match(/\/user\/([^/]+)/);
if (wUserMatch) params.append('user', decodeURIComponent(wUserMatch[1]));
if ([...params].length > 0) {
randomUrl += '?' + params.toString();
}
fetch(randomUrl)
.then(r => r.json())
.then(data => {
if (data.success && data.items && data.items.id) {
// Inherit context so URL matches current filter
loadItemAjax(`/${data.items.id}`, true);
} else {
window.location.href = link.href;
}
})
.catch(() => window.location.href = link.href);
return;
}
// Standard item links
e.preventDefault();
loadItemAjax(link.href, true);
}
});
window.addEventListener('popstate', (e) => {
loadItemAjax(window.location.href, true);
});
// <keybindings>
const clickOnElementBinding = selector => () => (elem = document.querySelector(selector)) ? elem.click() : null;
const keybindings = {
@@ -72,8 +265,8 @@ window.requestAnimFrame = (function(){
" ": clickOnElementBinding("#f0ck-image")
};
document.addEventListener("keydown", e => {
if(e.key in keybindings && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
if(e.shiftKey || e.ctrlKey || e.metaKey || e.altKey)
if (e.key in keybindings && e.target.tagName !== "INPUT" && e.target.tagName !== "TEXTAREA") {
if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey)
return;
e.preventDefault();
keybindings[e.key]();
@@ -84,19 +277,21 @@ window.requestAnimFrame = (function(){
// <image-responsive>
const imgSize = e => new Promise((res, _) => {
const i = new Image();
i.addEventListener('load', function() {
i.addEventListener('load', function () {
res({ width: this.width, height: this.height });
});
i.src = e.src;
});
// <wheeler>
const wheelEventListener = function(event) {
const wheelEventListener = function (event) {
if (event.target.closest('.media-object, .steuerung')) {
if (event.deltaY < 0) {
document.getElementById('next').click();
const el = document.getElementById('next');
if (el && el.href && !el.href.endsWith('#')) el.click();
} else if (event.deltaY > 0) {
document.getElementById('prev').click();
const el = document.getElementById('prev');
if (el && el.href && !el.href.endsWith('#')) el.click();
}
}
};
@@ -105,7 +300,7 @@ window.requestAnimFrame = (function(){
// </wheeler>
if(f0ckimage = document.querySelector("img#f0ck-image")) {
if (f0ckimage = document.querySelector("img#f0ck-image")) {
const f0ckimagescroll = document.querySelector("#image-scroll");
let isImageExpanded = false;
@@ -135,24 +330,32 @@ window.requestAnimFrame = (function(){
// <scroller>
let tts = 0;
const scroll_treshold = 1;
if([...document.querySelectorAll("div.posts")].length === 1) {
if ([...document.querySelectorAll("div.posts")].length === 1) {
document.addEventListener("wheel", e => {
if(Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down
if(elem = document.querySelector(".pagination > .next:not(.disabled)")) {
if(tts < scroll_treshold) {
document.querySelector("div#footbar").style.boxShadow = "inset 0px 4px 0px var(--footbar-color)";
document.querySelector("div#footbar").style.color = "var(--footbar-color)";
if (!document.querySelector('#main')) return;
if (Math.ceil(window.innerHeight + window.scrollY) >= document.querySelector('#main').offsetHeight && e.deltaY > 0) { // down
if (elem = document.querySelector(".pagination > .next:not(.disabled)")) {
if (tts < scroll_treshold) {
const foot = document.querySelector("div#footbar");
if (foot) {
foot.style.boxShadow = "inset 0px 4px 0px var(--footbar-color)";
foot.style.color = "var(--footbar-color)";
}
tts++;
}
else
changePage(elem);
}
}
else if(window.scrollY <= 0 && e.deltaY < 0) { // up
if(elem = document.querySelector(".pagination > .prev:not(.disabled)")) {
if(tts < scroll_treshold) {
document.querySelector("nav.navbar").style.boxShadow = "0px 2px 0px var(--loading-indicator-color)";
document.querySelector("nav.navbar").style.transition = ".2s ease-in-out";
else if (window.scrollY <= 0 && e.deltaY < 0) { // up
if (elem = document.querySelector(".pagination > .prev:not(.disabled)")) {
if (tts < scroll_treshold) {
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
@@ -161,19 +364,23 @@ window.requestAnimFrame = (function(){
}
else {
tts = 0;
document.querySelector("div#footbar").style.boxShadow = "unset";
document.querySelector("div#footbar").style.color = "transparent";
document.querySelector("nav.navbar").style.boxShadow = "unset";
const foot = document.querySelector("div#footbar");
if (foot) {
foot.style.boxShadow = "unset";
foot.style.color = "transparent";
}
const nav = document.querySelector("nav.navbar");
if (nav) nav.style.boxShadow = "unset";
}
});
}
const rmatch = /\/p\/(\d+?)/;
if(document.referrer.match(rmatch) && document.location.href.match(rmatch))
if(document.location.href.match(rmatch) < document.referrer.match(rmatch))
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>
const swipeRT = {
xDown: null,
@@ -198,33 +405,33 @@ window.requestAnimFrame = (function(){
}, false);
document.addEventListener('touchmove', e => {
if(!swipeRT.xDown || !swipeRT.yDown)
if (!swipeRT.xDown || !swipeRT.yDown)
return;
swipeRT.xDiff = swipeRT.xDown - e.touches[0].clientX;
swipeRT.yDiff = swipeRT.yDown - e.touches[0].clientY;
}, false);
document.addEventListener('touchend', e => {
if(swipeRT.startEl !== e.target)
if (swipeRT.startEl !== e.target)
return;
const timeDiff = Date.now() - swipeRT.timeDown;
let elem;
if(Math.abs(swipeRT.xDiff) > Math.abs(swipeRT.yDiff)) {
if(Math.abs(swipeRT.xDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) {
if(swipeRT.xDiff > 0) // left
if (Math.abs(swipeRT.xDiff) > Math.abs(swipeRT.yDiff)) {
if (Math.abs(swipeRT.xDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) {
if (swipeRT.xDiff > 0) // left
elem = document.querySelector(".pagination > .next:not(.disabled)");
else // right
elem = document.querySelector(".pagination > .prev:not(.disabled)");
}
}
else {
if(Math.abs(swipeRT.yDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) {
if(navbar = document.querySelector("nav.navbar") && document.querySelector("div.posts")) {
if(swipeRT.yDiff > 0 && (window.innerHeight + window.scrollY) >= document.body.offsetHeight) // up
if (Math.abs(swipeRT.yDiff) > swipeOpt.treshold && timeDiff < swipeOpt.timeout) {
if (navbar = document.querySelector("nav.navbar") && document.querySelector("div.posts")) {
if (swipeRT.yDiff > 0 && (window.innerHeight + window.scrollY) >= document.body.offsetHeight) // up
elem = document.querySelector(".pagination > .next:not(.disabled)");
else if(swipeRT.yDiff <= 0 && window.scrollY <= 0 && document.querySelector("div.posts")) // down
else if (swipeRT.yDiff <= 0 && window.scrollY <= 0 && document.querySelector("div.posts")) // down
elem = document.querySelector(".pagination > .prev:not(.disabled)");
}
}
@@ -234,13 +441,13 @@ window.requestAnimFrame = (function(){
swipeRT.yDown = null;
swipeRT.timeDown = null;
if(elem)
if (elem)
changePage(elem);
}, false);
// </swipe>
// <visualizer>
if(audioElement = document.querySelector("audio")) {
if (audioElement = document.querySelector("audio")) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 1920;
@@ -267,7 +474,7 @@ window.requestAnimFrame = (function(){
draw(data);
}
function draw(data) {
data = [ ...data ];
data = [...data];
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = getComputedStyle(document.body).getPropertyValue("--accent");
data.forEach((value, i) => {
@@ -285,7 +492,7 @@ window.requestAnimFrame = (function(){
// </visualizer>
// <mediakeys>
if(elem = document.querySelector("#my-video") && "mediaSession" in navigator) {
if (elem = document.querySelector("#my-video") && "mediaSession" in navigator) {
const playpauseEvent = () => {
video[video.paused ? 'play' : 'pause']();
document.querySelector('.v0ck_overlay').classList[video.paused ? 'remove' : 'add']('v0ck_hidden');
@@ -294,11 +501,11 @@ window.requestAnimFrame = (function(){
navigator.mediaSession.setActionHandler('pause', playpauseEvent);
navigator.mediaSession.setActionHandler('stop', playpauseEvent);
navigator.mediaSession.setActionHandler('previoustrack', () => {
if(link = document.querySelector(".pagination > .prev:not(.disabled)"))
if (link = document.querySelector(".pagination > .prev:not(.disabled)"))
changePage(link);
});
navigator.mediaSession.setActionHandler('nexttrack', () => {
if(link = document.querySelector(".pagination > .next:not(.disabled)"))
if (link = document.querySelector(".pagination > .next:not(.disabled)"))
changePage(link);
});
}
@@ -326,18 +533,18 @@ function onWheel(e) {
function init() {
const el = document.querySelector(targetSelector);
if (!el) return;
el.addEventListener('mouseenter', () => isMouseOver = true);
el.addEventListener('mouseleave', () => isMouseOver = false);
window.addEventListener('wheel', onWheel, { passive: false });
if (!el) return;
el.addEventListener('mouseenter', () => isMouseOver = true);
el.addEventListener('mouseleave', () => isMouseOver = false);
window.addEventListener('wheel', onWheel, { passive: false });
}
window.addEventListener('load', init);
document.getElementById('sbtForm').addEventListener('submit', (e) => {
e.preventDefault();
const input = document.getElementById('sbtInput').value.trim();
if (input) {
window.location.href = `/tag/${encodeURIComponent(input)}`;
}
});
document.getElementById('sbtForm').addEventListener('submit', (e) => {
e.preventDefault();
const input = document.getElementById('sbtInput').value.trim();
if (input) {
window.location.href = `/tag/${encodeURIComponent(input)}`;
}
});