diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 3766e2a..61f4451 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -3144,7 +3144,7 @@ body.layout-legacy .scroll-to-bottom svg { color: #888; display: flex; justify-content: space-between; - align-items: flex-end; + align-items: flex-start; line-height: normal; flex-wrap: nowrap; gap: 2px 10px; @@ -3152,8 +3152,9 @@ body.layout-legacy .scroll-to-bottom svg { .comment-header-left { display: flex; + flex-wrap: wrap; align-items: center; - gap: 5px; + gap: 2px 6px; min-width: 0; flex-shrink: 1; } @@ -3276,9 +3277,13 @@ body.layout-legacy .scroll-to-bottom svg { .comment-author { font-weight: bold; color: var(--accent); - margin-right: 8px; + margin-right: 2px; padding-right: 0 !important; - word-break: break-word; + white-space: nowrap; + flex-shrink: 0; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; } .comment-time { @@ -3337,6 +3342,7 @@ body.layout-legacy .scroll-to-bottom svg { opacity: 1; cursor: pointer; vertical-align: top; + white-space: nowrap; } .comment-context-link:hover, @@ -3400,10 +3406,7 @@ body.layout-legacy .scroll-to-bottom svg { } .comment-backlinks { - display: inline-block; - font-size: 0.85em; - opacity: 0.8; - margin-left: 10px; + display: contents; } .comments-header-actions { diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 4811b4d..547180e 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -596,7 +596,7 @@ class CommentSystem { if (cached) cached.content = fullContent; } - contentEl.dataset.raw = this.escapeHtml(fullContent); + contentEl.dataset.raw = fullContent; contentEl.innerHTML = this.renderCommentContent(fullContent, commentId); CommentSystem.autoplayConvertedGifs(contentEl); CommentSystem.playEmojiVideos(contentEl); @@ -622,6 +622,7 @@ class CommentSystem { const contentEl = el.querySelector('.comment-content'); if (contentEl) { + contentEl.dataset.raw = data.content; contentEl.innerHTML = this.renderCommentContent(data.content, data.comment_id); CommentSystem.autoplayConvertedGifs(contentEl); CommentSystem.playEmojiVideos(contentEl); @@ -1173,7 +1174,12 @@ class CommentSystem { const contentEl = body.querySelector('.comment-content'); if (contentEl) { const LINE_MAX = 200; - const rawText = (contentEl.dataset.raw || '').trim(); + let rawText = (contentEl.dataset.raw || '').trim(); + if (rawText.includes('>') || rawText.includes('<') || rawText.includes('&')) { + const txt = document.createElement('textarea'); + txt.innerHTML = rawText; + rawText = txt.value; + } // Preserve all lines but cap any single line exceeding LINE_MAX chars const lines = rawText.split('\n').map(line => line.length > LINE_MAX ? line.substring(0, LINE_MAX) + '\u2026' : line @@ -1794,7 +1800,7 @@ class CommentSystem { const renderedContent = quoteEmojis ? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n)) : quoteContent; - return `>${renderedContent}`; + return `>${renderedContent.replace(/>/g, '>')}`; } // 2. Per-line limit to prevent marked.parse recursion on single giant lines @@ -2199,7 +2205,7 @@ class CommentSystem { if (!unsafe) return ''; const div = document.createElement('div'); div.textContent = unsafe; - return div.innerHTML; + return div.innerHTML.replace(/"/g, '"').replace(/'/g, '''); } renderCommentAttachments(files, content = '') { @@ -3336,9 +3342,15 @@ class CommentSystem { params.append('has_poll', '1'); } + const csrfToken = window.f0ckSession?.csrf_token || ''; + if (csrfToken) params.append('csrf_token', csrfToken); + const res = await fetch('/api/comments', { method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) + }, body: params }); diff --git a/public/s/js/scroller.js b/public/s/js/scroller.js index af63a15..e26972f 100644 --- a/public/s/js/scroller.js +++ b/public/s/js/scroller.js @@ -2119,7 +2119,12 @@ const contentEl = commentEl.querySelector('.comment-content'); if (!contentEl) return; - const raw = (contentEl.dataset.raw || '').replace(//gi, '\n').trim(); + let raw = (contentEl.dataset.raw || '').replace(//gi, '\n').trim(); + if (raw.includes('>') || raw.includes('<') || raw.includes('&')) { + const txt = document.createElement('textarea'); + txt.innerHTML = raw; + raw = txt.value; + } const lines = raw.split('\n'); const quote = `>>${id}\n${lines.map(line => `>${line}`).join('\n')}\n`; @@ -2787,11 +2792,16 @@ if (!content || !commentsItemId || commentsPosting) return; commentsPosting = true; commentSendBtn.disabled = true; try { + const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || ''; let postBody = `item_id=${commentsItemId}&content=${encodeURIComponent(content)}`; if (replyToCommentId) postBody += `&parent_id=${replyToCommentId}`; + if (csrfToken) postBody += `&csrf_token=${encodeURIComponent(csrfToken)}`; const resp = await fetch('/api/comments', { method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) + }, body: postBody }); const data = await resp.json(); @@ -3658,7 +3668,11 @@ if (sMarkAll) { sMarkAll.addEventListener('click', async () => { try { - await fetch('/api/notifications/read', { method: 'POST' }); + const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || ''; + await fetch('/api/notifications/read', { + method: 'POST', + headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) } + }); updateScrollerNotifBadge(0); sCachedNotifs = sCachedNotifs.map(n => ({ ...n, is_read: true })); updateScrollerTabBadges(sCachedNotifs); @@ -3673,7 +3687,12 @@ if (!item) return; const nid = item.dataset.id; if (nid && item.classList.contains('unread')) { - fetch(`/api/notifications/${nid}/read`, { method: 'POST', keepalive: true }).catch(() => {}); + const csrfToken = window.f0ckSession?.csrf_token || window.scrollerCsrf || ''; + fetch(`/api/notifications/${nid}/read`, { + method: 'POST', + keepalive: true, + headers: { ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}) } + }).catch(() => {}); item.classList.remove('unread'); // Update cache const cached = sCachedNotifs.find(n => String(n.id) === String(nid)); diff --git a/public/s/js/sidebar-activity.js b/public/s/js/sidebar-activity.js index cc4aa86..5e192d3 100644 --- a/public/s/js/sidebar-activity.js +++ b/public/s/js/sidebar-activity.js @@ -47,7 +47,7 @@ if (!unsafe) return ''; const div = document.createElement('div'); div.textContent = unsafe; - return div.innerHTML; + return div.innerHTML.replace(/"/g, '"').replace(/'/g, '''); }; const playSidebarEmojiVideos = (container) => { @@ -271,7 +271,7 @@ const rendered = quoteEmojis ? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n)) : quoteContent; - return `>${rendered}`; + return `>${rendered.replace(/>/g, '>')}`; } // Per-line limit to prevent marked.parse recursion on single giant lines diff --git a/public/s/js/user_comments.js b/public/s/js/user_comments.js index d61b08b..8a9eb31 100644 --- a/public/s/js/user_comments.js +++ b/public/s/js/user_comments.js @@ -306,7 +306,7 @@ if (!window.UserCommentSystem) { const trimmed = line.trimStart(); if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) { const quoteContent = line.substring(line.indexOf('>') + 1); - return `>${quoteContent}`; + return `>${quoteContent.replace(/>/g, '>')}`; } // Per-line limit @@ -450,7 +450,7 @@ if (!window.UserCommentSystem) { if (!unsafe) return ''; const div = document.createElement('div'); div.textContent = unsafe; - return div.innerHTML; + return div.innerHTML.replace(/"/g, '"').replace(/'/g, '''); } } }