This commit is contained in:
2026-09-14 15:51:11 +02:00
parent ba5bc18b98
commit 22ffc3b51a
13 changed files with 3162 additions and 284 deletions
+54 -8
View File
@@ -27,6 +27,8 @@ class CommentSystem {
if (this.displayMode === 1) this.sort = 'old';
this.customEmojis = CommentSystem.emojiCache || {};
this._selfPostedCommentIds = new Set();
this._pendingSelfCommentTexts = new Set();
this.icons = {
reply: `<i class="fa-solid fa-reply"></i>`,
@@ -472,6 +474,34 @@ class CommentSystem {
}
}
_isSelfComment(data) {
if (!data) return false;
if (data.id && this._selfPostedCommentIds?.has(data.id)) return true;
if (data.body && this._pendingSelfCommentTexts?.has(data.body)) return true;
const session = window.f0ckSession || {};
const currentUserId = session.id || session.user_id;
if (currentUserId && data.user_id && parseInt(data.user_id, 10) === parseInt(currentUserId, 10)) {
return true;
}
const currentUsername = session.user || this.user;
if (currentUsername && data.username && currentUsername.toLowerCase() === data.username.toLowerCase()) {
return true;
}
if (session.is_anon && data.is_anon) {
if (data.anon_fingerprint && session.fingerprint && data.anon_fingerprint === session.fingerprint) {
return true;
}
if (data.anon_short_fingerprint && session.fingerprint && session.fingerprint.includes(data.anon_short_fingerprint)) {
return true;
}
}
return false;
}
handleLiveComment(data) {
if (!this.container || !this.itemId) return;
// 1. Check if comment belongs to this item
@@ -537,14 +567,20 @@ class CommentSystem {
// Danmaku: fire one-shot for other users' comments.
// Own comment is handled by the optimistic submit path (fire + addItem).
// The _loadDanmaku re-render will add this comment to the items rotation.
const currentUser = window.f0ckSession?.user;
if (window.danmakuInstance && data.username !== currentUser) {
const isSelf = this._isSelfComment(data);
if (window.danmakuInstance && !isSelf) {
window.danmakuInstance.fire(
data.body,
data.display_name || data.username || '?',
data.username_color || null
);
window.danmakuInstance.addItem({
id: data.id,
content: data.body,
video_time: data.video_time ?? null,
display_name: data.display_name || data.username || '?',
username_color: data.username_color || null
});
}
// Update backlinks for live comment
@@ -3448,6 +3484,7 @@ class CommentSystem {
} else {
this.isMainSubmitting = true;
}
this._pendingSelfCommentTexts.add(text);
let retryCount = 0;
const maxRetries = 20; // Allow several minutes of retrying during restart
@@ -3534,7 +3571,7 @@ class CommentSystem {
// For 4xx errors, we stop and show the error to user (likely validation or auth)
const json = await res.json().catch(() => ({}));
alert('Error: ' + (json.message || `Status ${res.status}`));
this._finishSubmit(submitBtn, originalBtnHtml, parentId);
this._finishSubmit(submitBtn, originalBtnHtml, parentId, text);
return;
}
@@ -3692,6 +3729,11 @@ class CommentSystem {
poll: null
};
if (json.comment?.id) {
this._selfPostedCommentIds.add(json.comment.id);
}
this._pendingSelfCommentTexts.delete(text);
// Danmaku: fire immediately (one-shot) + add to future rotation
if (window.danmakuInstance) {
window.danmakuInstance.fire(
@@ -3700,6 +3742,7 @@ class CommentSystem {
session.username_color || null
);
window.danmakuInstance.addItem({
id: newComment.id,
content: text,
video_time: newComment.video_time ?? null,
display_name: session.display_name || currentUsername || '?',
@@ -3810,10 +3853,10 @@ class CommentSystem {
}
this._silentSync();
this._finishSubmit(submitBtn, originalBtnHtml, parentId);
this._finishSubmit(submitBtn, originalBtnHtml, parentId, text);
} else {
alert('Error: ' + json.message);
this._finishSubmit(submitBtn, originalBtnHtml, parentId);
this._finishSubmit(submitBtn, originalBtnHtml, parentId, text);
}
} catch (err) {
console.warn(`[CommentSystem] Submit attempt ${retryCount + 1} failed:`, err);
@@ -3825,7 +3868,7 @@ class CommentSystem {
setTimeout(attemptSubmit, delay);
} else {
alert('Failed to send comment after multiple attempts. Please check your connection.');
this._finishSubmit(submitBtn, originalBtnHtml, parentId);
this._finishSubmit(submitBtn, originalBtnHtml, parentId, text);
}
}
};
@@ -3857,7 +3900,10 @@ class CommentSystem {
console.log('[ensureBtn] done, button in DOM:', !!contentEl.querySelector('.load-full-comment-btn'));
}
_finishSubmit(btn, originalHtml, parentId) {
_finishSubmit(btn, originalHtml, parentId, submittedText = null) {
if (submittedText) {
this._pendingSelfCommentTexts?.delete(submittedText);
}
if (parentId) {
this.pendingSubmissions.delete(parentId);
} else {
+10
View File
@@ -195,6 +195,7 @@ class Danmaku {
const mapped = comments
.filter(c => !c.is_deleted && c.content)
.map(c => ({
id: c.id || null,
text: this._prepareText(c.content),
username: c.display_name || c.username || '?',
color: c.username_color || null,
@@ -288,6 +289,7 @@ class Danmaku {
*/
addItem(comment) {
if (!comment || !comment.content) return;
if (comment.id && this.items && this.items.some(i => i.id === comment.id)) return;
const duration = this.media.duration;
const hasDuration = isFinite(duration) && duration > 0;
@@ -307,6 +309,7 @@ class Danmaku {
}
const item = {
id: comment.id || null,
video_time: t,
text: this._prepareText(comment.content),
username: comment.display_name || comment.username || '?',
@@ -314,6 +317,13 @@ class Danmaku {
fired: true // mark as already fired — caller handles any immediate one-shot
};
if (this._synthClock && this._flashPool) {
if (!this._flashPool.some(i => (comment.id && i.id === comment.id) || (i.text === item.text && i.username === item.username))) {
this._flashPool.push(item);
}
return;
}
// Insert in sorted order
const idx = this.items.findIndex(i => i.video_time > t);
if (idx === -1) this.items.push(item);
+2255 -150
View File
File diff suppressed because it is too large Load Diff
+22 -3
View File
@@ -532,14 +532,32 @@
else if (rClass === 'sfw' && blurSfw) isBlurred = true;
else if (rClass === 'untagged' && blurUntagged) isBlurred = true;
let thumbUrl = `/t/${c.item_id}.webp`;
const mime = c.mime || c.item_mime || '';
const activeDest = c.item_dest || c.dest || '';
const isAudio = mime.startsWith('audio/') || (!mime && activeDest.match(/\.(mp3|wav|ogg|flac|m4a|aac|opus)$/i));
let thumbUrl = c.thumb || `/t/${c.item_id}.webp`;
if (isBlurred) {
thumbUrl = `/t/${c.item_id}_blur.webp`;
const baseThumb = thumbUrl.replace(/\.webp$/, '');
thumbUrl = `${baseThumb}_blur.webp`;
}
if (window.applyThumbCacheBust) thumbUrl = window.applyThumbCacheBust(thumbUrl);
mediaHtml = `<img src="${thumbUrl}" style="width: 32px; height: 32px; object-fit: cover; border-radius: 2px;" loading="lazy" onerror="this.style.display='none'" />`;
if (isAudio && !c.has_coverart) {
mediaHtml = `
<div class="sidebar-media-placeholder audio">
<i class="fa-solid fa-music"></i>
</div>
`;
} else {
mediaHtml = `
<img src="${thumbUrl}" style="width: 32px; height: 32px; object-fit: cover; border-radius: 2px;" loading="lazy" onerror="this.style.display='none'; if(this.nextElementSibling) this.nextElementSibling.classList.remove('hidden');" />
<div class="sidebar-media-placeholder ${isAudio ? 'audio' : ''} hidden">
<i class="${isAudio ? 'fa-solid fa-music' : 'fa-solid fa-image'}"></i>
</div>
`;
}
itemPreview = `
<div class="item-preview">
@@ -1869,6 +1887,7 @@
}
}
};
window.switchSidebarTab = switchSidebarTab;
const initSidebarTabs = () => {
const tabsContainer = document.querySelector('.sidebar-tabs');
+39 -8
View File
@@ -88,6 +88,8 @@ const updateHoverStates = () => {
const isOver = mouseX >= rect.left && mouseX <= rect.right &&
mouseY >= rect.top && mouseY <= rect.bottom;
p.classList.toggle("v0ck_hover", isOver);
const gal = p.closest('.album-gallery-container');
if (gal) gal.classList.toggle("v0ck_hover", isOver);
});
};
@@ -117,6 +119,8 @@ class v0ck {
if (mouseX >= rect.left && mouseX <= rect.right &&
mouseY >= rect.top && mouseY <= rect.bottom) {
parent.classList.add("v0ck_hover", "v0ck_no_transition");
const gal = parent.closest('.album-gallery-container');
if (gal) gal.classList.add("v0ck_hover");
// Remove no-transition after a frame
setTimeout(() => parent.classList.remove("v0ck_no_transition"), 50);
}
@@ -142,14 +146,24 @@ class v0ck {
if (!ph) {
ph = document.createElement('div');
ph.className = 'sidebar-media-placeholder audio';
ph.innerHTML = '<div class="audio-cover-circle"></div><i class="fa-solid fa-music"></i>';
ph.innerHTML = '<div class="audio-cover-circle"><i class="fa-solid fa-music"></i></div>';
player.prepend(ph);
}
let coverCircle = ph.querySelector('.audio-cover-circle');
if (!coverCircle) {
coverCircle = document.createElement('div');
coverCircle.className = 'audio-cover-circle';
coverCircle.innerHTML = '<i class="fa-solid fa-music"></i>';
ph.insertBefore(coverCircle, ph.firstChild);
} else {
if (!coverCircle.querySelector('i')) {
const existingI = ph.querySelector(':scope > i');
if (existingI) {
coverCircle.appendChild(existingI);
} else {
coverCircle.insertAdjacentHTML('beforeend', '<i class="fa-solid fa-music"></i>');
}
}
}
player.style.backgroundImage = 'none';
player.style.backgroundColor = 'transparent';
@@ -157,7 +171,7 @@ class v0ck {
coverCircle.style.backgroundImage = `url('${poster}')`;
ph.classList.add('has-cover');
} else {
coverCircle.style.backgroundImage = 'none';
coverCircle.style.backgroundImage = '';
ph.classList.remove('has-cover');
}
}
@@ -203,6 +217,23 @@ class v0ck {
let wasPausedWhenStarted = false;
// Mobile tap-to-show-controls: true when this touch revealed the controls bar
let controlsJustShown = false;
const setHover = (active) => {
if (active) {
player.classList.add('v0ck_hover');
const gal = player.closest('.album-gallery-container');
if (gal) gal.classList.add('v0ck_hover');
} else {
player.classList.remove('v0ck_hover');
const gal = player.closest('.album-gallery-container');
if (gal) {
gal.classList.remove('v0ck_hover');
gal.classList.remove('strip-peek');
const strip = gal.querySelector('.album-thumbnails-strip');
if (strip) strip.classList.remove('strip-peek');
}
}
};
const speedIndicator = player.querySelector('.v0ck_speed_indicator');
// (mouse position is now tracked via docMouseX/docMouseY in resetControlsTimer block)
@@ -343,7 +374,7 @@ class v0ck {
if (isMobile && controlsJustShown) {
// First tap: controls were just revealed by this touch — don't toggle play
controlsJustShown = false;
player.classList.add('v0ck_hover');
setHover(true);
return;
}
controlsJustShown = false;
@@ -353,7 +384,7 @@ class v0ck {
toggle.addEventListener('click', togglePlay);
overlay.addEventListener('click', e => {
e.stopPropagation();
player.classList.add('v0ck_hover');
setHover(true);
togglePlay();
});
video.addEventListener('play', updatePlayIcon);
@@ -831,7 +862,7 @@ class v0ck {
}
if (isMobile && !isInsidePlayer && !isFlashYankUI) {
player.classList.remove('v0ck_hover');
setHover(false);
}
});
@@ -928,7 +959,7 @@ class v0ck {
const isFullscreen = player.classList.contains('v0ck_fullscreen');
if (!video.paused || isFullscreen) {
controlsTimer = setTimeout(() => {
player.classList.remove('v0ck_hover');
setHover(false);
if (settingsMenu && !settingsMenu.classList.contains('v0ck_hidden')) {
settingsMenu.classList.add('v0ck_hidden');
document.dispatchEvent(new CustomEvent('v0ck_settings_closed'));
@@ -950,7 +981,7 @@ class v0ck {
docMouseX = e.clientX;
docMouseY = e.clientY;
}
player.classList.add('v0ck_hover');
setHover(true);
resetControlsTimer();
}
@@ -998,7 +1029,7 @@ class v0ck {
}
docMouseX = -1;
docMouseY = -1;
player.classList.remove('v0ck_hover');
setHover(false);
clearTimeout(controlsTimer);
});