diff --git a/static/css/style.css b/static/css/style.css
index fc551ba..0671396 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -165,3 +165,20 @@ text-decoration:underline;
display:none;
}
+/** Progress bar */
+
+.progress {
+ display:none;
+}
+
+.progress .bar {
+ width: 25%;
+ text-indent: 10px;
+ text-align:left;
+}
+
+-webkit-transition: width 0.1s ease;
+-moz-transition: width 0.1s ease;
+-ms-transition: width 0.1s ease;
+-o-transition: width 0.1s ease;
+transition: width 0.1s ease;
\ No newline at end of file
diff --git a/static/js/behavior.js b/static/js/behavior.js
index c8d5c52..9d64b81 100644
--- a/static/js/behavior.js
+++ b/static/js/behavior.js
@@ -5,24 +5,119 @@ sjcl.random.startCollectors();
/* Ensure jquery use cache for ajax requests */
$.ajaxSetup({ cache: true });
-
-
zerobin = {
- encrypt: function(key, content) {
- content = sjcl.codec.base64.fromBits(sjcl.codec.utf8String.toBits(content));
- return sjcl.encrypt(key, lzw.compress(content));
+
+ /** Base64 + compress + encrypt, with callbacks before each operation,
+ and all of them are executed in a timed continuation to give
+ a change to the UI to respond.
+ */
+ encrypt: function(key, content, toBase64Callback,
+ compressCallback, encryptCallback, doneCallback) {
+
+ setTimeout (function(){
+
+ content = sjcl.codec.utf8String.toBits(content);
+ if (toBase64Callback) {toBase64Callback()}
+
+ setTimeout(function(){
+
+ content = sjcl.codec.base64.fromBits(content);
+ if (compressCallback) {compressCallback()}
+
+ setTimeout(function(){
+
+ content = lzw.compress(content);
+ if (encryptCallback) {encryptCallback()}
+
+ setTimeout(function(){
+ content = sjcl.encrypt(key, content);
+ if (doneCallback) {doneCallback(content)}
+ }, 250);
+
+ }, 250);
+
+ }, 250);
+
+ }, 250);
},
- decrypt: function(key, content) {
- content = lzw.decompress(sjcl.decrypt(key, content));
- return sjcl.codec.utf8String.fromBits(sjcl.codec.base64.toBits(content));
+
+ /** Base64 decoding + uncompress + decrypt, with callbacks before each operation,
+ and all of them are executed in a timed continuation to give
+ a change to the UI to respond.
+
+ This is where using a library to fake synchronicity could start to be
+ useful, this code is starting be difficult to read. If anyone read this
+ and got a suggestion, by all means, speak your mind.
+ */
+ decrypt: function(key, content, errorCallback, uncompressCallback,
+ fromBase64Callback, toStringCallback, doneCallback) {
+
+ /* Decrypt */
+ setTimeout(function(){
+
+ try {
+
+ content = sjcl.decrypt(key, content);
+ if (uncompressCallback) {uncompressCallback()}
+
+ /* Decompress */
+ setTimeout(function(){
+
+ try {
+
+ content = lzw.decompress(content);
+ if (fromBase64Callback) {fromBase64Callback()}
+
+ /* From base 64 to bits */
+ setTimeout(function(){
+
+ try {
+
+ content = sjcl.codec.base64.toBits(content);
+ if (toStringCallback) {toStringCallback()}
+
+ /* From bits to string */
+ setTimeout(function(){
+
+ try {
+ content = sjcl.codec.utf8String.fromBits(content);
+ if (doneCallback) {doneCallback(content)}
+ } catch (err) {
+ errorCallback(err);
+ }
+
+ }, 250); /* "End of from bits to string" */
+
+ } catch (err) {
+ errorCallback(err);
+ }
+
+ }, 250); /* End of "from base 64 to bits" */
+
+ } catch (err) {
+ errorCallback(err);
+ }
+
+ }, 250); /* End of "decompress" */
+
+ } catch (err) {
+ errorCallback(err);
+ }
+
+ }, 250); /* End of "decrypt" */
},
+
+ /** Create a random base64 string long enought to be suitable as
+ an encryption key */
make_key: function() {
return sjcl.codec.base64.fromBits(sjcl.random.randomWords(8, 0), 0);
},
+
get_date: function(){
var date = new Date();
return date.getDate()+"-"+(date.getMonth()+1)+"-"+date.getFullYear();
},
+
get_time: function(){
var date = new Date();
var h=date.getHours();
@@ -33,9 +128,11 @@ zerobin = {
if (s<10) {s = "0" + s}
return h+":"+m+":"+s;
},
- numOrdA: function(a, b){
- return (a-b);
+
+ numOrdA: function(a, b){
+ return (a-b);
},
+
get_keys: function(){
var keys = new Array();
for(i=0; i<=localStorage.length; i++){
@@ -44,6 +141,7 @@ zerobin = {
}
return keys.sort(zerobin.numOrdA);
},
+
/** Get a tinyurl using JSONP */
getTinyURL: function(longURL, success) {
@@ -56,6 +154,7 @@ zerobin = {
var api = 'http://json-tinyurl.appspot.com/?url=';
$.getJSON(api + encodeURIComponent(longURL) + '&callback=' + callback);
},
+
support_localstorage: function(){
if (localStorage){
return true;
@@ -63,6 +162,7 @@ zerobin = {
return false;
}
},
+
store_paste: function(url){
if (zerobin.support_localstorage){
var date = new Date();
@@ -77,9 +177,10 @@ zerobin = {
localStorage.setItem(keys.reverse()[0]+1, paste);
}
},
+
get_pastes: function(){
if (zerobin.support_localstorage){
- var pastes = '';
+ var pastes = '';
var keys = zerobin.get_keys();
keys.reverse();
@@ -94,7 +195,7 @@ zerobin = {
var on_at = 'on ';
}
pastes = pastes + '
' + on_at + display_date + '';
- }
+ }
if (!pastes){
return 'Your previous pastes will be saved in your browser using localStorage.';
}
@@ -103,7 +204,8 @@ zerobin = {
return 'Sorry your browser does not support LocalStorage, We cannot display your previous pastes.';
}
},
- get_paste_content: function(){
+
+ getPasteContent: function(){
var content_clone = '' ;
$("#paste-content li").each(function(index) {
content_clone = content_clone + $(this).text() + '\n';
@@ -127,73 +229,137 @@ $('button[type=submit]').live("click", function(e){
var paste = $('textarea').val();
if (paste.trim()) {
- var expiration = $('#expiration').val();
- var key = zerobin.make_key();
- var data = {content: zerobin.encrypt(key, paste), expiration: expiration}
- $.post('/paste/create', data)
- .error(function(error) {
- alert('Paste could not be saved. Please try again later.');
- })
- .success(function(data) {
- var paste_url = '/paste/' + data['paste'] + '#' + key;
- window.location = (paste_url);
- zerobin.store_paste(paste_url);
- });
+ $('form.well p').hide();
+ $loading = $('form.well .progress').show();
+ var $loading = $('form.well .progress .bar')
+ .css('width', '25%')
+ .text('Converting paste to bits...');
+
+ /* Encode, compress, encrypt and send the paste then redirect the user
+ to the new paste. We ensure a loading animation is updated
+ during the process by passing callbacks.
+ */
+ try {
+
+ var expiration = $('#expiration').val();
+ var key = zerobin.make_key();
+
+ zerobin.encrypt(key, paste,
+
+ function(){$loading.text('Encoding to base64...').css('width', '45%')},
+ function(){$loading.text('Compressing...').css('width', '65%')},
+ function(){$loading.text('Encrypting...').css('width', '85%')},
+
+ /* This block deal with sending the data, redirection or error handling */
+ function(content){
+
+ $loading.text('Sending...').css('width', '95%');
+ var data = {content: content, expiration: expiration};
+
+ $.post('/paste/create', data)
+ .error(function(error) {
+ $('form.well p').show();
+ $loading.hide();
+ alert('Error: paste could not be saved. Please try again later.');
+ })
+ .success(function(data) {
+ $loading.text('Redirecting to new paste...').css('width', '100%');
+ var paste_url = '/paste/' + data['paste'] + '#' + key;
+ zerobin.store_paste(paste_url);
+ window.location = (paste_url);
+ });
+ }
+ );
+ } catch (err) {
+ $('form.well p').show();
+ $loading.hide();
+ alert('Error: paste could not be encrypted. Aborting.');
+ }
}
});
-/** On the display paste page.
- Decrypt and decompress the paste content, add syntax coloration then
- setup the copy to clipboard button.
+/**
+ DECRYPTION:
+ On the display paste page, decrypt and decompress the paste content,
+ add syntax coloration then setup the copy to clipboard button.
*/
var content = $('#paste-content').text().trim();
var key = window.location.hash.substring(1);
var error = false;
if (content && key) {
- try {
- $('#paste-content').text(zerobin.decrypt(key, content));
- } catch(err) {
- error = true;
- alert('Could not decrypt data (Wrong key ?)');
- }
- content = '';
+ var $bar = $('.well form .progress').show();
+ var $loading = $('.well form .progress .bar').css('width', '25%')
+ .text('Decrypting paste...');
- if (!error) {
+ zerobin.decrypt(key, content,
- $('#short-url').click(function(e) {
- e.preventDefault();
- $('#short-url').text('Loading short url...');
- zerobin.getTinyURL(window.location.toString(), function(tinyurl){
- clip.setText(tinyurl);
- $('#copy-success').hide();
- $('#short-url-success')
- .html('Short url: ' + tinyurk + '')
- .show('fadeUp');
- $('#short-url').text('Get short url');
+ /* On error*/
+ function(){
+ $bar.hide();
+ alert('Could not decrypt data (Wrong key ?)');
+ },
+
+ /* Update progress bar */
+ function(){$loading.text('Decompressing...').css('width', '45%')},
+ function(){$loading.text('Base64 decoding...').css('width', '65%')},
+ function(){$loading.text('From bits to string...').css('width', '85%')},
+
+ /* When done */
+ function(content){
+
+ /* Decrypted content goes back to initial container*/
+ $('#paste-content').text(content);
+ content = '';
+
+ $loading.text('Code coloration...').css('width', '95%');
+
+ /* Add a continuation to let the UI redraw */
+ setTimeout(function(){
+
+ /* Setup link to get the paste short url*/
+ $('#short-url').click(function(e) {
+ e.preventDefault();
+ $('#short-url').text('Loading short url...');
+ zerobin.getTinyURL(window.location.toString(), function(tinyurl){
+ clip.setText(tinyurl);
+ $('#copy-success').hide();
+ $('#short-url-success')
+ .html('Short url: ' + tinyurk + '')
+ .show('fadeUp');
+ $('#short-url').text('Get short url');
+ });
});
- });
- prettyPrint();
+ /* Setup flash clipboard button */
+ ZeroClipboard.setMoviePath('/static/js/ZeroClipboard.swf' );
- /* Setup flash clipboard button */
- ZeroClipboard.setMoviePath('/static/js/ZeroClipboard.swf' );
+ var clip = new ZeroClipboard.Client();
+ clip.addEventListener('mouseup', function(){
+ clip.setText(zerobin.getPasteContent());
+ });
+ clip.addEventListener('complete', function(){
+ $('#copy-success').show('fadeUp', function(){clip.reposition()});
+ });
+ clip.glue('clip-button');
- var clip = new ZeroClipboard.Client();
- clip.addEventListener('mouseup', function(){
- clip.setText(zerobin.get_paste_content());
- });
- clip.addEventListener('complete', function(){
- $('#copy-success').show('fadeUp', function(){clip.reposition()});
- });
- clip.glue('clip-button');
+ window.onresize = clip.reposition;
+
+ /** Syntaxic coloration */
+ prettyPrint();
+
+ /* Display result */
+ $loading.text('Done').css('width', '100%');
+ $bar.hide();
+ }, 250);
- window.onresize = clip.reposition;
}
+ );
+
+} /* End of "DECRYPTION" */
-}
/* Synchronize expiration select boxes value */
$('.paste-option select').live('change', function(){
@@ -201,11 +367,9 @@ $('.paste-option select').live('change', function(){
$('.paste-option select').val(value);
});
-
/* Resize Textarea according to content */
$('#content').elastic();
-
/* Display bottom paste option buttons when needed */
$('#content').live('keyup change', function(){
if($('#content').height() < 400 ){
@@ -221,33 +385,14 @@ $('#content').live('keyup change', function(){
/* Display previous pastes */
$('.previous-pastes .items').html(zerobin.get_pastes());
-
-
/* clone a paste */
$('.btn-clone').click(function(e){
e.preventDefault();
- var content_clone = zerobin.get_paste_content();
+ var content_clone = zerobin.getPasteContent();
$('.submit-form').show();
$('.paste-form').remove();
$('#content').val(content_clone);
$('#content').trigger('change');
-
});
-});
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+}); /* End of "document ready" jquery callback */
diff --git a/views/base.tpl b/views/base.tpl
index d6e6d04..1eb3ae0 100644
--- a/views/base.tpl
+++ b/views/base.tpl
@@ -9,7 +9,7 @@
pastebin featuring burn after reading, an history and
a clipboard">
-
+
diff --git a/views/home.tpl b/views/home.tpl
index ca70732..48bd6bc 100644
--- a/views/home.tpl
+++ b/views/home.tpl
@@ -14,6 +14,9 @@
class="input-xlarge"
id="content" name="content">
+
diff --git a/views/paste.tpl b/views/paste.tpl
index 5cc1942..057d6c2 100644
--- a/views/paste.tpl
+++ b/views/paste.tpl
@@ -33,6 +33,10 @@
+
+