fix comment on profiles lol
This commit is contained in:
186
public/s/js/user_comments.js
Normal file
186
public/s/js/user_comments.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
class UserCommentSystem {
|
||||||
|
constructor() {
|
||||||
|
this.container = document.getElementById('user-comments-container');
|
||||||
|
this.username = this.container ? this.container.dataset.user : null;
|
||||||
|
this.page = 1;
|
||||||
|
this.loading = false;
|
||||||
|
this.finished = false;
|
||||||
|
this.customEmojis = UserCommentSystem.emojiCache || {};
|
||||||
|
|
||||||
|
if (this.username) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.loadEmojis();
|
||||||
|
this.loadMore();
|
||||||
|
this.bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadEmojis() {
|
||||||
|
if (UserCommentSystem.emojiCache) {
|
||||||
|
this.customEmojis = UserCommentSystem.emojiCache;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/v2/emojis');
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
this.customEmojis = {};
|
||||||
|
data.emojis.forEach(e => {
|
||||||
|
this.customEmojis[e.name] = e.url;
|
||||||
|
});
|
||||||
|
UserCommentSystem.emojiCache = this.customEmojis;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load emojis", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (this.loading || this.finished) return;
|
||||||
|
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMore() {
|
||||||
|
if (this.loading || this.finished) return;
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const loader = document.createElement('div');
|
||||||
|
loader.className = 'loader-placeholder';
|
||||||
|
loader.innerText = 'Loading...';
|
||||||
|
loader.style.textAlign = 'center';
|
||||||
|
loader.style.padding = '10px';
|
||||||
|
this.container.appendChild(loader);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/user/' + encodeURIComponent(this.username) + '/comments?page=' + this.page + '&json=true');
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
loader.remove();
|
||||||
|
|
||||||
|
if (json.success && json.comments.length > 0) {
|
||||||
|
json.comments.forEach(c => {
|
||||||
|
console.log('Raw Comment Content (ID ' + c.id + '):', c.content);
|
||||||
|
const html = this.renderComment(c);
|
||||||
|
this.container.insertAdjacentHTML('beforeend', html);
|
||||||
|
});
|
||||||
|
this.page++;
|
||||||
|
} else {
|
||||||
|
this.finished = true;
|
||||||
|
if (this.page === 1 && (!json.comments || json.comments.length === 0)) {
|
||||||
|
this.container.innerHTML = '<div style="text-align:center;padding:20px;color:#888;">No comments found.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
loader.remove();
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEmoji(match, name) {
|
||||||
|
if (this.customEmojis && this.customEmojis[name]) {
|
||||||
|
return `<img src="${this.customEmojis[name]}" style="height:24px;vertical-align:middle;" alt="${name}">`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCommentContent(content) {
|
||||||
|
if (typeof marked === 'undefined') {
|
||||||
|
console.error('UserCommentSystem: marked.js is undefined!');
|
||||||
|
return this.escapeHtml(content).replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Pre-process server-escaped content
|
||||||
|
|
||||||
|
// Fix Greentext: Server sends >lool -> convert to > lool for marked
|
||||||
|
let safe = content.replace(/^>/gm, '> ');
|
||||||
|
// Also handle raw > just in case
|
||||||
|
safe = safe.replace(/^>(?=[^ ])/gm, '> ');
|
||||||
|
|
||||||
|
// Fix Images: Server sends <img src="..." ...> -> convert to markdown ![]()
|
||||||
|
// This avoids them being escaped by our HTML escaping later
|
||||||
|
safe = safe.replace(/<img\s+[^>]*src="([^"]+)"[^>]*>/g, (match, src) => {
|
||||||
|
let altMatch = match.match(/alt="([^"]*)"/);
|
||||||
|
let alt = altMatch ? altMatch[1] : '';
|
||||||
|
return ``;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Escape HTML (standard safety)
|
||||||
|
safe = safe
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
|
||||||
|
const renderer = new marked.Renderer();
|
||||||
|
renderer.blockquote = function (quote) {
|
||||||
|
let text = (typeof quote === 'string') ? quote : (quote.text || '');
|
||||||
|
let cleanQuote = text.replace(/<p>|<\/p>|\n/g, '');
|
||||||
|
return `<span class="greentext">> ${cleanQuote}</span><br>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. Parse Markdown
|
||||||
|
let md = marked.parse(safe, {
|
||||||
|
breaks: true,
|
||||||
|
renderer: renderer
|
||||||
|
});
|
||||||
|
|
||||||
|
return md.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('UserCommentSystem Markdown Render Error:', e);
|
||||||
|
return this.escapeHtml(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComment(c) {
|
||||||
|
const date = new Date(c.created_at).toLocaleString();
|
||||||
|
const content = this.renderCommentContent(c.content);
|
||||||
|
|
||||||
|
// Replicating the structure of comments.js but adapting for the list view
|
||||||
|
// We add a header indicating which item this comment belongs to
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="user-comment-wrapper" style="margin-bottom: 20px; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 4px;">
|
||||||
|
<div style="margin-bottom: 10px; font-size: 0.9em; color: #aaa; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 5px;">
|
||||||
|
Comment on <a href="/${c.item_id}#c${c.id}" style="color: #fff; font-weight: bold;">Item #${c.item_id}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment" id="c${c.id}" style="border:none; padding:0; background:transparent;">
|
||||||
|
<div class="comment-avatar">
|
||||||
|
<a href="/${c.item_id}"><img src="/t/${c.item_id}.webp" alt="Item Thumbnail" style="width:50px; height:50px; object-fit:cover; border-radius:4px;"></a>
|
||||||
|
</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
<div class="comment-meta">
|
||||||
|
<span class="comment-author">${this.username}</span>
|
||||||
|
<span class="comment-time">${date}</span>
|
||||||
|
<a href="/${c.item_id}#c${c.id}" class="comment-permalink">#${c.id}</a>
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">${content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeHtml(unsafe) {
|
||||||
|
return unsafe
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new UserCommentSystem();
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@include(snippets/header)
|
@include(snippets/header)
|
||||||
|
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div class="profile_head">
|
<div class="profile_head">
|
||||||
@if(user.avatar)
|
@if(user.avatar)
|
||||||
@@ -18,91 +19,12 @@
|
|||||||
|
|
||||||
<div class="user_content_wrapper" style="display: block;">
|
<div class="user_content_wrapper" style="display: block;">
|
||||||
<div class="comments-list-page" style="max-width: 800px; margin: 0 auto;">
|
<div class="comments-list-page" style="max-width: 800px; margin: 0 auto;">
|
||||||
@each(comments as c)
|
<!-- Container for CSR comments -->
|
||||||
<div class="user-comment-row"
|
<div id="user-comments-container" data-user="{{ user.user }}"></div>
|
||||||
style="display: flex; gap: 10px; margin-bottom: 10px; background: rgba(255,255,255,0.05); padding: 10px; border-radius: 4px;">
|
|
||||||
<div class="comment-thumbnail" style="flex-shrink: 0;">
|
|
||||||
<a href="/{{ c.item_id }}#c{{ c.id }}">
|
|
||||||
<img src="/t/{{ c.item_id }}.webp"
|
|
||||||
style="width: 80px; height: 80px; object-fit: cover; border-radius: 4px;">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="comment-preview" style="flex-grow: 1;">
|
|
||||||
<div style="font-size: 0.8em; color: #888; margin-bottom: 5px;">
|
|
||||||
On <a href="/{{ c.item_id }}">Item #{{ c.item_id }}</a> - {{ c.created_at }}
|
|
||||||
</div>
|
|
||||||
<div class="comment-text">
|
|
||||||
{{ c.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endeach
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include(snippets/footer)
|
@include(snippets/footer)
|
||||||
|
<!-- Include local script for this page -->
|
||||||
<script>
|
<script src="/s/js/user_comments.js?v=1"></script>
|
||||||
let page = 1;
|
|
||||||
let loading = false;
|
|
||||||
let finished = false;
|
|
||||||
const user = "{{ user.user }}";
|
|
||||||
const container = document.querySelector('.comments-list-page');
|
|
||||||
|
|
||||||
window.addEventListener('scroll', () => {
|
|
||||||
if (loading || finished) return;
|
|
||||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
|
|
||||||
loadMore();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadMore() {
|
|
||||||
loading = true;
|
|
||||||
page++;
|
|
||||||
|
|
||||||
// Show loading indicator?
|
|
||||||
const loader = document.createElement('div');
|
|
||||||
loader.className = 'loader-placeholder';
|
|
||||||
loader.innerText = 'Loading...';
|
|
||||||
loader.style.textAlign = 'center';
|
|
||||||
loader.style.padding = '10px';
|
|
||||||
container.appendChild(loader);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch('/user/' + encodeURIComponent(user) + '/comments?page=' + page + '&json=true');
|
|
||||||
const json = await res.json();
|
|
||||||
|
|
||||||
loader.remove();
|
|
||||||
|
|
||||||
if (json.success && json.comments.length > 0) {
|
|
||||||
json.comments.forEach(c => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.className = 'user-comment-row';
|
|
||||||
div.style.cssText = 'display: flex; gap: 10px; margin-bottom: 10px; background: rgba(255,255,255,0.05); padding: 10px; border-radius: 4px;';
|
|
||||||
|
|
||||||
let html = '<div class="comment-thumbnail" style="flex-shrink: 0;">';
|
|
||||||
html += '<a href="/' + c.item_id + '#c' + c.id + '">';
|
|
||||||
html += '<img src="/t/' + c.item_id + '.webp" style="width: 80px; height: 80px; object-fit: cover; border-radius: 4px;">';
|
|
||||||
html += '</a></div>';
|
|
||||||
|
|
||||||
html += '<div class="comment-preview" style="flex-grow: 1;">';
|
|
||||||
html += '<div style="font-size: 0.8em; color: #888; margin-bottom: 5px;">';
|
|
||||||
html += 'On <a href="/' + c.item_id + '">Item #' + c.item_id + '</a> - ' + new Date(c.created_at).toLocaleString();
|
|
||||||
html += '</div>';
|
|
||||||
html += '<div class="comment-text">' + c.content + '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
div.innerHTML = html;
|
|
||||||
container.appendChild(div);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
loader.remove();
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
Reference in New Issue
Block a user