2018-08-06 22:19:35 +00:00
{% macro comments_input ( category , user , perms , reply_to ) %}
{% set reply_mode = reply_to is not null %}
2018-10-27 14:29:13 +00:00
{% from '_layout/input.twig' import input_hidden , input_csrf , input_checkbox %}
2018-10-25 01:35:53 +00:00
2018-08-08 01:53:33 +00:00
<form class="comment comment--input {% if reply_mode %} comment--reply {% endif %} "
2018-08-10 04:20:54 +00:00
method="post" action="/comments.php?m=create"
2018-08-06 22:19:35 +00:00
id="comment- {{ reply_mode ? 'reply-' ~ reply_to .comment_id : 'create-' ~ category .category_id }} ">
2018-10-25 01:35:53 +00:00
{{ input_hidden ( 'comment[category]' , category .category_id ) }}
{{ input_csrf ( 'comments' ) }}
2018-08-08 01:53:33 +00:00
2018-08-06 22:19:35 +00:00
{% if reply_mode %}
2018-10-25 01:35:53 +00:00
{{ input_hidden ( 'comment[reply]' , reply_to .comment_id ) }}
2018-08-06 22:19:35 +00:00
{% endif %}
2018-08-08 01:53:33 +00:00
2018-08-06 22:19:35 +00:00
<div class="comment__container">
<div class="avatar comment__avatar"
style="background-image:url('/profile.php?m=avatar&u= {{ user .user_id }} ')">
</div>
<div class="comment__content">
<div class="comment__info">
<div class="comment__user"
style=" {{ user .user_colour | html_colour }} "> {{ user .username }} </div>
</div>
<textarea
class="comment__text input__textarea comment__text--input"
2018-08-10 21:21:39 +00:00
name="comment[text]" placeholder="Share your extensive insights..."></textarea>
2018-08-06 22:19:35 +00:00
<div class="comment__actions">
{% if not reply_mode %}
{% if perms .can_pin %}
2018-10-27 14:29:13 +00:00
{{ input_checkbox ( 'comment[pin]' , 'Pin this comment' , false , 'comment__action' ) }}
2018-08-06 22:19:35 +00:00
{% endif %}
{% if perms .can_lock %}
2018-10-27 14:29:13 +00:00
{{ input_checkbox ( 'comment[lock]' , 'Toggle locked status' , false , 'comment__action' ) }}
2018-08-06 22:19:35 +00:00
{% endif %}
{% endif %}
2018-10-22 19:53:21 +00:00
<button class="input__button comment__action comment__action--button comment__action--post">
2018-08-10 21:21:39 +00:00
{{ reply_mode ? 'Reply' : 'Post' }}
</button>
2018-08-06 22:19:35 +00:00
</div>
</div>
</div>
</form>
{% endmacro %}
2018-08-10 21:21:39 +00:00
{% macro comments_entry ( comment , indent , category , user , perms ) %}
{% if comment .comment_deleted is null or comment .comment_replies | length > 0 %}
<div class="comment" id="comment- {{ comment .comment_id }} ">
<div class="comment__container">
<a class="avatar comment__avatar"
href="/profile.php?u= {{ comment .user_id }} "
style="background-image:url('/profile.php?m=avatar&u= {{ comment .user_id }} ')">
</a>
<div class="comment__content">
<div class="comment__info">
<a class="comment__user comment__user--link"
href="/profile.php?u= {{ comment .user_id }} "
style=" {{ comment .user_colour | html_colour }} "> {{ comment .username }} </a>
<a class="comment__link" href="#comment- {{ comment .comment_id }} ">
<time class="comment__date"
title=" {{ comment .comment_created | date ( 'r' ) }} "
datetime=" {{ comment .comment_created | date ( 'c' ) }} ">
{{ comment .comment_created | time_diff }}
</time>
</a>
{% if comment .comment_pinned is not null %}
<span class="comment__pin"> {% spaceless %}
Pinned
{% if comment .comment_pinned != comment .comment_created %}
<time title=" {{ comment .comment_pinned | date ( 'r' ) }} "
datetime=" {{ comment .comment_pinned | date ( 'c' ) }} ">
{{ comment .comment_pinned | time_diff }}
</time>
{% endif %}
{% endspaceless %} </span>
{% endif %}
</div>
<div class="comment__text {{ comment .comment_deleted is null ? '' : ' comment__text--deleted' }} ">
2018-08-17 01:39:44 +00:00
{{ comment .comment_deleted is null ? ( comment .comment_html is defined ? comment .comment_html | raw : comment .comment_text | nl2br ) : 'deleted' }}
2018-08-10 21:21:39 +00:00
</div>
2018-08-10 21:39:17 +00:00
{% if comment .comment_deleted is null and user is not null %}
2018-08-10 21:21:39 +00:00
<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 %} "
2018-10-02 19:16:42 +00:00
href="/comments.php?m=vote&c= {{ comment .comment_id }} &v= {{ comment .comment_user_vote == 'Like' ? '0' : '1' }} &csrf= {{ csrf_token ( 'comments' ) }} ">
2018-08-10 21:21:39 +00:00
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 %} "
2018-10-02 19:16:42 +00:00
href="/comments.php?m=vote&c= {{ comment .comment_id }} &v= {{ comment .comment_user_vote == 'Dislike' ? '0' : '-1' }} &csrf= {{ csrf_token ( 'comments' ) }} ">
2018-08-10 21:21:39 +00:00
Dislike
{% if comment .comment_dislikes > 0 %}
( {{ comment .comment_dislikes | number_format }} )
{% endif %}
</a>
{% endif %}
{% if perms .can_comment %}
<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"
2018-10-02 19:40:46 +00:00
href="/comments.php?m=delete&c= {{ comment .comment_id }} &csrf= {{ csrf_token ( 'comments' ) }} ">Delete</a>
2018-08-10 21:21:39 +00:00
{% endif %}
{ # if user is not null %}
<a class="comment__action comment__action--link comment__action--hide" href="#">Report</a>
{% endif # }
</div>
{% endif %}
</div>
</div>
<div class="comment__replies comment__replies--indent- {{ indent }} " id="comment- {{ comment .comment_id }} -replies">
{% from _self import comments_entry , comments_input %}
{% if user | default ( null ) is not null and category | default ( null ) is not null and perms | default ( null ) is not null and perms .can_comment %}
<input type="checkbox" class="comment__reply-toggle" id="comment-reply-toggle- {{ comment .comment_id }} ">
{{ comments_input ( category , user , perms , comment ) }}
{% endif %}
{% if comment .comment_replies is defined and comment .comment_replies | length > 0 %}
{% for reply in comment .comment_replies %}
{{ comments_entry ( reply , indent + 1 , category , user , perms ) }}
{% endfor %}
{% endif %}
</div>
</div>
{% endif %}
{% endmacro %}
2018-08-06 22:19:35 +00:00
{% macro comments_section ( comments , category , user , perms ) %}
<div class="comments">
<div class="comments__input">
{% if user | default ( null ) is null %}
<div class="comments__notice">
Please <a href="/auth.php?m=login" class="comments__notice__link">login</a> to comment.
</div>
{% elseif category | default ( null ) is null or perms | default ( null ) is null %}
<div class="comments__notice">
Posting new comments here is disabled.
</div>
2018-08-10 21:21:39 +00:00
{% elseif not perms .can_lock and category .category_locked is not null %}
<div class="comments__notice">
This comment section was locked, <time datetime=" {{ category .category_locked | date ( 'c' ) }} " title=" {{ category .category_locked | date ( 'r' ) }} "> {{ category .category_locked | time_diff }} </time>.
</div>
2018-08-06 22:19:35 +00:00
{% elseif not perms .can_comment %}
<div class="comments__notice">
You are not allowed to post comments.
</div>
{% else %}
{% from _self import comments_input %}
{{ comments_input ( category , user , perms ) }}
{% endif %}
</div>
2018-08-10 21:21:39 +00:00
{% if perms .can_lock and category .category_locked is not null %}
<div class="comments__notice comments__notice--staff">
This comment section was locked, <time datetime=" {{ category .category_locked | date ( 'c' ) }} " title=" {{ category .category_locked | date ( 'r' ) }} "> {{ category .category_locked | time_diff }} </time>.
</div>
{% endif %}
2018-08-06 22:19:35 +00:00
<noscript>
<div class="comments__javascript">
2018-08-10 21:21:39 +00:00
While the comments work fine without Javascript, it is recommended you enable it as it has a lower bandwidth overhead.
2018-08-06 22:19:35 +00:00
</div>
</noscript>
2018-08-10 21:21:39 +00:00
<div class="comments__listing">
{% if comments | length > 0 %}
2018-08-06 22:19:35 +00:00
{% from _self import comments_entry %}
{% for comment in comments %}
{{ comments_entry ( comment , 1 , category , user , perms ) }}
{% endfor %}
2018-08-10 21:21:39 +00:00
{% else %}
2018-10-08 13:28:19 +00:00
<div class="comments__none" id="_no_comments_notice_ {{ category .category_id }} ">
2018-08-10 21:21:39 +00:00
There are no comments yet.
</div>
{% endif %}
</div>
2018-08-06 22:19:35 +00:00
</div>
2018-08-10 04:20:54 +00:00
<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');
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;
}
}
2018-08-10 21:21:39 +00:00
if (typeof commentPost === '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 = commentPost;
}
}
if (typeof commentDelete === 'function') { // can delete
var commentDeletes = document.getElementsByClassName('comment__action--delete');
for (var i = 0; i < commentDeletes.length; i++) {
commentDeletes[i].onclick = commentDelete;
commentDeletes[i].dataset.href = commentDeletes[i].href;
commentDeletes[i].href = 'javascript:void(0);';
}
}
2018-08-10 04:20:54 +00:00
});
</script>
2018-08-10 21:21:39 +00:00
{% if perms .can_delete %}
<script>
var commentDeleteLock = false;
function commentDelete(ev)
{
if (commentDeleteLock)
return;
commentDeleteLock = true;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState !== 4)
return;
commentDeleteLock = false;
var json = JSON.parse(this.responseText),
message = json.error || json.message;
if (message)
alert(message);
else {
var elem = document.getElementById('comment-' + json.comment_id);
if (elem)
elem.parentNode.removeChild(elem);
}
};
xhr.open('GET', ev.target.dataset.href);
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
xhr.send();
}
</script>
{% endif %}
2018-08-06 22:19:35 +00:00
{% if perms .can_comment %}
<script>
var commentPostLock = false;
2018-08-10 21:21:39 +00:00
function commentPost(ev)
2018-08-06 22:19:35 +00:00
{
2018-08-10 21:21:39 +00:00
// the moment we find the id we engage vote lock
if (commentPostLock)
return;
commentPostLock = true;
var form = ev.target,
formData = new FormData;
for (var i = 0; i < form.length; i++) {
var isCheckbox = form[i].type === 'checkbox';
if (isCheckbox && !form[i].checked)
continue;
formData.append(form[i].name, form[i].value || '');
if (isCheckbox)
form[i].checked = false;
else if (form[i].type !== 'hidden')
form[i].value = '';
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState !== 4)
return;
var json = JSON.parse(this.responseText),
message = json.error || json.message;
if (message)
alert(message);
else {
if (form.classList.contains('comment--reply'))
form.parentNode.parentNode.querySelector('label.comment__action').click();
commentInsert(json, form);
}
commentPostLock = false;
};
xhr.open('POST', '/comments.php?m=create');
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
xhr.send(formData);
}
// this is the biggest doozy of them all, this should create an element identical to comments_entry
function commentInsert(comment, form)
{
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;
// layer 1
var commentElement = document.createElement('div');
commentElement.className = 'comment';
commentElement.id = 'comment-' + comment.comment_id;
// layer 2
var commentContainer = document.createElement('div');
commentContainer.className = 'comment__container';
commentElement.appendChild(commentContainer);
var commentReplies = document.createElement('div');
commentReplies.className = 'comment__replies comment__replies--indent-' + repliesIndent;
commentReplies.id = commentElement.id + '-replies';
commentElement.appendChild(commentReplies);
// container
var commentAvatar = 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= { 0}\')'.replace(' { 0}', comment.user_id);
commentContainer.appendChild(commentAvatar);
var commentContent = document.createElement('div');
commentContent.className = 'comment__content';
commentContainer.appendChild(commentContent);
// content
var commentInfo = document.createElement('div');
commentInfo.className = 'comment__info';
commentContent.appendChild(commentInfo);
var commentText = document.createElement('div');
commentText.className = 'comment__text';
2018-08-17 01:39:44 +00:00
if (comment.comment_html)
commentText.innerHTML = comment.comment_html;
else
commentText.textContent = comment.comment_text;
2018-08-10 21:21:39 +00:00
commentContent.appendChild(commentText);
var commentActions = document.createElement('div');
commentActions.className = 'comment__actions';
commentContent.appendChild(commentActions);
// 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 = commentPost;
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';
2018-10-02 19:16:42 +00:00
replyCsrf.value = ' {{ csrf_token ( "comments" ) }} ';
2018-08-10 21:21:39 +00:00
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');
2018-10-22 19:53:21 +00:00
replyButton.className = 'input__button comment__action comment__action--button comment__action--post';
2018-08-10 21:21:39 +00:00
replyButton.textContent = 'Reply';
replyActions.appendChild(replyButton);
if (isReply)
parent.appendChild(commentElement);
else
parent.insertBefore(commentElement, parent.firstElementChild);
timeago().render(commentTime);
2018-10-08 13:28:19 +00:00
var placeholder = document.getElementById('_no_comments_notice_' + comment.category_id);
placeholder.parentNode.removeChild(placeholder);
2018-08-06 22:19:35 +00:00
}
</script>
{% endif %}
{% if perms .can_vote %}
<script>
2018-08-10 04:20:54 +00:00
var commentVoteLock = false,
commentLikeClass = 'comment__action--like',
commentDislikeClass = 'comment__action--dislike',
2018-08-10 21:21:39 +00:00
commentVotedClass = 'comment__action--voted',
commentLikeText = 'Like',
commentDislikeText = 'Dislike',
commentVoteCountSuffix = ' ( { 0})';
2018-08-06 22:19:35 +00:00
2018-08-10 04:20:54 +00:00
// DEBUG THIS IF YOU MAKE MAJOR DOM CHANGES TO COMMENTS
function commentVote(ev)
2018-08-06 22:19:35 +00:00
{
2018-08-10 04:20:54 +00:00
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
2018-08-06 22:19:35 +00:00
if (id < 1 || commentVoteLock)
return;
commentVoteLock = true;
2018-08-10 04:20:54 +00:00
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);
2018-08-06 22:19:35 +00:00
2018-08-10 04:20:54 +00:00
friend.textContent = '';
elem.textContent += '.';
2018-08-06 22:19:35 +00:00
var xhr = new XMLHttpRequest();
2018-08-10 21:21:39 +00:00
xhr.onreadystatechange = function () {
2018-08-10 04:20:54 +00:00
if (this.readyState !== 4)
return;
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);
2018-08-10 21:21:39 +00:00
var likes = json.likes || 0,
dislikes = json.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()) : '');
}
commentVoteLock = false;
2018-08-06 22:19:35 +00:00
};
2018-10-02 19:16:42 +00:00
xhr.open('GET', '/comments.php?m=vote&c= { 0}&v= { 1}&csrf= {{ csrf_token ( "comments" ) }} '.replace(' { 0}', id).replace(' { 1}', vote));
2018-08-10 04:20:54 +00:00
xhr.setRequestHeader('X-Misuzu-XHR', 'comments');
xhr.send();
2018-08-06 22:19:35 +00:00
}
</script>
{% endif %}
{% endmacro %}