commit
f56846309a
15 changed files with 1105 additions and 147 deletions
16
README.md
16
README.md
|
@ -1,5 +1,8 @@
|
|||
# Description
|
||||
This plugin allows users to subscribe to pads and receive email updates when a pad is being modified. You can modify the frequency. This plugin is very much in alpha stage and has a lot of things TODO (See TODO).
|
||||
This plugin allows users to subscribe to pads and receive email updates when a pad is being modified. You can modify the frequency. This plugin is still in early stage and has things TODO (See TODO).
|
||||
|
||||
# Source code
|
||||
On Github : https://github.com/JohnMcLear/ep_email_notifications
|
||||
|
||||
# Installation
|
||||
Make sure an SMTP gateway is installed IE postfix
|
||||
|
@ -11,6 +14,10 @@ NOTE: You will NOT receive an email if you(the author that registered their emai
|
|||
|
||||
```
|
||||
"ep_email_notifications" : {
|
||||
panelDisplayLocation: { // Where you want to have the subscription panel
|
||||
mysettings: true, // In the "mysettings" menu
|
||||
popup: true // A popup that pop in the bottom right corner of the pad after 10 seconds
|
||||
},
|
||||
checkFrequency: 6000, // checkFrequency = How frequently(milliseconds) to check for pad updates -- Move me to the settings file
|
||||
staleTime: 30000, // staleTime = How stale(milliseconds) does a pad need to be before notifying subscribers? Move me to settings
|
||||
fromName: "Etherpad SETTINGS FILE!",
|
||||
|
@ -22,10 +29,15 @@ NOTE: You will NOT receive an email if you(the author that registered their emai
|
|||
}
|
||||
```
|
||||
|
||||
# Translation
|
||||
This plugin has for now an english and french translation.
|
||||
In case you would like to have it in another language, you can easily translate the few sentences and then contact us on irc (#etherpad-lite-dev on irc.freenode.net) or create a Pull-Request on the GitHub repository.
|
||||
You can find the sentences to translate in the ep_email_notifications/locales/ directory.
|
||||
Specials chars written in unicode (See https://fr.wikipedia.org/wiki/Table_des_caract%C3%A8res_Unicode_%280000-0FFF%29)
|
||||
|
||||
# TODO
|
||||
* Clean up all code
|
||||
|
||||
# FUTURE VERSIONS TODO
|
||||
* v2 - Get the modified contents from the API HTML diff and append that to the Email and make the email from the server HTML not plain text
|
||||
* v2 - a point to unsubscribe and validate/verify email https://github.com/alfredwesterveld/node-email-verification
|
||||
* v2 - Keep a record of when a user was last on a pad
|
||||
|
|
16
client.js
16
client.js
|
@ -1,11 +1,23 @@
|
|||
var eejs = require("ep_etherpad-lite/node/eejs");
|
||||
var settings = require('../../src/node/utils/Settings');
|
||||
|
||||
exports.eejsBlock_scripts = function (hook_name, args, cb) {
|
||||
args.content = args.content + eejs.require("ep_email_notifications/templates/scripts.html", {}, module);
|
||||
return cb();
|
||||
};
|
||||
|
||||
exports.eejsBlock_embedPopup = function (hook_name, args, cb) {
|
||||
args.content = args.content + eejs.require("ep_email_notifications/templates/embedFrame.html", {}, module);
|
||||
exports.eejsBlock_mySettings = function (hook_name, args, cb) {
|
||||
args.content = args.content + eejs.require('ep_email_notifications/templates/email_notifications_settings.ejs');
|
||||
return cb();
|
||||
};
|
||||
|
||||
exports.eejsBlock_styles = function (hook_name, args, cb) {
|
||||
args.content = args.content + '<link href="../static/plugins/ep_email_notifications/static/css/email_notifications.css" rel="stylesheet">';
|
||||
};
|
||||
|
||||
exports.clientVars = function(hook, context, callback) {
|
||||
var pluginSettings = settings.ep_email_notifications;
|
||||
var panelDisplayLocation = (pluginSettings && pluginSettings.panelDisplayLocation)?pluginSettings.panelDisplayLocation:"undefiend";
|
||||
// return the setting to the clientVars, sending the value
|
||||
return callback({ "panelDisplayLocation": panelDisplayLocation });
|
||||
};
|
||||
|
|
10
ep.json
10
ep.json
|
@ -6,11 +6,17 @@
|
|||
"padUpdate": "ep_email_notifications/update",
|
||||
"handleMessage": "ep_email_notifications/handleMessage",
|
||||
"eejsBlock_scripts": "ep_email_notifications/client",
|
||||
"eejsBlock_embedPopup": "ep_email_notifications/client:eejsBlock_embedPopup"
|
||||
"eejsBlock_mySettings": "ep_email_notifications/client:eejsBlock_mySettings",
|
||||
"eejsBlock_styles": "ep_email_notifications/client:eejsBlock_styles",
|
||||
"clientVars": "ep_email_notifications/client:clientVars",
|
||||
"expressCreateServer" : "ep_email_notifications/index:registerRoute"
|
||||
},
|
||||
"client_hooks": {
|
||||
"postAceInit":"ep_email_notifications/static/js/ep_email:postAceInit",
|
||||
"handleClientMessage_emailSubscriptionSuccess":"ep_email_notifications/static/js/ep_email"
|
||||
"handleClientMessage_emailSubscriptionSuccess":"ep_email_notifications/static/js/ep_email",
|
||||
"handleClientMessage_emailUnsubscriptionSuccess":"ep_email_notifications/static/js/ep_email",
|
||||
"handleClientMessage_emailNotificationGetUserInfo":"ep_email_notifications/static/js/ep_email",
|
||||
"handleClientMessage_emailNotificationMissingParams":"ep_email_notifications/static/js/ep_email"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
385
handleMessage.js
385
handleMessage.js
|
@ -1,38 +1,72 @@
|
|||
var db = require('../../src/node/db/DB').db,
|
||||
API = require('../../src/node/db/API.js'),
|
||||
async = require('../../src/node_modules/async'),
|
||||
check = require('validator').check,
|
||||
email = require('emailjs'),
|
||||
randomString = require('../../src/static/js/pad_utils').randomString;
|
||||
settings = require('../../src/node/utils/Settings');
|
||||
|
||||
var pluginSettings = settings.ep_email_notifications;
|
||||
var areParamsOk = (pluginSettings)?true:false;
|
||||
var fromName = (pluginSettings && pluginSettings.fromName)?pluginSettings.fromName:"Etherpad";
|
||||
var fromEmail = (pluginSettings && pluginSettings.fromEmail)?pluginSettings.fromEmail:"pad@etherpad.org";
|
||||
var urlToPads = (pluginSettings && pluginSettings.urlToPads)?pluginSettings.urlToPads:"http://beta.etherpad.org/p/";
|
||||
var emailServer = (pluginSettings && pluginSettings.emailServer)?pluginSettings.emailServer:{host:"127.0.0.1"};
|
||||
|
||||
if (areParamsOk == false) console.warn("Settings for ep_email_notifications plugin are missing in settings.json file");
|
||||
|
||||
// Connect to the email server -- This might not be the ideal place to connect but it stops us having lots of connections
|
||||
var server = email.server.connect(emailServer);
|
||||
|
||||
// When a new message comes in from the client - FML this is ugly
|
||||
exports.handleMessage = function(hook_name, context, callback){
|
||||
if (context.message && context.message.data){
|
||||
if (context.message.data.type == 'USERINFO_UPDATE' ) { // if it's a request to update an authors email
|
||||
if (context.message.data.userInfo){
|
||||
if (areParamsOk == false) {
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailNotificationMissingParams",
|
||||
payload: true
|
||||
}
|
||||
});
|
||||
console.warn("Settings for ep_email_notifications plugin are missing in settings.json file");
|
||||
|
||||
callback([null]); // don't run onto passing colorId or anything else to the message handler
|
||||
|
||||
} else if (context.message.data.userInfo){
|
||||
if(context.message.data.userInfo.email){ // it contains email
|
||||
console.debug(context.message);
|
||||
|
||||
// does email Subscription already exist for this email address?
|
||||
// does email (Un)Subscription already exist for this email address?
|
||||
db.get("emailSubscription:"+context.message.data.padId, function(err, userIds){
|
||||
|
||||
var alreadyExists = false;
|
||||
|
||||
if(userIds){
|
||||
async.forEach(Object.keys(userIds), function(user, cb){
|
||||
console.debug("UserIds subscribed by email to this pad:", userIds);
|
||||
if(user == context.message.data.userInfo.email){ // If we already have this email registered for this pad
|
||||
|
||||
if(user == context.message.data.userInfo.email){ // If we already have this email registered for this pad
|
||||
// This user ID is already assigned to this padId so don't do anything except tell the user they are already subscribed somehow..
|
||||
alreadyExists = true;
|
||||
console.debug("email ", user, "already subscribed to ", context.message.data.padId, " so sending message to client");
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: false
|
||||
}
|
||||
});
|
||||
if(context.message.data.userInfo.email_option == 'subscribe') {
|
||||
// Subscription process
|
||||
exports.subscriptionEmail(
|
||||
context,
|
||||
context.message.data.userInfo.email,
|
||||
alreadyExists,
|
||||
context.message.data.userInfo,
|
||||
context.message.data.padId,
|
||||
callback
|
||||
);
|
||||
} else if (context.message.data.userInfo.email_option == 'unsubscribe') {
|
||||
// Unsubscription process
|
||||
exports.unsubscriptionEmail(
|
||||
context,
|
||||
alreadyExists,
|
||||
context.message.data.userInfo,
|
||||
context.message.data.padId
|
||||
);
|
||||
}
|
||||
}
|
||||
cb();
|
||||
},
|
||||
|
@ -41,68 +75,321 @@ exports.handleMessage = function(hook_name, context, callback){
|
|||
// There should be something in here!
|
||||
}); // end async for each
|
||||
}
|
||||
var validatesAsEmail = check(context.message.data.userInfo.email).isEmail();
|
||||
if(!validatesAsEmail){ // send validation failed if it's malformed.. y'know in general fuck em!
|
||||
console.warn("Dropped email subscription due to malformed email address");
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: false
|
||||
}
|
||||
});
|
||||
|
||||
// In case we didn't find it in the Db
|
||||
if (alreadyExists == false) {
|
||||
if(context.message.data.userInfo.email_option == 'subscribe') {
|
||||
// Subscription process
|
||||
exports.subscriptionEmail(
|
||||
context,
|
||||
context.message.data.userInfo.email,
|
||||
alreadyExists,
|
||||
context.message.data.userInfo,
|
||||
context.message.data.padId,
|
||||
callback
|
||||
);
|
||||
} else if (context.message.data.userInfo.email_option == 'unsubscribe') {
|
||||
// Unsubscription process
|
||||
exports.unsubscriptionEmail(
|
||||
context,
|
||||
alreadyExists,
|
||||
context.message.data.userInfo,
|
||||
context.message.data.padId
|
||||
);
|
||||
}
|
||||
}
|
||||
if(alreadyExists == false && validatesAsEmail){
|
||||
console.debug ("Wrote to the database and sent client a positive response ",context.message.data.userInfo.email);
|
||||
|
||||
exports.setAuthorEmail(
|
||||
context.message.data.userInfo.userId,
|
||||
context.message.data.userInfo.email, callback
|
||||
);
|
||||
|
||||
exports.setAuthorEmailRegistered(
|
||||
context.message.data.userInfo.email,
|
||||
context.message.data.userInfo.userId,
|
||||
context.message.data.padId
|
||||
);
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}); // close db get
|
||||
|
||||
callback([null]); // don't run onto passing colorId or anything else to the message handler
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else if (context.message.data.type == 'USERINFO_GET' ) { // A request to find datas for a userId
|
||||
if (context.message.data.userInfo){
|
||||
if(context.message.data.userInfo.userId){ // it contains the userId
|
||||
console.debug(context.message);
|
||||
|
||||
// does email Subscription already exist for this UserId?
|
||||
db.get("emailSubscription:"+context.message.data.padId, function(err, userIds){
|
||||
var userIdFound = false;
|
||||
|
||||
if(userIds){
|
||||
async.forEach(Object.keys(userIds), function(user, cb){
|
||||
if(userIds[user].authorId == context.message.data.userInfo.userId){ // if we find the same Id in the Db as the one used by the user
|
||||
console.debug("Options for this pad ", userIds[user].authorId, " found in the Db");
|
||||
userIdFound = true;
|
||||
|
||||
// Request user subscription info process
|
||||
exports.sendUserInfo (
|
||||
context,
|
||||
userIdFound,
|
||||
user,
|
||||
userIds[user]
|
||||
);
|
||||
}
|
||||
cb();
|
||||
},
|
||||
|
||||
function(err){
|
||||
// There should be something in here!
|
||||
}); // end async for each
|
||||
}
|
||||
|
||||
if (userIdFound == false) {
|
||||
// Request user subscription info process
|
||||
exports.sendUserInfo (
|
||||
context,
|
||||
userIdFound,
|
||||
"",
|
||||
""
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
callback([null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
// Updates the database with the email record
|
||||
exports.setAuthorEmail = function (author, email, callback){
|
||||
db.setSub("globalAuthor:" + author, ["email"], email, callback);
|
||||
/**
|
||||
* Subscription process
|
||||
*/
|
||||
exports.subscriptionEmail = function (context, email, emailFound, userInfo, padId, callback) {
|
||||
var validatesAsEmail = exports.checkEmailValidation(email);
|
||||
var subscribeId = randomString(25);
|
||||
|
||||
if(emailFound == false && validatesAsEmail){
|
||||
// Subscription -> Go for it
|
||||
console.debug ("Subscription: Wrote to the database and sent client a positive response ",context.message.data.userInfo.email);
|
||||
|
||||
exports.setAuthorEmailRegistered(
|
||||
userInfo,
|
||||
userInfo.userId,
|
||||
subscribeId,
|
||||
padId
|
||||
);
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: {
|
||||
formName: userInfo.formName,
|
||||
success: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Send mail to user with the link for validation
|
||||
server.send(
|
||||
{
|
||||
text: "Please click on this link in order to validate your subscription to the pad " + padId + "\n" + urlToPads+padId + "/subscribe=" + subscribeId,
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: userInfo.email,
|
||||
subject: "Email subscription confirmation for pad "+padId
|
||||
},
|
||||
function(err, message) {
|
||||
console.log(err || message);
|
||||
}
|
||||
);
|
||||
|
||||
} else if (!validatesAsEmail) {
|
||||
// Subscription -> failed coz mail malformed.. y'know in general fuck em!
|
||||
console.warn("Dropped email subscription due to malformed email address");
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: {
|
||||
type: "malformedEmail",
|
||||
formName: userInfo.formName,
|
||||
success: false
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Subscription -> failed coz email already subscribed for this pad
|
||||
console.debug("email ", context.message.data.userInfo.email, "already subscribed to ", context.message.data.padId, " so sending message to client");
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailSubscriptionSuccess",
|
||||
payload: {
|
||||
type: "alreadyRegistered",
|
||||
formName: userInfo.formName,
|
||||
success: false
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Write email and padId to the database
|
||||
exports.setAuthorEmailRegistered = function(email, authorId, padId){
|
||||
/**
|
||||
* UnsUbscription process
|
||||
*/
|
||||
exports.unsubscriptionEmail = function (context, emailFound, userInfo, padId) {
|
||||
var unsubscribeId = randomString(25);
|
||||
|
||||
if(emailFound == true) {
|
||||
// Unsubscription -> Go for it
|
||||
console.debug ("Unsubscription: Remove from the database and sent client a positive response ",context.message.data.userInfo.email);
|
||||
|
||||
exports.unsetAuthorEmailRegistered(
|
||||
userInfo,
|
||||
userInfo.userId,
|
||||
unsubscribeId,
|
||||
padId
|
||||
);
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailUnsubscriptionSuccess",
|
||||
payload: {
|
||||
formName: userInfo.formName,
|
||||
success: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Send mail to user with the link for validation
|
||||
server.send(
|
||||
{
|
||||
text: "Please click on this link in order to validate your unsubscription to the pad " + padId + "\n" + urlToPads+padId + "/unsubscribe=" + unsubscribeId,
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: userInfo.email,
|
||||
subject: "Email unsubscription confirmation for pad "+padId
|
||||
},
|
||||
function(err, message) {
|
||||
console.log(err || message);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Unsubscription -> Send failed as email not found
|
||||
console.debug ("Unsubscription: Send client a negative response ",context.message.data.userInfo.email);
|
||||
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailUnsubscriptionSuccess",
|
||||
payload: {
|
||||
formName: userInfo.formName,
|
||||
success: false
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request user subscription info process
|
||||
*/
|
||||
exports.sendUserInfo = function (context, emailFound, email, userInfo) {
|
||||
var defaultOnStartOption = true;
|
||||
var defaultOnEndOption = false;
|
||||
|
||||
if (typeof userInfo.onStart == 'boolean' && typeof userInfo.onEnd == 'boolean') {
|
||||
var onStart = userInfo.onStart;
|
||||
var onEnd = userInfo.onEnd;
|
||||
} else { // In case these options are not yet defined for this userId
|
||||
var onStart = defaultOnStartOption;
|
||||
var onEnd = defaultOnEndOption;
|
||||
}
|
||||
|
||||
if (emailFound == true) {
|
||||
// We send back the options associated to this userId
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailNotificationGetUserInfo",
|
||||
payload: {
|
||||
email: email,
|
||||
onStart: onStart,
|
||||
onEnd: onEnd,
|
||||
formName: context.message.data.userInfo.formName,
|
||||
success:true
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No options set for this userId
|
||||
context.client.json.send({ type: "COLLABROOM",
|
||||
data:{
|
||||
type: "emailNotificationGetUserInfo",
|
||||
payload: {
|
||||
formName: context.message.data.userInfo.formName,
|
||||
success:false
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check if an email is valid
|
||||
*/
|
||||
exports.checkEmailValidation = function (email) {
|
||||
var Validator = require('validator').Validator;
|
||||
var validator = new Validator();
|
||||
validator.error = function() {
|
||||
return false;
|
||||
};
|
||||
return validator.check(email).isEmail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Database manipulation
|
||||
*/
|
||||
|
||||
// Write email, options, authorId and pendingId to the database
|
||||
exports.setAuthorEmailRegistered = function(userInfo, authorId, subscribeId, padId){
|
||||
var timestamp = new Date().getTime();
|
||||
var registered = {
|
||||
authorId: authorId,
|
||||
onStart: userInfo.email_onStart,
|
||||
onEnd: userInfo.email_onEnd,
|
||||
subscribeId: subscribeId,
|
||||
timestamp: timestamp
|
||||
};
|
||||
console.debug("registered", registered, " to ", padId);
|
||||
|
||||
// Here we have to basically hack a new value into the database, this isn't clean or polite.
|
||||
db.get("emailSubscription:" + padId, function(err, value){ // get the current value
|
||||
if(!value){value = {};} // if an emailSubscription doesnt exist yet for this padId don't panic
|
||||
value[email] = registered; // add the registered values to the object
|
||||
console.warn("written to database");
|
||||
if(!value){
|
||||
// if an emailSubscription doesnt exist yet for this padId don't panic
|
||||
value = {"pending":{}};
|
||||
} else if (!value['pending']) {
|
||||
// if the pending section doesn't exist yet for this padId, we create it
|
||||
value['pending'] = {};
|
||||
}
|
||||
|
||||
// add the registered values to the pending section of the object
|
||||
value['pending'][userInfo.email] = registered;
|
||||
|
||||
// Write the modified datas back in the Db
|
||||
db.set("emailSubscription:" + padId, value); // stick it in the database
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Write email, authorId and pendingId to the database
|
||||
exports.unsetAuthorEmailRegistered = function(userInfo, authorId, unsubscribeId, padId){
|
||||
var timestamp = new Date().getTime();
|
||||
var registered = {
|
||||
authorId: authorId,
|
||||
unsubscribeId: unsubscribeId,
|
||||
timestamp: timestamp
|
||||
};
|
||||
console.debug("unregistered", userInfo.email, " to ", padId);
|
||||
|
||||
db.get("emailSubscription:" + padId, function(err, value){ // get the current value
|
||||
// if the pending section doesn't exist yet for this padId, we create it (this shouldn't happen)
|
||||
if (!value['pending']) {value['pending'] = {};}
|
||||
|
||||
// add the registered values to the pending section of the object
|
||||
value['pending'][userInfo.email] = registered;
|
||||
|
||||
// Write the modified datas back in the Db
|
||||
db.set("emailSubscription:" + padId, value);
|
||||
});
|
||||
}
|
||||
|
|
254
index.js
Normal file
254
index.js
Normal file
|
@ -0,0 +1,254 @@
|
|||
var db = require('ep_etherpad-lite/node/db/DB').db,
|
||||
fs = require("fs"),
|
||||
async = require('../../src/node_modules/async'),
|
||||
settings = require('../../src/node/utils/Settings');
|
||||
|
||||
// Remove cache for this procedure
|
||||
db['dbSettings'].cache = 0;
|
||||
|
||||
exports.registerRoute = function (hook_name, args, callback) {
|
||||
// Catching (un)subscribe addresses
|
||||
args.app.get('/p/*/(un){0,1}subscribe=*', function(req, res) {
|
||||
var fullURL = req.protocol + "://" + req.get('host') + req.url;
|
||||
var path=req.url.split("/");
|
||||
var padId=path[2];
|
||||
var param = path[3].split("=");
|
||||
var action = param[0];
|
||||
var actionId = param[1];
|
||||
var padURL = req.protocol + "://" + req.get('host') + "/p/" +padId;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function(cb) {
|
||||
// Is the (un)subscription valid (exists & not older than 24h)
|
||||
db.get("emailSubscription:"+padId, function(err, userIds){
|
||||
var foundInDb = false;
|
||||
var timeDiffGood = false;
|
||||
var email = "your email";
|
||||
var resultDb = {
|
||||
"foundInDb": foundInDb,
|
||||
"timeDiffGood": timeDiffGood,
|
||||
"email": email
|
||||
}
|
||||
|
||||
if(userIds && userIds['pending']){
|
||||
async.forEach(Object.keys(userIds['pending']), function(user){
|
||||
var userInfo = userIds['pending'][user];
|
||||
|
||||
// If we have Id int the Db, then we are good ot really unsubscribe the user
|
||||
if(userInfo[action + 'Id'] == actionId){
|
||||
console.debug("emailSubscription:", user, "found in DB:", userInfo);
|
||||
|
||||
foundInDb = true;
|
||||
email = user;
|
||||
|
||||
// Checking if the demand is not older than 24h
|
||||
var timeDiff = new Date().getTime() - userInfo.timestamp;
|
||||
timeDiffGood = timeDiff < 1000 * 60 * 60 * 24;
|
||||
|
||||
if(action == 'subscribe' && timeDiffGood == true) {
|
||||
// Subscription process
|
||||
setAuthorEmail(
|
||||
userInfo,
|
||||
user
|
||||
);
|
||||
|
||||
setAuthorEmailRegistered(
|
||||
userIds,
|
||||
userInfo,
|
||||
user,
|
||||
padId
|
||||
);
|
||||
} else if (action == 'unsubscribe' && timeDiffGood == true) {
|
||||
// Unsubscription process
|
||||
unsetAuthorEmail(
|
||||
userInfo,
|
||||
user
|
||||
);
|
||||
|
||||
unsetAuthorEmailRegistered(
|
||||
userIds,
|
||||
user,
|
||||
padId
|
||||
);
|
||||
}
|
||||
|
||||
resultDb = {
|
||||
"foundInDb": foundInDb,
|
||||
"timeDiffGood": timeDiffGood,
|
||||
"email": user
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
function(err, msg){
|
||||
if (err != null) {
|
||||
console.error("Error in async.forEach", err, " -> ", msg);
|
||||
}
|
||||
}); // end async for each
|
||||
}
|
||||
cb(null, resultDb);
|
||||
});
|
||||
},
|
||||
|
||||
function(resultDb, cb) {
|
||||
// Create and send the output message
|
||||
sendContent(res, args, action, padId, padURL, resultDb);
|
||||
cb(null, resultDb);
|
||||
},
|
||||
|
||||
function(resultDb, cb) {
|
||||
// Take a moment to clean all obsolete pending data
|
||||
cleanPendingData(padId);
|
||||
cb(null, resultDb);
|
||||
}
|
||||
],
|
||||
function(err, results){
|
||||
if (err != null) {
|
||||
console.error("Callback async.series: Err -> ", err, " / results -> ", results);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
callback(); // Am I even called?
|
||||
}
|
||||
|
||||
/**
|
||||
* Database manipulation
|
||||
*/
|
||||
|
||||
// Updates the database with the email record
|
||||
setAuthorEmail = function (userInfo, email){
|
||||
db.setSub("globalAuthor:" + userInfo.authorId, ["email"], email);
|
||||
}
|
||||
|
||||
// Write email and padId to the database
|
||||
setAuthorEmailRegistered = function(userIds, userInfo, email, padId){
|
||||
console.debug("setAuthorEmailRegistered: Initial userIds:", userIds);
|
||||
var timestamp = new Date().getTime();
|
||||
var registered = {
|
||||
authorId: userInfo.authorId,
|
||||
onStart: userInfo.onStart,
|
||||
onEnd: userInfo.onEnd,
|
||||
timestamp: timestamp
|
||||
};
|
||||
|
||||
// add the registered values to the object
|
||||
userIds[email] = registered;
|
||||
|
||||
// remove the pending data
|
||||
delete userIds['pending'][email];
|
||||
|
||||
// Write the modified datas back in the Db
|
||||
console.warn("written to database");
|
||||
db.set("emailSubscription:" + padId, userIds); // stick it in the database
|
||||
|
||||
console.debug("setAuthorEmailRegistered: Modified userIds:", userIds);
|
||||
}
|
||||
|
||||
// Updates the database by removing the email record for that AuthorId
|
||||
unsetAuthorEmail = function (userInfo, email){
|
||||
db.get("globalAuthor:" + userInfo.authorId, function(err, value){ // get the current value
|
||||
if (value['email'] == email) {
|
||||
// Remove the email option from the datas
|
||||
delete value['email'];
|
||||
|
||||
// Write the modified datas back in the Db
|
||||
db.set("globalAuthor:" + userInfo.authorId, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove email, options and padId from the database
|
||||
unsetAuthorEmailRegistered = function(userIds, email, padId){
|
||||
console.debug("unsetAuthorEmailRegistered: initial userIds:", userIds);
|
||||
// remove the registered options from the object
|
||||
delete userIds[email];
|
||||
|
||||
// remove the pending data
|
||||
delete userIds['pending'][email];
|
||||
|
||||
// Write the modified datas back in the Db
|
||||
console.warn("written to database");
|
||||
db.set("emailSubscription:" + padId, userIds);
|
||||
|
||||
console.debug("unsetAuthorEmailRegistered: modified userIds:", userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* We take a moment to remove too old pending (un)subscription
|
||||
*/
|
||||
cleanPendingData = function (padId) {
|
||||
var modifiedData, areDataModified = false;
|
||||
|
||||
db.get("emailSubscription:" + padId, function(err, userIds){ // get the current value
|
||||
console.debug("cleanPendingData: Initial userIds:", userIds);
|
||||
modifiedData = userIds;
|
||||
if(userIds && userIds['pending']){
|
||||
async.forEach(Object.keys(userIds['pending']), function(user){
|
||||
var timeDiff = new Date().getTime() - userIds['pending'][user].timestamp;
|
||||
var timeDiffGood = timeDiff < 1000 * 60 * 60 * 24;
|
||||
|
||||
if(timeDiffGood == false) {
|
||||
delete modifiedData['pending'][user];
|
||||
|
||||
areDataModified = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (areDataModified == true) {
|
||||
// Write the modified datas back in the Db
|
||||
db.set("emailSubscription:" + padId, modifiedData);
|
||||
}
|
||||
|
||||
console.debug("cleanPendingData: Modified userIds:", modifiedData, " / areDataModified:", areDataModified);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html output with the status of the process
|
||||
*/
|
||||
function sendContent(res, args, action, padId, padURL, resultDb) {
|
||||
console.debug("starting sendContent: args ->", action, " / ", padId, " / ", padURL, " / ", resultDb);
|
||||
|
||||
if (action == 'subscribe') {
|
||||
var actionMsg = "Subscribing '" + resultDb.email + "' to pad " + padId;
|
||||
} else {
|
||||
var actionMsg = "Unsubscribing '" + resultDb.email + "' from pad " + padId;
|
||||
}
|
||||
var msgCause, resultMsg, classResult;
|
||||
|
||||
if (resultDb.foundInDb == true && resultDb.timeDiffGood == true) {
|
||||
// Pending data were found un Db and updated -> good
|
||||
resultMsg = "Success";
|
||||
classResult = "validationGood";
|
||||
if (action == 'subscribe') {
|
||||
msgCause = "You will receive email when someone changes this pad.";
|
||||
} else {
|
||||
msgCause = "You won't receive anymore email when someone changes this pad.";
|
||||
}
|
||||
} else if (resultDb.foundInDb == true) {
|
||||
// Pending data were found but older than a day -> fail
|
||||
resultMsg = "Too late!";
|
||||
classResult = "validationBad";
|
||||
msgCause = "You have max 24h to click the link in your confirmation email.";
|
||||
} else {
|
||||
// Pending data weren't found in Db -> fail
|
||||
resultMsg = "Fail";
|
||||
classResult = "validationBad";
|
||||
msgCause = "We couldn't find any pending " + (action == 'subscribe'?'subscription':'unsubscription') + "<br />in our system with this Id.<br />Maybe you wait more than 24h before validating";
|
||||
}
|
||||
|
||||
args.content = fs.readFileSync(__dirname + "/templates/response.ejs", 'utf-8');
|
||||
args.content = args.content
|
||||
.replace(/\<%action%\>/, actionMsg)
|
||||
.replace(/\<%classResult%\>/, classResult)
|
||||
.replace(/\<%result%\>/, resultMsg)
|
||||
.replace(/\<%explanation%\>/, msgCause)
|
||||
.replace(/\<%padUrl%\>/g, padURL);
|
||||
|
||||
res.contentType("text/html; charset=utf-8");
|
||||
res.send(args.content); // Send it to the requester*/
|
||||
}
|
19
locales/en.json
Normal file
19
locales/en.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{ "ep_email_notifications.titleGritterError": "Email subscription error"
|
||||
, "ep_email_notifications.titleGritterSubscr": "Email subscription"
|
||||
, "ep_email_notifications.titleGritterUnsubscr": "Email unsubscription"
|
||||
, "ep_email_notifications.headerGritterSubscr": "(Receive an email when someone modifies this pad)"
|
||||
, "ep_email_notifications.msgOptionsNotChecked": "You need to check at least one of the two options from 'Send a mail when someone..'"
|
||||
, "ep_email_notifications.msgParamsMissing": "Settings for the 'email_Notifications' plugin are missing.<br />Please contact your administrator."
|
||||
, "ep_email_notifications.msgEmailMalformed": "The email address is malformed"
|
||||
, "ep_email_notifications.msgAlreadySubscr": "You are already registered for emails for this pad"
|
||||
, "ep_email_notifications.msgUnsubscrNotExisting": "This email address is not registered for this pad"
|
||||
, "ep_email_notifications.msgUnknownErr": "Unknown error"
|
||||
, "ep_email_notifications.msgSubscrSuccess": "An email was sent to your address.<br />Click on the link in order to validate your subscription."
|
||||
, "ep_email_notifications.msgUnsubscrSuccess": "An email was sent to your address.<br />Click on the link in order to validate your unsubscription"
|
||||
, "ep_email_notifications.menuLabel": "Email Notifications"
|
||||
, "ep_email_notifications.formOptionsTitle": "Send a mail when someone.."
|
||||
, "ep_email_notifications.formOptionOnStart": "starts editing the pad"
|
||||
, "ep_email_notifications.formOptionOnEnd": "finish editing the pad"
|
||||
, "ep_email_notifications.formBtnSubscr": "subscribe"
|
||||
, "ep_email_notifications.formBtnUnsubscr": "unsubscribe"
|
||||
}
|
19
locales/fr.json
Normal file
19
locales/fr.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{ "ep_email_notifications.titleGritterError": "Notification par email - Erreur"
|
||||
, "ep_email_notifications.titleGritterSubscr": "Notification par email"
|
||||
, "ep_email_notifications.titleGritterUnsubscr": "D\u00e9sinscription d\u2019email"
|
||||
, "ep_email_notifications.headerGritterSubscr": "(\u00catre pr\u00e9venu lorsque quelqu\u2019un modifie ce pad)"
|
||||
, "ep_email_notifications.msgOptionsNotChecked": "Il faut cocher au moins une des 2 options pour l\u2019inscription de l\u2019email..\u2019"
|
||||
, "ep_email_notifications.msgParamsMissing": "Les param\u00e8tres de configurations du plugin \u2019email_Notifications\u2019 sont manquants.<br />Veuillez contacter votre administrateur."
|
||||
, "ep_email_notifications.msgEmailMalformed": "L\u2019adresse email n\u2019est pas valide"
|
||||
, "ep_email_notifications.msgAlreadySubscr": "Vous avez d\u00e9j\u00e0 enregistr\u00e9 cette adresse email pour ce pad"
|
||||
, "ep_email_notifications.msgUnsubscrNotExisting": "Cette adresse email n\u2019est pas enregistr\u00e9e pour ce pad"
|
||||
, "ep_email_notifications.msgUnknownErr": "Erreur inconnue"
|
||||
, "ep_email_notifications.msgSubscrSuccess": "Un email a \u00e9t\u00e9 envoy\u00e9 \u00e0 votre adresse.<br />Cliquez sur le lien afin de valider votre inscription."
|
||||
, "ep_email_notifications.msgUnsubscrSuccess": "Un email a \u00e9t\u00e9 envoy\u00e9 \u00e0 votre adresse.<br />Cliquez sur le lien afin de valider votre d\u00e9sinscription."
|
||||
, "ep_email_notifications.menuLabel": "Notification par email"
|
||||
, "ep_email_notifications.formOptionsTitle": "Envoyer un email quand qqu\u2019un.."
|
||||
, "ep_email_notifications.formOptionOnStart": "commence l\u2019\u00e9dition du pad"
|
||||
, "ep_email_notifications.formOptionOnEnd": "a fini d\u2019\u00e9diter le pad"
|
||||
, "ep_email_notifications.formBtnSubscr": "inscription"
|
||||
, "ep_email_notifications.formBtnUnsubscr": "d\u00e9sinscription"
|
||||
}
|
17
package.json
17
package.json
|
@ -1,13 +1,17 @@
|
|||
{
|
||||
"name": "ep_email_notifications",
|
||||
"description": "Subscribe to a pad and receive an email when someone edits your pad",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"author": {
|
||||
"name": "johnyma22",
|
||||
"email": "john@mclear.co.uk",
|
||||
"url": "John McLear"
|
||||
},
|
||||
"contributors": [],
|
||||
"contributors": [{
|
||||
"name": "quenenni",
|
||||
"email": "quenenni@bruxxel.org",
|
||||
"url": "https://github.com/quenenni"
|
||||
}],
|
||||
"dependencies": {
|
||||
"emailjs": ">= 0.2.7",
|
||||
"buffertools": ">= 1.0.8",
|
||||
|
@ -15,5 +19,12 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.1"
|
||||
}
|
||||
},
|
||||
"readme": "# Description\nThis plugin allows users to subscribe to pads and receive email updates when a pad is being modified. You can modify the frequency. This plugin is very much in alpha stage and has a lot of things TODO (See TODO).\n\n# Installation\nMake sure an SMTP gateway is installed IE postfix\nConfigure SPF and RDNS records to ensure proper mail flow <-- Search online\nCopy/Edit the below to your settings.json\nConnect to a pad, Click on the Share/Embed link and enter in your email address.\nOpen that pad in ANOTHER BROWSER then begin modifying, you should receive an email when the pad has begun editing and once the pad has gone stale (when everyone stops editing it and a time period passes).\nNOTE: You will NOT receive an email if you(the author that registered their email) are currently on or editing that pad!\n\n```\n \"ep_email_notifications\" : {\n checkFrequency: 6000, // checkFrequency = How frequently(milliseconds) to check for pad updates -- Move me to the settings file\n staleTime: 30000, // staleTime = How stale(milliseconds) does a pad need to be before notifying subscribers? Move me to settings\n fromName: \"Etherpad SETTINGS FILE!\",\n fromEmail: \"pad@etherpad.org\",\n urlToPads: \"http://beta.etherpad.org/p/\", // urlToPads = The URL to your pads note the trailing /\n emailServer: { // See https://github.com/eleith/emailjs for settings\n host: \"127.0.0.1\"\n }\n }\n```\n\n# TODO\n* Clean up all code\n\n# FUTURE VERSIONS TODO\n* v2 - Get the modified contents from the API HTML diff and append that to the Email and make the email from the server HTML not plain text\n* v2 - a point to unsubscribe and validate/verify email https://github.com/alfredwesterveld/node-email-verification\n* v2 - Keep a record of when a user was last on a pad\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_id": "ep_email_notifications@0.0.7",
|
||||
"dist": {
|
||||
"shasum": "1f32eee4c8d5f3903c549b5a7985afc0053ed451"
|
||||
},
|
||||
"_from": "ep_email_notifications"
|
||||
}
|
||||
|
|
64
static/css/email_notifications.css
Normal file
64
static/css/email_notifications.css
Normal file
|
@ -0,0 +1,64 @@
|
|||
.ep_email_settings {
|
||||
display: none;
|
||||
padding: 0.2em 0.2em 0.2em 0.5em;
|
||||
}
|
||||
|
||||
.ep_email_buttons {
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
.ep_email_checkbox {
|
||||
margin-left:0.3em;
|
||||
}
|
||||
|
||||
.ep_email_input {
|
||||
padding:.2em;
|
||||
width:177px;
|
||||
}
|
||||
|
||||
.ep_email_form_popup_header {
|
||||
font-size:x-small;
|
||||
}
|
||||
|
||||
#ep_email_form_popup .ep_email_buttons {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
#ep_email_form_popup .ep_email_checkbox {
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
#ep_email_form_popup .ep_email_input {
|
||||
margin: .3em 0;
|
||||
width:95%;
|
||||
}
|
||||
/* (un)subscription validation page */
|
||||
.validationTitle {
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
.validationEmailSubscription {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-size: bigger;
|
||||
font-weight: bold;
|
||||
font-color: green;
|
||||
}
|
||||
.validationEmailSubscription > div {
|
||||
border: solid 2px #333;
|
||||
padding: .3em;
|
||||
margin: .5em 0;
|
||||
}
|
||||
.validationEmailSubscription > div > div {
|
||||
margin:0;
|
||||
padding:.2em;
|
||||
font-weight:normal;
|
||||
}
|
||||
.validationGood {
|
||||
background-color: #CCFF66;
|
||||
}
|
||||
.validationBad {
|
||||
background-color: #FF3300;
|
||||
}
|
|
@ -1,62 +1,156 @@
|
|||
var cookie = require('ep_etherpad-lite/static/js/pad_cookie').padcookie;
|
||||
var optionsAlreadyRecovered = false;
|
||||
|
||||
if(typeof exports == 'undefined'){
|
||||
var exports = this['mymodule'] = {};
|
||||
}
|
||||
|
||||
exports.postAceInit = function(hook, context){
|
||||
// after 10 seconds if we dont already have an email for this author then prompt them
|
||||
setTimeout(function(){init()},10000);
|
||||
// If panelDisplayLocation setting is missing, set default value
|
||||
if (typeof clientVars.panelDisplayLocation != "object") {
|
||||
clientVars.panelDisplayLocation = {
|
||||
mysettings: true, // In the "mysettings" menu
|
||||
popup: true
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe by email can be active..
|
||||
$('.ep_email_form').submit(function(){
|
||||
sendEmailToServer();
|
||||
return false;
|
||||
});
|
||||
// If plugin settings set panel form in mysettings menu
|
||||
if (clientVars.panelDisplayLocation.mysettings == true) {
|
||||
// Uncheck the checkbox incase of reminiscence
|
||||
$('#options-emailNotifications').prop('checked', false);
|
||||
|
||||
}
|
||||
$('#options-emailNotifications').on('click', function() {
|
||||
if (!optionsAlreadyRecovered) {
|
||||
getDataForUserId('ep_email_form_mysettings');
|
||||
optionsAlreadyRecovered = true;
|
||||
} else {
|
||||
$('.ep_email_settings').slideToggle();
|
||||
}
|
||||
});
|
||||
|
||||
exports.handleClientMessage_emailSubscriptionSuccess = function(hook, context){ // was subscribing to the email a big win or fail?
|
||||
if(context.payload == false){
|
||||
showAlreadyRegistered();
|
||||
}else{
|
||||
showRegistrationSuccess();
|
||||
// Prepare subscription before submit form
|
||||
$('[name=ep_email_subscribe]').on('click', function(e) {
|
||||
$('[name=ep_email_option]').val('subscribe');
|
||||
checkAndSend(e);
|
||||
});
|
||||
|
||||
// Prepare unsubscription before submit form
|
||||
$('[name=ep_email_unsubscribe]').on('click', function(e) {
|
||||
$('[name=ep_email_option]').val('unsubscribe');
|
||||
checkAndSend(e);
|
||||
});
|
||||
|
||||
// subscribe by email can be active..
|
||||
$('#ep_email_form_mysettings').submit(function(){
|
||||
sendEmailToServer('ep_email_form_mysettings');
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
// Hide the notification menu in mysettings
|
||||
$('#options-emailNotifications').parent().hide();
|
||||
}
|
||||
|
||||
// If settings set popup panel form to true, show it
|
||||
if (clientVars.panelDisplayLocation.popup == true) {
|
||||
// after 10 seconds if we dont already have an email for this author then prompt them
|
||||
setTimeout(function(){initPopupForm()},10000);
|
||||
}
|
||||
}
|
||||
|
||||
function init(){
|
||||
var popUpIsAlreadyVisible = $('.ep_email_form').is(":visible");
|
||||
if(!popUpIsAlreadyVisible){ // if the popup isn't already visible
|
||||
if(clientHasAlreadyRegistered()){ // if the client has already registered for emails on this pad.
|
||||
// showAlreadyRegistered(); // client has already registered, let em know..
|
||||
}else{
|
||||
var cookieVal = pad.getPadId() + "email";
|
||||
if(cookie.getPref(cookieVal) !== "true"){ // if this user hasn't already subscribed
|
||||
askClientToEnterEmail(); // ask the client to register TODO uncomment me for a pop up
|
||||
}
|
||||
exports.handleClientMessage_emailSubscriptionSuccess = function(hook, context){ // was subscribing to the email a big win or fail?
|
||||
if(context.payload.success == false) {
|
||||
showAlreadyRegistered(context.payload.type);
|
||||
$('#' + context.payload.formName + ' [name=ep_email]').select();
|
||||
} else {
|
||||
showRegistrationSuccess();
|
||||
|
||||
// Add cookie to say an email is registered for this pad
|
||||
cookie.setPref(pad.getPadId() + "email", "true");
|
||||
|
||||
if (clientVars.panelDisplayLocation.mysettings == true && $('.ep_email_settings').is(":visible")) {
|
||||
$('.ep_email_settings').slideToggle();
|
||||
$('#options-emailNotifications').prop('checked', false);
|
||||
}
|
||||
|
||||
if (clientVars.panelDisplayLocation.popup == true && $('#ep_email_form_popup').is(":visible")) {
|
||||
$('#ep_email_form_popup').parent().parent().parent().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showRegistrationSuccess(){ // show a successful registration message
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: "Email subscribed",
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: "You will receive email when someone changes this pad. If this is the first time you have requested emails you may need to confirm your email address"
|
||||
});
|
||||
exports.handleClientMessage_emailUnsubscriptionSuccess = function(hook, context){ // was subscribing to the email a big win or fail?
|
||||
if(context.payload.success == false) {
|
||||
showWasNotRegistered();
|
||||
$('#' + context.payload.formName + ' [name=ep_email]').select();
|
||||
} else {
|
||||
showUnregistrationSuccess();
|
||||
|
||||
// Set cookie to say no email is registered for this pad
|
||||
cookie.setPref(pad.getPadId() + "email", "false");
|
||||
|
||||
if (clientVars.panelDisplayLocation.mysettings == true && $('.ep_email_settings').is(":visible")) {
|
||||
$('.ep_email_settings').slideToggle();
|
||||
$('#options-emailNotifications').prop('checked', false);
|
||||
}
|
||||
|
||||
if (clientVars.panelDisplayLocation.popup == true && $('#ep_email_form_popup').is(":visible")) {
|
||||
$('#ep_email_form_popup').parent().parent().parent().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showAlreadyRegistered(){ // the client already registered for emails on this pad so notify the UI
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: "Email subscription",
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: "You are already registered for emails for this pad",
|
||||
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||
sticky: false
|
||||
});
|
||||
exports.handleClientMessage_emailNotificationGetUserInfo = function (hook, context) { // return the existing options for this userId
|
||||
var result = context.payload;
|
||||
if(result.success == true){ // If data found, set the options with them
|
||||
$('[name=ep_email]').val(result.email);
|
||||
$('[name=ep_email_onStart]').prop('checked', result.onStart);
|
||||
$('[name=ep_email_onEnd]').prop('checked', result.onEnd);
|
||||
} else { // No data found, set the options to default values
|
||||
$('[name=ep_email_onStart]').prop('checked', true);
|
||||
$('[name=ep_email_onEnd]').prop('checked', false);
|
||||
}
|
||||
|
||||
if (result.formName == 'ep_email_form_mysettings') {
|
||||
$('.ep_email_settings').slideToggle();
|
||||
}
|
||||
}
|
||||
|
||||
exports.handleClientMessage_emailNotificationMissingParams = function (hook, context) { // Settings are missing in settings.json file
|
||||
if (context.payload == true) {
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterError'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: window._('ep_email_notifications.msgParamsMissing'),
|
||||
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||
sticky: true
|
||||
});
|
||||
|
||||
// Hide the notification menu in mysettings
|
||||
if (clientVars.panelDisplayLocation.mysettings == true && $('.ep_email_settings').is(":visible")) {
|
||||
$('.ep_email_settings').slideToggle();
|
||||
$('#options-emailNotifications').prop('checked', false);
|
||||
$('#options-emailNotifications').parent().hide();
|
||||
}
|
||||
|
||||
// Hide the popup if it is visible
|
||||
if (clientVars.panelDisplayLocation.popup == true && $('#ep_email_form_popup').is(":visible")) {
|
||||
$('#ep_email_form_popup').parent().parent().parent().hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the popup panel form for subscription
|
||||
*/
|
||||
function initPopupForm(){
|
||||
var popUpIsAlreadyVisible = $('#ep_email_form_popup').is(":visible");
|
||||
if(!popUpIsAlreadyVisible){ // if the popup isn't already visible
|
||||
var cookieVal = pad.getPadId() + "email";
|
||||
if(cookie.getPref(cookieVal) !== "true"){ // if this user hasn't already subscribed
|
||||
askClientToEnterEmail(); // ask the client to register TODO uncomment me for a pop up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clientHasAlreadyRegistered(){ // Has the client already registered for emails on this?
|
||||
|
@ -72,41 +166,169 @@ function clientHasAlreadyRegistered(){ // Has the client already registered for
|
|||
}
|
||||
|
||||
function askClientToEnterEmail(){
|
||||
var formContent = $('.ep_email_settings')
|
||||
.html()
|
||||
.replace('ep_email_form_mysettings', 'ep_email_form_popup');
|
||||
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: "Enter your email to receive an email when someone modifies this pad",
|
||||
title: window._('ep_email_notifications.titleGritterSubscr'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: "<form class='ep_email_form'><label for='ep_email'><input id='ep_email_notification' placeholder='your@email.com' style='padding:5px;width:180px;' type=email><input type=submit value=subscribe style='padding:5px;'></form>",
|
||||
text: "<p class='ep_email_form_popup_header'>" + window._('ep_email_notifications.headerGritterSubscr') + "</p>" + formContent,
|
||||
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||
sticky: true,
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: '2000',
|
||||
time: 2000,
|
||||
// the function to bind to the form
|
||||
after_open: function(e){
|
||||
$('.ep_email_form').submit(function(){
|
||||
$(e).hide();
|
||||
sendEmailToServer();
|
||||
$('#ep_email_form_popup').submit(function(){
|
||||
sendEmailToServer('ep_email_form_popup');
|
||||
return false;
|
||||
});
|
||||
|
||||
// Prepare subscription before submit form
|
||||
$('#ep_email_form_popup [name=ep_email_subscribe]').on('click', function(e) {
|
||||
$('#ep_email_form_popup [name=ep_email_option]').val('subscribe');
|
||||
checkAndSend(e);
|
||||
});
|
||||
|
||||
// Prepare unsubscription before submit form
|
||||
$('#ep_email_form_popup [name=ep_email_unsubscribe]').on('click', function(e) {
|
||||
$('#ep_email_form_popup [name=ep_email_option]').val('unsubscribe');
|
||||
checkAndSend(e);
|
||||
});
|
||||
|
||||
getDataForUserId('ep_email_form_popup');
|
||||
optionsAlreadyRecovered = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendEmailToServer(){
|
||||
var email = $('#ep_email').val();
|
||||
if(!email){ // if we're not using the top value use the notification value
|
||||
email = $('#ep_email_notification').val();
|
||||
/**
|
||||
* Control options before submitting the form
|
||||
*/
|
||||
function checkAndSend(e) {
|
||||
var formName = $(e.currentTarget.parentNode).attr('id');
|
||||
|
||||
var email = $('#' + formName + ' [name=ep_email]').val();
|
||||
|
||||
if (email && $('#' + formName + ' [name=ep_email_option]').val() == 'subscribe'
|
||||
&& !$('#' + formName + ' [name=ep_email_onStart]').is(':checked')
|
||||
&& !$('#' + formName + ' [name=ep_email_onEnd]').is(':checked')) {
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterError'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: window._('ep_email_notifications.msgOptionsNotChecked')
|
||||
});
|
||||
} else if (email) {
|
||||
$('#' + formName).submit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the server to register the email
|
||||
*/
|
||||
function sendEmailToServer(formName){
|
||||
var email = $('#' + formName + ' [name=ep_email]').val();
|
||||
var userId = pad.getUserId();
|
||||
var message = {};
|
||||
message.type = 'USERINFO_UPDATE';
|
||||
message.userInfo = {};
|
||||
message.padId = pad.getPadId();
|
||||
message.userInfo.email = email;
|
||||
message.userInfo.email_option = $('#' + formName + ' [name=ep_email_option]').val();
|
||||
message.userInfo.email_onStart = $('#' + formName + ' [name=ep_email_onStart]').is(':checked');
|
||||
message.userInfo.email_onEnd = $('#' + formName + ' [name=ep_email_onEnd]').is(':checked');
|
||||
message.userInfo.formName = formName;
|
||||
message.userInfo.userId = userId;
|
||||
if(email){
|
||||
pad.collabClient.sendMessage(message);
|
||||
cookie.setPref(message.padId+"email", "true");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thanks to the userId, we can get back from the Db the options set for this user
|
||||
* and fill the fields with them
|
||||
*/
|
||||
function getDataForUserId(formName) {
|
||||
var userId = pad.getUserId();
|
||||
var message = {};
|
||||
message.type = 'USERINFO_GET';
|
||||
message.padId = pad.getPadId();
|
||||
message.userInfo = {};
|
||||
message.userInfo.userId = userId;
|
||||
message.userInfo.formName = formName;
|
||||
|
||||
pad.collabClient.sendMessage(message);
|
||||
}
|
||||
|
||||
/*************************************
|
||||
Manage return msgs from server
|
||||
*************************************/
|
||||
|
||||
/**
|
||||
* Show a successful registration message
|
||||
*/
|
||||
function showRegistrationSuccess(){
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterSubscr'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: window._('ep_email_notifications.msgSubscrSuccess'),
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: 10000
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The client already registered for emails on this pad so notify the UI
|
||||
*/
|
||||
function showAlreadyRegistered(type){
|
||||
if (type == "malformedEmail") {
|
||||
var msg = window._('ep_email_notifications.msgEmailMalformed');
|
||||
} else if (type == "alreadyRegistered") {
|
||||
var msg = window._('ep_email_notifications.msgAlreadySubscr');
|
||||
} else {
|
||||
var msg = window._('ep_email_notifications.msgUnknownErr');
|
||||
}
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterSubscr'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: msg,
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: 7000
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a successful unregistration message
|
||||
*/
|
||||
function showUnregistrationSuccess(){
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterUnsubscr'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: window._('ep_email_notifications.msgUnsubscrSuccess'),
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: 10000
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The client wasn't registered for emails
|
||||
*/
|
||||
function showWasNotRegistered(){
|
||||
$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: window._('ep_email_notifications.titleGritterUnsubscr'),
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: window._('ep_email_notifications.msgUnsubscrNotExisting'),
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: 7000
|
||||
});
|
||||
|
||||
}
|
||||
|
|
21
templates/email_notifications_settings.ejs
Normal file
21
templates/email_notifications_settings.ejs
Normal file
|
@ -0,0 +1,21 @@
|
|||
<p>
|
||||
<input type="checkbox" id="options-emailNotifications"></input>
|
||||
<label for="options-emailNotifications" data-l10n-id="ep_email_notifications.menuLabel"></label>
|
||||
<div class="ep_email_settings">
|
||||
<form id='ep_email_form_mysettings'>
|
||||
<input name='ep_email' class='ep_email_input' placeholder='your@email.com' type=email>
|
||||
<br />
|
||||
<label data-l10n-id="ep_email_notifications.formOptionsTitle"></label>
|
||||
<br />
|
||||
<input name='ep_email_onStart' type="checkbox" class="ep_email_checkbox"></input>
|
||||
<label for="ep_email_onStart" data-l10n-id="ep_email_notifications.formOptionOnStart"></label>
|
||||
<br />
|
||||
<input name='ep_email_onEnd' type="checkbox" class="ep_email_checkbox"></input>
|
||||
<label for="ep_email_onEnd" data-l10n-id="ep_email_notifications.formOptionOnEnd"></label>
|
||||
<input name='ep_email_option' type=hidden >
|
||||
<br />
|
||||
<button name='ep_email_subscribe' type=button class="ep_email_buttons" data-l10n-id="ep_email_notifications.formBtnSubscr"></button>
|
||||
<button name='ep_email_unsubscribe'type=button class="ep_email_buttons" data-l10n-id="ep_email_notifications.formBtnUnsubscr"></button>
|
||||
</form>
|
||||
</div>
|
||||
</p>
|
|
@ -1,3 +0,0 @@
|
|||
<br>
|
||||
<h2>Recieve email notifications on change</h2>
|
||||
<form class='ep_email_form'><label for='ep_email'><input id='ep_email' placeholder='your@email.com' style="padding:5px;margin-top:10px;width:300px;" type=email><input style="padding:5px;" type=submit value=subscribe></form>
|
22
templates/response.ejs
Normal file
22
templates/response.ejs
Normal file
|
@ -0,0 +1,22 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Email Notifications Subscription</title>
|
||||
<link href="../../static/plugins/ep_email_notifications/static/css/email_notifications.css" media="all" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="validationTitle">
|
||||
<h1>Email notifications</h1>
|
||||
</div>
|
||||
<div class="validationEmailSubscription">
|
||||
<%action%>
|
||||
<div class="<%classResult%>">
|
||||
<%result%>
|
||||
<div>
|
||||
<%explanation%>
|
||||
</div>
|
||||
</div>
|
||||
Go to the pad: <a href="<%padUrl%>"><%padUrl%></a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
84
update.js
84
update.js
|
@ -8,12 +8,13 @@ settings = require('../../src/node/utils/Settings');
|
|||
|
||||
// Settings -- EDIT THESE IN settings.json not here..
|
||||
var pluginSettings = settings.ep_email_notifications;
|
||||
var checkFrequency = pluginSettings.checkFrequency || 60000; // 10 seconds
|
||||
var staleTime = pluginSettings.staleTime || 300000; // 5 minutes
|
||||
var fromName = pluginSettings.fromName || "Etherpad";
|
||||
var fromEmail = pluginSettings.fromEmail || "pad@etherpad.org";
|
||||
var urlToPads = pluginSettings.urlToPads || "http://beta.etherpad.org/p/";
|
||||
var emailServer = pluginSettings.emailServer || {host:"127.0.0.1"};
|
||||
var areParamsOk = (pluginSettings)?true:false;
|
||||
var checkFrequency = (pluginSettings && pluginSettings.checkFrequency)?pluginSettings.checkFrequency:60000; // 10 seconds
|
||||
var staleTime = (pluginSettings && pluginSettings.staleTime)?pluginSettings.staleTime:300000; // 5 minutes
|
||||
var fromName = (pluginSettings && pluginSettings.fromName)?pluginSettings.fromName:"Etherpad";
|
||||
var fromEmail = (pluginSettings && pluginSettings.fromEmail)?pluginSettings.fromEmail:"pad@etherpad.org";
|
||||
var urlToPads = (pluginSettings && pluginSettings.urlToPads)?pluginSettings.urlToPads:"http://beta.etherpad.org/p/";
|
||||
var emailServer = (pluginSettings && pluginSettings.emailServer)?pluginSettings.emailServer:{host:"127.0.0.1"};
|
||||
|
||||
// A timer object we maintain to control how we send emails
|
||||
var timers = {};
|
||||
|
@ -22,6 +23,8 @@ var timers = {};
|
|||
var server = email.server.connect(emailServer);
|
||||
|
||||
exports.padUpdate = function (hook_name, _pad) {
|
||||
if (areParamsOk == false) return false;
|
||||
|
||||
var pad = _pad.pad;
|
||||
var padId = pad.id;
|
||||
exports.sendUpdates(padId);
|
||||
|
@ -43,21 +46,25 @@ exports.notifyBegin = function(padId){
|
|||
db.get("emailSubscription:" + padId, function(err, recipients){ // get everyone we need to email
|
||||
if(recipients){
|
||||
async.forEach(Object.keys(recipients), function(recipient, cb){
|
||||
// Is this recipient already on the pad?
|
||||
exports.isUserEditingPad(padId, recipients[recipient].authorId, function(err,userIsOnPad){ // is the user already on the pad?
|
||||
if(!userIsOnPad){
|
||||
console.debug("Emailing "+recipient +" about a new begin update");
|
||||
server.send({
|
||||
text: "Your pad at "+urlToPads+padId +" is being edited, we're just emailing you let you know :)\n\n -- This plugin is in alpha state, can you help fund it's development? https://github.com/johnmclear/ep_email_notifications",
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: recipient,
|
||||
subject: "Someone started editing "+padId
|
||||
}, function(err, message) { console.log(err || message); });
|
||||
}
|
||||
else{
|
||||
console.debug("Didn't send an email because user is already on the pad");
|
||||
}
|
||||
});
|
||||
//avoid the 'pending' section
|
||||
if (recipient != 'pending') {
|
||||
// Is this recipient already on the pad?
|
||||
exports.isUserEditingPad(padId, recipients[recipient].authorId, function(err,userIsOnPad){ // is the user already on the pad?
|
||||
var onStart = typeof(recipients[recipient].onStart) == "undefined" || recipients[recipient].onStart?true:false; // In case onStart wasn't defined we set it to true
|
||||
if(!userIsOnPad && onStart){
|
||||
console.debug("Emailing "+recipient +" about a new begin update");
|
||||
server.send({
|
||||
text: "Your pad at "+urlToPads+padId +" is being edited, we're just emailing you let you know :)",
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: recipient,
|
||||
subject: "Someone started editing "+padId
|
||||
}, function(err, message) { console.log(err || message); });
|
||||
}
|
||||
else{
|
||||
console.debug("Didn't send an email because user is already on the pad");
|
||||
}
|
||||
});
|
||||
}
|
||||
cb(); // finish each user
|
||||
},
|
||||
function(err){
|
||||
|
@ -74,21 +81,26 @@ exports.notifyEnd = function(padId){
|
|||
db.get("emailSubscription:" + padId, function(err, recipients){ // get everyone we need to email
|
||||
if(recipients){
|
||||
async.forEach(Object.keys(recipients), function(recipient, cb){
|
||||
// Is this recipient already on the pad?
|
||||
exports.isUserEditingPad(padId, recipients[recipient].authorId, function(err,userIsOnPad){ // is the user already on the$
|
||||
if(!userIsOnPad){
|
||||
console.debug("Emailing "+recipient +" about a pad finished being updated");
|
||||
server.send({
|
||||
text: "Your pad at "+urlToPads+padId +" has finished being edited, we're just emailing you let you know :) \n\n The changes look like this: \n" + changesToPad,
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: recipient,
|
||||
subject: "Someone finished editing "+padId
|
||||
}, function(err, message) { console.log(err || message); });
|
||||
}
|
||||
else{
|
||||
console.debug("Didn't send an email because user is already on the pad");
|
||||
}
|
||||
});
|
||||
//avoid the 'pending' section
|
||||
if (recipient != 'pending') {
|
||||
// Is this recipient already on the pad?
|
||||
exports.isUserEditingPad(padId, recipients[recipient].authorId, function(err,userIsOnPad){ // is the user already on the$
|
||||
var onEnd = typeof(recipients[recipient].onEnd) == "undefined" || recipients[recipient].onEnd?true:false; // In case onEnd wasn't defined we set it to false
|
||||
|
||||
if(!userIsOnPad && onEnd){
|
||||
console.debug("Emailing "+recipient +" about a pad finished being updated");
|
||||
server.send({
|
||||
text: "Your pad at "+urlToPads+padId +" has finished being edited, we're just emailing you let you know :) \n\n The changes look like this: \n" + changesToPad,
|
||||
from: fromName+ "<"+fromEmail+">",
|
||||
to: recipient,
|
||||
subject: "Someone finished editing "+padId
|
||||
}, function(err, message) { console.log(err || message); });
|
||||
}
|
||||
else{
|
||||
console.debug("Didn't send an email because user is already on the pad");
|
||||
}
|
||||
});
|
||||
}
|
||||
cb(); // finish each user
|
||||
},
|
||||
function(err){
|
||||
|
|
Loading…
Reference in a new issue