160 lines
7.4 KiB
JavaScript
160 lines
7.4 KiB
JavaScript
#include comments/api.js
|
|
#include comments/form.jsx
|
|
|
|
const MszCommentsEntry = function(userInfo, postInfo, root) {
|
|
userInfo ??= {};
|
|
|
|
const actions = <div class="comments-entry-actions" />;
|
|
actions.appendChild(<div class="comments-entry-actions-group comments-entry-actions-group-votes">
|
|
<button class={{ 'comments-entry-action': true, 'comments-entry-action-vote-like': true, 'comments-entry-action-vote-cast': postInfo.vote > 0 }} disabled={!userInfo.can_vote}>
|
|
<i class="fas fa-chevron-up" />
|
|
{postInfo.positive > 0 ? <span>{postInfo.positive.toLocaleString()}</span> : null}
|
|
</button>
|
|
<button class={{ 'comments-entry-action': true, 'comments-entry-action-vote-dislike': true, 'comments-entry-action-vote-cast': postInfo.vote < 0 }} disabled={!userInfo.can_vote}>
|
|
<i class="fas fa-chevron-down" />
|
|
{postInfo.negative < 0 ? <span>{Math.abs(postInfo.negative).toLocaleString()}</span> : null}
|
|
</button>
|
|
</div>);
|
|
|
|
const repliesIsArray = Array.isArray(postInfo.replies);
|
|
const form = userInfo?.can_create ? new MszCommentsForm(userInfo) : null;
|
|
const listing = new MszCommentsListing({ hidden: !repliesIsArray });
|
|
if(repliesIsArray)
|
|
listing.addPosts(userInfo, postInfo.replies);
|
|
|
|
const replyCount = repliesIsArray ? postInfo.replies.length : (postInfo.replies ?? 0);
|
|
if(replyCount > 0 || userInfo.can_create) {
|
|
const replyElem = <button class={{ 'comments-entry-action': true, 'comments-entry-action-replies-open': listing.visible }}>
|
|
<i class="fas fa-reply" />
|
|
{replyCount > 0 ? <span>{replyCount.toLocaleString()}</span> : null}
|
|
</button>;
|
|
|
|
let loaded = listing.loaded;
|
|
replyElem.onclick = async () => {
|
|
replyElem.classList.toggle('comments-entry-action-replies-open', listing.visible = !listing.visible);
|
|
if(!loaded) {
|
|
loaded = true;
|
|
try {
|
|
listing.addPosts(userInfo, await MszCommentsApi.getPostReplies(postInfo.id));
|
|
} catch(ex) {
|
|
loaded = false;
|
|
console.error(ex);
|
|
|
|
// THIS IS NOT FINAL DO NOT PUSH THIS TO PUBLIC THIS WOULD BE HORRIBLE
|
|
if(typeof ex === 'string')
|
|
MszShowMessageBox(ex);
|
|
}
|
|
}
|
|
};
|
|
|
|
actions.appendChild(<div class="comments-entry-actions-group comments-entry-actions-group-replies">
|
|
{replyElem}
|
|
</div>);
|
|
}
|
|
|
|
if(postInfo.can_delete || userInfo.can_pin) {
|
|
const misc = <div class="comments-entry-actions-group" />;
|
|
if(postInfo.can_delete)
|
|
misc.appendChild(<button class="comments-entry-action">
|
|
<i class="fas fa-trash" />
|
|
</button>);
|
|
if(userInfo.can_pin)
|
|
misc.appendChild(<button class="comments-entry-action" disabled={!userInfo.can_pin}>
|
|
<i class="fas fa-thumbtack" />
|
|
</button>);
|
|
actions.appendChild(misc);
|
|
}
|
|
|
|
const created = new Date(postInfo.created);
|
|
const edited = postInfo.edited ? new Date(postInfo.edited) : null;
|
|
const deleted = postInfo.deleted ? new Date(postInfo.deleted) : null;
|
|
const pinned = postInfo.pinned ? new Date(postInfo.pinned) : null;
|
|
|
|
const element = <div id={`comment-${postInfo.id}`} data-comment={postInfo.id} class={{ 'comments-entry': true, 'comments-entry-root': root, 'comments-entry-deleted': postInfo.deleted }} style={{ '--user-colour': postInfo.user?.colour }}>
|
|
<div class="comments-entry-main">
|
|
<div class="comments-entry-avatar">
|
|
<img src={postInfo.user?.avatar ?? '/images/no-avatar.png'} alt="" width="40" height="40" class="avatar" />
|
|
</div>
|
|
<div class="comments-entry-wrap">
|
|
<div class="comments-entry-meta">
|
|
<div class="comments-entry-user">
|
|
{postInfo.user
|
|
? <a class="comments-entry-user-link" href={postInfo.user.profile} style="color: var(--user-colour);">{postInfo.user.name}</a>
|
|
: <span class="comments-entry-user-dead">Deleted user</span>}
|
|
</div>
|
|
<div class="comments-entry-time">
|
|
<div class="comments-entry-time-icon">—</div>
|
|
<a href={`#comment-${postInfo.id}`} class="comments-entry-time-link">
|
|
<time class="comments-entry-time-text" datetime={created.toISOString()} title={created.toString()}>{MszSakuya.formatTimeAgo(created)}</time>
|
|
</a>
|
|
</div>
|
|
{edited !== null ? <div class="comments-entry-time comments-entry-time-edited">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-pencil-alt" /></div>
|
|
<time class="comments-entry-time-text" datetime={edited.toISOString()} title={edited.toString()}>{MszSakuya.formatTimeAgo(edited)}</time>
|
|
</div> : null}
|
|
{pinned !== null ? <div class="comments-entry-time comments-entry-time-pinned">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-thumbtack" /></div>
|
|
<time class="comments-entry-time-text" datetime={pinned.toISOString()} title={pinned.toString()}>{MszSakuya.formatTimeAgo(pinned)}</time>
|
|
</div> : null}
|
|
{deleted !== null ? <div class="comments-entry-time comments-entry-time-deleted">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-trash" /></div>
|
|
<time class="comments-entry-time-text" datetime={deleted.toISOString()} title={deleted.toString()}>{MszSakuya.formatTimeAgo(deleted)}</time>
|
|
</div> : null}
|
|
</div>
|
|
<div class="comments-entry-body">{postInfo.body}</div>
|
|
{actions.childElementCount > 0 ? actions : null}
|
|
</div>
|
|
</div>
|
|
<div class="comments-entry-replies">
|
|
{form}
|
|
{listing}
|
|
</div>
|
|
</div>;
|
|
|
|
MszSakuya.trackElements(element.querySelectorAll('time'));
|
|
|
|
return {
|
|
get element() {
|
|
return element;
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsListing = function(options) {
|
|
let { hidden, root } = options ?? {};
|
|
|
|
let loading = new MszLoading;
|
|
const entries = new Map;
|
|
const element = <div class={{ 'comments-listing': true, 'hidden': hidden }}>
|
|
{loading}
|
|
</div>;
|
|
|
|
const addPost = function(userInfo, postInfo, parentId=null) {
|
|
const entry = new MszCommentsEntry(userInfo ?? {}, postInfo, root);
|
|
entries.set(postInfo.id, entry);
|
|
element.appendChild(entry.element);
|
|
};
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { element.classList.toggle('hidden', !state); },
|
|
|
|
get loaded() { return loading === null; },
|
|
|
|
addPost: addPost,
|
|
addPosts: function(userInfo, posts) {
|
|
try {
|
|
if(!Array.isArray(posts))
|
|
throw 'posts must be an array';
|
|
userInfo ??= {};
|
|
for(const postInfo of posts)
|
|
addPost(userInfo, postInfo);
|
|
} finally {
|
|
loading.element.remove();
|
|
loading = null;
|
|
}
|
|
},
|
|
};
|
|
};
|