update reply cancel button with less clutter
This commit is contained in:
@@ -12,21 +12,21 @@ class CommentSystem {
|
||||
this.isLocked = this.container ? this.container.dataset.isLocked === 'true' : false;
|
||||
this.displayMode = window.f0ckSession?.comment_display_mode || 0; // 0=Tree, 1=Linear
|
||||
this.sort = (document.body.classList.contains('layout-legacy') || document.body.classList.contains('legacy-view')) ? 'old' : 'new';
|
||||
|
||||
|
||||
// Linear mode usually implies chronological order (4chan style)
|
||||
if (this.displayMode === 1) this.sort = 'old';
|
||||
|
||||
this.customEmojis = CommentSystem.emojiCache || {};
|
||||
|
||||
this.icons = {
|
||||
reply: `<i class="fa-solid fa-reply"></i>`,
|
||||
pin: `<i class="fa-solid fa-thumbtack"></i>`,
|
||||
unpin: `<i class="fa-solid fa-thumbtack"></i>`,
|
||||
edit: `<i class="fa-solid fa-pen"></i>`,
|
||||
reply: `<i class="fa-solid fa-reply"></i>`,
|
||||
pin: `<i class="fa-solid fa-thumbtack"></i>`,
|
||||
unpin: `<i class="fa-solid fa-thumbtack"></i>`,
|
||||
edit: `<i class="fa-solid fa-pen"></i>`,
|
||||
delete: `<i class="fa-solid fa-trash"></i>`,
|
||||
link: `<i class="fa-solid fa-link"></i>`,
|
||||
link: `<i class="fa-solid fa-link"></i>`,
|
||||
pinned: `<i class="fa-solid fa-thumbtack"></i>`,
|
||||
lock: `<i class="fa-solid fa-lock"></i>`,
|
||||
lock: `<i class="fa-solid fa-lock"></i>`,
|
||||
unlock: `<i class="fa-solid fa-lock-open"></i>`
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ class CommentSystem {
|
||||
const rect = container.getBoundingClientRect();
|
||||
// Detect if we are at the bottom of the comments section
|
||||
const isAtBottom = rect.bottom < window.innerHeight + 100;
|
||||
|
||||
|
||||
if (isAtBottom) {
|
||||
btn.classList.add('is-at-bottom');
|
||||
btn.setAttribute('title', 'Scroll to top');
|
||||
@@ -107,9 +107,9 @@ class CommentSystem {
|
||||
this.scrollHandler = updateBtn;
|
||||
window.addEventListener('scroll', this.scrollHandler, { passive: true });
|
||||
this.scrollListenerAdded = true;
|
||||
|
||||
|
||||
// Dynamic detection after potential renders
|
||||
this.scrollTimer = setInterval(this.scrollHandler, 1000);
|
||||
this.scrollTimer = setInterval(this.scrollHandler, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +143,7 @@ class CommentSystem {
|
||||
// Stale-check: only compare player element (not mediaEl — SyntheticClock is always new)
|
||||
if (window.danmakuInstance) {
|
||||
const stale = window.danmakuInstance.player !== playerEl
|
||||
|| !document.contains(window.danmakuInstance.player);
|
||||
|| !document.contains(window.danmakuInstance.player);
|
||||
if (stale) {
|
||||
window.danmakuInstance.destroy();
|
||||
window.danmakuInstance = null;
|
||||
@@ -355,9 +355,9 @@ class CommentSystem {
|
||||
try {
|
||||
targetEl.setSelectionRange(state.focused.start, state.focused.end);
|
||||
} catch (e) {
|
||||
// Fallback to end if range is invalid
|
||||
const len = targetEl.value.length;
|
||||
targetEl.setSelectionRange(len, len);
|
||||
// Fallback to end if range is invalid
|
||||
const len = targetEl.value.length;
|
||||
targetEl.setSelectionRange(len, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,8 +402,8 @@ class CommentSystem {
|
||||
const comment = {
|
||||
id: data.id,
|
||||
item_id: data.item_id,
|
||||
parent_id: data.parent_id || null,
|
||||
content: data.body,
|
||||
parent_id: data.parent_id || null,
|
||||
content: data.body,
|
||||
created_at: data.created_at,
|
||||
username: data.username,
|
||||
user_id: data.user_id,
|
||||
@@ -428,7 +428,7 @@ class CommentSystem {
|
||||
data.username_color || null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Update backlinks for live comment
|
||||
if (data.body) {
|
||||
const matches = data.body.matchAll(/(?<!\w)>>(\d+)/g);
|
||||
@@ -632,10 +632,10 @@ class CommentSystem {
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/comments/${this.itemId}?sort=${this.sort}`);
|
||||
|
||||
|
||||
// If server is restarting (502/503), res.ok will be false.
|
||||
if (!res.ok) throw new Error(`Server returned ${res.status}`);
|
||||
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
@@ -660,7 +660,7 @@ class CommentSystem {
|
||||
const savedHash = (preserveScroll && window.location.hash.startsWith('#c'))
|
||||
? window.location.hash
|
||||
: null;
|
||||
|
||||
|
||||
// 1. Early Bail-out: If data is bit-for-bit identical, do nothing.
|
||||
// This is the primary defense against tab-switch reloads when nothing changed.
|
||||
if (preserveScroll && this._isDeeplyIdentical(data.comments, data.user_id, data.is_subscribed)) {
|
||||
@@ -676,7 +676,7 @@ class CommentSystem {
|
||||
this.reconcile(data.comments, data.user_id, data.is_subscribed);
|
||||
this.initialLoadDone = true;
|
||||
this.restoreState(state);
|
||||
|
||||
|
||||
if (scrollToId) {
|
||||
this.preservingScroll = false;
|
||||
this.scrollToComment(scrollToId);
|
||||
@@ -770,7 +770,7 @@ class CommentSystem {
|
||||
document.querySelectorAll('.comment-highlighted').forEach(c => c.classList.remove('comment-highlighted'));
|
||||
document.querySelectorAll('.comment-entering').forEach(c => c.classList.remove('comment-entering'));
|
||||
document.querySelectorAll('.new-item-fade').forEach(c => c.classList.remove('new-item-fade'));
|
||||
|
||||
|
||||
// Always smooth-scroll so there's no jarring jump
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('comment-highlighted');
|
||||
@@ -793,7 +793,7 @@ class CommentSystem {
|
||||
startStabilization(id) {
|
||||
this.stopStabilization(); // Clear any existing
|
||||
this.isUserInteracting = false;
|
||||
|
||||
|
||||
const el = document.getElementById(`c${id}`);
|
||||
if (!el) return;
|
||||
|
||||
@@ -821,17 +821,17 @@ class CommentSystem {
|
||||
if (this.isUserInteracting || this.preservingScroll) return;
|
||||
const currentEl = document.getElementById(`c${id}`);
|
||||
if (!currentEl) return;
|
||||
|
||||
|
||||
const currentTop = currentEl.getBoundingClientRect().top;
|
||||
const diff = Math.abs(currentTop - lastTop);
|
||||
|
||||
|
||||
// If it shifted more than 10px (e.g. media loaded), re-scroll
|
||||
if (diff > 10 && checks < maxChecks) {
|
||||
_f0ckDebug(`[CommentSystem] Layout shift detected (${Math.round(diff)}px), re-stabilizing scroll...`);
|
||||
this.scrollToComment(id, 0, true);
|
||||
lastTop = currentEl.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
|
||||
checks++;
|
||||
if (checks < maxChecks) {
|
||||
this.stabilizationTimer = setTimeout(checkShift, 200);
|
||||
@@ -839,7 +839,7 @@ class CommentSystem {
|
||||
};
|
||||
|
||||
this.stabilizationTimer = setTimeout(checkShift, 200);
|
||||
|
||||
|
||||
// Also use ResizeObserver for immediate reaction to content loading
|
||||
if (window.ResizeObserver) {
|
||||
this.stabilizationObserver = new ResizeObserver(() => {
|
||||
@@ -856,7 +856,7 @@ class CommentSystem {
|
||||
}, 100);
|
||||
});
|
||||
this.stabilizationObserver.observe(this.container);
|
||||
|
||||
|
||||
// Disconnect after 5 seconds to save resources
|
||||
setTimeout(() => this.stopStabilization(), 5000);
|
||||
}
|
||||
@@ -875,7 +875,7 @@ class CommentSystem {
|
||||
this.boundStopStabilization = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async showCommentPreview(link, event) {
|
||||
if (this.previewCloseTimer) {
|
||||
clearTimeout(this.previewCloseTimer);
|
||||
@@ -884,7 +884,7 @@ class CommentSystem {
|
||||
|
||||
const targetId = link.dataset.id;
|
||||
let targetEl = document.getElementById('c' + targetId);
|
||||
|
||||
|
||||
const parentPopup = link.closest('.comment-preview-popup');
|
||||
const level = parentPopup ? parseInt(parentPopup.dataset.level || 0) + 1 : 0;
|
||||
|
||||
@@ -912,10 +912,10 @@ class CommentSystem {
|
||||
preview.classList.add('comment-preview-popup');
|
||||
preview.dataset.level = level;
|
||||
preview.dataset.id = targetId;
|
||||
|
||||
|
||||
// Remove temporary animation/highlight classes
|
||||
preview.classList.remove('new-item-fade', 'comment-highlighted', 'comment-entering');
|
||||
|
||||
|
||||
// Remove input forms or action buttons
|
||||
const actions = preview.querySelector('.comment-actions');
|
||||
if (actions) actions.remove();
|
||||
@@ -926,15 +926,15 @@ class CommentSystem {
|
||||
const rect = link.getBoundingClientRect();
|
||||
preview.style.position = 'fixed';
|
||||
preview.style.zIndex = (100000 + level).toString();
|
||||
|
||||
|
||||
// Try to place it to the right of the link, or top/bottom if needed
|
||||
let left = rect.right + 10;
|
||||
let top = rect.top;
|
||||
|
||||
document.body.appendChild(preview);
|
||||
|
||||
|
||||
const previewRect = preview.getBoundingClientRect();
|
||||
|
||||
|
||||
// Boundary checks
|
||||
if (left + previewRect.width > window.innerWidth) {
|
||||
left = rect.left - previewRect.width - 10;
|
||||
@@ -952,7 +952,7 @@ class CommentSystem {
|
||||
|
||||
async fetchCommentForPreview(id, link, level) {
|
||||
if (this.commentCache.has(id)) return; // Already fetching or fetched
|
||||
|
||||
|
||||
// Prevent double fetches
|
||||
this.commentCache.set(id, { loading: true });
|
||||
|
||||
@@ -1078,7 +1078,7 @@ class CommentSystem {
|
||||
el.muted = snap.muted;
|
||||
el.volume = snap.volume;
|
||||
if (!snap.paused) {
|
||||
el.play().catch(() => {/* autoplay may be blocked */});
|
||||
el.play().catch(() => {/* autoplay may be blocked */ });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1088,7 +1088,7 @@ class CommentSystem {
|
||||
this.lastData = comments;
|
||||
this.lastUserId = currentUserId;
|
||||
this.lastIsSubscribed = isSubscribed;
|
||||
|
||||
|
||||
// Build map of who replied to whom for back-references (>>ID)
|
||||
this.buildBacklinkMap(comments);
|
||||
|
||||
@@ -1225,7 +1225,7 @@ class CommentSystem {
|
||||
document.querySelectorAll('#subscribe-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', isSubscribed);
|
||||
btn.setAttribute('title', isSubscribed ? 'Subscribed' : 'Subscribe');
|
||||
|
||||
|
||||
// Update text if it exists (legacy support)
|
||||
const textNodes = Array.from(btn.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
|
||||
if (textNodes.length > 0) {
|
||||
@@ -1240,22 +1240,22 @@ class CommentSystem {
|
||||
|
||||
_isDeeplyIdentical(newComments, currentUserId, isSubscribed) {
|
||||
if (!this.lastData) return false;
|
||||
|
||||
|
||||
// Use lax comparison for user IDs as they might flip between string/int
|
||||
const userMatches = (this.lastUserId == currentUserId);
|
||||
if (!userMatches) return false;
|
||||
|
||||
if (this.lastIsSubscribed !== isSubscribed) return false;
|
||||
if (this.lastData.length !== newComments.length) return false;
|
||||
|
||||
|
||||
for (let i = 0; i < newComments.length; i++) {
|
||||
const a = this.lastData[i];
|
||||
const b = newComments[i];
|
||||
|
||||
|
||||
// Compare essential data that affects the UI
|
||||
if (a.id !== b.id ||
|
||||
a.content !== b.content ||
|
||||
a.is_deleted !== b.is_deleted ||
|
||||
if (a.id !== b.id ||
|
||||
a.content !== b.content ||
|
||||
a.is_deleted !== b.is_deleted ||
|
||||
a.is_pinned !== b.is_pinned ||
|
||||
a.display_name !== b.display_name) {
|
||||
return false;
|
||||
@@ -1278,7 +1278,7 @@ class CommentSystem {
|
||||
this.lastData = comments;
|
||||
this.lastUserId = currentUserId;
|
||||
this.lastIsSubscribed = isSubscribed;
|
||||
|
||||
|
||||
// Build map of who replied to whom for back-references (>>ID)
|
||||
this.buildBacklinkMap(comments);
|
||||
|
||||
@@ -1288,7 +1288,7 @@ class CommentSystem {
|
||||
// 1. Identify missing or edited comments already in DOM
|
||||
const existingEls = list.querySelectorAll('[id^="c"]');
|
||||
const existingIds = new Set();
|
||||
|
||||
|
||||
existingEls.forEach(el => {
|
||||
const id = el.id.substring(1);
|
||||
existingIds.add(id);
|
||||
@@ -1309,7 +1309,7 @@ class CommentSystem {
|
||||
contentEl.innerHTML = this.renderCommentContent(incoming.content, incoming.id);
|
||||
contentEl.dataset.raw = incoming.content;
|
||||
}
|
||||
|
||||
|
||||
// Update pinned status
|
||||
el.classList.toggle('pinned', !!incoming.is_pinned);
|
||||
const pinIcon = el.querySelector('.pin-icon');
|
||||
@@ -1396,7 +1396,7 @@ class CommentSystem {
|
||||
const tmp = document.createElement('div');
|
||||
tmp.innerHTML = html;
|
||||
const newEl = tmp.firstElementChild;
|
||||
|
||||
|
||||
// Insertion point: after previous sibling if it exists, else prepend
|
||||
if (idx === 0) {
|
||||
const nav = container.querySelector('.scroll-nav-wrapper');
|
||||
@@ -1466,7 +1466,7 @@ class CommentSystem {
|
||||
|
||||
// 2. Initial escaping for the rest of the text. Preserve > for manual blockquote handling.
|
||||
let escaped = this.escapeHtml(processed)
|
||||
.replace(/>/g, ">");
|
||||
.replace(/>/g, ">");
|
||||
|
||||
|
||||
const siteOrigin = window.location.origin;
|
||||
@@ -1493,7 +1493,7 @@ class CommentSystem {
|
||||
const titleAttr = title ? ` title="${title}"` : '';
|
||||
const isExternal = href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//');
|
||||
let isSameSite = false;
|
||||
|
||||
|
||||
// Marked greedy autolink fix for spoiler brackets appended to URLs
|
||||
let extraSuffix = '';
|
||||
const lowerHref = href.toLowerCase();
|
||||
@@ -1515,7 +1515,7 @@ class CommentSystem {
|
||||
const urlToParse = href.startsWith('//') ? window.location.protocol + href : href;
|
||||
const urlObj = new URL(urlToParse, siteOrigin);
|
||||
isSameSite = (urlObj.hostname === window.location.hostname);
|
||||
} catch(e) {}
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
// Shorten internal links if text matches the URL
|
||||
@@ -1548,7 +1548,7 @@ class CommentSystem {
|
||||
}
|
||||
const hostsRegexPart = allowedHosts.join('|');
|
||||
const domainOrRelative = `(?:(?:https?:\\/\\/|\\/\\/)?(?:${hostsRegexPart})|(?<!\\S)(?=\\/[a-zA-Z0-9_\\-]))`;
|
||||
|
||||
|
||||
// "Safe non-whitespace" — matches any \S except the start of an https?:// boundary.
|
||||
// Prevents concatenated URLs (url1.webpurl2.webp) being consumed as one giant src.
|
||||
const safeS = `(?:(?!https?:\\/\\/)\\S)`;
|
||||
@@ -1560,7 +1560,7 @@ class CommentSystem {
|
||||
// Line-by-line processing: handles quotes, mentions, images, and basic markdown safely
|
||||
const renderedLines = escaped.split('\n').map(line => {
|
||||
const trimmed = line.trimStart();
|
||||
|
||||
|
||||
// 1. Manual Greentext/Quote handling (avoids recursive blockquote parsing)
|
||||
// Exclude only the numeric context links (>>ID) so they can be handled as interactive links.
|
||||
// Multiple chevrons (>>text, >>>text) should still be greentext.
|
||||
@@ -1572,7 +1572,7 @@ class CommentSystem {
|
||||
: quoteContent;
|
||||
return `<span class="greentext">>${renderedContent}</span>`;
|
||||
}
|
||||
|
||||
|
||||
// 2. Per-line limit to prevent marked.parse recursion on single giant lines
|
||||
if (line.length > 10000) return line;
|
||||
|
||||
@@ -1580,7 +1580,7 @@ class CommentSystem {
|
||||
|
||||
// 3. Perform replacements on the single line (prevents regex stack overflow on huge strings)
|
||||
let processedLine = line;
|
||||
|
||||
|
||||
// Handle Mentions
|
||||
processedLine = processedLine.replace(mentionRegex, (match, g1, g2) => {
|
||||
const user = g1 || g2;
|
||||
@@ -1591,7 +1591,7 @@ class CommentSystem {
|
||||
processedLine = processedLine.replace(/(?<!\w)>>(\d+)/g, (match, id) => {
|
||||
return `<a href="#c${id}" class="comment-context-link" data-id="${id}">>>${id}</a>`;
|
||||
});
|
||||
|
||||
|
||||
// Handle Image Embeds
|
||||
processedLine = processedLine.replace(imageRegex, (match, url) => {
|
||||
let fullUrl = url;
|
||||
@@ -1600,14 +1600,14 @@ class CommentSystem {
|
||||
}
|
||||
return ``;
|
||||
});
|
||||
|
||||
|
||||
// Handle Raw Video/Audio links so Marked converts them to <a>
|
||||
processedLine = processedLine.replace(rawVideoRegex, (match, url) => {
|
||||
let fullUrl = url;
|
||||
if (!url.startsWith('http') && !url.startsWith('//') && !url.startsWith('/')) fullUrl = '//' + url;
|
||||
return `[video](${fullUrl})`;
|
||||
});
|
||||
|
||||
|
||||
processedLine = processedLine.replace(rawAudioRegex, (match, url) => {
|
||||
let fullUrl = url;
|
||||
if (!url.startsWith('http') && !url.startsWith('//') && !url.startsWith('/')) fullUrl = '//' + url;
|
||||
@@ -1616,13 +1616,13 @@ class CommentSystem {
|
||||
|
||||
// 3. Render Markdown for the line
|
||||
const escapedAsterisks = processedLine.replace(/\*/g, '\\*');
|
||||
let rendered = marked.parseInline
|
||||
? marked.parseInline(escapedAsterisks, { renderer: renderer })
|
||||
let rendered = marked.parseInline
|
||||
? marked.parseInline(escapedAsterisks, { renderer: renderer })
|
||||
: marked.parse(escapedAsterisks, { renderer: renderer }).replace(/<p>|<\/p>/g, '');
|
||||
|
||||
|
||||
// 4. Emojis
|
||||
rendered = rendered.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n));
|
||||
|
||||
|
||||
return rendered;
|
||||
});
|
||||
|
||||
@@ -1641,7 +1641,7 @@ class CommentSystem {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Vocaroo embed
|
||||
md = md.replace(
|
||||
/<a\s[^>]*href="https?:\/\/(?:www\.)?(?:voca\.ro|vocaroo\.com)\/([a-zA-Z0-9_-]+)[^"]*"[^>]*>([\s\S]*?)<\/a>/gi,
|
||||
@@ -1696,7 +1696,7 @@ class CommentSystem {
|
||||
});
|
||||
iterations++;
|
||||
} while (md !== prevMd && iterations < 10);
|
||||
|
||||
|
||||
// Restore protected code blocks
|
||||
md = md.replace(/BLOCKPORTALX(\d+)X/g, (match, index) => {
|
||||
return codeBlocks[index] || '';
|
||||
@@ -1724,7 +1724,7 @@ class CommentSystem {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildBacklinkMap(comments) {
|
||||
this.backlinkMap = {};
|
||||
const process = (c) => {
|
||||
@@ -1777,7 +1777,7 @@ class CommentSystem {
|
||||
if (!this.backlinkMap) this.backlinkMap = {};
|
||||
if (!this.backlinkMap[targetId]) this.backlinkMap[targetId] = new Set();
|
||||
this.backlinkMap[targetId].add(replierId);
|
||||
|
||||
|
||||
const targetEl = document.getElementById('c' + targetId);
|
||||
if (targetEl) {
|
||||
const headerLeft = targetEl.querySelector('.comment-header-left');
|
||||
@@ -1905,8 +1905,8 @@ class CommentSystem {
|
||||
renderInput(parentId = null) {
|
||||
const i18n = window.f0ckI18n || {};
|
||||
const placeholder = i18n.write_comment || 'Write a comment...';
|
||||
const postLabel = i18n.post || 'Post';
|
||||
const cancelLabel = i18n.cancel || 'Cancel';
|
||||
const postLabel = i18n.post || 'Post';
|
||||
const cancelLabel = i18n.cancel || 'Cancel';
|
||||
const maxLen = window.f0ckSession?.comment_max_length;
|
||||
const maxLenAttr = (maxLen !== null && maxLen !== undefined) ? ` maxlength="${maxLen}"` : '';
|
||||
const counter = (maxLen !== null && maxLen !== undefined)
|
||||
@@ -1917,7 +1917,7 @@ class CommentSystem {
|
||||
<textarea placeholder="${placeholder}"${maxLenAttr}></textarea>
|
||||
<div class="input-actions">
|
||||
${counter}
|
||||
${parentId ? `<button class="cancel-reply">${cancelLabel}</button>` : ''}
|
||||
${parentId ? `<button class="cancel-reply" title="${cancelLabel}"><i class="fa-solid fa-xmark"></i></button>` : ''}
|
||||
<button class="submit-comment">${postLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1934,7 +1934,7 @@ class CommentSystem {
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
const contextLink = e.target.closest('.comment-context-link');
|
||||
const popup = e.target.closest('.comment-preview-popup');
|
||||
|
||||
|
||||
if (contextLink || popup) {
|
||||
if (this.previewCloseTimer) {
|
||||
clearTimeout(this.previewCloseTimer);
|
||||
@@ -1962,7 +1962,7 @@ class CommentSystem {
|
||||
this.currentHoverLink = null;
|
||||
|
||||
const level = popup ? parseInt(popup.dataset.level || 0) : -1;
|
||||
|
||||
|
||||
// If we move back to a parent level or blank area of a popup, close its children
|
||||
// but use a delay so the user can reach the child popup if they are moving towards it.
|
||||
if (popup && !contextLink) {
|
||||
@@ -1970,7 +1970,7 @@ class CommentSystem {
|
||||
this.closePreviewsAboveLevel(level);
|
||||
}, 400);
|
||||
}
|
||||
|
||||
|
||||
this.mouseCurrentLevel = level;
|
||||
}
|
||||
});
|
||||
@@ -1978,7 +1978,7 @@ class CommentSystem {
|
||||
document.addEventListener('mouseout', (e) => {
|
||||
const contextLink = e.target.closest('.comment-context-link');
|
||||
const popup = e.target.closest('.comment-preview-popup');
|
||||
|
||||
|
||||
if (contextLink) {
|
||||
if (this.previewOpenTimer) {
|
||||
clearTimeout(this.previewOpenTimer);
|
||||
@@ -2026,7 +2026,7 @@ class CommentSystem {
|
||||
document.addEventListener('click', (e) => {
|
||||
const isLink = e.target.closest('.comment-context-link');
|
||||
const isPopup = e.target.closest('.comment-preview-popup');
|
||||
|
||||
|
||||
if (!isLink && !isPopup) {
|
||||
this.closePreviewsAboveLevel(-1);
|
||||
}
|
||||
@@ -2068,7 +2068,7 @@ class CommentSystem {
|
||||
.length;
|
||||
counter.textContent = `${nonQuotedLen} / ${max}`;
|
||||
counter.classList.toggle('near-limit', nonQuotedLen >= max * 0.9);
|
||||
counter.classList.toggle('at-limit', nonQuotedLen >= max);
|
||||
counter.classList.toggle('at-limit', nonQuotedLen >= max);
|
||||
});
|
||||
|
||||
// Single Change Listener for Sort
|
||||
@@ -2156,7 +2156,7 @@ class CommentSystem {
|
||||
e.preventDefault();
|
||||
const targetId = contextLink.dataset.id;
|
||||
this.scrollToComment(targetId, 0, true);
|
||||
|
||||
|
||||
// Highlight effect
|
||||
const targetEl = document.getElementById('c' + targetId);
|
||||
if (targetEl) {
|
||||
@@ -2183,7 +2183,7 @@ class CommentSystem {
|
||||
}
|
||||
|
||||
const id = delBtn.dataset.id;
|
||||
const res = await fetch(`/api/comments/${id}/delete`, {
|
||||
const res = await fetch(`/api/comments/${id}/delete`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
@@ -2222,7 +2222,7 @@ class CommentSystem {
|
||||
const adminPinBtn = target.closest('.admin-pin-btn');
|
||||
if (adminPinBtn) {
|
||||
const id = adminPinBtn.dataset.id;
|
||||
const res = await fetch(`/api/comments/${id}/pin`, {
|
||||
const res = await fetch(`/api/comments/${id}/pin`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
@@ -2244,7 +2244,7 @@ class CommentSystem {
|
||||
contentEl.innerHTML = `
|
||||
<textarea class="edit-textarea">${currentContent}</textarea>
|
||||
<div class="input-actions">
|
||||
<button class="cancel-edit-btn">Cancel</button>
|
||||
<button class="cancel-edit-btn" title="Cancel"><i class="fa-solid fa-comment-slash"></i></button>
|
||||
<button class="save-edit-btn">Save</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -2291,11 +2291,11 @@ class CommentSystem {
|
||||
const id = replyBtn.dataset.id;
|
||||
const commentEl = replyBtn.closest('[id^="c"]');
|
||||
const body = commentEl ? commentEl.querySelector('.comment-body') : null;
|
||||
|
||||
|
||||
if (body) {
|
||||
// Check if any reply input is ALREADY open
|
||||
let textarea = document.querySelector('.comment-input.reply-input textarea');
|
||||
|
||||
|
||||
// If none open, open the local one for this comment
|
||||
if (!textarea && !body.querySelector('.reply-input')) {
|
||||
const div = document.createElement('div');
|
||||
@@ -2307,7 +2307,7 @@ class CommentSystem {
|
||||
} else if (!textarea) {
|
||||
textarea = body.querySelector('.reply-input textarea');
|
||||
}
|
||||
|
||||
|
||||
if (textarea) {
|
||||
const quote = `>>${id} `;
|
||||
const start = textarea.selectionStart;
|
||||
@@ -2339,7 +2339,7 @@ class CommentSystem {
|
||||
const isSubscribed = subBtn.textContent === 'Subscribed';
|
||||
subBtn.textContent = 'Wait...';
|
||||
try {
|
||||
const res = await fetch(`/api/subscribe/${this.itemId}`, {
|
||||
const res = await fetch(`/api/subscribe/${this.itemId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
@@ -2362,7 +2362,7 @@ class CommentSystem {
|
||||
if (lockBtn) {
|
||||
const action = this.isLocked ? 'unlock' : 'lock';
|
||||
lockBtn.disabled = true;
|
||||
const res = await fetch(`/api/comments/${this.itemId}/lock`, {
|
||||
const res = await fetch(`/api/comments/${this.itemId}/lock`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
@@ -2718,7 +2718,7 @@ class CommentSystem {
|
||||
window.commentSystem.scrollToComment(id);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
window.addEventListener('f0ck:emojis_updated', () => {
|
||||
const cs = window.commentSystem;
|
||||
if (!cs) return;
|
||||
@@ -2778,13 +2778,13 @@ class CommentSystem {
|
||||
if (subBtn) {
|
||||
e.preventDefault();
|
||||
if (subBtn.style.opacity === '0.5') return;
|
||||
|
||||
|
||||
const itemId = subBtn.dataset.itemId || (window.commentSystem ? window.commentSystem.itemId : null);
|
||||
if (!itemId) return;
|
||||
|
||||
subBtn.style.opacity = '0.5';
|
||||
try {
|
||||
const res = await fetch(`/api/subscribe/${itemId}`, {
|
||||
const res = await fetch(`/api/subscribe/${itemId}`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
@@ -2936,7 +2936,7 @@ class CommentSystem {
|
||||
acActiveIdx = -1;
|
||||
acDisplayedCount = 0;
|
||||
appendMoreItems(); // Initial batch
|
||||
|
||||
|
||||
positionAC();
|
||||
autocomplete.style.display = 'flex';
|
||||
};
|
||||
@@ -2993,7 +2993,7 @@ class CommentSystem {
|
||||
|
||||
// Lazy load on scroll
|
||||
autocomplete.addEventListener('scroll', () => {
|
||||
const threshold = 50;
|
||||
const threshold = 50;
|
||||
if (autocomplete.scrollHeight - autocomplete.scrollTop - autocomplete.clientHeight < threshold) {
|
||||
appendMoreItems();
|
||||
}
|
||||
@@ -3037,7 +3037,7 @@ class CommentSystem {
|
||||
const end = textarea.selectionEnd;
|
||||
const val = textarea.value;
|
||||
const selected = val.substring(start, end);
|
||||
|
||||
|
||||
if (selected) {
|
||||
const tagStart = '[spoiler]';
|
||||
const tagEnd = '[/spoiler]';
|
||||
@@ -3052,8 +3052,11 @@ class CommentSystem {
|
||||
}
|
||||
textarea.focus();
|
||||
});
|
||||
const submitBtn = actions.querySelector('.submit-comment');
|
||||
actions.insertBefore(trigger, submitBtn);
|
||||
const referenceNode = actions.querySelector('.cancel-reply') ||
|
||||
actions.querySelector('.cancel-edit-btn') ||
|
||||
actions.querySelector('.submit-comment') ||
|
||||
actions.querySelector('.save-edit-btn');
|
||||
actions.insertBefore(trigger, referenceNode);
|
||||
actions.insertBefore(spoilerBtn, trigger);
|
||||
if (this.isAdmin) {
|
||||
const lockBtn = document.createElement('button');
|
||||
|
||||
Reference in New Issue
Block a user