10 changed files with 1067 additions and 387 deletions
@ -0,0 +1,86 @@ |
|||
/* |
|||
=============================================================================================================================== |
|||
Comments Plugin Styles |
|||
=============================================================================================================================== |
|||
*/ |
|||
|
|||
.comment { |
|||
margin-top: 0px; |
|||
width:100%; |
|||
} |
|||
.comment:first-child { |
|||
margin-top: 0px; |
|||
} |
|||
.comment, |
|||
.comment-body { |
|||
zoom: 1; |
|||
} |
|||
.comment-body { |
|||
overflow: hidden; |
|||
margin-left: 10px; |
|||
} |
|||
.comment-object { |
|||
display: block; |
|||
} |
|||
.comment-right, |
|||
.comment > .pull-right { |
|||
padding-left: 10px; |
|||
} |
|||
.comment-left, |
|||
.comment > .pull-left { |
|||
padding-right: 10px; |
|||
} |
|||
.comment-middle { |
|||
vertical-align: middle; |
|||
} |
|||
.comment-bottom { |
|||
vertical-align: bottom; |
|||
} |
|||
.comment-heading { |
|||
margin-top: 0px; |
|||
margin-bottom: 5px; |
|||
} |
|||
.comment-meta { |
|||
font-size: small; |
|||
} |
|||
.comment-text { |
|||
clear: both; |
|||
} |
|||
.comment-list { |
|||
padding-left: 0px; |
|||
list-style: none; |
|||
} |
|||
.comment-flag-new { |
|||
background-color: lightcyan; |
|||
} |
|||
.comment-wrapper { |
|||
display: -webkit-box; |
|||
display: -moz-box; |
|||
display: -ms-flexbox; |
|||
display: -webkit-flex; |
|||
display: flex; |
|||
} |
|||
.comment-avatar { |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-left: -10px; |
|||
margin-right:5px; |
|||
vertical-align: middle; |
|||
} |
|||
/*.comment-thread-line:hover { |
|||
border-left:3px solid #1BB3E9; |
|||
}*/ |
|||
.comment-thread-top { |
|||
margin-top:20px; |
|||
} |
|||
.comment-thread { |
|||
float: left; |
|||
border-left:3px solid #444; |
|||
margin-left:10px; |
|||
} |
|||
.comment-footer { |
|||
font-size: small; |
|||
} |
|||
.comment-reply { |
|||
display: inline; |
|||
} |
@ -0,0 +1,216 @@ |
|||
function escapeRegExp(str) { |
|||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); |
|||
} |
|||
jQuery(document).ready(function() { |
|||
var commentForm = $('#comments-form'); //$(document).find('.comments-form').first();
|
|||
var commentSection = $('#comments-section'); //$(document).find('.comments').first();
|
|||
var commentAlert = $('#comments-alert'); //$(document).find('.alert').first();
|
|||
|
|||
//hide form, show link
|
|||
commentForm.hide(); |
|||
$(document).find('.comment-add-new').show(); |
|||
|
|||
//show comment form above comments section (new comment thread)
|
|||
$('body').on('click', '.comment-add-new', function(e) { |
|||
e.preventDefault(); |
|||
if ($(this).prev().filter('#comments-form').length > 0) { |
|||
//form is already in the right place.
|
|||
//just make sure it is visible.
|
|||
commentForm.show(); |
|||
return; |
|||
} |
|||
commentForm.hide(); //hide it to make sure that it is not shown after move to make "show" transition work.
|
|||
$(this).before(commentForm); |
|||
commentForm.show('slow'); |
|||
commentAlert.empty().slideUp(); |
|||
}); |
|||
|
|||
//show comment form below selected comment (reply to existing comment)
|
|||
$('body').on('click', '.comment-add-reply', function(e) { |
|||
e.preventDefault(); |
|||
var comment = $(this).closest('.comment'); |
|||
if (comment.find('#comments-form').length > 0) { |
|||
//form is already in the right place.
|
|||
//just make sure it is visible.
|
|||
commentForm.show(); |
|||
return; |
|||
} |
|||
commentForm.hide(); |
|||
comment.find('.comment-body').last().append(commentForm); |
|||
commentForm.show('slow'); |
|||
commentAlert.empty().slideUp(); |
|||
}); |
|||
|
|||
//delete comment (authorized user only)
|
|||
$('body').on('click', '.comment-delete', function(e) { |
|||
e.preventDefault(); |
|||
var comment = $(this).closest('.comment'); |
|||
var id = parseInt(comment.attr('data-id'), 10); |
|||
var level = parseInt(comment.attr('data-level'), 10); |
|||
var nonce = commentForm.find("input[name='form-nonce']").val(); |
|||
if (comment.next().filter(".comment[data-level='" + (level + 1) + "']").length > 0) { |
|||
alert('Deletion not allowed. There are replies to this comment. Please delete them first.'); |
|||
return; |
|||
} |
|||
var url = commentForm.attr("action"); |
|||
var posting = $.post(url, { |
|||
action: 'delete', |
|||
id: id, |
|||
nonce: nonce |
|||
}, null, 'json'); |
|||
// Register events to ajax call
|
|||
posting.done(function(response) { |
|||
|
|||
//make sure that commentForm is definitely not within the deleted DOM part.
|
|||
//hide
|
|||
//temporary move it outside the comment selected for deletion. (this definitely exists, not taking any chances here)
|
|||
//finally move back to start of commentSection. (preferred target)
|
|||
//Hint: Don't forget commentAlert as it is not inside the form.
|
|||
commentAlert.empty().hide(); |
|||
commentForm.hide(); |
|||
comment.before(commentForm); |
|||
comment.before(commentAlert); |
|||
commentSection.prepend(commentAlert); |
|||
commentSection.prepend(commentForm); |
|||
//remove the comment and all content from DOM.
|
|||
//detach would be a soft delete but as there is no reason to reuse the deleted comment, means should not be provided.
|
|||
comment.remove(); |
|||
}); |
|||
posting.fail(function(status, error, title) { |
|||
//alert('error');
|
|||
//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status)));
|
|||
commentForm.after(commentAlert); |
|||
commentAlert.show(); |
|||
commentAlert.empty().append("<p>Error: </p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(status) + "</p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(error) + "</p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(title) + "</p>"); |
|||
}); |
|||
posting.always(function() { |
|||
//alert("finished, be it successful or not");
|
|||
}); |
|||
}); |
|||
|
|||
// Attach a submit handler to the form
|
|||
$(commentForm).on('submit', function(event) { |
|||
event.preventDefault(); |
|||
// Get form data:
|
|||
var data = $(this).serialize(); |
|||
//console.log("Form Data (submit)", JSON.parse(JSON.stringify(data)));
|
|||
var url = $(this).attr("action"); |
|||
//var url = '/nested-comments';
|
|||
var parent = 0; |
|||
var ownLevel = 0; |
|||
if ($(this).parents('.comment').length > 0) { |
|||
parent = $(this).closest('.comment').attr('data-id'); |
|||
ownLevel = parseInt($(this).closest('.comment').attr('data-level'), 10) + 1; |
|||
} |
|||
|
|||
// Send the data using post
|
|||
//var posting = $.post(url, { parent: parent, data: data }, null, 'json');
|
|||
var posting = $.post(url, data + '&parent=' + parent, null, 'json'); |
|||
|
|||
// Register events to ajax call
|
|||
posting.done(function(response) { |
|||
//alert('success');
|
|||
//console.log("Response Data (done)", JSON.parse(JSON.stringify(response)));
|
|||
//response = JSON.parse(response); //not needed, post was done using json
|
|||
commentForm.after(commentAlert); |
|||
if (!response.status) { |
|||
//should not trigger at all, if all bad requests return the right http status code
|
|||
//i.e. <> 200 success => thus triggering posting.fail()
|
|||
//leave this check just in case
|
|||
commentAlert.stop().css('opacity', 1).text('Error: ' + response.message).fadeIn(30).fadeOut(5000); |
|||
return; |
|||
} |
|||
if (response.status) { |
|||
commentAlert.css('color', 'green').empty().append(document.createTextNode(response.message)).fadeIn(30); |
|||
/*var newMedia = "<div class='comment comment-level-{{comment.level|e}} comment-flag-new' data-level='{{comment.level}}' data-id='{{comment.id}}' >" + |
|||
"<div class='comment-left'>" + |
|||
"<img class='comment-object' src='https://www.gravatar.com/avatar/{{comment.email|trim|lower|md5}}?d=identicon' alt='user icon'>" + |
|||
"</div>" + |
|||
"<div class='comment-body'>" + |
|||
"<div class='comment-heading'>" + |
|||
"<div class='comment-title'><h4>{{comment.title}}</h4></div>" + |
|||
"<div class='comment-reply'><a class='comment-add-reply' href='#'><i class='fa fa-reply' title='{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}'></i> {{'PLUGIN_COMMENTS.REPLY'|t}}</a></div>" + |
|||
"<div class='comment-meta'>{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}</div>" + |
|||
"</div>" + |
|||
"<div class='comment-text' >" + |
|||
"{{comment.text}}" + |
|||
"</div>" + |
|||
"{{nested}}" + |
|||
"</div>" + |
|||
"</div>";*/ |
|||
var newMedia = `<div id="comment-{{comment.id}}" class="comment comment-level-{{comment.level|e}}" data-id="{{comment.id}}" itemtype="http://schema.org/UserComments">
|
|||
<header class="comment-heading"> |
|||
<img class="comment-avatar" src="https://www.gravatar.com/avatar/{{comment.email|trim|lower|md5}}?size=20&d=identicon" alt="user icon"> |
|||
<span class="comment-meta"> |
|||
{% if comment.site %} |
|||
<a href="{{comment.site}}">{{comment.author}}</a> |
|||
{% else %} |
|||
{{comment.author}} |
|||
{% endif %} |
|||
<a href="{{uri.url(true)}}#comment-{{comment.id}}" title="Link to this comment" itemprop="url"> |
|||
<time class="comment-date" datetime="{{comment.date|e}}" itemprop="commentTime"> |
|||
{{comment.date|nicetime(false)}} |
|||
</time> |
|||
</a> |
|||
</span> |
|||
</header> |
|||
<div class="comment-body"> |
|||
<div class="comment-text" > |
|||
{{comment.text}} |
|||
</div> |
|||
{{nested}} |
|||
<div class="comment-footer"> |
|||
<span class="comment-reply"> |
|||
{% if grav.twig.commenting_enabled %} |
|||
<a class="comment-add-reply" href="#"><i class="fa fa-reply" title="{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}"></i> {{'PLUGIN_COMMENTS.REPLY'|t}}</a> |
|||
{% endif %} |
|||
{% if grav.user.access.admin.super %} |
|||
<a class="comment-delete" href="#"><i class="fa fa-trash" title="{{'PLUGIN_COMMENTS.DELETE_COMMENT'|t}}"></i> {{'PLUGIN_COMMENTS.DELETE'|t}}</a> |
|||
{% endif %} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</div>`; |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.id}}"), 'g'), response.data.id); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.level|e}}"), 'g'), ownLevel); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.level}}"), 'g'), ownLevel); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.email|trim|lower|md5}}"), 'g'), response.data.hash); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{parent}}"), 'g'), response.data.parent); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.title}}"), 'g'), response.data.title); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.text}}"), 'g'), response.data.text); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.author}}"), 'g'), response.data.name); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.site}}"), 'g'), response.data.site); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{comment.date|e}}"), 'g'), response.data.date); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{nested}}"), 'g'), ''); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.ADD_REPLY'|t}}"), 'g'), response.data.ADD_REPLY); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.REPLY'|t}}"), 'g'), response.data.REPLY); |
|||
newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}}"), 'g'), response.data.WRITTEN_ON); |
|||
if ($("div[data-id='" + response.data.parent + "']").length > 0) { |
|||
$("div[data-id='" + response.data.parent + "']").first().after(newMedia); |
|||
} else { |
|||
$("#comments").prepend(newMedia); |
|||
} |
|||
} |
|||
setTimeout(function() { |
|||
commentForm.slideUp(); |
|||
commentAlert.fadeOut(5000); |
|||
}, 5000); |
|||
}); |
|||
posting.fail(function(status, error, title) { |
|||
//alert('error');
|
|||
//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status)));
|
|||
commentForm.after(commentAlert); |
|||
commentAlert.show(); |
|||
commentAlert.empty().append("<p>Error: </p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(status) + "</p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(error) + "</p>"); |
|||
commentAlert.append("<p>" + JSON.stringify(title) + "</p>"); |
|||
}); |
|||
posting.always(function() { |
|||
//alert("finished, be it successful or not");
|
|||
}); |
|||
}); |
|||
}); |
@ -0,0 +1,44 @@ |
|||
<?php |
|||
|
|||
namespace Grav\Plugin; |
|||
|
|||
class Comment |
|||
{ |
|||
private $id = 0; |
|||
private $value = array(); |
|||
private $parent = null; |
|||
private $children = array(); |
|||
|
|||
public function __construct($id, $content) { |
|||
$this->id = $id; |
|||
$this->value = $content; |
|||
} |
|||
|
|||
public function addItem($obj, $key = null) { |
|||
} |
|||
|
|||
public function deleteItem($key) { |
|||
} |
|||
|
|||
public function getItem($key) { |
|||
} |
|||
|
|||
public function getContent($level = 0) { |
|||
$this->value['level'] = $level; |
|||
$comments[] = $this->value; |
|||
|
|||
foreach($this->children as $child) { |
|||
//$comments[] = $child->getContent($level + 1); //produces nested result array. |
|||
$comments = array_merge($comments, $child->getContent($level + 1)); //produces flat result array. |
|||
} |
|||
return $comments; |
|||
} |
|||
|
|||
public function setParent($parent) { |
|||
$this->parent = $parent; |
|||
} |
|||
public function addSubComment($obj) { |
|||
$this->children[] = $obj; |
|||
} |
|||
|
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,37 @@ |
|||
<form id="comments-form" name="{{ grav.config.plugins.comments.form.name }}" class="comments-form" |
|||
action="{{ grav.config.plugins.comments.form.action ? base_url ~ grav.config.plugins.comments.form.action : page.url }}" |
|||
method="{{ grav.config.plugins.comments.form.method|upper|default('POST') }}"> |
|||
|
|||
{% for field in grav.config.plugins.comments.form.fields %} |
|||
{% set value = form.value(field.name) %} |
|||
{% if field.evaluateDefault %} |
|||
{% set value = evaluate(field.evaluateDefault) %} |
|||
{% endif %} |
|||
{% if config.plugins.login.enabled and grav.user.authenticated %} |
|||
{% if field.name == 'name' %} |
|||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.fullname}}"> |
|||
{% elseif field.name == 'email' %} |
|||
<input type="hidden" name="{{ (scope ~ field.name)|fieldName }}" value="{{grav.user.email}}"> |
|||
{% else %} |
|||
<div> |
|||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} |
|||
</div> |
|||
{% endif %} |
|||
{% else %} |
|||
<div> |
|||
{% include "forms/fields/#{field.type}/#{field.type}.html.twig" %} |
|||
</div> |
|||
{% endif %} |
|||
{% endfor %} |
|||
{% include "forms/fields/formname/formname.html.twig" %} |
|||
|
|||
<div class="buttons"> |
|||
{% for button in grav.config.plugins.comments.form.buttons %} |
|||
<button class="button" type="{{ button.type|default('submit') }}">{{ button.value|t|default('Submit') }}</button> |
|||
{% endfor %} |
|||
</div> |
|||
|
|||
{{ nonce_field('comments', 'form-nonce')|raw }} |
|||
</form> |
|||
|
|||
<div id="comments-alert" class="alert">{{ form.message }}</div> |
@ -0,0 +1,54 @@ |
|||
{# you may set options when using this partial. Example: include 'partials/recentcomments.html.twig' with {'limit': 5, 'pages_limit': 3} #} |
|||
{% if grav.twig.enable_comments_plugin %} |
|||
<h1>{{'PLUGIN_COMMENTS.COMMENTS_STATS'|t}}</h1> |
|||
{% set stats = recent_comments(limit|default(5), pages_limit|default(3)) %} |
|||
{% if stats.global_stats.active_entries %} |
|||
<i class="fa fa-comments" title="active_entries"></i> {{stats.global_stats.active_entries}} |
|||
(<i class="fa fa-trash" title="deleted_entries"></i> {{stats.global_stats.deleted_entries}}) |
|||
- <i class="fa fa-comment" title="active_comments"></i>{{stats.global_stats.active_comments}} |
|||
(<i class="fa fa-trash" title="deleted_comments"></i> {{stats.global_stats.deleted_comments}}) |
|||
- <i class="fa fa-commenting" title="active_replies"></i>{{stats.global_stats.active_replies}} |
|||
(<i class="fa fa-trash" title="deleted_replies"></i> {{stats.global_stats.deleted_replies}}) |
|||
- <i class="fa fa-files-o" title="pages_with_active_entries"></i> {{stats.global_stats.pages_with_active_entries}} |
|||
{% endif %} |
|||
{% for key, entry in stats.pages %} |
|||
{% if loop.first %} |
|||
<h2>{{'PLUGIN_COMMENTS.RECENT_PAGES'|t}} (limit {{stats.options.pages_limit}})</h2> |
|||
<ul class="fa-ul"> |
|||
{% endif %} |
|||
<li><i class="fa-li fa fa-file" title="{{entry.route}}"></i> |
|||
{% if entry.route %} |
|||
<a href="{{entry.route}}#comments"> |
|||
{% endif %} |
|||
{{entry.active_entries}} |
|||
{% if entry.route %} |
|||
</a> |
|||
{% endif %} |
|||
</li> |
|||
{% if loop.last %} |
|||
</ul> |
|||
{% endif %} |
|||
{% endfor %} |
|||
{% for key, entry in stats.comments %} |
|||
{% if loop.first %} |
|||
<h2>{{'PLUGIN_COMMENTS.RECENT_COMMENTS'|t}} (limit {{stats.options.comments_limit}})</h2> |
|||
<ul class="fa-ul"> |
|||
{% endif %} |
|||
{% set entry_icon = 'fa-comment' %} |
|||
{% if not empty(entry.parent) %} |
|||
{% set entry_icon = 'fa-commenting' %} |
|||
{% endif %} |
|||
<li><i class="fa-li fa {{entry_icon}}" title="{{key}}: {{entry.id}}, {{entry.parent}}"></i> |
|||
{% if entry.route %} |
|||
<a href="{{entry.route}}#comments"> |
|||
{% endif %} |
|||
{{entry.date}}, {{entry.author}}, {{entry.text|truncate(15)}} |
|||
{% if entry.route %} |
|||
</a> |
|||
{% endif %} |
|||
</li> |
|||
{% if loop.last %} |
|||
</ul> |
|||
{% endif %} |
|||
{% endfor %} |
|||
{% endif %} |
Loading…
Reference in new issue