From 53c1df824838d164468b6a95113cbc3e1db57dfe Mon Sep 17 00:00:00 2001 From: etherpad Date: Tue, 26 Mar 2013 20:44:34 +0100 Subject: [PATCH] Unsubscription & choose to receive a mail onStart / onEnd - The subscription panel is placed in the "mysettings" menu (not anymmore a popup) - A Unsubscribe button were added. It removes the email if found - The 2 options to receive a mail when someone is starting to edit and finished to edit can be chosen for each user (at least one of the two is compulsory) - First time you go on the email notification panel in the mysettings menu, a call is made to get the email and options already subscribed for this userId * If found, the form is pre-filled with the datas * If not, the form is set with defaults values It's just informative. Users can still subscribe another mail for this userId. - Removed the integration of this plugin in the "Share this pad" menu. First becaue, imo, it's not the place, and second, because it meant 2 forms with the same id. --- client.js | 14 ++ ep.json | 7 +- handleMessage.js | 198 ++++++++++++++----- static/css/email_notifications.css | 10 + static/js/ep_email.js | 210 +++++++++++++++------ templates/email_notifications_settings.ejs | 20 ++ update.js | 9 +- 7 files changed, 359 insertions(+), 109 deletions(-) create mode 100644 static/css/email_notifications.css create mode 100644 templates/email_notifications_settings.ejs diff --git a/client.js b/client.js index 4f465ba..86da61c 100644 --- a/client.js +++ b/client.js @@ -1,11 +1,25 @@ var eejs = require("ep_etherpad-lite/node/eejs"); +var settings = require('ep_etherpad-lite/node/utils/Settings'); +var checked_state = ''; 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); return cb(); }; +*/ + +exports.eejsBlock_mySettings = function (hook_name, args, cb) { + args.content = args.content + eejs.require('ep_email_notifications/templates/email_notifications_settings.ejs', {checked : checked_state}); + return cb(); +}; + +exports.eejsBlock_styles = function (hook_name, args, cb) { + args.content = args.content + ''; +}; + diff --git a/ep.json b/ep.json index b9471cf..9202804 100644 --- a/ep.json +++ b/ep.json @@ -6,11 +6,14 @@ "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" }, "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" } } ] diff --git a/handleMessage.js b/handleMessage.js index e48d044..4aa2d97 100644 --- a/handleMessage.js +++ b/handleMessage.js @@ -13,24 +13,135 @@ exports.handleMessage = function(hook_name, context, callback){ if (context.message.data.userInfo){ if(context.message.data.userInfo.email){ // it contains email console.debug(context.message); + console.info("ep_mail: " + context.message.data.userInfo.email + " / " + context.message.data.userInfo.email_option); + console.info(context.message.data.userInfo); // does email Subscription already exist for this email address? db.get("emailSubscription:"+context.message.data.padId, function(err, userIds){ var alreadyExists = false; + console.info(userIds); 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 - + console.info("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 // 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"); + } + cb(); + }, + function(err){ + // There should be something in here! + }); // end async for each + } + + if(context.message.data.userInfo.email_option == 'subscribe' && alreadyExists == true){ + // SUbscription + console.info("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: false + } + }); + } else if(context.message.data.userInfo.email_option == 'subscribe' && alreadyExists == false){ + // SUbscription + 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 + } + }); + } else { + console.info ("Subscription: 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, + callback + ); + + exports.setAuthorEmailRegistered( + context.message.data.userInfo, + context.message.data.userInfo.userId, + context.message.data.padId + ); + + context.client.json.send({ type: "COLLABROOM", + data:{ + type: "emailSubscriptionSuccess", + payload: true + } + }); + } + } else if(context.message.data.userInfo.email_option == 'unsubscribe' && alreadyExists == true) { + // Unsubscription + console.info ("Unsubscription: Remove from the database and sent client a positive response ",context.message.data.userInfo.email); + + exports.unsetAuthorEmail( + context.message.data.userInfo.userId, + context.message.data.userInfo, + callback + ); + + exports.unsetAuthorEmailRegistered( + context.message.data.userInfo, + context.message.data.userInfo.userId, + context.message.data.padId + ); + + context.client.json.send({ type: "COLLABROOM", + data:{ + type: "emailUnsubscriptionSuccess", + payload: true + } + }); + } else if(context.message.data.userInfo.email_option == 'unsubscribe' && alreadyExists == false) { + // Unsubscription + console.info ("Unsubscription: Send client a negative response ",context.message.data.userInfo.email); + + context.client.json.send({ type: "COLLABROOM", + data:{ + type: "emailUnsubscriptionSuccess", + payload: false + } + }); + } + }); // 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 username + if (context.message.data.userInfo){ + if(context.message.data.userInfo.userId){ // it contains the userId + console.debug(context.message); + + var userIdFound = false; + // does email Subscription already exist for this name and padID? + db.get("emailSubscription:"+context.message.data.padId, function(err, userIds){ + 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.info("Options for this pad ", userIds[user].authorId, " found in the Db"); + userIdFound = true; + + // We send back the options set for this user context.client.json.send({ type: "COLLABROOM", data:{ - type: "emailSubscriptionSuccess", - payload: false + type: "emailNotificationGetUserInfo", + payload: { + email: user, + onStart: userIds[user].onStart && typeof userIds[user].onStart === 'boolean'?userIds[user].onStart:true, + onEnd: userIds[user].onEnd && typeof userIds[user].onEnd === 'boolean'?userIds[user].onEnd:false, + success:true + } } }); } @@ -41,41 +152,19 @@ 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 - } - }); - } - 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 + }); + if (!userIdFound) { + // We send back the options set for this user + context.client.json.send({ type: "COLLABROOM", + data:{ + type: "emailNotificationGetUserInfo", + payload: { + success:false + } + } + }); + } } } } @@ -84,25 +173,46 @@ exports.handleMessage = function(hook_name, context, callback){ } // Updates the database with the email record -exports.setAuthorEmail = function (author, email, callback){ - db.setSub("globalAuthor:" + author, ["email"], email, callback); +exports.setAuthorEmail = function (author, datas, callback){ + db.setSub("globalAuthor:" + author, ["email"], datas.email, callback); } // Write email and padId to the database -exports.setAuthorEmailRegistered = function(email, authorId, padId){ +exports.setAuthorEmailRegistered = function(datas, authorId, padId){ var timestamp = new Date().getTime(); var registered = { authorId: authorId, + onStart: datas.email_onStart, + onEnd: datas.email_onEnd, 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 + value[datas.email] = registered; // add the registered values to the object console.warn("written to database"); db.set("emailSubscription:" + padId, value); // stick it in the database }); } +// Updates the database by removing the email record for that AuthorId +exports.unsetAuthorEmail = function (author, datas, callback){ + db.get("globalAuthor:" + author, function(err, value){ // get the current value + delete value['email']; + db.set("globalAuthor:" + author, value); + }); +} + +// Remove email and padId from the database +exports.unsetAuthorEmailRegistered = function(datas, authorId, padId){ + console.debug("unregistered", datas.email, " 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 + delete value[datas.email]; // remove the registered values to the object + console.warn("written to database"); + db.set("emailSubscription:" + padId, value); // stick it in the database + }); + +} diff --git a/static/css/email_notifications.css b/static/css/email_notifications.css new file mode 100644 index 0000000..f8e3264 --- /dev/null +++ b/static/css/email_notifications.css @@ -0,0 +1,10 @@ +.ep_email_settings { + display: none; + padding-left: 1.5em; + padding: .2em; +} + +.ep_email_input { + padding:.2em; + width:177px; +} diff --git a/static/js/ep_email.js b/static/js/ep_email.js index 9f70476..709b3a4 100644 --- a/static/js/ep_email.js +++ b/static/js/ep_email.js @@ -1,19 +1,44 @@ var cookie = require('ep_etherpad-lite/static/js/pad_cookie').padcookie; +var firstRun = true; 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); + // Uncheck the checkbox + $('#options-emailNotifications').attr('checked', false); + setDefaultOptions(); + + /* on click */ + $('#options-emailNotifications').on('click', function() { + if (firstRun) { + getDatasForUserId(); + firstRun = false; + } else { + $('.ep_email_settings').slideToggle(); + } + }); + + // Prepare subscription before submit form + $('#ep_email_subscribe').on('click', function() { + $('#ep_email_option').val('subscribe'); + checkAndSend(); + }); + + // Prepare unsubscription before submit form + $('#ep_email_unsubscribe').on('click', function() { + $('#ep_email_option').val('unsubscribe'); + checkAndSend(); + }); // subscribe by email can be active.. $('.ep_email_form').submit(function(){ sendEmailToServer(); + $('.ep_email_settings').slideToggle(); + $('#options-emailNotifications').attr('checked', false); return false; }); - } exports.handleClientMessage_emailSubscriptionSuccess = function(hook, context){ // was subscribing to the email a big win or fail? @@ -24,21 +49,108 @@ exports.handleClientMessage_emailSubscriptionSuccess = function(hook, context){ } } -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_emailUnsubscriptionSuccess = function(hook, context){ // was subscribing to the email a big win or fail? + if(context.payload == false){ + showWasNotRegistered(); + }else{ + showUnregistrationSuccess(); } } -function showRegistrationSuccess(){ // show a successful registration message +exports.handleClientMessage_emailNotificationGetUserInfo = function (hook, context) { // return the existing options for this userId + var datas = context.payload; + if(datas.success == true){ // If datas were found, set the options with them + if (datas.email) $('#ep_email').val(datas.email); + if (datas.onStart && typeof datas.onStart === 'boolean') $('#ep_email_onStart').attr('checked', datas.onStart); + if (datas.onEnd && typeof datas.onEnd === 'boolean') $('#ep_email_onEnd').attr('checked', datas.onEnd); + } else { // No datas were found, set the options to default values + setDefaultOptions(); + } + + $('.ep_email_settings').slideToggle(); +} + +/** + * Set the options in the frame to a default value + */ +function setDefaultOptions() { + $('#ep_email_onStart').attr('checked', true); + $('#ep_email_onEnd').attr('checked', false); +} + +/** + * Control options before submitting the form + */ +function checkAndSend() { + var email = getEmail(); + if (email && $('#ep_email_option').val() == 'subscribe' && !$('#ep_email_onStart').is(':checked') && !$('#ep_email_onEnd').is(':checked')) { + $.gritter.add({ + // (string | mandatory) the heading of the notification + title: "Email subscription error", + // (string | mandatory) the text inside the notification + text: "You need to check at least one of the two options from 'Send a mail when someone..'" + }); + } else if (email) { + $('.ep_email_form').submit(); + } + return false; +} + +/** + * Return the email from the user + */ +function getEmail() { + var email = $('#ep_email').val(); + if(!email){ // if we're not using the top value use the notification value + email = $('#ep_email_notification').val(); + } + return email; +} + +/** + * Ask the server to register the email + */ +function sendEmailToServer(){ + var email = getEmail(); + var userId = pad.getUserId(); + var message = {}; + message.type = 'USERINFO_UPDATE'; + message.userInfo = {}; + message.padId = pad.getPadId(); + message.userInfo.email = email; + message.userInfo.email_option = $('#ep_email_option').val(); + message.userInfo.email_onStart = $('#ep_email_onStart').is(':checked'); + message.userInfo.email_onEnd = $('#ep_email_onEnd').is(':checked'); + 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 getDatasForUserId() { + var userId = pad.getUserId(); + var message = {}; + message.type = 'USERINFO_GET'; + message.padId = pad.getPadId(); + message.userInfo = {}; + message.userInfo.userId = userId; + + 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: "Email subscribed", @@ -47,7 +159,10 @@ function showRegistrationSuccess(){ // show a successful registration message }); } -function showAlreadyRegistered(){ // the client already registered for emails on this pad so notify the UI +/** + * The client already registered for emails on this pad so notify the UI + */ +function showAlreadyRegistered(){ $.gritter.add({ // (string | mandatory) the heading of the notification title: "Email subscription", @@ -59,54 +174,29 @@ function showAlreadyRegistered(){ // the client already registered for emails on } -function clientHasAlreadyRegistered(){ // Has the client already registered for emails on this? - // Given a specific AuthorID do we have an email address in the database? - // Given that email address is it registered to this pad? - // need to pass the server a message to check - var userId = pad.getUserId(); - var message = {}; - message.type = 'USERINFO_AUTHOR_EMAIL_IS_REGISTERED_TO_PAD'; - message.userInfo = {}; - message.userInfo.userId = userId; - pad.collabClient.sendMessage(message); -} - -function askClientToEnterEmail(){ +/** + * Show a successful unregistration message + */ +function showUnregistrationSuccess(){ $.gritter.add({ // (string | mandatory) the heading of the notification - title: "Enter your email to receive an email when someone modifies this pad", + title: "Email unsubscribed", // (string | mandatory) the text inside the notification - text: "