old one basically bitrotted to death, may it rinse in prosciutto Reviewed-on: #1 Co-authored-by: flashwave <me@flash.moe> Co-committed-by: flashwave <me@flash.moe>
652 lines
23 KiB
JavaScript
652 lines
23 KiB
JavaScript
#include msgbox.jsx
|
|
#include comments/api.js
|
|
#include comments/form.jsx
|
|
|
|
const MszCommentsEntryVoteButton = function(args) {
|
|
const { name, title, icon, vote } = args ?? {};
|
|
|
|
let element, counter;
|
|
const isCast = () => element?.classList.contains('comments-entry-action-vote-cast') === true;
|
|
|
|
element = <button class={`comments-entry-action comments-entry-action-vote-${name}`} onclick={() => { vote(isCast()); }}>
|
|
{icon}
|
|
</button>;
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get disabled() { return element.disabled; },
|
|
set disabled(state) {
|
|
element.disabled = state;
|
|
},
|
|
|
|
get cast() { return isCast(); },
|
|
set cast(state) {
|
|
element.classList.toggle('comments-entry-action-vote-cast', state);
|
|
},
|
|
|
|
get count() { return counter ? parseInt(counter.textContent) : 0; },
|
|
set count(count) {
|
|
if(count > 0) {
|
|
if(!counter)
|
|
element.appendChild(counter = <span />);
|
|
|
|
const formatted = count.toLocaleString();
|
|
counter.textContent = formatted;
|
|
element.title = title.replace('$0', formatted).replace('$s', count === 1 ? '' : 's');
|
|
} else {
|
|
if(counter) {
|
|
element.removeChild(counter);
|
|
counter = undefined;
|
|
}
|
|
|
|
element.title = title.replace('$0', 'No').replace('$s', 's');
|
|
}
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryVoteActions = function(args) {
|
|
const { vote } = args ?? {};
|
|
|
|
const like = new MszCommentsEntryVoteButton({
|
|
name: 'like',
|
|
title: '$0 like$s',
|
|
icon: <i class="fas fa-chevron-up" />,
|
|
vote: cast => { vote(cast ? 0 : 1); }
|
|
});
|
|
const dislike = new MszCommentsEntryVoteButton({
|
|
name: 'dislike',
|
|
title: '$0 dislike$s',
|
|
icon: <i class="fas fa-chevron-down" />,
|
|
vote: cast => { vote(cast ? 0 : -1); }
|
|
});
|
|
|
|
const element = <div class="comments-entry-actions-group comments-entry-actions-group-votes">
|
|
{like}
|
|
{dislike}
|
|
</div>;
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get disabled() { return element.classList.contains('comments-entry-actions-group-disabled'); },
|
|
set disabled(state) {
|
|
element.classList.toggle('comments-entry-actions-group-disabled', state);
|
|
like.disabled = dislike.disabled = state;
|
|
},
|
|
|
|
updateVotes(votes) {
|
|
like.count = votes?.positive ?? 0;
|
|
like.cast = votes?.vote > 0;
|
|
dislike.count = Math.abs(votes?.negative ?? 0);
|
|
dislike.cast = votes?.vote < 0;
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryReplyToggleButton = function(args) {
|
|
const { replies, toggle } = args ?? {};
|
|
|
|
let icon, counter;
|
|
const element = <button class="comments-entry-action" title="No replies" onclick={() => { toggle(); }}>
|
|
{icon = <i class="fas fa-plus" />}
|
|
{counter = <span />}
|
|
</button>;
|
|
|
|
const setVisible = state => {
|
|
element.classList.toggle('hidden', !state);
|
|
};
|
|
const setOpen = state => {
|
|
icon.classList.toggle('fa-plus', !state);
|
|
icon.classList.toggle('fa-minus', state);
|
|
};
|
|
const setCount = count => {
|
|
const formatted = count.toLocaleString();
|
|
counter.textContent = formatted;
|
|
element.title = `${count} ${count === 1 ? 'reply' : 'replies'}`;
|
|
setVisible(count > 0);
|
|
};
|
|
|
|
setCount(Array.isArray(replies) ? replies.length : (replies ?? 0));
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { setVisible(state); },
|
|
|
|
get count() { return parseInt(counter.textContent); },
|
|
set count(count) { setCount(count); },
|
|
|
|
get open() { return element.classList.contains('fa-plus'); },
|
|
set open(state) { setOpen(state); },
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryReplyCreateButton = function(args) {
|
|
const { toggle } = args ?? {};
|
|
|
|
const element = <button class="comments-entry-action" title="Reply" onclick={() => { toggle(); }}>
|
|
<i class="fas fa-reply" />
|
|
<span>Reply</span>
|
|
</button>;
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { element.classList.toggle('hidden', !state); },
|
|
|
|
get active() { return element.classList.contains('comments-entry-action-reply-active'); },
|
|
set active(state) { element.classList.toggle('comments-entry-action-reply-active', state); },
|
|
|
|
click() {
|
|
element.click();
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryReplyActions = function(args) {
|
|
const { replies, toggleReplies, toggleForm } = args ?? {};
|
|
|
|
const toggle = new MszCommentsEntryReplyToggleButton({ replies, toggle: toggleReplies });
|
|
const button = toggleForm ? new MszCommentsEntryReplyCreateButton({ toggle: toggleForm }) : undefined;
|
|
|
|
const element = <div class="comments-entry-actions-group comments-entry-actions-group-replies">
|
|
{toggle}
|
|
{button}
|
|
</div>;
|
|
|
|
const setVisible = state => {
|
|
element.classList.toggle('hidden', !state);
|
|
};
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { setVisible(state); },
|
|
|
|
get toggle() { return toggle; },
|
|
get button() { return button; },
|
|
|
|
updateVisible() {
|
|
setVisible(toggle.visible || button?.visible === true);
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryGeneralButton = function(args) {
|
|
const { icon, title, action } = args ?? {};
|
|
const element = <button class="comments-entry-action" title={title} onclick={() => { action(); }}>{icon}</button>;
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { element.classList.toggle('hidden', !state); },
|
|
|
|
get disabled() { return element.disabled; },
|
|
set disabled(state) { element.disabled = state; },
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryGeneralActions = function(args) {
|
|
let deleteButton, restoreButton, nukeButton, pinButton, unpinButton;
|
|
const element = <div class="comments-entry-actions-group">
|
|
{deleteButton = args.delete ? new MszCommentsEntryGeneralButton({ icon: <i class="fas fa-trash" />, title: 'Delete', action: args.delete }) : null}
|
|
{restoreButton = args.restore ? new MszCommentsEntryGeneralButton({ icon: <i class="fas fa-trash-restore" />, title: 'Restore', action: args.restore }) : null}
|
|
{nukeButton = args.nuke ? new MszCommentsEntryGeneralButton({ icon: <i class="fas fa-radiation-alt" />, title: 'Permanently delete', action: args.nuke }) : null}
|
|
{pinButton = args.pin ? new MszCommentsEntryGeneralButton({ icon: <i class="fas fa-thumbtack" />, title: 'Pin', action: args.pin }) : null}
|
|
{unpinButton = args.unpin ? new MszCommentsEntryGeneralButton({ icon: <i class="fas fa-screwdriver" />, title: 'Unpin', action: args.unpin }) : null}
|
|
</div>;
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { element.classList.toggle('hidden', !state); },
|
|
|
|
get disabled() { return element.classList.contains('comments-entry-actions-group-disabled'); },
|
|
set disabled(state) {
|
|
element.classList.toggle('comments-entry-actions-group-disabled', state);
|
|
if(deleteButton)
|
|
deleteButton.disabled = state;
|
|
if(restoreButton)
|
|
restoreButton.disabled = state;
|
|
if(nukeButton)
|
|
nukeButton.disabled = state;
|
|
if(pinButton)
|
|
pinButton.disabled = state;
|
|
if(unpinButton)
|
|
unpinButton.disabled = state;
|
|
},
|
|
|
|
get deleteButton() { return deleteButton; },
|
|
get restoreButton() { return restoreButton; },
|
|
get nukeButton() { return nukeButton; },
|
|
|
|
get deleteVisible() { return deleteButton?.visible === true; },
|
|
set deleteVisible(state) {
|
|
if(deleteButton)
|
|
deleteButton.visible = state;
|
|
if(restoreButton)
|
|
restoreButton.visible = !state;
|
|
if(nukeButton)
|
|
nukeButton.visible = !state;
|
|
},
|
|
|
|
get pinButton() { return pinButton; },
|
|
get unpinButton() { return unpinButton; },
|
|
|
|
get pinVisible() { return pinButton?.visible === true; },
|
|
set pinVisible(state) {
|
|
if(pinButton)
|
|
pinButton.visible = state;
|
|
if(unpinButton)
|
|
unpinButton.visible = !state;
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntryActions = function() {
|
|
const element = <div class="comments-entry-actions hidden" />;
|
|
|
|
const hideIfNoChildren = () => {
|
|
element.classList.toggle('hidden', element.childElementCount < 1);
|
|
};
|
|
hideIfNoChildren();
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
appendGroup(group) {
|
|
element.appendChild(group.element);
|
|
hideIfNoChildren();
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsEntry = function(catInfo, userInfo, postInfo, listing, root) {
|
|
const actions = new MszCommentsEntryActions;
|
|
|
|
const voteActions = new MszCommentsEntryVoteActions({
|
|
vote: async vote => {
|
|
if(voteActions.disabled)
|
|
return;
|
|
|
|
voteActions.disabled = true;
|
|
try {
|
|
voteActions.updateVotes(vote === 0
|
|
? await MszCommentsApi.deleteVote(postInfo.id)
|
|
: await MszCommentsApi.createVote(postInfo.id, vote));
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
} finally {
|
|
enableVoteActionsMaybe();
|
|
}
|
|
}
|
|
});
|
|
actions.appendGroup(voteActions);
|
|
|
|
const enableVoteActionsMaybe = () => {
|
|
voteActions.disabled = !userInfo.can_vote || !!postInfo.deleted || !!catInfo.locked;
|
|
};
|
|
|
|
enableVoteActionsMaybe();
|
|
voteActions.updateVotes(postInfo);
|
|
|
|
const repliesIsArray = Array.isArray(postInfo.replies);
|
|
const replies = new MszCommentsListing({ hidden: !repliesIsArray });
|
|
if(repliesIsArray)
|
|
replies.addPosts(catInfo, userInfo, postInfo.replies);
|
|
|
|
const repliesElem = <div class="comments-entry-replies">
|
|
{replies}
|
|
</div>;
|
|
|
|
let replyForm;
|
|
let repliesLoaded = replies.loaded;
|
|
const replyActions = new MszCommentsEntryReplyActions({
|
|
replies: postInfo.replies,
|
|
toggleReplies: async () => {
|
|
replyActions.toggle.open = replies.visible = !replies.visible;
|
|
if(!repliesLoaded) {
|
|
repliesLoaded = true;
|
|
try {
|
|
replies.addPosts(catInfo, userInfo, await MszCommentsApi.getPostReplies(postInfo.id));
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
replyActions.toggle.open = false;
|
|
repliesLoaded = false;
|
|
}
|
|
}
|
|
},
|
|
toggleForm: userInfo.can_create ? () => {
|
|
if(replyForm) {
|
|
replyActions.button.active = false;
|
|
repliesElem.removeChild(replyForm.element);
|
|
replyForm = null;
|
|
} else {
|
|
replyActions.button.active = true;
|
|
replyForm = new MszCommentsForm({
|
|
userInfo, catInfo, postInfo,
|
|
listing: replies,
|
|
repliesToggle: replyActions.toggle,
|
|
replyToggle: replyActions.button,
|
|
});
|
|
$insertBefore(replies.element, replyForm.element);
|
|
replyForm.focus();
|
|
}
|
|
} : null,
|
|
});
|
|
actions.appendGroup(replyActions);
|
|
|
|
const enableReplyButtonMaybe = () => {
|
|
if(replyActions.button)
|
|
replyActions.button.visible = !catInfo.locked && !postInfo.deleted;
|
|
replyActions.updateVisible();
|
|
};
|
|
|
|
replyActions.toggle.open = replies.visible;
|
|
enableReplyButtonMaybe();
|
|
|
|
const generalActions = new MszCommentsEntryGeneralActions({
|
|
delete: postInfo.can_delete ? async () => {
|
|
generalActions.disabled = true;
|
|
try {
|
|
if(!await MszShowConfirmBox(`Are you sure you want to delete comment #${postInfo.id}?`, 'Deleting a comment'))
|
|
return;
|
|
|
|
postInfo.deleted = new Date;
|
|
await MszCommentsApi.deletePost(postInfo.id);
|
|
if(generalActions.restoreButton) {
|
|
setOptionalTime(deletedElem, new Date, 'commentDeleted');
|
|
generalActions.deleteVisible = false;
|
|
enableVoteActionsMaybe();
|
|
enableReplyButtonMaybe();
|
|
listing.reorder();
|
|
} else
|
|
nukeThePost();
|
|
} catch(ex) {
|
|
delete postInfo.deleted;
|
|
console.error(ex);
|
|
} finally {
|
|
generalActions.disabled = false;
|
|
}
|
|
} : null,
|
|
restore: postInfo.can_delete_any ? async () => {
|
|
generalActions.disabled = true;
|
|
const deleted = postInfo.deleted;
|
|
try {
|
|
delete postInfo.deleted;
|
|
await MszCommentsApi.restorePost(postInfo.id);
|
|
setOptionalTime(deletedElem, null, 'commentDeleted');
|
|
generalActions.deleteVisible = true;
|
|
enableVoteActionsMaybe();
|
|
enableReplyButtonMaybe();
|
|
listing.reorder();
|
|
} catch(ex) {
|
|
postInfo.deleted = deleted;
|
|
console.error(ex);
|
|
} finally {
|
|
generalActions.disabled = false;
|
|
}
|
|
} : null,
|
|
nuke: postInfo.can_delete_any ? async () => {
|
|
generalActions.disabled = true;
|
|
try {
|
|
await MszCommentsApi.nukePost(postInfo.id);
|
|
nukeThePost();
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
} finally {
|
|
generalActions.disabled = false;
|
|
}
|
|
} : null,
|
|
pin: root && userInfo.can_pin ? async () => {
|
|
generalActions.disabled = true;
|
|
try {
|
|
if(!await MszShowConfirmBox(`Are you sure you want to pin comment #${postInfo.id}?`, 'Pinning a comment'))
|
|
return;
|
|
|
|
const result = await MszCommentsApi.updatePost(postInfo.id, { pin: 'on' });
|
|
generalActions.pinVisible = !result.pinned;
|
|
setOptionalTime(pinnedElem, result.pinned, 'commentPinned');
|
|
listing.reorder();
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
} finally {
|
|
generalActions.disabled = false;
|
|
}
|
|
} : null,
|
|
unpin: root && userInfo.can_pin ? async () => {
|
|
generalActions.disabled = true;
|
|
try {
|
|
const result = await MszCommentsApi.updatePost(postInfo.id, { pin: '' });
|
|
generalActions.pinVisible = !result.pinned;
|
|
setOptionalTime(pinnedElem, result.pinned, 'commentPinned');
|
|
listing.reorder();
|
|
} catch(ex) {
|
|
console.error(ex);
|
|
} finally {
|
|
generalActions.disabled = false;
|
|
}
|
|
} : null,
|
|
});
|
|
actions.appendGroup(generalActions);
|
|
|
|
generalActions.deleteVisible = !postInfo.deleted;
|
|
generalActions.pinVisible = !postInfo.pinned;
|
|
|
|
const userAvatarElem = <img alt="" width="40" height="40" class="avatar" />;
|
|
const userNameElem = <div class="comments-entry-user" />;
|
|
|
|
const createdTimeElem = <a href={`#comment-${postInfo.id}`} class="comments-entry-time-link" />;
|
|
const editedElem = <div class="comments-entry-time comments-entry-time-edited">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-pencil-alt" /></div>
|
|
</div>;
|
|
const pinnedElem = <div class="comments-entry-time comments-entry-time-pinned">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-thumbtack" /></div>
|
|
</div>;
|
|
const deletedElem = <div class="comments-entry-time comments-entry-time-deleted">
|
|
<div class="comments-entry-time-icon"><i class="fas fa-trash" /></div>
|
|
</div>;
|
|
|
|
const bodyElem = <div class="comments-entry-body" />;
|
|
const setBody = body => { bodyElem.textContent = body ?? '[deleted]'; };
|
|
setBody(postInfo?.body);
|
|
|
|
const element = <div id={`comment-${postInfo.id}`} data-comment={postInfo.id} class={{ 'comments-entry': true, 'comments-entry-root': root }}>
|
|
<div class="comments-entry-main">
|
|
<div class="comments-entry-avatar">
|
|
{userAvatarElem}
|
|
</div>
|
|
<div class="comments-entry-wrap">
|
|
<div class="comments-entry-meta">
|
|
<div class="comments-entry-user">
|
|
{userNameElem}
|
|
</div>
|
|
<div class="comments-entry-time">
|
|
<div class="comments-entry-time-icon">—</div>
|
|
{createdTimeElem}
|
|
</div>
|
|
{editedElem}
|
|
{pinnedElem}
|
|
{deletedElem}
|
|
</div>
|
|
{bodyElem}
|
|
{actions}
|
|
</div>
|
|
</div>
|
|
{repliesElem}
|
|
</div>;
|
|
|
|
const setUserInfo = userInfo => {
|
|
$removeChildren(userNameElem);
|
|
if(userInfo) {
|
|
if(typeof userInfo.colour === 'string')
|
|
element.style.setProperty('--user-colour', userInfo.colour);
|
|
userAvatarElem.src = userInfo.avatar;
|
|
userNameElem.appendChild(<a class="comments-entry-user-link" href={userInfo.profile} style="color: var(--user-colour);">{userInfo.name}</a>);
|
|
} else {
|
|
element.style.removeProperty('--user-colour');
|
|
userAvatarElem.src = '/images/no-avatar.png';
|
|
userNameElem.appendChild(<span class="comments-entry-user-dead">Deleted user</span>);
|
|
}
|
|
};
|
|
setUserInfo(postInfo.user);
|
|
|
|
const setCreatedTime = date => {
|
|
if(typeof date === 'string')
|
|
date = new Date(date);
|
|
|
|
$removeChildren(createdTimeElem);
|
|
element.dataset.commentCreated = date.getTime();
|
|
const time = <time class="comments-entry-time-text" datetime={date.toISOString()} title={date.toString()}>{MszSakuya.formatTimeAgo(date)}</time>;
|
|
createdTimeElem.appendChild(time);
|
|
MszSakuya.trackElement(time);
|
|
};
|
|
setCreatedTime(postInfo.created);
|
|
|
|
const updateOrderValue = () => {
|
|
let order = parseInt(element.dataset.commentCreated ?? 0);
|
|
|
|
if(element.dataset.commentDeleted !== undefined)
|
|
order -= parseInt(element.dataset.commentDeleted);
|
|
else if(element.dataset.commentPinned !== undefined)
|
|
order += parseInt(element.dataset.commentPinned);
|
|
|
|
element.dataset.commentOrder = order;
|
|
};
|
|
|
|
const setOptionalTime = (elem, date, name, reorder=true, textIfTrue=null) => {
|
|
if(typeof date === 'string')
|
|
date = new Date(date);
|
|
|
|
while(!(elem.lastChild instanceof HTMLDivElement))
|
|
elem.removeChild(elem.lastChild);
|
|
|
|
if(date) {
|
|
if(date instanceof Date) {
|
|
if(name)
|
|
element.dataset[name] = date.getTime();
|
|
|
|
const timeElem = <time class="comments-entry-time-text" datetime={date.toISOString()} title={date.toString()}>{MszSakuya.formatTimeAgo(date)}</time>
|
|
elem.appendChild(timeElem);
|
|
MszSakuya.trackElement(timeElem);
|
|
} else {
|
|
// this is kiiiind of a hack but commentCreated isn't updated through this function so who cares lol !
|
|
if(name)
|
|
element.dataset[name] = element.dataset.commentCreated;
|
|
|
|
if(typeof textIfTrue === 'string')
|
|
elem.appendChild(<span>{textIfTrue}</span>);
|
|
}
|
|
|
|
elem.classList.remove('hidden');
|
|
} else {
|
|
if(name)
|
|
delete element.dataset[name];
|
|
elem.classList.add('hidden');
|
|
}
|
|
|
|
if(reorder)
|
|
updateOrderValue();
|
|
};
|
|
|
|
setOptionalTime(editedElem, postInfo.edited, 'commentEdited', false);
|
|
setOptionalTime(pinnedElem, postInfo.pinned, 'commentPinned', false);
|
|
setOptionalTime(deletedElem, postInfo.deleted, 'commentDeleted', false, 'deleted');
|
|
updateOrderValue();
|
|
|
|
const nukeThePost = () => {
|
|
if(replies.count < 1 && replyActions.toggle.count < 1)
|
|
listing.element.removeChild(element);
|
|
else {
|
|
generalActions.visible = false;
|
|
generalActions.disabled = true;
|
|
enableVoteActionsMaybe();
|
|
enableReplyButtonMaybe();
|
|
setUserInfo(null);
|
|
setBody(null);
|
|
setOptionalTime(deletedElem, true, 'commentDeleted', true, 'deleted');
|
|
listing.reorder();
|
|
}
|
|
};
|
|
|
|
return {
|
|
get element() { return element; },
|
|
|
|
updateLocked() {
|
|
enableVoteActionsMaybe();
|
|
enableReplyButtonMaybe();
|
|
replies.updateLocked();
|
|
},
|
|
};
|
|
};
|
|
|
|
const MszCommentsListing = function(options) {
|
|
let { hidden, root } = options ?? {};
|
|
|
|
let loading; // intentionally left as undefined here so the === null is still false
|
|
const entries = new Map;
|
|
const element = <div class={{ 'comments-listing': true, 'comments-listing-root': root, 'hidden': hidden }} />;
|
|
|
|
const pub = {
|
|
get element() { return element; },
|
|
get count() { return loading === null ? element.childElementCount : 0; },
|
|
|
|
get visible() { return !element.classList.contains('hidden'); },
|
|
set visible(state) { element.classList.toggle('hidden', !state); },
|
|
|
|
get loaded() { return loading === null; },
|
|
|
|
reset() {
|
|
entries.clear();
|
|
$removeChildren(element);
|
|
loading = new MszLoading;
|
|
element.appendChild(loading.element);
|
|
},
|
|
removeLoading() {
|
|
loading?.element.remove();
|
|
loading = null;
|
|
},
|
|
|
|
reorder() {
|
|
// this feels yucky but it works
|
|
const items = Array.from(element.children).sort((a, b) => parseInt(b.dataset.commentOrder) - parseInt(a.dataset.commentOrder));
|
|
for(const item of items)
|
|
element.appendChild(item);
|
|
},
|
|
updateLocked() {
|
|
for(const [, value] of entries)
|
|
value.updateLocked();
|
|
},
|
|
|
|
addPost(catInfo, userInfo, postInfo) {
|
|
const existing = element.querySelector(`[data-comment="${postInfo.id}"]`);
|
|
if(existing)
|
|
element.removeChild(existing);
|
|
|
|
const entry = new MszCommentsEntry(catInfo ?? {}, userInfo ?? {}, postInfo, pub, root);
|
|
entries.set(postInfo.id, entry);
|
|
element.appendChild(entry.element);
|
|
},
|
|
addPosts(catInfo, userInfo, posts) {
|
|
try {
|
|
if(!Array.isArray(posts))
|
|
throw 'posts must be an array';
|
|
catInfo ??= {};
|
|
userInfo ??= {};
|
|
for(const postInfo of posts)
|
|
pub.addPost(catInfo, userInfo, postInfo);
|
|
} finally {
|
|
pub.removeLoading();
|
|
}
|
|
},
|
|
};
|
|
|
|
return pub;
|
|
};
|