From c54f3dae20a3d39154717f67df6a90ffa008d47e Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Jun 2016 11:14:45 +0100 Subject: [PATCH 01/17] Create human readable logs module on top of LatexLogParser; use it in the PDF controller. --- .../ide/human-readable-logs/HumanReadableLogs.coffee | 6 ++++++ .../coffee/ide/pdf/controllers/PdfController.coffee | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee new file mode 100644 index 0000000000..ad3c27a505 --- /dev/null +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -0,0 +1,6 @@ +define [ + "libs/latex-log-parser" +], (LogParser) -> + parse : (rawLog, options) -> + parsedLogEntries = LogParser.parse(rawLog, options) + return parsedLogEntries diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 4baa2a22ff..ed1dee8da2 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -1,9 +1,9 @@ define [ "base" "ace/ace" - "libs/latex-log-parser" + "ide/human-readable-logs/HumanReadableLogs" "libs/bib-log-parser" -], (App, Ace, LogParser, BibLogParser) -> +], (App, Ace, HumanReadableLogs, BibLogParser) -> App.controller "PdfController", ($scope, $http, ide, $modal, synctex, event_tracking, localStorage) -> # enable per-user containers if querystring includes isolated=true @@ -162,11 +162,11 @@ define [ accumulateResults = (newEntries) -> for key in ['all', 'errors', 'warnings'] logEntries[key] = logEntries[key].concat newEntries[key] - + # use the parsers for each file type processLog = (log) -> $scope.pdf.rawLog = log - {errors, warnings, typesetting} = LogParser.parse(log, ignoreDuplicates: true) + {errors, warnings, typesetting} = HumanReadableLogs.parse(log, ignoreDuplicates: true) all = [].concat errors, warnings, typesetting accumulateResults {all, errors, warnings} From f167aacc0819483ef6aec5fec44c60898efc2a7c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Jun 2016 15:29:00 +0100 Subject: [PATCH 02/17] Add sample rules file. --- .../HumanReadableLogs.coffee | 6 +- .../HumanReadableLogsRules.coffee | 102 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee index ad3c27a505..2c54168812 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -1,6 +1,10 @@ define [ "libs/latex-log-parser" -], (LogParser) -> + "ide/human-readable-logs/HumanReadableLogsRules" +], (LogParser, ruleset) -> parse : (rawLog, options) -> parsedLogEntries = LogParser.parse(rawLog, options) + + console.log entry.message for entry in parsedLogEntries.all + return parsedLogEntries diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee new file mode 100644 index 0000000000..a33a0a7ed0 --- /dev/null +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -0,0 +1,102 @@ +define -> [ + regExToMatch: /Too many }'s/ + humanReadableLogText: "The reason LaTeX thinks there are too many }'s + here is that the opening curly brace is missing after the \\date control + sequence and before the word December, so the closing curly brace is + seen as one too many (which it is!). In fact, there are other things + which can follow the \\date command apart from a date in curly braces, + so LaTeX cannot possibly guess that you've missed out the opening curly + brace until it finds a closing one!" + , + regExToMatch: /Undefined control sequence/ + humanReadableLogText: "In this example, LaTeX is complaining that it has + no such command (\"control sequence\") as \\dtae. Obviously it's been + mistyped, but only a human can detect that fact: all LaTeX knows is that + \\dtae is not a command it knows about: it's undefined. Mistypings are + the most common source of errors. Some editors allow common commands and + environments to be inserted using drop-down menus or icons, which may + be used to avoid these errors." + , + regExToMatch: /Missing \$ inserted/ + humanReadableLogText: "A character that can only be used in the + mathematics was inserted in normal text. If you intended to use + mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use + the 'quick math mode': \ensuremath{...}. If you did not intend to use + mathematics mode, then perhaps you are trying to use a special character + that needs to be entered in a different way; for example _ will be + interpreted as a subscript operator in mathematics mode, and you need + \\_ to get an underscore character. + + This can also happen if you use the wrong character encoding, for + example using utf8 without \"\\usepackage[utf8]{inputenc}\" or using + iso8859-1 without \"\\usepackage[latin1]{inputenc}\", there are several + character encoding formats, make sure to pick the right one." + , + regExToMatch: /Runaway argument/ + humanReadableLogText: "In this error, the closing curly brace has been + omitted from the date. It's the opposite of the error of too many }'s, + and it results in \\maketitle trying to format the title page while + LaTeX is still expecting more text for the date! As \\maketitle creates + new paragraphs on the title page, this is detected and LaTeX complains + that the previous paragraph has ended but \\date is not yet finished." + , + regExToMatch: /Underfull \\hbox/ + humanReadableLogText: "This is a warning that LaTeX cannot stretch the + line wide enough to fit, without making the spacing bigger than its + currently permitted maximum. The badness (0-10,000) indicates how severe + this is (here you can probably ignore a badness of 1394). It says what + lines of your file it was typesetting when it found this, and the number + in square brackets is the number of the page onto which the offending + line was printed. The codes separated by slashes are the typeface and + font style and size used in the line. Ignore them for the moment. + + This comes up if you force a linebreak, e.g., \\\\, and have a return + before it. Normally TeX ignores linebreaks, providing full paragraphs to + ragged text. In this case it is necessary to pull the linebreak up one + line to the end of the previous sentence. + + This warning may also appear when inserting images. It can be avoided by + using the \\textwidth or possibly \\linewidth options, e.g. + \\includegraphics[width=\\textwidth]{image_name}" + , + regExToMatch: /Overfull \\hbox/ + humanReadableLogText: "An overfull \hbox means that there is a + hyphenation or justification problem: moving the last word on the line + to the next line would make the spaces in the line wider than the + current limit; keeping the word on the line would make the spaces + smaller than the current limit, so the word is left on the line, but + with the minimum allowed space between words, and which makes the line + go over the edge. + + The warning is given so that you can find the line in the code that + originates the problem (in this case: 860-861) and fix it. The line on + this example is too long by a shade over 9pt. The chosen hyphenation + point which minimizes the error is shown at the end of the line (Win-). + Line numbers and page numbers are given as before. In this case, 9pt is + too much to ignore (over 3mm), and a manual correction needs making + (such as a change to the hyphenation), or the flexibility settings need + changing. + + If the \"overfull\" word includes a forward slash, such as + \"input/output\", this should be properly typeset as \"input\\slash + output\". The use of \\slash has the same effect as using the \"/\" + character, except that it can form the end of a line (with the following + words appearing at the start of the next line). The \"/\" character is + typically used in units, such as \"mm/year\" character, which should not + be broken over multiple lines. + + The warning can also be issued when the \\end{document} tag was not + included or was deleted." + , + regExToMatch: /LaTeX Error: File .* not found/ + humanReadableLogText: "When you use the \\usepackage command to request + LaTeX to use a certain package, it will look for a file with the + specified name and the filetype .sty. In this case the user has mistyped + the name of the paralist package, so it's easy to fix. However, if you + get the name right, but the package is not installed on your machine, + you will need to download and install it before continuing. If you don't + want to affect the global installation of the machine, you can simply + download from Internet the necessary .sty file and put it in the same + folder of the document you are compiling." +] + From 9b8d695e439c7878b89e1ca8412988209283f0bd Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Jun 2016 15:53:39 +0100 Subject: [PATCH 03/17] Match logs against ruleset; show human readable message when appropriate. --- .../web/app/views/project/editor/pdf.jade | 3 +- .../HumanReadableLogs.coffee | 7 ++++- .../HumanReadableLogsRules.coffee | 28 +++++++++---------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index ce5bd16b53..1cdab7a2b2 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -105,7 +105,8 @@ div.full-size.pdf(ng-controller="PdfController") span(ng-show="entry.file") {{ entry.file }} span(ng-show="entry.line") , line {{ entry.line }} p.entry-message(ng-show="entry.message") {{ entry.message }} - p.entry-content(ng-show="entry.content") {{ entry.content }} + p.entry-content(ng-show="entry.humanReadableMessage") {{ entry.humanReadableMessage }} + p.entry-content(ng-show="entry.content && !entry.humanReadableMessage") {{ entry.content }} p .pull-right diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee index 2c54168812..3109db2669 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -5,6 +5,11 @@ define [ parse : (rawLog, options) -> parsedLogEntries = LogParser.parse(rawLog, options) - console.log entry.message for entry in parsedLogEntries.all + _getHumanReadableMessage = (logMessage) -> + return rule.humanReadableMessage for rule in ruleset when rule.regexToMatch.test logMessage + + for entry in parsedLogEntries.all + humanReadableMessage = _getHumanReadableMessage entry.message + entry.humanReadableMessage = humanReadableMessage if humanReadableMessage? return parsedLogEntries diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index a33a0a7ed0..58ffa9bb75 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -1,6 +1,6 @@ define -> [ - regExToMatch: /Too many }'s/ - humanReadableLogText: "The reason LaTeX thinks there are too many }'s + regexToMatch: /Too many }'s/ + humanReadableMessage: "The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). In fact, there are other things @@ -8,8 +8,8 @@ define -> [ so LaTeX cannot possibly guess that you've missed out the opening curly brace until it finds a closing one!" , - regExToMatch: /Undefined control sequence/ - humanReadableLogText: "In this example, LaTeX is complaining that it has + regexToMatch: /Undefined control sequence/ + humanReadableMessage: "In this example, LaTeX is complaining that it has no such command (\"control sequence\") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. Mistypings are @@ -17,8 +17,8 @@ define -> [ environments to be inserted using drop-down menus or icons, which may be used to avoid these errors." , - regExToMatch: /Missing \$ inserted/ - humanReadableLogText: "A character that can only be used in the + regexToMatch: /Missing \$ inserted/ + humanReadableMessage: "A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \ensuremath{...}. If you did not intend to use @@ -32,16 +32,16 @@ define -> [ iso8859-1 without \"\\usepackage[latin1]{inputenc}\", there are several character encoding formats, make sure to pick the right one." , - regExToMatch: /Runaway argument/ - humanReadableLogText: "In this error, the closing curly brace has been + regexToMatch: /Runaway argument/ + humanReadableMessage: "In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! As \\maketitle creates new paragraphs on the title page, this is detected and LaTeX complains that the previous paragraph has ended but \\date is not yet finished." , - regExToMatch: /Underfull \\hbox/ - humanReadableLogText: "This is a warning that LaTeX cannot stretch the + regexToMatch: /Underfull \\hbox/ + humanReadableMessage: "This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). It says what @@ -59,8 +59,8 @@ define -> [ using the \\textwidth or possibly \\linewidth options, e.g. \\includegraphics[width=\\textwidth]{image_name}" , - regExToMatch: /Overfull \\hbox/ - humanReadableLogText: "An overfull \hbox means that there is a + regexToMatch: /Overfull \\hbox/ + humanReadableMessage: "An overfull \hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces @@ -88,8 +88,8 @@ define -> [ The warning can also be issued when the \\end{document} tag was not included or was deleted." , - regExToMatch: /LaTeX Error: File .* not found/ - humanReadableLogText: "When you use the \\usepackage command to request + regexToMatch: /LaTeX Error: File .* not found/ + humanReadableMessage: "When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. However, if you From 40fda6e3618c2e31096021a5262596d3471332be Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 16 Jun 2016 16:29:19 +0100 Subject: [PATCH 04/17] Support HTML in human readable log messages. --- .../web/app/views/project/editor/pdf.jade | 2 +- .../HumanReadableLogsRules.coffee | 116 +++++------------- 2 files changed, 29 insertions(+), 89 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 1cdab7a2b2..a97a4c5835 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -105,7 +105,7 @@ div.full-size.pdf(ng-controller="PdfController") span(ng-show="entry.file") {{ entry.file }} span(ng-show="entry.line") , line {{ entry.line }} p.entry-message(ng-show="entry.message") {{ entry.message }} - p.entry-content(ng-show="entry.humanReadableMessage") {{ entry.humanReadableMessage }} + p.entry-content(ng-show="entry.humanReadableMessage", ng-bind-html="entry.humanReadableMessage") p.entry-content(ng-show="entry.content && !entry.humanReadableMessage") {{ entry.content }} p diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index 58ffa9bb75..87b6e33895 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -1,102 +1,42 @@ define -> [ regexToMatch: /Too many }'s/ - humanReadableMessage: "The reason LaTeX thinks there are too many }'s - here is that the opening curly brace is missing after the \\date control - sequence and before the word December, so the closing curly brace is - seen as one too many (which it is!). In fact, there are other things - which can follow the \\date command apart from a date in curly braces, - so LaTeX cannot possibly guess that you've missed out the opening curly - brace until it finds a closing one!" + humanReadableMessage: """ +

The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). In fact, there are other things which can follow the \date command apart from a date in curly braces, so LaTeX cannot possibly guess that you've missed out the opening curly brace until it finds a closing one!

+ """ , regexToMatch: /Undefined control sequence/ - humanReadableMessage: "In this example, LaTeX is complaining that it has - no such command (\"control sequence\") as \\dtae. Obviously it's been - mistyped, but only a human can detect that fact: all LaTeX knows is that - \\dtae is not a command it knows about: it's undefined. Mistypings are - the most common source of errors. Some editors allow common commands and - environments to be inserted using drop-down menus or icons, which may - be used to avoid these errors." + humanReadableMessage: """ +

In this example, LaTeX is complaining that it has no such command ("control sequence") as \dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \dtae is not a command it knows about: it's undefined. Mistypings are the most common source of errors. Some editors allow common commands and environments to be inserted using drop-down menus or icons, which may be used to avoid these errors.

+ """ , regexToMatch: /Missing \$ inserted/ - humanReadableMessage: "A character that can only be used in the - mathematics was inserted in normal text. If you intended to use - mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use - the 'quick math mode': \ensuremath{...}. If you did not intend to use - mathematics mode, then perhaps you are trying to use a special character - that needs to be entered in a different way; for example _ will be - interpreted as a subscript operator in mathematics mode, and you need - \\_ to get an underscore character. - - This can also happen if you use the wrong character encoding, for - example using utf8 without \"\\usepackage[utf8]{inputenc}\" or using - iso8859-1 without \"\\usepackage[latin1]{inputenc}\", there are several - character encoding formats, make sure to pick the right one." + humanReadableMessage: """ +

A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \begin{math}...\end{math} or use the 'quick math mode': \ensuremath{...}. If you did not intend to use mathematics mode, then perhaps you are trying to use a special character that needs to be entered in a different way; for example _ will be interpreted as a subscript operator in mathematics mode, and you need \_ to get an underscore character.

+

This can also happen if you use the wrong character encoding, for example using utf8 without "\\usepackage[utf8]{inputenc}" or using iso8859-1 without "\\usepackage[latin1]{inputenc}", there are several character encoding formats, make sure to pick the right one.

+ """ , regexToMatch: /Runaway argument/ - humanReadableMessage: "In this error, the closing curly brace has been - omitted from the date. It's the opposite of the error of too many }'s, - and it results in \\maketitle trying to format the title page while - LaTeX is still expecting more text for the date! As \\maketitle creates - new paragraphs on the title page, this is detected and LaTeX complains - that the previous paragraph has ended but \\date is not yet finished." + humanReadableMessage: """ +

In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \maketitle trying to format the title page while LaTeX is still expecting more text for the date! As \maketitle creates new paragraphs on the title page, this is detected and LaTeX complains that the previous paragraph has ended but \date is not yet finished.

+ """ , regexToMatch: /Underfull \\hbox/ - humanReadableMessage: "This is a warning that LaTeX cannot stretch the - line wide enough to fit, without making the spacing bigger than its - currently permitted maximum. The badness (0-10,000) indicates how severe - this is (here you can probably ignore a badness of 1394). It says what - lines of your file it was typesetting when it found this, and the number - in square brackets is the number of the page onto which the offending - line was printed. The codes separated by slashes are the typeface and - font style and size used in the line. Ignore them for the moment. - - This comes up if you force a linebreak, e.g., \\\\, and have a return - before it. Normally TeX ignores linebreaks, providing full paragraphs to - ragged text. In this case it is necessary to pull the linebreak up one - line to the end of the previous sentence. - - This warning may also appear when inserting images. It can be avoided by - using the \\textwidth or possibly \\linewidth options, e.g. - \\includegraphics[width=\\textwidth]{image_name}" + humanReadableMessage: """ +

This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). It says what lines of your file it was typesetting when it found this, and the number in square brackets is the number of the page onto which the offending line was printed. The codes separated by slashes are the typeface and font style and size used in the line. Ignore them for the moment.

+

This comes up if you force a linebreak, e.g., \\, and have a return before it. Normally TeX ignores linebreaks, providing full paragraphs to ragged text. In this case it is necessary to pull the linebreak up one line to the end of the previous sentence.

+

This warning may also appear when inserting images. It can be avoided by using the \textwidth or possibly \linewidth options, e.g. \includegraphics[width=\textwidth]{image_name}

+ """ , regexToMatch: /Overfull \\hbox/ - humanReadableMessage: "An overfull \hbox means that there is a - hyphenation or justification problem: moving the last word on the line - to the next line would make the spaces in the line wider than the - current limit; keeping the word on the line would make the spaces - smaller than the current limit, so the word is left on the line, but - with the minimum allowed space between words, and which makes the line - go over the edge. - - The warning is given so that you can find the line in the code that - originates the problem (in this case: 860-861) and fix it. The line on - this example is too long by a shade over 9pt. The chosen hyphenation - point which minimizes the error is shown at the end of the line (Win-). - Line numbers and page numbers are given as before. In this case, 9pt is - too much to ignore (over 3mm), and a manual correction needs making - (such as a change to the hyphenation), or the flexibility settings need - changing. - - If the \"overfull\" word includes a forward slash, such as - \"input/output\", this should be properly typeset as \"input\\slash - output\". The use of \\slash has the same effect as using the \"/\" - character, except that it can form the end of a line (with the following - words appearing at the start of the next line). The \"/\" character is - typically used in units, such as \"mm/year\" character, which should not - be broken over multiple lines. - - The warning can also be issued when the \\end{document} tag was not - included or was deleted." + humanReadableMessage: """ +

An overfull \hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge.

+

The warning is given so that you can find the line in the code that originates the problem (in this case: 860-861) and fix it. The line on this example is too long by a shade over 9pt. The chosen hyphenation point which minimizes the error is shown at the end of the line (Win-). Line numbers and page numbers are given as before. In this case, 9pt is too much to ignore (over 3mm), and a manual correction needs making (such as a change to the hyphenation), or the flexibility settings need changing.

+

If the "overfull" word includes a forward slash, such as "input/output", this should be properly typeset as "input\slash output". The use of \slash has the same effect as using the "/" character, except that it can form the end of a line (with the following words appearing at the start of the next line). The "/" character is typically used in units, such as "mm/year" character, which should not be broken over multiple lines.

+

The warning can also be issued when the \end{document} tag was not included or was deleted.

+ """ , regexToMatch: /LaTeX Error: File .* not found/ - humanReadableMessage: "When you use the \\usepackage command to request - LaTeX to use a certain package, it will look for a file with the - specified name and the filetype .sty. In this case the user has mistyped - the name of the paralist package, so it's easy to fix. However, if you - get the name right, but the package is not installed on your machine, - you will need to download and install it before continuing. If you don't - want to affect the global installation of the machine, you can simply - download from Internet the necessary .sty file and put it in the same - folder of the document you are compiling." -] - + humanReadableMessage: """ +

When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. However, if you get the name right, but the package is not installed on your machine, you will need to download and install it before continuing. If you don't want to affect the global installation of the machine, you can simply download from Internet the necessary .sty file and put it in the same folder of the document you are compiling.

+ """ +] \ No newline at end of file From 54d48ce0eaea42fef99e56a904a9c61c253ed7d4 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Jun 2016 11:42:32 +0100 Subject: [PATCH 05/17] Support extra info URL in human readable hints. --- .../web/app/views/project/editor/pdf.jade | 7 +++- .../HumanReadableLogs.coffee | 9 +++-- .../HumanReadableLogsRules.coffee | 39 +++++++++++-------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index a97a4c5835..f1d2e232c5 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -105,8 +105,11 @@ div.full-size.pdf(ng-controller="PdfController") span(ng-show="entry.file") {{ entry.file }} span(ng-show="entry.line") , line {{ entry.line }} p.entry-message(ng-show="entry.message") {{ entry.message }} - p.entry-content(ng-show="entry.humanReadableMessage", ng-bind-html="entry.humanReadableMessage") - p.entry-content(ng-show="entry.content && !entry.humanReadableMessage") {{ entry.content }} + div + i.fa.fa-hand-o-right(aria-hidden="true") + p.entry-content(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") + a(ng-href="{{ entry.extraInfoURL }}", target="_blank") #{translate("log_entry_extra_info")} + p.entry-content(ng-show="entry.content") {{ entry.content }} p .pull-right diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee index 3109db2669..5f4627cb95 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -5,11 +5,12 @@ define [ parse : (rawLog, options) -> parsedLogEntries = LogParser.parse(rawLog, options) - _getHumanReadableMessage = (logMessage) -> - return rule.humanReadableMessage for rule in ruleset when rule.regexToMatch.test logMessage + _getRule = (logMessage) -> + return rule for rule in ruleset when rule.regexToMatch.test logMessage for entry in parsedLogEntries.all - humanReadableMessage = _getHumanReadableMessage entry.message - entry.humanReadableMessage = humanReadableMessage if humanReadableMessage? + { humanReadableHint, extraInfoURL } = _getRule entry.message + entry.humanReadableHint = humanReadableHint if humanReadableHint? + entry.extraInfoURL = extraInfoURL if extraInfoURL? return parsedLogEntries diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index 87b6e33895..b0121b6c61 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -1,42 +1,49 @@ define -> [ regexToMatch: /Too many }'s/ - humanReadableMessage: """ -

The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). In fact, there are other things which can follow the \date command apart from a date in curly braces, so LaTeX cannot possibly guess that you've missed out the opening curly brace until it finds a closing one!

+ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Too_many_.7D.27s" + humanReadableHint: """ +

The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). In fact, there are other things which can follow the \\date command apart from a date in curly braces, so LaTeX cannot possibly guess that you've missed out the opening curly brace until it finds a closing one!

""" , regexToMatch: /Undefined control sequence/ - humanReadableMessage: """ -

In this example, LaTeX is complaining that it has no such command ("control sequence") as \dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \dtae is not a command it knows about: it's undefined. Mistypings are the most common source of errors. Some editors allow common commands and environments to be inserted using drop-down menus or icons, which may be used to avoid these errors.

+ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Undefined_control_sequence" + humanReadableHint: """ +

In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. Mistypings are the most common source of errors. Some editors allow common commands and environments to be inserted using drop-down menus or icons, which may be used to avoid these errors.

""" , regexToMatch: /Missing \$ inserted/ - humanReadableMessage: """ -

A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \begin{math}...\end{math} or use the 'quick math mode': \ensuremath{...}. If you did not intend to use mathematics mode, then perhaps you are trying to use a special character that needs to be entered in a different way; for example _ will be interpreted as a subscript operator in mathematics mode, and you need \_ to get an underscore character.

+ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Not_in_Mathematics_Mode" + humanReadableHint: """ +

A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. If you did not intend to use mathematics mode, then perhaps you are trying to use a special character that needs to be entered in a different way; for example _ will be interpreted as a subscript operator in mathematics mode, and you need \\_ to get an underscore character.

This can also happen if you use the wrong character encoding, for example using utf8 without "\\usepackage[utf8]{inputenc}" or using iso8859-1 without "\\usepackage[latin1]{inputenc}", there are several character encoding formats, make sure to pick the right one.

""" , regexToMatch: /Runaway argument/ - humanReadableMessage: """ -

In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \maketitle trying to format the title page while LaTeX is still expecting more text for the date! As \maketitle creates new paragraphs on the title page, this is detected and LaTeX complains that the previous paragraph has ended but \date is not yet finished.

+ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Runaway_argument" + humanReadableHint: """ +

In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! As \\maketitle creates new paragraphs on the title page, this is detected and LaTeX complains that the previous paragraph has ended but \\date is not yet finished.

""" , regexToMatch: /Underfull \\hbox/ - humanReadableMessage: """ + extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Underfull_hbox" + humanReadableHint: """

This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). It says what lines of your file it was typesetting when it found this, and the number in square brackets is the number of the page onto which the offending line was printed. The codes separated by slashes are the typeface and font style and size used in the line. Ignore them for the moment.

-

This comes up if you force a linebreak, e.g., \\, and have a return before it. Normally TeX ignores linebreaks, providing full paragraphs to ragged text. In this case it is necessary to pull the linebreak up one line to the end of the previous sentence.

-

This warning may also appear when inserting images. It can be avoided by using the \textwidth or possibly \linewidth options, e.g. \includegraphics[width=\textwidth]{image_name}

+

This comes up if you force a linebreak, e.g., \\\\, and have a return before it. Normally TeX ignores linebreaks, providing full paragraphs to ragged text. In this case it is necessary to pull the linebreak up one line to the end of the previous sentence.

+

This warning may also appear when inserting images. It can be avoided by using the \\textwidth or possibly \\linewidth options, e.g. \\includegraphics[width=\\textwidth]{image_name}

""" , regexToMatch: /Overfull \\hbox/ - humanReadableMessage: """ -

An overfull \hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge.

+ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Overfull_hbox" + humanReadableHint: """ +

An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge.

The warning is given so that you can find the line in the code that originates the problem (in this case: 860-861) and fix it. The line on this example is too long by a shade over 9pt. The chosen hyphenation point which minimizes the error is shown at the end of the line (Win-). Line numbers and page numbers are given as before. In this case, 9pt is too much to ignore (over 3mm), and a manual correction needs making (such as a change to the hyphenation), or the flexibility settings need changing.

-

If the "overfull" word includes a forward slash, such as "input/output", this should be properly typeset as "input\slash output". The use of \slash has the same effect as using the "/" character, except that it can form the end of a line (with the following words appearing at the start of the next line). The "/" character is typically used in units, such as "mm/year" character, which should not be broken over multiple lines.

-

The warning can also be issued when the \end{document} tag was not included or was deleted.

+

If the "overfull" word includes a forward slash, such as "input/output", this should be properly typeset as "input\\slash output". The use of \\slash has the same effect as using the "/" character, except that it can form the end of a line (with the following words appearing at the start of the next line). The "/" character is typically used in units, such as "mm/year" character, which should not be broken over multiple lines.

+

The warning can also be issued when the \\end{document} tag was not included or was deleted.

""" , regexToMatch: /LaTeX Error: File .* not found/ - humanReadableMessage: """ + extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Missing_package" + humanReadableHint: """

When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. However, if you get the name right, but the package is not installed on your machine, you will need to download and install it before continuing. If you don't want to affect the global installation of the machine, you can simply download from Internet the necessary .sty file and put it in the same folder of the document you are compiling.

""" ] \ No newline at end of file From 2bfe0d0ba9659903d565734f3b057f89fdc68453 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Fri, 17 Jun 2016 14:16:38 +0100 Subject: [PATCH 06/17] Basic mark-up for hint card. --- services/web/app/views/project/editor/pdf.jade | 5 +++-- services/web/public/stylesheets/app/editor/pdf.less | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index f1d2e232c5..79b328eada 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -105,8 +105,9 @@ div.full-size.pdf(ng-controller="PdfController") span(ng-show="entry.file") {{ entry.file }} span(ng-show="entry.line") , line {{ entry.line }} p.entry-message(ng-show="entry.message") {{ entry.message }} - div - i.fa.fa-hand-o-right(aria-hidden="true") + .card.card-hint + figure.card-hint-icon-container + i.fa.fa-lightbulb-o(aria-hidden="true") p.entry-content(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") a(ng-href="{{ entry.extraInfoURL }}", target="_blank") #{translate("log_entry_extra_info")} p.entry-content(ng-show="entry.content") {{ entry.content }} diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 6e813d5eaf..b9b71cc268 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -177,4 +177,8 @@ .keyboard-shortcut { white-space: nowrap; +} + +.card-hint:extend(.card-thin) { + margin-top: 10px; } \ No newline at end of file From 605bb07ba3414d6963f9b5c5c7098fe56e54c779 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 09:45:14 +0100 Subject: [PATCH 07/17] Hint card styling. --- .../web/app/views/project/editor/pdf.jade | 7 +++-- .../HumanReadableLogsRules.coffee | 20 +++++--------- .../public/stylesheets/app/editor/pdf.less | 27 +++++++++++++++++++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 79b328eada..63add3e559 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -108,8 +108,11 @@ div.full-size.pdf(ng-controller="PdfController") .card.card-hint figure.card-hint-icon-container i.fa.fa-lightbulb-o(aria-hidden="true") - p.entry-content(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") - a(ng-href="{{ entry.extraInfoURL }}", target="_blank") #{translate("log_entry_extra_info")} + p.card-hint-text(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") + .clearfix + a.btn.btn-sm.btn-default.pull-right(ng-href="{{ entry.extraInfoURL }}", target="_blank") + i.fa.fa-external-link + | #{translate("log_entry_extra_info")} p.entry-content(ng-show="entry.content") {{ entry.content }} p diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index b0121b6c61..16293b0387 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -2,48 +2,42 @@ define -> [ regexToMatch: /Too many }'s/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Too_many_.7D.27s" humanReadableHint: """ -

The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). In fact, there are other things which can follow the \\date command apart from a date in curly braces, so LaTeX cannot possibly guess that you've missed out the opening curly brace until it finds a closing one!

+ The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). """ , regexToMatch: /Undefined control sequence/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Undefined_control_sequence" humanReadableHint: """ -

In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. Mistypings are the most common source of errors. Some editors allow common commands and environments to be inserted using drop-down menus or icons, which may be used to avoid these errors.

+ In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. """ , regexToMatch: /Missing \$ inserted/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Not_in_Mathematics_Mode" humanReadableHint: """ -

A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. If you did not intend to use mathematics mode, then perhaps you are trying to use a special character that needs to be entered in a different way; for example _ will be interpreted as a subscript operator in mathematics mode, and you need \\_ to get an underscore character.

-

This can also happen if you use the wrong character encoding, for example using utf8 without "\\usepackage[utf8]{inputenc}" or using iso8859-1 without "\\usepackage[latin1]{inputenc}", there are several character encoding formats, make sure to pick the right one.

+ A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. """ , regexToMatch: /Runaway argument/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Runaway_argument" humanReadableHint: """ -

In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! As \\maketitle creates new paragraphs on the title page, this is detected and LaTeX complains that the previous paragraph has ended but \\date is not yet finished.

+ In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! """ , regexToMatch: /Underfull \\hbox/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Underfull_hbox" humanReadableHint: """ -

This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). It says what lines of your file it was typesetting when it found this, and the number in square brackets is the number of the page onto which the offending line was printed. The codes separated by slashes are the typeface and font style and size used in the line. Ignore them for the moment.

-

This comes up if you force a linebreak, e.g., \\\\, and have a return before it. Normally TeX ignores linebreaks, providing full paragraphs to ragged text. In this case it is necessary to pull the linebreak up one line to the end of the previous sentence.

-

This warning may also appear when inserting images. It can be avoided by using the \\textwidth or possibly \\linewidth options, e.g. \\includegraphics[width=\\textwidth]{image_name}

+ This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). """ , regexToMatch: /Overfull \\hbox/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Overfull_hbox" humanReadableHint: """ -

An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge.

-

The warning is given so that you can find the line in the code that originates the problem (in this case: 860-861) and fix it. The line on this example is too long by a shade over 9pt. The chosen hyphenation point which minimizes the error is shown at the end of the line (Win-). Line numbers and page numbers are given as before. In this case, 9pt is too much to ignore (over 3mm), and a manual correction needs making (such as a change to the hyphenation), or the flexibility settings need changing.

-

If the "overfull" word includes a forward slash, such as "input/output", this should be properly typeset as "input\\slash output". The use of \\slash has the same effect as using the "/" character, except that it can form the end of a line (with the following words appearing at the start of the next line). The "/" character is typically used in units, such as "mm/year" character, which should not be broken over multiple lines.

