diff --git a/css/bootstrap/privatebin.css b/css/bootstrap/privatebin.css
index 9ee68ec..5c940a4 100644
--- a/css/bootstrap/privatebin.css
+++ b/css/bootstrap/privatebin.css
@@ -17,6 +17,10 @@ body.navbar-spacing {
padding-top: 70px;
}
+body.loading {
+ cursor: wait;
+}
+
.buttondisabled {
opacity: 0.3;
}
@@ -106,8 +110,17 @@ body.navbar-spacing {
border-left: 1px solid #ccc;
padding: 5px 0 5px 10px;
white-space: pre-wrap;
+
+ transition: background-color 0.75s ease-out;
}
+.comment.highlight {
+ background-color: #ffdd86;
+ transition: background-color 0.2s ease-in;
+}
+
+
+
footer h4 {
margin-top: 0;
}
diff --git a/i18n/de.json b/i18n/de.json
index 7466d62..6211a4f 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -92,16 +92,16 @@
"Antworten",
"Anonymous":
"Anonym",
- "Anonymous avatar (Vizhash of the IP address)":
- "Anonymer Avatar (Vizhash der IP-Addresse)",
+ "Avatar generated from IP address":
+ "Anvatar (generiert aus der IP-Adresse)",
"Add comment":
"Kommentar hinzufügen",
- "Optional nickname...":
- "Optionales Pseudonym...",
+ "Optional nickname…":
+ "Optionales Pseudonym…",
"Post comment":
"Kommentar absenden",
- "Sending comment...":
- "Sende Kommentar...",
+ "Sending comment…":
+ "Sende Kommentar…",
"Comment posted.":
"Kommentar gesendet.",
"Could not refresh display: %s":
@@ -151,6 +151,6 @@
"Preparing new paste…": "Bereite neues Paste vor…",
"In case this message never disappears please have a look at this FAQ for information to troubleshoot.":
"Wenn diese Nachricht nicht mehr verschwindet, schau bitte in die FAQ (englisch), um zu sehen, wie der Fehler behoben werden kann.",
- "Nothing to see… Try to enter some text.":
- "Nichts zu sehen… Versuche etwas Text einzugeben."
+ "+++ no paste text +++":
+ "+++ kein Paste-Text +++"
}
diff --git a/i18n/es.json b/i18n/es.json
index 61b1540..0af97eb 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -96,12 +96,12 @@
"Avatar anónimo (Vizhash de la dirección IP)",
"Add comment":
"Añadir comentario",
- "Optional nickname...":
- "Seudónimo opcional...",
+ "Optional nickname…":
+ "Seudónimo opcional…",
"Post comment":
"Publicar comentario",
- "Sending comment...":
- "Enviando comentario...",
+ "Sending comment…":
+ "Enviando comentario…",
"Comment posted.":
"Comentario publicado.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"Error del servidor o el servidor no responde",
"Could not post comment: %s":
"No fue posible publicar comentario: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Enviando texto (Por favor, mueva el ratón para mayor entropía)...",
- "Sending paste...":
- "Enviando texto...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Enviando texto (Por favor, mueva el ratón para mayor entropía)…",
+ "Sending paste…":
+ "Enviando texto…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Su texto está en %s (Presione [Ctrl]+[c] para copiar)",
"Delete data":
diff --git a/i18n/fr.json b/i18n/fr.json
index 89a4504..97c4e24 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -96,12 +96,12 @@
"Avatar anonyme (Vizhash de l'adresse IP)",
"Add comment":
"Ajouter un commentaire",
- "Optional nickname...":
- "Pseudonyme optionnel...",
+ "Optional nickname…":
+ "Pseudonyme optionnel…",
"Post comment":
"Poster le commentaire",
- "Sending comment...":
- "Envoi du commentaire...",
+ "Sending comment…":
+ "Envoi du commentaire…",
"Comment posted.":
"Commentaire posté.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"Le serveur ne répond pas ou a rencontré une erreur",
"Could not post comment: %s":
"Impossible de poster le commentaire : %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Envoi du paste (Merci de bouger votre souris pour plus d'entropie)...",
- "Sending paste...":
- "Envoi du paste...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Envoi du paste (Merci de bouger votre souris pour plus d'entropie)…",
+ "Sending paste…":
+ "Envoi du paste…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Votre paste est disponible à l'adresse %s (Appuyez sur [Ctrl]+[c] pour copier)",
"Delete data":
diff --git a/i18n/it.json b/i18n/it.json
index 24b79ec..df5aee2 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -96,12 +96,12 @@
"Avatar Anonino (Vizhash dell'indirizzo IP)",
"Add comment":
"Aggiungi un commento",
- "Optional nickname...":
- "Nickname opzionale...",
+ "Optional nickname…":
+ "Nickname opzionale…",
"Post comment":
"Invia commento",
- "Sending comment...":
- "Commento in fase di invio...",
+ "Sending comment…":
+ "Commento in fase di invio…",
"Comment posted.":
"Commento inviato.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"errore o mancata risposta dal server",
"Could not post comment: %s":
"Impossibile inviare il commento: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Invio messaggio (Muovi il mouse in modo casuale, per generare maggior entropia)...",
- "Sending paste...":
- "Messaggio in fase di invio...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Invio messaggio (Muovi il mouse in modo casuale, per generare maggior entropia)…",
+ "Sending paste…":
+ "Messaggio in fase di invio…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Il tuo messaggio è qui: %s ([CTRL | CMD]+[C] per copiare il link)",
"Delete data":
diff --git a/i18n/no.json b/i18n/no.json
index 1408b81..4d92cc8 100644
--- a/i18n/no.json
+++ b/i18n/no.json
@@ -96,12 +96,12 @@
"Anonym avatar (Vizhash av IP adressen)",
"Add comment":
"Legg til kommentar",
- "Optional nickname...":
- "Valgfritt kallenavn...",
+ "Optional nickname…":
+ "Valgfritt kallenavn…",
"Post comment":
"Send kommentar",
- "Sending comment...":
- "Sender Kommentar...",
+ "Sending comment…":
+ "Sender Kommentar…",
"Comment posted.":
"Kommentar sendt.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"server feilet eller svarer ikke",
"Could not post comment: %s":
"Kunne ikke sende kommentar: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Sender innlegg (Flytt musen for mere entropi)...",
- "Sending paste...":
- "Sender innlegg...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Sender innlegg (Flytt musen for mere entropi)…",
+ "Sending paste…":
+ "Sender innlegg…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Ditt innlegg er %s (Trykk [Ctrl]+[c] for å kopiere)",
"Delete data":
diff --git a/i18n/oc.json b/i18n/oc.json
index efbb9b2..a29bce0 100644
--- a/i18n/oc.json
+++ b/i18n/oc.json
@@ -96,12 +96,12 @@
"Avatar anonime (Vizhash de l'adreça IP)",
"Add comment":
"Apondre un comentari",
- "Optional nickname...":
- "Escais opcional...",
+ "Optional nickname…":
+ "Escais opcional…",
"Post comment":
"Mandar lo comentari",
- "Sending comment...":
- "Mandadís del comentari...",
+ "Sending comment…":
+ "Mandadís del comentari…",
"Comment posted.":
"Comentari mandat.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"Lo servidor respond pas o a rencontrat una error",
"Could not post comment: %s":
"Impossible de mandar lo comentari : %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Mandadís del tèxte (Mercés de bolegar vòstra mirga per mai entropia)...",
- "Sending paste...":
- "Mandadís del tèxte...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Mandadís del tèxte (Mercés de bolegar vòstra mirga per mai entropia)…",
+ "Sending paste…":
+ "Mandadís del tèxte…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Vòstre tèxte es disponible a l'adreça %s (Picatz sus [Ctrl]+[c] per copiar)",
"Delete data":
diff --git a/i18n/pl.json b/i18n/pl.json
index 2757439..b9cc8f2 100644
--- a/i18n/pl.json
+++ b/i18n/pl.json
@@ -96,12 +96,12 @@
"Anonimowy avatar (Vizhash z adresu IP)",
"Add comment":
"Dodaj komentarz",
- "Optional nickname...":
- "Opcjonalny nick...",
+ "Optional nickname…":
+ "Opcjonalny nick…",
"Post comment":
"Wyślij komentarz",
- "Sending comment...":
- "Wysyłanie komentarza...",
+ "Sending comment…":
+ "Wysyłanie komentarza…",
"Comment posted.":
"Wysłano komentarz.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"bląd serwera lub brak odpowiedzi",
"Could not post comment: %s":
"Nie udało się wysłać komentarza: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Wysyłanie wklejki (proszę poruszać myszą aby uzyskać większą entropię)...",
- "Sending paste...":
- "Wysyłanie wklejki...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Wysyłanie wklejki (proszę poruszać myszą aby uzyskać większą entropię)…",
+ "Sending paste…":
+ "Wysyłanie wklejki…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Twoja wklejka to %s (wciśnij [Ctrl]+[c] aby skopiować)",
"Delete data":
diff --git a/i18n/ru.json b/i18n/ru.json
index de7ad05..7e92da7 100644
--- a/i18n/ru.json
+++ b/i18n/ru.json
@@ -96,12 +96,12 @@
"Анонимный аватар (Vizhash IP адреса)",
"Add comment":
"Добавить комментарий",
- "Optional nickname...":
- "Опциональный никнейм...",
+ "Optional nickname…":
+ "Опциональный никнейм…",
"Post comment":
"Отправить комментарий",
- "Sending comment...":
- "Отправка комментария...",
+ "Sending comment…":
+ "Отправка комментария…",
"Comment posted.":
"Комментарий опубликован.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"ошибка сервера или нет ответа",
"Could not post comment: %s":
"Не удалось опубликовать комментарий: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Отправка записи (Пожалуйста двигайте мышкой для большей энтропии)...",
- "Sending paste...":
- "Отправка записи...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Отправка записи (Пожалуйста двигайте мышкой для большей энтропии)…",
+ "Sending paste…":
+ "Отправка записи…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Ссылка на запись %s (Нажмите [Ctrl]+[c] чтобы скопировать ссылку)",
"Delete data":
@@ -155,5 +155,5 @@
"Enter password":
"Введите пароль",
"Uploading paste… Please wait.":
- "Отправка записи... Пожалуйста подождите."
+ "Отправка записи… Пожалуйста подождите."
}
diff --git a/i18n/sl.json b/i18n/sl.json
index 4cf3d5a..2df2608 100644
--- a/i18n/sl.json
+++ b/i18n/sl.json
@@ -96,12 +96,12 @@
"Anonimen avatar (Vizhash IP naslova)",
"Add comment":
"Dodaj komentar",
- "Optional nickname...":
+ "Optional nickname…":
"Uporabniško ime (lahko izpustiš)",
"Post comment":
"Objavi komentar",
- "Sending comment...":
- "Pošiljam komentar ...",
+ "Sending comment…":
+ "Pošiljam komentar …",
"Comment posted.":
"Komentar poslan.",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"napaka na strežniku, ali pa se strežnik ne odziva",
"Could not post comment: %s":
"Komentarja ni bilo mogoče objaviti : %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "Pošiljam prilepek (prosim premakni svojo miško za več entropije) ...",
- "Sending paste...":
- "Pošiljam prilepek...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "Pošiljam prilepek (prosim premakni svojo miško za več entropije) …",
+ "Sending paste…":
+ "Pošiljam prilepek…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"Tvoj prilepek je dostopen na naslovu: %s (Pritisni [Ctrl]+[c] ali [Cmd] + [c] in skopiraj)",
"Delete data":
diff --git a/i18n/zh.json b/i18n/zh.json
index a1a9b96..3ec80f1 100644
--- a/i18n/zh.json
+++ b/i18n/zh.json
@@ -96,12 +96,12 @@
"匿名头像 (由IP地址生成Vizhash)",
"Add comment":
"添加评论",
- "Optional nickname...":
- "可选昵称...",
+ "Optional nickname…":
+ "可选昵称…",
"Post comment":
"评论",
- "Sending comment...":
- "评论发送中...",
+ "Sending comment…":
+ "评论发送中…",
"Comment posted.":
"评论已发送。",
"Could not refresh display: %s":
@@ -112,10 +112,10 @@
"服务器错误或无回应",
"Could not post comment: %s":
"无法发送评论: %s",
- "Sending paste (Please move your mouse for more entropy)...":
- "粘贴提交中 (请移动鼠标以产生更多熵)...",
- "Sending paste...":
- "粘贴提交中...",
+ "Sending paste (Please move your mouse for more entropy)…":
+ "粘贴提交中 (请移动鼠标以产生更多熵)…",
+ "Sending paste…":
+ "粘贴提交中…",
"Your paste is %s (Hit [Ctrl]+[c] to copy)":
"您的粘贴的链接是%s (按下 [Ctrl]+[c] 以复制)",
"Delete data":
diff --git a/js/privatebin.js b/js/privatebin.js
index c0b0669..8c5a785 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -599,8 +599,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
ts: 128
};
- if ((password || '').trim().length === 0)
- {
+ if ((password || '').trim().length === 0) {
return sjcl.encrypt(key, compress(message), options);
}
return sjcl.encrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), compress(message), options);
@@ -618,20 +617,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.decipher = function(key, password, data)
{
- if (data !== undefined)
- {
- try
- {
+ if (data !== undefined) {
+ try {
return decompress(sjcl.decrypt(key, data));
- }
- catch(err)
- {
- try
- {
+ } catch(err) {
+ try {
return decompress(sjcl.decrypt(key + sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(password)), data));
- }
- catch(e)
- {
+ } catch(e) {
// ignore error, because ????? @TODO
}
}
@@ -705,7 +697,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var Model = (function (window, document) {
var me = {};
- var $cipherData;
+ var $cipherData,
+ $templates;
var id = null, symmetricKey = null;
@@ -766,11 +759,16 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* @name Model.getPasteId
* @function
* @return {string} unique identifier
+ * @throws {string}
*/
me.getPasteId = function()
{
if (id === null) {
id = window.location.search.substring(1);
+
+ if (id === '') {
+ throw 'no paste id given';
+ }
}
return id;
@@ -781,13 +779,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*
* @name Model.getPasteKey
* @function
- * @return {string} key
+ * @return {string|null} key
+ * @throws {string}
*/
me.getPasteKey = function()
{
if (symmetricKey === null) {
symmetricKey = window.location.hash.substring(1);
+ if (symmetricKey === '') {
+ throw 'no encryption key given';
+ }
+
// Some web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...). We will strip any additional data.
var ampersandPos = symmetricKey.indexOf('&');
@@ -801,6 +804,23 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return symmetricKey;
};
+ /**
+ * returns a jQuery copy of the HTML template
+ *
+ * @name Model.getTemplate
+ * @function
+ * @param {string} name - the name of the template
+ * @return {jQuery}
+ */
+ me.getTemplate = function(name)
+ {
+ // find template
+ var $element = $templates.find('#' + name + 'template').clone(true);
+ // change ID to avoid collisions (one ID should really be unique)
+ return $element.prop('id', name);
+ }
+
+
/**
* init navigation manager
*
@@ -812,6 +832,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.init = function()
{
$cipherData = $('#cipherdata');
+ $templates = $('#templates');
};
return me;
@@ -863,6 +884,80 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
window.location.href = Helper.baseUri();
};
+ /**
+ * checks whether the element is currently visible in the viewport (so
+ * the user can actually see it)
+ *
+ * THanks to https://stackoverflow.com/a/40658647
+ *
+ * @name UiHelper.isVisible
+ * @function
+ * @param {jQuery} $element The link hash to move to.
+ */
+ me.isVisible = function($element)
+ {
+ var elementTop = $element.offset().top;
+ var elementBottom = elementTop + $element.outerHeight();
+
+ var viewportTop = $(window).scrollTop();
+ var viewportBottom = viewportTop + $(window).height();
+
+ return (elementTop > viewportTop && elementTop < viewportBottom);
+ };
+
+ /**
+ * scrolls to a specific element
+ *
+ * Based on code by @hanoo: https://stackoverflow.com/questions/4198041/jquery-smooth-scroll-to-an-anchor#answer-12714767
+ *
+ * @name UiHelper.scrollTo
+ * @param {jQuery} $element The link hash to move to.
+ * @param {(number|string)} animationDuration passed to jQuery .animate, when set to 0 the animation is skipped
+ * @param {string} animationEffect passed to jQuery .animate
+ * @param {function} finishedCallback function to call after animation finished
+ */
+ me.scrollTo = function($element, animationDuration, animationEffect, finishedCallback)
+ {
+ var $body = $('html, body'),
+ margin = 50,
+ callbackCalled = false;
+
+ //calculate destination place
+ var dest = 0;
+ // if it would scroll out of the screen at the bottom only scroll it as
+ // far as the screen can go
+ if ($element.offset().top > $(document).height() - $(window).height()) {
+ dest = $(document).height() - $(window).height();
+ } else {
+ dest = $element.offset().top - margin;
+ }
+ // skip animation if duration is set to 0
+ if (animationDuration === 0) {
+ window.scrollTo(0, dest);
+ } else {
+ // stop previous animation
+ $body.stop();
+ // scroll to destination
+ $body.animate({
+ scrollTop: dest
+ }, animationDuration, animationEffect);
+ }
+
+ // as we have finished we can enable scrolling again
+ $body.queue(function (next) {
+ if (!callbackCalled) {
+ // call user function if needed
+ if (typeof finishedCallback !== 'undefined') {
+ finishedCallback();
+ }
+
+ // prevent calling this function twice
+ callbackCalled = true;
+ }
+ next();
+ });
+ };
+
/**
* initialize
*
@@ -901,6 +996,15 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
'glyphicon-alert' // error icon
];
+ var alertType = [
+ 'loading', // not in bootstrap, but using a good value here
+ 'info', // status icon
+ 'warning', // not used yet
+ 'danger' // error icon
+ ];
+
+ var customHandler;
+
/**
* forwards a request to the i18n module and shows the element
*
@@ -913,9 +1017,33 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
function handleNotification(id, $element, args, icon)
{
+ // basic parsing/conversion of parameters
+ if (typeof icon === 'undefined') {
+ icon = null;
+ }
+ if (typeof args === 'undefined') {
+ args = null;
+ } else if (typeof args === 'string') {
+ // convert string to array if needed
+ args = [args];
+ }
+
+ // pass to custom handler if dfined
+ if (typeof customHandler === 'function') {
+ var handlerResult = customHandler(alertType[id], $element, args, icon);
+ if (handlerResult === true) {
+ // if it returs true, skip own handler
+ return;
+ }
+ if (handlerResult instanceof jQuery) {
+ // continue processing with new element
+ $element = handlerResult;
+ icon = null; // icons not supported in this case
+ }
+ }
+
// handle icon
- if (
- typeof icon !== 'undefined' && // if icon is passed
+ if (icon !== null && // icon was passed
icon !== currentIcon[id] // and it differs from current icon
) {
var $glyphIcon = $element.find(':first');
@@ -932,12 +1060,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
// show text
- if (typeof args !== 'undefined' && args !== null) {
- // convert string to array if needed
- if (typeof args === 'string') {
- args = [args];
- }
-
+ if (args !== null) {
// add jQuery object to it as first parameter
args.unshift($element.find(':last'));
@@ -993,7 +1116,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.showError = function(message, icon, dismissable, autoclose)
{
console.error('error message shown: ', message);
- // @TODO: implement dismissable
+ // @TODO: implement dismissable (bootstrap add-on has it)
// @TODO: implement autoclose
handleNotification(3, $errorMessage, message, icon);
@@ -1025,6 +1148,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
// // @TODO handle it here…
handleNotification(0, $loadingIndicator, message, icon);
+
+ // show loading status (cursor)
+ $('body').addClass('loading');
};
/**
@@ -1036,6 +1162,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.hideLoading = function()
{
$loadingIndicator.addClass('hidden');
+
+ // hide loading cursor
+ $('body').removeClass('loading');
};
/**
@@ -1048,10 +1177,34 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.hideMessages = function()
{
+ // also possible: $('.statusmessage').addClass('hidden');
$statusMessage.addClass('hidden');
$errorMessage.addClass('hidden');
};
+ /**
+ * set a custom handler, which gets all notifications.
+ *
+ * This handler gets the following arguments:
+ * alertType (see array), $element, args, icon
+ * If it returns true, the own processing will be stopped so the message
+ * will not be displayed. Otherwise it will continue.
+ * As an aditional feature it can return q jQuery element, which will
+ * then be used to add the message there. Icons are not supported in
+ * that case and will be ignored.
+ * Pass 'null' to reset/delete the custom handler.
+ * Note that there is no notification when a message is supposed to get
+ * hidden.
+ *
+ * @name Alert.setCustomHandler
+ * @function
+ * @param {function|null} newHandler
+ */
+ me.setCustomHandler = function(newHandler)
+ {
+ customHandler = newHandler;
+ };
+
/**
* init status manager
*
@@ -1062,9 +1215,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.init = function()
{
- // hide "no javascript" message
+ // hide "no javascript" error message
$('#noscript').hide();
+ // not a reset, but first set of the elements
$errorMessage = $('#errormessage');
$statusMessage = $('#status');
$loadingIndicator = $('#loadingindicator');
@@ -1246,7 +1400,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var Prompt = (function (window, document) {
var me = {};
- var $passwordModel,
+ var $passwordModal,
$passwordForm,
$passwordDecrypt;
@@ -1263,31 +1417,31 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.requestPassword = function()
{
- // show new bootstrap method
- $passwordModel.Model({
- backdrop: 'static',
- keyboard: false
- });
-
- if ($passwordModel.length === 0) {
- // old method for page template
- var newPassword = prompt(I18n._('Please enter the password for this paste:'), '');
- if (newPassword === null) {
- throw 'password prompt canceled';
- }
- if (password.length === 0) {
- // recursive…
- return me.requestPassword();
- }
-
- password = newPassword;
-
- if (passwordCallback !== null) {
- passwordCallback();
- }
+ // show new bootstrap method (if available)
+ if ($passwordModal.length !== 0) {
+ $passwordModal.modal({
+ backdrop: 'static',
+ keyboard: false
+ });
return;
}
+
+ // fallback to old method for page template
+ var newPassword = prompt(I18n._('Please enter the password for this paste:'), '');
+ if (newPassword === null) {
+ throw 'password prompt canceled';
+ }
+ if (password.length === 0) {
+ // recursive…
+ return me.requestPassword();
+ }
+
+ password = newPassword;
+
+ if (passwordCallback !== null) {
+ passwordCallback();
+ }
};
/**
@@ -1318,19 +1472,19 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
};
/**
- * submit a password in the Model dialog
+ * submit a password in the modal dialog
*
* @private
* @function
* @param {Event} event
*/
- function submitPasswordModel(event)
+ function submitPasswordModal(event)
{
// get input
password = $passwordDecrypt.val();
- // hide Model
- $passwordModel.Model('hide');
+ // hide modal
+ $passwordModal.modal('hide');
if (passwordCallback !== null) {
passwordCallback();
@@ -1350,18 +1504,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.init = function()
{
- $passwordModel = $('#passwordModel');
+ $passwordModal = $('#passwordmodal');
$passwordForm = $('#passwordform');
$passwordDecrypt = $('#passworddecrypt');
// bind events
// focus password input when it is shown
- $passwordModel.on('shown.bs.Model', function () {
+ $passwordModal.on('shown.bs.Model', function () {
$passwordDecrypt.focus();
});
// handle Model password submission
- $passwordForm.submit(submitPasswordModel);
+ $passwordForm.submit(submitPasswordModal);
};
return me;
@@ -2012,65 +2166,254 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
var DiscussionViewer = (function (window, document) {
var me = {};
- var $comments,
- $discussion;
+ var $commentContainer,
+ $commentTail,
+ $discussion,
+ $reply,
+ $replyMessage,
+ $replyNickname,
+ $replyStatus;
+
+ var replyCommentId;
/**
- * display a status message for replying to comments
+ * initializes the templates
*
- * @name DiscussionViewer.showStatus
+ * @private
* @function
- * @param {string} message - text to display
- * @param {boolean} [spin=false] - (optional) tell if the "spinning" animation should be displayed, defaults to false
*/
- me.showReplyStatus = function(message, spin)
+ function initTemplates()
{
- if (spin || false) {
- $replyalert.find('.spinner').removeClass('hidden')
+ $reply = Model.getTemplate('reply');
+ $replyMessage = $reply.find('#replymessage');
+ $replyNickname = $reply.find('#nickname');
+ $replyStatus = $reply.find('#replystatus');
+
+ // cache jQuery elements
+ $commentTail = Model.getTemplate('commenttail');
+ }
+
+ /**
+ * custom handler for displaying notifications in own status message area
+ *
+ * @name DiscussionViewer.handleNotification
+ * @function
+ * @param {string} alertType
+ * @param {jQuery} $element
+ * @param {string|array} args
+ * @param {string|null} icon
+ * @return {bool|jQuery}
+ */
+ me.handleNotification = function(alertType, $element, args, icon)
+ {
+ // ignore loading messages
+ if (alertType === 'loading') {
+ return false;
}
- $replyalert.text(message);
- };
- /**
- * display an error message
- *
- * @name DiscussionViewer.showReplyError
- * @function
- * @param {string} message - text to display
- */
- me.showReplyError = function(message)
- {
- $replyalert.addClass('Alert-danger');
- $replyalert.addClass($errorMessage.attr('class')); // @TODO ????
+ if (alertType === 'danger') {
+ $replyStatus.removeClass('alert-info');
+ $replyStatus.addClass('alert-danger');
+ $replyStatus.find(':first').removeClass('glyphicon-alert');
+ $replyStatus.find(':first').addClass('glyphicon-info-sign');
+ } else {
+ $replyStatus.removeClass('alert-danger');
+ $replyStatus.addClass('alert-info');
+ $replyStatus.find(':first').removeClass('glyphicon-info-sign');
+ $replyStatus.find(':first').addClass('glyphicon-alert');
+ }
- $replyalert.text(message);
+ return $replyStatus;
};
/**
* open the comment entry when clicking the "Reply" button of a comment
*
- * @name DiscussionViewer.openReply
+ * @private
* @function
* @param {Event} event
*/
- me.openReply = function(event)
+ function openReply(event)
{
+ var $source = $(event.target);
+
+ // clear input
+ $replyMessage.val('');
+ $replyNickname.val('');
+
+ // get comment id from source element
+ replyCommentId = $source.parent().prop('id').split('_')[1];
+
+ // move to correct position
+ $source.after($reply);
+
+ // show
+ $reply.removeClass('hidden');
+ $replyMessage.focus();
+
event.preventDefault();
+ }
- // remove any other reply area
- $('div.reply').remove();
+ /**
+ * adds another comment
+ *
+ * @name DiscussionViewer.addComment
+ * @function
+ * @param {object} comment
+ * @param {string} commentText
+ * @param {jQuery} $place - optional, tries to find the best position otherwise
+ */
+ me.addComment = function(comment, commentText, nickname, $place)
+ {
+ if (typeof $place === 'undefined') {
+ // starting point (default value/fallback)
+ $place = $commentContainer;
- var source = $(event.target),
- commentid = event.data.commentid,
- hint = I18n._('Optional nickname...'),
- $reply = $('#replytemplate');
- $reply.find('button').click(
- {parentid: commentid},
- me.sendComment
- );
- source.after($reply);
- $replyStatus = $('#replystatus'); // when ID --> put into HTML
- $('#replymessage').focus();
+ // if parent comment exists
+ var $parentComment = $('#comment_' + comment.parentid);
+ if ($parentComment.length) {
+ // use parent as position for noew comment, so it shifted
+ // to the right
+ $place = $parentComment;
+ }
+ }
+ if (commentText === '') {
+ commentText = 'comment decryption failed';
+ }
+
+ // create new comment based on template
+ var $commentEntry = Model.getTemplate('comment');
+ $commentEntry.prop('id', 'comment_' + comment.id);
+ var $commentEntryData = $commentEntry.find('div.commentdata');
+
+ // set & parse text
+ Helper.setElementText($commentEntryData, commentText);
+ Helper.urls2links($commentEntryData);
+
+ // set nickname
+ if (nickname.length > 0){
+ $commentEntry.find('span.nickname').text(nickname);
+ } else {
+ $commentEntry.find('span.nickname').html('' + I18n._('Anonymous') + '');
+ }
+
+ // set date
+ $commentEntry.find('span.commentdate')
+ .text(' (' + (new Date(comment.meta.postdate * 1000).toLocaleString()) + ')')
+ .attr('title', 'CommentID: ' + comment.id);
+
+ // if an avatar is available, display it
+ if (comment.meta.vizhash) {
+ $commentEntry.find('span.nickname')
+ .before(
+ ' '
+ );
+ }
+
+ // finally append comment
+ $place.append($commentEntry);
+ };
+
+ /**
+ * finishes the discussion area after last comment
+ *
+ * @name DiscussionViewer.finishDiscussion
+ * @function
+ */
+ me.finishDiscussion = function()
+ {
+ // add 'add new comment' area
+ $commentContainer.append($commentTail);
+
+ // show discussions
+ $discussion.removeClass('hidden');
+ };
+
+ /**
+ * shows the discussion area
+ *
+ * @name DiscussionViewer.showDiscussion
+ * @function
+ */
+ me.showDiscussion = function()
+ {
+ $discussion.removeClass('hidden');
+ };
+
+ /**
+ * removes the old discussion and prepares everything for creating a new
+ * one.
+ *
+ * @name DiscussionViewer.prepareNewDisucssion
+ * @function
+ */
+ me.prepareNewDisucssion = function()
+ {
+ $commentContainer.html('');
+ $discussion.addClass('hidden');
+
+ // (re-)init templates
+ initTemplates();
+ };
+
+ /**
+ * returns the user put into the reply form
+ *
+ * @name DiscussionViewer.getReplyData
+ * @function
+ * @return {array}
+ */
+ me.getReplyData = function()
+ {
+ return [
+ $replyMessage.val(),
+ $replyNickname.val()
+ ];
+ };
+
+ /**
+ * highlights a specific comment and scrolls to it if necessary
+ *
+ * @name DiscussionViewer.highlightComment
+ * @function
+ * @param {string} commentId
+ * @param {bool} fadeOut - whether to fade out the comment
+ */
+ me.highlightComment = function(commentId, fadeOut)
+ {
+ var $comment = $('#comment_' + commentId);
+ // in case comment does not exist, cancel
+ if ($comment.length === 0) {
+ return;
+ }
+
+ var highlightComment = function () {
+ $comment.addClass('highlight');
+ if (fadeOut === true) {
+ setTimeout(function () {
+ $comment.removeClass('highlight');
+ }, 300);
+ }
+ }
+
+ if (UiHelper.isVisible($comment)) {
+ return highlightComment();
+ }
+
+ UiHelper.scrollTo($comment, 100, 'swing', highlightComment);
+ };
+
+ /**
+ * returns the id of the parent comment the user is replying to
+ *
+ * @name DiscussionViewer.getReplyCommentId
+ * @function
+ * @return {int|undefined}
+ */
+ me.getReplyCommentId = function()
+ {
+ return replyCommentId;
};
/**
@@ -2083,9 +2426,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.init = function()
{
- $comments = $('#comments');
+ // bind events to templates (so they are later cloned)
+ $('#commenttailtemplate, #commenttemplate').find('button').on('click', openReply);
+ $('#replytemplate').find('button').on('click', PasteEncrypter.sendComment);
+
+ $commentContainer = $('#commentcontainer');
$discussion = $('#discussion');
- // $replyStatus in openReply()
};
return me;
@@ -2094,8 +2440,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* Manage top (navigation) bar
*
- * @param {object} window
- * @param {object} document
+ * @param {object} window
+ * @param {object} document
* @class
*/
var TopNav = (function (window, document) {
@@ -2307,6 +2653,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return;
}
+ $newButton.removeClass('hidden');
$cloneButton.removeClass('hidden');
$rawTextButton.removeClass('hidden');
@@ -2326,7 +2673,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return;
}
- $newButton.removeClass('hidden');
+ $newButton.addClass('hidden');
$cloneButton.addClass('hidden');
$rawTextButton.addClass('hidden');
@@ -2336,7 +2683,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* shows all elements needed when creating a new paste
*
- * @name TopNav.setLanguage
+ * @name TopNav.showCreateButtons
* @function
*/
me.showCreateButtons = function()
@@ -2361,7 +2708,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* shows all elements needed when creating a new paste
*
- * @name TopNav.setLanguage
+ * @name TopNav.hideCreateButtons
* @function
*/
me.hideCreateButtons = function()
@@ -2386,7 +2733,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* only shows the "new paste" button
*
- * @name TopNav.setLanguage
+ * @name TopNav.showNewPasteButton
* @function
*/
me.showNewPasteButton = function()
@@ -2569,9 +2916,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
$fileWrap = $('#filewrap');
// bootstrap template drop down
- $('#language ul.dropdown-menu li a').click(me.setLanguage);
+ $('#language ul.dropdown-menu li a').click(setLanguage);
// page template drop down
- $('#language select option').click(me.setLanguage);
+ $('#language select option').click(setLanguage);
// bind events
$burnAfterReading.change(changeBurnAfterReading);
@@ -2611,7 +2958,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
failureFunc = null,
url,
data,
- randomKey,
+ symmetricKey,
password;
/**
@@ -2639,6 +2986,39 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* called after successful upload
*
+ * @private
+ * @function
+ * @throws {string}
+ */
+ function checkCryptParameters()
+ {
+ // workaround for this nasty 'bug' in ECMAScript
+ // see https://stackoverflow.com/questions/18808226/why-is-typeof-null-object
+ var typeOfKey = typeof symmetricKey;
+ if (symmetricKey === null) {
+ typeOfKey = 'null';
+ }
+
+ // in case of missing preparation, throw error
+ switch (typeOfKey) {
+ case 'string':
+ // already set, all right
+ return;
+ case 'null':
+ // needs to be generated auto-generate
+ symmetricKey = CryptTool.getSymmetricKey();
+ break;
+ default:
+ console.error('current invalid symmetricKey:', symmetricKey);
+ throw 'symmetricKey is invalid, probably the module was not prepared';
+ }
+ // password is optional
+ }
+
+ /**
+ * called after successful upload
+ *
+ * @private
* @function
* @param {int} status
* @param {int} data - optional
@@ -2646,7 +3026,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
function success(status, result)
{
// add useful data to result
- result.encryptionKey = randomKey;
+ result.encryptionKey = symmetricKey;
result.requestData = data;
if (successFunc !== null) {
@@ -2657,7 +3037,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* called after a upload failure
*
- * @name Uploader.submitPasteUpload
+ * @private
* @function
* @param {int} status - internal code
* @param {int} data - original error code
@@ -2711,6 +3091,26 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
url = newUrl;
};
+ /**
+ * sets the password to use (first value) and optionally also the
+ * encryption key (not recommend, it is automatically generated).
+ *
+ * Note: Call this after prepare() as prepare() resets these values.
+ *
+ * @name Uploader.setCryptValues
+ * @function
+ * @param {string} newPassword
+ * @param {string} newKey - optional
+ */
+ me.setCryptParameters = function(newPassword, newKey)
+ {
+ password = newPassword;
+
+ if (typeof newKey !== 'undefined') {
+ symmetricKey = newKey;
+ }
+ };
+
/**
* set success function
*
@@ -2738,25 +3138,28 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* prepares a new upload
*
+ * Call this when doing a new upload to reset any data from potential
+ * previous uploads. Must be called before any other method of this
+ * module.
+ *
* @name Uploader.prepare
* @function
- * @param {string} newPassword
* @return {object}
*/
- me.prepare = function(newPassword)
+ me.prepare = function()
{
- // set password
- password = newPassword;
-
// entropy should already be checked!
- // generate a new random key
- randomKey = CryptTool.getSymmetricKey();
+ // reset password
+ password = '';
+
+ // reset key, so it a new one is generated when it is used
+ symmetricKey = null;
// reset data
successFunc = null;
failureFunc = null;
- url = Helper.baseUri()
+ url = Helper.baseUri();
data = {};
};
@@ -2770,7 +3173,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*/
me.setData = function(index, element)
{
- data[index] = CryptTool.cipher(randomKey, password, element);
+ checkCryptParameters();
+ data[index] = CryptTool.cipher(symmetricKey, password, element);
};
/**
@@ -2798,6 +3202,36 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
$.extend(data, newData);
};
+ /**
+ * Helper, which parses shows a general error message based on the result of the Uploader
+ *
+ * @name Uploader.parseUploadError
+ * @function
+ * @param {int} status
+ * @param {object} data
+ * @param {string} doThisThing - a human description of the action, which was tried
+ * @return {array}
+ */
+ me.parseUploadError = function(status, data, doThisThing) {
+ var errorArray = ['Error while parsing error message.'];
+
+ switch (status) {
+ case Uploader.error['custom']:
+ errorArray = ['Could not ' + doThisThing + ': %s', data.message];
+ break;
+ case Uploader.error['unknown']:
+ errorArray = ['Could not ' + doThisThing + ': %s', I18n._('unknown status')];
+ break;
+ case Uploader.error['serverError']:
+ errorArray = ['Could not ' + doThisThing + ': %s', I18n._('server error or not responding')]; break;
+ default:
+ errorArray = ['Could not ' + doThisThing + ': %s', I18n._('unknown error')];
+ break;
+ }
+
+ return errorArray;
+ }
+
/**
* init Uploader
*
@@ -2815,6 +3249,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* (controller) Responsible for encrypting paste and sending it to server.
*
+ * Does upload, encryption is done transparently by Uploader.
+ *
* @name state
* @class
*/
@@ -2851,12 +3287,12 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
/**
- * called after successful upload
+ * called after successful paste upload
*
* @private
* @function
* @param {int} status
- * @param {int} data
+ * @param {object} data
*/
function showCreatedPaste(status, data) {
Alert.hideLoading();
@@ -2881,6 +3317,27 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
PasteViewer.run();
}
+ /**
+ * called after successful comment upload
+ *
+ * @private
+ * @function
+ * @param {int} status
+ * @param {object} data
+ */
+ function showUploadedComment(status, data) {
+ // show success message
+ // Alert.showStatus('Comment posted.');
+
+ // reload paste
+ Controller.refreshPaste(function () {
+ // highlight sent comment
+ DiscussionViewer.highlightComment(data.id, true);
+ // reset error handler
+ Alert.setCustomHandler(null);
+ });
+ }
+
/**
* adds attachments to the Uploader
*
@@ -2929,84 +3386,71 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
*
* @name PasteEncrypter.sendComment
* @function
- * @param {Event} event
- * @TODO WIP
*/
- me.sendComment = function(event)
+ me.sendComment = function()
{
- event.preventDefault();
- $errorMessage.addClass('hidden');
- // do not send if no data
- var replyMessage = $('#replymessage');
- if (replyMessage.val().length === 0)
- {
+ Alert.hideMessages();
+ Alert.setCustomHandler(DiscussionViewer.handleNotification);
+
+ // UI loading state
+ TopNav.hideViewButtons();
+ Alert.showLoading('Sending comment…', 0, 'cloud-upload');
+
+ // get data
+ var [plainText, nickname] = DiscussionViewer.getReplyData(),
+ parentid = DiscussionViewer.getReplyCommentId();
+
+ // do not send if there is no data
+ if (plainText.length === 0) {
+ // revert loading status…
+ Alert.hideLoading();
+ Alert.setCustomHandler(null);
+ TopNav.showViewButtons();
return;
}
- me.showStatus(I18n._('Sending comment...'), true);
- var parentid = event.data.parentid,
- key = Model.getPasteKey(),
- cipherdata = CryptTool.cipher(key, $passwordInput.val(), replyMessage.val()),
- ciphernickname = '',
- nick = $('#nickname').val();
- if (nick.length > 0)
- {
- ciphernickname = CryptTool.cipher(key, $passwordInput.val(), nick);
+ // check entropy
+ if (!checkRequirements(function () {
+ me.sendComment();
+ })) {
+ return; // to prevent multiple executions
}
- var dataToSend = {
- data: cipherdata,
- parentid: parentid,
- pasteid: Model.getPasteId(),
- nickname: ciphernickname
- };
+ Alert.showLoading(null, 10);
- $.ajax({
- type: 'POST',
- url: Helper.baseUri(),
- data: dataToSend,
- dataType: 'json',
- headers: ajaxHeaders,
- success: function(data) {
- if (data.status === 0)
- {
- status.showStatus(I18n._('Comment posted.'));
- $.ajax({
- type: 'GET',
- url: Helper.baseUri() + '?' + Model.getPasteId(),
- dataType: 'json',
- headers: ajaxHeaders,
- success: function(data) {
- if (data.status === 0)
- {
- me.displayMessages(data);
- }
- else if (data.status === 1)
- {
- Alert.showError(['Could not refresh display: %s', data.message]);
- }
- else
- {
- Alert.showError(['Could not refresh display: %s', I18n._('unknown status')]);
- }
- }
- })
- .fail(function() {
- Alert.showError(['Could not refresh display: %s', I18n._('server error or not responding')]);
- });
- }
- else if (data.status === 1)
- {
- Alert.showError(['Could not post comment: %s', data.message]);
- }
- else
- {
- Alert.showError(['Could not post comment: %s', I18n._('unknown status')]);
- }
- }
- })
- .fail(function() {
- Alert.showError(['Could not post comment: %s', I18n._('server error or not responding')]);
+ // prepare Uploader
+ Uploader.prepare();
+ Uploader.setCryptParameters(Prompt.getPassword(), Model.getPasteKey());
+
+ // set success/fail functions
+ Uploader.setSuccess(showUploadedComment);
+ Uploader.setFailure(function (status, data) {
+ // revert loading status…
+ Alert.hideLoading();
+ Alert.setCustomHandler(null);
+ TopNav.showViewButtons();
+
+ // show error message
+ Alert.showError(Uploader.parseUploadError(status, data, 'post comment'));
});
+
+ // fill it with unencrypted params
+ Uploader.setUnencryptedData('pasteid', Model.getPasteId());
+ if (typeof parentid === 'undefined') {
+ // if parent id is not set, this is the top-most comment, so use
+ // paste id as parent @TODO is this really good?
+ Uploader.setUnencryptedData('parentid', Model.getPasteId());
+ } else {
+ Uploader.setUnencryptedData('parentid', parentid);
+ }
+
+ // encrypt data
+ Uploader.setData('data', plainText);
+
+ if (nickname.length > 0) {
+ Uploader.setData('nickname', nickname);
+ }
+
+ Uploader.run();
};
/**
@@ -3018,7 +3462,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.submitPaste = function()
{
// hide previous (error) messages
- Alert.hideMessages();
+ Controller.hideStatusMessages();
// UI loading state
TopNav.hideCreateButtons();
@@ -3048,7 +3492,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
}
// prepare Uploader
- Uploader.prepare(TopNav.getPassword());
+ Uploader.prepare();
+ Uploader.setCryptParameters(TopNav.getPassword());
// set success/fail functions
Uploader.setSuccess(showCreatedPaste);
@@ -3058,20 +3503,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
TopNav.showCreateButtons();
// show error message
- switch (status) {
- case Uploader.error['custom']:
- Alert.showError(['Could not create paste: %s', data.message]);
- break;
- case Uploader.error['unknown']:
- Alert.showError(['Could not create paste: %s', I18n._('unknown status')]);
- break;
- case Uploader.error['serverError']:
- Alert.showError(['Could not create paste: %s', I18n._('server error or not responding')]);
- break;
- default:
- Alert.showError(['Could not create paste: %s', I18n._('unknown error')]);
- break;
- }
+ Alert.showError(Uploader.parseUploadError(status, data, 'create paste'));
});
// fill it with unencrypted submitted options
@@ -3116,12 +3548,59 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* (controller) Responsible for decrypting cipherdata and passing data to view.
*
+ * Only decryption, no download.
+ *
* @name state
* @class
*/
var PasteDecrypter = (function () {
var me = {};
+ /**
+ * decrypt data or prompts for password in cvase of failure
+ *
+ * @private
+ * @function
+ * @param {string} key
+ * @param {string} password - optional, may be an empty string
+ * @param {string} cipherdata
+ * @throws {string}
+ * @return {false|string} - false, when unsuccessful or string (decrypted data)
+ */
+ function decryptOrPromptPassword(key, password, cipherdata)
+ {
+ // try decryption without password
+ var plaindata = CryptTool.decipher(key, password, cipherdata);
+
+ // if it fails, request password
+ if (plaindata.length === 0 && password.length === 0) {
+ // try to get cached password first
+ password = Prompt.getPassword();
+
+ // if password is there, re-try
+ if (password.length !== 0) {
+ // recursive
+ // note: an infinite loop is prevented as the previous if
+ // clause checks whether a password is already set and ignores
+ // errors when a password has been passed
+ return decryptOrPromptPassword.apply(arguments);
+ }
+
+ // trigger password request
+ Prompt.requestPassword();
+ // the callback (via setPasswordCallback()) should have been set
+ // by a parent function
+ return false;
+ }
+
+ // if all tries failed, we can only return an error
+ if (plaindata.length === 0) {
+ throw 'failed to decipher data';
+ }
+
+ return plaindata;
+ }
+
/**
* decrypt the actual paste text
*
@@ -3130,45 +3609,24 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* @param {object} paste - paste data in object form
* @param {string} key
* @param {string} password
+ * @param {bool} ignoreError - ignore decryption errors iof set to true
* @return {bool} - whether action was successful
+ * @throws {string}
*/
- function decryptPaste(paste, key, password)
+ function decryptPaste(paste, key, password, ignoreError)
{
- // try decryption without password
- var plaintext = CryptTool.decipher(key, password, paste.data);
-
- // if it fails, request password
- if (plaintext.length === 0 && password.length === 0) {
- // get password
+ var plaintext
+ if (ignoreError === true) {
+ plaintext = CryptTool.decipher(key, password, paste.data);
+ } else {
try {
- console.log('bef');
- password = Prompt.getPassword();
- console.log('pass');
- } catch (e) {
- console.error(e);
+ plaintext = decryptOrPromptPassword(key, password, paste.data);
+ } catch (err) {
+ throw 'failed to decipher paste text: ' + err
+ }
+ if (plaintext === false) {
return false;
}
-
-
- // if password is there, re-try
- if (password.length !== 0) {
- // recursive
- // note: an infinite loop is prevented as the previous if
- // clause checks whether a password is already set and ignores
- // error with password being passed
- return decryptPaste(paste, key, password);
- }
-
- // trigger password request
- Prompt.requestPassword();
- // the callback (via setPasswordCallback()) should have been set
- // by parent function
- return false;
- }
-
- // if all tries failed, we can only throw an error
- if (plaintext.length === 0) {
- throw 'failed to decipher message';
}
// on success show paste
@@ -3189,27 +3647,68 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* @param {string} key
* @param {string} password
* @return {bool} - whether action was successful
+ * @throws {string}
*/
function decryptAttachment(paste, key, password)
{
// decrypt attachment
- var attachment = CryptTool.decipher(key, password, paste.attachment);
- if (attachment.length === 0) {
- throw 'failed to decipher attachment';
+ try {
+ var attachment = decryptOrPromptPassword(key, password, paste.attachment);
+ } catch (err) {
+ throw 'failed to decipher attachment: ' + err
+ }
+ if (attachment === false) {
+ return false;
}
// decrypt attachment name
var attachmentName;
if (paste.attachmentname) {
- attachmentName = attachmentName = CryptTool.decipher(key, password, paste.attachmentname);
- if (attachmentName.length === 0) {
- // @TODO considering the buggy cloning (?, see other todo comment) this might affect previous pastes
- throw 'failed to decipher attachment name';
+ try {
+ var attachmentName = decryptOrPromptPassword(key, password, paste.attachmentname);
+ } catch (err) {
+ throw 'failed to decipher attachment name: ' + err
+ }
+ if (attachmentName === false) {
+ return false;
}
}
AttachmentViewer.setAttachment(attachment, attachmentName);
AttachmentViewer.showAttachment();
+
+ return true;
+ }
+
+ /**
+ * decrypts all comments and shows them
+ *
+ * @private
+ * @function
+ * @param {object} paste - paste data in object form
+ * @param {string} key
+ * @param {string} password
+ * @return {bool} - whether action was successful
+ */
+ function decryptComments(paste, key, password)
+ {
+ // remove potentially previous discussion
+ DiscussionViewer.prepareNewDisucssion();
+
+ // iterate over comments
+ for (var i = 0; i < paste.comments.length; ++i) {
+ var comment = paste.comments[i];
+
+ DiscussionViewer.addComment(
+ comment,
+ CryptTool.decipher(key, password, comment.data),
+ CryptTool.decipher(key, password, comment.meta.nickname)
+ );
+ }
+
+ DiscussionViewer.finishDiscussion();
+ DiscussionViewer.showDiscussion();
+ return true;
}
/**
@@ -3241,22 +3740,28 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
me.run(paste);
});
- // try to decrypt paste and if it fails (because the password is
- // missing) return to let JS continue and wait for user
- if (!decryptPaste(paste, key, password)) {
- return;
- }
-
// decrypt attachments
if (paste.attachment) {
- decryptAttachment(paste, key, password);
+ // try to decrypt paste and if it fails (because the password is
+ // missing) return to let JS continue and wait for user
+ if (!decryptAttachment(paste, key, password)) {
+ return;
+ }
}
+
+ // Deliberately ignores non-critical errors as this decryption
+ // can also return an empty string and when this is done, the
+ // decryption routine cannot differenciate this to an error.
+ // As, however, the attachment could already be decrypted we
+ // can continue here without showing an error, but just an empty
+ // paste text.
+ decryptPaste(paste, key, password, true);
} catch(err) {
Alert.hideLoading();
// log and show error
console.error(err);
- Alert.showError('Could not decrypt data (Wrong key?)'); // @TODO error is not translated
+ Alert.showError('Could not decrypt data (Wrong key?)');
// still go on to potentially show potentially partially decrypted data
}
@@ -3265,73 +3770,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
PasteStatus.showRemainingTime(paste.meta);
// if the discussion is opened on this paste, display it
- // @TODO BELOW
if (paste.meta.opendiscussion) {
- $comments.html('');
-
- var $divComment;
-
- // iterate over comments
- for (var i = 0; i < paste.comments.length; ++i)
- {
- var $place = $comments,
- comment = paste.comments[i],
- commentText = CryptTool.decipher(key, password, comment.data),
- $parentComment = $('#comment_' + comment.parentid);
-
- $divComment = $('