diff --git a/img/icon_email.png b/img/icon_email.png
new file mode 100644
index 00000000..a2f4e2f8
Binary files /dev/null and b/img/icon_email.png differ
diff --git a/js/privatebin.js b/js/privatebin.js
index de791ad2..2613862e 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -411,6 +411,45 @@ jQuery.PrivateBin = (function($, RawDeflate) {
baseUri = null;
};
+ /**
+ * calculate expiration date
+ *
+ * @name Helper.calculateExpirationDate
+ * @function
+ * @param {Date} initialDate - may not be empty
+ * @param {string|number} expirationDisplayStringOrSecondsToExpire - may not be empty
+ * @return {Date}
+ */
+ me.calculateExpirationDate = function(initialDate, expirationDisplayStringOrSecondsToExpire) {
+ let expirationDate = new Date(initialDate);
+
+ const expirationDisplayStringToSecondsDict = {
+ '5min': 300,
+ '10min': 600,
+ '1hour': 3500,
+ '1day': 86400,
+ '1week': 604800,
+ '1month': 2592000,
+ '1year': 31536000,
+ 'never': 0
+ };
+
+ let secondsToExpiration = expirationDisplayStringOrSecondsToExpire;
+ if (typeof expirationDisplayStringOrSecondsToExpire === 'string') {
+ secondsToExpiration = expirationDisplayStringToSecondsDict[expirationDisplayStringOrSecondsToExpire];
+ }
+
+ if (typeof secondsToExpiration !== 'number') {
+ throw new Error('Cannot calculate expiration date.');
+ }
+ if (secondsToExpiration === 0) {
+ return null;
+ }
+
+ expirationDate = expirationDate.setUTCSeconds(expirationDate.getUTCSeconds() + secondsToExpiration);
+ return expirationDate;
+ }
+
return me;
})();
@@ -3292,9 +3331,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$passwordInput,
$rawTextButton,
$qrCodeLink,
+ $emailLink,
$sendButton,
$retryButton,
- pasteExpiration = '1week',
+ pasteExpiration = null,
retryButtonCallback;
/**
@@ -3502,6 +3542,64 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$('#qrcode-display').html(qrCanvas);
}
+ /**
+ * Send Email with current paste (URL).
+ *
+ * @name TopNav.sendEmail
+ * @private
+ * @function
+ * @param {Date|null} expirationDate date of expiration
+ * @param {bool} isBurnafterreading whether it is burn after reading
+ */
+ function sendEmail(expirationDate, isBurnafterreading)
+ {
+ const EOL = '\n';
+ const BULLET = ' - ';
+ let emailBody = '';
+ const expirationDateRoundedToSecond = new Date(expirationDate);
+
+ // round down at least 30 seconds to make up for the delay of request
+ expirationDateRoundedToSecond.setUTCSeconds(
+ expirationDateRoundedToSecond.getUTCSeconds() - 30
+ );
+ expirationDateRoundedToSecond.setUTCSeconds(0);
+
+ const expirationDateString = window.confirm(
+ I18n._('Recipient may become aware of your timezone, convert time to GMT?')
+ ) ? expirationDateRoundedToSecond.toUTCString() : expirationDateRoundedToSecond.toLocaleString();
+ if (expirationDate !== null || isBurnafterreading) {
+ emailBody += I18n._('Notice:');
+ emailBody += EOL;
+
+ if (expirationDate !== null) {
+ emailBody += EOL;
+ emailBody += BULLET;
+ emailBody += I18n._(
+ 'This link will expire after %s.',
+ expirationDateString
+ );
+ }
+ if (isBurnafterreading) {
+ emailBody += EOL;
+ emailBody += BULLET;
+ emailBody += I18n._(
+ 'This link can only be accessed once, do not use back or refresh button in your browser.'
+ );
+ }
+
+ emailBody += EOL;
+ emailBody += EOL;
+ }
+ emailBody += I18n._('Link:');
+ emailBody += EOL;
+ emailBody += `${window.location.href}`;
+ window.open(
+ `mailto:?body=${encodeURIComponent(emailBody)}`,
+ '_self',
+ 'noopener, noreferrer'
+ );
+ }
+
/**
* Shows all navigation elements for viewing an existing paste
*
@@ -3538,6 +3636,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$newButton.addClass('hidden');
$rawTextButton.addClass('hidden');
$qrCodeLink.addClass('hidden');
+ me.hideEmailbutton();
viewButtonsDisplayed = false;
};
@@ -3635,6 +3734,47 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$retryButton.addClass('hidden');
}
+ /**
+ * show the "email" button
+ *
+ * @name TopNav.showEmailbutton
+ * @function
+ * @param {function} optionalRemainingTimeInSeconds
+ */
+ me.showEmailButton = function(optionalRemainingTimeInSeconds)
+ {
+ try {
+ // we cache expiration date in closure to avoid inaccurate expiration datetime
+ const expirationDate = Helper.calculateExpirationDate(new Date(), optionalRemainingTimeInSeconds || TopNav.getExpiration());
+ const isBurnafterreading = TopNav.getBurnAfterReading();
+
+ $emailLink.removeClass('hidden');
+ $emailLink.off('click.sendEmail');
+ $emailLink.on('click.sendEmail', function(expirationDate, isBurnafterreading) {
+ return function() {
+ sendEmail(expirationDate, isBurnafterreading);
+ };
+ } (expirationDate, isBurnafterreading));
+ } catch (error) {
+ console.error(error);
+ Alert.showError(
+ I18n._('Cannot calculate expiration date.')
+ );
+ }
+ }
+
+ /**
+ * hide the "email" button
+ *
+ * @name TopNav.hideEmailbutton
+ * @function
+ */
+ me.hideEmailbutton = function()
+ {
+ $emailLink.addClass('hidden');
+ $emailLink.off('click.sendEmail');
+ }
+
/**
* only hides the clone button
*
@@ -3657,6 +3797,30 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$rawTextButton.addClass('hidden');
};
+ /**
+ * only hides the qr code button
+ *
+ * @name TopNav.hideQrCodeButton
+ * @function
+ */
+ me.hideQrCodeButton = function()
+ {
+ $qrCodeLink.addClass('hidden');
+ }
+
+ /**
+ * hide all irrelevant buttons when viewing burn after reading paste
+ *
+ * @name TopNav.hideBurnAfterReadingButtons
+ * @function
+ */
+ me.hideBurnAfterReadingButtons = function()
+ {
+ me.hideCloneButton();
+ me.hideQrCodeButton();
+ me.hideEmailbutton();
+ }
+
/**
* hides the file selector in attachment
*
@@ -3744,7 +3908,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/**
* returns the state of the burn after reading checkbox
*
- * @name TopNav.getExpiration
+ * @name TopNav.getBurnAfterReading
* @function
* @return {bool}
*/
@@ -3873,6 +4037,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$retryButton = $('#retrybutton');
$sendButton = $('#sendbutton');
$qrCodeLink = $('#qrcodelink');
+ $emailLink = $('#emaillink');
// bootstrap template drop down
$('#language ul.dropdown-menu li a').click(setLanguage);
@@ -4219,6 +4384,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
history.pushState({type: 'newpaste'}, document.title, url);
TopNav.showViewButtons();
+
+ // this cannot be grouped with showViewButtons due to remaining time calculation
+ TopNav.showEmailButton();
+
TopNav.hideRawButton();
Editor.hide();
@@ -4684,7 +4853,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// discourage cloning (it cannot really be prevented)
if (paste.isBurnAfterReadingEnabled()) {
- TopNav.hideCloneButton();
+ TopNav.hideBurnAfterReadingButtons();
+ } else {
+ // we have to pass in remaining_time here
+ TopNav.showEmailButton(paste.getTimeToLive());
}
})
.catch((err) => {
diff --git a/js/test/TopNav.js b/js/test/TopNav.js
index e83d5059..e5a29c41 100644
--- a/js/test/TopNav.js
+++ b/js/test/TopNav.js
@@ -326,7 +326,7 @@ describe('TopNav', function () {
'returns the currently selected expiration date',
function () {
$.PrivateBin.TopNav.init();
- assert.ok($.PrivateBin.TopNav.getExpiration() === '1week');
+ assert.ok($.PrivateBin.TopNav.getExpiration() === null);
}
);
});
diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php
index 2d07fe22..0c24ef9f 100644
--- a/tpl/bootstrap.php
+++ b/tpl/bootstrap.php
@@ -71,7 +71,7 @@ if ($MARKDOWN):
endif;
?>
-
+
@@ -173,6 +173,9 @@ endif;
+
@@ -561,4 +564,4 @@ if ($FILEUPLOAD):
endif;
?>