/// let globalCommentLock = false; function commentsLocked(): boolean { return globalCommentLock; } function commentsRequestLock(): boolean { if (commentsLocked()) return false; return globalCommentLock = true; } function commentsFreeLock(): void { globalCommentLock = false; } interface CommentNotice { error: string; message: string; } interface CommentDeletionInfo extends CommentNotice { comment_id: number; } interface CommentPostInfo extends CommentNotice { comment_id: number; category_id: number; comment_text: string; comment_html: string; comment_created: Date; comment_edited: Date | null; comment_deleted: Date | null; comment_reply_to: number; comment_pinned: Date | null; user_id: number; username: string; user_colour: number; } function commentDelete(ev: Event): void { if (!checkUserPerm('comments', CommentPermission.Delete) || !commentsRequestLock()) return; const xhr: XMLHttpRequest = new XMLHttpRequest(), target: HTMLAnchorElement = ev.target as HTMLAnchorElement; xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; commentsFreeLock(); let json: CommentDeletionInfo = JSON.parse(xhr.responseText) as CommentDeletionInfo, message = json.error || json.message; if (message) alert(message); else { let elem = document.getElementById('comment-' + json.comment_id); if (elem) elem.parentNode.removeChild(elem); } }); xhr.open('GET', target.dataset.href); xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); xhr.send(); } function commentPostEventHandler(ev: Event): void { const form: HTMLFormElement = ev.target as HTMLFormElement, formData: FormData = ExtractFormData(form, true); commentPost( formData, info => commentPostSuccess(form, info), message => commentPostFail ); } function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) => void = null, onFail: (message: string) => void = null): void { if (!commentsRequestLock()) return; const xhr = new XMLHttpRequest(); xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; commentsFreeLock(); const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo, message: string = json.error || json.message; if (message && onFail) onFail(message); else if (!message && onSuccess) onSuccess(json); }); xhr.open('POST', '/comments.php?m=create'); xhr.setRequestHeader('X-Misuzu-XHR', 'comments'); xhr.send(formData); } function commentPostSuccess(form: HTMLFormElement, comment: CommentPostInfo): void { if (form.classList.contains('comment--reply')) (form.parentNode.parentNode.querySelector('label.comment__action') as HTMLLabelElement).click(); //commentInsert(info, form); } function commentPostFail(message: string): void { alert(message); } function commentsInit(): void { const commentDeletes: HTMLCollectionOf = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf; for (let i = 0; i < commentDeletes.length; i++) { commentDeletes[i].addEventListener('click', commentDelete); commentDeletes[i].dataset.href = commentDeletes[i].href; commentDeletes[i].href = 'javascript:void(0);'; } const commentInputs: HTMLCollectionOf = document.getElementsByClassName('comment__text--input') as HTMLCollectionOf; for (let i = 0; i < commentInputs.length; i++) { commentInputs[i].addEventListener('keydown', ev => { if (ev.keyCode === 13 && ev.ctrlKey && !ev.altKey && !ev.shiftKey) { let form = commentInputs[i].form; commentPost( ExtractFormData(form, true), info => commentPostSuccess(form, info), message => commentPostFail ); } }); } } 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; // layer 2 const commentContainer: HTMLDivElement = commentElement.appendChild(document.createElement('div')); commentContainer.className = 'comment__container'; const commentReplies: HTMLDivElement = commentElement.appendChild(document.createElement('div')); commentReplies.className = 'comment__replies comment__replies--indent-' + layer; commentReplies.id = commentElement.id + '-replies'; // container const commentAvatar: HTMLAnchorElement = commentContainer.appendChild(document.createElement('a')); commentAvatar.className = 'avatar comment__avatar'; commentAvatar.href = '/profile.php?u=' + comment.user_id; commentAvatar.style.backgroundImage = `url('/profile.php?m=avatar&u=${comment.user_id}')`; const commentContent: HTMLDivElement = commentContainer.appendChild(document.createElement('div')); commentContent.className = 'comment__content'; // content const commentInfo = commentContent.appendChild(document.createElement('div')); commentInfo.className = 'comment__info'; const commentText = commentContent.appendChild(document.createElement('div')); commentText.className = 'comment__text'; if (comment.comment_html) commentText.innerHTML = comment.comment_html; else commentText.textContent = comment.comment_text; 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'); 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'); commentLink.className = 'comment__link'; commentLink.href = '#' + commentElement.id; commentInfo.appendChild(commentLink); var commentTime = 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); // actions if (typeof commentVote === 'function') { var commentLike = 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); var commentDislike = 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); } // 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'); 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'); commentReplyState.id = commentReply.htmlFor; commentReplyState.type = 'checkbox'; commentReplyState.className = 'comment__reply-toggle'; commentReplies.appendChild(commentReplyState); var commentReplyInput = 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); // reply attributes var replyCategory = document.createElement('input'); replyCategory.name = 'comment[category]'; replyCategory.value = comment.category_id; replyCategory.type = 'hidden'; commentReplyInput.appendChild(replyCategory); var replyCsrf = document.createElement('input'); replyCsrf.name = 'csrf'; replyCsrf.value = '{{ csrf_token("comments") }}'; replyCsrf.type = 'hidden'; commentReplyInput.appendChild(replyCsrf); var replyId = document.createElement('input'); replyId.name = 'comment[reply]'; replyId.value = comment.comment_id; replyId.type = 'hidden'; commentReplyInput.appendChild(replyId); var replyContainer = document.createElement('div'); replyContainer.className = 'comment__container'; commentReplyInput.appendChild(replyContainer); // reply container var replyAvatar = 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); var replyContent = document.createElement('div'); replyContent.className = 'comment__content'; replyContainer.appendChild(replyContent); // reply content var replyInfo = document.createElement('div'); replyInfo.className = 'comment__info'; replyContent.appendChild(replyInfo); var replyUser = document.createElement('div'); replyUser.className = 'comment__user'; replyUser.textContent = comment.username; replyUser.style.color = comment.user_colour == null || (comment.user_colour & 0x40000000) > 0 ? 'inherit' : '#' + (comment.user_colour & 0xFFFFFF).toString(16); replyInfo.appendChild(replyUser); var replyText = 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'); replyActions.className = 'comment__actions'; replyContent.appendChild(replyActions); var replyButton = document.createElement('button'); replyButton.className = 'input__button comment__action comment__action--button comment__action--post'; replyButton.textContent = 'Reply'; replyActions.appendChild(replyButton); if (isReply) parent.appendChild(commentElement); else parent.insertBefore(commentElement, parent.firstElementChild); timeago().render(commentTime); var placeholder = document.getElementById('_no_comments_notice_' + comment.category_id); if (placeholder) placeholder.parentNode.removeChild(placeholder); }