ep_comments_page
Advanced tools
Comparing version 0.1.7 to 0.1.8
@@ -0,231 +1,143 @@ | ||
/* global exports, require */ | ||
var _ = require('ep_etherpad-lite/static/js/underscore'); | ||
var db = require('ep_etherpad-lite/node/db/DB').db; | ||
var ERR = require("ep_etherpad-lite/node_modules/async-stacktrace"); | ||
var db = require('ep_etherpad-lite/node/db/DB'); | ||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; | ||
var readOnlyManager = require("ep_etherpad-lite/node/db/ReadOnlyManager.js"); | ||
var shared = require('./static/js/shared'); | ||
exports.getComments = async function (padId, callback) | ||
{ | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
exports.getComments = async (padId) => { | ||
// Not sure if we will encouter race conditions here.. Be careful. | ||
//get the globalComments | ||
db.get("comments:" + padId, function(err, comments) | ||
{ | ||
if(ERR(err, callback)) return; | ||
//comment does not exists | ||
if(comments == null) comments = {}; | ||
callback(null, { comments: comments }); | ||
}); | ||
// get the globalComments | ||
let comments = await db.get('comments:' + padId); | ||
if (comments == null) comments = {}; | ||
return {comments}; | ||
}; | ||
exports.deleteComment = function (padId, commentId, callback) | ||
{ | ||
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 = {}; | ||
delete comments[commentId]; | ||
db.set("comments:" + padId, comments); | ||
callback(padId, commentId); | ||
}); | ||
exports.deleteComment = async (padId, commentId) => { | ||
let comments = await db.get('comments:' + padId); | ||
// the entry doesn't exist so far, let's create it | ||
if (comments == null) comments = {}; | ||
delete comments[commentId]; | ||
await db.set('comments:' + padId, comments); | ||
}; | ||
exports.deleteComments = function (padId, callback) | ||
{ | ||
db.remove('comments:' + padId, function(err) | ||
{ | ||
if(ERR(err, callback)) return; | ||
callback(null); | ||
}); | ||
exports.deleteComments = async (padId) => { | ||
await db.remove('comments:' + padId); | ||
}; | ||
exports.addComment = function(padId, data, callback) | ||
{ | ||
exports.bulkAddComments(padId, [data], function(err, commentIds, comments) { | ||
if(ERR(err, callback)) return; | ||
if(commentIds && commentIds.length > 0 && comments && comments.length > 0) { | ||
callback(null, commentIds[0], comments[0]); | ||
} | ||
}); | ||
exports.addComment = async (padId, data) => { | ||
const [commentIds, comments] = await exports.bulkAddComments(padId, [data]); | ||
return [commentIds[0], comments[0]]; | ||
}; | ||
exports.bulkAddComments = async function(padId, data, callback) | ||
{ | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
exports.bulkAddComments = async (padId, data) => { | ||
// get the entry | ||
let comments = await db.get('comments:' + padId); | ||
//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 = {}; | ||
// the entry doesn't exist so far, let's create it | ||
if(comments == null) comments = {}; | ||
const newComments = []; | ||
const commentIds = data.map((commentData) => { | ||
// if the comment was copied it already has a commentID, so we don't need create one | ||
const commentId = commentData.commentId || shared.generateCommentId(); | ||
var newComments = []; | ||
var commentIds = _.map(data, function(commentData) { | ||
//if the comment was copied it already has a commentID, so we don't need create one | ||
var commentId = commentData.commentId || shared.generateCommentId(); | ||
const comment = { | ||
author: commentData.author || 'empty', | ||
name: commentData.name, | ||
text: commentData.text, | ||
changeTo: commentData.changeTo, | ||
changeFrom: commentData.changeFrom, | ||
timestamp: parseInt(commentData.timestamp) || new Date().getTime(), | ||
}; | ||
// add the entry for this pad | ||
comments[commentId] = comment; | ||
var comment = { | ||
"author": commentData.author || "empty", | ||
"name": commentData.name, | ||
"text": commentData.text, | ||
"changeTo": commentData.changeTo, | ||
"changeFrom": commentData.changeFrom, | ||
"timestamp": parseInt(commentData.timestamp) || new Date().getTime() | ||
}; | ||
//add the entry for this pad | ||
comments[commentId] = comment; | ||
newComments.push(comment); | ||
return commentId; | ||
}); | ||
newComments.push(comment); | ||
return commentId; | ||
}); | ||
// save the new element back | ||
await db.set('comments:' + padId, comments); | ||
//save the new element back | ||
db.set("comments:" + padId, comments); | ||
callback(null, commentIds, newComments); | ||
}); | ||
return [commentIds, newComments]; | ||
}; | ||
exports.copyComments = function(originalPadId, newPadID, callback) | ||
{ | ||
//get the comments of original pad | ||
db.get('comments:' + originalPadId, function(err, originalComments) { | ||
if(ERR(err, callback)) return; | ||
exports.copyComments = async (originalPadId, newPadID) => { | ||
// get the comments of original pad | ||
const originalComments = await db.get('comments:' + originalPadId); | ||
// make sure we have different copies of the comment between pads | ||
const copiedComments = _.mapObject(originalComments, (thisComment) => _.clone(thisComment)); | ||
var copiedComments = _.mapObject(originalComments, function(thisComment, thisCommentId) { | ||
// make sure we have different copies of the comment between pads | ||
return _.clone(thisComment); | ||
}); | ||
//save the comments on new pad | ||
db.set('comments:' + newPadID, copiedComments); | ||
callback(null); | ||
}); | ||
// save the comments on new pad | ||
await db.set('comments:' + newPadID, copiedComments); | ||
}; | ||
exports.getCommentReplies = async function (padId, callback){ | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
//get the globalComments replies | ||
db.get("comment-replies:" + padId, function(err, replies) | ||
{ | ||
if(ERR(err, callback)) return; | ||
//comment does not exists | ||
if(replies == null) replies = {}; | ||
callback(null, { replies: replies }); | ||
}); | ||
exports.getCommentReplies = async (padId) => { | ||
// get the globalComments replies | ||
let replies = await db.get('comment-replies:' + padId); | ||
// comment does not exist | ||
if (replies == null) replies = {}; | ||
return {replies}; | ||
}; | ||
exports.deleteCommentReplies = function (padId, callback){ | ||
db.remove('comment-replies:' + padId, function(err) | ||
{ | ||
if(ERR(err, callback)) return; | ||
callback(null); | ||
}); | ||
exports.deleteCommentReplies = async (padId) => { | ||
await db.remove('comment-replies:' + padId); | ||
}; | ||
exports.addCommentReply = function(padId, data, callback){ | ||
exports.bulkAddCommentReplies(padId, [data], function(err, replyIds, replies) { | ||
if(ERR(err, callback)) return; | ||
if(replyIds && replyIds.length > 0 && replies && replies.length > 0) { | ||
callback(null, replyIds[0], replies[0]); | ||
} | ||
}); | ||
exports.addCommentReply = async (padId, data) => { | ||
const [replyIds, replies] = await exports.bulkAddCommentReplies(padId, [data]); | ||
return [replyIds[0], replies[0]]; | ||
}; | ||
exports.bulkAddCommentReplies = async function(padId, data, callback){ | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
exports.bulkAddCommentReplies = async (padId, data) => { | ||
// get the entry | ||
let replies = await db.get('comment-replies:' + padId); | ||
// the entry doesn't exist so far, let's create it | ||
if (replies == null) replies = {}; | ||
//get the entry | ||
db.get("comment-replies:" + padId, function(err, replies){ | ||
if(ERR(err, callback)) return; | ||
const newReplies = []; | ||
const replyIds = _.map(data, (replyData) => { | ||
// create the new reply id | ||
const replyId = "c-reply-" + randomString(16); | ||
// the entry doesn't exist so far, let's create it | ||
if(replies == null) replies = {}; | ||
const metadata = replyData.comment || {}; | ||
var newReplies = []; | ||
var replyIds = _.map(data, function(replyData) { | ||
//create the new reply id | ||
var replyId = "c-reply-" + randomString(16); | ||
const reply = { | ||
commentId: replyData.commentId, | ||
text: replyData.reply || replyData.text, | ||
changeTo: replyData.changeTo || null, | ||
changeFrom: replyData.changeFrom || null, | ||
author: metadata.author || 'empty', | ||
name: metadata.name || replyData.name, | ||
timestamp: parseInt(replyData.timestamp) || new Date().getTime() | ||
}; | ||
metadata = replyData.comment || {}; | ||
// add the entry for this pad | ||
replies[replyId] = reply; | ||
var reply = { | ||
"commentId" : replyData.commentId, | ||
"text" : replyData.reply || replyData.text, | ||
"changeTo" : replyData.changeTo || null, | ||
"changeFrom" : replyData.changeFrom || null, | ||
"author" : metadata.author || "empty", | ||
"name" : metadata.name || replyData.name, | ||
"timestamp" : parseInt(replyData.timestamp) || new Date().getTime() | ||
}; | ||
newReplies.push(reply); | ||
return replyId; | ||
}); | ||
//add the entry for this pad | ||
replies[replyId] = reply; | ||
// save the new element back | ||
await db.set('comment-replies:' + padId, replies); | ||
newReplies.push(reply); | ||
return replyId; | ||
}); | ||
//save the new element back | ||
db.set("comment-replies:" + padId, replies); | ||
callback(null, replyIds, newReplies); | ||
}); | ||
return [replyIds, newReplies]; | ||
}; | ||
exports.copyCommentReplies = function(originalPadId, newPadID, callback){ | ||
//get the replies of original pad | ||
db.get('comment-replies:' + originalPadId, function(err, originalReplies){ | ||
if(ERR(err, callback)) return; | ||
exports.copyCommentReplies = async (originalPadId, newPadID) => { | ||
// get the replies of original pad | ||
const originalReplies = await db.get('comment-replies:' + originalPadId); | ||
// make sure we have different copies of the reply between pads | ||
const copiedReplies = _.mapObject(originalReplies, (thisReply) => _.clone(thisReply)); | ||
var copiedReplies = _.mapObject(originalReplies, function(thisReply, thisReplyId) { | ||
// make sure we have different copies of the reply between pads | ||
return _.clone(thisReply); | ||
}); | ||
//save the comment replies on new pad | ||
db.set('comment-replies:' + newPadID, copiedReplies); | ||
callback(null); | ||
}); | ||
// save the comment replies on new pad | ||
await db.set('comment-replies:' + newPadID, copiedReplies); | ||
}; | ||
exports.changeAcceptedState = async function(padId, commentId, state, callback){ | ||
exports.changeAcceptedState = async (padId, commentId, state) => { | ||
// Given a comment we update that comment to say the change was accepted or reverted | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
// If we're dealing with comment replies we need to a different query | ||
@@ -237,59 +149,43 @@ var prefix = "comments:"; | ||
//get the entry | ||
db.get(prefix + padId, function(err, comments){ | ||
// get the entry | ||
const comments = await db.get(prefix + padId); | ||
if(ERR(err, callback)) return; | ||
// add the entry for this pad | ||
const comment = comments[commentId]; | ||
//add the entry for this pad | ||
var comment = comments[commentId]; | ||
if (state) { | ||
comment.changeAccepted = true; | ||
comment.changeReverted = false; | ||
} else { | ||
comment.changeAccepted = false; | ||
comment.changeReverted = true; | ||
} | ||
if(state){ | ||
comment.changeAccepted = true; | ||
comment.changeReverted = false; | ||
}else{ | ||
comment.changeAccepted = false; | ||
comment.changeReverted = true; | ||
} | ||
comments[commentId] = comment; | ||
comments[commentId] = comment; | ||
//save the new element back | ||
await db.set(prefix + padId, comments); | ||
}; | ||
//save the new element back | ||
db.set(prefix + padId, comments); | ||
exports.changeCommentText = async (padId, commentId, commentText) => { | ||
if (commentText.length <= 0) return true; | ||
callback(null, commentId, comment); | ||
}); | ||
} | ||
// Given a comment we update the comment text | ||
exports.changeCommentText = async function(padId, commentId, commentText, callback){ | ||
var commentTextIsNotEmpty = commentText.length > 0; | ||
if(commentTextIsNotEmpty){ | ||
// Given a comment we update the comment text | ||
// We need to change readOnly PadIds to Normal PadIds | ||
var isReadOnly = padId.indexOf("r.") === 0; | ||
if(isReadOnly){ | ||
padId = await readOnlyManager.getPadId(padId); | ||
}; | ||
// If we're dealing with comment replies we need to a different query | ||
var prefix = 'comments:'; | ||
if (commentId.substring(0,7) === 'c-reply') { | ||
prefix = 'comment-replies:'; | ||
} | ||
// If we're dealing with comment replies we need to a different query | ||
var prefix = "comments:"; | ||
if(commentId.substring(0,7) === "c-reply"){ | ||
prefix = "comment-replies:"; | ||
} | ||
// get the entry | ||
const comments = await db.get(prefix + padId); | ||
// update the comment text | ||
comments[commentId].text = commentText; | ||
//get the entry | ||
db.get(prefix + padId, function(err, comments){ | ||
if(ERR(err, callback)) return; | ||
// save the comment updated back | ||
await db.set(prefix + padId, comments); | ||
//update the comment text | ||
comments[commentId].text = commentText; | ||
//save the comment updated back | ||
db.set(prefix + padId, comments); | ||
callback(null); | ||
}); | ||
}else{// don't save comment text blank | ||
callback(true); | ||
} | ||
} | ||
return null; | ||
}; |
389
index.js
@@ -0,173 +1,123 @@ | ||
/* global exports, require */ | ||
var eejs = require('ep_etherpad-lite/node/eejs/'); | ||
var settings = require('ep_etherpad-lite/node/utils/Settings'); | ||
var formidable = require('ep_etherpad-lite/node_modules/formidable'); | ||
var clientIO = require('ep_etherpad-lite/node_modules/socket.io-client'); | ||
var commentManager = require('./commentManager'); | ||
var comments = require('./comments'); | ||
var apiUtils = require('./apiUtils'); | ||
var _ = require('ep_etherpad-lite/static/js/underscore'); | ||
const readOnlyManager = require('ep_etherpad-lite/node/db/ReadOnlyManager.js'); | ||
exports.padRemove = function(hook_name, context, callback) { | ||
commentManager.deleteCommentReplies(context.padID, function() { | ||
commentManager.deleteComments(context.padID, callback); | ||
}); | ||
} | ||
exports.padCopy = function(hook_name, context, callback) { | ||
commentManager.copyComments(context.originalPad.id, context.destinationID, function() { | ||
commentManager.copyCommentReplies(context.originalPad.id, context.destinationID, callback); | ||
}); | ||
} | ||
let io; | ||
exports.padRemove = async (hookName, context) => { | ||
await Promise.all([ | ||
commentManager.deleteCommentReplies(context.padID), | ||
commentManager.deleteComments(context.padID), | ||
]); | ||
}; | ||
exports.padCopy = async (hookName, context) => { | ||
await Promise.all([ | ||
commentManager.copyComments(context.originalPad.id, context.destinationID), | ||
commentManager.copyCommentReplies(context.originalPad.id, context.destinationID), | ||
]); | ||
}; | ||
exports.handleMessageSecurity = function(hook_name, context, callback){ | ||
if(context.message && context.message.data && context.message.data.apool){ | ||
var apool = context.message.data.apool; | ||
if(apool.numToAttrib && apool.numToAttrib[0] && apool.numToAttrib[0][0]){ | ||
if(apool.numToAttrib[0][0] === "comment"){ | ||
// Comment change, allow it to override readonly security model!! | ||
callback(true); | ||
}else{ | ||
callback(); | ||
} | ||
}else{ | ||
callback(); | ||
} | ||
}else{ | ||
callback(); | ||
const {message: {data: {apool} = {}} = {}} = context; | ||
if (apool && apool[0] && apool[0][0] === 'comment') { | ||
// Comment change, allow it to override readonly security model!! | ||
return callback(true); | ||
} | ||
return callback(); | ||
}; | ||
exports.socketio = function (hook_name, args, cb){ | ||
var app = args.app; | ||
var io = args.io; | ||
var pushComment; | ||
var padComment = io; | ||
io = args.io.of('/comment'); | ||
io.on('connection', (socket) => { | ||
var commentSocket = io | ||
.of('/comment') | ||
.on('connection', function (socket) { | ||
// Join the rooms | ||
socket.on('getComments', function (data, callback) { | ||
var padId = data.padId; | ||
socket.on('getComments', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
// Put read-only and read-write users in the same socket.io "room" so that they can see each | ||
// other's updates. | ||
socket.join(padId); | ||
commentManager.getComments(padId, function (err, comments){ | ||
callback(comments); | ||
}); | ||
respond(await commentManager.getComments(padId)); | ||
}); | ||
socket.on('getCommentReplies', function (data, callback) { | ||
var padId = data.padId; | ||
commentManager.getCommentReplies(padId, function (err, replies){ | ||
callback(replies); | ||
}); | ||
socket.on('getCommentReplies', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
respond(await commentManager.getCommentReplies(padId)); | ||
}); | ||
// On add events | ||
socket.on('addComment', function (data, callback) { | ||
var padId = data.padId; | ||
socket.on('addComment', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
var content = data.comment; | ||
commentManager.addComment(padId, content, function (err, commentId, comment){ | ||
const [commentId, comment] = await commentManager.addComment(padId, content); | ||
if (commentId != null && comment != null) { | ||
socket.broadcast.to(padId).emit('pushAddComment', commentId, comment); | ||
callback(commentId, comment); | ||
}); | ||
respond(commentId, comment); | ||
} | ||
}); | ||
socket.on('deleteComment', function(data, callback) { | ||
socket.on('deleteComment', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
// delete the comment on the database | ||
commentManager.deleteComment(data.padId, data.commentId, function (){ | ||
// Broadcast to all other users that this comment was deleted | ||
socket.broadcast.to(data.padId).emit('commentDeleted', data.commentId); | ||
}); | ||
await commentManager.deleteComment(padId, data.commentId); | ||
// Broadcast to all other users that this comment was deleted | ||
socket.broadcast.to(padId).emit('commentDeleted', data.commentId); | ||
}); | ||
socket.on('revertChange', function(data, callback) { | ||
socket.on('revertChange', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
// Broadcast to all other users that this change was accepted. | ||
// Note that commentId here can either be the commentId or replyId.. | ||
var padId = data.padId; | ||
commentManager.changeAcceptedState(padId, data.commentId, false, function(){ | ||
socket.broadcast.to(padId).emit('changeReverted', data.commentId); | ||
}); | ||
await commentManager.changeAcceptedState(padId, data.commentId, false); | ||
socket.broadcast.to(padId).emit('changeReverted', data.commentId); | ||
}); | ||
socket.on('acceptChange', function(data, callback) { | ||
socket.on('acceptChange', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
// Broadcast to all other users that this change was accepted. | ||
// Note that commentId here can either be the commentId or replyId.. | ||
var padId = data.padId; | ||
commentManager.changeAcceptedState(padId, data.commentId, true, function(){ | ||
socket.broadcast.to(padId).emit('changeAccepted', data.commentId); | ||
}); | ||
await commentManager.changeAcceptedState(padId, data.commentId, true); | ||
socket.broadcast.to(padId).emit('changeAccepted', data.commentId); | ||
}); | ||
socket.on('bulkAddComment', function (padId, data, callback) { | ||
commentManager.bulkAddComments(padId, data, function(error, commentsId, comments){ | ||
socket.broadcast.to(padId).emit('pushAddCommentInBulk'); | ||
var commentWithCommentId = _.object(commentsId, comments); // {c-123:data, c-124:data} | ||
callback(commentWithCommentId) | ||
}); | ||
socket.on('bulkAddComment', async (padId, data, respond) => { | ||
padId = (await readOnlyManager.getIds(padId)).padId; | ||
const [commentIds, comments] = await commentManager.bulkAddComments(padId, data); | ||
socket.broadcast.to(padId).emit('pushAddCommentInBulk'); | ||
respond(_.object(commentIds, comments)); // {c-123:data, c-124:data} | ||
}); | ||
socket.on('bulkAddCommentReplies', function(padId, data, callback){ | ||
commentManager.bulkAddCommentReplies(padId, data, function (err, repliesId, replies){ | ||
socket.broadcast.to(padId).emit('pushAddCommentReply', repliesId, replies); | ||
var repliesWithReplyId = _.zip(repliesId, replies); | ||
callback(repliesWithReplyId); | ||
}); | ||
socket.on('bulkAddCommentReplies', async (padId, data, respond) => { | ||
padId = (await readOnlyManager.getIds(padId)).padId; | ||
const [repliesId, replies] = await commentManager.bulkAddCommentReplies(padId, data); | ||
socket.broadcast.to(padId).emit('pushAddCommentReply', repliesId, replies); | ||
respond(_.zip(repliesId, replies)); | ||
}); | ||
socket.on('updateCommentText', function(data, callback) { | ||
socket.on('updateCommentText', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
// Broadcast to all other users that the comment text was changed. | ||
// Note that commentId here can either be the commentId or replyId.. | ||
var padId = data.padId; | ||
var commentId = data.commentId; | ||
var commentText = data.commentText; | ||
commentManager.changeCommentText(padId, commentId, commentText, function(err) { | ||
if(!err){ | ||
socket.broadcast.to(padId).emit('textCommentUpdated', commentId, commentText); | ||
} | ||
callback(err); | ||
}); | ||
const failed = await commentManager.changeCommentText(padId, commentId, commentText); | ||
if (!failed) socket.broadcast.to(padId).emit('textCommentUpdated', commentId, commentText); | ||
respond(failed); | ||
}); | ||
socket.on('addCommentReply', function (data, callback) { | ||
var padId = data.padId; | ||
var content = data.reply; | ||
var changeTo = data.changeTo || null; | ||
var changeFrom = data.changeFrom || null; | ||
var changeAccepted = data.changeAccepted || null; | ||
var changeReverted = data.changeReverted || null; | ||
var commentId = data.commentId; | ||
commentManager.addCommentReply(padId, data, function (err, replyId, reply, changeTo, changeFrom, changeAccepted, changeReverted){ | ||
reply.replyId = replyId; | ||
socket.broadcast.to(padId).emit('pushAddCommentReply', replyId, reply, changeTo, changeFrom, changeAccepted, changeReverted); | ||
callback(replyId, reply); | ||
}); | ||
socket.on('addCommentReply', async (data, respond) => { | ||
const {padId} = await readOnlyManager.getIds(data.padId); | ||
const [replyId, reply] = await commentManager.addCommentReply(padId, data); | ||
reply.replyId = replyId; | ||
socket.broadcast.to(padId).emit('pushAddCommentReply', replyId, reply); | ||
respond(replyId, reply); | ||
}); | ||
// comment added via API | ||
socket.on('apiAddComments', function (data) { | ||
var padId = data.padId; | ||
var commentIds = data.commentIds; | ||
var comments = data.comments; | ||
for (var i = 0, len = commentIds.length; i < len; i++) { | ||
socket.broadcast.to(padId).emit('pushAddComment', commentIds[i], comments[i]); | ||
} | ||
}); | ||
// comment reply added via API | ||
socket.on('apiAddCommentReplies', function (data) { | ||
var padId = data.padId; | ||
var replyIds = data.replyIds; | ||
var replies = data.replies; | ||
for (var i = 0, len = replyIds.length; i < len; i++) { | ||
var reply = replies[i]; | ||
var replyId = replyIds[i]; | ||
reply.replyId = replyId; | ||
socket.broadcast.to(padId).emit('pushAddCommentReply', replyId, reply); | ||
} | ||
}); | ||
}); | ||
return cb(); | ||
}; | ||
@@ -191,4 +141,4 @@ | ||
exports.eejsBlock_scripts = function (hook_name, args, cb) { | ||
args.content = args.content + eejs.require("ep_comments_page/templates/comments.html", {}, module); | ||
args.content = args.content + eejs.require("ep_comments_page/templates/commentIcons.html", {}, module); | ||
args.content = args.content + eejs.require("ep_comments_page/templates/comments.html"); | ||
args.content = args.content + eejs.require("ep_comments_page/templates/commentIcons.html"); | ||
return cb(); | ||
@@ -198,3 +148,3 @@ }; | ||
exports.eejsBlock_styles = function (hook_name, args, cb) { | ||
args.content = args.content + eejs.require("ep_comments_page/templates/styles.html", {}, module); | ||
args.content = args.content + eejs.require("ep_comments_page/templates/styles.html"); | ||
return cb(); | ||
@@ -213,3 +163,3 @@ }; | ||
exports.expressCreateServer = function (hook_name, args, callback) { | ||
args.app.get('/p/:pad/:rev?/comments', function(req, res) { | ||
args.app.get('/p/:pad/:rev?/comments', async (req, res) => { | ||
var fields = req.query; | ||
@@ -220,43 +170,58 @@ // check the api key | ||
// sanitize pad id before continuing | ||
var padIdReceived = apiUtils.sanitizePadId(req); | ||
const padIdReceived = (await readOnlyManager.getIds(apiUtils.sanitizePadId(req))).padId; | ||
comments.getPadComments(padIdReceived, function(err, data) { | ||
if(err) { | ||
res.json({code: 2, message: "internal error", data: null}); | ||
} else { | ||
res.json({code: 0, data: data}); | ||
} | ||
}); | ||
let data; | ||
try { | ||
data = await commentManager.getComments(padIdReceived); | ||
} catch (err) { | ||
console.error(err.stack ? err.stack : err.toString()); | ||
res.json({code: 2, message: 'internal error', data: null}); | ||
return; | ||
} | ||
if (data == null) return; | ||
res.json({code: 0, data}); | ||
}); | ||
args.app.post('/p/:pad/:rev?/comments', function(req, res) { | ||
new formidable.IncomingForm().parse(req, function (err, fields, files) { | ||
// check the api key | ||
if(!apiUtils.validateApiKey(fields, res)) return; | ||
args.app.post('/p/:pad/:rev?/comments', async (req, res) => { | ||
const [fields, files] = await new Promise((resolve, reject) => { | ||
(new formidable.IncomingForm()).parse(req, (err, fields, files) => { | ||
if (err != null) return reject(err); | ||
resolve([fields, files]); | ||
}); | ||
}); | ||
// check required fields from comment data | ||
if(!apiUtils.validateRequiredFields(fields, ['data'], res)) return; | ||
// check the api key | ||
if (!apiUtils.validateApiKey(fields, res)) return; | ||
// sanitize pad id before continuing | ||
var padIdReceived = apiUtils.sanitizePadId(req); | ||
// check required fields from comment data | ||
if (!apiUtils.validateRequiredFields(fields, ['data'], res)) return; | ||
// create data to hold comment information: | ||
try { | ||
var data = JSON.parse(fields.data); | ||
// sanitize pad id before continuing | ||
const padIdReceived = (await readOnlyManager.getIds(apiUtils.sanitizePadId(req))).padId; | ||
comments.bulkAddPadComments(padIdReceived, data, function(err, commentIds, comments) { | ||
if(err) { | ||
res.json({code: 2, message: "internal error", data: null}); | ||
} else { | ||
broadcastCommentsAdded(padIdReceived, commentIds, comments); | ||
res.json({code: 0, commentIds: commentIds}); | ||
} | ||
}); | ||
} catch(e) { | ||
res.json({code: 1, message: "data must be a JSON", data: null}); | ||
} | ||
}); | ||
// create data to hold comment information: | ||
let data; | ||
try { | ||
data = JSON.parse(fields.data); | ||
} catch (err) { | ||
res.json({code: 1, message: "data must be a JSON", data: null}); | ||
return; | ||
} | ||
let commentIds, comments; | ||
try { | ||
[commentIds, comments] = await commentManager.bulkAddComments(padIdReceived, data); | ||
} catch (err) { | ||
console.error(err.stack ? err.stack : err.toString()); | ||
res.json({code: 2, message: "internal error", data: null}); | ||
return; | ||
} | ||
if (commentIds == null) return; | ||
for (let i = 0; i < commentIds.length; i++) { | ||
io.to(padIdReceived).emit('pushAddComment', commentIds[i], comments[i]); | ||
} | ||
res.json({code: 0, commentIds: commentIds}); | ||
}); | ||
args.app.get('/p/:pad/:rev?/commentReplies', function(req, res){ | ||
args.app.get('/p/:pad/:rev?/commentReplies', async (req, res) => { | ||
//it's the same thing as the formidable's fields | ||
@@ -268,69 +233,59 @@ var fields = req.query; | ||
//sanitize pad id before continuing | ||
var padIdReceived = apiUtils.sanitizePadId(req); | ||
const padIdReceived = (await readOnlyManager.getIds(apiUtils.sanitizePadId(req))).padId; | ||
// call the route with the pad id sanitized | ||
comments.getPadCommentReplies(padIdReceived, function(err, data) { | ||
if(err) { | ||
res.json({code: 2, message: "internal error", data:null}) | ||
} else { | ||
res.json({code: 0, data: data}); | ||
} | ||
}); | ||
let data; | ||
try { | ||
data = await commentManager.getCommentReplies(padIdReceived); | ||
} catch (err) { | ||
console.error(err.stack ? err.stack : err.toString()); | ||
res.json({code: 2, message: "internal error", data:null}); | ||
return; | ||
} | ||
if (data == null) return; | ||
res.json({code: 0, data: data}); | ||
}); | ||
args.app.post('/p/:pad/:rev?/commentReplies', function(req, res) { | ||
new formidable.IncomingForm().parse(req, function (err, fields, files) { | ||
// check the api key | ||
if(!apiUtils.validateApiKey(fields, res)) return; | ||
// check required fields from comment data | ||
if(!apiUtils.validateRequiredFields(fields, ['data'], res)) return; | ||
// sanitize pad id before continuing | ||
var padIdReceived = apiUtils.sanitizePadId(req); | ||
// create data to hold comment reply information: | ||
try { | ||
var data = JSON.parse(fields.data); | ||
comments.bulkAddPadCommentReplies(padIdReceived, data, function(err, replyIds, replies) { | ||
if(err) { | ||
res.json({code: 2, message: "internal error", data: null}); | ||
} else { | ||
broadcastCommentRepliesAdded(padIdReceived, replyIds, replies); | ||
res.json({code: 0, replyIds: replyIds}); | ||
} | ||
}); | ||
} catch(e) { | ||
res.json({code: 1, message: "data must be a JSON", data: null}); | ||
} | ||
args.app.post('/p/:pad/:rev?/commentReplies', async (req, res) => { | ||
const [fields, files] = await new Promise((resolve, reject) => { | ||
(new formidable.IncomingForm()).parse(req, (err, fields, files) => { | ||
if (err != null) return reject(err); | ||
resolve([fields, files]); | ||
}); | ||
}); | ||
}); | ||
} | ||
// check the api key | ||
if (!apiUtils.validateApiKey(fields, res)) return; | ||
var broadcastCommentsAdded = function(padId, commentIds, comments) { | ||
var socket = clientIO.connect(broadcastUrl); | ||
// check required fields from comment data | ||
if (!apiUtils.validateRequiredFields(fields, ['data'], res)) return; | ||
var data = { | ||
padId: padId, | ||
commentIds: commentIds, | ||
comments: comments | ||
}; | ||
// sanitize pad id before continuing | ||
const padIdReceived = (await readOnlyManager.getIds(apiUtils.sanitizePadId(req))).padId; | ||
socket.emit('apiAddComments', data); | ||
} | ||
// create data to hold comment reply information: | ||
let data; | ||
try { | ||
data = JSON.parse(fields.data); | ||
} catch (err) { | ||
res.json({code: 1, message: "data must be a JSON", data: null}); | ||
return; | ||
} | ||
var broadcastCommentRepliesAdded = function(padId, replyIds, replies) { | ||
var socket = clientIO.connect(broadcastUrl); | ||
var data = { | ||
padId: padId, | ||
replyIds: replyIds, | ||
replies: replies | ||
}; | ||
socket.emit('apiAddCommentReplies', data); | ||
let replyIds, replies; | ||
try { | ||
[replyIds, replies] = await commentManager.bulkAddCommentReplies(padIdReceived, data); | ||
} catch (err) { | ||
console.error(err.stack ? err.stack : err.toString()); | ||
res.json({code: 2, message: "internal error", data: null}); | ||
return; | ||
} | ||
if (replyIds == null) return; | ||
for (let i = 0; i < replyIds.length; i++) { | ||
replies[i].replyId = replyIds[i]; | ||
io.to(padIdReceived).emit('pushAddCommentReply', replyIds[i], replies[i]); | ||
} | ||
res.json({code: 0, replyIds: replyIds}); | ||
}); | ||
return callback(); | ||
} | ||
var broadcastUrl = apiUtils.broadcastUrlFor("/comment"); |
@@ -21,34 +21,20 @@ { | ||
"ep_comments_page.comments_template.reply.placeholder" : "Reply", | ||
"ep_comments_page.time.seconds.past" : "{{count}} seconds ago", | ||
"ep_comments_page.time.seconds.future" : "{{count}} seconds from now", | ||
"ep_comments_page.time.one_minute.past" : "1 minute ago", | ||
"ep_comments_page.time.one_minute.future" : "1 minute from now", | ||
"ep_comments_page.time.minutes.past" : "{{count}} minutes ago", | ||
"ep_comments_page.time.minutes.future" : "{{count}} minutes from now", | ||
"ep_comments_page.time.one_hour.past" : "1 hour ago", | ||
"ep_comments_page.time.one_hour.future" : "1 hour from now", | ||
"ep_comments_page.time.hours.past" : "{{count}} hours ago", | ||
"ep_comments_page.time.hours.future" : "{{count}} hours from now", | ||
"ep_comments_page.time.one_day.past" : "yesterday", | ||
"ep_comments_page.time.one_day.future" : "tomorrow", | ||
"ep_comments_page.time.days.past" : "{{count}} days ago", | ||
"ep_comments_page.time.days.future" : "{{count}} days from now", | ||
"ep_comments_page.time.one_week.past" : "last week", | ||
"ep_comments_page.time.one_week.future" : "next week", | ||
"ep_comments_page.time.weeks.past" : "{{count}} weeks ago", | ||
"ep_comments_page.time.weeks.future" : "{{count}} weeks from now", | ||
"ep_comments_page.time.one_month.past" : "last month", | ||
"ep_comments_page.time.one_month.future" : "next month", | ||
"ep_comments_page.time.months.past" : "{{count}} months ago", | ||
"ep_comments_page.time.months.future" : "{{count}} months from now", | ||
"ep_comments_page.time.one_year.past" : "last year", | ||
"ep_comments_page.time.one_year.future" : "next year", | ||
"ep_comments_page.time.years.past" : "{{count}} years ago", | ||
"ep_comments_page.time.years.future" : "{{count}} years from now", | ||
"ep_comments_page.time.one_century.past" : "last century", | ||
"ep_comments_page.time.one_century.future" : "next century", | ||
"ep_comments_page.time.centuries.past" : "{{count}} centuries ago", | ||
"ep_comments_page.time.centuries.future" : "{{count}} centuries from now", | ||
"ep_comments_page.time.seconds.past": "{{count}} {[ plural(count) one: second, other: seconds ]} ago", | ||
"ep_comments_page.time.seconds.future": "{{count}} {[ plural(count) one: second, other: seconds ]} from now", | ||
"ep_comments_page.time.minutes.past": "{{count}} {[ plural(count) one: minute, other: minutes ]} ago", | ||
"ep_comments_page.time.minutes.future": "{{count}} {[ plural(count) one: minute, other: minutes ]} from now", | ||
"ep_comments_page.time.hours.past": "{{count}} {[ plural(count) one: hour, other: hours ]} ago", | ||
"ep_comments_page.time.hours.future": "{{count}} {[ plural(count) one: hour, other: hours ]} from now", | ||
"ep_comments_page.time.days.past": "{[ plural(count) one: yesterday, other: {{count}} days ago ]}", | ||
"ep_comments_page.time.days.future": "{[ plural(count) one: tomorrow, other: {{count}} days from now ]}", | ||
"ep_comments_page.time.weeks.past": "{[ plural(count) one: last week, other: {{count}} weeks ago ]}", | ||
"ep_comments_page.time.weeks.future": "{[ plural(count) one: next week, other: {{count}} weeks from now ]}", | ||
"ep_comments_page.time.months.past": "{[ plural(count) one: last month, other: {{count}} months ago ]}", | ||
"ep_comments_page.time.months.future": "{[ plural(count) one: next month, other: {{count}} months from now ]}", | ||
"ep_comments_page.time.years.past": "{[ plural(count) one: last year, other: {{count}} years ago ]}", | ||
"ep_comments_page.time.years.future": "{[ plural(count) one: next year, other: {{count}} years from now ]}", | ||
"ep_comments_page.time.centuries.past": "{[ plural(count) one: last century, other: {{count}} centuries ago ]}", | ||
"ep_comments_page.time.centuries.future": "{[ plural(count) one: next century, other: {{count}} centuries from now ]}", | ||
"ep_comments_page.comments_template.edit_comment.save" : "save", | ||
"ep_comments_page.comments_template.edit_comment.cancel" :"cancel" | ||
} |
{ | ||
"description": "Adds comments on sidebar and link it to the text. For no-skin use ep_page_view.", | ||
"name": "ep_comments_page", | ||
"version": "0.1.7", | ||
"version": "0.1.8", | ||
"author": { | ||
@@ -9,2 +9,3 @@ "name": "Nicolas Lescop", | ||
}, | ||
"license": "Apache-2.0", | ||
"contributors": [ | ||
@@ -25,7 +26,7 @@ { | ||
"dependencies": { | ||
"formidable": "*", | ||
"socket.io-client": "*" | ||
"formidable": "*" | ||
}, | ||
"devDependencies": { | ||
"request": "*" | ||
"request": "*", | ||
"socket.io-client": "*" | ||
}, | ||
@@ -32,0 +33,0 @@ "engines": { |
@@ -0,1 +1,3 @@ | ||
/* global clearTimeout, clientVars, exports, html10n, pad, require, setTimeout */ | ||
/* TODO: | ||
@@ -1105,17 +1107,10 @@ - lable reply textarea | ||
ep_comments.prototype.commentRepliesListen = function(){ | ||
var self = this; | ||
var socket = this.socket; | ||
socket.on('pushAddCommentReply', function (replyId, reply, changeTo, changeFrom){ | ||
// console.warn("pAcR response", replyId, reply, changeTo, changeFrom); | ||
// callback(replyId, reply); | ||
// self.collectCommentReplies(); | ||
self.getCommentReplies(function (replies){ | ||
this.socket.on('pushAddCommentReply', (replyId, reply) => { | ||
this.getCommentReplies((replies) => { | ||
if (!$.isEmptyObject(replies)){ | ||
// console.log("collecting comment replies"); | ||
self.commentReplies = replies; | ||
self.collectCommentReplies(); | ||
this.commentReplies = replies; | ||
this.collectCommentReplies(); | ||
} | ||
}); | ||
}); | ||
}; | ||
@@ -1197,3 +1192,3 @@ | ||
// Init pad comments | ||
postAceInit: function(hook, context){ | ||
postAceInit: function(hookName, context, cb) { | ||
if(!pad.plugins) pad.plugins = {}; | ||
@@ -1211,5 +1206,6 @@ var Comments = new ep_comments(context); | ||
} | ||
return cb(); | ||
}, | ||
aceEditEvent: function(hook, context){ | ||
aceEditEvent: function(hookName, context, cb) { | ||
if(!pad.plugins) pad.plugins = {}; | ||
@@ -1224,4 +1220,4 @@ // first check if some text is being marked/unmarked to add comment to it | ||
if(eventType == "setup" || eventType == "setBaseText" || eventType == "importText") return; | ||
if (eventType == 'setup' || eventType == 'setBaseText' || eventType == 'importText') return cb(); | ||
if(context.callstack.docTextChanged && pad.plugins.ep_comments_page){ | ||
@@ -1243,17 +1239,19 @@ pad.plugins.ep_comments_page.setYofComments(); | ||
} | ||
return cb(); | ||
}, | ||
// Insert comments classes | ||
aceAttribsToClasses: function(hook, context){ | ||
aceAttribsToClasses: function(hookName, context, cb) { | ||
if(context.key === 'comment' && context.value !== "comment-deleted") { | ||
return ['comment', context.value]; | ||
return cb(['comment', context.value]); | ||
} | ||
// only read marks made by current user | ||
if(context.key === preCommentMark.MARK_CLASS && context.value === clientVars.userId) { | ||
return [preCommentMark.MARK_CLASS, context.value]; | ||
return cb([preCommentMark.MARK_CLASS, context.value]); | ||
} | ||
return cb(); | ||
}, | ||
aceEditorCSS: function(){ | ||
return cssFiles; | ||
aceEditorCSS: function(hookName, context, cb) { | ||
return cb(cssFiles); | ||
} | ||
@@ -1359,3 +1357,3 @@ | ||
// Once ace is initialized, we set ace_doInsertHeading and bind it to the context | ||
exports.aceInitialized = function(hook, context){ | ||
exports.aceInitialized = function(hookName, context, cb) { | ||
var editorInfo = context.editorInfo; | ||
@@ -1365,3 +1363,3 @@ editorInfo.ace_getRepFromSelector = _(getRepFromSelector).bind(context); | ||
editorInfo.ace_hasCommentOnSelection = _(hasCommentOnSelection).bind(context); | ||
return cb(); | ||
} | ||
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString; | ||
var collectContentPre = function(hook, context){ | ||
var collectContentPre = function(hookName, context, cb) { | ||
var comment = /(?:^| )(c-[A-Za-z0-9]*)/.exec(context.cls); | ||
@@ -20,2 +20,3 @@ var fakeComment = /(?:^| )(fakecomment-[A-Za-z0-9]*)/.exec(context.cls); | ||
} | ||
return cb(); | ||
}; | ||
@@ -22,0 +23,0 @@ |
@@ -1,36 +0,13 @@ | ||
var localizable = typeof html10n !== "undefined"; | ||
/* global exports, html10n */ | ||
l10nKeys = { | ||
"seconds" : "ep_comments_page.time.seconds", | ||
"1 minute ago" : "ep_comments_page.time.one_minute", | ||
"minutes" : "ep_comments_page.time.minutes", | ||
"1 hour ago" : "ep_comments_page.time.one_hour", | ||
"hours" : "ep_comments_page.time.hours", | ||
"yesterday" : "ep_comments_page.time.one_day", | ||
"days" : "ep_comments_page.time.days", | ||
"last week" : "ep_comments_page.time.one_week", | ||
"weeks" : "ep_comments_page.time.weeks", | ||
"last month" : "ep_comments_page.time.one_month", | ||
"months" : "ep_comments_page.time.months", | ||
"last year" : "ep_comments_page.time.one_year", | ||
"years" : "ep_comments_page.time.years", | ||
"last century" : "ep_comments_page.time.one_century", | ||
"centuries" : "ep_comments_page.time.centuries" | ||
} | ||
const localizable = typeof html10n !== 'undefined'; | ||
var time_formats = [ | ||
const 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 | ||
@@ -40,38 +17,19 @@ ]; | ||
function prettyDate(time){ | ||
/* | ||
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 seconds = new Date() - new Date(time) / 1000; | ||
var token = 'ago', | ||
list_choice = 1, | ||
l10n_appendix = '.past'; | ||
const now = new Date(); | ||
const then = new Date(time); | ||
const seconds = Math.abs((now - then) / 1000); | ||
const future = now < then; | ||
const token = future ? 'from now' : 'ago'; | ||
const list_choice = future ? 2 : 1; | ||
const l10n_appendix = future ? '.future' : '.past'; | ||
if (seconds < 0) { | ||
seconds = Math.abs(seconds); | ||
token = 'from now'; | ||
l10n_appendix = '.future'; | ||
list_choice = 2; | ||
} | ||
var i = 0, format; | ||
while (format = time_formats[i++]) | ||
for (const format of time_formats) { | ||
if (seconds < format[0]) { | ||
var count = Math.floor(seconds / format[2]); | ||
var formatted_time; | ||
const count = Math.floor(seconds / format[2]); | ||
if (localizable) { | ||
var key = l10nKeys[format[1]] + l10n_appendix; | ||
formatted_time = html10n.get(key, { count: count }); | ||
return html10n.get(`ep_comments_page.time.${format[1]}${l10n_appendix}`, {count}); | ||
} | ||
// Wasn't able to localize properly the date, so use the default: | ||
if (formatted_time === undefined) { | ||
if (typeof format[2] == 'string') | ||
formatted_time = format[list_choice]; | ||
else | ||
formatted_time = count + ' ' + format[1] + ' ' + token; | ||
} | ||
return formatted_time; | ||
return `${count} ${count === 1 ? format[1].slice(0, -1) : format[1]} ${token}`; | ||
} | ||
} | ||
return time; | ||
@@ -78,0 +36,0 @@ } |
describe("ep_comments_page - Comment icons", function() { | ||
//create a new pad with comment before each test run | ||
beforeEach(function(cb){ | ||
helper.newPad(function() { | ||
// make sure Etherpad has enough space to display comment icons | ||
enlargeScreen(function() { | ||
// force sidebar comments to be shown | ||
chooseToShowComments(true, function() { | ||
createComment(cb); | ||
}); | ||
}); | ||
}); | ||
beforeEach(async function() { | ||
await new Promise((resolve) => helper.newPad(resolve)); | ||
// make sure Etherpad has enough space to display comment icons | ||
enlargeScreen(); | ||
// force sidebar comments to be shown | ||
chooseToShowComments(true); | ||
await createComment(); | ||
this.timeout(60000); | ||
}); | ||
after(function(cb) { | ||
after(async function() { | ||
// undo frame resize that was done on before() | ||
$('#iframe-container iframe').css("max-width", ""); | ||
cb(); | ||
}); | ||
it("adds a comment icon on the same height of commented text", function(done) { | ||
it('adds a comment icon on the same height of commented text', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var inner$ = helper.padInner$; | ||
var outer$ = helper.padOuter$; | ||
var commentId = getCommentId(); | ||
const commentId = await getCommentId(); | ||
var $commentIcon = outer$("#commentIcons #icon-"+commentId); | ||
@@ -37,10 +33,8 @@ | ||
expect($commentIcon.offset().top).to.be(expectedTop); | ||
done(); | ||
}); | ||
}); | ||
// TODO: Needs fixing | ||
xit("does not show comment icon when commented text is removed", function(done) { | ||
xit('does not show comment icon when commented text is removed', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var inner$ = helper.padInner$; | ||
@@ -53,33 +47,29 @@ var outer$ = helper.padOuter$; | ||
// wait until comment deletion is done | ||
helper.waitFor(function() { | ||
await helper.waitForPromise(() => { | ||
// check icon is not visible | ||
var $commentIcons = outer$("#commentIcons .comment-icon:visible"); | ||
return $commentIcons.length === 0; | ||
}) | ||
.done(done); | ||
}); | ||
}); | ||
}); | ||
// TODO: Needs fixing | ||
xit("does not show comment icon when comment is deleted", function(done) { | ||
xit('does not show comment icon when comment is deleted', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var inner$ = helper.padInner$; | ||
var outer$ = helper.padOuter$; | ||
deleteComment(function() { | ||
// check icon is not visible | ||
var $commentIcons = outer$("#commentIcons .comment-icon:visible"); | ||
expect($commentIcons.length).to.be(0); | ||
done(); | ||
}); | ||
await deleteComment(); | ||
// check icon is not visible | ||
var $commentIcons = outer$("#commentIcons .comment-icon:visible"); | ||
expect($commentIcons.length).to.be(0); | ||
}); | ||
}); | ||
it("updates comment icon height when commented text is moved to another line", function(done) { | ||
it('updates comment icon height when commented text is moved to another line', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var inner$ = helper.padInner$; | ||
var outer$ = helper.padOuter$; | ||
var commentId = getCommentId(); | ||
const commentId = await getCommentId(); | ||
@@ -91,29 +81,23 @@ // adds some new lines on the beginning of the text | ||
// wait until the new lines are split into separated .ace-line's | ||
helper.waitFor(function() { | ||
return inner$("div").length > 2; | ||
}) | ||
.done(function() { | ||
// wait until comment is visible again | ||
helper.waitFor(function() { | ||
var $commentIcons = outer$("#commentIcons .comment-icon:visible"); | ||
return $commentIcons.length !== 0; | ||
}) | ||
.done(function() { | ||
// check height is the same | ||
var $commentIcon = outer$("#commentIcons #icon-"+commentId); | ||
var $commentedText = inner$("."+commentId); | ||
var expectedTop = $commentedText.offset().top + 5; // all icons are +5px down to adjust position | ||
expect($commentIcon.offset().top).to.be(expectedTop); | ||
await helper.waitForPromise(() => inner$('div').length > 2); | ||
done(); | ||
}); | ||
// wait until comment is visible again | ||
await helper.waitForPromise(() => { | ||
var $commentIcons = outer$("#commentIcons .comment-icon:visible"); | ||
return $commentIcons.length !== 0; | ||
}); | ||
// check height is the same | ||
var $commentIcon = outer$("#commentIcons #icon-"+commentId); | ||
var $commentedText = inner$("."+commentId); | ||
var expectedTop = $commentedText.offset().top + 5; // all icons are +5px down to adjust position | ||
expect($commentIcon.offset().top).to.be(expectedTop); | ||
}); | ||
}); | ||
it("shows comment when user clicks on comment icon", function(done) { | ||
it('shows comment when user clicks on comment icon', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var outer$ = helper.padOuter$; | ||
var commentId = getCommentId(); | ||
const commentId = await getCommentId(); | ||
@@ -127,12 +111,10 @@ // click on the icon | ||
expect($openedSidebarComments.length).to.be(1); | ||
done(); | ||
}); | ||
}); | ||
it("hides comment when user clicks on comment icon twice", function(done) { | ||
it('hides comment when user clicks on comment icon twice', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var outer$ = helper.padOuter$; | ||
var commentId = getCommentId(); | ||
const commentId = await getCommentId(); | ||
@@ -147,12 +129,10 @@ // click on the icon to open, then click again to close | ||
expect($openedSidebarComments.length).to.be(0); | ||
done(); | ||
}); | ||
}); | ||
it("hides comment when user clicks outside of comment box", function(done) { | ||
it('hides comment when user clicks outside of comment box', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var outer$ = helper.padOuter$; | ||
var commentId = getCommentId(); | ||
const commentId = await getCommentId(); | ||
@@ -169,10 +149,8 @@ // click on the icon to open | ||
expect($openedSidebarComments.length).to.be(0); | ||
done(); | ||
}); | ||
}); | ||
it("hides first comment and shows second comment when user clicks on one icon then on another icon", function(done) { | ||
it('hides first comment and shows second comment when user clicks on one icon then on another icon', async function() { | ||
// we only run test if icons are enabled | ||
finishTestIfIconsAreNotEnabled(done, function(){ | ||
await finishTestIfIconsAreNotEnabled(async () => { | ||
var inner$ = helper.padInner$; | ||
@@ -186,25 +164,19 @@ var outer$ = helper.padOuter$; | ||
// wait until the new line is split into a separated .ace-line | ||
helper.waitFor(function() { | ||
return inner$("div").length > 2; | ||
}) | ||
.done(function() { | ||
// ... then add a comment to second line | ||
var $secondLine = inner$("div").eq(1); | ||
$secondLine.sendkeys('{selectall}'); | ||
addComment("Second Comment", function() { | ||
// click on the icon of first comment... | ||
var $firstCommentIcon = outer$("#commentIcons #icon-"+getCommentId(0)).first(); | ||
$firstCommentIcon.click(); | ||
// ... then click on the icon of last comment | ||
var $secondCommentIcon = outer$("#commentIcons #icon-"+getCommentId(1)).first(); | ||
$secondCommentIcon.click(); | ||
await helper.waitForPromise(() => inner$('div').length > 2); | ||
// check modal is visible | ||
var $commentText = outer$("#comments .sidebar-comment:visible .comment-text").text(); | ||
expect($commentText).to.be("Second Comment"); | ||
// ... then add a comment to second line | ||
var $secondLine = inner$("div").eq(1); | ||
$secondLine.sendkeys('{selectall}'); | ||
await addComment('Second Comment'); | ||
done(); | ||
// click on the icon of first comment... | ||
var $firstCommentIcon = outer$("#commentIcons #icon-"+(await getCommentId(0))).first(); | ||
$firstCommentIcon.click(); | ||
// ... then click on the icon of last comment | ||
var $secondCommentIcon = outer$("#commentIcons #icon-"+(await getCommentId(1))).first(); | ||
$secondCommentIcon.click(); | ||
}); | ||
}); | ||
// check modal is visible | ||
var $commentText = outer$("#comments .sidebar-comment:visible .comment-text").text(); | ||
expect($commentText).to.be("Second Comment"); | ||
}); | ||
@@ -215,3 +187,3 @@ }); | ||
var createComment = function(callback) { | ||
const createComment = async () => { | ||
var inner$ = helper.padInner$; | ||
@@ -227,16 +199,13 @@ | ||
// wait until the two lines are split into two .ace-line's | ||
helper.waitFor(function() { | ||
return inner$("div").length > 1; | ||
}) | ||
.done(function() { | ||
// add comment to last line of the text | ||
var $lastTextElement = inner$("div").first(); | ||
$lastTextElement.sendkeys('{selectall}'); // need to select content to add comment to | ||
await helper.waitForPromise(() => inner$("div").length > 1); | ||
addComment("My comment", callback); | ||
}); | ||
// add comment to last line of the text | ||
var $lastTextElement = inner$("div").first(); | ||
$lastTextElement.sendkeys('{selectall}'); // need to select content to add comment to | ||
await addComment('My comment'); | ||
} | ||
// Assumes text is already selected, then add comment to the selected text | ||
var addComment = function(commentText, callback) { | ||
const addComment = async (commentText) => { | ||
var inner$ = helper.padInner$; | ||
@@ -265,9 +234,18 @@ var outer$ = helper.padOuter$; | ||
// wait until comment is created and comment id is set | ||
helper.waitFor(function() { | ||
return getCommentId(numberOfComments) !== null; | ||
}) | ||
.done(callback); | ||
} | ||
let running = false; | ||
let success = false; | ||
await helper.waitForPromise(() => { | ||
if (success) return true; | ||
if (!running) { | ||
running = true; | ||
getCommentId(numberOfComments).then((id) => { | ||
running = false; | ||
success = id != null; | ||
}); | ||
} | ||
return success; | ||
}); | ||
}; | ||
var deleteComment = function(callback) { | ||
const deleteComment = async () => { | ||
var chrome$ = helper.padChrome$; | ||
@@ -280,31 +258,25 @@ var outer$ = helper.padOuter$; | ||
helper.waitFor(function() { | ||
return chrome$(".sidebar-comment").is(":visible") === false; | ||
}) | ||
.done(callback); | ||
} | ||
await helper.waitForPromise(() => chrome$('.sidebar-comment').is(':visible') === false); | ||
}; | ||
var getCommentId = function(numberOfComments) { | ||
const getCommentId = async (numberOfComments) => { | ||
var nthComment = numberOfComments || 0; | ||
helper.waitFor(function(){ | ||
var inner$ = helper.padInner$; | ||
if(inner$) return true; | ||
}).done(function(){ | ||
var inner$ = helper.padInner$; | ||
var comment = inner$(".comment").eq(nthComment); | ||
var cls = comment.attr('class'); | ||
var classCommentId = /(?:^| )(c-[A-Za-z0-9]*)/.exec(cls); | ||
var commentId = (classCommentId) ? classCommentId[1] : null; | ||
return commentId; | ||
}); | ||
} | ||
const p = helper.waitFor(() => helper.padInner$); | ||
p.fail(() => {}); // Prevent p from throwing an uncatchable exception on error. | ||
await p; | ||
var inner$ = helper.padInner$; | ||
var comment = inner$(".comment").eq(nthComment); | ||
var cls = comment.attr('class'); | ||
var classCommentId = /(?:^| )(c-[A-Za-z0-9]*)/.exec(cls); | ||
var commentId = (classCommentId) ? classCommentId[1] : null; | ||
return commentId; | ||
}; | ||
var finishTestIfIconsAreNotEnabled = function(done, theTest) { | ||
const finishTestIfIconsAreNotEnabled = async (theTest) => { | ||
// #commentIcons will only be inserted if icons are enabled | ||
if (helper.padOuter$("#commentIcons").length === 0) done(); | ||
else theTest(done); | ||
} | ||
if (helper.padOuter$('#commentIcons').length !== 0) await theTest(); | ||
}; | ||
var chooseToShowComments = function(shouldShowComments, callback) { | ||
const chooseToShowComments = (shouldShowComments) => { | ||
var chrome$ = helper.padChrome$; | ||
@@ -322,11 +294,8 @@ | ||
$settingsButton.click(); | ||
}; | ||
callback(); | ||
} | ||
var enlargeScreen = function(callback) { | ||
const enlargeScreen = () => { | ||
$('#iframe-container iframe').css("max-width", "1000px"); | ||
callback(); | ||
} | ||
}; | ||
}); |
@@ -20,3 +20,7 @@ describe('ep_comments_page - Comment copy and paste', function () { | ||
helper.selectLines($firstLine, $firstLine, 1, 8); //'omethin' | ||
event = helperFunctions.copyLine(); | ||
try{ | ||
event = helperFunctions.copyLine(); | ||
}catch(e){ | ||
// suppress e.preventDefault issue with certain browsers | ||
}; | ||
cb(); | ||
@@ -107,4 +111,12 @@ }); | ||
helperFunctions.addComentAndReplyToLine(FIRST_LINE, commentText, replyText, function(){ | ||
event = helperFunctions.copyLine(); | ||
helperFunctions.pasteTextOnLine(event, SECOND_LINE); | ||
try{ | ||
event = helperFunctions.copyLine(); | ||
}catch(e){ | ||
// suppress e.preventDefault issue with certain browsers | ||
}; | ||
try{ | ||
helperFunctions.pasteTextOnLine(event, SECOND_LINE); | ||
}catch(e){ | ||
// allowing helper to fail silently. | ||
}; | ||
cb(); | ||
@@ -111,0 +123,0 @@ }); |
@@ -5,5 +5,2 @@ var appUrl = 'http://localhost:9001'; | ||
var supertest = require('ep_etherpad-lite/node_modules/supertest'), | ||
fs = require('fs'), | ||
path = require('path'), | ||
// io = require('socket.io-client'), | ||
request = require('ep_etherpad-lite/node_modules/request'), | ||
@@ -13,12 +10,4 @@ api = supertest(appUrl), | ||
// Loads the APIKEY.txt content into a string, and returns it. | ||
var getApiKey = function() { | ||
var etherpad_root = '/../../../ep_etherpad-lite/../..'; | ||
var filePath = path.join(__dirname, etherpad_root + '/APIKEY.txt'); | ||
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'}); | ||
return apiKey.replace(/\n$/, ""); | ||
} | ||
const apiKey = require('ep_etherpad-lite/node/handler/APIHandler.js').exportedForTestingOnly.apiKey; | ||
var apiKey = getApiKey(); | ||
// Functions to validate API responses: | ||
@@ -25,0 +14,0 @@ var codeToBe = function(expectedCode, res) { |
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
298060
1
55
2
2
2
6273
- Removedsocket.io-client@*
- Removed@socket.io/component-emitter@3.1.2(transitive)
- Removeddebug@4.3.7(transitive)
- Removedengine.io-client@6.6.3(transitive)
- Removedengine.io-parser@5.2.3(transitive)
- Removedms@2.1.3(transitive)
- Removedsocket.io-client@4.8.1(transitive)
- Removedsocket.io-parser@4.2.4(transitive)
- Removedws@8.17.1(transitive)
- Removedxmlhttprequest-ssl@2.1.2(transitive)