-

The warning can also be issued when the \\end{document} tag was not included or was deleted.

+ An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge. """ , regexToMatch: /LaTeX Error: File .* not found/ extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Missing_package" humanReadableHint: """ -

When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. However, if you get the name right, but the package is not installed on your machine, you will need to download and install it before continuing. If you don't want to affect the global installation of the machine, you can simply download from Internet the necessary .sty file and put it in the same folder of the document you are compiling.

+ When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. """ ] \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index b9b71cc268..25416e0aa0 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -181,4 +181,31 @@ .card-hint:extend(.card-thin) { margin-top: 10px; + + &-icon-container { + background: currentColor; + width: 2.5rem; + height: 2.5rem; + font-size: 1.5rem; + text-align: center; + border-radius: 50%; + float: left; + margin-right: 10px; + + .fa { + color: #FFF; + } + } + + &-text { + color: @gray-dark; + } + + & + p { + margin-top: 20px; + } + + a { + + } } \ No newline at end of file From 1893ae68a9397c80b902eb28df430894adae2573 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 11:53:10 +0100 Subject: [PATCH 08/17] Add feedback widget for log hints. --- .../web/app/views/project/editor/pdf.jade | 14 +++++++--- .../public/stylesheets/app/editor/pdf.less | 26 ++++++++++++++++++- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 63add3e559..192bdf71d0 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -109,10 +109,16 @@ div.full-size.pdf(ng-controller="PdfController") figure.card-hint-icon-container i.fa.fa-lightbulb-o(aria-hidden="true") p.card-hint-text(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") - .clearfix - a.btn.btn-sm.btn-default.pull-right(ng-href="{{ entry.extraInfoURL }}", target="_blank") - i.fa.fa-external-link - | #{translate("log_entry_extra_info")} + .card-hint-actions + .card-hint-action-ext-link + a.btn.btn-sm.btn-default(ng-href="{{ entry.extraInfoURL }}", target="_blank") + i.fa.fa-external-link + | #{translate("log_hint_extra_info")} + .card-hint-action-feedback + label.card-hint-enquiry-label #{translate("log_hint_enquiry_helpful")}  + .btn-group.btn-group-sm(role="group") + button.btn.btn-default #{translate("answer_yes")} + button.btn.btn-default #{translate("answer_no")} p.entry-content(ng-show="entry.content") {{ entry.content }} p diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 25416e0aa0..2fa851c190 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -197,10 +197,34 @@ } } - &-text { + &-text, + &-enquiry-label { color: @gray-dark; + font-size: 0.9rem; + margin-bottom: 30px; } + &-enquiry-label { + font-size: 0.8rem; + margin-bottom: 0; + margin-right: 0.5em; + font-weight: normal; + } + + &-actions { + + } + + &-action-ext-link, + &-action-feedback { + display: inline-block; + } + + &-action-feedback { + float: right; + } + + & + p { margin-top: 20px; } From a8976fd4dc5bc3da51744a527d442df072287b65 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 14:42:36 +0100 Subject: [PATCH 09/17] Use plain links instead of buttons. --- .../web/app/views/project/editor/pdf.jade | 9 +- .../HumanReadableLogsRules.coffee | 82 +++++++++---------- .../public/stylesheets/app/editor/pdf.less | 27 ++++-- 3 files changed, 65 insertions(+), 53 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 192bdf71d0..6b24e240e7 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -111,14 +111,13 @@ div.full-size.pdf(ng-controller="PdfController") p.card-hint-text(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") .card-hint-actions .card-hint-action-ext-link - a.btn.btn-sm.btn-default(ng-href="{{ entry.extraInfoURL }}", target="_blank") + a(ng-href="{{ entry.extraInfoURL }}", target="_blank") i.fa.fa-external-link | #{translate("log_hint_extra_info")} .card-hint-action-feedback - label.card-hint-enquiry-label #{translate("log_hint_enquiry_helpful")}  - .btn-group.btn-group-sm(role="group") - button.btn.btn-default #{translate("answer_yes")} - button.btn.btn-default #{translate("answer_no")} + label.card-hint-enquiry-label #{translate("log_hint_enquiry_helpful")} + a #{translate("answer_yes")} + a #{translate("answer_no")} p.entry-content(ng-show="entry.content") {{ entry.content }} p diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index 16293b0387..95967fbbce 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -1,43 +1,43 @@ define -> [ - regexToMatch: /Too many }'s/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Too_many_.7D.27s" - humanReadableHint: """ - The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). - """ - , - regexToMatch: /Undefined control sequence/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Undefined_control_sequence" - humanReadableHint: """ - In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. - """ - , - regexToMatch: /Missing \$ inserted/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Not_in_Mathematics_Mode" - humanReadableHint: """ - A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. - """ - , - regexToMatch: /Runaway argument/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Runaway_argument" - humanReadableHint: """ - In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! - """ - , - regexToMatch: /Underfull \\hbox/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Underfull_hbox" - humanReadableHint: """ - This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). - """ - , - regexToMatch: /Overfull \\hbox/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Overfull_hbox" - humanReadableHint: """ - An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge. - """ - , - regexToMatch: /LaTeX Error: File .* not found/ - extraInfoURL: "https://en.wikibooks.org/wiki/LaTeX/Errors_and_Warnings#Missing_package" - humanReadableHint: """ - When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. - """ + regexToMatch: /Too many }'s/ + extraInfoURL: "" + humanReadableHint: """ + The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). + """ + , + regexToMatch: /Undefined control sequence/ + extraInfoURL: "/learn/Errors:Undefined_control_sequence." + humanReadableHint: """ + In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. + """ + , + regexToMatch: /Missing \$ inserted/ + extraInfoURL: "/learn/Errors:Missing_$_inserted" + humanReadableHint: """ + A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. + """ + , + regexToMatch: /Runaway argument/ + extraInfoURL: "/learn/Errors:Undefined_control_sequence." + humanReadableHint: """ + In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! + """ + , + regexToMatch: /Underfull \\hbox/ + extraInfoURL: "/learn/Errors:Underfull_%5Chbox" + humanReadableHint: """ + This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). + """ + , + regexToMatch: /Overfull \\hbox/ + extraInfoURL: "" + humanReadableHint: """ + An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge. + """ + , + regexToMatch: /LaTeX Error: File .* not found/ + extraInfoURL: "/learn/Errors:missing_package" + humanReadableHint: """ + When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. + """ ] \ No newline at end of file diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 2fa851c190..cdb994c1b2 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -195,6 +195,19 @@ .fa { color: #FFF; } + + .alert-danger & { + color: @alert-danger-border; + } + + .alert-warning & { + color: @alert-warning-border; + } + + .alert-info & { + color: @alert-info-border; + } + } &-text, @@ -205,9 +218,8 @@ } &-enquiry-label { - font-size: 0.8rem; + font-size: inherit; margin-bottom: 0; - margin-right: 0.5em; font-weight: normal; } @@ -218,18 +230,19 @@ &-action-ext-link, &-action-feedback { display: inline-block; + font-size: 0.8rem; } &-action-feedback { float: right; + + a { + margin-left: 0.5em; + } } & + p { margin-top: 20px; } - - a { - - } -} \ No newline at end of file +} From baea017adb0d3833f7b19465246ef3a32f407682 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 14:59:30 +0100 Subject: [PATCH 10/17] Better mark-up for analytics tracking. --- services/web/app/views/project/editor/pdf.jade | 10 +++++----- services/web/public/stylesheets/app/editor/pdf.less | 11 +++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 6b24e240e7..b65c766952 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -110,14 +110,14 @@ div.full-size.pdf(ng-controller="PdfController") i.fa.fa-lightbulb-o(aria-hidden="true") p.card-hint-text(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") .card-hint-actions - .card-hint-action-ext-link + .card-hint-ext-link a(ng-href="{{ entry.extraInfoURL }}", target="_blank") i.fa.fa-external-link | #{translate("log_hint_extra_info")} - .card-hint-action-feedback - label.card-hint-enquiry-label #{translate("log_hint_enquiry_helpful")} - a #{translate("answer_yes")} - a #{translate("answer_no")} + .card-hint-feedback + label.card-hint-feedback-label #{translate("log_hint_enquiry_helpful")} + a.card-hint-feedback-positive #{translate("answer_yes")} + a.card-hint-feedback-negative #{translate("answer_no")} p.entry-content(ng-show="entry.content") {{ entry.content }} p diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index cdb994c1b2..19a92fa3ce 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -211,13 +211,13 @@ } &-text, - &-enquiry-label { + &-feedback-label { color: @gray-dark; font-size: 0.9rem; margin-bottom: 30px; } - &-enquiry-label { + &-feedback-label { font-size: inherit; margin-bottom: 0; font-weight: normal; @@ -227,13 +227,13 @@ } - &-action-ext-link, - &-action-feedback { + &-ext-link, + &-feedback { display: inline-block; font-size: 0.8rem; } - &-action-feedback { + &-feedback { float: right; a { @@ -241,7 +241,6 @@ } } - & + p { margin-top: 20px; } From fe53083afd89168cfb41e69f5652f600e7db0a15 Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 15:04:47 +0100 Subject: [PATCH 11/17] Show thank you note after receiving feedback. --- services/web/app/views/project/editor/pdf.jade | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index b65c766952..5fa6ec84ef 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -100,6 +100,7 @@ div.full-size.pdf(ng-controller="PdfController") 'alert-info': entry.level == 'typesetting'\ }" ng-click="openInEditor(entry)" + ng-init="feedbackSent = false;" ) span.line-no span(ng-show="entry.file") {{ entry.file }} @@ -114,10 +115,16 @@ div.full-size.pdf(ng-controller="PdfController") a(ng-href="{{ entry.extraInfoURL }}", target="_blank") i.fa.fa-external-link | #{translate("log_hint_extra_info")} - .card-hint-feedback + .card-hint-feedback(ng-show="!feedbackSent") label.card-hint-feedback-label #{translate("log_hint_enquiry_helpful")} - a.card-hint-feedback-positive #{translate("answer_yes")} - a.card-hint-feedback-negative #{translate("answer_no")} + a.card-hint-feedback-positive( + ng-click="feedbackSent = true;" + ) #{translate("answer_yes")} + a.card-hint-feedback-negative( + ng-click="feedbackSent = true;" + ) #{translate("answer_no")} + .card-hint-feedback(ng-show="feedbackSent") + label.card-hint-feedback-label #{translate("log_hint_enquiry_gratitude")} p.entry-content(ng-show="entry.content") {{ entry.content }} p From d8e0026bc6d2637237a644eea18d3a4b9b3eee8c Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Mon, 20 Jun 2016 17:32:57 +0100 Subject: [PATCH 12/17] Add class to enable per-hint feedback tracking. --- services/web/app/views/project/editor/pdf.jade | 9 ++++++--- .../ide/human-readable-logs/HumanReadableLogs.coffee | 3 ++- services/web/public/stylesheets/app/editor/pdf.less | 4 ---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 5fa6ec84ef..dab38e670d 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -115,8 +115,11 @@ div.full-size.pdf(ng-controller="PdfController") a(ng-href="{{ entry.extraInfoURL }}", target="_blank") i.fa.fa-external-link | #{translate("log_hint_extra_info")} - .card-hint-feedback(ng-show="!feedbackSent") - label.card-hint-feedback-label #{translate("log_hint_enquiry_helpful")} + .card-hint-feedback( + ng-show="!feedbackSent" + ng-class="entry.ruleId" + ) + label.card-hint-feedback-label #{translate("log_hint_feedback_label")} a.card-hint-feedback-positive( ng-click="feedbackSent = true;" ) #{translate("answer_yes")} @@ -124,7 +127,7 @@ div.full-size.pdf(ng-controller="PdfController") ng-click="feedbackSent = true;" ) #{translate("answer_no")} .card-hint-feedback(ng-show="feedbackSent") - label.card-hint-feedback-label #{translate("log_hint_enquiry_gratitude")} + label.card-hint-feedback-label #{translate("log_hint_feedback_gratitude")} p.entry-content(ng-show="entry.content") {{ entry.content }} p diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee index 5f4627cb95..6c5ef03bea 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -9,7 +9,8 @@ define [ return rule for rule in ruleset when rule.regexToMatch.test logMessage for entry in parsedLogEntries.all - { humanReadableHint, extraInfoURL } = _getRule entry.message + { regexToMatch, humanReadableHint, extraInfoURL } = _getRule entry.message + entry.ruleId = 'hint_' + regexToMatch.toString().replace(/[^a-zA-Z0-9]/g, '_').toLowerCase() entry.humanReadableHint = humanReadableHint if humanReadableHint? entry.extraInfoURL = extraInfoURL if extraInfoURL? diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 19a92fa3ce..27d943000b 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -223,10 +223,6 @@ font-weight: normal; } - &-actions { - - } - &-ext-link, &-feedback { display: inline-block; From 1aa7d9c6b377448839c33bc56b01f60cbfcbc35a Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 21 Jun 2016 10:49:24 +0100 Subject: [PATCH 13/17] Explicitly set link color according to log entry type. --- .../web/public/stylesheets/app/editor/pdf.less | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 27d943000b..1ca40d5179 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -229,6 +229,20 @@ font-size: 0.8rem; } + &-actions a { + .alert-danger & { + color: @alert-danger-text; + } + + .alert-warning & { + color: @alert-warning-text; + } + + .alert-info & { + color: @alert-info-text; + } + } + &-feedback { float: right; From e607de0b9143b131cc292eac2e2a37f74b3333bf Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 21 Jun 2016 12:07:47 +0100 Subject: [PATCH 14/17] Safety checks for non-existing values; avoid rendering the hint template when there is no hint. --- .../web/app/views/project/editor/pdf.jade | 2 +- .../HumanReadableLogs.coffee | 10 ++- .../HumanReadableLogsRules.coffee | 84 +++++++++---------- 3 files changed, 49 insertions(+), 47 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index dab38e670d..618b83bbed 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -106,7 +106,7 @@ div.full-size.pdf(ng-controller="PdfController") span(ng-show="entry.file") {{ entry.file }} span(ng-show="entry.line") , line {{ entry.line }} p.entry-message(ng-show="entry.message") {{ entry.message }} - .card.card-hint + .card.card-hint(ng-if="entry.humanReadableHint") figure.card-hint-icon-container i.fa.fa-lightbulb-o(aria-hidden="true") p.card-hint-text(ng-show="entry.humanReadableHint", ng-bind-html="entry.humanReadableHint") diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee index 6c5ef03bea..31fc11b6a3 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee @@ -9,9 +9,11 @@ define [ return rule for rule in ruleset when rule.regexToMatch.test logMessage for entry in parsedLogEntries.all - { regexToMatch, humanReadableHint, extraInfoURL } = _getRule entry.message - entry.ruleId = 'hint_' + regexToMatch.toString().replace(/[^a-zA-Z0-9]/g, '_').toLowerCase() - entry.humanReadableHint = humanReadableHint if humanReadableHint? - entry.extraInfoURL = extraInfoURL if extraInfoURL? + ruleDetails = _getRule entry.message + if (ruleDetails?) + entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/[^a-zA-Z0-9]/g, '_').toLowerCase() if ruleDetails.regexToMatch? + entry.humanReadableHint = ruleDetails.humanReadableHint if ruleDetails.humanReadableHint? + entry.extraInfoURL = ruleDetails.extraInfoURL if ruleDetails.extraInfoURL? + return parsedLogEntries diff --git a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee index 95967fbbce..a94e756244 100644 --- a/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee +++ b/services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee @@ -1,43 +1,43 @@ define -> [ - regexToMatch: /Too many }'s/ - extraInfoURL: "" - humanReadableHint: """ - The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). - """ - , - regexToMatch: /Undefined control sequence/ - extraInfoURL: "/learn/Errors:Undefined_control_sequence." - humanReadableHint: """ - In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. - """ - , - regexToMatch: /Missing \$ inserted/ - extraInfoURL: "/learn/Errors:Missing_$_inserted" - humanReadableHint: """ - A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. - """ - , - regexToMatch: /Runaway argument/ - extraInfoURL: "/learn/Errors:Undefined_control_sequence." - humanReadableHint: """ - In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! - """ - , - regexToMatch: /Underfull \\hbox/ - extraInfoURL: "/learn/Errors:Underfull_%5Chbox" - humanReadableHint: """ - This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). - """ - , - regexToMatch: /Overfull \\hbox/ - extraInfoURL: "" - humanReadableHint: """ - An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge. - """ - , - regexToMatch: /LaTeX Error: File .* not found/ - extraInfoURL: "/learn/Errors:missing_package" - humanReadableHint: """ - When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. - """ -] \ No newline at end of file + # regexToMatch: /Too many }'s/ + # extraInfoURL: "" + # humanReadableHint: """ + # The reason LaTeX thinks there are too many }'s here is that the opening curly brace is missing after the \\date control sequence and before the word December, so the closing curly brace is seen as one too many (which it is!). + # """ + # , + # regexToMatch: /Undefined control sequence/ + # extraInfoURL: "/learn/Errors:Undefined_control_sequence." + # humanReadableHint: """ + # In this example, LaTeX is complaining that it has no such command ("control sequence") as \\dtae. Obviously it's been mistyped, but only a human can detect that fact: all LaTeX knows is that \\dtae is not a command it knows about: it's undefined. + # """ + # , + # regexToMatch: /Missing \$ inserted/ + # extraInfoURL: "/learn/Errors:Missing_$_inserted" + # humanReadableHint: """ + # A character that can only be used in the mathematics was inserted in normal text. If you intended to use mathematics mode, then use $...$ or \\begin{math}...\\end{math} or use the 'quick math mode': \\ensuremath{...}. + # """ + # , + # regexToMatch: /Runaway argument/ + # extraInfoURL: "/learn/Errors:Undefined_control_sequence." + # humanReadableHint: """ + # In this error, the closing curly brace has been omitted from the date. It's the opposite of the error of too many }'s, and it results in \\maketitle trying to format the title page while LaTeX is still expecting more text for the date! + # """ + # , + # regexToMatch: /Underfull \\hbox/ + # extraInfoURL: "/learn/Errors:Underfull_%5Chbox" + # humanReadableHint: """ + # This is a warning that LaTeX cannot stretch the line wide enough to fit, without making the spacing bigger than its currently permitted maximum. The badness (0-10,000) indicates how severe this is (here you can probably ignore a badness of 1394). + # """ + # , + # regexToMatch: /Overfull \\hbox/ + # extraInfoURL: "" + # humanReadableHint: """ + # An overfull \\hbox means that there is a hyphenation or justification problem: moving the last word on the line to the next line would make the spaces in the line wider than the current limit; keeping the word on the line would make the spaces smaller than the current limit, so the word is left on the line, but with the minimum allowed space between words, and which makes the line go over the edge. + # """ + # , + # regexToMatch: /LaTeX Error: File .* not found/ + # extraInfoURL: "/learn/Errors:missing_package" + # humanReadableHint: """ + # When you use the \\usepackage command to request LaTeX to use a certain package, it will look for a file with the specified name and the filetype .sty. In this case the user has mistyped the name of the paralist package, so it's easy to fix. + # """ +] From ed659fd9738cbe857c7abc51e4a799a4f79b583d Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Tue, 21 Jun 2016 14:20:23 +0100 Subject: [PATCH 15/17] Minor UI improvements. --- services/web/app/views/project/editor/pdf.jade | 1 + services/web/public/stylesheets/app/editor/pdf.less | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 618b83bbed..34c9ece719 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -123,6 +123,7 @@ div.full-size.pdf(ng-controller="PdfController") a.card-hint-feedback-positive( ng-click="feedbackSent = true;" ) #{translate("answer_yes")} + span  /  a.card-hint-feedback-negative( ng-click="feedbackSent = true;" ) #{translate("answer_no")} diff --git a/services/web/public/stylesheets/app/editor/pdf.less b/services/web/public/stylesheets/app/editor/pdf.less index 1ca40d5179..f77d497eb0 100644 --- a/services/web/public/stylesheets/app/editor/pdf.less +++ b/services/web/public/stylesheets/app/editor/pdf.less @@ -214,11 +214,12 @@ &-feedback-label { color: @gray-dark; font-size: 0.9rem; - margin-bottom: 30px; + margin-bottom: 20px; } &-feedback-label { font-size: inherit; + margin-right: 0.5em; margin-bottom: 0; font-weight: normal; } @@ -242,13 +243,10 @@ color: @alert-info-text; } } - - &-feedback { - float: right; - a { - margin-left: 0.5em; - } + &-feedback { + color: @gray-dark; + float: right; } & + p { From 594ec9cbdd255daa6e5adf553891ba4aa582508d Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 22 Jun 2016 08:00:25 +0100 Subject: [PATCH 16/17] Revert "minify algolia and moment.js" This reverts commit f32a92b3c2b6c321ea7e7339110c644f9caa790c. --- .../public/coffee/filters/formatDate.coffee | 2 +- services/web/public/js/libs/algolia-2.5.2.js | 2042 ++++++++++- services/web/public/js/libs/moment-2.9.0.js | 3042 ++++++++++++++++- 3 files changed, 5077 insertions(+), 9 deletions(-) diff --git a/services/web/public/coffee/filters/formatDate.coffee b/services/web/public/coffee/filters/formatDate.coffee index fa1734b6be..395194456e 100644 --- a/services/web/public/coffee/filters/formatDate.coffee +++ b/services/web/public/coffee/filters/formatDate.coffee @@ -2,7 +2,7 @@ define [ "base" "libs/moment-2.9.0" ], (App, moment) -> - moment.updateLocale "en", calendar: + moment.locale "en", calendar: lastDay : '[Yesterday]' sameDay : '[Today]' nextDay : '[Tomorrow]' diff --git a/services/web/public/js/libs/algolia-2.5.2.js b/services/web/public/js/libs/algolia-2.5.2.js index de02c26a9f..fead438491 100644 --- a/services/web/public/js/libs/algolia-2.5.2.js +++ b/services/web/public/js/libs/algolia-2.5.2.js @@ -1,7 +1,2039 @@ -/*! - * algoliasearch 2.5.2 - * https://github.com/algolia/algoliasearch-client-js - * Copyright 2014 Algolia SAS; Licensed MIT +/* + * Copyright (c) 2013 Algolia + * http://www.algolia.com/ + * + * 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. */ -function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i.5&&this.hosts.reverse(),this.hosts.push(this._isUndefined(c)||null==c?("https:"==document.location.protocol?"https":"http")+"://"+e[g]:"https"===c||"HTTPS"===c?"https://"+e[g]:"http://"+e[g]);Math.random()>.5&&this.hosts.reverse(),this.jsonp=null,this.jsonpWait=0,this._jsonRequest({method:"GET",url:"/1/isalive",callback:function(a){f.jsonp=!a}}),this.extraHeaders=[]};AlgoliaSearch.prototype={deleteIndex:function(a,b){this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(a),callback:b})},moveIndex:function(a,b,c){var d={operation:"move",destination:b};this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},copyIndex:function(a,b,c){var d={operation:"copy",destination:b};this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},getLogs:function(a,b,c){this._isUndefined(b)&&(b=0),this._isUndefined(c)&&(c=10),this._jsonRequest({method:"GET",url:"/1/logs?offset="+b+"&length="+c,callback:a})},listIndexes:function(a){this._jsonRequest({method:"GET",url:"/1/indexes",callback:a})},initIndex:function(a){return new this.Index(this,a)},listUserKeys:function(a){this._jsonRequest({method:"GET",url:"/1/keys",callback:a})},getUserKeyACL:function(a,b){this._jsonRequest({method:"GET",url:"/1/keys/"+a,callback:b})},deleteUserKey:function(a,b){this._jsonRequest({method:"DELETE",url:"/1/keys/"+a,callback:b})},addUserKey:function(a,b){var c={};c.acl=a,this._jsonRequest({method:"POST",url:"/1/keys",body:c,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this._jsonRequest({method:"POST",url:"/1/indexes/"+f.indexName+"/keys",body:g,callback:e})},setSecurityTags:function(a){if("[object Array]"===Object.prototype.toString.call(a)){for(var b=[],c=0;c0){var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f}else this._sendQueriesBatch(d,a)},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null==this.jsonp){var c=this;return void this._waitReady(function(){c._sendQueriesBatch(a,b)})}if(this.jsonp){for(var d="",e=0;e2e3&&(this.jsonp=!0),setTimeout(a,100))},_getSearchParams:function(a,b){if(this._isUndefined(a)||null==a)return b;for(var c in a)null!=c&&a.hasOwnProperty(c)&&(b+=0===b.length?"?":"&",b+=c+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(a[c])?JSON.stringify(a[c]):a[c]));return b},_isUndefined:function(a){return void 0===a},applicationID:null,apiKey:null,tagFilters:null,userToken:null,hosts:[],cache:{},extraHeaders:[]},AlgoliaSearch.prototype.Index.prototype={clearCache:function(){this.cache={}},addObject:function(a,b,c){var d=this;this.as._jsonRequest(this.as._isUndefined(c)?{method:"POST",url:"/1/indexes/"+encodeURIComponent(d.indexName),body:a,callback:b}:{method:"PUT",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(c),body:a,callback:b})},addObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e0){var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g}else this._search(f,b)},browse:function(a,b,c){var d=this,e="?page="+a;_.isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){a&&d(b.hits)},a)}},waitTask:function(a,b){var c=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){if(null==this.as.jsonp){var c=this;return void this.as._waitReady(function(){c._search(a,b)})}var d={params:a,apiKey:this.as.apiKey,appID:this.as.applicationID};this.as.tagFilters&&(d["X-Algolia-TagFilters"]=this.as.tagFilters),this.as.userToken&&(d["X-Algolia-UserToken"]=this.as.userToken),this.as._jsonRequest(this.as.jsonp?{cache:this.cache,method:"GET",jsonp:!0,url:"/1/indexes/"+encodeURIComponent(this.indexName),body:d,callback:b}:{cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:d,callback:b})},as:null,indexName:null,cache:{},typeAheadArgs:null,typeAheadValueOption:null,emptyConstructor:function(){}},function(){var a=function(a){a=a||{};for(var b=1;b0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());for(var a=0;a0&&b.push(e)}return b}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(); \ No newline at end of file +var ALGOLIA_VERSION = '2.5.2'; + +/* + * Copyright (c) 2013 Algolia + * http://www.algolia.com/ + * + * 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. + */ + +/* + * Algolia Search library initialization + * @param applicationID the application ID you have in your admin interface + * @param apiKey a valid API key for the service + * @param method specify if the protocol used is http or https (http by default to make the first search query faster). + * You need to use https is you are doing something else than just search queries. + * @param resolveDNS let you disable first empty query that is launch to warmup the service + * @param hostsArray (optionnal) the list of hosts that you have received for the service + */ +var AlgoliaSearch = function(applicationID, apiKey, method, resolveDNS, hostsArray) { + var self = this; + this.applicationID = applicationID; + this.apiKey = apiKey; + + if (this._isUndefined(hostsArray)) { + hostsArray = [applicationID + '-1.algolia.io', + applicationID + '-2.algolia.io', + applicationID + '-3.algolia.io']; + } + this.hosts = []; + // Add hosts in random order + for (var i = 0; i < hostsArray.length; ++i) { + if (Math.random() > 0.5) { + this.hosts.reverse(); + } + if (this._isUndefined(method) || method == null) { + this.hosts.push(('https:' == document.location.protocol ? 'https' : 'http') + '://' + hostsArray[i]); + } else if (method === 'https' || method === 'HTTPS') { + this.hosts.push('https://' + hostsArray[i]); + } else { + this.hosts.push('http://' + hostsArray[i]); + } + } + if (Math.random() > 0.5) { + this.hosts.reverse(); + } + + // resolve DNS + check CORS support (JSONP fallback) + this.jsonp = null; + this.jsonpWait = 0; + this._jsonRequest({ + method: 'GET', + url: '/1/isalive', + callback: function(success, content) { + self.jsonp = !success; + } + }); + this.extraHeaders = []; +}; + +function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) { + + function _getHitExplanationForOneAttr_recurse(obj, foundWords) { + var res = []; + if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) { + var match = false; + for (var j = 0; j < obj.matchedWords.length; ++j) { + var word = obj.matchedWords[j]; + if (!(word in foundWords)) { + foundWords[word] = 1; + match = true; + } + } + if (match) { + res.push(obj.value); + } + } else if (Object.prototype.toString.call(obj) === '[object Array]') { + for (var i = 0; i < obj.length; ++i) { + var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords); + res = res.concat(array); + } + } else if (typeof obj === 'object') { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)){ + res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords)); + } + } + } + return res; + } + + function _getHitExplanationForOneAttr(hit, foundWords, attr) { + var base = hit._highlightResult || hit; + if (attr.indexOf('.') === -1) { + if (attr in base) { + return _getHitExplanationForOneAttr_recurse(base[attr], foundWords); + } + return []; + } + var array = attr.split('.'); + var obj = base; + for (var i = 0; i < array.length; ++i) { + if (Object.prototype.toString.call(obj) === '[object Array]') { + var res = []; + for (var j = 0; j < obj.length; ++j) { + res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.'))); + } + return res; + } + if (array[i] in obj) { + obj = obj[array[i]]; + } else { + return []; + } + } + return _getHitExplanationForOneAttr_recurse(obj, foundWords); + } + + var res = {}; + var foundWords = {}; + var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute); + res.title = (title.length > 0) ? title[0] : ''; + res.subtitles = []; + + if (typeof otherAttributes !== 'undefined') { + for (var i = 0; i < otherAttributes.length; ++i) { + var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]); + for (var j = 0; j < attr.length; ++j) { + res.subtitles.push({ attr: otherAttributes[i], value: attr[j] }); + } + } + } + return res; +} + + +AlgoliaSearch.prototype = { + /* + * Delete an index + * + * @param indexName the name of index to delete + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + deleteIndex: function(indexName, callback) { + this._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexName), + callback: callback }); + }, + /** + * Move an existing index. + * @param srcIndexName the name of index to copy. + * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + moveIndex: function(srcIndexName, dstIndexName, callback) { + var postObj = {operation: 'move', destination: dstIndexName}; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', + body: postObj, + callback: callback }); + + }, + /** + * Copy an existing index. + * @param srcIndexName the name of index to copy. + * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + copyIndex: function(srcIndexName, dstIndexName, callback) { + var postObj = {operation: 'copy', destination: dstIndexName}; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', + body: postObj, + callback: callback }); + }, + /** + * Return last log entries. + * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry). + * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer that contains the task ID + */ + getLogs: function(callback, offset, length) { + if (this._isUndefined(offset)) { + offset = 0; + } + if (this._isUndefined(length)) { + length = 10; + } + + this._jsonRequest({ method: 'GET', + url: '/1/logs?offset=' + offset + '&length=' + length, + callback: callback }); + }, + /* + * List all existing indexes + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with index list or error description if success is false. + */ + listIndexes: function(callback) { + this._jsonRequest({ method: 'GET', + url: '/1/indexes', + callback: callback }); + }, + + /* + * Get the index object initialized + * + * @param indexName the name of index + * @param callback the result callback with one argument (the Index instance) + */ + initIndex: function(indexName) { + return new this.Index(this, indexName); + }, + /* + * List all existing user keys with their associated ACLs + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + listUserKeys: function(callback) { + this._jsonRequest({ method: 'GET', + url: '/1/keys', + callback: callback }); + }, + /* + * Get ACL of a user key + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + getUserKeyACL: function(key, callback) { + this._jsonRequest({ method: 'GET', + url: '/1/keys/' + key, + callback: callback }); + }, + /* + * Delete an existing user key + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + deleteUserKey: function(key, callback) { + this._jsonRequest({ method: 'DELETE', + url: '/1/keys/' + key, + callback: callback }); + }, + /* + * Add an existing user key + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKey: function(acls, callback) { + var aclsObject = {}; + aclsObject.acl = acls; + this._jsonRequest({ method: 'POST', + url: '/1/keys', + body: aclsObject, + callback: callback }); + }, + /* + * Add an existing user key + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) + * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. + * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + aclsObject.validity = validity; + aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; + aclsObject.maxHitsPerQuery = maxHitsPerQuery; + this._jsonRequest({ method: 'POST', + url: '/1/indexes/' + indexObj.indexName + '/keys', + body: aclsObject, + callback: callback }); + }, + + /** + * Set the extra security tagFilters header + * @param {string|array} tags The list of tags defining the current security filters + */ + setSecurityTags: function(tags) { + if (Object.prototype.toString.call(tags) === '[object Array]') { + var strTags = []; + for (var i = 0; i < tags.length; ++i) { + if (Object.prototype.toString.call(tags[i]) === '[object Array]') { + var oredTags = []; + for (var j = 0; j < tags[i].length; ++j) { + oredTags.push(tags[i][j]); + } + strTags.push('(' + oredTags.join(',') + ')'); + } else { + strTags.push(tags[i]); + } + } + tags = strTags.join(','); + } + this.tagFilters = tags; + }, + + /** + * Set the extra user token header + * @param {string} userToken The token identifying a uniq user (used to apply rate limits) + */ + setUserToken: function(userToken) { + this.userToken = userToken; + }, + + /* + * Initialize a new batch of search queries + */ + startQueriesBatch: function() { + this.batch = []; + }, + /* + * Add a search query in the batch + * + * @param query the full text query + * @param args (optional) if set, contains an object with query parameters: + * - attributes: an array of object attribute names to retrieve + * (if not set all attributes are retrieve) + * - attributesToHighlight: an array of object attribute names to highlight + * (if not set indexed attributes are highlighted) + * - minWordSizefor1Typo: the minimum number of characters to accept one typo. + * Defaults to 3. + * - minWordSizefor2Typos: the minimum number of characters to accept two typos. + * Defaults to 7. + * - getRankingInfo: if set, the result hits will contain ranking information in + * _rankingInfo attribute + * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0. + * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10. + */ + addQueryInBatch: function(indexName, query, args) { + var params = 'query=' + encodeURIComponent(query); + if (!this._isUndefined(args) && args != null) { + params = this._getSearchParams(args, params); + } + this.batch.push({ indexName: indexName, params: params }); + }, + /* + * Clear all queries in cache + */ + clearCache: function() { + this.cache = {}; + }, + /* + * Launch the batch of queries using XMLHttpRequest. + * (Optimized for browser using a POST query to minimize number of OPTIONS queries) + * + * @param callback the function that will receive results + * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime. + */ + sendQueriesBatch: function(callback, delay) { + var as = this; + var params = {requests: [], apiKey: this.apiKey, appID: this.applicationID}; + if (this.userToken) { + params['X-Algolia-UserToken'] = this.userToken; + } + if (this.tagFilters) { + params['X-Algolia-TagFilters'] = this.tagFilters; + } + for (var i = 0; i < as.batch.length; ++i) { + params.requests.push(as.batch[i]); + } + window.clearTimeout(as.onDelayTrigger); + if (!this._isUndefined(delay) && delay != null && delay > 0) { + var onDelayTrigger = window.setTimeout( function() { + as._sendQueriesBatch(params, callback); + }, delay); + as.onDelayTrigger = onDelayTrigger; + } else { + this._sendQueriesBatch(params, callback); + } + }, + /* + * Index class constructor. + * You should not use this method directly but use initIndex() function + */ + Index: function(algoliasearch, indexName) { + this.indexName = indexName; + this.as = algoliasearch; + this.typeAheadArgs = null; + this.typeAheadValueOption = null; + }, + + setExtraHeader: function(key, value) { + this.extraHeaders.push({ key: key, value: value}); + }, + + _sendQueriesBatch: function(params, callback) { + if (this.jsonp == null) { + var self = this; + this._waitReady(function() { self._sendQueriesBatch(params, callback); }); + return; + } + if (this.jsonp) { + var jsonpParams = ''; + for (var i = 0; i < params.requests.length; ++i) { + var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params; + jsonpParams += i + '=' + encodeURIComponent(q) + '&'; + } + this._jsonRequest({ cache: this.cache, + method: 'GET', jsonp: true, + url: '/1/indexes/*', + body: { params: jsonpParams }, + callback: callback }); + } else { + this._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/*/queries', + body: params, + callback: callback }); + } + }, + /* + * Wrapper that try all hosts to maximize the quality of service + */ + _jsonRequest: function(opts) { + var self = this; + var callback = opts.callback; + var cache = null; + var cacheID = opts.url; + if (!this._isUndefined(opts.body)) { + cacheID = opts.url + '_body_' + JSON.stringify(opts.body); + } + if (!this._isUndefined(opts.cache)) { + cache = opts.cache; + if (!this._isUndefined(cache[cacheID])) { + if (!this._isUndefined(callback)) { + setTimeout(function () { callback(true, cache[cacheID]); }, 1); + } + return; + } + } + + var impl = function(position) { + var idx = 0; + if (!self._isUndefined(position)) { + idx = position; + } + if (self.hosts.length <= idx) { + if (!self._isUndefined(callback)) { + callback(false, { message: 'Cannot contact server'}); + } + return; + } + opts.callback = function(retry, success, res, body) { + if (!success && !self._isUndefined(body)) { + if (window.console) { console.log('Error: ' + body.message); } + } + if (success && !self._isUndefined(opts.cache)) { + cache[cacheID] = body; + } + if (!success && retry && (idx + 1) < self.hosts.length) { + impl(idx + 1); + } else { + if (!self._isUndefined(callback)) { + callback(success, body); + } + } + }; + opts.hostname = self.hosts[idx]; + self._jsonRequestByHost(opts); + }; + impl(); + }, + + _jsonRequestByHost: function(opts) { + var self = this; + var url = opts.hostname + opts.url; + + if (this.jsonp) { + if (!opts.jsonp) { + opts.callback(true, false, null, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' }); + return; + } + this.jsonpCounter = this.jsonpCounter || 0; + this.jsonpCounter += 1; + var cb = 'algoliaJSONP_' + this.jsonpCounter; + window[cb] = function(data) { + opts.callback(false, true, null, data); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + }; + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url + '?callback=' + cb + ',' + this.applicationID + ',' + this.apiKey; + if (opts['X-Algolia-TagFilters']) { + script.src += '&X-Algolia-TagFilters=' + opts['X-Algolia-TagFilters']; + } + if (opts['X-Algolia-UserToken']) { + script.src += '&X-Algolia-UserToken=' + opts['X-Algolia-UserToken']; + } + if (opts.body && opts.body.params) { + script.src += '&' + opts.body.params; + } + var head = document.getElementsByTagName('head')[0]; + script.onerror = function() { + opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); + head.removeChild(script); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + }; + var done = false; + script.onload = script.onreadystatechange = function() { + if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) { + done = true; + if (typeof window[cb + '_loaded'] === 'undefined') { + opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); + try { delete window[cb]; } catch (e) { window[cb] = undefined; } + } else { + try { delete window[cb + '_loaded']; } catch (e) { window[cb + '_loaded'] = undefined; } + } + script.onload = script.onreadystatechange = null; // Handle memory leak in IE + head.removeChild(script); + } + }; + head.appendChild(script); + } else { + var body = null; + if (!this._isUndefined(opts.body)) { + body = JSON.stringify(opts.body); + } + var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : {}; + if ('withCredentials' in xmlHttp) { + xmlHttp.open(opts.method, url , true); + xmlHttp.setRequestHeader('X-Algolia-API-Key', this.apiKey); + xmlHttp.setRequestHeader('X-Algolia-Application-Id', this.applicationID); + for (var i = 0; i < this.extraHeaders.length; ++i) { + xmlHttp.setRequestHeader(this.extraHeaders[i].key, this.extraHeaders[i].value); + } + if (body != null) { + xmlHttp.setRequestHeader('Content-type', 'application/json'); + } + } else if (typeof XDomainRequest != 'undefined') { + // Handle IE8/IE9 + // XDomainRequest only exists in IE, and is IE's way of making CORS requests. + xmlHttp = new XDomainRequest(); + xmlHttp.open(opts.method, url); + } else { + // very old browser, not supported + if (window.console) { console.log('Your browser is too old to support CORS requests'); } + opts.callback(false, false, null, { 'message': 'CORS not supported' }); + return; + } + xmlHttp.send(body); + xmlHttp.onload = function(event) { + if (!self._isUndefined(event) && event.target != null) { + var retry = (event.target.status === 0 || event.target.status === 503); + var success = (event.target.status === 200 || event.target.status === 201); + opts.callback(retry, success, event.target, event.target.response != null ? JSON.parse(event.target.response) : null); + } else { + opts.callback(false, true, event, JSON.parse(xmlHttp.responseText)); + } + }; + xmlHttp.onerror = function(event) { + opts.callback(true, false, null, { 'message': 'Could not connect to host', 'error': event } ); + }; + } + }, + + /** + * Wait until JSONP flag has been set to perform the first query + */ + _waitReady: function(cb) { + if (this.jsonp == null) { + this.jsonpWait += 100; + if (this.jsonpWait > 2000) { + this.jsonp = true; + } + setTimeout(cb, 100); + } + }, + + /* + * Transform search param object in query string + */ + _getSearchParams: function(args, params) { + if (this._isUndefined(args) || args == null) { + return params; + } + for (var key in args) { + if (key != null && args.hasOwnProperty(key)) { + params += (params.length === 0) ? '?' : '&'; + params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]); + } + } + return params; + }, + _isUndefined: function(obj) { + return obj === void 0; + }, + + /// internal attributes + applicationID: null, + apiKey: null, + tagFilters: null, + userToken: null, + hosts: [], + cache: {}, + extraHeaders: [] +}; + +/* + * Contains all the functions related to one index + * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object + */ +AlgoliaSearch.prototype.Index.prototype = { + /* + * Clear all queries in cache + */ + clearCache: function() { + this.cache = {}; + }, + /* + * Add an object in this index + * + * @param content contains the javascript object to add inside the index + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + * @param objectID (optional) an objectID you want to attribute to this object + * (if the attribute already exist the old object will be overwrite) + */ + addObject: function(content, callback, objectID) { + var indexObj = this; + if (this.as._isUndefined(objectID)) { + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName), + body: content, + callback: callback }); + } else { + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), + body: content, + callback: callback }); + } + + }, + /* + * Add several objects + * + * @param objects contains an array of objects to add + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + addObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'addObject', + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Get an object from this index + * + * @param objectID the unique identifier of the object to retrieve + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the object to retrieve or the error message if a failure occured + * @param attributes (optional) if set, contains the array of attribute names to retrieve + */ + getObject: function(objectID, callback, attributes) { + if (this.as.jsonp == null) { + var self = this; + this.as._waitReady(function() { self.getObject(objectID, callback, attributes); }); + return; + } + var indexObj = this; + var params = ''; + if (!this.as._isUndefined(attributes)) { + params = '?attributes='; + for (var i = 0; i < attributes.length; ++i) { + if (i !== 0) { + params += ','; + } + params += attributes[i]; + } + } + this.as._jsonRequest({ method: 'GET', jsonp: true, + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params, + callback: callback }); + }, + + /* + * Update partially an object (only update attributes passed in argument) + * + * @param partialObject contains the javascript attributes to override, the + * object must contains an objectID attribute + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + */ + partialUpdateObject: function(partialObject, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial', + body: partialObject, + callback: callback }); + }, + /* + * Partially Override the content of several objects + * + * @param objects contains an array of objects to update (each object must contains a objectID attribute) + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + partialUpdateObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'partialUpdateObject', + objectID: objects[i].objectID, + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Override the content of object + * + * @param object contains the javascript object to save, the object must contains an objectID attribute + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + saveObject: function(object, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID), + body: object, + callback: callback }); + }, + /* + * Override the content of several objects + * + * @param objects contains an array of objects to update (each object must contains a objectID attribute) + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that updateAt and taskID + */ + saveObjects: function(objects, callback) { + var indexObj = this; + var postObj = {requests:[]}; + for (var i = 0; i < objects.length; ++i) { + var request = { action: 'updateObject', + objectID: objects[i].objectID, + body: objects[i] }; + postObj.requests.push(request); + } + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', + body: postObj, + callback: callback }); + }, + /* + * Delete an object from the index + * + * @param objectID the unique identifier of object to delete + * @param callback (optional) the result callback with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains 3 elements: createAt, taskId and objectID + */ + deleteObject: function(objectID, callback) { + if (objectID == null || objectID.length === 0) { + callback(false, { message: 'empty objectID'}); + return; + } + var indexObj = this; + this.as._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), + callback: callback }); + }, + /* + * Search inside the index using XMLHttpRequest request (Using a POST query to + * minimize number of OPTIONS queries: Cross-Origin Resource Sharing). + * + * @param query the full text query + * @param callback the result callback with two arguments: + * success: boolean set to true if the request was successfull. If false, the content contains the error. + * content: the server answer that contains the list of results. + * @param args (optional) if set, contains an object with query parameters: + * - page: (integer) Pagination parameter used to select the page to retrieve. + * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 + * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20. + * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size). + * Attributes are separated with a comma (for example "name,address"). + * You can also use a string array encoding (for example ["name","address"]). + * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index. + * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query. + * Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]). + * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted. + * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted. + * A matchLevel is returned for each highlighted attribute and can contain: + * - full: if all the query terms were found in the attribute, + * - partial: if only some of the query terms were found, + * - none: if none of the query terms were found. + * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`). + * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10). + * You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed. + * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3. + * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7. + * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute. + * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma). + * For example aroundLatLng=47.316669,5.016670). + * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision + * (for example if you set aroundPrecision=100, two objects that are distant of less than 100m will be considered as identical for "geo" ranking parameter). + * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) + * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng). + * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201). + * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) + * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma. + * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`. + * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000. + * You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]). + * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas. + * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3). + * You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3). + * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}). + * - facetFilters: filter the query by a list of facets. + * Facets are separated by commas and each facet is encoded as `attributeName:value`. + * For example: `facetFilters=category:Book,author:John%20Doe`. + * You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`). + * - facets: List of object attributes that you want to use for faceting. + * Attributes are separated with a comma (for example `"category,author"` ). + * You can also use a JSON string array encoding (for example ["category","author"]). + * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter. + * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**. + * - queryType: select how the query words are interpreted, it can be one of the following value: + * - prefixAll: all query words are interpreted as prefixes, + * - prefixLast: only the last word is interpreted as a prefix (default behavior), + * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. + * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query. + * The list of words is comma separated. + * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set. + * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter, + * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results. + * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best + * one is kept and others are removed. + * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime. + */ + search: function(query, callback, args, delay) { + var indexObj = this; + var params = 'query=' + encodeURIComponent(query); + if (!this.as._isUndefined(args) && args != null) { + params = this.as._getSearchParams(args, params); + } + window.clearTimeout(indexObj.onDelayTrigger); + if (!this.as._isUndefined(delay) && delay != null && delay > 0) { + var onDelayTrigger = window.setTimeout( function() { + indexObj._search(params, callback); + }, delay); + indexObj.onDelayTrigger = onDelayTrigger; + } else { + this._search(params, callback); + } + }, + + /* + * Browse all index content + * + * @param page Pagination parameter used to select the page to retrieve. + * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 + * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000. + */ + browse: function(page, callback, hitsPerPage) { + var indexObj = this; + var params = '?page=' + page; + if (!_.isUndefined(hitsPerPage)) { + params += '&hitsPerPage=' + hitsPerPage; + } + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params, + callback: callback }); + }, + + /* + * Get a Typeahead.js adapter + * @param searchParams contains an object with query parameters (see search for details) + */ + ttAdapter: function(params) { + var self = this; + return function(query, cb) { + self.search(query, function(success, content) { + if (success) { + cb(content.hits); + } + }, params); + }; + }, + + /* + * Wait the publication of a task on the server. + * All server task are asynchronous and you can check with this method that the task is published. + * + * @param taskID the id of the task returned by server + * @param callback the result callback with with two arguments: + * success: boolean set to true if the request was successfull + * content: the server answer that contains the list of results + */ + waitTask: function(taskID, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID, + callback: function(success, body) { + if (success) { + if (body.status === 'published') { + callback(true, body); + } else { + setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100); + } + } else { + callback(false, body); + } + }}); + }, + + /* + * This function deletes the index content. Settings and index specific API keys are kept untouched. + * + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the settings object or the error message if a failure occured + */ + clearIndex: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear', + callback: callback }); + }, + /* + * Get settings of this index + * + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the settings object or the error message if a failure occured + */ + getSettings: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + callback: callback }); + }, + + /* + * Set settings for this index + * + * @param settigns the settings object that can contains : + * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3). + * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7). + * - hitsPerPage: (integer) the number of hits per page (default = 10). + * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects. + * If set to null, all attributes are retrieved. + * - attributesToHighlight: (array of strings) default list of attributes to highlight. + * If set to null, all indexed attributes are highlighted. + * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords). + * By default no snippet is computed. If set to null, no snippet is computed. + * - attributesToIndex: (array of strings) the list of fields you want to index. + * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results. + * This parameter has two important uses: + * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to + * retrieve it but you don't want to search in the base64 string. + * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of + * the list will be considered more important than matches in attributes further down the list. + * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable + * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"]. + * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting. + * All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting. + * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled + * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results. + * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best one is kept and others are removed. + * - ranking: (array of strings) controls the way results are sorted. + * We have six available criteria: + * - typo: sort according to number of typos, + * - geo: sort according to decreassing distance when performing a geo-location based search, + * - proximity: sort according to the proximity of query words in hits, + * - attribute: sort according to the order of attributes defined by attributesToIndex, + * - exact: + * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others. + * For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV + * show starting by the v letter before it. + * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix). + * - custom: sort according to a user defined formula set in **customRanking** attribute. + * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"] + * - customRanking: (array of strings) lets you specify part of the ranking. + * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator. + * For example `"customRanking" => ["desc(population)", "asc(name)"]` + * - queryType: Select how the query words are interpreted, it can be one of the following value: + * - prefixAll: all query words are interpreted as prefixes, + * - prefixLast: only the last word is interpreted as a prefix (default behavior), + * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. + * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to ""). + * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to ""). + * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query. + * @param callback (optional) the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer or the error message if a failure occured + */ + setSettings: function(settings, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'PUT', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', + body: settings, + callback: callback }); + }, + /* + * List all existing user keys associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + listUserKeys: function(callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + callback: callback }); + }, + /* + * Get ACL of a user key associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + getUserKeyACL: function(key, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'GET', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + callback: callback }); + }, + /* + * Delete an existing user key associated to this index + * + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + deleteUserKey: function(key, callback) { + var indexObj = this; + this.as._jsonRequest({ method: 'DELETE', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, + callback: callback }); + }, + /* + * Add an existing user key associated to this index + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKey: function(acls, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + body: aclsObject, + callback: callback }); + }, + /* + * Add an existing user key associated to this index + * + * @param acls the list of ACL for this key. Defined by an array of strings that + * can contains the following values: + * - search: allow to search (https and http) + * - addObject: allows to add/update an object in the index (https only) + * - deleteObject : allows to delete an existing object (https only) + * - deleteIndex : allows to delete index content (https only) + * - settings : allows to get index settings (https only) + * - editSettings : allows to change index settings (https only) + * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) + * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. + * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. + * @param callback the result callback with two arguments + * success: boolean set to true if the request was successfull + * content: the server answer with user keys list or error description if success is false. + */ + addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { + var indexObj = this; + var aclsObject = {}; + aclsObject.acl = acls; + aclsObject.validity = validity; + aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; + aclsObject.maxHitsPerQuery = maxHitsPerQuery; + this.as._jsonRequest({ method: 'POST', + url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', + body: aclsObject, + callback: callback }); + }, + /// + /// Internal methods only after this line + /// + _search: function(params, callback) { + if (this.as.jsonp == null) { + var self = this; + this.as._waitReady(function() { self._search(params, callback); }); + return; + } + var pObj = {params: params, apiKey: this.as.apiKey, appID: this.as.applicationID}; + if (this.as.tagFilters) { + pObj['X-Algolia-TagFilters'] = this.as.tagFilters; + } + if (this.as.userToken) { + pObj['X-Algolia-UserToken'] = this.as.userToken; + } + if (this.as.jsonp) { + this.as._jsonRequest({ cache: this.cache, + method: 'GET', jsonp: true, + url: '/1/indexes/' + encodeURIComponent(this.indexName), + body: pObj, + callback: callback }); + } else { + this.as._jsonRequest({ cache: this.cache, + method: 'POST', + url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', + body: pObj, + callback: callback }); + } + }, + + // internal attributes + as: null, + indexName: null, + cache: {}, + typeAheadArgs: null, + typeAheadValueOption: null, + emptyConstructor: function() {} +}; + +/* + * Copyright (c) 2014 Algolia + * http://www.algolia.com/ + * + * 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. + */ + +(function($) { + var extend = function(out) { + out = out || {}; + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) { + continue; + } + for (var key in arguments[i]) { + if (arguments[i].hasOwnProperty(key)) { + out[key] = arguments[i][key]; + } + } + } + return out; + }; + + /** + * Algolia Search Helper providing faceting and disjunctive faceting + * @param {AlgoliaSearch} client an AlgoliaSearch client + * @param {string} index the index name to query + * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets + */ + window.AlgoliaSearchHelper = function(client, index, options) { + /// Default options + var defaults = { + facets: [], // list of facets to compute + disjunctiveFacets: [], // list of disjunctive facets to compute + hitsPerPage: 20 // number of hits per page + }; + + this.init(client, index, extend({}, defaults, options)); + }; + + AlgoliaSearchHelper.prototype = { + /** + * Initialize a new AlgoliaSearchHelper + * @param {AlgoliaSearch} client an AlgoliaSearch client + * @param {string} index the index name to query + * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets + * @return {AlgoliaSearchHelper} + */ + init: function(client, index, options) { + this.client = client; + this.index = index; + this.options = options; + this.page = 0; + this.refinements = {}; + this.disjunctiveRefinements = {}; + }, + + /** + * Perform a query + * @param {string} q the user query + * @param {function} searchCallback the result callback called with two arguments: + * success: boolean set to true if the request was successfull + * content: the query answer with an extra 'disjunctiveFacets' attribute + */ + search: function(q, searchCallback, searchParams) { + this.q = q; + this.searchCallback = searchCallback; + this.searchParams = searchParams || {}; + this.page = this.page || 0; + this.refinements = this.refinements || {}; + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this._search(); + }, + + /** + * Remove all refinements (disjunctive + conjunctive) + */ + clearRefinements: function() { + this.disjunctiveRefinements = {}; + this.refinements = {}; + }, + + /** + * Ensure a facet refinement exists + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + addDisjunctiveRefine: function(facet, value) { + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + this.disjunctiveRefinements[facet][value] = true; + }, + + /** + * Ensure a facet refinement does not exist + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + removeDisjunctiveRefine: function(facet, value) { + this.disjunctiveRefinements = this.disjunctiveRefinements || {}; + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + try { + delete this.disjunctiveRefinements[facet][value]; + } catch (e) { + this.disjunctiveRefinements[facet][value] = undefined; // IE compat + } + }, + + /** + * Ensure a facet refinement exists + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + addRefine: function(facet, value) { + var refinement = facet + ':' + value; + this.refinements = this.refinements || {}; + this.refinements[refinement] = true; + }, + + /** + * Ensure a facet refinement does not exist + * @param {string} facet the facet to refine + * @param {string} value the associated value + */ + removeRefine: function(facet, value) { + var refinement = facet + ':' + value; + this.refinements = this.refinements || {}; + this.refinements[refinement] = false; + }, + + /** + * Toggle refinement state of a facet + * @param {string} facet the facet to refine + * @param {string} value the associated value + * @return {boolean} true if the facet has been found + */ + toggleRefine: function(facet, value) { + for (var i = 0; i < this.options.facets.length; ++i) { + if (this.options.facets[i] == facet) { + var refinement = facet + ':' + value; + this.refinements[refinement] = !this.refinements[refinement]; + this.page = 0; + this._search(); + return true; + } + } + this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; + for (var j = 0; j < this.options.disjunctiveFacets.length; ++j) { + if (this.options.disjunctiveFacets[j] == facet) { + this.disjunctiveRefinements[facet][value] = !this.disjunctiveRefinements[facet][value]; + this.page = 0; + this._search(); + return true; + } + } + return false; + }, + + /** + * Check the refinement state of a facet + * @param {string} facet the facet + * @param {string} value the associated value + * @return {boolean} true if refined + */ + isRefined: function(facet, value) { + var refinement = facet + ':' + value; + if (this.refinements[refinement]) { + return true; + } + if (this.disjunctiveRefinements[facet] && this.disjunctiveRefinements[facet][value]) { + return true; + } + return false; + }, + + /** + * Go to next page + */ + nextPage: function() { + this._gotoPage(this.page + 1); + }, + + /** + * Go to previous page + */ + previousPage: function() { + if (this.page > 0) { + this._gotoPage(this.page - 1); + } + }, + + /** + * Goto a page + * @param {integer} page The page number + */ + gotoPage: function(page) { + this._gotoPage(page); + }, + + /** + * Configure the page but do not trigger a reload + * @param {integer} page The page number + */ + setPage: function(page) { + this.page = page; + }, + + ///////////// PRIVATE + + /** + * Goto a page + * @param {integer} page The page number + */ + _gotoPage: function(page) { + this.page = page; + this._search(); + }, + + /** + * Perform the underlying queries + */ + _search: function() { + this.client.startQueriesBatch(); + this.client.addQueryInBatch(this.index, this.q, this._getHitsSearchParams()); + for (var i = 0; i < this.options.disjunctiveFacets.length; ++i) { + this.client.addQueryInBatch(this.index, this.q, this._getDisjunctiveFacetSearchParams(this.options.disjunctiveFacets[i])); + } + var self = this; + this.client.sendQueriesBatch(function(success, content) { + if (!success) { + self.searchCallback(false, content); + return; + } + var aggregatedAnswer = content.results[0]; + aggregatedAnswer.disjunctiveFacets = {}; + aggregatedAnswer.facetStats = {}; + for (var i = 1; i < content.results.length; ++i) { + for (var facet in content.results[i].facets) { + aggregatedAnswer.disjunctiveFacets[facet] = content.results[i].facets[facet]; + if (self.disjunctiveRefinements[facet]) { + for (var value in self.disjunctiveRefinements[facet]) { + if (!aggregatedAnswer.disjunctiveFacets[facet][value] && self.disjunctiveRefinements[facet][value]) { + aggregatedAnswer.disjunctiveFacets[facet][value] = 0; + } + } + } + } + for (var stats in content.results[i].facets_stats) + { + aggregatedAnswer.facetStats[stats] = content.results[i].facets_stats[stats]; + } + } + self.searchCallback(true, aggregatedAnswer); + }); + }, + + /** + * Build search parameters used to fetch hits + * @return {hash} + */ + _getHitsSearchParams: function() { + return extend({}, { + hitsPerPage: this.options.hitsPerPage, + page: this.page, + facets: this.options.facets, + facetFilters: this._getFacetFilters() + }, this.searchParams); + }, + + /** + * Build search parameters used to fetch a disjunctive facet + * @param {string} facet the associated facet name + * @return {hash} + */ + _getDisjunctiveFacetSearchParams: function(facet) { + return extend({}, this.searchParams, { + hitsPerPage: 1, + page: 0, + facets: facet, + facetFilters: this._getFacetFilters(facet) + }); + }, + + /** + * Build facetFilters parameter based on current refinements + * @param {string} facet if set, the current disjunctive facet + * @return {hash} + */ + _getFacetFilters: function(facet) { + var facetFilters = []; + for (var refinement in this.refinements) { + if (this.refinements[refinement]) { + facetFilters.push(refinement); + } + } + for (var disjunctiveRefinement in this.disjunctiveRefinements) { + if (disjunctiveRefinement != facet) { + var refinements = []; + for (var value in this.disjunctiveRefinements[disjunctiveRefinement]) { + if (this.disjunctiveRefinements[disjunctiveRefinement][value]) { + refinements.push(disjunctiveRefinement + ':' + value); + } + } + if (refinements.length > 0) { + facetFilters.push(refinements); + } + } + } + return facetFilters; + } + }; +})(); + +/* + json2.js + 2014-02-04 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== 'object') { + JSON = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function () { + return this.valueOf(); + }; + } + + var cx, + escapable, + gap, + indent, + meta, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); \ No newline at end of file diff --git a/services/web/public/js/libs/moment-2.9.0.js b/services/web/public/js/libs/moment-2.9.0.js index d301ddbbe8..d98958d75a 100755 --- a/services/web/public/js/libs/moment-2.9.0.js +++ b/services/web/public/js/libs/moment-2.9.0.js @@ -1,7 +1,3043 @@ //! moment.js -//! version : 2.13.0 +//! version : 2.9.0 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com -!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return fd.apply(null,arguments)}function b(a){fd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in hd)d=hd[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),id===!1&&(id=!0,a.updateOffset(this),id=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function u(b,c){var d=!0;return g(function(){return null!=a.deprecationHandler&&a.deprecationHandler(null,b),d&&(t(b+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),d=!1),c.apply(this,arguments)},c)}function v(b,c){null!=a.deprecationHandler&&a.deprecationHandler(b,c),jd[b]||(t(c),jd[b]=!0)}function w(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function x(a){return"[object Object]"===Object.prototype.toString.call(a)}function y(a){var b,c;for(c in a)b=a[c],w(b)?this[c]=b:this["_"+c]=b;this._config=a,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function z(a,b){var c,d=g({},a);for(c in b)f(b,c)&&(x(a[c])&&x(b[c])?(d[c]={},g(d[c],a[c]),g(d[c],b[c])):null!=b[c]?d[c]=b[c]:delete d[c]);return d}function A(a){null!=a&&this.set(a)}function B(a){return a?a.toLowerCase().replace("_","-"):a}function C(a){for(var b,c,d,e,f=0;f0;){if(d=D(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function D(a){var b=null;if(!nd[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=ld._abbr,require("./locale/"+a),E(b)}catch(c){}return nd[a]}function E(a,b){var c;return a&&(c=m(b)?H(a):F(a,b),c&&(ld=c)),ld._abbr}function F(a,b){return null!==b?(b.abbr=a,null!=nd[a]?(v("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),b=z(nd[a]._config,b)):null!=b.parentLocale&&(null!=nd[b.parentLocale]?b=z(nd[b.parentLocale]._config,b):v("parentLocaleUndefined","specified parentLocale is not defined yet")),nd[a]=new A(b),E(a),nd[a]):(delete nd[a],null)}function G(a,b){if(null!=b){var c;null!=nd[a]&&(b=z(nd[a]._config,b)),c=new A(b),c.parentLocale=nd[a],nd[a]=c,E(a)}else null!=nd[a]&&(null!=nd[a].parentLocale?nd[a]=nd[a].parentLocale:null!=nd[a]&&delete nd[a]);return nd[a]}function H(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return ld;if(!c(a)){if(b=D(a))return b;a=[a]}return C(a)}function I(){return kd(nd)}function J(a,b){var c=a.toLowerCase();od[c]=od[c+"s"]=od[b]=a}function K(a){return"string"==typeof a?od[a]||od[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)f(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(b,c){return function(d){return null!=d?(O(this,b,d),a.updateOffset(this,c),this):N(this,b)}}function N(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function O(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function P(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=K(a),w(this[a]))return this[a](b);return this}function Q(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function R(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(sd[a]=e),b&&(sd[b[0]]=function(){return Q(e.apply(this,arguments),b[1],b[2])}),c&&(sd[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function S(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function T(a){var b,c,d=a.match(pd);for(b=0,c=d.length;c>b;b++)sd[d[b]]?d[b]=sd[d[b]]:d[b]=S(d[b]);return function(b){var e,f="";for(e=0;c>e;e++)f+=d[e]instanceof Function?d[e].call(b,a):d[e];return f}}function U(a,b){return a.isValid()?(b=V(b,a.localeData()),rd[b]=rd[b]||T(b),rd[b](a)):a.localeData().invalidDate()}function V(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(qd.lastIndex=0;d>=0&&qd.test(a);)a=a.replace(qd,c),qd.lastIndex=0,d-=1;return a}function W(a,b,c){Kd[a]=w(b)?b:function(a,d){return a&&c?c:b}}function X(a,b){return f(Kd,a)?Kd[a](b._strict,b._locale):new RegExp(Y(a))}function Y(a){return Z(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function Z(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function $(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;cd;++d)f=h([2e3,d]),this._shortMonthsParse[d]=this.monthsShort(f,"").toLocaleLowerCase(),this._longMonthsParse[d]=this.months(f,"").toLocaleLowerCase();return c?"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:null):(e=md.call(this._longMonthsParse,g),-1!==e?e:null):"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:(e=md.call(this._longMonthsParse,g),-1!==e?e:null)):(e=md.call(this._longMonthsParse,g),-1!==e?e:(e=md.call(this._shortMonthsParse,g),-1!==e?e:null))}function fa(a,b,c){var d,e,f;if(this._monthsParseExact)return ea.call(this,a,b,c);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function ga(a,b){var c;if(!a.isValid())return a;if("string"==typeof b)if(/^\d+$/.test(b))b=r(b);else if(b=a.localeData().monthsParse(b),"number"!=typeof b)return a;return c=Math.min(a.date(),ba(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a}function ha(b){return null!=b?(ga(this,b),a.updateOffset(this,!0),this):N(this,"Month")}function ia(){return ba(this.year(),this.month())}function ja(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex}function ka(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex}function la(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;12>b;b++)c=h([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;12>b;b++)d[b]=Z(d[b]),e[b]=Z(e[b]),f[b]=Z(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")","i")}function ma(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[Nd]<0||c[Nd]>11?Nd:c[Od]<1||c[Od]>ba(c[Md],c[Nd])?Od:c[Pd]<0||c[Pd]>24||24===c[Pd]&&(0!==c[Qd]||0!==c[Rd]||0!==c[Sd])?Pd:c[Qd]<0||c[Qd]>59?Qd:c[Rd]<0||c[Rd]>59?Rd:c[Sd]<0||c[Sd]>999?Sd:-1,j(a)._overflowDayOfYear&&(Md>b||b>Od)&&(b=Od),j(a)._overflowWeeks&&-1===b&&(b=Td),j(a)._overflowWeekday&&-1===b&&(b=Ud),j(a).overflow=b),a}function na(a){var b,c,d,e,f,g,h=a._i,i=$d.exec(h)||_d.exec(h);if(i){for(j(a).iso=!0,b=0,c=be.length;c>b;b++)if(be[b][1].exec(i[1])){e=be[b][0],d=be[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=ce.length;c>b;b++)if(ce[b][1].exec(i[3])){f=(i[2]||" ")+ce[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!ae.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),Ca(a)}else a._isValid=!1}function oa(b){var c=de.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(na(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function pa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function qa(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ra(a){return sa(a)?366:365}function sa(a){return a%4===0&&a%100!==0||a%400===0}function ta(){return sa(this.year())}function ua(a,b,c){var d=7+b-c,e=(7+qa(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=ra(f)+j):j>ra(a)?(f=a+1,g=j-ra(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(ra(a)-d+e)/7}function ya(a,b,c){return null!=a?a:null!=b?b:c}function za(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function Aa(a){var b,c,d,e,f=[];if(!a._d){for(d=za(a),a._w&&null==a._a[Od]&&null==a._a[Nd]&&Ba(a),a._dayOfYear&&(e=ya(a._a[Md],d[Md]),a._dayOfYear>ra(e)&&(j(a)._overflowDayOfYear=!0),c=qa(e,0,a._dayOfYear),a._a[Nd]=c.getUTCMonth(),a._a[Od]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[Pd]&&0===a._a[Qd]&&0===a._a[Rd]&&0===a._a[Sd]&&(a._nextDay=!0,a._a[Pd]=0),a._d=(a._useUTC?qa:pa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Pd]=24)}}function Ba(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ya(b.GG,a._a[Md],wa(Ka(),1,4).year),d=ya(b.W,1),e=ya(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ya(b.gg,a._a[Md],wa(Ka(),f,g).year),d=ya(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>xa(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[Md]=h.year,a._dayOfYear=h.dayOfYear)}function Ca(b){if(b._f===a.ISO_8601)return void na(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=V(b._f,b._locale).match(pd)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),sd[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),aa(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[Pd]<=12&&b._a[Pd]>0&&(j(b).bigHour=void 0),j(b).parsedDateParts=b._a.slice(0),j(b).meridiem=b._meridiem,b._a[Pd]=Da(b._locale,b._a[Pd],b._meridiem),Aa(b),ma(b)}function Da(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function Ea(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function Fa(a){if(!a._d){var b=L(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),Aa(a)}}function Ga(a){var b=new o(ma(Ha(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Ha(a){var b=a._i,e=a._f;return a._locale=a._locale||H(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(ma(b)):(c(e)?Ea(a):e?Ca(a):d(b)?a._d=b:Ia(a),k(a)||(a._d=null),a))}function Ia(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(f.valueOf()):"string"==typeof f?oa(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),Aa(b)):"object"==typeof f?Fa(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ja(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,Ga(f)}function Ka(a,b,c,d){return Ja(a,b,c,d,!1)}function La(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Ka();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+Q(~~(a/60),2)+b+Q(~~a%60,2)})}function Ra(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(ie)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Sa(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?b.valueOf():Ka(b).valueOf())-e.valueOf(),e._d.setTime(e._d.valueOf()+f),a.updateOffset(e,!1),e):Ka(b).local()}function Ta(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ua(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=Ra(Hd,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ta(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?jb(this,db(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ta(this):null!=b?this:NaN}function Va(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Wa(a){return this.utcOffset(0,a)}function Xa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ta(this),"m")),this}function Ya(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ra(Gd,this._i)),this}function Za(a){return this.isValid()?(a=a?Ka(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function $a(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function _a(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=Ha(a),a._a){var b=a._isUTC?h(a._a):Ka(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function ab(){return this.isValid()?!this._isUTC:!1}function bb(){return this.isValid()?this._isUTC:!1}function cb(){return this.isValid()?this._isUTC&&0===this._offset:!1}function db(a,b){var c,d,e,g=a,h=null;return Pa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=je.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[Od])*c,h:r(h[Pd])*c,m:r(h[Qd])*c,s:r(h[Rd])*c,ms:r(h[Sd])*c}):(h=ke.exec(a))?(c="-"===h[1]?-1:1,g={y:eb(h[2],c),M:eb(h[3],c),w:eb(h[4],c),d:eb(h[5],c),h:eb(h[6],c),m:eb(h[7],c),s:eb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=gb(Ka(g.from),Ka(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Oa(g),Pa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function eb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function fb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function gb(a,b){var c;return a.isValid()&&b.isValid()?(b=Sa(b,a),a.isBefore(b)?c=fb(a,b):(c=fb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function hb(a){return 0>a?-1*Math.round(-1*a):Math.round(a)}function ib(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(v(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=db(c,d),jb(this,e,a),this}}function jb(b,c,d,e){var f=c._milliseconds,g=hb(c._days),h=hb(c._months);b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&O(b,"Date",N(b,"Date")+g*d),h&&ga(b,N(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function kb(a,b){var c=a||Ka(),d=Sa(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(w(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Ka(c)))}function lb(){return new o(this)}function mb(a,b){var c=p(a)?a:Ka(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)||0}function ub(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function vb(){var a=this.clone().utc();return 0f&&(b=f),Vb.call(this,a,b,c,d,e))}function Vb(a,b,c,d,e){var f=va(a,b,c,d,e),g=qa(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Wb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Xb(a){return wa(a,this._week.dow,this._week.doy).week}function Yb(){return this._week.dow}function Zb(){return this._week.doy}function $b(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function _b(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ac(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function bc(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function cc(a){return this._weekdaysShort[a.day()]}function dc(a){return this._weekdaysMin[a.day()]}function ec(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;7>d;++d)f=h([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:null):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null):"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null)))}function fc(a,b,c){var d,e,f;if(this._weekdaysParseExact)return ec.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=h([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function gc(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=ac(a,this.localeData()),this.add(a-b,"d")):b}function hc(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function ic(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function jc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex}function kc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}function lc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}function mc(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],i=[],j=[],k=[];for(b=0;7>b;b++)c=h([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),i.push(e),j.push(f),k.push(d),k.push(e),k.push(f);for(g.sort(a),i.sort(a),j.sort(a),k.sort(a),b=0;7>b;b++)i[b]=Z(i[b]),j[b]=Z(j[b]),k[b]=Z(k[b]);this._weekdaysRegex=new RegExp("^("+k.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function nc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oc(){return this.hours()%12||12}function pc(){return this.hours()||24}function qc(a,b){R(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function rc(a,b){return b._meridiemParse}function sc(a){return"p"===(a+"").toLowerCase().charAt(0)}function tc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function uc(a,b){b[Sd]=r(1e3*("0."+a))}function vc(){return this._isUTC?"UTC":""}function wc(){return this._isUTC?"Coordinated Universal Time":""}function xc(a){return Ka(1e3*a)}function yc(){return Ka.apply(null,arguments).parseZone()}function zc(a,b,c){var d=this._calendar[a];return w(d)?d.call(b,c):d}function Ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function Bc(){return this._invalidDate}function Cc(a){return this._ordinal.replace("%d",a)}function Dc(a){return a}function Ec(a,b,c,d){var e=this._relativeTime[c];return w(e)?e(a,b,c,d):e.replace(/%d/i,a)}function Fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return w(c)?c(b):c.replace(/%s/i,b)}function Gc(a,b,c,d){var e=H(),f=h().set(d,b);return e[c](f,a)}function Hc(a,b,c){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return Gc(a,b,c,"month");var d,e=[];for(d=0;12>d;d++)e[d]=Gc(a,d,c,"month");return e}function Ic(a,b,c,d){"boolean"==typeof a?("number"==typeof b&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,"number"==typeof b&&(c=b,b=void 0),b=b||"");var e=H(),f=a?e._week.dow:0;if(null!=c)return Gc(b,(c+f)%7,d,"day");var g,h=[];for(g=0;7>g;g++)h[g]=Gc(b,(g+f)%7,d,"day");return h}function Jc(a,b){return Hc(a,b,"months")}function Kc(a,b){return Hc(a,b,"monthsShort")}function Lc(a,b,c){return Ic(a,b,c,"weekdays")}function Mc(a,b,c){return Ic(a,b,c,"weekdaysShort")}function Nc(a,b,c){return Ic(a,b,c,"weekdaysMin")}function Oc(){var a=this._data;return this._milliseconds=Le(this._milliseconds),this._days=Le(this._days),this._months=Le(this._months),a.milliseconds=Le(a.milliseconds),a.seconds=Le(a.seconds),a.minutes=Le(a.minutes),a.hours=Le(a.hours),a.months=Le(a.months),a.years=Le(a.years),this}function Pc(a,b,c,d){var e=db(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function Qc(a,b){return Pc(this,a,b,1)}function Rc(a,b){return Pc(this,a,b,-1)}function Sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Sc(Vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Uc(g)),h+=e,g-=Sc(Vc(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Uc(a){return 4800*a/146097}function Vc(a){return 146097*a/4800}function Wc(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Yc(a){return function(){return this.as(a)}}function Zc(a){ -return a=K(a),this[a+"s"]()}function $c(a){return function(){return this._data[a]}}function _c(){return q(this.days()/7)}function ad(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function bd(a,b,c){var d=db(a).abs(),e=_e(d.as("s")),f=_e(d.as("m")),g=_e(d.as("h")),h=_e(d.as("d")),i=_e(d.as("M")),j=_e(d.as("y")),k=e=f&&["m"]||f=g&&["h"]||g=h&&["d"]||h=i&&["M"]||i=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,ad.apply(null,k)}function cd(a,b){return void 0===af[a]?!1:void 0===b?af[a]:(af[a]=b,!0)}function dd(a){var b=this.localeData(),c=bd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function ed(){var a,b,c,d=bf(this._milliseconds)/1e3,e=bf(this._days),f=bf(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var fd,gd;gd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;c>d;d++)if(d in b&&a.call(this,b[d],d,b))return!0;return!1};var hd=a.momentProperties=[],id=!1,jd={};a.suppressDeprecationWarnings=!1,a.deprecationHandler=null;var kd;kd=Object.keys?Object.keys:function(a){var b,c=[];for(b in a)f(a,b)&&c.push(b);return c};var ld,md,nd={},od={},pd=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,qd=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,rd={},sd={},td=/\d/,ud=/\d\d/,vd=/\d{3}/,wd=/\d{4}/,xd=/[+-]?\d{6}/,yd=/\d\d?/,zd=/\d\d\d\d?/,Ad=/\d\d\d\d\d\d?/,Bd=/\d{1,3}/,Cd=/\d{1,4}/,Dd=/[+-]?\d{1,6}/,Ed=/\d+/,Fd=/[+-]?\d+/,Gd=/Z|[+-]\d\d:?\d\d/gi,Hd=/Z|[+-]\d\d(?::?\d\d)?/gi,Id=/[+-]?\d+(\.\d{1,3})?/,Jd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Kd={},Ld={},Md=0,Nd=1,Od=2,Pd=3,Qd=4,Rd=5,Sd=6,Td=7,Ud=8;md=Array.prototype.indexOf?Array.prototype.indexOf:function(a){var b;for(b=0;b=a?""+a:"+"+a}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),J("year","y"),W("Y",Fd),W("YY",yd,ud),W("YYYY",Cd,wd),W("YYYYY",Dd,xd),W("YYYYYY",Dd,xd),$(["YYYYY","YYYYYY"],Md),$("YYYY",function(b,c){c[Md]=2===b.length?a.parseTwoDigitYear(b):r(b)}),$("YY",function(b,c){c[Md]=a.parseTwoDigitYear(b)}),$("Y",function(a,b){b[Md]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var ee=M("FullYear",!0);a.ISO_8601=function(){};var fe=u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),ge=u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),he=function(){return Date.now?Date.now():+new Date};Qa("Z",":"),Qa("ZZ",""),W("Z",Hd),W("ZZ",Hd),$(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ra(Hd,a)});var ie=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var je=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ke=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;db.fn=Oa.prototype;var le=ib(1,"add"),me=ib(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ne=u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Pb("gggg","weekYear"),Pb("ggggg","weekYear"),Pb("GGGG","isoWeekYear"),Pb("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),W("G",Fd),W("g",Fd),W("GG",yd,ud),W("gg",yd,ud),W("GGGG",Cd,wd),W("gggg",Cd,wd),W("GGGGG",Dd,xd),W("ggggg",Dd,xd),_(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),_(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),R("Q",0,"Qo","quarter"),J("quarter","Q"),W("Q",td),$("Q",function(a,b){b[Nd]=3*(r(a)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),W("w",yd),W("ww",yd,ud),W("W",yd),W("WW",yd,ud),_(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var oe={dow:0,doy:6};R("D",["DD",2],"Do","date"),J("date","D"),W("D",yd),W("DD",yd,ud),W("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),$(["D","DD"],Od),$("Do",function(a,b){b[Od]=r(a.match(yd)[0],10)});var pe=M("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),R("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),R("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),W("d",yd),W("e",yd),W("E",yd),W("dd",function(a,b){return b.weekdaysMinRegex(a)}),W("ddd",function(a,b){return b.weekdaysShortRegex(a)}),W("dddd",function(a,b){return b.weekdaysRegex(a)}),_(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),_(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var qe="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),re="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),se="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),te=Jd,ue=Jd,ve=Jd;R("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),W("DDD",Bd),W("DDDD",vd),$(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,oc),R("k",["kk",2],0,pc),R("hmm",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)}),R("hmmss",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)+Q(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+Q(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+Q(this.minutes(),2)+Q(this.seconds(),2)}),qc("a",!0),qc("A",!1),J("hour","h"),W("a",rc),W("A",rc),W("H",yd),W("h",yd),W("HH",yd,ud),W("hh",yd,ud),W("hmm",zd),W("hmmss",Ad),W("Hmm",zd),W("Hmmss",Ad),$(["H","HH"],Pd),$(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),$(["h","hh"],function(a,b,c){b[Pd]=r(a),j(c).bigHour=!0}),$("hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d)),j(c).bigHour=!0}),$("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e)),j(c).bigHour=!0}),$("Hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d))}),$("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e))});var we=/[ap]\.?m?\.?/i,xe=M("Hours",!0);R("m",["mm",2],0,"minute"),J("minute","m"),W("m",yd),W("mm",yd,ud),$(["m","mm"],Qd);var ye=M("Minutes",!1);R("s",["ss",2],0,"second"),J("second","s"),W("s",yd),W("ss",yd,ud),$(["s","ss"],Rd);var ze=M("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),W("S",Bd,td),W("SS",Bd,ud),W("SSS",Bd,vd);var Ae;for(Ae="SSSS";Ae.length<=9;Ae+="S")W(Ae,Ed);for(Ae="S";Ae.length<=9;Ae+="S")$(Ae,uc);var Be=M("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var Ce=o.prototype;Ce.add=le,Ce.calendar=kb,Ce.clone=lb,Ce.diff=sb,Ce.endOf=Eb,Ce.format=wb,Ce.from=xb,Ce.fromNow=yb,Ce.to=zb,Ce.toNow=Ab,Ce.get=P,Ce.invalidAt=Nb,Ce.isAfter=mb,Ce.isBefore=nb,Ce.isBetween=ob,Ce.isSame=pb,Ce.isSameOrAfter=qb,Ce.isSameOrBefore=rb,Ce.isValid=Lb,Ce.lang=ne,Ce.locale=Bb,Ce.localeData=Cb,Ce.max=ge,Ce.min=fe,Ce.parsingFlags=Mb,Ce.set=P,Ce.startOf=Db,Ce.subtract=me,Ce.toArray=Ib,Ce.toObject=Jb,Ce.toDate=Hb,Ce.toISOString=vb,Ce.toJSON=Kb,Ce.toString=ub,Ce.unix=Gb,Ce.valueOf=Fb,Ce.creationData=Ob,Ce.year=ee,Ce.isLeapYear=ta,Ce.weekYear=Qb,Ce.isoWeekYear=Rb,Ce.quarter=Ce.quarters=Wb,Ce.month=ha,Ce.daysInMonth=ia,Ce.week=Ce.weeks=$b,Ce.isoWeek=Ce.isoWeeks=_b,Ce.weeksInYear=Tb,Ce.isoWeeksInYear=Sb,Ce.date=pe,Ce.day=Ce.days=gc,Ce.weekday=hc,Ce.isoWeekday=ic,Ce.dayOfYear=nc,Ce.hour=Ce.hours=xe,Ce.minute=Ce.minutes=ye,Ce.second=Ce.seconds=ze,Ce.millisecond=Ce.milliseconds=Be,Ce.utcOffset=Ua,Ce.utc=Wa,Ce.local=Xa,Ce.parseZone=Ya,Ce.hasAlignedHourOffset=Za,Ce.isDST=$a,Ce.isDSTShifted=_a,Ce.isLocal=ab,Ce.isUtcOffset=bb,Ce.isUtc=cb,Ce.isUTC=cb,Ce.zoneAbbr=vc,Ce.zoneName=wc,Ce.dates=u("dates accessor is deprecated. Use date instead.",pe),Ce.months=u("months accessor is deprecated. Use month instead",ha),Ce.years=u("years accessor is deprecated. Use year instead",ee),Ce.zone=u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Va);var De=Ce,Ee={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Fe={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Ge="Invalid date",He="%d",Ie=/\d{1,2}/,Je={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ke=A.prototype;Ke._calendar=Ee,Ke.calendar=zc,Ke._longDateFormat=Fe,Ke.longDateFormat=Ac,Ke._invalidDate=Ge,Ke.invalidDate=Bc,Ke._ordinal=He,Ke.ordinal=Cc,Ke._ordinalParse=Ie,Ke.preparse=Dc,Ke.postformat=Dc,Ke._relativeTime=Je,Ke.relativeTime=Ec,Ke.pastFuture=Fc,Ke.set=y,Ke.months=ca,Ke._months=Wd,Ke.monthsShort=da,Ke._monthsShort=Xd,Ke.monthsParse=fa,Ke._monthsRegex=Zd,Ke.monthsRegex=ka,Ke._monthsShortRegex=Yd,Ke.monthsShortRegex=ja,Ke.week=Xb,Ke._week=oe,Ke.firstDayOfYear=Zb,Ke.firstDayOfWeek=Yb,Ke.weekdays=bc,Ke._weekdays=qe,Ke.weekdaysMin=dc,Ke._weekdaysMin=se,Ke.weekdaysShort=cc,Ke._weekdaysShort=re,Ke.weekdaysParse=fc,Ke._weekdaysRegex=te,Ke.weekdaysRegex=jc,Ke._weekdaysShortRegex=ue,Ke.weekdaysShortRegex=kc,Ke._weekdaysMinRegex=ve,Ke.weekdaysMinRegex=lc,Ke.isPM=sc,Ke._meridiemParse=we,Ke.meridiem=tc,E("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=u("moment.lang is deprecated. Use moment.locale instead.",E),a.langData=u("moment.langData is deprecated. Use moment.localeData instead.",H);var Le=Math.abs,Me=Yc("ms"),Ne=Yc("s"),Oe=Yc("m"),Pe=Yc("h"),Qe=Yc("d"),Re=Yc("w"),Se=Yc("M"),Te=Yc("y"),Ue=$c("milliseconds"),Ve=$c("seconds"),We=$c("minutes"),Xe=$c("hours"),Ye=$c("days"),Ze=$c("months"),$e=$c("years"),_e=Math.round,af={s:45,m:45,h:22,d:26,M:11},bf=Math.abs,cf=Oa.prototype;cf.abs=Oc,cf.add=Qc,cf.subtract=Rc,cf.as=Wc,cf.asMilliseconds=Me,cf.asSeconds=Ne,cf.asMinutes=Oe,cf.asHours=Pe,cf.asDays=Qe,cf.asWeeks=Re,cf.asMonths=Se,cf.asYears=Te,cf.valueOf=Xc,cf._bubble=Tc,cf.get=Zc,cf.milliseconds=Ue,cf.seconds=Ve,cf.minutes=We,cf.hours=Xe,cf.days=Ye,cf.weeks=_c,cf.months=Ze,cf.years=$e,cf.humanize=dd,cf.toISOString=ed,cf.toString=ed,cf.toJSON=ed,cf.locale=Bb,cf.localeData=Cb,cf.toIsoString=u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ed),cf.lang=ne,R("X",0,0,"unix"),R("x",0,0,"valueOf"),W("x",Fd),W("X",Id),$("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),$("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.13.0",b(Ka),a.fn=De,a.min=Ma,a.max=Na,a.now=he,a.utc=h,a.unix=xc,a.months=Jc,a.isDate=d,a.locale=E,a.invalid=l,a.duration=db,a.isMoment=p,a.weekdays=Lc,a.parseZone=yc,a.localeData=H,a.isDuration=Pa,a.monthsShort=Kc,a.weekdaysMin=Nc,a.defineLocale=F,a.updateLocale=G,a.locales=I,a.weekdaysShort=Mc,a.normalizeUnits=K,a.relativeTimeThreshold=cd,a.prototype=De;var df=a;return df}); \ No newline at end of file + +(function (undefined) { + /************************************ + Constants + ************************************/ + + var moment, + VERSION = '2.9.0', + // the global-scope this is NOT the global object in Node.js + globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this, + oldGlobalMoment, + round = Math.round, + hasOwnProperty = Object.prototype.hasOwnProperty, + i, + + YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + + // internal storage for locale config files + locales = {}, + + // extra moment internal properties (plugins register props here) + momentProperties = [], + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module && module.exports), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenDigits = /\d+/, // nonzero number of digits + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO separator) + parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123 + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + //strict parsing regexes + parseTokenOneDigit = /\d/, // 0 - 9 + parseTokenTwoDigits = /\d\d/, // 00 - 99 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 + parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] + ], + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30'] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + D : 'date', + w : 'week', + W : 'isoWeek', + M : 'month', + Q : 'quarter', + y : 'year', + DDD : 'dayOfYear', + e : 'weekday', + E : 'isoWeekday', + gg: 'weekYear', + GG: 'isoWeekYear' + }, + + camelFunctions = { + dayofyear : 'dayOfYear', + isoweekday : 'isoWeekday', + isoweek : 'isoWeek', + weekyear : 'weekYear', + isoweekyear : 'isoWeekYear' + }, + + // format function strings + formatFunctions = {}, + + // default relative time thresholds + relativeTimeThresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.localeData().monthsShort(this, format); + }, + MMMM : function (format) { + return this.localeData().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.localeData().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.localeData().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.localeData().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + YYYYYY : function () { + var y = this.year(), sign = y >= 0 ? '+' : '-'; + return sign + leftZeroFill(Math.abs(y), 6); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return leftZeroFill(this.weekYear(), 4); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return leftZeroFill(this.isoWeekYear(), 4); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.localeData().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.localeData().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return toInt(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(toInt(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + SSSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = this.utcOffset(), + b = '+'; + if (a < 0) { + a = -a; + b = '-'; + } + return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); + }, + ZZ : function () { + var a = this.utcOffset(), + b = '+'; + if (a < 0) { + a = -a; + b = '-'; + } + return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + x : function () { + return this.valueOf(); + }, + X : function () { + return this.unix(); + }, + Q : function () { + return this.quarter(); + } + }, + + deprecations = {}, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'], + + updateInProgress = false; + + // Pick the first defined of two or three arguments. dfl comes from + // default. + function dfl(a, b, c) { + switch (arguments.length) { + case 2: return a != null ? a : b; + case 3: return a != null ? a : b != null ? b : c; + default: throw new Error('Implement me'); + } + } + + function hasOwnProp(a, b) { + return hasOwnProperty.call(a, b); + } + + function defaultParsingFlags() { + // We need to deep clone this object, and es5 standard is not very + // helpful. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso: false + }; + } + + function printMsg(msg) { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + return extend(function () { + if (firstTime) { + printMsg(msg); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + function deprecateSimple(name, msg) { + if (!deprecations[name]) { + printMsg(msg); + deprecations[name] = true; + } + } + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.localeData().ordinal(func.call(this, a), period); + }; + } + + function monthDiff(a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + return -(wholeMonthDiff + adjust); + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // thie is not supposed to happen + return hour; + } + } + + /************************************ + Constructors + ************************************/ + + function Locale() { + } + + // Moment prototype object + function Moment(config, skipOverflow) { + if (skipOverflow !== false) { + checkOverflow(config); + } + copyConfig(this, config); + this._d = new Date(+config._d); + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + moment.updateOffset(this); + updateInProgress = false; + } + } + + // Duration Constructor + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = moment.localeData(); + + this._bubble(); + } + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function copyConfig(to, from) { + var i, prop, val; + + if (typeof from._isAMomentObject !== 'undefined') { + to._isAMomentObject = from._isAMomentObject; + } + if (typeof from._i !== 'undefined') { + to._i = from._i; + } + if (typeof from._f !== 'undefined') { + to._f = from._f; + } + if (typeof from._l !== 'undefined') { + to._l = from._l; + } + if (typeof from._strict !== 'undefined') { + to._strict = from._strict; + } + if (typeof from._tzm !== 'undefined') { + to._tzm = from._tzm; + } + if (typeof from._isUTC !== 'undefined') { + to._isUTC = from._isUTC; + } + if (typeof from._offset !== 'undefined') { + to._offset = from._offset; + } + if (typeof from._pf !== 'undefined') { + to._pf = from._pf; + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } + } + } + + return to; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + other = makeAs(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = moment.duration(val, period); + addOrSubtractDurationFromMoment(this, dur, direction); + return this; + }; + } + + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + if (days) { + rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); + } + if (months) { + rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); + } + if (updateOffset) { + moment.updateOffset(mom, days || months); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + if (units) { + var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); + units = unitAliases[units] || camelFunctions[lowered] || lowered; + } + return units; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeList(field) { + var count, setter; + + if (field.indexOf('week') === 0) { + count = 7; + setter = 'day'; + } + else if (field.indexOf('month') === 0) { + count = 12; + setter = 'month'; + } + else { + return; + } + + moment[field] = function (format, index) { + var i, getter, + method = moment._locale[field], + results = []; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + getter = function (i) { + var m = moment().utc().set(setter, i); + return method.call(moment._locale, m, format || ''); + }; + + if (index != null) { + return getter(index); + } + else { + for (i = 0; i < count; i++) { + results.push(getter(i)); + } + return results; + } + }; + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + function weeksInYear(year, dow, doy) { + return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; + } + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function checkOverflow(m) { + var overflow; + if (m._a && m._pf.overflow === -2) { + overflow = + m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : + m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : + m._a[HOUR] < 0 || m._a[HOUR] > 24 || + (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 || + m._a[SECOND] !== 0 || + m._a[MILLISECOND] !== 0)) ? HOUR : + m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : + m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : + m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + } + + function isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0 && + m._pf.bigHour === undefined; + } + } + return m._isValid; + } + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + if (!locales[name] && hasModule) { + try { + oldLocale = moment.locale(); + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales + moment.locale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + + // Return a moment from input, that is local/utc/utcOffset equivalent to + // model. + function makeAs(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (moment.isMoment(input) || isDate(input) ? + +input : +moment(input)) - (+res); + // Use low-level api, because this fn is low-level api. + res._d.setTime(+res._d + diff); + moment.updateOffset(res, false); + return res; + } else { + return moment(input).local(); + } + } + + /************************************ + Locale + ************************************/ + + + extend(Locale.prototype, { + + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _ordinalParseLenient. + this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); + }, + + _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName, format, strict) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = moment.utc([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY LT', + LLLL : 'dddd, MMMM D, YYYY LT' + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom, now) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom, [now]) : output; + }, + + _relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace('%d', number); + }, + _ordinal : '%d', + _ordinalParse : /\d{1,2}/, + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }, + + firstDayOfWeek : function () { + return this._week.dow; + }, + + firstDayOfYear : function () { + return this._week.doy; + }, + + _invalidDate: 'Invalid date', + invalidDate: function () { + return this._invalidDate; + } + }); + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ''; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + var a, strict = config._strict; + switch (token) { + case 'Q': + return parseTokenOneDigit; + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + case 'GGGG': + case 'gggg': + return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; + case 'Y': + case 'G': + case 'g': + return parseTokenSignedNumber; + case 'YYYYYY': + case 'YYYYY': + case 'GGGGG': + case 'ggggg': + return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; + case 'S': + if (strict) { + return parseTokenOneDigit; + } + /* falls through */ + case 'SS': + if (strict) { + return parseTokenTwoDigits; + } + /* falls through */ + case 'SSS': + if (strict) { + return parseTokenThreeDigits; + } + /* falls through */ + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return config._locale._meridiemParse; + case 'x': + return parseTokenOffsetMs; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'SSSS': + return parseTokenDigits; + case 'MM': + case 'DD': + case 'YY': + case 'GG': + case 'gg': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'ww': + case 'WW': + return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + case 'w': + case 'W': + case 'e': + case 'E': + return parseTokenOneOrTwoDigits; + case 'Do': + return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient; + default : + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); + return a; + } + } + + function utcOffsetFromString(string) { + string = string || ''; + var possibleTzMatches = (string.match(parseTokenTimezone) || []), + tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], + parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? minutes : -minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // QUARTER + case 'Q': + if (input != null) { + datePartArray[MONTH] = (toInt(input) - 1) * 3; + } + break; + // MONTH + case 'M' : // fall through to MM + case 'MM' : + if (input != null) { + datePartArray[MONTH] = toInt(input) - 1; + } + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[MONTH] = a; + } else { + config._pf.invalidMonth = input; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DD + case 'DD' : + if (input != null) { + datePartArray[DATE] = toInt(input); + } + break; + case 'Do' : + if (input != null) { + datePartArray[DATE] = toInt(parseInt( + input.match(/\d{1,2}/)[0], 10)); + } + break; + // DAY OF YEAR + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + config._dayOfYear = toInt(input); + } + + break; + // YEAR + case 'YY' : + datePartArray[YEAR] = moment.parseTwoDigitYear(input); + break; + case 'YYYY' : + case 'YYYYY' : + case 'YYYYYY' : + datePartArray[YEAR] = toInt(input); + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._meridiem = input; + // config._isPm = config._locale.isPM(input); + break; + // HOUR + case 'h' : // fall through to hh + case 'hh' : + config._pf.bigHour = true; + /* falls through */ + case 'H' : // fall through to HH + case 'HH' : + datePartArray[HOUR] = toInt(input); + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[MINUTE] = toInt(input); + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[SECOND] = toInt(input); + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + case 'SSSS' : + datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); + break; + // UNIX OFFSET (MILLISECONDS) + case 'x': + config._d = new Date(toInt(input)); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = utcOffsetFromString(input); + break; + // WEEKDAY - human + case 'dd': + case 'ddd': + case 'dddd': + a = config._locale.weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (a != null) { + config._w = config._w || {}; + config._w['d'] = a; + } else { + config._pf.invalidWeekday = input; + } + break; + // WEEK, WEEK DAY - numeric + case 'w': + case 'ww': + case 'W': + case 'WW': + case 'd': + case 'e': + case 'E': + token = token.substr(0, 1); + /* falls through */ + case 'gggg': + case 'GGGG': + case 'GGGGG': + token = token.substr(0, 2); + if (input) { + config._w = config._w || {}; + config._w[token] = toInt(input); + } + break; + case 'gg': + case 'GG': + config._w = config._w || {}; + config._w[token] = moment.parseTwoDigitYear(input); + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); + week = dfl(w.W, 1); + weekday = dfl(w.E, 1); + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); + week = dfl(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } + } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromConfig(config) { + var i, date, input = [], currentDate, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = makeUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + } + + function dateFromObject(config) { + var normalizedInput; + + if (config._d) { + return; + } + + normalizedInput = normalizeObjectUnits(config._i); + config._a = [ + normalizedInput.year, + normalizedInput.month, + normalizedInput.day || normalizedInput.date, + normalizedInput.hour, + normalizedInput.minute, + normalizedInput.second, + normalizedInput.millisecond + ]; + + dateFromConfig(config); + } + + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [ + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + ]; + } else { + return [now.getFullYear(), now.getMonth(), now.getDate()]; + } + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + if (config._f === moment.ISO_8601) { + parseISO(config); + return; + } + + config._a = []; + config._pf.empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._pf.bigHour === true && config._a[HOUR] <= 12) { + config._pf.bigHour = undefined; + } + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], + config._meridiem); + dateFromConfig(config); + checkOverflow(config); + } + + function unescapeFormat(s) { + return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function regexpEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + // date from iso format + function parseISO(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be 'T' or undefined + config._f = isoDates[i][0] + (match[6] || ' '); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(parseTokenTimezone)) { + config._f += 'Z'; + } + makeDateFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + // date from iso format or fallback + function makeDateFromString(config) { + parseISO(config); + if (config._isValid === false) { + delete config._isValid; + moment.createFromInputFallback(config); + } + } + + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function makeDateFromInput(config) { + var input = config._i, matched; + if (input === undefined) { + config._d = new Date(); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + dateFromConfig(config); + } else if (typeof(input) === 'object') { + dateFromObject(config); + } else if (typeof(input) === 'number') { + // from milliseconds + config._d = new Date(input); + } else { + moment.createFromInputFallback(config); + } + } + + function makeDate(y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function makeUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + function parseWeekday(input, locale) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = locale.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; + } + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(posNegDuration, withoutSuffix, locale) { + var duration = moment.duration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + years = round(duration.as('y')), + + args = seconds < relativeTimeThresholds.s && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < relativeTimeThresholds.m && ['mm', minutes] || + hours === 1 && ['h'] || + hours < relativeTimeThresholds.h && ['hh', hours] || + days === 1 && ['d'] || + days < relativeTimeThresholds.d && ['dd', days] || + months === 1 && ['M'] || + months < relativeTimeThresholds.M && ['MM', months] || + years === 1 && ['y'] || ['yy', years]; + + args[2] = withoutSuffix; + args[3] = +posNegDuration > 0; + args[4] = locale; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + + d = d === 0 ? 7 : d; + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year: dayOfYear > 0 ? year : year - 1, + dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; + } + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f, + res; + + config._locale = config._locale || moment.localeData(config._l); + + if (input === null || (format === undefined && input === '')) { + return moment.invalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (moment.isMoment(input)) { + return new Moment(input, true); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + res = new Moment(config); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + moment = function (input, format, locale, strict) { + var c; + + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._i = input; + c._f = format; + c._l = locale; + c._strict = strict; + c._isUTC = false; + c._pf = defaultParsingFlags(); + + return makeMoment(c); + }; + + moment.suppressDeprecationWarnings = false; + + moment.createFromInputFallback = deprecate( + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return moment(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + moment.min = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + }; + + moment.max = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + }; + + // creating with utc + moment.utc = function (input, format, locale, strict) { + var c; + + if (typeof(locale) === 'boolean') { + strict = locale; + locale = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._useUTC = true; + c._isUTC = true; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return makeMoment(c).utc(); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + parseIso, + diffRes; + + if (moment.isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoDurationRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + parseIso = function (inp) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + }; + duration = { + y: parseIso(match[2]), + M: parseIso(match[3]), + d: parseIso(match[4]), + h: parseIso(match[5]), + m: parseIso(match[6]), + s: parseIso(match[7]), + w: parseIso(match[8]) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && + ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(moment(duration.from), moment(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (moment.isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // constant that refers to the ISO standard + moment.ISO_8601 = function () {}; + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + moment.momentProperties = momentProperties; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function allows you to set a threshold for relative time strings + moment.relativeTimeThreshold = function (threshold, limit) { + if (relativeTimeThresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return relativeTimeThresholds[threshold]; + } + relativeTimeThresholds[threshold] = limit; + return true; + }; + + moment.lang = deprecate( + 'moment.lang is deprecated. Use moment.locale instead.', + function (key, value) { + return moment.locale(key, value); + } + ); + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + moment.locale = function (key, values) { + var data; + if (key) { + if (typeof(values) !== 'undefined') { + data = moment.defineLocale(key, values); + } + else { + data = moment.localeData(key); + } + + if (data) { + moment.duration._locale = moment._locale = data; + } + } + + return moment._locale._abbr; + }; + + moment.defineLocale = function (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); + + // backwards compat for now: also set the locale + moment.locale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + }; + + moment.langData = deprecate( + 'moment.langData is deprecated. Use moment.localeData instead.', + function (key) { + return moment.localeData(key); + } + ); + + // returns locale data + moment.localeData = function (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return moment._locale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment || + (obj != null && hasOwnProp(obj, '_isAMomentObject')); + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + for (i = lists.length - 1; i >= 0; --i) { + makeList(lists[i]); + } + + moment.normalizeUnits = function (units) { + return normalizeUnits(units); + }; + + moment.invalid = function (flags) { + var m = moment.utc(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; + }; + + moment.parseZone = function () { + return moment.apply(null, arguments).parseZone(); + }; + + moment.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + moment.isDate = isDate; + + /************************************ + Moment Prototype + ************************************/ + + + extend(moment.fn = Moment.prototype, { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d - ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + var m = moment(this).utc(); + if (0 < m.year() && m.year() <= 9999) { + if ('function' === typeof Date.prototype.toISOString) { + // native implementation is ~50x faster, use it when we can + return this.toDate().toISOString(); + } else { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + return isValid(this); + }, + + isDSTShifted : function () { + if (this._a) { + return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; + } + + return false; + }, + + parsingFlags : function () { + return extend({}, this._pf); + }, + + invalidAt: function () { + return this._pf.overflow; + }, + + utc : function (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + }, + + local : function (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(this._dateUtcOffset(), 'm'); + } + } + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.localeData().postformat(output); + }, + + add : createAdder(1, 'add'), + + subtract : createAdder(-1, 'subtract'), + + diff : function (input, units, asFloat) { + var that = makeAs(input, this), + zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4, + anchor, diff, output, daysAdjust; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month' || units === 'quarter') { + output = monthDiff(this, that); + if (units === 'quarter') { + output = output / 3; + } else if (units === 'year') { + output = output / 12; + } + } else { + diff = this - that; + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function (time) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're locat/utc/offset + // or not. + var now = time || moment(), + sod = makeAs(now, this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.localeData().calendar(format, this, moment(now))); + }, + + isLeapYear : function () { + return isLeapYear(this.year()); + }, + + isDST : function () { + return (this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + }, + + month : makeAccessor('Month', true), + + startOf : function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'quarter': + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } else if (units === 'isoWeek') { + this.isoWeekday(1); + } + + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + + return this; + }, + + endOf: function (units) { + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond') { + return this; + } + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); + }, + + isAfter: function (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this > +input; + } else { + inputMs = moment.isMoment(input) ? +input : +moment(input); + return inputMs < +this.clone().startOf(units); + } + }, + + isBefore: function (input, units) { + var inputMs; + units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this < +input; + } else { + inputMs = moment.isMoment(input) ? +input : +moment(input); + return +this.clone().endOf(units) < inputMs; + } + }, + + isBetween: function (from, to, units) { + return this.isAfter(from, units) && this.isBefore(to, units); + }, + + isSame: function (input, units) { + var inputMs; + units = normalizeUnits(units || 'millisecond'); + if (units === 'millisecond') { + input = moment.isMoment(input) ? input : moment(input); + return +this === +input; + } else { + inputMs = +moment(input); + return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); + } + }, + + min: deprecate( + 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + } + ), + + max: deprecate( + 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + } + ), + + zone : deprecate( + 'moment().zone is deprecated, use moment().utcOffset instead. ' + + 'https://github.com/moment/moment/issues/1779', + function (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + ), + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + utcOffset : function (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; + if (input != null) { + if (typeof input === 'string') { + input = utcOffsetFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = this._dateUtcOffset(); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addOrSubtractDurationFromMoment(this, + moment.duration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + moment.updateOffset(this, true); + this._changeInProgress = null; + } + } + + return this; + } else { + return this._isUTC ? offset : this._dateUtcOffset(); + } + }, + + isLocal : function () { + return !this._isUTC; + }, + + isUtcOffset : function () { + return this._isUTC; + }, + + isUtc : function () { + return this._isUTC && this._offset === 0; + }, + + zoneAbbr : function () { + return this._isUTC ? 'UTC' : ''; + }, + + zoneName : function () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + }, + + parseZone : function () { + if (this._tzm) { + this.utcOffset(this._tzm); + } else if (typeof this._i === 'string') { + this.utcOffset(utcOffsetFromString(this._i)); + } + return this; + }, + + hasAlignedHourOffset : function (input) { + if (!input) { + input = 0; + } + else { + input = moment(input).utcOffset(); + } + + return (this.utcOffset() - input) % 60 === 0; + }, + + daysInMonth : function () { + return daysInMonth(this.year(), this.month()); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); + }, + + quarter : function (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; + return input == null ? year : this.add((input - year), 'y'); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add((input - year), 'y'); + }, + + week : function (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + }, + + weekday : function (input) { + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + isoWeeksInYear : function () { + return weeksInYear(this.year(), 1, 4); + }, + + weeksInYear : function () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units](); + }, + + set : function (units, value) { + var unit; + if (typeof units === 'object') { + for (unit in units) { + this.set(unit, units[unit]); + } + } + else { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } + } + return this; + }, + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + locale : function (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = moment.localeData(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + }, + + lang : deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ), + + localeData : function () { + return this._locale; + }, + + _dateUtcOffset : function () { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(this._d.getTimezoneOffset() / 15) * 15; + } + + }); + + function rawMonthSetter(mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), + daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function rawGetter(mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function rawSetter(mom, unit, value) { + if (unit === 'Month') { + return rawMonthSetter(mom, value); + } else { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + function makeAccessor(unit, keepTime) { + return function (value) { + if (value != null) { + rawSetter(this, unit, value); + moment.updateOffset(this, keepTime); + return this; + } else { + return rawGetter(this, unit); + } + }; + } + + moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); + moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); + moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); + // moment.fn.month is defined separately + moment.fn.date = makeAccessor('Date', true); + moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); + moment.fn.year = makeAccessor('FullYear', true); + moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + moment.fn.quarters = moment.fn.quarter; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + // alias isUtc for dev-friendliness + moment.fn.isUTC = moment.fn.isUtc; + + /************************************ + Duration Prototype + ************************************/ + + + function daysToYears (days) { + // 400 years have 146097 days (taking into account leap year rules) + return days * 400 / 146097; + } + + function yearsToDays (years) { + // years * 365 + absRound(years / 4) - + // absRound(years / 100) + absRound(years / 400); + return years * 146097 / 400; + } + + extend(moment.duration.fn = Duration.prototype, { + + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years = 0; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + + // Accurately convert days to years, assume start from year 0. + years = absRound(daysToYears(days)); + days -= absRound(yearsToDays(years)); + + // 30 days to a month + // TODO (iskren): Use anchor date (like 1st Jan) to compute this. + months += absRound(days / 30); + days %= 30; + + // 12 months -> 1 year + years += absRound(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + }, + + abs : function () { + this._milliseconds = Math.abs(this._milliseconds); + this._days = Math.abs(this._days); + this._months = Math.abs(this._months); + + this._data.milliseconds = Math.abs(this._data.milliseconds); + this._data.seconds = Math.abs(this._data.seconds); + this._data.minutes = Math.abs(this._data.minutes); + this._data.hours = Math.abs(this._data.hours); + this._data.months = Math.abs(this._data.months); + this._data.years = Math.abs(this._data.years); + + return this; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var output = relativeTime(this, !withSuffix, this.localeData()); + + if (withSuffix) { + output = this.localeData().pastFuture(+this, output); + } + + return this.localeData().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + var days, months; + units = normalizeUnits(units); + + if (units === 'month' || units === 'year') { + days = this._days + this._milliseconds / 864e5; + months = this._months + daysToYears(days) * 12; + return units === 'month' ? months : months / 12; + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(yearsToDays(this._months / 12)); + switch (units) { + case 'week': return days / 7 + this._milliseconds / 6048e5; + case 'day': return days + this._milliseconds / 864e5; + case 'hour': return days * 24 + this._milliseconds / 36e5; + case 'minute': return days * 24 * 60 + this._milliseconds / 6e4; + case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + }, + + lang : moment.fn.lang, + locale : moment.fn.locale, + + toIsoString : deprecate( + 'toIsoString() is deprecated. Please use toISOString() instead ' + + '(notice the capitals)', + function () { + return this.toISOString(); + } + ), + + toISOString : function () { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var years = Math.abs(this.years()), + months = Math.abs(this.months()), + days = Math.abs(this.days()), + hours = Math.abs(this.hours()), + minutes = Math.abs(this.minutes()), + seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); + + if (!this.asSeconds()) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (this.asSeconds() < 0 ? '-' : '') + + 'P' + + (years ? years + 'Y' : '') + + (months ? months + 'M' : '') + + (days ? days + 'D' : '') + + ((hours || minutes || seconds) ? 'T' : '') + + (hours ? hours + 'H' : '') + + (minutes ? minutes + 'M' : '') + + (seconds ? seconds + 'S' : ''); + }, + + localeData : function () { + return this._locale; + }, + + toJSON : function () { + return this.toISOString(); + } + }); + + moment.duration.fn.toString = moment.duration.fn.toISOString; + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + for (i in unitMillisecondFactors) { + if (hasOwnProp(unitMillisecondFactors, i)) { + makeDurationGetter(i.toLowerCase()); + } + } + + moment.duration.fn.asMilliseconds = function () { + return this.as('ms'); + }; + moment.duration.fn.asSeconds = function () { + return this.as('s'); + }; + moment.duration.fn.asMinutes = function () { + return this.as('m'); + }; + moment.duration.fn.asHours = function () { + return this.as('h'); + }; + moment.duration.fn.asDays = function () { + return this.as('d'); + }; + moment.duration.fn.asWeeks = function () { + return this.as('weeks'); + }; + moment.duration.fn.asMonths = function () { + return this.as('M'); + }; + moment.duration.fn.asYears = function () { + return this.as('y'); + }; + + /************************************ + Default Locale + ************************************/ + + + // Set default locale, other locale will inherit from English. + moment.locale('en', { + ordinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + /* EMBED_LOCALES */ + + /************************************ + Exposing Moment + ************************************/ + + function makeGlobal(shouldDeprecate) { + /*global ender:false */ + if (typeof ender !== 'undefined') { + return; + } + oldGlobalMoment = globalScope.moment; + if (shouldDeprecate) { + globalScope.moment = deprecate( + 'Accessing Moment through the global scope is ' + + 'deprecated, and will be removed in an upcoming ' + + 'release.', + moment); + } else { + globalScope.moment = moment; + } + } + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } else if (typeof define === 'function' && define.amd) { + define(function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal === true) { + // release the global variable + globalScope.moment = oldGlobalMoment; + } + + return moment; + }); + makeGlobal(true); + } else { + makeGlobal(); + } +}).call(this); \ No newline at end of file From 50352c94f691d041631dbb2ef38c82550832e8c1 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 22 Jun 2016 10:16:47 +0100 Subject: [PATCH 17/17] re minify aloglia and moment --- services/web/public/js/libs/algolia-2.5.2.js | 2042 +----------- services/web/public/js/libs/moment-2.9.0.js | 3040 +----------------- 2 files changed, 7 insertions(+), 5075 deletions(-) diff --git a/services/web/public/js/libs/algolia-2.5.2.js b/services/web/public/js/libs/algolia-2.5.2.js index fead438491..5b0e30b297 100644 --- a/services/web/public/js/libs/algolia-2.5.2.js +++ b/services/web/public/js/libs/algolia-2.5.2.js @@ -1,2039 +1,7 @@ -/* - * Copyright (c) 2013 Algolia - * http://www.algolia.com/ - * - * 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. +/*! + * algoliasearch 2.5.2 + * https://github.com/algolia/algoliasearch-client-js + * Copyright 2014 Algolia SAS; Licensed MIT */ -var ALGOLIA_VERSION = '2.5.2'; - -/* - * Copyright (c) 2013 Algolia - * http://www.algolia.com/ - * - * 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. - */ - -/* - * Algolia Search library initialization - * @param applicationID the application ID you have in your admin interface - * @param apiKey a valid API key for the service - * @param method specify if the protocol used is http or https (http by default to make the first search query faster). - * You need to use https is you are doing something else than just search queries. - * @param resolveDNS let you disable first empty query that is launch to warmup the service - * @param hostsArray (optionnal) the list of hosts that you have received for the service - */ -var AlgoliaSearch = function(applicationID, apiKey, method, resolveDNS, hostsArray) { - var self = this; - this.applicationID = applicationID; - this.apiKey = apiKey; - - if (this._isUndefined(hostsArray)) { - hostsArray = [applicationID + '-1.algolia.io', - applicationID + '-2.algolia.io', - applicationID + '-3.algolia.io']; - } - this.hosts = []; - // Add hosts in random order - for (var i = 0; i < hostsArray.length; ++i) { - if (Math.random() > 0.5) { - this.hosts.reverse(); - } - if (this._isUndefined(method) || method == null) { - this.hosts.push(('https:' == document.location.protocol ? 'https' : 'http') + '://' + hostsArray[i]); - } else if (method === 'https' || method === 'HTTPS') { - this.hosts.push('https://' + hostsArray[i]); - } else { - this.hosts.push('http://' + hostsArray[i]); - } - } - if (Math.random() > 0.5) { - this.hosts.reverse(); - } - - // resolve DNS + check CORS support (JSONP fallback) - this.jsonp = null; - this.jsonpWait = 0; - this._jsonRequest({ - method: 'GET', - url: '/1/isalive', - callback: function(success, content) { - self.jsonp = !success; - } - }); - this.extraHeaders = []; -}; - -function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) { - - function _getHitExplanationForOneAttr_recurse(obj, foundWords) { - var res = []; - if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) { - var match = false; - for (var j = 0; j < obj.matchedWords.length; ++j) { - var word = obj.matchedWords[j]; - if (!(word in foundWords)) { - foundWords[word] = 1; - match = true; - } - } - if (match) { - res.push(obj.value); - } - } else if (Object.prototype.toString.call(obj) === '[object Array]') { - for (var i = 0; i < obj.length; ++i) { - var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords); - res = res.concat(array); - } - } else if (typeof obj === 'object') { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)){ - res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords)); - } - } - } - return res; - } - - function _getHitExplanationForOneAttr(hit, foundWords, attr) { - var base = hit._highlightResult || hit; - if (attr.indexOf('.') === -1) { - if (attr in base) { - return _getHitExplanationForOneAttr_recurse(base[attr], foundWords); - } - return []; - } - var array = attr.split('.'); - var obj = base; - for (var i = 0; i < array.length; ++i) { - if (Object.prototype.toString.call(obj) === '[object Array]') { - var res = []; - for (var j = 0; j < obj.length; ++j) { - res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.'))); - } - return res; - } - if (array[i] in obj) { - obj = obj[array[i]]; - } else { - return []; - } - } - return _getHitExplanationForOneAttr_recurse(obj, foundWords); - } - - var res = {}; - var foundWords = {}; - var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute); - res.title = (title.length > 0) ? title[0] : ''; - res.subtitles = []; - - if (typeof otherAttributes !== 'undefined') { - for (var i = 0; i < otherAttributes.length; ++i) { - var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]); - for (var j = 0; j < attr.length; ++j) { - res.subtitles.push({ attr: otherAttributes[i], value: attr[j] }); - } - } - } - return res; -} - - -AlgoliaSearch.prototype = { - /* - * Delete an index - * - * @param indexName the name of index to delete - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer that contains the task ID - */ - deleteIndex: function(indexName, callback) { - this._jsonRequest({ method: 'DELETE', - url: '/1/indexes/' + encodeURIComponent(indexName), - callback: callback }); - }, - /** - * Move an existing index. - * @param srcIndexName the name of index to copy. - * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer that contains the task ID - */ - moveIndex: function(srcIndexName, dstIndexName, callback) { - var postObj = {operation: 'move', destination: dstIndexName}; - this._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', - body: postObj, - callback: callback }); - - }, - /** - * Copy an existing index. - * @param srcIndexName the name of index to copy. - * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist). - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer that contains the task ID - */ - copyIndex: function(srcIndexName, dstIndexName, callback) { - var postObj = {operation: 'copy', destination: dstIndexName}; - this._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation', - body: postObj, - callback: callback }); - }, - /** - * Return last log entries. - * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry). - * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000. - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer that contains the task ID - */ - getLogs: function(callback, offset, length) { - if (this._isUndefined(offset)) { - offset = 0; - } - if (this._isUndefined(length)) { - length = 10; - } - - this._jsonRequest({ method: 'GET', - url: '/1/logs?offset=' + offset + '&length=' + length, - callback: callback }); - }, - /* - * List all existing indexes - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with index list or error description if success is false. - */ - listIndexes: function(callback) { - this._jsonRequest({ method: 'GET', - url: '/1/indexes', - callback: callback }); - }, - - /* - * Get the index object initialized - * - * @param indexName the name of index - * @param callback the result callback with one argument (the Index instance) - */ - initIndex: function(indexName) { - return new this.Index(this, indexName); - }, - /* - * List all existing user keys with their associated ACLs - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - listUserKeys: function(callback) { - this._jsonRequest({ method: 'GET', - url: '/1/keys', - callback: callback }); - }, - /* - * Get ACL of a user key - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - getUserKeyACL: function(key, callback) { - this._jsonRequest({ method: 'GET', - url: '/1/keys/' + key, - callback: callback }); - }, - /* - * Delete an existing user key - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - deleteUserKey: function(key, callback) { - this._jsonRequest({ method: 'DELETE', - url: '/1/keys/' + key, - callback: callback }); - }, - /* - * Add an existing user key - * - * @param acls the list of ACL for this key. Defined by an array of strings that - * can contains the following values: - * - search: allow to search (https and http) - * - addObject: allows to add/update an object in the index (https only) - * - deleteObject : allows to delete an existing object (https only) - * - deleteIndex : allows to delete index content (https only) - * - settings : allows to get index settings (https only) - * - editSettings : allows to change index settings (https only) - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - addUserKey: function(acls, callback) { - var aclsObject = {}; - aclsObject.acl = acls; - this._jsonRequest({ method: 'POST', - url: '/1/keys', - body: aclsObject, - callback: callback }); - }, - /* - * Add an existing user key - * - * @param acls the list of ACL for this key. Defined by an array of strings that - * can contains the following values: - * - search: allow to search (https and http) - * - addObject: allows to add/update an object in the index (https only) - * - deleteObject : allows to delete an existing object (https only) - * - deleteIndex : allows to delete index content (https only) - * - settings : allows to get index settings (https only) - * - editSettings : allows to change index settings (https only) - * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) - * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. - * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { - var indexObj = this; - var aclsObject = {}; - aclsObject.acl = acls; - aclsObject.validity = validity; - aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; - aclsObject.maxHitsPerQuery = maxHitsPerQuery; - this._jsonRequest({ method: 'POST', - url: '/1/indexes/' + indexObj.indexName + '/keys', - body: aclsObject, - callback: callback }); - }, - - /** - * Set the extra security tagFilters header - * @param {string|array} tags The list of tags defining the current security filters - */ - setSecurityTags: function(tags) { - if (Object.prototype.toString.call(tags) === '[object Array]') { - var strTags = []; - for (var i = 0; i < tags.length; ++i) { - if (Object.prototype.toString.call(tags[i]) === '[object Array]') { - var oredTags = []; - for (var j = 0; j < tags[i].length; ++j) { - oredTags.push(tags[i][j]); - } - strTags.push('(' + oredTags.join(',') + ')'); - } else { - strTags.push(tags[i]); - } - } - tags = strTags.join(','); - } - this.tagFilters = tags; - }, - - /** - * Set the extra user token header - * @param {string} userToken The token identifying a uniq user (used to apply rate limits) - */ - setUserToken: function(userToken) { - this.userToken = userToken; - }, - - /* - * Initialize a new batch of search queries - */ - startQueriesBatch: function() { - this.batch = []; - }, - /* - * Add a search query in the batch - * - * @param query the full text query - * @param args (optional) if set, contains an object with query parameters: - * - attributes: an array of object attribute names to retrieve - * (if not set all attributes are retrieve) - * - attributesToHighlight: an array of object attribute names to highlight - * (if not set indexed attributes are highlighted) - * - minWordSizefor1Typo: the minimum number of characters to accept one typo. - * Defaults to 3. - * - minWordSizefor2Typos: the minimum number of characters to accept two typos. - * Defaults to 7. - * - getRankingInfo: if set, the result hits will contain ranking information in - * _rankingInfo attribute - * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0. - * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10. - */ - addQueryInBatch: function(indexName, query, args) { - var params = 'query=' + encodeURIComponent(query); - if (!this._isUndefined(args) && args != null) { - params = this._getSearchParams(args, params); - } - this.batch.push({ indexName: indexName, params: params }); - }, - /* - * Clear all queries in cache - */ - clearCache: function() { - this.cache = {}; - }, - /* - * Launch the batch of queries using XMLHttpRequest. - * (Optimized for browser using a POST query to minimize number of OPTIONS queries) - * - * @param callback the function that will receive results - * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime. - */ - sendQueriesBatch: function(callback, delay) { - var as = this; - var params = {requests: [], apiKey: this.apiKey, appID: this.applicationID}; - if (this.userToken) { - params['X-Algolia-UserToken'] = this.userToken; - } - if (this.tagFilters) { - params['X-Algolia-TagFilters'] = this.tagFilters; - } - for (var i = 0; i < as.batch.length; ++i) { - params.requests.push(as.batch[i]); - } - window.clearTimeout(as.onDelayTrigger); - if (!this._isUndefined(delay) && delay != null && delay > 0) { - var onDelayTrigger = window.setTimeout( function() { - as._sendQueriesBatch(params, callback); - }, delay); - as.onDelayTrigger = onDelayTrigger; - } else { - this._sendQueriesBatch(params, callback); - } - }, - /* - * Index class constructor. - * You should not use this method directly but use initIndex() function - */ - Index: function(algoliasearch, indexName) { - this.indexName = indexName; - this.as = algoliasearch; - this.typeAheadArgs = null; - this.typeAheadValueOption = null; - }, - - setExtraHeader: function(key, value) { - this.extraHeaders.push({ key: key, value: value}); - }, - - _sendQueriesBatch: function(params, callback) { - if (this.jsonp == null) { - var self = this; - this._waitReady(function() { self._sendQueriesBatch(params, callback); }); - return; - } - if (this.jsonp) { - var jsonpParams = ''; - for (var i = 0; i < params.requests.length; ++i) { - var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params; - jsonpParams += i + '=' + encodeURIComponent(q) + '&'; - } - this._jsonRequest({ cache: this.cache, - method: 'GET', jsonp: true, - url: '/1/indexes/*', - body: { params: jsonpParams }, - callback: callback }); - } else { - this._jsonRequest({ cache: this.cache, - method: 'POST', - url: '/1/indexes/*/queries', - body: params, - callback: callback }); - } - }, - /* - * Wrapper that try all hosts to maximize the quality of service - */ - _jsonRequest: function(opts) { - var self = this; - var callback = opts.callback; - var cache = null; - var cacheID = opts.url; - if (!this._isUndefined(opts.body)) { - cacheID = opts.url + '_body_' + JSON.stringify(opts.body); - } - if (!this._isUndefined(opts.cache)) { - cache = opts.cache; - if (!this._isUndefined(cache[cacheID])) { - if (!this._isUndefined(callback)) { - setTimeout(function () { callback(true, cache[cacheID]); }, 1); - } - return; - } - } - - var impl = function(position) { - var idx = 0; - if (!self._isUndefined(position)) { - idx = position; - } - if (self.hosts.length <= idx) { - if (!self._isUndefined(callback)) { - callback(false, { message: 'Cannot contact server'}); - } - return; - } - opts.callback = function(retry, success, res, body) { - if (!success && !self._isUndefined(body)) { - if (window.console) { console.log('Error: ' + body.message); } - } - if (success && !self._isUndefined(opts.cache)) { - cache[cacheID] = body; - } - if (!success && retry && (idx + 1) < self.hosts.length) { - impl(idx + 1); - } else { - if (!self._isUndefined(callback)) { - callback(success, body); - } - } - }; - opts.hostname = self.hosts[idx]; - self._jsonRequestByHost(opts); - }; - impl(); - }, - - _jsonRequestByHost: function(opts) { - var self = this; - var url = opts.hostname + opts.url; - - if (this.jsonp) { - if (!opts.jsonp) { - opts.callback(true, false, null, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' }); - return; - } - this.jsonpCounter = this.jsonpCounter || 0; - this.jsonpCounter += 1; - var cb = 'algoliaJSONP_' + this.jsonpCounter; - window[cb] = function(data) { - opts.callback(false, true, null, data); - try { delete window[cb]; } catch (e) { window[cb] = undefined; } - }; - var script = document.createElement('script'); - script.type = 'text/javascript'; - script.src = url + '?callback=' + cb + ',' + this.applicationID + ',' + this.apiKey; - if (opts['X-Algolia-TagFilters']) { - script.src += '&X-Algolia-TagFilters=' + opts['X-Algolia-TagFilters']; - } - if (opts['X-Algolia-UserToken']) { - script.src += '&X-Algolia-UserToken=' + opts['X-Algolia-UserToken']; - } - if (opts.body && opts.body.params) { - script.src += '&' + opts.body.params; - } - var head = document.getElementsByTagName('head')[0]; - script.onerror = function() { - opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); - head.removeChild(script); - try { delete window[cb]; } catch (e) { window[cb] = undefined; } - }; - var done = false; - script.onload = script.onreadystatechange = function() { - if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) { - done = true; - if (typeof window[cb + '_loaded'] === 'undefined') { - opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' }); - try { delete window[cb]; } catch (e) { window[cb] = undefined; } - } else { - try { delete window[cb + '_loaded']; } catch (e) { window[cb + '_loaded'] = undefined; } - } - script.onload = script.onreadystatechange = null; // Handle memory leak in IE - head.removeChild(script); - } - }; - head.appendChild(script); - } else { - var body = null; - if (!this._isUndefined(opts.body)) { - body = JSON.stringify(opts.body); - } - var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : {}; - if ('withCredentials' in xmlHttp) { - xmlHttp.open(opts.method, url , true); - xmlHttp.setRequestHeader('X-Algolia-API-Key', this.apiKey); - xmlHttp.setRequestHeader('X-Algolia-Application-Id', this.applicationID); - for (var i = 0; i < this.extraHeaders.length; ++i) { - xmlHttp.setRequestHeader(this.extraHeaders[i].key, this.extraHeaders[i].value); - } - if (body != null) { - xmlHttp.setRequestHeader('Content-type', 'application/json'); - } - } else if (typeof XDomainRequest != 'undefined') { - // Handle IE8/IE9 - // XDomainRequest only exists in IE, and is IE's way of making CORS requests. - xmlHttp = new XDomainRequest(); - xmlHttp.open(opts.method, url); - } else { - // very old browser, not supported - if (window.console) { console.log('Your browser is too old to support CORS requests'); } - opts.callback(false, false, null, { 'message': 'CORS not supported' }); - return; - } - xmlHttp.send(body); - xmlHttp.onload = function(event) { - if (!self._isUndefined(event) && event.target != null) { - var retry = (event.target.status === 0 || event.target.status === 503); - var success = (event.target.status === 200 || event.target.status === 201); - opts.callback(retry, success, event.target, event.target.response != null ? JSON.parse(event.target.response) : null); - } else { - opts.callback(false, true, event, JSON.parse(xmlHttp.responseText)); - } - }; - xmlHttp.onerror = function(event) { - opts.callback(true, false, null, { 'message': 'Could not connect to host', 'error': event } ); - }; - } - }, - - /** - * Wait until JSONP flag has been set to perform the first query - */ - _waitReady: function(cb) { - if (this.jsonp == null) { - this.jsonpWait += 100; - if (this.jsonpWait > 2000) { - this.jsonp = true; - } - setTimeout(cb, 100); - } - }, - - /* - * Transform search param object in query string - */ - _getSearchParams: function(args, params) { - if (this._isUndefined(args) || args == null) { - return params; - } - for (var key in args) { - if (key != null && args.hasOwnProperty(key)) { - params += (params.length === 0) ? '?' : '&'; - params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]); - } - } - return params; - }, - _isUndefined: function(obj) { - return obj === void 0; - }, - - /// internal attributes - applicationID: null, - apiKey: null, - tagFilters: null, - userToken: null, - hosts: [], - cache: {}, - extraHeaders: [] -}; - -/* - * Contains all the functions related to one index - * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object - */ -AlgoliaSearch.prototype.Index.prototype = { - /* - * Clear all queries in cache - */ - clearCache: function() { - this.cache = {}; - }, - /* - * Add an object in this index - * - * @param content contains the javascript object to add inside the index - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that contains 3 elements: createAt, taskId and objectID - * @param objectID (optional) an objectID you want to attribute to this object - * (if the attribute already exist the old object will be overwrite) - */ - addObject: function(content, callback, objectID) { - var indexObj = this; - if (this.as._isUndefined(objectID)) { - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName), - body: content, - callback: callback }); - } else { - this.as._jsonRequest({ method: 'PUT', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), - body: content, - callback: callback }); - } - - }, - /* - * Add several objects - * - * @param objects contains an array of objects to add - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that updateAt and taskID - */ - addObjects: function(objects, callback) { - var indexObj = this; - var postObj = {requests:[]}; - for (var i = 0; i < objects.length; ++i) { - var request = { action: 'addObject', - body: objects[i] }; - postObj.requests.push(request); - } - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', - body: postObj, - callback: callback }); - }, - /* - * Get an object from this index - * - * @param objectID the unique identifier of the object to retrieve - * @param callback (optional) the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the object to retrieve or the error message if a failure occured - * @param attributes (optional) if set, contains the array of attribute names to retrieve - */ - getObject: function(objectID, callback, attributes) { - if (this.as.jsonp == null) { - var self = this; - this.as._waitReady(function() { self.getObject(objectID, callback, attributes); }); - return; - } - var indexObj = this; - var params = ''; - if (!this.as._isUndefined(attributes)) { - params = '?attributes='; - for (var i = 0; i < attributes.length; ++i) { - if (i !== 0) { - params += ','; - } - params += attributes[i]; - } - } - this.as._jsonRequest({ method: 'GET', jsonp: true, - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params, - callback: callback }); - }, - - /* - * Update partially an object (only update attributes passed in argument) - * - * @param partialObject contains the javascript attributes to override, the - * object must contains an objectID attribute - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that contains 3 elements: createAt, taskId and objectID - */ - partialUpdateObject: function(partialObject, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial', - body: partialObject, - callback: callback }); - }, - /* - * Partially Override the content of several objects - * - * @param objects contains an array of objects to update (each object must contains a objectID attribute) - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that updateAt and taskID - */ - partialUpdateObjects: function(objects, callback) { - var indexObj = this; - var postObj = {requests:[]}; - for (var i = 0; i < objects.length; ++i) { - var request = { action: 'partialUpdateObject', - objectID: objects[i].objectID, - body: objects[i] }; - postObj.requests.push(request); - } - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', - body: postObj, - callback: callback }); - }, - /* - * Override the content of object - * - * @param object contains the javascript object to save, the object must contains an objectID attribute - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that updateAt and taskID - */ - saveObject: function(object, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'PUT', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID), - body: object, - callback: callback }); - }, - /* - * Override the content of several objects - * - * @param objects contains an array of objects to update (each object must contains a objectID attribute) - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that updateAt and taskID - */ - saveObjects: function(objects, callback) { - var indexObj = this; - var postObj = {requests:[]}; - for (var i = 0; i < objects.length; ++i) { - var request = { action: 'updateObject', - objectID: objects[i].objectID, - body: objects[i] }; - postObj.requests.push(request); - } - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch', - body: postObj, - callback: callback }); - }, - /* - * Delete an object from the index - * - * @param objectID the unique identifier of object to delete - * @param callback (optional) the result callback with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that contains 3 elements: createAt, taskId and objectID - */ - deleteObject: function(objectID, callback) { - if (objectID == null || objectID.length === 0) { - callback(false, { message: 'empty objectID'}); - return; - } - var indexObj = this; - this.as._jsonRequest({ method: 'DELETE', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID), - callback: callback }); - }, - /* - * Search inside the index using XMLHttpRequest request (Using a POST query to - * minimize number of OPTIONS queries: Cross-Origin Resource Sharing). - * - * @param query the full text query - * @param callback the result callback with two arguments: - * success: boolean set to true if the request was successfull. If false, the content contains the error. - * content: the server answer that contains the list of results. - * @param args (optional) if set, contains an object with query parameters: - * - page: (integer) Pagination parameter used to select the page to retrieve. - * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 - * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20. - * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size). - * Attributes are separated with a comma (for example "name,address"). - * You can also use a string array encoding (for example ["name","address"]). - * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index. - * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query. - * Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]). - * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted. - * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted. - * A matchLevel is returned for each highlighted attribute and can contain: - * - full: if all the query terms were found in the attribute, - * - partial: if only some of the query terms were found, - * - none: if none of the query terms were found. - * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`). - * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10). - * You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed. - * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3. - * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7. - * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute. - * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma). - * For example aroundLatLng=47.316669,5.016670). - * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision - * (for example if you set aroundPrecision=100, two objects that are distant of less than 100m will be considered as identical for "geo" ranking parameter). - * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) - * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng). - * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201). - * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}}) - * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma. - * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`. - * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000. - * You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]). - * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas. - * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3). - * You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3). - * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}). - * - facetFilters: filter the query by a list of facets. - * Facets are separated by commas and each facet is encoded as `attributeName:value`. - * For example: `facetFilters=category:Book,author:John%20Doe`. - * You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`). - * - facets: List of object attributes that you want to use for faceting. - * Attributes are separated with a comma (for example `"category,author"` ). - * You can also use a JSON string array encoding (for example ["category","author"]). - * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter. - * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**. - * - queryType: select how the query words are interpreted, it can be one of the following value: - * - prefixAll: all query words are interpreted as prefixes, - * - prefixLast: only the last word is interpreted as a prefix (default behavior), - * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. - * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query. - * The list of words is comma separated. - * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set. - * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter, - * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results. - * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best - * one is kept and others are removed. - * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime. - */ - search: function(query, callback, args, delay) { - var indexObj = this; - var params = 'query=' + encodeURIComponent(query); - if (!this.as._isUndefined(args) && args != null) { - params = this.as._getSearchParams(args, params); - } - window.clearTimeout(indexObj.onDelayTrigger); - if (!this.as._isUndefined(delay) && delay != null && delay > 0) { - var onDelayTrigger = window.setTimeout( function() { - indexObj._search(params, callback); - }, delay); - indexObj.onDelayTrigger = onDelayTrigger; - } else { - this._search(params, callback); - } - }, - - /* - * Browse all index content - * - * @param page Pagination parameter used to select the page to retrieve. - * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9 - * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000. - */ - browse: function(page, callback, hitsPerPage) { - var indexObj = this; - var params = '?page=' + page; - if (!_.isUndefined(hitsPerPage)) { - params += '&hitsPerPage=' + hitsPerPage; - } - this.as._jsonRequest({ method: 'GET', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params, - callback: callback }); - }, - - /* - * Get a Typeahead.js adapter - * @param searchParams contains an object with query parameters (see search for details) - */ - ttAdapter: function(params) { - var self = this; - return function(query, cb) { - self.search(query, function(success, content) { - if (success) { - cb(content.hits); - } - }, params); - }; - }, - - /* - * Wait the publication of a task on the server. - * All server task are asynchronous and you can check with this method that the task is published. - * - * @param taskID the id of the task returned by server - * @param callback the result callback with with two arguments: - * success: boolean set to true if the request was successfull - * content: the server answer that contains the list of results - */ - waitTask: function(taskID, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'GET', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID, - callback: function(success, body) { - if (success) { - if (body.status === 'published') { - callback(true, body); - } else { - setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100); - } - } else { - callback(false, body); - } - }}); - }, - - /* - * This function deletes the index content. Settings and index specific API keys are kept untouched. - * - * @param callback (optional) the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the settings object or the error message if a failure occured - */ - clearIndex: function(callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear', - callback: callback }); - }, - /* - * Get settings of this index - * - * @param callback (optional) the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the settings object or the error message if a failure occured - */ - getSettings: function(callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'GET', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', - callback: callback }); - }, - - /* - * Set settings for this index - * - * @param settigns the settings object that can contains : - * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3). - * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7). - * - hitsPerPage: (integer) the number of hits per page (default = 10). - * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects. - * If set to null, all attributes are retrieved. - * - attributesToHighlight: (array of strings) default list of attributes to highlight. - * If set to null, all indexed attributes are highlighted. - * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords). - * By default no snippet is computed. If set to null, no snippet is computed. - * - attributesToIndex: (array of strings) the list of fields you want to index. - * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results. - * This parameter has two important uses: - * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to - * retrieve it but you don't want to search in the base64 string. - * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of - * the list will be considered more important than matches in attributes further down the list. - * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable - * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"]. - * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting. - * All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting. - * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled - * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results. - * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best one is kept and others are removed. - * - ranking: (array of strings) controls the way results are sorted. - * We have six available criteria: - * - typo: sort according to number of typos, - * - geo: sort according to decreassing distance when performing a geo-location based search, - * - proximity: sort according to the proximity of query words in hits, - * - attribute: sort according to the order of attributes defined by attributesToIndex, - * - exact: - * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others. - * For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV - * show starting by the v letter before it. - * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix). - * - custom: sort according to a user defined formula set in **customRanking** attribute. - * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"] - * - customRanking: (array of strings) lets you specify part of the ranking. - * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator. - * For example `"customRanking" => ["desc(population)", "asc(name)"]` - * - queryType: Select how the query words are interpreted, it can be one of the following value: - * - prefixAll: all query words are interpreted as prefixes, - * - prefixLast: only the last word is interpreted as a prefix (default behavior), - * - prefixNone: no query word is interpreted as a prefix. This option is not recommended. - * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to ""). - * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to ""). - * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query. - * @param callback (optional) the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer or the error message if a failure occured - */ - setSettings: function(settings, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'PUT', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings', - body: settings, - callback: callback }); - }, - /* - * List all existing user keys associated to this index - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - listUserKeys: function(callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'GET', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', - callback: callback }); - }, - /* - * Get ACL of a user key associated to this index - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - getUserKeyACL: function(key, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'GET', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, - callback: callback }); - }, - /* - * Delete an existing user key associated to this index - * - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - deleteUserKey: function(key, callback) { - var indexObj = this; - this.as._jsonRequest({ method: 'DELETE', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key, - callback: callback }); - }, - /* - * Add an existing user key associated to this index - * - * @param acls the list of ACL for this key. Defined by an array of strings that - * can contains the following values: - * - search: allow to search (https and http) - * - addObject: allows to add/update an object in the index (https only) - * - deleteObject : allows to delete an existing object (https only) - * - deleteIndex : allows to delete index content (https only) - * - settings : allows to get index settings (https only) - * - editSettings : allows to change index settings (https only) - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - addUserKey: function(acls, callback) { - var indexObj = this; - var aclsObject = {}; - aclsObject.acl = acls; - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', - body: aclsObject, - callback: callback }); - }, - /* - * Add an existing user key associated to this index - * - * @param acls the list of ACL for this key. Defined by an array of strings that - * can contains the following values: - * - search: allow to search (https and http) - * - addObject: allows to add/update an object in the index (https only) - * - deleteObject : allows to delete an existing object (https only) - * - deleteIndex : allows to delete index content (https only) - * - settings : allows to get index settings (https only) - * - editSettings : allows to change index settings (https only) - * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key) - * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour. - * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call. - * @param callback the result callback with two arguments - * success: boolean set to true if the request was successfull - * content: the server answer with user keys list or error description if success is false. - */ - addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) { - var indexObj = this; - var aclsObject = {}; - aclsObject.acl = acls; - aclsObject.validity = validity; - aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour; - aclsObject.maxHitsPerQuery = maxHitsPerQuery; - this.as._jsonRequest({ method: 'POST', - url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys', - body: aclsObject, - callback: callback }); - }, - /// - /// Internal methods only after this line - /// - _search: function(params, callback) { - if (this.as.jsonp == null) { - var self = this; - this.as._waitReady(function() { self._search(params, callback); }); - return; - } - var pObj = {params: params, apiKey: this.as.apiKey, appID: this.as.applicationID}; - if (this.as.tagFilters) { - pObj['X-Algolia-TagFilters'] = this.as.tagFilters; - } - if (this.as.userToken) { - pObj['X-Algolia-UserToken'] = this.as.userToken; - } - if (this.as.jsonp) { - this.as._jsonRequest({ cache: this.cache, - method: 'GET', jsonp: true, - url: '/1/indexes/' + encodeURIComponent(this.indexName), - body: pObj, - callback: callback }); - } else { - this.as._jsonRequest({ cache: this.cache, - method: 'POST', - url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query', - body: pObj, - callback: callback }); - } - }, - - // internal attributes - as: null, - indexName: null, - cache: {}, - typeAheadArgs: null, - typeAheadValueOption: null, - emptyConstructor: function() {} -}; - -/* - * Copyright (c) 2014 Algolia - * http://www.algolia.com/ - * - * 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. - */ - -(function($) { - var extend = function(out) { - out = out || {}; - for (var i = 1; i < arguments.length; i++) { - if (!arguments[i]) { - continue; - } - for (var key in arguments[i]) { - if (arguments[i].hasOwnProperty(key)) { - out[key] = arguments[i][key]; - } - } - } - return out; - }; - - /** - * Algolia Search Helper providing faceting and disjunctive faceting - * @param {AlgoliaSearch} client an AlgoliaSearch client - * @param {string} index the index name to query - * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets - */ - window.AlgoliaSearchHelper = function(client, index, options) { - /// Default options - var defaults = { - facets: [], // list of facets to compute - disjunctiveFacets: [], // list of disjunctive facets to compute - hitsPerPage: 20 // number of hits per page - }; - - this.init(client, index, extend({}, defaults, options)); - }; - - AlgoliaSearchHelper.prototype = { - /** - * Initialize a new AlgoliaSearchHelper - * @param {AlgoliaSearch} client an AlgoliaSearch client - * @param {string} index the index name to query - * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets - * @return {AlgoliaSearchHelper} - */ - init: function(client, index, options) { - this.client = client; - this.index = index; - this.options = options; - this.page = 0; - this.refinements = {}; - this.disjunctiveRefinements = {}; - }, - - /** - * Perform a query - * @param {string} q the user query - * @param {function} searchCallback the result callback called with two arguments: - * success: boolean set to true if the request was successfull - * content: the query answer with an extra 'disjunctiveFacets' attribute - */ - search: function(q, searchCallback, searchParams) { - this.q = q; - this.searchCallback = searchCallback; - this.searchParams = searchParams || {}; - this.page = this.page || 0; - this.refinements = this.refinements || {}; - this.disjunctiveRefinements = this.disjunctiveRefinements || {}; - this._search(); - }, - - /** - * Remove all refinements (disjunctive + conjunctive) - */ - clearRefinements: function() { - this.disjunctiveRefinements = {}; - this.refinements = {}; - }, - - /** - * Ensure a facet refinement exists - * @param {string} facet the facet to refine - * @param {string} value the associated value - */ - addDisjunctiveRefine: function(facet, value) { - this.disjunctiveRefinements = this.disjunctiveRefinements || {}; - this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; - this.disjunctiveRefinements[facet][value] = true; - }, - - /** - * Ensure a facet refinement does not exist - * @param {string} facet the facet to refine - * @param {string} value the associated value - */ - removeDisjunctiveRefine: function(facet, value) { - this.disjunctiveRefinements = this.disjunctiveRefinements || {}; - this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; - try { - delete this.disjunctiveRefinements[facet][value]; - } catch (e) { - this.disjunctiveRefinements[facet][value] = undefined; // IE compat - } - }, - - /** - * Ensure a facet refinement exists - * @param {string} facet the facet to refine - * @param {string} value the associated value - */ - addRefine: function(facet, value) { - var refinement = facet + ':' + value; - this.refinements = this.refinements || {}; - this.refinements[refinement] = true; - }, - - /** - * Ensure a facet refinement does not exist - * @param {string} facet the facet to refine - * @param {string} value the associated value - */ - removeRefine: function(facet, value) { - var refinement = facet + ':' + value; - this.refinements = this.refinements || {}; - this.refinements[refinement] = false; - }, - - /** - * Toggle refinement state of a facet - * @param {string} facet the facet to refine - * @param {string} value the associated value - * @return {boolean} true if the facet has been found - */ - toggleRefine: function(facet, value) { - for (var i = 0; i < this.options.facets.length; ++i) { - if (this.options.facets[i] == facet) { - var refinement = facet + ':' + value; - this.refinements[refinement] = !this.refinements[refinement]; - this.page = 0; - this._search(); - return true; - } - } - this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {}; - for (var j = 0; j < this.options.disjunctiveFacets.length; ++j) { - if (this.options.disjunctiveFacets[j] == facet) { - this.disjunctiveRefinements[facet][value] = !this.disjunctiveRefinements[facet][value]; - this.page = 0; - this._search(); - return true; - } - } - return false; - }, - - /** - * Check the refinement state of a facet - * @param {string} facet the facet - * @param {string} value the associated value - * @return {boolean} true if refined - */ - isRefined: function(facet, value) { - var refinement = facet + ':' + value; - if (this.refinements[refinement]) { - return true; - } - if (this.disjunctiveRefinements[facet] && this.disjunctiveRefinements[facet][value]) { - return true; - } - return false; - }, - - /** - * Go to next page - */ - nextPage: function() { - this._gotoPage(this.page + 1); - }, - - /** - * Go to previous page - */ - previousPage: function() { - if (this.page > 0) { - this._gotoPage(this.page - 1); - } - }, - - /** - * Goto a page - * @param {integer} page The page number - */ - gotoPage: function(page) { - this._gotoPage(page); - }, - - /** - * Configure the page but do not trigger a reload - * @param {integer} page The page number - */ - setPage: function(page) { - this.page = page; - }, - - ///////////// PRIVATE - - /** - * Goto a page - * @param {integer} page The page number - */ - _gotoPage: function(page) { - this.page = page; - this._search(); - }, - - /** - * Perform the underlying queries - */ - _search: function() { - this.client.startQueriesBatch(); - this.client.addQueryInBatch(this.index, this.q, this._getHitsSearchParams()); - for (var i = 0; i < this.options.disjunctiveFacets.length; ++i) { - this.client.addQueryInBatch(this.index, this.q, this._getDisjunctiveFacetSearchParams(this.options.disjunctiveFacets[i])); - } - var self = this; - this.client.sendQueriesBatch(function(success, content) { - if (!success) { - self.searchCallback(false, content); - return; - } - var aggregatedAnswer = content.results[0]; - aggregatedAnswer.disjunctiveFacets = {}; - aggregatedAnswer.facetStats = {}; - for (var i = 1; i < content.results.length; ++i) { - for (var facet in content.results[i].facets) { - aggregatedAnswer.disjunctiveFacets[facet] = content.results[i].facets[facet]; - if (self.disjunctiveRefinements[facet]) { - for (var value in self.disjunctiveRefinements[facet]) { - if (!aggregatedAnswer.disjunctiveFacets[facet][value] && self.disjunctiveRefinements[facet][value]) { - aggregatedAnswer.disjunctiveFacets[facet][value] = 0; - } - } - } - } - for (var stats in content.results[i].facets_stats) - { - aggregatedAnswer.facetStats[stats] = content.results[i].facets_stats[stats]; - } - } - self.searchCallback(true, aggregatedAnswer); - }); - }, - - /** - * Build search parameters used to fetch hits - * @return {hash} - */ - _getHitsSearchParams: function() { - return extend({}, { - hitsPerPage: this.options.hitsPerPage, - page: this.page, - facets: this.options.facets, - facetFilters: this._getFacetFilters() - }, this.searchParams); - }, - - /** - * Build search parameters used to fetch a disjunctive facet - * @param {string} facet the associated facet name - * @return {hash} - */ - _getDisjunctiveFacetSearchParams: function(facet) { - return extend({}, this.searchParams, { - hitsPerPage: 1, - page: 0, - facets: facet, - facetFilters: this._getFacetFilters(facet) - }); - }, - - /** - * Build facetFilters parameter based on current refinements - * @param {string} facet if set, the current disjunctive facet - * @return {hash} - */ - _getFacetFilters: function(facet) { - var facetFilters = []; - for (var refinement in this.refinements) { - if (this.refinements[refinement]) { - facetFilters.push(refinement); - } - } - for (var disjunctiveRefinement in this.disjunctiveRefinements) { - if (disjunctiveRefinement != facet) { - var refinements = []; - for (var value in this.disjunctiveRefinements[disjunctiveRefinement]) { - if (this.disjunctiveRefinements[disjunctiveRefinement][value]) { - refinements.push(disjunctiveRefinement + ':' + value); - } - } - if (refinements.length > 0) { - facetFilters.push(refinements); - } - } - } - return facetFilters; - } - }; -})(); - -/* - json2.js - 2014-02-04 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, regexp: true */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (typeof JSON !== 'object') { - JSON = {}; -} - -(function () { - 'use strict'; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function () { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function () { - return this.valueOf(); - }; - } - - var cx, - escapable, - gap, - indent, - meta, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }; - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); \ No newline at end of file +function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i.5&&this.hosts.reverse(),this.hosts.push(this._isUndefined(c)||null==c?("https:"==document.location.protocol?"https":"http")+"://"+e[g]:"https"===c||"HTTPS"===c?"https://"+e[g]:"http://"+e[g]);Math.random()>.5&&this.hosts.reverse(),this.jsonp=null,this.jsonpWait=0,this._jsonRequest({method:"GET",url:"/1/isalive",callback:function(a){f.jsonp=!a}}),this.extraHeaders=[]};AlgoliaSearch.prototype={deleteIndex:function(a,b){this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(a),callback:b})},moveIndex:function(a,b,c){var d={operation:"move",destination:b};this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},copyIndex:function(a,b,c){var d={operation:"copy",destination:b};this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},getLogs:function(a,b,c){this._isUndefined(b)&&(b=0),this._isUndefined(c)&&(c=10),this._jsonRequest({method:"GET",url:"/1/logs?offset="+b+"&length="+c,callback:a})},listIndexes:function(a){this._jsonRequest({method:"GET",url:"/1/indexes",callback:a})},initIndex:function(a){return new this.Index(this,a)},listUserKeys:function(a){this._jsonRequest({method:"GET",url:"/1/keys",callback:a})},getUserKeyACL:function(a,b){this._jsonRequest({method:"GET",url:"/1/keys/"+a,callback:b})},deleteUserKey:function(a,b){this._jsonRequest({method:"DELETE",url:"/1/keys/"+a,callback:b})},addUserKey:function(a,b){var c={};c.acl=a,this._jsonRequest({method:"POST",url:"/1/keys",body:c,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this._jsonRequest({method:"POST",url:"/1/indexes/"+f.indexName+"/keys",body:g,callback:e})},setSecurityTags:function(a){if("[object Array]"===Object.prototype.toString.call(a)){for(var b=[],c=0;c0){var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f}else this._sendQueriesBatch(d,a)},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null==this.jsonp){var c=this;return void this._waitReady(function(){c._sendQueriesBatch(a,b)})}if(this.jsonp){for(var d="",e=0;e2e3&&(this.jsonp=!0),setTimeout(a,100))},_getSearchParams:function(a,b){if(this._isUndefined(a)||null==a)return b;for(var c in a)null!=c&&a.hasOwnProperty(c)&&(b+=0===b.length?"?":"&",b+=c+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(a[c])?JSON.stringify(a[c]):a[c]));return b},_isUndefined:function(a){return void 0===a},applicationID:null,apiKey:null,tagFilters:null,userToken:null,hosts:[],cache:{},extraHeaders:[]},AlgoliaSearch.prototype.Index.prototype={clearCache:function(){this.cache={}},addObject:function(a,b,c){var d=this;this.as._jsonRequest(this.as._isUndefined(c)?{method:"POST",url:"/1/indexes/"+encodeURIComponent(d.indexName),body:a,callback:b}:{method:"PUT",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(c),body:a,callback:b})},addObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e0){var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g}else this._search(f,b)},browse:function(a,b,c){var d=this,e="?page="+a;_.isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){a&&d(b.hits)},a)}},waitTask:function(a,b){var c=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){if(null==this.as.jsonp){var c=this;return void this.as._waitReady(function(){c._search(a,b)})}var d={params:a,apiKey:this.as.apiKey,appID:this.as.applicationID};this.as.tagFilters&&(d["X-Algolia-TagFilters"]=this.as.tagFilters),this.as.userToken&&(d["X-Algolia-UserToken"]=this.as.userToken),this.as._jsonRequest(this.as.jsonp?{cache:this.cache,method:"GET",jsonp:!0,url:"/1/indexes/"+encodeURIComponent(this.indexName),body:d,callback:b}:{cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:d,callback:b})},as:null,indexName:null,cache:{},typeAheadArgs:null,typeAheadValueOption:null,emptyConstructor:function(){}},function(){var a=function(a){a=a||{};for(var b=1;b0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());for(var a=0;a0&&b.push(e)}return b}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(); \ No newline at end of file diff --git a/services/web/public/js/libs/moment-2.9.0.js b/services/web/public/js/libs/moment-2.9.0.js index d98958d75a..024d488fbc 100755 --- a/services/web/public/js/libs/moment-2.9.0.js +++ b/services/web/public/js/libs/moment-2.9.0.js @@ -3,3041 +3,5 @@ //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com - -(function (undefined) { - /************************************ - Constants - ************************************/ - - var moment, - VERSION = '2.9.0', - // the global-scope this is NOT the global object in Node.js - globalScope = (typeof global !== 'undefined' && (typeof window === 'undefined' || window === global.window)) ? global : this, - oldGlobalMoment, - round = Math.round, - hasOwnProperty = Object.prototype.hasOwnProperty, - i, - - YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - - // internal storage for locale config files - locales = {}, - - // extra moment internal properties (plugins register props here) - momentProperties = [], - - // check for nodeJS - hasModule = (typeof module !== 'undefined' && module && module.exports), - - // ASP.NET json date format regex - aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, - aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, - - // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, - - // parsing token regexes - parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 - parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 - parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 - parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 - parseTokenDigits = /\d+/, // nonzero number of digits - parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. - parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - parseTokenT = /T/i, // T (ISO separator) - parseTokenOffsetMs = /[\+\-]?\d+/, // 1234567890123 - parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - - //strict parsing regexes - parseTokenOneDigit = /\d/, // 0 - 9 - parseTokenTwoDigits = /\d\d/, // 00 - 99 - parseTokenThreeDigits = /\d{3}/, // 000 - 999 - parseTokenFourDigits = /\d{4}/, // 0000 - 9999 - parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 - parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - - isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', - - isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], - ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], - ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], - ['GGGG-[W]WW', /\d{4}-W\d{2}/], - ['YYYY-DDD', /\d{4}-\d{3}/] - ], - - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], - ['HH:mm', /(T| )\d\d:\d\d/], - ['HH', /(T| )\d\d/] - ], - - // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-', '15', '30'] - parseTimezoneChunker = /([\+\-]|\d\d)/gi, - - // getter and setter names - proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), - unitMillisecondFactors = { - 'Milliseconds' : 1, - 'Seconds' : 1e3, - 'Minutes' : 6e4, - 'Hours' : 36e5, - 'Days' : 864e5, - 'Months' : 2592e6, - 'Years' : 31536e6 - }, - - unitAliases = { - ms : 'millisecond', - s : 'second', - m : 'minute', - h : 'hour', - d : 'day', - D : 'date', - w : 'week', - W : 'isoWeek', - M : 'month', - Q : 'quarter', - y : 'year', - DDD : 'dayOfYear', - e : 'weekday', - E : 'isoWeekday', - gg: 'weekYear', - GG: 'isoWeekYear' - }, - - camelFunctions = { - dayofyear : 'dayOfYear', - isoweekday : 'isoWeekday', - isoweek : 'isoWeek', - weekyear : 'weekYear', - isoweekyear : 'isoWeekYear' - }, - - // format function strings - formatFunctions = {}, - - // default relative time thresholds - relativeTimeThresholds = { - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month - M: 11 // months to year - }, - - // tokens to ordinalize and pad - ordinalizeTokens = 'DDD w W M D d'.split(' '), - paddedTokens = 'M D H h m s w W'.split(' '), - - formatTokenFunctions = { - M : function () { - return this.month() + 1; - }, - MMM : function (format) { - return this.localeData().monthsShort(this, format); - }, - MMMM : function (format) { - return this.localeData().months(this, format); - }, - D : function () { - return this.date(); - }, - DDD : function () { - return this.dayOfYear(); - }, - d : function () { - return this.day(); - }, - dd : function (format) { - return this.localeData().weekdaysMin(this, format); - }, - ddd : function (format) { - return this.localeData().weekdaysShort(this, format); - }, - dddd : function (format) { - return this.localeData().weekdays(this, format); - }, - w : function () { - return this.week(); - }, - W : function () { - return this.isoWeek(); - }, - YY : function () { - return leftZeroFill(this.year() % 100, 2); - }, - YYYY : function () { - return leftZeroFill(this.year(), 4); - }, - YYYYY : function () { - return leftZeroFill(this.year(), 5); - }, - YYYYYY : function () { - var y = this.year(), sign = y >= 0 ? '+' : '-'; - return sign + leftZeroFill(Math.abs(y), 6); - }, - gg : function () { - return leftZeroFill(this.weekYear() % 100, 2); - }, - gggg : function () { - return leftZeroFill(this.weekYear(), 4); - }, - ggggg : function () { - return leftZeroFill(this.weekYear(), 5); - }, - GG : function () { - return leftZeroFill(this.isoWeekYear() % 100, 2); - }, - GGGG : function () { - return leftZeroFill(this.isoWeekYear(), 4); - }, - GGGGG : function () { - return leftZeroFill(this.isoWeekYear(), 5); - }, - e : function () { - return this.weekday(); - }, - E : function () { - return this.isoWeekday(); - }, - a : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), true); - }, - A : function () { - return this.localeData().meridiem(this.hours(), this.minutes(), false); - }, - H : function () { - return this.hours(); - }, - h : function () { - return this.hours() % 12 || 12; - }, - m : function () { - return this.minutes(); - }, - s : function () { - return this.seconds(); - }, - S : function () { - return toInt(this.milliseconds() / 100); - }, - SS : function () { - return leftZeroFill(toInt(this.milliseconds() / 10), 2); - }, - SSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - SSSS : function () { - return leftZeroFill(this.milliseconds(), 3); - }, - Z : function () { - var a = this.utcOffset(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); - }, - ZZ : function () { - var a = this.utcOffset(), - b = '+'; - if (a < 0) { - a = -a; - b = '-'; - } - return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); - }, - z : function () { - return this.zoneAbbr(); - }, - zz : function () { - return this.zoneName(); - }, - x : function () { - return this.valueOf(); - }, - X : function () { - return this.unix(); - }, - Q : function () { - return this.quarter(); - } - }, - - deprecations = {}, - - lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'], - - updateInProgress = false; - - // Pick the first defined of two or three arguments. dfl comes from - // default. - function dfl(a, b, c) { - switch (arguments.length) { - case 2: return a != null ? a : b; - case 3: return a != null ? a : b != null ? b : c; - default: throw new Error('Implement me'); - } - } - - function hasOwnProp(a, b) { - return hasOwnProperty.call(a, b); - } - - function defaultParsingFlags() { - // We need to deep clone this object, and es5 standard is not very - // helpful. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso: false - }; - } - - function printMsg(msg) { - if (moment.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && console.warn) { - console.warn('Deprecation warning: ' + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - return extend(function () { - if (firstTime) { - printMsg(msg); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - function deprecateSimple(name, msg) { - if (!deprecations[name]) { - printMsg(msg); - deprecations[name] = true; - } - } - - function padToken(func, count) { - return function (a) { - return leftZeroFill(func.call(this, a), count); - }; - } - function ordinalizeToken(func, period) { - return function (a) { - return this.localeData().ordinal(func.call(this, a), period); - }; - } - - function monthDiff(a, b) { - // difference in months - var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, adjust; - - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); - } - - return -(wholeMonthDiff + adjust); - } - - while (ordinalizeTokens.length) { - i = ordinalizeTokens.pop(); - formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); - } - while (paddedTokens.length) { - i = paddedTokens.pop(); - formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); - } - formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); - - - function meridiemFixWrap(locale, hour, meridiem) { - var isPm; - - if (meridiem == null) { - // nothing to do - return hour; - } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; - } else { - // thie is not supposed to happen - return hour; - } - } - - /************************************ - Constructors - ************************************/ - - function Locale() { - } - - // Moment prototype object - function Moment(config, skipOverflow) { - if (skipOverflow !== false) { - checkOverflow(config); - } - copyConfig(this, config); - this._d = new Date(+config._d); - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - moment.updateOffset(this); - updateInProgress = false; - } - } - - // Duration Constructor - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 36e5; // 1000 * 60 * 60 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._locale = moment.localeData(); - - this._bubble(); - } - - /************************************ - Helpers - ************************************/ - - - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } - - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function copyConfig(to, from) { - var i, prop, val; - - if (typeof from._isAMomentObject !== 'undefined') { - to._isAMomentObject = from._isAMomentObject; - } - if (typeof from._i !== 'undefined') { - to._i = from._i; - } - if (typeof from._f !== 'undefined') { - to._f = from._f; - } - if (typeof from._l !== 'undefined') { - to._l = from._l; - } - if (typeof from._strict !== 'undefined') { - to._strict = from._strict; - } - if (typeof from._tzm !== 'undefined') { - to._tzm = from._tzm; - } - if (typeof from._isUTC !== 'undefined') { - to._isUTC = from._isUTC; - } - if (typeof from._offset !== 'undefined') { - to._offset = from._offset; - } - if (typeof from._pf !== 'undefined') { - to._pf = from._pf; - } - if (typeof from._locale !== 'undefined') { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i in momentProperties) { - prop = momentProperties[i]; - val = from[prop]; - if (typeof val !== 'undefined') { - to[prop] = val; - } - } - } - - return to; - } - - function absRound(number) { - if (number < 0) { - return Math.ceil(number); - } else { - return Math.floor(number); - } - } - - // left zero fill a number - // see http://jsperf.com/left-zero-filling for performance comparison - function leftZeroFill(number, targetLength, forceSign) { - var output = '' + Math.abs(number), - sign = number >= 0; - - while (output.length < targetLength) { - output = '0' + output; - } - return (sign ? (forceSign ? '+' : '') : '-') + output; - } - - function positiveMomentsDifference(base, other) { - var res = {milliseconds: 0, months: 0}; - - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - other = makeAs(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).'); - tmp = val; val = period; period = tmp; - } - - val = typeof val === 'string' ? +val : val; - dur = moment.duration(val, period); - addOrSubtractDurationFromMoment(this, dur, direction); - return this; - }; - } - - function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = duration._days, - months = duration._months; - updateOffset = updateOffset == null ? true : updateOffset; - - if (milliseconds) { - mom._d.setTime(+mom._d + milliseconds * isAdding); - } - if (days) { - rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); - } - if (months) { - rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); - } - if (updateOffset) { - moment.updateOffset(mom, days || months); - } - } - - // check if is an array - function isArray(input) { - return Object.prototype.toString.call(input) === '[object Array]'; - } - - function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function normalizeUnits(units) { - if (units) { - var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); - units = unitAliases[units] || camelFunctions[lowered] || lowered; - } - return units; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - function makeList(field) { - var count, setter; - - if (field.indexOf('week') === 0) { - count = 7; - setter = 'day'; - } - else if (field.indexOf('month') === 0) { - count = 12; - setter = 'month'; - } - else { - return; - } - - moment[field] = function (format, index) { - var i, getter, - method = moment._locale[field], - results = []; - - if (typeof format === 'number') { - index = format; - format = undefined; - } - - getter = function (i) { - var m = moment().utc().set(setter, i); - return method.call(moment._locale, m, format || ''); - }; - - if (index != null) { - return getter(index); - } - else { - for (i = 0; i < count; i++) { - results.push(getter(i)); - } - return results; - } - }; - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - if (coercedNumber >= 0) { - value = Math.floor(coercedNumber); - } else { - value = Math.ceil(coercedNumber); - } - } - - return value; - } - - function daysInMonth(year, month) { - return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); - } - - function weeksInYear(year, dow, doy) { - return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; - } - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - function checkOverflow(m) { - var overflow; - if (m._a && m._pf.overflow === -2) { - overflow = - m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : - m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : - m._a[HOUR] < 0 || m._a[HOUR] > 24 || - (m._a[HOUR] === 24 && (m._a[MINUTE] !== 0 || - m._a[SECOND] !== 0 || - m._a[MILLISECOND] !== 0)) ? HOUR : - m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : - m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : - m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - - m._pf.overflow = overflow; - } - } - - function isValid(m) { - if (m._isValid == null) { - m._isValid = !isNaN(m._d.getTime()) && - m._pf.overflow < 0 && - !m._pf.empty && - !m._pf.invalidMonth && - !m._pf.nullInput && - !m._pf.invalidFormat && - !m._pf.userInvalidated; - - if (m._strict) { - m._isValid = m._isValid && - m._pf.charsLeftOver === 0 && - m._pf.unusedTokens.length === 0 && - m._pf.bigHour === undefined; - } - } - return m._isValid; - } - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return null; - } - - function loadLocale(name) { - var oldLocale = null; - if (!locales[name] && hasModule) { - try { - oldLocale = moment.locale(); - require('./locale/' + name); - // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales - moment.locale(oldLocale); - } catch (e) { } - } - return locales[name]; - } - - // Return a moment from input, that is local/utc/utcOffset equivalent to - // model. - function makeAs(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (moment.isMoment(input) || isDate(input) ? - +input : +moment(input)) - (+res); - // Use low-level api, because this fn is low-level api. - res._d.setTime(+res._d + diff); - moment.updateOffset(res, false); - return res; - } else { - return moment(input).local(); - } - } - - /************************************ - Locale - ************************************/ - - - extend(Locale.prototype, { - - set : function (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (typeof prop === 'function') { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _ordinalParseLenient. - this._ordinalParseLenient = new RegExp(this._ordinalParse.source + '|' + /\d{1,2}/.source); - }, - - _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), - months : function (m) { - return this._months[m.month()]; - }, - - _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), - monthsShort : function (m) { - return this._monthsShort[m.month()]; - }, - - monthsParse : function (monthName, format, strict) { - var i, mom, regex; - - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } - - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = moment.utc([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); - this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); - } - if (!strict && !this._monthsParse[i]) { - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { - return i; - } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } - } - }, - - _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), - weekdays : function (m) { - return this._weekdays[m.day()]; - }, - - _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - weekdaysShort : function (m) { - return this._weekdaysShort[m.day()]; - }, - - _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - weekdaysMin : function (m) { - return this._weekdaysMin[m.day()]; - }, - - weekdaysParse : function (weekdayName) { - var i, mom, regex; - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - if (!this._weekdaysParse[i]) { - mom = moment([2000, 1]).day(i); - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - }, - - _longDateFormat : { - LTS : 'h:mm:ss A', - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY LT', - LLLL : 'dddd, MMMM D, YYYY LT' - }, - longDateFormat : function (key) { - var output = this._longDateFormat[key]; - if (!output && this._longDateFormat[key.toUpperCase()]) { - output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - this._longDateFormat[key] = output; - } - return output; - }, - - isPM : function (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - }, - - _meridiemParse : /[ap]\.?m?\.?/i, - meridiem : function (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - }, - - - _calendar : { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }, - calendar : function (key, mom, now) { - var output = this._calendar[key]; - return typeof output === 'function' ? output.apply(mom, [now]) : output; - }, - - _relativeTime : { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }, - - relativeTime : function (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (typeof output === 'function') ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - }, - - pastFuture : function (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); - }, - - ordinal : function (number) { - return this._ordinal.replace('%d', number); - }, - _ordinal : '%d', - _ordinalParse : /\d{1,2}/, - - preparse : function (string) { - return string; - }, - - postformat : function (string) { - return string; - }, - - week : function (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - }, - - _week : { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 1st is the first week of the year. - }, - - firstDayOfWeek : function () { - return this._week.dow; - }, - - firstDayOfYear : function () { - return this._week.doy; - }, - - _invalidDate: 'Invalid date', - invalidDate: function () { - return this._invalidDate; - } - }); - - /************************************ - Formatting - ************************************/ - - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = ''; - for (i = 0; i < length; i++) { - output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - - if (!formatFunctions[format]) { - formatFunctions[format] = makeFormatFunction(format); - } - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - - /************************************ - Parsing - ************************************/ - - - // get the regex to find the next token - function getParseRegexForToken(token, config) { - var a, strict = config._strict; - switch (token) { - case 'Q': - return parseTokenOneDigit; - case 'DDDD': - return parseTokenThreeDigits; - case 'YYYY': - case 'GGGG': - case 'gggg': - return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; - case 'Y': - case 'G': - case 'g': - return parseTokenSignedNumber; - case 'YYYYYY': - case 'YYYYY': - case 'GGGGG': - case 'ggggg': - return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; - case 'S': - if (strict) { - return parseTokenOneDigit; - } - /* falls through */ - case 'SS': - if (strict) { - return parseTokenTwoDigits; - } - /* falls through */ - case 'SSS': - if (strict) { - return parseTokenThreeDigits; - } - /* falls through */ - case 'DDD': - return parseTokenOneToThreeDigits; - case 'MMM': - case 'MMMM': - case 'dd': - case 'ddd': - case 'dddd': - return parseTokenWord; - case 'a': - case 'A': - return config._locale._meridiemParse; - case 'x': - return parseTokenOffsetMs; - case 'X': - return parseTokenTimestampMs; - case 'Z': - case 'ZZ': - return parseTokenTimezone; - case 'T': - return parseTokenT; - case 'SSSS': - return parseTokenDigits; - case 'MM': - case 'DD': - case 'YY': - case 'GG': - case 'gg': - case 'HH': - case 'hh': - case 'mm': - case 'ss': - case 'ww': - case 'WW': - return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; - case 'M': - case 'D': - case 'd': - case 'H': - case 'h': - case 'm': - case 's': - case 'w': - case 'W': - case 'e': - case 'E': - return parseTokenOneOrTwoDigits; - case 'Do': - return strict ? config._locale._ordinalParse : config._locale._ordinalParseLenient; - default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); - return a; - } - } - - function utcOffsetFromString(string) { - string = string || ''; - var possibleTzMatches = (string.match(parseTokenTimezone) || []), - tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], - parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], - minutes = +(parts[1] * 60) + toInt(parts[2]); - - return parts[0] === '+' ? minutes : -minutes; - } - - // function to convert string input to date - function addTimeToArrayFromToken(token, input, config) { - var a, datePartArray = config._a; - - switch (token) { - // QUARTER - case 'Q': - if (input != null) { - datePartArray[MONTH] = (toInt(input) - 1) * 3; - } - break; - // MONTH - case 'M' : // fall through to MM - case 'MM' : - if (input != null) { - datePartArray[MONTH] = toInt(input) - 1; - } - break; - case 'MMM' : // fall through to MMMM - case 'MMMM' : - a = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (a != null) { - datePartArray[MONTH] = a; - } else { - config._pf.invalidMonth = input; - } - break; - // DAY OF MONTH - case 'D' : // fall through to DD - case 'DD' : - if (input != null) { - datePartArray[DATE] = toInt(input); - } - break; - case 'Do' : - if (input != null) { - datePartArray[DATE] = toInt(parseInt( - input.match(/\d{1,2}/)[0], 10)); - } - break; - // DAY OF YEAR - case 'DDD' : // fall through to DDDD - case 'DDDD' : - if (input != null) { - config._dayOfYear = toInt(input); - } - - break; - // YEAR - case 'YY' : - datePartArray[YEAR] = moment.parseTwoDigitYear(input); - break; - case 'YYYY' : - case 'YYYYY' : - case 'YYYYYY' : - datePartArray[YEAR] = toInt(input); - break; - // AM / PM - case 'a' : // fall through to A - case 'A' : - config._meridiem = input; - // config._isPm = config._locale.isPM(input); - break; - // HOUR - case 'h' : // fall through to hh - case 'hh' : - config._pf.bigHour = true; - /* falls through */ - case 'H' : // fall through to HH - case 'HH' : - datePartArray[HOUR] = toInt(input); - break; - // MINUTE - case 'm' : // fall through to mm - case 'mm' : - datePartArray[MINUTE] = toInt(input); - break; - // SECOND - case 's' : // fall through to ss - case 'ss' : - datePartArray[SECOND] = toInt(input); - break; - // MILLISECOND - case 'S' : - case 'SS' : - case 'SSS' : - case 'SSSS' : - datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); - break; - // UNIX OFFSET (MILLISECONDS) - case 'x': - config._d = new Date(toInt(input)); - break; - // UNIX TIMESTAMP WITH MS - case 'X': - config._d = new Date(parseFloat(input) * 1000); - break; - // TIMEZONE - case 'Z' : // fall through to ZZ - case 'ZZ' : - config._useUTC = true; - config._tzm = utcOffsetFromString(input); - break; - // WEEKDAY - human - case 'dd': - case 'ddd': - case 'dddd': - a = config._locale.weekdaysParse(input); - // if we didn't get a weekday name, mark the date as invalid - if (a != null) { - config._w = config._w || {}; - config._w['d'] = a; - } else { - config._pf.invalidWeekday = input; - } - break; - // WEEK, WEEK DAY - numeric - case 'w': - case 'ww': - case 'W': - case 'WW': - case 'd': - case 'e': - case 'E': - token = token.substr(0, 1); - /* falls through */ - case 'gggg': - case 'GGGG': - case 'GGGGG': - token = token.substr(0, 2); - if (input) { - config._w = config._w || {}; - config._w[token] = toInt(input); - } - break; - case 'gg': - case 'GG': - config._w = config._w || {}; - config._w[token] = moment.parseTwoDigitYear(input); - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); - week = dfl(w.W, 1); - weekday = dfl(w.E, 1); - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); - week = dfl(w.w, 1); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < dow) { - ++week; - } - } else if (w.e != null) { - // local weekday -- counting starts from begining of week - weekday = w.e + dow; - } else { - // default to begining of week - weekday = dow; - } - } - temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function dateFromConfig(config) { - var i, date, input = [], currentDate, yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear) { - yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); - - if (config._dayOfYear > daysInYear(yearToUse)) { - config._pf._overflowDayOfYear = true; - } - - date = makeUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - // Check for 24:00:00.000 - if (config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0) { - config._nextDay = true; - config._a[HOUR] = 0; - } - - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } - - if (config._nextDay) { - config._a[HOUR] = 24; - } - } - - function dateFromObject(config) { - var normalizedInput; - - if (config._d) { - return; - } - - normalizedInput = normalizeObjectUnits(config._i); - config._a = [ - normalizedInput.year, - normalizedInput.month, - normalizedInput.day || normalizedInput.date, - normalizedInput.hour, - normalizedInput.minute, - normalizedInput.second, - normalizedInput.millisecond - ]; - - dateFromConfig(config); - } - - function currentDateArray(config) { - var now = new Date(); - if (config._useUTC) { - return [ - now.getUTCFullYear(), - now.getUTCMonth(), - now.getUTCDate() - ]; - } else { - return [now.getFullYear(), now.getMonth(), now.getDate()]; - } - } - - // date from string and format string - function makeDateFromStringAndFormat(config) { - if (config._f === moment.ISO_8601) { - parseISO(config); - return; - } - - config._a = []; - config._pf.empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - config._pf.unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - config._pf.empty = false; - } - else { - config._pf.unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - config._pf.unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - config._pf.charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - config._pf.unusedInput.push(string); - } - - // clear _12h flag if hour is <= 12 - if (config._pf.bigHour === true && config._a[HOUR] <= 12) { - config._pf.bigHour = undefined; - } - // handle meridiem - config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], - config._meridiem); - dateFromConfig(config); - checkOverflow(config); - } - - function unescapeFormat(s) { - return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - }); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function regexpEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - // date from string and array of format strings - function makeDateFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - config._pf.invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._pf = defaultParsingFlags(); - tempConfig._f = config._f[i]; - makeDateFromStringAndFormat(tempConfig); - - if (!isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += tempConfig._pf.charsLeftOver; - - //or tokens - currentScore += tempConfig._pf.unusedTokens.length * 10; - - tempConfig._pf.score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - // date from iso format - function parseISO(config) { - var i, l, - string = config._i, - match = isoRegex.exec(string); - - if (match) { - config._pf.iso = true; - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(string)) { - // match[5] should be 'T' or undefined - config._f = isoDates[i][0] + (match[6] || ' '); - break; - } - } - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(string)) { - config._f += isoTimes[i][0]; - break; - } - } - if (string.match(parseTokenTimezone)) { - config._f += 'Z'; - } - makeDateFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - // date from iso format or fallback - function makeDateFromString(config) { - parseISO(config); - if (config._isValid === false) { - delete config._isValid; - moment.createFromInputFallback(config); - } - } - - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } - - function makeDateFromInput(config) { - var input = config._i, matched; - if (input === undefined) { - config._d = new Date(); - } else if (isDate(input)) { - config._d = new Date(+input); - } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { - config._d = new Date(+matched[1]); - } else if (typeof input === 'string') { - makeDateFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - dateFromConfig(config); - } else if (typeof(input) === 'object') { - dateFromObject(config); - } else if (typeof(input) === 'number') { - // from milliseconds - config._d = new Date(input); - } else { - moment.createFromInputFallback(config); - } - } - - function makeDate(y, m, d, h, M, s, ms) { - //can't just apply() to create a date: - //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply - var date = new Date(y, m, d, h, M, s, ms); - - //the date constructor doesn't accept years < 1970 - if (y < 1970) { - date.setFullYear(y); - } - return date; - } - - function makeUTCDate(y) { - var date = new Date(Date.UTC.apply(null, arguments)); - if (y < 1970) { - date.setUTCFullYear(y); - } - return date; - } - - function parseWeekday(input, locale) { - if (typeof input === 'string') { - if (!isNaN(input)) { - input = parseInt(input, 10); - } - else { - input = locale.weekdaysParse(input); - if (typeof input !== 'number') { - return null; - } - } - } - return input; - } - - /************************************ - Relative Time - ************************************/ - - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime(posNegDuration, withoutSuffix, locale) { - var duration = moment.duration(posNegDuration).abs(), - seconds = round(duration.as('s')), - minutes = round(duration.as('m')), - hours = round(duration.as('h')), - days = round(duration.as('d')), - months = round(duration.as('M')), - years = round(duration.as('y')), - - args = seconds < relativeTimeThresholds.s && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < relativeTimeThresholds.m && ['mm', minutes] || - hours === 1 && ['h'] || - hours < relativeTimeThresholds.h && ['hh', hours] || - days === 1 && ['d'] || - days < relativeTimeThresholds.d && ['dd', days] || - months === 1 && ['M'] || - months < relativeTimeThresholds.M && ['MM', months] || - years === 1 && ['y'] || ['yy', years]; - - args[2] = withoutSuffix; - args[3] = +posNegDuration > 0; - args[4] = locale; - return substituteTimeAgo.apply({}, args); - } - - - /************************************ - Week of Year - ************************************/ - - - // firstDayOfWeek 0 = sun, 6 = sat - // the day of the week that starts the week - // (usually sunday or monday) - // firstDayOfWeekOfYear 0 = sun, 6 = sat - // the first week is the week that contains the first - // of this day of the week - // (eg. ISO weeks use thursday (4)) - function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { - var end = firstDayOfWeekOfYear - firstDayOfWeek, - daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), - adjustedMoment; - - - if (daysToDayOfWeek > end) { - daysToDayOfWeek -= 7; - } - - if (daysToDayOfWeek < end - 7) { - daysToDayOfWeek += 7; - } - - adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); - return { - week: Math.ceil(adjustedMoment.dayOfYear() / 7), - year: adjustedMoment.year() - }; - } - - //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { - var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; - - d = d === 0 ? 7 : d; - weekday = weekday != null ? weekday : firstDayOfWeek; - daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); - dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; - - return { - year: dayOfYear > 0 ? year : year - 1, - dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear - }; - } - - /************************************ - Top Level Functions - ************************************/ - - function makeMoment(config) { - var input = config._i, - format = config._f, - res; - - config._locale = config._locale || moment.localeData(config._l); - - if (input === null || (format === undefined && input === '')) { - return moment.invalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (moment.isMoment(input)) { - return new Moment(input, true); - } else if (format) { - if (isArray(format)) { - makeDateFromStringAndArray(config); - } else { - makeDateFromStringAndFormat(config); - } - } else { - makeDateFromInput(config); - } - - res = new Moment(config); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } - - return res; - } - - moment = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._i = input; - c._f = format; - c._l = locale; - c._strict = strict; - c._isUTC = false; - c._pf = defaultParsingFlags(); - - return makeMoment(c); - }; - - moment.suppressDeprecationWarnings = false; - - moment.createFromInputFallback = deprecate( - 'moment construction falls back to js Date. This is ' + - 'discouraged and will be removed in upcoming major ' + - 'release. Please refer to ' + - 'https://github.com/moment/moment/issues/1407 for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return moment(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - moment.min = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - }; - - moment.max = function () { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - }; - - // creating with utc - moment.utc = function (input, format, locale, strict) { - var c; - - if (typeof(locale) === 'boolean') { - strict = locale; - locale = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c = {}; - c._isAMomentObject = true; - c._useUTC = true; - c._isUTC = true; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - c._pf = defaultParsingFlags(); - - return makeMoment(c).utc(); - }; - - // creating with unix timestamp (in seconds) - moment.unix = function (input) { - return moment(input * 1000); - }; - - // duration - moment.duration = function (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - parseIso, - diffRes; - - if (moment.isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months - }; - } else if (typeof input === 'number') { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(match[MILLISECOND]) * sign - }; - } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - parseIso = function (inp) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - }; - duration = { - y: parseIso(match[2]), - M: parseIso(match[3]), - d: parseIso(match[4]), - h: parseIso(match[5]), - m: parseIso(match[6]), - s: parseIso(match[7]), - w: parseIso(match[8]) - }; - } else if (duration == null) {// checks for null or undefined - duration = {}; - } else if (typeof duration === 'object' && - ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(moment(duration.from), moment(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (moment.isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } - - return ret; - }; - - // version number - moment.version = VERSION; - - // default format - moment.defaultFormat = isoFormat; - - // constant that refers to the ISO standard - moment.ISO_8601 = function () {}; - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - moment.momentProperties = momentProperties; - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - moment.updateOffset = function () {}; - - // This function allows you to set a threshold for relative time strings - moment.relativeTimeThreshold = function (threshold, limit) { - if (relativeTimeThresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return relativeTimeThresholds[threshold]; - } - relativeTimeThresholds[threshold] = limit; - return true; - }; - - moment.lang = deprecate( - 'moment.lang is deprecated. Use moment.locale instead.', - function (key, value) { - return moment.locale(key, value); - } - ); - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - moment.locale = function (key, values) { - var data; - if (key) { - if (typeof(values) !== 'undefined') { - data = moment.defineLocale(key, values); - } - else { - data = moment.localeData(key); - } - - if (data) { - moment.duration._locale = moment._locale = data; - } - } - - return moment._locale._abbr; - }; - - moment.defineLocale = function (name, values) { - if (values !== null) { - values.abbr = name; - if (!locales[name]) { - locales[name] = new Locale(); - } - locales[name].set(values); - - // backwards compat for now: also set the locale - moment.locale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - }; - - moment.langData = deprecate( - 'moment.langData is deprecated. Use moment.localeData instead.', - function (key) { - return moment.localeData(key); - } - ); - - // returns locale data - moment.localeData = function (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return moment._locale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - }; - - // compare moment object - moment.isMoment = function (obj) { - return obj instanceof Moment || - (obj != null && hasOwnProp(obj, '_isAMomentObject')); - }; - - // for typechecking Duration objects - moment.isDuration = function (obj) { - return obj instanceof Duration; - }; - - for (i = lists.length - 1; i >= 0; --i) { - makeList(lists[i]); - } - - moment.normalizeUnits = function (units) { - return normalizeUnits(units); - }; - - moment.invalid = function (flags) { - var m = moment.utc(NaN); - if (flags != null) { - extend(m._pf, flags); - } - else { - m._pf.userInvalidated = true; - } - - return m; - }; - - moment.parseZone = function () { - return moment.apply(null, arguments).parseZone(); - }; - - moment.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - moment.isDate = isDate; - - /************************************ - Moment Prototype - ************************************/ - - - extend(moment.fn = Moment.prototype, { - - clone : function () { - return moment(this); - }, - - valueOf : function () { - return +this._d - ((this._offset || 0) * 60000); - }, - - unix : function () { - return Math.floor(+this / 1000); - }, - - toString : function () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - }, - - toDate : function () { - return this._offset ? new Date(+this) : this._d; - }, - - toISOString : function () { - var m = moment(this).utc(); - if (0 < m.year() && m.year() <= 9999) { - if ('function' === typeof Date.prototype.toISOString) { - // native implementation is ~50x faster, use it when we can - return this.toDate().toISOString(); - } else { - return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - } else { - return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); - } - }, - - toArray : function () { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hours(), - m.minutes(), - m.seconds(), - m.milliseconds() - ]; - }, - - isValid : function () { - return isValid(this); - }, - - isDSTShifted : function () { - if (this._a) { - return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; - } - - return false; - }, - - parsingFlags : function () { - return extend({}, this._pf); - }, - - invalidAt: function () { - return this._pf.overflow; - }, - - utc : function (keepLocalTime) { - return this.utcOffset(0, keepLocalTime); - }, - - local : function (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.subtract(this._dateUtcOffset(), 'm'); - } - } - return this; - }, - - format : function (inputString) { - var output = formatMoment(this, inputString || moment.defaultFormat); - return this.localeData().postformat(output); - }, - - add : createAdder(1, 'add'), - - subtract : createAdder(-1, 'subtract'), - - diff : function (input, units, asFloat) { - var that = makeAs(input, this), - zoneDiff = (that.utcOffset() - this.utcOffset()) * 6e4, - anchor, diff, output, daysAdjust; - - units = normalizeUnits(units); - - if (units === 'year' || units === 'month' || units === 'quarter') { - output = monthDiff(this, that); - if (units === 'quarter') { - output = output / 3; - } else if (units === 'year') { - output = output / 12; - } - } else { - diff = this - that; - output = units === 'second' ? diff / 1e3 : // 1000 - units === 'minute' ? diff / 6e4 : // 1000 * 60 - units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 - units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst - units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst - diff; - } - return asFloat ? output : absRound(output); - }, - - from : function (time, withoutSuffix) { - return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - }, - - fromNow : function (withoutSuffix) { - return this.from(moment(), withoutSuffix); - }, - - calendar : function (time) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're locat/utc/offset - // or not. - var now = time || moment(), - sod = makeAs(now, this).startOf('day'), - diff = this.diff(sod, 'days', true), - format = diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.localeData().calendar(format, this, moment(now))); - }, - - isLeapYear : function () { - return isLeapYear(this.year()); - }, - - isDST : function () { - return (this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset()); - }, - - day : function (input) { - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - }, - - month : makeAccessor('Month', true), - - startOf : function (units) { - units = normalizeUnits(units); - // the following switch intentionally omits break keywords - // to utilize falling through the cases. - switch (units) { - case 'year': - this.month(0); - /* falls through */ - case 'quarter': - case 'month': - this.date(1); - /* falls through */ - case 'week': - case 'isoWeek': - case 'day': - this.hours(0); - /* falls through */ - case 'hour': - this.minutes(0); - /* falls through */ - case 'minute': - this.seconds(0); - /* falls through */ - case 'second': - this.milliseconds(0); - /* falls through */ - } - - // weeks are a special case - if (units === 'week') { - this.weekday(0); - } else if (units === 'isoWeek') { - this.isoWeekday(1); - } - - // quarters are also special - if (units === 'quarter') { - this.month(Math.floor(this.month() / 3) * 3); - } - - return this; - }, - - endOf: function (units) { - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond') { - return this; - } - return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); - }, - - isAfter: function (input, units) { - var inputMs; - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this > +input; - } else { - inputMs = moment.isMoment(input) ? +input : +moment(input); - return inputMs < +this.clone().startOf(units); - } - }, - - isBefore: function (input, units) { - var inputMs; - units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this < +input; - } else { - inputMs = moment.isMoment(input) ? +input : +moment(input); - return +this.clone().endOf(units) < inputMs; - } - }, - - isBetween: function (from, to, units) { - return this.isAfter(from, units) && this.isBefore(to, units); - }, - - isSame: function (input, units) { - var inputMs; - units = normalizeUnits(units || 'millisecond'); - if (units === 'millisecond') { - input = moment.isMoment(input) ? input : moment(input); - return +this === +input; - } else { - inputMs = +moment(input); - return +(this.clone().startOf(units)) <= inputMs && inputMs <= +(this.clone().endOf(units)); - } - }, - - min: deprecate( - 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - } - ), - - max: deprecate( - 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', - function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - } - ), - - zone : deprecate( - 'moment().zone is deprecated, use moment().utcOffset instead. ' + - 'https://github.com/moment/moment/issues/1779', - function (input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } - - this.utcOffset(input, keepLocalTime); - - return this; - } else { - return -this.utcOffset(); - } - } - ), - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - utcOffset : function (input, keepLocalTime) { - var offset = this._offset || 0, - localAdjust; - if (input != null) { - if (typeof input === 'string') { - input = utcOffsetFromString(input); - } - if (Math.abs(input) < 16) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = this._dateUtcOffset(); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addOrSubtractDurationFromMoment(this, - moment.duration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - moment.updateOffset(this, true); - this._changeInProgress = null; - } - } - - return this; - } else { - return this._isUTC ? offset : this._dateUtcOffset(); - } - }, - - isLocal : function () { - return !this._isUTC; - }, - - isUtcOffset : function () { - return this._isUTC; - }, - - isUtc : function () { - return this._isUTC && this._offset === 0; - }, - - zoneAbbr : function () { - return this._isUTC ? 'UTC' : ''; - }, - - zoneName : function () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - }, - - parseZone : function () { - if (this._tzm) { - this.utcOffset(this._tzm); - } else if (typeof this._i === 'string') { - this.utcOffset(utcOffsetFromString(this._i)); - } - return this; - }, - - hasAlignedHourOffset : function (input) { - if (!input) { - input = 0; - } - else { - input = moment(input).utcOffset(); - } - - return (this.utcOffset() - input) % 60 === 0; - }, - - daysInMonth : function () { - return daysInMonth(this.year(), this.month()); - }, - - dayOfYear : function (input) { - var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - }, - - quarter : function (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - }, - - weekYear : function (input) { - var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - isoWeekYear : function (input) { - var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add((input - year), 'y'); - }, - - week : function (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - isoWeek : function (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - }, - - weekday : function (input) { - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - }, - - isoWeekday : function (input) { - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); - }, - - isoWeeksInYear : function () { - return weeksInYear(this.year(), 1, 4); - }, - - weeksInYear : function () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units](); - }, - - set : function (units, value) { - var unit; - if (typeof units === 'object') { - for (unit in units) { - this.set(unit, units[unit]); - } - } - else { - units = normalizeUnits(units); - if (typeof this[units] === 'function') { - this[units](value); - } - } - return this; - }, - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - locale : function (key) { - var newLocaleData; - - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = moment.localeData(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } - }, - - lang : deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ), - - localeData : function () { - return this._locale; - }, - - _dateUtcOffset : function () { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(this._d.getTimezoneOffset() / 15) * 15; - } - - }); - - function rawMonthSetter(mom, value) { - var dayOfMonth; - - // TODO: Move this out of here! - if (typeof value === 'string') { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (typeof value !== 'number') { - return mom; - } - } - - dayOfMonth = Math.min(mom.date(), - daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function rawGetter(mom, unit) { - return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); - } - - function rawSetter(mom, unit, value) { - if (unit === 'Month') { - return rawMonthSetter(mom, value); - } else { - return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - - function makeAccessor(unit, keepTime) { - return function (value) { - if (value != null) { - rawSetter(this, unit, value); - moment.updateOffset(this, keepTime); - return this; - } else { - return rawGetter(this, unit); - } - }; - } - - moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); - moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); - moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); - // Setting the hour should keep the time, because the user explicitly - // specified which hour he wants. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); - // moment.fn.month is defined separately - moment.fn.date = makeAccessor('Date', true); - moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); - moment.fn.year = makeAccessor('FullYear', true); - moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); - - // add plural methods - moment.fn.days = moment.fn.day; - moment.fn.months = moment.fn.month; - moment.fn.weeks = moment.fn.week; - moment.fn.isoWeeks = moment.fn.isoWeek; - moment.fn.quarters = moment.fn.quarter; - - // add aliased format methods - moment.fn.toJSON = moment.fn.toISOString; - - // alias isUtc for dev-friendliness - moment.fn.isUTC = moment.fn.isUtc; - - /************************************ - Duration Prototype - ************************************/ - - - function daysToYears (days) { - // 400 years have 146097 days (taking into account leap year rules) - return days * 400 / 146097; - } - - function yearsToDays (years) { - // years * 365 + absRound(years / 4) - - // absRound(years / 100) + absRound(years / 400); - return years * 146097 / 400; - } - - extend(moment.duration.fn = Duration.prototype, { - - _bubble : function () { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, minutes, hours, years = 0; - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absRound(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absRound(seconds / 60); - data.minutes = minutes % 60; - - hours = absRound(minutes / 60); - data.hours = hours % 24; - - days += absRound(hours / 24); - - // Accurately convert days to years, assume start from year 0. - years = absRound(daysToYears(days)); - days -= absRound(yearsToDays(years)); - - // 30 days to a month - // TODO (iskren): Use anchor date (like 1st Jan) to compute this. - months += absRound(days / 30); - days %= 30; - - // 12 months -> 1 year - years += absRound(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - }, - - abs : function () { - this._milliseconds = Math.abs(this._milliseconds); - this._days = Math.abs(this._days); - this._months = Math.abs(this._months); - - this._data.milliseconds = Math.abs(this._data.milliseconds); - this._data.seconds = Math.abs(this._data.seconds); - this._data.minutes = Math.abs(this._data.minutes); - this._data.hours = Math.abs(this._data.hours); - this._data.months = Math.abs(this._data.months); - this._data.years = Math.abs(this._data.years); - - return this; - }, - - weeks : function () { - return absRound(this.days() / 7); - }, - - valueOf : function () { - return this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6; - }, - - humanize : function (withSuffix) { - var output = relativeTime(this, !withSuffix, this.localeData()); - - if (withSuffix) { - output = this.localeData().pastFuture(+this, output); - } - - return this.localeData().postformat(output); - }, - - add : function (input, val) { - // supports only 2.0-style add(1, 's') or add(moment) - var dur = moment.duration(input, val); - - this._milliseconds += dur._milliseconds; - this._days += dur._days; - this._months += dur._months; - - this._bubble(); - - return this; - }, - - subtract : function (input, val) { - var dur = moment.duration(input, val); - - this._milliseconds -= dur._milliseconds; - this._days -= dur._days; - this._months -= dur._months; - - this._bubble(); - - return this; - }, - - get : function (units) { - units = normalizeUnits(units); - return this[units.toLowerCase() + 's'](); - }, - - as : function (units) { - var days, months; - units = normalizeUnits(units); - - if (units === 'month' || units === 'year') { - days = this._days + this._milliseconds / 864e5; - months = this._months + daysToYears(days) * 12; - return units === 'month' ? months : months / 12; - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(yearsToDays(this._months / 12)); - switch (units) { - case 'week': return days / 7 + this._milliseconds / 6048e5; - case 'day': return days + this._milliseconds / 864e5; - case 'hour': return days * 24 + this._milliseconds / 36e5; - case 'minute': return days * 24 * 60 + this._milliseconds / 6e4; - case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds; - default: throw new Error('Unknown unit ' + units); - } - } - }, - - lang : moment.fn.lang, - locale : moment.fn.locale, - - toIsoString : deprecate( - 'toIsoString() is deprecated. Please use toISOString() instead ' + - '(notice the capitals)', - function () { - return this.toISOString(); - } - ), - - toISOString : function () { - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var years = Math.abs(this.years()), - months = Math.abs(this.months()), - days = Math.abs(this.days()), - hours = Math.abs(this.hours()), - minutes = Math.abs(this.minutes()), - seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); - - if (!this.asSeconds()) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - return (this.asSeconds() < 0 ? '-' : '') + - 'P' + - (years ? years + 'Y' : '') + - (months ? months + 'M' : '') + - (days ? days + 'D' : '') + - ((hours || minutes || seconds) ? 'T' : '') + - (hours ? hours + 'H' : '') + - (minutes ? minutes + 'M' : '') + - (seconds ? seconds + 'S' : ''); - }, - - localeData : function () { - return this._locale; - }, - - toJSON : function () { - return this.toISOString(); - } - }); - - moment.duration.fn.toString = moment.duration.fn.toISOString; - - function makeDurationGetter(name) { - moment.duration.fn[name] = function () { - return this._data[name]; - }; - } - - for (i in unitMillisecondFactors) { - if (hasOwnProp(unitMillisecondFactors, i)) { - makeDurationGetter(i.toLowerCase()); - } - } - - moment.duration.fn.asMilliseconds = function () { - return this.as('ms'); - }; - moment.duration.fn.asSeconds = function () { - return this.as('s'); - }; - moment.duration.fn.asMinutes = function () { - return this.as('m'); - }; - moment.duration.fn.asHours = function () { - return this.as('h'); - }; - moment.duration.fn.asDays = function () { - return this.as('d'); - }; - moment.duration.fn.asWeeks = function () { - return this.as('weeks'); - }; - moment.duration.fn.asMonths = function () { - return this.as('M'); - }; - moment.duration.fn.asYears = function () { - return this.as('y'); - }; - - /************************************ - Default Locale - ************************************/ - - - // Set default locale, other locale will inherit from English. - moment.locale('en', { - ordinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - /* EMBED_LOCALES */ - - /************************************ - Exposing Moment - ************************************/ - - function makeGlobal(shouldDeprecate) { - /*global ender:false */ - if (typeof ender !== 'undefined') { - return; - } - oldGlobalMoment = globalScope.moment; - if (shouldDeprecate) { - globalScope.moment = deprecate( - 'Accessing Moment through the global scope is ' + - 'deprecated, and will be removed in an upcoming ' + - 'release.', - moment); - } else { - globalScope.moment = moment; - } - } - - // CommonJS module is defined - if (hasModule) { - module.exports = moment; - } else if (typeof define === 'function' && define.amd) { - define(function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal === true) { - // release the global variable - globalScope.moment = oldGlobalMoment; - } - - return moment; - }); - makeGlobal(true); - } else { - makeGlobal(); - } -}).call(this); \ No newline at end of file +(function(a){function b(a,b,c){switch(arguments.length){case 2:return null!=a?a:b;case 3:return null!=a?a:null!=b?b:c;default:throw new Error("Implement me")}}function c(a,b){return Bb.call(a,b)}function d(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function e(a){vb.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+a)}function f(a,b){var c=!0;return o(function(){return c&&(e(a),c=!1),b.apply(this,arguments)},b)}function g(a,b){sc[a]||(e(b),sc[a]=!0)}function h(a,b){return function(c){return r(a.call(this,c),b)}}function i(a,b){return function(c){return this.localeData().ordinal(a.call(this,c),b)}}function j(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function k(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function l(){}function m(a,b){b!==!1&&H(a),p(this,a),this._d=new Date(+a._d),uc===!1&&(uc=!0,vb.updateOffset(this),uc=!1)}function n(a){var b=A(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=vb.localeData(),this._bubble()}function o(a,b){for(var d in b)c(b,d)&&(a[d]=b[d]);return c(b,"toString")&&(a.toString=b.toString),c(b,"valueOf")&&(a.valueOf=b.valueOf),a}function p(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Kb.length>0)for(c in Kb)d=Kb[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&C(a[d])!==C(b[d]))&&g++;return g+f}function z(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=lc[a]||mc[b]||b}return a}function A(a){var b,d,e={};for(d in a)c(a,d)&&(b=z(d),b&&(e[b]=a[d]));return e}function B(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}vb[b]=function(e,f){var g,h,i=vb._locale[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=vb().utc().set(d,a);return i.call(vb._locale,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function C(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function D(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function E(a,b,c){return jb(vb([a,11,31+b-c]),b,c).week}function F(a){return G(a)?366:365}function G(a){return a%4===0&&a%100!==0||a%400===0}function H(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[Db]<0||a._a[Db]>11?Db:a._a[Eb]<1||a._a[Eb]>D(a._a[Cb],a._a[Db])?Eb:a._a[Fb]<0||a._a[Fb]>24||24===a._a[Fb]&&(0!==a._a[Gb]||0!==a._a[Hb]||0!==a._a[Ib])?Fb:a._a[Gb]<0||a._a[Gb]>59?Gb:a._a[Hb]<0||a._a[Hb]>59?Hb:a._a[Ib]<0||a._a[Ib]>999?Ib:-1,a._pf._overflowDayOfYear&&(Cb>b||b>Eb)&&(b=Eb),a._pf.overflow=b)}function I(b){return null==b._isValid&&(b._isValid=!isNaN(b._d.getTime())&&b._pf.overflow<0&&!b._pf.empty&&!b._pf.invalidMonth&&!b._pf.nullInput&&!b._pf.invalidFormat&&!b._pf.userInvalidated,b._strict&&(b._isValid=b._isValid&&0===b._pf.charsLeftOver&&0===b._pf.unusedTokens.length&&b._pf.bigHour===a)),b._isValid}function J(a){return a?a.toLowerCase().replace("_","-"):a}function K(a){for(var b,c,d,e,f=0;f0;){if(d=L(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&y(e,c,!0)>=b-1)break;b--}f++}return null}function L(a){var b=null;if(!Jb[a]&&Lb)try{b=vb.locale(),require("./locale/"+a),vb.locale(b)}catch(c){}return Jb[a]}function M(a,b){var c,d;return b._isUTC?(c=b.clone(),d=(vb.isMoment(a)||x(a)?+a:+vb(a))-+c,c._d.setTime(+c._d+d),vb.updateOffset(c,!1),c):vb(a).local()}function N(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function O(a){var b,c,d=a.match(Pb);for(b=0,c=d.length;c>b;b++)d[b]=rc[d[b]]?rc[d[b]]:N(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function P(a,b){return a.isValid()?(b=Q(b,a.localeData()),nc[b]||(nc[b]=O(b)),nc[b](a)):a.localeData().invalidDate()}function Q(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Qb.lastIndex=0;d>=0&&Qb.test(a);)a=a.replace(Qb,c),Qb.lastIndex=0,d-=1;return a}function R(a,b){var c,d=b._strict;switch(a){case"Q":return _b;case"DDDD":return bc;case"YYYY":case"GGGG":case"gggg":return d?cc:Tb;case"Y":case"G":case"g":return ec;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?dc:Ub;case"S":if(d)return _b;case"SS":if(d)return ac;case"SSS":if(d)return bc;case"DDD":return Sb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Wb;case"a":case"A":return b._locale._meridiemParse;case"x":return Zb;case"X":return $b;case"Z":case"ZZ":return Xb;case"T":return Yb;case"SSSS":return Vb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?ac:Rb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Rb;case"Do":return d?b._locale._ordinalParse:b._locale._ordinalParseLenient;default:return c=new RegExp($(Z(a.replace("\\","")),"i"))}}function S(a){a=a||"";var b=a.match(Xb)||[],c=b[b.length-1]||[],d=(c+"").match(jc)||["-",0,0],e=+(60*d[1])+C(d[2]);return"+"===d[0]?e:-e}function T(a,b,c){var d,e=c._a;switch(a){case"Q":null!=b&&(e[Db]=3*(C(b)-1));break;case"M":case"MM":null!=b&&(e[Db]=C(b)-1);break;case"MMM":case"MMMM":d=c._locale.monthsParse(b,a,c._strict),null!=d?e[Db]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[Eb]=C(b));break;case"Do":null!=b&&(e[Eb]=C(parseInt(b.match(/\d{1,2}/)[0],10)));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=C(b));break;case"YY":e[Cb]=vb.parseTwoDigitYear(b);break;case"YYYY":case"YYYYY":case"YYYYYY":e[Cb]=C(b);break;case"a":case"A":c._meridiem=b;break;case"h":case"hh":c._pf.bigHour=!0;case"H":case"HH":e[Fb]=C(b);break;case"m":case"mm":e[Gb]=C(b);break;case"s":case"ss":e[Hb]=C(b);break;case"S":case"SS":case"SSS":case"SSSS":e[Ib]=C(1e3*("0."+b));break;case"x":c._d=new Date(C(b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=S(b);break;case"dd":case"ddd":case"dddd":d=c._locale.weekdaysParse(b),null!=d?(c._w=c._w||{},c._w.d=d):c._pf.invalidWeekday=b;break;case"w":case"ww":case"W":case"WW":case"d":case"e":case"E":a=a.substr(0,1);case"gggg":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=C(b));break;case"gg":case"GG":c._w=c._w||{},c._w[a]=vb.parseTwoDigitYear(b)}}function U(a){var c,d,e,f,g,h,i;c=a._w,null!=c.GG||null!=c.W||null!=c.E?(g=1,h=4,d=b(c.GG,a._a[Cb],jb(vb(),1,4).year),e=b(c.W,1),f=b(c.E,1)):(g=a._locale._week.dow,h=a._locale._week.doy,d=b(c.gg,a._a[Cb],jb(vb(),g,h).year),e=b(c.w,1),null!=c.d?(f=c.d,g>f&&++e):f=null!=c.e?c.e+g:g),i=kb(d,e,f,h,g),a._a[Cb]=i.year,a._dayOfYear=i.dayOfYear}function V(a){var c,d,e,f,g=[];if(!a._d){for(e=X(a),a._w&&null==a._a[Eb]&&null==a._a[Db]&&U(a),a._dayOfYear&&(f=b(a._a[Cb],e[Cb]),a._dayOfYear>F(f)&&(a._pf._overflowDayOfYear=!0),d=fb(f,0,a._dayOfYear),a._a[Db]=d.getUTCMonth(),a._a[Eb]=d.getUTCDate()),c=0;3>c&&null==a._a[c];++c)a._a[c]=g[c]=e[c];for(;7>c;c++)a._a[c]=g[c]=null==a._a[c]?2===c?1:0:a._a[c];24===a._a[Fb]&&0===a._a[Gb]&&0===a._a[Hb]&&0===a._a[Ib]&&(a._nextDay=!0,a._a[Fb]=0),a._d=(a._useUTC?fb:eb).apply(null,g),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Fb]=24)}}function W(a){var b;a._d||(b=A(a._i),a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],V(a))}function X(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function Y(b){if(b._f===vb.ISO_8601)return void ab(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Q(b._f,b._locale).match(Pb)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),rc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),T(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[Fb]<=12&&(b._pf.bigHour=a),b._a[Fb]=k(b._locale,b._a[Fb],b._meridiem),V(b),H(b)}function Z(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function $(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function _(a){var b,c,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,c=b));o(a,c||b)}function ab(a){var b,c,d=a._i,e=fc.exec(d);if(e){for(a._pf.iso=!0,b=0,c=hc.length;c>b;b++)if(hc[b][1].exec(d)){a._f=hc[b][0]+(e[6]||" ");break}for(b=0,c=ic.length;c>b;b++)if(ic[b][1].exec(d)){a._f+=ic[b][0];break}d.match(Xb)&&(a._f+="Z"),Y(a)}else a._isValid=!1}function bb(a){ab(a),a._isValid===!1&&(delete a._isValid,vb.createFromInputFallback(a))}function cb(a,b){var c,d=[];for(c=0;ca&&h.setFullYear(a),h}function fb(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function gb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function hb(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function ib(a,b,c){var d=vb.duration(a).abs(),e=Ab(d.as("s")),f=Ab(d.as("m")),g=Ab(d.as("h")),h=Ab(d.as("d")),i=Ab(d.as("M")),j=Ab(d.as("y")),k=e0,k[4]=c,hb.apply({},k)}function jb(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=vb(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function kb(a,b,c,d,e){var f,g,h=fb(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:F(a-1)+g}}function lb(b){var c,d=b._i,e=b._f;return b._locale=b._locale||vb.localeData(b._l),null===d||e===a&&""===d?vb.invalid({nullInput:!0}):("string"==typeof d&&(b._i=d=b._locale.preparse(d)),vb.isMoment(d)?new m(d,!0):(e?w(e)?_(b):Y(b):db(b),c=new m(b),c._nextDay&&(c.add(1,"d"),c._nextDay=a),c))}function mb(a,b){var c,d;if(1===b.length&&w(b[0])&&(b=b[0]),!b.length)return vb();for(c=b[0],d=1;d=0?"+":"-";return b+r(Math.abs(a),6)},gg:function(){return r(this.weekYear()%100,2)},gggg:function(){return r(this.weekYear(),4)},ggggg:function(){return r(this.weekYear(),5)},GG:function(){return r(this.isoWeekYear()%100,2)},GGGG:function(){return r(this.isoWeekYear(),4)},GGGGG:function(){return r(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return C(this.milliseconds()/100)},SS:function(){return r(C(this.milliseconds()/10),2)},SSS:function(){return r(this.milliseconds(),3)},SSSS:function(){return r(this.milliseconds(),3)},Z:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+":"+r(C(a)%60,2)},ZZ:function(){var a=this.utcOffset(),b="+";return 0>a&&(a=-a,b="-"),b+r(C(a/60),2)+r(C(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},x:function(){return this.valueOf()},X:function(){return this.unix()},Q:function(){return this.quarter()}},sc={},tc=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"],uc=!1;pc.length;)xb=pc.pop(),rc[xb+"o"]=i(rc[xb],xb);for(;qc.length;)xb=qc.pop(),rc[xb+xb]=h(rc[xb],2);rc.DDDD=h(rc.DDD,3),o(l.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=vb.utc([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=vb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.apply(b,[c]):d},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",_ordinalParse:/\d{1,2}/,preparse:function(a){return a},postformat:function(a){return a},week:function(a){return jb(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},firstDayOfWeek:function(){return this._week.dow},firstDayOfYear:function(){return this._week.doy},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),vb=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=b,g._f=c,g._l=e,g._strict=f,g._isUTC=!1,g._pf=d(),lb(g)},vb.suppressDeprecationWarnings=!1,vb.createFromInputFallback=f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),vb.min=function(){var a=[].slice.call(arguments,0);return mb("isBefore",a)},vb.max=function(){var a=[].slice.call(arguments,0);return mb("isAfter",a)},vb.utc=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=b,g._f=c,g._strict=f,g._pf=d(),lb(g).utc()},vb.unix=function(a){return vb(1e3*a)},vb.duration=function(a,b){var d,e,f,g,h=a,i=null;return vb.isDuration(a)?h={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(h={},b?h[b]=a:h.milliseconds=a):(i=Nb.exec(a))?(d="-"===i[1]?-1:1,h={y:0,d:C(i[Eb])*d,h:C(i[Fb])*d,m:C(i[Gb])*d,s:C(i[Hb])*d,ms:C(i[Ib])*d}):(i=Ob.exec(a))?(d="-"===i[1]?-1:1,f=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*d},h={y:f(i[2]),M:f(i[3]),d:f(i[4]),h:f(i[5]),m:f(i[6]),s:f(i[7]),w:f(i[8])}):null==h?h={}:"object"==typeof h&&("from"in h||"to"in h)&&(g=t(vb(h.from),vb(h.to)),h={},h.ms=g.milliseconds,h.M=g.months),e=new n(h),vb.isDuration(a)&&c(a,"_locale")&&(e._locale=a._locale),e},vb.version=yb,vb.defaultFormat=gc,vb.ISO_8601=function(){},vb.momentProperties=Kb,vb.updateOffset=function(){},vb.relativeTimeThreshold=function(b,c){return oc[b]===a?!1:c===a?oc[b]:(oc[b]=c,!0)},vb.lang=f("moment.lang is deprecated. Use moment.locale instead.",function(a,b){return vb.locale(a,b)}),vb.locale=function(a,b){var c;return a&&(c="undefined"!=typeof b?vb.defineLocale(a,b):vb.localeData(a),c&&(vb.duration._locale=vb._locale=c)),vb._locale._abbr},vb.defineLocale=function(a,b){return null!==b?(b.abbr=a,Jb[a]||(Jb[a]=new l),Jb[a].set(b),vb.locale(a),Jb[a]):(delete Jb[a],null)},vb.langData=f("moment.langData is deprecated. Use moment.localeData instead.",function(a){return vb.localeData(a)}),vb.localeData=function(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return vb._locale;if(!w(a)){if(b=L(a))return b;a=[a]}return K(a)},vb.isMoment=function(a){return a instanceof m||null!=a&&c(a,"_isAMomentObject")},vb.isDuration=function(a){return a instanceof n};for(xb=tc.length-1;xb>=0;--xb)B(tc[xb]);vb.normalizeUnits=function(a){return z(a)},vb.invalid=function(a){var b=vb.utc(0/0);return null!=a?o(b._pf,a):b._pf.userInvalidated=!0,b},vb.parseZone=function(){return vb.apply(null,arguments).parseZone()},vb.parseTwoDigitYear=function(a){return C(a)+(C(a)>68?1900:2e3)},vb.isDate=x,o(vb.fn=m.prototype,{clone:function(){return vb(this)},valueOf:function(){return+this._d-6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=vb(this).utc();return 00:!1},parsingFlags:function(){return o({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(a){return this.utcOffset(0,a)},local:function(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(this._dateUtcOffset(),"m")),this},format:function(a){var b=P(this,a||vb.defaultFormat);return this.localeData().postformat(b)},add:u(1,"add"),subtract:u(-1,"subtract"),diff:function(a,b,c){var d,e,f=M(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=z(b),"year"===b||"month"===b||"quarter"===b?(e=j(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:q(e)},from:function(a,b){return vb.duration({to:this,from:a}).locale(this.locale()).humanize(!b)},fromNow:function(a){return this.from(vb(),a)},calendar:function(a){var b=a||vb(),c=M(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,vb(b)))},isLeapYear:function(){return G(this.year())},isDST:function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=gb(a,this.localeData()),this.add(a-b,"d")):b},month:qb("Month",!0),startOf:function(a){switch(a=z(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this},endOf:function(b){return b=z(b),b===a||"millisecond"===b?this:this.startOf(b).add(1,"isoWeek"===b?"week":b).subtract(1,"ms")},isAfter:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+this>+a):(c=vb.isMoment(a)?+a:+vb(a),c<+this.clone().startOf(b))},isBefore:function(a,b){var c;return b=z("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=vb.isMoment(a)?a:vb(a),+a>+this):(c=vb.isMoment(a)?+a:+vb(a),+this.clone().endOf(b)a?this:a}),max:f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(a){return a=vb.apply(null,arguments),a>this?this:a}),zone:f("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",function(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}),utcOffset:function(a,b){var c,d=this._offset||0;return null!=a?("string"==typeof a&&(a=S(a)),Math.abs(a)<16&&(a=60*a),!this._isUTC&&b&&(c=this._dateUtcOffset()),this._offset=a,this._isUTC=!0,null!=c&&this.add(c,"m"),d!==a&&(!b||this._changeInProgress?v(this,vb.duration(a-d,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,vb.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?d:this._dateUtcOffset()},isLocal:function(){return!this._isUTC},isUtcOffset:function(){return this._isUTC},isUtc:function(){return this._isUTC&&0===this._offset},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(S(this._i)),this},hasAlignedHourOffset:function(a){return a=a?vb(a).utcOffset():0,(this.utcOffset()-a)%60===0},daysInMonth:function(){return D(this.year(),this.month())},dayOfYear:function(a){var b=Ab((vb(this).startOf("day")-vb(this).startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")},quarter:function(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)},weekYear:function(a){var b=jb(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")},isoWeekYear:function(a){var b=jb(this,1,4).year;return null==a?b:this.add(a-b,"y")},week:function(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")},isoWeek:function(a){var b=jb(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")},weekday:function(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},isoWeeksInYear:function(){return E(this.year(),1,4)},weeksInYear:function(){var a=this.localeData()._week;return E(this.year(),a.dow,a.doy)},get:function(a){return a=z(a),this[a]()},set:function(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else a=z(a),"function"==typeof this[a]&&this[a](b);return this},locale:function(b){var c;return b===a?this._locale._abbr:(c=vb.localeData(b),null!=c&&(this._locale=c),this)},lang:f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(b){return b===a?this.localeData():this.locale(b)}),localeData:function(){return this._locale},_dateUtcOffset:function(){return 15*-Math.round(this._d.getTimezoneOffset()/15)}}),vb.fn.millisecond=vb.fn.milliseconds=qb("Milliseconds",!1),vb.fn.second=vb.fn.seconds=qb("Seconds",!1),vb.fn.minute=vb.fn.minutes=qb("Minutes",!1),vb.fn.hour=vb.fn.hours=qb("Hours",!0),vb.fn.date=qb("Date",!0),vb.fn.dates=f("dates accessor is deprecated. Use date instead.",qb("Date",!0)),vb.fn.year=qb("FullYear",!0),vb.fn.years=f("years accessor is deprecated. Use year instead.",qb("FullYear",!0)),vb.fn.days=vb.fn.day,vb.fn.months=vb.fn.month,vb.fn.weeks=vb.fn.week,vb.fn.isoWeeks=vb.fn.isoWeek,vb.fn.quarters=vb.fn.quarter,vb.fn.toJSON=vb.fn.toISOString,vb.fn.isUTC=vb.fn.isUtc,o(vb.duration.fn=n.prototype,{_bubble:function(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;g.milliseconds=d%1e3,a=q(d/1e3),g.seconds=a%60,b=q(a/60),g.minutes=b%60,c=q(b/60),g.hours=c%24,e+=q(c/24),h=q(rb(e)),e-=q(sb(h)),f+=q(e/30),e%=30,h+=q(f/12),f%=12,g.days=e,g.months=f,g.years=h},abs:function(){return this._milliseconds=Math.abs(this._milliseconds),this._days=Math.abs(this._days),this._months=Math.abs(this._months),this._data.milliseconds=Math.abs(this._data.milliseconds),this._data.seconds=Math.abs(this._data.seconds),this._data.minutes=Math.abs(this._data.minutes),this._data.hours=Math.abs(this._data.hours),this._data.months=Math.abs(this._data.months),this._data.years=Math.abs(this._data.years),this},weeks:function(){return q(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*C(this._months/12) +},humanize:function(a){var b=ib(this,!a,this.localeData());return a&&(b=this.localeData().pastFuture(+this,b)),this.localeData().postformat(b)},add:function(a,b){var c=vb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=vb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=z(a),this[a.toLowerCase()+"s"]()},as:function(a){var b,c;if(a=z(a),"month"===a||"year"===a)return b=this._days+this._milliseconds/864e5,c=this._months+12*rb(b),"month"===a?c:c/12;switch(b=this._days+Math.round(sb(this._months/12)),a){case"week":return b/7+this._milliseconds/6048e5;case"day":return b+this._milliseconds/864e5;case"hour":return 24*b+this._milliseconds/36e5;case"minute":return 24*b*60+this._milliseconds/6e4;case"second":return 24*b*60*60+this._milliseconds/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+a)}},lang:vb.fn.lang,locale:vb.fn.locale,toIsoString:f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"},localeData:function(){return this._locale},toJSON:function(){return this.toISOString()}}),vb.duration.fn.toString=vb.duration.fn.toISOString;for(xb in kc)c(kc,xb)&&tb(xb.toLowerCase());vb.duration.fn.asMilliseconds=function(){return this.as("ms")},vb.duration.fn.asSeconds=function(){return this.as("s")},vb.duration.fn.asMinutes=function(){return this.as("m")},vb.duration.fn.asHours=function(){return this.as("h")},vb.duration.fn.asDays=function(){return this.as("d")},vb.duration.fn.asWeeks=function(){return this.as("weeks")},vb.duration.fn.asMonths=function(){return this.as("M")},vb.duration.fn.asYears=function(){return this.as("y")},vb.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===C(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),Lb?module.exports=vb:"function"==typeof define&&define.amd?(define(function(a,b,c){return c.config&&c.config()&&c.config().noGlobal===!0&&(zb.moment=wb),vb}),ub(!0)):ub()}).call(this); \ No newline at end of file