@@ -8541,7 +8567,7 @@ class NotificationSystem {

-
${label} #${n.item_id}
+
${label} #${itemKey}
${(window.f0ckI18n && window.f0ckI18n.notif_click_reason) || 'Click to see reason'}
${new Date(n.created_at).toLocaleString()}
@@ -8600,10 +8626,10 @@ class NotificationSystem {
if (n.type === 'comment_reply') typeText = (window.f0ckI18n && window.f0ckI18n.notif_replied) || 'replied to you';
else if (n.type === 'subscription') typeText = (window.f0ckI18n && window.f0ckI18n.notif_subscribed) || 'commented in a thread you follow';
else if (n.type === 'mention') typeText = (window.f0ckI18n && window.f0ckI18n.notif_mentioned) || 'highlighted you';
- else if (n.type === 'upload_comment') typeText = `${(window.f0ckI18n && window.f0ckI18n.notif_commented) || 'commented on your upload'} #${n.item_id}`;
+ else if (n.type === 'upload_comment') typeText = `${(window.f0ckI18n && window.f0ckI18n.notif_commented) || 'commented on your upload'} #${itemKey}`;
const cid = n.comment_id || n.reference_id;
- const link = `/${n.item_id}#c${cid}`;
+ const link = `/${itemKey}#c${cid}`;
const thumb = n.item_id ? `

` : '';
return `
@@ -8768,10 +8794,11 @@ class NotificationSystem {
// Build DOM nodes imperatively (avoids Sanitizer stripping inline border-color)
const fragment = document.createDocumentFragment();
data.favs.forEach(fav => {
- if (fav.hide_fav_badge) {
+ const isSelf = window.f0ckSession && window.f0ckSession.user && (window.f0ckSession.user === fav.user);
+ if (fav.hide_fav_badge && !isSelf) {
const a = document.createElement('a');
a.className = 'ghost-fav';
- a.setAttribute('tooltip', 'Ghost Fav');
+ a.setAttribute('tooltip', '?');
a.setAttribute('flow', 'up');
a.style.cursor = 'default';
@@ -8884,12 +8911,14 @@ class NotificationSystem {
if (!isOwnUpload) return; // Silently drop as before for other users' uploads
+ const itemKey = data.slug || data.id;
+
// Don't add duplicates
- if (grid.querySelector(`a[href$="/${data.id}"]`)) return;
+ if (grid.querySelector(`a[href$="/${itemKey}"]`) || grid.querySelector(`a[href$="/${data.id}"]`)) return;
// Determine the link prefix from existing items
const firstThumb = grid.querySelector('a.thumb, a.lazy-thumb');
- const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/\d+$/, '') : '/';
+ const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/(\d+|[a-zA-Z0-9_-]{11})$/, '') : '/';
// Build filter-reason label
const i18n = window.f0ckI18n || {};
@@ -8904,7 +8933,7 @@ class NotificationSystem {
const mode = data.tag_id ? (data.tag_id === 1 ? 'sfw' : (data.tag_id === 2 ? 'nsfw' : (data.tag_id == nsflId ? 'nsfl' : 'null'))) : 'null';
const ghost = document.createElement('a');
- ghost.href = `${linkBase}${data.id}`;
+ ghost.href = `${linkBase}${itemKey}`;
ghost.className = 'thumb lazy-thumb filtered-upload-ghost loaded';
ghost.dataset.file = data.dest;
ghost.dataset.mime = data.mime;
@@ -8935,19 +8964,21 @@ class NotificationSystem {
}
}
+ const itemKey = data.slug || data.id;
+
// Don't add duplicates
- if (grid.querySelector(`a[href$="/${data.id}"]`)) return;
+ if (grid.querySelector(`a[href$="/${itemKey}"]`) || grid.querySelector(`a[href$="/${data.id}"]`)) return;
// Determine the link prefix from existing items
const firstThumb = grid.querySelector('a.thumb, a.lazy-thumb');
- const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/\d+$/, '') : '/';
+ const linkBase = firstThumb ? firstThumb.getAttribute('href').replace(/(\d+|[a-zA-Z0-9_-]{11})$/, '') : '/';
// Respect mode filter
const nsflId = window.f0ckSession?.nsfl_tag_id;
const mode = data.tag_id ? (data.tag_id === 1 ? 'sfw' : (data.tag_id === 2 ? 'nsfw' : (data.tag_id == nsflId ? 'nsfl' : 'null'))) : 'null';
const thumb = document.createElement('a');
- thumb.href = `${linkBase}${data.id}`;
+ thumb.href = `${linkBase}${itemKey}`;
thumb.className = 'thumb lazy-thumb';
thumb.dataset.file = data.dest;
thumb.dataset.mime = data.mime;
@@ -9129,10 +9160,11 @@ class NotificationSystem {
`;
}
+ const itemKey = c.item_slug || c.slug || c.item_id;
itemPreview = `
`;
}
@@ -11302,6 +11334,83 @@ document.addEventListener('click', (e) => {
return;
}
+ const visCancelBtn = e.target.closest('#visibility-modal-cancel');
+ if (visCancelBtn || e.target.id === 'visibility-modal') {
+ const modal = document.getElementById('visibility-modal');
+ if (modal) {
+ modal.style.display = 'none';
+ document.body.classList.remove('modal-open');
+ }
+ return;
+ }
+
+ const visSaveBtn = e.target.closest('#visibility-modal-save');
+ if (visSaveBtn) {
+ e.preventDefault();
+ const modal = document.getElementById('visibility-modal');
+ const inputId = document.getElementById('visibility-item-id');
+ const selectedRadio = modal ? modal.querySelector('input[name="visibility"]:checked') : null;
+ if (!inputId || !selectedRadio) return;
+
+ const id = inputId.value;
+ const nextVis = parseInt(selectedRadio.value, 10);
+
+ visSaveBtn.disabled = true;
+ const origText = visSaveBtn.textContent;
+ visSaveBtn.textContent = 'Saving...';
+
+ fetch('/api/v2/item/visibility', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-Token': window.f0ckSession?.csrf_token
+ },
+ body: new URLSearchParams({ postid: id, id: id, visibility: nextVis })
+ })
+ .then(r => r.json())
+ .then(data => {
+ visSaveBtn.disabled = false;
+ visSaveBtn.textContent = origText;
+ if (data.success) {
+ const visVal = parseInt(data.visibility, 10);
+ const titles = ['Public', 'Unlisted', 'Private'];
+ const icons = ['fa-globe', 'fa-link', 'fa-lock'];
+ const colors = ['var(--color-success, #00C851)', 'var(--color-warning, #ffbb33)', 'var(--color-danger, #ff4444)'];
+
+ const visBtn = document.getElementById('a_visibility');
+ if (visBtn) {
+ visBtn.dataset.visibility = visVal;
+ visBtn.setAttribute('title', `Visibility: ${titles[visVal]} (Click to change)`);
+ }
+
+ const infoVisBtn = document.getElementById('info-visibility-edit-btn');
+ if (infoVisBtn) {
+ infoVisBtn.dataset.visibility = visVal;
+ }
+
+ const infoVisLabel = document.getElementById('info-visibility-label');
+ if (infoVisLabel) {
+ infoVisLabel.innerHTML = `
${titles[visVal]}`;
+ }
+
+ if (modal) {
+ modal.style.display = 'none';
+ document.body.classList.remove('modal-open');
+ }
+ if (window.flashMessage) window.flashMessage(`VISIBILITY SET TO ${titles[visVal].toUpperCase()}`);
+ } else {
+ if (window.flashError) window.flashError(data.msg || 'Failed to update visibility');
+ }
+ })
+ .catch(err => {
+ visSaveBtn.disabled = false;
+ visSaveBtn.textContent = origText;
+ console.error('Error changing visibility:', err);
+ if (window.flashError) window.flashError('Network error');
+ });
+ return;
+ }
+
// Title save button
const saveBtn = e.target.closest('#info-title-save');
if (saveBtn) {
diff --git a/public/s/js/settings.js b/public/s/js/settings.js
index d765818..df57235 100644
--- a/public/s/js/settings.js
+++ b/public/s/js/settings.js
@@ -1181,6 +1181,35 @@
});
}
+ const defaultUploadVisSelect = document.getElementById('default_upload_visibility_select');
+ if (defaultUploadVisSelect) {
+ defaultUploadVisSelect.addEventListener('change', async function() {
+ const vis = parseInt(defaultUploadVisSelect.value, 10);
+ try {
+ const res = await fetch('/api/v2/settings/default_upload_visibility', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-CSRF-Token': window.f0ckSession ? window.f0ckSession.csrf_token : ''
+ },
+ body: new URLSearchParams({ default_upload_visibility: vis })
+ });
+ const data = await res.json();
+ if (data.success) {
+ showStatus('Default upload visibility updated!', 'success');
+ if (window.f0ckSession) {
+ window.f0ckSession.default_upload_visibility = vis;
+ }
+ } else {
+ alert(data.msg || 'Error saving preference');
+ }
+ } catch (err) {
+ console.error('Update Default Upload Visibility error:', err);
+ alert('Connection error');
+ }
+ });
+ }
+
// New Dual Column Layout Toggle
const layoutToggle = document.getElementById('use_new_layout_toggle');
if (layoutToggle) {
diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js
index 964ebf6..a90f2e7 100644
--- a/public/s/js/sidebar-activity.js
+++ b/public/s/js/sidebar-activity.js
@@ -479,8 +479,9 @@
};
const renderActivityItem = (c) => {
+ const itemKey = c.item_slug || c.slug || c.item_id;
const rawContent = c.content || c.body || '';
- let displayContent = renderCommentContent(rawContent, c.id, c.item_id);
+ let displayContent = renderCommentContent(rawContent, c.id, itemKey);
displayContent = window.f0cklib?.processMentions ? window.f0cklib.processMentions(displayContent) : displayContent;
@@ -497,7 +498,7 @@
}
const attachmentsHtml = renderCommentAttachments(c.files, rawContent);
- const pollHtml = renderSidebarPoll(c.poll, c.id, c.item_id);
+ const pollHtml = renderSidebarPoll(c.poll, c.id, itemKey);
// Build avatar URL — same priority as the rest of the app
let avatarSrc = '/a/default.png';
@@ -542,8 +543,8 @@
itemPreview = `
`;
}
diff --git a/public/s/js/upload.js b/public/s/js/upload.js
index 5983812..c0cdbf2 100644
--- a/public/s/js/upload.js
+++ b/public/s/js/upload.js
@@ -908,7 +908,7 @@ window.initUploadForm = (selector) => {
}
lines.forEach(url => {
if (!selectedFiles.some(item => item.type === 'url' && item.url === url)) {
- selectedFiles.push({ type: 'url', url, rating: '', tags: [], comment: '', title: '', is_oc: false });
+ selectedFiles.push({ type: 'url', url, rating: '', visibility: '', tags: [], comment: '', title: '', is_oc: false });
}
});
urlInput.value = '';
@@ -931,7 +931,7 @@ window.initUploadForm = (selector) => {
const val = urlInput.value.trim();
if (!val || !/^https?:\/\//i.test(val)) return;
if (!selectedFiles.some(item => item.type === 'url' && item.url === val)) {
- selectedFiles.push({ type: 'url', url: val, rating: '', tags: [], comment: '', title: '', is_oc: false });
+ selectedFiles.push({ type: 'url', url: val, rating: '', visibility: '', tags: [], comment: '', title: '', is_oc: false });
}
urlInput.value = '';
if (urlBadge) urlBadge.style.display = 'none';
@@ -1177,7 +1177,7 @@ window.initUploadForm = (selector) => {
if (!selectedFiles.some(f => (f.file || f).name === file.name && (f.file || f).size === file.size)) {
if (isShitpost) {
- selectedFiles.push({ type: 'file', file: file, rating: '', tags: [], comment: '', title: '', is_oc: false });
+ selectedFiles.push({ type: 'file', file: file, rating: '', visibility: '', tags: [], comment: '', title: '', is_oc: false });
} else {
selectedFiles.push(file); // Legacy single file mode uses raw File
}
@@ -1490,6 +1490,7 @@ window.initUploadForm = (selector) => {
infoRow.className = 'file-meta-row-small';
let ratingSwitch = '';
+ let visibilitySwitch = '';
let tagsUI = '';
let ocUI = '';
let commentUI = '';
@@ -1517,6 +1518,26 @@ window.initUploadForm = (selector) => {
`;
+ const globalVis = form.querySelector('input[name="visibility"]:checked')?.value || '0';
+ const visValue = (item.visibility !== undefined && item.visibility !== '') ? item.visibility : globalVis;
+ item.visibility = visValue;
+ visibilitySwitch = `
+