diff options
author | tg(x) <*@tg-x.net> | 2016-10-05 00:02:10 +0200 |
---|---|---|
committer | tg(x) <*@tg-x.net> | 2016-10-05 00:02:10 +0200 |
commit | ec62d29c90958aa8d41474ed2fe5a179d6fafed7 (patch) | |
tree | 367cc55bc6772cf194ed6c4778cd344d581d3d7c | |
parent | fda241d74d5c1c39203b64da676c684d4dc9d800 (diff) | |
parent | d3ccf4103900b8d990b1970d135695b938d94eae (diff) |
Merge branch 'master' of taler.net:/var/git/wallet-webex
78 files changed, 7639 insertions, 1072 deletions
diff --git a/.gitmodules b/.gitmodules index 50f41d7fb..f2656da2f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,7 +2,3 @@ path = web-common url = git://taler.net/web-common branch = master -[submodule "pages/help"] - path = pages/help - url = git://taler.net/help - branch = master diff --git a/articles/ui/ui.bib b/articles/ui/ui.bib index 05e00a44e..793cd721d 100644 --- a/articles/ui/ui.bib +++ b/articles/ui/ui.bib @@ -1,9 +1,9 @@ -@Misc{pigs, - author = {W3c}, +@Misc{w3cpig, + author = {W3C}, title = {Web Payments Payment Flows}, - howpublished = {\url{https://github.com/w3c/webpayments/tree/gh-pages/PaymentFlows}}, + howpublished = {\url{https://github.com/w3c/webpayments-flows/tree/gh-pages/TargetFlows}}, month = {February}, year = {2016}, } diff --git a/articles/ui/ui.tex b/articles/ui/ui.tex index 6a4dab10e..adf1a025f 100644 --- a/articles/ui/ui.tex +++ b/articles/ui/ui.tex @@ -174,7 +174,7 @@ protocols.\footnote{Details of the protocol are documented at \url{https://api.taler.net/}} The basic cryptography behind blind-signature based payment systems has been known for over 25 years~\cite{chaum1990untraceable}. However, it was not until 2015 that the W3C -started the payments working group~\cite{pigs} to explore requirements +started the payments working group~\cite{w3cpig} to explore requirements for deploying payment systems that are more secure and easy to use for the Web. Our work describes how a modern payment system using blind signatures could practically be integrated with the modern Web to @@ -263,7 +263,7 @@ issued by the customer's bank. \begin{center} \includegraphics[width=0.95\textwidth]{figs/cc3ds.pdf} \end{center} -\caption{Card payment processing with 3-D Secure (3DS). (From: W3C Web Payments IG.)} +\caption{Card payment processing with 3-D Secure (3DS). (From: W3C Web Payments WG \cite{w3cpig})} \label{fig:cc3ds} \end{figure*} %KG: I hope they can print this larger, because this is WAY too small to be of any use. @@ -291,7 +291,7 @@ of this, the data entry is often performed on a Web site that is operated by a third-party payment processor and {\em not} the merchant or the customer's bank. Figure~\ref{fig:cc3ds} shows a typical card-based payment process on the Web using the UML style of the W3C payment interest -group~\cite{pigs}. Most of the details are not relevant to this +group~\cite{w3cpig}. Most of the details are not relevant to this paper, but the diagram nicely illustrates the complexity of the common 3-D secure process. %KG: Define 3DS the FIRST time you use it. @@ -355,7 +355,7 @@ online shopping~\cite[p. 50]{ibi2014}. \begin{figure*}[b!] \includegraphics[width=\textwidth]{figs/bitcoin.pdf} -\caption{Bitcoin payment processing. (From: W3C Web Payments IG.)} +\caption{Bitcoin payment processing. (From: W3C Web Payments WG \cite{w3cpig})} \label{fig:bitcoin} \end{figure*} @@ -1549,7 +1549,7 @@ at \url{https://demo.taler.net/}. This work benefits from the financial support of the Brittany Region (ARED 9178) and a grant from the Renewable Freedom Foundation. We thank Bruno Haible for his financial support enabling us to -participate with the W3c payment working group. We thank the W3C +participate with the W3C payment working group. We thank the W3C payment working group for insightful discussions about Web payments. We thank Krista Grothoff and Neal Walfield for comments on an earlier draft of the paper. We thank Gabor Toth for his help with the diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts index 64f65e7e5..0e723b52c 100644 --- a/content_scripts/notify.ts +++ b/content_scripts/notify.ts @@ -287,6 +287,11 @@ namespace TalerNotify { }) }); + addHandler("taler-payment-succeeded", (msg: any, sendResponse: any) => { + console.log("got taler-payment-succeeded"); + sendResponse(); + }); + addHandler("taler-get-payment", (msg: any, sendResponse: any) => { const walletMsg = { type: "execute-payment", diff --git a/gulpfile.js b/gulpfile.js index ce90fec8e..443eabc43 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -217,7 +217,7 @@ gulp.task("compile-prod", ["clean"], function () { tsArgs.outDir = "."; // We don't want source maps for production tsArgs.sourceMap = undefined; - return gulp.src(paths.ts.release) + return gulp.src(paths.ts.release, {base: "."}) .pipe(ts(tsArgs)) .pipe(gulp.dest("build/ext/")); }); diff --git a/img/taler-logo-24.png b/img/taler-logo-24.png Binary files differdeleted file mode 100644 index 00a908e79..000000000 --- a/img/taler-logo-24.png +++ /dev/null diff --git a/lib/vendor/jed.js b/lib/vendor/jed.js index fad370d83..72ac5c3ec 100644..120000 --- a/lib/vendor/jed.js +++ b/lib/vendor/jed.js @@ -1,1022 +1 @@ -/** - * @preserve jed.js https://github.com/SlexAxton/Jed - */ -/* ------------ -A gettext compatible i18n library for modern JavaScript Applications - -by Alex Sexton - AlexSexton [at] gmail - @SlexAxton -WTFPL license for use -Dojo CLA for contributions - -Jed offers the entire applicable GNU gettext spec'd set of -functions, but also offers some nicer wrappers around them. -The api for gettext was written for a language with no function -overloading, so Jed allows a little more of that. - -Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote -gettext.js back in 2008. I was able to vet a lot of my ideas -against his. I also made sure Jed passed against his tests -in order to offer easy upgrades -- jsgettext.berlios.de -*/ -(function (root, undef) { - - // Set up some underscore-style functions, if you already have - // underscore, feel free to delete this section, and use it - // directly, however, the amount of functions used doesn't - // warrant having underscore as a full dependency. - // Underscore 1.3.0 was used to port and is licensed - // under the MIT License by Jeremy Ashkenas. - var ArrayProto = Array.prototype, - ObjProto = Object.prototype, - slice = ArrayProto.slice, - hasOwnProp = ObjProto.hasOwnProperty, - nativeForEach = ArrayProto.forEach, - breaker = {}; - - // We're not using the OOP style _ so we don't need the - // extra level of indirection. This still means that you - // sub out for real `_` though. - var _ = { - forEach : function( obj, iterator, context ) { - var i, l, key; - if ( obj === null ) { - return; - } - - if ( nativeForEach && obj.forEach === nativeForEach ) { - obj.forEach( iterator, context ); - } - else if ( obj.length === +obj.length ) { - for ( i = 0, l = obj.length; i < l; i++ ) { - if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) { - return; - } - } - } - else { - for ( key in obj) { - if ( hasOwnProp.call( obj, key ) ) { - if ( iterator.call (context, obj[key], key, obj ) === breaker ) { - return; - } - } - } - } - }, - extend : function( obj ) { - this.forEach( slice.call( arguments, 1 ), function ( source ) { - for ( var prop in source ) { - obj[prop] = source[prop]; - } - }); - return obj; - } - }; - // END Miniature underscore impl - - // Jed is a constructor function - var Jed = function ( options ) { - // Some minimal defaults - this.defaults = { - "locale_data" : { - "messages" : { - "" : { - "domain" : "messages", - "lang" : "en", - "plural_forms" : "nplurals=2; plural=(n != 1);" - } - // There are no default keys, though - } - }, - // The default domain if one is missing - "domain" : "messages", - // enable debug mode to log untranslated strings to the console - "debug" : false - }; - - // Mix in the sent options with the default options - this.options = _.extend( {}, this.defaults, options ); - this.textdomain( this.options.domain ); - - if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) { - throw new Error('Text domain set to non-existent domain: `' + options.domain + '`'); - } - }; - - // The gettext spec sets this character as the default - // delimiter for context lookups. - // e.g.: context\u0004key - // If your translation company uses something different, - // just change this at any time and it will use that instead. - Jed.context_delimiter = String.fromCharCode( 4 ); - - function getPluralFormFunc ( plural_form_string ) { - return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);"); - } - - function Chain( key, i18n ){ - this._key = key; - this._i18n = i18n; - } - - // Create a chainable api for adding args prettily - _.extend( Chain.prototype, { - onDomain : function ( domain ) { - this._domain = domain; - return this; - }, - withContext : function ( context ) { - this._context = context; - return this; - }, - ifPlural : function ( num, pkey ) { - this._val = num; - this._pkey = pkey; - return this; - }, - fetch : function ( sArr ) { - if ( {}.toString.call( sArr ) != '[object Array]' ) { - sArr = [].slice.call(arguments, 0); - } - return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )( - this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val), - sArr - ); - } - }); - - // Add functions to the Jed prototype. - // These will be the functions on the object that's returned - // from creating a `new Jed()` - // These seem redundant, but they gzip pretty well. - _.extend( Jed.prototype, { - // The sexier api start point - translate : function ( key ) { - return new Chain( key, this ); - }, - - textdomain : function ( domain ) { - if ( ! domain ) { - return this._textdomain; - } - this._textdomain = domain; - }, - - gettext : function ( key ) { - return this.dcnpgettext.call( this, undef, undef, key ); - }, - - dgettext : function ( domain, key ) { - return this.dcnpgettext.call( this, domain, undef, key ); - }, - - dcgettext : function ( domain , key /*, category */ ) { - // Ignores the category anyways - return this.dcnpgettext.call( this, domain, undef, key ); - }, - - ngettext : function ( skey, pkey, val ) { - return this.dcnpgettext.call( this, undef, undef, skey, pkey, val ); - }, - - dngettext : function ( domain, skey, pkey, val ) { - return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); - }, - - dcngettext : function ( domain, skey, pkey, val/*, category */) { - return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); - }, - - pgettext : function ( context, key ) { - return this.dcnpgettext.call( this, undef, context, key ); - }, - - dpgettext : function ( domain, context, key ) { - return this.dcnpgettext.call( this, domain, context, key ); - }, - - dcpgettext : function ( domain, context, key/*, category */) { - return this.dcnpgettext.call( this, domain, context, key ); - }, - - npgettext : function ( context, skey, pkey, val ) { - return this.dcnpgettext.call( this, undef, context, skey, pkey, val ); - }, - - dnpgettext : function ( domain, context, skey, pkey, val ) { - return this.dcnpgettext.call( this, domain, context, skey, pkey, val ); - }, - - // The most fully qualified gettext function. It has every option. - // Since it has every option, we can use it from every other method. - // This is the bread and butter. - // Technically there should be one more argument in this function for 'Category', - // but since we never use it, we might as well not waste the bytes to define it. - dcnpgettext : function ( domain, context, singular_key, plural_key, val ) { - // Set some defaults - - plural_key = plural_key || singular_key; - - // Use the global domain default if one - // isn't explicitly passed in - domain = domain || this._textdomain; - - var fallback; - - // Handle special cases - - // No options found - if ( ! this.options ) { - // There's likely something wrong, but we'll return the correct key for english - // We do this by instantiating a brand new Jed instance with the default set - // for everything that could be broken. - fallback = new Jed(); - return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val ); - } - - // No translation data provided - if ( ! this.options.locale_data ) { - throw new Error('No locale data provided.'); - } - - if ( ! this.options.locale_data[ domain ] ) { - throw new Error('Domain `' + domain + '` was not found.'); - } - - if ( ! this.options.locale_data[ domain ][ "" ] ) { - throw new Error('No locale meta information provided.'); - } - - // Make sure we have a truthy key. Otherwise we might start looking - // into the empty string key, which is the options for the locale - // data. - if ( ! singular_key ) { - throw new Error('No translation key found.'); - } - - var key = context ? context + Jed.context_delimiter + singular_key : singular_key, - locale_data = this.options.locale_data, - dict = locale_data[ domain ], - defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""], - pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"], - val_list, - res; - - var val_idx; - if (val === undefined) { - // No value passed in; assume singular key lookup. - val_idx = 0; - - } else { - // Value has been passed in; use plural-forms calculations. - - // Handle invalid numbers, but try casting strings for good measure - if ( typeof val != 'number' ) { - val = parseInt( val, 10 ); - - if ( isNaN( val ) ) { - throw new Error('The number that was passed in is not a number.'); - } - } - - val_idx = getPluralFormFunc(pluralForms)(val); - } - - // Throw an error if a domain isn't found - if ( ! dict ) { - throw new Error('No domain named `' + domain + '` could be found.'); - } - - val_list = dict[ key ]; - - // If there is no match, then revert back to - // english style singular/plural with the keys passed in. - if ( ! val_list || val_idx > val_list.length ) { - if (this.options.missing_key_callback) { - this.options.missing_key_callback(key, domain); - } - res = [ singular_key, plural_key ]; - - // collect untranslated strings - if (this.options.debug===true) { - console.log(res[ getPluralFormFunc(pluralForms)( val ) ]); - } - return res[ getPluralFormFunc()( val ) ]; - } - - res = val_list[ val_idx ]; - - // This includes empty strings on purpose - if ( ! res ) { - res = [ singular_key, plural_key ]; - return res[ getPluralFormFunc()( val ) ]; - } - return res; - } - }); - - - // We add in sprintf capabilities for post translation value interolation - // This is not internally used, so you can remove it if you have this - // available somewhere else, or want to use a different system. - - // We _slightly_ modify the normal sprintf behavior to more gracefully handle - // undefined values. - - /** - sprintf() for JavaScript 0.7-beta1 - http://www.diveintojavascript.com/projects/javascript-sprintf - - Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com> - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of sprintf() for JavaScript nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - var sprintf = (function() { - function get_type(variable) { - return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); - } - function str_repeat(input, multiplier) { - for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} - return output.join(''); - } - - var str_format = function() { - if (!str_format.cache.hasOwnProperty(arguments[0])) { - str_format.cache[arguments[0]] = str_format.parse(arguments[0]); - } - return str_format.format.call(null, str_format.cache[arguments[0]], arguments); - }; - - str_format.format = function(parse_tree, argv) { - var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; - for (i = 0; i < tree_length; i++) { - node_type = get_type(parse_tree[i]); - if (node_type === 'string') { - output.push(parse_tree[i]); - } - else if (node_type === 'array') { - match = parse_tree[i]; // convenience purposes only - if (match[2]) { // keyword argument - arg = argv[cursor]; - for (k = 0; k < match[2].length; k++) { - if (!arg.hasOwnProperty(match[2][k])) { - throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); - } - arg = arg[match[2][k]]; - } - } - else if (match[1]) { // positional argument (explicit) - arg = argv[match[1]]; - } - else { // positional argument (implicit) - arg = argv[cursor++]; - } - - if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { - throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); - } - - // Jed EDIT - if ( typeof arg == 'undefined' || arg === null ) { - arg = ''; - } - // Jed EDIT - - switch (match[8]) { - case 'b': arg = arg.toString(2); break; - case 'c': arg = String.fromCharCode(arg); break; - case 'd': arg = parseInt(arg, 10); break; - case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; - case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; - case 'o': arg = arg.toString(8); break; - case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; - case 'u': arg = Math.abs(arg); break; - case 'x': arg = arg.toString(16); break; - case 'X': arg = arg.toString(16).toUpperCase(); break; - } - arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); - pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; - pad_length = match[6] - String(arg).length; - pad = match[6] ? str_repeat(pad_character, pad_length) : ''; - output.push(match[5] ? arg + pad : pad + arg); - } - } - return output.join(''); - }; - - str_format.cache = {}; - - str_format.parse = function(fmt) { - var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; - while (_fmt) { - if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { - parse_tree.push(match[0]); - } - else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { - parse_tree.push('%'); - } - else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { - if (match[2]) { - arg_names |= 1; - var field_list = [], replacement_field = match[2], field_match = []; - if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { - if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { - field_list.push(field_match[1]); - } - else { - throw('[sprintf] huh?'); - } - } - } - else { - throw('[sprintf] huh?'); - } - match[2] = field_list; - } - else { - arg_names |= 2; - } - if (arg_names === 3) { - throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); - } - parse_tree.push(match); - } - else { - throw('[sprintf] huh?'); - } - _fmt = _fmt.substring(match[0].length); - } - return parse_tree; - }; - - return str_format; - })(); - - var vsprintf = function(fmt, argv) { - argv.unshift(fmt); - return sprintf.apply(null, argv); - }; - - Jed.parse_plural = function ( plural_forms, n ) { - plural_forms = plural_forms.replace(/n/g, n); - return Jed.parse_expression(plural_forms); - }; - - Jed.sprintf = function ( fmt, args ) { - if ( {}.toString.call( args ) == '[object Array]' ) { - return vsprintf( fmt, [].slice.call(args) ); - } - return sprintf.apply(this, [].slice.call(arguments) ); - }; - - Jed.prototype.sprintf = function () { - return Jed.sprintf.apply(this, arguments); - }; - // END sprintf Implementation - - // Start the Plural forms section - // This is a full plural form expression parser. It is used to avoid - // running 'eval' or 'new Function' directly against the plural - // forms. - // - // This can be important if you get translations done through a 3rd - // party vendor. I encourage you to use this instead, however, I - // also will provide a 'precompiler' that you can use at build time - // to output valid/safe function representations of the plural form - // expressions. This means you can build this code out for the most - // part. - Jed.PF = {}; - - Jed.PF.parse = function ( p ) { - var plural_str = Jed.PF.extractPluralExpr( p ); - return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); - }; - - Jed.PF.compile = function ( p ) { - // Handle trues and falses as 0 and 1 - function imply( val ) { - return (val === true ? 1 : val ? val : 0); - } - - var ast = Jed.PF.parse( p ); - return function ( n ) { - return imply( Jed.PF.interpreter( ast )( n ) ); - }; - }; - - Jed.PF.interpreter = function ( ast ) { - return function ( n ) { - var res; - switch ( ast.type ) { - case 'GROUP': - return Jed.PF.interpreter( ast.expr )( n ); - case 'TERNARY': - if ( Jed.PF.interpreter( ast.expr )( n ) ) { - return Jed.PF.interpreter( ast.truthy )( n ); - } - return Jed.PF.interpreter( ast.falsey )( n ); - case 'OR': - return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); - case 'AND': - return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); - case 'LT': - return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); - case 'GT': - return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); - case 'LTE': - return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); - case 'GTE': - return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); - case 'EQ': - return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); - case 'NEQ': - return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); - case 'MOD': - return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); - case 'VAR': - return n; - case 'NUM': - return ast.val; - default: - throw new Error("Invalid Token found."); - } - }; - }; - - Jed.PF.extractPluralExpr = function ( p ) { - // trim first - p = p.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - - if (! /;\s*$/.test(p)) { - p = p.concat(';'); - } - - var nplurals_re = /nplurals\=(\d+);/, - plural_re = /plural\=(.*);/, - nplurals_matches = p.match( nplurals_re ), - res = {}, - plural_matches; - - // Find the nplurals number - if ( nplurals_matches.length > 1 ) { - res.nplurals = nplurals_matches[1]; - } - else { - throw new Error('nplurals not found in plural_forms string: ' + p ); - } - - // remove that data to get to the formula - p = p.replace( nplurals_re, "" ); - plural_matches = p.match( plural_re ); - - if (!( plural_matches && plural_matches.length > 1 ) ) { - throw new Error('`plural` expression not found: ' + p); - } - return plural_matches[ 1 ]; - }; - - /* Jison generated parser */ - Jed.PF.parser = (function(){ - -var parser = {trace: function trace() { }, -yy: {}, -symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, -productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], -performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { - -var $0 = $$.length - 1; -switch (yystate) { -case 1: return { type : 'GROUP', expr: $$[$0-1] }; -break; -case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; -break; -case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; -break; -case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; -break; -case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; -break; -case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; -break; -case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; -break; -case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; -break; -case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; -break; -case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; -break; -case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; -break; -case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; -break; -case 13:this.$ = { type: 'VAR' }; -break; -case 14:this.$ = { type: 'NUM', val: Number(yytext) }; -break; -} -}, -table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], -defaultActions: {6:[2,1]}, -parseError: function parseError(str, hash) { - throw new Error(str); -}, -parse: function parse(input) { - var self = this, - stack = [0], - vstack = [null], // semantic value stack - lstack = [], // location stack - table = this.table, - yytext = '', - yylineno = 0, - yyleng = 0, - recovering = 0, - TERROR = 2, - EOF = 1; - - //this.reductionCount = this.shiftCount = 0; - - this.lexer.setInput(input); - this.lexer.yy = this.yy; - this.yy.lexer = this.lexer; - if (typeof this.lexer.yylloc == 'undefined') - this.lexer.yylloc = {}; - var yyloc = this.lexer.yylloc; - lstack.push(yyloc); - - if (typeof this.yy.parseError === 'function') - this.parseError = this.yy.parseError; - - function popStack (n) { - stack.length = stack.length - 2*n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; - } - - function lex() { - var token; - token = self.lexer.lex() || 1; // $end = 1 - // if token isn't its numeric value, convert - if (typeof token !== 'number') { - token = self.symbols_[token] || token; - } - return token; - } - - var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; - while (true) { - // retreive state number from top of stack - state = stack[stack.length-1]; - - // use default actions if available - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol == null) - symbol = lex(); - // read action for current state and first input - action = table[state] && table[state][symbol]; - } - - // handle parse error - _handle_error: - if (typeof action === 'undefined' || !action.length || !action[0]) { - - if (!recovering) { - // Report error - expected = []; - for (p in table[state]) if (this.terminals_[p] && p > 2) { - expected.push("'"+this.terminals_[p]+"'"); - } - var errStr = ''; - if (this.lexer.showPosition) { - errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; - } else { - errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + - (symbol == 1 /*EOF*/ ? "end of input" : - ("'"+(this.terminals_[symbol] || symbol)+"'")); - } - this.parseError(errStr, - {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); - } - - // just recovered from another error - if (recovering == 3) { - if (symbol == EOF) { - throw new Error(errStr || 'Parsing halted.'); - } - - // discard current lookahead and grab another - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - symbol = lex(); - } - - // try to recover from error - while (1) { - // check for error recovery rule in this state - if ((TERROR.toString()) in table[state]) { - break; - } - if (state == 0) { - throw new Error(errStr || 'Parsing halted.'); - } - popStack(1); - state = stack[stack.length-1]; - } - - preErrorSymbol = symbol; // save the lookahead token - symbol = TERROR; // insert generic error symbol as new lookahead - state = stack[stack.length-1]; - action = table[state] && table[state][TERROR]; - recovering = 3; // allow 3 real symbols to be shifted before reporting a new error - } - - // this shouldn't happen, unless resolve defaults are off - if (action[0] instanceof Array && action.length > 1) { - throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); - } - - switch (action[0]) { - - case 1: // shift - //this.shiftCount++; - - stack.push(symbol); - vstack.push(this.lexer.yytext); - lstack.push(this.lexer.yylloc); - stack.push(action[1]); // push state - symbol = null; - if (!preErrorSymbol) { // normal execution/no error - yyleng = this.lexer.yyleng; - yytext = this.lexer.yytext; - yylineno = this.lexer.yylineno; - yyloc = this.lexer.yylloc; - if (recovering > 0) - recovering--; - } else { // error just occurred, resume old lookahead f/ before error - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - - case 2: // reduce - //this.reductionCount++; - - len = this.productions_[action[1]][1]; - - // perform semantic action - yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 - // default location, uses first token for firsts, last for lasts - yyval._$ = { - first_line: lstack[lstack.length-(len||1)].first_line, - last_line: lstack[lstack.length-1].last_line, - first_column: lstack[lstack.length-(len||1)].first_column, - last_column: lstack[lstack.length-1].last_column - }; - r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); - - if (typeof r !== 'undefined') { - return r; - } - - // pop off stack - if (len) { - stack = stack.slice(0,-1*len*2); - vstack = vstack.slice(0, -1*len); - lstack = lstack.slice(0, -1*len); - } - - stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) - vstack.push(yyval.$); - lstack.push(yyval._$); - // goto new state = table[STATE][NONTERMINAL] - newState = table[stack[stack.length-2]][stack[stack.length-1]]; - stack.push(newState); - break; - - case 3: // accept - return true; - } - - } - - return true; -}};/* Jison generated lexer */ -var lexer = (function(){ - -var lexer = ({EOF:1, -parseError:function parseError(str, hash) { - if (this.yy.parseError) { - this.yy.parseError(str, hash); - } else { - throw new Error(str); - } - }, -setInput:function (input) { - this._input = input; - this._more = this._less = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; - return this; - }, -input:function () { - var ch = this._input[0]; - this.yytext+=ch; - this.yyleng++; - this.match+=ch; - this.matched+=ch; - var lines = ch.match(/\n/); - if (lines) this.yylineno++; - this._input = this._input.slice(1); - return ch; - }, -unput:function (ch) { - this._input = ch + this._input; - return this; - }, -more:function () { - this._more = true; - return this; - }, -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); - }, -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c+"^"; - }, -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) this.done = true; - - var token, - match, - col, - lines; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i=0;i < rules.length; i++) { - match = this._input.match(this.rules[rules[i]]); - if (match) { - lines = match[0].match(/\n.*/g); - if (lines) this.yylineno += lines.length; - this.yylloc = {first_line: this.yylloc.last_line, - last_line: this.yylineno+1, - first_column: this.yylloc.last_column, - last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - this._more = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); - if (token) return token; - else return; - } - } - if (this._input === "") { - return this.EOF; - } else { - this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), - {text: "", token: null, line: this.yylineno}); - } - }, -lex:function lex() { - var r = this.next(); - if (typeof r !== 'undefined') { - return r; - } else { - return this.lex(); - } - }, -begin:function begin(condition) { - this.conditionStack.push(condition); - }, -popState:function popState() { - return this.conditionStack.pop(); - }, -_currentRules:function _currentRules() { - return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; - }, -topState:function () { - return this.conditionStack[this.conditionStack.length-2]; - }, -pushState:function begin(condition) { - this.begin(condition); - }}); -lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { - -var YYSTATE=YY_START; -switch($avoiding_name_collisions) { -case 0:/* skip whitespace */ -break; -case 1:return 20 -break; -case 2:return 19 -break; -case 3:return 8 -break; -case 4:return 9 -break; -case 5:return 6 -break; -case 6:return 7 -break; -case 7:return 11 -break; -case 8:return 13 -break; -case 9:return 10 -break; -case 10:return 12 -break; -case 11:return 14 -break; -case 12:return 15 -break; -case 13:return 16 -break; -case 14:return 17 -break; -case 15:return 18 -break; -case 16:return 5 -break; -case 17:return 'INVALID' -break; -} -}; -lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; -lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() -parser.lexer = lexer; -return parser; -})(); -// End parser - - // Handle node, amd, and global systems - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = Jed; - } - exports.Jed = Jed; - } - else { - if (typeof define === 'function' && define.amd) { - define('jed', function() { - return Jed; - }); - } - // Leak a global regardless of module system - root['Jed'] = Jed; - } - -})(this); +../../thirdparty/jed/jed.js
\ No newline at end of file diff --git a/manifest.json b/manifest.json index b33a91a12..fea858369 100644 --- a/manifest.json +++ b/manifest.json @@ -2,8 +2,8 @@ "description": "Privacy preserving and transparent payments", "manifest_version": 2, "name": "GNU Taler Wallet (git)", - "version": "0.6.8", - "version_name": "0.0.1-pre4", + "version": "0.6.14", + "version_name": "0.0.1-pre8", "applications": { "gecko": { diff --git a/package.json b/package.json index 9ff26935a..41f1faac1 100644 --- a/package.json +++ b/package.json @@ -21,21 +21,20 @@ "gulp-concat": "^2.6.0", "gulp-debug": "^2.1.2", "gulp-gzip": "^1.2.0", - "gulp-json-transform": "^0.3.0", + "gulp-json-transform": "^0.4.2", "gulp-rename": "^1.2.2", "gulp-stream": "0.0.2", "gulp-tar": "^1.8.0", - "gulp-typescript": "^2.10.0", + "gulp-typescript": "^3.0.2", "gulp-zip": "^3.1.0", "istanbul-lib-instrument": "^1.0.0-alpha.6", - "jed": "^1.1.0", "map-stream": "0.0.6", "mocha": "^2.4.5", "po2json": "git+https://github.com/mikeedwards/po2json", "systemjs": "^0.19.14", "through2": "^2.0.1", - "typescript": "^2.0.2", + "typescript": "^2.0.3", "typhonjs-istanbul-instrument-jspm": "^0.1.0", - "vinyl": "^1.1.1" + "vinyl": "^2.0.0" } } diff --git a/pages/confirm-contract.html b/pages/confirm-contract.html index c2d75ef5a..e7200910a 100644 --- a/pages/confirm-contract.html +++ b/pages/confirm-contract.html @@ -5,6 +5,9 @@ <title>Taler Wallet: Confirm Reserve Creation</title> <link rel="stylesheet" type="text/css" href="../style/lang.css"> + <link rel="stylesheet" type="text/css" href="../style/wallet.css"> + + <link rel="icon" href="../img/icon.png"> <script src="../lib/vendor/URI.js"></script> <script src="../lib/vendor/mithril.js"></script> @@ -16,14 +19,6 @@ <script src="../lib/module-trampoline.js"></script> <style> - #main { - border: solid 1px black; - border-radius: 10px; - margin: auto; - max-width: 50%; - padding: 2em; - } - button.accept { background-color: #5757D2; border: 1px solid black; diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx index 52e4cb79a..f162dca85 100644 --- a/pages/confirm-contract.tsx +++ b/pages/confirm-contract.tsx @@ -52,7 +52,8 @@ const Details = { }, "show less details"), m("div", [ "Accepted exchanges:", - m("ul", contract.exchanges.map(e => m("li", `${e.url}: ${e.master_pub}`))) + m("ul", + contract.exchanges.map(e => m("li", `${e.url}: ${e.master_pub}`))) ]) ]); } @@ -72,7 +73,9 @@ export function main() { view(ctrl: any) { return [ renderContract(contract), - m("button.accept", {onclick: doPayment, disabled: payDisabled}, i18n`Confirm Payment`), + m("button.accept", + {onclick: doPayment, disabled: payDisabled}, + i18n`Confirm Payment`), (error ? m("p.errorbox", error) : []), m(Details, contract) ]; @@ -87,7 +90,7 @@ export function main() { console.log("check-pay error", JSON.stringify(resp)); switch (resp.error) { case "coins-insufficient": - error = "You do not have enough coins of the requested currency."; + error = i18n`You have insufficient funds of the requested currency in your wallet.`; break; default: error = `Error: ${resp.error}`; diff --git a/pages/confirm-create-reserve.html b/pages/confirm-create-reserve.html index 995a85aec..b8a825bd1 100644 --- a/pages/confirm-create-reserve.html +++ b/pages/confirm-create-reserve.html @@ -4,6 +4,8 @@ <head> <title>Taler Wallet: Select Taler Provider</title> + <link rel="icon" href="../img/icon.png"> + <script src="../lib/vendor/URI.js"></script> <script src="../lib/vendor/mithril.js"></script> <script src="../lib/vendor/system-csp-production.src.js"></script> diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx index 0a509118d..8e8067052 100644 --- a/pages/confirm-create-reserve.tsx +++ b/pages/confirm-create-reserve.tsx @@ -214,6 +214,10 @@ function viewSimple(ctrl: Controller) { }, "advanced options"); } else if (ctrl.reserveCreationInfo != undefined) { + let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead, + ctrl.reserveCreationInfo.withdrawFee).amount; + yield m("p", `Withdraw fees: ${amountToPretty(totalCost)}`); + yield m("button.accept", { onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!, ctrl.url(), @@ -228,9 +232,6 @@ function viewSimple(ctrl: Controller) { ctrl.complexViewRequested = true; } }, "advanced options"); - let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead, - ctrl.reserveCreationInfo.withdrawFee).amount; - yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`); } else { yield m("p", "Please wait ..."); } @@ -242,6 +243,12 @@ function viewSimple(ctrl: Controller) { function viewComplex(ctrl: Controller) { function *f() { + if (ctrl.reserveCreationInfo) { + let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead, + ctrl.reserveCreationInfo.withdrawFee).amount; + yield m("p", `Withdraw fees: ${amountToPretty(totalCost)}`); + } + yield m("button.accept", { onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!, ctrl.url(), @@ -277,9 +284,6 @@ function viewComplex(ctrl: Controller) { } if (ctrl.reserveCreationInfo) { - let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead, - ctrl.reserveCreationInfo.withdrawFee).amount; - yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`); if (ctrl.detailCollapsed()) { yield m("button.linky", { onclick: () => { @@ -328,8 +332,8 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo) { let withdrawFeeStr = amountToPretty(rci.withdrawFee); let overheadStr = amountToPretty(rci.overhead); return [ - m("p", `Fee for withdrawal: ${withdrawFeeStr}`), - m("p", `Overhead: ${overheadStr}`), + m("p", `Withdrawal fees: ${withdrawFeeStr}`), + m("p", `Rounding loss: ${overheadStr}`), m("table", [ m("tr", [ m("th", "Count"), diff --git a/pages/debug.html b/pages/debug.html index 24682dd24..221c7380c 100644 --- a/pages/debug.html +++ b/pages/debug.html @@ -2,6 +2,7 @@ <html> <head> <title>Taler Wallet Debugging</title> + <link rel="icon" href="../img/icon.png"> </head> <body> <h1>Debug Pages</h1> diff --git a/pages/help b/pages/help deleted file mode 160000 -Subproject 1ca9f1b35b286625b05672feea1c16a4de368ed diff --git a/pages/help/empty-wallet.html b/pages/help/empty-wallet.html new file mode 100644 index 000000000..952bd92b7 --- /dev/null +++ b/pages/help/empty-wallet.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>GNU Taler Help - Empty Wallet</title> + <link rel="icon" href="../../img/icon.png"> + <meta name="description" content=""> + <link rel="stylesheet" type="text/css" href="../../style/wallet.css"> + </head> + <body> + <div class="container" id="main"> + <div class="row"> + <div class="col-lg-12"> + <h2 lang="en">Your wallet is empty!</h2> + <p lang="en">You have succeeded with installing the Taler wallet. However, before + you can buy articles using the Taler wallet, you must withdraw electronic coins. + This is typically done by visiting your bank's online banking Web site. There, + you instruct your bank to transfer the funds to a Taler exchange operator. In + return, your wallet will be allowed to withdraw electronic coins.</p> + <p lang="en">At this stage, we are not aware of any regular exchange operators issuing + coins in well-known currencies. However, to see how Taler would work, you + can visit our "fake" bank at + <a href="https://bank.demo.taler.net/">bank.demo.taler.net</a> to + withdraw coins in the "KUDOS" currency that we created just for + demonstrating the system.</p> + </div> + </div> + </div> + </body> +</html> diff --git a/pages/show-db.html b/pages/show-db.html index ee54d0e08..024e844ee 100644 --- a/pages/show-db.html +++ b/pages/show-db.html @@ -4,7 +4,8 @@ <html> <head> <title>Taler Wallet: Reserve Created</title> - <link rel="stylesheet" type="text/css" href="../style/wallet.css"> + <link rel="stylesheet" type="text/css" href="../style/wallet.css"> + <link rel="icon" href="../img/icon.png"> <script src="show-db.js"></script> </head> <body> diff --git a/popup/popup.tsx b/popup/popup.tsx index e5e929eb0..5b2b1e9f9 100644 --- a/popup/popup.tsx +++ b/popup/popup.tsx @@ -193,9 +193,6 @@ function formatHistoryItem(historyItem: HistoryRecord) { return m("p", i18n.parts`Started to withdraw from reserve (${abbrev(d.reservePub)}) of ${formatAmount( d.requestedAmount)}.`); - case "withdraw": - return m("p", - i18n`Withdraw at ${formatTimestamp(t)}`); case "offer-contract": { let link = chrome.extension.getURL("view-contract.html"); let linkElem = m("a", {href: link}, abbrev(d.contractHash)); diff --git a/style/wallet.css b/style/wallet.css index 230cf671e..4609a1f96 100644 --- a/style/wallet.css +++ b/style/wallet.css @@ -1,8 +1,9 @@ -body { - background-color: white; - margin: 0; - padding: 0; - font-family: Verdana, sans; +#main { + border: solid 1px black; + border-radius: 10px; + margin: auto; + max-width: 50%; + padding: 2em; } header { @@ -40,11 +41,11 @@ aside { } section#main { - margin: 0 0 0 100px; + margin: auto; padding: 20px; border-left: 1px solid black; height: 100%; - max-width: 40em; + max-width: 50%; } section#main h1:first-child { diff --git a/thirdparty/README b/thirdparty/README new file mode 100644 index 000000000..03b715fae --- /dev/null +++ b/thirdparty/README @@ -0,0 +1,18 @@ +We maintain copies of third-party repositories in this directory. +Preferably each subdirectory should be maintained via 'git subtree'. +The rest of the project references third party components either via +symlinks, or via the build system. + +The following third party modules are used: + +Directory: preact +Copyright: Jason Miller +License: The MIT License +Description: Lightweight DOM rendering library +Repository: https://github.com/developit/preact + +Directory: jed +Copyright: jQuery Foundation and other contributors, https://jquery.org/ +License: See jed/LICENSE +Description: gettext .po parser for JavaScript +Repository: https://github.com/SlexAxton/Jed/blob/master/LICENSE diff --git a/thirdparty/jed/.gitignore b/thirdparty/jed/.gitignore new file mode 100644 index 000000000..8d87b1d26 --- /dev/null +++ b/thirdparty/jed/.gitignore @@ -0,0 +1 @@ +node_modules/* diff --git a/thirdparty/jed/.travis.yml b/thirdparty/jed/.travis.yml new file mode 100644 index 000000000..7b5e9514e --- /dev/null +++ b/thirdparty/jed/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - node + - 4 diff --git a/thirdparty/jed/LICENSE b/thirdparty/jed/LICENSE new file mode 100644 index 000000000..284f09cda --- /dev/null +++ b/thirdparty/jed/LICENSE @@ -0,0 +1,19 @@ +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/thirdparty/jed/Makefile b/thirdparty/jed/Makefile new file mode 100644 index 000000000..6e9858501 --- /dev/null +++ b/thirdparty/jed/Makefile @@ -0,0 +1,13 @@ +REPORTER = dot + +test: + @./node_modules/.bin/mocha \ + --require test/common \ + --reporter $(REPORTER) \ + --growl \ + test/tests.js + +test-browser: + @./node_modules/.bin/serve . + +.PHONY: test diff --git a/thirdparty/jed/README.md b/thirdparty/jed/README.md new file mode 100644 index 000000000..c7cb86e3d --- /dev/null +++ b/thirdparty/jed/README.md @@ -0,0 +1,63 @@ +[![Build Status](https://secure.travis-ci.org/SlexAxton/Jed.png)](http://travis-ci.org/SlexAxton/Jed) + +# Jed + +*Gettext Style i18n for Modern JavaScript Apps* + +For more info, please visit the docs site at <http://slexaxton.github.com/Jed>. + +## You sure you don't want something more modern? + +Jed is feature complete in my opinion. I am happy to fix bugs, but generally am not interested in adding more to the library. + +I also maintain [messageformat.js](https://github.com/SlexAxton/messageformat.js). If you don't specifically need a gettext implementation, I might suggest using MessageFormat instead, as it has better support for plurals/gender and has built-in locale data. + + +## Parsing Gettext Files + +Jed doesn't include a Gettext file parser, but several third-party parsers exist that can have their output adapted for Jed. + +#### Node + +Just search the npm repository, there are several PO and MO file parsers available. + +#### Browser + +[Jed Gettext Parser](https://github.com/WrinklyNinja/jed-gettext-parser) is the only known browser MO file parser, and it also works in Node, and outputs Jed-compatible data directly. + +[gettext.js](https://code.google.com/p/gettext-js) and [Pomo.js](https://github.com/cfv1984/pomo) both include browser-compatible PO file parsers. + +## Todo + +* Build time generation of plural form functions +* Web interface for building translation sets +* Code introspection for default values + +## License + +Jed is a member project of the [jQuery Foundation](https://jquery.org/) + +You may use this software under the MIT License. + +You may contribute to this software under the jQuery Foundation CLA - <https://contribute.jquery.org/CLA/> + + +## Author + +* Alex Sexton - @slexaxton - <https://alexsexton.com/> + + +## Credits + +A good chunk of sanity checking was done against the gettext.js tests. That was written by: + +* Joshua I. Miller + +The sprintf implementation is from: + +* Alexandru Marasteanu <alexaholic [at) gmail (dot] com> + + +## The name + +The name jed.js is an homage to Jed Schmidt (<https://github.com/jed>) the JavaScript community member who is a japanese translator by day, and a "hobbyist" JavaScript programmer by night. Give your kids three character names and they'll probably get software named after them too. diff --git a/thirdparty/jed/jed.js b/thirdparty/jed/jed.js new file mode 100644 index 000000000..bda163bef --- /dev/null +++ b/thirdparty/jed/jed.js @@ -0,0 +1,1033 @@ +/** + * @preserve jed.js https://github.com/SlexAxton/Jed + */ +/* +----------- +A gettext compatible i18n library for modern JavaScript Applications + +by Alex Sexton - AlexSexton [at] gmail - @SlexAxton + +MIT License + +A jQuery Foundation project - requires CLA to contribute - +https://contribute.jquery.org/CLA/ + + + +Jed offers the entire applicable GNU gettext spec'd set of +functions, but also offers some nicer wrappers around them. +The api for gettext was written for a language with no function +overloading, so Jed allows a little more of that. + +Many thanks to Joshua I. Miller - unrtst@cpan.org - who wrote +gettext.js back in 2008. I was able to vet a lot of my ideas +against his. I also made sure Jed passed against his tests +in order to offer easy upgrades -- jsgettext.berlios.de +*/ +(function (root, undef) { + + // Set up some underscore-style functions, if you already have + // underscore, feel free to delete this section, and use it + // directly, however, the amount of functions used doesn't + // warrant having underscore as a full dependency. + // Underscore 1.3.0 was used to port and is licensed + // under the MIT License by Jeremy Ashkenas. + var ArrayProto = Array.prototype, + ObjProto = Object.prototype, + slice = ArrayProto.slice, + hasOwnProp = ObjProto.hasOwnProperty, + nativeForEach = ArrayProto.forEach, + breaker = {}; + + // We're not using the OOP style _ so we don't need the + // extra level of indirection. This still means that you + // sub out for real `_` though. + var _ = { + forEach : function( obj, iterator, context ) { + var i, l, key; + if ( obj === null ) { + return; + } + + if ( nativeForEach && obj.forEach === nativeForEach ) { + obj.forEach( iterator, context ); + } + else if ( obj.length === +obj.length ) { + for ( i = 0, l = obj.length; i < l; i++ ) { + if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) { + return; + } + } + } + else { + for ( key in obj) { + if ( hasOwnProp.call( obj, key ) ) { + if ( iterator.call (context, obj[key], key, obj ) === breaker ) { + return; + } + } + } + } + }, + extend : function( obj ) { + this.forEach( slice.call( arguments, 1 ), function ( source ) { + for ( var prop in source ) { + obj[prop] = source[prop]; + } + }); + return obj; + } + }; + // END Miniature underscore impl + + // Jed is a constructor function + var Jed = function ( options ) { + // Some minimal defaults + this.defaults = { + "locale_data" : { + "messages" : { + "" : { + "domain" : "messages", + "lang" : "en", + "plural_forms" : "nplurals=2; plural=(n != 1);" + } + // There are no default keys, though + } + }, + // The default domain if one is missing + "domain" : "messages", + // enable debug mode to log untranslated strings to the console + "debug" : false + }; + + // Mix in the sent options with the default options + this.options = _.extend( {}, this.defaults, options ); + this.textdomain( this.options.domain ); + + if ( options.domain && ! this.options.locale_data[ this.options.domain ] ) { + throw new Error('Text domain set to non-existent domain: `' + options.domain + '`'); + } + }; + + // The gettext spec sets this character as the default + // delimiter for context lookups. + // e.g.: context\u0004key + // If your translation company uses something different, + // just change this at any time and it will use that instead. + Jed.context_delimiter = String.fromCharCode( 4 ); + + function getPluralFormFunc ( plural_form_string ) { + return Jed.PF.compile( plural_form_string || "nplurals=2; plural=(n != 1);"); + } + + function Chain( key, i18n ){ + this._key = key; + this._i18n = i18n; + } + + // Create a chainable api for adding args prettily + _.extend( Chain.prototype, { + onDomain : function ( domain ) { + this._domain = domain; + return this; + }, + withContext : function ( context ) { + this._context = context; + return this; + }, + ifPlural : function ( num, pkey ) { + this._val = num; + this._pkey = pkey; + return this; + }, + fetch : function ( sArr ) { + if ( {}.toString.call( sArr ) != '[object Array]' ) { + sArr = [].slice.call(arguments, 0); + } + return ( sArr && sArr.length ? Jed.sprintf : function(x){ return x; } )( + this._i18n.dcnpgettext(this._domain, this._context, this._key, this._pkey, this._val), + sArr + ); + } + }); + + // Add functions to the Jed prototype. + // These will be the functions on the object that's returned + // from creating a `new Jed()` + // These seem redundant, but they gzip pretty well. + _.extend( Jed.prototype, { + // The sexier api start point + translate : function ( key ) { + return new Chain( key, this ); + }, + + textdomain : function ( domain ) { + if ( ! domain ) { + return this._textdomain; + } + this._textdomain = domain; + }, + + gettext : function ( key ) { + return this.dcnpgettext.call( this, undef, undef, key ); + }, + + dgettext : function ( domain, key ) { + return this.dcnpgettext.call( this, domain, undef, key ); + }, + + dcgettext : function ( domain , key /*, category */ ) { + // Ignores the category anyways + return this.dcnpgettext.call( this, domain, undef, key ); + }, + + ngettext : function ( skey, pkey, val ) { + return this.dcnpgettext.call( this, undef, undef, skey, pkey, val ); + }, + + dngettext : function ( domain, skey, pkey, val ) { + return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); + }, + + dcngettext : function ( domain, skey, pkey, val/*, category */) { + return this.dcnpgettext.call( this, domain, undef, skey, pkey, val ); + }, + + pgettext : function ( context, key ) { + return this.dcnpgettext.call( this, undef, context, key ); + }, + + dpgettext : function ( domain, context, key ) { + return this.dcnpgettext.call( this, domain, context, key ); + }, + + dcpgettext : function ( domain, context, key/*, category */) { + return this.dcnpgettext.call( this, domain, context, key ); + }, + + npgettext : function ( context, skey, pkey, val ) { + return this.dcnpgettext.call( this, undef, context, skey, pkey, val ); + }, + + dnpgettext : function ( domain, context, skey, pkey, val ) { + return this.dcnpgettext.call( this, domain, context, skey, pkey, val ); + }, + + // The most fully qualified gettext function. It has every option. + // Since it has every option, we can use it from every other method. + // This is the bread and butter. + // Technically there should be one more argument in this function for 'Category', + // but since we never use it, we might as well not waste the bytes to define it. + dcnpgettext : function ( domain, context, singular_key, plural_key, val ) { + // Set some defaults + + plural_key = plural_key || singular_key; + + // Use the global domain default if one + // isn't explicitly passed in + domain = domain || this._textdomain; + + var fallback; + + // Handle special cases + + // No options found + if ( ! this.options ) { + // There's likely something wrong, but we'll return the correct key for english + // We do this by instantiating a brand new Jed instance with the default set + // for everything that could be broken. + fallback = new Jed(); + return fallback.dcnpgettext.call( fallback, undefined, undefined, singular_key, plural_key, val ); + } + + // No translation data provided + if ( ! this.options.locale_data ) { + throw new Error('No locale data provided.'); + } + + if ( ! this.options.locale_data[ domain ] ) { + throw new Error('Domain `' + domain + '` was not found.'); + } + + if ( ! this.options.locale_data[ domain ][ "" ] ) { + throw new Error('No locale meta information provided.'); + } + + // Make sure we have a truthy key. Otherwise we might start looking + // into the empty string key, which is the options for the locale + // data. + if ( ! singular_key ) { + throw new Error('No translation key found.'); + } + + var key = context ? context + Jed.context_delimiter + singular_key : singular_key, + locale_data = this.options.locale_data, + dict = locale_data[ domain ], + defaultConf = (locale_data.messages || this.defaults.locale_data.messages)[""], + pluralForms = dict[""].plural_forms || dict[""]["Plural-Forms"] || dict[""]["plural-forms"] || defaultConf.plural_forms || defaultConf["Plural-Forms"] || defaultConf["plural-forms"], + val_list, + res; + + var val_idx; + if (val === undefined) { + // No value passed in; assume singular key lookup. + val_idx = 0; + + } else { + // Value has been passed in; use plural-forms calculations. + + // Handle invalid numbers, but try casting strings for good measure + if ( typeof val != 'number' ) { + val = parseInt( val, 10 ); + + if ( isNaN( val ) ) { + throw new Error('The number that was passed in is not a number.'); + } + } + + val_idx = getPluralFormFunc(pluralForms)(val); + } + + // Throw an error if a domain isn't found + if ( ! dict ) { + throw new Error('No domain named `' + domain + '` could be found.'); + } + + val_list = dict[ key ]; + + // If there is no match, then revert back to + // english style singular/plural with the keys passed in. + if ( ! val_list || val_idx > val_list.length ) { + if (this.options.missing_key_callback) { + this.options.missing_key_callback(key, domain); + } + res = [ singular_key, plural_key ]; + + // collect untranslated strings + if (this.options.debug===true) { + console.log(res[ getPluralFormFunc(pluralForms)( val ) ]); + } + return res[ getPluralFormFunc()( val ) ]; + } + + res = val_list[ val_idx ]; + + // This includes empty strings on purpose + if ( ! res ) { + res = [ singular_key, plural_key ]; + return res[ getPluralFormFunc()( val ) ]; + } + return res; + } + }); + + + // We add in sprintf capabilities for post translation value interolation + // This is not internally used, so you can remove it if you have this + // available somewhere else, or want to use a different system. + + // We _slightly_ modify the normal sprintf behavior to more gracefully handle + // undefined values. + + /** + sprintf() for JavaScript 0.7-beta1 + http://www.diveintojavascript.com/projects/javascript-sprintf + + Copyright (c) Alexandru Marasteanu <alexaholic [at) gmail (dot] com> + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of sprintf() for JavaScript nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + var sprintf = (function() { + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + var str_format = function() { + if (!str_format.cache.hasOwnProperty(arguments[0])) { + str_format.cache[arguments[0]] = str_format.parse(arguments[0]); + } + return str_format.format.call(null, str_format.cache[arguments[0]], arguments); + }; + + str_format.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); + } + + // Jed EDIT + if ( typeof arg == 'undefined' || arg === null ) { + arg = ''; + } + // Jed EDIT + + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = Math.abs(arg); break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + str_format.cache = {}; + + str_format.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf] huh?'); + } + } + } + else { + throw('[sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + return str_format; + })(); + + var vsprintf = function(fmt, argv) { + argv.unshift(fmt); + return sprintf.apply(null, argv); + }; + + Jed.parse_plural = function ( plural_forms, n ) { + plural_forms = plural_forms.replace(/n/g, n); + return Jed.parse_expression(plural_forms); + }; + + Jed.sprintf = function ( fmt, args ) { + if ( {}.toString.call( args ) == '[object Array]' ) { + return vsprintf( fmt, [].slice.call(args) ); + } + return sprintf.apply(this, [].slice.call(arguments) ); + }; + + Jed.prototype.sprintf = function () { + return Jed.sprintf.apply(this, arguments); + }; + // END sprintf Implementation + + // Start the Plural forms section + // This is a full plural form expression parser. It is used to avoid + // running 'eval' or 'new Function' directly against the plural + // forms. + // + // This can be important if you get translations done through a 3rd + // party vendor. I encourage you to use this instead, however, I + // also will provide a 'precompiler' that you can use at build time + // to output valid/safe function representations of the plural form + // expressions. This means you can build this code out for the most + // part. + Jed.PF = {}; + + Jed.PF.parse = function ( p ) { + var plural_str = Jed.PF.extractPluralExpr( p ); + return Jed.PF.parser.parse.call(Jed.PF.parser, plural_str); + }; + + Jed.PF.compile = function ( p ) { + // Handle trues and falses as 0 and 1 + function imply( val ) { + return (val === true ? 1 : val ? val : 0); + } + + var ast = Jed.PF.parse( p ); + return function ( n ) { + return imply( Jed.PF.interpreter( ast )( n ) ); + }; + }; + + Jed.PF.interpreter = function ( ast ) { + return function ( n ) { + var res; + switch ( ast.type ) { + case 'GROUP': + return Jed.PF.interpreter( ast.expr )( n ); + case 'TERNARY': + if ( Jed.PF.interpreter( ast.expr )( n ) ) { + return Jed.PF.interpreter( ast.truthy )( n ); + } + return Jed.PF.interpreter( ast.falsey )( n ); + case 'OR': + return Jed.PF.interpreter( ast.left )( n ) || Jed.PF.interpreter( ast.right )( n ); + case 'AND': + return Jed.PF.interpreter( ast.left )( n ) && Jed.PF.interpreter( ast.right )( n ); + case 'LT': + return Jed.PF.interpreter( ast.left )( n ) < Jed.PF.interpreter( ast.right )( n ); + case 'GT': + return Jed.PF.interpreter( ast.left )( n ) > Jed.PF.interpreter( ast.right )( n ); + case 'LTE': + return Jed.PF.interpreter( ast.left )( n ) <= Jed.PF.interpreter( ast.right )( n ); + case 'GTE': + return Jed.PF.interpreter( ast.left )( n ) >= Jed.PF.interpreter( ast.right )( n ); + case 'EQ': + return Jed.PF.interpreter( ast.left )( n ) == Jed.PF.interpreter( ast.right )( n ); + case 'NEQ': + return Jed.PF.interpreter( ast.left )( n ) != Jed.PF.interpreter( ast.right )( n ); + case 'MOD': + return Jed.PF.interpreter( ast.left )( n ) % Jed.PF.interpreter( ast.right )( n ); + case 'VAR': + return n; + case 'NUM': + return ast.val; + default: + throw new Error("Invalid Token found."); + } + }; + }; + + Jed.PF.regexps = { + TRIM_BEG: /^\s\s*/, + TRIM_END: /\s\s*$/, + HAS_SEMICOLON: /;\s*$/, + NPLURALS: /nplurals\=(\d+);/, + PLURAL: /plural\=(.*);/ + }; + + Jed.PF.extractPluralExpr = function ( p ) { + // trim first + p = p.replace(Jed.PF.regexps.TRIM_BEG, '').replace(Jed.PF.regexps.TRIM_END, ''); + + if (! Jed.PF.regexps.HAS_SEMICOLON.test(p)) { + p = p.concat(';'); + } + + var nplurals_matches = p.match( Jed.PF.regexps.NPLURALS ), + res = {}, + plural_matches; + + // Find the nplurals number + if ( nplurals_matches.length > 1 ) { + res.nplurals = nplurals_matches[1]; + } + else { + throw new Error('nplurals not found in plural_forms string: ' + p ); + } + + // remove that data to get to the formula + p = p.replace( Jed.PF.regexps.NPLURALS, "" ); + plural_matches = p.match( Jed.PF.regexps.PLURAL ); + + if (!( plural_matches && plural_matches.length > 1 ) ) { + throw new Error('`plural` expression not found: ' + p); + } + return plural_matches[ 1 ]; + }; + + /* Jison generated parser */ + Jed.PF.parser = (function(){ + +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"?":6,":":7,"||":8,"&&":9,"<":10,"<=":11,">":12,">=":13,"!=":14,"==":15,"%":16,"(":17,")":18,"n":19,"NUMBER":20,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",6:"?",7:":",8:"||",9:"&&",10:"<",11:"<=",12:">",13:">=",14:"!=",15:"==",16:"%",17:"(",18:")",19:"n",20:"NUMBER"}, +productions_: [0,[3,2],[4,5],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[4,1]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: return { type : 'GROUP', expr: $$[$0-1] }; +break; +case 2:this.$ = { type: 'TERNARY', expr: $$[$0-4], truthy : $$[$0-2], falsey: $$[$0] }; +break; +case 3:this.$ = { type: "OR", left: $$[$0-2], right: $$[$0] }; +break; +case 4:this.$ = { type: "AND", left: $$[$0-2], right: $$[$0] }; +break; +case 5:this.$ = { type: 'LT', left: $$[$0-2], right: $$[$0] }; +break; +case 6:this.$ = { type: 'LTE', left: $$[$0-2], right: $$[$0] }; +break; +case 7:this.$ = { type: 'GT', left: $$[$0-2], right: $$[$0] }; +break; +case 8:this.$ = { type: 'GTE', left: $$[$0-2], right: $$[$0] }; +break; +case 9:this.$ = { type: 'NEQ', left: $$[$0-2], right: $$[$0] }; +break; +case 10:this.$ = { type: 'EQ', left: $$[$0-2], right: $$[$0] }; +break; +case 11:this.$ = { type: 'MOD', left: $$[$0-2], right: $$[$0] }; +break; +case 12:this.$ = { type: 'GROUP', expr: $$[$0-1] }; +break; +case 13:this.$ = { type: 'VAR' }; +break; +case 14:this.$ = { type: 'NUM', val: Number(yytext) }; +break; +} +}, +table: [{3:1,4:2,17:[1,3],19:[1,4],20:[1,5]},{1:[3]},{5:[1,6],6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{4:17,17:[1,3],19:[1,4],20:[1,5]},{5:[2,13],6:[2,13],7:[2,13],8:[2,13],9:[2,13],10:[2,13],11:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13],18:[2,13]},{5:[2,14],6:[2,14],7:[2,14],8:[2,14],9:[2,14],10:[2,14],11:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14],18:[2,14]},{1:[2,1]},{4:18,17:[1,3],19:[1,4],20:[1,5]},{4:19,17:[1,3],19:[1,4],20:[1,5]},{4:20,17:[1,3],19:[1,4],20:[1,5]},{4:21,17:[1,3],19:[1,4],20:[1,5]},{4:22,17:[1,3],19:[1,4],20:[1,5]},{4:23,17:[1,3],19:[1,4],20:[1,5]},{4:24,17:[1,3],19:[1,4],20:[1,5]},{4:25,17:[1,3],19:[1,4],20:[1,5]},{4:26,17:[1,3],19:[1,4],20:[1,5]},{4:27,17:[1,3],19:[1,4],20:[1,5]},{6:[1,7],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[1,28]},{6:[1,7],7:[1,29],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16]},{5:[2,3],6:[2,3],7:[2,3],8:[2,3],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,3]},{5:[2,4],6:[2,4],7:[2,4],8:[2,4],9:[2,4],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,4]},{5:[2,5],6:[2,5],7:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],13:[2,5],14:[2,5],15:[2,5],16:[1,16],18:[2,5]},{5:[2,6],6:[2,6],7:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],13:[2,6],14:[2,6],15:[2,6],16:[1,16],18:[2,6]},{5:[2,7],6:[2,7],7:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],13:[2,7],14:[2,7],15:[2,7],16:[1,16],18:[2,7]},{5:[2,8],6:[2,8],7:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],13:[2,8],14:[2,8],15:[2,8],16:[1,16],18:[2,8]},{5:[2,9],6:[2,9],7:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],13:[2,9],14:[2,9],15:[2,9],16:[1,16],18:[2,9]},{5:[2,10],6:[2,10],7:[2,10],8:[2,10],9:[2,10],10:[2,10],11:[2,10],12:[2,10],13:[2,10],14:[2,10],15:[2,10],16:[1,16],18:[2,10]},{5:[2,11],6:[2,11],7:[2,11],8:[2,11],9:[2,11],10:[2,11],11:[2,11],12:[2,11],13:[2,11],14:[2,11],15:[2,11],16:[2,11],18:[2,11]},{5:[2,12],6:[2,12],7:[2,12],8:[2,12],9:[2,12],10:[2,12],11:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12],18:[2,12]},{4:30,17:[1,3],19:[1,4],20:[1,5]},{5:[2,2],6:[1,7],7:[2,2],8:[1,8],9:[1,9],10:[1,10],11:[1,11],12:[1,12],13:[1,13],14:[1,14],15:[1,15],16:[1,16],18:[2,2]}], +defaultActions: {6:[2,1]}, +parseError: function parseError(str, hash) { + throw new Error(str); +}, +parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], // semantic value stack + lstack = [], // location stack + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + + //this.reductionCount = this.shiftCount = 0; + + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + + if (typeof this.yy.parseError === 'function') + this.parseError = this.yy.parseError; + + function popStack (n) { + stack.length = stack.length - 2*n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + + function lex() { + var token; + token = self.lexer.lex() || 1; // $end = 1 + // if token isn't its numeric value, convert + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + } + + var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; + while (true) { + // retreive state number from top of stack + state = stack[stack.length-1]; + + // use default actions if available + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol == null) + symbol = lex(); + // read action for current state and first input + action = table[state] && table[state][symbol]; + } + + // handle parse error + _handle_error: + if (typeof action === 'undefined' || !action.length || !action[0]) { + + if (!recovering) { + // Report error + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'"+this.terminals_[p]+"'"); + } + var errStr = ''; + if (this.lexer.showPosition) { + errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; + } else { + errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + + (symbol == 1 /*EOF*/ ? "end of input" : + ("'"+(this.terminals_[symbol] || symbol)+"'")); + } + this.parseError(errStr, + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + + // just recovered from another error + if (recovering == 3) { + if (symbol == EOF) { + throw new Error(errStr || 'Parsing halted.'); + } + + // discard current lookahead and grab another + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + symbol = lex(); + } + + // try to recover from error + while (1) { + // check for error recovery rule in this state + if ((TERROR.toString()) in table[state]) { + break; + } + if (state == 0) { + throw new Error(errStr || 'Parsing halted.'); + } + popStack(1); + state = stack[stack.length-1]; + } + + preErrorSymbol = symbol; // save the lookahead token + symbol = TERROR; // insert generic error symbol as new lookahead + state = stack[stack.length-1]; + action = table[state] && table[state][TERROR]; + recovering = 3; // allow 3 real symbols to be shifted before reporting a new error + } + + // this shouldn't happen, unless resolve defaults are off + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); + } + + switch (action[0]) { + + case 1: // shift + //this.shiftCount++; + + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); // push state + symbol = null; + if (!preErrorSymbol) { // normal execution/no error + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { // error just occurred, resume old lookahead f/ before error + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + + case 2: // reduce + //this.reductionCount++; + + len = this.productions_[action[1]][1]; + + // perform semantic action + yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 + // default location, uses first token for firsts, last for lasts + yyval._$ = { + first_line: lstack[lstack.length-(len||1)].first_line, + last_line: lstack[lstack.length-1].last_line, + first_column: lstack[lstack.length-(len||1)].first_column, + last_column: lstack[lstack.length-1].last_column + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + + if (typeof r !== 'undefined') { + return r; + } + + // pop off stack + if (len) { + stack = stack.slice(0,-1*len*2); + vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); + } + + stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) + vstack.push(yyval.$); + lstack.push(yyval._$); + // goto new state = table[STATE][NONTERMINAL] + newState = table[stack[stack.length-2]][stack[stack.length-1]]; + stack.push(newState); + break; + + case 3: // accept + return true; + } + + } + + return true; +}};/* Jison generated lexer */ +var lexer = (function(){ + +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + match = this._input.match(this.rules[rules[i]]); + if (match) { + lines = match[0].match(/\n.*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); + if (token) return token; + else return; + } + } + if (this._input === "") { + return this.EOF; + } else { + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 20 +break; +case 2:return 19 +break; +case 3:return 8 +break; +case 4:return 9 +break; +case 5:return 6 +break; +case 6:return 7 +break; +case 7:return 11 +break; +case 8:return 13 +break; +case 9:return 10 +break; +case 10:return 12 +break; +case 11:return 14 +break; +case 12:return 15 +break; +case 13:return 16 +break; +case 14:return 17 +break; +case 15:return 18 +break; +case 16:return 5 +break; +case 17:return 'INVALID' +break; +} +}; +lexer.rules = [/^\s+/,/^[0-9]+(\.[0-9]+)?\b/,/^n\b/,/^\|\|/,/^&&/,/^\?/,/^:/,/^<=/,/^>=/,/^</,/^>/,/^!=/,/^==/,/^%/,/^\(/,/^\)/,/^$/,/^./]; +lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],"inclusive":true}};return lexer;})() +parser.lexer = lexer; +return parser; +})(); +// End parser + + // Handle node, amd, and global systems + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = Jed; + } + exports.Jed = Jed; + } + else { + if (typeof define === 'function' && define.amd) { + define(function() { + return Jed; + }); + } + // Leak a global regardless of module system + root['Jed'] = Jed; + } + +})(this); diff --git a/thirdparty/jed/package.json b/thirdparty/jed/package.json new file mode 100644 index 000000000..0e37bc704 --- /dev/null +++ b/thirdparty/jed/package.json @@ -0,0 +1,27 @@ +{ + "name": "jed", + "version": "1.1.1", + "author": "Alex Sexton <alexsexton@gmail.com>", + "description": "Gettext Style i18n for Modern JavaScript Apps", + "repository": { + "type": "git", + "url": "https://SlexAxton@github.com/SlexAxton/Jed.git" + }, + "scripts": { + "test": "make test" + }, + "main": "./jed", + "keywords": [ + "i18n", + "Jed", + "gettext", + "internationalization" + ], + "dependencies" : {}, + "devDependencies" : { + "mocha" : "*", + "expect.js" : "*", + "serve": "*" + }, + "license": "MIT" +} diff --git a/thirdparty/jed/plurals.jison b/thirdparty/jed/plurals.jison new file mode 100644 index 000000000..9364f994e --- /dev/null +++ b/thirdparty/jed/plurals.jison @@ -0,0 +1,72 @@ +/* description: Parses end executes mathematical expressions. */ + +/* lexical grammar */ +%lex +%% + +\s+ /* skip whitespace */ +[0-9]+("."[0-9]+)?\b return 'NUMBER' +"n" return 'n' +"||" return '||' +"&&" return '&&' +"?" return '?' +":" return ':' +"<=" return '<=' +">=" return '>=' +"<" return '<' +">" return '>' +"!=" return '!=' +"==" return '==' +"%" return '%' +"(" return '(' +")" return ')' +<<EOF>> return 'EOF' +. return 'INVALID' + +/lex + +/* operator associations and precedence */ + +%right <code> '?' ':' +%left '||' +%left '&&' +%left '<=' '>=' '<' '>' '!=' '==' +%left '%' + +%start expressions + +%% /* language grammar */ + +expressions + : e EOF + { return { type : 'GROUP', expr: $1 }; } + ; + +e + : e '?' e ':' e + {$$ = { type: 'TERNARY', expr: $1, truthy : $3, falsey: $5 }; } + | e '||' e + {$$ = { type: "OR", left: $1, right: $3 };} + | e '&&' e + {$$ = { type: "AND", left: $1, right: $3 };} + | e '<' e + {$$ = { type: 'LT', left: $1, right: $3 }; } + | e '<=' e + {$$ = { type: 'LTE', left: $1, right: $3 };} + | e '>' e + {$$ = { type: 'GT', left: $1, right: $3 };} + | e '>=' e + {$$ = { type: 'GTE', left: $1, right: $3 };} + | e '!=' e + {$$ = { type: 'NEQ', left: $1, right: $3 };} + | e '==' e + {$$ = { type: 'EQ', left: $1, right: $3 };} + | e '%' e + {$$ = { type: 'MOD', left: $1, right: $3 };} + | '(' e ')' + {$$ = { type: 'GROUP', expr: $2 }; } + | 'n' + {$$ = { type: 'VAR' }; } + | NUMBER + {$$ = { type: 'NUM', val: Number(yytext) }; } + ; diff --git a/thirdparty/jed/test/common.js b/thirdparty/jed/test/common.js new file mode 100644 index 000000000..7568d624c --- /dev/null +++ b/thirdparty/jed/test/common.js @@ -0,0 +1,2 @@ +expect = require('expect.js'); +Jed = require('../jed'); diff --git a/thirdparty/jed/test/index.html b/thirdparty/jed/test/index.html new file mode 100644 index 000000000..bd92793a3 --- /dev/null +++ b/thirdparty/jed/test/index.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8 /> + <title>Jed.js Test Suite</title> + <link rel="stylesheet" href="/node_modules/mocha/mocha.css" /> + <script src="/test/jquery.min.js"></script> + <script src="/node_modules/mocha/mocha.js"></script> + <script src="/node_modules/expect.js/expect.js"></script> + <script>mocha.setup('bdd')</script> + + <script src="/jed.js"></script> + <script src="/test/tests.js"></script> + </head> + <body> + <h1>Jed.js Test Suite</h1> + <div id="mocha"></div> + <script> + window.onload = function () { + mocha.run().globals(['Jed']); + }; + </script> + </body> +</html> diff --git a/thirdparty/jed/test/jquery.min.js b/thirdparty/jed/test/jquery.min.js new file mode 100644 index 000000000..198b3ff07 --- /dev/null +++ b/thirdparty/jed/test/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test("Â ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file diff --git a/thirdparty/jed/test/tests.js b/thirdparty/jed/test/tests.js new file mode 100644 index 000000000..7ebc8c0bf --- /dev/null +++ b/thirdparty/jed/test/tests.js @@ -0,0 +1,746 @@ +(function (Jed){ + + describe("Property Checks", function () { + it("should exist", function () { + expect( Jed ).to.be.ok(); + }); + + it("should have a context delimiter as per the gettext spec", function () { + expect( Jed.context_delimiter ).to.be( "\u0004" ); + expect( Jed.context_delimiter ).to.be( String.fromCharCode( 4 ) ); + }); + }); + + // Group tests that need similar data + (function () { + var locale_data = { + "messages" : { + "" : { + "domain" : "messages", + "lang" : "en", + "plural-forms" : "nplurals=2; plural=(n != 1);" + }, + "test" : ["test_translation_output"] + } + }; + + var locale_data2 = { + "some_domain" : { + "" : { + "domain" : "some_domain", + "lang" : "en", + "plural-forms" : "nplurals=2; plural=(n != 1);" + }, + "test" : ["test_translation_output2"], + "zero length translation" : [""] + } + }; + + var locale_data3 = { + "some_domain" : { + "" : { + "domain" : "some_domain", + "lang" : "ar", + "plural-forms" : "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);" + }, + "test" : ["test_translation_output3"], + "zero length translation" : [""] + } + }; + + var i18n = new Jed({ + "domain" : "messages", + "locale_data" : locale_data + }); + + var i18n_2 = new Jed({ + "domain" : "some_domain", + "locale_data" : locale_data2 + }); + + var i18n_3 = new Jed({ + "domain" : "some_domain", + "locale_data" : locale_data3 + }); + + // Standard shorthand function + function _(msgid) { + return i18n_2.gettext(msgid); + } + + // Actual tests + describe("Instantiation", function () { + it("should exist", function () { + expect( i18n ).to.be.ok(); + expect( i18n_2 ).to.be.ok(); + expect( i18n_3 ).to.be.ok(); + expect( _ ).to.be.ok(); + }); + }); + + describe("Basic", function () { + it("should translate a key that exists in the translation", function () { + expect( i18n.gettext('test') ).to.be( 'test_translation_output' ); + }); + + it("should just pass through strings that aren't translatable", function () { + expect( i18n.gettext('missing') ).to.be( 'missing' ); + }); + + it("should translate a key in a locale with plural-forms rules that don't assume n==1 will return 0", function () { + expect(i18n_3.gettext('test')).to.be('test_translation_output3'); + }); + + it("should allow you to wrap it as a shorthand function", function () { + expect( _('test') ).to.be( 'test_translation_output2' ); + expect( _('missing') ).to.be( 'missing' ); + }); + + it("should have identical output for wrapped and non-wrapped instances", function () { + expect( _('test') ).to.be( i18n_2.gettext('test') ); + expect( _('missing') ).to.be( i18n_2.gettext('missing') ); + }); + + it("should not allow you to use domains that don't exist", function () { + function badCreate() { + var x = new Jed({ + "domain" : "missing_domain", + "locale_data" : locale_data + }); + return x; + } + expect( badCreate ).to.throwException(); + }); + + it("should just pass through translations that are empty strings", function () { + expect( _('zero length translation') ).to.be('zero length translation' ); + }); + + it("should call the callback function (if given) when a key is missing", function() { + var callbackCalled; + function missingKeyCallback(key) { + callbackCalled = true; + } + + callbackCalled = false; + var jedWithCallback = new Jed({ + "missing_key_callback" : missingKeyCallback + }); + jedWithCallback.gettext('missing key'); + expect(callbackCalled).to.be(true); + + callbackCalled = false; + var jedWithoutCallback = new Jed({}); + jedWithoutCallback.gettext('missing key'); + expect(callbackCalled).to.be(false); + }); + }); + })(); + + (function () { + var locale_data = { + "messages_1": { + "": { + "domain": "messages_1", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test": ["test_1"], + "test singular": ["test_1 singular", "test_1 plural"], + "context\u0004test": ["test_1 context"], + "context\u0004test singular": ["test_1 context singular", "test_1 context plural"] + }, + "messages_2": { + "": { + "domain": "messages_2", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test": ["test_2"], + "test singular": ["test_2 singular", "test_2 plural"], + "context\u0004test": ["test_2 context"], + "context\u0004test singular": ["test_2 context singular", "test_2 context plural"] + } + }; + + describe("Domain", function () { + var i18n1 = new Jed({ + domain : "messages_1", + locale_data : locale_data + }); + + var i18n_2 = new Jed({ + domain : "messages_2", + locale_data : locale_data + }); + + // No default domain + var i18n_3 = new Jed({ + locale_data : locale_data + }); + + it("should use the correct domain when there are multiple", function () { + expect( i18n1.gettext('test') ).to.be('test_1'); + expect( i18n_2.gettext('test') ).to.be('test_2'); + }); + + it("should still pass through non-existent keys", function () { + expect( i18n1.gettext('nope') ).to.be('nope'); + expect( i18n_2.gettext('nope again') ).to.be('nope again'); + }); + + it("should reveal the current domain on any instance", function () { + expect( i18n1.textdomain() ).to.be( 'messages_1' ); + expect( i18n_2.textdomain() ).to.be( 'messages_2' ); + }); + + it("should use `messages` as the default domain if none given", function () { + expect( i18n_3.textdomain() ).to.be('messages'); + }); + + it("should allow on the fly domain switching", function () { + // Switch these up + i18n1.textdomain('messages_2'); + i18n_2.textdomain('messages_1'); + + expect( i18n1.gettext('test') ).to.be('test_2'); + expect( i18n_2.gettext('test') ).to.be('test_1'); + expect( i18n1.textdomain() ).to.be( 'messages_2' ); + expect( i18n_2.textdomain() ).to.be( 'messages_1' ); + }); + + describe("#dgettext", function () { + it("should have the dgettext function", function () { + expect( i18n_3.dgettext ).to.be.ok(); + }); + + it("should allow you to call the domain on the fly", function () { + expect( i18n_3.dgettext('messages_1', 'test') ).to.be('test_1'); + expect( i18n_3.dgettext('messages_2', 'test') ).to.be('test_2'); + }); + + it("should pass through non-existent keys", function () { + expect( i18n_3.dgettext('messages_1', 'nope') ).to.be('nope'); + expect( i18n_3.dgettext('messages_2', 'nope again') ).to.be('nope again'); + }); + }); + + describe("#dcgettext", function () { + var i18n_4 = new Jed({ + locale_data : locale_data + }); + + it("should have the dcgettext function", function () { + expect( i18n_4.dcgettext ).to.be.ok(); + }); + + it("should ignore categories altogether", function () { + expect( i18n_4.dcgettext('messages_1', 'test', 'A_CATEGORY') ).to.be('test_1'); + }); + }); + }); + + describe("Pluralization", function () { + var locale_data1 = { + "plural_test": { + "": { + "domain": "plural_test", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test singular": ["test_1"], + "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], + "context\u0004test context": ["test_1context"], + "test2": ["test_2"], + "zero length translation": [""], + "context\u0004test2": ["test_2context"], + "Not translated plural": ["asdf", "asdf"], // this should never hit, since it's msgid2 + "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + } + }; + + var locale_data2 = { + "plural_test2": { + "": { + "domain": "plural_test2", + "lang": "sl", + // actual Slovenian pluralization rules + "plural_forms": "nplurals=4; plural=(n==1 ? 0 : n%10==2 ? 1 : n%10==3 || n%10==4 ? 2 : 3);" + }, + "Singular" : ["Numerus 0", "Numerus 1", "Numerus 2", "Numerus 3" ] + } + }; + + var i18n = new Jed({ + domain: "plural_test", + locale_data: locale_data1 + }); + + var i18n_2 = new Jed({ + domain: "plural_test2", + locale_data: locale_data2 + }); + + describe("#ngettext", function () { + + it("should have a ngettext function", function () { + expect( i18n.ngettext ).to.be.ok(); + }); + + it("should choose the correct pluralization translation", function () { + expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 1) ).to.be( 'test_1_singular %1$d' ); + expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 2) ).to.be( 'test_1_plural %1$d' ); + expect( i18n.ngettext('test plural %1$d', 'test plural %1$d', 0) ).to.be( 'test_1_plural %1$d' ); + }); + + it("should still pass through on plurals", function () { + expect(i18n.ngettext('Not translated', 'Not translated plural', 1) ).to.be( 'Not translated' ); + expect(i18n.ngettext('Not translated', 'Not translated plural', 2) ).to.be( 'Not translated plural' ); + expect(i18n.ngettext('Not translated', 'Not translated plural', 0) ).to.be( 'Not translated plural' ); + expect(i18n_2.ngettext('Not translated', 'Not translated plural', 3) ).to.be( 'Not translated plural' ); + }); + + it("should be able to parse complex pluralization rules", function () { + var strings = ['Singular', 'Plural']; + for (var i=0; i<=40; i++) { + var translation = i18n_2.ngettext(strings[0], strings[1], i); + var plural = ((i == 1) ? 0 : + (i % 10 == 2) ? 1 : + (i % 10 == 3 || i % 10 == 4) ? 2 : 3); + + expect(translation).to.be( 'Numerus ' + plural ); + } + }); + }); + + var locale_data_multi = { + "messages_3": { + "": { + "domain": "messages_3", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test": ["test_1"], + "test singular": ["test_1 singular", "test_1 plural"], + "context\u0004test": ["test_1 context"], + "context\u0004test singular": ["test_1 context singular", "test_1 context plural"] + }, + "messages_4": { + "": { + "domain": "messages_4", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test": ["test_2"], + "test singular": ["test_2 singular", "test_2 plural"], + "context\u0004test": ["test_2 context"], + "context\u0004test singular": ["test_2 context singular", "test_2 context plural"] + } + }; + + describe("#dngettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should have a dngettext function", function () { + expect( i18n.dngettext).to.be.ok(); + }); + + it("should pluralize correctly, based on domain rules", function () { + expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 1)).to.be('test_1 singular'); + expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 2)).to.be('test_1 plural'); + expect(i18n.dngettext('messages_3', 'test singular', 'test plural', 0)).to.be('test_1 plural'); + + expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 1)).to.be('test_2 singular'); + expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 2)).to.be('test_2 plural'); + expect(i18n.dngettext('messages_4', 'test singular', 'test plural', 0)).to.be('test_2 plural'); + }); + + it("should passthrough non-found keys regardless of pluralization addition", function (){ + expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); + expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); + expect(i18n.dngettext('messages_3', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); + + expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); + expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); + expect(i18n.dngettext('messages_4', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); + }); + }); + + describe("#dcngettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should more or less ignore the category", function () { + expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 1, 'LC_MESSAGES')).to.be('test_1 singular'); + expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 2, 'LC_MESSAGES')).to.be('test_1 plural'); + expect(i18n.dcngettext('messages_3', 'test singular', 'test plural', 0, 'LC_MESSAGES')).to.be('test_1 plural'); + + expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 1, 'LC_MESSAGES')).to.be('test_2 singular'); + expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 2, 'LC_MESSAGES')).to.be('test_2 plural'); + expect(i18n.dcngettext('messages_4', 'test singular', 'test plural', 0, 'LC_MESSAGES')).to.be('test_2 plural'); + + expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 1, 'LC_MESSAGES')).to.be('Not translated'); + expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 2, 'LC_MESSAGES')).to.be('Not translated plural'); + expect(i18n.dcngettext('messages_3', 'Not translated', 'Not translated plural', 0, 'LC_MESSAGES')).to.be('Not translated plural'); + + expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 1, 'LC_MESSAGES')).to.be('Not translated'); + expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 2, 'LC_MESSAGES')).to.be('Not translated plural'); + expect(i18n.dcngettext('messages_4', 'Not translated', 'Not translated plural', 0, 'LC_MESSAGES')).to.be('Not translated plural'); + }); + }); + + describe("#pgettext", function () { + var locale_data_w_context = { + "context_test": { + "": { + "domain": "context_test", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test singular": ["test_1"], + "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], + "context\u0004test context": ["test_1context"], + "test2": ["test_2"], + "zero length translation": [""], + "context\u0004test2": ["test_2context"], + "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + } + }; + + var i18n = new Jed({ + domain : "context_test", + locale_data : locale_data_w_context + }); + + it("should expose the pgettext function", function () { + expect( i18n.pgettext ).to.be.ok(); + }); + + it("should accept a context and look up a new key using the context_glue", function () { + expect( i18n.pgettext('context', 'test context') ).to.be( 'test_1context' ); + }); + + it("should still pass through missing keys", function () { + expect( i18n.pgettext('context', 'Not translated') ).to.be( 'Not translated' ); + }); + + it("should make sure same msgid returns diff results w/ context when appropriate", function () { + expect(i18n.gettext('test2')).to.be('test_2'); + expect(i18n.pgettext('context', 'test2')).to.be( 'test_2context' ); + }); + }); + + describe("#dpgettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should have a dpgettext function", function () { + expect( i18n.dpgettext ).to.be.ok(); + }); + + it("should use the domain and the context simultaneously", function () { + expect(i18n.dpgettext('messages_3', 'context', 'test')).to.be('test_1 context'); + expect(i18n.dpgettext('messages_4', 'context', 'test')).to.be('test_2 context'); + }); + + it("should pass through if either the domain, the key or the context isn't found", function () { + expect(i18n.dpgettext('messages_3', 'context', 'Not translated')).to.be('Not translated'); + expect(i18n.dpgettext('messages_4', 'context', 'Not translated')).to.be('Not translated'); + }); + + }); + + describe("#dcpgettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should have a dcpgettext function", function () { + expect( i18n.dcpgettext ).to.be.ok(); + }); + + it("should use the domain and the context simultaneously - ignore the category", function () { + expect(i18n.dcpgettext('messages_3', 'context', 'test', 'LC_MESSAGES')).to.be('test_1 context'); + expect(i18n.dcpgettext('messages_4', 'context', 'test', 'LC_MESSAGES')).to.be('test_2 context'); + }); + + it("should pass through if either the domain, the key or the context isn't found", function () { + expect(i18n.dcpgettext('messages_3', 'context', 'Not translated', 'LC_MESSAGES')).to.be('Not translated'); + expect(i18n.dcpgettext('messages_4', 'context', 'Not translated', 'LC_MESSAGES')).to.be('Not translated'); + }); + + }); + + describe("#npgettext", function () { + var locale_data_w_context = { + "context_plural_test": { + "": { + "domain": "context_plural_test", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test singular": ["test_1"], + "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], + "context\u0004test context": ["test_1context"], + "test2": ["test_2"], + "zero length translation": [""], + "context\u0004test2": ["test_2context"], + "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + } + }; + + var i18n = new Jed({ + domain : "context_plural_test", + locale_data : locale_data_w_context + }); + + it("should have a dcpgettext function", function () { + expect( i18n.dcpgettext ).to.be.ok(); + }); + + it("should handle plurals at the same time as contexts", function () { + expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 1)).to.be('context_plural_1 singular %1$d'); + expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 2)).to.be('context_plural_1 plural %1$d'); + expect(i18n.npgettext('context', 'context plural %1$d', 'plural %1$d', 0)).to.be('context_plural_1 plural %1$d'); + }); + + it("should just pass through on not-found cases", function () { + expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); + expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); + expect(i18n.npgettext('context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); + }); + }); + + describe("#dnpgettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should have a dnpgettext function", function () { + expect( i18n.dnpgettext ).to.be.ok(); + }); + + it("should be able to do a domain, context, and pluralization lookup all at once", function () { + expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 1)).to.be('test_1 context singular'); + expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 2)).to.be('test_1 context plural'); + expect(i18n.dnpgettext('messages_3', 'context', 'test singular', 'test plural', 0)).to.be('test_1 context plural'); + + expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 1)).to.be('test_2 context singular'); + expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 2)).to.be('test_2 context plural'); + expect(i18n.dnpgettext('messages_4', 'context', 'test singular', 'test plural', 0)).to.be('test_2 context plural'); + }); + + it("should pass through if everything doesn't point towards a key", function () { + expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); + expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); + expect(i18n.dnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); + + expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 1)).to.be('Not translated'); + expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 2)).to.be('Not translated plural'); + expect(i18n.dnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 0)).to.be('Not translated plural'); + }); + }); + + describe("#dcnpgettext", function () { + var i18n = new Jed({ + locale_data : locale_data_multi + }); + + it("should have a dcnpgettext function", function () { + expect( i18n.dcnpgettext ).to.be.ok(); + }); + + it("should be able to do a domain, context, and pluralization lookup all at once - ignore category", function () { + expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 1, "LC_MESSAGES")).to.be('test_1 context singular'); + expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 2, "LC_MESSAGES")).to.be('test_1 context plural'); + expect(i18n.dcnpgettext('messages_3', 'context', 'test singular', 'test plural', 0, "LC_MESSAGES")).to.be('test_1 context plural'); + + expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 1, "LC_MESSAGES")).to.be('test_2 context singular'); + expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 2, "LC_MESSAGES")).to.be('test_2 context plural'); + expect(i18n.dcnpgettext('messages_4', 'context', 'test singular', 'test plural', 0, "LC_MESSAGES")).to.be('test_2 context plural'); + }); + + it("should pass through if everything doesn't point towards a key", function () { + expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 1, "LC_MESSAGES")).to.be('Not translated'); + expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 2, "LC_MESSAGES")).to.be('Not translated plural'); + expect(i18n.dcnpgettext('messages_3', 'context', 'Not translated', 'Not translated plural', 0, "LC_MESSAGES")).to.be('Not translated plural'); + + expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 1, "LC_MESSAGES")).to.be('Not translated'); + expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 2, "LC_MESSAGES")).to.be('Not translated plural'); + expect(i18n.dcnpgettext('messages_4', 'context', 'Not translated', 'Not translated plural', 0, "LC_MESSAGES")).to.be('Not translated plural'); + }); + }); + }); + + describe("Plural Forms Parsing", function (){ + // This is the method from the original gettext.js that uses new Function + function evalParse( plural_forms ) { + var pf_re = new RegExp('^(\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;a-zA-Z0-9_\(\)])+)', 'm'); + if (pf_re.test(plural_forms)) { + var pf = plural_forms; + if (! /;\s*$/.test(pf)) pf = pf.concat(';'); + + var code = 'var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) };'; + return (new Function("n", code)); + } else { + throw new Error("Syntax error in language file. Plural-Forms header is invalid ["+plural_forms+"]"); + } + } + + // http://translate.sourceforge.net/wiki/l10n/pluralforms + it("should have the same result as doing an eval on the expression for all known plural-forms.", function (){ + var pfs = ["nplurals=2; plural=(n > 1)","nplurals=2; plural=(n != 1)","nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;","nplurals=1; plural=0","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2","nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2","nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3","nplurals=2; plural=n > 1","nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4","nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3","nplurals=2; plural= (n > 1)","nplurals=2; plural=(n%10!=1 || n%100==11)","nplurals=2; plural=n!=0","nplurals=2; plural=(n!=1)","nplurals=2; plural=(n!= 1)","nplurals=4; plural= (n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3","nplurals=2; plural=n>1;","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)","nplurals=2; plural= n==1 || n%10==1 ? 0 : 1","nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2)","nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3)","nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)","nplurals=2; plural=(n!=1);","nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);","nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0)","nplurals=2; plural=n != 1","nplurals=2; plural=(n>1)","nplurals=1; plural=0;"], + pf, pfc, pfe, pfi, i; + for ( pfi = 0; pfi < pfs.length; pfi++ ) { + pf = ""+pfs[ pfi ]; + for( i = 0; i < 106; i++ ){ + pfc = Jed.PF.compile( ""+pf )( i ); + pfe = evalParse( ""+pf )( i ).plural; + if (pfc !== pfe) { + throw new Error('expected ' + pfe + ' but got ' + pfc); + } + } + } + }); + + }); + + describe("Chainable API", function () { + var locale_data_w_context = { + "context_sprintf_test": { + "": { + "domain": "context_sprintf_test", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test singular": ["test_1"], + "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], + "context\u0004test context": ["test_1context"], + "test2": ["test_2"], + "zero length translation": [""], + "context\u0004test2": ["test_2context"], + "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + }, + "other_domain": { + "": { + "domain": "other_domain", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test other_domain singular": ["other domain test 1"], + "context\u0004context other plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + } + }; + var i18n = new Jed({ + "locale_data" : locale_data_w_context, + "domain": "context_sprintf_test" + }); + + it("should handle a simple gettext passthrough", function (){ + expect( i18n.translate('test singular').fetch() ).to.be('test_1'); + }); + + it("should handle changing domains", function (){ + expect( i18n.translate('test other_domain singular').onDomain('other_domain').fetch() ).to.be('other domain test 1'); + }); + + it("should allow you to add plural information in the chain.", function () { + expect( i18n.translate("test plural %1$d").ifPlural(5, "dont matta").fetch() ).to.be( "test_1_plural %1$d" ); + }); + + it("should take in a sprintf set of args (as array) on the plural lookup", function(){ + expect( i18n.translate("test plural %1$d").ifPlural(5, "dont matta").fetch([5]) ).to.be( "test_1_plural 5" ); + expect( i18n.translate("test plural %1$d %2$d").ifPlural(5, "dont matta %1$d %2$d").fetch([5, 6]) ).to.be( "dont matta 5 6" ); + expect( i18n.translate("test plural %1$d %2$d").ifPlural(1, "dont matta %1$d %2$d").fetch([1, 6]) ).to.be( "test plural 1 6" ); + }); + + it("should take in a sprintf set of args (as args) on the plural lookup", function(){ + expect( i18n.translate("test plural %1$d %2$d").ifPlural(5, "dont matta %1$d %2$d").fetch(5, 6) ).to.be( "dont matta 5 6" ); + expect( i18n.translate("test plural %1$d %2$d").ifPlural(1, "dont matta %1$d %2$d").fetch(1, 6) ).to.be( "test plural 1 6" ); + }); + + it("should handle context information.", function () { + expect(i18n.translate('test context').withContext('context').fetch() ).to.be('test_1context'); + }); + + it("should be able to do all at the same time.", function () { + expect( i18n.translate("context other plural %1$d").withContext('context').onDomain('other_domain').ifPlural(5, "ignored %1$d").fetch(5) ).to.be( "context_plural_1 plural 5" ); + expect( i18n.translate("context other plural %1$d").withContext('context').onDomain('other_domain').ifPlural(1, "ignored %1$d").fetch(1) ).to.be( "context_plural_1 singular 1" ); + }); + + }); + + describe("Sprintf", function () { + var locale_data_w_context = { + "context_sprintf_test": { + "": { + "domain": "context_sprintf_test", + "lang": "en", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "test singular": ["test_1"], + "test plural %1$d": ["test_1_singular %1$d", "test_1_plural %1$d"], + "context\u0004test context": ["test_1context"], + "test2": ["test_2"], + "zero length translation": [""], + "context\u0004test2": ["test_2context"], + "context\u0004context plural %1$d": ["context_plural_1 singular %1$d", "context_plural_1 plural %1$d"] + } + }; + + var i18n = new Jed({ + "locale_data" : locale_data_w_context, + "domain": "context_sprintf_test" + }); + + + it("should take multiple types of arrays as input", function () { + var strings = { + "blah" : "blah", + "thing%1$sbob" : "thing[one]bob", + "thing%1$s%2$sbob" : "thing[one][two]bob", + "thing%1$sasdf%2$sasdf" : "thing[one]asdf[two]asdf", + "%1$s%2$s%3$s" : "[one][two]", + "tom%1$saDick" : "tom[one]aDick" + }; + var args = ["[one]", "[two]"]; + + for (var i in strings) { + // test using new Array + expect(Jed.sprintf(i, ["[one]","[two]"])).to.be(strings[i]); + expect(i18n.sprintf(i, ["[one]","[two]"])).to.be(strings[i]); + // test using predefined array + expect(Jed.sprintf(i, args)).to.be(strings[i]); + expect(i18n.sprintf(i, args)).to.be(strings[i]); + } + }); + + + + it("should accept a single string instead of an array", function () { + // test using scalar rather than array + var strings = { + "blah" : "blah", + "" : "", + "%%" : "%", + "tom%%dick" : "tom%dick", + "thing%1$sbob" : "thing[one]bob", + "thing%1$s%2$sbob" : "thing[one]bob", + "thing%1$sasdf%2$sasdf" : "thing[one]asdfasdf", + "%1$s%2$s%3$s" : "[one]" + }; + var arg = "[one]"; + + for (var i in strings) { + expect(Jed.sprintf(i, arg)).to.be(strings[i]); + expect(i18n.sprintf(i, arg)).to.be(strings[i]); + } + }); + }); + })(); + +})( Jed ); diff --git a/thirdparty/preact/.editorconfig b/thirdparty/preact/.editorconfig new file mode 100644 index 000000000..9c378bd19 --- /dev/null +++ b/thirdparty/preact/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = tab +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{package.json,.*rc,*.yml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/thirdparty/preact/.gitignore b/thirdparty/preact/.gitignore new file mode 100644 index 000000000..17acf3f20 --- /dev/null +++ b/thirdparty/preact/.gitignore @@ -0,0 +1,8 @@ +/node_modules +/npm-debug.log +.DS_Store +/dist +/_dev +/coverage +aliases.js +aliases.js.map diff --git a/thirdparty/preact/.travis.yml b/thirdparty/preact/.travis.yml new file mode 100644 index 000000000..5953c1a84 --- /dev/null +++ b/thirdparty/preact/.travis.yml @@ -0,0 +1,30 @@ +sudo: false + +language: node_js + +node_js: + - "6" + +cache: + directories: + - node_modules + +install: + - npm install + +script: + - npm run build + - npm run test + - SAUCELABS=true COVERAGE=false FLAKEY=false PERFORMANCE=false npm run test:karma + +# Necessary to compile native modules for io.js v3 or Node.js v4 +env: + - CXX=g++-4.8 + +# Necessary to compile native modules for io.js v3 or Node.js v4 +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 diff --git a/thirdparty/preact/LICENSE b/thirdparty/preact/LICENSE new file mode 100644 index 000000000..445ef15a4 --- /dev/null +++ b/thirdparty/preact/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/thirdparty/preact/README.md b/thirdparty/preact/README.md new file mode 100644 index 000000000..582c55216 --- /dev/null +++ b/thirdparty/preact/README.md @@ -0,0 +1,345 @@ +<a href="https://preactjs.com"> +<img alt="Preact" title="Preact" src="https://cdn.rawgit.com/developit/b4416d5c92b743dbaec1e68bc4c27cda/raw/3235dc508f7eb834ebf48418aea212a05df13db1/preact-logo-trans.svg" width="550"> +</a> + +**Preact is a fast, `3kb` alternative to React, with the same ES2015 API.** + +Preact retains a large amount of compatibility with React, but only the modern ([ES6 Classes] and [stateless functional components](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components)) interfaces. +As one would expect coming from React, Components are simple building blocks for composing a User Interface. + +### :information_desk_person: Full documentation is available at the [Preact Website âžž](https://preactjs.com) + +[![npm](https://img.shields.io/npm/v/preact.svg)](http://npm.im/preact) +[![travis](https://travis-ci.org/developit/preact.svg?branch=master)](https://travis-ci.org/developit/preact) +[![gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/developit/preact) + +[![Browsers](https://saucelabs.com/browser-matrix/preact.svg)](https://saucelabs.com/u/preact) + + +--- + + +## Demos + +- [**ESBench**](http://esbench.com) is built using Preact. +- [**Nectarine.rocks**](http://nectarine.rocks) _([Github Project](https://github.com/developit/nectarine))_ :peach: +- [**Documentation Viewer**](https://documentation-viewer.firebaseapp.com) _([Github Project](https://github.com/developit/documentation-viewer))_ +- [**TodoMVC**](http://developit.github.io/preact-todomvc/) _([Github Project](https://github.com/developit/preact-todomvc))_ +- [**Hacker News Minimal**](https://developit.github.io/hn_minimal/) _([Github Project](https://github.com/developit/hn_minimal))_ +- [**Preact Boilerplate**](https://preact-boilerplate.surge.sh) _([Github Project](https://github.com/developit/preact-boilerplate))_ :zap: +- [**Preact Redux Example**](https://github.com/developit/preact-redux-example) :star: +- [**Flickr Browser**](http://codepen.io/developit/full/VvMZwK/) (@ CodePen) +- [**Animating Text**](http://codepen.io/developit/full/LpNOdm/) (@ CodePen) +- [**60FPS Rainbow Spiral**](http://codepen.io/developit/full/xGoagz/) (@ CodePen) +- [**Simple Clock**](http://jsfiddle.net/developit/u9m5x0L7/embedded/result,js/) (@ JSFiddle) +- [**3D + ThreeJS**](http://codepen.io/developit/pen/PPMNjd?editors=0010) (@ CodePen) +- [**Stock Ticker**](http://codepen.io/developit/pen/wMYoBb?editors=0010) (@ CodePen) +- [**Create your Own!**](https://jsfiddle.net/developit/rs6zrh5f/embedded/result/) (@ JSFiddle) +- [**Preact Coffeescript**](https://github.com/crisward/preact-coffee) +- [**GuriVR**](https://gurivr.com) _([Github Project](https://github.com/opennewslabs/guri-vr))_ +- [**V2EX Preact**](https://github.com/yanni4night/v2ex-preact) + +## Libraries & Add-ons + +- :earth_americas: [**preact-router**](https://git.io/preact-router): URL routing for your components. +- :page_facing_up: [**preact-render-to-string**](https://git.io/preact-render-to-string): Universal rendering. +- :raised_hands: [**preact-compat**](https://git.io/preact-compat): use any React library with Preact. *([full example](http://git.io/preact-compat-example))* +- :rocket: [**preact-photon**](https://git.io/preact-photon): build beautiful desktop UI with [photon](http://photonkit.com). +- :microscope: [**preact-jsx-chai**](https://git.io/preact-jsx-chai): JSX assertion testing _(no DOM, right in Node)_ +- :bookmark_tabs: [**preact-markup**](https://git.io/preact-markup): Render HTML & Custom Elements as JSX & Components +- :pencil: [**preact-richtextarea**](https://git.io/preact-richtextarea): Simple HTML editor component +- :repeat: [**preact-cycle**](https://git.io/preact-cycle): Functional-reactive paradigm for Preact. +- :satellite: [**preact-portal**](https://git.io/preact-portal): Render Preact components into (a) SPACE :milky_way: +- :construction: [**preact-classless-component**](https://github.com/ld0rman/preact-classless-component): A utility method to create components without the `class` keyword +- :hammer: [**preact-hyperscript**](https://github.com/queckezz/preact-hyperscript): Hyperscript-like syntax for creating elements + + +## Getting Started + +> :information_desk_person: You [don't _have_ to use ES2015 to use Preact](https://github.com/developit/preact-without-babel)... but you should. + +The following guide assumes you have some sort of ES2015 build set up using babel and/or webpack/browserify/gulp/grunt/etc. If you don't, start with [preact-boilerplate] or a [CodePen Template](http://codepen.io/developit/pen/pgaROe?editors=0010). + + +### Import what you need + +The `preact` module provides both named and default exports, so you can either import everything under a namespace of your choosing, or just what you need as locals: + +##### Named: + +```js +import { h, render, Component } from 'preact'; + +// Tell Babel to transform JSX into h() calls: +/** @jsx h */ +``` + +##### Default: + +```js +import preact from 'preact'; + +// Tell Babel to transform JSX into preact.h() calls: +/** @jsx preact.h */ +``` + +> Named imports work well for highly structured applications, whereas the default import is quick and never needs to be updated when using different parts of the library. +> +> Instead of declaring the `@jsx` pragma in your code, it's best to configure it globally in a `.babelrc`: +> +> **For Babel 5 and prior:** +> +> ```json +> { "jsxPragma": "h" } +> ``` +> +> **For Babel 6:** +> +> ```json +> { +> "plugins": [ +> ["transform-react-jsx", { "pragma":"h" }] +> ] +> } +> ``` + + +### Rendering JSX + +Out of the box, Preact provides an `h()` function that turns your JSX into Virtual DOM elements _([here's how](http://jasonformat.com/wtf-is-jsx))_. It also provides a `render()` function that creates a DOM tree from that Virtual DOM. + +To render some JSX, just import those two functions and use them like so: + +```js +import { h, render } from 'preact'; + +render(( + <div id="foo"> + <span>Hello, world!</span> + <button onClick={ e => alert("hi!") }>Click Me</button> + </div> +), document.body); +``` + +This should seem pretty straightforward if you've used [hyperscript] or one of its many friends. + +Rendering hyperscript with a virtual DOM is pointless, though. We want to render components and have them updated when data changes - that's where the power of virtual DOM diffing shines. :star2: + + +### Components + +Preact exports a generic `Component` class, which can be extended to build encapsulated, self-updating pieces of a User Interface. Components support all of the standard React [lifecycle methods], like `shouldComponentUpdate()` and `componentWillReceiveProps()`. Providing specific implementations of these methods is the preferred mechanism for controlling _when_ and _how_ components update. + +Components also have a `render()` method, but unlike React this method is passed `(props, state)` as arguments. This provides an ergonomic means to destructure `props` and `state` into local variables to be referenced from JSX. + +Let's take a look at a very simple `Clock` component, which shows the current time. + +```js +import { h, render, Component } from 'preact'; + +class Clock extends Component { + render() { + let time = new Date().toLocaleTimeString(); + return <span>{ time }</span>; + } +} + +// render an instance of Clock into <body>: +render(<Clock />, document.body); +``` + + +That's great. Running this produces the following HTML DOM structure: + +```html +<span>10:28:57 PM</span> +``` + +In order to have the clock's time update every second, we need to know when `<Clock>` gets mounted to the DOM. _If you've used HTML5 Custom Elements, this is similar to the `attachedCallback` and `detachedCallback` lifecycle methods._ Preact invokes the following lifecycle methods if they are defined for a Component: + +| Lifecycle method | When it gets called | +|-----------------------------|--------------------------------------------------| +| `componentWillMount` | before the component gets mounted to the DOM | +| `componentDidMount` | after the component gets mounted to the DOM | +| `componentWillUnmount` | prior to removal from the DOM | +| `componentDidUnmount` | after removal from the DOM | +| `componentWillReceiveProps` | before new props get accepted | +| `shouldComponentUpdate` | before `render()`. Return `false` to skip render | +| `componentWillUpdate` | before `render()` | +| `componentDidUpdate` | after `render()` | + + + +So, we want to have a 1-second timer start once the Component gets added to the DOM, and stop if it is removed. We'll create the timer and store a reference to it in `componentDidMount`, and stop the timer in `componentWillUnmount`. On each timer tick, we'll update the component's `state` object with a new time value. Doing this will automatically re-render the component. + +```js +import { h, render, Component } from 'preact'; + +class Clock extends Component { + constructor() { + super(); + // set initial time: + this.state.time = Date.now(); + } + + componentDidMount() { + // update time every second + this.timer = setInterval(() => { + this.setState({ time: Date.now() }); + }, 1000); + } + + componentWillUnmount() { + // stop when not renderable + clearInterval(this.timer); + } + + render(props, state) { + let time = new Date(state.time).toLocaleTimeString(); + return <span>{ time }</span>; + } +} + +// render an instance of Clock into <body>: +render(<Clock />, document.body); +``` + +Now we have [a ticking clock](http://jsfiddle.net/developit/u9m5x0L7/embedded/result,js/)! + + +### Props & State + +The concept (and nomenclature) for `props` and `state` is the same as in React. `props` are passed to a component by defining attributes in JSX, `state` is internal state. Changing either triggers a re-render, though by default Preact re-renders Components asynchronously for `state` changes and synchronously for `props` changes. You can tell Preact to render `prop` changes asynchronously by setting `options.syncComponentUpdates` to `false`. + + +--- + + +## Linked State + +One area Preact takes a little further than React is in optimizing state changes. A common pattern in ES2015 React code is to use Arrow functions within a `render()` method in order to update state in response to events. Creating functions enclosed in a scope on every render is inefficient and forces the garbage collector to do more work than is necessary. + +One solution to this is to bind component methods declaratively. +Here is an example using [decko](http://git.io/decko): + +```js +class Foo extends Component { + @bind + updateText(e) { + this.setState({ text: e.target.value }); + } + render({ }, { text }) { + return <input value={text} onInput={this.updateText} />; + } +} +``` + +While this achieves much better runtime performance, it's still a lot of unnecessary code to wire up state to UI. + +Fortunately there is a solution, in the form of `linkState()`. Calling `component.linkState('text')` returns a function that accepts an Event and uses it's associated value to update the given property in your component's state. Calls to linkState() with the same state property are cached, so there is no performance penalty. Here is the previous example rewritten using _Linked State_: + +```js +class Foo extends Component { + render({ }, { text }) { + return <input value={text} onInput={this.linkState('text')} />; + } +} +``` + +Simple and effective. It handles linking state from any input type, or an optional second parameter can be used to explicitly provide a keypath to the new state value. + + +## Examples + +Here is a somewhat verbose Preact `<Link>` component: + +```js +class Link extends Component { + render(props, state) { + return <a href={ props.href }>{ props.children }</a>; + } +} +``` + +Since this is ES6/ES2015, we can further simplify: + +```js +class Link extends Component { + render({ href, children }) { + return <a {...{ href, children }} />; + } +} + +// or, for wide-open props support: +class Link extends Component { + render(props) { + return <a {...props} />; + } +} + +// or, as a stateless functional component: +const Link = ({ children, ...props }) => ( + <a {...props}>{ children }</a> +); +``` + + +## Extensions + +It is likely that some projects based on Preact would wish to extend Component with great new functionality. + +Perhaps automatic connection to stores for a Flux-like architecture, or mixed-in context bindings to make it feel more like `React.createClass()`. Just use ES2015 inheritance: + +```js +class BoundComponent extends Component { + constructor(props) { + super(props); + this.bind(); + } + bind() { + this.binds = {}; + for (let i in this) { + this.binds[i] = this[i].bind(this); + } + } +} + +// example usage +class Link extends BoundComponent { + click() { + open(this.href); + } + render() { + let { click } = this.binds; + return <span onclick={ click }>{ children }</span>; + } +} +``` + + +The possibilities are pretty endless here. You could even add support for rudimentary mixins: + +```js +class MixedComponent extends Component { + constructor() { + super(); + (this.mixins || []).forEach( m => Object.assign(this, m) ); + } +} +``` + + +## License + +MIT + + + +[![Preact](http://i.imgur.com/YqCHvEW.gif)](https://preactjs.com) + + + +[ES6 Classes]: https://facebook.github.io/react/docs/reusable-components.html#es6-classes +[hyperscript]: https://github.com/dominictarr/hyperscript +[preact-boilerplate]: https://github.com/developit/preact-boilerplate +[lifecycle methods]: https://facebook.github.io/react/docs/component-specs.html diff --git a/thirdparty/preact/config/codemod-const.js b/thirdparty/preact/config/codemod-const.js new file mode 100644 index 000000000..4bffd9add --- /dev/null +++ b/thirdparty/preact/config/codemod-const.js @@ -0,0 +1,39 @@ +/* eslint no-console:0 */ + +/** Find constants (identified by ALL_CAPS_DECLARATIONS), and inline them globally. + * This is safe because Preact *only* uses global constants. + */ +export default (file, api) => { + let j = api.jscodeshift, + code = j(file.source), + constants = {}, + found = 0; + + code.find(j.VariableDeclaration) + .filter( decl => { + for (let i=decl.value.declarations.length; i--; ) { + let node = decl.value.declarations[i], + name = node.id && node.id.name, + init = node.init; + if (name && init && name.match(/^[A-Z0-9_$]+$/g)) { + if (init.type==='Literal') { + console.log(`Inlining constant: ${name}=${init.raw}`); + found++; + constants[name] = init; + // remove declaration + decl.value.declarations.splice(i, 1); + // if it's the last, we'll remove the whole statement + return !decl.value.declarations.length; + } + } + } + return false; + }) + .remove(); + + code.find(j.Identifier) + .filter( path => path.value.name && constants.hasOwnProperty(path.value.name) ) + .replaceWith( path => (found++, constants[path.value.name]) ); + + return found ? code.toSource({ quote: 'single' }) : null; +}; diff --git a/thirdparty/preact/config/codemod-strip-tdz.js b/thirdparty/preact/config/codemod-strip-tdz.js new file mode 100644 index 000000000..a1f07fafe --- /dev/null +++ b/thirdparty/preact/config/codemod-strip-tdz.js @@ -0,0 +1,56 @@ +/* eslint no-console:0 */ + + +// parent node types that we don't want to remove pointless initializations from (because it breaks hoisting) +const BLOCKED = ['ForStatement', 'WhileStatement']; // 'IfStatement', 'SwitchStatement' + +/** Removes var initialization to `void 0`, which Babel adds for TDZ strictness. */ +export default (file, api) => { + let { jscodeshift } = api, + found = 0; + + let code = jscodeshift(file.source) + .find(jscodeshift.VariableDeclaration) + .forEach(handleDeclaration); + + function handleDeclaration(decl) { + let p = decl, + remove = true; + + while ((p = p.parentPath)) { + if (~BLOCKED.indexOf(p.value.type)) { + remove = false; + break; + } + } + + decl.value.declarations.filter(isPointless).forEach( node => { + if (remove===false) { + console.log(`> Skipping removal of undefined init for "${node.id.name}": within ${p.value.type}`); + } + else { + removeNodeInitialization(node); + } + }); + } + + function removeNodeInitialization(node) { + node.init = null; + found++; + } + + function isPointless(node) { + let { init } = node; + if (init) { + if (init.type==='UnaryExpression' && init.operator==='void' && init.argument.value==0) { + return true; + } + if (init.type==='Identifier' && init.name==='undefined') { + return true; + } + } + return false; + } + + return found ? code.toSource({ quote: 'single' }) : null; +}; diff --git a/thirdparty/preact/config/eslint-config.js b/thirdparty/preact/config/eslint-config.js new file mode 100644 index 000000000..95ac48965 --- /dev/null +++ b/thirdparty/preact/config/eslint-config.js @@ -0,0 +1,66 @@ +module.exports = { + parser: 'babel-eslint', + extends: 'eslint:recommended', + plugins: [ + 'react' + ], + env: { + browser: true, + mocha: true, + node: true, + es6: true + }, + parserOptions: { + ecmaFeatures: { + modules: true, + jsx: true + } + }, + globals: { + sinon: true, + expect: true + }, + rules: { + 'react/jsx-uses-react': 2, + 'react/jsx-uses-vars': 2, + 'no-unused-vars': [1, { varsIgnorePattern: '^h$' }], + 'no-cond-assign': 1, + 'no-empty': 0, + 'no-console': 1, + semi: 2, + camelcase: 0, + 'comma-style': 2, + 'comma-dangle': [2, 'never'], + indent: [2, 'tab', {SwitchCase: 1}], + 'no-mixed-spaces-and-tabs': [2, 'smart-tabs'], + 'no-trailing-spaces': [2, { skipBlankLines: true }], + 'max-nested-callbacks': [2, 3], + 'no-eval': 2, + 'no-implied-eval': 2, + 'no-new-func': 2, + 'guard-for-in': 0, + eqeqeq: 0, + 'no-else-return': 2, + 'no-redeclare': 2, + 'no-dupe-keys': 2, + radix: 2, + strict: [2, 'never'], + 'no-shadow': 0, + 'callback-return': [1, ['callback', 'cb', 'next', 'done']], + 'no-delete-var': 2, + 'no-undef-init': 2, + 'no-shadow-restricted-names': 2, + 'handle-callback-err': 0, + 'no-lonely-if': 2, + 'keyword-spacing': 2, + 'constructor-super': 2, + 'no-this-before-super': 2, + 'no-dupe-class-members': 2, + 'no-const-assign': 2, + 'prefer-spread': 2, + 'no-useless-concat': 2, + 'no-var': 2, + 'object-shorthand': 2, + 'prefer-arrow-callback': 2 + } +}; diff --git a/thirdparty/preact/config/rollup.config.aliases.js b/thirdparty/preact/config/rollup.config.aliases.js new file mode 100644 index 000000000..e5c549fc3 --- /dev/null +++ b/thirdparty/preact/config/rollup.config.aliases.js @@ -0,0 +1,12 @@ +import memory from 'rollup-plugin-memory'; +import rollupConfig from './rollup.config'; + +export default Object.assign({}, rollupConfig, { + plugins: [ + memory({ + path: 'src/preact', + contents: `import { h } from './preact';export * from './preact';export { h as createElement };` + }), + ...rollupConfig.plugins.slice(1) + ] +}); diff --git a/thirdparty/preact/config/rollup.config.js b/thirdparty/preact/config/rollup.config.js new file mode 100644 index 000000000..57f71e1e7 --- /dev/null +++ b/thirdparty/preact/config/rollup.config.js @@ -0,0 +1,23 @@ +import nodeResolve from 'rollup-plugin-node-resolve'; +import babel from 'rollup-plugin-babel'; +import memory from 'rollup-plugin-memory'; + +export default { + exports: 'named', + useStrict: false, + plugins: [ + memory({ + path: 'src/preact', + contents: "export * from './preact';" + }), + nodeResolve({ + main: true + }), + babel({ + sourceMap: true, + loose: 'all', + blacklist: ['es6.tailCall'], + exclude: 'node_modules/**' + }) + ] +}; diff --git a/thirdparty/preact/package.json b/thirdparty/preact/package.json new file mode 100644 index 000000000..293597fae --- /dev/null +++ b/thirdparty/preact/package.json @@ -0,0 +1,103 @@ +{ + "name": "preact", + "amdName": "preact", + "version": "6.2.1", + "description": "Tiny & fast Component-based virtual DOM framework.", + "main": "dist/preact.js", + "jsnext:main": "src/preact.js", + "aliases:main": "aliases.js", + "dev:main": "dist/preact.dev.js", + "minified:main": "dist/preact.min.js", + "scripts": { + "clean": "rimraf dist/ $npm_package_aliases_main ${npm_package_aliases_main}.map", + "copy-flow-definition": "cp src/preact.js.flow dist/preact.js.flow", + "copy-typescript-definition": "cp src/preact.d.ts dist/preact.d.ts", + "build": "npm-run-all --silent clean transpile copy-flow-definition copy-typescript-definition strip optimize minify size", + "transpile:main": "rollup -c config/rollup.config.js -m ${npm_package_dev_main}.map -f umd -n $npm_package_amdName $npm_package_jsnext_main -o $npm_package_dev_main", + "transpile:aliases": "rollup -c config/rollup.config.aliases.js -m ${npm_package_aliases_main}.map -f umd -n $npm_package_amdName $npm_package_jsnext_main -o $npm_package_aliases_main", + "transpile": "npm-run-all transpile:main transpile:aliases", + "optimize": "uglifyjs $npm_package_dev_main -c conditionals=false,sequences=false,loops=false,join_vars=false,collapse_vars=false --pure-funcs=Object.defineProperty -b width=120,quote_style=3 -o $npm_package_main -p relative --in-source-map ${npm_package_dev_main}.map --source-map ${npm_package_main}.map", + "minify": "uglifyjs $npm_package_main -c collapse_vars,evaluate,screw_ie8,unsafe,loops=false,keep_fargs=false,pure_getters,unused,dead_code -m -o $npm_package_minified_main -p relative --in-source-map ${npm_package_main}.map --source-map ${npm_package_minified_main}.map", + "strip": "jscodeshift --run-in-band -s -t config/codemod-strip-tdz.js dist/preact.dev.js && jscodeshift --run-in-band -s -t config/codemod-const.js dist/preact.dev.js", + "size": "size=$(gzip-size $npm_package_minified_main) && echo \"gzip size: $size / $(pretty-bytes $size)\"", + "test": "npm-run-all lint --parallel test:mocha test:karma", + "test:mocha": "mocha --recursive --compilers js:babel/register test/shared test/node", + "test:karma": "karma start test/karma.conf.js --single-run", + "test:mocha:watch": "npm run test:mocha -- --watch", + "test:karma:watch": "npm run test:karma -- no-single-run", + "lint": "eslint src test", + "prepublish": "npm run build", + "release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish" + }, + "eslintConfig": { + "extends": "./config/eslint-config.js" + }, + "typings": "./src/preact.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/developit/preact.git" + }, + "files": [ + "src", + "dist", + "aliases.js", + "aliases.js.map", + "typings.json" + ], + "author": "Jason Miller <jason@developit.ca>", + "license": "MIT", + "bugs": { + "url": "https://github.com/developit/preact/issues" + }, + "homepage": "https://github.com/developit/preact", + "devDependencies": { + "babel": "^5.8.23", + "babel-core": "^5.8.24", + "babel-eslint": "^6.1.0", + "babel-loader": "^5.3.2", + "babel-runtime": "^5.8.24", + "chai": "^3.4.1", + "diff": "^3.0.0", + "eslint": "^3.0.0", + "eslint-plugin-react": "^6.0.0", + "gzip-size-cli": "^1.0.0", + "isparta-loader": "^2.0.0", + "jscodeshift": "^0.3.25", + "karma": "^1.1.0", + "karma-babel-preprocessor": "^5.2.2", + "karma-chai": "^0.1.0", + "karma-chai-sinon": "^0.1.5", + "karma-coverage": "^1.0.0", + "karma-mocha": "^1.1.1", + "karma-mocha-reporter": "^2.0.4", + "karma-phantomjs-launcher": "^1.0.1", + "karma-sauce-launcher": "^1.0.0", + "karma-source-map-support": "^1.1.0", + "karma-sourcemap-loader": "^0.3.6", + "karma-webpack": "^1.7.0", + "mocha": "^3.0.1", + "npm-run-all": "^3.0.0", + "phantomjs-prebuilt": "^2.1.7", + "pretty-bytes-cli": "^2.0.0", + "rimraf": "^2.5.3", + "rollup": "^0.34.1", + "rollup-plugin-babel": "^1.0.0", + "rollup-plugin-memory": "^2.0.0", + "rollup-plugin-node-resolve": "^2.0.0", + "sinon": "^1.17.4", + "sinon-chai": "^2.8.0", + "uglify-js": "^2.7.0", + "webpack": "^1.13.1" + }, + "greenkeeper": { + "ignore": [ + "rollup-plugin-babel", + "babel", + "babel-core", + "babel-eslint", + "babel-loader", + "babel-runtime", + "jscodeshift" + ] + } +} diff --git a/thirdparty/preact/src/clone-element.js b/thirdparty/preact/src/clone-element.js new file mode 100644 index 000000000..fc7103056 --- /dev/null +++ b/thirdparty/preact/src/clone-element.js @@ -0,0 +1,10 @@ +import { clone, extend } from './util'; +import { h } from './h'; + +export function cloneElement(vnode, props) { + return h( + vnode.nodeName, + extend(clone(vnode.attributes), props), + arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children + ); +} diff --git a/thirdparty/preact/src/component.js b/thirdparty/preact/src/component.js new file mode 100644 index 000000000..aefaebe98 --- /dev/null +++ b/thirdparty/preact/src/component.js @@ -0,0 +1,102 @@ +import { FORCE_RENDER } from './constants'; +import { extend, clone, isFunction } from './util'; +import { createLinkedState } from './linked-state'; +import { renderComponent } from './vdom/component'; +import { enqueueRender } from './render-queue'; + +/** Base Component class, for he ES6 Class method of creating Components + * @public + * + * @example + * class MyFoo extends Component { + * render(props, state) { + * return <div />; + * } + * } + */ +export function Component(props, context) { + /** @private */ + this._dirty = true; + // /** @public */ + // this._disableRendering = false; + // /** @public */ + // this.prevState = this.prevProps = this.prevContext = this.base = this.nextBase = this._parentComponent = this._component = this.__ref = this.__key = this._linkedStates = this._renderCallbacks = null; + /** @public */ + this.context = context; + /** @type {object} */ + this.props = props; + /** @type {object} */ + if (!this.state) this.state = {}; +} + + +extend(Component.prototype, { + + /** Returns a `boolean` value indicating if the component should re-render when receiving the given `props` and `state`. + * @param {object} nextProps + * @param {object} nextState + * @param {object} nextContext + * @returns {Boolean} should the component re-render + * @name shouldComponentUpdate + * @function + */ + // shouldComponentUpdate() { + // return true; + // }, + + + /** Returns a function that sets a state property when called. + * Calling linkState() repeatedly with the same arguments returns a cached link function. + * + * Provides some built-in special cases: + * - Checkboxes and radio buttons link their boolean `checked` value + * - Inputs automatically link their `value` property + * - Event paths fall back to any associated Component if not found on an element + * - If linked value is a function, will invoke it and use the result + * + * @param {string} key The path to set - can be a dot-notated deep key + * @param {string} [eventPath] If set, attempts to find the new state value at a given dot-notated path within the object passed to the linkedState setter. + * @returns {function} linkStateSetter(e) + * + * @example Update a "text" state value when an input changes: + * <input onChange={ this.linkState('text') } /> + * + * @example Set a deep state value on click + * <button onClick={ this.linkState('touch.coords', 'touches.0') }>Tap</button + */ + linkState(key, eventPath) { + let c = this._linkedStates || (this._linkedStates = {}); + return c[key+eventPath] || (c[key+eventPath] = createLinkedState(this, key, eventPath)); + }, + + + /** Update component state by copying properties from `state` to `this.state`. + * @param {object} state A hash of state properties to update with new values + */ + setState(state, callback) { + let s = this.state; + if (!this.prevState) this.prevState = clone(s); + extend(s, isFunction(state) ? state(s, this.props) : state); + if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback); + enqueueRender(this); + }, + + + /** Immediately perform a synchronous re-render of the component. + * @private + */ + forceUpdate() { + renderComponent(this, FORCE_RENDER); + }, + + + /** Accepts `props` and `state`, and returns a new Virtual DOM tree to build. + * Virtual DOM is generally constructed via [JSX](http://jasonformat.com/wtf-is-jsx). + * @param {object} props Props (eg: JSX attributes) received from parent element/component + * @param {object} state The component's current state + * @param {object} context Context object (if a parent component has provided context) + * @returns VNode + */ + render() {} + +}); diff --git a/thirdparty/preact/src/constants.js b/thirdparty/preact/src/constants.js new file mode 100644 index 000000000..138cd0983 --- /dev/null +++ b/thirdparty/preact/src/constants.js @@ -0,0 +1,20 @@ +// render modes + +export const NO_RENDER = 0; +export const SYNC_RENDER = 1; +export const FORCE_RENDER = 2; +export const ASYNC_RENDER = 3; + +export const EMPTY = {}; + +export const ATTR_KEY = typeof Symbol!=='undefined' ? Symbol.for('preactattr') : '__preactattr_'; + +// DOM properties that should NOT have "px" added when numeric +export const NON_DIMENSION_PROPS = { + boxFlex:1, boxFlexGroup:1, columnCount:1, fillOpacity:1, flex:1, flexGrow:1, + flexPositive:1, flexShrink:1, flexNegative:1, fontWeight:1, lineClamp:1, lineHeight:1, + opacity:1, order:1, orphans:1, strokeOpacity:1, widows:1, zIndex:1, zoom:1 +}; + +// DOM event types that do not bubble and should be attached via useCapture +export const NON_BUBBLING_EVENTS = { blur:1, error:1, focus:1, load:1, resize:1, scroll:1 }; diff --git a/thirdparty/preact/src/dom/index.js b/thirdparty/preact/src/dom/index.js new file mode 100644 index 000000000..248a3cdc5 --- /dev/null +++ b/thirdparty/preact/src/dom/index.js @@ -0,0 +1,100 @@ +import { ATTR_KEY, NON_DIMENSION_PROPS, NON_BUBBLING_EVENTS } from '../constants'; +import options from '../options'; +import { toLowerCase, isString, isFunction, hashToClassName } from '../util'; + + + + +/** Removes a given DOM Node from its parent. */ +export function removeNode(node) { + let p = node.parentNode; + if (p) p.removeChild(node); +} + + +/** Set a named attribute on the given Node, with special behavior for some names and event handlers. + * If `value` is `null`, the attribute/handler will be removed. + * @param {Element} node An element to mutate + * @param {string} name The name/key to set, such as an event or attribute name + * @param {any} value An attribute value, such as a function to be used as an event handler + * @param {any} previousValue The last value that was set for this name/node pair + * @private + */ +export function setAccessor(node, name, value, old, isSvg) { + node[ATTR_KEY][name] = value; + + if (name==='className') name = 'class'; + + if (name==='class' && value && typeof value==='object') { + value = hashToClassName(value); + } + + if (name==='key' || name==='children' || name==='innerHTML') { + // skip these + } + else if (name==='class' && !isSvg) { + node.className = value || ''; + } + else if (name==='style') { + if (!value || isString(value) || isString(old)) { + node.style.cssText = value || ''; + } + if (value && typeof value==='object') { + if (!isString(old)) { + for (let i in old) if (!(i in value)) node.style[i] = ''; + } + for (let i in value) { + node.style[i] = typeof value[i]==='number' && !NON_DIMENSION_PROPS[i] ? (value[i]+'px') : value[i]; + } + } + } + else if (name==='dangerouslySetInnerHTML') { + if (value) node.innerHTML = value.__html; + } + else if (name[0]=='o' && name[1]=='n') { + let l = node._listeners || (node._listeners = {}); + name = toLowerCase(name.substring(2)); + // @TODO: this might be worth it later, un-breaks focus/blur bubbling in IE9: + // if (node.attachEvent) name = name=='focus'?'focusin':name=='blur'?'focusout':name; + if (value) { + if (!l[name]) node.addEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); + } + else if (l[name]) { + node.removeEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); + } + l[name] = value; + } + else if (name!=='list' && name!=='type' && !isSvg && name in node) { + setProperty(node, name, value==null ? '' : value); + if (value==null || value===false) node.removeAttribute(name); + } + else { + let ns = isSvg && name.match(/^xlink\:?(.+)/); + if (value==null || value===false) { + if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1])); + else node.removeAttribute(name); + } + else if (typeof value!=='object' && !isFunction(value)) { + if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1]), value); + else node.setAttribute(name, value); + } + } +} + + +/** Attempt to set a DOM property to the given value. + * IE & FF throw for certain property-value combinations. + */ +function setProperty(node, name, value) { + try { + node[name] = value; + } catch (e) { } +} + + +/** Proxy an event to hooked event handlers + * @private + */ +function eventProxy(e) { + return this._listeners[e.type](options.event && options.event(e) || e); +} diff --git a/thirdparty/preact/src/dom/recycler.js b/thirdparty/preact/src/dom/recycler.js new file mode 100644 index 000000000..22085a916 --- /dev/null +++ b/thirdparty/preact/src/dom/recycler.js @@ -0,0 +1,25 @@ +import { toLowerCase } from '../util'; +import { removeNode } from './index'; + +/** DOM node pool, keyed on nodeName. */ + +const nodes = {}; + +export function collectNode(node) { + removeNode(node); + + if (node instanceof Element) { + node._component = node._componentConstructor = null; + + let name = node.normalizedNodeName || toLowerCase(node.nodeName); + (nodes[name] || (nodes[name] = [])).push(node); + } +} + + +export function createNode(nodeName, isSvg) { + let name = toLowerCase(nodeName), + node = nodes[name] && nodes[name].pop() || (isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName)); + node.normalizedNodeName = name; + return node; +} diff --git a/thirdparty/preact/src/h.js b/thirdparty/preact/src/h.js new file mode 100644 index 000000000..e57ce4bde --- /dev/null +++ b/thirdparty/preact/src/h.js @@ -0,0 +1,51 @@ +import { VNode } from './vnode'; +import options from './options'; + + +let stack = []; + + + +/** JSX/hyperscript reviver +* Benchmarks: https://esbench.com/bench/57ee8f8e330ab09900a1a1a0 + * @see http://jasonformat.com/wtf-is-jsx + * @public + * @example + * /** @jsx h *\/ + * import { render, h } from 'preact'; + * render(<span>foo</span>, document.body); + */ +export function h(nodeName, attributes) { + let children, lastSimple, child, simple, i; + for (i=arguments.length; i-- > 2; ) { + stack.push(arguments[i]); + } + if (attributes && attributes.children) { + if (!stack.length) stack.push(attributes.children); + delete attributes.children; + } + while (stack.length) { + if ((child = stack.pop()) instanceof Array) { + for (i=child.length; i--; ) stack.push(child[i]); + } + else if (child!=null && child!==false) { + if (typeof child=='number' || child===true) child = String(child); + simple = typeof child=='string'; + if (simple && lastSimple) { + children[children.length-1] += child; + } + else { + if (children) children.push(child); + else children = [child]; + lastSimple = simple; + } + } + } + + let p = new VNode(nodeName, attributes || undefined, children); + + // if a "vnode hook" is defined, pass every created VNode to it + if (options.vnode) options.vnode(p); + + return p; +} diff --git a/thirdparty/preact/src/linked-state.js b/thirdparty/preact/src/linked-state.js new file mode 100644 index 000000000..ed72bd8bc --- /dev/null +++ b/thirdparty/preact/src/linked-state.js @@ -0,0 +1,28 @@ +import { isString, delve } from './util'; + +/** Create an Event handler function that sets a given state property. + * @param {Component} component The component whose state should be updated + * @param {string} key A dot-notated key path to update in the component's state + * @param {string} eventPath A dot-notated key path to the value that should be retrieved from the Event or component + * @returns {function} linkedStateHandler + * @private + */ +export function createLinkedState(component, key, eventPath) { + let path = key.split('.'), + p0 = path[0]; + return function(e) { + let t = e && e.currentTarget || this, + s = component.state, + obj = s, + v = isString(eventPath) ? delve(e, eventPath) : t.nodeName ? ((t.nodeName+t.type).match(/^input(che|rad)/i) ? t.checked : t.value) : e, + i; + if (path.length>1) { + for (i=0; i<path.length-1; i++) { + obj = obj[path[i]] || (obj[path[i]] = {}); + } + obj[path[i]] = v; + v = s[p0]; + } + component.setState({ [p0]: v }); + }; +} diff --git a/thirdparty/preact/src/options.js b/thirdparty/preact/src/options.js new file mode 100644 index 000000000..35b7418fc --- /dev/null +++ b/thirdparty/preact/src/options.js @@ -0,0 +1,18 @@ +/** Global options + * @public + * @namespace options {Object} + */ +export default { + + /** If `true`, `prop` changes trigger synchronous component updates. + * @name syncComponentUpdates + * @type Boolean + * @default true + */ + //syncComponentUpdates: true, + + /** Processes all created VNodes. + * @param {VNode} vnode A newly-created VNode to normalize/process + */ + //vnode(vnode) { } +}; diff --git a/thirdparty/preact/src/preact.d.ts b/thirdparty/preact/src/preact.d.ts new file mode 100644 index 000000000..2dd8299a9 --- /dev/null +++ b/thirdparty/preact/src/preact.d.ts @@ -0,0 +1,554 @@ +declare namespace preact { + interface ComponentProps { + children?:JSX.Element[]; + key?:string; + } + + interface PreactHTMLAttributes { + key?:string; + } + + interface VNode { + nodeName:ComponentConstructor<any, any>|string; + attributes:{[name:string]:any}; + children:VNode[]; + key:string; + } + + interface ComponentLifecycle<PropsType, StateType> { + componentWillMount?():void; + + componentDidMount?():void; + + componentWillUnmount?():void; + + componentDidUnmount?():void; + + componentWillReceiveProps?(props:PropsType):void; + + shouldComponentUpdate?(props:PropsType):boolean; + + componentWillUpdate?():void; + + componentDidUpdate?():void; + } + + interface ComponentConstructor<PropsType, StateType> { + new (props?:PropsType):Component<PropsType, StateType>; + } + + abstract class Component<PropsType, StateType> implements ComponentLifecycle<PropsType, StateType> { + constructor(props?:PropsType); + + state:StateType; + props:PropsType & ComponentProps; + base:HTMLElement; + + linkState:(name:string) => void; + + setState(state:StateType, opts?:any):void; + + abstract render(props:PropsType & ComponentProps, state:any):JSX.Element; + } + + function h<PropsType>(node:ComponentConstructor<PropsType, any>, params:PropsType, ...children:(JSX.Element|string)[]):JSX.Element; + function h(node:string, params:JSX.HTMLAttributes&JSX.SVGAttributes, ...children:(JSX.Element|string)[]):JSX.Element; + + function render(node:JSX.Element, parent:Element, merge?:boolean):Element; + + function rerender():void; + + function cloneElement(element:JSX.Element, props:any):JSX.Element; + + var options:{ + syncComponentUpdates?:boolean; + debounceRendering?:(render:() => void) => void; + vnode?:(vnode:VNode) => void; + event?:(event:Event) => Event; + }; +} + +declare module "preact" { + export = preact; +} + +declare namespace JSX { + interface Element extends preact.VNode { + + } + + interface ElementClass extends preact.Component<any, any> { + + } + + interface ElementAttributesProperty { + props:any; + } + + interface SVGAttributes { + clipPath?:string; + cx?:number | string; + cy?:number | string; + d?:string; + dx?:number | string; + dy?:number | string; + fill?:string; + fillOpacity?:number | string; + fontFamily?:string; + fontSize?:number | string; + fx?:number | string; + fy?:number | string; + gradientTransform?:string; + gradientUnits?:string; + markerEnd?:string; + markerMid?:string; + markerStart?:string; + offset?:number | string; + opacity?:number | string; + patternContentUnits?:string; + patternUnits?:string; + points?:string; + preserveAspectRatio?:string; + r?:number | string; + rx?:number | string; + ry?:number | string; + spreadMethod?:string; + stopColor?:string; + stopOpacity?:number | string; + stroke?:string; + strokeDasharray?:string; + strokeLinecap?:string; + strokeMiterlimit?:string; + strokeOpacity?:number | string; + strokeWidth?:number | string; + textAnchor?:string; + transform?:string; + version?:string; + viewBox?:string; + x1?:number | string; + x2?:number | string; + x?:number | string; + xlinkActuate?:string; + xlinkArcrole?:string; + xlinkHref?:string; + xlinkRole?:string; + xlinkShow?:string; + xlinkTitle?:string; + xlinkType?:string; + xmlBase?:string; + xmlLang?:string; + xmlSpace?:string; + y1?:number | string; + y2?:number | string; + y?:number | string; + } + + interface PathAttributes { + d:string; + } + + interface EventHandler<E extends Event> { + (event:E):void; + } + + type ClipboardEventHandler = EventHandler<ClipboardEvent>; + type CompositionEventHandler = EventHandler<CompositionEvent>; + type DragEventHandler = EventHandler<DragEvent>; + type FocusEventHandler = EventHandler<FocusEvent>; + type KeyboardEventHandler = EventHandler<KeyboardEvent>; + type MouseEventHandler = EventHandler<MouseEvent>; + type TouchEventHandler = EventHandler<TouchEvent>; + type UIEventHandler = EventHandler<UIEvent>; + type WheelEventHandler = EventHandler<WheelEvent>; + type AnimationEventHandler = EventHandler<AnimationEvent>; + type TransitionEventHandler = EventHandler<TransitionEvent>; + + type GenericEventHandler = EventHandler<Event>; + + interface DOMAttributed { + // Clipboard Events + onCopy?:ClipboardEventHandler; + onCut?:ClipboardEventHandler; + onPaste?:ClipboardEventHandler; + + // Composition Events + onCompositionEnd?:CompositionEventHandler; + onCompositionStart?:CompositionEventHandler; + onCompositionUpdate?:CompositionEventHandler; + + // Focus Events + onFocus?:FocusEventHandler; + onBlur?:FocusEventHandler; + + // Form Events + onChange?:GenericEventHandler; + onInput?:GenericEventHandler; + onSubmit?:GenericEventHandler; + + // Keyboard Events + onKeyDown?:KeyboardEventHandler; + onKeyPress?:KeyboardEventHandler; + onKeyUp?:KeyboardEventHandler; + + // Media Events + onAbort?:GenericEventHandler; + onCanPlay?:GenericEventHandler; + onCanPlayThrough?:GenericEventHandler; + onDurationChange?:GenericEventHandler; + onEmptied?:GenericEventHandler; + onEncrypted?:GenericEventHandler; + onEnded?:GenericEventHandler; + onLoadedData?:GenericEventHandler; + onLoadedMetadata?:GenericEventHandler; + onLoadStart?:GenericEventHandler; + onPause?:GenericEventHandler; + onPlay?:GenericEventHandler; + onPlaying?:GenericEventHandler; + onProgress?:GenericEventHandler; + onRateChange?:GenericEventHandler; + onSeeked?:GenericEventHandler; + onSeeking?:GenericEventHandler; + onStalled?:GenericEventHandler; + onSuspend?:GenericEventHandler; + onTimeUpdate?:GenericEventHandler; + onVolumeChange?:GenericEventHandler; + onWaiting?:GenericEventHandler; + + // MouseEvents + onClick?:MouseEventHandler; + onContextMenu?:MouseEventHandler; + onDoubleClick?:MouseEventHandler; + onDrag?:DragEventHandler; + onDragEnd?:DragEventHandler; + onDragEnter?:DragEventHandler; + onDragExit?:DragEventHandler; + onDragLeave?:DragEventHandler; + onDragOver?:DragEventHandler; + onDragStart?:DragEventHandler; + onDrop?:DragEventHandler; + onMouseDown?:MouseEventHandler; + onMouseEnter?:MouseEventHandler; + onMouseLeave?:MouseEventHandler; + onMouseMove?:MouseEventHandler; + onMouseOut?:MouseEventHandler; + onMouseOver?:MouseEventHandler; + onMouseUp?:MouseEventHandler; + + // Selection Events + onSelect?:GenericEventHandler; + + // Touch Events + onTouchCancel?:TouchEventHandler; + onTouchEnd?:TouchEventHandler; + onTouchMove?:TouchEventHandler; + onTouchStart?:TouchEventHandler; + + // UI Events + onScroll?:UIEventHandler; + + // Wheel Events + onWheel?:WheelEventHandler; + + // Animation Events + onAnimationStart?:AnimationEventHandler; + onAnimationEnd?:AnimationEventHandler; + onAnimationIteration?:AnimationEventHandler; + + // Transition Events + onTransitionEnd?:TransitionEventHandler; + } + + interface HTMLAttributes extends preact.PreactHTMLAttributes, DOMAttributed { + // Standard HTML Attributes + accept?:string; + acceptCharset?:string; + accessKey?:string; + action?:string; + allowFullScreen?:boolean; + allowTransparency?:boolean; + alt?:string; + async?:boolean; + autocomplete?:string; + autofocus?:boolean; + autoPlay?:boolean; + capture?:boolean; + cellPadding?:number | string; + cellSpacing?:number | string; + charSet?:string; + challenge?:string; + checked?:boolean; + class?:string; + className?:string; + cols?:number; + colSpan?:number; + content?:string; + contentEditable?:boolean; + contextMenu?:string; + controls?:boolean; + coords?:string; + crossOrigin?:string; + data?:string; + dateTime?:string; + default?:boolean; + defer?:boolean; + dir?:string; + disabled?:boolean; + download?:any; + draggable?:boolean; + encType?:string; + form?:string; + formAction?:string; + formEncType?:string; + formMethod?:string; + formNoValidate?:boolean; + formTarget?:string; + frameBorder?:number | string; + headers?:string; + height?:number | string; + hidden?:boolean; + high?:number; + href?:string; + hrefLang?:string; + for?:string; + httpEquiv?:string; + icon?:string; + id?:string; + inputMode?:string; + integrity?:string; + is?:string; + keyParams?:string; + keyType?:string; + kind?:string; + label?:string; + lang?:string; + list?:string; + loop?:boolean; + low?:number; + manifest?:string; + marginHeight?:number; + marginWidth?:number; + max?:number | string; + maxLength?:number; + media?:string; + mediaGroup?:string; + method?:string; + min?:number | string; + minLength?:number; + multiple?:boolean; + muted?:boolean; + name?:string; + noValidate?:boolean; + open?:boolean; + optimum?:number; + pattern?:string; + placeholder?:string; + poster?:string; + preload?:string; + radioGroup?:string; + readOnly?:boolean; + rel?:string; + required?:boolean; + role?:string; + rows?:number; + rowSpan?:number; + sandbox?:string; + scope?:string; + scoped?:boolean; + scrolling?:string; + seamless?:boolean; + selected?:boolean; + shape?:string; + size?:number; + sizes?:string; + span?:number; + spellCheck?:boolean; + src?:string; + srcset?:string; + srcDoc?:string; + srcLang?:string; + srcSet?:string; + start?:number; + step?:number | string; + style?:any; + summary?:string; + tabIndex?:number; + target?:string; + title?:string; + type?:string; + useMap?:string; + value?:string | string[]; + width?:number | string; + wmode?:string; + wrap?:string; + + // RDFa Attributes + about?:string; + datatype?:string; + inlist?:any; + prefix?:string; + property?:string; + resource?:string; + typeof?:string; + vocab?:string; + } + + interface IntrinsicElements { + // HTML + a:HTMLAttributes; + abbr:HTMLAttributes; + address:HTMLAttributes; + area:HTMLAttributes; + article:HTMLAttributes; + aside:HTMLAttributes; + audio:HTMLAttributes; + b:HTMLAttributes; + base:HTMLAttributes; + bdi:HTMLAttributes; + bdo:HTMLAttributes; + big:HTMLAttributes; + blockquote:HTMLAttributes; + body:HTMLAttributes; + br:HTMLAttributes; + button:HTMLAttributes; + canvas:HTMLAttributes; + caption:HTMLAttributes; + cite:HTMLAttributes; + code:HTMLAttributes; + col:HTMLAttributes; + colgroup:HTMLAttributes; + data:HTMLAttributes; + datalist:HTMLAttributes; + dd:HTMLAttributes; + del:HTMLAttributes; + details:HTMLAttributes; + dfn:HTMLAttributes; + dialog:HTMLAttributes; + div:HTMLAttributes; + dl:HTMLAttributes; + dt:HTMLAttributes; + em:HTMLAttributes; + embed:HTMLAttributes; + fieldset:HTMLAttributes; + figcaption:HTMLAttributes; + figure:HTMLAttributes; + footer:HTMLAttributes; + form:HTMLAttributes; + h1:HTMLAttributes; + h2:HTMLAttributes; + h3:HTMLAttributes; + h4:HTMLAttributes; + h5:HTMLAttributes; + h6:HTMLAttributes; + head:HTMLAttributes; + header:HTMLAttributes; + hr:HTMLAttributes; + html:HTMLAttributes; + i:HTMLAttributes; + iframe:HTMLAttributes; + img:HTMLAttributes; + input:HTMLAttributes; + ins:HTMLAttributes; + kbd:HTMLAttributes; + keygen:HTMLAttributes; + label:HTMLAttributes; + legend:HTMLAttributes; + li:HTMLAttributes; + link:HTMLAttributes; + main:HTMLAttributes; + map:HTMLAttributes; + mark:HTMLAttributes; + menu:HTMLAttributes; + menuitem:HTMLAttributes; + meta:HTMLAttributes; + meter:HTMLAttributes; + nav:HTMLAttributes; + noscript:HTMLAttributes; + object:HTMLAttributes; + ol:HTMLAttributes; + optgroup:HTMLAttributes; + option:HTMLAttributes; + output:HTMLAttributes; + p:HTMLAttributes; + param:HTMLAttributes; + picture:HTMLAttributes; + pre:HTMLAttributes; + progress:HTMLAttributes; + q:HTMLAttributes; + rp:HTMLAttributes; + rt:HTMLAttributes; + ruby:HTMLAttributes; + s:HTMLAttributes; + samp:HTMLAttributes; + script:HTMLAttributes; + section:HTMLAttributes; + select:HTMLAttributes; + small:HTMLAttributes; + source:HTMLAttributes; + span:HTMLAttributes; + strong:HTMLAttributes; + style:HTMLAttributes; + sub:HTMLAttributes; + summary:HTMLAttributes; + sup:HTMLAttributes; + table:HTMLAttributes; + tbody:HTMLAttributes; + td:HTMLAttributes; + textarea:HTMLAttributes; + tfoot:HTMLAttributes; + th:HTMLAttributes; + thead:HTMLAttributes; + time:HTMLAttributes; + title:HTMLAttributes; + tr:HTMLAttributes; + track:HTMLAttributes; + u:HTMLAttributes; + ul:HTMLAttributes; + "var":HTMLAttributes; + video:HTMLAttributes; + wbr:HTMLAttributes; + + //SVG + svg:SVGAttributes; + + circle:SVGAttributes; + clipPath:SVGAttributes; + defs:SVGAttributes; + ellipse:SVGAttributes; + feBlend:SVGAttributes; + feColorMatrix:SVGAttributes; + feComponentTransfer:SVGAttributes; + feComposite:SVGAttributes; + feConvolveMatrix:SVGAttributes; + feDiffuseLighting:SVGAttributes; + feDisplacementMap:SVGAttributes; + feFlood:SVGAttributes; + feGaussianBlur:SVGAttributes; + feImage:SVGAttributes; + feMerge:SVGAttributes; + feMergeNode:SVGAttributes; + feMorphology:SVGAttributes; + feOffset:SVGAttributes; + feSpecularLighting:SVGAttributes; + feTile:SVGAttributes; + feTurbulence:SVGAttributes; + filter:SVGAttributes; + foreignObject:SVGAttributes; + g:SVGAttributes; + image:SVGAttributes; + line:SVGAttributes; + linearGradient:SVGAttributes; + marker:SVGAttributes; + mask:SVGAttributes; + path:SVGAttributes; + pattern:SVGAttributes; + polygon:SVGAttributes; + polyline:SVGAttributes; + radialGradient:SVGAttributes; + rect:SVGAttributes; + stop:SVGAttributes; + symbol:SVGAttributes; + text:SVGAttributes; + tspan:SVGAttributes; + use:SVGAttributes; + } +} diff --git a/thirdparty/preact/src/preact.js b/thirdparty/preact/src/preact.js new file mode 100644 index 000000000..1fa169c04 --- /dev/null +++ b/thirdparty/preact/src/preact.js @@ -0,0 +1,24 @@ +import { h } from './h'; +import { cloneElement } from './clone-element'; +import { Component } from './component'; +import { render } from './render'; +import { rerender } from './render-queue'; +import options from './options'; + +export default { + h, + cloneElement, + Component, + render, + rerender, + options +}; + +export { + h, + cloneElement, + Component, + render, + rerender, + options +}; diff --git a/thirdparty/preact/src/preact.js.flow b/thirdparty/preact/src/preact.js.flow new file mode 100644 index 000000000..37745faf6 --- /dev/null +++ b/thirdparty/preact/src/preact.js.flow @@ -0,0 +1,9 @@ +/* @flow */ + +import { createElement as h, cloneElement, Component, render } from 'react'; + +export { h, cloneElement, Component, render }; +export default { h, cloneElement, Component, render }; + +declare export function rerender(): void; +declare export var options: Object; diff --git a/thirdparty/preact/src/render-queue.js b/thirdparty/preact/src/render-queue.js new file mode 100644 index 000000000..ff603611b --- /dev/null +++ b/thirdparty/preact/src/render-queue.js @@ -0,0 +1,23 @@ +import options from './options'; +import { defer } from './util'; +import { renderComponent } from './vdom/component'; + +/** Managed queue of dirty components to be re-rendered */ + +// items/itemsOffline swap on each rerender() call (just a simple pool technique) +let items = []; + +export function enqueueRender(component) { + if (!component._dirty && (component._dirty = true) && items.push(component)==1) { + (options.debounceRendering || defer)(rerender); + } +} + + +export function rerender() { + let p, list = items; + items = []; + while ( (p = list.pop()) ) { + if (p._dirty) renderComponent(p); + } +} diff --git a/thirdparty/preact/src/render.js b/thirdparty/preact/src/render.js new file mode 100644 index 000000000..e0e8526ec --- /dev/null +++ b/thirdparty/preact/src/render.js @@ -0,0 +1,20 @@ +import { diff } from './vdom/diff'; + +/** Render JSX into a `parent` Element. + * @param {VNode} vnode A (JSX) VNode to render + * @param {Element} parent DOM element to render into + * @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge` + * @public + * + * @example + * // render a div into <body>: + * render(<div id="hello">hello!</div>, document.body); + * + * @example + * // render a "Thing" component into #foo: + * const Thing = ({ name }) => <span>{ name }</span>; + * render(<Thing name="one" />, document.querySelector('#foo')); + */ +export function render(vnode, parent, merge) { + return diff(merge, vnode, {}, false, parent); +} diff --git a/thirdparty/preact/src/util.js b/thirdparty/preact/src/util.js new file mode 100644 index 000000000..d2e63b090 --- /dev/null +++ b/thirdparty/preact/src/util.js @@ -0,0 +1,68 @@ +/** Copy own-properties from `props` onto `obj`. + * @returns obj + * @private + */ +export function extend(obj, props) { + if (props) { + for (let i in props) obj[i] = props[i]; + } + return obj; +} + + +/** Fast clone. Note: does not filter out non-own properties. + * @see https://esbench.com/bench/56baa34f45df6895002e03b6 + */ +export function clone(obj) { + return extend({}, obj); +} + + +/** Get a deep property value from the given object, expressed in dot-notation. + * @private + */ +export function delve(obj, key) { + for (let p=key.split('.'), i=0; i<p.length && obj; i++) { + obj = obj[p[i]]; + } + return obj; +} + + +/** @private is the given object a Function? */ +export function isFunction(obj) { + return 'function'===typeof obj; +} + + +/** @private is the given object a String? */ +export function isString(obj) { + return 'string'===typeof obj; +} + + +/** Convert a hashmap of CSS classes to a space-delimited className string + * @private + */ +export function hashToClassName(c) { + let str = ''; + for (let prop in c) { + if (c[prop]) { + if (str) str += ' '; + str += prop; + } + } + return str; +} + + +/** Just a memoized String#toLowerCase */ +let lcCache = {}; +export const toLowerCase = s => lcCache[s] || (lcCache[s] = s.toLowerCase()); + + +/** Call a function asynchronously, as soon as possible. + * @param {Function} callback + */ +let resolved = typeof Promise!=='undefined' && Promise.resolve(); +export const defer = resolved ? (f => { resolved.then(f); }) : setTimeout; diff --git a/thirdparty/preact/src/vdom/component-recycler.js b/thirdparty/preact/src/vdom/component-recycler.js new file mode 100644 index 000000000..a70f0ece0 --- /dev/null +++ b/thirdparty/preact/src/vdom/component-recycler.js @@ -0,0 +1,32 @@ +import { Component } from '../component'; + +/** Retains a pool of Components for re-use, keyed on component name. + * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding. + * @private + */ +const components = {}; + + +export function collectComponent(component) { + let name = component.constructor.name, + list = components[name]; + if (list) list.push(component); + else components[name] = [component]; +} + + +export function createComponent(Ctor, props, context) { + let inst = new Ctor(props, context), + list = components[Ctor.name]; + Component.call(inst, props, context); + if (list) { + for (let i=list.length; i--; ) { + if (list[i].constructor===Ctor) { + inst.nextBase = list[i].nextBase; + list.splice(i, 1); + break; + } + } + } + return inst; +} diff --git a/thirdparty/preact/src/vdom/component.js b/thirdparty/preact/src/vdom/component.js new file mode 100644 index 000000000..bb2e4fa5d --- /dev/null +++ b/thirdparty/preact/src/vdom/component.js @@ -0,0 +1,270 @@ +import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants'; +import options from '../options'; +import { isFunction, clone, extend } from '../util'; +import { enqueueRender } from '../render-queue'; +import { getNodeProps } from './index'; +import { diff, mounts, diffLevel, flushMounts, removeOrphanedChildren, recollectNodeTree } from './diff'; +import { isFunctionalComponent, buildFunctionalComponent } from './functional-component'; +import { createComponent, collectComponent } from './component-recycler'; +import { removeNode } from '../dom/index'; + + + +/** Set a component's `props` (generally derived from JSX attributes). + * @param {Object} props + * @param {Object} [opts] + * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering. + * @param {boolean} [opts.render=true] If `false`, no render will be triggered. + */ +export function setComponentProps(component, props, opts, context, mountAll) { + if (component._disable) return; + component._disable = true; + + if ((component.__ref = props.ref)) delete props.ref; + if ((component.__key = props.key)) delete props.key; + + if (!component.base || mountAll) { + if (component.componentWillMount) component.componentWillMount(); + } + else if (component.componentWillReceiveProps) { + component.componentWillReceiveProps(props, context); + } + + if (context && context!==component.context) { + if (!component.prevContext) component.prevContext = component.context; + component.context = context; + } + + if (!component.prevProps) component.prevProps = component.props; + component.props = props; + + component._disable = false; + + if (opts!==NO_RENDER) { + if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { + renderComponent(component, SYNC_RENDER, mountAll); + } + else { + enqueueRender(component); + } + } + + if (component.__ref) component.__ref(component); +} + + + +/** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. + * @param {Component} component + * @param {Object} [opts] + * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one. + * @private + */ +export function renderComponent(component, opts, mountAll, isChild) { + if (component._disable) return; + + let skip, rendered, + props = component.props, + state = component.state, + context = component.context, + previousProps = component.prevProps || props, + previousState = component.prevState || state, + previousContext = component.prevContext || context, + isUpdate = component.base, + nextBase = component.nextBase, + initialBase = isUpdate || nextBase, + initialChildComponent = component._component, + inst, cbase; + + // if updating + if (isUpdate) { + component.props = previousProps; + component.state = previousState; + component.context = previousContext; + if (opts!==FORCE_RENDER + && component.shouldComponentUpdate + && component.shouldComponentUpdate(props, state, context) === false) { + skip = true; + } + else if (component.componentWillUpdate) { + component.componentWillUpdate(props, state, context); + } + component.props = props; + component.state = state; + component.context = context; + } + + component.prevProps = component.prevState = component.prevContext = component.nextBase = null; + component._dirty = false; + + if (!skip) { + if (component.render) rendered = component.render(props, state, context); + + // context to pass to the child, can be updated via (grand-)parent component + if (component.getChildContext) { + context = extend(clone(context), component.getChildContext()); + } + + while (isFunctionalComponent(rendered)) { + rendered = buildFunctionalComponent(rendered, context); + } + + let childComponent = rendered && rendered.nodeName, + toUnmount, base; + + if (isFunction(childComponent)) { + // set up high order component link + + + inst = initialChildComponent; + let childProps = getNodeProps(rendered); + + if (inst && inst.constructor===childComponent) { + setComponentProps(inst, childProps, SYNC_RENDER, context); + } + else { + toUnmount = inst; + + inst = createComponent(childComponent, childProps, context); + inst.nextBase = inst.nextBase || nextBase; + inst._parentComponent = component; + component._component = inst; + setComponentProps(inst, childProps, NO_RENDER, context); + renderComponent(inst, SYNC_RENDER, mountAll, true); + } + + base = inst.base; + } + else { + cbase = initialBase; + + // destroy high order component link + toUnmount = initialChildComponent; + if (toUnmount) { + cbase = component._component = null; + } + + if (initialBase || opts===SYNC_RENDER) { + if (cbase) cbase._component = null; + base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); + } + } + + if (initialBase && base!==initialBase && inst!==initialChildComponent) { + let baseParent = initialBase.parentNode; + if (baseParent && base!==baseParent) { + baseParent.replaceChild(base, initialBase); + } + + if (!cbase && !toUnmount && component._parentComponent) { + initialBase._component = null; + recollectNodeTree(initialBase); + } + } + + if (toUnmount) { + unmountComponent(toUnmount, base!==initialBase); + } + + component.base = base; + if (base && !isChild) { + let componentRef = component, + t = component; + while ((t=t._parentComponent)) { componentRef = t; } + base._component = componentRef; + base._componentConstructor = componentRef.constructor; + } + } + + if (!isUpdate || mountAll) { + mounts.unshift(component); + } + else if (!skip && component.componentDidUpdate) { + component.componentDidUpdate(previousProps, previousState, previousContext); + } + + let cb = component._renderCallbacks, fn; + if (cb) while ( (fn = cb.pop()) ) fn.call(component); + + if (!diffLevel && !isChild) flushMounts(); +} + + + +/** Apply the Component referenced by a VNode to the DOM. + * @param {Element} dom The DOM node to mutate + * @param {VNode} vnode A Component-referencing VNode + * @returns {Element} dom The created/mutated element + * @private + */ +export function buildComponentFromVNode(dom, vnode, context, mountAll) { + let c = dom && dom._component, + oldDom = dom, + isDirectOwner = c && dom._componentConstructor===vnode.nodeName, + isOwner = isDirectOwner, + props = getNodeProps(vnode); + while (c && !isOwner && (c=c._parentComponent)) { + isOwner = c.constructor===vnode.nodeName; + } + + if (c && isOwner && (!mountAll || c._component)) { + setComponentProps(c, props, ASYNC_RENDER, context, mountAll); + dom = c.base; + } + else { + if (c && !isDirectOwner) { + unmountComponent(c, true); + dom = oldDom = null; + } + + c = createComponent(vnode.nodeName, props, context); + if (dom && !c.nextBase) c.nextBase = dom; + setComponentProps(c, props, SYNC_RENDER, context, mountAll); + dom = c.base; + + if (oldDom && dom!==oldDom) { + oldDom._component = null; + recollectNodeTree(oldDom); + } + } + + return dom; +} + + + +/** Remove a component from the DOM and recycle it. + * @param {Element} dom A DOM node from which to unmount the given Component + * @param {Component} component The Component instance to unmount + * @private + */ +export function unmountComponent(component, remove) { + // console.log(`${remove?'Removing':'Unmounting'} component: ${component.constructor.name}`); + let base = component.base; + + component._disable = true; + + if (component.componentWillUnmount) component.componentWillUnmount(); + + component.base = null; + + // recursively tear down & recollect high-order component children: + let inner = component._component; + if (inner) { + unmountComponent(inner, remove); + } + else if (base) { + if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null); + + component.nextBase = base; + + if (remove) { + removeNode(base); + collectComponent(component); + } + removeOrphanedChildren(base.childNodes, !remove); + } + + if (component.__ref) component.__ref(null); + if (component.componentDidUnmount) component.componentDidUnmount(); +} diff --git a/thirdparty/preact/src/vdom/diff.js b/thirdparty/preact/src/vdom/diff.js new file mode 100644 index 000000000..691434e98 --- /dev/null +++ b/thirdparty/preact/src/vdom/diff.js @@ -0,0 +1,247 @@ +import { ATTR_KEY } from '../constants'; +import { isString, isFunction } from '../util'; +import { isSameNodeType, isNamedNode } from './index'; +import { isFunctionalComponent, buildFunctionalComponent } from './functional-component'; +import { buildComponentFromVNode } from './component'; +import { setAccessor } from '../dom/index'; +import { createNode, collectNode } from '../dom/recycler'; +import { unmountComponent } from './component'; + + +/** Diff recursion count, used to track the end of the diff cycle. */ +export const mounts = []; + +/** Diff recursion count, used to track the end of the diff cycle. */ +export let diffLevel = 0; + +let isSvgMode = false; + + +export function flushMounts() { + let c; + while ((c=mounts.pop())) { + if (c.componentDidMount) c.componentDidMount(); + } +} + + +/** Apply differences in a given vnode (and it's deep children) to a real DOM Node. + * @param {Element} [dom=null] A DOM node to mutate into the shape of the `vnode` + * @param {VNode} vnode A VNode (with descendants forming a tree) representing the desired DOM structure + * @returns {Element} dom The created/mutated element + * @private + */ +export function diff(dom, vnode, context, mountAll, parent, componentRoot) { + if (!diffLevel++) isSvgMode = parent instanceof SVGElement; + let ret = idiff(dom, vnode, context, mountAll); + if (parent && ret.parentNode!==parent) parent.appendChild(ret); + if (!--diffLevel && !componentRoot) flushMounts(); + return ret; +} + + +function idiff(dom, vnode, context, mountAll) { + let originalAttributes = vnode && vnode.attributes; + + while (isFunctionalComponent(vnode)) { + vnode = buildFunctionalComponent(vnode, context); + } + + if (vnode==null) vnode = ''; + + if (isString(vnode)) { + if (dom) { + if (dom instanceof Text && dom.parentNode) { + dom.nodeValue = vnode; + return dom; + } + recollectNodeTree(dom); + } + return document.createTextNode(vnode); + } + + if (isFunction(vnode.nodeName)) { + return buildComponentFromVNode(dom, vnode, context, mountAll); + } + + let out = dom, + nodeName = vnode.nodeName, + prevSvgMode = isSvgMode; + + if (!isString(nodeName)) { + nodeName = String(nodeName); + } + + isSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode; + + if (!dom) { + out = createNode(nodeName, isSvgMode); + } + else if (!isNamedNode(dom, nodeName)) { + out = createNode(nodeName, isSvgMode); + // move children into the replacement node + while (dom.firstChild) out.appendChild(dom.firstChild); + // reclaim element nodes + recollectNodeTree(dom); + } + + // fast-path for elements containing a single TextNode: + if (vnode.children && vnode.children.length===1 && typeof vnode.children[0]==='string' && out.childNodes.length===1 && out.firstChild instanceof Text) { + out.firstChild.nodeValue = vnode.children[0]; + } + else if (vnode.children || out.firstChild) { + innerDiffNode(out, vnode.children, context, mountAll); + } + + let props = out[ATTR_KEY]; + if (!props) { + out[ATTR_KEY] = props = {}; + for (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value; + } + + diffAttributes(out, vnode.attributes, props); + + if (originalAttributes && typeof originalAttributes.ref==='function') { + (props.ref = originalAttributes.ref)(out); + } + + isSvgMode = prevSvgMode; + + return out; +} + + +/** Apply child and attribute changes between a VNode and a DOM Node to the DOM. */ +function innerDiffNode(dom, vchildren, context, mountAll) { + let originalChildren = dom.childNodes, + children = [], + keyed = {}, + keyedLen = 0, + min = 0, + len = originalChildren.length, + childrenLen = 0, + vlen = vchildren && vchildren.length, + j, c, vchild, child; + + if (len) { + for (let i=0; i<len; i++) { + let child = originalChildren[i], + key = vlen ? ((c = child._component) ? c.__key : (c = child[ATTR_KEY]) ? c.key : null) : null; + if (key || key===0) { + keyedLen++; + keyed[key] = child; + } + else { + children[childrenLen++] = child; + } + } + } + + if (vlen) { + for (let i=0; i<vlen; i++) { + vchild = vchildren[i]; + child = null; + + // if (isFunctionalComponent(vchild)) { + // vchild = buildFunctionalComponent(vchild); + // } + + // attempt to find a node based on key matching + let key = vchild.key; + if (key!=null) { + if (keyedLen && key in keyed) { + child = keyed[key]; + keyed[key] = undefined; + keyedLen--; + } + } + // attempt to pluck a node of the same type from the existing children + else if (!child && min<childrenLen) { + for (j=min; j<childrenLen; j++) { + c = children[j]; + if (c && isSameNodeType(c, vchild)) { + child = c; + children[j] = undefined; + if (j===childrenLen-1) childrenLen--; + if (j===min) min++; + break; + } + } + if (!child && min<childrenLen && isFunction(vchild.nodeName) && mountAll) { + child = children[min]; + children[min++] = undefined; + } + } + + // morph the matched/found/created DOM child to match vchild (deep) + child = idiff(child, vchild, context, mountAll); + + if (child && child!==dom && child!==originalChildren[i]) { + dom.insertBefore(child, originalChildren[i] || null); + } + } + } + + + if (keyedLen) { + for (let i in keyed) if (keyed[i]) recollectNodeTree(keyed[i]); + } + + // remove orphaned children + if (min<childrenLen) { + removeOrphanedChildren(children); + } +} + + +/** Reclaim children that were unreferenced in the desired VTree */ +export function removeOrphanedChildren(children, unmountOnly) { + for (let i=children.length; i--; ) { + if (children[i]) { + recollectNodeTree(children[i], unmountOnly); + } + } +} + + +/** Reclaim an entire tree of nodes, starting at the root. */ +export function recollectNodeTree(node, unmountOnly) { + // @TODO: Need to make a call on whether Preact should remove nodes not created by itself. + // Currently it *does* remove them. Discussion: https://github.com/developit/preact/issues/39 + //if (!node[ATTR_KEY]) return; + + let component = node._component; + if (component) { + unmountComponent(component, !unmountOnly); + } + else { + if (node[ATTR_KEY] && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null); + + if (!unmountOnly) { + collectNode(node); + } + + if (node.childNodes && node.childNodes.length) { + removeOrphanedChildren(node.childNodes, unmountOnly); + } + } +} + + +/** Apply differences in attributes from a VNode to the given DOM Node. */ +function diffAttributes(dom, attrs, old) { + for (let name in old) { + if (!(attrs && name in attrs) && old[name]!=null) { + setAccessor(dom, name, null, old[name], isSvgMode); + } + } + + // new & updated + if (attrs) { + for (let name in attrs) { + if (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name])) { + setAccessor(dom, name, attrs[name], old[name], isSvgMode); + } + } + } +} diff --git a/thirdparty/preact/src/vdom/functional-component.js b/thirdparty/preact/src/vdom/functional-component.js new file mode 100644 index 000000000..04bc5a464 --- /dev/null +++ b/thirdparty/preact/src/vdom/functional-component.js @@ -0,0 +1,25 @@ +import { EMPTY } from '../constants'; +import { getNodeProps } from './index'; +import { isFunction } from '../util'; + + +/** Check if a VNode is a reference to a stateless functional component. + * A function component is represented as a VNode whose `nodeName` property is a reference to a function. + * If that function is not a Component (ie, has no `.render()` method on a prototype), it is considered a stateless functional component. + * @param {VNode} vnode A VNode + * @private + */ +export function isFunctionalComponent(vnode) { + let nodeName = vnode && vnode.nodeName; + return nodeName && isFunction(nodeName) && !(nodeName.prototype && nodeName.prototype.render); +} + + + +/** Construct a resultant VNode from a VNode referencing a stateless functional component. + * @param {VNode} vnode A VNode with a `nodeName` property that is a reference to a function. + * @private + */ +export function buildFunctionalComponent(vnode, context) { + return vnode.nodeName(getNodeProps(vnode), context || EMPTY); +} diff --git a/thirdparty/preact/src/vdom/index.js b/thirdparty/preact/src/vdom/index.js new file mode 100644 index 000000000..50d4ca2b9 --- /dev/null +++ b/thirdparty/preact/src/vdom/index.js @@ -0,0 +1,50 @@ +import { clone, isString, isFunction, toLowerCase } from '../util'; +import { isFunctionalComponent } from './functional-component'; + + +/** Check if two nodes are equivalent. + * @param {Element} node + * @param {VNode} vnode + * @private + */ +export function isSameNodeType(node, vnode) { + if (isString(vnode)) { + return node instanceof Text; + } + if (isString(vnode.nodeName)) { + return isNamedNode(node, vnode.nodeName); + } + if (isFunction(vnode.nodeName)) { + return node._componentConstructor===vnode.nodeName || isFunctionalComponent(vnode); + } +} + + +export function isNamedNode(node, nodeName) { + return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName); +} + + +/** + * Reconstruct Component-style `props` from a VNode. + * Ensures default/fallback values from `defaultProps`: + * Own-properties of `defaultProps` not present in `vnode.attributes` are added. + * @param {VNode} vnode + * @returns {Object} props + */ +export function getNodeProps(vnode) { + let defaultProps = vnode.nodeName.defaultProps, + props = clone(vnode.attributes); + + if (defaultProps) { + for (let i in defaultProps) { + if (props[i]===undefined) { + props[i] = defaultProps[i]; + } + } + } + + if (vnode.children) props.children = vnode.children; + + return props; +} diff --git a/thirdparty/preact/src/vnode.js b/thirdparty/preact/src/vnode.js new file mode 100644 index 000000000..1c3f10e3d --- /dev/null +++ b/thirdparty/preact/src/vnode.js @@ -0,0 +1,14 @@ +/** Virtual DOM Node */ +export function VNode(nodeName, attributes, children) { + /** @type {string|function} */ + this.nodeName = nodeName; + + /** @type {object<string>|undefined} */ + this.attributes = attributes; + + /** @type {array<VNode>|undefined} */ + this.children = children; + + /** Reference to the given key. */ + this.key = attributes && attributes.key; +} diff --git a/thirdparty/preact/test/browser/components.js b/thirdparty/preact/test/browser/components.js new file mode 100644 index 000000000..b4649a719 --- /dev/null +++ b/thirdparty/preact/test/browser/components.js @@ -0,0 +1,713 @@ +import { h, render, rerender, Component } from '../../src/preact'; +/** @jsx h */ + +let spyAll = obj => Object.keys(obj).forEach( key => sinon.spy(obj,key) ); + +function getAttributes(node) { + let attrs = {}; + if (node.attributes) { + for (let i=node.attributes.length; i--; ) { + attrs[node.attributes[i].name] = node.attributes[i].value; + } + } + return attrs; +} + +// hacky normalization of attribute order across browsers. +function sortAttributes(html) { + return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => { + let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 ); + if (~after.indexOf('/')) after = '></'+pre+'>'; + return '<' + pre + list.join('') + after; + }); +} + +const Empty = () => null; + +describe('Components', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + let c = scratch.firstElementChild; + if (c) render(<Empty />, scratch, c); + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should render components', () => { + class C1 extends Component { + render() { + return <div>C1</div>; + } + } + sinon.spy(C1.prototype, 'render'); + render(<C1 />, scratch); + + expect(C1.prototype.render) + .to.have.been.calledOnce + .and.to.have.been.calledWithMatch({}, {}) + .and.to.have.returned(sinon.match({ nodeName:'div' })); + + expect(scratch.innerHTML).to.equal('<div>C1</div>'); + }); + + + it('should render functional components', () => { + const PROPS = { foo:'bar', onBaz:()=>{} }; + + const C3 = sinon.spy( props => <div {...props} /> ); + + render(<C3 {...PROPS} />, scratch); + + expect(C3) + .to.have.been.calledOnce + .and.to.have.been.calledWith(PROPS) + .and.to.have.returned(sinon.match({ + nodeName: 'div', + attributes: PROPS + })); + + expect(scratch.innerHTML).to.equal('<div foo="bar"></div>'); + }); + + + it('should render components with props', () => { + const PROPS = { foo:'bar', onBaz:()=>{} }; + let constructorProps; + + class C2 extends Component { + constructor(props) { + super(props); + constructorProps = props; + } + render(props) { + return <div {...props} />; + } + } + sinon.spy(C2.prototype, 'render'); + + render(<C2 {...PROPS} />, scratch); + + expect(constructorProps).to.deep.equal(PROPS); + + expect(C2.prototype.render) + .to.have.been.calledOnce + .and.to.have.been.calledWithMatch(PROPS, {}) + .and.to.have.returned(sinon.match({ + nodeName: 'div', + attributes: PROPS + })); + + expect(scratch.innerHTML).to.equal('<div foo="bar"></div>'); + }); + + + // Test for Issue #73 + it('should remove orphaned elements replaced by Components', () => { + class Comp extends Component { + render() { + return <span>span in a component</span>; + } + } + + let root; + function test(content) { + root = render(content, scratch, root); + } + + test(<Comp />); + test(<div>just a div</div>); + test(<Comp />); + + expect(scratch.innerHTML).to.equal('<span>span in a component</span>'); + }); + + + // Test for Issue #176 + it('should remove children when root changes to text node', () => { + let comp; + + class Comp extends Component { + render(_, { alt }) { + return alt ? 'asdf' : <div>test</div>; + } + } + + render(<Comp ref={c=>comp=c} />, scratch); + + comp.setState({ alt:true }); + comp.forceUpdate(); + expect(scratch.innerHTML, 'switching to textnode').to.equal('asdf'); + + comp.setState({ alt:false }); + comp.forceUpdate(); + expect(scratch.innerHTML, 'switching to element').to.equal('<div>test</div>'); + + comp.setState({ alt:true }); + comp.forceUpdate(); + expect(scratch.innerHTML, 'switching to textnode 2').to.equal('asdf'); + }); + + + describe('props.children', () => { + it('should support passing children as a prop', () => { + const Foo = props => <div {...props} />; + + render(<Foo a="b" children={[ + <span class="bar">bar</span>, + '123', + 456 + ]} />, scratch); + + expect(scratch.innerHTML).to.equal('<div a="b"><span class="bar">bar</span>123456</div>'); + }); + + it('should be ignored when explicit children exist', () => { + const Foo = props => <div {...props}>a</div>; + + render(<Foo children={'b'} />, scratch); + + expect(scratch.innerHTML).to.equal('<div>a</div>'); + }); + }); + + + describe('High-Order Components', () => { + it('should render nested functional components', () => { + const PROPS = { foo:'bar', onBaz:()=>{} }; + + const Outer = sinon.spy( + props => <Inner {...props} /> + ); + + const Inner = sinon.spy( + props => <div {...props}>inner</div> + ); + + render(<Outer {...PROPS} />, scratch); + + expect(Outer) + .to.have.been.calledOnce + .and.to.have.been.calledWith(PROPS) + .and.to.have.returned(sinon.match({ + nodeName: Inner, + attributes: PROPS + })); + + expect(Inner) + .to.have.been.calledOnce + .and.to.have.been.calledWith(PROPS) + .and.to.have.returned(sinon.match({ + nodeName: 'div', + attributes: PROPS, + children: ['inner'] + })); + + expect(scratch.innerHTML).to.equal('<div foo="bar">inner</div>'); + }); + + it('should re-render nested functional components', () => { + let doRender = null; + class Outer extends Component { + componentDidMount() { + let i = 1; + doRender = () => this.setState({ i: ++i }); + } + componentWillUnmount() {} + render(props, { i }) { + return <Inner i={i} {...props} />; + } + } + sinon.spy(Outer.prototype, 'render'); + sinon.spy(Outer.prototype, 'componentWillUnmount'); + + let j = 0; + const Inner = sinon.spy( + props => <div j={ ++j } {...props}>inner</div> + ); + + render(<Outer foo="bar" />, scratch); + + // update & flush + doRender(); + rerender(); + + expect(Outer.prototype.componentWillUnmount) + .not.to.have.been.called; + + expect(Inner).to.have.been.calledTwice; + + expect(Inner.secondCall) + .to.have.been.calledWith({ foo:'bar', i:2 }) + .and.to.have.returned(sinon.match({ + attributes: { + j: 2, + i: 2, + foo: 'bar' + } + })); + + expect(getAttributes(scratch.firstElementChild)).to.eql({ + j: '2', + i: '2', + foo: 'bar' + }); + + // update & flush + doRender(); + rerender(); + + expect(Inner).to.have.been.calledThrice; + + expect(Inner.thirdCall) + .to.have.been.calledWith({ foo:'bar', i:3 }) + .and.to.have.returned(sinon.match({ + attributes: { + j: 3, + i: 3, + foo: 'bar' + } + })); + + expect(getAttributes(scratch.firstElementChild)).to.eql({ + j: '3', + i: '3', + foo: 'bar' + }); + }); + + it('should re-render nested components', () => { + let doRender = null, + alt = false; + + class Outer extends Component { + componentDidMount() { + let i = 1; + doRender = () => this.setState({ i: ++i }); + } + componentWillUnmount() {} + render(props, { i }) { + if (alt) return <div is-alt />; + return <Inner i={i} {...props} />; + } + } + sinon.spy(Outer.prototype, 'render'); + sinon.spy(Outer.prototype, 'componentDidMount'); + sinon.spy(Outer.prototype, 'componentWillUnmount'); + + let j = 0; + class Inner extends Component { + constructor(...args) { + super(); + this._constructor(...args); + } + _constructor() {} + componentWillMount() {} + componentDidMount() {} + componentWillUnmount() {} + componentDidUnmount() {} + render(props) { + return <div j={ ++j } {...props}>inner</div>; + } + } + sinon.spy(Inner.prototype, '_constructor'); + sinon.spy(Inner.prototype, 'render'); + sinon.spy(Inner.prototype, 'componentWillMount'); + sinon.spy(Inner.prototype, 'componentDidMount'); + sinon.spy(Inner.prototype, 'componentDidUnmount'); + sinon.spy(Inner.prototype, 'componentWillUnmount'); + + render(<Outer foo="bar" />, scratch); + + expect(Outer.prototype.componentDidMount).to.have.been.calledOnce; + + // update & flush + doRender(); + rerender(); + + expect(Outer.prototype.componentWillUnmount).not.to.have.been.called; + + expect(Inner.prototype._constructor).to.have.been.calledOnce; + expect(Inner.prototype.componentWillUnmount).not.to.have.been.called; + expect(Inner.prototype.componentDidUnmount).not.to.have.been.called; + expect(Inner.prototype.componentWillMount).to.have.been.calledOnce; + expect(Inner.prototype.componentDidMount).to.have.been.calledOnce; + expect(Inner.prototype.render).to.have.been.calledTwice; + + expect(Inner.prototype.render.secondCall) + .to.have.been.calledWith({ foo:'bar', i:2 }) + .and.to.have.returned(sinon.match({ + attributes: { + j: 2, + i: 2, + foo: 'bar' + } + })); + + expect(getAttributes(scratch.firstElementChild)).to.eql({ + j: '2', + i: '2', + foo: 'bar' + }); + + expect(sortAttributes(scratch.innerHTML)).to.equal(sortAttributes('<div foo="bar" j="2" i="2">inner</div>')); + + // update & flush + doRender(); + rerender(); + + expect(Inner.prototype.componentWillUnmount).not.to.have.been.called; + expect(Inner.prototype.componentDidUnmount).not.to.have.been.called; + expect(Inner.prototype.componentWillMount).to.have.been.calledOnce; + expect(Inner.prototype.componentDidMount).to.have.been.calledOnce; + expect(Inner.prototype.render).to.have.been.calledThrice; + + expect(Inner.prototype.render.thirdCall) + .to.have.been.calledWith({ foo:'bar', i:3 }) + .and.to.have.returned(sinon.match({ + attributes: { + j: 3, + i: 3, + foo: 'bar' + } + })); + + expect(getAttributes(scratch.firstElementChild)).to.eql({ + j: '3', + i: '3', + foo: 'bar' + }); + + + // update & flush + alt = true; + doRender(); + rerender(); + + expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce; + expect(Inner.prototype.componentDidUnmount).to.have.been.calledOnce; + + expect(scratch.innerHTML).to.equal('<div is-alt="true"></div>'); + + // update & flush + alt = false; + doRender(); + rerender(); + + expect(sortAttributes(scratch.innerHTML)).to.equal(sortAttributes('<div foo="bar" j="4" i="5">inner</div>')); + }); + + it('should resolve intermediary functional component', () => { + let ctx = {}; + class Root extends Component { + getChildContext() { + return { ctx }; + } + render() { + return <Func />; + } + } + const Func = sinon.spy( () => <Inner /> ); + class Inner extends Component { + componentWillMount() {} + componentDidMount() {} + componentWillUnmount() {} + componentDidUnmount() {} + render() { + return <div>inner</div>; + } + } + + spyAll(Inner.prototype); + + let root = render(<Root />, scratch); + + expect(Inner.prototype.componentWillMount).to.have.been.calledOnce; + expect(Inner.prototype.componentDidMount).to.have.been.calledOnce; + expect(Inner.prototype.componentWillMount).to.have.been.calledBefore(Inner.prototype.componentDidMount); + + root = render(<asdf />, scratch, root); + + expect(Inner.prototype.componentWillUnmount).to.have.been.calledOnce; + expect(Inner.prototype.componentDidUnmount).to.have.been.calledOnce; + expect(Inner.prototype.componentWillUnmount).to.have.been.calledBefore(Inner.prototype.componentDidUnmount); + }); + + it('should unmount children of high-order components without unmounting parent', () => { + let outer, inner2, counter=0; + + class Outer extends Component { + constructor(props, context) { + super(props, context); + outer = this; + this.state = { + child: this.props.child + }; + } + componentWillUnmount(){} + componentDidUnmount(){} + componentWillMount(){} + componentDidMount(){} + render(_, { child:C }) { + return <C />; + } + } + spyAll(Outer.prototype); + + class Inner extends Component { + componentWillUnmount(){} + componentDidUnmount(){} + componentWillMount(){} + componentDidMount(){} + render() { + return h('element'+(++counter)); + } + } + spyAll(Inner.prototype); + + class Inner2 extends Component { + constructor(props, context) { + super(props, context); + inner2 = this; + } + componentWillUnmount(){} + componentDidUnmount(){} + componentWillMount(){} + componentDidMount(){} + render() { + return h('element'+(++counter)); + } + } + spyAll(Inner2.prototype); + + render(<Outer child={Inner} />, scratch); + + // outer should only have been mounted once + expect(Outer.prototype.componentWillMount, 'outer initial').to.have.been.calledOnce; + expect(Outer.prototype.componentDidMount, 'outer initial').to.have.been.calledOnce; + expect(Outer.prototype.componentWillUnmount, 'outer initial').not.to.have.been.called; + expect(Outer.prototype.componentDidUnmount, 'outer initial').not.to.have.been.called; + + // inner should only have been mounted once + expect(Inner.prototype.componentWillMount, 'inner initial').to.have.been.calledOnce; + expect(Inner.prototype.componentDidMount, 'inner initial').to.have.been.calledOnce; + expect(Inner.prototype.componentWillUnmount, 'inner initial').not.to.have.been.called; + expect(Inner.prototype.componentDidUnmount, 'inner initial').not.to.have.been.called; + + outer.setState({ child:Inner2 }); + outer.forceUpdate(); + + expect(Inner2.prototype.render).to.have.been.calledOnce; + + // outer should still only have been mounted once + expect(Outer.prototype.componentWillMount, 'outer swap').to.have.been.calledOnce; + expect(Outer.prototype.componentDidMount, 'outer swap').to.have.been.calledOnce; + expect(Outer.prototype.componentWillUnmount, 'outer swap').not.to.have.been.called; + expect(Outer.prototype.componentDidUnmount, 'outer swap').not.to.have.been.called; + + // inner should only have been mounted once + expect(Inner2.prototype.componentWillMount, 'inner2 swap').to.have.been.calledOnce; + expect(Inner2.prototype.componentDidMount, 'inner2 swap').to.have.been.calledOnce; + expect(Inner2.prototype.componentWillUnmount, 'inner2 swap').not.to.have.been.called; + expect(Inner2.prototype.componentDidUnmount, 'inner2 swap').not.to.have.been.called; + + inner2.forceUpdate(); + + expect(Inner2.prototype.render, 'inner2 update').to.have.been.calledTwice; + expect(Inner2.prototype.componentWillMount, 'inner2 update').to.have.been.calledOnce; + expect(Inner2.prototype.componentDidMount, 'inner2 update').to.have.been.calledOnce; + expect(Inner2.prototype.componentWillUnmount, 'inner2 update').not.to.have.been.called; + expect(Inner2.prototype.componentDidUnmount, 'inner2 update').not.to.have.been.called; + }); + + it('should remount when swapping between HOC child types', () => { + class Outer extends Component { + render({ child: Child }) { + return <Child />; + } + } + + class Inner extends Component { + componentWillMount() {} + componentWillUnmount() {} + render() { + return <div class="inner">foo</div>; + } + } + spyAll(Inner.prototype); + + const InnerFunc = () => ( + <div class="inner-func">bar</div> + ); + + let root = render(<Outer child={Inner} />, scratch, root); + + expect(Inner.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce; + expect(Inner.prototype.componentWillUnmount, 'initial mount').not.to.have.been.called; + + Inner.prototype.componentWillMount.reset(); + root = render(<Outer child={InnerFunc} />, scratch, root); + + expect(Inner.prototype.componentWillMount, 'unmount').not.to.have.been.called; + expect(Inner.prototype.componentWillUnmount, 'unmount').to.have.been.calledOnce; + + Inner.prototype.componentWillUnmount.reset(); + root = render(<Outer child={Inner} />, scratch, root); + + expect(Inner.prototype.componentWillMount, 'remount').to.have.been.calledOnce; + expect(Inner.prototype.componentWillUnmount, 'remount').not.to.have.been.called; + }); + }); + + describe('Component Nesting', () => { + let useIntermediary = false; + + let createComponent = (Intermediary) => { + class C extends Component { + componentWillMount() {} + componentDidUnmount() {} + render({ children }) { + if (!useIntermediary) return children[0]; + let I = useIntermediary===true ? Intermediary : useIntermediary; + return <I>{children}</I>; + } + } + spyAll(C.prototype); + return C; + }; + + let createFunction = () => sinon.spy( ({ children }) => children[0] ); + + let root; + let rndr = n => root = render(n, scratch, root); + + let F1 = createFunction(); + let F2 = createFunction(); + let F3 = createFunction(); + + let C1 = createComponent(F1); + let C2 = createComponent(F2); + let C3 = createComponent(F3); + + let reset = () => [C1, C2, C3].reduce( + (acc, c) => acc.concat( Object.keys(c.prototype).map(key => c.prototype[key]) ), + [F1, F2, F3] + ).forEach( c => c.reset && c.reset() ); + + + it('should handle lifecycle for no intermediary in component tree', () => { + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce; + expect(C2.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'initial mount').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2>Some Text</C2></C1>); + + expect(C1.prototype.componentWillMount, 'unmount innermost, C1').not.to.have.been.called; + expect(C2.prototype.componentWillMount, 'unmount innermost, C2').not.to.have.been.called; + expect(C3.prototype.componentDidUnmount, 'unmount innermost, C3').to.have.been.calledOnce; + + reset(); + rndr(<C1><C3>Some Text</C3></C1>); + + expect(C1.prototype.componentWillMount, 'swap innermost').not.to.have.been.called; + expect(C2.prototype.componentDidUnmount, 'swap innermost').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'swap innermost').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentDidUnmount, 'inject between, C1').not.to.have.been.called; + expect(C1.prototype.componentWillMount, 'inject between, C1').not.to.have.been.called; + expect(C2.prototype.componentWillMount, 'inject between, C2').to.have.been.calledOnce; + expect(C3.prototype.componentDidUnmount, 'inject between, C3').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'inject between, C3').to.have.been.calledOnce; + }); + + + it('should handle lifecycle for nested intermediary functional components', () => { + useIntermediary = true; + + rndr(<div />); + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentWillMount, 'initial mount w/ intermediary fn, C1').to.have.been.calledOnce; + expect(C2.prototype.componentWillMount, 'initial mount w/ intermediary fn, C2').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'initial mount w/ intermediary fn, C3').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2>Some Text</C2></C1>); + + expect(C1.prototype.componentWillMount, 'unmount innermost w/ intermediary fn, C1').not.to.have.been.called; + expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary fn, C2').not.to.have.been.called; + expect(C3.prototype.componentDidUnmount, 'unmount innermost w/ intermediary fn, C3').to.have.been.calledOnce; + + reset(); + rndr(<C1><C3>Some Text</C3></C1>); + + expect(C1.prototype.componentWillMount, 'swap innermost w/ intermediary fn').not.to.have.been.called; + expect(C2.prototype.componentDidUnmount, 'swap innermost w/ intermediary fn').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'swap innermost w/ intermediary fn').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentDidUnmount, 'inject between, C1 w/ intermediary fn').not.to.have.been.called; + expect(C1.prototype.componentWillMount, 'inject between, C1 w/ intermediary fn').not.to.have.been.called; + expect(C2.prototype.componentWillMount, 'inject between, C2 w/ intermediary fn').to.have.been.calledOnce; + expect(C3.prototype.componentDidUnmount, 'inject between, C3 w/ intermediary fn').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'inject between, C3 w/ intermediary fn').to.have.been.calledOnce; + }); + + + it('should handle lifecycle for nested intermediary elements', () => { + useIntermediary = 'div'; + + rndr(<div />); + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentWillMount, 'initial mount w/ intermediary div, C1').to.have.been.calledOnce; + expect(C2.prototype.componentWillMount, 'initial mount w/ intermediary div, C2').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'initial mount w/ intermediary div, C3').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2>Some Text</C2></C1>); + + expect(C1.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C1').not.to.have.been.called; + expect(C2.prototype.componentDidUnmount, 'unmount innermost w/ intermediary div, C2 ummount').not.to.have.been.called; + // @TODO this was just incorrect? + // expect(C2.prototype.componentWillMount, 'unmount innermost w/ intermediary div, C2').not.to.have.been.called; + expect(C3.prototype.componentDidUnmount, 'unmount innermost w/ intermediary div, C3').to.have.been.calledOnce; + + reset(); + rndr(<C1><C3>Some Text</C3></C1>); + + expect(C1.prototype.componentWillMount, 'swap innermost w/ intermediary div').not.to.have.been.called; + expect(C2.prototype.componentDidUnmount, 'swap innermost w/ intermediary div').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'swap innermost w/ intermediary div').to.have.been.calledOnce; + + reset(); + rndr(<C1><C2><C3>Some Text</C3></C2></C1>); + + expect(C1.prototype.componentDidUnmount, 'inject between, C1 w/ intermediary div').not.to.have.been.called; + expect(C1.prototype.componentWillMount, 'inject between, C1 w/ intermediary div').not.to.have.been.called; + expect(C2.prototype.componentWillMount, 'inject between, C2 w/ intermediary div').to.have.been.calledOnce; + expect(C3.prototype.componentDidUnmount, 'inject between, C3 w/ intermediary div').to.have.been.calledOnce; + expect(C3.prototype.componentWillMount, 'inject between, C3 w/ intermediary div').to.have.been.calledOnce; + }); + }); +}); diff --git a/thirdparty/preact/test/browser/context.js b/thirdparty/preact/test/browser/context.js new file mode 100644 index 000000000..e62a948a4 --- /dev/null +++ b/thirdparty/preact/test/browser/context.js @@ -0,0 +1,170 @@ +import { h, render, Component } from '../../src/preact'; +/** @jsx h */ + +describe('context', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should pass context to grandchildren', () => { + const CONTEXT = { a:'a' }; + const PROPS = { b:'b' }; + // let inner; + + class Outer extends Component { + getChildContext() { + return CONTEXT; + } + render(props) { + return <div><Inner {...props} /></div>; + } + } + sinon.spy(Outer.prototype, 'getChildContext'); + + class Inner extends Component { + // constructor() { + // super(); + // inner = this; + // } + shouldComponentUpdate() { return true; } + componentWillReceiveProps() {} + componentWillUpdate() {} + componentDidUpdate() {} + render(props, state, context) { + return <div>{ context && context.a }</div>; + } + } + sinon.spy(Inner.prototype, 'shouldComponentUpdate'); + sinon.spy(Inner.prototype, 'componentWillReceiveProps'); + sinon.spy(Inner.prototype, 'componentWillUpdate'); + sinon.spy(Inner.prototype, 'componentDidUpdate'); + sinon.spy(Inner.prototype, 'render'); + + render(<Outer />, scratch, scratch.lastChild); + + expect(Outer.prototype.getChildContext).to.have.been.calledOnce; + + // initial render does not invoke anything but render(): + expect(Inner.prototype.render).to.have.been.calledWith({}, {}, CONTEXT); + + CONTEXT.foo = 'bar'; + render(<Outer {...PROPS} />, scratch, scratch.lastChild); + + expect(Outer.prototype.getChildContext).to.have.been.calledTwice; + + expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(PROPS, {}, CONTEXT); + expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(PROPS, CONTEXT); + expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(PROPS, {}); + expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({}, {}); + expect(Inner.prototype.render).to.have.been.calledWith(PROPS, {}, CONTEXT); + + + /* Future: + * Newly created context objects are *not* currently cloned. + * This test checks that they *are* cloned. + */ + // Inner.prototype.render.reset(); + // CONTEXT.foo = 'baz'; + // inner.forceUpdate(); + // expect(Inner.prototype.render).to.have.been.calledWith(PROPS, {}, { a:'a', foo:'bar' }); + }); + + it('should pass context to direct children', () => { + const CONTEXT = { a:'a' }; + const PROPS = { b:'b' }; + + class Outer extends Component { + getChildContext() { + return CONTEXT; + } + render(props) { + return <Inner {...props} />; + } + } + sinon.spy(Outer.prototype, 'getChildContext'); + + class Inner extends Component { + shouldComponentUpdate() { return true; } + componentWillReceiveProps() {} + componentWillUpdate() {} + componentDidUpdate() {} + render(props, state, context) { + return <div>{ context && context.a }</div>; + } + } + sinon.spy(Inner.prototype, 'shouldComponentUpdate'); + sinon.spy(Inner.prototype, 'componentWillReceiveProps'); + sinon.spy(Inner.prototype, 'componentWillUpdate'); + sinon.spy(Inner.prototype, 'componentDidUpdate'); + sinon.spy(Inner.prototype, 'render'); + + render(<Outer />, scratch, scratch.lastChild); + + expect(Outer.prototype.getChildContext).to.have.been.calledOnce; + + // initial render does not invoke anything but render(): + expect(Inner.prototype.render).to.have.been.calledWith({}, {}, CONTEXT); + + CONTEXT.foo = 'bar'; + render(<Outer {...PROPS} />, scratch, scratch.lastChild); + + expect(Outer.prototype.getChildContext).to.have.been.calledTwice; + + expect(Inner.prototype.shouldComponentUpdate).to.have.been.calledOnce.and.calledWith(PROPS, {}, CONTEXT); + expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledWith(PROPS, CONTEXT); + expect(Inner.prototype.componentWillUpdate).to.have.been.calledWith(PROPS, {}); + expect(Inner.prototype.componentDidUpdate).to.have.been.calledWith({}, {}); + expect(Inner.prototype.render).to.have.been.calledWith(PROPS, {}, CONTEXT); + + // make sure render() could make use of context.a + expect(Inner.prototype.render).to.have.returned(sinon.match({ children:['a'] })); + }); + + it('should preserve existing context properties when creating child contexts', () => { + let outerContext = { outer:true }, + innerContext = { inner:true }; + class Outer extends Component { + getChildContext() { + return { outerContext }; + } + render() { + return <div><Inner /></div>; + } + } + + class Inner extends Component { + getChildContext() { + return { innerContext }; + } + render() { + return <InnerMost />; + } + } + + class InnerMost extends Component { + render() { + return <strong>test</strong>; + } + } + + sinon.spy(Inner.prototype, 'render'); + sinon.spy(InnerMost.prototype, 'render'); + + render(<Outer />, scratch); + + expect(Inner.prototype.render).to.have.been.calledWith({}, {}, { outerContext }); + expect(InnerMost.prototype.render).to.have.been.calledWith({}, {}, { outerContext, innerContext }); + }); +}); diff --git a/thirdparty/preact/test/browser/keys.js b/thirdparty/preact/test/browser/keys.js new file mode 100644 index 000000000..e0a6b9ae8 --- /dev/null +++ b/thirdparty/preact/test/browser/keys.js @@ -0,0 +1,85 @@ +import { h, Component, render } from '../../src/preact'; +/** @jsx h */ + +describe('keys', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + // See developit/preact-compat#21 + it('should remove orphaned keyed nodes', () => { + let root = render(( + <div> + <div>1</div> + <li key="a">a</li> + </div> + ), scratch); + + root = render(( + <div> + <div>2</div> + <li key="b">b</li> + </div> + ), scratch, root); + + expect(scratch.innerHTML).to.equal('<div><div>2</div><li>b</li></div>'); + }); + + it('should set VNode#key property', () => { + expect(<div />).to.have.property('key').that.is.empty; + expect(<div a="a" />).to.have.property('key').that.is.empty; + expect(<div key="1" />).to.have.property('key', '1'); + }); + + it('should remove keyed nodes (#232)', () => { + class App extends Component { + componentDidMount() { + setTimeout(() => this.setState({opened: true,loading: true}), 10); + setTimeout(() => this.setState({opened: true,loading: false}), 20); + } + + render({ opened, loading }) { + return ( + <BusyIndicator id="app" busy={loading}> + <div>This div needs to be here for this to break</div> + { opened && !loading && <div>{[]}</div> } + </BusyIndicator> + ); + } + } + + class BusyIndicator extends Component { + render({ children, busy }) { + return <div class={busy ? "busy" : ""}> + { children && children.length ? children : <div class="busy-placeholder"></div> } + <div class="indicator"> + <div>indicator</div> + <div>indicator</div> + <div>indicator</div> + </div> + </div>; + } + } + + let root; + + root = render(<App />, scratch, root); + root = render(<App opened loading />, scratch, root); + root = render(<App opened />, scratch, root); + + let html = String(root.innerHTML).replace(/ class=""/g, ''); + expect(html).to.equal('<div>This div needs to be here for this to break</div><div></div><div class="indicator"><div>indicator</div><div>indicator</div><div>indicator</div></div>'); + }); +}); diff --git a/thirdparty/preact/test/browser/lifecycle.js b/thirdparty/preact/test/browser/lifecycle.js new file mode 100644 index 000000000..d6204ca8f --- /dev/null +++ b/thirdparty/preact/test/browser/lifecycle.js @@ -0,0 +1,493 @@ +import { h, render, rerender, Component } from '../../src/preact'; +/** @jsx h */ + +let spyAll = obj => Object.keys(obj).forEach( key => sinon.spy(obj,key) ); + +describe('Lifecycle methods', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + + describe('#componentWillUpdate', () => { + it('should NOT be called on initial render', () => { + class ReceivePropsComponent extends Component { + componentWillUpdate() {} + render() { + return <div />; + } + } + sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate'); + render(<ReceivePropsComponent />, scratch); + expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called; + }); + + it('should be called when rerender with new props from parent', () => { + let doRender; + class Outer extends Component { + constructor(p, c) { + super(p, c); + this.state = { i: 0 }; + } + componentDidMount() { + doRender = () => this.setState({ i: this.state.i + 1 }); + } + render(props, { i }) { + return <Inner i={i} {...props} />; + } + } + class Inner extends Component { + componentWillUpdate(nextProps, nextState) { + expect(nextProps).to.be.deep.equal({i: 1}); + expect(nextState).to.be.deep.equal({}); + } + render() { + return <div />; + } + } + sinon.spy(Inner.prototype, 'componentWillUpdate'); + sinon.spy(Outer.prototype, 'componentDidMount'); + + // Initial render + render(<Outer />, scratch); + expect(Inner.prototype.componentWillUpdate).not.to.have.been.called; + + // Rerender inner with new props + doRender(); + rerender(); + expect(Inner.prototype.componentWillUpdate).to.have.been.called; + }); + + it('should be called on new state', () => { + let doRender; + class ReceivePropsComponent extends Component { + componentWillUpdate() {} + componentDidMount() { + doRender = () => this.setState({ i: this.state.i + 1 }); + } + render() { + return <div />; + } + } + sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate'); + render(<ReceivePropsComponent />, scratch); + expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have.been.called; + + doRender(); + rerender(); + expect(ReceivePropsComponent.prototype.componentWillUpdate).to.have.been.called; + }); + }); + + describe('#componentWillReceiveProps', () => { + it('should NOT be called on initial render', () => { + class ReceivePropsComponent extends Component { + componentWillReceiveProps() {} + render() { + return <div />; + } + } + sinon.spy(ReceivePropsComponent.prototype, 'componentWillReceiveProps'); + render(<ReceivePropsComponent />, scratch); + expect(ReceivePropsComponent.prototype.componentWillReceiveProps).not.to.have.been.called; + }); + + it('should be called when rerender with new props from parent', () => { + let doRender; + class Outer extends Component { + constructor(p, c) { + super(p, c); + this.state = { i: 0 }; + } + componentDidMount() { + doRender = () => this.setState({ i: this.state.i + 1 }); + } + render(props, { i }) { + return <Inner i={i} {...props} />; + } + } + class Inner extends Component { + componentWillMount() { + expect(this.props.i).to.be.equal(0); + } + componentWillReceiveProps(nextProps) { + expect(nextProps.i).to.be.equal(1); + } + render() { + return <div />; + } + } + sinon.spy(Inner.prototype, 'componentWillReceiveProps'); + sinon.spy(Outer.prototype, 'componentDidMount'); + + // Initial render + render(<Outer />, scratch); + expect(Inner.prototype.componentWillReceiveProps).not.to.have.been.called; + + // Rerender inner with new props + doRender(); + rerender(); + expect(Inner.prototype.componentWillReceiveProps).to.have.been.called; + }); + + it('should be called in right execution order', () => { + let doRender; + class Outer extends Component { + constructor(p, c) { + super(p, c); + this.state = { i: 0 }; + } + componentDidMount() { + doRender = () => this.setState({ i: this.state.i + 1 }); + } + render(props, { i }) { + return <Inner i={i} {...props} />; + } + } + class Inner extends Component { + componentDidUpdate() { + expect(Inner.prototype.componentWillReceiveProps).to.have.been.called; + expect(Inner.prototype.componentWillUpdate).to.have.been.called; + } + componentWillReceiveProps() { + expect(Inner.prototype.componentWillUpdate).not.to.have.been.called; + expect(Inner.prototype.componentDidUpdate).not.to.have.been.called; + } + componentWillUpdate() { + expect(Inner.prototype.componentWillReceiveProps).to.have.been.called; + expect(Inner.prototype.componentDidUpdate).not.to.have.been.called; + } + render() { + return <div />; + } + } + sinon.spy(Inner.prototype, 'componentWillReceiveProps'); + sinon.spy(Inner.prototype, 'componentDidUpdate'); + sinon.spy(Inner.prototype, 'componentWillUpdate'); + sinon.spy(Outer.prototype, 'componentDidMount'); + + render(<Outer />, scratch); + doRender(); + rerender(); + + expect(Inner.prototype.componentWillReceiveProps).to.have.been.calledBefore(Inner.prototype.componentWillUpdate); + expect(Inner.prototype.componentWillUpdate).to.have.been.calledBefore(Inner.prototype.componentDidUpdate); + }); + }); + + + let _it = it; + describe('#constructor and component(Did|Will)(Mount|Unmount)', () => { + /* global DISABLE_FLAKEY */ + let it = DISABLE_FLAKEY ? xit : _it; + + let setState; + class Outer extends Component { + constructor(p, c) { + super(p, c); + this.state = { show:true }; + setState = s => this.setState(s); + } + render(props, { show }) { + return ( + <div> + { show && ( + <Inner {...props} /> + ) } + </div> + ); + } + } + + class LifecycleTestComponent extends Component { + constructor(p, c) { super(p, c); this._constructor(); } + _constructor() {} + componentWillMount() {} + componentDidMount() {} + componentWillUnmount() {} + componentDidUnmount() {} + render() { return <div />; } + } + + class Inner extends LifecycleTestComponent { + render() { + return ( + <div> + <InnerMost /> + </div> + ); + } + } + + class InnerMost extends LifecycleTestComponent { + render() { return <div />; } + } + + let spies = ['_constructor', 'componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount']; + + let verifyLifycycleMethods = (TestComponent) => { + let proto = TestComponent.prototype; + spies.forEach( s => sinon.spy(proto, s) ); + let reset = () => spies.forEach( s => proto[s].reset() ); + + it('should be invoked for components on initial render', () => { + reset(); + render(<Outer />, scratch); + expect(proto._constructor).to.have.been.called; + expect(proto.componentDidMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + }); + + it('should be invoked for components on unmount', () => { + reset(); + setState({ show:false }); + rerender(); + + expect(proto.componentDidUnmount).to.have.been.called; + expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount); + expect(proto.componentDidUnmount).to.have.been.called; + }); + + it('should be invoked for components on re-render', () => { + reset(); + setState({ show:true }); + rerender(); + + expect(proto._constructor).to.have.been.called; + expect(proto.componentDidMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + }); + }; + + describe('inner components', () => { + verifyLifycycleMethods(Inner); + }); + + describe('innermost components', () => { + verifyLifycycleMethods(InnerMost); + }); + + describe('when shouldComponentUpdate() returns false', () => { + let setState; + + class Outer extends Component { + constructor() { + super(); + this.state = { show:true }; + setState = s => this.setState(s); + } + render(props, { show }) { + return ( + <div> + { show && ( + <div> + <Inner {...props} /> + </div> + ) } + </div> + ); + } + } + + class Inner extends Component { + shouldComponentUpdate(){ return false; } + componentWillMount() {} + componentDidMount() {} + componentWillUnmount() {} + componentDidUnmount() {} + render() { + return <div />; + } + } + + let proto = Inner.prototype; + let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount']; + spies.forEach( s => sinon.spy(proto, s) ); + + let reset = () => spies.forEach( s => proto[s].reset() ); + + beforeEach( () => reset() ); + + it('should be invoke normally on initial mount', () => { + render(<Outer />, scratch); + expect(proto.componentWillMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + }); + + it('should be invoked normally on unmount', () => { + setState({ show:false }); + rerender(); + + expect(proto.componentWillUnmount).to.have.been.called; + expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount); + expect(proto.componentDidUnmount).to.have.been.called; + }); + + it('should still invoke mount for shouldComponentUpdate():false', () => { + setState({ show:true }); + rerender(); + + expect(proto.componentWillMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + }); + + it('should still invoke unmount for shouldComponentUpdate():false', () => { + setState({ show:false }); + rerender(); + + expect(proto.componentWillUnmount).to.have.been.called; + expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount); + expect(proto.componentDidUnmount).to.have.been.called; + }); + }); + }); + + describe('Lifecycle DOM Timing', () => { + it('should be invoked when dom does (DidMount, WillUnmount) or does not (WillMount, DidUnmount) exist', () => { + let setState; + class Outer extends Component { + constructor() { + super(); + this.state = { show:true }; + setState = s => { + this.setState(s); + this.forceUpdate(); + }; + } + componentWillMount() { + expect(document.getElementById('OuterDiv'), 'Outer componentWillMount').to.not.exist; + } + componentDidMount() { + expect(document.getElementById('OuterDiv'), 'Outer componentDidMount').to.exist; + } + componentWillUnmount() { + expect(document.getElementById('OuterDiv'), 'Outer componentWillUnmount').to.exist; + } + componentDidUnmount() { + expect(document.getElementById('OuterDiv'), 'Outer componentDidUnmount').to.not.exist; + } + render(props, { show }) { + return ( + <div id="OuterDiv"> + { show && ( + <div> + <Inner {...props} /> + </div> + ) } + </div> + ); + } + } + + class Inner extends Component { + componentWillMount() { + expect(document.getElementById('InnerDiv'), 'Inner componentWillMount').to.not.exist; + } + componentDidMount() { + expect(document.getElementById('InnerDiv'), 'Inner componentDidMount').to.exist; + } + componentWillUnmount() { + // @TODO Component mounted into elements (non-components) + // are currently unmounted after those elements, so their + // DOM is unmounted prior to the method being called. + //expect(document.getElementById('InnerDiv'), 'Inner componentWillUnmount').to.exist; + } + componentDidUnmount() { + expect(document.getElementById('InnerDiv'), 'Inner componentDidUnmount').to.not.exist; + } + + render() { + return <div id="InnerDiv" />; + } + } + + let proto = Inner.prototype; + let spies = ['componentWillMount', 'componentDidMount', 'componentWillUnmount', 'componentDidUnmount']; + spies.forEach( s => sinon.spy(proto, s) ); + + let reset = () => spies.forEach( s => proto[s].reset() ); + + render(<Outer />, scratch); + expect(proto.componentWillMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + + reset(); + setState({ show:false }); + + expect(proto.componentWillUnmount).to.have.been.called; + expect(proto.componentWillUnmount).to.have.been.calledBefore(proto.componentDidUnmount); + expect(proto.componentDidUnmount).to.have.been.called; + + reset(); + setState({ show:true }); + + expect(proto.componentWillMount).to.have.been.called; + expect(proto.componentWillMount).to.have.been.calledBefore(proto.componentDidMount); + expect(proto.componentDidMount).to.have.been.called; + }); + + it('should remove this.base for HOC', () => { + let createComponent = (name, fn) => { + class C extends Component { + componentWillUnmount() { + expect(this.base, `${name}.componentWillUnmount`).to.exist; + } + componentDidUnmount() { + expect(this.base, `${name}.componentDidUnmount`).not.to.exist; + } + render(props) { return fn(props); } + } + spyAll(C.prototype); + return C; + }; + + class Wrapper extends Component { + render({ children }) { + return <div class="wrapper">{children}</div>; + } + } + + let One = createComponent('One', () => <Wrapper>one</Wrapper> ); + let Two = createComponent('Two', () => <Wrapper>two</Wrapper> ); + let Three = createComponent('Three', () => <Wrapper>three</Wrapper> ); + + let components = [One, Two, Three]; + + let Selector = createComponent('Selector', ({ page }) => { + let Child = components[page]; + return <Child />; + }); + + class App extends Component { + render(_, { page }) { + return <Selector page={page} />; + } + } + + let app; + render(<App ref={ c => app=c } />, scratch); + + for (let i=0; i<20; i++) { + app.setState({ page: i%components.length }); + app.forceUpdate(); + } + }); + }); +}); diff --git a/thirdparty/preact/test/browser/linked-state.js b/thirdparty/preact/test/browser/linked-state.js new file mode 100644 index 000000000..1ca84cdc6 --- /dev/null +++ b/thirdparty/preact/test/browser/linked-state.js @@ -0,0 +1,98 @@ +import { Component } from '../../src/preact'; +import { createLinkedState } from '../../src/linked-state'; + +describe('linked-state', () => { + class TestComponent extends Component { } + let testComponent, linkFunction; + + before( () => { + testComponent = new TestComponent(); + sinon.spy(TestComponent.prototype, 'setState'); + }); + + describe('createLinkedState without eventPath argument', () => { + + before( () => { + linkFunction = createLinkedState(testComponent,'testStateKey'); + expect(linkFunction).to.be.a('function'); + }); + + beforeEach( () => { + TestComponent.prototype['setState'].reset(); + }); + + it('should use value attribute on text input when no eventPath is supplied', () => { + let element = document.createElement('input'); + element.type= 'text'; + element.value = 'newValue'; + + linkFunction({ currentTarget: element }); + + expect(TestComponent.prototype.setState).to.have.been.calledOnce; + expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': 'newValue'}); + + linkFunction.call(element); + + expect(TestComponent.prototype.setState).to.have.been.calledTwice; + expect(TestComponent.prototype.setState.secondCall).to.have.been.calledWith({'testStateKey': 'newValue'}); + }); + + it('should use checked attribute on checkbox input when no eventPath is supplied', () => { + let checkboxElement = document.createElement('input'); + checkboxElement.type= 'checkbox'; + checkboxElement.checked = true; + + linkFunction({ currentTarget: checkboxElement }); + + expect(TestComponent.prototype.setState).to.have.been.calledOnce; + expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': true}); + }); + + it('should use checked attribute on radio input when no eventPath is supplied', () => { + let radioElement = document.createElement('input'); + radioElement.type= 'radio'; + radioElement.checked = true; + + linkFunction({ currentTarget: radioElement }); + + expect(TestComponent.prototype.setState).to.have.been.calledOnce; + expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': true}); + }); + + + it('should set dot notated state key appropriately', () => { + linkFunction = createLinkedState(testComponent,'nested.state.key'); + let element = document.createElement('input'); + element.type= 'text'; + element.value = 'newValue'; + + linkFunction({ currentTarget: element }); + + expect(TestComponent.prototype.setState).to.have.been.calledOnce; + expect(TestComponent.prototype.setState).to.have.been.calledWith({nested: {state: {key: 'newValue'}}}); + }); + + }); + + describe('createLinkedState with eventPath argument', () => { + + before( () => { + linkFunction = createLinkedState(testComponent,'testStateKey', 'nested.path'); + expect(linkFunction).to.be.a('function'); + }); + + beforeEach( () => { + TestComponent.prototype['setState'].reset(); + }); + + it('should give precedence to nested.path on event over nested.path on component', () => { + let event = {nested: {path: 'nestedPathValueFromEvent'}}; + let component = {_component: {nested: {path: 'nestedPathValueFromComponent'}}}; + + linkFunction.call(component, event); + + expect(TestComponent.prototype.setState).to.have.been.calledOnce; + expect(TestComponent.prototype.setState).to.have.been.calledWith({'testStateKey': 'nestedPathValueFromEvent'}); + }); + }); +}); diff --git a/thirdparty/preact/test/browser/performance.js b/thirdparty/preact/test/browser/performance.js new file mode 100644 index 000000000..e1f7d7956 --- /dev/null +++ b/thirdparty/preact/test/browser/performance.js @@ -0,0 +1,245 @@ +/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/ +/*eslint no-console:0*/ +/** @jsx h */ + +let { h, Component, render } = require(NODE_ENV==='production' ? '../../dist/preact.min.js' : '../../src/preact'); + +const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999; + + +let now = typeof performance!=='undefined' && performance.now ? () => performance.now() : () => +new Date(); + +function loop(iter, time) { + let start = now(), + count = 0; + while ( now()-start < time ) { + count++; + iter(); + } + return count; +} + + +function benchmark(iter, callback) { + let a = 0; + function noop() { + try { a++; } finally { a += Math.random(); } + } + + // warm + for (let i=3; i--; ) noop(), iter(); + + let count = 5, + time = 200, + passes = 0, + noops = loop(noop, time), + iterations = 0; + + function next() { + iterations += loop(iter, time); + setTimeout(++passes===count ? done : next, 10); + } + + function done() { + let ticks = Math.round(noops / iterations * count), + hz = iterations / count / time * 1000, + message = `${hz|0}/s (${ticks} ticks)`; + callback({ iterations, noops, count, time, ticks, hz, message }); + } + + next(); +} + + +describe('performance', function() { + let scratch; + + this.timeout(10000); + + before( () => { + if (coverage) { + console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.'); + } + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should rerender without changes fast', done => { + let jsx = ( + <div class="foo bar" data-foo="bar" p={2}> + <header> + <h1 class="asdf">a {'b'} c {0} d</h1> + <nav> + <a href="/foo">Foo</a> + <a href="/bar">Bar</a> + </nav> + </header> + <main> + <form onSubmit={()=>{}}> + <input type="checkbox" checked={true} /> + <input type="checkbox" checked={false} /> + <fieldset> + <label><input type="radio" checked /></label> + <label><input type="radio" /></label> + </fieldset> + <button-bar> + <button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button> + <button style="top:0 ; right: 20">Poor CSS</button> + <button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button> + <button style={{ margin:0, padding:'10px', overflow:'visible' }}>Object CSS</button> + </button-bar> + </form> + </main> + </div> + ); + + let root; + benchmark( () => { + root = render(jsx, scratch, root); + }, ({ ticks, message }) => { + console.log(`PERF: empty diff: ${message}`); + expect(ticks).to.be.below(350 * MULTIPLIER); + done(); + }); + }); + + it('should rerender repeated trees fast', done => { + class Header extends Component { + render() { + return ( + <header> + <h1 class="asdf">a {'b'} c {0} d</h1> + <nav> + <a href="/foo">Foo</a> + <a href="/bar">Bar</a> + </nav> + </header> + ); + } + } + class Form extends Component { + render() { + return ( + <form onSubmit={()=>{}}> + <input type="checkbox" checked={true} /> + <input type="checkbox" checked={false} /> + <fieldset> + <label><input type="radio" checked /></label> + <label><input type="radio" /></label> + </fieldset> + <ButtonBar /> + </form> + ); + } + } + class ButtonBar extends Component { + render() { + return ( + <button-bar> + <Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button> + <Button style="top:0 ; right: 20">Poor CSS</Button> + <Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button> + <Button style={{ margin:0, padding:'10px', overflow:'visible' }}>Object CSS</Button> + </button-bar> + ); + } + } + class Button extends Component { + render(props) { + return <button {...props} />; + } + } + class Main extends Component { + render() { + return <Form />; + } + } + class Root extends Component { + render() { + return ( + <div class="foo bar" data-foo="bar" p={2}> + <Header /> + <Main /> + </div> + ); + } + } + class Empty extends Component { + render() { + return <div />; + } + } + class Parent extends Component { + render({ child:C }) { + return <C />; + } + } + + let root; + benchmark( () => { + root = render(<Parent child={Root} />, scratch, root); + root = render(<Parent child={Empty} />, scratch, root); + }, ({ ticks, message }) => { + console.log(`PERF: repeat diff: ${message}`); + expect(ticks).to.be.below(2000 * MULTIPLIER); + done(); + }); + }); + + it('should construct large VDOM trees fast', done => { + const FIELDS = []; + for (let i=100; i--; ) FIELDS.push((i*999).toString(36)); + + let out = []; + function digest(vnode) { + out.push(vnode); + out.length = 0; + } + benchmark( () => { + digest( + <div class="foo bar" data-foo="bar" p={2}> + <header> + <h1 class="asdf">a {'b'} c {0} d</h1> + <nav> + <a href="/foo">Foo</a> + <a href="/bar">Bar</a> + </nav> + </header> + <main> + <form onSubmit={()=>{}}> + <input type="checkbox" checked /> + <input type="checkbox" /> + <fieldset> + { FIELDS.map( field => ( + <label> + {field}: + <input placeholder={field} /> + </label> + )) } + </fieldset> + <button-bar> + <button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button> + <button style="top:0 ; right: 20">Poor CSS</button> + <button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button> + <button style={{ margin:0, padding:'10px', overflow:'visible' }}>Object CSS</button> + </button-bar> + </form> + </main> + </div> + ); + }, ({ ticks, message }) => { + console.log(`PERF: large VTree: ${message}`); + expect(ticks).to.be.below(2000 * MULTIPLIER); + done(); + }); + }); +}); diff --git a/thirdparty/preact/test/browser/refs.js b/thirdparty/preact/test/browser/refs.js new file mode 100644 index 000000000..89678b76e --- /dev/null +++ b/thirdparty/preact/test/browser/refs.js @@ -0,0 +1,287 @@ +import { h, render, Component } from '../../src/preact'; +/** @jsx h */ + +// gives call count and argument errors names (otherwise sinon just uses "spy"): +let spy = (name, ...args) => { + let spy = sinon.spy(...args); + spy.displayName = `spy('${name}')`; + return spy; +}; + +describe('refs', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should invoke refs in render()', () => { + let ref = spy('ref'); + render(<div ref={ref} />, scratch); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + }); + + it('should invoke refs in Component.render()', () => { + let outer = spy('outer'), + inner = spy('inner'); + class Foo extends Component { + render() { + return ( + <div ref={outer}> + <span ref={inner} /> + </div> + ); + } + } + render(<Foo />, scratch); + + expect(outer).to.have.been.calledWith(scratch.firstChild); + expect(inner).to.have.been.calledWith(scratch.firstChild.firstChild); + }); + + it('should pass components to ref functions', () => { + let ref = spy('ref'), + instance; + class Foo extends Component { + constructor() { + super(); + instance = this; + } + render() { + return <div />; + } + } + render(<Foo ref={ref} />, scratch); + + expect(ref).to.have.been.calledOnce.and.calledWith(instance); + }); + + it('should pass rendered DOM from functional components to ref functions', () => { + let ref = spy('ref'); + + const Foo = () => <div />; + + let root = render(<Foo ref={ref} />, scratch); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + + ref.reset(); + render(<Foo ref={ref} />, scratch, root); + expect(ref).to.have.been.calledOnce.and.calledWith(scratch.firstChild); + + ref.reset(); + render(<span />, scratch, root); + expect(ref).to.have.been.calledOnce.and.calledWith(null); + }); + + it('should pass children to ref functions', () => { + let outer = spy('outer'), + inner = spy('inner'), + rerender, inst; + class Outer extends Component { + constructor() { + super(); + rerender = () => this.forceUpdate(); + } + render() { + return ( + <div> + <Inner ref={outer} /> + </div> + ); + } + } + class Inner extends Component { + constructor() { + super(); + inst = this; + } + render() { + return <span ref={inner} />; + } + } + + let root = render(<Outer />, scratch); + + expect(outer).to.have.been.calledOnce.and.calledWith(inst); + expect(inner).to.have.been.calledOnce.and.calledWith(inst.base); + + outer.reset(); + inner.reset(); + + rerender(); + + expect(outer).to.have.been.calledOnce.and.calledWith(inst); + expect(inner).to.have.been.calledOnce.and.calledWith(inst.base); + + outer.reset(); + inner.reset(); + + render(<div />, scratch, root); + + expect(outer).to.have.been.calledOnce.and.calledWith(null); + expect(inner).to.have.been.calledOnce.and.calledWith(null); + }); + + it('should pass high-order children to ref functions', () => { + let outer = spy('outer'), + inner = spy('inner'), + innermost = spy('innermost'), + outerInst, + innerInst; + class Outer extends Component { + constructor() { + super(); + outerInst = this; + } + render() { + return <Inner ref={inner} />; + } + } + class Inner extends Component { + constructor() { + super(); + innerInst = this; + } + render() { + return <span ref={innermost} />; + } + } + + let root = render(<Outer ref={outer} />, scratch); + + expect(outer, 'outer initial').to.have.been.calledOnce.and.calledWith(outerInst); + expect(inner, 'inner initial').to.have.been.calledOnce.and.calledWith(innerInst); + expect(innermost, 'innerMost initial').to.have.been.calledOnce.and.calledWith(innerInst.base); + + outer.reset(); + inner.reset(); + innermost.reset(); + root = render(<Outer ref={outer} />, scratch, root); + + expect(outer, 'outer update').to.have.been.calledOnce.and.calledWith(outerInst); + expect(inner, 'inner update').to.have.been.calledOnce.and.calledWith(innerInst); + expect(innermost, 'innerMost update').to.have.been.calledOnce.and.calledWith(innerInst.base); + + outer.reset(); + inner.reset(); + innermost.reset(); + root = render(<div />, scratch, root); + + expect(outer, 'outer unmount').to.have.been.calledOnce.and.calledWith(null); + expect(inner, 'inner unmount').to.have.been.calledOnce.and.calledWith(null); + expect(innermost, 'innerMost unmount').to.have.been.calledOnce.and.calledWith(null); + }); + + it('should not pass ref into component as a prop', () => { + let foo = spy('foo'), + bar = spy('bar'); + + class Foo extends Component { + render(){ return <div />; } + } + const Bar = spy('Bar', () => <div />); + + sinon.spy(Foo.prototype, 'render'); + + render(( + <div> + <Foo ref={foo} a="a" /> + <Bar ref={bar} b="b" /> + </div> + ), scratch); + + expect(Foo.prototype.render).to.have.been.calledWithExactly({ a:'a' }, { }, { }); + expect(Bar).to.have.been.calledWithExactly({ b:'b', ref:bar }, { }); + }); + + // Test for #232 + it('should only null refs after unmount', () => { + let root, outer, inner; + + class TestUnmount extends Component { + componentWillUnmount() { + expect(this).to.have.property('outer', outer); + expect(this).to.have.property('inner', inner); + } + + componentDidUnmount() { + expect(this).to.have.property('outer', null); + expect(this).to.have.property('inner', null); + } + + render() { + return ( + <div id="outer" ref={ c => this.outer=c }> + <div id="inner" ref={ c => this.inner=c } /> + </div> + ); + } + } + + sinon.spy(TestUnmount.prototype, 'componentWillUnmount'); + sinon.spy(TestUnmount.prototype, 'componentDidUnmount'); + + root = render(<div><TestUnmount /></div>, scratch, root); + outer = scratch.querySelector('#outer'); + inner = scratch.querySelector('#inner'); + + expect(TestUnmount.prototype.componentWillUnmount).not.to.have.been.called; + expect(TestUnmount.prototype.componentDidUnmount).not.to.have.been.called; + + root = render(<div />, scratch, root); + + expect(TestUnmount.prototype.componentWillUnmount).to.have.been.calledOnce; + expect(TestUnmount.prototype.componentDidUnmount).to.have.been.calledOnce; + }); + + it('should null and re-invoke refs when swapping component root element type', () => { + let inst; + + class App extends Component { + render() { + return <div><Child /></div>; + } + } + + class Child extends Component { + constructor(props, context) { + super(props, context); + this.state = { show:false }; + inst = this; + } + handleMount(){} + render(_, { show }) { + if (!show) return <div id="div" ref={this.handleMount}></div>; + return <span id="span" ref={this.handleMount}>some test content</span>; + } + } + sinon.spy(Child.prototype, 'handleMount'); + + render(<App />, scratch); + expect(inst.handleMount).to.have.been.calledOnce.and.calledWith(scratch.querySelector('#div')); + inst.handleMount.reset(); + + inst.setState({ show:true }); + inst.forceUpdate(); + expect(inst.handleMount).to.have.been.calledTwice; + expect(inst.handleMount.firstCall).to.have.been.calledWith(null); + expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#span')); + inst.handleMount.reset(); + + inst.setState({ show:false }); + inst.forceUpdate(); + expect(inst.handleMount).to.have.been.calledTwice; + expect(inst.handleMount.firstCall).to.have.been.calledWith(null); + expect(inst.handleMount.secondCall).to.have.been.calledWith(scratch.querySelector('#div')); + }); +}); diff --git a/thirdparty/preact/test/browser/render.js b/thirdparty/preact/test/browser/render.js new file mode 100644 index 000000000..5d18fb282 --- /dev/null +++ b/thirdparty/preact/test/browser/render.js @@ -0,0 +1,439 @@ +/* global DISABLE_FLAKEY */ + +import { h, render } from '../../src/preact'; +/** @jsx h */ + +function getAttributes(node) { + let attrs = {}; + for (let i=node.attributes.length; i--; ) { + attrs[node.attributes[i].name] = node.attributes[i].value; + } + return attrs; +} + +// hacky normalization of attribute order across browsers. +function sortAttributes(html) { + return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => { + let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 ); + if (~after.indexOf('/')) after = '></'+pre+'>'; + return '<' + pre + list.join('') + after; + }); +} + +describe('render()', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should create empty nodes (<* />)', () => { + render(<div />, scratch); + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'DIV'); + + scratch.innerHTML = ''; + + render(<span />, scratch); + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'SPAN'); + + scratch.innerHTML = ''; + + render(<foo />, scratch); + render(<x-bar />, scratch); + expect(scratch.childNodes).to.have.length(2); + expect(scratch.childNodes[0]).to.have.property('nodeName', 'FOO'); + expect(scratch.childNodes[1]).to.have.property('nodeName', 'X-BAR'); + }); + + it('should nest empty nodes', () => { + render(( + <div> + <span /> + <foo /> + <x-bar /> + </div> + ), scratch); + + expect(scratch.childNodes) + .to.have.length(1) + .and.to.have.deep.property('0.nodeName', 'DIV'); + + let c = scratch.childNodes[0].childNodes; + expect(c).to.have.length(3); + expect(c).to.have.deep.property('0.nodeName', 'SPAN'); + expect(c).to.have.deep.property('1.nodeName', 'FOO'); + expect(c).to.have.deep.property('2.nodeName', 'X-BAR'); + }); + + it('should not render falsey values', () => { + render(( + <div> + {null},{undefined},{false},{0},{NaN} + </div> + ), scratch); + + expect(scratch.firstChild).to.have.property('innerHTML', ',,,0,NaN'); + }); + + it('should clear falsey attributes', () => { + let root = render(( + <div anull="anull" aundefined="aundefined" afalse="afalse" anan="aNaN" a0="a0" /> + ), scratch); + + root = render(( + <div anull={null} aundefined={undefined} afalse={false} anan={NaN} a0={0} /> + ), scratch, root); + + expect(getAttributes(scratch.firstChild), 'from previous truthy values').to.eql({ + a0: '0', + anan: 'NaN' + }); + + scratch.innerHTML = ''; + + root = render(( + <div anull={null} aundefined={undefined} afalse={false} anan={NaN} a0={0} /> + ), scratch); + + expect(getAttributes(scratch.firstChild), 'initial render').to.eql({ + a0: '0', + anan: 'NaN' + }); + }); + + it('should clear falsey input values', () => { + let root = render(( + <div> + <input value={0} /> + <input value={false} /> + <input value={null} /> + <input value={undefined} /> + </div> + ), scratch); + + expect(root.children[0]).to.have.property('value', '0'); + expect(root.children[1]).to.have.property('value', 'false'); + expect(root.children[2]).to.have.property('value', ''); + expect(root.children[3]).to.have.property('value', ''); + }); + + it('should clear falsey DOM properties', () => { + let root; + function test(val) { + root = render(( + <div> + <input value={val} /> + <table border={val} /> + </div> + ), scratch, root); + } + + test('2'); + test(false); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for false'); + + test('3'); + test(null); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for null'); + + test('4'); + test(undefined); + expect(scratch).to.have.property('innerHTML', '<div><input><table></table></div>', 'for undefined'); + }); + + it('should apply string attributes', () => { + render(<div foo="bar" data-foo="databar" />, scratch); + + let div = scratch.childNodes[0]; + expect(div).to.have.deep.property('attributes.length', 2); + + expect(div).to.have.deep.property('attributes[0].name', 'foo'); + expect(div).to.have.deep.property('attributes[0].value', 'bar'); + + expect(div).to.have.deep.property('attributes[1].name', 'data-foo'); + expect(div).to.have.deep.property('attributes[1].value', 'databar'); + }); + + it('should apply class as String', () => { + render(<div class="foo" />, scratch); + expect(scratch.childNodes[0]).to.have.property('className', 'foo'); + }); + + it('should alias className to class', () => { + render(<div className="bar" />, scratch); + expect(scratch.childNodes[0]).to.have.property('className', 'bar'); + }); + + it('should apply style as String', () => { + render(<div style="top:5px; position:relative;" />, scratch); + expect(scratch.childNodes[0]).to.have.deep.property('style.cssText') + .that.matches(/top\s*:\s*5px\s*/) + .and.matches(/position\s*:\s*relative\s*/); + }); + + it('should only register on* functions as handlers', () => { + let click = () => {}, + onclick = () => {}; + + let proto = document.createElement('div').constructor.prototype; + + sinon.spy(proto, 'addEventListener'); + + render(<div click={ click } onClick={ onclick } />, scratch); + + expect(scratch.childNodes[0]).to.have.deep.property('attributes.length', 0); + + expect(proto.addEventListener).to.have.been.calledOnce + .and.to.have.been.calledWithExactly('click', sinon.match.func, false); + + proto.addEventListener.restore(); + }); + + it('should add and remove event handlers', () => { + let click = sinon.spy(), + mousedown = sinon.spy(); + + let proto = document.createElement('div').constructor.prototype; + sinon.spy(proto, 'addEventListener'); + sinon.spy(proto, 'removeEventListener'); + + function fireEvent(on, type) { + let e = document.createEvent('Event'); + e.initEvent(type, true, true); + on.dispatchEvent(e); + } + + render(<div onClick={ () => click(1) } onMouseDown={ mousedown } />, scratch); + + expect(proto.addEventListener).to.have.been.calledTwice + .and.to.have.been.calledWith('click') + .and.calledWith('mousedown'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).to.have.been.calledOnce + .and.calledWith(1); + + proto.addEventListener.reset(); + click.reset(); + + render(<div onClick={ () => click(2) } />, scratch, scratch.firstChild); + + expect(proto.addEventListener).not.to.have.been.called; + + expect(proto.removeEventListener) + .to.have.been.calledOnce + .and.calledWith('mousedown'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).to.have.been.calledOnce + .and.to.have.been.calledWith(2); + + fireEvent(scratch.childNodes[0], 'mousedown'); + expect(mousedown).not.to.have.been.called; + + proto.removeEventListener.reset(); + click.reset(); + mousedown.reset(); + + render(<div />, scratch, scratch.firstChild); + + expect(proto.removeEventListener) + .to.have.been.calledOnce + .and.calledWith('click'); + + fireEvent(scratch.childNodes[0], 'click'); + expect(click).not.to.have.been.called; + + proto.addEventListener.restore(); + proto.removeEventListener.restore(); + }); + + it('should use capturing for events that do not bubble', () => { + let click = sinon.spy(), + focus = sinon.spy(); + + let root = render(( + <div onClick={click} onFocus={focus}> + <button /> + </div> + ), scratch); + + root.firstElementChild.click(); + root.firstElementChild.focus(); + + expect(click, 'click').to.have.been.calledOnce; + + if (DISABLE_FLAKEY!==true) { + // Focus delegation requires a 50b hack I'm not sure we want to incur + expect(focus, 'focus').to.have.been.calledOnce; + + // IE doesn't set it + expect(click).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing + expect(focus).to.have.been.calledWithMatch({ eventPhase: 0 }); // capturing + } + }); + + it('should serialize style objects', () => { + let root = render(( + <div style={{ + color: 'rgb(255, 255, 255)', + background: 'rgb(255, 100, 0)', + backgroundPosition: '10px 10px', + 'background-size': 'cover', + padding: 5, + top: 100, + left: '100%' + }}> + test + </div> + ), scratch); + + let { style } = scratch.childNodes[0]; + expect(style).to.have.property('color').that.equals('rgb(255, 255, 255)'); + expect(style).to.have.property('background').that.contains('rgb(255, 100, 0)'); + expect(style).to.have.property('backgroundPosition').that.equals('10px 10px'); + expect(style).to.have.property('backgroundSize', 'cover'); + expect(style).to.have.property('padding', '5px'); + expect(style).to.have.property('top', '100px'); + expect(style).to.have.property('left', '100%'); + + root = render(( + <div style={{ color: 'rgb(0, 255, 255)' }}>test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('color: rgb(0, 255, 255);'); + + root = render(( + <div style="display: inline;">test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('display: inline;'); + + root = render(( + <div style={{ backgroundColor: 'rgb(0, 255, 255)' }}>test</div> + ), scratch, root); + + expect(root).to.have.deep.property('style.cssText').that.equals('background-color: rgb(0, 255, 255);'); + }); + + it('should serialize class/className', () => { + render(<div class={{ + no1: false, + no2: 0, + no3: null, + no4: undefined, + no5: '', + yes1: true, + yes2: 1, + yes3: {}, + yes4: [], + yes5: ' ' + }} />, scratch); + + let { className } = scratch.childNodes[0]; + expect(className).to.be.a.string; + expect(className.split(' ')) + .to.include.members(['yes1', 'yes2', 'yes3', 'yes4', 'yes5']) + .and.not.include.members(['no1', 'no2', 'no3', 'no4', 'no5']); + }); + + it('should support dangerouslySetInnerHTML', () => { + let html = '<b>foo & bar</b>'; + let root = render(<div dangerouslySetInnerHTML={{ __html: html }} />, scratch); + + expect(scratch.firstChild).to.have.property('innerHTML', html); + expect(scratch.innerHTML).to.equal('<div>'+html+'</div>'); + + root = render(<div>a<strong>b</strong></div>, scratch, root); + + expect(scratch).to.have.property('innerHTML', `<div>a<strong>b</strong></div>`); + + root = render(<div dangerouslySetInnerHTML={{ __html: html }} />, scratch, root); + + expect(scratch.innerHTML).to.equal('<div>'+html+'</div>'); + }); + + it('should reconcile mutated DOM attributes', () => { + let check = p => render(<input type="checkbox" checked={p} />, scratch, scratch.lastChild), + value = () => scratch.lastChild.checked, + setValue = p => scratch.lastChild.checked = p; + check(true); + expect(value()).to.equal(true); + check(false); + expect(value()).to.equal(false); + check(true); + expect(value()).to.equal(true); + setValue(true); + check(false); + expect(value()).to.equal(false); + setValue(false); + check(true); + expect(value()).to.equal(true); + }); + + it('should ignore props.children if children are manually specified', () => { + expect( + <div a children={['a', 'b']}>c</div> + ).to.eql( + <div a>c</div> + ); + }); + + it('should reorder child pairs', () => { + let root = render(( + <div> + <a>a</a> + <b>b</b> + </div> + ), scratch, root); + + let a = scratch.firstChild.firstChild; + let b = scratch.firstChild.lastChild; + + expect(a).to.have.property('nodeName', 'A'); + expect(b).to.have.property('nodeName', 'B'); + + root = render(( + <div> + <b>b</b> + <a>a</a> + </div> + ), scratch, root); + + expect(scratch.firstChild.firstChild).to.have.property('nodeName', 'B'); + expect(scratch.firstChild.lastChild).to.have.property('nodeName', 'A'); + expect(scratch.firstChild.firstChild).to.equal(b); + expect(scratch.firstChild.lastChild).to.equal(a); + }); + + // Discussion: https://github.com/developit/preact/issues/287 + ('HTMLDataListElement' in window ? it : xit)('should allow <input list /> to pass through as an attribute', () => { + render(( + <div> + <input type="range" min="0" max="100" list="steplist" /> + <datalist id="steplist"> + <option>0</option> + <option>50</option> + <option>100</option> + </datalist> + </div> + ), scratch); + + let html = scratch.firstElementChild.firstElementChild.outerHTML; + expect(sortAttributes(html)).to.equal(sortAttributes('<input type="range" min="0" max="100" list="steplist">')); + }); +}); diff --git a/thirdparty/preact/test/browser/spec.js b/thirdparty/preact/test/browser/spec.js new file mode 100644 index 000000000..eb48151f0 --- /dev/null +++ b/thirdparty/preact/test/browser/spec.js @@ -0,0 +1,124 @@ +import { h, render, rerender, Component } from '../../src/preact'; +/** @jsx h */ + +describe('Component spec', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + describe('defaultProps', () => { + it('should apply default props on initial render', () => { + class WithDefaultProps extends Component { + constructor(props, context) { + super(props, context); + expect(props).to.be.deep.equal({ + fieldA: 1, fieldB: 2, + fieldC: 1, fieldD: 2 + }); + } + render() { + return <div />; + } + } + WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 }; + render(<WithDefaultProps fieldA={1} fieldB={2} fieldD={2} />, scratch); + }); + + it('should apply default props on rerender', () => { + let doRender; + class Outer extends Component { + constructor() { + super(); + this.state = { i:1 }; + } + componentDidMount() { + doRender = () => this.setState({ i: 2 }); + } + render(props, { i }) { + return <WithDefaultProps fieldA={1} fieldB={i} fieldD={i} />; + } + } + class WithDefaultProps extends Component { + constructor(props, context) { + super(props, context); + this.ctor(props, context); + } + ctor(){} + componentWillReceiveProps() {} + render() { + return <div />; + } + } + WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 }; + + let proto = WithDefaultProps.prototype; + sinon.spy(proto, 'ctor'); + sinon.spy(proto, 'componentWillReceiveProps'); + sinon.spy(proto, 'render'); + + render(<Outer />, scratch); + doRender(); + + const PROPS1 = { + fieldA: 1, fieldB: 1, + fieldC: 1, fieldD: 1 + }; + + const PROPS2 = { + fieldA: 1, fieldB: 2, + fieldC: 1, fieldD: 2 + }; + + expect(proto.ctor).to.have.been.calledWith(PROPS1); + expect(proto.render).to.have.been.calledWith(PROPS1); + + rerender(); + + // expect(proto.ctor).to.have.been.calledWith(PROPS2); + expect(proto.componentWillReceiveProps).to.have.been.calledWith(PROPS2); + expect(proto.render).to.have.been.calledWith(PROPS2); + }); + + // @TODO: migrate this to preact-compat + xit('should cache default props', () => { + class WithDefaultProps extends Component { + constructor(props, context) { + super(props, context); + expect(props).to.be.deep.equal({ + fieldA: 1, fieldB: 2, + fieldC: 1, fieldD: 2, + fieldX: 10 + }); + } + getDefaultProps() { + return { fieldA: 1, fieldB: 1 }; + } + render() { + return <div />; + } + } + WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 }; + sinon.spy(WithDefaultProps.prototype, 'getDefaultProps'); + render(( + <div> + <WithDefaultProps fieldB={2} fieldD={2} fieldX={10} /> + <WithDefaultProps fieldB={2} fieldD={2} fieldX={10} /> + <WithDefaultProps fieldB={2} fieldD={2} fieldX={10} /> + </div> + ), scratch); + expect(WithDefaultProps.prototype.getDefaultProps).to.be.calledOnce; + }); + }); +}); diff --git a/thirdparty/preact/test/browser/svg.js b/thirdparty/preact/test/browser/svg.js new file mode 100644 index 000000000..684f4dd96 --- /dev/null +++ b/thirdparty/preact/test/browser/svg.js @@ -0,0 +1,112 @@ +import { h, render } from '../../src/preact'; +/** @jsx h */ + + +// hacky normalization of attribute order across browsers. +function sortAttributes(html) { + return html.replace(/<([a-z0-9-]+)((?:\s[a-z0-9:_.-]+=".*?")+)((?:\s*\/)?>)/gi, (s, pre, attrs, after) => { + let list = attrs.match(/\s[a-z0-9:_.-]+=".*?"/gi).sort( (a, b) => a>b ? 1 : -1 ); + if (~after.indexOf('/')) after = '></'+pre+'>'; + return '<' + pre + list.join('') + after; + }); +} + + +describe('svg', () => { + let scratch; + + before( () => { + scratch = document.createElement('div'); + (document.body || document.documentElement).appendChild(scratch); + }); + + beforeEach( () => { + scratch.innerHTML = ''; + }); + + after( () => { + scratch.parentNode.removeChild(scratch); + scratch = null; + }); + + it('should render SVG to string', () => { + render(( + <svg viewBox="0 0 360 360"> + <path stroke="white" fill="black" d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" /> + </svg> + ), scratch); + + let html = sortAttributes(String(scratch.innerHTML).replace(' xmlns="http://www.w3.org/2000/svg"', '')); + expect(html).to.equal(sortAttributes(` + <svg viewBox="0 0 360 360"> + <path d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" fill="black" stroke="white"></path> + </svg> + `.replace(/[\n\t]+/g,''))); + }); + + it('should render SVG to DOM', () => { + const Demo = () => ( + <svg viewBox="0 0 360 360"> + <path d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z" fill="black" stroke="white" /> + </svg> + ); + render(<Demo />, scratch); + + let html = sortAttributes(String(scratch.innerHTML).replace(' xmlns="http://www.w3.org/2000/svg"', '')); + expect(html).to.equal(sortAttributes('<svg viewBox="0 0 360 360"><path stroke="white" fill="black" d="M 347.1 357.9 L 183.3 256.5 L 13 357.9 V 1.7 h 334.1 v 356.2 Z M 58.5 47.2 v 231.4 l 124.8 -74.1 l 118.3 72.8 V 47.2 H 58.5 Z"></path></svg>')); + }); + + it('should use attributes for className', () => { + const Demo = ({ c }) => ( + <svg viewBox="0 0 360 360" {...(c ? {class:'foo_'+c} : {})}> + <path class={c && ('bar_'+c)} stroke="white" fill="black" d="M347.1 357.9L183.3 256.5 13 357.9V1.7h334.1v356.2zM58.5 47.2v231.4l124.8-74.1 118.3 72.8V47.2H58.5z" /> + </svg> + ); + let root = render(<Demo c="1" />, scratch, root); + sinon.spy(root, 'removeAttribute'); + root = render(<Demo />, scratch, root); + expect(root.removeAttribute).to.have.been.calledOnce.and.calledWith('class'); + root.removeAttribute.restore(); + + root = render(<div />, scratch, root); + root = render(<Demo />, scratch, root); + sinon.spy(root, 'setAttribute'); + root = render(<Demo c="2" />, scratch, root); + expect(root.setAttribute).to.have.been.calledOnce.and.calledWith('class', 'foo_2'); + root.setAttribute.restore(); + root = render(<Demo c="3" />, scratch, root); + root = render(<Demo />, scratch, root); + }); + + it('should still support class attribute', () => { + render(( + <svg viewBox="0 0 1 1" class="foo bar" /> + ), scratch); + + expect(scratch.innerHTML).to.contain(` class="foo bar"`); + }); + + it('should serialize class', () => { + render(( + <svg viewBox="0 0 1 1" class={{ foo: true, bar: false, other: 'hello' }} /> + ), scratch); + + expect(scratch.innerHTML).to.contain(` class="foo other"`); + }); + + it('should switch back to HTML for <foreignObject>', () => { + render(( + <svg> + <g> + <foreignObject> + <a href="#foo">test</a> + </foreignObject> + </g> + </svg> + ), scratch); + + expect(scratch.getElementsByTagName('a')) + .to.have.property('0') + .that.is.a('HTMLAnchorElement'); + }); +}); diff --git a/thirdparty/preact/test/karma.conf.js b/thirdparty/preact/test/karma.conf.js new file mode 100644 index 000000000..6ed5397fb --- /dev/null +++ b/thirdparty/preact/test/karma.conf.js @@ -0,0 +1,126 @@ +/*eslint no-var:0, object-shorthand:0 */ + +var coverage = String(process.env.COVERAGE)!=='false', + sauceLabs = String(process.env.SAUCELABS).match(/^(1|true)$/gi) && !String(process.env.TRAVIS_PULL_REQUEST).match(/^(1|true)$/gi), + performance = !coverage && !sauceLabs && String(process.env.PERFORMANCE)!=='false', + webpack = require('webpack'); + +var sauceLabsLaunchers = { + sl_chrome: { + base: 'SauceLabs', + browserName: 'chrome' + }, + sl_firefox: { + base: 'SauceLabs', + browserName: 'firefox' + }, + sl_ios_safari: { + base: 'SauceLabs', + browserName: 'iphone', + platform: 'OS X 10.9', + version: '7.1' + }, + sl_ie_11: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '11' + }, + sl_ie_10: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '10' + }, + sl_ie_9: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '9' + } +}; + +module.exports = function(config) { + config.set({ + browsers: sauceLabs ? Object.keys(sauceLabsLaunchers) : ['PhantomJS'], + + frameworks: ['source-map-support', 'mocha', 'chai-sinon'], + + reporters: ['mocha'].concat( + coverage ? 'coverage' : [], + sauceLabs ? 'saucelabs' : [] + ), + + coverageReporter: { + reporters: [ + { + type: 'text-summary' + }, + { + type: 'html', + dir: __dirname+'/../coverage' + } + ] + }, + + mochaReporter: { + showDiff: true + }, + + browserLogOptions: { terminal: true }, + browserConsoleLogOptions: { terminal: true }, + + browserNoActivityTimeout: 5 * 60 * 1000, + + // sauceLabs: { + // tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER || ('local'+require('./package.json').version), + // startConnect: false + // }, + + customLaunchers: sauceLabsLaunchers, + + files: [ + { pattern: '{browser,shared}/**.js', watched: false } + ], + + preprocessors: { + '**/*': ['webpack', 'sourcemap'] + }, + + webpack: { + devtool: 'inline-source-map', + module: { + /* Transpile source and test files */ + preLoaders: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + loader: 'babel', + query: { + loose: 'all', + blacklist: ['es6.tailCall'] + } + } + ], + /* Only Instrument our source files for coverage */ + loaders: [].concat( coverage ? { + test: /\.jsx?$/, + loader: 'isparta', + include: /src/ + } : []) + }, + resolve: { + modulesDirectories: [__dirname, 'node_modules'] + }, + plugins: [ + new webpack.DefinePlugin({ + coverage: coverage, + NODE_ENV: JSON.stringify(process.env.NODE_ENV || ''), + ENABLE_PERFORMANCE: performance, + DISABLE_FLAKEY: !!String(process.env.FLAKEY).match(/^(0|false)$/gi) + }) + ] + }, + + webpackMiddleware: { + noInfo: true + } + }); +}; diff --git a/thirdparty/preact/test/node/index.js b/thirdparty/preact/test/node/index.js new file mode 100644 index 000000000..81fb567fb --- /dev/null +++ b/thirdparty/preact/test/node/index.js @@ -0,0 +1 @@ +// this is just a placeholder diff --git a/thirdparty/preact/test/shared/exports.js b/thirdparty/preact/test/shared/exports.js new file mode 100644 index 000000000..7ef3c659b --- /dev/null +++ b/thirdparty/preact/test/shared/exports.js @@ -0,0 +1,21 @@ +import preact, { h, Component, render, rerender, options } from '../../src/preact'; +import { expect } from 'chai'; + +describe('preact', () => { + it('should be available as a default export', () => { + expect(preact).to.be.an('object'); + expect(preact).to.have.property('h', h); + expect(preact).to.have.property('Component', Component); + expect(preact).to.have.property('render', render); + expect(preact).to.have.property('rerender', rerender); + expect(preact).to.have.property('options', options); + }); + + it('should be available as named exports', () => { + expect(h).to.be.a('function'); + expect(Component).to.be.a('function'); + expect(render).to.be.a('function'); + expect(rerender).to.be.a('function'); + expect(options).to.exist.and.be.an('object'); + }); +}); diff --git a/thirdparty/preact/test/shared/h.js b/thirdparty/preact/test/shared/h.js new file mode 100644 index 000000000..b0cf7f0e8 --- /dev/null +++ b/thirdparty/preact/test/shared/h.js @@ -0,0 +1,201 @@ +import { h } from '../../src/preact'; +import { VNode } from '../../src/vnode'; +import { expect } from 'chai'; + +/*eslint-env browser, mocha */ + +/** @jsx h */ + +let flatten = obj => JSON.parse(JSON.stringify(obj)); + +describe('h(jsx)', () => { + it('should return a VNode', () => { + let r; + expect( () => r = h('foo') ).not.to.throw(); + expect(r).to.be.an('object'); + expect(r).to.be.an.instanceof(VNode); + expect(r).to.have.property('nodeName', 'foo'); + expect(r).to.have.property('attributes', undefined); + expect(r).to.have.property('children', undefined); + }); + + it('should perserve raw attributes', () => { + let attrs = { foo:'bar', baz:10, func:()=>{} }, + r = h('foo', attrs); + expect(r).to.be.an('object') + .with.property('attributes') + .that.deep.equals(attrs); + }); + + it('should support element children', () => { + let r = h( + 'foo', + null, + h('bar'), + h('baz') + ); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + new VNode('bar'), + new VNode('baz') + ]); + }); + + it('should support multiple element children, given as arg list', () => { + let r = h( + 'foo', + null, + h('bar'), + h('baz', null, h('test')) + ); + + r = flatten(r); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + { nodeName:'bar' }, + { nodeName:'baz', children:[ + { nodeName:'test' } + ]} + ]); + }); + + it('should handle multiple element children, given as an array', () => { + let r = h( + 'foo', + null, + [ + h('bar'), + h('baz', null, h('test')) + ] + ); + + r = flatten(r); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + { nodeName:'bar' }, + { nodeName:'baz', children:[ + { nodeName:'test' } + ]} + ]); + }); + + it('should handle multiple children, flattening one layer as needed', () => { + let r = h( + 'foo', + null, + h('bar'), + [ + h('baz', null, h('test')) + ] + ); + + r = flatten(r); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + { nodeName:'bar' }, + { nodeName:'baz', children:[ + { nodeName:'test' } + ]} + ]); + }); + + it('should support nested children', () => { + const m = x => h(x); + expect( + h('foo', null, m('a'), [m('b'), m('c')], m('d')) + ).to.have.property('children').that.eql(['a', 'b', 'c', 'd'].map(m)); + + expect( + h('foo', null, [m('a'), [m('b'), m('c')], m('d')]) + ).to.have.property('children').that.eql(['a', 'b', 'c', 'd'].map(m)); + + expect( + h('foo', { children: [m('a'), [m('b'), m('c')], m('d')] }) + ).to.have.property('children').that.eql(['a', 'b', 'c', 'd'].map(m)); + + expect( + h('foo', { children: [[m('a'), [m('b'), m('c')], m('d')]] }) + ).to.have.property('children').that.eql(['a', 'b', 'c', 'd'].map(m)); + + expect( + h('foo', { children: m('a') }) + ).to.have.property('children').that.eql([m('a')]); + + expect( + h('foo', { children: 'a' }) + ).to.have.property('children').that.eql(['a']); + }); + + it('should support text children', () => { + let r = h( + 'foo', + null, + 'textstuff' + ); + + expect(r).to.be.an('object') + .with.property('children') + .with.length(1) + .with.property('0') + .that.equals('textstuff'); + }); + + it('should merge adjacent text children', () => { + let r = h( + 'foo', + null, + 'one', + 'two', + h('bar'), + 'three', + h('baz'), + h('baz'), + 'four', + null, + 'five', + 'six' + ); + + r = flatten(r); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + 'onetwo', + { nodeName:'bar' }, + 'three', + { nodeName:'baz' }, + { nodeName:'baz' }, + 'fourfivesix' + ]); + }); + + it('should merge nested adjacent text children', () => { + let r = h( + 'foo', + null, + 'one', + ['two', null, 'three'], + null, + ['four', null, 'five', null], + 'six', + null + ); + + r = flatten(r); + + expect(r).to.be.an('object') + .with.property('children') + .that.deep.equals([ + 'onetwothreefourfivesix' + ]); + }); +}); diff --git a/thirdparty/preact/typings.json b/thirdparty/preact/typings.json new file mode 100644 index 000000000..196fc9b6b --- /dev/null +++ b/thirdparty/preact/typings.json @@ -0,0 +1,5 @@ +{ + "name": "preact", + "main": "src/preact.d.ts", + "version": false +} |