// More comments system updates.

This commit is contained in:
flash 2018-12-10 00:56:36 +01:00
parent b4abea015a
commit d3531fc2c1
5 changed files with 159 additions and 150 deletions

View file

@ -21,6 +21,12 @@ function commentsFreeLock(): void
globalCommentLock = false;
}
enum CommentVoteType {
Indifferent = 0,
Like = 1,
Dislike = -1,
}
interface CommentNotice {
error: string;
message: string;
@ -45,6 +51,12 @@ interface CommentPostInfo extends CommentNotice {
user_colour: number;
}
interface CommentVotesInfo extends CommentNotice {
comment_id: number;
likes: number;
dislikes: number;
}
function commentDelete(ev: Event): void
{
if (!checkUserPerm('comments', CommentPermission.Delete) || !commentsRequestLock())
@ -77,19 +89,18 @@ function commentDelete(ev: Event): void
function commentPostEventHandler(ev: Event): void
{
const form: HTMLFormElement = ev.target as HTMLFormElement,
formData: FormData = ExtractFormData(form, true);
const form: HTMLFormElement = ev.target as HTMLFormElement;
commentPost(
formData,
ExtractFormData(form, true),
info => commentPostSuccess(form, info),
message => commentPostFail
commentPostFail
);
}
function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) => void = null, onFail: (message: string) => void = null): void
{
if (!commentsRequestLock())
if (!checkUserPerm('comments', CommentPermission.Create) || !commentsRequestLock())
return;
const xhr = new XMLHttpRequest();
@ -100,6 +111,8 @@ function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) =
commentsFreeLock();
console.log(xhr.getResponseHeader('X-Misuzu-CSRF'));
const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo,
message: string = json.error || json.message;
@ -118,7 +131,7 @@ function commentPostSuccess(form: HTMLFormElement, comment: CommentPostInfo): vo
if (form.classList.contains('comment--reply'))
(form.parentNode.parentNode.querySelector('label.comment__action') as HTMLLabelElement).click();
//commentInsert(info, form);
commentInsert(comment, form);
}
function commentPostFail(message: string): void {
@ -137,6 +150,8 @@ function commentsInit(): void {
const commentInputs: HTMLCollectionOf<HTMLTextAreaElement> = document.getElementsByClassName('comment__text--input') as HTMLCollectionOf<HTMLTextAreaElement>;
for (let i = 0; i < commentInputs.length; i++) {
commentInputs[i].form.action = 'javascript:void(0);';
commentInputs[i].form.addEventListener('submit', commentPostEventHandler);
commentInputs[i].addEventListener('keydown', ev => {
if (ev.keyCode === 13 && ev.ctrlKey && !ev.altKey && !ev.shiftKey) {
let form = commentInputs[i].form;
@ -151,8 +166,6 @@ function commentsInit(): void {
}
function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElement {
const isReply = comment.comment_reply_to > 0;
const commentElement: HTMLDivElement = document.createElement('div');
commentElement.className = 'comment';
commentElement.id = 'comment-' + comment.comment_id;
@ -189,120 +202,94 @@ function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElem
const commentActions = commentContent.appendChild(document.createElement('div'));
commentActions.className = 'comment__actions';
}
function commentInsert(comment, form): void
{
var isReply = form.classList.contains('comment--reply'),
parent = isReply
? form.parentNode
: form.parentNode.parentNode.getElementsByClassName('comments__listing')[0],
repliesIndent = isReply
? (parseInt(parent.classList[1].substr(25)) + 1)
: 1;
// info
var commentUser = document.createElement('a');
const commentUser: HTMLAnchorElement = commentInfo.appendChild(document.createElement('a'));
commentUser.className = 'comment__user comment__user--link';
commentUser.textContent = comment.username;
commentUser.href = '/profile?u=' + comment.user_id;
commentUser.style.color = comment.user_colour == null || (comment.user_colour & 0x40000000) > 0
? 'inherit'
: '#' + (comment.user_colour & 0xFFFFFF).toString(16);
commentInfo.appendChild(commentUser);
var commentLink = document.createElement('a');
const commentLink: HTMLAnchorElement = commentInfo.appendChild(document.createElement('a'));
commentLink.className = 'comment__link';
commentLink.href = '#' + commentElement.id;
commentInfo.appendChild(commentLink);
var commentTime = document.createElement('time'),
const commentTime: HTMLTimeElement = commentLink.appendChild(document.createElement('time')),
commentDate = new Date(comment.comment_created + 'Z');
commentTime.className = 'comment__date';
commentTime.title = commentDate.toLocaleString();
commentTime.dateTime = commentDate.toISOString();
commentTime.textContent = timeago().format(commentDate);
commentLink.appendChild(commentTime);
timeago().render(commentTime);
// actions
if (typeof commentVote === 'function') {
var commentLike = document.createElement('a');
if (checkUserPerm('comments', CommentPermission.Vote)) {
const commentLike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
commentLike.className = 'comment__action comment__action--link comment__action--like';
commentLike.href = 'javascript:void(0);';
commentLike.textContent = 'Like';
commentLike.onclick = commentVote;
commentActions.appendChild(commentLike);
commentLike.addEventListener('click', commentVoteEventHandler);
var commentDislike = document.createElement('a');
const commentDislike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
commentDislike.className = 'comment__action comment__action--link comment__action--dislike';
commentDislike.href = 'javascript:void(0);';
commentDislike.textContent = 'Dislike';
commentDislike.onclick = commentVote;
commentActions.appendChild(commentDislike);
commentLike.addEventListener('click', commentVoteEventHandler);
}
// if we're executing this it's fairly obvious that we can reply,
// so no need to have a permission check on it here
var commentReply = document.createElement('label');
const commentReply: HTMLLabelElement = commentActions.appendChild(document.createElement('label'));
commentReply.className = 'comment__action comment__action--link';
commentReply.htmlFor = 'comment-reply-toggle-' + comment.comment_id;
commentReply.textContent = 'Reply';
commentActions.appendChild(commentReply);
// reply section
var commentReplyState = document.createElement('input');
const commentReplyState: HTMLInputElement = commentReplies.appendChild(document.createElement('input'));
commentReplyState.id = commentReply.htmlFor;
commentReplyState.type = 'checkbox';
commentReplyState.className = 'comment__reply-toggle';
commentReplies.appendChild(commentReplyState);
var commentReplyInput = document.createElement('form');
const commentReplyInput: HTMLFormElement = commentReplies.appendChild(document.createElement('form'));
commentReplyInput.id = 'comment-reply-' + comment.comment_id;
commentReplyInput.className = 'comment comment--input comment--reply';
commentReplyInput.method = 'post';
commentReplyInput.action = 'javascript:void(0);';
commentReplyInput.onsubmit = commentPostEventHandler;
commentReplies.appendChild(commentReplyInput);
commentReplyInput.addEventListener('submit', commentPostEventHandler);
// reply attributes
var replyCategory = document.createElement('input');
const replyCategory: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
replyCategory.name = 'comment[category]';
replyCategory.value = comment.category_id;
replyCategory.value = comment.category_id.toString();
replyCategory.type = 'hidden';
commentReplyInput.appendChild(replyCategory);
var replyCsrf = document.createElement('input');
const replyCsrf: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
replyCsrf.name = 'csrf';
replyCsrf.value = '{{ csrf_token("comments") }}';
replyCsrf.type = 'hidden';
commentReplyInput.appendChild(replyCsrf);
var replyId = document.createElement('input');
const replyId: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
replyId.name = 'comment[reply]';
replyId.value = comment.comment_id;
replyId.value = comment.comment_id.toString();
replyId.type = 'hidden';
commentReplyInput.appendChild(replyId);
var replyContainer = document.createElement('div');
const replyContainer: HTMLDivElement = commentReplyInput.appendChild(document.createElement('div'));
replyContainer.className = 'comment__container';
commentReplyInput.appendChild(replyContainer);
// reply container
var replyAvatar = document.createElement('div');
const replyAvatar: HTMLDivElement = replyContainer.appendChild(document.createElement('div'));
replyAvatar.className = 'avatar comment__avatar';
replyAvatar.style.backgroundImage = 'url(\'/profile.php?m=avatar&u={0}\')'.replace('{0}', comment.user_id);
replyContainer.appendChild(replyAvatar);
replyAvatar.style.backgroundImage = `url('/profile.php?m=avatar&u=${comment.user_id}')`;
var replyContent = document.createElement('div');
const replyContent: HTMLDivElement = replyContainer.appendChild(document.createElement('div'));
replyContent.className = 'comment__content';
replyContainer.appendChild(replyContent);
// reply content
var replyInfo = document.createElement('div');
const replyInfo: HTMLDivElement = replyContent.appendChild(document.createElement('div'));
replyInfo.className = 'comment__info';
replyContent.appendChild(replyInfo);
var replyUser = document.createElement('div');
const replyUser: HTMLDivElement = document.createElement('div');
replyUser.className = 'comment__user';
replyUser.textContent = comment.username;
replyUser.style.color = comment.user_colour == null || (comment.user_colour & 0x40000000) > 0
@ -310,30 +297,72 @@ function commentInsert(comment, form): void
: '#' + (comment.user_colour & 0xFFFFFF).toString(16);
replyInfo.appendChild(replyUser);
var replyText = document.createElement('textarea');
const replyText: HTMLTextAreaElement = replyContent.appendChild(document.createElement('textarea'));
replyText.className = 'comment__text input__textarea comment__text--input';
replyText.name = 'comment[text]';
replyText.placeholder = 'Share your extensive insights...';
replyContent.appendChild(replyText);
var replyActions = document.createElement('div');
const replyActions: HTMLDivElement = replyContent.appendChild(document.createElement('div'));
replyActions.className = 'comment__actions';
replyContent.appendChild(replyActions);
var replyButton = document.createElement('button');
const replyButton: HTMLButtonElement = replyActions.appendChild(document.createElement('button'));
replyButton.className = 'input__button comment__action comment__action--button comment__action--post';
replyButton.textContent = 'Reply';
replyActions.appendChild(replyButton);
return commentElement;
}
function commentInsert(comment: CommentPostInfo, form: HTMLFormElement): void
{
const isReply: boolean = form.classList.contains('comment--reply'),
parent: Element = isReply
? form.parentElement
: form.parentElement.parentElement.getElementsByClassName('comments__listing')[0],
repliesIndent: number = isReply
? (parseInt(parent.classList[1].substr(25)) + 1)
: 1,
commentElement: HTMLElement = commentConstruct(comment, repliesIndent);
if (isReply)
parent.appendChild(commentElement);
else
parent.insertBefore(commentElement, parent.firstElementChild);
timeago().render(commentTime);
var placeholder = document.getElementById('_no_comments_notice_' + comment.category_id);
const placeholder: HTMLElement = document.getElementById('_no_comments_notice_' + comment.category_id);
if (placeholder)
placeholder.parentNode.removeChild(placeholder);
}
function commentVoteEventHandler(ev: Event): void {
//
}
function commentVoteV2(
commentId: number,
vote: CommentVoteType,
onSuccess: (voteInfo: CommentVotesInfo, userVote: CommentVoteType) => void,
onFail: (message: string) => void
): void {
if (!checkUserPerm('comments', CommentPermission.Vote) || !commentsRequestLock())
return;
const xhr: XMLHttpRequest = new XMLHttpRequest;
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4)
return;
commentsFreeLock();
const json: CommentVotesInfo = JSON.parse(xhr.responseText),
message: string = json.error || json.message;
if (message && onFail)
onFail(message);
else if (!message && onSuccess)
onSuccess(json, vote);
};
xhr.open('GET', `/comments.php?m=vote&c=${commentId}&v=${vote}&csrf={{ csrf_token("comments") }}`);
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
xhr.send();
}

View file

@ -23,6 +23,7 @@ if (!user_session_active()) {
return;
}
header(csrf_http_header('comments'));
$commentPerms = comments_get_perms(user_session_current('user_id', 0));
switch ($_GET['m'] ?? null) {

View file

@ -101,17 +101,17 @@ function comments_vote_add(int $comment, int $user, ?string $vote): bool
function comments_votes_get(int $commentId): array
{
$getVotes = db_prepare('
SELECT :id as `id`,
SELECT :id as `comment_id`,
(
SELECT COUNT(`user_id`)
FROM `msz_comments_votes`
WHERE `comment_id` = `id`
WHERE `comment_id` = `comment_id`
AND `comment_vote` = \'Like\'
) as `likes`,
(
SELECT COUNT(`user_id`)
FROM `msz_comments_votes`
WHERE `comment_id` = `id`
WHERE `comment_id` = `comment_id`
AND `comment_vote` = \'Dislike\'
) as `dislikes`
');

View file

@ -112,3 +112,8 @@ function csrf_html(string $realm, string $name = 'csrf'): string
{
return sprintf(MSZ_CSRF_HTML, $name, csrf_token($realm));
}
function csrf_http_header(string $realm, string $name = 'X-Misuzu-CSRF'): string
{
return "{$name}: {$realm};" . csrf_token($realm);
}

View file

@ -182,97 +182,75 @@
<script>
window.addEventListener('load', function () {
if (typeof commentVote === 'function') { // if this exists, the user is allowed to vote
var likeButtons = document.getElementsByClassName('comment__action--like'),
dislikeButtons = document.getElementsByClassName('comment__action--dislike');
var likeButtons = document.getElementsByClassName('comment__action--like'),
dislikeButtons = document.getElementsByClassName('comment__action--dislike');
for (var i = 0; i < likeButtons.length; i++) // there's gonna be an equal amount of like and dislike buttons
{
likeButtons[i].href = 'javascript:void(0);';
likeButtons[i].onclick = commentVote;
dislikeButtons[i].href = 'javascript:void(0);';
dislikeButtons[i].onclick = commentVote;
}
}
if (typeof commentPostEventHandler === 'function') { // can comment
var commentForms = document.getElementsByClassName('comment--input');
for (var i = 0; i < commentForms.length; i++) {
commentForms[i].action = 'javascript:void(0);';
commentForms[i].onsubmit = commentPostEventHandler;
}
for (var i = 0; i < likeButtons.length; i++) // there's gonna be an equal amount of like and dislike buttons
{
likeButtons[i].href = 'javascript:void(0);';
likeButtons[i].onclick = commentVote;
dislikeButtons[i].href = 'javascript:void(0);';
dislikeButtons[i].onclick = commentVote;
}
});
</script>
{% if perms.can_vote %}
<script>
var commentVoteLock = false,
commentLikeClass = 'comment__action--like',
commentDislikeClass = 'comment__action--dislike',
commentVotedClass = 'comment__action--voted',
commentLikeText = 'Like',
commentDislikeText = 'Dislike',
commentVoteCountSuffix = ' ({0})';
var commentVoteLock = false,
commentLikeClass = 'comment__action--like',
commentDislikeClass = 'comment__action--dislike',
commentVotedClass = 'comment__action--voted',
commentLikeText = 'Like',
commentDislikeText = 'Dislike',
commentVoteCountSuffix = ' ({0})';
// DEBUG THIS IF YOU MAKE MAJOR DOM CHANGES TO COMMENTS
function commentVote(ev)
{
var elem = ev.target,
id = elem.parentNode.parentNode.parentNode.parentNode.id.substr(8); // STACK UP
// DEBUG THIS IF YOU MAKE MAJOR DOM CHANGES TO COMMENTS
function commentVote(ev)
{
var elem = ev.target,
id = elem.parentNode.parentNode.parentNode.parentNode.id.substr(8); // STACK UP
// the moment we find the id we engage vote lock
if (id < 1 || commentVoteLock)
return;
commentVoteLock = true;
elem.textContent = '.';
// the moment we find the id we engage vote lock
if (id < 1 || commentVoteLock)
return;
commentVoteLock = true;
elem.textContent = '.';
var isLike = elem.classList.contains(commentLikeClass),
isDislike = elem.classList.contains(commentDislikeClass),
isIndifferent = elem.classList.contains(commentVotedClass),
vote = isIndifferent ? 0 : (isLike ? 1 : -1);
var isLike = elem.classList.contains(commentLikeClass),
isDislike = elem.classList.contains(commentDislikeClass),
isIndifferent = elem.classList.contains(commentVotedClass),
vote = isIndifferent ? 0 : (isLike ? 1 : -1);
elem.textContent += '.';
elem.textContent += '.';
// find friendo (the other vote button), this'll fuck up if the parent element is fucked with
for (var i = 0; i < elem.parentNode.childNodes.length; i++) {
var current = elem.parentNode.childNodes[i];
if (current.nodeName.toLowerCase() === 'a' && current !== elem) {
var friend = current;
break;
}
// find friendo (the other vote button), this'll fuck up if the parent element is fucked with
for (var i = 0; i < elem.parentNode.childNodes.length; i++) {
var current = elem.parentNode.childNodes[i];
if (current.nodeName.toLowerCase() === 'a' && current !== elem) {
var friend = current;
break;
}
}
if (typeof friend !== 'object') {
console.error('something happened');
return;
}
if (typeof friend !== 'object') {
console.error('something happened');
return;
}
friend.classList.remove(commentVotedClass);
friend.classList.remove(commentVotedClass);
friend.textContent = '';
friend.textContent = '';
elem.textContent += '.';
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState !== 4)
return;
elem.textContent += '.';
commentVoteV2(
id, vote,
(vInfo, uVote) => {
if (vote)
elem.classList.add(commentVotedClass);
else
elem.classList.remove(commentVotedClass);
var json = JSON.parse(this.responseText),
message = json.error || json.message;
if (message)
alert(message);
var likes = json.likes || 0,
dislikes = json.dislikes || 0;
var likes = vInfo.likes || 0,
dislikes = vInfo.dislikes || 0;
if (isLike) { // somewhat implicitly defined, like will always come before dislike
elem.textContent = commentLikeText + (likes > 0 ? commentVoteCountSuffix.replace('{0}', likes.toLocaleString()) : '');
@ -281,13 +259,9 @@
elem.textContent = commentDislikeText + (dislikes > 0 ? commentVoteCountSuffix.replace('{0}', dislikes.toLocaleString()) : '');
friend.textContent = commentLikeText + (likes > 0 ? commentVoteCountSuffix.replace('{0}', likes.toLocaleString()) : '');
}
commentVoteLock = false;
};
xhr.open('GET', '/comments.php?m=vote&c={0}&v={1}&csrf={{ csrf_token("comments") }}'.replace('{0}', id).replace('{1}', vote));
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
xhr.send();
}
</script>
{% endif %}
},
alert
);
}
</script>
{% endmacro %}