From 362c85b2d1b41582ebc8b9d177276f9ccacad7c2 Mon Sep 17 00:00:00 2001
From: Dan Head
Date: Tue, 11 Dec 2018 17:35:34 +0000
Subject: [PATCH] Inline commenting functionality
---
css/comment-respond.css | 33 +++++++++
css/comment.css | 4 ++
js/reply-to-comment.js | 47 ++++++++++++
js/reply-to-comment.spec.js | 139 ++++++++++++++++++++++++++++++++++++
js/windy.js | 5 ++
theme/comments.php | 5 +-
theme/functions.php | 7 +-
7 files changed, 237 insertions(+), 3 deletions(-)
create mode 100644 js/reply-to-comment.js
create mode 100644 js/reply-to-comment.spec.js
diff --git a/css/comment-respond.css b/css/comment-respond.css
index 3e18eb3..93f0fba 100644
--- a/css/comment-respond.css
+++ b/css/comment-respond.css
@@ -4,12 +4,17 @@
padding-bottom: var(--space-sm);
}
+.comment-respond__title-text {
+ margin-right: var(--space-xs);
+}
+
.comment-respond__text {
font-size: 14px;
}
.comment-respond__item {
margin-bottom: var(--space-sm);
+ max-width: 360px;
}
.comment-respond__label {
@@ -21,6 +26,7 @@
background-color: #fff;
border: 1px solid var(--color-near-black);
padding: var(--space-xs);
+ width: 100%;
}
.comment-respond__input:focus {
@@ -38,3 +44,30 @@
min-width: 100px;
padding: var(--space-xs);
}
+
+.comment-respond__cancel {
+ font-size: 12px;
+}
+
+.comment-respond__cancel a,
+.comment-respond__cancel a:visited {
+ position: relative;
+ color: var(--color-light-blue);
+}
+
+.comment-respond__cancel a:after {
+ background-color: var(--color-light-blue);
+ bottom: -1px;
+ content: '';
+ left: 0;
+ height: 2px;
+ opacity: 0;
+ position: absolute;
+ right: 0;
+ transition: opacity var(--transition-md) ease-out;
+}
+
+.comment-respond__cancel a:focus:after,
+.comment-respond__cancel a:hover:after {
+ opacity: 1;
+}
diff --git a/css/comment.css b/css/comment.css
index b586fe7..4a17dc7 100644
--- a/css/comment.css
+++ b/css/comment.css
@@ -28,6 +28,10 @@
margin-bottom: var(--space-sm);
}
+.comment__reply.is-hidden {
+ display: none;
+}
+
.comment__reply:focus,
.comment__reply:hover {
color: var(--color-light-blue);
diff --git a/js/reply-to-comment.js b/js/reply-to-comment.js
new file mode 100644
index 0000000..aa468a5
--- /dev/null
+++ b/js/reply-to-comment.js
@@ -0,0 +1,47 @@
+export default class ReplyToComment {
+ constructor(cfg = {}) {
+ if (!cfg.form || cfg.form.className !== 'comments__foot') {
+ return false;
+ }
+ if (!cfg.links || cfg.links.length === 0) {
+ return false;
+ }
+ this.form = cfg.form;
+ this.backupForm = cfg.form.querySelector('#respond').cloneNode(true);
+ this.links = Array.prototype.slice.call(cfg.links);
+ this.links.forEach((link) => {
+ link.addEventListener('click', e => this.handleLinkClick(e, link));
+ });
+ }
+
+ insertForm(container, id, author) {
+ const form = this.backupForm.cloneNode(true);
+ const cancel = form.querySelector('#cancel-comment-reply-link');
+ form.querySelector('.comment-respond__title-text').innerHTML = `Reply to ${author}`;
+ form.querySelector('#submit').value = 'Post reply';
+ form.querySelector('#comment_parent').value = id;
+ cancel.style.display = 'inline-block';
+ cancel.addEventListener('click', e => this.handleCancelClick(e, container));
+ container.appendChild(form);
+ }
+
+ handleCancelClick(e, container) {
+ e.preventDefault();
+ container.parentNode.querySelector('.comment__reply').classList.remove('is-hidden');
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+ this.form.appendChild(this.backupForm);
+ }
+
+ handleLinkClick(e, link) {
+ e.preventDefault();
+ link.classList.add('is-hidden');
+ while (this.form.firstChild) {
+ this.form.removeChild(this.form.firstChild);
+ }
+ const container = link.parentNode.querySelector('.comment__inline-reply');
+ const { commentId, commentAuthor } = link.dataset;
+ this.insertForm(container, commentId, commentAuthor);
+ }
+}
diff --git a/js/reply-to-comment.spec.js b/js/reply-to-comment.spec.js
new file mode 100644
index 0000000..a9cc956
--- /dev/null
+++ b/js/reply-to-comment.spec.js
@@ -0,0 +1,139 @@
+import ReplyToComment from './reply-to-comment';
+
+const commentHtml = `
+`;
+
+const formHtml = `
+
+`;
+
+let comments;
+let form;
+let links;
+let inlineReply;
+
+beforeEach(() => {
+ document.body.innerHTML = `${commentHtml}${formHtml}`;
+ form = document.querySelector('.comments__foot');
+ links = document.querySelectorAll('.comment__reply');
+ inlineReply = document.querySelector('.comment__inline-reply');
+ comments = new ReplyToComment({ form, links });
+});
+
+describe('ReplyToComment', () => {
+ it('Should initialise', () => {
+ expect(comments.form).toBeInstanceOf(HTMLDivElement);
+ expect(comments.links).toBeInstanceOf(Array);
+ });
+
+ it('Should fail to initialise if form is missing', () => {
+ expect(new ReplyToComment()).toEqual({});
+ });
+
+ it('Should fail to initialise if there are no links', () => {
+ expect(new ReplyToComment({
+ form: document.querySelector('#respond'),
+ })).toEqual({});
+ expect(new ReplyToComment({
+ form: document.querySelector('#respond'),
+ links: document.querySelectorAll('.no-links-here'),
+ })).toEqual({});
+ });
+
+ it('Should remove the form from the DOM when a reply link is clicked', () => {
+ const event = new Event('click');
+ comments.handleLinkClick(event, links[0]);
+ expect(form.innerHTML).toEqual('');
+ });
+
+ it('Should hide the link when it is clicked', () => {
+ const event = new Event('click');
+ comments.handleLinkClick(event, links[0]);
+ expect(links[0].classList.contains('is-hidden')).toBe(true);
+ });
+
+ it('Should insert the form into the inline reply container', () => {
+ comments.insertForm(inlineReply);
+ const inlineForm = inlineReply.querySelectorAll('form');
+ expect(inlineForm.length).toEqual(1);
+ });
+
+ it('Should update the comment_parent hidden element', () => {
+ comments.insertForm(inlineReply, '123', 'Mr Smith');
+ const { value } = inlineReply.querySelector('#comment_parent');
+ expect(value).toEqual('123');
+ });
+
+ it('Should add a cancel link to the inline form', () => {
+ comments.insertForm(inlineReply);
+ const el = inlineReply.querySelector('#cancel-comment-reply-link');
+ expect(el.style.display).toEqual('inline-block');
+ });
+
+ it('Should restore the reply link when the cancel link is clicked', () => {
+ const event = new Event('click');
+ comments.insertForm(inlineReply);
+ comments.handleCancelClick(event, inlineReply);
+ expect(links[0].classList.contains('is-hidden')).toBe(false);
+ });
+
+ it('Should remove the inline form when the cancel link is clicked', () => {
+ const event = new Event('click');
+ comments.insertForm(inlineReply);
+ comments.handleCancelClick(event, inlineReply);
+ expect(inlineReply.innerHTML).toEqual('');
+ });
+});
+
diff --git a/js/windy.js b/js/windy.js
index 55b3bda..f48c9ad 100644
--- a/js/windy.js
+++ b/js/windy.js
@@ -1,6 +1,7 @@
import Menu from './menu';
import Navigation from './navigation';
import Image from './image';
+import ReplyToComment from './reply-to-comment';
const windy = {
images: [],
@@ -21,4 +22,8 @@ export default function init() {
});
windy.menu.init();
windy.navigation.init();
+ windy.comments = new ReplyToComment({
+ form: document.querySelector('.comments__foot'),
+ links: document.querySelectorAll('.comment__reply'),
+ });
}
diff --git a/theme/comments.php b/theme/comments.php
index ff6f522..122825d 100644
--- a/theme/comments.php
+++ b/theme/comments.php
@@ -2,9 +2,12 @@
$form_settings = array(
class_form => 'comment-respond__form',
class_submit => 'comment-respond__submit',
- comment_field => '
',
+ comment_field => '',
logged_in_as => '',
title_reply_before => '',
fields => array(
author => '',
diff --git a/theme/functions.php b/theme/functions.php
index 93b9841..d171c45 100644
--- a/theme/functions.php
+++ b/theme/functions.php
@@ -1,8 +1,10 @@
comment_ID;
+ $post_id = $comment->comment_post_ID;
+ $reply_link = get_permalink($post_id) . '?replytocom='.$id.'#respond';
$author_email = $comment->comment_author_email;
$author = $comment->comment_author;
$avatar = get_avatar($comment, 128);
@@ -19,10 +21,11 @@ class Windycoys_Walker_Comment extends Walker_Comment {
$output .= '';
$output .= ''.$content.'
';
$output .= '';
}
}