You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
8.6 KiB

// site-comments.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
import DtpLog from 'dtp/dtp-log.js';
import UIkit from 'uikit';
import * as picmo from 'picmo';
export default class SiteComments {
constructor (app) {
this.app = app;
this.log = new DtpLog({ logId: 'site-comments', index: 'siteComments', className: 'SiteComments' });
this.createEmojiPickers();
}
createEmojiPickers ( ) {
const pickerContainers = document.querySelectorAll('li.comment-emoji-picker:not([data-initialized])');
for (const container of pickerContainers) {
const picker = { };
picker.drop = container.querySelector('.comment-emoji-picker-drop');
picker.ui = picker.drop.querySelector('.comment-emoji-picker-ui');
const formId = picker.drop.getAttribute('data-form-id');
picker.form = document.querySelector(`form#${formId}`);
picker.input = picker.form.querySelector(`textarea[data-form-id=${formId}]`);
picker.characterCount = picker.form.querySelector('span.comment-character-count');
picker.picmo = picmo.createPicker({
emojisPerRow: 7,
rootElement: picker.ui,
theme: picmo.darkTheme,
});
picker.picmo.addEventListener('emoji:select', this.onEmojiSelected.bind(this, picker));
picker.emojiPickerDrop = UIkit.drop(picker.drop);
UIkit.util.on(picker.drop, 'show', ( ) => {
this.log.info('SiteComments', 'showing emoji picker');
picker.picmo.reset();
});
container.setAttribute('data-initialized', true);
}
}
async onCommentInput (event) {
const target = event.currentTarget || event.target;
const formId = target.getAttribute('data-form-id');
if (!formId) { return; }
const form = document.getElementById(formId);
if (!form) { return; }
const label = form.querySelector('span.comment-character-count');
if (!label) { return; }
label.textContent = numeral(event.target.value.charCount()).format('0,0');
}
async showReportCommentForm (event) {
event.preventDefault();
event.stopPropagation();
const resourceType = event.currentTarget.getAttribute('data-resource-type');
const resourceId = event.currentTarget.getAttribute('data-resource-id');
const commentId = event.currentTarget.getAttribute('data-comment-id');
this.closeCommentDropdownMenu(commentId);
try {
const response = await fetch('/content-report/comment/form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
resourceType, resourceId, commentId
}),
});
if (!response.ok) {
throw new Error('failed to load report form');
}
const html = await response.text();
this.currentDialog = UIkit.modal.dialog(html);
} catch (error) {
this.log.error('reportComment', 'failed to process comment request', { resourceType, resourceId, commentId, error });
UIkit.modal.alert(`Failed to report comment: ${error.message}`);
}
return true;
}
async deleteComment (event) {
event.preventDefault();
event.stopPropagation();
const commentId = (event.currentTarget || event.target).getAttribute('data-comment-id');
try {
const response = fetch(`/comment/${commentId}`, { method: 'DELETE' });
if (!response.ok) {
throw new Error('Server error');
}
this.app.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to delete comment: ${error.message}`);
}
}
async blockCommentAuthor (event) {
event.preventDefault();
event.stopPropagation();
const resourceType = event.currentTarget.getAttribute('data-resource-type');
const resourceId = event.currentTarget.getAttribute('data-resource-id');
const commentId = event.currentTarget.getAttribute('data-comment-id');
const actionUrl = this.getCommentActionUrl(resourceType, resourceId, commentId, 'block-author');
this.closeCommentDropdownMenu(commentId);
try {
this.log.info('blockCommentAuthor', 'blocking comment author', { resourceType, resourceId, commentId });
const response = await fetch(actionUrl, { method: 'POST'});
await this.app.processResponse(response);
} catch (error) {
this.log.error('reportComment', 'failed to process comment request', { resourceType, resourceId, commentId, error });
UIkit.modal.alert(`Failed to block comment author: ${error.message}`);
}
return true;
}
closeCommentDropdownMenu (commentId) {
const dropdown = document.querySelector(`[data-comment-id="${commentId}"][uk-dropdown]`);
UIkit.dropdown(dropdown).hide(false);
}
getCommentActionUrl (resourceType, resourceId, commentId, action) {
switch (resourceType) {
case 'Newsletter':
return `/newsletter/${resourceId}/comment/${commentId}/${action}`;
case 'Page':
return `/page/${resourceId}/comment/${commentId}/${action}`;
case 'Post':
return `/post/${resourceId}/comment/${commentId}/${action}`;
default:
break;
}
throw new Error('Invalid resource type for comment operation');
}
async submitCommentVote (event) {
const target = (event.currentTarget || event.target);
const commentId = target.getAttribute('data-comment-id');
const vote = target.getAttribute('data-vote');
try {
const response = await fetch(`/comment/${commentId}/vote`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ vote }),
});
await this.app.processResponse(response);
} catch (error) {
UIkit.modal.alert(`Failed to submit vote: ${error.message}`);
}
}
async openReplies (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const commentId = target.getAttribute('data-comment-id');
const container = document.querySelector(`.dtp-reply-list-container[data-comment-id="${commentId}"]`);
const replyList = document.querySelector(`ul.dtp-reply-list[data-comment-id="${commentId}"]`);
const isOpen = !container.hasAttribute('hidden');
if (isOpen) {
container.setAttribute('hidden', '');
while (replyList.firstChild) {
replyList.removeChild(replyList.firstChild);
}
return;
}
try {
const response = await fetch(`/comment/${commentId}/replies`);
this.app.processResponse(response);
this.createEmojiPickers();
} catch (error) {
UIkit.modal.alert(`Failed to load replies: ${error.message}`);
}
return true;
}
async openReplyComposer (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const commentId = target.getAttribute('data-comment-id');
const composer = document.querySelector(`.dtp-reply-composer[data-comment-id="${commentId}"]`);
composer.toggleAttribute('hidden');
return true;
}
async loadMoreComments (event) {
event.preventDefault();
event.stopPropagation();
const target = event.currentTarget || event.target;
const buttonId = target.getAttribute('data-button-id');
const rootUrl = target.getAttribute('data-root-url');
const nextPage = target.getAttribute('data-next-page');
try {
const response = await fetch(`${rootUrl}?p=${nextPage}&buttonId=${buttonId}`);
await this.app.processResponse(response);
this.createEmojiPickers();
} catch (error) {
UIkit.modal.alert(`Failed to load more comments: ${error.message}`);
}
}
async onEmojiSelected (picker, event) {
picker.emojiPickerDrop.hide(false);
await this.insertContentAtCursor(picker, event.emoji);
picker.characterCount.textContent = numeral(picker.input.value.charCount()).format('0,0');
}
async insertContentAtCursor (picker, content) {
picker.input.focus();
if (document.selection) {
let sel = document.selection.createRange();
sel.text = content;
} else if (picker.input.selectionStart || (picker.input.selectionStart === 0)) {
let startPos = picker.input.selectionStart;
let endPos = picker.input.selectionEnd;
let oldLength = picker.input.value.length;
picker.input.value =
picker.input.value.substring(0, startPos) +
content +
picker.input.value.substring(endPos, picker.input.value.length);
picker.input.selectionStart = startPos + (picker.input.value.length - oldLength);
picker.input.selectionEnd = picker.input.selectionStart;
} else {
picker.input.value += content;
}
}
}