diff --git a/README.md b/README.md index 05d072c..9a7060e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,19 @@ You'll need to include the following single JavaScript file and two CSS files to - `release/side-comments.css` - `release/themes/default-theme.css` -You can choose **not** to include `default-theme.css`, but you'll need to style SideComments youself if you choose not to include it, as `side-comments.css` handles only the basic layout styling and not making it all pretty and looking like Medium.com. +You can choose **not** to include `default-theme.css`, but you'll need to style SideComments yourself if you choose not to include it, as `side-comments.css` handles only the basic layout styling and not making it all pretty and looking like Medium.com. + +#### 2.1 Include SideComments.js in your Wordpress theme. + +You must ensure that jQuery is enqueued by your theme before loading SideComments.js and that the directory paths are set appropriately. + +It's currently set to look for the SideComments.js and SideComments.css file in your root theme directory. + +Include the PHP functions in wordpress/functions.php to +- Register and enqueue SideComments.js and SideComments.css +- Append the commentable-container class to each articles container using the post_class function +- Create commentable sections from each paragraph in the post appendinng the commentable-section class +- Append section_id to each paragraph incrementally starting at 1 ### 3. Set up your HTML. diff --git a/wordpress/functions.php b/wordpress/functions.php new file mode 100644 index 0000000..84514f6 --- /dev/null +++ b/wordpress/functions.php @@ -0,0 +1 @@ +|', function ( $matches ) { static $i = 1; return sprintf( '
', $i++ );
}, $content
);
}
add_filter( 'the_content', 'incrementparagraphs', 10, 1 );
}
\ No newline at end of file
diff --git a/wordpress/side-comments.css b/wordpress/side-comments.css
new file mode 100644
index 0000000..4701721
--- /dev/null
+++ b/wordpress/side-comments.css
@@ -0,0 +1,205 @@
+@-webkit-keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+@keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+.commentable-container {
+ -webkit-transition: all 0.22s ease;
+ transition: all 0.22s ease;
+}
+.side-comments-open {
+ -webkit-transform: translate(-300px, 0);
+ -ms-transform: translate(-300px, 0);
+ transform: translate(-300px, 0);
+}
+.commentable-section {
+ position: relative;
+}
+.commentable-section:hover .side-comment .marker {
+ display: block;
+}
+.side-comment {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 20px;
+ min-height: 100%;
+ height: 100%;
+}
+.side-comment * {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.side-comment .hide {
+ display: none;
+}
+.side-comment .marker {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 20px;
+ height: 18px;
+ cursor: pointer;
+ background: #DEDEDC;
+ border-radius: 2px;
+ text-decoration: none;
+}
+.side-comment .marker:before,
+.side-comment .marker span {
+ content: "+";
+ display: block;
+ position: absolute;
+ width: 20px;
+ height: 18px;
+ line-height: 16px;
+ font-size: 14px;
+ color: #FFF;
+ text-align: center;
+}
+.side-comment .marker span {
+ display: none;
+ line-height: 20px;
+ font-size: 12px;
+}
+.side-comment.active .marker,
+.side-comment.has-comments .marker,
+.side-comment.has-comments ul.comments {
+ display: block;
+}
+.side-comment .marker:after {
+ content: "";
+ display: block;
+ position: absolute;
+ bottom: -7px;
+ left: 5px;
+ width: 0;
+ border-width: 7px 8px 0 0;
+ border-style: solid;
+ border-color: #DEDEDC transparent;
+}
+.side-comment .marker:hover,
+.side-comment.active .marker {
+ background: #4FAF62;
+}
+.side-comment .marker:hover:after,
+.side-comment.active .marker:after {
+ border-color: #4FAF62 transparent;
+}
+.side-comment .add-comment {
+ display: none;
+}
+.side-comment.has-comments .add-comment,
+.side-comment.no-current-user .add-comment {
+ display: block;
+}
+.side-comment.no-current-user .add-comment {
+ margin-top: 20px;
+}
+.side-comment.has-comments .marker:before {
+ content: "";
+}
+.side-comment.has-comments .marker span {
+ display: block;
+}
+.side-comment.has-comments .add-comment.hide {
+ display: none;
+}
+.side-comment.has-comments .comment-form {
+ display: none;
+}
+.side-comment .comments-wrapper {
+ display: none;
+ position: absolute;
+ top: 0;
+ left: 40px;
+ width: 280px;
+ padding-bottom: 120px;
+}
+.side-comment .comments {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: none;
+ width: 100%;
+}
+.side-comment .comments li {
+ width: 100%;
+ overflow: hidden;
+}
+.side-comment .author-avatar {
+ float: left;
+ width: 32px;
+ height: 32px;
+ margin-right: 10px;
+}
+.side-comment .author-avatar img {
+ width: 100%;
+ height: 100%;
+}
+.side-comment .right-of-avatar {
+ float: left;
+ width: 238px;
+}
+.side-comment .comment-box:empty:not(:focus):before {
+ content: attr(data-placeholder-content);
+ float: left;
+}
+.side-comment .comment,
+.side-comment .comment-box,
+.side-comment .actions {
+ margin: 0;
+}
+.side-comment .actions,
+.side-comment .delete {
+ margin-left: 42px;
+}
+.side-comment .comment-box {
+ outline: none;
+}
+.side-comment .add-comment.active {
+ display: block;
+}
+.side-comment .comment-form {
+ overflow: hidden;
+}
+.side-comment .comment-form.active {
+ display: block;
+}
+.side-comment.active .comments-wrapper {
+ display: block;
+ -webkit-animation: fadein 0.2s;
+ animation: fadein 0.2s;
+}
+@media (max-width: 768px) {
+ body {
+ -webkit-overflow-scrolling: touch;
+ overflow-x: hidden;
+ }
+ .side-comments-open {
+ -webkit-transform: translate(-220px, 0);
+ -ms-transform: translate(-220px, 0);
+ transform: translate(-220px, 0);
+ }
+ .side-comment .comments-wrapper {
+ width: 200px;
+ }
+ .side-comment .right-of-avatar {
+ width: 158px;
+ }
+ .side-comment .marker {
+ display: block;
+ }
+}
diff --git a/wordpress/side-comments.js b/wordpress/side-comments.js
new file mode 100644
index 0000000..6a16dda
--- /dev/null
+++ b/wordpress/side-comments.js
@@ -0,0 +1,3250 @@
+
+/**
+ * Require the given path.
+ *
+ * @param {String} path
+ * @return {Object} exports
+ * @api public
+ */
+
+function require(path, parent, orig) {
+ var resolved = require.resolve(path);
+
+ // lookup failed
+ if (null == resolved) {
+ orig = orig || path;
+ parent = parent || 'root';
+ var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
+ err.path = orig;
+ err.parent = parent;
+ err.require = true;
+ throw err;
+ }
+
+ var module = require.modules[resolved];
+
+ // perform real require()
+ // by invoking the module's
+ // registered function
+ if (!module._resolving && !module.exports) {
+ var mod = {};
+ mod.exports = {};
+ mod.client = mod.component = true;
+ module._resolving = true;
+ module.call(this, mod.exports, require.relative(resolved), mod);
+ delete module._resolving;
+ module.exports = mod.exports;
+ }
+
+ return module.exports;
+}
+
+/**
+ * Registered modules.
+ */
+
+require.modules = {};
+
+/**
+ * Registered aliases.
+ */
+
+require.aliases = {};
+
+/**
+ * Resolve `path`.
+ *
+ * Lookup:
+ *
+ * - PATH/index.js
+ * - PATH.js
+ * - PATH
+ *
+ * @param {String} path
+ * @return {String} path or null
+ * @api private
+ */
+
+require.resolve = function(path) {
+ if (path.charAt(0) === '/') path = path.slice(1);
+
+ var paths = [
+ path,
+ path + '.js',
+ path + '.json',
+ path + '/index.js',
+ path + '/index.json'
+ ];
+
+ for (var i = 0; i < paths.length; i++) {
+ var path = paths[i];
+ if (require.modules.hasOwnProperty(path)) return path;
+ if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
+ }
+};
+
+/**
+ * Normalize `path` relative to the current path.
+ *
+ * @param {String} curr
+ * @param {String} path
+ * @return {String}
+ * @api private
+ */
+
+require.normalize = function(curr, path) {
+ var segs = [];
+
+ if ('.' != path.charAt(0)) return path;
+
+ curr = curr.split('/');
+ path = path.split('/');
+
+ for (var i = 0; i < path.length; ++i) {
+ if ('..' == path[i]) {
+ curr.pop();
+ } else if ('.' != path[i] && '' != path[i]) {
+ segs.push(path[i]);
+ }
+ }
+
+ return curr.concat(segs).join('/');
+};
+
+/**
+ * Register module at `path` with callback `definition`.
+ *
+ * @param {String} path
+ * @param {Function} definition
+ * @api private
+ */
+
+require.register = function(path, definition) {
+ require.modules[path] = definition;
+};
+
+/**
+ * Alias a module definition.
+ *
+ * @param {String} from
+ * @param {String} to
+ * @api private
+ */
+
+require.alias = function(from, to) {
+ if (!require.modules.hasOwnProperty(from)) {
+ throw new Error('Failed to alias "' + from + '", it does not exist');
+ }
+ require.aliases[to] = from;
+};
+
+/**
+ * Return a require function relative to the `parent` path.
+ *
+ * @param {String} parent
+ * @return {Function}
+ * @api private
+ */
+
+require.relative = function(parent) {
+ var p = require.normalize(parent, '..');
+
+ /**
+ * lastIndexOf helper.
+ */
+
+ function lastIndexOf(arr, obj) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * The relative require() itself.
+ */
+
+ function localRequire(path) {
+ var resolved = localRequire.resolve(path);
+ return require(resolved, parent, path);
+ }
+
+ /**
+ * Resolve relative to the parent.
+ */
+
+ localRequire.resolve = function(path) {
+ var c = path.charAt(0);
+ if ('/' == c) return path.slice(1);
+ if ('.' == c) return require.normalize(p, path);
+
+ // resolve deps by returning
+ // the dep in the nearest "deps"
+ // directory
+ var segs = parent.split('/');
+ var i = lastIndexOf(segs, 'deps') + 1;
+ if (!i) i = 0;
+ path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
+ return path;
+ };
+
+ /**
+ * Check if module is defined at `path`.
+ */
+
+ localRequire.exists = function(path) {
+ return require.modules.hasOwnProperty(localRequire.resolve(path));
+ };
+
+ return localRequire;
+};
+require.register("component-emitter/index.js", function(exports, require, module){
+
+/**
+ * Expose `Emitter`.
+ */
+
+module.exports = Emitter;
+
+/**
+ * Initialize a new `Emitter`.
+ *
+ * @api public
+ */
+
+function Emitter(obj) {
+ if (obj) return mixin(obj);
+};
+
+/**
+ * Mixin the emitter properties.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function mixin(obj) {
+ for (var key in Emitter.prototype) {
+ obj[key] = Emitter.prototype[key];
+ }
+ return obj;
+}
+
+/**
+ * Listen on the given `event` with `fn`.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.on =
+Emitter.prototype.addEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+ (this._callbacks[event] = this._callbacks[event] || [])
+ .push(fn);
+ return this;
+};
+
+/**
+ * Adds an `event` listener that will be invoked a single
+ * time then automatically removed.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.once = function(event, fn){
+ var self = this;
+ this._callbacks = this._callbacks || {};
+
+ function on() {
+ self.off(event, on);
+ fn.apply(this, arguments);
+ }
+
+ on.fn = fn;
+ this.on(event, on);
+ return this;
+};
+
+/**
+ * Remove the given callback for `event` or all
+ * registered callbacks.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.off =
+Emitter.prototype.removeListener =
+Emitter.prototype.removeAllListeners =
+Emitter.prototype.removeEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+
+ // all
+ if (0 == arguments.length) {
+ this._callbacks = {};
+ return this;
+ }
+
+ // specific event
+ var callbacks = this._callbacks[event];
+ if (!callbacks) return this;
+
+ // remove all handlers
+ if (1 == arguments.length) {
+ delete this._callbacks[event];
+ return this;
+ }
+
+ // remove specific handler
+ var cb;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ if (cb === fn || cb.fn === fn) {
+ callbacks.splice(i, 1);
+ break;
+ }
+ }
+ return this;
+};
+
+/**
+ * Emit `event` with the given args.
+ *
+ * @param {String} event
+ * @param {Mixed} ...
+ * @return {Emitter}
+ */
+
+Emitter.prototype.emit = function(event){
+ this._callbacks = this._callbacks || {};
+ var args = [].slice.call(arguments, 1)
+ , callbacks = this._callbacks[event];
+
+ if (callbacks) {
+ callbacks = callbacks.slice(0);
+ for (var i = 0, len = callbacks.length; i < len; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return array of callbacks for `event`.
+ *
+ * @param {String} event
+ * @return {Array}
+ * @api public
+ */
+
+Emitter.prototype.listeners = function(event){
+ this._callbacks = this._callbacks || {};
+ return this._callbacks[event] || [];
+};
+
+/**
+ * Check if this emitter has `event` handlers.
+ *
+ * @param {String} event
+ * @return {Boolean}
+ * @api public
+ */
+
+Emitter.prototype.hasListeners = function(event){
+ return !! this.listeners(event).length;
+};
+
+});
+require.register("side-comments/js/main.js", function(exports, require, module){
+var _ = require('./vendor/lodash-custom.js');
+var Section = require('./section.js');
+var Emitter = require('emitter');
+var eventPipe = new Emitter;
+
+/**
+ * Creates a new SideComments instance.
+ * @param {Object} el The selector for the element for
+ * which side comments need to be initialized
+ * @param {Object} currentUser An object defining the current user. Used
+ * for posting new comments and deciding
+ * whether existing ones can be deleted
+ * or not.
+ * @param {Array} existingComments An array of existing comments, in
+ * the proper structure.
+ *
+ * TODO: **GIVE EXAMPLE OF STRUCTURE HERE***
+ */
+function SideComments( el, currentUser, existingComments ) {
+ this.$el = $(el);
+ this.$body = $('body');
+ this.eventPipe = eventPipe;
+
+ this.currentUser = _.clone(currentUser) || null;
+ this.existingComments = _.cloneDeep(existingComments) || [];
+ this.sections = [];
+ this.activeSection = null;
+
+ // Event bindings
+ this.eventPipe.on('showComments', _.bind(this.showComments, this));
+ this.eventPipe.on('hideComments', _.bind(this.hideComments, this));
+ this.eventPipe.on('sectionSelected', _.bind(this.sectionSelected, this));
+ this.eventPipe.on('sectionDeselected', _.bind(this.sectionDeselected, this));
+ this.eventPipe.on('commentPosted', _.bind(this.commentPosted, this));
+ this.eventPipe.on('commentDeleted', _.bind(this.commentDeleted, this));
+ this.eventPipe.on('addCommentAttempted', _.bind(this.addCommentAttempted, this));
+ this.$body.on('click', _.bind(this.bodyClick, this));
+ this.initialize(this.existingComments);
+}
+
+// Mix in Emitter
+Emitter(SideComments.prototype);
+
+/**
+ * Adds the comments beside each commentable section.
+ */
+SideComments.prototype.initialize = function( existingComments ) {
+ _.each(this.$el.find('.commentable-section'), function( section ){
+ var $section = $(section);
+ var sectionId = $section.data('section-id').toString();
+ var sectionComments = _.find(this.existingComments, { sectionId: sectionId });
+
+ this.sections.push(new Section(this.eventPipe, $section, this.currentUser, sectionComments));
+ }, this);
+};
+
+/**
+ * Shows the side comments.
+ */
+SideComments.prototype.showComments = function() {
+ this.$el.addClass('side-comments-open');
+};
+
+/**
+ * Hide the comments.
+ */
+SideComments.prototype.hideComments = function() {
+ if (this.activeSection) {
+ this.activeSection.deselect();
+ this.activeSection = null;
+ }
+
+ this.$el.removeClass('side-comments-open');
+};
+
+/**
+ * Callback after a section has been selected.
+ * @param {Object} section The Section object to be selected.
+ */
+SideComments.prototype.sectionSelected = function( section ) {
+ this.showComments();
+
+ if (this.activeSection) {
+ this.activeSection.deselect();
+ }
+
+ this.activeSection = section;
+};
+
+/**
+ * Callback after a section has been deselected.
+ * @param {Object} section The Section object to be selected.
+ */
+SideComments.prototype.sectionDeselected = function( section ) {
+ this.hideComments();
+ this.activeSection = null;
+};
+
+/**
+ * Fired when the commentPosted event is triggered.
+ * @param {Object} comment The comment object to be posted.
+ */
+SideComments.prototype.commentPosted = function( comment ) {
+ this.emit('commentPosted', comment);
+};
+
+/**
+ * Fired when the commentDeleted event is triggered.
+ * @param {Object} comment The commentId of the deleted comment.
+ */
+SideComments.prototype.commentDeleted = function( comment ) {
+ this.emit('commentDeleted', comment);
+};
+
+/**
+ * Fire an event to to signal that a comment as attempted to be added without
+ * a currentUser.
+ */
+SideComments.prototype.addCommentAttempted = function() {
+ this.emit('addCommentAttempted');
+};
+
+/**
+ * Inserts the given comment into the right section.
+ * @param {Object} comment A comment to be inserted.
+ */
+SideComments.prototype.insertComment = function( comment ) {
+ var section = _.find(this.sections, { id: comment.sectionId });
+ section.insertComment(comment);
+};
+
+/**
+ * Removes the given comment from the right section.
+ * @param sectionId The ID of the section where the comment exists.
+ * @param commentId The ID of the comment to be removed.
+ */
+SideComments.prototype.removeComment = function( sectionId, commentId ) {
+ var section = _.find(this.sections, { id: sectionId });
+ section.removeComment(commentId);
+};
+
+/**
+ * Delete the comment specified by the given sectionID and commentID.
+ * @param sectionId The section the comment belongs to.
+ * @param commentId The comment's ID
+ */
+SideComments.prototype.deleteComment = function( sectionId, commentId ) {
+ var section = _.find(this.sections, { id: sectionId });
+ section.deleteComment(commentId);
+};
+
+/**
+ * Checks if comments are visible or not.
+ * @return {Boolean} Whether or not the comments are visible.
+ */
+SideComments.prototype.commentsAreVisible = function() {
+ return this.$el.hasClass('side-comments-open');
+};
+
+/**
+ * Callback for body clicks. We hide the comments if someone clicks outside of the comments section.
+ * @param {Object} event The event object.
+ */
+SideComments.prototype.bodyClick = function( event ) {
+ var $target = $(event.target);
+
+ // We do a check on $('body') existing here because if the $target has
+ // no parent body then it's because it belongs to a deleted comment and
+ // we should NOT hide the SideComments.
+ if ($target.closest('.side-comment').length < 1 && $target.closest('body').length > 0) {
+ if (this.activeSection) {
+ this.activeSection.deselect();
+ }
+ this.hideComments();
+ }
+};
+
+/**
+ * Set the currentUser and update the UI as necessary.
+ * @param {Object} currentUser The currentUser to be used.
+ */
+SideComments.prototype.setCurrentUser = function( currentUser ) {
+ this.hideComments();
+ this.currentUser = currentUser;
+ _.each(this.sections, function( section ) {
+ section.currentUser = this.currentUser;
+ section.render();
+ });
+};
+
+/**
+ * Remove the currentUser and update the UI as necessary.
+ */
+SideComments.prototype.removeCurrentUser = function() {
+ this.hideComments();
+ this.currentUser = null;
+ _.each(this.sections, function( section ) {
+ section.currentUser = null;
+ section.render();
+ });
+};
+
+/**
+ * Destroys the instance of SideComments, including unbinding from DOM events.
+ */
+SideComments.prototype.destroy = function() {
+ this.hideComments();
+ this.$el.off();
+};
+
+module.exports = SideComments;
+});
+require.register("side-comments/js/section.js", function(exports, require, module){
+var _ = require('./vendor/lodash-custom.js');
+var Template = require('../templates/section.html');
+var CommentTemplate = require('../templates/comment.html');
+var mobileCheck = require('./helpers/mobile-check.js');
+
+/**
+ * Creates a new Section object, which is responsible for managing a
+ * single comment section.
+ * @param {Object} eventPipe The Emitter object used for passing around events.
+ * @param {Array} comments The array of comments for this section. Optional.
+ */
+function Section( eventPipe, $el, currentUser, comments ) {
+ this.eventPipe = eventPipe;
+ this.$el = $el;
+ this.comments = comments ? comments.comments : [];
+ this.currentUser = currentUser || null;
+ this.clickEventName = mobileCheck() ? 'touchstart' : 'click';
+
+ this.id = $el.data('section-id');
+
+ this.$el.on(this.clickEventName, '.side-comment .marker', _.bind(this.markerClick, this));
+ this.$el.on(this.clickEventName, '.side-comment .add-comment', _.bind(this.addCommentClick, this));
+ this.$el.on(this.clickEventName, '.side-comment .post', _.bind(this.postCommentClick, this));
+ this.$el.on(this.clickEventName, '.side-comment .cancel', _.bind(this.cancelCommentClick, this));
+ this.$el.on(this.clickEventName, '.side-comment .delete', _.bind(this.deleteCommentClick, this));
+ this.render();
+}
+
+/**
+ * Click callback event on markers.
+ * @param {Object} event The event object.
+ */
+Section.prototype.markerClick = function( event ) {
+ console.log('clicked');
+ event.preventDefault();
+ this.select();
+};
+
+/**
+ * Callback for the comment button click event.
+ * @param {Object} event The event object.
+ */
+Section.prototype.addCommentClick = function( event ) {
+ event.preventDefault();
+ if (this.currentUser) {
+ this.showCommentForm();
+ } else {
+ this.eventPipe.emit('addCommentAttempted');
+ }
+};
+
+/**
+ * Show the comment form for this section.
+ */
+Section.prototype.showCommentForm = function() {
+ if (this.comments.length > 0) {
+ this.$el.find('.add-comment').addClass('hide');
+ this.$el.find('.comment-form').addClass('active');
+ }
+
+ this.focusCommentBox();
+};
+
+/**
+ * Hides the comment form for this section.
+ */
+Section.prototype.hideCommentForm = function() {
+ if (this.comments.length > 0) {
+ this.$el.find('.add-comment').removeClass('hide');
+ this.$el.find('.comment-form').removeClass('active');
+ }
+
+ this.$el.find('.comment-box').empty();
+};
+
+/**
+ * Focus on the comment box in the comment form.
+ */
+Section.prototype.focusCommentBox = function() {
+ this.$el.find('.comment-box').get(0).focus();
+};
+
+/**
+ * Cancel comment callback.
+ * @param {Object} event The event object.
+ */
+Section.prototype.cancelCommentClick = function( event ) {
+ event.preventDefault();
+ this.cancelComment();
+};
+
+/**
+ * Cancel adding of a comment.
+ */
+Section.prototype.cancelComment = function() {
+ if (this.comments.length > 0) {
+ this.hideCommentForm();
+ } else {
+ this.deselect();
+ this.eventPipe.emit('hideComments');
+ }
+};
+
+/**
+ * Post comment callback.
+ * @param {Object} event The event object.
+ */
+Section.prototype.postCommentClick = function( event ) {
+ event.preventDefault();
+ this.postComment();
+};
+
+/**
+ * Post a comment to this section.
+ */
+Section.prototype.postComment = function() {
+ this.$el.find(".comment-box").children().not("br").each(function() {
+ $(this).replaceWith(this.innerHTML);
+ });
+ var commentBody = this.$el.find('.comment-box').html();
+ var comment = {
+ sectionId: this.id,
+ comment: commentBody,
+ authorAvatarUrl: this.currentUser.avatarUrl,
+ authorName: this.currentUser.name,
+ authorId: this.currentUser.id
+ };
+ this.eventPipe.emit('commentPosted', comment);
+};
+
+/**
+ * Insert a comment into this sections comment list.
+ * @param {Object} comment A comment object.
+ */
+Section.prototype.insertComment = function( comment ) {
+ this.comments.push(comment);
+ var newCommentHtml = _.template(CommentTemplate, {
+ comment: comment,
+ currentUser: this.currentUser
+ });
+ this.$el.find('.comments').append(newCommentHtml);
+ this.$el.find('.side-comment').addClass('has-comments');
+ this.updateCommentCount();
+ this.hideCommentForm();
+};
+
+/**
+ * Increments the comment count for a given section.
+ */
+Section.prototype.updateCommentCount = function() {
+ this.$el.find('.marker span').text(this.comments.length);
+};
+
+/**
+ * Event handler for delete comment clicks.
+ * @param {Object} event The event object.
+ */
+Section.prototype.deleteCommentClick = function( event ) {
+ event.preventDefault();
+ var commentId = $(event.target).closest('li').data('comment-id');
+
+ if (window.confirm("Are you sure you want to delete this comment?")) {
+ this.deleteComment(commentId);
+ }
+};
+
+/**
+ * Finds the comment and emits an event with the comment to be deleted.
+ */
+Section.prototype.deleteComment = function( commentId ) {
+ var comment = _.find(this.comments, { id: commentId });
+ comment.sectionId = this.id;
+ this.eventPipe.emit('commentDeleted', comment);
+};
+
+/**
+ * Removes the comment from the list of comments and the comment array.
+ * @param commentId The ID of the comment to be removed from this section.
+ */
+Section.prototype.removeComment = function( commentId ) {
+ this.comments = _.reject(this.comments, { id: commentId });
+ this.$el.find('.side-comment .comments li[data-comment-id="'+commentId+'"]').remove();
+ this.updateCommentCount();
+ if (this.comments.length < 1) {
+ this.$el.find('.side-comment').removeClass('has-comments');
+ }
+};
+
+/**
+ * Mark this section as selected. Delsect if this section is already selected.
+ */
+Section.prototype.select = function() {
+ if (this.isSelected()) {
+ this.deselect();
+ this.eventPipe.emit('sectionDeselected', this);
+ } else {
+ this.$el.find('.side-comment').addClass('active');
+
+ if (this.comments.length === 0 && this.currentUser) {
+ this.focusCommentBox();
+ }
+
+ this.eventPipe.emit('sectionSelected', this);
+ }
+};
+
+/**
+ * Deselect this section.
+ */
+Section.prototype.deselect = function() {
+ this.$el.find('.side-comment').removeClass('active');
+ this.hideCommentForm();
+};
+
+Section.prototype.isSelected = function() {
+ return this.$el.find('.side-comment').hasClass('active');
+};
+
+/**
+ * Get the class to be used on the side comment section wrapper.
+ * @return {String} The class names to use.
+ */
+Section.prototype.sectionClasses = function() {
+ var classes = '';
+
+ if (this.comments.length > 0) {
+ classes = classes + ' has-comments';
+ }
+ if (!this.currentUser) {
+ classes = classes + ' no-current-user'
+ }
+
+ return classes;
+};
+
+/**
+ * Render this section into the DOM.
+ */
+Section.prototype.render = function() {
+ this.$el.find('.side-comment').remove();
+ $(_.template(Template, {
+ commentTemplate: CommentTemplate,
+ comments: this.comments,
+ sectionClasses: this.sectionClasses(),
+ currentUser: this.currentUser
+ })).appendTo(this.$el);
+};
+
+/**
+ * Desttroy this Section object. Generally meaning unbind events.
+ */
+Section.prototype.destroy = function() {
+ this.$el.off();
+}
+
+module.exports = Section;
+});
+require.register("side-comments/js/vendor/lodash-custom.js", function(exports, require, module){
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build)