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, ''');
}
}
}