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" />
|
/// <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 {
|
enum CommentVoteType {
|
||||||
Indifferent = 0,
|
Indifferent = 0,
|
||||||
Like = 1,
|
Like = 1,
|
||||||
|
@ -33,7 +12,7 @@ interface CommentNotice {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentDeletionInfo extends CommentNotice {
|
interface CommentDeletionInfo extends CommentNotice {
|
||||||
comment_id: number;
|
id: number; // minor inconsistency, deal with it
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommentPostInfo extends CommentNotice {
|
interface CommentPostInfo extends CommentNotice {
|
||||||
|
@ -57,32 +36,49 @@ interface CommentVotesInfo extends CommentNotice {
|
||||||
dislikes: number;
|
dislikes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentDelete(ev: Event): void
|
function commentDeleteEventHandler(ev: Event): void {
|
||||||
{
|
const target: HTMLAnchorElement = ev.target as HTMLAnchorElement,
|
||||||
if (!checkUserPerm('comments', CommentPermission.Delete) || !commentsRequestLock())
|
commentId: number = parseInt(target.dataset.commentId);
|
||||||
return;
|
|
||||||
|
|
||||||
const xhr: XMLHttpRequest = new XMLHttpRequest(),
|
commentDelete(
|
||||||
target: HTMLAnchorElement = ev.target as HTMLAnchorElement;
|
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', () => {
|
xhr.addEventListener('readystatechange', () => {
|
||||||
if (xhr.readyState !== 4)
|
if (xhr.readyState !== 4)
|
||||||
return;
|
return;
|
||||||
commentsFreeLock();
|
|
||||||
|
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||||
|
|
||||||
let json: CommentDeletionInfo = JSON.parse(xhr.responseText) as CommentDeletionInfo,
|
let json: CommentDeletionInfo = JSON.parse(xhr.responseText) as CommentDeletionInfo,
|
||||||
message = json.error || json.message;
|
message = json.error || json.message;
|
||||||
|
|
||||||
if (message)
|
if (message && onFail)
|
||||||
alert(message);
|
onFail(message);
|
||||||
else {
|
else if (!message && onSuccess)
|
||||||
let elem = document.getElementById('comment-' + json.comment_id);
|
onSuccess(json);
|
||||||
|
|
||||||
if (elem)
|
|
||||||
elem.parentNode.removeChild(elem);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
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.setRequestHeader('X-Misuzu-XHR', 'comments');
|
||||||
xhr.send();
|
xhr.send();
|
||||||
}
|
}
|
||||||
|
@ -91,17 +87,25 @@ function commentPostEventHandler(ev: Event): void
|
||||||
{
|
{
|
||||||
const form: HTMLFormElement = ev.target as HTMLFormElement;
|
const form: HTMLFormElement = ev.target as HTMLFormElement;
|
||||||
|
|
||||||
|
if (form.dataset.disabled)
|
||||||
|
return;
|
||||||
|
form.dataset.disabled = '1';
|
||||||
|
form.style.opacity = '0.5';
|
||||||
|
|
||||||
commentPost(
|
commentPost(
|
||||||
ExtractFormData(form, true),
|
extractFormData(form, true),
|
||||||
info => commentPostSuccess(form, info),
|
info => commentPostSuccess(form, info),
|
||||||
commentPostFail
|
message => commentPostFail(form, message)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) => void = null, onFail: (message: string) => void = null): void
|
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;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
@ -109,9 +113,7 @@ function commentPost(formData: FormData, onSuccess: (comment: CommentPostInfo) =
|
||||||
if (xhr.readyState !== 4)
|
if (xhr.readyState !== 4)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
commentsFreeLock();
|
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||||
|
|
||||||
console.log(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
|
||||||
|
|
||||||
const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo,
|
const json: CommentPostInfo = JSON.parse(xhr.responseText) as CommentPostInfo,
|
||||||
message: string = json.error || json.message;
|
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();
|
(form.parentNode.parentNode.querySelector('label.comment__action') as HTMLLabelElement).click();
|
||||||
|
|
||||||
commentInsert(comment, form);
|
commentInsert(comment, form);
|
||||||
|
form.style.opacity = '1';
|
||||||
|
form.dataset.disabled = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentPostFail(message: string): void {
|
function commentPostFail(form: HTMLFormElement, message: string): void {
|
||||||
alert(message);
|
alert(message);
|
||||||
|
form.style.opacity = '1';
|
||||||
|
form.dataset.disabled = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentsInit(): void {
|
function commentsInit(): void {
|
||||||
const commentDeletes: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf<HTMLAnchorElement>;
|
const commentDeletes: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--delete') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||||
|
|
||||||
for (let i = 0; i < commentDeletes.length; i++) {
|
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].dataset.href = commentDeletes[i].href;
|
||||||
commentDeletes[i].href = 'javascript:void(0);';
|
commentDeletes[i].href = 'javascript:void(0);';
|
||||||
}
|
}
|
||||||
|
@ -152,16 +158,26 @@ function commentsInit(): void {
|
||||||
for (let i = 0; i < commentInputs.length; i++) {
|
for (let i = 0; i < commentInputs.length; i++) {
|
||||||
commentInputs[i].form.action = 'javascript:void(0);';
|
commentInputs[i].form.action = 'javascript:void(0);';
|
||||||
commentInputs[i].form.addEventListener('submit', commentPostEventHandler);
|
commentInputs[i].form.addEventListener('submit', commentPostEventHandler);
|
||||||
commentInputs[i].addEventListener('keydown', ev => {
|
commentInputs[i].addEventListener('keydown', commentInputEventHandler);
|
||||||
if (ev.keyCode === 13 && ev.ctrlKey && !ev.altKey && !ev.shiftKey) {
|
}
|
||||||
let form = commentInputs[i].form;
|
|
||||||
commentPost(
|
const voteButtons: HTMLCollectionOf<HTMLAnchorElement> = document.getElementsByClassName('comment__action--vote') as HTMLCollectionOf<HTMLAnchorElement>;
|
||||||
ExtractFormData(form, true),
|
|
||||||
info => commentPostSuccess(form, info),
|
for (var i = 0; i < voteButtons.length; i++)
|
||||||
message => commentPostFail
|
{
|
||||||
);
|
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
|
// actions
|
||||||
if (checkUserPerm('comments', CommentPermission.Vote)) {
|
if (checkUserPerm('comments', CommentPermission.Vote)) {
|
||||||
const commentLike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
|
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.href = 'javascript:void(0);';
|
||||||
commentLike.textContent = 'Like';
|
commentLike.textContent = 'Like';
|
||||||
commentLike.addEventListener('click', commentVoteEventHandler);
|
commentLike.addEventListener('click', commentVoteEventHandler);
|
||||||
|
|
||||||
const commentDislike: HTMLAnchorElement = commentActions.appendChild(document.createElement('a'));
|
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.href = 'javascript:void(0);';
|
||||||
commentDislike.textContent = 'Dislike';
|
commentDislike.textContent = 'Dislike';
|
||||||
commentLike.addEventListener('click', commentVoteEventHandler);
|
commentDislike.addEventListener('click', commentVoteEventHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're executing this it's fairly obvious that we can reply,
|
// 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';
|
replyCategory.type = 'hidden';
|
||||||
|
|
||||||
const replyCsrf: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
|
const replyCsrf: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
|
||||||
replyCsrf.name = 'csrf';
|
replyCsrf.name = 'csrf[comments]';
|
||||||
replyCsrf.value = '{{ csrf_token("comments") }}';
|
replyCsrf.value = getCSRFToken('comments');
|
||||||
replyCsrf.type = 'hidden';
|
replyCsrf.type = 'hidden';
|
||||||
|
|
||||||
const replyId: HTMLInputElement = commentReplyInput.appendChild(document.createElement('input'));
|
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.className = 'comment__text input__textarea comment__text--input';
|
||||||
replyText.name = 'comment[text]';
|
replyText.name = 'comment[text]';
|
||||||
replyText.placeholder = 'Share your extensive insights...';
|
replyText.placeholder = 'Share your extensive insights...';
|
||||||
|
replyText.addEventListener('keydown', commentInputEventHandler);
|
||||||
|
|
||||||
const replyActions: HTMLDivElement = replyContent.appendChild(document.createElement('div'));
|
const replyActions: HTMLDivElement = replyContent.appendChild(document.createElement('div'));
|
||||||
replyActions.className = 'comment__actions';
|
replyActions.className = 'comment__actions';
|
||||||
|
@ -335,24 +356,70 @@ function commentInsert(comment: CommentPostInfo, form: HTMLFormElement): void
|
||||||
}
|
}
|
||||||
|
|
||||||
function commentVoteEventHandler(ev: Event): 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,
|
commentId: number,
|
||||||
vote: CommentVoteType,
|
vote: CommentVoteType,
|
||||||
onSuccess: (voteInfo: CommentVotesInfo, userVote: CommentVoteType) => void,
|
onSuccess: (voteInfo: CommentVotesInfo) => void = null,
|
||||||
onFail: (message: string) => void
|
onFail: (message: string) => void = null
|
||||||
): void {
|
): void {
|
||||||
if (!checkUserPerm('comments', CommentPermission.Vote) || !commentsRequestLock())
|
if (!checkUserPerm('comments', CommentPermission.Vote)) {
|
||||||
|
if (onFail)
|
||||||
|
onFail("You aren't allowed to vote on comments.");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const xhr: XMLHttpRequest = new XMLHttpRequest;
|
const xhr: XMLHttpRequest = new XMLHttpRequest;
|
||||||
xhr.onreadystatechange = () => {
|
xhr.onreadystatechange = () => {
|
||||||
if (xhr.readyState !== 4)
|
if (xhr.readyState !== 4)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
commentsFreeLock();
|
updateCSRF(xhr.getResponseHeader('X-Misuzu-CSRF'));
|
||||||
|
|
||||||
const json: CommentVotesInfo = JSON.parse(xhr.responseText),
|
const json: CommentVotesInfo = JSON.parse(xhr.responseText),
|
||||||
message: string = json.error || json.message;
|
message: string = json.error || json.message;
|
||||||
|
@ -360,9 +427,9 @@ function commentVoteV2(
|
||||||
if (message && onFail)
|
if (message && onFail)
|
||||||
onFail(message);
|
onFail(message);
|
||||||
else if (!message && onSuccess)
|
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.setRequestHeader('X-Misuzu-XHR', 'comments');
|
||||||
xhr.send();
|
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;
|
const formData: FormData = new FormData;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ function ExtractFormData(form: HTMLFormElement, resetSource: boolean = false): F
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetSource)
|
if (resetSource)
|
||||||
ResetForm(form);
|
resetForm(form);
|
||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ interface FormHiddenDefault {
|
||||||
Value: string;
|
Value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ResetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): void
|
function resetForm(form: HTMLFormElement, defaults: FormHiddenDefault[] = []): void
|
||||||
{
|
{
|
||||||
for (let i = 0; i < form.length; i++) {
|
for (let i = 0; i < form.length; i++) {
|
||||||
let input: HTMLInputElement = form[i] as HTMLInputElement;
|
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="Permissions.ts" />
|
||||||
/// <reference path="Comments.ts" />
|
/// <reference path="Comments.ts" />
|
||||||
/// <reference path="Common.ts" />
|
/// <reference path="Common.ts" />
|
||||||
|
/// <reference path="FormUtilities.ts" />
|
||||||
|
|
||||||
declare const timeago: any;
|
declare const timeago: any;
|
||||||
declare const hljs: any;
|
declare const hljs: any;
|
||||||
|
@ -15,6 +16,7 @@ window.addEventListener('load', () => {
|
||||||
timeago().render(document.querySelectorAll('time'));
|
timeago().render(document.querySelectorAll('time'));
|
||||||
hljs.initHighlighting();
|
hljs.initHighlighting();
|
||||||
|
|
||||||
|
initCSRF();
|
||||||
userInit();
|
userInit();
|
||||||
|
|
||||||
const changelogChangeAction: HTMLDivElement = document.querySelector('.changelog__change__action') as HTMLDivElement;
|
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
|
function comments_votes_get(int $commentId): array
|
||||||
{
|
{
|
||||||
$getVotes = db_prepare('
|
$getVotes = db_prepare('
|
||||||
SELECT :id as `comment_id`,
|
SELECT :id as `id`,
|
||||||
(
|
(
|
||||||
SELECT COUNT(`user_id`)
|
SELECT COUNT(`user_id`)
|
||||||
FROM `msz_comments_votes`
|
FROM `msz_comments_votes`
|
||||||
WHERE `comment_id` = `comment_id`
|
WHERE `comment_id` = `id`
|
||||||
AND `comment_vote` = \'Like\'
|
AND `comment_vote` = \'Like\'
|
||||||
) as `likes`,
|
) as `likes`,
|
||||||
(
|
(
|
||||||
SELECT COUNT(`user_id`)
|
SELECT COUNT(`user_id`)
|
||||||
FROM `msz_comments_votes`
|
FROM `msz_comments_votes`
|
||||||
WHERE `comment_id` = `comment_id`
|
WHERE `comment_id` = `id`
|
||||||
AND `comment_vote` = \'Dislike\'
|
AND `comment_vote` = \'Dislike\'
|
||||||
) as `dislikes`
|
) as `dislikes`
|
||||||
');
|
');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
define('MSZ_CSRF_TOLERANCE', 15 * 60); // DO NOT EXCEED 16-BIT INTEGER SIZES, SHIT _WILL_ BREAK
|
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_SECRET_STORE', '_msz_csrf_secret');
|
||||||
define('MSZ_CSRF_IDENTITY_STORE', '_msz_csrf_identity');
|
define('MSZ_CSRF_IDENTITY_STORE', '_msz_csrf_identity');
|
||||||
define('MSZ_CSRF_TOKEN_STORE', '_msz_csrf_tokens');
|
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(
|
return csrf_token_verify(
|
||||||
$realm,
|
$realm,
|
||||||
$token,
|
$token,
|
||||||
|
@ -110,7 +112,7 @@ function csrf_verify(string $realm, string $token): bool
|
||||||
|
|
||||||
function csrf_html(string $realm, string $name = 'csrf'): string
|
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
|
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 %}
|
{% if comment.comment_deleted is null and user is not null %}
|
||||||
<div class="comment__actions">
|
<div class="comment__actions">
|
||||||
{% if perms.can_vote %}
|
{% if perms.can_vote %}
|
||||||
<a class="comment__action comment__action--link comment__action--like{% if comment.comment_user_vote == 'Like' %} comment__action--voted{% endif %}"
|
<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') }}">
|
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Like' ? '0' : '1' }}&csrf={{ csrf_token('comments') }}">
|
||||||
Like
|
Like
|
||||||
{% if comment.comment_likes > 0 %}
|
{% if comment.comment_likes > 0 %}
|
||||||
({{ comment.comment_likes|number_format }})
|
({{ comment.comment_likes|number_format }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
<a class="comment__action comment__action--link comment__action--dislike{% if comment.comment_user_vote == 'Dislike' %} comment__action--voted{% endif %}"
|
<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') }}">
|
href="/comments.php?m=vote&c={{ comment.comment_id }}&v={{ comment.comment_user_vote == 'Dislike' ? '0' : '-1' }}&csrf={{ csrf_token('comments') }}">
|
||||||
Dislike
|
Dislike
|
||||||
{% if comment.comment_dislikes > 0 %}
|
{% if comment.comment_dislikes > 0 %}
|
||||||
({{ comment.comment_dislikes|number_format }})
|
({{ 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>
|
<label class="comment__action comment__action--link" for="comment-reply-toggle-{{ comment.comment_id }}">Reply</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.can_delete_any or (comment.user_id == user.user_id and perms.can_delete) %}
|
{% 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>
|
href="/comments.php?m=delete&c={{ comment.comment_id }}&csrf={{ csrf_token('comments') }}">Delete</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# if user is not null %}
|
{# if user is not null %}
|
||||||
|
@ -179,89 +179,4 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% endmacro %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue