diff --git a/js/privatebin.js b/js/privatebin.js index 4589526f..fcdf160f 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -137,10 +137,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * @function * @param {jQuery} $element - a jQuery element * @param {string} text - the text to enter - * @TODO check for XSS attacks, usually no CSS can prevent them so this looks weird on the first look */ me.setElementText = function($element, text) { + // @TODO: Can we drop IE 10 support? This function looks crazy and checking oldienotice slows everything down… + // I cannot really say, whether this IE10 method is XSS-safe… // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this... if ($('#oldienotice').is(':visible')) { var html = me.htmlEntities(text).replace(/\n/ig, '\r\n
'); @@ -662,6 +663,32 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { return $cipherData.text(); }; + /** + * returns the expiration set in the HTML + * + * @name modal.getExpirationDefault + * @function + * @return string + * @TODO the template can be simplified as #pasteExpiration is no longer modified (only default value) + */ + me.getExpirationDefault = function() + { + return $('#pasteExpiration').val(); + }; + + /** + * returns the format set in the HTML + * + * @name modal.getFormatDefault + * @function + * @return string + * @TODO the template can be simplified as #pasteFormatter is no longer modified (only default value) + */ + me.getFormatDefault = function() + { + return $('#pasteFormatter').val(); + }; + /** * init navigation manager * @@ -698,293 +725,10 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $pasteUrl, $prettyMessage, $prettyPrint, - $preview, + $editorTabs, $remainingTime, $replyStatus; - /** - * use given format on paste, defaults to plain text - * - * @name controller.formatPaste - * @function - * @param {string} format - * @param {string} text - */ - me.formatPaste = function(format, text) - { - helper.setElementText($clearText, text); - helper.setElementText($prettyPrint, text); - - switch (format || 'plaintext') { - case 'markdown': - // silently fail if showdown is not available - // @TODO: maybe better show an error message? At least a warning? - if (typeof showdown === 'object') - { - var converter = new showdown.Converter({ - strikethrough: true, - tables: true, - tablesHeaderId: true - }); - $clearText.html( - converter.makeHtml(text) - ); - // add table classes from bootstrap css - $clearText.find('table').addClass('table-condensed table-bordered'); - - $clearText.removeClass('hidden'); - } else { - console.error('showdown is not loaded, could not parse Markdown'); - } - $prettyMessage.addClass('hidden'); - break; - case 'syntaxhighlighting': - // silently fail if prettyprint is not available - // @TODO: maybe better show an error message? At least a warning? - if (typeof prettyPrintOne === 'function') - { - if (typeof prettyPrint === 'function') - { - prettyPrint(); - } - $prettyPrint.html( - prettyPrintOne( - helper.htmlEntities(text), null, true - ) - ); - } else { - console.error('pretty print is not loaded, could not link '); - } - // fall through, as the rest is the same - default: // = 'plaintext' - // convert URLs to clickable links - helper.urls2links($clearText); - helper.urls2links($prettyPrint); - $clearText.addClass('hidden'); - - - $prettyPrint.css('white-space', 'pre-wrap'); - $prettyPrint.css('word-break', 'normal'); - $prettyPrint.removeClass('prettyprint'); - - $prettyMessage.removeClass('hidden'); - } - }; - - /** - * show decrypted text in the display area, including discussion (if open) - * - * @name controller.displayMessages - * @function - * @param {Object} [paste] - (optional) object including comments to display (items = array with keys ('data','meta')) - */ - me.displayMessages = function(paste) - { - paste = paste || $.parseJSON(modal.getCipherData()); - var key = helper.pageKey(), - password = $passwordInput.val(); - if (!$prettyPrint.hasClass('prettyprinted')) { - // Try to decrypt the paste. - try - { - if (paste.attachment) - { - var attachment = cryptTool.decipher(key, password, paste.attachment); - if (attachment.length === 0) - { - if (password.length === 0) - { - me.requestPassword(); - return; - } - attachment = cryptTool.decipher(key, password, paste.attachment); - } - if (attachment.length === 0) - { - throw 'failed to decipher attachment'; - } - - if (paste.attachmentname) - { - var attachmentname = cryptTool.decipher(key, password, paste.attachmentname); - if (attachmentname.length > 0) - { - $attachmentLink.attr('download', attachmentname); - } - } - $attachmentLink.attr('href', attachment); - $attachment.removeClass('hidden'); - - // if the attachment is an image, display it - var imagePrefix = 'data:image/'; - if (attachment.substring(0, imagePrefix.length) === imagePrefix) - { - $image.html( - $(document.createElement('img')) - .attr('src', attachment) - .attr('class', 'img-thumbnail') - ); - $image.removeClass('hidden'); - } - } - var cleartext = cryptTool.decipher(key, password, paste.data); - if (cleartext.length === 0 && password.length === 0 && !paste.attachment) - { - me.requestPassword(); - return; - } - if (cleartext.length === 0 && !paste.attachment) - { - throw 'failed to decipher message'; - } - - $passwordInput.val(password); - if (cleartext.length > 0) - { - $('#pasteFormatter').val(paste.meta.formatter); - me.formatPaste(paste.meta.formatter, cleartext); - } - } - catch(err) - { - me.stateOnlyNewPaste(); - me.showError(i18n._('Could not decrypt data (Wrong key?)')); - return; - } - } - - // display paste expiration / for your eyes only - if (paste.meta.expire_date) - { - var expiration = helper.secondsToHuman(paste.meta.remaining_time), - expirationLabel = [ - 'This document will expire in %d ' + expiration[1] + '.', - 'This document will expire in %d ' + expiration[1] + 's.' - ]; - helper.appendMessage($remainingTime, i18n._(expirationLabel, expiration[0])); - $remainingTime.removeClass('foryoureyesonly') - .removeClass('hidden'); - } - if (paste.meta.burnafterreading) - { - // unfortunately many web servers don't support DELETE (and PUT) out of the box - $.ajax({ - type: 'POST', - url: helper.scriptLocation() + '?' + helper.pasteId(), - data: {deletetoken: 'burnafterreading'}, - dataType: 'json', - headers: headers - }) - .fail(function() { - controller.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.')); - }); - helper.appendMessage($remainingTime, i18n._( - 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.' - )); - $remainingTime.addClass('foryoureyesonly') - .removeClass('hidden'); - // discourage cloning (as it can't really be prevented) - $cloneButton.addClass('hidden'); - } - - // if the discussion is opened on this paste, display it - 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 = $('
' - + '
' - + '
' - + '
'); - var $divCommentData = $divComment.find('div.commentdata'); - - // if parent comment exists - if ($parentComment.length) - { - // shift comment to the right - $place = $parentComment; - } - $divComment.find('button').click({commentid: comment.id}, me.openReply); - helper.setElementText($divCommentData, commentText); - helper.urls2links($divCommentData); - - // try to get optional nickname - var nick = cryptTool.decipher(key, password, comment.meta.nickname); - if (nick.length > 0) - { - $divComment.find('span.nickname').text(nick); - } - else - { - divComment.find('span.nickname').html('' + i18n._('Anonymous') + ''); - } - $divComment.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) - { - $divComment.find('span.nickname') - .before( - ' ' - ); - } - - $place.append($divComment); - } - - // add 'add new comment' area - $divComment = $( - '
' - ); - $divComment.find('button').click({commentid: helper.pasteId()}, me.openReply); - $comments.append($divComment); - $discussion.removeClass('hidden'); - } - }; - - /** - * open the comment entry when clicking the "Reply" button of a comment - * - * @name controller.openReply - * @function - * @param {Event} event - */ - me.openReply = function(event) - { - event.preventDefault(); - - // remove any other reply area - $('div.reply').remove(); - - 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(); - }; - /** * handle history (pop) state changes * @@ -1052,25 +796,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $('#noscript').hide(); // preload jQuery elements - $clearText = $('#cleartext'); - $clonedFile = $('#clonedfile'); - $comments = $('#comments'); - $discussion = $('#discussion'); - $image = $('#image'); $pasteResult = $('#pasteresult'); // $pasteUrl is saved in sendDataContinue() if/after it is // actually created - $prettyMessage = $('#prettymessage'); - $prettyPrint = $('#prettyprint'); - $remainingTime = $('#remainingtime'); // bind events $('.reloadlink').click(me.reloadPage); - // bootstrap template drop downs - $('ul.dropdown-menu li a', $('#expiration').parent()).click(me.setExpiration); - $('ul.dropdown-menu li a', $('#formatter').parent()).click(me.setFormat); - $(window).on('popstate', me.historyChange); }; @@ -1138,15 +870,8 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { if (!preview) { - // no "clone" for IE<10. - if ($('#oldienotice').is(":visible")) - { - $cloneButton.addClass('hidden'); - } - else - { - $cloneButton.removeClass('hidden'); - } + + console.log('show no preview'); } @@ -1386,7 +1111,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { var $message, $messageEdit, $messagePreview, - $preview; + $editorTabs; + + var isPreview = false; /** * support input of tab character @@ -1421,16 +1148,27 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { * * @name editor.viewEditor * @function - * @param {Event} event + * @param {Event} event - optional */ function viewEditor(event) { - $messagePreview.parent().removeClass('active'); - $messageEdit.parent().addClass('active'); - $message.focus(); - me.stateNewPaste(); + // toggle buttons + $messageEdit.addClass('active'); + $messagePreview.removeClass('active'); - event.preventDefault(); + pasteViewer.hide(); + + // reshow input + $message.removeClass('hidden'); + + me.focusInput(); + // me.stateNewPaste(); + + // finish + isPreview = false; + // if (typeof event === 'undefined') { + // event.preventDefault(); + // } // @TODO confirm this is not needed } /** @@ -1442,13 +1180,33 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function viewPreview(event) { - $messageEdit.parent().removeClass('active'); - $messagePreview.parent().addClass('active'); - $message.focus(); - me.stateExistingPaste(true); - me.formatPaste($('#pasteFormatter').val(), $message.val()); + // toggle buttons + $messageEdit.removeClass('active'); + $messagePreview.addClass('active'); - event.preventDefault(); + // hide input as now preview is shown + $message.addClass('hidden'); + + // show preview + pasteViewer.setText($message.val()); + pasteViewer.trigger(); + + // finish + isPreview = true; + // if (typeof event === 'undefined') { + // event.preventDefault(); + // } // @TODO confirm this is not needed + } + + /** + * get the state of the preview + * + * @name editor.isPreview + * @function + */ + me.isPreview = function() + { + return isPreview; } /** @@ -1459,6 +1217,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ me.resetInput = function() { + // go back to input + if (isPreview) { + viewEditor(); + } + // clear content $message.val(''); }; @@ -1472,12 +1235,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.show = function() { $message.removeClass('hidden'); - $preview.removeClass('hidden'); - // $clearText ?? - // $discussion.removeClass('hidden'); - // $pasteResult.removeClass('hidden'); //?? - // $prettyMessage.removeClass('hidden'); - // $remainingTime.removeClass('hidden'); + $editorTabs.removeClass('hidden'); }; /** @@ -1489,11 +1247,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.hide = function() { $message.addClass('hidden'); - $preview.addClass('hidden'); - // $discussion.addClass('hidden'); - // $pasteResult.addClass('hidden'); - // $prettyMessage.addClass('hidden'); - // $remainingTime.addClass('hidden'); + $editorTabs.addClass('hidden'); }; /** @@ -1507,6 +1261,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $message.focus(); }; + /** + * returns the current text + * + * @name editor.getText + * @function + * @return {string} + */ + me.getText = function() + { + return $message.val() + }; + /** * init status manager * @@ -1518,14 +1284,458 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { me.init = function() { $message = $('#message'); - $messageEdit = $('#messageedit'); - $messagePreview = $('#messagepreview'); - $preview = $('#preview'); + $editorTabs = $('#editorTabs'); // bind events $message.keydown(supportTabs); - $messageEdit.click(viewEditor); - $messagePreview.click(viewPreview); + + // bind click events to tab switchers (a), but save parent of them + // (li) + $messageEdit = $('#messageedit').click(viewEditor).parent(); + $messagePreview = $('#messagepreview').click(viewPreview).parent(); + }; + + return me; + })(window, document); + + + /** + * Parse and show paste. + * + * @param {object} window + * @param {object} document + * @class + */ + var pasteViewer = (function (window, document) { + var me = {}; + + var $clearText, + $comments, + $discussion, + $image, + $placeholder, + $prettyMessage, + $prettyPrint, + $remainingTime; + + var text, + format = 'plaintext', + isDisplayed = false, + isChanged = true; // by default true as nothing was parsed yet + + /** + * apply the set format on paste and displays it + * + * @name pasteViewer.parsePaste + * @private + * @function + */ + function parsePaste() + { + // skip parsing if no text is given + if (text === '') { + return; + } + + // set text + helper.setElementText($clearText, text); + helper.setElementText($prettyPrint, text); + + switch (format) { + case 'markdown': + var converter = new showdown.Converter({ + strikethrough: true, + tables: true, + tablesHeaderId: true + }); + $clearText.html( + converter.makeHtml(text) + ); + // add table classes from bootstrap css + $clearText.find('table').addClass('table-condensed table-bordered'); + break; + case 'syntaxhighlighting': + // @TODO is this really needed or is "one" enough? + if (typeof prettyPrint === 'function') + { + prettyPrint(); + } + + $prettyPrint.html( + prettyPrintOne( + helper.htmlEntities(text), null, true + ) + ); + // fall through, as the rest is the same + default: // = 'plaintext' + // convert URLs to clickable links + helper.urls2links($clearText); + helper.urls2links($prettyPrint); + + $prettyPrint.css('white-space', 'pre-wrap'); + $prettyPrint.css('word-break', 'normal'); + $prettyPrint.removeClass('prettyprint'); + } + } + + /** + * displays the paste + * + * @name pasteViewer.show + * @private + * @function + */ + function showPaste() + { + // instead of "nothing" better display a placeholder + if (text === '') { + $placeholder.removeClass('hidden') + return; + } + // otherwise hide the placeholder + $placeholder.addClass('hidden') + + switch (format) { + case 'markdown': + $clearText.removeClass('hidden'); + $prettyMessage.addClass('hidden'); + break; + default: + $clearText.addClass('hidden'); + $prettyMessage.removeClass('hidden'); + break; + } + } + + /** + * show decrypted text in the display area, including discussion (if open) + * + * @name pasteViewer.displayPaste + * @function + * @param {Object} [paste] - (optional) object including comments to display (items = array with keys ('data','meta')) + */ + me.displayPaste = function(paste) + { + paste = paste || $.parseJSON(modal.getCipherData()); + var key = helper.pageKey(), + password = $passwordInput.val(); + if (!$prettyPrint.hasClass('prettyprinted')) { + // Try to decrypt the paste. + try + { + if (paste.attachment) + { + var attachment = cryptTool.decipher(key, password, paste.attachment); + if (attachment.length === 0) + { + if (password.length === 0) + { + me.requestPassword(); + return; + } + attachment = cryptTool.decipher(key, password, paste.attachment); + } + if (attachment.length === 0) + { + throw 'failed to decipher attachment'; + } + + if (paste.attachmentname) + { + var attachmentname = cryptTool.decipher(key, password, paste.attachmentname); + if (attachmentname.length > 0) + { + $attachmentLink.attr('download', attachmentname); + } + } + $attachmentLink.attr('href', attachment); + $attachment.removeClass('hidden'); + + // if the attachment is an image, display it + var imagePrefix = 'data:image/'; + if (attachment.substring(0, imagePrefix.length) === imagePrefix) + { + $image.html( + $(document.createElement('img')) + .attr('src', attachment) + .attr('class', 'img-thumbnail') + ); + $image.removeClass('hidden'); + } + } + var cleartext = cryptTool.decipher(key, password, paste.data); + if (cleartext.length === 0 && password.length === 0 && !paste.attachment) + { + me.requestPassword(); + return; + } + if (cleartext.length === 0 && !paste.attachment) + { + throw 'failed to decipher message'; + } + + $passwordInput.val(password); + if (cleartext.length > 0) + { + pasteViewer.setFormat(paste.meta.formatter); + me.formatPaste(paste.meta.formatter, cleartext); + } + } + catch(err) + { + me.stateOnlyNewPaste(); + me.showError(i18n._('Could not decrypt data (Wrong key?)')); + return; + } + } + + // display paste expiration / for your eyes only + if (paste.meta.expire_date) + { + var expiration = helper.secondsToHuman(paste.meta.remaining_time), + expirationLabel = [ + 'This document will expire in %d ' + expiration[1] + '.', + 'This document will expire in %d ' + expiration[1] + 's.' + ]; + helper.appendMessage($remainingTime, i18n._(expirationLabel, expiration[0])); + $remainingTime.removeClass('foryoureyesonly') + .removeClass('hidden'); + } + if (paste.meta.burnafterreading) + { + // unfortunately many web servers don't support DELETE (and PUT) out of the box + $.ajax({ + type: 'POST', + url: helper.scriptLocation() + '?' + helper.pasteId(), + data: {deletetoken: 'burnafterreading'}, + dataType: 'json', + headers: headers + }) + .fail(function() { + controller.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.')); + }); + helper.appendMessage($remainingTime, i18n._( + 'FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.' + )); + $remainingTime.addClass('foryoureyesonly') + .removeClass('hidden'); + // discourage cloning (as it can't really be prevented) + $cloneButton.addClass('hidden'); + } + + // if the discussion is opened on this paste, display it + 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 = $('
' + + '
' + + '
' + + '
'); + var $divCommentData = $divComment.find('div.commentdata'); + + // if parent comment exists + if ($parentComment.length) + { + // shift comment to the right + $place = $parentComment; + } + $divComment.find('button').click({commentid: comment.id}, me.openReply); + helper.setElementText($divCommentData, commentText); + helper.urls2links($divCommentData); + + // try to get optional nickname + var nick = cryptTool.decipher(key, password, comment.meta.nickname); + if (nick.length > 0) + { + $divComment.find('span.nickname').text(nick); + } + else + { + divComment.find('span.nickname').html('' + i18n._('Anonymous') + ''); + } + $divComment.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) + { + $divComment.find('span.nickname') + .before( + ' ' + ); + } + + $place.append($divComment); + } + + // add 'add new comment' area + $divComment = $( + '
' + ); + $divComment.find('button').click({commentid: helper.pasteId()}, me.openReply); + $comments.append($divComment); + $discussion.removeClass('hidden'); + } + }; + + /** + * open the comment entry when clicking the "Reply" button of a comment + * + * @name pasteViewer.openReply + * @function + * @param {Event} event + */ + me.openReply = function(event) + { + event.preventDefault(); + + // remove any other reply area + $('div.reply').remove(); + + 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(); + }; + + /** + * sets the format in which the text is shown + * + * @name pasteViewer.setFormat + * @function + * @param {string} the the new format + */ + me.setFormat = function(newFormat) + { + if (format !== newFormat) { + format = newFormat; + isChanged = true; + } + }; + + /** + * returns the current format + * + * @name pasteViewer.setFormat + * @function + * @return {string} + */ + me.getFormat = function() + { + return format; + }; + + /** + * sets the text to show + * + * @name editor.init + * @function + * @param {string} the text to show + */ + me.setText = function(newText) + { + if (text !== newText) { + text = newText; + isChanged = true; + } + }; + + /** + * show/update the parsed text (preview) + * + * @name pasteViewer.trigger + * @function + */ + me.trigger = function() + { + if (isChanged) { + parsePaste(); + isChanged = false; + } + + if (!isDisplayed) { + showPaste(); + isDisplayed = true; + } + }; + + /** + * hide parsed text (preview) + * + * @name pasteViewer.hide + * @function + */ + me.hide = function() + { + if (!isDisplayed) { + console.warn('pasteViewer was called to hide the parsed view, but it is already hidden.'); + } + + $clearText.addClass('hidden'); + $prettyMessage.addClass('hidden'); + $placeholder.addClass('hidden'); + + isDisplayed = false; + }; + + /** + * init status manager + * + * preloads jQuery elements + * + * @name editor.init + * @function + */ + me.init = function() + { + $clearText = $('#cleartext'); + $comments = $('#comments'); + $discussion = $('#discussion'); + $image = $('#image'); + $placeholder = $('#placeholder'); + $prettyMessage = $('#prettymessage'); + $prettyPrint = $('#prettyprint'); + $remainingTime = $('#remainingtime'); + + // check requirements + if (typeof prettyPrintOne !== 'function') { + status.showError( + i18n._('The library %s is not available.', 'pretty print') + + i18n._('This may cause display errors.') + ); + } + if (typeof showdown !== 'object') { + status.showError( + i18n._('The library %s is not available.', 'showdown') + + i18n._('This may cause display errors.') + ); + } + + // get default option from template/HTML or fall back to set value + format = modal.getFormatDefault() || format; }; return me; @@ -1551,50 +1761,63 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $burnAfterReading, $burnAfterReadingOption, $cloneButton, + $clonedFile, $expiration, $fileRemoveButton, $fileWrap, $formatter, $newButton, - $openDisc, // @TODO: rename - too similar to openDiscussion, difference unclear + $openDiscussionOption, $openDiscussion, $password, $rawTextButton, $sendButton; + var pasteExpiration = '1week'; + /** - * set the expiration on bootstrap templates + * set the expiration on bootstrap templates in dropdown * - * @name topNav.setExpiration + * @name topNav.updateExpiration * @function * @param {Event} event */ - function setExpiration(event) + function updateExpiration(event) { - event.preventDefault(); + // get selected option var target = $(event.target); - $('#pasteExpiration').val(target.data('expiration')); + + // update dropdown display and save new expiration time $('#pasteExpirationDisplay').text(target.text()); + pasteExpiration = target.data('expiration'); + + event.preventDefault(); } /** - * set the format on bootstrap templates + * set the format on bootstrap templates in dropdown * - * @name topNav.setFormat + * @name topNav.updateFormat * @function * @param {Event} event */ - me.setFormat = function(event) + function updateFormat(event) { - var target = $(event.target); - $('#pasteFormatter').val(target.data('format')); - $('#pasteFormatterDisplay').text(target.text()); + // get selected option + var $target = $(event.target); - if ($messagePreview.parent().hasClass('active')) { - me.viewPreview(event); + // update dropdown display and save new format + var newFormat = $target.data('format'); + $('#pasteFormatterDisplay').text($target.text()); + pasteViewer.setFormat(newFormat); + + // update preview + if (editor.isPreview()) { + pasteViewer.trigger(); } + event.preventDefault(); - }; + } /** * when "burn after reading" is checked, disable discussion @@ -1604,35 +1827,33 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function changeBurnAfterReading() { - if ($burnAfterReading.is(':checked') ) - { - $openDisc.addClass('buttondisabled'); - $openDiscussion.attr({checked: false, disabled: true}); - } - else - { - $openDisc.removeClass('buttondisabled'); - $openDiscussion.removeAttr('disabled'); + if ($burnAfterReading.is(':checked')) { + $openDiscussionOption.addClass('buttondisabled'); + $openDiscussion.prop('checked', false); + + // if button is actually disabled, force-enable it and uncheck other button + $burnAfterReadingOption.removeClass('buttondisabled'); + } else { + $openDiscussionOption.removeClass('buttondisabled'); } } /** * when discussion is checked, disable "burn after reading" * - * @name topNav.changeOpenDisc + * @name topNav.changeOpenDiscussion * @function */ - function changeOpenDisc() + function changeOpenDiscussion() { - if ($openDiscussion.is(':checked') ) - { + if ($openDiscussion.is(':checked')) { $burnAfterReadingOption.addClass('buttondisabled'); - $burnAfterReading.attr({checked: false, disabled: true}); - } - else - { + $burnAfterReading.prop('checked', false); + + // if button is actually disabled, force-enable it and uncheck other button + $openDiscussionOption.removeClass('buttondisabled'); + } else { $burnAfterReadingOption.removeClass('buttondisabled'); - $burnAfterReading.removeAttr('disabled'); } } @@ -1645,7 +1866,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ function rawText(event) { - var paste = $('#pasteFormatter').val() === 'markdown' ? + var paste = pasteViewer.getFormat() === 'markdown' ? $prettyPrint.text() : $clearText.text(); history.pushState( null, document.title, helper.scriptLocation() + '?' + @@ -1745,10 +1966,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $expiration.removeClass('hidden'); $formatter.removeClass('hidden'); $burnAfterReadingOption.removeClass('hidden'); - $openDisc.removeClass('hidden'); + $openDiscussionOption.removeClass('hidden'); $newButton.removeClass('hidden'); $password.removeClass('hidden'); $attach.removeClass('hidden'); + // $clonedFile.removeClass('hidden'); // @TODO createButtonsDisplayed = true; }; @@ -1771,10 +1993,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $expiration.addClass('hidden'); $formatter.addClass('hidden'); $burnAfterReadingOption.addClass('hidden'); - $openDisc.addClass('hidden'); + $openDiscussionOption.addClass('hidden'); $newButton.addClass('hidden'); $password.addClass('hidden'); $attach.addClass('hidden'); + // $clonedFile.addClass('hidden'); // @TODO createButtonsDisplayed = false; }; @@ -1815,6 +2038,18 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $loadingIndicator.removeClass('hidden'); }; + /** + * returns the currently set expiration time + * + * @name topNav.getExpiration + * @function + * @return {int} + */ + me.getExpiration = function() + { + return pasteExpiration; + }; + /** * init navigation manager * @@ -1831,12 +2066,13 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $burnAfterReading = $('#burnafterreading'); $burnAfterReadingOption = $('#burnafterreadingoption'); $cloneButton = $('#clonebutton'); + $clonedFile = $('#clonedfile'); $expiration = $('#expiration'); $fileRemoveButton = $('#fileremovebutton'); $fileWrap = $('#filewrap'); $formatter = $('#formatter'); $newButton = $('#newbutton'); - $openDisc = $('#opendisc'); + $openDiscussionOption = $('#opendiscussionoption'); $openDiscussion = $('#opendiscussion'); $password = $('#password'); $rawTextButton = $('#rawtextbutton'); @@ -1849,16 +2085,23 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { // bind events $burnAfterReading.change(changeBurnAfterReading); - $openDisc.change(changeOpenDisc); + $openDiscussionOption.change(changeOpenDiscussion); $newButton.click(controller.newPaste); $sendButton.click(controller.sendData); $cloneButton.click(controller.clonePaste); $rawTextButton.click(rawText); $fileRemoveButton.click(me.removeAttachment); + // bootstrap template drop downs + $('ul.dropdown-menu li a', $('#expiration').parent()).click(updateExpiration); + $('ul.dropdown-menu li a', $('#formatter').parent()).click(updateFormat); + // initiate default state of checkboxes changeBurnAfterReading(); - changeOpenDisc(); + changeOpenDiscussion(); + + // get default value from template or fall back to set value + pasteExpiration = modal.getExpirationDefault() || pasteExpiration; }; return me; @@ -2070,11 +2313,11 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ me.sendDataContinue = function(randomkey, cipherdata_attachment, cipherdata_attachment_name) { - var cipherdata = cryptTool.cipher(randomkey, $passwordInput.val(), $message.val()), + var cipherdata = cryptTool.cipher(randomkey, $passwordInput.val(), editor.getText()), data_to_send = { data: cipherdata, - expire: $('#pasteExpiration').val(), - formatter: $('#pasteFormatter').val(), + expire: topNav.getExpiration(), + formatter: pasteViewer.getFormat(), burnafterreading: $burnAfterReading.is(':checked') ? 1 : 0, opendiscussion: $openDiscussion.is(':checked') ? 1 : 0 }; @@ -2187,7 +2430,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { */ me.newPaste = function() { - // topNav.hideViewButtons(); // should not be necessary as they are not yet shown + topNav.hideViewButtons(); topNav.showCreateButtons(); editor.resetInput(); editor.show(); @@ -2215,7 +2458,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { $fileWrap.addClass('hidden'); } $message.val( - $('#pasteFormatter').val() === 'markdown' ? + pasteViewer.getFormat() === 'markdown' ? $prettyPrint.val() : $clearText.val() ); $('.navbar-toggle').click(); @@ -2240,6 +2483,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) { uiMan.init(); topNav.init(); editor.init(); + pasteViewer.init(); prompt.init(); // display an existing paste diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 7d25d620..93f1faf9 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -198,7 +198,7 @@ if ($isCpct): -
  • - -