Merge branch 'Haocen-398'

This commit is contained in:
El RIDO 2019-11-02 17:04:19 +01:00
commit ffe26a8841
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
7 changed files with 318 additions and 9 deletions

View file

@ -1,6 +1,7 @@
# PrivateBin version history # PrivateBin version history
* **1.4 (not yet released)** * **1.4 (not yet released)**
* ADDED: Option to send a mail with the link, when creating a paste (#398)
* FIXED: Password disabling option (#527) * FIXED: Password disabling option (#527)
* **1.3.1 (2019-09-22)** * **1.3.1 (2019-09-22)**
* ADDED: Translation for Bulgarian (#455) * ADDED: Translation for Bulgarian (#455)

View file

@ -180,3 +180,28 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
padding-right: 4ch; padding-right: 4ch;
} }
} }
.modal-dialog {
margin: auto !important;
}
/* makeup for the original margin on modal-dialog */
@media (min-width: 768px) {
.modal-content {
margin: 30px 0;
}
}
.modal-content {
margin: 10px;
}
.modal-body {
display: flex;
justify-content: center;
align-items: center;
}
.modal .modal-content button {
margin: 0.5em 0;
}

BIN
img/icon_email.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

View file

@ -403,6 +403,45 @@ jQuery.PrivateBin = (function($, RawDeflate) {
baseUri = null; baseUri = null;
}; };
/**
* calculate expiration date given initial date and expiration period
*
* @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; return me;
})(); })();
@ -1822,7 +1861,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
`${$shortenButton.data('shortener')}${encodeURIComponent($pasteUrl.attr('href'))}`, `${$shortenButton.data('shortener')}${encodeURIComponent($pasteUrl.attr('href'))}`,
'_blank', '_blank',
'noopener, noreferrer' 'noopener, noreferrer'
) );
}); });
} }
@ -3332,9 +3371,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$passwordInput, $passwordInput,
$rawTextButton, $rawTextButton,
$qrCodeLink, $qrCodeLink,
$emailLink,
$sendButton, $sendButton,
$retryButton, $retryButton,
pasteExpiration = '1week', pasteExpiration = null,
retryButtonCallback; retryButtonCallback;
/** /**
@ -3542,6 +3582,133 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$('#qrcode-display').html(qrCanvas); $('#qrcode-display').html(qrCanvas);
} }
/**
* Template Email body.
*
* @name TopNav.templateEmailBody
* @private
* @param {string} expirationDateString
* @param {bool} isBurnafterreading
*/
function templateEmailBody(expirationDateString, isBurnafterreading)
{
const EOL = '\n';
const BULLET = ' - ';
let emailBody = '';
if (expirationDateString !== null || isBurnafterreading) {
emailBody += I18n._('Notice:');
emailBody += EOL;
if (expirationDateString !== 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}`;
return emailBody;
}
/**
* Trigger Email send.
*
* @name TopNav.triggerEmailSend
* @private
* @param {string} emailBody
*/
function triggerEmailSend(emailBody)
{
window.open(
`mailto:?body=${encodeURIComponent(emailBody)}`,
'_self',
'noopener, noreferrer'
);
}
/**
* 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 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 $emailconfirmmodal = $('#emailconfirmmodal');
if ($emailconfirmmodal.length > 0) {
if (expirationDate !== null) {
$emailconfirmmodal.find('#emailconfirm-display').text(
I18n._('Recipient may become aware of your timezone, convert time to UTC?')
);
const $emailconfirmTimezoneCurrent = $emailconfirmmodal.find('#emailconfirm-timezone-current');
const $emailconfirmTimezoneUtc = $emailconfirmmodal.find('#emailconfirm-timezone-utc');
$emailconfirmTimezoneCurrent.off('click.sendEmailCurrentTimezone');
$emailconfirmTimezoneCurrent.on('click.sendEmailCurrentTimezone', function(expirationDateRoundedToSecond, isBurnafterreading) {
return function() {
const emailBody = templateEmailBody(expirationDateRoundedToSecond.toLocaleString(), isBurnafterreading);
$emailconfirmmodal.modal('hide');
triggerEmailSend(emailBody);
};
} (expirationDateRoundedToSecond, isBurnafterreading));
$emailconfirmTimezoneUtc.off('click.sendEmailUtcTimezone');
$emailconfirmTimezoneUtc.on('click.sendEmailUtcTimezone', function(expirationDateRoundedToSecond, isBurnafterreading) {
return function() {
const emailBody = templateEmailBody(expirationDateRoundedToSecond.toLocaleString(
undefined,
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
{ timeZone: 'UTC', dateStyle: 'long', timeStyle: 'long' }
), isBurnafterreading);
$emailconfirmmodal.modal('hide');
triggerEmailSend(emailBody);
};
} (expirationDateRoundedToSecond, isBurnafterreading));
$emailconfirmmodal.modal('show');
} else {
triggerEmailSend(templateEmailBody(null, isBurnafterreading));
}
} else {
let emailBody = '';
if (expirationDate !== null) {
const expirationDateString = window.confirm(
I18n._('Recipient may become aware of your timezone, convert time to UTC?')
) ? expirationDateRoundedToSecond.toLocaleString(
undefined,
// we don't use Date.prototype.toUTCString() because we would like to avoid GMT
{ timeZone: 'UTC', dateStyle: 'long', timeStyle: 'long' }
) : expirationDateRoundedToSecond.toLocaleString();
emailBody = templateEmailBody(expirationDateString, isBurnafterreading);
} else {
emailBody = templateEmailBody(null, isBurnafterreading);
}
triggerEmailSend(emailBody);
}
}
/** /**
* Shows all navigation elements for viewing an existing paste * Shows all navigation elements for viewing an existing paste
* *
@ -3578,6 +3745,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$newButton.addClass('hidden'); $newButton.addClass('hidden');
$rawTextButton.addClass('hidden'); $rawTextButton.addClass('hidden');
$qrCodeLink.addClass('hidden'); $qrCodeLink.addClass('hidden');
me.hideEmailButton();
viewButtonsDisplayed = false; viewButtonsDisplayed = false;
}; };
@ -3675,6 +3843,50 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$retryButton.addClass('hidden'); $retryButton.addClass('hidden');
} }
/**
* show the "email" button
*
* @name TopNav.showEmailbutton
* @function
* @param {int|undefined} optionalRemainingTimeInSeconds
*/
me.showEmailButton = function(optionalRemainingTimeInSeconds)
{
try {
// we cache expiration date in closure to avoid inaccurate expiration datetime
const expirationDate = Helper.calculateExpirationDate(
new Date(),
typeof optionalRemainingTimeInSeconds === 'number' ? 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 * only hides the clone button
* *
@ -3697,6 +3909,30 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$rawTextButton.addClass('hidden'); $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 * hides the file selector in attachment
* *
@ -3784,7 +4020,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
/** /**
* returns the state of the burn after reading checkbox * returns the state of the burn after reading checkbox
* *
* @name TopNav.getExpiration * @name TopNav.getBurnAfterReading
* @function * @function
* @return {bool} * @return {bool}
*/ */
@ -3914,6 +4150,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
$retryButton = $('#retrybutton'); $retryButton = $('#retrybutton');
$sendButton = $('#sendbutton'); $sendButton = $('#sendbutton');
$qrCodeLink = $('#qrcodelink'); $qrCodeLink = $('#qrcodelink');
$emailLink = $('#emaillink');
// bootstrap template drop down // bootstrap template drop down
$('#language ul.dropdown-menu li a').click(setLanguage); $('#language ul.dropdown-menu li a').click(setLanguage);
@ -4258,6 +4495,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
history.pushState({type: 'newpaste'}, document.title, url); history.pushState({type: 'newpaste'}, document.title, url);
TopNav.showViewButtons(); TopNav.showViewButtons();
// this cannot be grouped with showViewButtons due to remaining time calculation
TopNav.showEmailButton();
TopNav.hideRawButton(); TopNav.hideRawButton();
Editor.hide(); Editor.hide();
@ -4698,7 +4939,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
// discourage cloning (it cannot really be prevented) // discourage cloning (it cannot really be prevented)
if (paste.isBurnAfterReadingEnabled()) { if (paste.isBurnAfterReadingEnabled()) {
TopNav.hideCloneButton(); TopNav.hideBurnAfterReadingButtons();
} else {
// we have to pass in remaining_time here
TopNav.showEmailButton(paste.getTimeToLive());
} }
}) })
.catch((err) => { .catch((err) => {
@ -4914,6 +5158,13 @@ jQuery.PrivateBin = (function($, RawDeflate) {
DOMPurify.setConfig({SAFE_FOR_JQUERY: true}); DOMPurify.setConfig({SAFE_FOR_JQUERY: true});
// center all modals
$('.modal').on('show.bs.modal', function(e) {
$(e.target).css({
display: 'flex'
});
});
// initialize other modules/"classes" // initialize other modules/"classes"
Alert.init(); Alert.init();
Model.init(); Model.init();

View file

@ -326,7 +326,7 @@ describe('TopNav', function () {
'returns the currently selected expiration date', 'returns the currently selected expiration date',
function () { function () {
$.PrivateBin.TopNav.init(); $.PrivateBin.TopNav.init();
assert.ok($.PrivateBin.TopNav.getExpiration() === '1week'); assert.ok($.PrivateBin.TopNav.getExpiration() === null);
} }
); );
}); });

View file

@ -72,7 +72,7 @@ endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-rgDjBvJguZa4Liw2R/0vtFNZBSBTLhjdHROUseVyl+x1aVUbo5U8x/5lewWzYxrS9dxHOtgQkEWjx5ZM31JiNg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-60d7BURzTV858Sry9vJEMgDXEqLT/IqQnjnx/iR3QfQ0LLWZI3bbM9nb4vbS0vQPYFVOP8lXpHRnNE7QEohsDw==" crossorigin="anonymous"></script>
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" /> <link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" /> <link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" /> <link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
@ -118,13 +118,41 @@ if ($QRCODE):
<div class="modal-body"> <div class="modal-body">
<div class="mx-auto" id="qrcode-display"></div> <div class="mx-auto" id="qrcode-display"></div>
</div> </div>
<div class="row">
<div class="btn-group col-xs-12">
<span class="col-xs-12">
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button> <button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<?php <?php
endif; endif;
?> ?>
<div id="emailconfirmmodal" tabindex="-1" class="modal fade" aria-labelledby="emailconfirmmodalTitle" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<div id="emailconfirm-display"></div>
</div>
<div class="row">
<div class="btn-group col-xs-12" data-toggle="buttons">
<span class="col-xs-12 col-md-4">
<button id="emailconfirm-timezone-current" type="button" class="btn btn-danger btn-block" data-dismiss="modal"><?php echo I18n::_('Use Current Timezone') ?></button>
</span>
<span class="col-xs-12 col-md-4">
<button id="emailconfirm-timezone-utc" type="button" class="btn btn-default btn-block" data-dismiss="modal"><?php echo I18n::_('Convert To UTC') ?></button>
</span>
<span class="col-xs-12 col-md-4">
<button type="button" class="btn btn-primary btn-block" data-dismiss="modal"><?php echo I18n::_('Close') ?></button>
</span>
</div>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php <nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php
if ($isCpct): if ($isCpct):
?><div class="container"><?php ?><div class="container"><?php
@ -171,6 +199,9 @@ endif;
<button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn"> <button id="rawtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?> <span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> <?php echo I18n::_('Raw text'), PHP_EOL; ?>
</button> </button>
<button id="emaillink" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
<span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> <?php echo I18n::_('Email'), PHP_EOL; ?>
</button>
<?php <?php
if ($QRCODE): if ($QRCODE):
?> ?>

View file

@ -50,7 +50,7 @@ endif;
?> ?>
<script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/purify-2.0.1.js" integrity="sha512-ddI36MdUoXp/o7yhQtr9/qj4G3oFwCRga4jCGaoUYtORg0PPmFKVKG4Ess3fIknYzxwwKMlrIL9o4NwuPTCc1Q==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-rgDjBvJguZa4Liw2R/0vtFNZBSBTLhjdHROUseVyl+x1aVUbo5U8x/5lewWzYxrS9dxHOtgQkEWjx5ZM31JiNg==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-60d7BURzTV858Sry9vJEMgDXEqLT/IqQnjnx/iR3QfQ0LLWZI3bbM9nb4vbS0vQPYFVOP8lXpHRnNE7QEohsDw==" crossorigin="anonymous"></script>
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" /> <link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" /> <link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
<link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" /> <link rel="icon" type="image/png" href="img/favicon-16x16.png?<?php echo rawurlencode($VERSION); ?>" sizes="16x16" />
@ -105,6 +105,7 @@ endif;
<button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" /><?php echo I18n::_('Send'); ?></button> <button id="sendbutton" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" /><?php echo I18n::_('Send'); ?></button>
<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" /><?php echo I18n::_('Clone'); ?></button> <button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" /><?php echo I18n::_('Clone'); ?></button>
<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button> <button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
<button id="emaillink" class="hidden"><img src="img/icon_email.png" width="15" height="15" alt="" /><?php echo I18n::_('Email'); ?></button>
<?php <?php
if ($QRCODE): if ($QRCODE):
?> ?>