diff --git a/.ep_initialized b/.ep_initialized new file mode 100644 index 00000000..348ebd94 --- /dev/null +++ b/.ep_initialized @@ -0,0 +1 @@ +done \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..3aab6299 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +.git* +docs/ +examples/ +support/ +test/ +testing.js +.DS_Store +.ep_initialized \ No newline at end of file diff --git a/commentManager.js b/commentManager.js new file mode 100644 index 00000000..7b107c40 --- /dev/null +++ b/commentManager.js @@ -0,0 +1,55 @@ + +var db = require('ep_etherpad-lite/node/db/DB').db; +var ERR = require("ep_etherpad-lite/node_modules/async-stacktrace"); +var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; + +exports.getComments = function (padId, callback) +{ + //get the globalComments + db.get("comments:" + padId, function(err, comments) + { + if(ERR(err, callback)) return; + + //comment does not exists + if(comments == null) comments = {}; + + /*var comments = []; + var jsonComments = globalComments.comments; + for (var commentId in jsonComments) + { + comments[commentId] = jsonComments; + }*/ + + callback(null, { comments: comments }); + }); +}; + +exports.addComment = function(padId, data, callback) +{ + //create the new comment + var commentId = "c-" + randomString(16); + + //get the entry + db.get("comments:" + padId, function(err, comments){ + + if(ERR(err, callback)) return; + + // the entry doesn't exist so far, let's create it + if(comments == null) comments = {}; + + var comment = { + "author": data.author, + "name": data.name, + "text": data.text, + "timestamp": new Date().getTime() + }; + + //add the entry for this pad + comments[commentId] = comment; + + //save the new element back + db.set("comments:" + padId, comments); + + callback(null, commentId, comment); + }); +}; \ No newline at end of file diff --git a/comments.js b/comments.js new file mode 100644 index 00000000..83fe0690 --- /dev/null +++ b/comments.js @@ -0,0 +1,30 @@ + +var commentManager = require('./commentManager'); +var padManager = require("ep_etherpad-lite/node/db/PadManager"); +var ERR = require("ep_etherpad-lite/node_modules/async-stacktrace"); + +function padExists(padID){ + padManager.doesPadExists(padID, function(err, exists){ + return exists; + }); +} + +exports.getPadComments = function(padID, callback) +{ + commentManager.getComments(padID, function (err, padComments) + { + if(ERR(err, callback)) return; + + if(padComments !== null) callback(null, padComments); + }); +}; + +exports.addPadComment = function(padID, data, callback) +{ + commentManager.addComment(padID, data, function (err, commentID) + { + if(ERR(err, callback)) return; + + if(commentID !== null) callback(null, commentID); + }); +}; \ No newline at end of file diff --git a/ep.json b/ep.json new file mode 100644 index 00000000..77062138 --- /dev/null +++ b/ep.json @@ -0,0 +1,19 @@ +{ + "parts": [ + { + "name": "main", + "pre": ["ep_etherpad-lite/webaccess"], + "client_hooks": { + "postAceInit": "ep_comments/static/js/index", + "aceAttribsToClasses": "ep_comments/static/js/index", + "aceEditorCSS": "ep_comments/static/js/index" + }, + "hooks": { + "socketio": "ep_comments/index", + "eejsBlock_editbarMenuLeft": "ep_comments/index", + "eejsBlock_scripts": "ep_comments/index", + "eejsBlock_styles": "ep_comments/index" + } + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 00000000..c3f09855 --- /dev/null +++ b/index.js @@ -0,0 +1,86 @@ + +var eejs = require('ep_etherpad-lite/node/eejs/'); +var commentManager = require('./commentManager'); + +exports.socketio = function (hook_name, args, cb){ + var app = args.app; + var io = args.io; + var pushComment; + var padComment = io; + + var commentSocket = io + .of('/comment') + .on('connection', function (socket) { + + socket.on('getComments', function (data, callback) { + var padId = data.padId; + + socket.join(padId); + + commentManager.getComments(padId, function (err, comments){ + callback(comments); + }); + }); + + socket.on('addComment', function (data, callback) { + var padId = data.padId; + var content = data.comment; + + commentManager.addComment(padId, content, function (err, commentId, comment){ + socket.broadcast.to(padId).emit('pushAddComment', commentId, comment); + callback(commentId, comment); + }); + }); + + }); +}; + +exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) { + args.content = args.content + eejs.require("ep_comments/templates/commentBarButtons.ejs"); + return cb(); +}; + +exports.eejsBlock_scripts = function (hook_name, args, cb) { + args.content = args.content + eejs.require("ep_comments/templates/comments.html", {}, module); + return cb(); +}; + +exports.eejsBlock_styles = function (hook_name, args, cb) { + args.content = args.content + eejs.require("ep_comments/templates/styles.html", {}, module); + return cb(); +}; + +/* +exports.expressCreateServer = function (hook_name, args, cb) { + var app = args.app; + + app.get('/p/:pad/:rev?/comments', function(req, res, next) { + var padId = req.params.pad; + var revision = req.params.rev ? req.params.rev : null; + + comments.getPadComments(padId, revision, function(err, padComments) { + res.render('comments.ejs', { locals: { comments: padComments } }); + }); + }); + + app.get('/p/:pad/:rev?/add/comment/:name/:text', function(req, res, next) { + var padId = req.params.pad; + var revision = req.params.rev ? req.params.rev : null; + var data = { + author: "empty", + selection: "empty", + name: req.params.name, + text: req.params.text + }; + + comments.addPadComment(padId, data, revision, function(err, commentId) { + res.contentType('text/x-json'); + res.send('{ "commentId": "'+ commentId +'" }'); + }); + }); + app.configure(function(){ + args.app.set('views', __dirname + '/views'); + args.app.set('view options', {layout: false}); + args.app.engine('ejs', require('ejs').renderFile); + } +};*/ \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..14e96d7b --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "description": "Adds comments on sidebar and link it to the text.", + "name": "ep_comments", + "version": "0.0.1", + "author": { + "name": "Nicolas Lescop", + "email": "limplementeur@gmail.com" + }, + "contributors": [ + { + "name": "Nicolas Lescop", + "email": "limplementeur@gmail.com" + } + ], + "dependencies": {}, + "engines": { + "node": "*" + }, + "homepage": "https://github.com/nicolas-lescop/etherpad-plugins", + "devDependencies": {}, + "repository": { + "type": "git", + "url": "git://github.com/nicolas-lescop/etherpad-plugins.git" + }, + "_id": "ep_comments@0.0.1", + "_from": "etherpad-plugins/ep_comments", + "readme": "ERROR: No README.md file found!", + "dist": { + "shasum": "99884ed7ddab0e5b74f57d7d59cc618e8cf681f3" + } +} diff --git a/static/css/comment.css b/static/css/comment.css new file mode 100644 index 00000000..f31f2fae --- /dev/null +++ b/static/css/comment.css @@ -0,0 +1,84 @@ +.comment { + background: #FFCC91 !important; + border-radius: 2px; +} + +#comments { + width: 180px; + font-family: Helvetica, Arial, sans-serif; +} + +.sidebar-comment { + margin-left: 10px; + padding: 8px 5px; + margin-top: 10px; + background: white; + width: 167px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + -moz-box-shadow: 0 0 2px #888; + -webkit-box-shadow: 0 0 2px #888; + box-shadow: 0 0 2px #888; +} + +.sidebar-comment.mouseover { + margin: 8px 0 0 14px; + /*-moz-box-shadow: 0 0 2px #EB5C2E; + -webkit-box-shadow: 0 0 2px #EB5C2E; + box-shadow: 0 0 2px #EB5C2E;*/ + background: #FFF9F7; + -webkit-transition: margin 300ms ease-in, background 300ms ease-in; + -moz-transition: margin 300ms ease-in, background 300ms ease-in; + -ms-transition: margin 300ms ease-in, background 300ms ease-in; + -o-transition: margin 300ms ease-in, background 300ms ease-in; + transition: margin 300ms ease-in, background 300ms ease-in; +} + +.comment-author-name { + color: #555; + font-weight:bold; + font-size: 1.2em; +} + +.comment-date { + line-height: 0.9em; + font-size: 0.9em; +} + +.comment-text { + margin-top: 5px; + font-size: 1em; + color: #333; + width: 160px; + word-wrap: break-word; + white-space : normal; +} + +.comment-content { + border: 2px solid #DDD; + background: #fff; + width: 160px; + height: 60px; + padding: 2px; +} + +.comment-content:focus { + border: 2px solid #ccc; +} + +.comment-buttons input { + color: #666; + border: 2px solid #DDD; + background: #EEE; + width: 81px; + height: 30px; +} + +.comment-buttons input[type="submit"] { + margin-right: 5px; +} + +.comment-buttons input:hover { + color: #333; + cursor: pointer; +} \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 00000000..ef342f91 --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,10 @@ +.commenticon { + /*background-image: url("../../static/plugins/ep_comments/static/img/balloon-box.png");*/ + + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2lpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wUmlnaHRzPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvcmlnaHRzLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcFJpZ2h0czpNYXJrZWQ9IkZhbHNlIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjZDRDA5NkYwNDAwRDExRTE4ODdFRTIxODU1QTc4MDg0IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjZDRDA5NkVGNDAwRDExRTE4ODdFRTIxODU1QTc4MDg0IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzMgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOkFDMUYyRTgzMzI0QURGMTFBQUI4QzUzOTBEODVCNUIzIiBzdFJlZjpkb2N1bWVudElEPSJ1dWlkOkM5RDM0OTY2NEEzQ0REMTFCMDhBQkJCQ0ZGMTcyMTU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+jGjYgAAAAY9JREFUeNqkU7tOAkEUvTO7rmJBTPBBYXxEIVRa2mhBjIWJ/oD2dEps/AltaPkLEowlJv6AoeJhTKQwi/gmIOvOwzvMLqzFRhJucnbuzOScPffODJFSwjhBYcww1ec6a6RwSCNmRuR9IEr7OV7pCwgJuzvnpdNoPJGE0JL8dQJfdqV2e7Gn3GsBLiAWXVhPgtMe6ffR+bWk4gxK4BIMkC5acUcrgHucgYDAiWB/BDhvQ899AsdtAsNciJ7uOp0Cy4prji/ABI5cCXDc6ELHqSPRxqr58Lio5WUCGHvTnIADE6SAH/4M384DNtUF05gOrYAQqjkDBxxMxlrgMhtMGsGVyL9tUJxgCZ+tx7tGbHF5STnRR4aQMnB8w3hp3DcUJ+igULg8U/ls2B8zuWImnz3I+xoEoNAvR72Fw00KoddHEroSI1urc3Ci5jdVcaTGehOganuNKJYl8dxMICwPkxpSrXXSKVrcTpDjq7LcwPk74hXR7TsgGN7DUjA8mIHc3yP6GoG6FF3kumTc5/wrwACz+7J0ldOl/AAAAABJRU5ErkJggg=="); + background-repeat: no-repeat; + display: inline-block; + height: 16px; + vertical-align: middle; + width: 16px; +} \ No newline at end of file diff --git a/static/js/index.js b/static/js/index.js new file mode 100644 index 00000000..fd8fe949 --- /dev/null +++ b/static/js/index.js @@ -0,0 +1,358 @@ +var _, $, jQuery; + +var $ = require('ep_etherpad-lite/static/js/rjquery').$; +var _ = require('ep_etherpad-lite/static/js/underscore'); +var cssFiles = ['ep_comments/static/css/comment.css']; + +/************************************************************************/ +/* ep_comments Plugin */ +/************************************************************************/ +function prettyDate(time){ + var time_formats = [ + [60, 'seconds', 1], // 60 + [120, '1 minute ago', '1 minute from now'], // 60*2 + [3600, 'minutes', 60], // 60*60, 60 + [7200, '1 hour ago', '1 hour from now'], // 60*60*2 + [86400, 'hours', 3600], // 60*60*24, 60*60 + [172800, 'yesterday', 'tomorrow'], // 60*60*24*2 + [604800, 'days', 86400], // 60*60*24*7, 60*60*24 + [1209600, 'last week', 'next week'], // 60*60*24*7*4*2 + [2419200, 'weeks', 604800], // 60*60*24*7*4, 60*60*24*7 + [4838400, 'last month', 'next month'], // 60*60*24*7*4*2 + [29030400, 'months', 2419200], // 60*60*24*7*4*12, 60*60*24*7*4 + [58060800, 'last year', 'next year'], // 60*60*24*7*4*12*2 + [2903040000, 'years', 29030400], // 60*60*24*7*4*12*100, 60*60*24*7*4*12 + [5806080000, 'last century', 'next century'], // 60*60*24*7*4*12*100*2 + [58060800000, 'centuries', 2903040000] // 60*60*24*7*4*12*100*20, 60*60*24*7*4*12*100 + ]; + /* + var time = ('' + date_str).replace(/-/g,"/").replace(/[TZ]/g," ").replace(/^\s\s*/ /*rappel , '').replace(/\s\s*$/, ''); + if(time.substr(time.length-4,1)==".") time =time.substr(0,time.length-4); + */ + var seconds = (new Date - new Date(time)) / 1000; + var token = 'ago', list_choice = 1; + if (seconds < 0) { + seconds = Math.abs(seconds); + token = 'from now'; + list_choice = 2; + } + var i = 0, format; + while (format = time_formats[i++]) + if (seconds < format[0]) { + if (typeof format[2] == 'string') + return format[list_choice]; + else + return Math.floor(seconds / format[2]) + ' ' + format[1] + ' ' + token; + } + return time; +}; + + +// Container +function ep_comments(context){ + this.container = null; + this.padOuter = null; + this.sideDiv = null; + this.padInner = null; + this.ace = context.ace; + this.socket = io.connect('/comment'); + this.padId = clientVars.padId; + this.comments = []; + + this.init(); +} + +// Init Etherpad plugin comment pads +ep_comments.prototype.init = function(){ + var self = this; + var ace = this.ace; + + // Init prerequisite + this.findContainers(); + this.insertContainer(); + this.hideLineNumbers(); + + // Get all comments + this.getComments(function (comments){ + if (!$.isEmptyObject(comments)){ + self.setComments(comments); + self.collectComments(); + } + }); + + // Init add push event + this.pushComment('add', function (commentId, comment){ + self.setComment(commentId, comment); + console.log('pushComment', comment); + window.setTimeout(function() { + self.collectComments(); + }, 300); + }); + + // On click comment icon toolbar + $('#addComment').on('click', function(){ + // If a new comment box doesn't already exist + // Add a new comment and link it to the selection + if (self.container.find('#newComment').length == 0) self.addComment(); + }); +}; + +// Insert comments container on sideDiv element use for linenumbers +ep_comments.prototype.findContainers = function(){ + var padOuter = $('iframe[name="ace_outer"]').contents(); + + this.padOuter = padOuter; + this.sideDiv = padOuter.find('#sidediv'); + this.padInner = padOuter.find('iframe[name="ace_inner"]'); +}; + +// Hide line numbers +ep_comments.prototype.hideLineNumbers = function(){ + this.sideDiv.find('table').hide(); +}; + +// Collect Comments and link text content to the sidediv +ep_comments.prototype.collectComments = function(callback){ + var self = this; + var container = this.container; + var comments = this.comments; + var padComment = this.padInner.contents().find('.comment'); + + padComment.each(function(it){ + var $this = $(this); + var cls = $this.attr('class'); + var classCommentId = /(?:^| )(c-[A-Za-z0-9]*)/.exec(cls); + var commentId = (classCommentId) ? classCommentId[1] : null; + + if (commentId === null) { + var isAuthorClassName = /(?:^| )(a.[A-Za-z0-9]*)/.exec(cls); + if (isAuthorClassName) self.removeComment(isAuthorClassName[1], it); + + console.log('o_O', cls); + return; + } + + var commentId = classCommentId[1]; + var commentElm = container.find('#'+ commentId); + var comment = comments[commentId]; + + if (comment !== null) { + // If comment is not in sidebar insert it + if (commentElm.length == 0) { + self.insertComment(commentId, comment.data, it); + commentElm = container.find('#'+ commentId); + + $this.mouseenter(function(){ + commentElm.addClass('mouseover'); + }).mouseleave(function(){ + commentElm.removeClass('mouseover'); + }); + + $(this).on('click', function(){ + markerTop = $(this).position().top; + commentTop = commentElm.position().top; + containerTop = container.css('top'); + console.log(container); + container.css('top', containerTop - (commentTop - markerTop)); + }); + } + } + + var prevCommentElm = commentElm.prev(); + var commentPos; + + if (prevCommentElm.length == 0) { + commentPos = 0; + } else { + var prevCommentPos = prevCommentElm.css('top'); + var prevCommentHeight = prevCommentElm.innerHeight(); + + commentPos = parseInt(prevCommentPos) + prevCommentHeight + 30; + } + + commentElm.css({ 'top': commentPos }); + }); +}; + +ep_comments.prototype.removeComment = function(className, id){ + console.log('remove comment', className, id); +} + +// Insert comment container in sidebar +ep_comments.prototype.insertContainer = function(){ + var sideDiv = this.sideDiv; + + // Add comments + sideDiv.prepend('
'); + + this.container = sideDiv.find('#comments'); +}; + +// Insert new Comment Form +ep_comments.prototype.insertNewComment = function(comment, callback){ + var index = 0; + + this.insertComment("", comment, index, true); + + this.container.find('#newComment #comment-reset').on('click',function(){ + var form = $(this).parent().parent(); + form.remove(); + }); + + this.container.find('#newComment').submit(function(){ + var form = $(this); + var text = form.find('.comment-content').val(); + + if (text.length != 0) { + form.remove(); + callback(text, index); + } + + return false; + }); +}; + +// Insert a comment node +ep_comments.prototype.insertComment = function(commentId, comment, index, isNew){ + var template = (isNew === true) ? 'newCommentTemplate' : 'commentsTemplate'; + var content = null; + var container = this.container; + var commentAfterIndex = container.find('.sidebar-comment').eq(index); + + comment.commentId = commentId; + content = $('#'+ template).tmpl(comment); + + console.log('position', index, commentAfterIndex); + if (index === 0) { + content.prependTo(container); + } else if (commentAfterIndex.length === 0) { + content.appendTo(container); + } else { + commentAfterIndex.before(content); + } +}; + +// Set comments content data +ep_comments.prototype.setComments = function(comments){ + for(var commentId in comments){ + this.setComment(commentId, comments[commentId]); + } +}; + +// Set comment data +ep_comments.prototype.setComment = function(commentId, comment){ + var comments = this.comments; + comment.date = prettyDate(comment.timestamp); + if (comments[commentId] == null) comments[commentId] = {}; + comments[commentId].data = comment; +}; + +// Get all comments +ep_comments.prototype.getComments = function (callback){ + var req = { padId: this.padId }; + + this.socket.emit('getComments', req, function (res){ + callback(res.comments); + }); +}; + +ep_comments.prototype.getCommentData = function (){ + var data = {}; + + // Insert comment data + data.padId = this.padId; + data.comment = {}; + data.comment.author = clientVars.userId; + data.comment.name = clientVars.userName; + data.comment.timestamp = new Date().getTime(); + + // Si le client est Anonyme + if(data.comment.name === undefined){ + data.comment.name = clientVars.userAgent; + } + + return data; +} + +// Add a pad comment +ep_comments.prototype.addComment = function (callback){ + var socket = this.socket; + var data = this.getCommentData(); + var ace = this.ace; + var self = this; + var rep = {}; + + ace.callWithAce(function (ace){ + var saveRep = ace.ace_getRep(); + rep.selStart = saveRep.selStart; + rep.selEnd = saveRep.selEnd; + },'saveCommentedSelection', true); + + if (rep.selStart[0] == rep.selEnd[0] && rep.selStart[1] == rep.selEnd[1]) { + return; + } + + this.insertNewComment(data, function (text, index){ + data.comment.text = text; + + // Save comment + socket.emit('addComment', data, function (commentId, comment){ + comment.commentId = commentId; + + //callback(commentId); + ace.callWithAce(function (ace){ + console.log('addComment :: ', commentId); + ace.ace_performSelectionChange(rep.selStart,rep.selEnd,true); + ace.ace_setAttributeOnSelection('comment', commentId); + },'insertComment', true); + + self.setComment(commentId, comment); + self.collectComments(); + }); + }); +}; + +// Push comment from collaborators +ep_comments.prototype.pushComment = function(eventType, callback){ + var socket = this.socket; + + // On collaborator add a comment in the current pad + if (eventType == 'add'){ + socket.on('pushAddComment', function (commentId, comment){ + callback(commentId, comment); + }); + } + + // On collaborator delete a comment in the current pad + else if (eventType == 'remove'){ + socket.on('pushRemoveComment', function (commentId){ + callback(commentId); + }); + } +}; + +/************************************************************************/ +/* Etherpad Hooks */ +/************************************************************************/ + +var hooks = { + + // Init pad comments + postAceInit: function(hook, context){ + var Comments = new ep_comments(context); + }, + + // Insert comments classes + aceAttribsToClasses: function(hook, context){ + if(context.key == 'comment') return ['comment', context.value]; + }, + + aceEditorCSS: function(){ + return cssFiles; + } + +}; + +exports.aceEditorCSS = hooks.aceEditorCSS; +exports.postAceInit = hooks.postAceInit; +exports.aceAttribsToClasses = hooks.aceAttribsToClasses; \ No newline at end of file diff --git a/static/js/jquery.tmpl.min.js b/static/js/jquery.tmpl.min.js new file mode 100644 index 00000000..f08e81dc --- /dev/null +++ b/static/js/jquery.tmpl.min.js @@ -0,0 +1 @@ +(function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},h=0,c=0,l=[];function g(e,d,g,i){var c={data:i||(d?d.data:{}),_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};e&&a.extend(c,e,{nodes:[],parent:d});if(g){c.tmpl=g;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++h;(l.length?f:b)[h]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h=0;i--)m(j[i]);m(k)}function m(j){var p,i=j,k,e,m;if(m=j.getAttribute(d)){while(i.parentNode&&(i=i.parentNode).nodeType===1&&!(p=i.getAttribute(d)));if(p!==m){i=i.parentNode?i.nodeType===11?0:i.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[i]||f[i],null,true);e.key=++h;b[h]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;i=a.data(j.parentNode,"tmplItem");i=i?i.key:0}if(e){k=e;while(k&&k.key!=i){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent,null,true)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery) \ No newline at end of file diff --git a/templates/commentBarButtons.ejs b/templates/commentBarButtons.ejs new file mode 100644 index 00000000..8e375d81 --- /dev/null +++ b/templates/commentBarButtons.ejs @@ -0,0 +1,4 @@ +
+