diff --git a/public/s/js/comments.js b/public/s/js/comments.js
index e72aa56..50cd00e 100644
--- a/public/s/js/comments.js
+++ b/public/s/js/comments.js
@@ -1668,7 +1668,14 @@ class CommentSystem {
}
);
- // Build regex for allowed media hosters (video/audio)
+ // Abyss label replacement
+ md = md.replace(
+ /]*href="(?:https?:\/\/[^\/]+)?\/abyss(?:#|\/)(\d+)"[^>]*>([\s\S]*?)<\/a>/gi,
+ (match, abyssId) => {
+ return ` /abyss/${abyssId}`;
+ }
+ );
+
const mediaHosts = [escapedSiteHost];
if (window.f0ckAllowedImages && Array.isArray(window.f0ckAllowedImages)) {
window.f0ckAllowedImages.forEach(h => {
diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js
index c3e26f6..6d1c971 100644
--- a/public/s/js/f0ckm.js
+++ b/public/s/js/f0ckm.js
@@ -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]))
);
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;
if (isItem) {
diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js
index cdfcc01..dd3cd91 100644
--- a/public/s/js/scroller.js
+++ b/public/s/js/scroller.js
@@ -229,28 +229,32 @@
return ago(fmt(y === 1 ? i.ta_year : i.ta_years, y, 'year'));
}
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();
if (/^\d+$/.test(raw)) return raw;
if (/^[a-z0-9]+\/\d+$/.test(raw)) return raw;
return '';
}
- let lastPushedHash = location.hash;
+ let lastPushedUrl = location.pathname + location.hash;
function pushHash(id) {
if (!id) return;
- const newHash = '#' + id;
- if (newHash === lastPushedHash) return;
- lastPushedHash = newHash;
- history.pushState({ scrollerId: id }, '', '/abyss' + newHash);
+ const newUrl = '/abyss/' + id;
+ if (newUrl === lastPushedUrl) return;
+ lastPushedUrl = newUrl;
+ history.pushState({ scrollerId: id }, '', newUrl);
updateCacheActiveId(id);
}
// Handle back/forward within abyss — scroll to the target slide
window.addEventListener('popstate', (e) => {
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;
- lastPushedHash = '#' + id;
+ lastPushedUrl = '/abyss/' + id;
const slide = feed.querySelector(`.scroll-slide[data-id="${id}"], .scroll-slide[data-local-id="${id}"]`);
if (slide) {
slide.scrollIntoView({ behavior: 'smooth', block: 'start' });
diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js
index 8b01c46..f775d7b 100644
--- a/public/s/js/sidebar-activity.js
+++ b/public/s/js/sidebar-activity.js
@@ -40,6 +40,7 @@
const ytOembedCache = new Map(); // videoId -> meta object
const ytOembedPending = new Map(); // videoId -> Promise
+
const fetchSidebarYoutubeTitles = async (container) => {
const links = container.querySelectorAll('.sidebar-video-link[data-yt-id]');
if (links.length === 0) return;
@@ -290,6 +291,14 @@
}
);
+ // Abyss label replacement
+ md = md.replace(
+ /]*href="(?:https?:\/\/[^\/]+)?\/abyss(?:#|\/)(\d+)"[^>]*>([\s\S]*?)<\/a>/gi,
+ (match, abyssId) => {
+ return ``;
+ }
+ );
+
// Build regex for allowed media hosters (video/audio)
const escapedSiteHost = window.location.host.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const mediaHosts = [escapedSiteHost];
diff --git a/src/inc/routes/scroller.mjs b/src/inc/routes/scroller.mjs
index 1ffef6b..d473933 100644
--- a/src/inc/routes/scroller.mjs
+++ b/src/inc/routes/scroller.mjs
@@ -4,22 +4,56 @@ import lib from "../lib.mjs";
export default (router, tpl) => {
// Serve the scroller page
- router.get(/^\/abyss\/?$/, async (req, res) => {
+ router.get(/^\/abyss(?:\/(?[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.private_society && !req.session) {
return res.reply({ code: 502, body: '502 Bad Gateway' });
}
+
+ 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({
body: tpl.render('scroller', {
tmp: null,
session: req.session ? { ...req.session } : false,
enable_nsfl: !!cfg.enable_nsfl,
enable_swf: !!cfg.websrv.enable_swf,
- page_meta: {
- title: 'doomscroll',
- description: 'Scroll through content endlessly',
- url: `https://${cfg.main.url.domain}/abyss`
- }
+ page_meta
}, req)
});
});