2012-04-22 12:45:45 +02:00
/ * *
2016-07-11 11:58:15 +02:00
* PrivateBin
2012-04-23 16:30:02 +02:00
*
2012-04-30 22:58:08 +02:00
* a zero - knowledge paste bin
*
2017-01-14 15:29:12 +01:00
* @ see { @ link https : //github.com/PrivateBin/PrivateBin}
* @ copyright 2012 Sébastien SAUVAGE ( { @ link http : //sebsauvage.net})
* @ license { @ link https : //www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
2016-12-26 12:13:50 +01:00
* @ version 1.1
2017-01-14 15:29:12 +01:00
* @ name PrivateBin
* @ namespace
2012-04-22 12:45:45 +02:00
* /
2012-04-21 21:59:45 +02:00
2016-07-11 15:47:42 +02:00
/** global: Base64 */
/** global: FileReader */
/** global: RawDeflate */
/** global: history */
/** global: navigator */
/** global: prettyPrint */
/** global: prettyPrintOne */
/** global: showdown */
/** global: sjcl */
2015-09-06 13:07:46 +02:00
2012-04-21 21:59:45 +02:00
// Immediately start random number generator collector.
sjcl . random . startCollectors ( ) ;
2017-02-08 20:12:22 +01:00
jQuery . PrivateBin = function ( $ , sjcl , Base64 , RawDeflate ) {
2017-02-12 18:08:08 +01:00
'use strict' ;
2017-02-08 20:11:04 +01:00
/ * *
2017-02-08 20:12:22 +01:00
* static helper methods
2017-02-08 20:11:04 +01:00
*
2017-02-08 20:12:22 +01:00
* @ param { object } window
* @ param { object } document
* @ class
2017-02-08 20:11:04 +01:00
* /
2017-02-08 20:12:22 +01:00
var helper = ( function ( window , document ) {
var me = { } ;
/ * *
* character to HTML entity lookup table
*
* @ see { @ link https : //github.com/janl/mustache.js/blob/master/mustache.js#L60}
* @ private
* @ enum { Object }
* @ readonly
* /
var entityMap = {
'&' : '&' ,
'<' : '<' ,
'>' : '>' ,
'"' : '"' ,
"'" : ''' ,
'/' : '/' ,
'`' : '`' ,
'=' : '='
} ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
* cache for script location
*
* @ private
* @ enum { string | null }
* /
var scriptLocation = null ;
/ * *
* converts a duration ( in seconds ) into human friendly approximation
*
* @ name helper . secondsToHuman
* @ function
* @ param { number } seconds
* @ return { Array }
* /
me . secondsToHuman = function ( seconds )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var v ;
if ( seconds < 60 )
{
v = Math . floor ( seconds ) ;
return [ v , 'second' ] ;
}
if ( seconds < 60 * 60 )
{
v = Math . floor ( seconds / 60 ) ;
return [ v , 'minute' ] ;
}
if ( seconds < 60 * 60 * 24 )
{
v = Math . floor ( seconds / ( 60 * 60 ) ) ;
return [ v , 'hour' ] ;
}
// If less than 2 months, display in days:
if ( seconds < 60 * 60 * 24 * 60 )
{
v = Math . floor ( seconds / ( 60 * 60 * 24 ) ) ;
return [ v , 'day' ] ;
}
v = Math . floor ( seconds / ( 60 * 60 * 24 * 30 ) ) ;
return [ v , 'month' ] ;
} ;
/ * *
* text range selection
*
* @ see { @ link https : //stackoverflow.com/questions/985272/jquery-selecting-text-in-an-element-akin-to-highlighting-with-your-mouse}
* @ name helper . selectText
* @ function
* @ param { HTMLElement } element
* /
me . selectText = function ( element )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var range , selection ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
// MS
if ( document . body . createTextRange )
{
range = document . body . createTextRange ( ) ;
range . moveToElementText ( element ) ;
range . select ( ) ;
}
// all others
else if ( window . getSelection )
{
selection = window . getSelection ( ) ;
range = document . createRange ( ) ;
range . selectNodeContents ( element ) ;
selection . removeAllRanges ( ) ;
selection . addRange ( range ) ;
}
} ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
* set text of a jQuery element ( required for IE ) ,
*
* @ name helper . setElementText
* @ function
* @ param { jQuery } $element - a jQuery element
* @ param { string } text - the text to enter
* /
me . setElementText = function ( $element , text )
2015-09-12 17:33:16 +02:00
{
2017-02-13 11:35:04 +01:00
// @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…
2017-02-08 20:12:22 +01:00
// 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<br>' ) ;
$element . html ( '<pre>' + html + '</pre>' ) ;
}
// for other (sane) browsers:
else
{
$element . text ( text ) ;
}
} ;
2015-09-12 17:33:16 +02:00
2017-02-08 20:12:22 +01:00
/ * *
* convert URLs to clickable links .
* URLs to handle :
* < pre >
* magnet : ? xt . 1 = urn : sha1 : YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C & xt . 2 = urn : sha1 : TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
* http : //example.com:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* http : //user:example.com@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
* < / p r e >
*
* @ name helper . urls2links
* @ function
* @ param { Object } element - a jQuery DOM element
* /
me . urls2links = function ( element )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var markup = '<a href="$1" rel="nofollow">$1</a>' ;
element . html (
element . html ( ) . replace (
/((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig ,
markup
)
) ;
element . html (
element . html ( ) . replace (
/((magnet):[\w?=&.\/-;#@~%+-]+)/ig ,
markup
)
) ;
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* minimal sprintf emulation for % s and % d formats
*
* @ see { @ link https : //stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914}
* @ name helper . sprintf
* @ function
* @ param { string } format
* @ param { ... * } args - one or multiple parameters injected into format string
* @ return { string }
* /
me . sprintf = function ( )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var args = arguments ;
if ( typeof arguments [ 0 ] === 'object' )
{
args = arguments [ 0 ] ;
}
var format = args [ 0 ] ,
i = 1 ;
return format . replace ( /%((%)|s|d)/g , function ( m ) {
// m is the matched format, e.g. %s, %d
var val ;
if ( m [ 2 ] ) {
val = m [ 2 ] ;
} else {
val = args [ i ] ;
// A switch statement so that the formatter can be extended.
switch ( m )
{
case '%d' :
val = parseFloat ( val ) ;
if ( isNaN ( val ) ) {
val = 0 ;
}
break ;
default :
// Default is %s
}
++ i ;
}
return val ;
} ) ;
} ;
2017-02-12 21:13:04 +01:00
/ * *
* replace last child of element with message
*
* @ name helper . appendMessage
* @ function
* @ param { jQuery } $element - a jQuery wrapped DOM element
* @ param { string } message - the message to append
* @ TODO : make private if possible & move to function
* /
me . appendMessage = function ( $element , message )
{
var content = $element . contents ( ) ;
if ( content . length > 0 )
{
content [ content . length - 1 ] . nodeValue = ' ' + message ;
}
else
{
me . setElementText ( $element , message ) ;
}
} ;
2017-02-08 20:12:22 +01:00
/ * *
* get value of cookie , if it was set , empty string otherwise
*
* @ see { @ link http : //www.w3schools.com/js/js_cookies.asp}
* @ name helper . getCookie
* @ function
* @ param { string } cname
* @ return { string }
* /
me . getCookie = function ( cname ) {
var name = cname + '=' ,
ca = document . cookie . split ( ';' ) ;
for ( var i = 0 ; i < ca . length ; ++ i ) {
var c = ca [ i ] ;
while ( c . charAt ( 0 ) === ' ' )
2016-07-11 15:47:42 +02:00
{
2017-02-08 20:12:22 +01:00
c = c . substring ( 1 ) ;
}
if ( c . indexOf ( name ) === 0 )
{
return c . substring ( name . length , c . length ) ;
2016-07-11 15:47:42 +02:00
}
2015-09-19 11:21:13 +02:00
}
2017-02-08 20:12:22 +01:00
return '' ;
} ;
2016-07-19 16:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
* get the current script location ( without search or hash part of the URL ) ,
* eg . http : //example.com/path/?aaaa#bbbb --> http://example.com/path/
*
* @ name helper . scriptLocation
* @ function
* @ return { string } current script location
* /
me . scriptLocation = function ( )
{
// check for cached version
if ( scriptLocation !== null ) {
return scriptLocation ;
2017-02-08 13:20:51 +01:00
}
2017-02-08 20:12:22 +01:00
scriptLocation = window . location . href . substring (
0 ,
window . location . href . length - window . location . search . length - window . location . hash . length
) ;
var hashIndex = scriptLocation . indexOf ( '?' ) ;
if ( hashIndex !== - 1 )
2017-02-05 14:47:03 +01:00
{
2017-02-08 20:12:22 +01:00
scriptLocation = scriptLocation . substring ( 0 , hashIndex ) ;
2017-02-05 14:47:03 +01:00
}
2017-02-08 13:20:51 +01:00
2017-02-05 14:47:03 +01:00
return scriptLocation ;
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* get the pastes unique identifier from the URL ,
* eg . http : //example.com/path/?c05354954c49a487#c05354954c49a487 returns c05354954c49a487
*
* @ name helper . pasteId
* @ function
* @ return { string } unique identifier
* /
me . pasteId = function ( )
2017-02-05 14:47:03 +01:00
{
2017-02-08 20:12:22 +01:00
return window . location . search . substring ( 1 ) ;
} ;
2017-02-05 14:47:03 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* return the deciphering key stored in anchor part of the URL
*
* @ name helper . pageKey
* @ function
* @ return { string } key
* /
me . pageKey = function ( )
2017-02-05 14:47:03 +01:00
{
2017-02-08 20:12:22 +01:00
var key = window . location . hash . substring ( 1 ) ,
i = key . indexOf ( '&' ) ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
// Some web 2.0 services and redirectors add data AFTER the anchor
// (such as &utm_source=...). We will strip any additional data.
if ( i > - 1 )
{
key = key . substring ( 0 , i ) ;
}
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
return key ;
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* convert all applicable characters to HTML entities
*
* @ see { @ link https : //www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content}
* @ name helper . htmlEntities
* @ function
* @ param { string } str
* @ return { string } escaped HTML
* /
me . htmlEntities = function ( str ) {
return String ( str ) . replace (
/[&<>"'`=\/]/g , function ( s ) {
return entityMap [ s ] ;
} ) ;
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
return me ;
} ) ( window , document ) ;
2015-09-06 13:07:46 +02:00
2017-02-08 20:11:04 +01:00
/ * *
2017-02-08 20:12:22 +01:00
* internationalization methods
2017-02-08 20:11:04 +01:00
*
2017-02-08 20:12:22 +01:00
* @ param { object } window
* @ param { object } document
* @ class
2017-02-08 20:11:04 +01:00
* /
2017-02-08 20:12:22 +01:00
var i18n = ( function ( window , document ) {
var me = { } ;
/ * *
* supported languages , minus the built in 'en'
*
* @ private
* @ prop { string [ ] }
* @ readonly
* /
var supportedLanguages = [ 'de' , 'es' , 'fr' , 'it' , 'no' , 'pl' , 'oc' , 'ru' , 'sl' , 'zh' ] ;
/ * *
* built in language
*
* @ private
* @ prop { string }
* /
var language = 'en' ;
/ * *
* translation cache
*
* @ private
* @ enum { Object }
* /
var translations = { } ;
/ * *
* translate a string , alias for i18n . translate ( )
*
* @ name i18n . _
* @ function
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
* /
me . _ = function ( )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
return me . translate ( arguments ) ;
} ;
/ * *
* translate a string
*
* @ name i18n . translate
* @ function
* @ param { string } messageId
* @ param { ... * } args - one or multiple parameters injected into placeholders
* @ return { string }
* /
me . translate = function ( )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var args = arguments , messageId ;
if ( typeof arguments [ 0 ] === 'object' )
2015-09-06 15:54:43 +02:00
{
2017-02-08 20:12:22 +01:00
args = arguments [ 0 ] ;
2015-09-06 15:54:43 +02:00
}
2017-02-08 20:12:22 +01:00
var usesPlurals = $ . isArray ( args [ 0 ] ) ;
if ( usesPlurals )
2015-09-06 15:54:43 +02:00
{
2017-02-08 20:12:22 +01:00
// use the first plural form as messageId, otherwise the singular
messageId = ( args [ 0 ] . length > 1 ? args [ 0 ] [ 1 ] : args [ 0 ] [ 0 ] ) ;
2015-09-06 13:07:46 +02:00
}
2017-02-08 20:12:22 +01:00
else
{
messageId = args [ 0 ] ;
}
if ( messageId . length === 0 )
{
return messageId ;
}
if ( ! translations . hasOwnProperty ( messageId ) )
{
if ( language !== 'en' )
{
console . error (
'Missing ' + language + ' translation for: ' + messageId
) ;
}
translations [ messageId ] = args [ 0 ] ;
}
if ( usesPlurals && $ . isArray ( translations [ messageId ] ) )
{
var n = parseInt ( args [ 1 ] || 1 , 10 ) ,
key = me . getPluralForm ( n ) ,
maxKey = translations [ messageId ] . length - 1 ;
if ( key > maxKey )
{
key = maxKey ;
}
args [ 0 ] = translations [ messageId ] [ key ] ;
args [ 1 ] = n ;
}
else
{
args [ 0 ] = translations [ messageId ] ;
}
return helper . sprintf ( args ) ;
} ;
2017-02-08 13:20:51 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* per language functions to use to determine the plural form
*
* @ see { @ link http : //localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html}
* @ name i18n . getPluralForm
* @ function
* @ param { number } n
* @ return { number } array key
* /
me . getPluralForm = function ( n ) {
switch ( language )
{
case 'fr' :
case 'oc' :
case 'zh' :
return ( n > 1 ? 1 : 0 ) ;
case 'pl' :
return ( n === 1 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ) ;
case 'ru' :
return ( n % 10 === 1 && n % 100 !== 11 ? 0 : ( n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ) ;
case 'sl' :
return ( n % 100 === 1 ? 1 : ( n % 100 === 2 ? 2 : ( n % 100 === 3 || n % 100 === 4 ? 3 : 0 ) ) ) ;
// de, en, es, it, no
default :
return ( n !== 1 ? 1 : 0 ) ;
}
} ;
2017-02-08 13:20:51 +01:00
2017-02-08 20:12:22 +01:00
/ * *
* load translations into cache , then trigger controller initialization
*
* @ name i18n . loadTranslations
* @ function
* /
me . loadTranslations = function ( )
2017-02-08 20:11:04 +01:00
{
2017-02-08 20:12:22 +01:00
var newLanguage = helper . getCookie ( 'lang' ) ;
2017-02-08 13:20:51 +01:00
2017-02-08 20:12:22 +01:00
// auto-select language based on browser settings
if ( newLanguage . length === 0 )
{
newLanguage = ( navigator . language || navigator . userLanguage ) . substring ( 0 , 2 ) ;
}
2015-09-06 13:07:46 +02:00
2017-02-08 20:12:22 +01:00
// if language is already used (e.g, default 'en'), skip update
2017-02-12 18:08:08 +01:00
if ( newLanguage === language ) {
2017-02-08 20:12:22 +01:00
return ;
}
2015-09-06 15:54:43 +02:00
2017-02-08 20:12:22 +01:00
// if language is not supported, show error
2017-02-12 18:08:08 +01:00
if ( supportedLanguages . indexOf ( newLanguage ) === - 1 ) {
2017-02-08 20:12:22 +01:00
console . error ( 'Language \'%s\' is not supported. Translation failed, fallback to English.' , newLanguage ) ;
}
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
// load strongs from JSON
$ . getJSON ( 'i18n/' + newLanguage + '.json' , function ( data ) {
language = newLanguage ;
translations = data ;
} ) . fail ( function ( data , textStatus , errorMsg ) {
console . error ( 'Language \'%s\' could not be loaded (%s: %s). Translation failed, fallback to English.' , newLanguage , textStatus , errorMsg ) ;
} ) ;
} ;
2015-09-06 13:07:46 +02:00
2017-02-08 20:12:22 +01:00
return me ;
} ) ( window , document ) ;
2017-02-08 20:11:04 +01:00
/ * *
2017-02-12 21:13:04 +01:00
* handles everything related to en / decryption
2017-02-08 20:11:04 +01:00
*
2017-02-08 20:12:22 +01:00
* @ class
2017-02-08 20:11:04 +01:00
* /
2017-02-12 18:08:08 +01:00
var cryptTool = ( function ( ) {
2017-02-08 20:12:22 +01:00
var me = { } ;
/ * *
* compress a message ( deflate compression ) , returns base64 encoded data
*
2017-02-12 18:08:08 +01:00
* @ name cryptToolcompress
2017-02-08 20:12:22 +01:00
* @ function
2017-02-12 18:08:08 +01:00
* @ private
2017-02-08 20:12:22 +01:00
* @ param { string } message
* @ return { string } base64 data
* /
2017-02-12 18:08:08 +01:00
function compress ( message )
2015-09-05 17:12:11 +02:00
{
2017-02-08 20:12:22 +01:00
return Base64 . toBase64 ( RawDeflate . deflate ( Base64 . utob ( message ) ) ) ;
2017-02-12 18:08:08 +01:00
}
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* decompress a message compressed with cryptToolcompress ( )
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* @ name cryptTooldecompress
2017-02-08 20:12:22 +01:00
* @ function
2017-02-12 18:08:08 +01:00
* @ private
2017-02-08 20:12:22 +01:00
* @ param { string } data - base64 data
* @ return { string } message
* /
2017-02-12 18:08:08 +01:00
function decompress ( data )
2015-09-05 17:12:11 +02:00
{
2017-02-08 20:12:22 +01:00
return Base64 . btou ( RawDeflate . inflate ( Base64 . fromBase64 ( data ) ) ) ;
2017-02-12 18:08:08 +01:00
}
2017-02-08 20:12:22 +01:00
/ * *
* compress , then encrypt message with given key and password
*
2017-02-12 21:13:04 +01:00
* @ name cryptTool . cipher
2017-02-08 20:12:22 +01:00
* @ function
* @ param { string } key
* @ param { string } password
* @ param { string } message
* @ return { string } data - JSON with encrypted data
* /
me . cipher = function ( key , password , message )
{
// Galois Counter Mode, keysize 256 bit, authentication tag 128 bit
var options = { mode : 'gcm' , ks : 256 , ts : 128 } ;
if ( ( password || '' ) . trim ( ) . length === 0 )
2015-09-05 17:12:11 +02:00
{
2017-02-12 18:08:08 +01:00
return sjcl . encrypt ( key , compress ( message ) , options ) ;
2015-09-05 17:12:11 +02:00
}
2017-02-08 20:12:22 +01:00
return sjcl . encrypt ( key + sjcl . codec . hex . fromBits ( sjcl . hash . sha256 . hash ( password ) ) , me . compress ( message ) , options ) ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 20:12:22 +01:00
/ * *
* decrypt message with key , then decompress
*
2017-02-12 21:13:04 +01:00
* @ name cryptTool . decipher
2017-02-08 20:12:22 +01:00
* @ function
* @ param { string } key
* @ param { string } password
* @ param { string } data - JSON with encrypted data
* @ return { string } decrypted message
* /
me . decipher = function ( key , password , data )
{
if ( data !== undefined )
2015-09-05 17:12:11 +02:00
{
try
{
2017-02-12 18:08:08 +01:00
return decompress ( sjcl . decrypt ( key , data ) ) ;
2015-09-05 17:12:11 +02:00
}
2017-02-08 20:12:22 +01:00
catch ( err )
2015-09-05 17:12:11 +02:00
{
2017-02-08 20:12:22 +01:00
try
{
2017-02-12 18:08:08 +01:00
return decompress ( sjcl . decrypt ( key + sjcl . codec . hex . fromBits ( sjcl . hash . sha256 . hash ( password ) ) , data ) ) ;
2017-02-08 20:12:22 +01:00
}
catch ( e )
{
// ignore error, because ????? @TODO
}
2015-08-31 21:14:12 +02:00
}
}
2017-02-08 20:12:22 +01:00
return '' ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 13:20:51 +01:00
2017-02-08 20:12:22 +01:00
return me ;
2017-02-12 18:08:08 +01:00
} ) ( ) ;
2017-02-08 20:11:04 +01:00
2017-02-12 21:13:04 +01:00
/ * *
* Data source ( aka MVC )
*
* @ param { object } window
* @ param { object } document
* @ class
* /
var modal = ( function ( window , document ) {
var me = { } ;
var $cipherData ;
/ * *
* check if cipher data was supplied
*
* @ name modal . getCipherData
* @ function
* @ return boolean
* /
me . hasCipherData = function ( )
{
return ( me . getCipherData ( ) . length > 0 ) ;
} ;
/ * *
* returns the cipher data
*
* @ name modal . getCipherData
* @ function
* @ return string
* /
me . getCipherData = function ( )
{
return $cipherData . text ( ) ;
} ;
2017-02-13 11:35:04 +01:00
/ * *
* 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 ( ) ;
} ;
2017-02-12 21:13:04 +01:00
/ * *
* init navigation manager
*
* preloads jQuery elements
*
* @ name modal . init
* @ function
* /
me . init = function ( )
{
$cipherData = $ ( '#cipherdata' ) ;
} ;
return me ;
} ) ( window , document ) ;
2017-02-08 20:11:04 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* User interface manager
2017-02-08 20:11:04 +01:00
*
2017-02-08 20:12:22 +01:00
* @ param { object } window
* @ param { object } document
* @ class
2017-02-08 20:11:04 +01:00
* /
2017-02-12 18:08:08 +01:00
var uiMan = ( function ( window , document ) {
2017-02-08 20:12:22 +01:00
var me = { } ;
// jQuery pre-loaded objects
2017-02-12 21:13:04 +01:00
var $clearText ,
2017-02-08 20:12:22 +01:00
$clonedFile ,
$comments ,
$discussion ,
$image ,
$pasteResult ,
$pasteUrl ,
$prettyMessage ,
$prettyPrint ,
2017-02-13 11:35:04 +01:00
$editorTabs ,
2017-02-08 20:12:22 +01:00
$remainingTime ,
2017-02-12 18:08:08 +01:00
$replyStatus ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* handle history ( pop ) state changes
*
* currently this does only handle redirects to the home page .
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . historyChange
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { Event } event
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . historyChange = function ( event )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
var currentLocation = helper . scriptLocation ( ) ;
if ( event . originalEvent . state === null && // no state object passed
event . originalEvent . target . location . href === currentLocation && // target location is home page
window . location . href === currentLocation // and we are not already on the home page
) {
// redirect to home page
window . location . href = currentLocation ;
}
} ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* Forces opening the paste if the link does not do this automatically .
*
* This is necessary as browsers will not reload the page when it is
* already loaded ( which is fake as it is set via history . pushState ( ) ) .
*
* @ name controller . pasteLinkClick
* @ function
* @ param { Event } event
* /
me . pasteLinkClick = function ( event )
{
// check if location is (already) shown in URL bar
if ( window . location . href === $pasteUrl . attr ( 'href' ) ) {
// if so we need to load link by reloading the current site
window . location . reload ( true ) ;
}
} ;
2016-12-12 17:37:51 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* reload the page
*
* This takes the user to the PrivateBin home page .
*
* @ name controller . reloadPage
* @ function
* @ param { Event } event
* /
me . reloadPage = function ( event )
{
window . location . href = helper . scriptLocation ( ) ;
event . preventDefault ( ) ;
} ;
2017-02-08 13:20:51 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* main UI manager
*
* @ name controller . init
* @ function
* /
me . init = function ( )
{
// hide "no javascript" message
$ ( '#noscript' ) . hide ( ) ;
2017-02-08 13:20:51 +01:00
2017-02-13 11:35:04 +01:00
// preload jQuery elements
$pasteResult = $ ( '#pasteresult' ) ;
// $pasteUrl is saved in sendDataContinue() if/after it is
// actually created
2017-02-08 13:20:51 +01:00
2017-02-13 11:35:04 +01:00
// bind events
$ ( '.reloadlink' ) . click ( me . reloadPage ) ;
$ ( window ) . on ( 'popstate' , me . historyChange ) ;
2017-02-08 20:12:22 +01:00
} ;
2015-09-12 17:33:16 +02:00
2017-02-13 11:35:04 +01:00
return me ;
} ) ( window , document ) ;
/ * *
* UI state manager
*
* @ param { object } window
* @ param { object } document
* @ class
* /
var state = ( function ( window , document ) {
var me = { } ;
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* put the screen in "New paste" mode
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . stateNewPaste
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . stateNewPaste = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
$remainingTime . removeClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
$loadingIndicator . addClass ( 'hidden' ) ;
console . error ( 'stateNewPaste is depreciated' ) ;
} ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* put the screen in mode after submitting a paste
*
* @ name controller . stateSubmittingPaste
* @ function
* /
me . stateSubmittingPaste = function ( )
{
console . error ( 'stateSubmittingPaste is depreciated' ) ;
} ;
2015-09-05 17:12:11 +02:00
2017-02-13 11:35:04 +01:00
/ * *
* put the screen in a state where the only option is to submit a
* new paste
*
* @ name controller . stateOnlyNewPaste
* @ function
* /
me . stateOnlyNewPaste = function ( )
{
console . error ( 'stateOnlyNewPaste is depreciated' ) ;
} ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* put the screen in "Existing paste" mode
*
* @ name controller . stateExistingPaste
* @ function
* @ param { boolean } [ preview = false ] - ( optional ) tell if the preview tabs should be displayed , defaults to false
* /
me . stateExistingPaste = function ( preview )
{
preview = preview || false ;
console . error ( 'stateExistingPaste is depreciated' ) ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
if ( ! preview )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
console . log ( 'show no preview' ) ;
}
} ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
return me ;
} ) ( window , document ) ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* UI status / error manager
*
* @ param { object } window
* @ param { object } document
* @ class
* /
var status = ( function ( window , document ) {
var me = { } ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
var $errorMessage ,
$status ,
$loadingIndicator ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* display a status message
*
* @ name controller . showStatus
* @ function
* @ param { string } message - text to display
* @ param { boolean } [ spin = false ] - ( optional ) tell if the "spinning" animation should be displayed , defaults to false
* /
me . showStatus = function ( message , spin )
{
// spin is ignored for now
$status . text ( message ) ;
2017-02-08 20:12:22 +01:00
} ;
/ * *
2017-02-13 11:35:04 +01:00
* display a status message for replying to comments
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . showStatus
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { string } message - text to display
* @ param { boolean } [ spin = false ] - ( optional ) tell if the "spinning" animation should be displayed , defaults to false
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . showReplyStatus = function ( message , spin )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
if ( spin || false ) {
$replyStatus . find ( '.spinner' ) . removeClass ( 'hidden' )
}
$replyStatus . text ( message ) ;
2017-02-08 20:12:22 +01:00
} ;
/ * *
2017-02-13 11:35:04 +01:00
* hides any status messages
2017-02-12 18:08:08 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . hideSTatus
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . hideStatus = function ( )
2015-09-01 22:33:07 +02:00
{
2017-02-13 11:35:04 +01:00
$status . html ( ' ' ) ;
2017-02-12 18:08:08 +01:00
} ;
2015-09-05 17:12:11 +02:00
2017-02-12 18:08:08 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* display an error message
2017-02-12 18:08:08 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name status . showError
2017-02-12 18:08:08 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { string } message - text to display
2017-02-12 18:08:08 +01:00
* /
2017-02-13 11:35:04 +01:00
me . showError = function ( message )
2017-02-12 18:08:08 +01:00
{
2017-02-13 11:35:04 +01:00
$errorMessage . removeClass ( 'hidden' ) ;
helper . appendMessage ( $errorMessage , message ) ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 20:12:22 +01:00
2017-02-12 18:08:08 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* display an error message
2017-02-12 18:08:08 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name status . showError
2017-02-12 18:08:08 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { string } message - text to display
2017-02-12 18:08:08 +01:00
* /
2017-02-13 11:35:04 +01:00
me . showReplyError = function ( message )
2017-02-12 18:08:08 +01:00
{
2017-02-13 11:35:04 +01:00
$replyStatus . addClass ( 'alert-danger' ) ;
$replyStatus . addClass ( $errorMessage . attr ( 'class' ) ) ; // @TODO ????
$replyStatus . text ( message ) ;
2017-02-12 18:08:08 +01:00
} ;
2012-04-21 21:59:45 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* init status manager
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* preloads jQuery elements
*
* @ name status . init
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-12 18:08:08 +01:00
me . init = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-12 18:08:08 +01:00
// hide "no javascript" message
$ ( '#noscript' ) . hide ( ) ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
$loadingIndicator = $ ( '#loadingindicator' ) ; // TODO: integrate $loadingIndicator into this module or leave it in state and remove it here
$errorMessage = $ ( '#errormessage' ) ;
$status = $ ( '#status' ) ;
// @TODO $replyStatus …
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
// display status returned by php code, if any (eg. paste was properly deleted)
// @TODO remove this by handling errors in a different way
if ( $status . text ( ) . length > 0 )
{
me . showStatus ( $status . text ( ) ) ;
return ;
}
2015-09-05 17:12:11 +02:00
2017-02-13 11:35:04 +01:00
// keep line height even if content empty
$status . html ( ' ' ) ; // @TODO what? remove?
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// display error message from php code
if ( $errorMessage . text ( ) . length > 1 ) {
me . showError ( $errorMessage . text ( ) ) ;
}
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 13:20:51 +01:00
2017-02-12 18:08:08 +01:00
return me ;
} ) ( window , document ) ;
/ * *
2017-02-13 11:35:04 +01:00
* Passwort prompt
2017-02-12 18:08:08 +01:00
*
* @ param { object } window
* @ param { object } document
* @ class
* /
2017-02-13 11:35:04 +01:00
var prompt = ( function ( window , document ) {
2017-02-12 18:08:08 +01:00
var me = { } ;
2017-02-13 11:35:04 +01:00
var $passwordInput ,
$passwordModal ,
$passwordForm ,
$passwordDecrypt ;
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* ask the user for the password and set it
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . requestPassword
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . requestPassword = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
if ( $passwordModal . length === 0 ) {
var password = prompt ( i18n . _ ( 'Please enter the password for this paste:' ) , '' ) ;
if ( password === null )
{
throw 'password prompt canceled' ;
}
if ( password . length === 0 )
{
// recursive…
me . requestPassword ( ) ;
} else {
$passwordInput . val ( password ) ;
me . displayMessages ( ) ;
}
} else {
$passwordModal . modal ( ) ;
}
2017-02-08 20:12:22 +01:00
} ;
2017-02-05 22:09:46 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* decrypt using the password from the modal dialog
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . decryptPasswordModal
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . decryptPasswordModal = function ( )
2017-02-06 22:39:45 +01:00
{
2017-02-13 11:35:04 +01:00
$passwordInput . val ( $passwordDecrypt . val ( ) ) ;
me . displayMessages ( ) ;
2017-02-08 20:12:22 +01:00
} ;
/ * *
2017-02-13 11:35:04 +01:00
* submit a password in the modal dialog
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name controller . submitPasswordModal
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { Event } event
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . submitPasswordModal = function ( event )
2015-09-05 17:12:11 +02:00
{
2017-02-13 11:35:04 +01:00
event . preventDefault ( ) ;
$passwordModal . modal ( 'hide' ) ;
2017-02-08 20:11:04 +01:00
} ;
2012-04-21 21:59:45 +02:00
2017-02-13 11:35:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* init status manager
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* preloads jQuery elements
*
* @ name controller . init
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . init = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
$passwordInput = $ ( '#passwordinput' ) ;
$passwordModal = $ ( '#passwordmodal' ) ;
$passwordForm = $ ( '#passwordform' ) ;
$passwordDecrypt = $ ( '#passworddecrypt' ) ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
// bind events
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
// focus password input when it is shown
$passwordModal . on ( 'shown.bs.modal' , function ( ) {
$passwordDecrypt . focus ( ) ;
} ) ;
// handle modal password request on decryption
$passwordModal . on ( 'hidden.bs.modal' , me . decryptPasswordModal ) ;
$passwordForm . submit ( me . submitPasswordModal ) ;
2017-02-08 20:12:22 +01:00
} ;
2015-09-05 17:12:11 +02:00
2017-02-12 18:08:08 +01:00
return me ;
} ) ( window , document ) ;
/ * *
2017-02-13 11:35:04 +01:00
* Manage paste / message input
2017-02-12 18:08:08 +01:00
*
* @ param { object } window
* @ param { object } document
* @ class
* /
2017-02-13 11:35:04 +01:00
var editor = ( function ( window , document ) {
2017-02-12 18:08:08 +01:00
var me = { } ;
2017-02-13 11:35:04 +01:00
var $message ,
$messageEdit ,
$messagePreview ,
$editorTabs ;
var isPreview = false ;
2017-02-12 18:08:08 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* support input of tab character
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . supportTabs
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { Event } event
* @ TODO doc what is @ this here ?
* @ TODO replace this with $message ? ?
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
function supportTabs ( event )
{
var keyCode = event . keyCode || event . which ;
// tab was pressed
if ( keyCode === 9 )
{
// prevent the textarea to lose focus
event . preventDefault ( ) ;
// get caret position & selection
var val = this . value ,
start = this . selectionStart ,
end = this . selectionEnd ;
// set textarea value to: text before caret + tab + text after caret
this . value = val . substring ( 0 , start ) + '\t' + val . substring ( end ) ;
// put caret at right position again
this . selectionStart = this . selectionEnd = start + 1 ;
}
}
/ * *
* view the editor tab
*
* @ name editor . viewEditor
* @ function
* @ param { Event } event - optional
* /
function viewEditor ( event )
{
// toggle buttons
$messageEdit . addClass ( 'active' ) ;
$messagePreview . removeClass ( 'active' ) ;
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
}
/ * *
* view the preview tab
*
* @ name editor . viewPreview
* @ function
* @ param { Event } event
* /
function viewPreview ( event )
{
// toggle buttons
$messageEdit . removeClass ( 'active' ) ;
$messagePreview . addClass ( 'active' ) ;
// 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 ;
}
/ * *
* reset the editor view
*
* @ name editor . resetInput
* @ function
* /
me . resetInput = function ( )
2015-09-05 17:12:11 +02:00
{
2017-02-13 11:35:04 +01:00
// go back to input
if ( isPreview ) {
viewEditor ( ) ;
}
// clear content
$message . val ( '' ) ;
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* shows the editor
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . show
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . show = function ( )
2016-08-11 11:31:34 +02:00
{
2017-02-13 11:35:04 +01:00
$message . removeClass ( 'hidden' ) ;
$editorTabs . removeClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
} ;
/ * *
2017-02-13 11:35:04 +01:00
* hides the editor
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . reset
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . hide = function ( )
2015-09-05 17:12:11 +02:00
{
2017-02-13 11:35:04 +01:00
$message . addClass ( 'hidden' ) ;
$editorTabs . addClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
} ;
2012-04-21 21:59:45 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* focuses the message input
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . focusInput
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . focusInput = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
$message . focus ( ) ;
2017-02-08 20:12:22 +01:00
} ;
2012-04-21 21:59:45 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* returns the current text
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . getText
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ return { string }
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . getText = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
return $message . val ( )
2017-02-08 20:12:22 +01:00
} ;
2016-08-11 11:40:37 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* init status manager
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* preloads jQuery elements
*
2017-02-13 11:35:04 +01:00
* @ name editor . init
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-12 18:08:08 +01:00
me . init = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
$message = $ ( '#message' ) ;
$editorTabs = $ ( '#editorTabs' ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// bind events
$message . keydown ( supportTabs ) ;
2017-02-12 21:13:04 +01:00
2017-02-13 11:35:04 +01:00
// bind click events to tab switchers (a), but save parent of them
// (li)
$messageEdit = $ ( '#messageedit' ) . click ( viewEditor ) . parent ( ) ;
$messagePreview = $ ( '#messagepreview' ) . click ( viewPreview ) . parent ( ) ;
2017-02-08 20:12:22 +01:00
} ;
2016-07-11 11:09:41 +02:00
2017-02-12 18:08:08 +01:00
return me ;
} ) ( window , document ) ;
2017-02-13 11:35:04 +01:00
2017-02-12 18:08:08 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* Parse and show paste .
2017-02-12 18:08:08 +01:00
*
* @ param { object } window
* @ param { object } document
* @ class
* /
2017-02-13 11:35:04 +01:00
var pasteViewer = ( function ( window , document ) {
2017-02-12 18:08:08 +01:00
var me = { } ;
2017-02-13 11:35:04 +01:00
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
2017-02-12 18:08:08 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* apply the set format on paste and displays it
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . parsePaste
* @ private
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
function parsePaste ( )
2016-07-11 11:09:41 +02:00
{
2017-02-13 11:35:04 +01:00
// skip parsing if no text is given
if ( text === '' ) {
return ;
2017-02-12 18:08:08 +01:00
}
2017-02-13 11:35:04 +01:00
// 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' ) ;
}
}
2017-02-05 14:47:03 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* displays the paste
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . show
* @ private
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
function showPaste ( )
2017-02-05 21:22:09 +01:00
{
2017-02-13 11:35:04 +01:00
// 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' ) ;
}
2017-02-08 20:11:04 +01:00
2017-02-13 11:35:04 +01:00
// if the discussion is opened on this paste, display it
if ( paste . meta . opendiscussion )
{
$comments . html ( '' ) ;
2017-02-08 20:11:04 +01:00
2017-02-13 11:35:04 +01:00
var $divComment ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// 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 ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
$divComment = $ ( '<article><div class="comment" id="comment_' + comment . id
+ '"><div class="commentmeta"><span class="nickname"></span>'
+ '<span class="commentdate"></span></div>'
+ '<div class="commentdata"></div>'
+ '<button class="btn btn-default btn-sm">'
+ i18n . _ ( 'Reply' ) + '</button></div></article>' ) ;
var $divCommentData = $divComment . find ( 'div.commentdata' ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// 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 ) ;
2017-02-08 20:11:04 +01:00
2017-02-13 11:35:04 +01:00
// 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 ( '<i>' + i18n . _ ( 'Anonymous' ) + '</i>' ) ;
}
$divComment . find ( 'span.commentdate' )
. text ( ' (' + ( new Date ( comment . meta . postdate * 1000 ) . toLocaleString ( ) ) + ')' )
. attr ( 'title' , 'CommentID: ' + comment . id ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// if an avatar is available, display it
if ( comment . meta . vizhash )
{
$divComment . find ( 'span.nickname' )
. before (
'<img src="' + comment . meta . vizhash + '" class="vizhash" title="' +
i18n . _ ( 'Anonymous avatar (Vizhash of the IP address)' ) + '" /> '
) ;
}
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
$place . append ( $divComment ) ;
}
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// add 'add new comment' area
$divComment = $ (
'<div class="comment"><button class="btn btn-default btn-sm">' +
i18n . _ ( 'Add comment' ) + '</button></div>'
) ;
$divComment . find ( 'button' ) . click ( { commentid : helper . pasteId ( ) } , me . openReply ) ;
$comments . append ( $divComment ) ;
$discussion . removeClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
}
2017-02-13 11:35:04 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* open the comment entry when clicking the "Reply" button of a comment
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . openReply
2017-02-08 20:12:22 +01:00
* @ function
* @ param { Event } event
* /
2017-02-13 11:35:04 +01:00
me . openReply = function ( event )
2016-11-13 18:12:10 +01:00
{
2017-02-08 20:12:22 +01:00
event . preventDefault ( ) ;
2017-02-13 11:35:04 +01:00
// 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 ( ) ;
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* sets the format in which the text is shown
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . setFormat
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { string } the the new format
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . setFormat = function ( newFormat )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
if ( format !== newFormat ) {
format = newFormat ;
isChanged = true ;
}
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* returns the current format
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . setFormat
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ return { string }
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . getFormat = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
return format ;
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* sets the text to show
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name editor . init
2017-02-08 20:12:22 +01:00
* @ function
2017-02-13 11:35:04 +01:00
* @ param { string } the text to show
2017-02-08 20:12:22 +01:00
* /
2017-02-13 11:35:04 +01:00
me . setText = function ( newText )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
if ( text !== newText ) {
text = newText ;
isChanged = true ;
}
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* show / update the parsed text ( preview )
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . trigger
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . trigger = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
if ( isChanged ) {
parsePaste ( ) ;
isChanged = false ;
}
if ( ! isDisplayed ) {
showPaste ( ) ;
isDisplayed = true ;
}
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* hide parsed text ( preview )
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name pasteViewer . hide
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
me . hide = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
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 ;
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* init status manager
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* preloads jQuery elements
*
* @ name editor . init
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-12 18:08:08 +01:00
me . init = function ( )
2017-02-08 20:12:22 +01:00
{
2017-02-13 11:35:04 +01:00
$clearText = $ ( '#cleartext' ) ;
$comments = $ ( '#comments' ) ;
$discussion = $ ( '#discussion' ) ;
$image = $ ( '#image' ) ;
$placeholder = $ ( '#placeholder' ) ;
$prettyMessage = $ ( '#prettymessage' ) ;
$prettyPrint = $ ( '#prettyprint' ) ;
$remainingTime = $ ( '#remainingtime' ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// 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 ;
2017-02-08 20:12:22 +01:00
} ;
2017-02-08 20:11:04 +01:00
2017-02-12 18:08:08 +01:00
return me ;
} ) ( window , document ) ;
/ * *
* Manage top ( navigation ) bar
*
* @ param { object } window
* @ param { object } document
* @ name state
* @ class
* /
var topNav = ( function ( window , document ) {
var me = { } ;
2017-02-12 21:13:04 +01:00
var createButtonsDisplayed = false ;
var viewButtonsDisplayed = false ;
2017-02-12 18:08:08 +01:00
var $attach ,
$attachment ,
$attachmentLink ,
$burnAfterReading ,
$burnAfterReadingOption ,
$cloneButton ,
2017-02-13 11:35:04 +01:00
$clonedFile ,
2017-02-12 18:08:08 +01:00
$expiration ,
$fileRemoveButton ,
$fileWrap ,
$formatter ,
$newButton ,
2017-02-13 11:35:04 +01:00
$openDiscussionOption ,
2017-02-12 18:08:08 +01:00
$openDiscussion ,
2017-02-12 21:13:04 +01:00
$password ,
2017-02-12 18:08:08 +01:00
$rawTextButton ,
$sendButton ;
2017-02-13 11:35:04 +01:00
var pasteExpiration = '1week' ;
2017-02-12 18:08:08 +01:00
/ * *
2017-02-13 11:35:04 +01:00
* set the expiration on bootstrap templates in dropdown
2017-02-12 18:08:08 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name topNav . updateExpiration
2017-02-12 18:08:08 +01:00
* @ function
* @ param { Event } event
* /
2017-02-13 11:35:04 +01:00
function updateExpiration ( event )
2017-02-12 18:08:08 +01:00
{
2017-02-13 11:35:04 +01:00
// get selected option
2017-02-12 18:08:08 +01:00
var target = $ ( event . target ) ;
2017-02-13 11:35:04 +01:00
// update dropdown display and save new expiration time
2017-02-12 18:08:08 +01:00
$ ( '#pasteExpirationDisplay' ) . text ( target . text ( ) ) ;
2017-02-13 11:35:04 +01:00
pasteExpiration = target . data ( 'expiration' ) ;
event . preventDefault ( ) ;
2017-02-12 18:08:08 +01:00
}
/ * *
2017-02-13 11:35:04 +01:00
* set the format on bootstrap templates in dropdown
2017-02-12 18:08:08 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name topNav . updateFormat
2017-02-12 18:08:08 +01:00
* @ function
* @ param { Event } event
* /
2017-02-13 11:35:04 +01:00
function updateFormat ( event )
2017-02-12 18:08:08 +01:00
{
2017-02-13 11:35:04 +01:00
// get selected option
var $target = $ ( event . target ) ;
2017-02-12 18:08:08 +01:00
2017-02-13 11:35:04 +01:00
// 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 ( ) ;
2017-02-12 18:08:08 +01:00
}
2017-02-13 11:35:04 +01:00
2017-02-12 18:08:08 +01:00
event . preventDefault ( ) ;
2017-02-13 11:35:04 +01:00
}
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* when "burn after reading" is checked , disable discussion
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* @ name topNav . changeBurnAfterReading
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-12 18:08:08 +01:00
function changeBurnAfterReading ( )
2017-02-08 20:11:04 +01:00
{
2017-02-13 11:35:04 +01:00
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' ) ;
2017-02-08 20:12:22 +01:00
}
2017-02-12 18:08:08 +01:00
}
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* when discussion is checked , disable "burn after reading"
2017-02-08 20:12:22 +01:00
*
2017-02-13 11:35:04 +01:00
* @ name topNav . changeOpenDiscussion
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-13 11:35:04 +01:00
function changeOpenDiscussion ( )
2015-09-05 17:12:11 +02:00
{
2017-02-13 11:35:04 +01:00
if ( $openDiscussion . is ( ':checked' ) ) {
2017-02-12 18:08:08 +01:00
$burnAfterReadingOption . addClass ( 'buttondisabled' ) ;
2017-02-13 11:35:04 +01:00
$burnAfterReading . prop ( 'checked' , false ) ;
// if button is actually disabled, force-enable it and uncheck other button
$openDiscussionOption . removeClass ( 'buttondisabled' ) ;
} else {
2017-02-12 18:08:08 +01:00
$burnAfterReadingOption . removeClass ( 'buttondisabled' ) ;
2017-02-08 20:12:22 +01:00
}
2017-02-12 18:08:08 +01:00
}
/ * *
* return raw text
*
* @ name topNav . rawText
* @ function
* @ param { Event } event
* /
function rawText ( event )
{
2017-02-13 11:35:04 +01:00
var paste = pasteViewer . getFormat ( ) === 'markdown' ?
2017-02-12 18:08:08 +01:00
$prettyPrint . text ( ) : $clearText . text ( ) ;
history . pushState (
null , document . title , helper . scriptLocation ( ) + '?' +
helper . pasteId ( ) + '#' + helper . pageKey ( )
) ;
// we use text/html instead of text/plain to avoid a bug when
// reloading the raw text view (it reverts to type text/html)
var newDoc = document . open ( 'text/html' , 'replace' ) ;
newDoc . write ( '<pre>' + helper . htmlEntities ( paste ) + '</pre>' ) ;
newDoc . close ( ) ;
event . preventDefault ( ) ;
}
/ * *
* set the language in a cookie and reload the page
*
* @ name topNav . setLanguage
* @ function
* @ param { Event } event
* /
function setLanguage ( event )
{
document . cookie = 'lang=' + $ ( event . target ) . data ( 'lang' ) ;
me . reloadPage ( event ) ;
}
/ * *
* removes an attachment
*
* @ name controller . removeAttachment
* @ function
* /
me . removeAttachment = function ( )
{
$clonedFile . addClass ( 'hidden' ) ;
// removes the saved decrypted file data
$attachmentLink . attr ( 'href' , '' ) ;
// the only way to deselect the file is to recreate the input // @TODO really?
$fileWrap . html ( $fileWrap . html ( ) ) ;
$fileWrap . removeClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
} ;
2015-09-05 17:12:11 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* Shows all elements belonging to viwing an existing pastes
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* @ name topNav . hideAllElem
2017-02-08 20:12:22 +01:00
* @ function
* /
2017-02-12 18:08:08 +01:00
me . showViewButtons = function ( )
2015-09-05 17:12:11 +02:00
{
2017-02-12 21:13:04 +01:00
if ( viewButtonsDisplayed ) {
console . log ( 'showViewButtons: view buttons are already displayed' ) ;
return ;
}
2017-02-12 18:08:08 +01:00
$cloneButton . removeClass ( 'hidden' ) ;
$rawTextButton . removeClass ( 'hidden' ) ;
2017-02-12 21:13:04 +01:00
viewButtonsDisplayed = true ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 20:12:22 +01:00
2017-02-12 18:08:08 +01:00
/ * *
* Hides all elements belonging to existing pastes
*
* @ name topNav . hideAllElem
* @ function
* /
me . hideViewButtons = function ( )
{
2017-02-12 21:13:04 +01:00
if ( ! viewButtonsDisplayed ) {
console . log ( 'hideViewButtons: view buttons are already hidden' ) ;
return ;
}
2017-02-12 18:08:08 +01:00
$cloneButton . addClass ( 'hidden' ) ;
$rawTextButton . addClass ( 'hidden' ) ;
2017-02-12 21:13:04 +01:00
viewButtonsDisplayed = false ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 20:12:22 +01:00
2017-02-12 18:08:08 +01:00
/ * *
* shows all elements needed when creating a new paste
*
* @ name topNav . setLanguage
* @ function
* /
me . showCreateButtons = function ( )
{
2017-02-12 21:13:04 +01:00
if ( createButtonsDisplayed ) {
console . log ( 'showCreateButtons: create buttons are already displayed' ) ;
return ;
}
$attachment . removeClass ( 'hidden' ) ;
2017-02-12 18:08:08 +01:00
$sendButton . removeClass ( 'hidden' ) ;
$expiration . removeClass ( 'hidden' ) ;
$formatter . removeClass ( 'hidden' ) ;
$burnAfterReadingOption . removeClass ( 'hidden' ) ;
2017-02-13 11:35:04 +01:00
$openDiscussionOption . removeClass ( 'hidden' ) ;
2017-02-12 18:08:08 +01:00
$newButton . removeClass ( 'hidden' ) ;
$password . removeClass ( 'hidden' ) ;
$attach . removeClass ( 'hidden' ) ;
2017-02-13 11:35:04 +01:00
// $clonedFile.removeClass('hidden'); // @TODO
2017-02-12 21:13:04 +01:00
createButtonsDisplayed = true ;
2017-02-12 18:08:08 +01:00
} ;
2017-02-08 20:12:22 +01:00
2017-02-12 18:08:08 +01:00
/ * *
* shows all elements needed when creating a new paste
*
* @ name topNav . setLanguage
* @ function
* /
me . hideCreateButtons = function ( )
{
2017-02-12 21:13:04 +01:00
if ( ! createButtonsDisplayed ) {
console . log ( 'hideCreateButtons: create buttons are already hidden' ) ;
return ;
}
$attachment . addClass ( 'hidden' ) ;
2017-02-12 18:08:08 +01:00
$sendButton . addClass ( 'hidden' ) ;
$expiration . addClass ( 'hidden' ) ;
$formatter . addClass ( 'hidden' ) ;
$burnAfterReadingOption . addClass ( 'hidden' ) ;
2017-02-13 11:35:04 +01:00
$openDiscussionOption . addClass ( 'hidden' ) ;
2017-02-12 18:08:08 +01:00
$newButton . addClass ( 'hidden' ) ;
$password . addClass ( 'hidden' ) ;
$attach . addClass ( 'hidden' ) ;
2017-02-13 11:35:04 +01:00
// $clonedFile.addClass('hidden'); // @TODO
2017-02-12 21:13:04 +01:00
createButtonsDisplayed = false ;
2017-02-12 18:08:08 +01:00
} ;
2012-04-23 16:30:02 +02:00
2017-02-12 18:08:08 +01:00
/ * *
* only shows the "new paste" button
*
* @ name topNav . setLanguage
* @ function
* /
me . showNewPasteButton = function ( )
{
$newButton . addClass ( 'hidden' ) ;
2017-02-08 20:12:22 +01:00
} ;
2012-04-23 16:30:02 +02:00
2017-02-08 20:12:22 +01:00
/ * *
2017-02-12 18:08:08 +01:00
* shows a loading message , optionally with a percentage
2017-02-08 20:12:22 +01:00
*
2017-02-12 18:08:08 +01:00
* @ name topNav . showLoading
2017-02-08 20:12:22 +01:00
* @ function
2017-02-12 18:08:08 +01:00
* @ param { string } message
* @ param { int } percentage
2017-02-08 20:12:22 +01:00
* /
2017-02-12 18:08:08 +01:00
me . showLoading = function ( message , percentage )
2017-02-08 20:11:04 +01:00
{
2017-02-12 18:08:08 +01:00
// currently parameters are ignored
$loadingIndicator . removeClass ( 'hidden' ) ;
} ;
/ * *
* hides the loading message
*
* @ name topNav . hideLoading
* @ function
* /
me . hideLoading = function ( )
{
$loadingIndicator . removeClass ( 'hidden' ) ;
} ;
2017-02-08 20:12:22 +01:00
2017-02-13 11:35:04 +01:00
/ * *
* returns the currently set expiration time
*
* @ name topNav . getExpiration
* @ function
* @ return { int }
* /
me . getExpiration = function ( )
{
return pasteExpiration ;
} ;
2017-02-12 18:08:08 +01:00
/ * *
* init navigation manager
*
* preloads jQuery elements
*
* @ name topNav . init
* @ function
* /
me . init = function ( )
{
2017-02-08 20:12:22 +01:00
$attach = $ ( '#attach' ) ;
$attachment = $ ( '#attachment' ) ;
$attachmentLink = $ ( '#attachment a' ) ;
$burnAfterReading = $ ( '#burnafterreading' ) ;
$burnAfterReadingOption = $ ( '#burnafterreadingoption' ) ;
$cloneButton = $ ( '#clonebutton' ) ;
2017-02-13 11:35:04 +01:00
$clonedFile = $ ( '#clonedfile' ) ;
2017-02-08 20:12:22 +01:00
$expiration = $ ( '#expiration' ) ;
$fileRemoveButton = $ ( '#fileremovebutton' ) ;
$fileWrap = $ ( '#filewrap' ) ;
$formatter = $ ( '#formatter' ) ;
$newButton = $ ( '#newbutton' ) ;
2017-02-13 11:35:04 +01:00
$openDiscussionOption = $ ( '#opendiscussionoption' ) ;
2017-02-08 20:12:22 +01:00
$openDiscussion = $ ( '#opendiscussion' ) ;
2017-02-12 21:13:04 +01:00
$password = $ ( '#password' ) ;
2017-02-08 20:12:22 +01:00
$rawTextButton = $ ( '#rawtextbutton' ) ;
$sendButton = $ ( '#sendbutton' ) ;
2013-11-01 01:15:14 +01:00
2017-02-12 18:08:08 +01:00
// bootstrap template drop down
$ ( '#language ul.dropdown-menu li a' ) . click ( me . setLanguage ) ;
// page template drop down
$ ( '#language select option' ) . click ( me . setLanguage ) ;
// bind events
$burnAfterReading . change ( changeBurnAfterReading ) ;
2017-02-13 11:35:04 +01:00
$openDiscussionOption . change ( changeOpenDiscussion ) ;
2017-02-12 21:13:04 +01:00
$newButton . click ( controller . newPaste ) ;
2017-02-12 18:08:08 +01:00
$sendButton . click ( controller . sendData ) ;
$cloneButton . click ( controller . clonePaste ) ;
2017-02-12 21:13:04 +01:00
$rawTextButton . click ( rawText ) ;
2017-02-12 18:08:08 +01:00
$fileRemoveButton . click ( me . removeAttachment ) ;
2017-02-13 11:35:04 +01:00
// bootstrap template drop downs
$ ( 'ul.dropdown-menu li a' , $ ( '#expiration' ) . parent ( ) ) . click ( updateExpiration ) ;
$ ( 'ul.dropdown-menu li a' , $ ( '#formatter' ) . parent ( ) ) . click ( updateFormat ) ;
2017-02-12 18:08:08 +01:00
// initiate default state of checkboxes
changeBurnAfterReading ( ) ;
2017-02-13 11:35:04 +01:00
changeOpenDiscussion ( ) ;
// get default value from template or fall back to set value
pasteExpiration = modal . getExpirationDefault ( ) || pasteExpiration ;
2017-02-12 18:08:08 +01:00
} ;
return me ;
} ) ( window , document ) ;
/ * *
* PrivateBin logic
*
* @ param { object } window
* @ param { object } document
* @ name controller
* @ class
* /
var controller = ( function ( window , document ) {
var me = { } ;
/ * *
* headers to send in AJAX requests
*
* @ private
* @ enum { Object }
* /
var headers = { 'X-Requested-With' : 'JSONHttpRequest' } ;
/ * *
* URL shortners create address
*
* @ private
* @ prop { string }
* /
var shortenerUrl = '' ;
/ * *
* URL of newly created paste
*
* @ private
* @ prop { string }
* /
var createdPasteUrl = '' ;
/ * *
* send a reply in a discussion
*
* @ name controller . sendComment
* @ function
* @ param { Event } event
* /
me . sendComment = function ( event )
{
event . preventDefault ( ) ;
$errorMessage . addClass ( 'hidden' ) ;
// do not send if no data
var replyMessage = $ ( '#replymessage' ) ;
if ( replyMessage . val ( ) . length === 0 )
{
return ;
}
me . showStatus ( i18n . _ ( 'Sending comment...' ) , true ) ;
var parentid = event . data . parentid ,
key = helper . pageKey ( ) ,
2017-02-12 21:13:04 +01:00
cipherdata = cryptTool . cipher ( key , $passwordInput . val ( ) , replyMessage . val ( ) ) ,
2017-02-12 18:08:08 +01:00
ciphernickname = '' ,
nick = $ ( '#nickname' ) . val ( ) ;
if ( nick . length > 0 )
{
2017-02-12 21:13:04 +01:00
ciphernickname = cryptTool . cipher ( key , $passwordInput . val ( ) , nick ) ;
2017-02-12 18:08:08 +01:00
}
var data _to _send = {
data : cipherdata ,
parentid : parentid ,
pasteid : helper . pasteId ( ) ,
nickname : ciphernickname
} ;
$ . ajax ( {
type : 'POST' ,
url : helper . scriptLocation ( ) ,
data : data _to _send ,
dataType : 'json' ,
headers : headers ,
success : function ( data )
{
if ( data . status === 0 )
{
controller . showStatus ( i18n . _ ( 'Comment posted.' ) ) ;
$ . ajax ( {
type : 'GET' ,
url : helper . scriptLocation ( ) + '?' + helper . pasteId ( ) ,
dataType : 'json' ,
headers : headers ,
success : function ( data )
{
if ( data . status === 0 )
{
controller . displayMessages ( data ) ;
}
else if ( data . status === 1 )
{
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , data . message ) ) ;
}
else
{
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'unknown status' ) ) ) ;
}
}
} )
. fail ( function ( ) {
controller . showError ( i18n . _ ( 'Could not refresh display: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
} ) ;
}
else if ( data . status === 1 )
{
controller . showError ( i18n . _ ( 'Could not post comment: %s' , data . message ) ) ;
}
else
{
controller . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'unknown status' ) ) ) ;
}
}
} )
. fail ( function ( ) {
controller . showError ( i18n . _ ( 'Could not post comment: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
} ) ;
} ;
/ * *
* send a new paste to server
*
* @ name controller . sendData
* @ function
* @ param { Event } event
* /
me . sendData = function ( event )
{
event . preventDefault ( ) ;
var file = document . getElementById ( 'file' ) ,
files = ( file && file . files ) ? file . files : null ; // FileList object
// do not send if no data.
if ( $message . val ( ) . length === 0 && ! ( files && files [ 0 ] ) )
{
return ;
}
// if sjcl has not collected enough entropy yet, display a message
if ( ! sjcl . random . isReady ( ) )
{
me . showStatus ( i18n . _ ( 'Sending paste (Please move your mouse for more entropy)...' ) , true ) ;
sjcl . random . addEventListener ( 'seeded' , function ( ) {
me . sendData ( event ) ;
} ) ;
return ;
}
$ ( '.navbar-toggle' ) . click ( ) ;
$password . addClass ( 'hidden' ) ;
me . showStatus ( i18n . _ ( 'Sending paste...' ) , true ) ;
me . stateSubmittingPaste ( ) ;
var randomkey = sjcl . codec . base64 . fromBits ( sjcl . random . randomWords ( 8 , 0 ) , 0 ) ,
password = $passwordInput . val ( ) ;
if ( files && files [ 0 ] )
{
if ( typeof FileReader === undefined )
{
// revert loading status…
me . stateNewPaste ( ) ;
me . showError ( i18n . _ ( 'Your browser does not support uploading encrypted files. Please use a newer browser.' ) ) ;
return ;
}
var reader = new FileReader ( ) ;
// closure to capture the file information
reader . onload = ( function ( theFile )
{
return function ( e ) {
controller . sendDataContinue (
randomkey ,
2017-02-12 21:13:04 +01:00
cryptTool . cipher ( randomkey , password , e . target . result ) ,
cryptTool . cipher ( randomkey , password , theFile . name )
2017-02-12 18:08:08 +01:00
) ;
} ;
} ) ( files [ 0 ] ) ;
reader . readAsDataURL ( files [ 0 ] ) ;
}
else if ( $attachmentLink . attr ( 'href' ) )
{
me . sendDataContinue (
randomkey ,
2017-02-12 21:13:04 +01:00
cryptTool . cipher ( randomkey , password , $attachmentLink . attr ( 'href' ) ) ,
2017-02-12 18:08:08 +01:00
$attachmentLink . attr ( 'download' )
) ;
}
else
{
me . sendDataContinue ( randomkey , '' , '' ) ;
}
} ;
/ * *
* send a new paste to server , step 2
*
* @ name controller . sendDataContinue
* @ function
* @ param { string } randomkey
* @ param { string } cipherdata _attachment
* @ param { string } cipherdata _attachment _name
* /
me . sendDataContinue = function ( randomkey , cipherdata _attachment , cipherdata _attachment _name )
{
2017-02-13 11:35:04 +01:00
var cipherdata = cryptTool . cipher ( randomkey , $passwordInput . val ( ) , editor . getText ( ) ) ,
2017-02-12 18:08:08 +01:00
data _to _send = {
data : cipherdata ,
2017-02-13 11:35:04 +01:00
expire : topNav . getExpiration ( ) ,
formatter : pasteViewer . getFormat ( ) ,
2017-02-12 18:08:08 +01:00
burnafterreading : $burnAfterReading . is ( ':checked' ) ? 1 : 0 ,
opendiscussion : $openDiscussion . is ( ':checked' ) ? 1 : 0
} ;
if ( cipherdata _attachment . length > 0 )
{
data _to _send . attachment = cipherdata _attachment ;
if ( cipherdata _attachment _name . length > 0 )
{
data _to _send . attachmentname = cipherdata _attachment _name ;
}
}
$ . ajax ( {
type : 'POST' ,
url : helper . scriptLocation ( ) ,
data : data _to _send ,
dataType : 'json' ,
headers : headers ,
success : function ( data )
{
if ( data . status === 0 ) {
me . stateExistingPaste ( ) ;
var url = helper . scriptLocation ( ) + '?' + data . id + '#' + randomkey ,
deleteUrl = helper . scriptLocation ( ) + '?pasteid=' + data . id + '&deletetoken=' + data . deletetoken ;
me . hideStatus ( ) ;
$errorMessage . addClass ( 'hidden' ) ;
// show new URL in browser bar
history . pushState ( { type : 'newpaste' } , document . title , url ) ;
$ ( '#pastelink' ) . html (
i18n . _ (
'Your paste is <a id="pasteurl" href="%s">%s</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span>' ,
url , url
) + me . shortenUrl ( url )
) ;
// save newly created element
$pasteUrl = $ ( '#pasteurl' ) ;
// and add click event
$pasteUrl . click ( me . pasteLinkClick ) ;
var shortenButton = $ ( '#shortenbutton' ) ;
if ( shortenButton ) {
shortenButton . click ( me . sendToShortener ) ;
}
$ ( '#deletelink' ) . html ( '<a href="' + deleteUrl + '">' + i18n . _ ( 'Delete data' ) + '</a>' ) ;
$pasteResult . removeClass ( 'hidden' ) ;
// we pre-select the link so that the user only has to [Ctrl]+[c] the link
helper . selectText ( $pasteUrl [ 0 ] ) ;
me . hideStatus ( ) ;
me . formatPaste ( data _to _send . formatter , $message . val ( ) ) ;
}
else if ( data . status === 1 )
{
// revert loading status…
controller . stateNewPaste ( ) ;
controller . showError ( i18n . _ ( 'Could not create paste: %s' , data . message ) ) ;
}
else
{
// revert loading status…
controller . stateNewPaste ( ) ;
controller . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'unknown status' ) ) ) ;
}
}
} )
. fail ( function ( )
{
// revert loading status…
me . stateNewPaste ( ) ;
controller . showError ( i18n . _ ( 'Could not create paste: %s' , i18n . _ ( 'server error or not responding' ) ) ) ;
} ) ;
} ;
/ * *
* check if a URL shortener was defined and create HTML containing a link to it
*
* @ name controller . shortenUrl
* @ function
* @ param { string } url
* @ return { string } html
* /
me . shortenUrl = function ( url )
{
var shortenerHtml = $ ( '#shortenbutton' ) ;
if ( shortenerHtml ) {
shortenerUrl = shortenerHtml . data ( 'shortener' ) ;
createdPasteUrl = url ;
return ' ' + $ ( '<div />' ) . append ( shortenerHtml . clone ( ) ) . html ( ) ;
}
return '' ;
} ;
/ * *
* forward to URL shortener
*
* @ name controller . sendToShortener
* @ function
* @ param { Event } event
* /
me . sendToShortener = function ( event )
{
window . location . href = shortenerUrl + encodeURIComponent ( createdPasteUrl ) ;
event . preventDefault ( ) ;
} ;
2017-02-12 21:13:04 +01:00
/ * *
* creates a new paste
*
* @ name controller . newPaste
* @ function
* /
me . newPaste = function ( )
{
2017-02-13 11:35:04 +01:00
topNav . hideViewButtons ( ) ;
2017-02-12 21:13:04 +01:00
topNav . showCreateButtons ( ) ;
editor . resetInput ( ) ;
editor . show ( ) ;
editor . focusInput ( ) ;
} ;
2017-02-12 18:08:08 +01:00
/ * *
* clone the current paste
*
* @ name controller . clonePaste
* @ function
* @ param { Event } event
* /
me . clonePaste = function ( event )
{
me . stateNewPaste ( ) ;
// erase the id and the key in url
history . replaceState ( null , document . title , helper . scriptLocation ( ) ) ;
status . hideStatus ( ) ;
if ( $attachmentLink . attr ( 'href' ) )
{
$clonedFile . removeClass ( 'hidden' ) ;
$fileWrap . addClass ( 'hidden' ) ;
}
2017-02-12 21:13:04 +01:00
$message . val (
2017-02-13 11:35:04 +01:00
pasteViewer . getFormat ( ) === 'markdown' ?
2017-02-12 21:13:04 +01:00
$prettyPrint . val ( ) : $clearText . val ( )
2017-02-12 18:08:08 +01:00
) ;
$ ( '.navbar-toggle' ) . click ( ) ;
event . preventDefault ( ) ;
} ;
/ * *
* application start
*
* @ name controller . init
* @ function
* /
me . init = function ( )
{
// first load translations
i18n . loadTranslations ( ) ;
2017-02-12 21:13:04 +01:00
// initialize other modules/"classes"
status . init ( ) ;
modal . init ( ) ;
2017-02-12 18:08:08 +01:00
uiMan . init ( ) ;
2017-02-12 21:13:04 +01:00
topNav . init ( ) ;
editor . init ( ) ;
2017-02-13 11:35:04 +01:00
pasteViewer . init ( ) ;
2017-02-12 21:13:04 +01:00
prompt . init ( ) ;
2017-02-08 20:11:04 +01:00
2017-02-08 20:12:22 +01:00
// display an existing paste
2017-02-12 21:13:04 +01:00
if ( modal . hasCipherData ( ) ) {
2017-02-08 20:12:22 +01:00
// missing decryption key in URL?
if ( window . location . hash . length === 0 )
{
2017-02-12 21:13:04 +01:00
status . showError ( i18n . _ ( 'Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)' ) ) ;
2017-02-08 20:12:22 +01:00
return ;
}
// show proper elements on screen
2017-02-12 21:13:04 +01:00
// me.hideCreateButtons(); // they should not be visible in the first place
me . showViewButtons ( ) ;
2017-02-08 20:12:22 +01:00
me . displayMessages ( ) ;
2017-02-12 21:13:04 +01:00
return ;
2017-02-08 20:12:22 +01:00
}
2017-02-12 21:13:04 +01:00
// otherwise create a new paste
me . newPaste ( ) ;
2017-02-08 20:12:22 +01:00
} ;
return me ;
} ) ( window , document ) ;
2017-02-12 21:13:04 +01:00
jQuery ( document ) . ready ( function ( ) {
/ * *
* main application start , called when DOM is fully loaded and
* runs controller initalization
* /
$ ( controller . init ) ;
} ) ;
2017-02-08 20:12:22 +01:00
return {
helper : helper ,
i18n : i18n ,
2017-02-12 21:13:04 +01:00
cryptTool : cryptTool ,
2017-02-08 20:12:22 +01:00
controller : controller
} ;
} ( jQuery , sjcl , Base64 , RawDeflate ) ;