diff --git a/assets/comments.css b/assets/comments.css index 4105deb..ad04314 100644 --- a/assets/comments.css +++ b/assets/comments.css @@ -9,7 +9,7 @@ Comments Plugin Styles border-top: gray solid 2px; } .comment:first-child { - margin-top: 0; + margin-top: 0px; } .comment, .comment-body { @@ -43,7 +43,7 @@ Comments Plugin Styles vertical-align: bottom; } .comment-heading { - margin-top: 0; + margin-top: 0px; margin-bottom: 5px; border-bottom: gray dashed 1px; } @@ -63,6 +63,55 @@ Comments Plugin Styles clear: both; } .comment-list { - padding-left: 0; + padding-left: 0px; list-style: none; } +.comment-flag-new { + background-color: lightcyan; +} +.comment-level-1 { margin-left: 20px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-2 { margin-left: 40px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-3 { margin-left: 60px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-4 { margin-left: 80px; padding-left: 0px; border-left: gray solid 0px; } +.comment-level-5 { margin-left: 100px; padding-left: 0px; border-left: gray solid 0px; } +.row.comments { position: relative; } +.comment-level-1::before { + content: "\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-2::before { + content: "\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-3::before { + content: "\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-4::before { + content: "\f105\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} +.comment-level-5::before { + content: "\f105\f105\f105\f105\f105"; + font-family: FontAwesome; + position: absolute; + left: 0px; + font-size: 3rem; + color: lightgray; +} diff --git a/assets/comments.js b/assets/comments.js index 0964879..3d63de1 100644 --- a/assets/comments.js +++ b/assets/comments.js @@ -1,36 +1,24 @@ +function escapeRegExp(str) { + return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); +} jQuery(document).ready(function () { var commentForm = $(document).find('.comments-form'); var commentSection = $(document).find('.comments').first(); - var commentAlert = commentForm.closest('.alert'); - //var newMedia; + var commentAlert = $(document).find('.alert').first(); + //hide form, show link commentForm.hide(); $(document).find('.comment-add-new').show(); - //get template for inserting new comments - /* -$.ajax({ - url: '/media_template.php', - method: 'GET', - dataType: 'html', - success: function (data) { - newMedia = data; - } -}); - */ - $('body').on('click', '.comment-add-new-sadf', function (e) { - e.preventDefault(); - alert('asdf'); - $('span').stop().css('opacity', 1).text('myName = ' + e.name).fadeIn(30).fadeOut(1000); - }); + //show comment form above comments section (new comment thread) $('body').on('click', '.comment-add-new', function (e) { e.preventDefault(); //commentForm.hide(1000); - //commentSection.before(commentForm); $(this).before(commentForm); commentForm.show('slow'); - //$(this).slideUp(); + commentAlert.slideUp(); }); + //show comment form below selected comment (reply to existing comment) $('body').on('click', '.comment-add-reply', function (e) { e.preventDefault(); @@ -38,93 +26,96 @@ $.ajax({ commentForm.hide(); media.find('>.comment-body>.comment-text').after(commentForm); commentForm.show('slow'); + commentAlert.slideUp(); }); + // Attach a submit handler to the form $(commentForm).on('submit', function (event) { event.preventDefault(); - // Get some values from elements on the page: - //var term = $(this).find( "input[name='s']" ).val(); - //var data = $(this).serializeArray(); + // 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'; +//console.log("Form Data (submit)", JSON.parse(JSON.stringify(data))); + var url = $(this).attr( "action" ); + //var url = '/nested-comments'; var parentId = 0; + var ownLevel = 0; if ($(this).parents('.comment').length > 0) { - parentId = $(this).closest('.comment').attr('data-Id'); + parentId = $(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, { parentId: parentId, data: data }, null, 'json'); var posting = $.post(url, data + '&parentID=' + parentId, null, 'json'); - //$.post( "test.php", $( "#testform" ).serialize() ); - // Put the results in a div + + // 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); - var message = response.status ? response.message : 'Error: ' + response.message; +//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); - commentAlert.empty().append(message); if (!response.status) { - return; + //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) { - var newMedia = ` -
-
- - user icon - -
-
-
-

{{comment.title}}

- -
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
-
-
- {{comment.text}} -
- {{nested}} -
-
-`; - newMedia = newMedia.replace('{{comment.id}}', response.id); - newMedia = newMedia.replace('{{comment.level|e}}', response.level); - newMedia = newMedia.replace('{{comment.email|trim|lower|md5}}', response.hash); - newMedia = newMedia.replace('{{parent_id}}', response.data.parent_id); - newMedia = newMedia.replace('{{comment.title}}', response.data.title); - newMedia = newMedia.replace('{{comment.text}}', response.data.text); - newMedia = newMedia.replace('{{comment.author}}', response.data.name); - //newMedia = newMedia.replace('{{comment.date|e}}', response.data.name); - if ($( "div[data-Id='" + response.data.parent_id + "']" ).length > 0) { - $( "div[data-Id='" + response.data.parent_id + "']" ).first().after(newMedia); - } else { + commentAlert.css('color', 'green').empty().append(document.createTextNode( response.message )).fadeIn(30); + var newMedia = "
" + + "
" + + "" + + "user icon" + + "" + + "
" + + "
" + + "
" + + "

{{comment.title}}

" + + "" + + "
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}
" + + "
" + + "
" + + "{{comment.text}}" + + "
" + + "{{nested}}" + + "
" + + "
"; + 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_id}}"), 'g'), response.data.parent_id); + 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.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.WRITTEN_ON'|t}}"), 'g'), response.data.WRITTEN_ON); + newMedia = newMedia.replace(new RegExp(escapeRegExp("{{'PLUGIN_COMMENTS.BY'|t}}"), 'g'), response.data.BY); + if ($( "div[data-id='" + response.data.parent_id + "']" ).length > 0) { + $( "div[data-id='" + response.data.parent_id + "']" ).first().after(newMedia); + } else { $( "div.comments" ).last().prepend(newMedia); - } - //phpComment.commentForm.before(newMedia); - //phpComment.titleField.val(""); - //phpComment.bodyField.val(""); + } } setTimeout(function () { - commentForm.hide(3000); + commentForm.hide(2000); + commentAlert.fadeOut(5000); }, 5000); }); posting.fail(function (status, error, title) { - alert('error'); - console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); +//alert('error'); +//console.log("Response Data (fail)", JSON.parse(JSON.stringify(status))); commentForm.after(commentAlert); commentAlert.empty().append("

TEST

"); commentAlert.append("

" + status + "

"); commentAlert.append("

" + error + "

"); commentAlert.append("

" + title + "

"); }); - posting.always(function (test) { + posting.always(function () { //alert("finished, be it successful or not"); - //test = JSON.parse(test); - //test = test.serialize(); - //alert(test); }); }); }); diff --git a/class/Comment.php b/class/Comment.php index affe1e9..5219f53 100644 --- a/class/Comment.php +++ b/class/Comment.php @@ -28,7 +28,8 @@ class Comment $comments[] = $this->value; foreach($this->children as $child) { - $comments[] = $child->getContent($level + 1); + //$comments[] = $child->getContent($level + 1); //produces nested result array. + $comments = array_merge($comments, $child->getContent($level + 1)); //produces flat result array. } return $comments; } diff --git a/comments.php b/comments.php index e0b6352..ecfbe11 100644 --- a/comments.php +++ b/comments.php @@ -182,7 +182,7 @@ class CommentsPlugin extends Plugin // Process comment if required if ($is_ajax || $callback === $this->grav['uri']->path()) { // try to add the comment - $result = $this->addComment(); + $result = $this->addComment(true); echo json_encode([ 'status' => $result[0], 'message' => $result[1], @@ -196,8 +196,10 @@ class CommentsPlugin extends Plugin } } - public function addComment() + public function addComment($is_ajax = false) { + if($is_ajax) { + $language = $this->grav['language']; if (!$_SERVER["REQUEST_METHOD"] == "POST") { // Not a POST request, set a 403 (forbidden) response code. http_response_code(403); @@ -220,43 +222,6 @@ class CommentsPlugin extends Plugin $input['path'] = isset($_POST['data']['path']) ? filter_var($_POST['data']['path'], FILTER_SANITIZE_STRING) : null; $input['form-name'] = filter_input(INPUT_POST, 'form-name', FILTER_SANITIZE_STRING); $input['form-nonce'] = filter_input(INPUT_POST, 'form-nonce', FILTER_SANITIZE_STRING); -/* - foreach ($_POST['data'] as $field) { - if (isset($field['name']) && isset($field['value'])) { - switch ($field['name']) { - case 'data[name]': - $input['name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[email]': - $input['email'] = filter_var($field['value'], FILTER_SANITIZE_EMAIL); - break; - case 'data[text]': - $input['text'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[date]': - $input['date'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[title]': - $input['title'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[lang]': - $input['lang'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'data[path]': - $input['path'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case '__form-name__': - $input['form-name'] = filter_var($field['value'], FILTER_SANITIZE_STRING); - break; - case 'form-nonce': - $input['form-nonce'] = filter_var($field['value'], FILTER_SANITIZE_STRING); //$this->grav['uri']->param('nonce'); - break; - default: - //ignore unexpected fields. - } - } - } -*/ if (!Utils::verifyNonce($input['form-nonce'], 'comments')) { http_response_code(403); return [false, 'Invalid security nonce', [$_POST, $input['form-nonce']]]; @@ -268,31 +233,138 @@ class CommentsPlugin extends Plugin return [false, 'missing either text or title', [0, 0]]; //return [false, $language->translate('PLUGIN_COMMENTS.FAIL'), $data]; } - $language = $this->grav['language']; - //$data = $this->getStars($id); - $data = array( - 'parent_id' => $input['parent_id'], - 'email' => $input['email'], - 'text' => $input['text'], - 'title' => $input['title'], - 'name' => $input['name'], - 'id' => 99, - 'level' => 0, - 'hash' => md5(strtolower(trim($input['email']))), - ); // sanity checks for parents - if ($data['parent_id'] < 0) { - $data['parent_id'] = 0; - } elseif ($data['parent_id'] > 999 ) { //TODO: Change to 'exists in list of comment ids - $data['parent_id'] = 0; + if ($input['parent_id'] < 0) { + $input['parent_id'] = 0; + } elseif ($input['parent_id'] > 999 ) { //TODO: Change to 'exists in list of comment ids + $input['parent_id'] = 0; } - //$this->saveVoteData($id, $rating); - // Set a 500 (internal server error) response code. -// http_response_code(500); - //$data = $this->getStars($id); + $lang = $this->grav['language']->getLanguage(); + $path = $this->grav['page']->path(); + $route = $this->grav['page']->route(); + $comment = $this->saveComment($route, $path, $input['parent_id'], $lang, $input['text'], $input['name'], $input['email'], $input['title']); + //$comments = $this->fetchComments(); + $data = array( + 'parent_id' => $comment['parent'], + 'id' => $comment['id'], + 'text' => $comment['text'], + 'title' => $comment['title'], + 'name' => $comment['author'], + 'date' => $comment['date'], + 'level' => 0, + 'hash' => md5(strtolower(trim($comment['email']))), + 'ADD_REPLY' => $language->translate('PLUGIN_COMMENTS.ADD_REPLY'), + 'WRITTEN_ON' => $language->translate('PLUGIN_COMMENTS.WRITTEN_ON'), + 'BY' => $language->translate('PLUGIN_COMMENTS.BY'), + ); return [true, $language->translate('PLUGIN_COMMENTS.SUCCESS'), $data]; + } else { +// Set a 500 (internal server error) response code. +// http_response_code(500); + + } } + /** + * Handle form processing instructions. + * + * @param Event $event + */ + public function saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title) + { + $date = date('D, d M Y H:i:s', time()); + + /******************************/ + /** store comments with page **/ + /******************************/ + $localfilename = $path . '/comments.yaml'; + $localfile = CompiledYamlFile::instance($localfilename); + if (file_exists($localfilename)) { + $data = $localfile->content(); + $data['autoincrement']++; + } else { + $data = array( + 'autoincrement' => 1, + 'comments' => array() + ); + } + $localid = $data['autoincrement']; + $newComment = [ + 'id' => $data['autoincrement'], + 'parent' => $parent_id, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + $data['comments'][] = $newComment; + $localfile->save($data); + /**********************************/ + /** store comments in index file **/ + /**********************************/ + $indexfilename = DATA_DIR . 'comments/index.yaml'; + $indexfile = CompiledYamlFile::instance($indexfilename); + if (file_exists($indexfilename)) { + $dataIndex = $indexfile->content(); + } else { + $dataIndex = array( + 'comments' => array() + ); + } + $dataIndex['comments'][] = [ + 'page' => $route, + 'id' => $localid, + 'parent' => $parent_id, + 'lang' => $lang, + 'title' => $title, + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + $indexfile->save($dataIndex); + + /**************************************/ + /** store comments in old data files **/ + /** TODO: remove as soon as admin **/ + /** panel uses new index file **/ + /**************************************/ + $filename = DATA_DIR . 'comments'; + $filename .= ($lang ? '/' . $lang : ''); + $filename .= $path . '.yaml'; + $file = CompiledYamlFile::instance($filename); + + if (file_exists($filename)) { + $dataLegacy = $file->content(); + + $dataLegacy['comments'][] = [ + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]; + } else { + $dataLegacy = array( + 'title' => $title, + 'lang' => $lang, + 'comments' => array([ + 'text' => $text, + 'date' => $date, + 'author' => $name, + 'email' => $email + ]) + ); + } + + $file->save($dataLegacy); + + //clear cache + $this->grav['cache']->delete($this->comments_cache_id); + + return $newComment; + } /** * Handle form processing instructions. @@ -319,6 +391,7 @@ class CommentsPlugin extends Plugin $name = filter_var(urldecode($post['name']), FILTER_SANITIZE_STRING); $email = filter_var(urldecode($post['email']), FILTER_SANITIZE_STRING); $title = filter_var(urldecode($post['title']), FILTER_SANITIZE_STRING); + $parent_id = 0; if (isset($this->grav['user'])) { $user = $this->grav['user']; @@ -329,117 +402,12 @@ class CommentsPlugin extends Plugin } /** @var Language $language */ - $language = $this->grav['language']; - $lang = $language->getLanguage(); + $lang = $this->grav['language']->getLanguage(); + + $path = $this->grav['page']->path(); + $route = $this->grav['page']->route(); - /******************************/ - /** store comments with page **/ - /******************************/ - $page = $this->grav['page']; - $localfilename = $page->path() . '/comments.yaml'; - $localfile = CompiledYamlFile::instance($localfilename); - if (file_exists($localfilename)) { - $data = $localfile->content(); - $data['autoincrement']++; - $data['comments'][] = [ - 'id' => $data['autoincrement'], - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]; - } else { - $data = array( - 'autoincrement' => 1, - 'comments' => array([ - 'id' => 1, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]) - ); - } - $localfile->save($data); - $localid = $data['autoincrement']; - $data = null; - /**********************************/ - /** store comments in index file **/ - /**********************************/ - $indexfilename = DATA_DIR . 'comments/index.yaml'; - $indexfile = CompiledYamlFile::instance($indexfilename); - if (file_exists($indexfilename)) { - $data = $indexfile->content(); - $data['comments'][] = [ - 'page' => $page->route(), - 'id' => $localid, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]; - } else { - $data = array( - 'comments' => array([ - 'page' => $page->route(), - 'id' => $localid, - 'parent' => 0, - 'lang' => $lang, - 'title' => $title, - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]) - ); - } - $indexfile->save($data); - $data = null; - /**************************************/ - /** store comments in old data files **/ - /** TODO: remove as soon as admin **/ - /** panel uses new index file **/ - /**************************************/ - $filename = DATA_DIR . 'comments'; - $filename .= ($lang ? '/' . $lang : ''); - $filename .= $path . '.yaml'; - $file = CompiledYamlFile::instance($filename); - - if (file_exists($filename)) { - $data = $file->content(); - - $data['comments'][] = [ - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]; - } else { - $data = array( - 'title' => $title, - 'lang' => $lang, - 'comments' => array([ - 'text' => $text, - 'date' => date('D, d M Y H:i:s', time()), - 'author' => $name, - 'email' => $email - ]) - ); - } - - $file->save($data); - - //clear cache - $this->grav['cache']->delete($this->comments_cache_id); + $this->saveComment($route, $path, $parent_id, $lang, $text, $name, $email, $title); break; } @@ -580,6 +548,9 @@ class CommentsPlugin extends Plugin $levelsflat[$parent_id]['class']->addSubComment($currentChild); } } + //youngest comments first (DESC date), only root comments. Keep replies in ASC date order. + //as long as comments are not editable, it is sufficient to reverse order from comment file + $leveltree = array_reverse($leveltree, true); //reset comment values to nested order $comments = array(); foreach($leveltree as $id => $comment) { diff --git a/languages.yaml b/languages.yaml index 812f22d..65b8db7 100644 --- a/languages.yaml +++ b/languages.yaml @@ -1,6 +1,10 @@ de: PLUGIN_COMMENTS: + ADD_NEW: Kommentar hinzufügen + ADD_REPLY: Antworten ADD_COMMENT: Kommentar hinzufügen + DELETE_COMMENT: Kommentar löschen + SUCCESS: Der Kommentar wurde erfolgreich gespeichert. COMMENTS: Kommentare EMAIL_NOT_CONFIGURED: Email nicht konfiguriert NEW_COMMENT_EMAIL_SUBJECT: 'Neuer Kommentar für %1$s' @@ -22,7 +26,11 @@ de: en: PLUGIN_COMMENTS: + ADD_NEW: Add a comment + ADD_REPLY: Reply ADD_COMMENT: Add a comment + DELETE_COMMENT: Delete comment + SUCCESS: Comment has been saved successfully. COMMENTS: Comments EMAIL_NOT_CONFIGURED: Email not configured NEW_COMMENT_EMAIL_SUBJECT: 'New comment on %1$s' diff --git a/templates/partials/comments.html.twig b/templates/partials/comments.html.twig index 1f61461..e50ead3 100644 --- a/templates/partials/comments.html.twig +++ b/templates/partials/comments.html.twig @@ -6,10 +6,10 @@ {% if grav.twig.comments|length %}

{{'PLUGIN_COMMENTS.COMMENTS'|t}}

- {{'PLUGIN_COMMENTS.ADD_NEW'|t}} + {{'PLUGIN_COMMENTS.ADD_NEW'|t}}
{% for comment in grav.twig.comments %} -
+
user icon @@ -18,7 +18,12 @@

{{comment.title}}

-
+
+ {{'PLUGIN_COMMENTS.ADD_REPLY'|t}} + {% if grav.user.access.admin.super %} + {{'PLUGIN_COMMENTS.DELETE'|t}} + {% endif %} +
{{'PLUGIN_COMMENTS.WRITTEN_ON'|t}} {{comment.date|e}} {{'PLUGIN_COMMENTS.BY'|t}} {{comment.author}}