Rewrote client side code for the comment system.
This commit is contained in:
parent
d3531fc2c1
commit
fa9059a9d9
6 changed files with 218 additions and 168 deletions
|
@ -1,26 +1,5 @@
|
|||
/// <reference path="FormUtilities.ts" />
|
||||
|
||||
|
||||
let globalCommentLock = false;
|
||||
|
||||
function commentsLocked(): boolean
|
||||
{
|
||||
return globalCommentLock;
|
||||
}
|
||||
|
||||
function commentsRequestLock(): boolean
|
||||
{
|
||||
if (commentsLocked())
|
||||
return false;
|
||||
|
||||
return globalCommentLock = true;
|
||||
}
|
||||
|
||||
function commentsFreeLock(): void
|
||||
{
|
||||
globalCommentLock = false;
|
||||
}
|
||||
|
||||
enum CommentVoteType {
|
||||
Indifferent = 0,
|
||||
Like = 1,
|
||||
|
@ -33,7 +12,7 @@ interface CommentNotice {
|
|||
}
|
||||
|
||||
interface CommentDeletionInfo extends CommentNotice {
|
||||
comment_id: number;
|
||||
id: number; // minor inconsistency, deal with it
|
||||
}
|
||||
|
||||
interface CommentPostInfo extends CommentNotice {
|
||||
|
@ -57,32 +36,49 @@ interface CommentVotesInfo extends CommentNotice {
|
|||
dislikes: number;
|
||||
}
|
||||
|
||||
function commentDelete(ev: Event): void
|
||||
{
|
||||
if (!checkUserPerm('comments', CommentPermission.Delete) || !commentsRequestLock())
|
||||
return;
|
||||
function commentDeleteEventHandler(ev: Event): void {
|
||||
const target: HTMLAnchorElement = ev.target as HTMLAnchorElement,
|
||||
commentId: number = parseInt(target.dataset.commentId);
|
||||
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest(),
|
||||
target: HTMLAnchorElement = ev.target as HTMLAnchorElement;
|
||||
commentDelete(
|
||||
commentId,
|
||||
info => {
|
||||
let elem = document.getElementById('comment-' + info.id);
|
||||
|
||||
if (elem)
|
||||
elem.parentNode.removeChild(elem);
|
||||
},
|
||||
message => {
|
||||
alert(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function commentDelete(commentId: number, onSuccess: (info: CommentDeletionInfo) => void = null, onFail: (message: string) => void = null): void
|
||||
{
|
||||
if (!checkUserPerm('comments', CommentPermission.Delete)) {
|
||||
if (onFail)
|
||||
onFail("You aren't allowed to delete comments.");
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest;
|
||||
|
||||
xhr.addEventListener('readystatechange', () => {
|
||||
if (xhr.readyState !== 4)
|
||||
return;
|
||||
commentsFreeLock();
|
||||
|
||||
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||
|
||||
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);
|
||||
}
|
||||
if (message && onFail)
|
||||
onFail(message);
|
||||
else if (!message && onSuccess)
|
||||
onSuccess(json);
|
||||
});
|
||||
xhr.open('GET', target.dataset.href);
|
||||
xhr.open('GET', `/comments.php?m=delete&c=${commentId}&csrf=${getCSRFToken('comments')}`);
|
||||
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
|
||||
xhr.send();
|
||||
}
|
||||
|
@ -91,17 +87,25 @@ function commentPostEventHandler(ev: Event): void
|
|||
{
|
||||
const form: HTMLFormElement = ev.target as HTMLFormElement;
|
||||
|
||||
if (form.dataset.disabled)
|
||||
return;
|
||||
form.dataset.disabled = '1';
|
||||
form.style.opacity = '0.5';
|
||||
|
||||
commentPost(
|
||||
ExtractFormData(form, true),
|
||||
extractFormData(form, true),
|
||||
info => commentPostSuccess(form, info),
|
||||
commentPostFail
|
||||
message => commentPostFail(form, message)
|
||||
);
|
||||
}
|
||||
|
||||
function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) => void = null, onFail: (message: string) => void = null): void
|
||||
{
|
||||
if (!checkUserPerm('comments', CommentPermission.Create) || !commentsRequestLock())
|
||||
if (!checkUserPerm('comments', CommentPermission.Create)) {
|
||||
if (onFail)
|
||||
onFail("You aren't allowed to post comments.");
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
|
@ -109,9 +113,7 @@ function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) =
|
|||
if (xhr.readyState !== 4)
|
||||
return;
|
||||
|
||||
commentsFreeLock();
|
||||
|
||||
console.log(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||
|
||||
const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo,
|
||||
message: string = json.error || json.message;
|
||||
|
@ -132,17 +134,21 @@ function commentPostSuccess(form: HTMLFormElement, comment: CommentPostInfo): vo
|
|||
(form.parentNode.parentNode.querySelector('label.comment__action') as HTMLLabelElement).click();
|
||||
|
||||
commentInsert(comment, form);
|
||||
form.style.opacity = '1';
|
||||
form.dataset.disabled = '';
|
||||
}
|
||||
|
||||
function commentPostFail(message: string): void {
|
||||
function commentPostFail(form: HTMLFormElement, message: string): void {
|
||||
alert(message);
|
||||
form.style.opacity = '1';
|
||||
form.dataset.disabled = '';
|
||||
}
|
||||
|
||||
function commentsInit(): void {
|
||||
const commentDeletes: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
for (let i = 0; i < commentDeletes.length; i++) {
|
||||
commentDeletes[i].addEventListener('click', commentDelete);
|
||||
commentDeletes[i].addEventListener('click', commentDeleteEventHandler);
|
||||
commentDeletes[i].dataset.href = commentDeletes[i].href;
|
||||
commentDeletes[i].href = 'javascript:void(0);';
|
||||
}
|
||||
|
@ -152,16 +158,26 @@ function commentsInit(): void {
|
|||
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;
|
||||
commentPost(
|
||||
ExtractFormData(form, true),
|
||||
info => commentPostSuccess(form, info),
|
||||
message => commentPostFail
|
||||
);
|
||||
}
|
||||
});
|
||||
commentInputs[i].addEventListener('keydown', commentInputEventHandler);
|
||||
}
|
||||
|
||||
const voteButtons: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--vote') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||
|
||||
for (var i = 0; i < voteButtons.length; i++)
|
||||
{
|
||||
voteButtons[i].href = 'javascript:void(0);';
|
||||
voteButtons[i].addEventListener('click', commentVoteEventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function commentInputEventHandler(ev: KeyboardEvent): void {
|
||||
if (ev.keyCode === 13 && ev.ctrlKey && !ev.altKey && !ev.shiftKey) {
|
||||
const form: HTMLFormElement = (ev.target as HTMLTextAreaElement).form;
|
||||
commentPost(
|
||||
extractFormData(form, true),
|
||||
info => commentPostSuccess(form, info),
|
||||
message => commentPostFail
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,16 +242,20 @@ function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElem
|
|||
// actions
|
||||
if (checkUserPerm('comments', CommentPermission.Vote)) {
|
||||
const commentLike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
|
||||
commentLike.className = 'comment__action comment__action--link comment__action--like';
|
||||
commentLike.dataset['commentId'] = comment.comment_id.toString();
|
||||
commentLike.dataset['commentVote'] = CommentVoteType.Like.toString();
|
||||
commentLike.className = 'comment__action comment__action--link comment__action--vote comment__action--like';
|
||||
commentLike.href = 'javascript:void(0);';
|
||||
commentLike.textContent = 'Like';
|
||||
commentLike.addEventListener('click', commentVoteEventHandler);
|
||||
|
||||
const commentDislike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
|
||||
commentDislike.className = 'comment__action comment__action--link comment__action--dislike';
|
||||
commentDislike.dataset['commentId'] = comment.comment_id.toString();
|
||||
commentDislike.dataset['commentVote'] = CommentVoteType.Dislike.toString();
|
||||
commentDislike.className = 'comment__action comment__action--link comment__action--vote comment__action--dislike';
|
||||
commentDislike.href = 'javascript:void(0);';
|
||||
commentDislike.textContent = 'Dislike';
|
||||
commentLike.addEventListener('click', commentVoteEventHandler);
|
||||
commentDislike.addEventListener('click', commentVoteEventHandler);
|
||||
}
|
||||
|
||||
// if we're executing this it's fairly obvious that we can reply,
|
||||
|
@ -265,8 +285,8 @@ function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElem
|
|||
replyCategory.type = 'hidden';
|
||||
|
||||
const replyCsrf: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
|
||||
replyCsrf.name = 'csrf';
|
||||
replyCsrf.value = '{{ csrf_token("comments") }}';
|
||||
replyCsrf.name = 'csrf[comments]';
|
||||
replyCsrf.value = getCSRFToken('comments');
|
||||
replyCsrf.type = 'hidden';
|
||||
|
||||
const replyId: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
|
||||
|
@ -301,6 +321,7 @@ function commentConstruct(comment: CommentPostInfo, layer: number = 0): HTMLElem
|
|||
replyText.className = 'comment__text input__textarea comment__text--input';
|
||||
replyText.name = 'comment[text]';
|
||||
replyText.placeholder = 'Share your extensive insights...';
|
||||
replyText.addEventListener('keydown', commentInputEventHandler);
|
||||
|
||||
const replyActions: HTMLDivElement = replyContent.appendChild(document.createElement('div'));
|
||||
replyActions.className = 'comment__actions';
|
||||
|
@ -335,24 +356,70 @@ function commentInsert(comment: CommentPostInfo, form: HTMLFormElement): void
|
|||
}
|
||||
|
||||
function commentVoteEventHandler(ev: Event): void {
|
||||
//
|
||||
const target: HTMLAnchorElement = ev.target as HTMLAnchorElement,
|
||||
commentId: number = parseInt(target.dataset.commentId),
|
||||
voteType: CommentVoteType = parseInt(target.dataset.commentVote),
|
||||
buttons: NodeListOf<HTMLAnchorElement> = document.querySelectorAll(`.comment__action--vote[data-comment-id="${commentId}"]`),
|
||||
likeButton: HTMLAnchorElement = document.querySelector(`.comment__action--like[data-comment-id="${commentId}"]`),
|
||||
dislikeButton: HTMLAnchorElement = document.querySelector(`.comment__action--dislike[data-comment-id="${commentId}"]`),
|
||||
classVoted: string = 'comment__action--voted';
|
||||
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
let button: HTMLAnchorElement = buttons[i];
|
||||
|
||||
button.textContent = button === target ? '...' : '';
|
||||
button.classList.remove(classVoted);
|
||||
|
||||
if (button === likeButton) {
|
||||
button.dataset.commentVote = (voteType === CommentVoteType.Like ? CommentVoteType.Indifferent : CommentVoteType.Like).toString();
|
||||
} else if (button === dislikeButton) {
|
||||
button.dataset.commentVote = (voteType === CommentVoteType.Dislike ? CommentVoteType.Indifferent : CommentVoteType.Dislike).toString();
|
||||
}
|
||||
}
|
||||
|
||||
commentVote(
|
||||
commentId,
|
||||
voteType,
|
||||
(info) => {
|
||||
switch (voteType) {
|
||||
case CommentVoteType.Like:
|
||||
likeButton.classList.add(classVoted);
|
||||
break;
|
||||
|
||||
case CommentVoteType.Dislike:
|
||||
dislikeButton.classList.add(classVoted);
|
||||
break;
|
||||
}
|
||||
|
||||
likeButton.textContent = info.likes > 0 ? `Like (${info.likes.toLocaleString()})` : 'Like';
|
||||
dislikeButton.textContent = info.dislikes > 0 ? `Dislike (${info.dislikes.toLocaleString()})` : 'Dislike';
|
||||
},
|
||||
(message) => {
|
||||
likeButton.textContent = 'Like';
|
||||
dislikeButton.textContent = 'Dislike';
|
||||
alert(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function commentVoteV2(
|
||||
function commentVote(
|
||||
commentId: number,
|
||||
vote: CommentVoteType,
|
||||
onSuccess: (voteInfo: CommentVotesInfo, userVote: CommentVoteType) => void,
|
||||
onFail: (message: string) => void
|
||||
onSuccess: (voteInfo: CommentVotesInfo) => void = null,
|
||||
onFail: (message: string) => void = null
|
||||
): void {
|
||||
if (!checkUserPerm('comments', CommentPermission.Vote) || !commentsRequestLock())
|
||||
if (!checkUserPerm('comments', CommentPermission.Vote)) {
|
||||
if (onFail)
|
||||
onFail("You aren't allowed to vote on comments.");
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest;
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState !== 4)
|
||||
return;
|
||||
|
||||
commentsFreeLock();
|
||||
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||
|
||||
const json: CommentVotesInfo = JSON.parse(xhr.responseText),
|
||||
message: string = json.error || json.message;
|
||||
|
@ -360,9 +427,9 @@ function commentVoteV2(
|
|||
if (message && onFail)
|
||||
onFail(message);
|
||||
else if (!message && onSuccess)
|
||||
onSuccess(json, vote);
|
||||
onSuccess(json);
|
||||
};
|
||||
xhr.open('GET', `/comments.php?m=vote&c=${commentId}&v=${vote}&csrf={{ csrf_token("comments") }}`);
|
||||
xhr.open('GET', `/comments.php?m=vote&c=${commentId}&v=${vote}&csrf=${getCSRFToken('comments')}`);
|
||||
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
|
||||
xhr.send();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function ExtractFormData(form: HTMLFormElement, resetSource: boolean = false): FormData
|
||||
function extractFormData(form: HTMLFormElement, resetSource: boolean = false): FormData
|
||||
{
|
||||
const formData: FormData = new FormData;
|
||||
|
||||
|
@ -14,7 +14,7 @@ function ExtractFormData(form: HTMLFormElement, resetSource: boolean = false): F
|
|||
}
|
||||
|
||||
if (resetSource)
|
||||
ResetForm(form);
|
||||
resetForm(form);
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ interface FormHiddenDefault {
|
|||
Value: string;
|
||||
}
|
||||
|
||||
function ResetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): void
|
||||
function resetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): void
|
||||
{
|
||||
for (let i = 0; i < form.length; i++) {
|
||||
let input: HTMLInputElement = form[i] as HTMLInputElement;
|
||||
|
@ -46,3 +46,67 @@ function ResetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): v
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CSRFToken {
|
||||
realm: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
const CSRFTokenStore: CSRFToken[] = [];
|
||||
|
||||
function initCSRF(): void {
|
||||
const csrfTokens: NodeListOf<HTMLInputElement> = document.querySelectorAll('[name^="csrf["]'),
|
||||
regex = /\[([a-z]+)\]/iu,
|
||||
handled: string[] = [];
|
||||
|
||||
for (let i = 0; i < csrfTokens.length; i++) {
|
||||
let csrfToken: HTMLInputElement = csrfTokens[i],
|
||||
realm: string = csrfToken.name.match(regex)[1] || '';
|
||||
|
||||
if (handled.indexOf(realm) >= 0)
|
||||
continue;
|
||||
handled.push(realm);
|
||||
|
||||
setCSRF(realm, csrfToken.value);
|
||||
}
|
||||
}
|
||||
|
||||
function getCSRF(realm: string): CSRFToken {
|
||||
return CSRFTokenStore.find(i => i.realm.toLowerCase() === realm.toLowerCase());
|
||||
}
|
||||
|
||||
function getCSRFToken(realm: string): string {
|
||||
return getCSRF(realm).token || '';
|
||||
}
|
||||
|
||||
function setCSRF(realm: string, token: string): void {
|
||||
let csrf: CSRFToken = getCSRF(realm);
|
||||
|
||||
if (csrf) {
|
||||
csrf.token = token;
|
||||
} else {
|
||||
csrf = new CSRFToken;
|
||||
csrf.realm = realm;
|
||||
csrf.token = token;
|
||||
CSRFTokenStore.push(csrf);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCSRF(token: string, realm: string = null, name: string = 'csrf'): void
|
||||
{
|
||||
const tokenSplit: string[] = token.split(';');
|
||||
|
||||
if (tokenSplit.length > 1) {
|
||||
token = tokenSplit[1];
|
||||
|
||||
if (!realm) {
|
||||
realm = tokenSplit[0];
|
||||
}
|
||||
}
|
||||
|
||||
const elements: NodeListOf<HTMLInputElement> = document.getElementsByName(`${name}[${realm}]`) as NodeListOf<HTMLInputElement>;
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].value = token;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
/// <reference path="Permissions.ts" />
|
||||
/// <reference path="Comments.ts" />
|
||||
/// <reference path="Common.ts" />
|
||||
/// <reference path="FormUtilities.ts" />
|
||||
|
||||
declare const timeago: any;
|
||||
declare const hljs: any;
|
||||
|
@ -15,6 +16,7 @@ window.addEventListener('load', () => {
|
|||
timeago().render(document.querySelectorAll('time'));
|
||||
hljs.initHighlighting();
|
||||
|
||||
initCSRF();
|
||||
userInit();
|
||||
|
||||
const changelogChangeAction: HTMLDivElement = document.querySelector('.changelog__change__action') as HTMLDivElement;
|
||||
|
|
|
@ -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 `comment_id`,
|
||||
SELECT :id as `id`,
|
||||
(
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_comments_votes`
|
||||
WHERE `comment_id` = `comment_id`
|
||||
WHERE `comment_id` = `id`
|
||||
AND `comment_vote` = \'Like\'
|
||||
) as `likes`,
|
||||
(
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_comments_votes`
|
||||
WHERE `comment_id` = `comment_id`
|
||||
WHERE `comment_id` = `id`
|
||||
AND `comment_vote` = \'Dislike\'
|
||||
) as `dislikes`
|
||||
');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
define('MSZ_CSRF_TOLERANCE', 15 * 60); // DO NOT EXCEED 16-BIT INTEGER SIZES, SHIT _WILL_ BREAK
|
||||
define('MSZ_CSRF_HTML', '<input type="hidden" name="%1$s" value="%2$s">');
|
||||
define('MSZ_CSRF_HTML', '<input type="hidden" name="%1$s[%3$s]" value="%2$s">');
|
||||
define('MSZ_CSRF_SECRET_STORE', '_msz_csrf_secret');
|
||||
define('MSZ_CSRF_IDENTITY_STORE', '_msz_csrf_identity');
|
||||
define('MSZ_CSRF_TOKEN_STORE', '_msz_csrf_tokens');
|
||||
|
@ -98,8 +98,10 @@ function csrf_token(string $realm): string
|
|||
);
|
||||
}
|
||||
|
||||
function csrf_verify(string $realm, string $token): bool
|
||||
function csrf_verify(string $realm, $token): bool
|
||||
{
|
||||
$token = (string)(is_array($token) && !empty($token[$realm]) ? $token[$realm] : $token);
|
||||
|
||||
return csrf_token_verify(
|
||||
$realm,
|
||||
$token,
|
||||
|
@ -110,7 +112,7 @@ function csrf_verify(string $realm, string $token): bool
|
|||
|
||||
function csrf_html(string $realm, string $name = 'csrf'): string
|
||||
{
|
||||
return sprintf(MSZ_CSRF_HTML, $name, csrf_token($realm));
|
||||
return sprintf(MSZ_CSRF_HTML, $name, csrf_token($realm), $realm);
|
||||
}
|
||||
|
||||
function csrf_http_header(string $realm, string $name = 'X-Misuzu-CSRF'): string
|
||||
|
|
|
@ -83,15 +83,15 @@
|
|||
{% if comment.comment_deleted is null and user is not null %}
|
||||
<div class="comment__actions">
|
||||
{% if perms.can_vote %}
|
||||
<a class="comment__action comment__action--link comment__action--like{% if comment.comment_user_vote == 'Like' %} comment__action--voted{% endif %}"
|
||||
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Like' ? '0' : '1' }}&csrf={{ csrf_token('comments') }}">
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--like{% if comment.comment_user_vote == 'Like' %} comment__action--voted{% endif %}" data-comment-id="{{ comment.comment_id }}" data-comment-vote="{{ comment.comment_user_vote == 'Like' ? '0' : '1' }}"
|
||||
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Like' ? '0' : '1' }}&csrf={{ csrf_token('comments') }}">
|
||||
Like
|
||||
{% if comment.comment_likes > 0 %}
|
||||
({{ comment.comment_likes|number_format }})
|
||||
{% endif %}
|
||||
</a>
|
||||
<a class="comment__action comment__action--link comment__action--dislike{% if comment.comment_user_vote == 'Dislike' %} comment__action--voted{% endif %}"
|
||||
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Dislike' ? '0' : '-1' }}&csrf={{ csrf_token('comments') }}">
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--dislike{% if comment.comment_user_vote == 'Dislike' %} comment__action--voted{% endif %}" data-comment-id="{{ comment.comment_id }}" data-comment-vote="{{ comment.comment_user_vote == 'Dislike' ? '0' : '-1' }}"
|
||||
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Dislike' ? '0' : '-1' }}&csrf={{ csrf_token('comments') }}">
|
||||
Dislike
|
||||
{% if comment.comment_dislikes > 0 %}
|
||||
({{ comment.comment_dislikes|number_format }})
|
||||
|
@ -102,7 +102,7 @@
|
|||
<label class="comment__action comment__action--link" for="comment-reply-toggle-{{ comment.comment_id }}">Reply</label>
|
||||
{% endif %}
|
||||
{% if perms.can_delete_any or (comment.user_id == user.user_id and perms.can_delete) %}
|
||||
<a class="comment__action comment__action--link comment__action--hide comment__action--delete"
|
||||
<a class="comment__action comment__action--link comment__action--hide comment__action--delete" data-comment-id="{{ comment.comment_id }}"
|
||||
href="/comments.php?m=delete&c={{ comment.comment_id }}&csrf={{ csrf_token('comments') }}">Delete</a>
|
||||
{% endif %}
|
||||
{# if user is not null %}
|
||||
|
@ -179,89 +179,4 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function () {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof friend !== 'object') {
|
||||
console.error('something happened');
|
||||
return;
|
||||
}
|
||||
|
||||
friend.classList.remove(commentVotedClass);
|
||||
|
||||
friend.textContent = '';
|
||||
|
||||
elem.textContent += '.';
|
||||
|
||||
commentVoteV2(
|
||||
id, vote,
|
||||
(vInfo, uVote) => {
|
||||
if (vote)
|
||||
elem.classList.add(commentVotedClass);
|
||||
else
|
||||
elem.classList.remove(commentVotedClass);
|
||||
|
||||
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()) : '');
|
||||
friend.textContent = commentDislikeText + (dislikes > 0 ? commentVoteCountSuffix.replace('{0}', dislikes.toLocaleString()) : '');
|
||||
} else {
|
||||
elem.textContent = commentDislikeText + (dislikes > 0 ? commentVoteCountSuffix.replace('{0}', dislikes.toLocaleString()) : '');
|
||||
friend.textContent = commentLikeText + (likes > 0 ? commentVoteCountSuffix.replace('{0}', likes.toLocaleString()) : '');
|
||||
}
|
||||
},
|
||||
alert
|
||||
);
|
||||
}
|
||||
</script>
|
||||
{% endmacro %}
|
||||
|
|
Loading…
Add table
Reference in a new issue