This commit is contained in:
2026-09-11 19:00:58 +02:00
parent 8d6edfef5e
commit d72eae7509
14 changed files with 681 additions and 373 deletions
+61 -28
View File
@@ -47,6 +47,12 @@
if (!tagsContainer) return;
const inner = tagsContainer.querySelector(".tags-inner") || tagsContainer;
const canManage = !!(
document.querySelector('#tags[data-can-manage="true"]') ||
(window.f0ckSession && (window.f0ckSession.is_admin || window.f0ckSession.is_moderator)) ||
(window.f0ckSession && window.f0ckSession.user && document.querySelector('#a_username[data-username]')?.dataset?.username?.toLowerCase() === window.f0ckSession.user.toLowerCase())
);
// Only remove existing dynamically generated tags
[...inner.querySelectorAll(".badge")].forEach(tag => {
// Don't remove the one containing the add/toggle buttons, and don't remove the autocomplete input itself
@@ -55,24 +61,47 @@
}
});
_tags.reverse().forEach(tag => {
const a = document.createElement("a");
a.href = `/tag/${tag.normalized}`;
a.style = "color: inherit !important";
a.textContent = tag.tag;
// Deduplicate: ensure only at most ONE rating tag exists in the tags list
const ratingTags = _tags.filter(t => ['sfw', 'nsfw', 'nsfl'].includes(t.normalized));
const lastRatingTag = ratingTags.length ? ratingTags[ratingTags.length - 1] : null;
const cleanTags = _tags.filter(t => !['sfw', 'nsfw', 'nsfl'].includes(t.normalized) || t === lastRatingTag);
const tagsCopy = [...cleanTags];
tagsCopy.reverse().forEach(tag => {
const isRating = ['sfw', 'nsfw', 'nsfl'].includes(tag.normalized);
let contentEl;
if (isRating) {
contentEl = document.createElement("span");
contentEl.className = "rating-label";
contentEl.textContent = tag.tag;
} else {
contentEl = document.createElement("a");
contentEl.href = `/tag/${tag.normalized}`;
contentEl.style = "color: inherit !important";
contentEl.textContent = tag.tag;
}
const span = document.createElement("span");
span.classList.add("badge");
if (highlightTag && (tag.tag === highlightTag || tag.normalized === highlightTag)) {
span.classList.add('new-tag-glow');
}
span.setAttribute('tooltip', tag.display_name || tag.user);
tag.badge.split(" ").forEach(b => span.classList.add(b));
span.insertAdjacentElement("beforeend", a);
if (isRating) {
span.classList.add('rating-tag', `is-${tag.normalized}`);
span.dataset.rating = tag.normalized;
if (canManage) {
span.classList.add('can-cycle');
}
} else if (tag.display_name || tag.user) {
span.setAttribute('tooltip', tag.display_name || tag.user);
}
if (window.f0ckSession && (window.f0ckSession.is_admin || window.f0ckSession.is_moderator)) {
span.insertAdjacentElement("beforeend", contentEl);
if (window.f0ckSession && (window.f0ckSession.is_admin || window.f0ckSession.is_moderator) && !isRating) {
const space = document.createTextNode('\u00A0'); //  
span.appendChild(space);
@@ -83,9 +112,29 @@
span.insertAdjacentElement("beforeend", del);
}
inner.insertAdjacentElement("afterbegin", span);
inner.insertAdjacentElement("afterbegin", span);
});
const hasRating = !!lastRatingTag;
if (!hasRating) {
const untaggedSpan = document.createElement("span");
untaggedSpan.className = `badge badge-untagged rating-tag is-untagged${canManage ? ' can-cycle' : ''}`;
untaggedSpan.dataset.rating = 'untagged';
const lbl = document.createElement("span");
lbl.className = "rating-label";
lbl.textContent = "untagged";
untaggedSpan.appendChild(lbl);
inner.insertAdjacentElement("afterbegin", untaggedSpan);
}
// Safeguard: remove any extra rating tags in DOM
const allRatingEls = inner.querySelectorAll('.rating-tag, .badge-success, .badge-danger, .badge-nsfl, .badge-untagged, [data-rating]');
if (allRatingEls.length > 1) {
for (let i = 1; i < allRatingEls.length; i++) {
allRatingEls[i].remove();
}
}
// Handle show more/less toggle visibility and count
const allBadges = [...inner.querySelectorAll(".badge")];
const realTags = allBadges.filter(b => !b.querySelector('#a_addtag') && !b.querySelector('#a_toggle') && !b.classList.contains('tag-ac-wrapper'));
@@ -139,25 +188,9 @@
const toggleEvent = async (e) => {
if (e) e.preventDefault();
const ctx = getContext();
if (!ctx) return;
const { postid } = ctx;
const res = await (await fetch('/api/v2/tags/' + encodeURIComponent(postid) + '/toggle', {
method: 'PUT',
headers: { "X-CSRF-Token": window.f0ckSession?.csrf_token }
})).json();
renderTags(res.tags);
const isNsfw = res.tags.some(t => t.id == 2);
const isUntagged = res.tags.length === 0;
const toggleBtn = document.querySelector('button#a_toggle');
if (toggleBtn) {
toggleBtn.classList.toggle('is-nsfw', isNsfw && !isUntagged);
toggleBtn.classList.toggle('is-sfw', !isNsfw && !isUntagged);
toggleBtn.classList.toggle('is-untagged', isUntagged);
const labels = { true: 'NSFW', false: 'SFW' };
toggleBtn.textContent = isUntagged ? '?' : (isNsfw ? 'NSFW' : 'SFW');
const ratingEl = document.querySelector('.rating-tag.can-cycle, button#a_toggle');
if (window.cycleRating) {
window.cycleRating(ratingEl);
}
};