abyss internal link shortening, removal of # for ids and external preview

This commit is contained in:
2026-05-17 14:16:13 +02:00
parent 832f97970e
commit 82574466ee
5 changed files with 70 additions and 16 deletions

View File

@@ -1668,7 +1668,14 @@ class CommentSystem {
} }
); );
// Build regex for allowed media hosters (video/audio) // Abyss label replacement
md = md.replace(
/<a\s[^>]*href="(?:https?:\/\/[^\/]+)?\/abyss(?:#|\/)(\d+)"[^>]*>([\s\S]*?)<\/a>/gi,
(match, abyssId) => {
return `<a href="/abyss/${abyssId}" class="abyss-link" data-abyss-id="${abyssId}"><i class="fa-solid fa-dice-d6"></i> /abyss/${abyssId}</a>`;
}
);
const mediaHosts = [escapedSiteHost]; const mediaHosts = [escapedSiteHost];
if (window.f0ckAllowedImages && Array.isArray(window.f0ckAllowedImages)) { if (window.f0ckAllowedImages && Array.isArray(window.f0ckAllowedImages)) {
window.f0ckAllowedImages.forEach(h => { window.f0ckAllowedImages.forEach(h => {

View File

@@ -1678,7 +1678,7 @@ window.cancelAnimFrame = (function () {
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1])) (parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
); );
const isMessages = !!pathname.match(/^\/messages(\/|$)/); const isMessages = !!pathname.match(/^\/messages(\/|$)/);
const isAbyss = !!pathname.match(/^\/abyss(\/?$|\?|#)/) || pathname === '/abyss'; const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isAbyss; const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isAbyss;
if (isItem) { if (isItem) {

View File

@@ -229,28 +229,32 @@
return ago(fmt(y === 1 ? i.ta_year : i.ta_years, y, 'year')); return ago(fmt(y === 1 ? i.ta_year : i.ta_years, y, 'year'));
} }
function hashId() { function hashId() {
// Strip the leading '#' and allow numeric IDs or board/postid format // Check path first /abyss/1234
const pathMatch = location.pathname.match(/\/abyss\/(\d+)$/);
if (pathMatch) return pathMatch[1];
// Fallback to hash
const raw = location.hash.replace(/^#/, '').trim(); const raw = location.hash.replace(/^#/, '').trim();
if (/^\d+$/.test(raw)) return raw; if (/^\d+$/.test(raw)) return raw;
if (/^[a-z0-9]+\/\d+$/.test(raw)) return raw; if (/^[a-z0-9]+\/\d+$/.test(raw)) return raw;
return ''; return '';
} }
let lastPushedHash = location.hash; let lastPushedUrl = location.pathname + location.hash;
function pushHash(id) { function pushHash(id) {
if (!id) return; if (!id) return;
const newHash = '#' + id; const newUrl = '/abyss/' + id;
if (newHash === lastPushedHash) return; if (newUrl === lastPushedUrl) return;
lastPushedHash = newHash; lastPushedUrl = newUrl;
history.pushState({ scrollerId: id }, '', '/abyss' + newHash); history.pushState({ scrollerId: id }, '', newUrl);
updateCacheActiveId(id); updateCacheActiveId(id);
} }
// Handle back/forward within abyss — scroll to the target slide // Handle back/forward within abyss — scroll to the target slide
window.addEventListener('popstate', (e) => { window.addEventListener('popstate', (e) => {
if (!document.body.classList.contains('scroller-active')) return; if (!document.body.classList.contains('scroller-active')) return;
const id = e.state?.scrollerId || location.hash.replace('#', ''); const id = e.state?.scrollerId || hashId();
if (!id) return; if (!id) return;
lastPushedHash = '#' + id; lastPushedUrl = '/abyss/' + id;
const slide = feed.querySelector(`.scroll-slide[data-id="${id}"], .scroll-slide[data-local-id="${id}"]`); const slide = feed.querySelector(`.scroll-slide[data-id="${id}"], .scroll-slide[data-local-id="${id}"]`);
if (slide) { if (slide) {
slide.scrollIntoView({ behavior: 'smooth', block: 'start' }); slide.scrollIntoView({ behavior: 'smooth', block: 'start' });

View File

@@ -40,6 +40,7 @@
const ytOembedCache = new Map(); // videoId -> meta object const ytOembedCache = new Map(); // videoId -> meta object
const ytOembedPending = new Map(); // videoId -> Promise const ytOembedPending = new Map(); // videoId -> Promise
const fetchSidebarYoutubeTitles = async (container) => { const fetchSidebarYoutubeTitles = async (container) => {
const links = container.querySelectorAll('.sidebar-video-link[data-yt-id]'); const links = container.querySelectorAll('.sidebar-video-link[data-yt-id]');
if (links.length === 0) return; if (links.length === 0) return;
@@ -290,6 +291,14 @@
} }
); );
// Abyss label replacement
md = md.replace(
/<a\s[^>]*href="(?:https?:\/\/[^\/]+)?\/abyss(?:#|\/)(\d+)"[^>]*>([\s\S]*?)<\/a>/gi,
(match, abyssId) => {
return `<a href="/abyss/${abyssId}" class="sidebar-abyss-link" data-abyss-id="${abyssId}"><i class="fa-solid fa-dice-d6"></i> /abyss/${abyssId}</a>`;
}
);
// Build regex for allowed media hosters (video/audio) // Build regex for allowed media hosters (video/audio)
const escapedSiteHost = window.location.host.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const escapedSiteHost = window.location.host.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const mediaHosts = [escapedSiteHost]; const mediaHosts = [escapedSiteHost];

View File

@@ -4,22 +4,56 @@ import lib from "../lib.mjs";
export default (router, tpl) => { export default (router, tpl) => {
// Serve the scroller page // Serve the scroller page
router.get(/^\/abyss\/?$/, async (req, res) => { router.get(/^\/abyss(?:\/(?<id>[0-9]+))?\/?$/, async (req, res) => {
if (cfg.websrv.abyss_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) }); if (cfg.websrv.abyss_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
if (cfg.websrv.private_society && !req.session) { if (cfg.websrv.private_society && !req.session) {
return res.reply({ code: 502, body: '<html><body>502 Bad Gateway</body></html>' }); return res.reply({ code: 502, body: '<html><body>502 Bad Gateway</body></html>' });
} }
const id = req.params?.id;
let page_meta = {
title: 'doomscroll',
description: 'Scroll through content endlessly',
url: `https://${cfg.main.url.domain}/abyss`
};
if (id) {
try {
const items = await db`
select i.*, uo.display_name
from "items" i
left join users u on u.id = i.author
left join user_options uo on uo.user_id = u.id
where i.id = ${+id} and i.active = true
limit 1
`;
if (items.length > 0) {
const item = items[0];
// Fetch tags to check for NSFW/NSFL
const tags = await db`
select tag_id from tags_assign where item_id = ${+id}
`;
const tagIds = tags.map(t => t.tag_id);
const isBlurred = tagIds.includes(2) || tagIds.includes(cfg.nsfl_tag_id || 3);
page_meta.title = `${id}`;
page_meta.description = cfg.websrv.description || "The webs dumpster";
page_meta.url = `https://${cfg.main.url.domain}/abyss/${id}`;
page_meta.image = `https://${cfg.main.url.domain}/t/${id}${isBlurred ? '_blur' : ''}.webp`;
}
} catch (e) {
console.error('[SCROLLER] Failed to fetch meta for ID:', id, e);
}
}
return res.reply({ return res.reply({
body: tpl.render('scroller', { body: tpl.render('scroller', {
tmp: null, tmp: null,
session: req.session ? { ...req.session } : false, session: req.session ? { ...req.session } : false,
enable_nsfl: !!cfg.enable_nsfl, enable_nsfl: !!cfg.enable_nsfl,
enable_swf: !!cfg.websrv.enable_swf, enable_swf: !!cfg.websrv.enable_swf,
page_meta: { page_meta
title: 'doomscroll',
description: 'Scroll through content endlessly',
url: `https://${cfg.main.url.domain}/abyss`
}
}, req) }, req)
}); });
}); });