From d3531fc2c194aaf7a76f9ead6fa701d4f19a6014 Mon Sep 17 00:00:00 2001 From: flashwave Date: Mon, 10 Dec 2018 00:56:36 +0100 Subject: [PATCH] // More comments system updates. --- assets/typescript/Comments.ts | 163 +++++++++++++++++++------------- public/comments.php | 1 + src/comments.php | 6 +- src/csrf.php | 5 + templates/_layout/comments.twig | 134 +++++++++++--------------- 5 files changed, 159 insertions(+), 150 deletions(-) diff --git a/assets/typescript/Comments.ts b/assets/typescript/Comments.ts index c2835821..cc88b8a2 100644 --- a/assets/typescript/Comments.ts +++ b/assets/typescript/Comments.ts @@ -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 = document.getElementsByClassName('comment__text--input') as HTMLCollectionOf; 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(); +} diff --git a/public/comments.php b/public/comments.php index 0b40d91b..4fe58ea3 100644 --- a/public/comments.php +++ b/public/comments.php @@ -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) { diff --git a/src/comments.php b/src/comments.php index 5f26e4d8..79c144fd 100644 --- a/src/comments.php +++ b/src/comments.php @@ -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` '); diff --git a/src/csrf.php b/src/csrf.php index e95ccd7a..31070af7 100644 --- a/src/csrf.php +++ b/src/csrf.php @@ -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); +} diff --git a/templates/_layout/comments.twig b/templates/_layout/comments.twig index 57ba1a3b..c57765cb 100644 --- a/templates/_layout/comments.twig +++ b/templates/_layout/comments.twig @@ -182,97 +182,75 @@ - {% if perms.can_vote %} - - {% endif %} + }, + alert + ); + } + {% endmacro %}