This commit is contained in:
2026-09-11 19:40:30 +02:00
parent d72eae7509
commit 52ed9fd7bd
7 changed files with 67 additions and 30 deletions
+5 -4
View File
@@ -757,10 +757,11 @@ class CommentSystem {
// Render skeleton (Result: Layout visible immediately)
// Skip when preserveScroll=true (tab re-focus refresh): the user already sees comments,
// so wiping the DOM causes the browser to lose the #c anchor element and auto-scroll to top.
// Only render skeleton for logged-in users to avoid flashing "Login to comment" before comments/permissions load.
if (!scrollToId && !preserveScroll && this.user) {
this.render([], this.user, initialIsSubscribed);
this.restoreState(state);
if (!scrollToId && !preserveScroll) {
if (this.user || !this.container.querySelector('.login-placeholder, .comment-input, .lock-notice')) {
this.render([], this.user, initialIsSubscribed);
this.restoreState(state);
}
}
try {
+46 -22
View File
@@ -18,23 +18,35 @@ const computeBaseMode = (mode, ratings, session) => {
const effMode = Number(mode ?? 0);
const ratingsArr = (Array.isArray(ratings) && ratings.length > 0) ? ratings : null;
// For guest sessions, sanitize ratingsArr to only allow permitted ratings
let safeRatingsArr = ratingsArr;
if (!session && ratingsArr) {
const allowedRatings = ['sfw'];
if (cfg.websrv.public_nsfw) allowedRatings.push('nsfw');
if (cfg.websrv.public_untagged) allowedRatings.push('untagged');
safeRatingsArr = ratingsArr.filter(r => allowedRatings.includes(r));
if (safeRatingsArr.length === 0) {
return "1 = 0";
}
}
let baseMode;
if (effMode === 2) {
if (ratingsArr && ratingsArr.includes('untagged') && ratingsArr.length > 1) {
baseMode = lib.getMultiRatingMode(ratingsArr);
if (safeRatingsArr && safeRatingsArr.includes('untagged') && safeRatingsArr.length > 1) {
baseMode = lib.getMultiRatingMode(safeRatingsArr);
} else {
baseMode = lib.getMode(2);
}
} else if (effMode === 3) {
baseMode = (ratingsArr && ratingsArr.length > 1) ? lib.getMultiRatingMode(ratingsArr) : lib.getMode(3);
baseMode = (safeRatingsArr && safeRatingsArr.length > 1) ? lib.getMultiRatingMode(safeRatingsArr) : lib.getMode(3);
} else {
const multiRatingSQL = ratingsArr ? lib.getMultiRatingMode(ratingsArr) : null;
const multiRatingSQL = safeRatingsArr ? lib.getMultiRatingMode(safeRatingsArr) : null;
baseMode = multiRatingSQL ?? lib.getMode(effMode);
}
if (!session) {
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
if ((effMode === 0 || effMode === 3 || mode === undefined || mode === null) && (!ratingsArr || ratingsArr.length <= 1)) {
if ((effMode === 0 || effMode === 3 || mode === undefined || mode === null) && (!safeRatingsArr || safeRatingsArr.length <= 1)) {
if (cfg.websrv.public_nsfw) {
baseMode = cfg.websrv.public_untagged
? `(items.id in (select item_id from tags_assign where tag_id in (1, 2)) or not exists (select 1 from tags_assign where item_id = items.id and tag_id in (1, 2, ${nsflId})))`
@@ -44,10 +56,16 @@ const computeBaseMode = (mode, ratings, session) => {
? `(items.id in (select item_id from tags_assign where tag_id = 1) or not exists (select 1 from tags_assign where item_id = items.id and tag_id in (1, 2, ${nsflId})))`
: "items.id in (select item_id from tags_assign where tag_id = 1)";
}
} else if (!cfg.websrv.public_untagged) {
if (effMode === 2 || (ratingsArr && ratingsArr.length === 1 && ratingsArr[0] === 'untagged')) {
} else if (effMode === 2) {
if (!cfg.websrv.public_untagged) {
baseMode = "1 = 0";
}
} else if (effMode === 1) {
if (!cfg.websrv.public_nsfw) {
baseMode = "1 = 0";
}
} else if (effMode === 4) {
baseMode = "1 = 0";
}
}
return baseMode;
@@ -824,12 +842,7 @@ export default {
const effMode = Number(mode ?? 0);
const nsflId = parseInt(cfg.nsfl_tag_id, 10) || 3;
const itemModeQuery = session
? computeBaseMode(mode, ratings, session)
: (cfg.websrv.public_nsfw
? `not exists (select 1 from tags_assign where item_id = items.id and tag_id = ${nsflId})`
: `not exists (select 1 from tags_assign where item_id = items.id and tag_id in (2, ${nsflId}))`
);
const itemModeQuery = computeBaseMode(mode, ratings, session);
let tagFilter = db``;
let titleFilter = db``;
@@ -969,12 +982,22 @@ export default {
last_viewed = now()
`.catch(e => console.error('Failed to track view:', e));
}
// Guest global filter check for public items (unlisted items requested by direct link/slug bypass guest rating blocks)
if (!session && getGlobalfilter() && (actitem.visibility || 0) === 0) {
const filteredItem = await db`
select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter())}) limit 1
`;
if (filteredItem.length > 0) {
// Guest rating restriction check for public items (unlisted items requested by direct link/slug bypass guest rating blocks)
if (!session && (actitem.visibility || 0) === 0) {
let blocked = false;
if (getGlobalfilter()) {
const filteredItem = await db`
select 1 from tags_assign where item_id = ${itemid} and (${db.unsafe(getGlobalfilter())}) limit 1
`;
if (filteredItem.length > 0) blocked = true;
}
if (!blocked && !cfg.websrv.public_untagged) {
const ratingTag = await db`
select 1 from tags_assign where item_id = ${itemid} and tag_id in (1, 2, ${nsflId}) limit 1
`;
if (ratingTag.length === 0) blocked = true;
}
if (blocked) {
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
return {
success: false,
@@ -1157,12 +1180,13 @@ export default {
const isNsfw = tags.some(t => t.id == 2);
const isSfw = tags.some(t => t.id == 1);
const isTagged = tags.length > 0;
const isUntagged = !isSfw && !isNsfw && !isNsfl;
// Guest rating restriction check for public uploads (visibility === 0)
// Direct links to public unrated/untagged posts are allowed for guests.
if (!session && !isOwnerOrAdmin && (actitem.visibility || 0) === 0) {
let guestBlocked = false;
if (isNsfw && !cfg.websrv.public_nsfw) guestBlocked = true;
else if (isNsfl) guestBlocked = true;
else if (isUntagged && !cfg.websrv.public_untagged) guestBlocked = true;
if (guestBlocked) {
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
@@ -1187,10 +1211,10 @@ export default {
const userMode = Number(mode ?? 0);
if (userMode !== 3) {
let modeBlocked = false;
if (userMode === 0 && (isNsfw || isNsfl)) modeBlocked = true; // SFW mode, item is NSFW or NSFL
if (userMode === 0 && (isNsfw || isNsfl || (!session && isUntagged && !cfg.websrv.public_untagged))) modeBlocked = true; // SFW mode, item is NSFW or NSFL
else if (userMode === 1 && !isNsfw) modeBlocked = true; // NSFW mode, item is not NSFW
else if (userMode === 4 && (!cfg.enable_nsfl || !isNsfl)) modeBlocked = true; // NSFL mode, item is not NSFL
else if (userMode === 2 && isTagged) modeBlocked = true; // Untagged mode, item has tags
else if (userMode === 2 && (isTagged || (!session && !cfg.websrv.public_untagged))) modeBlocked = true; // Untagged mode, item has tags
if (modeBlocked && !isOwnerOrAdmin && actitem.visibility !== 1) {
const hallSlug = hall && typeof hall === 'object' ? hall.slug : hall;
+1 -1
View File
@@ -1016,7 +1016,7 @@ export default (router, tpl) => {
const multiRatingSQL = (ratingsArr && ratingsArr.length > 0) ? lib.getMultiRatingMode(ratingsArr) : null;
// Build mode SQL — replace items.id alias with i.id used in the activity query
const modequery = (multiRatingSQL ?? lib.getMode(mode)).replace(/items\.id/g, 'i.id');
const modequery = f0cklib.computeBaseMode(mode, ratingsArr, req.session).replace(/items\.id/g, 'i.id');
const globalfilterTags = cfg.websrv.public_nsfw ? (cfg.nsfp || []).filter(id => id !== 2) : (cfg.nsfp || []);
const globalfilter = globalfilterTags.length ? globalfilterTags.map(n => `tag_id = ${n}`).join(' or ') : null;
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
+2
View File
@@ -1463,6 +1463,8 @@ process.on('uncaughtException', err => {
default_font: cfg.websrv.default_font || "",
site_description: cfg.websrv.description || "The webs dumpster",
enable_nsfl: !!cfg.enable_nsfl,
public_nsfw: !!cfg.websrv.public_nsfw,
public_untagged: !!cfg.websrv.public_untagged,
onara: !!(cfg.onara !== undefined ? cfg.onara : cfg.websrv?.onara),
is_onara_item: false,
enable_private_uploads: cfg.enable_private_uploads !== false,
+5 -1
View File
@@ -227,10 +227,14 @@
@if(session) data-user="{{ session.user }}" @endif
@if(session && (session.admin || session.is_moderator)) data-is-admin="true" @endif
@if(item.is_comments_locked) data-is-locked="true" @endif>
@if(session && !item.is_comments_locked)
@if(item.is_comments_locked && !is_mod_or_admin)
<div class="lock-notice">🔒 Comments are disabled on this thread.</div>
@elseif(session)
<div class="comment-input main-input">
<textarea disabled></textarea>
</div>
@else
<div class="login-placeholder"><a href="/login" class="login-trigger-btn">Login</a> to comment</div>
@endif
</div>
@endif
+5 -1
View File
@@ -19,10 +19,14 @@
@if(session) data-user="{{ session.user }}" @endif
@if(session && is_mod_or_admin) data-is-admin="true" @endif
@if(item.is_comments_locked) data-is-locked="true" @endif>
@if(session && !item.is_comments_locked)
@if(item.is_comments_locked && !is_mod_or_admin)
<div class="lock-notice">🔒 Comments are disabled on this thread.</div>
@elseif(session)
<div class="comment-input main-input">
<textarea disabled></textarea>
</div>
@else
<div class="login-placeholder"><a href="/login" class="login-trigger-btn">Login</a> to comment</div>
@endif
</div>
@endif
+3 -1
View File
@@ -139,7 +139,7 @@
@endif
@if(private_society && !session)
<script>
window.f0ckSession = { logged_in: false, onara: @if(onara) true @else false @endif, enable_item_slugs: @if(enable_item_slugs) true @else false @endif, hide_comments_from_public: @if(hide_comments_from_public) true @else false @endif, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif, allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif, hide_sidebar_default: @if(hide_sidebar_default) true @else false @endif };
window.f0ckSession = { logged_in: false, onara: @if(onara) true @else false @endif, enable_item_slugs: @if(enable_item_slugs) true @else false @endif, hide_comments_from_public: @if(hide_comments_from_public) true @else false @endif, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif, allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif, hide_sidebar_default: @if(hide_sidebar_default) true @else false @endif, public_untagged: @if(public_untagged) true @else false @endif, public_nsfw: @if(public_nsfw) true @else false @endif };
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
(() => {
const loginModal = document.getElementById('login-modal');
@@ -378,6 +378,8 @@
<script>
window.f0ckSession = {
onara: @if(onara) true @else false @endif,
public_untagged: @if(public_untagged) true @else false @endif,
public_nsfw: @if(public_nsfw) true @else false @endif,
enable_item_slugs: @if(enable_item_slugs) true @else false @endif,
strict_mode: @if(session && session.strict_mode) true @else false @endif,
logged_in: @if(session) true @else false @endif,