From 38832cb5f333b479affe32805583dba5da0801e3 Mon Sep 17 00:00:00 2001 From: Petr Vobornik Date: Tue, 25 Nov 2014 15:54:16 +0100 Subject: [PATCH] update redhat_access_angular_ui to 0.9.36 --- Makefile | 5 +- assets/redhat_access_angular_ui-deps.js | 17038 +++++++++++ assets/redhat_access_angular_ui.js | 28364 ++++--------------- assets/redhat_access_angular_ui.no-deps.js | 5368 ---- ...dhat_access_angular_ui-deps-embedded-images.css | 1 + .../redhat_access_angular_ui-embedded-images.css | 2 +- rhaccess.js | 18 +- 7 files changed, 22486 insertions(+), 28310 deletions(-) create mode 100644 assets/redhat_access_angular_ui-deps.js delete mode 100644 assets/redhat_access_angular_ui.no-deps.js create mode 100644 assets/styles/redhat_access_angular_ui-deps-embedded-images.css diff --git a/Makefile b/Makefile index 0376397..2239743 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ distdir=dist/$(FULLNAME) DESTDIR ?= /usr/share plugindir=$(DESTDIR)/ipa/ui/js/plugins/rhaccess jsfiles=assets/angular.min.js \ + assets/redhat_access_angular_ui-deps.js \ assets/redhat_access_angular_ui.js js: @@ -18,7 +19,9 @@ js: css: mkdir -p $(distdir)/styles - cat assets/styles/redhat_access_angular_ui-embedded-images.css > $(distdir)/styles/rhaccess.css + cat assets/styles/redhat_access_angular_ui-deps-embedded-images.css > $(distdir)/styles/rhaccess.css + echo ' ' >> $(distdir)/styles/rhaccess.css + cat assets/styles/redhat_access_angular_ui-embedded-images.css >> $(distdir)/styles/rhaccess.css echo ' ' >> $(distdir)/styles/rhaccess.css cat assets/styles/glyphicons.css >> $(distdir)/styles/rhaccess.css echo ' ' >> $(distdir)/styles/rhaccess.css diff --git a/assets/redhat_access_angular_ui-deps.js b/assets/redhat_access_angular_ui-deps.js new file mode 100644 index 0000000..6f7e077 --- /dev/null +++ b/assets/redhat_access_angular_ui-deps.js @@ -0,0 +1,17038 @@ +/*! redhat_access_angular_ui - v0.9.36 - 2014-11-25 + * Copyright (c) 2014 ; + * Licensed + */ +/*! + * Copyright (c) 2006 js-markdown-extra developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var MARKDOWN_VERSION = "1.0.1o"; +var MARKDOWNEXTRA_VERSION = "1.2.5"; + +// Global default settings: + +/** Change to ">" for HTML output */ +var MARKDOWN_EMPTY_ELEMENT_SUFFIX = " />"; + +/** Define the width of a tab for code blocks. */ +var MARKDOWN_TAB_WIDTH = 4; + +/** Optional title attribute for footnote links and backlinks. */ +var MARKDOWN_FN_LINK_TITLE = ""; +var MARKDOWN_FN_BACKLINK_TITLE = ""; + +/** Optional class attribute for footnote links and backlinks. */ +var MARKDOWN_FN_LINK_CLASS = ""; +var MARKDOWN_FN_BACKLINK_CLASS = ""; + +/** Change to false to remove Markdown from posts and/or comments. */ +var MARKDOWN_WP_POSTS = true; +var MARKDOWN_WP_COMMENTS = true; + +/** Standard Function Interface */ +MARKDOWN_PARSER_CLASS = 'MarkdownExtra_Parser'; + +/** + * Converts Markdown formatted text to HTML. + * @param text Markdown text + * @return HTML + */ +function Markdown(text) { + //Initialize the parser and return the result of its transform method. + var parser; + if('undefined' == typeof arguments.callee.parser) { + parser = eval("new " + MARKDOWN_PARSER_CLASS + "()"); + parser.init(); + arguments.callee.parser = parser; + } + else { + parser = arguments.callee.parser; + } + // Transform text using parser. + return parser.transform(text); +} + +/** + * Constructor function. Initialize appropriate member variables. + */ +function Markdown_Parser() { + + this.nested_brackets_depth = 6; + this.nested_url_parenthesis_depth = 4; + this.escape_chars = "\\\\`*_{}[]()>#+-.!"; + + // Document transformations + this.document_gamut = [ + // Strip link definitions, store in hashes. + ['stripLinkDefinitions', 20], + ['runBasicBlockGamut', 30] + ]; + + // These are all the transformations that form block-level + /// tags like paragraphs, headers, and list items. + this.block_gamut = [ + ['doHeaders', 10], + ['doHorizontalRules', 20], + ['doLists', 40], + ['doCodeBlocks', 50], + ['doBlockQuotes', 60] + ]; + + // These are all the transformations that occur *within* block-level + // tags like paragraphs, headers, and list items. + this.span_gamut = [ + // Process character escapes, code spans, and inline HTML + // in one shot. + ['parseSpan', -30], + // Process anchor and image tags. Images must come first, + // because ![foo][f] looks like an anchor. + ['doImages', 10], + ['doAnchors', 20], + // Make links out of things like `` + // Must come after doAnchors, because you can use < and > + // delimiters in inline links like [this](). + ['doAutoLinks', 30], + ['encodeAmpsAndAngles', 40], + ['doItalicsAndBold', 50], + ['doHardBreaks', 60] + ]; + + this.em_relist = [ + ['' , '(?:(^|[^\\*])(\\*)(?=[^\\*])|(^|[^_])(_)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['*', '((?:\\S|^)[^\\*])(\\*)(?!\\*)'], + ['_', '((?:\\S|^)[^_])(_)(?!_)'] + ]; + this.strong_relist = [ + ['' , '(?:(^|[^\\*])(\\*\\*)(?=[^\\*])|(^|[^_])(__)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['**', '((?:\\S|^)[^\\*])(\\*\\*)(?!\\*)'], + ['__', '((?:\\S|^)[^_])(__)(?!_)'] + ]; + this.em_strong_relist = [ + ['' , '(?:(^|[^\\*])(\\*\\*\\*)(?=[^\\*])|(^|[^_])(___)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['***', '((?:\\S|^)[^\\*])(\\*\\*\\*)(?!\\*)'], + ['___', '((?:\\S|^)[^_])(___)(?!_)'] + ]; +} + +Markdown_Parser.prototype.init = function() { + // this._initDetab(); // NOTE: JavaScript string length is already based on Unicode + this.prepareItalicsAndBold(); + + // Regex to match balanced [brackets]. + // Needed to insert a maximum bracked depth while converting to PHP. + // NOTE: JavaScript doesn't have so faster option for RegExp + //this.nested_brackets_re = new RegExp( + // str_repeat('(?>[^\\[\\]]+|\\[', this.nested_brackets_depth) + + // str_repeat('\\])*', this.nested_brackets_depth) + //); + // NOTE: JavaScript doesn't have so faster option for RegExp + //this.nested_url_parenthesis_re = new RegExp( + // str_repeat('(?>[^()\\s]+|\\(', this.nested_url_parenthesis_depth) + + // str_repeat('(?>\\)))*', this.nested_url_parenthesis_depth) + //); + this.nested_brackets_re = + this._php_str_repeat('(?:[^\\[\\]]+|\\[', this.nested_brackets_depth) + + this._php_str_repeat('\\])*', this.nested_brackets_depth); + this.nested_url_parenthesis_re = + this._php_str_repeat('(?:[^\\(\\)\\s]+|\\(', this.nested_url_parenthesis_depth) + + this._php_str_repeat('(?:\\)))*', this.nested_url_parenthesis_depth); + + // Table of hash values for escaped characters: + var tmp = []; + for(var i = 0; i < this.escape_chars.length; i++) { + tmp.push(this._php_preg_quote(this.escape_chars.charAt(i))); + } + this.escape_chars_re = '[' + tmp.join('') + ']'; + + // Change to ">" for HTML output. + this.empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; + this.tab_width = MARKDOWN_TAB_WIDTH; + + // Change to `true` to disallow markup or entities. + this.no_markup = false; + this.no_entities = false; + + // Predefined urls and titles for reference links and images. + this.predef_urls = {}; + this.predef_titles = {}; + + // Sort document, block, and span gamut in ascendent priority order. + function cmp_gamut(a, b) { + a = a[1]; b = b[1]; + return a > b ? 1 : a < b ? -1 : 0; + } + this.document_gamut.sort(cmp_gamut); + this.block_gamut.sort(cmp_gamut); + this.span_gamut.sort(cmp_gamut); + + // Internal hashes used during transformation. + this.urls = {}; + this.titles = {}; + this.html_hashes = {}; + + // Status flag to avoid invalid nesting. + this.in_anchor = false; +}; + +/** + * [porting note] + * JavaScript's RegExp doesn't have escape code \A and \Z. + * So multiline pattern can't match start/end of text. Instead + * wrap whole of text with STX(02) and ETX(03). + */ +Markdown_Parser.prototype.__wrapSTXETX__ = function(text) { + if(text.charAt(0) != '\x02') { text = '\x02' + text; } + if(text.charAt(text.length - 1) != '\x03') { text = text + '\x03'; } + return text; +}; + +/** + * [porting note] + * Strip STX(02) and ETX(03). + */ +Markdown_Parser.prototype.__unwrapSTXETX__ = function(text) { + if(text.charAt(0) == '\x02') { text = text.substr(1); } + if(text.charAt(text.length - 1) == '\x03') { text = text.substr(0, text.length - 1); } + return text; +}; + +/** + * + */ +Markdown_Parser.prototype._php_preg_quote = function(text) { + if(!arguments.callee.sRE) { + arguments.callee.sRE = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}\\)/g; + } + return text.replace(arguments.callee.sRE, '\\$1'); +}; + +Markdown_Parser.prototype._php_str_repeat = function(str, n) { + var tmp = str; + for(var i = 1; i < n; i++) { + tmp += str; + } + return tmp; +}; + +Markdown_Parser.prototype._php_trim = function(target, charlist) { + var chars = charlist || " \t\n\r"; + return target.replace( + new RegExp("^[" + chars + "]*|[" + chars + "]*$", "g"), "" + ); +}; + +Markdown_Parser.prototype._php_rtrim = function(target, charlist) { + var chars = charlist || " \t\n\r"; + return target.replace( + new RegExp( "[" + chars + "]*$", "g" ), "" + ); +}; + +Markdown_Parser.prototype._php_htmlspecialchars_ENT_NOQUOTES = function(str) { + return str.replace(/&/g, '&').replace(//g, '>'); +}; + + +/** + * Called before the transformation process starts to setup parser + * states. + */ +Markdown_Parser.prototype.setup = function() { + // Clear global hashes. + this.urls = this.predef_urls; + this.titles = this.predef_titles; + this.html_hashes = {}; + + this.in_anchor = false; +}; + +/** + * Called after the transformation process to clear any variable + * which may be taking up memory unnecessarly. + */ +Markdown_Parser.prototype.teardown = function() { + this.urls = {}; + this.titles = {}; + this.html_hashes = {}; +}; + +/** + * Main function. Performs some preprocessing on the input text + * and pass it through the document gamut. + */ +Markdown_Parser.prototype.transform = function(text) { + this.setup(); + + // Remove UTF-8 BOM and marker character in input, if present. + text = text.replace(/^\xEF\xBB\xBF|\x1A/, ""); + + // Standardize line endings: + // DOS to Unix and Mac to Unix + text = text.replace(/\r\n?/, "\n", text); + + // Make sure $text ends with a couple of newlines: + text += "\n\n"; + + // Convert all tabs to spaces. + text = this.detab(text); + + // Turn block-level HTML blocks into hash entries + text = this.hashHTMLBlocks(text); + + // Strip any lines consisting only of spaces and tabs. + // This makes subsequent regexen easier to write, because we can + // match consecutive blank lines with /\n+/ instead of something + // contorted like /[ ]*\n+/ . + text = text.replace(/^[ ]+$/m, ""); + + // Run document gamut methods. + for(var i = 0; i < this.document_gamut.length; i++) { + var method = this[this.document_gamut[i][0]]; + if(method) { + text = method.call(this, text); + } + else { + console.log(this.document_gamut[i][0] + ' not implemented'); + } + } + + this.teardown(); + + return text + "\n"; +}; + +Markdown_Parser.prototype.hashHTMLBlocks = function(text) { + if(this.no_markup) { return text; } + + var less_than_tab = this.tab_width - 1; + + // Hashify HTML blocks: + // We only want to do this for block-level HTML tags, such as headers, + // lists, and tables. That's because we still want to wrap

s around + // "paragraphs" that are wrapped in non-block-level tags, such as anchors, + // phrase emphasis, and spans. The list of tags we're looking for is + // hard-coded: + // + // * List "a" is made of tags which can be both inline or block-level. + // These will be treated block-level when the start tag is alone on + // its line, otherwise they're not matched here and will be taken as + // inline later. + // * List "b" is made of tags which are always block-level; + + var block_tags_a_re = 'ins|del'; + var block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' + + 'script|noscript|form|fieldset|iframe|math'; + + // Regular expression for the content of a block tag. + var nested_tags_level = 4; + var attr = + '(?:' + // optional tag attributes + '\\s' + // starts with whitespace + '(?:' + + '[^>"/]+' + // text outside quotes + '|' + + '/+(?!>)' + // slash not followed by ">" + '|' + + '"[^"]*"' + // text inside double quotes (tolerate ">") + '|' + + '\'[^\']*\'' + // text inside single quotes (tolerate ">") + ')*' + + ')?'; + var content = + this._php_str_repeat( + '(?:' + + '[^<]+' + // content without tag + '|' + + '<\\2' + // nested opening tag + attr + // attributes + '(?:' + + '/>' + + '|' + + '>', + nested_tags_level + ) + // end of opening tag + '.*?' + // last level nested tag content + this._php_str_repeat( + '' + // closing nested tag + ')' + + '|' + + '<(?!/\\2\\s*>)' + // other tags with a different name + ')*', + nested_tags_level + ); + + var content2 = content.replace('\\2', '\\3'); + + // First, look for nested blocks, e.g.: + //

+ //
+ // tags for inner block must be indented. + //
+ //
+ // + // The outermost tags must start at the left margin for this to match, and + // the inner nested divs must be indented. + // We need to do this before the next, more liberal match, because the next + // match will start at the first `
` and stop at the first `
`. + var all = new RegExp('(?:' + + '(?:' + + '(?:\\n\\n)' + // Starting after a blank line + '|' + // or + '(?:\\x02)\\n?' + // the beginning of the doc + ')' + + '(' + // save in $1 + + // Match from `\n` to `\n`, handling nested tags + // in between. + '[ ]{0,' + less_than_tab + '}' + + '<(' + block_tags_b_re + ')' + // start tag = $2 + attr + '>' + // attributes followed by > and \n + content + // content, support nesting + '' + // the matching end tag + '[ ]*' + // trailing spaces/tabs + '(?=\\n+|\\n*\\x03)' + // followed by a newline or end of document + + '|' + // Special version for tags of group a. + + '[ ]{0,' + less_than_tab + '}' + + '<(' + block_tags_a_re + ')' + // start tag = $3 + attr + '>[ ]*\\n' + // attributes followed by > + content2 + // content, support nesting + '' + // the matching end tag + '[ ]*' + // trailing spaces/tabs + '(?=\\n+|\\n*\\x03)' + // followed by a newline or end of document + + '|' + // Special case just for
. It was easier to make a special + // case than to make the other regex more complicated. + + '[ ]{0,' + less_than_tab + '}' + + '<(hr)' + // start tag = $2 + attr + // attributes + '/?>' + // the matching end tag + '[ ]*' + + '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document + + '|' + // Special case for standalone HTML comments: + + '[ ]{0,' + less_than_tab + '}' + + '(?:' + //'(?s:' + + '' + + ')' + + '[ ]*' + + '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document + + '|' + // PHP and ASP-style processor instructions (' + + ')' + + '[ ]*' + + '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document + + ')' + + ')', 'mig'); + // FIXME: JS doesnt have enough escape sequence \A nor \Z. + + var self = this; + text = this.__wrapSTXETX__(text); + text = text.replace(all, function(match, text) { + //console.log(match); + var key = self.hashBlock(text); + return "\n\n" + key + "\n\n"; + }); + text = this.__unwrapSTXETX__(text); + return text; +}; + +/** + * Called whenever a tag must be hashed when a function insert an atomic + * element in the text stream. Passing $text to through this function gives + * a unique text-token which will be reverted back when calling unhash. + * + * The boundary argument specify what character should be used to surround + * the token. By convension, "B" is used for block elements that needs not + * to be wrapped into paragraph tags at the end, ":" is used for elements + * that are word separators and "X" is used in the general case. + */ +Markdown_Parser.prototype.hashPart = function(text, boundary) { + if('undefined' === typeof boundary) { + boundary = 'X'; + } + // Swap back any tag hash found in text so we do not have to `unhash` + // multiple times at the end. + text = this.unhash(text); + + // Then hash the block. + if('undefined' === typeof arguments.callee.i) { + arguments.callee.i = 0; + } + var key = boundary + "\x1A" + (++arguments.callee.i) + boundary; + this.html_hashes[key] = text; + return key; // String that will replace the tag. +}; + +/** + * Shortcut function for hashPart with block-level boundaries. + */ +Markdown_Parser.prototype.hashBlock = function(text) { + return this.hashPart(text, 'B'); +}; + +/** + * Strips link definitions from text, stores the URLs and titles in + * hash references. + */ +Markdown_Parser.prototype.stripLinkDefinitions = function(text) { + var less_than_tab = this.tab_width - 1; + var self = this; + // Link defs are in the form: ^[id]: url "optional title" + text = this.__wrapSTXETX__(text); + text = text.replace(new RegExp( + '^[ ]{0,' + less_than_tab + '}\\[(.+)\\][ ]?:' + // id = $1 + '[ ]*' + + '\\n?' + // maybe *one* newline + '[ ]*' + + '(?:' + + '<(.+?)>' + // url = $2 + '|' + + '(\\S+?)' + // url = $3 + ')' + + '[ ]*' + + '\\n?' + // maybe one newline + '[ ]*' + + '(?:' + + //'(?=\\s)' + // lookbehind for whitespace + '["\\(]' + + '(.*?)' + // title = $4 + '["\\)]' + + '[ ]*' + + ')?' + // title is optional + '(?:\\n+|\\n*(?=\\x03))', + 'mg'), function(match, id, url2, url3, title) { + //console.log(match); + var link_id = id.toLowerCase(); + var url = url2 ? url2 : url3; + self.urls[link_id] = url; + self.titles[link_id] = title; + return ''; // String that will replace the block + } + ); + text = this.__unwrapSTXETX__(text); + return text; +}; + +/** + * Run block gamut tranformations. + */ +Markdown_Parser.prototype.runBlockGamut = function(text) { + // We need to escape raw HTML in Markdown source before doing anything + // else. This need to be done for each block, and not only at the + // begining in the Markdown function since hashed blocks can be part of + // list items and could have been indented. Indented blocks would have + // been seen as a code block in a previous pass of hashHTMLBlocks. + text = this.hashHTMLBlocks(text); + return this.runBasicBlockGamut(text); +}; + +/** + * Run block gamut tranformations, without hashing HTML blocks. This is + * useful when HTML blocks are known to be already hashed, like in the first + * whole-document pass. + */ +Markdown_Parser.prototype.runBasicBlockGamut = function(text) { + for(var i = 0; i < this.block_gamut.length; i++) { + var method = this[this.block_gamut[i][0]]; + if(method) { + text = method.call(this, text); + } + else { + console.log(this.block_gamut[i][0] + ' not implemented'); + } + } + // Finally form paragraph and restore hashed blocks. + text = this.formParagraphs(text); + return text; +}; + +/** + * Do Horizontal Rules: + */ +Markdown_Parser.prototype.doHorizontalRules = function(text) { + var self = this; + return text.replace(new RegExp( + '^[ ]{0,3}' + // Leading space + '([-\\*_])' + // $1: First marker + '(?:' + // Repeated marker group + '[ ]{0,2}' + // Zero, one, or two spaces. + '\\1' + // Marker character + '){2,}' + // Group repeated at least twice + '[ ]*' + //Tailing spaces + '$' , // End of line. + 'mg'), function(match) { + //console.log(match); + return "\n" + self.hashBlock(" tags. + */ +Markdown_Parser.prototype.doAnchors = function(text) { + if (this.in_anchor) return text; + this.in_anchor = true; + + var self = this; + + var _doAnchors_reference_callback = function(match, whole_match, link_text, link_id) { + //console.log(match); + if(typeof(link_id) !== 'string' || link_id === '') { + // for shortcut links like [this][] or [this]. + link_id = link_text; + } + + // lower-case and turn embedded newlines into spaces + link_id = link_id.toLowerCase(); + link_id = link_id.replace(/[ ]?\n/, ' '); + + var result; + if ('undefined' !== typeof self.urls[link_id]) { + var url = self.urls[link_id]; + url = self.encodeAttribute(url); + + result = ""; + result = self.hashPart(result); + } + else { + result = whole_match; + } + return result; + }; + + // + // First, handle reference-style links: [link text] [id] + // + text = text.replace(new RegExp( + '(' + // wrap whole match in $1 + '\\[' + + '(' + this.nested_brackets_re + ')' + // link text = $2 + '\\]' + + + '[ ]?' + // one optional space + '(?:\\n[ ]*)?' + // one optional newline followed by spaces + + '\\[' + + '(.*?)' + // id = $3 + '\\]' + + ')', + 'mg' + ), _doAnchors_reference_callback); + + // + // Next, inline-style links: [link text](url "optional title") + // + text = text.replace(new RegExp( + '(' + // wrap whole match in $1 + '\\[' + + '(' + this.nested_brackets_re + ')' + // link text = $2 + '\\]' + + '\\(' + // literal paren + '[ \\n]*' + + '(?:' + + '<(.+?)>' + // href = $3 + '|' + + '(' + this.nested_url_parenthesis_re + ')' + // href = $4 + ')' + + '[ \\n]*' + + '(' + // $5 + '([\'"])' + // quote char = $6 + '(.*?)' + // Title = $7 + '\\6' + // matching quote + '[ \\n]*' + // ignore any spaces/tabs between closing quote and ) + ')?' + // title is optional + '\\)' + + ')', + 'mg' + ), function(match, whole_match, link_text, url3, url4, x0, x1, title) { + //console.log(match); + link_text = self.runSpanGamut(link_text); + var url = url3 ? url3 : url4; + + url = self.encodeAttribute(url); + + var result = ""; + + return self.hashPart(result); + }); + + // + // Last, handle reference-style shortcuts: [link text] + // These must come last in case you've also got [link text][1] + // or [link text](/foo) + // + text = text.replace(new RegExp( + '(' + // wrap whole match in $1 + '\\[' + + '([^\\[\\]]+)' + // link text = $2; can\'t contain [ or ] + '\\]' + + ')', + 'mg' + ), _doAnchors_reference_callback); + + this.in_anchor = false; + return text; +}; + +/** + * Turn Markdown image shortcuts into tags. + */ +Markdown_Parser.prototype.doImages = function(text) { + var self = this; + + // + // First, handle reference-style labeled images: ![alt text][id] + // + text = text.replace(new RegExp( + '(' + // wrap whole match in $1 + '!\\[' + + '(' + this.nested_brackets_re + ')' + // alt text = $2 + '\\]' + + + '[ ]?' + // one optional space + '(?:\\n[ ]*)?' + // one optional newline followed by spaces + + '\\[' + + '(.*?)' + // id = $3 + '\\]' + + + ')', + 'mg' + ), function(match, whole_match, alt_text, link_id) { + //console.log(match); + link_id = link_id.toLowerCase(); + + if (typeof(link_id) !== 'string' || link_id === '') { + link_id = alt_text.toLowerCase(); // for shortcut links like ![this][]. + } + + alt_text = self.encodeAttribute(alt_text); + var result; + if ('undefined' !== typeof self.urls[link_id]) { + var url = self.encodeAttribute(self.urls[link_id]); + result = "\""' + // src url = $3 + '|' + + '(' + this.nested_url_parenthesis_re + ')' + // src url = $4 + ')' + + '[ \\n]*' + + '(' + // $5 + '([\'"])' + // quote char = $6 + '(.*?)' + // title = $7 + '\\6' + // matching quote + '[ \\n]*' + + ')?' + // title is optional + '\\)' + + ')', + 'mg' + ), function(match, whole_match, alt_text, url3, url4, x5, x6, title) { + //console.log(match); + var url = url3 ? url3 : url4; + + alt_text = self.encodeAttribute(alt_text); + url = self.encodeAttribute(url); + var result = "\""" + self.runSpanGamut(span) + ""; + return "\n" + self.hashBlock(block) + "\n\n"; + }); + + // atx-style headers: + // # Header 1 + // ## Header 2 + // ## Header 2 with closing hashes ## + // ... + // ###### Header 6 + // + text = text.replace(new RegExp( + '^(\\#{1,6})' + // $1 = string of #\'s + '[ ]*' + + '(.+?)' + // $2 = Header text + '[ ]*' + + '\\#*' + // optional closing #\'s (not counted) + '\\n+', + 'mg' + ), function(match, hashes, span) { + //console.log(match); + var level = hashes.length; + var block = "" + self.runSpanGamut(span) + ""; + return "\n" + self.hashBlock(block) + "\n\n"; + }); + + return text; +}; + +/** + * Form HTML ordered (numbered) and unordered (bulleted) lists. + */ +Markdown_Parser.prototype.doLists = function(text) { + var less_than_tab = this.tab_width - 1; + + // Re-usable patterns to match list item bullets and number markers: + var marker_ul_re = '[\\*\\+-]'; + var marker_ol_re = '\\d+[\\.]'; + var marker_any_re = "(?:" + marker_ul_re + "|" + marker_ol_re + ")"; + + var self = this; + var _doLists_callback = function(match, list, x2, x3, type) { + //console.log(match); + // Re-usable patterns to match list item bullets and number markers: + var list_type = type.match(marker_ul_re) ? "ul" : "ol"; + + var marker_any_re = list_type == "ul" ? marker_ul_re : marker_ol_re; + + list += "\n"; + var result = self.processListItems(list, marker_any_re); + + result = self.hashBlock("<" + list_type + ">\n" + result + ""); + return "\n" + result + "\n\n"; + }; + + var markers_relist = [ + [marker_ul_re, marker_ol_re], + [marker_ol_re, marker_ul_re] + ]; + + for (var i = 0; i < markers_relist.length; i++) { + var marker_re = markers_relist[i][0]; + var other_marker_re = markers_relist[i][1]; + // Re-usable pattern to match any entirel ul or ol list: + var whole_list_re = + '(' + // $1 = whole list + '(' + // $2 + '([ ]{0,' + less_than_tab + '})' + // $3 = number of spaces + '(' + marker_re + ')' + // $4 = first list item marker + '[ ]+' + + ')' + + '[\\s\\S]+?' + + '(' + // $5 + '(?=\\x03)' + // \z + '|' + + '\\n{2,}' + + '(?=\\S)' + + '(?!' + // Negative lookahead for another list item marker + '[ ]*' + + marker_re + '[ ]+' + + ')' + + '|' + + '(?=' + // Lookahead for another kind of list + '\\n' + + '\\3' + // Must have the same indentation + other_marker_re + '[ ]+' + + ')' + + ')' + + ')'; // mx + + // We use a different prefix before nested lists than top-level lists. + // See extended comment in _ProcessListItems(). + + text = this.__wrapSTXETX__(text); + if (this.list_level) { + text = text.replace(new RegExp('^' + whole_list_re, "mg"), _doLists_callback); + } + else { + text = text.replace(new RegExp( + '(?:(?=\\n)\\n|\\x02\\n?)' + // Must eat the newline + whole_list_re, "mg" + ), _doLists_callback); + } + text = this.__unwrapSTXETX__(text); + } + + return text; +}; + +// var $list_level = 0; + +/** + * Process the contents of a single ordered or unordered list, splitting it + * into individual list items. + */ +Markdown_Parser.prototype.processListItems = function(list_str, marker_any_re) { + // The $this->list_level global keeps track of when we're inside a list. + // Each time we enter a list, we increment it; when we leave a list, + // we decrement. If it's zero, we're not in a list anymore. + // + // We do this because when we're not inside a list, we want to treat + // something like this: + // + // I recommend upgrading to version + // 8. Oops, now this line is treated + // as a sub-list. + // + // As a single paragraph, despite the fact that the second line starts + // with a digit-period-space sequence. + // + // Whereas when we're inside a list (or sub-list), that line will be + // treated as the start of a sub-list. What a kludge, huh? This is + // an aspect of Markdown's syntax that's hard to parse perfectly + // without resorting to mind-reading. Perhaps the solution is to + // change the syntax rules such that sub-lists must start with a + // starting cardinal number; e.g. "1." or "a.". + + if('undefined' === typeof this.list_level) { + this.list_level = 0; + } + this.list_level++; + + // trim trailing blank lines: + list_str = this.__wrapSTXETX__(list_str); + list_str = list_str.replace(/\n{2,}(?=\x03)/m, "\n"); + list_str = this.__unwrapSTXETX__(list_str); + + var self = this; + list_str = this.__wrapSTXETX__(list_str); + list_str = list_str.replace(new RegExp( + '(\\n)?' + // leading line = $1 + '([ ]*)' + // leading whitespace = $2 + '(' + marker_any_re + // list marker and space = $3 + '(?:[ ]+|(?=\\n))' + // space only required if item is not empty + ')' + + '([\\s\\S]*?)' + // list item text = $4 + '(?:(\\n+(?=\\n))|\\n)' + // tailing blank line = $5 + '(?=\\n*(\\x03|\\2(' + marker_any_re + ')(?:[ ]+|(?=\\n))))', + "gm" + ), function(match, leading_line, leading_space, marker_space, item, tailing_blank_line) { + //console.log(match); + //console.log(item, [leading_line ? leading_line.length : 0, tailing_blank_line ? tailing_blank_line.length : 0]); + if (leading_line || tailing_blank_line || item.match(/\n{2,}/)) { + // Replace marker with the appropriate whitespace indentation + item = leading_space + self._php_str_repeat(' ', marker_space.length) + item; + item = self.runBlockGamut(self.outdent(item) + "\n"); + } + else { + // Recursion for sub-lists: + item = self.doLists(self.outdent(item)); + item = item.replace(/\n+$/m, ''); + item = self.runSpanGamut(item); + } + + return "
  • " + item + "
  • \n"; + }); + list_str = this.__unwrapSTXETX__(list_str); + + this.list_level--; + return list_str; +}; + +/** + * Process Markdown `
    ` blocks.
    + */
    +Markdown_Parser.prototype.doCodeBlocks = function(text) {
    +    var self = this;
    +    text = this.__wrapSTXETX__(text);
    +    text = text.replace(new RegExp(
    +        '(?:\\n\\n|(?=\\x02)\\n?)' +
    +        '('                        + // $1 = the code block -- one or more lines, starting with a space/tab
    +          '(?:^'                   +
    +            '[ ]{' + this.tab_width + ',}' +  // Lines must start with a tab or a tab-width of spaces
    +            '.*\\n+'               +
    +          ')+'                     +
    +        ')'                        +
    +        '((?=[ ]{0,' + this.tab_width + '}\\S)|(?:\\n*(?=\\x03)))',  // Lookahead for non-space at line-start, or end of doc
    +        'mg'
    +    ), function(match, codeblock) {
    +        //console.log(match);
    +        codeblock = self.outdent(codeblock);
    +        codeblock = self._php_htmlspecialchars_ENT_NOQUOTES(codeblock);
    +
    +        // trim leading newlines and trailing newlines
    +        codeblock = self.__wrapSTXETX__(codeblock);
    +        codeblock = codeblock.replace(/(?=\x02)\n+|\n+(?=\x03)/g, '');
    +        codeblock = self.__unwrapSTXETX__(codeblock);
    +
    +        codeblock = "
    " + codeblock + "\n
    "; + return "\n\n" + self.hashBlock(codeblock) + "\n\n"; + }); + text = this.__unwrapSTXETX__(text); + return text; +}; + +/** + * Create a code span markup for $code. Called from handleSpanToken. + */ +Markdown_Parser.prototype.makeCodeSpan = function(code) { + code = this._php_htmlspecialchars_ENT_NOQUOTES(this._php_trim(code)); + return this.hashPart("" + code + ""); +}; + +/** + * Prepare regular expressions for searching emphasis tokens in any + * context. + */ +Markdown_Parser.prototype.prepareItalicsAndBold = function() { + this.em_strong_prepared_relist = {}; + for(var i = 0; i < this.em_relist.length; i++) { + var em = this.em_relist[i][0]; + var em_re = this.em_relist[i][1]; + for(var j = 0; j < this.strong_relist.length; j++) { + var strong = this.strong_relist[j][0]; + var strong_re = this.strong_relist[j][1]; + // Construct list of allowed token expressions. + var token_relist = []; + for(var k = 0; k < this.em_strong_relist.length; k++) { + var em_strong = this.em_strong_relist[k][0]; + var em_strong_re = this.em_strong_relist[k][1]; + if(em + strong == em_strong) { + token_relist.push(em_strong_re); + } + } + token_relist.push(em_re); + token_relist.push(strong_re); + + // Construct master expression from list. + var token_re = new RegExp('(' + token_relist.join('|') + ')'); + this.em_strong_prepared_relist['rx_' + em + strong] = token_re; + } + } +}; + +Markdown_Parser.prototype.doItalicsAndBold = function(text) { + var em = ''; + var strong = ''; + var tree_char_em = false; + var text_stack = ['']; + var token_stack = []; + var token = ''; + + while (1) { + // + // Get prepared regular expression for seraching emphasis tokens + // in current context. + // + var token_re = this.em_strong_prepared_relist['rx_' + em + strong]; + + // + // Each loop iteration search for the next emphasis token. + // Each token is then passed to handleSpanToken. + // + var parts = text.match(token_re); //PREG_SPLIT_DELIM_CAPTURE + if(parts) { + var left = RegExp.leftContext; + var right = RegExp.rightContext; + var pre = ""; + var marker = parts[1]; + for(var mg = 2; mg < parts.length; mg += 2) { + if('undefined' !== typeof parts[mg]) { + pre = parts[mg]; + marker = parts[mg + 1]; + break; + } + } + //console.log([left + pre, marker]); + text_stack[0] += (left + pre); + token = marker; + text = right; + } + else { + text_stack[0] += text; + token = ''; + text = ''; + } + if(token == '') { + // Reached end of text span: empty stack without emitting. + // any more emphasis. + while (token_stack.length > 0 && token_stack[0].length > 0) { + text_stack[1] += token_stack.shift(); + var text_stack_prev0 = text_stack.shift(); // $text_stack[0] .= array_shift($text_stack); + text_stack[0] += text_stack_prev0; + } + break; + } + + var tag, span; + + var token_len = token.length; + if (tree_char_em) { + // Reached closing marker while inside a three-char emphasis. + if (token_len == 3) { + // Three-char closing marker, close em and strong. + token_stack.shift(); + span = text_stack.shift(); + span = this.runSpanGamut(span); + span = "" + span + ""; + text_stack[0] += this.hashPart(span); + em = ''; + strong = ''; + } else { + // Other closing marker: close one em or strong and + // change current token state to match the other + token_stack[0] = this._php_str_repeat(token.charAt(0), 3 - token_len); + tag = token_len == 2 ? "strong" : "em"; + span = text_stack[0]; + span = this.runSpanGamut(span); + span = "<" + tag + ">" + span + ""; + text_stack[0] = this.hashPart(span); + if(tag == 'strong') { strong = ''; } else { em = ''; } + } + tree_char_em = false; + } else if (token_len == 3) { + if (em != '') { + // Reached closing marker for both em and strong. + // Closing strong marker: + for (var i = 0; i < 2; ++i) { + var shifted_token = token_stack.shift(); + tag = shifted_token.length == 2 ? "strong" : "em"; + span = text_stack.shift(); + span = this.runSpanGamut(span); + span = "<" + tag + ">" + span + ""; + text_stack[0] = this.hashPart(span); + if(tag == 'strong') { strong = ''; } else { em = ''; } + } + } else { + // Reached opening three-char emphasis marker. Push on token + // stack; will be handled by the special condition above. + em = token.charAt(0); + strong = em + em; + token_stack.unshift(token); + text_stack.unshift(''); + tree_char_em = true; + } + } else if (token_len == 2) { + if (strong != '') { + // Unwind any dangling emphasis marker: + if (token_stack[0].length == 1) { + text_stack[1] += token_stack.shift(); + text_stack[0] += text_stack.shift(); + } + // Closing strong marker: + token_stack.shift(); + span = text_stack.shift(); + span = this.runSpanGamut(span); + span = "" + span + ""; + text_stack[0] += this.hashPart(span); + strong = ''; + } else { + token_stack.unshift(token); + text_stack.unshift(''); + strong = token; + } + } else { + // Here $token_len == 1 + if (em != '') { + if (token_stack[0].length == 1) { + // Closing emphasis marker: + token_stack.shift(); + span = text_stack.shift(); + span = this.runSpanGamut(span); + span = "" + span + ""; + text_stack[0] += this.hashPart(span); + em = ''; + } else { + text_stack[0] += token; + } + } else { + token_stack.unshift(token); + text_stack.unshift(''); + em = token; + } + } + } + return text_stack[0]; +}; + + +Markdown_Parser.prototype.doBlockQuotes = function(text) { + var self = this; + text = text.replace(new RegExp( + '(' + // Wrap whole match in $1 + '(?:' + + '^[ ]*>[ ]?' + // ">" at the start of a line + '.+\\n' + // rest of the first line + '(.+\\n)*' + // subsequent consecutive lines + '\\n*' + // blanks + ')+' + + ')', + 'mg' + ), function(match, bq) { + //console.log(match); + // trim one level of quoting - trim whitespace-only lines + bq = bq.replace(/^[ ]*>[ ]?|^[ ]+$/mg, ''); + bq = self.runBlockGamut(bq); // recurse + + bq = bq.replace(/^/mg, " "); + // These leading spaces cause problem with
     content, 
    +        // so we need to fix that:
    +        bq = bq.replace(/(\\s*
    [\\s\\S]+?<\/pre>)/mg, function(match, pre) {
    +            //console.log(match);
    +            pre = pre.replace(/^  /m, '');
    +            return pre;
    +        });
    +
    +        return "\n" + self.hashBlock("
    \n" + bq + "\n
    ") + "\n\n"; + }); + return text; +}; + +/** + * Params: + * $text - string to process with html

    tags + */ +Markdown_Parser.prototype.formParagraphs = function(text) { + + // Strip leading and trailing lines: + text = this.__wrapSTXETX__(text); + text = text.replace(/(?:\x02)\n+|\n+(?:\x03)/g, ""); + text = this.__unwrapSTXETX__(text); + // [porting note] + // below may be faster than js regexp. + //for(var s = 0; s < text.length && text.charAt(s) == "\n"; s++) { } + //text = text.substr(s); + //for(var e = text.length; e > 0 && text.charAt(e - 1) == "\n"; e--) { } + //text = text.substr(0, e); + + var grafs = text.split(/\n{2,}/m); + //preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); + + // + // Wrap

    tags and unhashify HTML blocks + // + for(var i = 0; i < grafs.length; i++) { + var value = grafs[i]; + if(value == "") { + // [porting note] + // This case is replacement for PREG_SPLIT_NO_EMPTY. + } + else if (!value.match(/^B\x1A[0-9]+B$/)) { + // Is a paragraph. + value = this.runSpanGamut(value); + value = value.replace(/^([ ]*)/, "

    "); + value += "

    "; + grafs[i] = this.unhash(value); + } + else { + // Is a block. + // Modify elements of @grafs in-place... + var graf = value; + var block = this.html_hashes[graf]; + graf = block; + //if (preg_match('{ + // \A + // ( # $1 =
    tag + //
    ]* + // \b + // markdown\s*=\s* ([\'"]) # $2 = attr quote char + // 1 + // \2 + // [^>]* + // > + // ) + // ( # $3 = contents + // .* + // ) + // (
    ) # $4 = closing tag + // \z + // }xs', $block, $matches)) + //{ + // list(, $div_open, , $div_content, $div_close) = $matches; + // + // # We can't call Markdown(), because that resets the hash; + // # that initialization code should be pulled into its own sub, though. + // $div_content = $this->hashHTMLBlocks($div_content); + // + // # Run document gamut methods on the content. + // foreach ($this->document_gamut as $method => $priority) { + // $div_content = $this->$method($div_content); + // } + // + // $div_open = preg_replace( + // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); + // + // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; + //} + grafs[i] = graf; + } + } + + return grafs.join("\n\n"); +}; + +/** + * Encode text for a double-quoted HTML attribute. This function + * is *not* suitable for attributes enclosed in single quotes. + */ +Markdown_Parser.prototype.encodeAttribute = function(text) { + text = this.encodeAmpsAndAngles(text); + text = text.replace(/"/g, '"'); + return text; +}; + +/** + * Smart processing for ampersands and angle brackets that need to + * be encoded. Valid character entities are left alone unless the + * no-entities mode is set. + */ +Markdown_Parser.prototype.encodeAmpsAndAngles = function(text) { + if (this.no_entities) { + text = text.replace(/&/g, '&'); + } else { + // Ampersand-encoding based entirely on Nat Irons's Amputator + // MT plugin: + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/, '&'); + } + // Encode remaining <'s + text = text.replace(/\s]+)>/i, function(match, address) { + //console.log(match); + var url = self.encodeAttribute(address); + var link = "
    " + url + ""; + return self.hashPart(link); + }); + + // Email addresses: + text = text.replace(new RegExp( + '<' + + '(?:mailto:)?' + + '(' + + '(?:' + + '[-!#$%&\'*+/=?^_`.{|}~\\w\\x80-\\xFF]+' + + '|' + + '".*?"' + + ')' + + '\\@' + + '(?:' + + '[-a-z0-9\\x80-\\xFF]+(\\.[-a-z0-9\\x80-\\xFF]+)*\\.[a-z]+' + + '|' + + '\\[[\\d.a-fA-F:]+\\]' + // IPv4 & IPv6 + ')' + + ')' + + '>', + 'i' + ), function(match, address) { + //console.log(match); + var link = self.encodeEmailAddress(address); + return self.hashPart(link); + }); + + return text; +}; + +/** + * Input: an email address, e.g. "foo@example.com" + * + * Output: the email address as a mailto link, with each character + * of the address encoded as either a decimal or hex entity, in + * the hopes of foiling most address harvesting spam bots. E.g.: + * + *

    foo@exampl + * e.com

    + * + * Based by a filter by Matthew Wickline, posted to BBEdit-Talk. + * With some optimizations by Milian Wolff. + */ +Markdown_Parser.prototype.encodeEmailAddress = function(addr) { + if('undefined' === typeof arguments.callee.crctable) { + arguments.callee.crctable = + "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 " + + "0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 " + + "1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 " + + "136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 " + + "3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B " + + "35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 " + + "26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F " + + "2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D " + + "76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 " + + "7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 " + + "6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 " + + "65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 " + + "4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB " + + "4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 " + + "5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F " + + "5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD " + + "EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 " + + "E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 " + + "F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 " + + "FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 " + + "D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B " + + "D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 " + + "CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F " + + "C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D " + + "9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 " + + "95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 " + + "86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 " + + "88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 " + + "A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB " + + "AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 " + + "BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF " + + "B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D".split(' '); + } + var crctable = arguments.callee.crctable; + function _crc32(str) { + var crc = 0; + crc = crc ^ (-1); + for (var i = 0; i < str.length; ++i) { + var y = (crc ^ str.charCodeAt(i)) & 0xff; + var x = "0x" + crctable[y]; + crc = (crc >>> 8) ^ x; + } + return (crc ^ (-1)) >>> 0; + } + + addr = "mailto:" + addr; + var chars = []; + var i; + for(i = 0; i < addr.length; i++) { + chars.push(addr.charAt(i)); + } + var seed = Math.floor(Math.abs(_crc32(addr) / addr.length)); // # Deterministic seed. + + for(i = 0; i < chars.length; i++) { + var c = chars[i]; + var ord = c.charCodeAt(0); + // Ignore non-ascii chars. + if(ord < 128) { + var r = (seed * (1 + i)) % 100; // Pseudo-random function. + // roughly 10% raw, 45% hex, 45% dec + // '@' *must* be encoded. I insist. + if(r > 90 && c != '@') { /* do nothing */ } + else if(r < 45) { chars[i] = '&#x' + ord.toString(16) + ';'; } + else { chars[i] = '&#' + ord.toString(10) + ';'; } + } + } + + addr = chars.join(''); + var text = chars.splice(7).join(''); // text without `mailto:` + addr = "" + text + ""; + + return addr; +}; + +/** + * Take the string $str and parse it into tokens, hashing embeded HTML, + * escaped characters and handling code spans. +*/ +Markdown_Parser.prototype.parseSpan = function(str) { + var output = ''; + + var span_re = new RegExp( + '(' + + '\\\\' + this.escape_chars_re + + '|' + + // This expression is too difficult for JS: '(?' + // comment + '|' + + '<\\?.*?\\?>|<%.*?%>' + // processing instruction + '|' + + '<[/!$]?[-a-zA-Z0-9:_]+' + // regular tags + '(?=' + + '\\s' + + '(?=[^"\'>]+|"[^"]*"|\'[^\']*\')*' + + ')?' + + '>' + )) + + ')' + ); + + while(1) { + // + // Each loop iteration seach for either the next tag, the next + // openning code span marker, or the next escaped character. + // Each token is then passed to handleSpanToken. + // + var parts = str.match(span_re); //PREG_SPLIT_DELIM_CAPTURE + if(parts) { + if(RegExp.leftContext) { + output += RegExp.leftContext; + } + // Back quote but after backslash is to be ignored. + if(RegExp.lastMatch.charAt(0) == "`" && + RegExp.leftContext.charAt(RegExp.leftContext.length - 1) == "\\" + ) { + output += RegExp.lastMatch; + str = RegExp.rightContext; + continue; + } + var r = this.handleSpanToken(RegExp.lastMatch, RegExp.rightContext); + output += r[0]; + str = r[1]; + } + else { + output += str; + break; + } + } + return output; +}; + + +/** + * Handle $token provided by parseSpan by determining its nature and + * returning the corresponding value that should replace it. +*/ +Markdown_Parser.prototype.handleSpanToken = function(token, str) { + //console.log([token, str]); + switch (token.charAt(0)) { + case "\\": + return [this.hashPart("&#" + token.charCodeAt(1) + ";"), str]; + case "`": + // Search for end marker in remaining text. + if (str.match(new RegExp('^([\\s\\S]*?[^`])' + this._php_preg_quote(token) + '(?!`)([\\s\\S]*)$', 'm'))) { + var code = RegExp.$1; + str = RegExp.$2; + var codespan = this.makeCodeSpan(code); + return [this.hashPart(codespan), str]; + } + return [token, str]; // return as text since no ending marker found. + default: + return [this.hashPart(token), str]; + } +}; + +/** + * Remove one level of line-leading tabs or spaces + */ +Markdown_Parser.prototype.outdent = function(text) { + return text.replace(new RegExp('^(\\t|[ ]{1,' + this.tab_width + '})', 'mg'), ''); +}; + + +//# String length function for detab. `_initDetab` will create a function to +//# hanlde UTF-8 if the default function does not exist. +//var $utf8_strlen = 'mb_strlen'; + +/** + * Replace tabs with the appropriate amount of space. + */ +Markdown_Parser.prototype.detab = function(text) { + // For each line we separate the line in blocks delemited by + // tab characters. Then we reconstruct every line by adding the + // appropriate number of space between each blocks. + var self = this; + return text.replace(/^.*\t.*$/mg, function(line) { + //$strlen = $this->utf8_strlen; # strlen function for UTF-8. + // Split in blocks. + var blocks = line.split("\t"); + // Add each blocks to the line. + line = blocks.shift(); // Do not add first block twice. + for(var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + // Calculate amount of space, insert spaces, insert block. + var amount = self.tab_width - line.length % self.tab_width; + line += self._php_str_repeat(" ", amount) + block; + } + return line; + }); +}; + +/** + * Swap back in all the tags hashed by _HashHTMLBlocks. + */ +Markdown_Parser.prototype.unhash = function(text) { + var self = this; + return text.replace(/(.)\x1A[0-9]+\1/g, function(match) { + return self.html_hashes[match]; + }); +}; +/*-------------------------------------------------------------------------*/ + +/** + * Constructor function. Initialize the parser object. + */ +function MarkdownExtra_Parser() { + + // Prefix for footnote ids. + this.fn_id_prefix = ""; + + // Optional title attribute for footnote links and backlinks. + this.fn_link_title = MARKDOWN_FN_LINK_TITLE; + this.fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE; + + // Optional class attribute for footnote links and backlinks. + this.fn_link_class = MARKDOWN_FN_LINK_CLASS; + this.fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; + + // Predefined abbreviations. + this.predef_abbr = {}; + + // Extra variables used during extra transformations. + this.footnotes = {}; + this.footnotes_ordered = []; + this.abbr_desciptions = {}; + this.abbr_word_re = ''; + + // Give the current footnote number. + this.footnote_counter = 1; + + // ### HTML Block Parser ### + + // Tags that are always treated as block tags: + this.block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend'; + + // Tags treated as block tags only if the opening tag is alone on it's line: + this.context_block_tags_re = 'script|noscript|math|ins|del'; + + // Tags where markdown="1" default to span mode: + this.contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; + + // Tags which must not have their contents modified, no matter where + // they appear: + this.clean_tags_re = 'script|math'; + + // Tags that do not need to be closed. + this.auto_close_tags_re = 'hr|img'; + + // Redefining emphasis markers so that emphasis by underscore does not + // work in the middle of a word. + this.em_relist = [ + ['' , '(?:(^|[^\\*])(\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(_)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['*', '((?:\\S|^)[^\\*])(\\*)(?!\\*)'], + ['_', '((?:\\S|^)[^_])(_)(?![a-zA-Z0-9_])'] + ]; + this.strong_relist = [ + ['' , '(?:(^|[^\\*])(\\*\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(__)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['**', '((?:\\S|^)[^\\*])(\\*\\*)(?!\\*)'], + ['__', '((?:\\S|^)[^_])(__)(?![a-zA-Z0-9_])'] + ]; + this.em_strong_relist = [ + ['' , '(?:(^|[^\\*])(\\*\\*\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(___)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], + ['***', '((?:\\S|^)[^\\*])(\\*\\*\\*)(?!\\*)'], + ['___', '((?:\\S|^)[^_])(___)(?![a-zA-Z0-9_])'] + ]; + + // Add extra escapable characters before parent constructor + // initialize the table. + this.escape_chars += ':|'; + + // Insert extra document, block, and span transformations. + // Parent constructor will do the sorting. + this.document_gamut.push(['doFencedCodeBlocks', 5]); + this.document_gamut.push(['stripFootnotes', 15]); + this.document_gamut.push(['stripAbbreviations', 25]); + this.document_gamut.push(['appendFootnotes', 50]); + + this.block_gamut.push(['doFencedCodeBlocks', 5]); + this.block_gamut.push(['doTables', 15]); + this.block_gamut.push(['doDefLists', 45]); + + this.span_gamut.push(['doFootnotes', 5]); + this.span_gamut.push(['doAbbreviations', 70]); +} +MarkdownExtra_Parser.prototype = new Markdown_Parser(); + +/** + * Setting up Extra-specific variables. + */ +MarkdownExtra_Parser.prototype.setup = function() { + this.constructor.prototype.setup.call(this); + + this.footnotes = {}; + this.footnotes_ordered = []; + this.abbr_desciptions = {}; + this.abbr_word_re = ''; + this.footnote_counter = 1; + + for(var abbr_word in this.predef_abbr) { + var abbr_desc = this.predef_abbr[abbr_word]; + if(this.abbr_word_re != '') { + this.abbr_word_re += '|'; + } + this.abbr_word_re += this._php_preg_quote(abbr_word); // ?? str -> re? + this.abbr_desciptions[abbr_word] = this._php_trim(abbr_desc); + } +}; + +/** + * Clearing Extra-specific variables. + */ +MarkdownExtra_Parser.prototype.teardown = function() { + this.footnotes = {}; + this.footnotes_ordered = []; + this.abbr_desciptions = {}; + this.abbr_word_re = ''; + + this.constructor.prototype.teardown.call(this); +}; + + +/** + * Hashify HTML Blocks and "clean tags". + * + * We only want to do this for block-level HTML tags, such as headers, + * lists, and tables. That's because we still want to wrap

    s around + * "paragraphs" that are wrapped in non-block-level tags, such as anchors, + * phrase emphasis, and spans. The list of tags we're looking for is + * hard-coded. + * + * This works by calling _HashHTMLBlocks_InMarkdown, which then calls + * _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" + * attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back + * _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. + * These two functions are calling each other. It's recursive! + */ +MarkdownExtra_Parser.prototype.hashHTMLBlocks = function(text) { + // + // Call the HTML-in-Markdown hasher. + // + var r = this._hashHTMLBlocks_inMarkdown(text); + text = r[0]; + + return text; +}; + +/** + * Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. + * + * * $indent is the number of space to be ignored when checking for code + * blocks. This is important because if we don't take the indent into + * account, something like this (which looks right) won't work as expected: + * + *

    + *
    + * Hello World. <-- Is this a Markdown code block or text? + *
    <-- Is this a Markdown code block or a real tag? + *
    + * + * If you don't like this, just don't indent the tag on which + * you apply the markdown="1" attribute. + * + * * If $enclosing_tag_re is not empty, stops at the first unmatched closing + * tag with that name. Nested tags supported. + * + * * If $span is true, text inside must treated as span. So any double + * newline will be replaced by a single newline so that it does not create + * paragraphs. + * + * Returns an array of that form: ( processed text , remaining text ) + */ +MarkdownExtra_Parser.prototype._hashHTMLBlocks_inMarkdown = function(text, indent, enclosing_tag_re, span) { + if('undefined' === typeof indent) { indent = 0; } + if('undefined' === typeof enclosing_tag_re) { enclosing_tag_re = ''; } + if('undefined' === typeof span) { span = false; } + + if(text === '') { return ['', '']; } + + var matches; + + // Regex to check for the presense of newlines around a block tag. + var newline_before_re = /(?:^\n?|\n\n)*$/; + var newline_after_re = new RegExp( + '^' + // Start of text following the tag. + '([ ]*)?' + // Optional comment. + '[ ]*\\n' , // Must be followed by newline. + 'm' + ); + + // Regex to match any tag. + var block_tag_re = new RegExp( + '(' + // $2: Capture hole tag. + '`) + '\'.*?\'|' + // Single quotes (can contain `>`) + '.+?' + // Anything but quotes and `>`. + ')*?' + + ')?' + + '>' + // End of tag. + '|' + + '' + // HTML Comment + '|' + + '<\\?.*?\\?>|<%.*?%>' + // Processing instruction + '|' + + '' + // CData Block + '|' + + // Code span marker + '`+' + + ( !span ? // If not in span. + '|' + + // Indented code block + '(?:^[ ]*\\n|^|\\n[ ]*\\n)' + + '[ ]{' + (indent + 4) + '}[^\\n]*\\n' + + '(?=' + + '(?:[ ]{' + (indent + 4) + '}[^\\n]*|[ ]*)\\n' + + ')*' + + '|' + + // Fenced code block marker + '(?:^|\\n)' + + '[ ]{0,' + indent + '}~~~+[ ]*\\n' + : '' ) + // # End (if not is span). + ')', + 'm' + ); + + var depth = 0; // Current depth inside the tag tree. + var parsed = ""; // Parsed text that will be returned. + + // + // Loop through every tag until we find the closing tag of the parent + // or loop until reaching the end of text if no parent tag specified. + // + do { + // + // Split the text using the first $tag_match pattern found. + // Text before pattern will be first in the array, text after + // pattern will be at the end, and between will be any catches made + // by the pattern. + // + var parts_available = text.match(block_tag_re); //PREG_SPLIT_DELIM_CAPTURE + var parts; + if(!parts_available) { + parts = [text]; + } + else { + parts = [RegExp.leftContext, RegExp.lastMatch, RegExp.rightContext]; + } + + // If in Markdown span mode, add a empty-string span-level hash + // after each newline to prevent triggering any block element. + if(span) { + var _void = this.hashPart("", ':'); + var newline = _void + "\n"; + parts[0] = _void + parts[0].replace(/\n/g, newline) + _void; + } + + parsed += parts[0]; // Text before current tag. + + // If end of $text has been reached. Stop loop. + if(!parts_available) { + text = ""; + break; + } + + var tag = parts[1]; // Tag to handle. + text = parts[2]; // Remaining text after current tag. + var tag_re = this._php_preg_quote(tag); // For use in a regular expression. + + var t; + var block_text; + // + // Check for: Code span marker + // + if (tag.charAt(0) == "`") { + // Find corresponding end marker. + tag_re = this._php_preg_quote(tag); + if(matches = text.match(new RegExp('^(.+?|\\n[^\\n])*?[^`]' + tag_re + '[^`]'))) { + // End marker found: pass text unchanged until marker. + parsed += tag + matches[0]; + text = text.substr(matches[0].length); + } + else { + // Unmatched marker: just skip it. + parsed += tag; + } + } + // + // Check for: Fenced code block marker. + // + else if(tag.match(new RegExp('^\\n?[ ]{0,' + (indent + 3) * '}~'))) { + // Fenced code block marker: find matching end marker. + tag_re = this._php_preg_quote(this._php_trim(tag)); + if(matches = text.match(new RegExp('^(?>.*\\n)+?[ ]{0,' + indent + '}' + tag_re + '[ ]*\\n'))) { + // End marker found: pass text unchanged until marker. + parsed += tag + matches[0]; + text = text.substr(matches[0].length); + } + else { + // No end marker: just skip it. + parsed += tag; + } + } + // + // Check for: Indented code block. + // + else if(tag.charAt(0) == "\n" || tag.charAt(0) == " ") { + // Indented code block: pass it unchanged, will be handled + // later. + parsed += tag; + } + // + // Check for: Opening Block level tag or + // Opening Context Block tag (like ins and del) + // used as a block tag (tag is alone on it's line). + // + else if (tag.match(new RegExp('^<(?:' + this.block_tags_re + ')\\b')) || + ( + tag.match(new RegExp('^<(?:' + this.context_block_tags_re + ')\\b')) && + parsed.match(newline_before_re) && + text.match(newline_after_re) + ) + ) { + // Need to parse tag and following text using the HTML parser. + t = this._hashHTMLBlocks_inHTML(tag + text, this.hashBlock, true); + block_text = t[0]; + text = t[1]; + + // Make sure it stays outside of any paragraph by adding newlines. + parsed += "\n\n" + block_text + "\n\n"; + } + // + // Check for: Clean tag (like script, math) + // HTML Comments, processing instructions. + // + else if( + tag.match(new RegExp('^<(?:' + this.clean_tags_re + ')\\b')) || + tag.charAt(1) == '!' || tag.charAt(1) == '?' + ) { + // Need to parse tag and following text using the HTML parser. + // (don't check for markdown attribute) + t = this._hashHTMLBlocks_inHTML(tag + text, this.hashClean, false); + block_text = t[0]; + text = t[1]; + + parsed += block_text; + } + // + // Check for: Tag with same name as enclosing tag. + // + else if (enclosing_tag_re !== '' && + // Same name as enclosing tag. + tag.match(new RegExp('^= 0); + + return [parsed, text]; +}; + +/** + * Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. + * + * * Calls $hash_method to convert any blocks. + * * Stops when the first opening tag closes. + * * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. + * (it is not inside clean tags) + * + * Returns an array of that form: ( processed text , remaining text ) + */ +MarkdownExtra_Parser.prototype._hashHTMLBlocks_inHTML = function(text, hash_method, md_attr) { + if(text === '') return ['', '']; + + var matches; + + // Regex to match `markdown` attribute inside of a tag. + var markdown_attr_re = new RegExp( + '\\s*' + // Eat whitespace before the `markdown` attribute + 'markdown' + + '\\s*=\\s*' + + '(?:' + + '(["\'])' + // $1: quote delimiter + '(.*?)' + // $2: attribute value + '\\1' + // matching delimiter + '|' + + '([^\\s>]*)' + // $3: unquoted attribute value + ')' + + '()' // $4: make $3 always defined (avoid warnings) + ); + + // Regex to match any tag. + var tag_re = new RegExp( + '(' + // $2: Capture hole tag. + '`) + '\'.*?\'|' + // Single quotes (can contain `>`) + '.+?' + // Anything but quotes and `>`. + ')*?' + + ')?' + + '>' + // End of tag. + '|' + + '' + // HTML Comment + '|' + + '<\\?.*?\\?>|<%.*?%>' + // Processing instruction + '|' + + '' + // CData Block + ')' + ); + + var original_text = text; // Save original text in case of faliure. + + var depth = 0; // Current depth inside the tag tree. + var block_text = ""; // Temporary text holder for current text. + var parsed = ""; // Parsed text that will be returned. + + // + // Get the name of the starting tag. + // (This pattern makes $base_tag_name_re safe without quoting.) + // + var base_tag_name_re = ""; + if(matches = text.match(/^<([\w:$]*)\b/)) { + base_tag_name_re = matches[1]; + } + + // + // Loop through every tag until we find the corresponding closing tag. + // + do { + // + // Split the text using the first $tag_match pattern found. + // Text before pattern will be first in the array, text after + // pattern will be at the end, and between will be any catches made + // by the pattern. + // + var parts_available = text.match(tag_re); //PREG_SPLIT_DELIM_CAPTURE); + // If end of $text has been reached. Stop loop. + if(!parts_available) { + // + // End of $text reached with unbalenced tag(s). + // In that case, we return original text unchanged and pass the + // first character as filtered to prevent an infinite loop in the + // parent function. + // + return [original_text.charAt(0), original_text.substr(1)]; + } + var parts = [RegExp.leftContext, RegExp.lastMatch, RegExp.rightContext]; + + block_text += parts[0]; // Text before current tag. + var tag = parts[1]; // Tag to handle. + text = parts[2]; // Remaining text after current tag. + + // + // Check for: Auto-close tag (like
    ) + // Comments and Processing Instructions. + // + if(tag.match(new RegExp('^ 0) { + block_text = block_text.replace(new RegExp('/^[ ]{1,' + indent + '}', 'm'), ""); + } + + // Append tag content to parsed text. + if (!span_mode) { parsed += "\n\n" + block_text + "\n\n"; } + else { parsed += block_text; } + + // Start over a new block. + block_text = ""; + } + else { + block_text += tag; + } + } + + } while(depth > 0); + + // + // Hash last block text that wasn't processed inside the loop. + // + parsed += hash_method.call(this, block_text); + + return [parsed, text]; +}; + + +/** + * Called whenever a tag must be hashed when a function insert a "clean" tag + * in $text, it pass through this function and is automaticaly escaped, + * blocking invalid nested overlap. + */ +MarkdownExtra_Parser.prototype.hashClean = function(text) { + return this.hashPart(text, 'C'); +}; + + +/** + * Redefined to add id attribute support. + */ +MarkdownExtra_Parser.prototype.doHeaders = function(text) { + var self = this; + + function _doHeaders_attr(attr) { + if('undefined' === typeof attr || attr == "") { return ""; } + return " id=\"" + attr + "\""; + } + + // Setext-style headers: + // Header 1 {#header1} + // ======== + // + // Header 2 {#header2} + // -------- + + text = text.replace(new RegExp( + '(^.+?)' + // $1: Header text + '(?:[ ]+\\{\\#([-_:a-zA-Z0-9]+)\\})?' + // $2: Id attribute + '[ ]*\\n(=+|-+)[ ]*\\n+', // $3: Header footer + 'mg' + ), function(match, span, id, line) { + //console.log(match); + if(line == '-' && span.match(/^- /)) { + return match; + } + var level = line.charAt(0) == '=' ? 1 : 2; + var attr = _doHeaders_attr(id); + var block = "" + self.runSpanGamut(span) + ""; + return "\n" + self.hashBlock(block) + "\n\n"; + }); + + // atx-style headers: + // # Header 1 {#header1} + // ## Header 2 {#header2} + // ## Header 2 with closing hashes ## {#header3} + // ... + // ###### Header 6 {#header2} + + text = text.replace(new RegExp( + '^(\\#{1,6})' + // $1 = string of #\'s + '[ ]*' + + '(.+?)' + // $2 = Header text + '[ ]*' + + '\\#*' + // optional closing #\'s (not counted) + '(?:[ ]+\\{\\#([-_:a-zA-Z0-9]+)\\})?' + // id attribute + '\\n+', + 'mg' + ), function(match, hashes, span, id) { + //console.log(match); + var level = hashes.length; + var attr = _doHeaders_attr(id); + var block = "" + self.runSpanGamut(span) + ""; + return "\n" + self.hashBlock(block) + "\n\n"; + }); + + return text; +}; + +/** + * Form HTML tables. + */ +MarkdownExtra_Parser.prototype.doTables = function(text) { + var self = this; + + var less_than_tab = this.tab_width - 1; + + var _doTable_callback = function(match, head, underline, content) { + //console.log(match); + // Remove any tailing pipes for each line. + head = head.replace(/[|] *$/m, ''); + underline = underline.replace(/[|] *$/m, ''); + content = content.replace(/[|] *$/m, ''); + + var attr = []; + + // Reading alignement from header underline. + var separators = underline.split(/[ ]*[|][ ]*/); + var n; + for(n = 0; n < separators.length; n++) { + var s = separators[n]; + if (s.match(/^ *-+: *$/)) { attr[n] = ' align="right"'; } + else if (s.match(/^ *:-+: *$/)) { attr[n] = ' align="center"'; } + else if (s.match(/^ *:-+ *$/)) { attr[n] = ' align="left"'; } + else { attr[n] = ''; } + } + + // Parsing span elements, including code spans, character escapes, + // and inline HTML tags, so that pipes inside those gets ignored. + head = self.parseSpan(head); + var headers = head.split(/ *[|] */); + var col_count = headers.length; + + // Write column headers. + var text = "\n"; + text += "\n"; + text += "\n"; + for(n = 0; n < headers.length; n++) { + var header = headers[n]; + text += " " + self.runSpanGamut(self._php_trim(header)) + "\n"; + } + text += "\n"; + text += "\n"; + + // Split content by row. + var rows = self._php_trim(content, "\n").split("\n"); + + text += "\n"; + for(var i = 0; i < rows.length; i++) { + var row = rows[i]; + // Parsing span elements, including code spans, character escapes, + // and inline HTML tags, so that pipes inside those gets ignored. + row = self.parseSpan(row); + + // Split row by cell. + var row_cells = row.split(/ *[|] */, col_count); + while(row_cells.length < col_count) { row_cells.push(''); } + + text += "\n"; + for(n = 0; n < row_cells.length; n++) { + var cell = row_cells[n]; + text += " " + self.runSpanGamut(self._php_trim(cell)) + "\n"; + } + text += "\n"; + } + text += "\n"; + text += "
    "; + + return self.hashBlock(text) + "\n"; + }; + + text = this.__wrapSTXETX__(text); + + // + // Find tables with leading pipe. + // + // | Header 1 | Header 2 + // | -------- | -------- + // | Cell 1 | Cell 2 + // | Cell 3 | Cell 4 + // + text = text.replace(new RegExp( + '^' + // Start of a line + '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. + '[|]' + // Optional leading pipe (present) + '(.+)\\n' + // $1: Header row (at least one pipe) + + '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. + '[|]([ ]*[-:]+[-| :]*)\\n' + // $2: Header underline + + '(' + // $3: Cells + '(?:' + + '[ ]*' + // Allowed whitespace. + '[|].*\\n' + // Row content. + ')*' + + ')' + + '(?=\\n|\\x03)' , // Stop at final double newline. + 'mg' + ), function(match, head, underline, content) { + // Remove leading pipe for each row. + content = content.replace(/^ *[|]/m, ''); + + return _doTable_callback.call(this, match, head, underline, content); + }); + + // + // Find tables without leading pipe. + // + // Header 1 | Header 2 + // -------- | -------- + // Cell 1 | Cell 2 + // Cell 3 | Cell 4 + // + text = text.replace(new RegExp( + '^' + // Start of a line + '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. + '(\\S.*[|].*)\\n' + // $1: Header row (at least one pipe) + + '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. + '([-:]+[ ]*[|][-| :]*)\\n' + // $2: Header underline + + '(' + // $3: Cells + '(?:' + + '.*[|].*\\n' + // Row content + ')*' + + ')' + + '(?=\\n|\\x03)' , // Stop at final double newline. + 'mg' + ), _doTable_callback); + + text = this.__unwrapSTXETX__(text); + + return text; +}; + +/** + * Form HTML definition lists. + */ +MarkdownExtra_Parser.prototype.doDefLists = function(text) { + var self = this; + + var less_than_tab = this.tab_width - 1; + + // Re-usable pattern to match any entire dl list: + var whole_list_re = '(?:' + + '(' + // $1 = whole list + '(' + // $2 + '[ ]{0,' + less_than_tab + '}' + + '((?:[ \\t]*\\S.*\\n)+)' + // $3 = defined term + // [porting note] Original regex from PHP is + // (?>.*\S.*\n), which matches a line with at + // least one non-space character. Change the + // first .* to [ \t]* stops unneccessary + // backtracking hence improves performance + '\\n?' + + '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition + ')' + + '([\\s\\S]+?)' + + '(' + // $4 + '(?=\\0x03)' + // \z + '|' + + '(?=' + // [porting note] Our regex will consume leading + // newline characters so we will leave the newlines + // here for the next definition + '\\n{2,}' + + '(?=\\S)' + + '(?!' + // Negative lookahead for another term + '[ ]{0,' + less_than_tab + '}' + + '(?:\\S.*\\n)+?' + // defined term + '\\n?' + + '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition + ')' + + '(?!' + // Negative lookahead for another definition + '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition + ')' + + ')' + + ')' + + ')' + + ')'; // mx + + text = this.__wrapSTXETX__(text); + text = text.replace(new RegExp( + '(\\x02\\n?|\\n\\n)' + + whole_list_re, 'mg' + ), function(match, pre, list) { + //console.log(match); + // Re-usable patterns to match list item bullets and number markers: + // [portiong note] changed to list = $2 in order to reserve previously \n\n. + + // Turn double returns into triple returns, so that we can make a + // paragraph for the last item in a list, if necessary: + var result = self._php_trim(self.processDefListItems(list)); + result = "
    \n" + result + "\n
    "; + return pre + self.hashBlock(result) + "\n\n"; + }); + text = this.__unwrapSTXETX__(text); + + return text; +}; + +/** + * Process the contents of a single definition list, splitting it + * into individual term and definition list items. + */ +MarkdownExtra_Parser.prototype.processDefListItems = function(list_str) { + var self = this; + + var less_than_tab = this.tab_width - 1; + + list_str = this.__wrapSTXETX__(list_str); + + // trim trailing blank lines: + list_str = list_str.replace(/\n{2,}(?=\\x03)/, "\n"); + + // Process definition terms. + list_str = list_str.replace(new RegExp( + '(\\x02\\n?|\\n\\n+)' + // leading line + '(' + // definition terms = $1 + '[ ]{0,' + less_than_tab + '}' + // leading whitespace + '(?![:][ ]|[ ])' + // negative lookahead for a definition + // mark (colon) or more whitespace. + '(?:\\S.*\\n)+?' + // actual term (not whitespace). + ')' + + '(?=\\n?[ ]{0,3}:[ ])' , // lookahead for following line feed + // with a definition mark. + 'mg' + ), function(match, pre, terms_str) { + // [portiong note] changed to list = $2 in order to reserve previously \n\n. + var terms = self._php_trim(terms_str).split("\n"); + var text = ''; + for (var i = 0; i < terms.length; i++) { + var term = terms[i]; + term = self.runSpanGamut(self._php_trim(term)); + text += "\n
    " + term + "
    "; + } + return text + "\n"; + }); + + // Process actual definitions. + list_str = list_str.replace(new RegExp( + '\\n(\\n+)?' + // leading line = $1 + '(' + // marker space = $2 + '[ ]{0,' + less_than_tab + '}' + // whitespace before colon + '[:][ ]+' + // definition mark (colon) + ')' + + '([\\s\\S]+?)' + // definition text = $3 + // [porting note] Maybe no trailing + // newlines in our version, changed the + // following line from \n+ to \n*. + '(?=\\n*' + // stop at next definition mark, + '(?:' + // next term or end of text + '\\n[ ]{0,' + less_than_tab + '}[:][ ]|' + // [porting note] do not match + // colon in the middle of a line + '
    |\\x03' + // \z + ')' + + ')', + 'mg' + ), function(match, leading_line, marker_space, def) { + if (leading_line || def.match(/\n{2,}/)) { + // Replace marker with the appropriate whitespace indentation + def = self._php_str_repeat(' ', marker_space.length) + def; + def = self.runBlockGamut(self.outdent(def + "\n\n")); + def = "\n" + def + "\n"; + } + else { + def = self._php_rtrim(def); + def = self.runSpanGamut(self.outdent(def)); + } + + return "\n
    " + def + "
    \n"; + }); + + list_str = this.__unwrapSTXETX__(list_str); + + return list_str; +}; + +/** + * Adding the fenced code block syntax to regular Markdown: + * + * ~~~ + * Code block + * ~~~ + */ +MarkdownExtra_Parser.prototype.doFencedCodeBlocks = function(text) { + var self = this; + + var less_than_tab = this.tab_width; + + text = this.__wrapSTXETX__(text); + text = text.replace(new RegExp( + '(?:\\n|\\x02)' + + // 1: Opening marker + '(' + + '~{3,}' + // Marker: three tilde or more. + ')' + + '[ ]*\\n' + // Whitespace and newline following marker. + // 2: Content + '(' + + '(?:' + + '(?!\\1[ ]*\\n)' + // Not a closing marker. + '.*\\n+' + + ')+' + + ')' + + // Closing marker. + '\\1[ ]*\\n', + "mg" + ), function(match, m1, codeblock) { + codeblock = self._php_htmlspecialchars_ENT_NOQUOTES(codeblock); + codeblock = codeblock.replace(/^\n+/, function(match) { + return self._php_str_repeat("
    "; + return "\n\n" + self.hashBlock(codeblock) + "\n\n"; + }); + text = this.__unwrapSTXETX__(text); + + return text; +}; + +/** + * Params: + * $text - string to process with html

    tags + */ +MarkdownExtra_Parser.prototype.formParagraphs = function(text) { + + // Strip leading and trailing lines: + text = this.__wrapSTXETX__(text); + text = text.replace(/(?:\x02)\n+|\n+(?:\x03)/g, ""); + text = this.__unwrapSTXETX__(text); + + var grafs = text.split(/\n{2,}/m); + //preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); + + // + // Wrap

    tags and unhashify HTML blocks + // + for(var i = 0; i < grafs.length; i++) { + var value = grafs[i]; + if(value == "") { + // [porting note] + // This case is replacement for PREG_SPLIT_NO_EMPTY. + continue; + } + value = this._php_trim(this.runSpanGamut(value)); + + // Check if this should be enclosed in a paragraph. + // Clean tag hashes & block tag hashes are left alone. + var is_p = !value.match(/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/); + + if (is_p) { + value = "

    " + value + "

    "; + } + grafs[i] = value; + } + + // Join grafs in one text, then unhash HTML tags. + text = grafs.join("\n\n"); + + // Finish by removing any tag hashes still present in $text. + text = this.unhash(text); + + return text; +}; + +// ### Footnotes + +/** + * Strips link definitions from text, stores the URLs and titles in + * hash references. + */ +MarkdownExtra_Parser.prototype.stripFootnotes = function(text) { + var self = this; + + var less_than_tab = this.tab_width - 1; + + // Link defs are in the form: [^id]: url "optional title" + text = text.replace(new RegExp( + '^[ ]{0,' + less_than_tab + '}\\[\\^(.+?)\\][ ]?:' + // note_id = $1 + '[ ]*' + + '\\n?' + // maybe *one* newline + '(' + // text = $2 (no blank lines allowed) + '(?:' + + '.+' + // actual text + '|' + + '\\n' + // newlines but + '(?!\\[\\^.+?\\]:\\s)' + // negative lookahead for footnote marker. + '(?!\\n+[ ]{0,3}\\S)' + // ensure line is not blank and followed + // by non-indented content + ')*' + + ')', + "mg" + ), function(match, m1, m2) { + var note_id = self.fn_id_prefix + m1; + self.footnotes[note_id] = self.outdent(m2); + return ''; //# String that will replace the block + }); + return text; +}; + +/** + * Replace footnote references in $text [^id] with a special text-token + * which will be replaced by the actual footnote marker in appendFootnotes. + */ +MarkdownExtra_Parser.prototype.doFootnotes = function(text) { + if (!this.in_anchor) { + text = text.replace(/\[\^(.+?)\]/g, "F\x1Afn:$1\x1A:"); + } + return text; +}; + +/** + * Append footnote list to text. + */ +MarkdownExtra_Parser.prototype.appendFootnotes = function(text) { + var self = this; + + var _appendFootnotes_callback = function(match, m1) { + var node_id = self.fn_id_prefix + m1; + + // Create footnote marker only if it has a corresponding footnote *and* + // the footnote hasn't been used by another marker. + if (node_id in self.footnotes) { + // Transfert footnote content to the ordered list. + self.footnotes_ordered.push([node_id, self.footnotes[node_id]]); + delete self.footnotes[node_id]; + + var num = self.footnote_counter++; + var attr = " rel=\"footnote\""; + if (self.fn_link_class != "") { + var classname = self.fn_link_class; + classname = self.encodeAttribute(classname); + attr += " class=\"" + classname + "\""; + } + if (self.fn_link_title != "") { + var title = self.fn_link_title; + title = self.encodeAttribute(title); + attr += " title=\"" + title +"\""; + } + + attr = attr.replace(/%%/g, num); + node_id = self.encodeAttribute(node_id); + + return "" + + "" + num + "" + + ""; + } + + return "[^" + m1 + "]"; + }; + + text = text.replace(/F\x1Afn:(.*?)\x1A:/g, _appendFootnotes_callback); + + if (this.footnotes_ordered.length > 0) { + text += "\n\n"; + text += "
    \n"; + text += " 0) { + var head = this.footnotes_ordered.shift(); + var note_id = head[0]; + var footnote = head[1]; + + footnote += "\n"; // Need to append newline before parsing. + footnote = this.runBlockGamut(footnote + "\n"); + footnote = footnote.replace(/F\x1Afn:(.*?)\x1A:/g, _appendFootnotes_callback); + + attr = attr.replace(/%%/g, ++num); + note_id = this.encodeAttribute(note_id); + + // Add backlink to last paragraph; create new paragraph if needed. + var backlink = ""; + if (footnote.match(/<\/p>$/)) { + footnote = footnote.substr(0, footnote.length - 4) + " " + backlink + "

    "; + } else { + footnote += "\n\n

    " + backlink + "

    "; + } + + text += "
  • \n"; + text += footnote + "\n"; + text += "
  • \n\n"; + } + + text += "\n"; + text += "
    "; + } + return text; +}; + +//### Abbreviations ### + +/** + * Strips abbreviations from text, stores titles in hash references. + */ +MarkdownExtra_Parser.prototype.stripAbbreviations = function(text) { + var self = this; + + var less_than_tab = this.tab_width - 1; + + // Link defs are in the form: [id]*: url "optional title" + text = text.replace(new RegExp( + '^[ ]{0,' + less_than_tab + '}\\*\\[(.+?)\\][ ]?:' + // abbr_id = $1 + '(.*)', // text = $2 (no blank lines allowed) + "m" + ), function(match, abbr_word, abbr_desc) { + if (self.abbr_word_re != '') { + self.abbr_word_re += '|'; + } + self.abbr_word_re += self._php_preg_quote(abbr_word); + self.abbr_desciptions[abbr_word] = self._php_trim(abbr_desc); + return ''; // String that will replace the block + }); + return text; +}; + +/** + * Find defined abbreviations in text and wrap them in elements. + */ +MarkdownExtra_Parser.prototype.doAbbreviations = function(text) { + var self = this; + + if (this.abbr_word_re) { + // cannot use the /x modifier because abbr_word_re may + // contain significant spaces: + text = text.replace(new RegExp( + '(^|[^\\w\\x1A])' + + '(' + this.abbr_word_re + ')' + + '(?![\\w\\x1A])' + ), function(match, prev, abbr) { + if (abbr in self.abbr_desciptions) { + var desc = self.abbr_desciptions[abbr]; + if (!desc || desc == "") { + return self.hashPart("" + abbr + ""); + } else { + desc = self.encodeAttribute(desc); + return self.hashPart("" + abbr + ""); + } + } else { + return match; + } + }); + } + return text; +}; + + +/** + * Export to Node.js + */ +this.Markdown = Markdown; +this.Markdown_Parser = Markdown_Parser; +this.MarkdownExtra_Parser = MarkdownExtra_Parser; + + +/*! + * jsUri + * https://github.com/derek-watson/jsUri + * + * Copyright 2012, Derek Watson + * Released under the MIT license. + * + * Includes parseUri regular expressions + * http://blog.stevenlevithan.com/archives/parseuri + * Copyright 2007, Steven Levithan + * Released under the MIT license. + * + */ + +(function(global) { + + /** + * Define forEach for older js environments + * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility + */ + if (!Array.prototype.forEach) { + Array.prototype.forEach = function(fn, scope) { + for (var i = 0, len = this.length; i < len; ++i) { + fn.call(scope || this, this[i], i, this); + } + }; + } + + /** + * unescape a query param value + * @param {string} s encoded value + * @return {string} decoded value + */ + function decode(s) { + s = decodeURIComponent(s); + s = s.replace('+', ' '); + return s; + } + + /** + * Breaks a uri string down into its individual parts + * @param {string} str uri + * @return {object} parts + */ + function parseUri(str) { + var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/, + parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"], + m = parser.exec(str || ''), + parts = {}; + + parserKeys.forEach(function(key, i) { + parts[key] = m[i] || ''; + }); + return parts; + } + + /** + * Breaks a query string down into an array of key/value pairs + * @param {string} str query + * @return {array} array of arrays (key/value pairs) + */ + function parseQuery(str) { + var i, ps, p, kvp, k, v, + pairs = []; + + if (typeof(str) === 'undefined' || str === null || str === '') { + return pairs; + } + + if (str.indexOf('?') === 0) { + str = str.substring(1); + } + + ps = str.toString().split(/[&;]/); + + for (i = 0; i < ps.length; i++) { + p = ps[i]; + kvp = p.split('='); + k = kvp[0]; + v = p.indexOf('=') === -1 ? null : (kvp[1] === null ? '' : kvp[1]); + pairs.push([k, v]); + } + return pairs; + } + + /** + * Creates a new Uri object + * @constructor + * @param {string} str + */ + function Uri(str) { + this.uriParts = parseUri(str); + this.queryPairs = parseQuery(this.uriParts.query); + this.hasAuthorityPrefixUserPref = null; + } + + /** + * Define getter/setter methods + */ + ['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) { + Uri.prototype[key] = function(val) { + if (typeof val !== 'undefined') { + this.uriParts[key] = val; + } + return this.uriParts[key]; + }; + }); + + /** + * if there is no protocol, the leading // can be enabled or disabled + * @param {Boolean} val + * @return {Boolean} + */ + Uri.prototype.hasAuthorityPrefix = function(val) { + if (typeof val !== 'undefined') { + this.hasAuthorityPrefixUserPref = val; + } + + if (this.hasAuthorityPrefixUserPref === null) { + return (this.uriParts.source.indexOf('//') !== -1); + } else { + return this.hasAuthorityPrefixUserPref; + } + }; + + /** + * Serializes the internal state of the query pairs + * @param {string} [val] set a new query string + * @return {string} query string + */ + Uri.prototype.query = function(val) { + var s = '', + i, param; + + if (typeof val !== 'undefined') { + this.queryPairs = parseQuery(val); + } + + for (i = 0; i < this.queryPairs.length; i++) { + param = this.queryPairs[i]; + if (s.length > 0) { + s += '&'; + } + if (param[1] === null) { + s += param[0]; + } else { + s += param.join('='); + } + } + return s.length > 0 ? '?' + s : s; + }; + + /** + * returns the first query param value found for the key + * @param {string} key query key + * @return {string} first value found for key + */ + Uri.prototype.getQueryParamValue = function (key) { + var param, i; + for (i = 0; i < this.queryPairs.length; i++) { + param = this.queryPairs[i]; + if (decode(key) === decode(param[0])) { + return param[1]; + } + } + }; + + /** + * returns an array of query param values for the key + * @param {string} key query key + * @return {array} array of values + */ + Uri.prototype.getQueryParamValues = function (key) { + var arr = [], + i, param; + for (i = 0; i < this.queryPairs.length; i++) { + param = this.queryPairs[i]; + if (decode(key) === decode(param[0])) { + arr.push(param[1]); + } + } + return arr; + }; + + /** + * removes query parameters + * @param {string} key remove values for key + * @param {val} [val] remove a specific value, otherwise removes all + * @return {Uri} returns self for fluent chaining + */ + Uri.prototype.deleteQueryParam = function (key, val) { + var arr = [], + i, param, keyMatchesFilter, valMatchesFilter; + + for (i = 0; i < this.queryPairs.length; i++) { + + param = this.queryPairs[i]; + keyMatchesFilter = decode(param[0]) === decode(key); + valMatchesFilter = decode(param[1]) === decode(val); + + if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && !keyMatchesFilter && !valMatchesFilter)) { + arr.push(param); + } + } + + this.queryPairs = arr; + + return this; + }; + + /** + * adds a query parameter + * @param {string} key add values for key + * @param {string} val value to add + * @param {integer} [index] specific index to add the value at + * @return {Uri} returns self for fluent chaining + */ + Uri.prototype.addQueryParam = function (key, val, index) { + if (arguments.length === 3 && index !== -1) { + index = Math.min(index, this.queryPairs.length); + this.queryPairs.splice(index, 0, [key, val]); + } else if (arguments.length > 0) { + this.queryPairs.push([key, val]); + } + return this; + }; + + /** + * replaces query param values + * @param {string} key key to replace value for + * @param {string} newVal new value + * @param {string} [oldVal] replace only one specific value (otherwise replaces all) + * @return {Uri} returns self for fluent chaining + */ + Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) { + + var index = -1, + i, param; + + if (arguments.length === 3) { + for (i = 0; i < this.queryPairs.length; i++) { + param = this.queryPairs[i]; + if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) { + index = i; + break; + } + } + this.deleteQueryParam(key, oldVal).addQueryParam(key, newVal, index); + } else { + for (i = 0; i < this.queryPairs.length; i++) { + param = this.queryPairs[i]; + if (decode(param[0]) === decode(key)) { + index = i; + break; + } + } + this.deleteQueryParam(key); + this.addQueryParam(key, newVal, index); + } + return this; + }; + + /** + * Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc) + */ + ['protocol', 'hasAuthorityPrefix', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) { + var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1); + Uri.prototype[method] = function(val) { + this[key](val); + return this; + }; + }); + + /** + * Scheme name, colon and doubleslash, as required + * @return {string} http:// or possibly just // + */ + Uri.prototype.scheme = function() { + + var s = ''; + + if (this.protocol()) { + s += this.protocol(); + if (this.protocol().indexOf(':') !== this.protocol().length - 1) { + s += ':'; + } + s += '//'; + } else { + if (this.hasAuthorityPrefix() && this.host()) { + s += '//'; + } + } + + return s; + }; + + /** + * Same as Mozilla nsIURI.prePath + * @return {string} scheme://user:password@host:port + * @see https://developer.mozilla.org/en/nsIURI + */ + Uri.prototype.origin = function() { + + var s = this.scheme(); + + if (this.userInfo() && this.host()) { + s += this.userInfo(); + if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) { + s += '@'; + } + } + + if (this.host()) { + s += this.host(); + if (this.port()) { + s += ':' + this.port(); + } + } + + return s; + }; + + /** + * Serializes the internal state of the Uri object + * @return {string} + */ + Uri.prototype.toString = function() { + + var s = this.origin(); + + if (this.path()) { + s += this.path(); + } else { + if (this.host() && (this.query().toString() || this.anchor())) { + s += '/'; + } + } + if (this.query().toString()) { + if (this.query().toString().indexOf('?') !== 0) { + s += '?'; + } + s += this.query().toString(); + } + + if (this.anchor()) { + if (this.anchor().indexOf('#') !== 0) { + s += '#'; + } + s += this.anchor(); + } + + return s; + }; + + /** + * Clone a Uri object + * @return {Uri} duplicate copy of the Uri + */ + Uri.prototype.clone = function() { + return new Uri(this.toString()); + }; + + /** + * export via CommonJS, otherwise leak a global + */ + if (typeof module === 'undefined') { + global.Uri = Uri; + } else { + module.exports = Uri; + } +}(this)); + +/*jslint browser: true, devel: true, todo: true, unparam: true, camelcase: false */ +/*global define, btoa */ +/* + Copyright 2014 Red Hat Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +(function (root, factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + define('strata', ['jquery', 'jsUri'], factory); + } else { + root.strata = factory(root.$, root.Uri); + } +}(this, function ($, Uri) { + 'use strict'; + + var strata = {}, + //Since we can't set the UserAgent + redhatClient = 'redhat_client', + redhatClientID, + //Internal copy of user, might be useful for logging, only valid for cookie auth + authedUser = {}, + basicAuthToken = '', + portalHostname, + strataHostname, + baseAjaxParams = {}, + checkCredentials, + fetchUser, + fetchSolution, + fetchArticle, + searchArticles, + fetchCase, + fetchCaseComments, + createComment, + fetchCases, + fetchCasesCSV, + addNotifiedUser, + removeNotifiedUser, + updateCase, + filterCases, + createAttachment, + deleteAttachment, + updateOwner, + listCaseAttachments, + getSymptomsFromText, + listGroups, + createGroup, + deleteGroup, + updateGroup, + updateGroupUsers, + fetchGroup, + listProducts, + fetchProduct, + fetchProductVersions, + caseTypes, + caseSeverities, + caseStatus, + fetchSystemProfiles, + fetchSystemProfile, + createSystemProfile, + fetchAccounts, + fetchAccount, + fetchURI, + fetchEntitlements, + fetchSfdcHealth, + fetchAccountUsers, + fetchUserChatSession; + + strata.version = '1.1.16'; + redhatClientID = 'stratajs-' + strata.version; + + if (window.portal && window.portal.host) { + //if this is a chromed app this will work otherwise we default to prod + portalHostname = new Uri(window.portal.host).host(); + + } else { + portalHostname = 'access.redhat.com'; + } + + if(localStorage && localStorage.getItem('portalHostname')) { + portalHostname = localStorage.getItem('portalHostname'); + } + + strataHostname = new Uri('https://api.' + portalHostname); + strataHostname.addQueryParam(redhatClient, redhatClientID); + + strata.setRedhatClientID = function (id) { + redhatClientID = id; + strataHostname.replaceQueryParam(redhatClient, redhatClientID); + }; + + strata.addAccountNumber = function (id) { + strataHostname.deleteQueryParam('account_number', id); + strataHostname.addQueryParam('account_number', id); + }; + + strata.setStrataHostname = function (hostname) { + portalHostname = hostname; + strataHostname = new Uri(portalHostname); + strataHostname.addQueryParam(redhatClient, redhatClientID); + }; + + strata.setPortalHostname = function (hostname) { + portalHostname = hostname; + strataHostname = new Uri('https://api.' + portalHostname); + strataHostname.addQueryParam(redhatClient, redhatClientID); + }; + + strata.getAuthInfo = function () { + return authedUser; + }; + + //Store Base64 Encoded Auth Token + basicAuthToken = localStorage.getItem('rhAuthToken'); + authedUser.login = localStorage.getItem('rhUserName'); + + strata.setCredentials = function (username, password) { + if(isASCII(username + password)){ + basicAuthToken = btoa(username + ':' + password); + localStorage.setItem('rhAuthToken', basicAuthToken); + localStorage.setItem('rhUserName', username); + authedUser.login = username; + return true; + } else{ + return false; + } + }; + + function isASCII(str) { + return /^[\x00-\x7F]*$/.test(str); + } + + strata.clearCredentials = function () { + strata.clearBasicAuth(); + strata.clearCookieAuth(); + authedUser = {}; + }; + + strata.clearBasicAuth = function () { + localStorage.setItem('rhAuthToken', ''); + localStorage.setItem('rhUserName', ''); + basicAuthToken = ''; + }; + + strata.clearCookieAuth = function () { + var logoutFrame = document.getElementById('rhLogoutFrame'); + if (!logoutFrame) { + // First time logging out. + $('body').append(''); + } else { + // Will force the iframe to reload + logoutFrame.src = logoutFrame.src; + } + }; + + + //Private vars related to the connection + baseAjaxParams = { + accepts: { + jsonp: 'application/json, text/json' + }, + crossDomain: true, + type: 'GET', + method: 'GET', + beforeSend: function (xhr) { + //Include Basic Auth Credentials if available, will try SSO Cookie otherwise + xhr.setRequestHeader('X-Omit', 'WWW-Authenticate'); + if (basicAuthToken !== null) { + if (basicAuthToken.length !== 0) { + xhr.setRequestHeader('Authorization', 'Basic ' + basicAuthToken); + } + } + }, + headers: { + Accept: 'application/json, text/json' + }, + xhrFields: { + withCredentials: true + }, + data: {}, + dataType: 'json' + }; + + //Helper Functions + //Convert Java Calendar class to something we can use + //TODO: Make this recursive + function convertDates(entry) { + //Iterate over the objects for *_date + var key; + for (key in entry) { + if (entry.hasOwnProperty(key)) { + if (/[\s\S]*_date/.test(key)) { + //Skip indexed_date, it's not a real "Date" + if (key !== 'indexed_date') { + entry[key] = new Date(entry[key]); + } + } + } + } + } + + //Remove empty fields from object + //TODO: Make this recursive, so it could remove nested objs + function removeEmpty(entry) { + var key; + for (key in entry) { + if (entry.hasOwnProperty(key)) { + //Removes anything with length 0 + if (entry[key].length === 0) { + delete entry[key]; + } + } + } + } + + //Function to test whether we've been passed a URL or just a string/ID + function isUrl(path) { + return path.search(/^http/) >= 0; + } + + //Helper classes + //Class to describe the required Case fields + strata.Case = function () { + return { + summary: '', + description: '', + product: '', + version: '' + }; + }; + + //Class to describe required Case Comment fields + strata.CaseComment = function () { + return { + text: '', + public: true + }; + }; + + //Class to help create System Profiles + strata.SystemProfile = function () { + return { + account_number: '', + case_number: '', + deprecated: false, + //Append SystemProfileCategoryDetails Objects here + system_profile_category: [ + ] + }; + }; + + //Helper to deal with SystemProfileCategories + strata.SystemProfileCategoryDetails = function () { + return { + system_profile_category_name: '', + system_profile_category_summary: '', + //Append key, value pairs here + system_profile_category_details: [] + }; + }; + + //Example of fields that could be supplied to case filter + //Fields with length 0 will be stripped out of this obj prior to being sent + strata.CaseFilter = function () { + var groupNumbers = []; + return { + //The _date objects should be real Date objs + start_date: '', + end_date: '', + account_number: '', + include_closed: false, + include_private: false, + keyword: '', + group_numbers: groupNumbers, + addGroupNumber: function (num) { + groupNumbers.push({group_number: num}); + }, + start: 0, + count: 50, + only_ungrouped: false, + owner_sso_name: '', + product: '', + severity: '', + sort_field: '', + sort_order: '', + status: '', + type: '', + created_by_sso_name: '', + resource_type: '', + id: '', + uri: '', + view_uri: '' + }; + }; + + //PUBLIC METHODS + //User provides a loginSuccess callback to handle the response + strata.checkLogin = function (loginHandler) { + if (!$.isFunction(loginHandler)) { throw 'loginHandler callback must be supplied'; } + + checkCredentials = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/users/current'), + headers: { + accept: 'application/vnd.redhat.user+json' + }, + success: function (response) { + response.loggedInUser = response.first_name + ' ' + response.last_name; + loginHandler(true, response); + }, + error: function () { + strata.clearBasicAuth(); + strata.clearCookieAuth(); + loginHandler(false); + } + }); + $.ajax(checkCredentials); + }; + + //Sends data to the strata diagnostic toolchain + strata.problems = function (data, onSuccess, onFailure, limit) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { data = ''; } + if (limit === undefined) { limit = 50; } + + var getSolutionsFromText = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/problems') + .addQueryParam('limit', limit), + data: data, + type: 'POST', + method: 'POST', + contentType: 'text/plain', + success: function (response) { + if (response.source_or_link_or_problem[2] !== undefined && response.source_or_link_or_problem[2].source_or_link !== undefined) { + //Gets the array of solutions + var suggestedSolutions = response.source_or_link_or_problem[2].source_or_link; + onSuccess(suggestedSolutions); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(getSolutionsFromText); + }; + + strata.recommendations = function (data, onSuccess, onFailure, limit, highlight, highlightTags) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { data = ''; } + if (limit === undefined) { limit = 50; } + if (highlight === undefined) { highlight = false; } + + var tmpUrl = strataHostname.clone().setPath('/rs/problems') + .addQueryParam('limit', limit).addQueryParam('highlight', highlight); + if(highlightTags !== undefined){ + tmpUrl.addQueryParam('highlightTags', highlightTags); + } + + var getRecommendationsFromText = $.extend({}, baseAjaxParams, { + url: tmpUrl, + data: JSON.stringify(data), + type: 'POST', + method: 'POST', + contentType: 'application/json', + headers: { + Accept: 'application/vnd.redhat.json.suggestions' + }, + success: function (response) { + if(response.recommendation !== undefined){ + var suggestedRecommendations = response.recommendation; + onSuccess(suggestedRecommendations); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(getRecommendationsFromText); + }; + + //TODO rip out when strata fixes endpoint + strata.recommendationsXmlHack = function (data, onSuccess, onFailure, limit, highlight, highlightTags) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { data = ''; } + if (limit === undefined) { limit = 50; } + if (highlight === undefined) { highlight = false; } + + var tmpUrl = strataHostname.clone().setPath('/rs/problems') + .addQueryParam('limit', limit).addQueryParam('highlight', highlight); + if(highlightTags !== undefined){ + tmpUrl.addQueryParam('highlightTags', highlightTags); + } + + var xmlString = ""; + if(data.product !== undefined){ + xmlString = xmlString.concat("" + data.product + ""); + }if(data.version !== undefined){ + xmlString = xmlString.concat("" + data.version + ""); + }if(data.summary !== undefined){ + xmlString = xmlString.concat("" + data.summary + ""); + }if(data.description !== undefined){ + xmlString = xmlString.concat("" + data.description + ""); + } + xmlString = xmlString.concat(""); + + var getRecommendationsFromText = $.extend({}, baseAjaxParams, { + url: tmpUrl, + data: xmlString, + type: 'POST', + method: 'POST', + contentType: 'application/xml', + headers: { + Accept: 'application/vnd.redhat.json.suggestions' + }, + success: function (response) { + if(response.recommendation !== undefined){ + var suggestedRecommendations = response.recommendation; + onSuccess(suggestedRecommendations); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(getRecommendationsFromText); + }; + + //Base for users + strata.users = {}; + strata.users.chatSession = {}; + strata.users.get = function (onSuccess, onFailure, userId) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (userId === undefined) { + userId = 'current'; + } + + fetchUser = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/users/' + userId), + headers: { + accept: 'application/vnd.redhat.user+json' + }, + success: function (response) { + onSuccess(response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchUser); + }; + + strata.users.chatSession.get = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + + fetchUserChatSession = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/users/current/chatSession'), + type: 'POST', + method: 'POST', + headers: { + accept: 'application/json' + }, + success: function (response) { + onSuccess(response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchUserChatSession); + }; + + //Base for solutions + strata.solutions = {}; + + //Retrieve a solution + strata.solutions.get = function (solution, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (solution === undefined) { onFailure('solution must be defined'); } + + var url; + if (isUrl(solution)) { + url = new Uri(solution); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/solutions/' + solution); + } + + fetchSolution = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + convertDates(response); + onSuccess(response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchSolution); + }; + + //Search for solutions + strata.solutions.search = function (keyword, onSuccess, onFailure, limit, chain) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (keyword === undefined) { keyword = ''; } + if (limit === undefined) {limit = 50; } + if (chain === undefined) {chain = false; } + + var searchSolutions = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/solutions') + .addQueryParam('keyword', encodeURIComponent(keyword)) + .addQueryParam('limit', limit), + success: function (response) { + if (chain && response.solution !== undefined) { + response.solution.forEach(function (entry) { + strata.solutions.get(entry.uri, onSuccess, onFailure); + }); + } else if (response.solution !== undefined) { + response.solution.forEach(convertDates); + onSuccess(response.solution); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(searchSolutions); + }; + + //Base for articles + strata.articles = {}; + + //Retrieve an article + strata.articles.get = function (article, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (article === undefined) { onFailure('article must be defined'); } + + var url; + if (isUrl(article)) { + url = new Uri(article); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/articles/' + article); + } + + fetchArticle = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + convertDates(response); + if (response !== undefined && response.body !== undefined && response.body.html !== undefined) { + onSuccess(response); + } else { + onFailure('Failed to retrieve Article ' + article); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchArticle); + }; + + //Search articles + strata.articles.search = function (keyword, onSuccess, onFailure, limit, chain) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (keyword === undefined) { keyword = ''; } + if (limit === undefined) {limit = 50; } + if (chain === undefined) {chain = false; } + + var url = strataHostname.clone().setPath('/rs/articles'); + url.addQueryParam('keyword', encodeURIComponent(keyword)); + url.addQueryParam('limit', limit); + + searchArticles = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (chain && response.article !== undefined) { + response.article.forEach(function (entry) { + strata.articles.get(entry.uri, onSuccess, onFailure); + }); + } else if (response.article !== undefined) { + response.article.forEach(convertDates); + onSuccess(response.article); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(searchArticles); + }; + + //Base for cases + strata.cases = {}; + strata.cases.attachments = {}; + strata.cases.comments = {}; + strata.cases.notified_users = {}; + strata.cases.owner = {}; + + //Retrieve a case + strata.cases.get = function (casenum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum); + } + + fetchCase = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response) { + convertDates(response); + onSuccess(response); + } else { + onFailure('Failed to retrieve Case: ' + casenum); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchCase); + }; + + //update case comment + strata.cases.comments.update = function (casenum, comment, commentId, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/comments'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments/' + commentId); + } + + fetchCaseComments = $.extend({}, baseAjaxParams, { + url: url, + type: 'PUT', + method: 'PUT', + contentType: 'application/json', + data: JSON.stringify(comment), + statusCode: { + 200: function() { + onSuccess(); + }, + 400: onFailure + } + }); + $.ajax(fetchCaseComments); + }; + + //Retrieve case comments + strata.cases.comments.get = function (casenum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/comments'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments'); + } + + fetchCaseComments = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.comment !== undefined) { + response.comment.forEach(convertDates); + onSuccess(response.comment); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchCaseComments); + }; + + //TODO: Support DRAFT comments? Only useful for internal + //Create a new case comment + strata.cases.comments.post = function (casenum, casecomment, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + if (casecomment === undefined) { onFailure('casecomment must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/comments'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments'); + } + + createComment = $.extend({}, baseAjaxParams, { + url: url, + data: JSON.stringify(casecomment), + type: 'POST', + method: 'POST', + contentType: 'application/json', + success: function (response, status, xhr) { + var commentnum; + if (response.id !== undefined){ + //For some reason the comment object is being returned in IE8 + commentnum = response.id; + } else if (response.location !== undefined && response.location[0] !== undefined){ + commentnum = response.location[0]; + commentnum = commentnum.split('/').pop(); + } else{ + //Created case comment data is in the XHR + commentnum = xhr.getResponseHeader('Location'); + commentnum = commentnum.split('/').pop(); + } + onSuccess(commentnum); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(createComment); + }; + + //List cases for the given user + strata.cases.list = function (onSuccess, onFailure, closed) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (closed === undefined) { closed = 'false'; } + + if (!closed) { + closed = 'false'; + } + + var url = strataHostname.clone().setPath('/rs/cases'); + url.addQueryParam('includeClosed', closed); + + fetchCases = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response['case'] !== undefined) { + response['case'].forEach(convertDates); + onSuccess(response['case']); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchCases); + }; + + //Create a new case comment + strata.cases.notified_users.add = function (casenum, ssoUserName, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + if (ssoUserName === undefined) { onFailure('ssoUserName must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/notified_users'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/notified_users'); + } + + addNotifiedUser = $.extend({}, baseAjaxParams, { + url: url, + data: '{"user": [{"ssoUsername":"' + ssoUserName + '"}]}', + type: 'POST', + method: 'POST', + contentType: 'application/json', + headers: { + Accept: 'text/plain' + }, + dataType: 'text', + success: onSuccess, + statusCode: { + 201: onSuccess, + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(addNotifiedUser); + }; + + strata.cases.notified_users.remove = function (casenum, ssoUserName, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + if (ssoUserName === undefined) { onFailure('ssoUserName must be defined'); } + + var url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/notified_users/' + ssoUserName); + + removeNotifiedUser = $.extend({}, baseAjaxParams, { + url: url, + type: 'DELETE', + method: 'DELETE', + contentType: 'text/plain', + headers: { + Accept: 'text/plain' + }, + dataType: 'text', + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(removeNotifiedUser); + }; + + //List cases in CSV for the given user, this casues a download to occur + strata.cases.csv = function (onSuccess, onFailure) { + var url = strataHostname.clone().setPath('/rs/cases'); + + fetchCasesCSV = $.extend({}, baseAjaxParams, { + headers: { + Accept: 'text/csv' + }, + url: url, + contentType: 'text/csv', + dataType: 'text', + success: function(data) { + var uri = 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); + window.location = uri; + onSuccess(); + }, + error: function (xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + } + }); + $.ajax(fetchCasesCSV); + }; + + //Filter cases + strata.cases.filter = function (casefilter, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casefilter === undefined) { onFailure('casefilter must be defined'); } + + var url = strataHostname.clone().setPath('/rs/cases/filter'); + + //Remove any 0 length fields + removeEmpty(casefilter); + + filterCases = $.extend({}, baseAjaxParams, { + url: url, + data: JSON.stringify(casefilter), + contentType: 'application/json', + type: 'POST', + method: 'POST', + success: function (response) { + if (response['case'] !== undefined) { + response['case'].forEach(convertDates); + onSuccess(response); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(filterCases); + }; + + //Create a new case + strata.cases.post = function (casedata, onSuccess, onFailure) { + //Default parameter value + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casedata === undefined) { onFailure('casedata must be defined'); } + + var url = strataHostname.clone().setPath('/rs/cases'); + + createAttachment = $.extend({}, baseAjaxParams, { + url: url, + data: JSON.stringify(casedata), + type: 'POST', + method: 'POST', + contentType: 'application/json', + success: function (response, status, xhr) { + //Created case data is in the XHR + var casenum; + if (response.location !== undefined && response.location[0] !== undefined){ + casenum = response.location[0]; + casenum = casenum.split('/').pop(); + } else{ + casenum = xhr.getResponseHeader('Location'); + casenum = casenum.split('/').pop(); + } + onSuccess(casenum); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(createAttachment); + }; + + //Update a case + strata.cases.put = function (casenum, casedata, onSuccess, onFailure) { + //Default parameter value + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + if (casedata === undefined) { onFailure('casedata must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum); + } + + var successCallback = function() { + onSuccess(); + }; + + updateCase = $.extend({}, baseAjaxParams, { + url: url, + data: JSON.stringify(casedata), + type: 'PUT', + method: 'PUT', + contentType: 'application/json', + statusCode: { + 200: successCallback, + 202: successCallback, + 400: onFailure + }, + success: function (response) { + onSuccess(response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(updateCase); + }; + + + //List case attachments + strata.cases.attachments.list = function (casenum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/attachments'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/attachments'); + } + + listCaseAttachments = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.attachment === undefined) { + onSuccess([]); + } else { + response.attachment.forEach(convertDates); + onSuccess(response.attachment); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(listCaseAttachments); + }; + + //POST an attachment + //data MUST be MULTIPART/FORM-DATA + strata.cases.attachments.post = function (data, casenum, onSuccess, onFailure) { + //Default parameter value + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { onFailure('data must be defined'); } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum + '/attachments'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/attachments'); + } + + createAttachment = $.extend({}, baseAjaxParams, { + url: url, + data: data, + type: 'POST', + method: 'POST', + processData: false, + contentType: false, + cache: false, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(createAttachment); + }; + + strata.cases.attachments.remove = function (attachmentId, casenum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (attachmentId === undefined) { onFailure('attachmentId must be defined'); } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url = + strataHostname.clone().setPath( + '/rs/cases/' + casenum + '/attachments/' + attachmentId + ); + deleteAttachment = $.extend({}, baseAjaxParams, { + url: url, + type: 'DELETE', + method: 'DELETE', + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(deleteAttachment); + }; + + //Change the case owner + strata.cases.owner.update = function (casenum, ssoUserName, onSuccess, onFailure) { + //Default parameter value + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url = strataHostname.clone().setPath('/rs/internal/cases/' + casenum + '/changeowner').addQueryParam('contactSsoName', ssoUserName.toString()); + + updateOwner = $.extend({}, baseAjaxParams, { + url: url, + type: 'POST', + method: 'POST', + contentType: false, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(updateOwner); + }; + + //Base for symptoms + strata.symptoms = {}; + + //Symptom Extractor + strata.symptoms.extractor = function (data, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { onFailure('data must be defined'); } + + var url = strataHostname.clone().setPath('/rs/symptoms/extractor'); + + getSymptomsFromText = $.extend({}, baseAjaxParams, { + url: url, + data: data, + type: 'POST', + method: 'POST', + contentType: 'text/plain', + success: function (response) { + if (response.extracted_symptom !== undefined) { + onSuccess(response.extracted_symptom); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(getSymptomsFromText); + }; + + //Base for groups + strata.groups = {}; + + //List groups for this user + strata.groups.list = function (onSuccess, onFailure, ssoUserName) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url; + if (ssoUserName === undefined) { + url = strataHostname.clone().setPath('/rs/groups'); + } else { + url = strataHostname.clone().setPath('/rs/groups/contact/' + ssoUserName); + } + + listGroups = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.group !== undefined) { + onSuccess(response.group); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(listGroups); + }; + + //Create a group + strata.groups.create = function (groupName, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (groupName === undefined) { onFailure('groupName must be defined'); } + + var url = strataHostname.clone().setPath('/rs/groups'); + url.addQueryParam(redhatClient, redhatClientID); + + var throwError = function(xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + }; + + createGroup = $.extend({}, baseAjaxParams, { + url: url, + type: 'POST', + method: 'POST', + contentType: 'application/json', + data: '{"name": "' + groupName + '"}', + success: onSuccess, + statusCode: { + 201: function(response) { + var groupNumber; + if(response !== null){ + var locationHeader = response.getResponseHeader('Location'); + groupNumber = + locationHeader.slice(locationHeader.lastIndexOf('/') + 1); + } + onSuccess(groupNumber); + }, + 400: throwError, + 409: throwError, + 500: throwError + } + }); + $.ajax(createGroup); + }; + + //Update a group + strata.groups.update = function (group, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (group === undefined) { onFailure('group must be defined'); } + + var url = strataHostname.clone().setPath('/rs/groups/' + group.number); + + var throwError = function(xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + }; + + updateGroup = $.extend({}, baseAjaxParams, { + url: url, + type: 'PUT', + method: 'PUT', + contentType: 'application/json', + data: group, + success: onSuccess, + statusCode: { + 200: function(response) { + var locationHeader = response.getResponseHeader('Location'); + var groupNumber = + locationHeader.slice(locationHeader.lastIndexOf('/') + 1); + onSuccess(groupNumber); + }, + 400: throwError, + 500: throwError + } + }); + $.ajax(updateGroup); + }; + + //Update a group + strata.groups.createDefault = function (group, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (group === undefined) { onFailure('group must be defined'); } + + var url = strataHostname.clone().setPath('/rs/groups/' + group.number + '/default/'); + + var throwError = function(xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + }; + + updateGroup = $.extend({}, baseAjaxParams, { + url: url, + type: 'POST', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(group), + success: onSuccess, + statusCode: { + 200: function() { + onSuccess(); + }, + 400: throwError, + 500: throwError + } + }); + $.ajax(updateGroup); + }; + + //Delete a group + strata.groups.remove = function (groupnum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (groupnum === undefined) { onFailure('groupnum must be defined'); } + + var url = strataHostname.clone().setPath('/rs/groups/' + groupnum); + url.addQueryParam(redhatClient, redhatClientID); + + var throwError = function(xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + }; + + deleteGroup = $.extend({}, baseAjaxParams, { + url: url, + type: 'DELETE', + method: 'DELETE', + success: onSuccess, + statusCode: { + 200: function() { + onSuccess(); + }, + 400: throwError, + 500: throwError, + 502: throwError + } + }); + $.ajax(deleteGroup); + }; + + //Retrieve a group + strata.groups.get = function (groupnum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (groupnum === undefined) { onFailure('groupnum must be defined'); } + + var url; + if (isUrl(groupnum)) { + url = new Uri(groupnum); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/groups/' + groupnum); + } + + fetchGroup = $.extend({}, baseAjaxParams, { + url: url, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchGroup); + }; + + //Base for groupUsers + strata.groupUsers = {}; + //Update a group + strata.groupUsers.update = function (users, accountId, groupnum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (users === undefined || users === accountId || users === groupnum) { onFailure('users, accountID and groupnum must be defined'); } + + var url = strataHostname.clone().setPath('/rs/account/'+ accountId + '/groups/' + groupnum + '/users'); + + var throwError = function(xhr, response, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, response, status); + }; + + updateGroupUsers = $.extend({}, baseAjaxParams, { + url: url, + type: 'PUT', + method: 'PUT', + contentType: 'application/json', + data: JSON.stringify(strata.utils.fixUsersObject(users)), + success: onSuccess, + statusCode: { + 200: function() { + onSuccess(); + }, + 400: throwError, + 500: throwError, + 502: throwError + } + }); + $.ajax(updateGroupUsers); + }; + + //Base for products + strata.products = {}; + + //List products for this user + strata.products.list = function (onSuccess, onFailure, ssoUserName) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/products/contact/' + ssoUserName); + if (ssoUserName === undefined) { + url = strataHostname.clone().setPath('/rs/products'); + } + + listProducts = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.product !== undefined) { + onSuccess(response.product); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(listProducts); + }; + + //Retrieve a product + strata.products.get = function (code, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (code === undefined) { onFailure('code must be defined'); } + + var url; + if (isUrl(code)) { + url = new Uri(code); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/products/' + code); + } + + fetchProduct = $.extend({}, baseAjaxParams, { + url: url, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchProduct); + }; + + //Retrieve versions for a product + strata.products.versions = function (code, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (code === undefined) { onFailure('code must be defined'); } + + var url; + if (isUrl(code)) { + url = new Uri(code + '/versions'); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/products/' + code + '/versions'); + } + + fetchProductVersions = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.version !== undefined) { + onSuccess(response.version); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchProductVersions); + }; + + //Base for values + strata.values = {}; + strata.values.cases = {}; + + //Retrieve the case types + strata.values.cases.types = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/values/case/types'); + + caseTypes = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.value !== undefined) { + onSuccess(response.value); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(caseTypes); + }; + + //Retrieve the case severities + strata.values.cases.severity = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/values/case/severity'); + + caseSeverities = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.value !== undefined) { + onSuccess(response.value); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(caseSeverities); + }; + + //Retrieve the case statuses + strata.values.cases.status = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/values/case/status'); + + caseStatus = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.value !== undefined) { + onSuccess(response.value); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(caseStatus); + }; + + //Base for System Profiles + strata.systemProfiles = {}; + + //List system profiles + strata.systemProfiles.list = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/system_profiles'); + + fetchSystemProfiles = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.system_profile !== undefined) { + onSuccess(response.system_profile); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchSystemProfiles); + }; + + //Get a specific system_profile, either by hash or casenum + //Case can return an array, hash will return a single result + strata.systemProfiles.get = function (casenum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (casenum === undefined) { onFailure('casenum must be defined'); } + + var url; + if (isUrl(casenum)) { + url = new Uri(casenum); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/system_profiles/' + casenum); + } + + fetchSystemProfile = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if ($.isArray(response.system_profile)) { + onSuccess(response.system_profile); + } else { + onSuccess(response); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchSystemProfile); + }; + //TODO: Create helper class to Handle list + filtering + + //Create a new System Profile + strata.systemProfiles.post = function (systemprofile, onSuccess, onFailure) { + //Default parameter value + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (systemprofile === undefined) { onFailure('systemprofile must be defined'); } + + var url = strataHostname.clone().setPath('/rs/system_profiles'); + + createSystemProfile = $.extend({}, baseAjaxParams, { + url: url, + data: JSON.stringify(systemprofile), + type: 'POST', + method: 'POST', + contentType: 'application/json', + success: function (response, status, xhr) { + //Created case data is in the XHR + var hash = xhr.getResponseHeader('Location'); + hash = hash.split('/').pop(); + onSuccess(hash); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(createSystemProfile); + }; + + strata.accounts = {}; + + //List Accounts for the given user + strata.accounts.list = function (onSuccess, onFailure, closed) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (closed === undefined) { closed = false; } + + var url = strataHostname.clone().setPath('/rs/accounts'); + + fetchAccounts = $.extend({}, baseAjaxParams, { + url: url, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchAccounts); + }; + + //Get an Account + strata.accounts.get = function (accountnum, onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (accountnum === undefined) { onFailure('accountnum must be defined'); } + + var url; + if (isUrl(accountnum)) { + url = new Uri(accountnum); + url.addQueryParam(redhatClient, redhatClientID); + } else { + url = strataHostname.clone().setPath('/rs/accounts/' + accountnum); + } + + fetchAccount = $.extend({}, baseAjaxParams, { + url: url, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchAccount); + }; + + //Get an Accounts Users + strata.accounts.users = function (accountnum, onSuccess, onFailure, group) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (accountnum === undefined) { onFailure('accountnum must be defined'); } + + var url; + if (isUrl(accountnum)) { + url = new Uri(accountnum); + url.addQueryParam(redhatClient, redhatClientID); + } else if (group === undefined) { + url = strataHostname.clone().setPath('/rs/accounts/' + accountnum + '/users'); + } else { + url = strataHostname.clone() + .setPath('/rs/accounts/' + accountnum + '/groups/' + group + '/users'); + } + + fetchAccountUsers = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + if (response.user !== undefined) { + onSuccess(response.user); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchAccountUsers); + }; + + strata.entitlements = {}; + strata.entitlements.get = function (showAll, onSuccess, onFailure, ssoUserName) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url; + if (ssoUserName === undefined) { + url = strataHostname.clone().setPath('/rs/entitlements?showAll=' + showAll.toString()); + } else { + url = strataHostname.clone().setPath('/rs/entitlements/contact/' + ssoUserName + '?showAll=' + showAll.toString()); + } + + fetchEntitlements = $.extend({}, baseAjaxParams, { + url: url, + success: onSuccess, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchEntitlements); + }; + + //Helper function to "diagnose" text, chains problems and solutions calls + //This will call 'onSuccess' for each solution + strata.diagnose = function (data, onSuccess, onFailure, limit) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (data === undefined) { onFailure('data must be defined'); } + if (limit === undefined) { limit = 50; } + + //Call problems, send that list to get solutions to get each one + strata.problems(data, function (response) { + response.forEach(function (entry) { + strata.solutions.get(entry.uri, onSuccess, onFailure); + }); + }, onFailure, limit); + }; + + strata.search = function (keyword, onSuccess, onFailure, limit, chain) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + if (keyword === undefined) { keyword = ''; } + if (limit === undefined) {limit = 50; } + if (chain === undefined) {chain = false; } + + var searchStrata = $.extend({}, baseAjaxParams, { + url: strataHostname.clone().setPath('/rs/search') + .addQueryParam('keyword', encodeURIComponent(keyword)) + .addQueryParam('contentType', 'article,solution') + .addQueryParam('limit', limit), + success: function (response) { + if (chain && response.search_result !== undefined) { + response.search_result.forEach(function (entry) { + strata.utils.getURI(entry.uri, entry.resource_type, onSuccess, onFailure); + }); + } else if (response.search_result !== undefined) { + onSuccess(response.search_result); + } else { + onSuccess([]); + } + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(searchStrata); + }; + + strata.health = {}; + // Get the SFDC health status. + // If this end point returns "SFDC:false" means SFDC is down/backend calls will not work + strata.health.sfdc = function (onSuccess, onFailure) { + if (!$.isFunction(onSuccess)) { throw 'onSuccess callback must be a function'; } + if (!$.isFunction(onFailure)) { throw 'onFailure callback must be a function'; } + + var url = strataHostname.clone().setPath('/rs/health/sfdc'); + + fetchSfdcHealth = $.extend({}, baseAjaxParams, { + url: url, + success: function (response) { + onSuccess(response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchSfdcHealth); + }; + + strata.utils = {}; + + //Get selected text from the browser, this should work on + //Chrome and FF. Have not tested anything else + strata.utils.getSelectedText = function () { + var t = ''; + if (window.getSelection) { + t = window.getSelection(); + } else if (document.getSelection) { + t = document.getSelection(); + } else if (document.selection) { + t = document.selection.createRange().text; + } + return t.toString(); + }; + + strata.utils.getURI = function (uri, resourceType, onSuccess, onFailure) { + fetchURI = $.extend({}, baseAjaxParams, { + url: uri, + success: function (response) { + convertDates(response); + onSuccess(resourceType, response); + }, + error: function (xhr, reponse, status) { + onFailure('Error ' + xhr.status + ' ' + xhr.statusText, xhr, reponse, status); + } + }); + $.ajax(fetchURI); + }; + + strata.utils.fixUsersObject = function (oldUsers) { + var users = {}; + users.user = []; + for(var i = 0; i < oldUsers.length; i++){ + var tempUser = { + ssoUsername : oldUsers[i].sso_username, + firstName : oldUsers[i].first_name, + lastName : oldUsers[i].last_name, + access : oldUsers[i].access, + write : oldUsers[i].write + }; + users.user.push(tempUser); + } + return users; + }; + + return strata; +})); + +/** + * @license AngularJS v1.2.1 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) +var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; + +function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); +} + +function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; +} + +/** + * @ngdoc overview + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * {@installModule resource} + * + *
    + * + * See {@link ngResource.$resource `$resource`} for usage. + */ + +/** + * @ngdoc object + * @name ngResource.$resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * @param {string} url A parametrized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value of that parameter is extracted from + * the data object (useful for non-GET operations). + * + * @param {Object.=} actions Hash with declaration of custom action that should extend the + * default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage_parameters $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, + * `DELETE`, and `JSONP`. + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 + * requests with credentials} for more information. + * - **`responseType`** - `{string}` - see {@link + * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + *
    +        var User = $resource('/user/:userId', {userId:'@id'});
    +        var user = User.get({userId:123}, function() {
    +          user.abc = true;
    +          user.$save();
    +        });
    +     
    + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * Success callback is called with (value, responseHeaders) arguments. Error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + *
    +     // Define CreditCard class
    +     var CreditCard = $resource('/user/:userId/card/:cardId',
    +      {userId:123, cardId:'@id'}, {
    +       charge: {method:'POST', params:{charge:true}}
    +      });
    +
    +     // We can retrieve a collection from the server
    +     var cards = CreditCard.query(function() {
    +       // GET: /user/123/card
    +       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
    +
    +       var card = cards[0];
    +       // each item is an instance of CreditCard
    +       expect(card instanceof CreditCard).toEqual(true);
    +       card.name = "J. Smith";
    +       // non GET methods are mapped onto the instances
    +       card.$save();
    +       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
    +       // server returns: {id:456, number:'1234', name: 'J. Smith'};
    +
    +       // our custom method is mapped as well.
    +       card.$charge({amount:9.99});
    +       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
    +     });
    +
    +     // we can create an instance as well
    +     var newCard = new CreditCard({number:'0123'});
    +     newCard.name = "Mike Smith";
    +     newCard.$save();
    +     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
    +     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
    +     expect(newCard.id).toEqual(789);
    + * 
    + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + +
    +     var User = $resource('/user/:userId', {userId:'@id'});
    +     var user = User.get({userId:123}, function() {
    +       user.abc = true;
    +       user.$save();
    +     });
    +   
    + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * +
    +     var User = $resource('/user/:userId', {userId:'@id'});
    +     User.get({userId:123}, function(u, getResponseHeaders){
    +       u.abc = true;
    +       u.$save(function(u, putResponseHeaders) {
    +         //u => saved user object
    +         //putResponseHeaders => $http header getter
    +       });
    +     });
    +   
    + */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$q', function($http, $q) { + + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = defaults || {}; + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param){ + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam){ + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url + url = url.replace(/\/+$/, ''); + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data, actionParams){ + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key){ + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value){ + copy(value || {}, this); + } + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = data instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if ( angular.isArray(data) !== (!!action.isArray) ) { + throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + + 'response to contain an {0} but got an {1}', + action.isArray?'array':'object', angular.isArray(data)?'array':'object'); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error||noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success||noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name](params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults){ + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]); + + +})(window, window.angular); + +/** + * @license AngularJS v1.2.1 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +var $sanitizeMinErr = angular.$$minErr('$sanitize'); + +/** + * @ngdoc overview + * @name ngSanitize + * @description + * + * # ngSanitize + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * {@installModule sanitize} + * + *
    + * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + +/* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + +/** + * @ngdoc service + * @name ngSanitize.$sanitize + * @function + * + * @description + * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. + * + * @param {string} html Html input. + * @returns {string} Sanitized html. + * + * @example + + + +
    + Snippet: + + + + + + + + + + + + + + + + + + + + + + + + + +
    DirectiveHowSourceRendered
    ng-bind-htmlAutomatically uses $sanitize
    <div ng-bind-html="snippet">
    </div>
    ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
    <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
    +</div>
    +
    ng-bindAutomatically escapes
    <div ng-bind="snippet">
    </div>
    +
    +
    + + it('should sanitize the html snippet by default', function() { + expect(using('#bind-html-with-sanitize').element('div').html()). + toBe('

    an html\nclick here\nsnippet

    '); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(using('#bind-html-with-trust').element("div").html()). + toBe("

    an html\n" + + "click here\n" + + "snippet

    "); + }); + + it('should escape snippet without any filter', function() { + expect(using('#bind-default').element('div').html()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + input('snippet').enter('new text'); + expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new text'); + expect(using('#bind-html-with-trust').element('div').html()).toBe( + 'new text'); + expect(using('#bind-default').element('div').html()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
    +
    + */ +var $sanitize = function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf)); + return buf.join(''); +}; + + +// Regular Expressions for parsing tags and attributes +var START_TAG_REGEXP = + /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //g, + URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements +var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags +var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 +var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 +var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + + +// Special Elements (can contain anything) +var specialElements = makeMap("script,style"); + +var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements); + +//Attributes that have href and hence need to be sanitized +var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); +var validAttrs = angular.extend({}, uriAttrs, makeMap( + 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ + 'scope,scrolling,shape,span,start,summary,target,title,type,'+ + 'valign,value,vspace,width')); + +function makeMap(str) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) obj[items[i]] = true; + return obj; +} + + +/** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ +function htmlParser( html, handler ) { + var index, chars, match, stack = [], last = html; + stack.last = function() { return stack[ stack.length - 1 ]; }; + + while ( html ) { + chars = true; + + // Make sure we're not in a script or style element + if ( !stack.last() || !specialElements[ stack.last() ] ) { + + // Comment + if ( html.indexOf("", index) === index) { + if (handler.comment) handler.comment( html.substring( 4, index ) ); + html = html.substring( index + 3 ); + chars = false; + } + // DOCTYPE + } else if ( DOCTYPE_REGEXP.test(html) ) { + match = html.match( DOCTYPE_REGEXP ); + + if ( match ) { + html = html.replace( match[0] , ''); + chars = false; + } + // end tag + } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { + match = html.match( END_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( END_TAG_REGEXP, parseEndTag ); + chars = false; + } + + // start tag + } else if ( BEGIN_TAG_REGEXP.test(html) ) { + match = html.match( START_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( START_TAG_REGEXP, parseStartTag ); + chars = false; + } + } + + if ( chars ) { + index = html.indexOf("<"); + + var text = index < 0 ? html : html.substring( 0, index ); + html = index < 0 ? "" : html.substring( index ); + + if (handler.chars) handler.chars( decodeEntities(text) ); + } + + } else { + html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text){ + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars( decodeEntities(text) ); + + return ""; + }); + + parseEndTag( "", stack.last() ); + } + + if ( html == last ) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag( tag, tagName, rest, unary ) { + tagName = angular.lowercase(tagName); + if ( blockElements[ tagName ] ) { + while ( stack.last() && inlineElements[ stack.last() ] ) { + parseEndTag( "", stack.last() ); + } + } + + if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { + parseEndTag( "", tagName ); + } + + unary = voidElements[ tagName ] || !!unary; + + if ( !unary ) + stack.push( tagName ); + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start( tagName, attrs, unary ); + } + + function parseEndTag( tag, tagName ) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if ( tagName ) + // Find the closest opened tag of the same type + for ( pos = stack.length - 1; pos >= 0; pos-- ) + if ( stack[ pos ] == tagName ) + break; + + if ( pos >= 0 ) { + // Close all the open elements, up the stack + for ( i = stack.length - 1; i >= pos; i-- ) + if (handler.end) handler.end( stack[ i ] ); + + // Remove the open elements from the stack + stack.length = pos; + } + } +} + +/** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ +var hiddenPre=document.createElement("pre"); +function decodeEntities(value) { + hiddenPre.innerHTML=value.replace(//g, '>'); +} + +/** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ +function htmlSanitizeWriter(buf){ + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary){ + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key){ + var lkey=angular.lowercase(key); + if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag){ + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars){ + if (!ignore) { + out(encodeEntities(chars)); + } + } + }; +} + + +// define ngSanitize module and register $sanitize service +angular.module('ngSanitize', []).value('$sanitize', $sanitize); + +/* global htmlSanitizeWriter: false */ + +/** + * @ngdoc filter + * @name ngSanitize.filter:linky + * @function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
    + Snippet: + + + + + + + + + + + + + + + + + + + + + +
    FilterSourceRendered
    linky filter +
    <div ng-bind-html="snippet | linky">
    </div>
    +
    +
    +
    linky target +
    <div ng-bind-html="snippetWithTarget | linky:'_blank'">
    </div>
    +
    +
    +
    no filter
    <div ng-bind="snippet">
    </div>
    + + + it('should linkify the snippet with urls', function() { + expect(using('#linky-filter').binding('snippet | linky')). + toBe('Pretty text with some links: ' + + 'http://angularjs.org/, ' + + 'us@somewhere.org, ' + + 'another@somewhere.org, ' + + 'and one more: ftp://127.0.0.1/.'); + }); + + it ('should not linkify snippet without the linky filter', function() { + expect(using('#escaped-html').binding('snippet')). + toBe("Pretty text with some links:\n" + + "http://angularjs.org/,\n" + + "mailto:us@somewhere.org,\n" + + "another@somewhere.org,\n" + + "and one more: ftp://127.0.0.1/."); + }); + + it('should update', function() { + input('snippet').enter('new http://link.'); + expect(using('#linky-filter').binding('snippet | linky')). + toBe('new http://link.'); + expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); + }); + + it('should work with the target property', function() { + expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")). + toBe('http://angularjs.org/'); + }); + + + */ +angular.module('ngSanitize').filter('linky', function() { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, + MAILTO_REGEXP = /^mailto:/; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + // TODO(vojta): use $sanitize instead + var writer = htmlSanitizeWriter(html); + var url; + var i; + var properties = {}; + if (angular.isDefined(target)) { + properties.target = target; + } + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/mailto then assume mailto + if (match[2] == match[3]) url = 'mailto:' + url; + i = match.index; + writer.chars(raw.substr(0, i)); + properties.href = url; + writer.start('a', properties); + writer.chars(match[0].replace(MAILTO_REGEXP, '')); + writer.end('a'); + raw = raw.substring(i + match[0].length); + } + writer.chars(raw); + return html.join(''); + }; +}); + + +})(window, window.angular); + +/** + * @license AngularJS v1.2.1 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + +/** + * @ngdoc overview + * @name ngRoute + * @description + * + * # ngRoute + * + * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. + * + * {@installModule route} + * + *
    + */ + /* global -ngRouteModule */ +var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider); + +/** + * @ngdoc object + * @name ngRoute.$routeProvider + * @function + * + * @description + * + * Used for configuring routes. See {@link ngRoute.$route $route} for an example. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ +function $RouteProvider(){ + function inherit(parent, extra) { + return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#when + * @methodOf ngRoute.$routeProvider + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon (`:name`). All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star (`:name*`). + * All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark (`:name?`). + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashs/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashs`. + * + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with + * newly created scope or the name of a {@link angular.Module#controller registered + * controller} if passed as a string. + * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be + * published to scope under the `controllerAs` name. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used by {@link + * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. + * This property takes precedence over `templateUrl`. + * + * If `template` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html + * template that should be used by {@link ngRoute.directive:ngView ngView}. + * + * If `templateUrl` is a function, it will be called with the following parameters: + * + * - `{Array.}` - route parameters extracted from the current + * `$location.path()` by applying the current route + * + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, the router + * will wait for them all to be resolved or one to be rejected before the controller is + * instantiated. + * If all the promises are resolved successfully, the values of the resolved promises are + * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is + * fired. If any of the promises are rejected the + * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object + * is: + * + * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} + * and the return value is treated as the dependency. If the result is a promise, it is + * resolved before its value is injected into the controller. Be aware that + * `ngRoute.$routeParams` will still refer to the previous route within these resolve + * functions. Use `$route.current.params` to access the new route parameters, instead. + * + * - `redirectTo` – {(string|function())=} – value to update + * {@link ng.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{string}` - current `$location.path()` + * - `{Object}` - current `$location.search()` + * + * The custom `redirectTo` function is expected to return a string which will be used + * to update `$location.path()` and `$location.search()`. + * + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + routes[path] = angular.extend( + {reloadOnSearch: true}, + route, + path && pathRegExp(path, route) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length-1] == '/') + ? path.substr(0, path.length-1) + : path +'/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, route) + ); + } + + return this; + }; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} + * + * @description + * Normalizes the given path, returning a regular expression + * and the original path. + * + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name ngRoute.$routeProvider#otherwise + * @methodOf ngRoute.$routeProvider + * + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. + * + * @param {Object} params Mapping information to be assigned to `$route.current`. + * @returns {Object} self + */ + this.otherwise = function(params) { + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$http', + '$templateCache', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { + + /** + * @ngdoc object + * @name ngRoute.$route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Array.} routes Array of all configured routes. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + This example shows how changing the URL hash causes the `$route` to match a route against the + URL, and the `ngView` pulls in the partial. + + Note that this example is using {@link ng.directive:script inlined templates} + to get it working on jsfiddle as well. + + + +
    + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
    + +
    +
    + +
    $location.path() = {{$location.path()}}
    +
    $route.current.templateUrl = {{$route.current.templateUrl}}
    +
    $route.current.params = {{$route.current.params}}
    +
    $route.current.scope.name = {{$route.current.scope.name}}
    +
    $routeParams = {{$routeParams}}
    +
    +
    + + + controller: {{name}}
    + Book Id: {{params.bookId}}
    +
    + + + controller: {{name}}
    + Book Id: {{params.bookId}}
    + Chapter Id: {{params.chapterId}} +
    + + + angular.module('ngViewExample', ['ngRoute']) + + .config(function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + resolve: { + // I will cause a 1 second delay + delay: function($q, $timeout) { + var delay = $q.defer(); + $timeout(delay.resolve, 1000); + return delay.promise; + } + } + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
    + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeStart + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeSuccess + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeChangeError + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name ngRoute.$route#$routeUpdate + * @eventOf ngRoute.$route + * @eventType broadcast on root scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + + var forceReload = false, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name ngRoute.$route#reload + * @methodOf ngRoute.$route + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope, reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(updateRoute); + } + }; + + $rootScope.$on('$locationChangeSuccess', updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = 'string' == typeof m[i] + ? decodeURIComponent(m[i]) + : m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function updateRoute() { + var next = parseRoute(), + last = $route.current; + + if (next && last && next.$$route === last.$$route + && angular.equals(next.pathParams, last.pathParams) + && !next.reloadOnSearch && !forceReload) { + last.params = next.params; + angular.copy(last.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', last); + } else if (next || last) { + forceReload = false; + $rootScope.$broadcast('$routeChangeStart', next, last); + $route.current = next; + if (next) { + if (next.redirectTo) { + if (angular.isString(next.redirectTo)) { + $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + .replace(); + } else { + $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(next). + then(function() { + if (next) { + var locals = angular.extend({}, next.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value); + }); + + if (angular.isDefined(template = next.template)) { + if (angular.isFunction(template)) { + template = template(next.params); + } + } else if (angular.isDefined(templateUrl = next.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(next.params); + } + templateUrl = $sce.getTrustedResourceUrl(templateUrl); + if (angular.isDefined(templateUrl)) { + next.loadedTemplateUrl = templateUrl; + template = $http.get(templateUrl, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + angular.copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); + } + } + + + /** + * @returns the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string||'').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; +} + +ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + +/** + * @ngdoc object + * @name ngRoute.$routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + *
    + *  // Given:
    + *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
    + *  // Route: /Chapter/:chapterId/Section/:sectionId
    + *  //
    + *  // Then
    + *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
    + * 
    + */ +function $RouteParamsProvider() { + this.$get = function() { return {}; }; +} + +ngRouteModule.directive('ngView', ngViewFactory); + +/** + * @ngdoc directive + * @name ngRoute.directive:ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @example + + +
    + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
    + +
    +
    +
    +
    + +
    $location.path() = {{main.$location.path()}}
    +
    $route.current.templateUrl = {{main.$route.current.templateUrl}}
    +
    $route.current.params = {{main.$route.current.params}}
    +
    $route.current.scope.name = {{main.$route.current.scope.name}}
    +
    $routeParams = {{main.$routeParams}}
    +
    +
    + + +
    + controller: {{book.name}}
    + Book Id: {{book.params.bookId}}
    +
    +
    + + +
    + controller: {{chapter.name}}
    + Book Id: {{chapter.params.bookId}}
    + Chapter Id: {{chapter.params.chapterId}} +
    +
    + + + .view-animate-container { + position:relative; + height:100px!important; + position:relative; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + + + + angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], + function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + controllerAs: 'book' + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl, + controllerAs: 'chapter' + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + } + + function BookCntl($routeParams) { + this.name = "BookCntl"; + this.params = $routeParams; + } + + function ChapterCntl($routeParams) { + this.name = "ChapterCntl"; + this.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
    + */ + + +/** + * @ngdoc event + * @name ngRoute.directive:ngView#$viewContentLoaded + * @eventOf ngRoute.directive:ngView + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; +function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + link: function(scope, $element, attr, ctrl, $transclude) { + var currentScope, + currentElement, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if(currentElement) { + $animate.leave(currentElement); + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (template) { + var newScope = scope.$new(); + $transclude(newScope, function(clone) { + clone.html(template); + $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + + cleanupLastView(); + + var link = $compile(clone.contents()), + current = $route.current; + + currentScope = current.scope = newScope; + currentElement = clone; + + if (current.controller) { + locals.$scope = currentScope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + currentScope[current.controllerAs] = controller; + } + clone.data('$ngControllerController', controller); + clone.children().data('$ngControllerController', controller); + } + + link(currentScope); + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + }); + } else { + cleanupLastView(); + } + } + } + }; +} + + +})(window, window.angular); + +/** + * State-based routing for AngularJS + * @version v0.2.8 + * @link http://angular-ui.github.com/ + * @license MIT License, http://www.opensource.org/licenses/MIT + */ + +/* commonjs package manager support (eg componentjs) */ +if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ + module.exports = 'ui.router'; +} + +(function (window, angular, undefined) { +/*jshint globalstrict:true*/ +/*global angular:false*/ +'use strict'; + +var isDefined = angular.isDefined, + isFunction = angular.isFunction, + isString = angular.isString, + isObject = angular.isObject, + isArray = angular.isArray, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy; + +function inherit(parent, extra) { + return extend(new (extend(function() {}, { prototype: parent }))(), extra); +} + +function merge(dst) { + forEach(arguments, function(obj) { + if (obj !== dst) { + forEach(obj, function(value, key) { + if (!dst.hasOwnProperty(key)) dst[key] = value; + }); + } + }); + return dst; +} + +/** + * Finds the common ancestor path between two states. + * + * @param {Object} first The first state. + * @param {Object} second The second state. + * @return {Array} Returns an array of state names in descending order, not including the root. + */ +function ancestors(first, second) { + var path = []; + + for (var n in first.path) { + if (first.path[n] !== second.path[n]) break; + path.push(first.path[n]); + } + return path; +} + +/** + * IE8-safe wrapper for `Object.keys()`. + * + * @param {Object} object A JavaScript object. + * @return {Array} Returns the keys of the object as an array. + */ +function keys(object) { + if (Object.keys) { + return Object.keys(object); + } + var result = []; + + angular.forEach(object, function(val, key) { + result.push(key); + }); + return result; +} + +/** + * IE8-safe wrapper for `Array.prototype.indexOf()`. + * + * @param {Array} array A JavaScript array. + * @param {*} value A value to search the array for. + * @return {Number} Returns the array index value of `value`, or `-1` if not present. + */ +function arraySearch(array, value) { + if (Array.prototype.indexOf) { + return array.indexOf(value, Number(arguments[2]) || 0); + } + var len = array.length >>> 0, from = Number(arguments[2]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + + if (from < 0) from += len; + + for (; from < len; from++) { + if (from in array && array[from] === value) return from; + } + return -1; +} + +/** + * Merges a set of parameters with all parameters inherited between the common parents of the + * current state and a given destination state. + * + * @param {Object} currentParams The value of the current state parameters ($stateParams). + * @param {Object} newParams The set of parameters which will be composited with inherited params. + * @param {Object} $current Internal definition of object representing the current state. + * @param {Object} $to Internal definition of object representing state to transition to. + */ +function inheritParams(currentParams, newParams, $current, $to) { + var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; + + for (var i in parents) { + if (!parents[i].params || !parents[i].params.length) continue; + parentParams = parents[i].params; + + for (var j in parentParams) { + if (arraySearch(inheritList, parentParams[j]) >= 0) continue; + inheritList.push(parentParams[j]); + inherited[parentParams[j]] = currentParams[parentParams[j]]; + } + } + return extend({}, inherited, newParams); +} + +/** + * Normalizes a set of values to string or `null`, filtering them by a list of keys. + * + * @param {Array} keys The list of keys to normalize/return. + * @param {Object} values An object hash of values to normalize. + * @return {Object} Returns an object hash of normalized string values. + */ +function normalize(keys, values) { + var normalized = {}; + + forEach(keys, function (name) { + var value = values[name]; + normalized[name] = (value != null) ? String(value) : null; + }); + return normalized; +} + +/** + * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. + * + * @param {Object} a The first object. + * @param {Object} b The second object. + * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, + * it defaults to the list of keys in `a`. + * @return {Boolean} Returns `true` if the keys match, otherwise `false`. + */ +function equalForKeys(a, b, keys) { + if (!keys) { + keys = []; + for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility + } + + for (var i=0; i + * $resolve.study(invocables)(locals, parent, self) + * + * is equivalent to + *
    +   * $resolve.resolve(invocables, locals, parent, self)
    +   * 
    + * but the former is more efficient (in fact `resolve` just calls `study` + * internally). + * + * @param {object} invocables Invocable objects + * @return {function} a function to pass in locals, parent and self + */ + this.study = function (invocables) { + if (!isObject(invocables)) throw new Error("'invocables' must be an object"); + + // Perform a topological sort of invocables to build an ordered plan + var plan = [], cycle = [], visited = {}; + function visit(value, key) { + if (visited[key] === VISIT_DONE) return; + + cycle.push(key); + if (visited[key] === VISIT_IN_PROGRESS) { + cycle.splice(0, cycle.indexOf(key)); + throw new Error("Cyclic dependency: " + cycle.join(" -> ")); + } + visited[key] = VISIT_IN_PROGRESS; + + if (isString(value)) { + plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); + } else { + var params = $injector.annotate(value); + forEach(params, function (param) { + if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); + }); + plan.push(key, value, params); + } + + cycle.pop(); + visited[key] = VISIT_DONE; + } + forEach(invocables, visit); + invocables = cycle = visited = null; // plan is all that's required + + function isResolve(value) { + return isObject(value) && value.then && value.$$promises; + } + + return function (locals, parent, self) { + if (isResolve(locals) && self === undefined) { + self = parent; parent = locals; locals = null; + } + if (!locals) locals = NO_LOCALS; + else if (!isObject(locals)) { + throw new Error("'locals' must be an object"); + } + if (!parent) parent = NO_PARENT; + else if (!isResolve(parent)) { + throw new Error("'parent' must be a promise returned by $resolve.resolve()"); + } + + // To complete the overall resolution, we have to wait for the parent + // promise and for the promise for each invokable in our plan. + var resolution = $q.defer(), + result = resolution.promise, + promises = result.$$promises = {}, + values = extend({}, locals), + wait = 1 + plan.length/3, + merged = false; + + function done() { + // Merge parent values we haven't got yet and publish our own $$values + if (!--wait) { + if (!merged) merge(values, parent.$$values); + result.$$values = values; + result.$$promises = true; // keep for isResolve() + resolution.resolve(values); + } + } + + function fail(reason) { + result.$$failure = reason; + resolution.reject(reason); + } + + // Short-circuit if parent has already failed + if (isDefined(parent.$$failure)) { + fail(parent.$$failure); + return result; + } + + // Merge parent values if the parent has already resolved, or merge + // parent promises and wait if the parent resolve is still in progress. + if (parent.$$values) { + merged = merge(values, parent.$$values); + done(); + } else { + extend(promises, parent.$$promises); + parent.then(done, fail); + } + + // Process each invocable in the plan, but ignore any where a local of the same name exists. + for (var i=0, ii=plan.length; i} The template html as a string, or a promise + * for that string. + */ + this.fromUrl = function (url, params) { + if (isFunction(url)) url = url(params); + if (url == null) return null; + else return $http + .get(url, { cache: $templateCache }) + .then(function(response) { return response.data; }); + }; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactory#fromUrl + * @methodOf ui.router.util.$templateFactory + * + * @description + * Creates a template by invoking an injectable provider function. + * + * @param {Function} provider Function to invoke via `$injector.invoke` + * @param {Object} params Parameters for the template. + * @param {Object} locals Locals to pass to `invoke`. Defaults to + * `{ params: params }`. + * @return {string|Promise.} The template html as a string, or a promise + * for that string. + */ + this.fromProvider = function (provider, params, locals) { + return $injector.invoke(provider, null, locals || { params: params }); + }; +} + +angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); + +/** + * Matches URLs against patterns and extracts named parameters from the path or the search + * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list + * of search parameters. Multiple search parameter names are separated by '&'. Search parameters + * do not influence whether or not a URL is matched, but their values are passed through into + * the matched parameters returned by {@link UrlMatcher#exec exec}. + * + * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace + * syntax, which optionally allows a regular expression for the parameter to be specified: + * + * * ':' name - colon placeholder + * * '*' name - catch-all placeholder + * * '{' name '}' - curly placeholder + * * '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain + * curly braces, they must be in matched pairs or escaped with a backslash. + * + * Parameter names may contain only word characters (latin letters, digits, and underscore) and + * must be unique within the pattern (across both path and search parameters). For colon + * placeholders or curly placeholders without an explicit regexp, a path parameter matches any + * number of characters other than '/'. For catch-all placeholders the path parameter matches + * any number of characters. + * + * ### Examples + * + * * '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for + * trailing slashes, and patterns have to match the entire path, not just a prefix. + * * '/user/:id' - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or + * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. + * * '/user/{id}' - Same as the previous example, but using curly brace syntax. + * * '/user/{id:[^/]*}' - Same as the previous example. + * * '/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id + * parameter consists of 1 to 8 hex digits. + * * '/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the + * path into the parameter 'path'. + * * '/files/*path' - ditto. + * + * @constructor + * @param {string} pattern the pattern to compile into a matcher. + * + * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any + * URL matching this matcher (i.e. any string for which {@link UrlMatcher#exec exec()} returns + * non-null) will start with this prefix. + */ +function UrlMatcher(pattern) { + + // Find all placeholders and create a compiled pattern, using either classic or curly syntax: + // '*' name + // ':' name + // '{' name '}' + // '{' name ':' regexp '}' + // The regular expression is somewhat complicated due to the need to allow curly braces + // inside the regular expression. The placeholder regexp breaks down as follows: + // ([:*])(\w+) classic placeholder ($1 / $2) + // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4) + // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either + // [^{}\\]+ - anything other than curly braces or backslash + // \\. - a backslash escape + // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms + var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, + names = {}, compiled = '^', last = 0, m, + segments = this.segments = [], + params = this.params = []; + + function addParameter(id) { + if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); + if (names[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); + names[id] = true; + params.push(id); + } + + function quoteRegExp(string) { + return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); + } + + this.source = pattern; + + // Split into static segments separated by path parameter placeholders. + // The number of segments is always 1 more than the number of parameters. + var id, regexp, segment; + while ((m = placeholder.exec(pattern))) { + id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null + regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); + segment = pattern.substring(last, m.index); + if (segment.indexOf('?') >= 0) break; // we're into the search part + compiled += quoteRegExp(segment) + '(' + regexp + ')'; + addParameter(id); + segments.push(segment); + last = placeholder.lastIndex; + } + segment = pattern.substring(last); + + // Find any search parameter names and remove them from the last segment + var i = segment.indexOf('?'); + if (i >= 0) { + var search = this.sourceSearch = segment.substring(i); + segment = segment.substring(0, i); + this.sourcePath = pattern.substring(0, last+i); + + // Allow parameters to be separated by '?' as well as '&' to make concat() easier + forEach(search.substring(1).split(/[&?]/), addParameter); + } else { + this.sourcePath = pattern; + this.sourceSearch = ''; + } + + compiled += quoteRegExp(segment) + '$'; + segments.push(segment); + this.regexp = new RegExp(compiled); + this.prefix = segments[0]; +} + +/** + * Returns a new matcher for a pattern constructed by appending the path part and adding the + * search parameters of the specified pattern to this pattern. The current pattern is not + * modified. This can be understood as creating a pattern for URLs that are relative to (or + * suffixes of) the current pattern. + * + * ### Example + * The following two matchers are equivalent: + * ``` + * new UrlMatcher('/user/{id}?q').concat('/details?date'); + * new UrlMatcher('/user/{id}/details?q&date'); + * ``` + * + * @param {string} pattern The pattern to append. + * @return {UrlMatcher} A matcher for the concatenated pattern. + */ +UrlMatcher.prototype.concat = function (pattern) { + // Because order of search parameters is irrelevant, we can add our own search + // parameters to the end of the new pattern. Parse the new pattern by itself + // and then join the bits together, but it's much easier to do this on a string level. + return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch); +}; + +UrlMatcher.prototype.toString = function () { + return this.source; +}; + +/** + * Tests the specified path against this matcher, and returns an object containing the captured + * parameter values, or null if the path does not match. The returned object contains the values + * of any search parameters that are mentioned in the pattern, but their value may be null if + * they are not present in `searchParams`. This means that search parameters are always treated + * as optional. + * + * ### Example + * ``` + * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' }); + * // returns { id:'bob', q:'hello', r:null } + * ``` + * + * @param {string} path The URL path to match, e.g. `$location.path()`. + * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. + * @return {Object} The captured parameter values. + */ +UrlMatcher.prototype.exec = function (path, searchParams) { + var m = this.regexp.exec(path); + if (!m) return null; + + var params = this.params, nTotal = params.length, + nPath = this.segments.length-1, + values = {}, i; + + if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); + + for (i=0; i} An array of parameter names. Must be treated as read-only. If the + * pattern has no parameters, an empty array is returned. + */ +UrlMatcher.prototype.parameters = function () { + return this.params; +}; + +/** + * Creates a URL that matches this pattern by substituting the specified values + * for the path and search parameters. Null values for path parameters are + * treated as empty strings. + * + * ### Example + * ``` + * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); + * // returns '/user/bob?q=yes' + * ``` + * + * @param {Object} values the values to substitute for the parameters in this pattern. + * @return {string} the formatted URL (path and optionally search part). + */ +UrlMatcher.prototype.format = function (values) { + var segments = this.segments, params = this.params; + if (!values) return segments.join(''); + + var nPath = segments.length-1, nTotal = params.length, + result = segments[0], i, search, value; + + for (i=0; i + * var app = angular.module('app', ['ui.router.router']); + * + * app.config(function ($urlRouterProvider) { + * // Here's an example of how you might allow case insensitive urls + * $urlRouterProvider.rule(function ($injector, $location) { + * var path = $location.path(), + * normalized = path.toLowerCase(); + * + * if (path !== normalized) { + * return normalized; + * } + * }); + * }); + * + * + * @param {object} rule Handler function that takes `$injector` and `$location` + * services as arguments. You can use them to return a valid path as a string. + * + * @return {object} $urlRouterProvider - $urlRouterProvider instance + */ + this.rule = + function (rule) { + if (!isFunction(rule)) throw new Error("'rule' must be a function"); + rules.push(rule); + return this; + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouterProvider#otherwise + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Defines a path that is used when an invalied route is requested. + * + * @example + *
    +   * var app = angular.module('app', ['ui.router.router']);
    +   *
    +   * app.config(function ($urlRouterProvider) {
    +   *   // if the path doesn't match any of the urls you configured
    +   *   // otherwise will take care of routing the user to the
    +   *   // specified url
    +   *   $urlRouterProvider.otherwise('/index');
    +   *
    +   *   // Example of using function rule as param
    +   *   $urlRouterProvider.otherwise(function ($injector, $location) {
    +   *     ...
    +   *   });
    +   * });
    +   * 
    + * + * @param {string|object} rule The url path you want to redirect to or a function + * rule that returns the url path. The function version is passed two params: + * `$injector` and `$location` services. + * + * @return {object} $urlRouterProvider - $urlRouterProvider instance + */ + this.otherwise = + function (rule) { + if (isString(rule)) { + var redirect = rule; + rule = function () { return redirect; }; + } + else if (!isFunction(rule)) throw new Error("'rule' must be a function"); + otherwise = rule; + return this; + }; + + + function handleIfMatch($injector, handler, match) { + if (!match) return false; + var result = $injector.invoke(handler, handler, { $match: match }); + return isDefined(result) ? result : true; + } + + /** + * @ngdoc function + * @name ui.router.router.$urlRouterProvider#when + * @methodOf ui.router.router.$urlRouterProvider + * + * @description + * Registers a handler for a given url matching. if handle is a string, it is + * treated as a redirect, and is interpolated according to the syyntax of match + * (i.e. like String.replace() for RegExp, or like a UrlMatcher pattern otherwise). + * + * If the handler is a function, it is injectable. It gets invoked if `$location` + * matches. You have the option of inject the match object as `$match`. + * + * The handler can return + * + * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` + * will continue trying to find another one that matches. + * - **string** which is treated as a redirect and passed to `$location.url()` + * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. + * + * @example + *
    +   * var app = angular.module('app', ['ui.router.router']);
    +   *
    +   * app.config(function ($urlRouterProvider) {
    +   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
    +   *     if ($state.$current.navigable !== state ||
    +   *         !equalForKeys($match, $stateParams) {
    +   *      $state.transitionTo(state, $match, false);
    +   *     }
    +   *   });
    +   * });
    +   * 
    + * + * @param {string|object} what The incoming path that you want to redirect. + * @param {string|object} handler The path you want to redirect your user to. + */ + this.when = + function (what, handler) { + var redirect, handlerIsString = isString(handler); + if (isString(what)) what = $urlMatcherFactory.compile(what); + + if (!handlerIsString && !isFunction(handler) && !isArray(handler)) + throw new Error("invalid 'handler' in when()"); + + var strategies = { + matcher: function (what, handler) { + if (handlerIsString) { + redirect = $urlMatcherFactory.compile(handler); + handler = ['$match', function ($match) { return redirect.format($match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); + }, { + prefix: isString(what.prefix) ? what.prefix : '' + }); + }, + regex: function (what, handler) { + if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); + + if (handlerIsString) { + redirect = handler; + handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; + } + return extend(function ($injector, $location) { + return handleIfMatch($injector, handler, what.exec($location.path())); + }, { + prefix: regExpPrefix(what) + }); + } + }; + + var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; + + for (var n in check) { + if (check[n]) { + return this.rule(strategies[n](what, handler)); + } + } + + throw new Error("invalid 'what' in when()"); + }; + + /** + * @ngdoc object + * @name ui.router.router.$urlRouter + * + * @requires $location + * @requires $rootScope + * @requires $injector + * + * @description + * + */ + this.$get = + [ '$location', '$rootScope', '$injector', + function ($location, $rootScope, $injector) { + // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree + function update(evt) { + if (evt && evt.defaultPrevented) return; + function check(rule) { + var handled = rule($injector, $location); + if (handled) { + if (isString(handled)) $location.replace().url(handled); + return true; + } + return false; + } + var n=rules.length, i; + for (i=0; i + * angular.module('app', ['ui.router']); + * .run(function($rootScope, $urlRouter) { + * $rootScope.$on('$locationChangeSuccess', function(evt) { + * // Halt state change from even starting + * evt.preventDefault(); + * // Perform custom logic + * var meetsRequirement = ... + * // Continue with the update and state transition if logic allows + * if (meetsRequirement) $urlRouter.sync(); + * }); + * }); + * + */ + sync: function () { + update(); + } + }; + }]; +} + +angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); + +/** + * @ngdoc object + * @name ui.router.state.$stateProvider + * + * @requires ui.router.router.$urlRouterProvider + * @requires ui.router.util.$urlMatcherFactoryProvider + * @requires $locationProvider + * + * @description + * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely + * on state. + * + * A state corresponds to a "place" in the application in terms of the overall UI and + * navigation. A state describes (via the controller / template / view properties) what + * the UI looks like and does at that place. + * + * States often have things in common, and the primary way of factoring out these + * commonalities in this model is via the state hierarchy, i.e. parent/child states aka + * nested states. + * + * The `$stateProvider` provides interfaces to declare these states for your app. + */ +$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider', '$locationProvider']; +function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $locationProvider) { + + var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; + + // Builds state properties from definition passed to registerState() + var stateBuilder = { + + // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. + // state.children = []; + // if (parent) parent.children.push(state); + parent: function(state) { + if (isDefined(state.parent) && state.parent) return findState(state.parent); + // regex matches any valid composite state name + // would match "contact.list" but not "contacts" + var compositeName = /^(.+)\.[^.]+$/.exec(state.name); + return compositeName ? findState(compositeName[1]) : root; + }, + + // inherit 'data' from parent and override by own values (if any) + data: function(state) { + if (state.parent && state.parent.data) { + state.data = state.self.data = extend({}, state.parent.data, state.data); + } + return state.data; + }, + + // Build a URLMatcher if necessary, either via a relative or absolute URL + url: function(state) { + var url = state.url; + + if (isString(url)) { + if (url.charAt(0) == '^') { + return $urlMatcherFactory.compile(url.substring(1)); + } + return (state.parent.navigable || root).url.concat(url); + } + + if ($urlMatcherFactory.isMatcher(url) || url == null) { + return url; + } + throw new Error("Invalid url '" + url + "' in state '" + state + "'"); + }, + + // Keep track of the closest ancestor state that has a URL (i.e. is navigable) + navigable: function(state) { + return state.url ? state : (state.parent ? state.parent.navigable : null); + }, + + // Derive parameters for this state and ensure they're a super-set of parent's parameters + params: function(state) { + if (!state.params) { + return state.url ? state.url.parameters() : state.parent.params; + } + if (!isArray(state.params)) throw new Error("Invalid params in state '" + state + "'"); + if (state.url) throw new Error("Both params and url specicified in state '" + state + "'"); + return state.params; + }, + + // If there is no explicit multi-view configuration, make one up so we don't have + // to handle both cases in the view directive later. Note that having an explicit + // 'views' property will mean the default unnamed view properties are ignored. This + // is also a good time to resolve view names to absolute names, so everything is a + // straight lookup at link time. + views: function(state) { + var views = {}; + + forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { + if (name.indexOf('@') < 0) name += '@' + state.parent.name; + views[name] = view; + }); + return views; + }, + + ownParams: function(state) { + if (!state.parent) { + return state.params; + } + var paramNames = {}; forEach(state.params, function (p) { paramNames[p] = true; }); + + forEach(state.parent.params, function (p) { + if (!paramNames[p]) { + throw new Error("Missing required parameter '" + p + "' in state '" + state.name + "'"); + } + paramNames[p] = false; + }); + var ownParams = []; + + forEach(paramNames, function (own, p) { + if (own) ownParams.push(p); + }); + return ownParams; + }, + + // Keep a full path from the root down to this state as this is needed for state activation. + path: function(state) { + return state.parent ? state.parent.path.concat(state) : []; // exclude root from path + }, + + // Speed up $state.contains() as it's used a lot + includes: function(state) { + var includes = state.parent ? extend({}, state.parent.includes) : {}; + includes[state.name] = true; + return includes; + }, + + $delegates: {} + }; + + function isRelative(stateName) { + return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; + } + + function findState(stateOrName, base) { + var isStr = isString(stateOrName), + name = isStr ? stateOrName : stateOrName.name, + path = isRelative(name); + + if (path) { + if (!base) throw new Error("No reference point given for path '" + name + "'"); + var rel = name.split("."), i = 0, pathLength = rel.length, current = base; + + for (; i < pathLength; i++) { + if (rel[i] === "" && i === 0) { + current = base; + continue; + } + if (rel[i] === "^") { + if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); + current = current.parent; + continue; + } + break; + } + rel = rel.slice(i).join("."); + name = current.name + (current.name && rel ? "." : "") + rel; + } + var state = states[name]; + + if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { + return state; + } + return undefined; + } + + function queueState(parentName, state) { + if (!queue[parentName]) { + queue[parentName] = []; + } + queue[parentName].push(state); + } + + function registerState(state) { + // Wrap a new object around the state so we can store our private details easily. + state = inherit(state, { + self: state, + resolve: state.resolve || {}, + toString: function() { return this.name; } + }); + + var name = state.name; + if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); + if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined"); + + // Get parent name + var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) + : (isString(state.parent)) ? state.parent + : ''; + + // If parent is not registered yet, add state to queue and register later + if (parentName && !states[parentName]) { + return queueState(parentName, state.self); + } + + for (var key in stateBuilder) { + if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); + } + states[name] = state; + + // Register the state in the global state list and with $urlRouter if necessary. + if (!state[abstractKey] && state.url) { + $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { + if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { + $state.transitionTo(state, $match, { location: false }); + } + }]); + } + + // Register any queued children + if (queue[name]) { + for (var i = 0; i < queue[name].length; i++) { + registerState(queue[name][i]); + } + } + + return state; + } + + + // Implicit root state that is always active + root = registerState({ + name: '', + url: '^', + views: null, + 'abstract': true + }); + root.navigable = null; + + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#decorator + * @methodOf ui.router.state.$stateProvider + * + * @description + * Allows you to extend (carefully) or override (at your own peril) the + * `stateBuilder` object used internally by `$stateProvider`. This can be used + * to add custom functionality to ui-router, for example inferring templateUrl + * based on the state name. + * + * When passing only a name, it returns the current (original or decorated) builder + * function that matches `name`. + * + * The builder functions that can be decorated are listed below. Though not all + * necessarily have a good use case for decoration, that is up to you to decide. + * + * In addition, users can attach custom decorators, which will generate new + * properties within the state's internal definition. There is currently no clear + * use-case for this beyond accessing internal states (i.e. $state.$current), + * however, expect this to become increasingly relevant as we introduce additional + * meta-programming features. + * + * **Warning**: Decorators should not be interdependent because the order of + * execution of the builder functions in nondeterministic. Builder functions + * should only be dependent on the state definition object and super function. + * + * + * Existing builder functions and current return values: + * + * - parent - `{object}` - returns the parent state object. + * - data - `{object}` - returns state data, including any inherited data that is not + * overridden by own values (if any). + * - url - `{object}` - returns a UrlMatcher or null. + * - navigable - returns closest ancestor state that has a URL (aka is + * navigable). + * - params - `{object}` - returns an array of state params that are ensured to + * be a super-set of parent's params. + * - views - `{object}` - returns a views object where each key is an absolute view + * name (i.e. "viewName@stateName") and each value is the config object + * (template, controller) for the view. Even when you don't use the views object + * explicitly on a state config, one is still created for you internally. + * So by decorating this builder function you have access to decorating template + * and controller properties. + * - ownParams - `{object}` - returns an array of params that belong to the state, + * not including any params defined by ancestor states. + * - path - `{string}` - returns the full path from the root down to this state. + * Needed for state activation. + * - includes - `{object}` - returns an object that includes every state that + * would pass a '$state.includes()' test. + * + * @example + *
    +   * // Override the internal 'views' builder with a function that takes the state
    +   * // definition, and a reference to the internal function being overridden:
    +   * $stateProvider.decorator('views', function ($state, parent) {
    +   *   var result = {},
    +   *       views = parent(state);
    +   *
    +   *   angular.forEach(view, function (config, name) {
    +   *     var autoName = (state.name + '.' + name).replace('.', '/');
    +   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
    +   *     result[name] = config;
    +   *   });
    +   *   return result;
    +   * });
    +   *
    +   * $stateProvider.state('home', {
    +   *   views: {
    +   *     'contact.list': { controller: 'ListController' },
    +   *     'contact.item': { controller: 'ItemController' }
    +   *   }
    +   * });
    +   *
    +   * // ...
    +   *
    +   * $state.go('home');
    +   * // Auto-populates list and item views with /partials/home/contact/list.html,
    +   * // and /partials/home/contact/item.html, respectively.
    +   * 
    + * + * @param {string} name The name of the builder function to decorate. + * @param {object} func A function that is responsible for decorating the original + * builder function. The function receives two parameters: + * + * - `{object}` - state - The state config object. + * - `{object}` - super - The original builder function. + * + * @return {object} $stateProvider - $stateProvider instance + */ + this.decorator = decorator; + function decorator(name, func) { + /*jshint validthis: true */ + if (isString(name) && !isDefined(func)) { + return stateBuilder[name]; + } + if (!isFunction(func) || !isString(name)) { + return this; + } + if (stateBuilder[name] && !stateBuilder.$delegates[name]) { + stateBuilder.$delegates[name] = stateBuilder[name]; + } + stateBuilder[name] = func; + return this; + } + + /** + * @ngdoc function + * @name ui.router.state.$stateProvider#state + * @methodOf ui.router.state.$stateProvider + * + * @description + * Registers a state configuration under a given state name. The stateConfig object + * has the following acceptable properties. + * + * - [`template`, `templateUrl`, `templateProvider`] - There are three ways to setup + * your templates. + * + * - `{string|object}` - template - String HTML content, or function that returns an HTML + * string. + * - `{string}` - templateUrl - String URL path to template file OR function, + * that returns URL path string. + * - `{object}` - templateProvider - Provider function that returns HTML content + * string. + * + * - [`controller`, `controllerProvider`] - A controller paired to the state. You can + * either use a controller, or a controller provider. + * + * - `{string|object}` - controller - Controller function or controller name. + * - `{object}` - controllerProvider - Injectable provider function that returns + * the actual controller or string. + * + * - `{object}` - resolve - A map of dependencies which should be injected into the + * controller. + * + * - `{string}` - url - A url with optional parameters. When a state is navigated or + * transitioned to, the `$stateParams` service will be populated with any + * parameters that were passed. + * + * - `{object}` - params - An array of parameter names or regular expressions. Only + * use this within a state if you are not using url. Otherwise you can specify your + * parameters within the url. When a state is navigated or transitioned to, the + * $stateParams service will be populated with any parameters that were passed. + * + * - `{object}` - views - Use the views property to set up multiple views. + * If you don't need multiple views within a single state this property is not + * needed. Tip: remember that often nested views are more useful and powerful + * than multiple sibling views. + * + * - `{boolean}` - abstract - An abstract state will never be directly activated, + * but can provide inherited properties to its common children states. + * + * - `{object}` - onEnter - Callback function for when a state is entered. Good way + * to trigger an action or dispatch an event, such as opening a dialog. + * + * - `{object}` - onExit - Callback function for when a state is exited. Good way to + * trigger an action or dispatch an event, such as opening a dialog. + * + * - `{object}` - data - Arbitrary data object, useful for custom configuration. + * + * @example + *
    +   * // The state() method takes a unique stateName (String) and a stateConfig (Object)
    +   * $stateProvider.state(stateName, stateConfig);
    +   *
    +   * // stateName can be a single top-level name (must be unique).
    +   * $stateProvider.state("home", {});
    +   *
    +   * // Or it can be a nested state name. This state is a child of the above "home" state.
    +   * $stateProvider.state("home.newest", {});
    +   *
    +   * // Nest states as deeply as needed.
    +   * $stateProvider.state("home.newest.abc.xyz.inception", {});
    +   *
    +   * // state() returns $stateProvider, so you can chain state declarations.
    +   * $stateProvider
    +   *   .state("home", {})
    +   *   .state("about", {})
    +   *   .state("contacts", {});
    +   * 
    + * + * @param {string} name A unique state name, e.g. "home", "about", "contacts". + * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". + * @param {object} definition State configuratino object. + */ + this.state = state; + function state(name, definition) { + /*jshint validthis: true */ + if (isObject(name)) definition = name; + else definition.name = name; + registerState(definition); + return this; + } + + /** + * @ngdoc object + * @name ui.router.state.$state + * + * @requires $rootScope + * @requires $q + * @requires ui.router.state.$view + * @requires $injector + * @requires ui.router.util.$resolve + * @requires ui.router.state.$stateParams + * + * @property {object} params A param object, e.g. {sectionId: section.id)}, that + * you'd like to test against the current active state. + * @property {object} current A reference to the state's config object. However + * you passed it in. Useful for accessing custom data. + * @property {object} transition Currently pending transition. A promise that'll + * resolve or reject. + * + * @description + * `$state` service is responsible for representing states as well as transitioning + * between them. It also provides interfaces to ask for current state or even states + * you're coming from. + */ + // $urlRouter is injected just to ensure it gets instantiated + this.$get = $get; + $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$location', '$urlRouter']; + function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $location, $urlRouter) { + + var TransitionSuperseded = $q.reject(new Error('transition superseded')); + var TransitionPrevented = $q.reject(new Error('transition prevented')); + var TransitionAborted = $q.reject(new Error('transition aborted')); + var TransitionFailed = $q.reject(new Error('transition failed')); + var currentLocation = $location.url(); + + function syncUrl() { + if ($location.url() !== currentLocation) { + $location.url(currentLocation); + $location.replace(); + } + } + + root.locals = { resolve: null, globals: { $stateParams: {} } }; + $state = { + params: {}, + current: root.self, + $current: root, + transition: null + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#reload + * @methodOf ui.router.state.$state + * + * @description + * Reloads the current state by re-transitioning to it. + * + * @example + *
    +     * var app angular.module('app', ['ui.router.state']);
    +     *
    +     * app.controller('ctrl', function ($state) {
    +     *   $state.reload();
    +     * });
    +     * 
    + */ + $state.reload = function reload() { + $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false }); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#go + * @methodOf ui.router.state.$state + * + * @description + * Convenience method for transitioning to a new state. `$state.go` calls + * `$state.transitionTo` internally but automatically sets options to + * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. + * This allows you to easily use an absolute or relative to path and specify + * only the parameters you'd like to update (while letting unspecified parameters + * inherit from the current state. + * + * Some examples: + * + * - `$state.go('contact.detail')` - will go to the `contact.detail` state + * - `$state.go('^')` - will go to a parent state + * - `$state.go('^.sibling')` - will go to a sibling state + * - `$state.go('.child.grandchild')` - will go to grandchild state + * + * @example + *
    +     * var app = angular.module('app', ['ui.router.state']);
    +     *
    +     * app.controller('ctrl', function ($scope, $state) {
    +     *   $scope.changeState = function () {
    +     *     $state.go('contact.detail');
    +     *   };
    +     * });
    +     * 
    + * + * @param {string} to Absolute State Name or Relative State Path. + * @param {object} params A map of the parameters that will be sent to the state, + * will populate $stateParams. + * @param {object} options If Object is passed, object is an options hash. + */ + $state.go = function go(to, params, options) { + return this.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#transitionTo + * @methodOf ui.router.state.$state + * + * @description + * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} + * uses `transitionTo` internally. `$state.go` is recommended in most situations. + * + * @example + *
    +     * var app = angular.module('app', ['ui.router.state']);
    +     *
    +     * app.controller('ctrl', function ($scope, $state) {
    +     *   $scope.changeState = function () {
    +     *     $state.transitionTo('contact.detail');
    +     *   };
    +     * });
    +     * 
    + * + * @param {string} to Absolute State Name or Relative State Path. + * @param {object} params A map of the parameters that will be sent to the state, + * will populate $stateParams. + * @param {object} options If Object is passed, object is an options hash. + */ + $state.transitionTo = function transitionTo(to, toParams, options) { + toParams = toParams || {}; + options = extend({ + location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false + }, options || {}); + + var from = $state.$current, fromParams = $state.params, fromPath = from.path; + var evt, toState = findState(to, options.relative); + + if (!isDefined(toState)) { + // Broadcast not found event and abort the transition if prevented + var redirect = { to: to, toParams: toParams, options: options }; + evt = $rootScope.$broadcast('$stateNotFound', redirect, from.self, fromParams); + if (evt.defaultPrevented) { + syncUrl(); + return TransitionAborted; + } + + // Allow the handler to return a promise to defer state lookup retry + if (evt.retry) { + if (options.$retry) { + syncUrl(); + return TransitionFailed; + } + var retryTransition = $state.transition = $q.when(evt.retry); + retryTransition.then(function() { + if (retryTransition !== $state.transition) return TransitionSuperseded; + redirect.options.$retry = true; + return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); + }, function() { + return TransitionAborted; + }); + syncUrl(); + return retryTransition; + } + + // Always retry once if the $stateNotFound was not prevented + // (handles either redirect changed or state lazy-definition) + to = redirect.to; + toParams = redirect.toParams; + options = redirect.options; + toState = findState(to, options.relative); + if (!isDefined(toState)) { + if (options.relative) throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); + throw new Error("No such state '" + to + "'"); + } + } + if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); + if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); + to = toState; + + var toPath = to.path; + + // Starting from the root of the path, keep all levels that haven't changed + var keep, state, locals = root.locals, toLocals = []; + for (keep = 0, state = toPath[keep]; + state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams) && !options.reload; + keep++, state = toPath[keep]) { + locals = toLocals[keep] = state.locals; + } + + // If we're going to the same state and all locals are kept, we've got nothing to do. + // But clear 'transition', as we still want to cancel any other pending transitions. + // TODO: We may not want to bump 'transition' if we're called from a location change that we've initiated ourselves, + // because we might accidentally abort a legitimate transition initiated from code? + if (shouldTriggerReload(to, from, locals, options) ) { + if ( to.self.reloadOnSearch !== false ) + syncUrl(); + $state.transition = null; + return $q.when($state.current); + } + + // Normalize/filter parameters before we pass them to event handlers etc. + toParams = normalize(to.params, toParams || {}); + + // Broadcast start event and cancel the transition if requested + if (options.notify) { + evt = $rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams); + if (evt.defaultPrevented) { + syncUrl(); + return TransitionPrevented; + } + } + + // Resolve locals for the remaining states, but don't update any global state just + // yet -- if anything fails to resolve the current state needs to remain untouched. + // We also set up an inheritance chain for the locals here. This allows the view directive + // to quickly look up the correct definition for each view in the current state. Even + // though we create the locals object itself outside resolveState(), it is initially + // empty and gets filled asynchronously. We need to keep track of the promise for the + // (fully resolved) current locals, and pass this down the chain. + var resolved = $q.when(locals); + for (var l=keep; l=keep; l--) { + exiting = fromPath[l]; + if (exiting.self.onExit) { + $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); + } + exiting.locals = null; + } + + // Enter 'to' states not kept + for (l=keep; l + * $state.is('contact.details.item'); // returns true + * $state.is(contactDetailItemStateObject); // returns true + * + * // everything else would return false + * + * + * @param {string|object} stateName The state name or state object you'd like to check. + * @param {object} params A param object, e.g. `{sectionId: section.id}`, that you'd like + * to test against the current active state. + * @returns {boolean} Returns true or false whether its the state or not. + */ + $state.is = function is(stateOrName, params) { + var state = findState(stateOrName); + + if (!isDefined(state)) { + return undefined; + } + + if ($state.$current !== state) { + return false; + } + + return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#includes + * @methodOf ui.router.state.$state + * + * @description + * A method to determine if the current active state is equal to or is the child of the + * state stateName. If any params are passed then they will be tested for a match as well. + * Not all the parameters need to be passed, just the ones you'd like to test for equality. + * + * @example + *
    +     * $state.includes("contacts"); // returns true
    +     * $state.includes("contacts.details"); // returns true
    +     * $state.includes("contacts.details.item"); // returns true
    +     * $state.includes("contacts.list"); // returns false
    +     * $state.includes("about"); // returns false
    +     * 
    + * + * @param {string} stateOrName A partial name to be searched for within the current state name. + * @param {object} params A param object, e.g. `{sectionId: section.id}`, + * that you'd like to test against the current active state. + * @returns {boolean} True or false + */ + $state.includes = function includes(stateOrName, params) { + var state = findState(stateOrName); + if (!isDefined(state)) { + return undefined; + } + + if (!isDefined($state.$current.includes[state.name])) { + return false; + } + + var validParams = true; + angular.forEach(params, function(value, key) { + if (!isDefined($stateParams[key]) || $stateParams[key] !== value) { + validParams = false; + } + }); + return validParams; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#href + * @methodOf ui.router.state.$state + * + * @description + * A url generation method that returns the compiled url for the given state populated with the given params. + * + * @example + *
    +     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
    +     * 
    + * + * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. + * @param {object} params An object of parameter values to fill the state's required parameters. + * @returns {string} url + */ + $state.href = function href(stateOrName, params, options) { + options = extend({ lossy: true, inherit: false, absolute: false, relative: $state.$current }, options || {}); + var state = findState(stateOrName, options.relative); + if (!isDefined(state)) return null; + + params = inheritParams($stateParams, params || {}, $state.$current, state); + var nav = (state && options.lossy) ? state.navigable : state; + var url = (nav && nav.url) ? nav.url.format(normalize(state.params, params || {})) : null; + if (!$locationProvider.html5Mode() && url) { + url = "#" + $locationProvider.hashPrefix() + url; + } + if (options.absolute && url) { + url = $location.protocol() + '://' + + $location.host() + + ($location.port() == 80 || $location.port() == 443 ? '' : ':' + $location.port()) + + (!$locationProvider.html5Mode() && url ? '/' : '') + + url; + } + return url; + }; + + /** + * @ngdoc function + * @name ui.router.state.$state#get + * @methodOf ui.router.state.$state + * + * @description + * Returns the state configuration object for any state by passing the name + * as a string. Without any arguments it'll return a array of all configured + * state objects. + * + * @param {string|object} stateOrName The name of the state for which you'd like + * to get the original state configuration object for. + * @returns {object} State configuration object or array of all objects. + */ + $state.get = function (stateOrName, context) { + if (!isDefined(stateOrName)) { + var list = []; + forEach(states, function(state) { list.push(state.self); }); + return list; + } + var state = findState(stateOrName, context); + return (state && state.self) ? state.self : null; + }; + + function resolveState(state, params, paramsAreFiltered, inherited, dst) { + // Make a restricted $stateParams with only the parameters that apply to this state if + // necessary. In addition to being available to the controller and onEnter/onExit callbacks, + // we also need $stateParams to be available for any $injector calls we make during the + // dependency resolution process. + var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params, params); + var locals = { $stateParams: $stateParams }; + + // Resolve 'global' dependencies for the state, i.e. those not specific to a view. + // We're also including $stateParams in this; that way the parameters are restricted + // to the set that should be visible to the state, and are independent of when we update + // the global $state and $stateParams values. + dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); + var promises = [ dst.resolve.then(function (globals) { + dst.globals = globals; + }) ]; + if (inherited) promises.push(inherited); + + // Resolve template and dependencies for all views. + forEach(state.views, function (view, name) { + var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); + injectables.$template = [ function () { + return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: false }) || ''; + }]; + + promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) { + // References to the controller (only instantiated at link time) + if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { + var injectLocals = angular.extend({}, injectables, locals); + result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); + } else { + result.$$controller = view.controller; + } + // Provide access to the state itself for internal use + result.$$state = state; + dst[name] = result; + })); + }); + + // Wait for all the promises and then return the activation object + return $q.all(promises).then(function (values) { + return dst; + }); + } + + return $state; + } + + function shouldTriggerReload(to, from, locals, options) { + if ( to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false)) ) { + return true; + } + } +} + +angular.module('ui.router.state') + .value('$stateParams', {}) + .provider('$state', $StateProvider); + + +$ViewProvider.$inject = []; +function $ViewProvider() { + + this.$get = $get; + /** + * @ngdoc object + * @name ui.router.state.$view + * + * @requires ui.router.util.$templateFactory + * @requires $rootScope + * + * @description + * + */ + $get.$inject = ['$rootScope', '$templateFactory']; + function $get( $rootScope, $templateFactory) { + return { + // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) + /** + * @ngdoc function + * @name ui.router.state.$view#load + * @methodOf ui.router.state.$view + * + * @description + * + * @param {string} name name + * @param {object} options option object. + */ + load: function load(name, options) { + var result, defaults = { + template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} + }; + options = extend(defaults, options); + + if (options.view) { + result = $templateFactory.fromConfig(options.view, options.params, options.locals); + } + if (result && options.notify) { + $rootScope.$broadcast('$viewContentLoading', options); + } + return result; + } + }; + } +} + +angular.module('ui.router.state').provider('$view', $ViewProvider); + +/** + * @ngdoc object + * @name ui.router.state.$uiViewScroll + * + * @requires $anchorScroll + * @requires $timeout + * + * @description + * When called with a jqLite element, it scrolls the element into view (after a + * `$timeout` so the DOM has time to refresh). + * + * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, + * this can be enabled by calling `$uiViewScrollProvider.useAnchorScroll()`. + */ +function $ViewScrollProvider() { + + var useAnchorScroll = false; + + this.useAnchorScroll = function () { + useAnchorScroll = true; + }; + + this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { + if (useAnchorScroll) { + return $anchorScroll; + } + + return function ($element) { + $timeout(function () { + $element[0].scrollIntoView(); + }, 0, false); + }; + }]; +} + +angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); + +/** + * @ngdoc directive + * @name ui.router.state.diretive.ui-view + * + * @requires ui.router.state.$state + * @requires $compile + * @requires $controller + * @requires $injector + * + * @restrict ECA + * + * @description + * The ui-view directive tells $state where to place your templates. + * A view can be unnamed or named. + * + * @param {string} ui-view A view name. + */ +$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$uiViewScroll', '$document']; +function $ViewDirective( $state, $compile, $controller, $injector, $uiViewScroll, $document) { + + function getService() { + return ($injector.has) ? function(service) { + return $injector.has(service) ? $injector.get(service) : null; + } : function(service) { + try { + return $injector.get(service); + } catch (e) { + return null; + } + }; + } + + var viewIsUpdating = false, + service = getService(), + $animator = service('$animator'), + $animate = service('$animate'); + + // Returns a set of DOM manipulation functions based on whether animation + // should be performed + function getRenderer(element, attrs, scope) { + var statics = function() { + return { + leave: function (element) { element.remove(); }, + enter: function (element, parent, anchor) { anchor.after(element); } + }; + }; + + if ($animate) { + return function(shouldAnimate) { + return !shouldAnimate ? statics() : { + enter: function(element, parent, anchor) { $animate.enter(element, null, anchor); }, + leave: function(element) { $animate.leave(element, function() { element.remove(); }); } + }; + }; + } + + if ($animator) { + var animate = $animator && $animator(scope, attrs); + + return function(shouldAnimate) { + return !shouldAnimate ? statics() : { + enter: function(element, parent, anchor) { animate.enter(element, parent); }, + leave: function(element) { animate.leave(element.contents(), element); } + }; + }; + } + + return statics; + } + + var directive = { + restrict: 'ECA', + compile: function (element, attrs) { + var initial = element.html(), + isDefault = true, + anchor = angular.element($document[0].createComment(' ui-view-anchor ')), + parentEl = element.parent(); + + element.prepend(anchor); + + return function ($scope) { + var inherited = parentEl.inheritedData('$uiView'); + + var currentScope, currentEl, viewLocals, + name = attrs[directive.name] || attrs.name || '', + onloadExp = attrs.onload || '', + autoscrollExp = attrs.autoscroll, + renderer = getRenderer(element, attrs, $scope); + + if (name.indexOf('@') < 0) name = name + '@' + (inherited ? inherited.state.name : ''); + var view = { name: name, state: null }; + + var eventHook = function () { + if (viewIsUpdating) return; + viewIsUpdating = true; + + try { updateView(true); } catch (e) { + viewIsUpdating = false; + throw e; + } + viewIsUpdating = false; + }; + + $scope.$on('$stateChangeSuccess', eventHook); + $scope.$on('$viewContentLoading', eventHook); + + updateView(false); + + function cleanupLastView() { + if (currentEl) { + renderer(true).leave(currentEl); + currentEl = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + } + + function updateView(shouldAnimate) { + var locals = $state.$current && $state.$current.locals[name]; + + if (isDefault) { + isDefault = false; + element.replaceWith(anchor); + } + + if (!locals) { + cleanupLastView(); + currentEl = element.clone(); + currentEl.html(initial); + renderer(shouldAnimate).enter(currentEl, parentEl, anchor); + + currentScope = $scope.$new(); + $compile(currentEl.contents())(currentScope); + return; + } + + if (locals === viewLocals) return; // nothing to do + + cleanupLastView(); + + currentEl = element.clone(); + currentEl.html(locals.$template ? locals.$template : initial); + renderer(true).enter(currentEl, parentEl, anchor); + + currentEl.data('$uiView', view); + + viewLocals = locals; + view.state = locals.$$state; + + var link = $compile(currentEl.contents()); + + currentScope = $scope.$new(); + + if (locals.$$controller) { + locals.$scope = currentScope; + var controller = $controller(locals.$$controller, locals); + currentEl.children().data('$ngControllerController', controller); + } + + link(currentScope); + + currentScope.$emit('$viewContentLoaded'); + if (onloadExp) currentScope.$eval(onloadExp); + + if (!angular.isDefined(autoscrollExp) || !autoscrollExp || $scope.$eval(autoscrollExp)) { + $uiViewScroll(currentEl); + } + } + }; + } + }; + + return directive; +} + +angular.module('ui.router.state').directive('uiView', $ViewDirective); + +function parseStateRef(ref) { + var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); + if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); + return { state: parsed[1], paramExpr: parsed[3] || null }; +} + +function stateContext(el) { + var stateData = el.parent().inheritedData('$uiView'); + + if (stateData && stateData.state && stateData.state.name) { + return stateData.state; + } +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref + * + * @requires ui.router.state.$state + * @requires $timeout + * + * @restrict A + * + * @description + * A directive that binds a link (`` tag) to a state. If the state has an associated + * URL, the directive will automatically generate & update the `href` attribute via + * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking + * the link will trigger a state transition with optional parameters. + * + * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be + * handled natively by the browser. + * + * You can also use relative state paths within ui-sref, just like the relative + * paths passed to `$state.go()`. You just need to be aware that the path is relative + * to the state that the link lives in, in other words the state that loaded the + * template containing the link. + * + * @example + *
    + * Home | About
    + *
    + * 
    + * 
    + * + * @param {string} ui-sref 'stateName' can be any valid absolute or relative state + */ +$StateRefDirective.$inject = ['$state', '$timeout']; +function $StateRefDirective($state, $timeout) { + return { + restrict: 'A', + require: '?^uiSrefActive', + link: function(scope, element, attrs, uiSrefActive) { + var ref = parseStateRef(attrs.uiSref); + var params = null, url = null, base = stateContext(element) || $state.$current; + var isForm = element[0].nodeName === "FORM"; + var attr = isForm ? "action" : "href", nav = true; + + var update = function(newVal) { + if (newVal) params = newVal; + if (!nav) return; + + var newHref = $state.href(ref.state, params, { relative: base }); + + if (uiSrefActive) { + uiSrefActive.$$setStateInfo(ref.state, params); + } + if (!newHref) { + nav = false; + return false; + } + element[0][attr] = newHref; + }; + + if (ref.paramExpr) { + scope.$watch(ref.paramExpr, function(newVal, oldVal) { + if (newVal !== params) update(newVal); + }, true); + params = scope.$eval(ref.paramExpr); + } + update(); + + if (isForm) return; + + element.bind("click", function(e) { + var button = e.which || e.button; + if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !element.attr('target')) { + // HACK: This is to allow ng-clicks to be processed before the transition is initiated: + $timeout(function() { + $state.go(ref.state, params, { relative: base }); + }); + e.preventDefault(); + } + }); + } + }; +} + +/** + * @ngdoc directive + * @name ui.router.state.directive:ui-sref-active + * + * @requires ui.router.state.$state + * @requires ui.router.state.$stateParams + * @requires $interpolate + * + * @restrict A + * + * @description + * A directive working alongside ui-sref to add classes to an element when the + * related ui-sref directive's state is active, and removing them when it is inactive. + * The primary use-case is to simplify the special appearance of navigation menus + * relying on `ui-sref`, by having the "active" state's menu button appear different, + * distinguishing it from the inactive menu items. + * + * @example + *
    + * 
    + * 
    + */ +$StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; +function $StateActiveDirective($state, $stateParams, $interpolate) { + return { + restrict: "A", + controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { + var state, params, activeClass; + + // There probably isn't much point in $observing this + activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope); + + // Allow uiSref to communicate with uiSrefActive + this.$$setStateInfo = function(newState, newParams) { + state = $state.get(newState, stateContext($element)); + params = newParams; + update(); + }; + + $scope.$on('$stateChangeSuccess', update); + + // Update route state + function update() { + if ($state.$current.self === state && matchesParams()) { + $element.addClass(activeClass); + } else { + $element.removeClass(activeClass); + } + } + + function matchesParams() { + return !params || equalForKeys(params, $stateParams); + } + }] + }; +} + +angular.module('ui.router.state') + .directive('uiSref', $StateRefDirective) + .directive('uiSrefActive', $StateActiveDirective); + +/** + * @ngdoc filter + * @name ui.router.state.filter:isState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#is $state.is("stateName")}. + */ +$IsStateFilter.$inject = ['$state']; +function $IsStateFilter($state) { + return function(state) { + return $state.is(state); + }; +} + +/** + * @ngdoc filter + * @name ui.router.state.filter:includeByState + * + * @requires ui.router.state.$state + * + * @description + * Translates to {@link ui.router.state.$state#includes $state.includes()}. + */ +$IncludedByStateFilter.$inject = ['$state']; +function $IncludedByStateFilter($state) { + return function(state) { + return $state.includes(state); + }; +} + +angular.module('ui.router.state') + .filter('isState', $IsStateFilter) + .filter('includedByState', $IncludedByStateFilter); + +/** + * @ngdoc object + * @name ui.router.compat.$routeProvider + * + * @requires ui.router.state.$stateProvider + * @requires ui.router.router.$urlRouterProvider + * + * @description + * `$routeProvider` of the `ui.router.compat` module overwrites the existing + * `routeProvider` from the core. This is done to provide compatibility between + * the UI Router and the core router. + * + * It also provides a `when()` method to register routes that map to certain urls. + * Behind the scenes it actually delegates either to + * {@link ui.router.router.$urlRouterProvider $urlRouterProvider} or to the + * {@link ui.router.state.$stateProvider $stateProvider} to postprocess the given + * router definition object. + */ +$RouteProvider.$inject = ['$stateProvider', '$urlRouterProvider']; +function $RouteProvider( $stateProvider, $urlRouterProvider) { + + var routes = []; + + onEnterRoute.$inject = ['$$state']; + function onEnterRoute( $$state) { + /*jshint validthis: true */ + this.locals = $$state.locals.globals; + this.params = this.locals.$stateParams; + } + + function onExitRoute() { + /*jshint validthis: true */ + this.locals = null; + this.params = null; + } + + this.when = when; + /** + * @ngdoc function + * @name ui.router.compat.$routeProvider#when + * @methodOf ui.router.compat.$routeProvider + * + * @description + * Registers a route with a given route definition object. The route definition + * object has the same interface the angular core route definition object has. + * + * @example + *
    +   * var app = angular.module('app', ['ui.router.compat']);
    +   *
    +   * app.config(function ($routeProvider) {
    +   *   $routeProvider.when('home', {
    +   *     controller: function () { ... },
    +   *     templateUrl: 'path/to/template'
    +   *   });
    +   * });
    +   * 
    + * + * @param {string} url URL as string + * @param {object} route Route definition object + * + * @return {object} $routeProvider - $routeProvider instance + */ + function when(url, route) { + /*jshint validthis: true */ + if (route.redirectTo != null) { + // Redirect, configure directly on $urlRouterProvider + var redirect = route.redirectTo, handler; + if (isString(redirect)) { + handler = redirect; // leave $urlRouterProvider to handle + } else if (isFunction(redirect)) { + // Adapt to $urlRouterProvider API + handler = function (params, $location) { + return redirect(params, $location.path(), $location.search()); + }; + } else { + throw new Error("Invalid 'redirectTo' in when()"); + } + $urlRouterProvider.when(url, handler); + } else { + // Regular route, configure as state + $stateProvider.state(inherit(route, { + parent: null, + name: 'route:' + encodeURIComponent(url), + url: url, + onEnter: onEnterRoute, + onExit: onExitRoute + })); + } + routes.push(route); + return this; + } + + /** + * @ngdoc object + * @name ui.router.compat.$route + * + * @requires ui.router.state.$state + * @requires $rootScope + * @requires $routeParams + * + * @property {object} routes - Array of registered routes. + * @property {object} params - Current route params as object. + * @property {string} current - Name of the current route. + * + * @description + * The `$route` service provides interfaces to access defined routes. It also let's + * you access route params through `$routeParams` service, so you have fully + * control over all the stuff you would actually get from angular's core `$route` + * service. + */ + this.$get = $get; + $get.$inject = ['$state', '$rootScope', '$routeParams']; + function $get( $state, $rootScope, $routeParams) { + + var $route = { + routes: routes, + params: $routeParams, + current: undefined + }; + + function stateAsRoute(state) { + return (state.name !== '') ? state : undefined; + } + + $rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) { + $rootScope.$broadcast('$routeChangeStart', stateAsRoute(to), stateAsRoute(from)); + }); + + $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { + $route.current = stateAsRoute(to); + $rootScope.$broadcast('$routeChangeSuccess', stateAsRoute(to), stateAsRoute(from)); + copy(toParams, $route.params); + }); + + $rootScope.$on('$stateChangeError', function (ev, to, toParams, from, fromParams, error) { + $rootScope.$broadcast('$routeChangeError', stateAsRoute(to), stateAsRoute(from), error); + }); + + return $route; + } +} + +angular.module('ui.router.compat') + .provider('$route', $RouteProvider) + .directive('ngView', $ViewDirective); +})(window, window.angular); +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.10.0 - 2014-01-13 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition, $timeout) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(this.groups.indexOf(group), 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', ['$parse', function($parse) { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + var getIsOpen, setIsOpen; + + accordionCtrl.addGroup(scope); + + scope.isOpen = false; + + if ( attrs.isOpen ) { + getIsOpen = $parse(attrs.isOpen); + setIsOpen = getIsOpen.assign; + + scope.$parent.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + if ( setIsOpen ) { + setIsOpen(scope.$parent, value); + } + }); + } + }; +}]) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + compile: function(element, attr, transclude) { + return function link(scope, element, attr, accordionGroupCtrl) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + }; + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
    +// +// ... +//
    +.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module("ui.bootstrap.alert", []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '=', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + if (!element.hasClass(buttonsCtrl.activeClass)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) { + var self = this, + slides = self.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? "next" : "prev"; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.select = function(slide) { + self.select(slide); + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.slides = function() { + return slides; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
    + + + + + + +
    +
    +
      +
    • + + {{$index}}: {{slide.text}} +
    • +
    + Add Slide +
    +
    + Interval, in milliseconds: +
    Enter a negative number to stop the interval. +
    +
    +
    +
    + +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; + var slides = $scope.slides = []; + $scope.addSlide = function() { + var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150); + slides.push({ + image: 'http://placekitten.com/' + newWidth + '/200', + text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' + ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4] + }); + }; + for (var i=0; i<4; i++) $scope.addSlide(); +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
    +*/ + +.directive('slide', ['$parse', function($parse) { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + }, + link: function (scope, element, attrs, carouselCtrl) { + //Set up optional 'active' = binding + if (attrs.active) { + var getActive = $parse(attrs.active); + var setActive = getActive.assign; + var lastValue = scope.active = getActive(scope.$parent); + scope.$watch(function parentActiveWatch() { + var parentActive = getActive(scope.$parent); + + if (parentActive !== scope.active) { + // we are out of sync and need to copy + if (parentActive !== lastValue) { + // parent changed and it has precedence + lastValue = scope.active = parentActive; + } else { + // if the parent can be assigned then do so + setActive(scope.$parent, parentActive = lastValue = scope.active); + } + } + return parentActive; + }); + } + + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, "position") || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position']) + +.constant('datepickerConfig', { + dayFormat: 'dd', + monthFormat: 'MMMM', + yearFormat: 'yyyy', + dayHeaderFormat: 'EEE', + dayTitleFormat: 'MMMM yyyy', + monthTitleFormat: 'yyyy', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) { + var format = { + day: getValue($attrs.dayFormat, dtConfig.dayFormat), + month: getValue($attrs.monthFormat, dtConfig.monthFormat), + year: getValue($attrs.yearFormat, dtConfig.yearFormat), + dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat), + dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat), + monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat) + }, + startingDay = getValue($attrs.startingDay, dtConfig.startingDay), + yearRange = getValue($attrs.yearRange, dtConfig.yearRange); + + this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null; + this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null; + + function getValue(value, defaultValue) { + return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue; + } + + function getDaysInMonth( year, month ) { + return new Date(year, month, 0).getDate(); + } + + function getDates(startDate, n) { + var dates = new Array(n); + var current = startDate, i = 0; + while (i < n) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + function makeDate(date, format, isSelected, isSecondary) { + return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary }; + } + + this.modes = [ + { + name: 'day', + getVisibleDates: function(date, selected) { + var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1); + var difference = startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth), numDates = 0; + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + numDates += numDisplayedFromPreviousMonth; // Previous + } + numDates += getDaysInMonth(year, month + 1); // Current + numDates += (7 - numDates % 7) % 7; // Next + + var days = getDates(firstDate, numDates), labels = new Array(7); + for (var i = 0; i < numDates; i ++) { + var dt = new Date(days[i]); + days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month); + } + for (var j = 0; j < 7; j++) { + labels[j] = dateFilter(days[j].date, format.dayHeader); + } + return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels }; + }, + compare: function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }, + split: 7, + step: { months: 1 } + }, + { + name: 'month', + getVisibleDates: function(date, selected) { + var months = new Array(12), year = date.getFullYear(); + for ( var i = 0; i < 12; i++ ) { + var dt = new Date(year, i, 1); + months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year)); + } + return { objects: months, title: dateFilter(date, format.monthTitle) }; + }, + compare: function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }, + split: 3, + step: { years: 1 } + }, + { + name: 'year', + getVisibleDates: function(date, selected) { + var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1; + for ( var i = 0; i < yearRange; i++ ) { + var dt = new Date(startYear + i, 0, 1); + years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear())); + } + return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') }; + }, + compare: function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }, + split: 5, + step: { years: yearRange } + } + ]; + + this.isDisabled = function(date, mode) { + var currentMode = this.modes[mode || 0]; + return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name}))); + }; +}]) + +.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModel = ctrls[1]; + + if (!ngModel) { + return; // do nothing if no ng-model + } + + // Configuration parameters + var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks; + + if (attrs.showWeeks) { + scope.$parent.$watch($parse(attrs.showWeeks), function(value) { + showWeeks = !! value; + updateShowWeekNumbers(); + }); + } else { + updateShowWeekNumbers(); + } + + if (attrs.min) { + scope.$parent.$watch($parse(attrs.min), function(value) { + datepickerCtrl.minDate = value ? new Date(value) : null; + refill(); + }); + } + if (attrs.max) { + scope.$parent.$watch($parse(attrs.max), function(value) { + datepickerCtrl.maxDate = value ? new Date(value) : null; + refill(); + }); + } + + function updateShowWeekNumbers() { + scope.showWeekNumbers = mode === 0 && showWeeks; + } + + // Split array into smaller arrays + function split(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + } + + function refill( updateSelected ) { + var date = null, valid = true; + + if ( ngModel.$modelValue ) { + date = new Date( ngModel.$modelValue ); + + if ( isNaN(date) ) { + valid = false; + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else if ( updateSelected ) { + selected = date; + } + } + ngModel.$setValidity('date', valid); + + var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date); + angular.forEach(data.objects, function(obj) { + obj.disabled = datepickerCtrl.isDisabled(obj.date, mode); + }); + + ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date))); + + scope.rows = split(data.objects, currentMode.split); + scope.labels = data.labels || []; + scope.title = data.title; + } + + function setMode(value) { + mode = value; + updateShowWeekNumbers(); + refill(); + } + + ngModel.$render = function() { + refill( true ); + }; + + scope.select = function( date ) { + if ( mode === 0 ) { + var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModel.$setViewValue( dt ); + refill( true ); + } else { + selected = date; + setMode( mode - 1 ); + } + }; + scope.move = function(direction) { + var step = datepickerCtrl.modes[mode].step; + selected.setMonth( selected.getMonth() + direction * (step.months || 0) ); + selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) ); + refill(); + }; + scope.toggleMode = function() { + setMode( (mode + 1) % datepickerCtrl.modes.length ); + }; + scope.getWeekNumber = function(row) { + return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null; + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + } + }; +}]) + +.constant('datepickerPopupConfig', { + dateFormat: 'yyyy-MM-dd', + currentText: 'Today', + toggleWeeksText: 'Weeks', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig', +function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) { + return { + restrict: 'EA', + require: 'ngModel', + link: function(originalScope, element, attrs, ngModel) { + var scope = originalScope.$new(), // create a child scope so we are not polluting original one + dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.dateFormat; + ngModel.$render(); + }); + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + originalScope.$on('$destroy', function() { + $popup.remove(); + scope.$destroy(); + }); + + attrs.$observe('currentText', function(text) { + scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText; + }); + attrs.$observe('toggleWeeksText', function(text) { + scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText; + }); + attrs.$observe('clearText', function(text) { + scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText; + }); + attrs.$observe('closeText', function(text) { + scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText; + }); + + var getIsOpen, setIsOpen; + if ( attrs.isOpen ) { + getIsOpen = $parse(attrs.isOpen); + setIsOpen = getIsOpen.assign; + + originalScope.$watch(getIsOpen, function updateOpen(value) { + scope.isOpen = !! value; + }); + } + scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state + + function setOpen( value ) { + if (setIsOpen) { + setIsOpen(originalScope, !!value); + } else { + scope.isOpen = !!value; + } + } + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + setOpen(false); + }); + } + }; + + var elementFocusBind = function() { + scope.$apply(function() { + setOpen( true ); + }); + }; + + // popup element used to display calendar + var popupEl = angular.element('
    '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + var datepickerEl = angular.element(popupEl.children()[0]), + datepickerOptions = {}; + if (attrs.datepickerOptions) { + datepickerOptions = originalScope.$eval(attrs.datepickerOptions); + datepickerEl.attr(angular.extend({}, datepickerOptions)); + } + + // TODO: reverse from dateFilter string to Date object + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if (closeOnDateSelection) { + setOpen( false ); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = ngModel.$modelValue; + }; + + function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) { + if (attribute) { + originalScope.$watch($parse(attribute), function(value){ + scope[scopeProperty] = value; + }); + datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty); + } + } + addWatchableAttribute(attrs.min, 'min'); + addWatchableAttribute(attrs.max, 'max'); + if (attrs.showWeeks) { + addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks'); + } else { + scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks; + datepickerEl.attr('show-weeks', 'showWeeks'); + } + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', attrs.dateDisabled); + } + + function updatePosition() { + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + } + + var documentBindingInitialized = false, elementFocusInitialized = false; + scope.$watch('isOpen', function(value) { + if (value) { + updatePosition(); + $document.bind('click', documentClickBind); + if(elementFocusInitialized) { + element.unbind('focus', elementFocusBind); + } + element[0].focus(); + documentBindingInitialized = true; + } else { + if(documentBindingInitialized) { + $document.unbind('click', documentClickBind); + } + element.bind('focus', elementFocusBind); + elementFocusInitialized = true; + } + + if ( setIsOpen ) { + setIsOpen(originalScope, value); + } + }); + + scope.today = function() { + scope.dateSelection(new Date()); + }; + scope.clear = function() { + scope.dateSelection(null); + }; + + var $popup = $compile(popupEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +/* + * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js + * @restrict class or attribute + * @example: + + */ + +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) { + var openElement = null, + closeMenu = angular.noop; + return { + restrict: 'CA', + link: function(scope, element, attrs) { + scope.$watch('$location.path', function() { closeMenu(); }); + element.parent().bind('click', function() { closeMenu(); }); + element.bind('click', function (event) { + + var elementWasOpen = (element === openElement); + + event.preventDefault(); + event.stopPropagation(); + + if (!!openElement) { + closeMenu(); + } + + if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { + element.parent().addClass('open'); + openElement = element; + closeMenu = function (event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + $document.unbind('click', closeMenu); + element.parent().removeClass('open'); + closeMenu = angular.noop; + openElement = null; + }; + $document.bind('click', closeMenu); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: 'template/modal/window.html', + link: function (scope, element, attrs) { + scope.windowClass = attrs.windowClass || ''; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + // focus a freshly-opened modal + element[0].focus(); + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating, 0); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + backdropDomEl = $compile('
    ')(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
    '); + angularDomEl.attr('window-class', modal.windowClass); + angularDomEl.attr('index', openedWindows.length() - 1); + angularDomEl.attr('animate', 'animate'); + angularDomEl.html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) { + var self = this, + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(defaultItemsPerPage) { + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = defaultItemsPerPage; + } + }; + + this.noPrevious = function() { + return this.page === 1; + }; + this.noNext = function() { + return this.page === $scope.totalPages; + }; + + this.isActive = function(page) { + return this.page === page; + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.getAttributeValue = function(attribute, defaultValue, interpolate) { + return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue; + }; + + this.render = function() { + this.page = parseInt($scope.page, 10) || 1; + if (this.page > 0 && this.page <= $scope.totalPages) { + $scope.pages = this.getPages(this.page, $scope.totalPages); + } + }; + + $scope.selectPage = function(page) { + if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) { + $scope.page = page; + $scope.onSelectPage({ page: page }); + } + }; + + $scope.$watch('page', function() { + self.render(); + }); + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( self.page > value ) { + $scope.selectPage(value); + } else { + self.render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) { + return { + restrict: 'EA', + scope: { + page: '=', + totalItems: '=', + onSelectPage:' &' + }, + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, paginationCtrl) { + + // Setup configuration parameters + var maxSize, + boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ), + directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ), + firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true), + previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), + nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), + lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true), + rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate); + + paginationCtrl.init(config.itemsPerPage); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive, isDisabled) { + return { + number: number, + text: text, + active: isActive, + disabled: isDisabled + }; + } + + paginationCtrl.getPages = function(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, paginationCtrl.isActive(number), false); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false, false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false, false); + pages.push(nextPageSet); + } + } + + // Add previous & next links + if (directionLinks) { + var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious()); + pages.unshift(previousPage); + + var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext()); + pages.push(nextPage); + } + + // Add first & last links + if (boundaryLinks) { + var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious()); + pages.unshift(firstPage); + + var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext()); + pages.push(lastPage); + } + + return pages; + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(config) { + return { + restrict: 'EA', + scope: { + page: '=', + totalItems: '=', + onSelectPage:' &' + }, + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, paginationCtrl) { + + // Setup configuration parameters + var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), + nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), + align = paginationCtrl.getAttributeValue(attrs.align, config.align); + + paginationCtrl.init(config.itemsPerPage); + + // Create page object used in template + function makePage(number, text, isDisabled, isPrevious, isNext) { + return { + number: number, + text: text, + disabled: isDisabled, + previous: ( align && isPrevious ), + next: ( align && isNext ) + }; + } + + paginationCtrl.getPages = function(currentPage) { + return [ + makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false), + makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true) + ]; + }; + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
    '+ + '
    '; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasRegisteredTriggers = false; + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function (){ + var position, + ttWidth, + ttHeight, + ttPosition; + // Get the position of the directive element. + position = appendToBody ? $position.offset( element ) : $position.position( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left + position.width + }; + break; + case 'bottom': + ttPosition = { + top: position.top + position.height, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + case 'left': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left - ttWidth + }; + break; + default: + ttPosition = { + top: position.top - ttHeight, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + } + + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + transitionTimeout = $timeout(removeTooltip, 500); + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function() { + if (hasRegisteredTriggers) { + element.unbind( triggers.show, showTooltipBind ); + element.unbind( triggers.hide, hideTooltipBind ); + } + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + + hasRegisteredTriggers = true; + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition']) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) { + var self = this, + bars = [], + max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.addBar = function(bar, element) { + var oldValue = 0, index = bar.$parent.$index; + if ( angular.isDefined(index) && bars[index] ) { + oldValue = bars[index].value; + } + bars.push(bar); + + this.update(element, bar.value, oldValue); + + bar.$watch('value', function(value, oldValue) { + if (value !== oldValue) { + self.update(element, value, oldValue); + } + }); + + bar.$on('$destroy', function() { + self.removeBar(bar); + }); + }; + + // Update bar element width + this.update = function(element, newValue, oldValue) { + var percent = this.getPercentage(newValue); + + if (animate) { + element.css('width', this.getPercentage(oldValue) + '%'); + $transition(element, {width: percent + '%'}); + } else { + element.css({'transition': 'none', 'width': percent + '%'}); + } + }; + + this.removeBar = function(bar) { + bars.splice(bars.indexOf(bar), 1); + }; + + this.getPercentage = function(value) { + return Math.round(100 * value / max); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + template: '
    ' + //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2 + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) { + + this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max; + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + this.createRateObjects = function(states) { + var defaultOptions = { + stateOn: this.stateOn, + stateOff: this.stateOff + }; + + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, defaultOptions, states[i]); + } + return states; + }; + + // Get objects used in template + $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange)); + + $scope.rate = function(value) { + if ( $scope.value !== value && !$scope.readonly ) { + $scope.value = value; + } + }; + + $scope.enter = function(value) { + if ( ! $scope.readonly ) { + $scope.val = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.val = angular.copy($scope.value); + $scope.onLeave(); + }; + + $scope.$watch('value', function(value) { + $scope.val = value; + }); + + $scope.readonly = false; + if ($attrs.readonly) { + $scope.$parent.$watch($parse($attrs.readonly), function(value) { + $scope.readonly = !!value; + }); + } +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + scope: { + value: '=', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(tab) { + angular.forEach(tabs, function(tab) { + tab.active = false; + }); + tab.active = true; + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + if (tabs.length === 1 || tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
    + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
    +
    + */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: {}, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs'; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
    + + +
    + + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
    +
    + + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
    + */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + var getActive, setActive; + if (attrs.active) { + getActive = $parse(attrs.active); + setActive = getActive.assign; + scope.$parent.$watch(getActive, function updateActive(value, oldVal) { + // Avoid re-initializing scope.active as it is already initialized + // below. (watcher is called async during init with value === + // oldVal) + if (value !== oldVal) { + scope.active = !!value; + } + }); + scope.active = getActive(scope.$parent); + } else { + setActive = getActive = angular.noop; + } + + scope.$watch('active', function(active) { + // Note this watcher also initializes and assigns scope.active to the + // attrs.active expression. + setActive(scope.$parent, active); + if (active) { + tabsetCtrl.select(scope); + scope.onSelect(); + } else { + scope.onDeselect(); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( ! scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) { + return { + restrict: 'EA', + require:'?^ngModel', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ngModel) { + if ( !ngModel ) { + return; // do nothing if no ng-model + } + + var selected = new Date(), + meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + var hourStep = timepickerConfig.hourStep; + if (attrs.hourStep) { + scope.$parent.$watch($parse(attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if (attrs.minuteStep) { + scope.$parent.$watch($parse(attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + scope.showMeridian = timepickerConfig.showMeridian; + if (attrs.showMeridian) { + scope.$parent.$watch($parse(attrs.showMeridian), function(value) { + scope.showMeridian = !!value; + + if ( ngModel.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( scope.hours, 10 ); + var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt(scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Input elements + var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1); + + // Respond on mousewheel spin + var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() ); + e.preventDefault(); + }); + } + + scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput; + if ( ! scope.readonlyInput ) { + + var invalidate = function(invalidHours, invalidMinutes) { + ngModel.$setViewValue( null ); + ngModel.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + scope.invalidMinutes = invalidMinutes; + } + }; + + scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !scope.validHours && scope.hours < 10) { + scope.$apply( function() { + scope.hours = pad( scope.hours ); + }); + } + }); + + scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !scope.invalidMinutes && scope.minutes < 10 ) { + scope.$apply( function() { + scope.minutes = pad( scope.minutes ); + }); + } + }); + } else { + scope.updateHours = angular.noop; + scope.updateMinutes = angular.noop; + } + + ngModel.$render = function() { + var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModel.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModel.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModel.$setValidity('time', true); + scope.invalidHours = false; + scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + scope.hours = keyboardChange === 'h' ? hours : pad(hours); + scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; + } + }; +}]); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source; + if (!match) { + throw new Error( + "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + + " but got '" + input + "'."); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //pop-up element used to display matches + var popUpEl = angular.element('
    '); + popUpEl.attr({ + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + }; + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + if (inputValue === modelCtrl.$viewValue && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + if (timeoutPromise) { + $timeout.cancel(timeoutPromise);//cancel previous timeout + } + timeoutPromise = $timeout(function () { + getMatchesAsync(inputValue); + }, waitTime); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a mach was selected via a mouse click event + element[0].focus(); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); + } + + return function(matchItem, query) { + return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion-group.html", + "
    \n" + + "
    \n" + + "

    \n" + + " {{heading}}\n" + + "

    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + "
    "); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion.html", + "
    "); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/alert/alert.html", + "
    \n" + + " \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/carousel.html", + "
    \n" + + "
      1\">\n" + + "
    1. \n" + + "
    \n" + + "
    \n" + + " 1\">\n" + + " 1\">\n" + + "
    \n" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/slide.html", + "
    \n" + + ""); +}]); + +angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/datepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\" class=\"h6\">\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
    #{{label}}
    {{ getWeekNumber(row) }}\n" + + " \n" + + "
    \n" + + ""); +}]); + +angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/popup.html", + "
      \n" + + "
    • \n" + + "
    • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
    • \n" + + "
    \n" + + ""); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
    "); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
    \n" + + "
    \n" + + "
    "); +}]); + +angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pager.html", + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pagination.html", + ""); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
    \n" + + "
    \n" + + "\n" + + "
    \n" + + "

    \n" + + "
    \n" + + "
    \n" + + "
    \n" + + ""); +}]); + +angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/bar.html", + "
    "); +}]); + +angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progress.html", + "
    "); +}]); + +angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progressbar.html", + "
    "); +}]); + +angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/rating/rating.html", + "\n" + + " \n" + + ""); +}]); + +angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tab.html", + "
  • \n" + + " {{heading}}\n" + + "
  • \n" + + ""); +}]); + +angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset-titles.html", + "
      \n" + + "
    \n" + + ""); +}]); + +angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset.html", + "\n" + + "
    \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
       
      \n" + + " \n" + + " :\n" + + " \n" + + "
       
      \n" + + ""); +}]); + +angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-match.html", + ""); +}]); + +angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-popup.html", + "
        \n" + + "
      • \n" + + "
        \n" + + "
      • \n" + + "
      "); +}]); + +/* + @license Angular Treeview version 0.1.6 + ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview + License: MIT + + + [TREE attribute] + angular-treeview: the treeview directive + tree-id : each tree's unique id. + tree-model : the tree model on $scope. + node-id : each node's id + node-label : each node's label + node-children: each node's children + +
      +
      +*/ + +(function ( angular ) { + 'use strict'; + + angular.module( 'angularTreeview', [] ).directive( 'treeModel', ['$compile', function( $compile ) { + return { + restrict: 'A', + link: function ( scope, element, attrs ) { + //tree id + var treeId = attrs.treeId; + + //tree model + var treeModel = attrs.treeModel; + + //node id + var nodeId = attrs.nodeId || 'id'; + + //node label + var nodeLabel = attrs.nodeLabel || 'label'; + + //children + var nodeChildren = attrs.nodeChildren || 'children'; + + //tree template + var template = + '
        ' + + '
      • ' + + '' + + '' + + ' ' + + '{{node.' + nodeLabel + '}}' + + '
        ' + + '
      • ' + + '
      '; + + + //check tree id, tree model + if( treeId && treeModel ) { + + //root node + if( attrs.angularTreeview ) { + + //create tree object if not exists + scope[treeId] = scope[treeId] || {}; + + //if node head clicks, + scope[treeId].selectNodeHead = scope[treeId].selectNodeHead || function( selectedNode ){ + + //Collapse or Expand + selectedNode.collapsed = !selectedNode.collapsed; + }; + + //if node label clicks, + scope[treeId].selectNodeLabel = scope[treeId].selectNodeLabel || function( selectedNode ){ + + //remove highlight from previous node + if( scope[treeId].currentNode && scope[treeId].currentNode.selected ) { + scope[treeId].currentNode.selected = undefined; + } + + //set highlight to selected node + selectedNode.selected = 'selected'; + + //set currentNode + scope[treeId].currentNode = selectedNode; + }; + } + + //Rendering template. + element.html('').append( $compile( template )( scope ) ); + } + } + }; + }]); +})( angular ); + +(function(angular, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + define(['angular'], function(angular) { + return factory(angular); + }); + } else { + return factory(angular); + } +}(angular || null, function(angular) { + 'use strict'; +/** + * ngTable: Table + Angular JS + * + * @author Vitalii Savchuk + * @url https://github.com/esvit/ng-table/ + * @license New BSD License + */ + +/** + * @ngdoc module + * @name ngTable + * @description ngTable: Table + Angular JS + * @example + + + + + + + + +
      {{user.name}}{{user.age}}
      +
      +
      + */ +var app = angular.module('ngTable', []); +/** + * ngTable: Table + Angular JS + * + * @author Vitalii Savchuk + * @url https://github.com/esvit/ng-table/ + * @license New BSD License + */ + +/** + * @ngdoc service + * @name ngTable.factory:ngTableParams + * @description Parameters manager for ngTable + */ +app.factory('ngTableParams', ['$q', '$log', function ($q, $log) { + var isNumber = function (n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + var ngTableParams = function (baseParameters, baseSettings) { + var self = this, + log = function () { + if (settings.debugMode && $log.debug) { + $log.debug.apply(this, arguments); + } + }; + + this.data = []; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#parameters + * @methodOf ngTable.factory:ngTableParams + * @description Set new parameters or get current parameters + * + * @param {string} newParameters New parameters + * @param {string} parseParamsFromUrl Flag if parse parameters like in url + * @returns {Object} Current parameters or `this` + */ + this.parameters = function (newParameters, parseParamsFromUrl) { + parseParamsFromUrl = parseParamsFromUrl || false; + if (angular.isDefined(newParameters)) { + for (var key in newParameters) { + var value = newParameters[key]; + if (parseParamsFromUrl && key.indexOf('[') >= 0) { + var keys = key.split(/\[(.*)\]/).reverse() + var lastKey = ''; + for (var i = 0, len = keys.length; i < len; i++) { + var name = keys[i]; + if (name !== '') { + var v = value; + value = {}; + value[lastKey = name] = (isNumber(v) ? parseFloat(v) : v); + } + } + if (lastKey === 'sorting') { + params[lastKey] = {}; + } + params[lastKey] = angular.extend(params[lastKey] || {}, value[lastKey]); + } else { + params[key] = (isNumber(newParameters[key]) ? parseFloat(newParameters[key]) : newParameters[key]); + } + } + log('ngTable: set parameters', params); + return this; + } + return params; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#settings + * @methodOf ngTable.factory:ngTableParams + * @description Set new settings for table + * + * @param {string} newSettings New settings or undefined + * @returns {Object} Current settings or `this` + */ + this.settings = function (newSettings) { + if (angular.isDefined(newSettings)) { + if (angular.isArray(newSettings.data)) { + //auto-set the total from passed in data + newSettings.total = newSettings.data.length; + } + settings = angular.extend(settings, newSettings); + log('ngTable: set settings', settings); + return this; + } + return settings; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#page + * @methodOf ngTable.factory:ngTableParams + * @description If parameter page not set return current page else set current page + * + * @param {string} page Page number + * @returns {Object|Number} Current page or `this` + */ + this.page = function (page) { + return angular.isDefined(page) ? this.parameters({'page': page}) : params.page; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#total + * @methodOf ngTable.factory:ngTableParams + * @description If parameter total not set return current quantity else set quantity + * + * @param {string} total Total quantity of items + * @returns {Object|Number} Current page or `this` + */ + this.total = function (total) { + return angular.isDefined(total) ? this.settings({'total': total}) : settings.total; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#count + * @methodOf ngTable.factory:ngTableParams + * @description If parameter count not set return current count per page else set count per page + * + * @param {string} count Count per number + * @returns {Object|Number} Count per page or `this` + */ + this.count = function (count) { + // reset to first page because can be blank page + return angular.isDefined(count) ? this.parameters({'count': count, 'page': 1}) : params.count; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#filter + * @methodOf ngTable.factory:ngTableParams + * @description If parameter page not set return current filter else set current filter + * + * @param {string} filter New filter + * @returns {Object} Current filter or `this` + */ + this.filter = function (filter) { + return angular.isDefined(filter) ? this.parameters({'filter': filter}) : params.filter; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#sorting + * @methodOf ngTable.factory:ngTableParams + * @description If 'sorting' parameter is not set, return current sorting. Otherwise set current sorting. + * + * @param {string} sorting New sorting + * @returns {Object} Current sorting or `this` + */ + this.sorting = function (sorting) { + if (arguments.length == 2) { + var sortArray = {}; + sortArray[sorting] = arguments[1]; + this.parameters({'sorting': sortArray}); + return this; + } + return angular.isDefined(sorting) ? this.parameters({'sorting': sorting}) : params.sorting; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#isSortBy + * @methodOf ngTable.factory:ngTableParams + * @description Checks sort field + * + * @param {string} field Field name + * @param {string} direction Direction of sorting 'asc' or 'desc' + * @returns {Array} Return true if field sorted by direction + */ + this.isSortBy = function (field, direction) { + return angular.isDefined(params.sorting[field]) && params.sorting[field] == direction; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#orderBy + * @methodOf ngTable.factory:ngTableParams + * @description Return object of sorting parameters for angular filter + * + * @returns {Array} Array like: [ '-name', '+age' ] + */ + this.orderBy = function () { + var sorting = []; + for (var column in params.sorting) { + sorting.push((params.sorting[column] === "asc" ? "+" : "-") + column); + } + return sorting; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#getData + * @methodOf ngTable.factory:ngTableParams + * @description Called when updated some of parameters for get new data + * + * @param {Object} $defer promise object + * @param {Object} params New parameters + */ + this.getData = function ($defer, params) { + if (angular.isArray(this.data) && angular.isObject(params)) { + $defer.resolve(this.data.slice((params.page() - 1) * params.count(), params.page() * params.count())); + } else { + $defer.resolve([]); + } + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#getGroups + * @methodOf ngTable.factory:ngTableParams + * @description Return groups for table grouping + */ + this.getGroups = function ($defer, column) { + var defer = $q.defer(); + + defer.promise.then(function (data) { + var groups = {}; + angular.forEach(data, function (item) { + var groupName = angular.isFunction(column) ? column(item) : item[column]; + + groups[groupName] = groups[groupName] || { + data: [] + }; + groups[groupName]['value'] = groupName; + groups[groupName].data.push(item); + }); + var result = []; + for (var i in groups) { + result.push(groups[i]); + } + log('ngTable: refresh groups', result); + $defer.resolve(result); + }); + this.getData(defer, self); + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#generatePagesArray + * @methodOf ngTable.factory:ngTableParams + * @description Generate array of pages + * + * @param {boolean} currentPage which page must be active + * @param {boolean} totalItems Total quantity of items + * @param {boolean} pageSize Quantity of items on page + * @returns {Array} Array of pages + */ + this.generatePagesArray = function (currentPage, totalItems, pageSize) { + var maxBlocks, maxPage, maxPivotPages, minPage, numPages, pages; + maxBlocks = 11; + pages = []; + numPages = Math.ceil(totalItems / pageSize); + if (numPages > 1) { + pages.push({ + type: 'prev', + number: Math.max(1, currentPage - 1), + active: currentPage > 1 + }); + pages.push({ + type: 'first', + number: 1, + active: currentPage > 1 + }); + maxPivotPages = Math.round((maxBlocks - 5) / 2); + minPage = Math.max(2, currentPage - maxPivotPages); + maxPage = Math.min(numPages - 1, currentPage + maxPivotPages * 2 - (currentPage - minPage)); + minPage = Math.max(2, minPage - (maxPivotPages * 2 - (maxPage - minPage))); + var i = minPage; + while (i <= maxPage) { + if ((i === minPage && i !== 2) || (i === maxPage && i !== numPages - 1)) { + pages.push({ + type: 'more', + active: false + }); + } else { + pages.push({ + type: 'page', + number: i, + active: currentPage !== i + }); + } + i++; + } + pages.push({ + type: 'last', + number: numPages, + active: currentPage !== numPages + }); + pages.push({ + type: 'next', + number: Math.min(numPages, currentPage + 1), + active: currentPage < numPages + }); + } + return pages; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#url + * @methodOf ngTable.factory:ngTableParams + * @description Return groups for table grouping + * + * @param {boolean} asString flag indicates return array of string or object + * @returns {Array} If asString = true will be return array of url string parameters else key-value object + */ + this.url = function (asString) { + asString = asString || false; + var pairs = (asString ? [] : {}); + for (var key in params) { + if (params.hasOwnProperty(key)) { + var item = params[key], + name = encodeURIComponent(key); + if (typeof item === "object") { + for (var subkey in item) { + if (!angular.isUndefined(item[subkey]) && item[subkey] !== "") { + var pname = name + "[" + encodeURIComponent(subkey) + "]"; + if (asString) { + pairs.push(pname + "=" + item[subkey]); + } else { + pairs[pname] = item[subkey]; + } + } + } + } else if (!angular.isFunction(item) && !angular.isUndefined(item) && item !== "") { + if (asString) { + pairs.push(name + "=" + encodeURIComponent(item)); + } else { + pairs[name] = encodeURIComponent(item); + } + } + } + } + return pairs; + }; + + /** + * @ngdoc method + * @name ngTable.factory:ngTableParams#reload + * @methodOf ngTable.factory:ngTableParams + * @description Reload table data + */ + this.reload = function () { + var $defer = $q.defer(), + self = this; + + settings.$loading = true; + if (settings.groupBy) { + settings.getGroups($defer, settings.groupBy, this); + } else { + settings.getData($defer, this); + } + log('ngTable: reload data'); + $defer.promise.then(function (data) { + settings.$loading = false; + log('ngTable: current scope', settings.$scope); + if (settings.groupBy) { + self.data = settings.$scope.$groups = data; + } else { + self.data = settings.$scope.$data = data; + } + settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count()); + settings.$scope.$emit('ngTableAfterReloadData'); + }); + }; + + this.reloadPages = function () { + var self = this; + settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count()); + }; + + var params = this.$params = { + page: 1, + count: 1, + filter: {}, + sorting: {}, + group: {}, + groupBy: null + }; + var settings = { + $scope: null, // set by ngTable controller + $loading: false, + data: null, //allows data to be set when table is initialized + total: 0, + defaultSort: 'desc', + filterDelay: 750, + counts: [10, 25, 50, 100], + getGroups: this.getGroups, + getData: this.getData + }; + + this.settings(baseSettings); + this.parameters(baseParameters, true); + return this; + }; + return ngTableParams; +}]); + +/** + * ngTable: Table + Angular JS + * + * @author Vitalii Savchuk + * @url https://github.com/esvit/ng-table/ + * @license New BSD License + */ + +/** + * @ngdoc object + * @name ngTable.directive:ngTable.ngTableController + * + * @description + * Each {@link ngTable.directive:ngTable ngTable} directive creates an instance of `ngTableController` + */ +var ngTableController = ['$scope', 'ngTableParams', '$timeout', function ($scope, ngTableParams, $timeout) { + $scope.$loading = false; + + if (!$scope.params) { + $scope.params = new ngTableParams(); + } + $scope.params.settings().$scope = $scope; + + var delayFilter = (function () { + var timer = 0; + return function (callback, ms) { + $timeout.cancel(timer); + timer = $timeout(callback, ms); + }; + })(); + + $scope.$watch('params.$params', function (newParams, oldParams) { + $scope.params.settings().$scope = $scope; + + if (!angular.equals(newParams.filter, oldParams.filter)) { + delayFilter(function () { + $scope.params.$params.page = 1; + $scope.params.reload(); + }, $scope.params.settings().filterDelay); + } else { + $scope.params.reload(); + } + }, true); + + $scope.sortBy = function (column, event) { + var parsedSortable = $scope.parse(column.sortable); + if (!parsedSortable) { + return; + } + var defaultSort = $scope.params.settings().defaultSort; + var inverseSort = (defaultSort === 'asc' ? 'desc' : 'asc'); + var sorting = $scope.params.sorting() && $scope.params.sorting()[parsedSortable] && ($scope.params.sorting()[parsedSortable] === defaultSort); + var sortingParams = (event.ctrlKey || event.metaKey) ? $scope.params.sorting() : {}; + sortingParams[parsedSortable] = (sorting ? inverseSort : defaultSort); + $scope.params.parameters({ + sorting: sortingParams + }); + }; +}]; +/** + * ngTable: Table + Angular JS + * + * @author Vitalii Savchuk + * @url https://github.com/esvit/ng-table/ + * @license New BSD License + */ + +/** + * @ngdoc directive + * @name ngTable.directive:ngTable + * @restrict A + * + * @description + * Directive that instantiates {@link ngTable.directive:ngTable.ngTableController ngTableController}. + */ +app.directive('ngTable', ['$compile', '$q', '$parse', + function ($compile, $q, $parse) { + 'use strict'; + + return { + restrict: 'A', + priority: 1001, + scope: true, + controller: ngTableController, + compile: function (element) { + var columns = [], i = 0, row = null; + + // custom header + var thead = element.find('thead'); + + // IE 8 fix :not(.ng-table-group) selector + angular.forEach(angular.element(element.find('tr')), function (tr) { + tr = angular.element(tr); + if (!tr.hasClass('ng-table-group') && !row) { + row = tr; + } + }); + if (!row) { + return; + } + angular.forEach(row.find('td'), function (item) { + var el = angular.element(item); + if (el.attr('ignore-cell') && 'true' === el.attr('ignore-cell')) { + return; + } + var parsedAttribute = function (attr, defaultValue) { + return function (scope) { + return $parse(el.attr('x-data-' + attr) || el.attr('data-' + attr) || el.attr(attr))(scope, { + $columns: columns + }) || defaultValue; + }; + }; + + var parsedTitle = parsedAttribute('title', ' '), + headerTemplateURL = parsedAttribute('header', false), + filter = parsedAttribute('filter', false)(), + filterTemplateURL = false, + filterName = false; + + if (filter && filter.$$name) { + filterName = filter.$$name; + delete filter.$$name; + } + if (filter && filter.templateURL) { + filterTemplateURL = filter.templateURL; + delete filter.templateURL; + } + + el.attr('data-title-text', parsedTitle()); // this used in responsive table + columns.push({ + id: i++, + title: parsedTitle, + sortable: parsedAttribute('sortable', false), + 'class': el.attr('x-data-header-class') || el.attr('data-header-class') || el.attr('header-class'), + filter: filter, + filterTemplateURL: filterTemplateURL, + filterName: filterName, + headerTemplateURL: headerTemplateURL, + filterData: (el.attr("filter-data") ? el.attr("filter-data") : null), + show: (el.attr("ng-show") ? function (scope) { + return $parse(el.attr("ng-show"))(scope); + } : function () { + return true; + }) + }); + }); + return function (scope, element, attrs) { + scope.$loading = false; + scope.$columns = columns; + + scope.$watch(attrs.ngTable, (function (params) { + if (angular.isUndefined(params)) { + return; + } + scope.paramsModel = $parse(attrs.ngTable); + scope.params = params; + }), true); + scope.parse = function (text) { + return angular.isDefined(text) ? text(scope) : ''; + }; + if (attrs.showFilter) { + scope.$parent.$watch(attrs.showFilter, function (value) { + scope.show_filter = value; + }); + } + angular.forEach(columns, function (column) { + var def; + if (!column.filterData) { + return; + } + def = $parse(column.filterData)(scope, { + $column: column + }); + if (!(angular.isObject(def) && angular.isObject(def.promise))) { + throw new Error('Function ' + column.filterData + ' must be instance of $q.defer()'); + } + delete column.filterData; + return def.promise.then(function (data) { + if (!angular.isArray(data)) { + data = []; + } + data.unshift({ + title: '-', + id: '' + }); + column.data = data; + }); + }); + if (!element.hasClass('ng-table')) { + scope.templates = { + header: (attrs.templateHeader ? attrs.templateHeader : 'ng-table/header.html'), + pagination: (attrs.templatePagination ? attrs.templatePagination : 'ng-table/pager.html') + }; + var headerTemplate = thead.length > 0 ? thead : angular.element(document.createElement('thead')).attr('ng-include', 'templates.header'); + var paginationTemplate = angular.element(document.createElement('div')).attr({ + 'ng-table-pagination': 'params', + 'template-url': 'templates.pagination' + }); + + element.find('thead').remove(); + + element.addClass('ng-table') + .prepend(headerTemplate) + .after(paginationTemplate); + + $compile(headerTemplate)(scope); + $compile(paginationTemplate)(scope); + } + }; + } + } + } +]); + +/** + * ngTable: Table + Angular JS + * + * @author Vitalii Savchuk + * @url https://github.com/esvit/ng-table/ + * @license New BSD License + */ + +/** + * @ngdoc directive + * @name ngTable.directive:ngTablePagination + * @restrict A + */ +app.directive('ngTablePagination', ['$compile', + function ($compile) { + 'use strict'; + + return { + restrict: 'A', + scope: { + 'params': '=ngTablePagination', + 'templateUrl': '=' + }, + replace: false, + link: function (scope, element, attrs) { + + scope.params.settings().$scope.$on('ngTableAfterReloadData', function () { + scope.pages = scope.params.generatePagesArray(scope.params.page(), scope.params.total(), scope.params.count()); + }, true); + + scope.$watch('templateUrl', function(templateUrl) { + if (angular.isUndefined(templateUrl)) { + return; + } + var template = angular.element(document.createElement('div')) + template.attr({ + 'ng-include': 'templateUrl' + }); + element.append(template); + $compile(template)(scope); + }); + } + }; + } +]); + +angular.module('ngTable').run(['$templateCache', function ($templateCache) { + $templateCache.put('ng-table/filters/select-multiple.html', ''); + $templateCache.put('ng-table/filters/select.html', ''); + $templateCache.put('ng-table/filters/text.html', ''); + $templateCache.put('ng-table/header.html', '
      '); + $templateCache.put('ng-table/pager.html', ' '); +}]); + return app; +})); +angular.module("gettext",[]),angular.module("gettext").constant("gettext",function(a){return a}),angular.module("gettext").factory("gettextCatalog",["gettextPlurals","$http","$cacheFactory",function(a,b,c){var d,e=function(a){return d.debug&&d.currentLanguage!==d.baseLanguage?"[MISSING]: "+a:a};return d={debug:!1,strings:{},baseLanguage:"en",currentLanguage:"en",cache:c("strings"),setStrings:function(a,b){var c,d;this.strings[a]||(this.strings[a]={});for(c in b)d=b[c],this.strings[a][c]="string"==typeof d?[d]:d},getStringForm:function(a,b){var c=this.strings[this.currentLanguage]||{},d=c[a]||[];return d[b]},getString:function(a){return this.getStringForm(a,0)||e(a)},getPlural:function(b,c,d){var f=a(this.currentLanguage,b);return this.getStringForm(c,f)||e(1===b?c:d)},loadRemote:function(a){return b({method:"GET",url:a,cache:d.cache}).success(function(a){for(var b in a)d.setStrings(b,a[b])})}}}]),angular.module("gettext").directive("translate",["gettextCatalog","$interpolate","$parse","$compile",function(a,b,c,d){var e=function(){return String.prototype.trim?function(a){return"string"==typeof a?a.trim():a}:function(a){return"string"==typeof a?a.replace(/^\s*/,"").replace(/\s*$/,""):a}}();return{transclude:"element",priority:499,compile:function(f,g,h){return function(f,i){var j=function(a,b,c){if(!a)throw new Error("You should add a "+b+" attribute whenever you add a "+c+" attribute.")};if(j(!g.translatePlural||g.translateN,"translate-n","translate-plural"),j(!g.translateN||g.translatePlural,"translate-plural","translate-n"),g.ngIf)throw new Error("You should not combine translate with ng-if, this will lead to problems.");if(g.ngSwitchWhen)throw new Error("You should not combine translate with ng-switch-when, this will lead to problems.");var k=c(g.translateN),l=null;h(f,function(c){var h=e(c.html());return c.removeAttr("translate"),i.replaceWith(c),f.$watch(function(){var e,i=c.html();g.translatePlural?(f=l||(l=f.$new()),f.$count=k(f),e=a.getPlural(f.$count,h,g.translatePlural)):e=a.getString(h);var j=b(e)(f);return i!==j?(c.html(j),void 0!==g.translateCompile&&d(c.contents())(f),c):void 0})})}}}}]),angular.module("gettext").filter("translate",["gettextCatalog","$interpolate","$parse",function(a){return function(b){return a.getString(b)}}]),angular.module("gettext").factory("gettextPlurals",function(){return function(a,b){switch(a){case"ay":case"bo":case"cgg":case"dz":case"fa":case"id":case"ja":case"jbo":case"ka":case"kk":case"km":case"ko":case"ky":case"lo":case"ms":case"my":case"sah":case"su":case"th":case"tt":case"ug":case"vi":case"wo":case"zh":return 0;case"is":return b%10!=1||b%100==11?1:0;case"jv":return 0!=b?1:0;case"mk":return 1==b||b%10==1?0:1;case"ach":case"ak":case"am":case"arn":case"br":case"fil":case"fr":case"gun":case"ln":case"mfe":case"mg":case"mi":case"oc":case"pt_BR":case"tg":case"ti":case"tr":case"uz":case"wa":case"zh":return b>1?1:0;case"lv":return b%10==1&&b%100!=11?0:0!=b?1:2;case"lt":return b%10==1&&b%100!=11?0:b%10>=2&&(10>b%100||b%100>=20)?1:2;case"be":case"bs":case"hr":case"ru":case"sr":case"uk":return b%10==1&&b%100!=11?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"mnk":return 0==b?0:1==b?1:2;case"ro":return 1==b?0:0==b||b%100>0&&20>b%100?1:2;case"pl":return 1==b?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"cs":case"sk":return 1==b?0:b>=2&&4>=b?1:2;case"sl":return b%100==1?1:b%100==2?2:b%100==3||b%100==4?3:0;case"mt":return 1==b?0:0==b||b%100>1&&11>b%100?1:b%100>10&&20>b%100?2:3;case"gd":return 1==b||11==b?0:2==b||12==b?1:b>2&&20>b?2:3;case"cy":return 1==b?0:2==b?1:8!=b&&11!=b?2:3;case"kw":return 1==b?0:2==b?1:3==b?2:3;case"ga":return 1==b?0:2==b?1:7>b?2:11>b?3:4;case"ar":return 0==b?0:1==b?1:2==b?2:b%100>=3&&10>=b%100?3:b%100>=11?4:5;default:return 1!=b?1:0}}}); +// Generated by CoffeeScript 1.6.2 +(function() { + var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + angular.module('localytics.directives', []); + + angular.module('localytics.directives').directive('chosen', function() { + var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase; + + NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/; + CHOSEN_OPTION_WHITELIST = ['noResultsText', 'allowSingleDeselect', 'disableSearchThreshold', 'disableSearch', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'singleBackstrokeDelete', 'displayDisabledOptions', 'displaySelectedOptions', 'width']; + snakeCase = function(input) { + return input.replace(/[A-Z]/g, function($1) { + return "_" + ($1.toLowerCase()); + }); + }; + isEmpty = function(value) { + var key; + + if (angular.isArray(value)) { + return value.length === 0; + } else if (angular.isObject(value)) { + for (key in value) { + if (value.hasOwnProperty(key)) { + return false; + } + } + } + return true; + }; + return { + restrict: 'A', + require: '?ngModel', + terminal: true, + link: function(scope, element, attr, ngModel) { + var chosen, defaultText, disableWithMessage, empty, initOrUpdate, match, options, origRender, removeEmptyMessage, startLoading, stopLoading, valuesExpr, viewWatch; + + element.addClass('localytics-chosen'); + options = scope.$eval(attr.chosen) || {}; + angular.forEach(attr, function(value, key) { + if (__indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) { + return options[snakeCase(key)] = scope.$eval(value); + } + }); + startLoading = function() { + return element.addClass('loading').attr('disabled', true).trigger('chosen:updated'); + }; + stopLoading = function() { + return element.removeClass('loading').attr('disabled', false).trigger('chosen:updated'); + }; + chosen = null; + defaultText = null; + empty = false; + initOrUpdate = function() { + if (chosen) { + return element.trigger('chosen:updated'); + } else { + chosen = element.chosen(options).data('chosen'); + return defaultText = chosen.default_text; + } + }; + removeEmptyMessage = function() { + empty = false; + return element.attr('data-placeholder', defaultText); + }; + disableWithMessage = function() { + empty = true; + return element.attr('data-placeholder', chosen.results_none_found).attr('disabled', true).trigger('chosen:updated'); + }; + if (ngModel) { + origRender = ngModel.$render; + ngModel.$render = function() { + origRender(); + return initOrUpdate(); + }; + if (attr.multiple) { + viewWatch = function() { + return ngModel.$viewValue; + }; + scope.$watch(viewWatch, ngModel.$render, true); + } + } else { + initOrUpdate(); + } + attr.$observe('disabled', function() { + return element.trigger('chosen:updated'); + }); + if (attr.ngOptions && ngModel) { + match = attr.ngOptions.match(NG_OPTIONS_REGEXP); + valuesExpr = match[7]; + return scope.$watchCollection(valuesExpr, function(newVal, oldVal) { + if (angular.isUndefined(newVal)) { + return startLoading(); + } else { + if (empty) { + removeEmptyMessage(); + } + stopLoading(); + if (isEmpty(newVal)) { + return disableWithMessage(); + } + } + }); + } + } + }; + }); + +}).call(this); + +/** + * @author Jason Dobry + * @file angular-cache.js + * @version 2.3.7 - Homepage + * @copyright (c) 2013-2014 Jason Dobry + * @license MIT + * + * @overview angular-cache is a very useful replacement for Angular's $cacheFactory. + */ +(function (window, angular, undefined) { + 'use strict'; + + /** + * @desc Provider for the BinaryHeap. + */ + function BinaryHeapProvider() { + this.$get = function () { + /** + * @method bubbleUp + * @param {array} heap The heap. + * @param {function} weightFunc The weight function. + * @param {number} n The index of the element to bubble up. + */ + function bubbleUp(heap, weightFunc, n) { + var element = heap[n], + weight = weightFunc(element); + // When at 0, an element can not go up any further. + while (n > 0) { + // Compute the parent element's index, and fetch it. + var parentN = Math.floor((n + 1) / 2) - 1, + parent = heap[parentN]; + // If the parent has a lesser weight, things are in order and we + // are done. + if (weight >= weightFunc(parent)) { + break; + } else { + heap[parentN] = element; + heap[n] = parent; + n = parentN; + } + } + } + + /** + * @method bubbleDown + * @param {array} heap The heap. + * @param {function} weightFunc The weight function. + * @param {number} n The index of the element to sink down. + */ + function bubbleDown(heap, weightFunc, n) { + var length = heap.length, + node = heap[n], + nodeWeight = weightFunc(node); + + while (true) { + var child2N = (n + 1) * 2, + child1N = child2N - 1; + var swap = null; + if (child1N < length) { + var child1 = heap[child1N], + child1Weight = weightFunc(child1); + // If the score is less than our node's, we need to swap. + if (child1Weight < nodeWeight) { + swap = child1N; + } + } + // Do the same checks for the other child. + if (child2N < length) { + var child2 = heap[child2N], + child2Weight = weightFunc(child2); + if (child2Weight < (swap === null ? nodeWeight : weightFunc(heap[child1N]))) { + swap = child2N; + } + } + + if (swap === null) { + break; + } else { + heap[n] = heap[swap]; + heap[swap] = node; + n = swap; + } + } + } + + /** + * @class BinaryHeap + * @desc BinaryHeap implementation of a priority queue. + * @param {function} weightFunc Function that returns the value that should be used for node value comparison. + * @example + * angular.module('app').controller(function (BinaryHeap) { + * var bHeap = new BinaryHeap(function (x) { + * return x.value; + * }); + * ); + */ + function BinaryHeap(weightFunc) { + if (weightFunc && !angular.isFunction(weightFunc)) { + throw new Error('BinaryHeap(weightFunc): weightFunc: must be a function!'); + } + weightFunc = weightFunc || function (x) { + return x; + }; + this.weightFunc = weightFunc; + this.heap = []; + } + + /** + * @method BinaryHeap.push + * @desc Push an element into the binary heap. + * @param {*} node The element to push into the binary heap. + */ + BinaryHeap.prototype.push = function (node) { + this.heap.push(node); + bubbleUp(this.heap, this.weightFunc, this.heap.length - 1); + }; + + /** + * @method BinaryHeap.peek + * @desc Return, but do not remove, the minimum element in the binary heap. + * @returns {*} + */ + BinaryHeap.prototype.peek = function () { + return this.heap[0]; + }; + + /** + * @method BinaryHeap.pop + * @desc Remove and return the minimum element in the binary heap. + * @returns {*} + */ + BinaryHeap.prototype.pop = function () { + var front = this.heap[0], + end = this.heap.pop(); + if (this.heap.length > 0) { + this.heap[0] = end; + bubbleDown(this.heap, this.weightFunc, 0); + } + return front; + }; + + /** + * @method BinaryHeap.remove + * @desc Remove the first node in the priority queue that satisfies angular.equals comparison with + * the given node. + * @param {*} node The node to remove. + * @returns {*} The removed node. + */ + BinaryHeap.prototype.remove = function (node) { + var length = this.heap.length; + for (var i = 0; i < length; i++) { + if (angular.equals(this.heap[i], node)) { + var removed = this.heap[i], + end = this.heap.pop(); + if (i !== length - 1) { + this.heap[i] = end; + bubbleUp(this.heap, this.weightFunc, i); + bubbleDown(this.heap, this.weightFunc, i); + } + return removed; + } + } + return null; + }; + + /** + * @method BinaryHeap.removeAll + * @desc Remove all nodes from this BinaryHeap. + */ + BinaryHeap.prototype.removeAll = function () { + this.heap = []; + }; + + /** + * @method BinaryHeap.size + * @desc Return the size of the priority queue. + * @returns {number} The size of the priority queue. + */ + BinaryHeap.prototype.size = function () { + return this.heap.length; + }; + + return BinaryHeap; + }; + } + + angular.module('jmdobry.binary-heap', []). + provider('BinaryHeap', BinaryHeapProvider); + + /** + * @class $AngularCacheFactoryProvider + * @desc Provider for the $angularCacheFactory. + * @see {@link http://docs.angularjs.org/api/ng.$cacheFactory|ng.$cacheFactory} + */ + function $AngularCacheFactoryProvider() { + + var cacheDefaults, + DEFAULTS = function () { + return { + capacity: Number.MAX_VALUE, + maxAge: null, + deleteOnExpire: 'none', + onExpire: null, + cacheFlushInterval: null, + recycleFreq: 1000, + storageMode: 'none', + storageImpl: null, + verifyIntegrity: true, + disabled: false + }; + }; + + /** + * @method _validateNumberOption + * @desc Validates the given number option. + * @param {number} option The number option to check. + * @param {function} cb Callback function + */ + function _validateNumberOption(option, cb) { + if (!angular.isNumber(option)) { + cb('must be a number!'); + } else if (option < 0) { + cb('must be greater than zero!'); + } else { + cb(null); + } + } + + /** + * @method $AngularCacheFactoryProvider.setCacheDefaults + * @desc Set the default configuration for all caches created by $angularCacheFactory. + * @param {object} options + */ + this.setCacheDefaults = function (options) { + var errStr = '$angularCacheFactoryProvider.setCacheDefaults(options): '; + options = options || {}; + + if (!angular.isObject(options)) { + throw new Error(errStr + 'options: must be an object!'); + } + + if ('disabled' in options) { + options.disabled = options.disabled === true; + } + + if ('capacity' in options) { + _validateNumberOption(options.capacity, function (err) { + if (err) { + throw new Error(errStr + 'capacity: ' + err); + } + }); + } + + if ('deleteOnExpire' in options) { + if (!angular.isString(options.deleteOnExpire)) { + throw new Error(errStr + 'deleteOnExpire: must be a string!'); + } else if (options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { + throw new Error(errStr + 'deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); + } + } + + if ('maxAge' in options) { + _validateNumberOption(options.maxAge, function (err) { + if (err) { + throw new Error(errStr + 'maxAge: ' + err); + } + }); + } + + if ('recycleFreq' in options) { + _validateNumberOption(options.recycleFreq, function (err) { + if (err) { + throw new Error(errStr + 'recycleFreq: ' + err); + } + }); + } + + if ('cacheFlushInterval' in options) { + _validateNumberOption(options.cacheFlushInterval, function (err) { + if (err) { + throw new Error(errStr + 'cacheFlushInterval: ' + err); + } + }); + } + + if ('storageMode' in options) { + if (!angular.isString(options.storageMode)) { + throw new Error(errStr + 'storageMode: must be a string!'); + } else if (options.storageMode !== 'none' && options.storageMode !== 'localStorage' && options.storageMode !== 'sessionStorage') { + throw new Error(errStr + 'storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); + } + if ('storageImpl' in options) { + if (!angular.isObject(options.storageImpl)) { + throw new Error(errStr + 'storageImpl: must be an object!'); + } else if (!('setItem' in options.storageImpl) || typeof options.storageImpl.setItem !== 'function') { + throw new Error(errStr + 'storageImpl: must implement "setItem(key, value)"!'); + } else if (!('getItem' in options.storageImpl) || typeof options.storageImpl.getItem !== 'function') { + throw new Error(errStr + 'storageImpl: must implement "getItem(key)"!'); + } else if (!('removeItem' in options.storageImpl) || typeof options.storageImpl.removeItem !== 'function') { + throw new Error(errStr + 'storageImpl: must implement "removeItem(key)"!'); + } + } + } + + if ('onExpire' in options) { + if (typeof options.onExpire !== 'function') { + throw new Error(errStr + 'onExpire: must be a function!'); + } + } + + cacheDefaults = angular.extend({}, DEFAULTS(), options); + }; + + // Initialize cacheDefaults with the defaults + this.setCacheDefaults({}); + + /** + * @ignore + */ + this.$get = ['$window', 'BinaryHeap', function ($window, BinaryHeap) { + var caches = {}; + + /** + * Stringify a number. + * @param {number|*} number The number to be stringified. + * @returns {*} number or a string. + * @private + */ + function _stringifyNumber(number) { + if (number && angular.isNumber(number)) { + return number.toString(); + } + return number; + } + + /** + * @method _keySet + * @desc Returns an object of the keys of the given collection. + * @param {object} collection The collection from which to get the set of keys. + * @returns {object} A hash of the keys of the given collection. + */ + function _keySet(collection) { + var keySet = {}, key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keySet[key] = key; + } + } + return keySet; + } + + /** + * @method _keys + * @desc Returns an array of the keys of the given collection. + * @param {object} collection The collection from which to get the keys. + * @returns {array} An array of the keys of the given collection. + */ + function _keys(collection) { + var keys = [], key; + for (key in collection) { + if (collection.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + } + + /** + * @class AngularCache + * @desc Instantiated via $angularCacheFactory(cacheId[, options]) + * @param {string} cacheId The id of the new cache. + * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. + */ + function AngularCache(cacheId, options) { + var config = angular.extend({}, { id: cacheId }), + data = {}, + expiresHeap = new BinaryHeap(function (x) { + return x.expires; + }), + lruHeap = new BinaryHeap(function (x) { + return x.accessed; + }), + prefix = 'angular-cache.caches.' + cacheId, + cacheDirty = false, + self = this, + storage = null; + + options = options || {}; + + /** + * @method _setCapacity + * @desc Set the capacity for this cache. + * @param {number} capacity The new capacity for this cache. + */ + function _setCapacity(capacity) { + _validateNumberOption(capacity, function (err) { + if (err) { + throw new Error('capacity: ' + err); + } else { + config.capacity = capacity; + while (lruHeap.size() > config.capacity) { + self.remove(lruHeap.peek().key, { verifyIntegrity: false }); + } + } + }); + } + + /** + * @method _setDeleteOnExpire + * @desc Set the deleteOnExpire setting for this cache. + * @param {string} deleteOnExpire The new deleteOnExpire for this cache. + */ + function _setDeleteOnExpire(deleteOnExpire) { + if (!angular.isString(deleteOnExpire)) { + throw new Error('deleteOnExpire: must be a string!'); + } else if (deleteOnExpire !== 'none' && deleteOnExpire !== 'passive' && deleteOnExpire !== 'aggressive') { + throw new Error('deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); + } else { + config.deleteOnExpire = deleteOnExpire; + } + } + + /** + * @method _setMaxAge + * @desc Set the maxAge for this cache. + * @param {number} maxAge The new maxAge for this cache. + */ + function _setMaxAge(maxAge) { + var keys = _keys(data); + if (maxAge === null) { + if (config.maxAge) { + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (!('maxAge' in data[key])) { + delete data[key].expires; + expiresHeap.remove(data[key]); + } + } + } + config.maxAge = maxAge; + } else { + _validateNumberOption(maxAge, function (err) { + if (err) { + throw new Error('maxAge: ' + err); + } else { + if (maxAge !== config.maxAge) { + config.maxAge = maxAge; + var now = new Date().getTime(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (!('maxAge' in data[key])) { + expiresHeap.remove(data[key]); + data[key].expires = data[key].created + config.maxAge; + expiresHeap.push(data[key]); + if (data[key].expires < now) { + self.remove(key, { verifyIntegrity: false }); + } + } + } + } + } + }); + } + } + + /** + * @method _flushExpired + * @desc Remove expired items from the expiresHeap. + * @private + */ + function _flushExpired() { + var now = new Date().getTime(), + item = expiresHeap.peek(); + + while (item && item.expires && item.expires < now) { + self.remove(item.key, { verifyIntegrity: false }); + if (config.onExpire) { + config.onExpire(item.key, item.value); + } + item = expiresHeap.peek(); + } + } + + /** + * @method _setRecycleFreq + * @desc Set the recycleFreq setting for this cache. + * @param {number} recycleFreq The new recycleFreq for this cache. + */ + function _setRecycleFreq(recycleFreq) { + if (recycleFreq === null) { + if (config.recycleFreqId) { + clearInterval(config.recycleFreqId); + delete config.recycleFreqId; + } + config.recycleFreq = cacheDefaults.recycleFreq; + config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); + } else { + _validateNumberOption(recycleFreq, function (err) { + if (err) { + throw new Error('recycleFreq: ' + err); + } else { + config.recycleFreq = recycleFreq; + if (config.recycleFreqId) { + clearInterval(config.recycleFreqId); + } + config.recycleFreqId = setInterval(_flushExpired, config.recycleFreq); + } + }); + } + } + + /** + * @method _setCacheFlushInterval + * @desc Set the cacheFlushInterval for this cache. + * @param {number} cacheFlushInterval The new cacheFlushInterval for this cache. + */ + function _setCacheFlushInterval(cacheFlushInterval) { + if (cacheFlushInterval === null) { + if (config.cacheFlushIntervalId) { + clearInterval(config.cacheFlushIntervalId); + delete config.cacheFlushIntervalId; + } + config.cacheFlushInterval = cacheFlushInterval; + } else { + _validateNumberOption(cacheFlushInterval, function (err) { + if (err) { + throw new Error('cacheFlushInterval: ' + err); + } else { + if (cacheFlushInterval !== config.cacheFlushInterval) { + if (config.cacheFlushIntervalId) { + clearInterval(config.cacheFlushIntervalId); + } + config.cacheFlushInterval = cacheFlushInterval; + config.cacheFlushIntervalId = setInterval(self.removeAll, config.cacheFlushInterval); + } + } + }); + } + } + + /** + * @method _setStorageMode + * @desc Configure the cache to use localStorage. + * @param {string} storageMode "localStorage"|"sessionStorage"|null + * @param {object} storageImpl The storage polyfill/replacement to use. + */ + function _setStorageMode(storageMode, storageImpl) { + var keys, i; + if (!angular.isString(storageMode)) { + throw new Error('storageMode: must be a string!'); + } else if (storageMode !== 'none' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') { + throw new Error('storageMode: accepted values are "none", "localStorage" or "sessionStorage"!'); + } + if ((config.storageMode === 'localStorage' || config.storageMode === 'sessionStorage') && + (storageMode !== config.storageMode)) { + keys = _keys(data); + for (i = 0; i < keys.length; i++) { + storage.removeItem(prefix + '.data.' + keys[i]); + } + storage.removeItem(prefix + '.keys'); + } + config.storageMode = storageMode; + if (storageImpl) { + if (!angular.isObject(storageImpl)) { + throw new Error('storageImpl: must be an object!'); + } else if (!('setItem' in storageImpl) || typeof storageImpl.setItem !== 'function') { + throw new Error('storageImpl: must implement "setItem(key, value)"!'); + } else if (!('getItem' in storageImpl) || typeof storageImpl.getItem !== 'function') { + throw new Error('storageImpl: must implement "getItem(key)"!'); + } else if (!('removeItem' in storageImpl) || typeof storageImpl.removeItem !== 'function') { + throw new Error('storageImpl: must implement "removeItem(key)"!'); + } + storage = storageImpl; + } else if (config.storageMode === 'localStorage') { + storage = $window.localStorage; + } else if (config.storageMode === 'sessionStorage') { + storage = $window.sessionStorage; + } + + if (config.storageMode !== 'none' && storage) { + if (!cacheDirty) { + _loadCacheConfig(); + } else { + keys = _keys(data); + for (i = 0; i < keys.length; i++) { + _syncToStorage(keys[i]); + } + } + } + } + + /** + * @method _setOptions + * @desc Configure this cache with the given options. + * @param {object} cacheOptions New configuration options for the cache. + * @param {boolean} [strict] If true then any existing configuration will be reset to default before + * applying the new options, otherwise only the options specified in the options hash will be altered. + * @param {object} [options] Configuration. + */ + function _setOptions(cacheOptions, strict, options) { + cacheOptions = cacheOptions || {}; + options = options || {}; + strict = !!strict; + if (!angular.isObject(cacheOptions)) { + throw new Error('AngularCache.setOptions(cacheOptions, strict, options): cacheOptions: must be an object!'); + } + + _verifyIntegrity(options.verifyIntegrity); + + if (strict) { + cacheOptions = angular.extend({}, cacheDefaults, cacheOptions); + } + + if ('disabled' in cacheOptions) { + config.disabled = cacheOptions.disabled === true; + } + if ('verifyIntegrity' in cacheOptions) { + config.verifyIntegrity = cacheOptions.verifyIntegrity === true; + } + if ('capacity' in cacheOptions) { + _setCapacity(cacheOptions.capacity); + } + + if ('deleteOnExpire' in cacheOptions) { + _setDeleteOnExpire(cacheOptions.deleteOnExpire); + } + + if ('maxAge' in cacheOptions) { + _setMaxAge(cacheOptions.maxAge); + } + + if ('recycleFreq' in cacheOptions) { + _setRecycleFreq(cacheOptions.recycleFreq); + } + + if ('cacheFlushInterval' in cacheOptions) { + _setCacheFlushInterval(cacheOptions.cacheFlushInterval); + } + + if ('storageMode' in cacheOptions) { + _setStorageMode(cacheOptions.storageMode, cacheOptions.storageImpl); + } + + if ('onExpire' in cacheOptions) { + if (cacheOptions.onExpire !== null && typeof cacheOptions.onExpire !== 'function') { + throw new Error('onExpire: must be a function!'); + } + config.onExpire = cacheOptions.onExpire; + } + + cacheDirty = true; + } + + /** + * @method _loadCacheConfig + * @desc If storageMode is set, attempt to load previous cache configuration from localStorage. + */ + function _loadCacheConfig() { + var keys = angular.fromJson(storage.getItem(prefix + '.keys')); + storage.removeItem(prefix + '.keys'); + if (keys && keys.length) { + for (var i = 0; i < keys.length; i++) { + var data = angular.fromJson(storage.getItem(prefix + '.data.' + keys[i])) || {}, + maxAge = data.maxAge || config.maxAge, + deleteOnExpire = data.deleteOnExpire || config.deleteOnExpire; + if (maxAge && ((new Date().getTime() - data.created) > maxAge) && deleteOnExpire === 'aggressive') { + storage.removeItem(prefix + '.data.' + keys[i]); + } else { + var options = { + created: data.created + }; + if (data.expires) { + options.expires = data.expires; + } + if (data.accessed) { + options.accessed = data.accessed; + } + if (data.maxAge) { + options.maxAge = data.maxAge; + } + if (data.deleteOnExpire) { + options.deleteOnExpire = data.deleteOnExpire; + } + self.put(keys[i], data.value, options); + } + } + _syncToStorage(null); + } + } + + /** + * @method _syncToStorage + * @desc If storageMode is set, sync to localStorage. + * @param {string} key The identifier of the item to sync. + */ + function _syncToStorage(key) { + if (config.storageMode !== 'none' && storage) { + storage.setItem(prefix + '.keys', angular.toJson(_keys(data))); + if (key) { + storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); + } + } + } + + function _verifyIntegrity(verifyIntegrity) { + if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { + if (config.storageMode !== 'none' && storage) { + var keys = _keys(data); + storage.setItem(prefix + '.keys', angular.toJson(keys)); + for (var i = 0; i < keys.length; i++) { + storage.setItem(prefix + '.data.' + keys[i], angular.toJson(data[keys[i]])); + } + } + } + } + + function _readItemFromStorage(key, verifyIntegrity) { + if (verifyIntegrity || (verifyIntegrity !== false && config.verifyIntegrity)) { + if (config.storageMode !== 'none' && storage) { + var itemJson = storage.getItem(prefix + '.data.' + key); + + if (!itemJson && key in data) { + self.remove(key); + } else if (itemJson) { + var item = angular.fromJson(itemJson), + value = item ? item.value : null; + + var options = {}; + if (item && item.created) { + options.created = item.created; + } + if (item && item.expires) { + options.expires = item.expires; + } + if (item && item.accessed) { + options.accessed = item.accessed; + } + if (item && item.maxAge) { + options.maxAge = item.maxAge; + } + if (item && item.deleteOnExpire) { + options.deleteOnExpire = item.deleteOnExpire; + } + + if (value) { + self.put(key, value, options); + } + } + } + } + } + + function _saveKeysToStorage(keys) { + if (config.storageMode !== 'none' && storage) { + var keysToSave = keys || _keys(data); + storage.setItem(prefix + '.keys', angular.toJson(keysToSave)); + } + } + + function _saveItemToStorage(key) { + if (config.storageMode !== 'none' && storage && key in data) { + storage.setItem(prefix + '.data.' + key, angular.toJson(data[key])); + } + } + + function _removeAllFromStorage() { + if (config.storageMode !== 'none' && storage) { + var keys = _keys(data); + for (var i = 0; i < keys.length; i++) { + storage.removeItem(prefix + '.data.' + keys[i]); + } + storage.setItem(prefix + '.keys', angular.toJson([])); + } + } + + /** + * @method AngularCache.put + * @desc Add a key-value pair to the cache. + * @param {string} key The identifier for the item to add to the cache. + * @param {*} value The value of the item to add to the cache. + * @param {object} [options] {{ maxAge: {number}, deleteOnExpire: {string} }} + * @returns {*} value The value of the item added to the cache. + */ + this.put = function (key, value, options) { + if (config.disabled) { + return; + } + if (value && value.then) { + value.then(function (v) { + if (angular.isObject(v) && 'status' in v && 'data' in v) { + self.put(key, [v.status, v.data, v.headers(), v.statusText]); + } else { + self.put(key, v, options); + } + }); + return; + } + options = options || {}; + + key = _stringifyNumber(key); + + if (!angular.isString(key)) { + throw new Error('AngularCache.put(key, value, options): key: must be a string!'); + } else if (options && !angular.isObject(options)) { + throw new Error('AngularCache.put(key, value, options): options: must be an object!'); + } else if (options.maxAge && options.maxAge !== null) { + _validateNumberOption(options.maxAge, function (err) { + if (err) { + throw new Error('AngularCache.put(key, value, options): maxAge: ' + err); + } + }); + } else if (options.deleteOnExpire && !angular.isString(options.deleteOnExpire)) { + throw new Error('AngularCache.put(key, value, options): deleteOnExpire: must be a string!'); + } else if (options.deleteOnExpire && options.deleteOnExpire !== 'none' && options.deleteOnExpire !== 'passive' && options.deleteOnExpire !== 'aggressive') { + throw new Error('AngularCache.put(key, value, options): deleteOnExpire: accepted values are "none", "passive" or "aggressive"!'); + } else if (angular.isUndefined(value)) { + return; + } + + var now = new Date().getTime(), + deleteOnExpire, item; + + _verifyIntegrity(options.verifyIntegrity); + + if (data[key]) { + expiresHeap.remove(data[key]); + lruHeap.remove(data[key]); + } else { + data[key] = { key: key }; + } + + item = data[key]; + item.value = value; + item.created = (parseInt(options.created, 10)) || item.created || now; + item.accessed = (parseInt(options.accessed, 10)) || now; + + if (options.deleteOnExpire) { + item.deleteOnExpire = options.deleteOnExpire; + } + if (options.maxAge) { + item.maxAge = options.maxAge; + } + + if (item.maxAge || config.maxAge) { + if ('expires' in options) { + item.expires = parseInt(options.expires, 10); + } else if (!angular.isNumber(item.expires)) { + item.expires = item.created + (item.maxAge || config.maxAge); + } + } + + deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; + + if (item.expires && deleteOnExpire === 'aggressive') { + expiresHeap.push(item); + } + + // Sync with localStorage, etc. + _saveKeysToStorage(); + _saveItemToStorage(key); + + lruHeap.push(item); + + if (lruHeap.size() > config.capacity) { + this.remove(lruHeap.peek().key, { verifyIntegrity: false }); + } + + return value; + }; + + /** + * @method AngularCache.get + * @desc Retrieve the item from the cache with the specified key. + * @param {string|Array} key The key of the item to retrieve or an array of keys of items to retrieve. + * @param {object} [options] Configuration. + * @returns {*} The value of the item in the cache with the specified key. + */ + this.get = function (key, options) { + if (config.disabled) { + return; + } + if (angular.isArray(key)) { + var keys = key, + values = []; + + angular.forEach(keys, function (key) { + var value = self.get(key, options); + if (angular.isDefined(value)) { + values.push(value); + } + }); + + return values; + } else { + key = _stringifyNumber(key); + } + + options = options || {}; + if (!angular.isString(key)) { + throw new Error('AngularCache.get(key, options): key: must be a string!'); + } else if (options && !angular.isObject(options)) { + throw new Error('AngularCache.get(key, options): options: must be an object!'); + } else if (options.onExpire && !angular.isFunction(options.onExpire)) { + throw new Error('AngularCache.get(key, options): onExpire: must be a function!'); + } + + _readItemFromStorage(key, options.verifyIntegrity); + + if (!(key in data)) { + return; + } + + var item = data[key], + value = item.value, + now = new Date().getTime(), + deleteOnExpire = item.deleteOnExpire || config.deleteOnExpire; + + lruHeap.remove(item); + item.accessed = now; + lruHeap.push(item); + + if (deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) { + this.remove(key, { verifyIntegrity: false }); + + if (config.onExpire) { + config.onExpire(key, item.value, (options.onExpire)); + } else if (options.onExpire) { + options.onExpire(key, item.value); + } + value = undefined; + } + + _saveItemToStorage(key); + + return value; + }; + + /** + * @method AngularCache.remove + * @desc Remove the item with the specified key from the cache. + * @param {string} key The key of the item to remove. + * @param {object} [options] Configuration. + */ + this.remove = function (key, options) { + options = options || {}; + _verifyIntegrity(options.verifyIntegrity); + lruHeap.remove(data[key]); + expiresHeap.remove(data[key]); + if (config.storageMode !== 'none' && storage) { + storage.removeItem(prefix + '.data.' + key); + } + delete data[key]; + _saveKeysToStorage(); + }; + + /** + * @method AngularCache.removeAll + * @desc Clear the cache. + */ + this.removeAll = function () { + _removeAllFromStorage(); + lruHeap.removeAll(); + expiresHeap.removeAll(); + data = {}; + }; + + /** + * @method AngularCache.removeExpired + * @desc Remove all expired items from the cache. + * @param {object} [options] Configuration. + * @returns {object|array} Object or array of removed items. + */ + this.removeExpired = function (options) { + options = options || {}; + var now = new Date().getTime(), + keys = _keys(data), + expired = {}; + + for (var i = 0; i < keys.length; i++) { + if (data[keys[i]] && data[keys[i]].expires && data[keys[i]].expires < now) { + expired[keys[i]] = data[keys[i]].value; + } + } + for (var key in expired) { + self.remove(key); + } + _verifyIntegrity(options.verifyIntegrity); + if (options.asArray) { + var expiredArray = []; + for (key in expired) { + expiredArray.push(expired[key]); + } + return expiredArray; + } else { + return expired; + } + }; + + /** + * @method AngularCache.destroy + * @desc Completely destroy the cache. + */ + this.destroy = function () { + if (config.cacheFlushIntervalId) { + clearInterval(config.cacheFlushIntervalId); + } + if (config.recycleFreqId) { + clearInterval(config.recycleFreqId); + } + this.removeAll(); + if (config.storageMode !== 'none' && storage) { + storage.removeItem(prefix + '.keys'); + storage.removeItem(prefix); + } + storage = null; + data = null; + lruHeap = null; + expiresHeap = null; + config = null; + prefix = null; + self = null; + var methodKeys = _keys(this); + + // Prevent this cache from being used after it has been destroyed + for (var i = 0; i < methodKeys.length; i++) { + if (this.hasOwnProperty(methodKeys[i])) { + delete this[methodKeys[i]]; + } + } + + caches[cacheId] = null; + delete caches[cacheId]; + }; + + /** + * @method AngularCache.info + * @desc Return an object containing information about the cache. + * @param {string} [key] The key of the item about which to retrieve information. + * @returns {object} stats Object containing information about this cache or the item with the + * specified key. + */ + this.info = function (key) { + if (key) { + if (data[key]) { + var info = { + created: data[key].created, + accessed: data[key].accessed, + expires: data[key].expires, + maxAge: data[key].maxAge || config.maxAge, + deleteOnExpire: data[key].deleteOnExpire || config.deleteOnExpire, + isExpired: false + }; + if (info.maxAge) { + info.isExpired = (new Date().getTime() - info.created) > info.maxAge; + } + return info; + } else { + return data[key]; + } + } else { + return angular.extend({}, config, { size: lruHeap && lruHeap.size() || 0 }); + } + }; + + /** + * @method AngularCache.keySet + * @desc Return the set of the keys of all items in the cache. + * @returns {object} The set of the keys of all items currently in this cache. + */ + this.keySet = function () { + return _keySet(data); + }; + + /** + * @method AngularCache.keys + * @desc Return an array of the keys of all items in the cache. + * @returns {array} An array of the keys of all items in the cache. + */ + this.keys = function () { + return _keys(data); + }; + + /** + * @method AngularCache.setOptions + * @desc Configure this cache with the given options. + * @param {object} cacheOptions + * @param {boolean} [strict] If true then any existing configuration will be reset to defaults before + * applying the new options, otherwise only the options specified in the hash will be altered. + * @param {object} [options] Configuration. + */ + this.setOptions = _setOptions; + + // Initialize this cache with the default and given options + _setOptions(options, true, { verifyIntegrity: false }); + } + + /** + * @class AngularCacheFactory + * @param {string} cacheId The id of the new cache. + * @param {object} [options] See [Configuration Options]{@link https://github.com/jmdobry/angular-cache#configuration}. + * @returns {AngularCache} + */ + function angularCacheFactory(cacheId, options) { + if (cacheId in caches) { + throw new Error('cacheId ' + cacheId + ' taken!'); + } else if (!angular.isString(cacheId)) { + throw new Error('cacheId must be a string!'); + } + + caches[cacheId] = new AngularCache(cacheId, options); + return caches[cacheId]; + } + + /** + * @method AngularCacheFactory.info + * @desc Return an object containing information about all caches in $angularCacheFactory. + * @returns {object} An object containing information about all caches of this factory. + */ + angularCacheFactory.info = function () { + var keys = _keys(caches); + var info = { + size: keys.length, + caches: {} + }; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + info.caches[key] = caches[key].info(); + } + info.cacheDefaults = angular.extend({}, cacheDefaults); + return info; + }; + + /** + * @method AngularCacheFactory.get + * @desc Return the cache with the specified cacheId. + * @param {string} cacheId The id of the desired cache. + * @returns {AngularCache} The cache with the specified cachedId. + */ + angularCacheFactory.get = function (cacheId) { + if (!angular.isString(cacheId)) { + throw new Error('$angularCacheFactory.get(cacheId): cacheId: must be a string!'); + } + return caches[cacheId]; + }; + + /** + * @method AngularCacheFactory.keySet + * @desc Return the set of keys of all current caches owned by angularCacheFactory. + * @returns {object} The set of keys associated with all current caches owned by this + * angularCacheFactory. + */ + angularCacheFactory.keySet = function () { + return _keySet(caches); + }; + + /** + * @method AngularCacheFactory.keys + * @desc Return an array of the keys of all caches owned by angularCacheFactory. + * @returns {array} An array of the keys associated with all current caches owned by + * this angularCacheFactory. + */ + angularCacheFactory.keys = function () { + return _keys(caches); + }; + + /** + * @method AngularCacheFactory.removeAll + * @desc Destroy all caches in $angularCacheFactory. + */ + angularCacheFactory.removeAll = function () { + var keys = _keys(caches); + for (var i = 0; i < keys.length; i++) { + caches[keys[i]].destroy(); + } + }; + + /** + * @method AngularCacheFactory.clearAll + * @desc Clears the contents of every cache in $angularCacheFactory. + */ + angularCacheFactory.clearAll = function () { + var keys = _keys(caches); + for (var i = 0; i < keys.length; i++) { + caches[keys[i]].removeAll(); + } + }; + + /** + * @method AngularCacheFactory.enableAll + * @desc Enable any disabled caches. + */ + angularCacheFactory.enableAll = function () { + var keys = _keys(caches); + for (var i = 0; i < keys.length; i++) { + caches[keys[i]].setOptions({ disabled: false }); + } + }; + + /** + * @method AngularCacheFactory.disableAll + * @desc Disable all caches. + */ + angularCacheFactory.disableAll = function () { + var keys = _keys(caches); + for (var i = 0; i < keys.length; i++) { + caches[keys[i]].setOptions({ disabled: true }); + } + }; + + return angularCacheFactory; + }]; + } + + /** + * @desc Provides an $AngularCacheFactoryProvider, which gives you the ability to use an + * $angularCacheFactory. The $angularCacheFactory produces AngularCache objects, which + * the same abilities as the cache objects that come with Angular, except with some added + * functionality. + */ + angular.module('jmdobry.angular-cache', ['ng', 'jmdobry.binary-heap']). + provider('$angularCacheFactory', $AngularCacheFactoryProvider); + +})(window, window.angular); + +// Chosen, a Select Box Enhancer for jQuery and Prototype +// by Patrick Filler for Harvest, http://getharvest.com +// +// Version 1.0.0 +// Full source at https://github.com/harvesthq/chosen +// Copyright (c) 2011 Harvest http://getharvest.com + +// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +// This file is generated by `grunt build`, do not edit it by hand. +(function() { + var $, AbstractChosen, Chosen, SelectParser, _ref, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + SelectParser = (function() { + function SelectParser() { + this.options_index = 0; + this.parsed = []; + } + + SelectParser.prototype.add_node = function(child) { + if (child.nodeName.toUpperCase() === "OPTGROUP") { + return this.add_group(child); + } else { + return this.add_option(child); + } + }; + + SelectParser.prototype.add_group = function(group) { + var group_position, option, _i, _len, _ref, _results; + + group_position = this.parsed.length; + this.parsed.push({ + array_index: group_position, + group: true, + label: this.escapeExpression(group.label), + children: 0, + disabled: group.disabled + }); + _ref = group.childNodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + _results.push(this.add_option(option, group_position, group.disabled)); + } + return _results; + }; + + SelectParser.prototype.add_option = function(option, group_position, group_disabled) { + if (option.nodeName.toUpperCase() === "OPTION") { + if (option.text !== "") { + if (group_position != null) { + this.parsed[group_position].children += 1; + } + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + value: option.value, + text: option.text, + html: option.innerHTML, + selected: option.selected, + disabled: group_disabled === true ? group_disabled : option.disabled, + group_array_index: group_position, + classes: option.className, + style: option.style.cssText + }); + } else { + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + empty: true + }); + } + return this.options_index += 1; + } + }; + + SelectParser.prototype.escapeExpression = function(text) { + var map, unsafe_chars; + + if ((text == null) || text === false) { + return ""; + } + if (!/[\&\<\>\"\'\`]/.test(text)) { + return text; + } + map = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g; + return text.replace(unsafe_chars, function(chr) { + return map[chr] || "&"; + }); + }; + + return SelectParser; + + })(); + + SelectParser.select_to_array = function(select) { + var child, parser, _i, _len, _ref; + + parser = new SelectParser(); + _ref = select.childNodes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + parser.add_node(child); + } + return parser.parsed; + }; + + AbstractChosen = (function() { + function AbstractChosen(form_field, options) { + this.form_field = form_field; + this.options = options != null ? options : {}; + if (!AbstractChosen.browser_is_supported()) { + return; + } + this.is_multiple = this.form_field.multiple; + this.set_default_text(); + this.set_default_values(); + this.setup(); + this.set_up_html(); + this.register_observers(); + } + + AbstractChosen.prototype.set_default_values = function() { + var _this = this; + + this.click_test_action = function(evt) { + return _this.test_active_click(evt); + }; + this.activate_action = function(evt) { + return _this.activate_field(evt); + }; + this.active_field = false; + this.mouse_on_container = false; + this.results_showing = false; + this.result_highlighted = null; + this.result_single_selected = null; + this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; + this.disable_search_threshold = this.options.disable_search_threshold || 0; + this.disable_search = this.options.disable_search || false; + this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true; + this.group_search = this.options.group_search != null ? this.options.group_search : true; + this.search_contains = this.options.search_contains || false; + this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true; + this.max_selected_options = this.options.max_selected_options || Infinity; + this.inherit_select_classes = this.options.inherit_select_classes || false; + this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true; + return this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true; + }; + + AbstractChosen.prototype.set_default_text = function() { + if (this.form_field.getAttribute("data-placeholder")) { + this.default_text = this.form_field.getAttribute("data-placeholder"); + } else if (this.is_multiple) { + this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text; + } else { + this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text; + } + return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text; + }; + + AbstractChosen.prototype.mouse_enter = function() { + return this.mouse_on_container = true; + }; + + AbstractChosen.prototype.mouse_leave = function() { + return this.mouse_on_container = false; + }; + + AbstractChosen.prototype.input_focus = function(evt) { + var _this = this; + + if (this.is_multiple) { + if (!this.active_field) { + return setTimeout((function() { + return _this.container_mousedown(); + }), 50); + } + } else { + if (!this.active_field) { + return this.activate_field(); + } + } + }; + + AbstractChosen.prototype.input_blur = function(evt) { + var _this = this; + + if (!this.mouse_on_container) { + this.active_field = false; + return setTimeout((function() { + return _this.blur_test(); + }), 100); + } + }; + + AbstractChosen.prototype.results_option_build = function(options) { + var content, data, _i, _len, _ref; + + content = ''; + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + if (data.group) { + content += this.result_add_group(data); + } else { + content += this.result_add_option(data); + } + if (options != null ? options.first : void 0) { + if (data.selected && this.is_multiple) { + this.choice_build(data); + } else if (data.selected && !this.is_multiple) { + this.single_set_selected_text(data.text); + } + } + } + return content; + }; + + AbstractChosen.prototype.result_add_option = function(option) { + var classes, style; + + if (!option.search_match) { + return ''; + } + if (!this.include_option_in_results(option)) { + return ''; + } + classes = []; + if (!option.disabled && !(option.selected && this.is_multiple)) { + classes.push("active-result"); + } + if (option.disabled && !(option.selected && this.is_multiple)) { + classes.push("disabled-result"); + } + if (option.selected) { + classes.push("result-selected"); + } + if (option.group_array_index != null) { + classes.push("group-option"); + } + if (option.classes !== "") { + classes.push(option.classes); + } + style = option.style.cssText !== "" ? " style=\"" + option.style + "\"" : ""; + return "
    • " + option.search_text + "
    • "; + }; + + AbstractChosen.prototype.result_add_group = function(group) { + if (!(group.search_match || group.group_match)) { + return ''; + } + if (!(group.active_options > 0)) { + return ''; + } + return "
    • " + group.search_text + "
    • "; + }; + + AbstractChosen.prototype.results_update_field = function() { + this.set_default_text(); + if (!this.is_multiple) { + this.results_reset_cleanup(); + } + this.result_clear_highlight(); + this.result_single_selected = null; + this.results_build(); + if (this.results_showing) { + return this.winnow_results(); + } + }; + + AbstractChosen.prototype.results_toggle = function() { + if (this.results_showing) { + return this.results_hide(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.results_search = function(evt) { + if (this.results_showing) { + return this.winnow_results(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.winnow_results = function() { + var escapedSearchText, option, regex, regexAnchor, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref; + + this.no_results_clear(); + results = 0; + searchText = this.get_search_text(); + escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + regexAnchor = this.search_contains ? "" : "^"; + regex = new RegExp(regexAnchor + escapedSearchText, 'i'); + zregex = new RegExp(escapedSearchText, 'i'); + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + option.search_match = false; + results_group = null; + if (this.include_option_in_results(option)) { + if (option.group) { + option.group_match = false; + option.active_options = 0; + } + if ((option.group_array_index != null) && this.results_data[option.group_array_index]) { + results_group = this.results_data[option.group_array_index]; + if (results_group.active_options === 0 && results_group.search_match) { + results += 1; + } + results_group.active_options += 1; + } + if (!(option.group && !this.group_search)) { + option.search_text = option.group ? option.label : option.html; + option.search_match = this.search_string_match(option.search_text, regex); + if (option.search_match && !option.group) { + results += 1; + } + if (option.search_match) { + if (searchText.length) { + startpos = option.search_text.search(zregex); + text = option.search_text.substr(0, startpos + searchText.length) + '' + option.search_text.substr(startpos + searchText.length); + option.search_text = text.substr(0, startpos) + '' + text.substr(startpos); + } + if (results_group != null) { + results_group.group_match = true; + } + } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) { + option.search_match = true; + } + } + } + } + this.result_clear_highlight(); + if (results < 1 && searchText.length) { + this.update_results_content(""); + return this.no_results(searchText); + } else { + this.update_results_content(this.results_option_build()); + return this.winnow_results_set_highlight(); + } + }; + + AbstractChosen.prototype.search_string_match = function(search_string, regex) { + var part, parts, _i, _len; + + if (regex.test(search_string)) { + return true; + } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) { + parts = search_string.replace(/\[|\]/g, "").split(" "); + if (parts.length) { + for (_i = 0, _len = parts.length; _i < _len; _i++) { + part = parts[_i]; + if (regex.test(part)) { + return true; + } + } + } + } + }; + + AbstractChosen.prototype.choices_count = function() { + var option, _i, _len, _ref; + + if (this.selected_option_count != null) { + return this.selected_option_count; + } + this.selected_option_count = 0; + _ref = this.form_field.options; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + if (option.selected) { + this.selected_option_count += 1; + } + } + return this.selected_option_count; + }; + + AbstractChosen.prototype.choices_click = function(evt) { + evt.preventDefault(); + if (!(this.results_showing || this.is_disabled)) { + return this.results_show(); + } + }; + + AbstractChosen.prototype.keyup_checker = function(evt) { + var stroke, _ref; + + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + switch (stroke) { + case 8: + if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) { + return this.keydown_backstroke(); + } else if (!this.pending_backstroke) { + this.result_clear_highlight(); + return this.results_search(); + } + break; + case 13: + evt.preventDefault(); + if (this.results_showing) { + return this.result_select(evt); + } + break; + case 27: + if (this.results_showing) { + this.results_hide(); + } + return true; + case 9: + case 38: + case 40: + case 16: + case 91: + case 17: + break; + default: + return this.results_search(); + } + }; + + AbstractChosen.prototype.container_width = function() { + if (this.options.width != null) { + return this.options.width; + } else { + return "" + this.form_field.offsetWidth + "px"; + } + }; + + AbstractChosen.prototype.include_option_in_results = function(option) { + if (this.is_multiple && (!this.display_selected_options && option.selected)) { + return false; + } + if (!this.display_disabled_options && option.disabled) { + return false; + } + if (option.empty) { + return false; + } + return true; + }; + + AbstractChosen.browser_is_supported = function() { + if (window.navigator.appName === "Microsoft Internet Explorer") { + return document.documentMode >= 8; + } + if (/iP(od|hone)/i.test(window.navigator.userAgent)) { + return false; + } + if (/Android/i.test(window.navigator.userAgent)) { + if (/Mobile/i.test(window.navigator.userAgent)) { + return false; + } + } + return true; + }; + + AbstractChosen.default_multiple_text = "Select Some Options"; + + AbstractChosen.default_single_text = "Select an Option"; + + AbstractChosen.default_no_result_text = "No results match"; + + return AbstractChosen; + + })(); + + $ = jQuery; + + $.fn.extend({ + chosen: function(options) { + if (!AbstractChosen.browser_is_supported()) { + return this; + } + return this.each(function(input_field) { + var $this, chosen; + + $this = $(this); + chosen = $this.data('chosen'); + if (options === 'destroy' && chosen) { + chosen.destroy(); + } else if (!chosen) { + $this.data('chosen', new Chosen(this, options)); + } + }); + } + }); + + Chosen = (function(_super) { + __extends(Chosen, _super); + + function Chosen() { + _ref = Chosen.__super__.constructor.apply(this, arguments); + return _ref; + } + + Chosen.prototype.setup = function() { + this.form_field_jq = $(this.form_field); + this.current_selectedIndex = this.form_field.selectedIndex; + return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl"); + }; + + Chosen.prototype.set_up_html = function() { + var container_classes, container_props; + + container_classes = ["chosen-container"]; + container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single")); + if (this.inherit_select_classes && this.form_field.className) { + container_classes.push(this.form_field.className); + } + if (this.is_rtl) { + container_classes.push("chosen-rtl"); + } + container_props = { + 'class': container_classes.join(' '), + 'style': "width: " + (this.container_width()) + ";", + 'title': this.form_field.title + }; + if (this.form_field.id.length) { + container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen"; + } + this.container = $("
      ", container_props); + if (this.is_multiple) { + this.container.html('
        '); + } else { + this.container.html('' + this.default_text + '
          '); + } + this.form_field_jq.hide().after(this.container); + this.dropdown = this.container.find('div.chosen-drop').first(); + this.search_field = this.container.find('input').first(); + this.search_results = this.container.find('ul.chosen-results').first(); + this.search_field_scale(); + this.search_no_results = this.container.find('li.no-results').first(); + if (this.is_multiple) { + this.search_choices = this.container.find('ul.chosen-choices').first(); + this.search_container = this.container.find('li.search-field').first(); + } else { + this.search_container = this.container.find('div.chosen-search').first(); + this.selected_item = this.container.find('.chosen-single').first(); + } + this.results_build(); + this.set_tab_index(); + this.set_label_behavior(); + return this.form_field_jq.trigger("chosen:ready", { + chosen: this + }); + }; + + Chosen.prototype.register_observers = function() { + var _this = this; + + this.container.bind('mousedown.chosen', function(evt) { + _this.container_mousedown(evt); + }); + this.container.bind('mouseup.chosen', function(evt) { + _this.container_mouseup(evt); + }); + this.container.bind('mouseenter.chosen', function(evt) { + _this.mouse_enter(evt); + }); + this.container.bind('mouseleave.chosen', function(evt) { + _this.mouse_leave(evt); + }); + this.search_results.bind('mouseup.chosen', function(evt) { + _this.search_results_mouseup(evt); + }); + this.search_results.bind('mouseover.chosen', function(evt) { + _this.search_results_mouseover(evt); + }); + this.search_results.bind('mouseout.chosen', function(evt) { + _this.search_results_mouseout(evt); + }); + this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) { + _this.search_results_mousewheel(evt); + }); + this.form_field_jq.bind("chosen:updated.chosen", function(evt) { + _this.results_update_field(evt); + }); + this.form_field_jq.bind("chosen:activate.chosen", function(evt) { + _this.activate_field(evt); + }); + this.form_field_jq.bind("chosen:open.chosen", function(evt) { + _this.container_mousedown(evt); + }); + this.search_field.bind('blur.chosen', function(evt) { + _this.input_blur(evt); + }); + this.search_field.bind('keyup.chosen', function(evt) { + _this.keyup_checker(evt); + }); + this.search_field.bind('keydown.chosen', function(evt) { + _this.keydown_checker(evt); + }); + this.search_field.bind('focus.chosen', function(evt) { + _this.input_focus(evt); + }); + if (this.is_multiple) { + return this.search_choices.bind('click.chosen', function(evt) { + _this.choices_click(evt); + }); + } else { + return this.container.bind('click.chosen', function(evt) { + evt.preventDefault(); + }); + } + }; + + Chosen.prototype.destroy = function() { + $(document).unbind("click.chosen", this.click_test_action); + if (this.search_field[0].tabIndex) { + this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex; + } + this.container.remove(); + this.form_field_jq.removeData('chosen'); + return this.form_field_jq.show(); + }; + + Chosen.prototype.search_field_disabled = function() { + this.is_disabled = this.form_field_jq[0].disabled; + if (this.is_disabled) { + this.container.addClass('chosen-disabled'); + this.search_field[0].disabled = true; + if (!this.is_multiple) { + this.selected_item.unbind("focus.chosen", this.activate_action); + } + return this.close_field(); + } else { + this.container.removeClass('chosen-disabled'); + this.search_field[0].disabled = false; + if (!this.is_multiple) { + return this.selected_item.bind("focus.chosen", this.activate_action); + } + } + }; + + Chosen.prototype.container_mousedown = function(evt) { + if (!this.is_disabled) { + if (evt && evt.type === "mousedown" && !this.results_showing) { + evt.preventDefault(); + } + if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) { + if (!this.active_field) { + if (this.is_multiple) { + this.search_field.val(""); + } + $(document).bind('click.chosen', this.click_test_action); + this.results_show(); + } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) { + evt.preventDefault(); + this.results_toggle(); + } + return this.activate_field(); + } + } + }; + + Chosen.prototype.container_mouseup = function(evt) { + if (evt.target.nodeName === "ABBR" && !this.is_disabled) { + return this.results_reset(evt); + } + }; + + Chosen.prototype.search_results_mousewheel = function(evt) { + var delta, _ref1, _ref2; + + delta = -((_ref1 = evt.originalEvent) != null ? _ref1.wheelDelta : void 0) || ((_ref2 = evt.originialEvent) != null ? _ref2.detail : void 0); + if (delta != null) { + evt.preventDefault(); + if (evt.type === 'DOMMouseScroll') { + delta = delta * 40; + } + return this.search_results.scrollTop(delta + this.search_results.scrollTop()); + } + }; + + Chosen.prototype.blur_test = function(evt) { + if (!this.active_field && this.container.hasClass("chosen-container-active")) { + return this.close_field(); + } + }; + + Chosen.prototype.close_field = function() { + $(document).unbind("click.chosen", this.click_test_action); + this.active_field = false; + this.results_hide(); + this.container.removeClass("chosen-container-active"); + this.clear_backstroke(); + this.show_search_field_default(); + return this.search_field_scale(); + }; + + Chosen.prototype.activate_field = function() { + this.container.addClass("chosen-container-active"); + this.active_field = true; + this.search_field.val(this.search_field.val()); + return this.search_field.focus(); + }; + + Chosen.prototype.test_active_click = function(evt) { + if (this.container.is($(evt.target).closest('.chosen-container'))) { + return this.active_field = true; + } else { + return this.close_field(); + } + }; + + Chosen.prototype.results_build = function() { + this.parsing = true; + this.selected_option_count = null; + this.results_data = SelectParser.select_to_array(this.form_field); + if (this.is_multiple) { + this.search_choices.find("li.search-choice").remove(); + } else if (!this.is_multiple) { + this.single_set_selected_text(); + if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) { + this.search_field[0].readOnly = true; + this.container.addClass("chosen-container-single-nosearch"); + } else { + this.search_field[0].readOnly = false; + this.container.removeClass("chosen-container-single-nosearch"); + } + } + this.update_results_content(this.results_option_build({ + first: true + })); + this.search_field_disabled(); + this.show_search_field_default(); + this.search_field_scale(); + return this.parsing = false; + }; + + Chosen.prototype.result_do_highlight = function(el) { + var high_bottom, high_top, maxHeight, visible_bottom, visible_top; + + if (el.length) { + this.result_clear_highlight(); + this.result_highlight = el; + this.result_highlight.addClass("highlighted"); + maxHeight = parseInt(this.search_results.css("maxHeight"), 10); + visible_top = this.search_results.scrollTop(); + visible_bottom = maxHeight + visible_top; + high_top = this.result_highlight.position().top + this.search_results.scrollTop(); + high_bottom = high_top + this.result_highlight.outerHeight(); + if (high_bottom >= visible_bottom) { + return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0); + } else if (high_top < visible_top) { + return this.search_results.scrollTop(high_top); + } + } + }; + + Chosen.prototype.result_clear_highlight = function() { + if (this.result_highlight) { + this.result_highlight.removeClass("highlighted"); + } + return this.result_highlight = null; + }; + + Chosen.prototype.results_show = function() { + if (this.is_multiple && this.max_selected_options <= this.choices_count()) { + this.form_field_jq.trigger("chosen:maxselected", { + chosen: this + }); + return false; + } + this.container.addClass("chosen-with-drop"); + this.form_field_jq.trigger("chosen:showing_dropdown", { + chosen: this + }); + this.results_showing = true; + this.search_field.focus(); + this.search_field.val(this.search_field.val()); + return this.winnow_results(); + }; + + Chosen.prototype.update_results_content = function(content) { + return this.search_results.html(content); + }; + + Chosen.prototype.results_hide = function() { + if (this.results_showing) { + this.result_clear_highlight(); + this.container.removeClass("chosen-with-drop"); + this.form_field_jq.trigger("chosen:hiding_dropdown", { + chosen: this + }); + } + return this.results_showing = false; + }; + + Chosen.prototype.set_tab_index = function(el) { + var ti; + + if (this.form_field.tabIndex) { + ti = this.form_field.tabIndex; + this.form_field.tabIndex = -1; + return this.search_field[0].tabIndex = ti; + } + }; + + Chosen.prototype.set_label_behavior = function() { + var _this = this; + + this.form_field_label = this.form_field_jq.parents("label"); + if (!this.form_field_label.length && this.form_field.id.length) { + this.form_field_label = $("label[for='" + this.form_field.id + "']"); + } + if (this.form_field_label.length > 0) { + return this.form_field_label.bind('click.chosen', function(evt) { + if (_this.is_multiple) { + return _this.container_mousedown(evt); + } else { + return _this.activate_field(); + } + }); + } + }; + + Chosen.prototype.show_search_field_default = function() { + if (this.is_multiple && this.choices_count() < 1 && !this.active_field) { + this.search_field.val(this.default_text); + return this.search_field.addClass("default"); + } else { + this.search_field.val(""); + return this.search_field.removeClass("default"); + } + }; + + Chosen.prototype.search_results_mouseup = function(evt) { + var target; + + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target.length) { + this.result_highlight = target; + this.result_select(evt); + return this.search_field.focus(); + } + }; + + Chosen.prototype.search_results_mouseover = function(evt) { + var target; + + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target) { + return this.result_do_highlight(target); + } + }; + + Chosen.prototype.search_results_mouseout = function(evt) { + if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) { + return this.result_clear_highlight(); + } + }; + + Chosen.prototype.choice_build = function(item) { + var choice, close_link, + _this = this; + + choice = $('
        • ', { + "class": "search-choice" + }).html("" + item.html + ""); + if (item.disabled) { + choice.addClass('search-choice-disabled'); + } else { + close_link = $('', { + "class": 'search-choice-close', + 'data-option-array-index': item.array_index + }); + close_link.bind('click.chosen', function(evt) { + return _this.choice_destroy_link_click(evt); + }); + choice.append(close_link); + } + return this.search_container.before(choice); + }; + + Chosen.prototype.choice_destroy_link_click = function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (!this.is_disabled) { + return this.choice_destroy($(evt.target)); + } + }; + + Chosen.prototype.choice_destroy = function(link) { + if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) { + this.show_search_field_default(); + if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) { + this.results_hide(); + } + link.parents('li').first().remove(); + return this.search_field_scale(); + } + }; + + Chosen.prototype.results_reset = function() { + this.form_field.options[0].selected = true; + this.selected_option_count = null; + this.single_set_selected_text(); + this.show_search_field_default(); + this.results_reset_cleanup(); + this.form_field_jq.trigger("change"); + if (this.active_field) { + return this.results_hide(); + } + }; + + Chosen.prototype.results_reset_cleanup = function() { + this.current_selectedIndex = this.form_field.selectedIndex; + return this.selected_item.find("abbr").remove(); + }; + + Chosen.prototype.result_select = function(evt) { + var high, item, selected_index; + + if (this.result_highlight) { + high = this.result_highlight; + this.result_clear_highlight(); + if (this.is_multiple && this.max_selected_options <= this.choices_count()) { + this.form_field_jq.trigger("chosen:maxselected", { + chosen: this + }); + return false; + } + if (this.is_multiple) { + high.removeClass("active-result"); + } else { + if (this.result_single_selected) { + this.result_single_selected.removeClass("result-selected"); + selected_index = this.result_single_selected[0].getAttribute('data-option-array-index'); + this.results_data[selected_index].selected = false; + } + this.result_single_selected = high; + } + high.addClass("result-selected"); + item = this.results_data[high[0].getAttribute("data-option-array-index")]; + item.selected = true; + this.form_field.options[item.options_index].selected = true; + this.selected_option_count = null; + if (this.is_multiple) { + this.choice_build(item); + } else { + this.single_set_selected_text(item.text); + } + if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) { + this.results_hide(); + } + this.search_field.val(""); + if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) { + this.form_field_jq.trigger("change", { + 'selected': this.form_field.options[item.options_index].value + }); + } + this.current_selectedIndex = this.form_field.selectedIndex; + return this.search_field_scale(); + } + }; + + Chosen.prototype.single_set_selected_text = function(text) { + if (text == null) { + text = this.default_text; + } + if (text === this.default_text) { + this.selected_item.addClass("chosen-default"); + } else { + this.single_deselect_control_build(); + this.selected_item.removeClass("chosen-default"); + } + return this.selected_item.find("span").text(text); + }; + + Chosen.prototype.result_deselect = function(pos) { + var result_data; + + result_data = this.results_data[pos]; + if (!this.form_field.options[result_data.options_index].disabled) { + result_data.selected = false; + this.form_field.options[result_data.options_index].selected = false; + this.selected_option_count = null; + this.result_clear_highlight(); + if (this.results_showing) { + this.winnow_results(); + } + this.form_field_jq.trigger("change", { + deselected: this.form_field.options[result_data.options_index].value + }); + this.search_field_scale(); + return true; + } else { + return false; + } + }; + + Chosen.prototype.single_deselect_control_build = function() { + if (!this.allow_single_deselect) { + return; + } + if (!this.selected_item.find("abbr").length) { + this.selected_item.find("span").first().after(""); + } + return this.selected_item.addClass("chosen-single-with-deselect"); + }; + + Chosen.prototype.get_search_text = function() { + if (this.search_field.val() === this.default_text) { + return ""; + } else { + return $('
          ').text($.trim(this.search_field.val())).html(); + } + }; + + Chosen.prototype.winnow_results_set_highlight = function() { + var do_high, selected_results; + + selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : []; + do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first(); + if (do_high != null) { + return this.result_do_highlight(do_high); + } + }; + + Chosen.prototype.no_results = function(terms) { + var no_results_html; + + no_results_html = $('
        • ' + this.results_none_found + ' ""
        • '); + no_results_html.find("span").first().html(terms); + return this.search_results.append(no_results_html); + }; + + Chosen.prototype.no_results_clear = function() { + return this.search_results.find(".no-results").remove(); + }; + + Chosen.prototype.keydown_arrow = function() { + var next_sib; + + if (this.results_showing && this.result_highlight) { + next_sib = this.result_highlight.nextAll("li.active-result").first(); + if (next_sib) { + return this.result_do_highlight(next_sib); + } + } else { + return this.results_show(); + } + }; + + Chosen.prototype.keyup_arrow = function() { + var prev_sibs; + + if (!this.results_showing && !this.is_multiple) { + return this.results_show(); + } else if (this.result_highlight) { + prev_sibs = this.result_highlight.prevAll("li.active-result"); + if (prev_sibs.length) { + return this.result_do_highlight(prev_sibs.first()); + } else { + if (this.choices_count() > 0) { + this.results_hide(); + } + return this.result_clear_highlight(); + } + } + }; + + Chosen.prototype.keydown_backstroke = function() { + var next_available_destroy; + + if (this.pending_backstroke) { + this.choice_destroy(this.pending_backstroke.find("a").first()); + return this.clear_backstroke(); + } else { + next_available_destroy = this.search_container.siblings("li.search-choice").last(); + if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) { + this.pending_backstroke = next_available_destroy; + if (this.single_backstroke_delete) { + return this.keydown_backstroke(); + } else { + return this.pending_backstroke.addClass("search-choice-focus"); + } + } + } + }; + + Chosen.prototype.clear_backstroke = function() { + if (this.pending_backstroke) { + this.pending_backstroke.removeClass("search-choice-focus"); + } + return this.pending_backstroke = null; + }; + + Chosen.prototype.keydown_checker = function(evt) { + var stroke, _ref1; + + stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode; + this.search_field_scale(); + if (stroke !== 8 && this.pending_backstroke) { + this.clear_backstroke(); + } + switch (stroke) { + case 8: + this.backstroke_length = this.search_field.val().length; + break; + case 9: + if (this.results_showing && !this.is_multiple) { + this.result_select(evt); + } + this.mouse_on_container = false; + break; + case 13: + evt.preventDefault(); + break; + case 38: + evt.preventDefault(); + this.keyup_arrow(); + break; + case 40: + evt.preventDefault(); + this.keydown_arrow(); + break; + } + }; + + Chosen.prototype.search_field_scale = function() { + var div, f_width, h, style, style_block, styles, w, _i, _len; + + if (this.is_multiple) { + h = 0; + w = 0; + style_block = "position:absolute; left: -1000px; top: -1000px; display:none;"; + styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing']; + for (_i = 0, _len = styles.length; _i < _len; _i++) { + style = styles[_i]; + style_block += style + ":" + this.search_field.css(style) + ";"; + } + div = $('
          ', { + 'style': style_block + }); + div.text(this.search_field.val()); + $('body').append(div); + w = div.width() + 25; + div.remove(); + f_width = this.container.outerWidth(); + if (w > f_width - 10) { + w = f_width - 10; + } + return this.search_field.css({ + 'width': w + 'px' + }); + } + }; + + return Chosen; + + })(AbstractChosen); + +}).call(this); + +/**! + * AngularJS file upload/drop directive with http post and progress + * @author Danial + * @version 1.6.12 + */ +(function() { + +var angularFileUpload = angular.module('angularFileUpload', []); + +angularFileUpload.service('$upload', ['$http', '$q', '$timeout', function($http, $q, $timeout) { + function sendHttp(config) { + config.method = config.method || 'POST'; + config.headers = config.headers || {}; + config.transformRequest = config.transformRequest || function(data, headersGetter) { + if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { + return data; + } + return $http.defaults.transformRequest[0](data, headersGetter); + }; + var deferred = $q.defer(); + + if (window.XMLHttpRequest.__isShim) { + config.headers['__setXHR_'] = function() { + return function(xhr) { + if (!xhr) return; + config.__XHR = xhr; + config.xhrFn && config.xhrFn(xhr); + xhr.upload.addEventListener('progress', function(e) { + deferred.notify(e); + }, false); + //fix for firefox not firing upload progress end, also IE8-9 + xhr.upload.addEventListener('load', function(e) { + if (e.lengthComputable) { + deferred.notify(e); + } + }, false); + }; + }; + } + + $http(config).then(function(r){deferred.resolve(r)}, function(e){deferred.reject(e)}, function(n){deferred.notify(n)}); + + var promise = deferred.promise; + promise.success = function(fn) { + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.progress = function(fn) { + promise.then(null, null, function(update) { + fn(update); + }); + return promise; + }; + promise.abort = function() { + if (config.__XHR) { + $timeout(function() { + config.__XHR.abort(); + }); + } + return promise; + }; + promise.xhr = function(fn) { + config.xhrFn = (function(origXhrFn) { + return function() { + origXhrFn && origXhrFn.apply(promise, arguments); + fn.apply(promise, arguments); + } + })(config.xhrFn); + return promise; + }; + + return promise; + } + + this.upload = function(config) { + config.headers = config.headers || {}; + config.headers['Content-Type'] = undefined; + config.transformRequest = config.transformRequest || $http.defaults.transformRequest; + var formData = new FormData(); + var origTransformRequest = config.transformRequest; + var origData = config.data; + config.transformRequest = function(formData, headerGetter) { + if (origData) { + if (config.formDataAppender) { + for (var key in origData) { + var val = origData[key]; + config.formDataAppender(formData, key, val); + } + } else { + for (var key in origData) { + var val = origData[key]; + if (typeof origTransformRequest == 'function') { + val = origTransformRequest(val, headerGetter); + } else { + for (var i = 0; i < origTransformRequest.length; i++) { + var transformFn = origTransformRequest[i]; + if (typeof transformFn == 'function') { + val = transformFn(val, headerGetter); + } + } + } + formData.append(key, val); + } + } + } + + if (config.file != null) { + var fileFormName = config.fileFormDataName || 'file'; + + if (Object.prototype.toString.call(config.file) === '[object Array]') { + var isFileFormNameString = Object.prototype.toString.call(fileFormName) === '[object String]'; + for (var i = 0; i < config.file.length; i++) { + formData.append(isFileFormNameString ? fileFormName : fileFormName[i], config.file[i], + (config.fileName && config.fileName[i]) || config.file[i].name); + } + } else { + formData.append(fileFormName, config.file, config.fileName || config.file.name); + } + } + return formData; + }; + + config.data = formData; + + return sendHttp(config); + }; + + this.http = function(config) { + return sendHttp(config); + } +}]); + +angularFileUpload.directive('ngFileSelect', [ '$parse', '$timeout', function($parse, $timeout) { + return function(scope, elem, attr) { + var fn = $parse(attr['ngFileSelect']); + if (elem[0].tagName.toLowerCase() !== 'input' || (elem.attr('type') && elem.attr('type').toLowerCase()) !== 'file') { + var fileElem = angular.element('') + var attrs = elem[0].attributes; + for (var i = 0; i < attrs.length; i++) { + if (attrs[i].name.toLowerCase() !== 'type') { + fileElem.attr(attrs[i].name, attrs[i].value); + } + } + if (attr["multiple"]) fileElem.attr("multiple", "true"); + fileElem.css("width", "1px").css("height", "1px").css("opacity", 0).css("position", "absolute").css('filter', 'alpha(opacity=0)') + .css("padding", 0).css("margin", 0).css("overflow", "hidden"); + fileElem.attr('__wrapper_for_parent_', true); + +// fileElem.css("top", 0).css("bottom", 0).css("left", 0).css("right", 0).css("width", "100%"). +// css("opacity", 0).css("position", "absolute").css('filter', 'alpha(opacity=0)').css("padding", 0).css("margin", 0); + elem.append(fileElem); + elem[0].__file_click_fn_delegate_ = function() { + fileElem[0].click(); + }; + elem.bind('click', elem[0].__file_click_fn_delegate_); + elem.css("overflow", "hidden"); +// if (fileElem.parent()[0] != elem[0]) { +// //fix #298 button element +// elem.wrap(''); +// elem.css("z-index", "-1000") +// elem.parent().append(fileElem); +// elem = elem.parent(); +// } +// if (elem.css("position") === '' || elem.css("position") === 'static') { +// elem.css("position", "relative"); +// } + elem = fileElem; + } + elem.bind('change', function(evt) { + var files = [], fileList, i; + fileList = evt.__files_ || evt.target.files; + if (fileList != null) { + for (i = 0; i < fileList.length; i++) { + files.push(fileList.item(i)); + } + } + $timeout(function() { + fn(scope, { + $files : files, + $event : evt + }); + }); + }); + // removed this since it was confusing if the user click on browse and then cancel #181 +// elem.bind('click', function(){ +// this.value = null; +// }); + + // removed because of #253 bug + // touch screens +// if (('ontouchstart' in window) || +// (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { +// elem.bind('touchend', function(e) { +// e.preventDefault(); +// e.target.click(); +// }); +// } + }; +} ]); + +angularFileUpload.directive('ngFileDropAvailable', [ '$parse', '$timeout', function($parse, $timeout) { + return function(scope, elem, attr) { + if ('draggable' in document.createElement('span')) { + var fn = $parse(attr['ngFileDropAvailable']); + $timeout(function() { + fn(scope); + }); + } + }; +} ]); + +angularFileUpload.directive('ngFileDrop', [ '$parse', '$timeout', '$location', function($parse, $timeout, $location) { + return function(scope, elem, attr) { + if ('draggable' in document.createElement('span')) { + var leaveTimeout = null; + elem[0].addEventListener("dragover", function(evt) { + evt.preventDefault(); + $timeout.cancel(leaveTimeout); + if (!elem[0].__drag_over_class_) { + if (attr['ngFileDragOverClass'] && attr['ngFileDragOverClass'].search(/\) *$/) > -1) { + var dragOverClass = $parse(attr['ngFileDragOverClass'])(scope, { + $event : evt + }); + elem[0].__drag_over_class_ = dragOverClass; + } else { + elem[0].__drag_over_class_ = attr['ngFileDragOverClass'] || "dragover"; + } + } + elem.addClass(elem[0].__drag_over_class_); + }, false); + elem[0].addEventListener("dragenter", function(evt) { + evt.preventDefault(); + }, false); + elem[0].addEventListener("dragleave", function(evt) { + leaveTimeout = $timeout(function() { + elem.removeClass(elem[0].__drag_over_class_); + elem[0].__drag_over_class_ = null; + }, attr['ngFileDragOverDelay'] || 1); + }, false); + var fn = $parse(attr['ngFileDrop']); + elem[0].addEventListener("drop", function(evt) { + evt.preventDefault(); + elem.removeClass(elem[0].__drag_over_class_); + elem[0].__drag_over_class_ = null; + extractFiles(evt, function(files) { + fn(scope, { + $files : files, + $event : evt + }); + }); + }, false); + + function isASCII(str) { + return /^[\000-\177]*$/.test(str); + } + + function extractFiles(evt, callback) { + var files = [], items = evt.dataTransfer.items; + if (items && items.length > 0 && items[0].webkitGetAsEntry && $location.protocol() != 'file' && + items[0].webkitGetAsEntry().isDirectory) { + for (var i = 0; i < items.length; i++) { + var entry = items[i].webkitGetAsEntry(); + if (entry != null) { + //fix for chrome bug https://code.google.com/p/chromium/issues/detail?id=149735 + if (isASCII(entry.name)) { + traverseFileTree(files, entry); + } else if (!items[i].webkitGetAsEntry().isDirectory) { + files.push(items[i].getAsFile()); + } + } + } + } else { + var fileList = evt.dataTransfer.files; + if (fileList != null) { + for (var i = 0; i < fileList.length; i++) { + files.push(fileList.item(i)); + } + } + } + (function waitForProcess(delay) { + $timeout(function() { + if (!processing) { + callback(files); + } else { + waitForProcess(10); + } + }, delay || 0) + })(); + } + + var processing = 0; + function traverseFileTree(files, entry, path) { + if (entry != null) { + if (entry.isDirectory) { + var dirReader = entry.createReader(); + processing++; + dirReader.readEntries(function(entries) { + for (var i = 0; i < entries.length; i++) { + traverseFileTree(files, entries[i], (path ? path : "") + entry.name + "/"); + } + processing--; + }); + } else { + processing++; + entry.file(function(file) { + processing--; + file._relativePath = (path ? path : "") + file.name; + files.push(file); + }); + } + } + } + } + }; +} ]); + +})(); diff --git a/assets/redhat_access_angular_ui.js b/assets/redhat_access_angular_ui.js index 39b5bb2..aaa6b17 100644 --- a/assets/redhat_access_angular_ui.js +++ b/assets/redhat_access_angular_ui.js @@ -1,17741 +1,147 @@ -/*! redhat_access_angular_ui - v0.9.1 - 2014-09-02 +/*! redhat_access_angular_ui - v0.9.36 - 2014-11-25 * Copyright (c) 2014 ; * Licensed */ -/*! - * Copyright (c) 2006 js-markdown-extra developers - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -var MARKDOWN_VERSION = "1.0.1o"; -var MARKDOWNEXTRA_VERSION = "1.2.5"; - -// Global default settings: - -/** Change to ">" for HTML output */ -var MARKDOWN_EMPTY_ELEMENT_SUFFIX = " />"; - -/** Define the width of a tab for code blocks. */ -var MARKDOWN_TAB_WIDTH = 4; - -/** Optional title attribute for footnote links and backlinks. */ -var MARKDOWN_FN_LINK_TITLE = ""; -var MARKDOWN_FN_BACKLINK_TITLE = ""; - -/** Optional class attribute for footnote links and backlinks. */ -var MARKDOWN_FN_LINK_CLASS = ""; -var MARKDOWN_FN_BACKLINK_CLASS = ""; - -/** Change to false to remove Markdown from posts and/or comments. */ -var MARKDOWN_WP_POSTS = true; -var MARKDOWN_WP_COMMENTS = true; - -/** Standard Function Interface */ -MARKDOWN_PARSER_CLASS = 'MarkdownExtra_Parser'; - -/** - * Converts Markdown formatted text to HTML. - * @param text Markdown text - * @return HTML - */ -function Markdown(text) { - //Initialize the parser and return the result of its transform method. - var parser; - if('undefined' == typeof arguments.callee.parser) { - parser = eval("new " + MARKDOWN_PARSER_CLASS + "()"); - parser.init(); - arguments.callee.parser = parser; - } - else { - parser = arguments.callee.parser; - } - // Transform text using parser. - return parser.transform(text); -} - -/** - * Constructor function. Initialize appropriate member variables. - */ -function Markdown_Parser() { - - this.nested_brackets_depth = 6; - this.nested_url_parenthesis_depth = 4; - this.escape_chars = "\\\\`*_{}[]()>#+-.!"; - - // Document transformations - this.document_gamut = [ - // Strip link definitions, store in hashes. - ['stripLinkDefinitions', 20], - ['runBasicBlockGamut', 30] - ]; - - // These are all the transformations that form block-level - /// tags like paragraphs, headers, and list items. - this.block_gamut = [ - ['doHeaders', 10], - ['doHorizontalRules', 20], - ['doLists', 40], - ['doCodeBlocks', 50], - ['doBlockQuotes', 60] - ]; - - // These are all the transformations that occur *within* block-level - // tags like paragraphs, headers, and list items. - this.span_gamut = [ - // Process character escapes, code spans, and inline HTML - // in one shot. - ['parseSpan', -30], - // Process anchor and image tags. Images must come first, - // because ![foo][f] looks like an anchor. - ['doImages', 10], - ['doAnchors', 20], - // Make links out of things like `` - // Must come after doAnchors, because you can use < and > - // delimiters in inline links like [this](). - ['doAutoLinks', 30], - ['encodeAmpsAndAngles', 40], - ['doItalicsAndBold', 50], - ['doHardBreaks', 60] - ]; - - this.em_relist = [ - ['' , '(?:(^|[^\\*])(\\*)(?=[^\\*])|(^|[^_])(_)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['*', '((?:\\S|^)[^\\*])(\\*)(?!\\*)'], - ['_', '((?:\\S|^)[^_])(_)(?!_)'] - ]; - this.strong_relist = [ - ['' , '(?:(^|[^\\*])(\\*\\*)(?=[^\\*])|(^|[^_])(__)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['**', '((?:\\S|^)[^\\*])(\\*\\*)(?!\\*)'], - ['__', '((?:\\S|^)[^_])(__)(?!_)'] - ]; - this.em_strong_relist = [ - ['' , '(?:(^|[^\\*])(\\*\\*\\*)(?=[^\\*])|(^|[^_])(___)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['***', '((?:\\S|^)[^\\*])(\\*\\*\\*)(?!\\*)'], - ['___', '((?:\\S|^)[^_])(___)(?!_)'] - ]; -} - -Markdown_Parser.prototype.init = function() { - // this._initDetab(); // NOTE: JavaScript string length is already based on Unicode - this.prepareItalicsAndBold(); - - // Regex to match balanced [brackets]. - // Needed to insert a maximum bracked depth while converting to PHP. - // NOTE: JavaScript doesn't have so faster option for RegExp - //this.nested_brackets_re = new RegExp( - // str_repeat('(?>[^\\[\\]]+|\\[', this.nested_brackets_depth) + - // str_repeat('\\])*', this.nested_brackets_depth) - //); - // NOTE: JavaScript doesn't have so faster option for RegExp - //this.nested_url_parenthesis_re = new RegExp( - // str_repeat('(?>[^()\\s]+|\\(', this.nested_url_parenthesis_depth) + - // str_repeat('(?>\\)))*', this.nested_url_parenthesis_depth) - //); - this.nested_brackets_re = - this._php_str_repeat('(?:[^\\[\\]]+|\\[', this.nested_brackets_depth) + - this._php_str_repeat('\\])*', this.nested_brackets_depth); - this.nested_url_parenthesis_re = - this._php_str_repeat('(?:[^\\(\\)\\s]+|\\(', this.nested_url_parenthesis_depth) + - this._php_str_repeat('(?:\\)))*', this.nested_url_parenthesis_depth); - - // Table of hash values for escaped characters: - var tmp = []; - for(var i = 0; i < this.escape_chars.length; i++) { - tmp.push(this._php_preg_quote(this.escape_chars.charAt(i))); - } - this.escape_chars_re = '[' + tmp.join('') + ']'; - - // Change to ">" for HTML output. - this.empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; - this.tab_width = MARKDOWN_TAB_WIDTH; - - // Change to `true` to disallow markup or entities. - this.no_markup = false; - this.no_entities = false; - - // Predefined urls and titles for reference links and images. - this.predef_urls = {}; - this.predef_titles = {}; - - // Sort document, block, and span gamut in ascendent priority order. - function cmp_gamut(a, b) { - a = a[1]; b = b[1]; - return a > b ? 1 : a < b ? -1 : 0; - } - this.document_gamut.sort(cmp_gamut); - this.block_gamut.sort(cmp_gamut); - this.span_gamut.sort(cmp_gamut); - - // Internal hashes used during transformation. - this.urls = {}; - this.titles = {}; - this.html_hashes = {}; - - // Status flag to avoid invalid nesting. - this.in_anchor = false; -}; - -/** - * [porting note] - * JavaScript's RegExp doesn't have escape code \A and \Z. - * So multiline pattern can't match start/end of text. Instead - * wrap whole of text with STX(02) and ETX(03). - */ -Markdown_Parser.prototype.__wrapSTXETX__ = function(text) { - if(text.charAt(0) != '\x02') { text = '\x02' + text; } - if(text.charAt(text.length - 1) != '\x03') { text = text + '\x03'; } - return text; -}; - -/** - * [porting note] - * Strip STX(02) and ETX(03). - */ -Markdown_Parser.prototype.__unwrapSTXETX__ = function(text) { - if(text.charAt(0) == '\x02') { text = text.substr(1); } - if(text.charAt(text.length - 1) == '\x03') { text = text.substr(0, text.length - 1); } - return text; -}; - -/** - * - */ -Markdown_Parser.prototype._php_preg_quote = function(text) { - if(!arguments.callee.sRE) { - arguments.callee.sRE = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}\\)/g; - } - return text.replace(arguments.callee.sRE, '\\$1'); -}; - -Markdown_Parser.prototype._php_str_repeat = function(str, n) { - var tmp = str; - for(var i = 1; i < n; i++) { - tmp += str; - } - return tmp; -}; - -Markdown_Parser.prototype._php_trim = function(target, charlist) { - var chars = charlist || " \t\n\r"; - return target.replace( - new RegExp("^[" + chars + "]*|[" + chars + "]*$", "g"), "" - ); -}; - -Markdown_Parser.prototype._php_rtrim = function(target, charlist) { - var chars = charlist || " \t\n\r"; - return target.replace( - new RegExp( "[" + chars + "]*$", "g" ), "" - ); -}; - -Markdown_Parser.prototype._php_htmlspecialchars_ENT_NOQUOTES = function(str) { - return str.replace(/&/g, '&').replace(//g, '>'); -}; - - -/** - * Called before the transformation process starts to setup parser - * states. - */ -Markdown_Parser.prototype.setup = function() { - // Clear global hashes. - this.urls = this.predef_urls; - this.titles = this.predef_titles; - this.html_hashes = {}; - - this.in_anchor = false; -}; - -/** - * Called after the transformation process to clear any variable - * which may be taking up memory unnecessarly. - */ -Markdown_Parser.prototype.teardown = function() { - this.urls = {}; - this.titles = {}; - this.html_hashes = {}; -}; - -/** - * Main function. Performs some preprocessing on the input text - * and pass it through the document gamut. - */ -Markdown_Parser.prototype.transform = function(text) { - this.setup(); - - // Remove UTF-8 BOM and marker character in input, if present. - text = text.replace(/^\xEF\xBB\xBF|\x1A/, ""); - - // Standardize line endings: - // DOS to Unix and Mac to Unix - text = text.replace(/\r\n?/, "\n", text); - - // Make sure $text ends with a couple of newlines: - text += "\n\n"; - - // Convert all tabs to spaces. - text = this.detab(text); - - // Turn block-level HTML blocks into hash entries - text = this.hashHTMLBlocks(text); - - // Strip any lines consisting only of spaces and tabs. - // This makes subsequent regexen easier to write, because we can - // match consecutive blank lines with /\n+/ instead of something - // contorted like /[ ]*\n+/ . - text = text.replace(/^[ ]+$/m, ""); - - // Run document gamut methods. - for(var i = 0; i < this.document_gamut.length; i++) { - var method = this[this.document_gamut[i][0]]; - if(method) { - text = method.call(this, text); - } - else { - console.log(this.document_gamut[i][0] + ' not implemented'); - } - } - - this.teardown(); - - return text + "\n"; -}; - -Markdown_Parser.prototype.hashHTMLBlocks = function(text) { - if(this.no_markup) { return text; } - - var less_than_tab = this.tab_width - 1; - - // Hashify HTML blocks: - // We only want to do this for block-level HTML tags, such as headers, - // lists, and tables. That's because we still want to wrap

          s around - // "paragraphs" that are wrapped in non-block-level tags, such as anchors, - // phrase emphasis, and spans. The list of tags we're looking for is - // hard-coded: - // - // * List "a" is made of tags which can be both inline or block-level. - // These will be treated block-level when the start tag is alone on - // its line, otherwise they're not matched here and will be taken as - // inline later. - // * List "b" is made of tags which are always block-level; - - var block_tags_a_re = 'ins|del'; - var block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' + - 'script|noscript|form|fieldset|iframe|math'; - - // Regular expression for the content of a block tag. - var nested_tags_level = 4; - var attr = - '(?:' + // optional tag attributes - '\\s' + // starts with whitespace - '(?:' + - '[^>"/]+' + // text outside quotes - '|' + - '/+(?!>)' + // slash not followed by ">" - '|' + - '"[^"]*"' + // text inside double quotes (tolerate ">") - '|' + - '\'[^\']*\'' + // text inside single quotes (tolerate ">") - ')*' + - ')?'; - var content = - this._php_str_repeat( - '(?:' + - '[^<]+' + // content without tag - '|' + - '<\\2' + // nested opening tag - attr + // attributes - '(?:' + - '/>' + - '|' + - '>', - nested_tags_level - ) + // end of opening tag - '.*?' + // last level nested tag content - this._php_str_repeat( - '' + // closing nested tag - ')' + - '|' + - '<(?!/\\2\\s*>)' + // other tags with a different name - ')*', - nested_tags_level - ); - - var content2 = content.replace('\\2', '\\3'); - - // First, look for nested blocks, e.g.: - //

          - //
          - // tags for inner block must be indented. - //
          - //
          - // - // The outermost tags must start at the left margin for this to match, and - // the inner nested divs must be indented. - // We need to do this before the next, more liberal match, because the next - // match will start at the first `
          ` and stop at the first `
          `. - var all = new RegExp('(?:' + - '(?:' + - '(?:\\n\\n)' + // Starting after a blank line - '|' + // or - '(?:\\x02)\\n?' + // the beginning of the doc - ')' + - '(' + // save in $1 - - // Match from `\n` to `\n`, handling nested tags - // in between. - '[ ]{0,' + less_than_tab + '}' + - '<(' + block_tags_b_re + ')' + // start tag = $2 - attr + '>' + // attributes followed by > and \n - content + // content, support nesting - '' + // the matching end tag - '[ ]*' + // trailing spaces/tabs - '(?=\\n+|\\n*\\x03)' + // followed by a newline or end of document - - '|' + // Special version for tags of group a. - - '[ ]{0,' + less_than_tab + '}' + - '<(' + block_tags_a_re + ')' + // start tag = $3 - attr + '>[ ]*\\n' + // attributes followed by > - content2 + // content, support nesting - '' + // the matching end tag - '[ ]*' + // trailing spaces/tabs - '(?=\\n+|\\n*\\x03)' + // followed by a newline or end of document - - '|' + // Special case just for
          . It was easier to make a special - // case than to make the other regex more complicated. - - '[ ]{0,' + less_than_tab + '}' + - '<(hr)' + // start tag = $2 - attr + // attributes - '/?>' + // the matching end tag - '[ ]*' + - '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document - - '|' + // Special case for standalone HTML comments: - - '[ ]{0,' + less_than_tab + '}' + - '(?:' + //'(?s:' + - '' + - ')' + - '[ ]*' + - '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document - - '|' + // PHP and ASP-style processor instructions (' + - ')' + - '[ ]*' + - '(?=\\n{2,}|\\n*\\x03)' + // followed by a blank line or end of document - - ')' + - ')', 'mig'); - // FIXME: JS doesnt have enough escape sequence \A nor \Z. - - var self = this; - text = this.__wrapSTXETX__(text); - text = text.replace(all, function(match, text) { - //console.log(match); - var key = self.hashBlock(text); - return "\n\n" + key + "\n\n"; - }); - text = this.__unwrapSTXETX__(text); - return text; -}; - -/** - * Called whenever a tag must be hashed when a function insert an atomic - * element in the text stream. Passing $text to through this function gives - * a unique text-token which will be reverted back when calling unhash. - * - * The boundary argument specify what character should be used to surround - * the token. By convension, "B" is used for block elements that needs not - * to be wrapped into paragraph tags at the end, ":" is used for elements - * that are word separators and "X" is used in the general case. - */ -Markdown_Parser.prototype.hashPart = function(text, boundary) { - if('undefined' === typeof boundary) { - boundary = 'X'; - } - // Swap back any tag hash found in text so we do not have to `unhash` - // multiple times at the end. - text = this.unhash(text); - - // Then hash the block. - if('undefined' === typeof arguments.callee.i) { - arguments.callee.i = 0; - } - var key = boundary + "\x1A" + (++arguments.callee.i) + boundary; - this.html_hashes[key] = text; - return key; // String that will replace the tag. -}; - -/** - * Shortcut function for hashPart with block-level boundaries. - */ -Markdown_Parser.prototype.hashBlock = function(text) { - return this.hashPart(text, 'B'); -}; - -/** - * Strips link definitions from text, stores the URLs and titles in - * hash references. - */ -Markdown_Parser.prototype.stripLinkDefinitions = function(text) { - var less_than_tab = this.tab_width - 1; - var self = this; - // Link defs are in the form: ^[id]: url "optional title" - text = this.__wrapSTXETX__(text); - text = text.replace(new RegExp( - '^[ ]{0,' + less_than_tab + '}\\[(.+)\\][ ]?:' + // id = $1 - '[ ]*' + - '\\n?' + // maybe *one* newline - '[ ]*' + - '(?:' + - '<(.+?)>' + // url = $2 - '|' + - '(\\S+?)' + // url = $3 - ')' + - '[ ]*' + - '\\n?' + // maybe one newline - '[ ]*' + - '(?:' + - //'(?=\\s)' + // lookbehind for whitespace - '["\\(]' + - '(.*?)' + // title = $4 - '["\\)]' + - '[ ]*' + - ')?' + // title is optional - '(?:\\n+|\\n*(?=\\x03))', - 'mg'), function(match, id, url2, url3, title) { - //console.log(match); - var link_id = id.toLowerCase(); - var url = url2 ? url2 : url3; - self.urls[link_id] = url; - self.titles[link_id] = title; - return ''; // String that will replace the block - } - ); - text = this.__unwrapSTXETX__(text); - return text; -}; - -/** - * Run block gamut tranformations. - */ -Markdown_Parser.prototype.runBlockGamut = function(text) { - // We need to escape raw HTML in Markdown source before doing anything - // else. This need to be done for each block, and not only at the - // begining in the Markdown function since hashed blocks can be part of - // list items and could have been indented. Indented blocks would have - // been seen as a code block in a previous pass of hashHTMLBlocks. - text = this.hashHTMLBlocks(text); - return this.runBasicBlockGamut(text); -}; - -/** - * Run block gamut tranformations, without hashing HTML blocks. This is - * useful when HTML blocks are known to be already hashed, like in the first - * whole-document pass. - */ -Markdown_Parser.prototype.runBasicBlockGamut = function(text) { - for(var i = 0; i < this.block_gamut.length; i++) { - var method = this[this.block_gamut[i][0]]; - if(method) { - text = method.call(this, text); - } - else { - console.log(this.block_gamut[i][0] + ' not implemented'); - } - } - // Finally form paragraph and restore hashed blocks. - text = this.formParagraphs(text); - return text; -}; - -/** - * Do Horizontal Rules: - */ -Markdown_Parser.prototype.doHorizontalRules = function(text) { - var self = this; - return text.replace(new RegExp( - '^[ ]{0,3}' + // Leading space - '([-\\*_])' + // $1: First marker - '(?:' + // Repeated marker group - '[ ]{0,2}' + // Zero, one, or two spaces. - '\\1' + // Marker character - '){2,}' + // Group repeated at least twice - '[ ]*' + //Tailing spaces - '$' , // End of line. - 'mg'), function(match) { - //console.log(match); - return "\n" + self.hashBlock(" tags. - */ -Markdown_Parser.prototype.doAnchors = function(text) { - if (this.in_anchor) return text; - this.in_anchor = true; - - var self = this; - - var _doAnchors_reference_callback = function(match, whole_match, link_text, link_id) { - //console.log(match); - if(typeof(link_id) !== 'string' || link_id === '') { - // for shortcut links like [this][] or [this]. - link_id = link_text; - } - - // lower-case and turn embedded newlines into spaces - link_id = link_id.toLowerCase(); - link_id = link_id.replace(/[ ]?\n/, ' '); - - var result; - if ('undefined' !== typeof self.urls[link_id]) { - var url = self.urls[link_id]; - url = self.encodeAttribute(url); - - result = "
          "; - result = self.hashPart(result); - } - else { - result = whole_match; - } - return result; - }; - - // - // First, handle reference-style links: [link text] [id] - // - text = text.replace(new RegExp( - '(' + // wrap whole match in $1 - '\\[' + - '(' + this.nested_brackets_re + ')' + // link text = $2 - '\\]' + - - '[ ]?' + // one optional space - '(?:\\n[ ]*)?' + // one optional newline followed by spaces - - '\\[' + - '(.*?)' + // id = $3 - '\\]' + - ')', - 'mg' - ), _doAnchors_reference_callback); - - // - // Next, inline-style links: [link text](url "optional title") - // - text = text.replace(new RegExp( - '(' + // wrap whole match in $1 - '\\[' + - '(' + this.nested_brackets_re + ')' + // link text = $2 - '\\]' + - '\\(' + // literal paren - '[ \\n]*' + - '(?:' + - '<(.+?)>' + // href = $3 - '|' + - '(' + this.nested_url_parenthesis_re + ')' + // href = $4 - ')' + - '[ \\n]*' + - '(' + // $5 - '([\'"])' + // quote char = $6 - '(.*?)' + // Title = $7 - '\\6' + // matching quote - '[ \\n]*' + // ignore any spaces/tabs between closing quote and ) - ')?' + // title is optional - '\\)' + - ')', - 'mg' - ), function(match, whole_match, link_text, url3, url4, x0, x1, title) { - //console.log(match); - link_text = self.runSpanGamut(link_text); - var url = url3 ? url3 : url4; - - url = self.encodeAttribute(url); - - var result = ""; - - return self.hashPart(result); - }); - - // - // Last, handle reference-style shortcuts: [link text] - // These must come last in case you've also got [link text][1] - // or [link text](/foo) - // - text = text.replace(new RegExp( - '(' + // wrap whole match in $1 - '\\[' + - '([^\\[\\]]+)' + // link text = $2; can\'t contain [ or ] - '\\]' + - ')', - 'mg' - ), _doAnchors_reference_callback); - - this.in_anchor = false; - return text; -}; - -/** - * Turn Markdown image shortcuts into tags. - */ -Markdown_Parser.prototype.doImages = function(text) { - var self = this; - - // - // First, handle reference-style labeled images: ![alt text][id] - // - text = text.replace(new RegExp( - '(' + // wrap whole match in $1 - '!\\[' + - '(' + this.nested_brackets_re + ')' + // alt text = $2 - '\\]' + - - '[ ]?' + // one optional space - '(?:\\n[ ]*)?' + // one optional newline followed by spaces - - '\\[' + - '(.*?)' + // id = $3 - '\\]' + - - ')', - 'mg' - ), function(match, whole_match, alt_text, link_id) { - //console.log(match); - link_id = link_id.toLowerCase(); - - if (typeof(link_id) !== 'string' || link_id === '') { - link_id = alt_text.toLowerCase(); // for shortcut links like ![this][]. - } - - alt_text = self.encodeAttribute(alt_text); - var result; - if ('undefined' !== typeof self.urls[link_id]) { - var url = self.encodeAttribute(self.urls[link_id]); - result = "\""' + // src url = $3 - '|' + - '(' + this.nested_url_parenthesis_re + ')' + // src url = $4 - ')' + - '[ \\n]*' + - '(' + // $5 - '([\'"])' + // quote char = $6 - '(.*?)' + // title = $7 - '\\6' + // matching quote - '[ \\n]*' + - ')?' + // title is optional - '\\)' + - ')', - 'mg' - ), function(match, whole_match, alt_text, url3, url4, x5, x6, title) { - //console.log(match); - var url = url3 ? url3 : url4; - - alt_text = self.encodeAttribute(alt_text); - url = self.encodeAttribute(url); - var result = "\""" + self.runSpanGamut(span) + ""; - return "\n" + self.hashBlock(block) + "\n\n"; - }); - - // atx-style headers: - // # Header 1 - // ## Header 2 - // ## Header 2 with closing hashes ## - // ... - // ###### Header 6 - // - text = text.replace(new RegExp( - '^(\\#{1,6})' + // $1 = string of #\'s - '[ ]*' + - '(.+?)' + // $2 = Header text - '[ ]*' + - '\\#*' + // optional closing #\'s (not counted) - '\\n+', - 'mg' - ), function(match, hashes, span) { - //console.log(match); - var level = hashes.length; - var block = "" + self.runSpanGamut(span) + ""; - return "\n" + self.hashBlock(block) + "\n\n"; - }); - - return text; -}; - -/** - * Form HTML ordered (numbered) and unordered (bulleted) lists. - */ -Markdown_Parser.prototype.doLists = function(text) { - var less_than_tab = this.tab_width - 1; - - // Re-usable patterns to match list item bullets and number markers: - var marker_ul_re = '[\\*\\+-]'; - var marker_ol_re = '\\d+[\\.]'; - var marker_any_re = "(?:" + marker_ul_re + "|" + marker_ol_re + ")"; - - var self = this; - var _doLists_callback = function(match, list, x2, x3, type) { - //console.log(match); - // Re-usable patterns to match list item bullets and number markers: - var list_type = type.match(marker_ul_re) ? "ul" : "ol"; - - var marker_any_re = list_type == "ul" ? marker_ul_re : marker_ol_re; - - list += "\n"; - var result = self.processListItems(list, marker_any_re); - - result = self.hashBlock("<" + list_type + ">\n" + result + ""); - return "\n" + result + "\n\n"; - }; - - var markers_relist = [ - [marker_ul_re, marker_ol_re], - [marker_ol_re, marker_ul_re] - ]; - - for (var i = 0; i < markers_relist.length; i++) { - var marker_re = markers_relist[i][0]; - var other_marker_re = markers_relist[i][1]; - // Re-usable pattern to match any entirel ul or ol list: - var whole_list_re = - '(' + // $1 = whole list - '(' + // $2 - '([ ]{0,' + less_than_tab + '})' + // $3 = number of spaces - '(' + marker_re + ')' + // $4 = first list item marker - '[ ]+' + - ')' + - '[\\s\\S]+?' + - '(' + // $5 - '(?=\\x03)' + // \z - '|' + - '\\n{2,}' + - '(?=\\S)' + - '(?!' + // Negative lookahead for another list item marker - '[ ]*' + - marker_re + '[ ]+' + - ')' + - '|' + - '(?=' + // Lookahead for another kind of list - '\\n' + - '\\3' + // Must have the same indentation - other_marker_re + '[ ]+' + - ')' + - ')' + - ')'; // mx - - // We use a different prefix before nested lists than top-level lists. - // See extended comment in _ProcessListItems(). - - text = this.__wrapSTXETX__(text); - if (this.list_level) { - text = text.replace(new RegExp('^' + whole_list_re, "mg"), _doLists_callback); - } - else { - text = text.replace(new RegExp( - '(?:(?=\\n)\\n|\\x02\\n?)' + // Must eat the newline - whole_list_re, "mg" - ), _doLists_callback); - } - text = this.__unwrapSTXETX__(text); - } - - return text; -}; - -// var $list_level = 0; - -/** - * Process the contents of a single ordered or unordered list, splitting it - * into individual list items. - */ -Markdown_Parser.prototype.processListItems = function(list_str, marker_any_re) { - // The $this->list_level global keeps track of when we're inside a list. - // Each time we enter a list, we increment it; when we leave a list, - // we decrement. If it's zero, we're not in a list anymore. - // - // We do this because when we're not inside a list, we want to treat - // something like this: - // - // I recommend upgrading to version - // 8. Oops, now this line is treated - // as a sub-list. - // - // As a single paragraph, despite the fact that the second line starts - // with a digit-period-space sequence. - // - // Whereas when we're inside a list (or sub-list), that line will be - // treated as the start of a sub-list. What a kludge, huh? This is - // an aspect of Markdown's syntax that's hard to parse perfectly - // without resorting to mind-reading. Perhaps the solution is to - // change the syntax rules such that sub-lists must start with a - // starting cardinal number; e.g. "1." or "a.". - - if('undefined' === typeof this.list_level) { - this.list_level = 0; - } - this.list_level++; - - // trim trailing blank lines: - list_str = this.__wrapSTXETX__(list_str); - list_str = list_str.replace(/\n{2,}(?=\x03)/m, "\n"); - list_str = this.__unwrapSTXETX__(list_str); - - var self = this; - list_str = this.__wrapSTXETX__(list_str); - list_str = list_str.replace(new RegExp( - '(\\n)?' + // leading line = $1 - '([ ]*)' + // leading whitespace = $2 - '(' + marker_any_re + // list marker and space = $3 - '(?:[ ]+|(?=\\n))' + // space only required if item is not empty - ')' + - '([\\s\\S]*?)' + // list item text = $4 - '(?:(\\n+(?=\\n))|\\n)' + // tailing blank line = $5 - '(?=\\n*(\\x03|\\2(' + marker_any_re + ')(?:[ ]+|(?=\\n))))', - "gm" - ), function(match, leading_line, leading_space, marker_space, item, tailing_blank_line) { - //console.log(match); - //console.log(item, [leading_line ? leading_line.length : 0, tailing_blank_line ? tailing_blank_line.length : 0]); - if (leading_line || tailing_blank_line || item.match(/\n{2,}/)) { - // Replace marker with the appropriate whitespace indentation - item = leading_space + self._php_str_repeat(' ', marker_space.length) + item; - item = self.runBlockGamut(self.outdent(item) + "\n"); - } - else { - // Recursion for sub-lists: - item = self.doLists(self.outdent(item)); - item = item.replace(/\n+$/m, ''); - item = self.runSpanGamut(item); - } - - return "
        • " + item + "
        • \n"; - }); - list_str = this.__unwrapSTXETX__(list_str); - - this.list_level--; - return list_str; -}; - -/** - * Process Markdown `
          ` blocks.
          - */
          -Markdown_Parser.prototype.doCodeBlocks = function(text) {
          -    var self = this;
          -    text = this.__wrapSTXETX__(text);
          -    text = text.replace(new RegExp(
          -        '(?:\\n\\n|(?=\\x02)\\n?)' +
          -        '('                        + // $1 = the code block -- one or more lines, starting with a space/tab
          -          '(?:^'                   +
          -            '[ ]{' + this.tab_width + ',}' +  // Lines must start with a tab or a tab-width of spaces
          -            '.*\\n+'               +
          -          ')+'                     +
          -        ')'                        +
          -        '((?=[ ]{0,' + this.tab_width + '}\\S)|(?:\\n*(?=\\x03)))',  // Lookahead for non-space at line-start, or end of doc
          -        'mg'
          -    ), function(match, codeblock) {
          -        //console.log(match);
          -        codeblock = self.outdent(codeblock);
          -        codeblock = self._php_htmlspecialchars_ENT_NOQUOTES(codeblock);
          -
          -        // trim leading newlines and trailing newlines
          -        codeblock = self.__wrapSTXETX__(codeblock);
          -        codeblock = codeblock.replace(/(?=\x02)\n+|\n+(?=\x03)/g, '');
          -        codeblock = self.__unwrapSTXETX__(codeblock);
          -
          -        codeblock = "
          " + codeblock + "\n
          "; - return "\n\n" + self.hashBlock(codeblock) + "\n\n"; - }); - text = this.__unwrapSTXETX__(text); - return text; -}; - -/** - * Create a code span markup for $code. Called from handleSpanToken. - */ -Markdown_Parser.prototype.makeCodeSpan = function(code) { - code = this._php_htmlspecialchars_ENT_NOQUOTES(this._php_trim(code)); - return this.hashPart("" + code + ""); -}; - -/** - * Prepare regular expressions for searching emphasis tokens in any - * context. - */ -Markdown_Parser.prototype.prepareItalicsAndBold = function() { - this.em_strong_prepared_relist = {}; - for(var i = 0; i < this.em_relist.length; i++) { - var em = this.em_relist[i][0]; - var em_re = this.em_relist[i][1]; - for(var j = 0; j < this.strong_relist.length; j++) { - var strong = this.strong_relist[j][0]; - var strong_re = this.strong_relist[j][1]; - // Construct list of allowed token expressions. - var token_relist = []; - for(var k = 0; k < this.em_strong_relist.length; k++) { - var em_strong = this.em_strong_relist[k][0]; - var em_strong_re = this.em_strong_relist[k][1]; - if(em + strong == em_strong) { - token_relist.push(em_strong_re); - } - } - token_relist.push(em_re); - token_relist.push(strong_re); - - // Construct master expression from list. - var token_re = new RegExp('(' + token_relist.join('|') + ')'); - this.em_strong_prepared_relist['rx_' + em + strong] = token_re; - } - } -}; - -Markdown_Parser.prototype.doItalicsAndBold = function(text) { - var em = ''; - var strong = ''; - var tree_char_em = false; - var text_stack = ['']; - var token_stack = []; - var token = ''; - - while (1) { - // - // Get prepared regular expression for seraching emphasis tokens - // in current context. - // - var token_re = this.em_strong_prepared_relist['rx_' + em + strong]; - - // - // Each loop iteration search for the next emphasis token. - // Each token is then passed to handleSpanToken. - // - var parts = text.match(token_re); //PREG_SPLIT_DELIM_CAPTURE - if(parts) { - var left = RegExp.leftContext; - var right = RegExp.rightContext; - var pre = ""; - var marker = parts[1]; - for(var mg = 2; mg < parts.length; mg += 2) { - if('undefined' !== typeof parts[mg]) { - pre = parts[mg]; - marker = parts[mg + 1]; - break; - } - } - //console.log([left + pre, marker]); - text_stack[0] += (left + pre); - token = marker; - text = right; - } - else { - text_stack[0] += text; - token = ''; - text = ''; - } - if(token == '') { - // Reached end of text span: empty stack without emitting. - // any more emphasis. - while (token_stack.length > 0 && token_stack[0].length > 0) { - text_stack[1] += token_stack.shift(); - var text_stack_prev0 = text_stack.shift(); // $text_stack[0] .= array_shift($text_stack); - text_stack[0] += text_stack_prev0; - } - break; - } - - var tag, span; - - var token_len = token.length; - if (tree_char_em) { - // Reached closing marker while inside a three-char emphasis. - if (token_len == 3) { - // Three-char closing marker, close em and strong. - token_stack.shift(); - span = text_stack.shift(); - span = this.runSpanGamut(span); - span = "" + span + ""; - text_stack[0] += this.hashPart(span); - em = ''; - strong = ''; - } else { - // Other closing marker: close one em or strong and - // change current token state to match the other - token_stack[0] = this._php_str_repeat(token.charAt(0), 3 - token_len); - tag = token_len == 2 ? "strong" : "em"; - span = text_stack[0]; - span = this.runSpanGamut(span); - span = "<" + tag + ">" + span + ""; - text_stack[0] = this.hashPart(span); - if(tag == 'strong') { strong = ''; } else { em = ''; } - } - tree_char_em = false; - } else if (token_len == 3) { - if (em != '') { - // Reached closing marker for both em and strong. - // Closing strong marker: - for (var i = 0; i < 2; ++i) { - var shifted_token = token_stack.shift(); - tag = shifted_token.length == 2 ? "strong" : "em"; - span = text_stack.shift(); - span = this.runSpanGamut(span); - span = "<" + tag + ">" + span + ""; - text_stack[0] = this.hashPart(span); - if(tag == 'strong') { strong = ''; } else { em = ''; } - } - } else { - // Reached opening three-char emphasis marker. Push on token - // stack; will be handled by the special condition above. - em = token.charAt(0); - strong = em + em; - token_stack.unshift(token); - text_stack.unshift(''); - tree_char_em = true; - } - } else if (token_len == 2) { - if (strong != '') { - // Unwind any dangling emphasis marker: - if (token_stack[0].length == 1) { - text_stack[1] += token_stack.shift(); - text_stack[0] += text_stack.shift(); - } - // Closing strong marker: - token_stack.shift(); - span = text_stack.shift(); - span = this.runSpanGamut(span); - span = "" + span + ""; - text_stack[0] += this.hashPart(span); - strong = ''; - } else { - token_stack.unshift(token); - text_stack.unshift(''); - strong = token; - } - } else { - // Here $token_len == 1 - if (em != '') { - if (token_stack[0].length == 1) { - // Closing emphasis marker: - token_stack.shift(); - span = text_stack.shift(); - span = this.runSpanGamut(span); - span = "" + span + ""; - text_stack[0] += this.hashPart(span); - em = ''; - } else { - text_stack[0] += token; - } - } else { - token_stack.unshift(token); - text_stack.unshift(''); - em = token; - } - } - } - return text_stack[0]; -}; - - -Markdown_Parser.prototype.doBlockQuotes = function(text) { - var self = this; - text = text.replace(new RegExp( - '(' + // Wrap whole match in $1 - '(?:' + - '^[ ]*>[ ]?' + // ">" at the start of a line - '.+\\n' + // rest of the first line - '(.+\\n)*' + // subsequent consecutive lines - '\\n*' + // blanks - ')+' + - ')', - 'mg' - ), function(match, bq) { - //console.log(match); - // trim one level of quoting - trim whitespace-only lines - bq = bq.replace(/^[ ]*>[ ]?|^[ ]+$/mg, ''); - bq = self.runBlockGamut(bq); // recurse - - bq = bq.replace(/^/mg, " "); - // These leading spaces cause problem with
           content, 
          -        // so we need to fix that:
          -        bq = bq.replace(/(\\s*
          [\\s\\S]+?<\/pre>)/mg, function(match, pre) {
          -            //console.log(match);
          -            pre = pre.replace(/^  /m, '');
          -            return pre;
          -        });
          -
          -        return "\n" + self.hashBlock("
          \n" + bq + "\n
          ") + "\n\n"; - }); - return text; -}; - -/** - * Params: - * $text - string to process with html

          tags - */ -Markdown_Parser.prototype.formParagraphs = function(text) { - - // Strip leading and trailing lines: - text = this.__wrapSTXETX__(text); - text = text.replace(/(?:\x02)\n+|\n+(?:\x03)/g, ""); - text = this.__unwrapSTXETX__(text); - // [porting note] - // below may be faster than js regexp. - //for(var s = 0; s < text.length && text.charAt(s) == "\n"; s++) { } - //text = text.substr(s); - //for(var e = text.length; e > 0 && text.charAt(e - 1) == "\n"; e--) { } - //text = text.substr(0, e); - - var grafs = text.split(/\n{2,}/m); - //preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - // - // Wrap

          tags and unhashify HTML blocks - // - for(var i = 0; i < grafs.length; i++) { - var value = grafs[i]; - if(value == "") { - // [porting note] - // This case is replacement for PREG_SPLIT_NO_EMPTY. - } - else if (!value.match(/^B\x1A[0-9]+B$/)) { - // Is a paragraph. - value = this.runSpanGamut(value); - value = value.replace(/^([ ]*)/, "

          "); - value += "

          "; - grafs[i] = this.unhash(value); - } - else { - // Is a block. - // Modify elements of @grafs in-place... - var graf = value; - var block = this.html_hashes[graf]; - graf = block; - //if (preg_match('{ - // \A - // ( # $1 =
          tag - //
          ]* - // \b - // markdown\s*=\s* ([\'"]) # $2 = attr quote char - // 1 - // \2 - // [^>]* - // > - // ) - // ( # $3 = contents - // .* - // ) - // (
          ) # $4 = closing tag - // \z - // }xs', $block, $matches)) - //{ - // list(, $div_open, , $div_content, $div_close) = $matches; - // - // # We can't call Markdown(), because that resets the hash; - // # that initialization code should be pulled into its own sub, though. - // $div_content = $this->hashHTMLBlocks($div_content); - // - // # Run document gamut methods on the content. - // foreach ($this->document_gamut as $method => $priority) { - // $div_content = $this->$method($div_content); - // } - // - // $div_open = preg_replace( - // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); - // - // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; - //} - grafs[i] = graf; - } - } - - return grafs.join("\n\n"); -}; - -/** - * Encode text for a double-quoted HTML attribute. This function - * is *not* suitable for attributes enclosed in single quotes. - */ -Markdown_Parser.prototype.encodeAttribute = function(text) { - text = this.encodeAmpsAndAngles(text); - text = text.replace(/"/g, '"'); - return text; -}; - -/** - * Smart processing for ampersands and angle brackets that need to - * be encoded. Valid character entities are left alone unless the - * no-entities mode is set. - */ -Markdown_Parser.prototype.encodeAmpsAndAngles = function(text) { - if (this.no_entities) { - text = text.replace(/&/g, '&'); - } else { - // Ampersand-encoding based entirely on Nat Irons's Amputator - // MT plugin: - text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/, '&'); - } - // Encode remaining <'s - text = text.replace(/\s]+)>/i, function(match, address) { - //console.log(match); - var url = self.encodeAttribute(address); - var link = "
          " + url + ""; - return self.hashPart(link); - }); - - // Email addresses: - text = text.replace(new RegExp( - '<' + - '(?:mailto:)?' + - '(' + - '(?:' + - '[-!#$%&\'*+/=?^_`.{|}~\\w\\x80-\\xFF]+' + - '|' + - '".*?"' + - ')' + - '\\@' + - '(?:' + - '[-a-z0-9\\x80-\\xFF]+(\\.[-a-z0-9\\x80-\\xFF]+)*\\.[a-z]+' + - '|' + - '\\[[\\d.a-fA-F:]+\\]' + // IPv4 & IPv6 - ')' + - ')' + - '>', - 'i' - ), function(match, address) { - //console.log(match); - var link = self.encodeEmailAddress(address); - return self.hashPart(link); - }); - - return text; -}; - -/** - * Input: an email address, e.g. "foo@example.com" - * - * Output: the email address as a mailto link, with each character - * of the address encoded as either a decimal or hex entity, in - * the hopes of foiling most address harvesting spam bots. E.g.: - * - *

          foo@exampl - * e.com

          - * - * Based by a filter by Matthew Wickline, posted to BBEdit-Talk. - * With some optimizations by Milian Wolff. - */ -Markdown_Parser.prototype.encodeEmailAddress = function(addr) { - if('undefined' === typeof arguments.callee.crctable) { - arguments.callee.crctable = - "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 " + - "0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 " + - "1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 " + - "136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 " + - "3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B " + - "35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 " + - "26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F " + - "2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D " + - "76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 " + - "7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 " + - "6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 " + - "65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 " + - "4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB " + - "4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 " + - "5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F " + - "5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD " + - "EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 " + - "E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 " + - "F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 " + - "FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 " + - "D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B " + - "D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 " + - "CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F " + - "C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D " + - "9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 " + - "95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 " + - "86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 " + - "88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 " + - "A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB " + - "AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 " + - "BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF " + - "B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D".split(' '); - } - var crctable = arguments.callee.crctable; - function _crc32(str) { - var crc = 0; - crc = crc ^ (-1); - for (var i = 0; i < str.length; ++i) { - var y = (crc ^ str.charCodeAt(i)) & 0xff; - var x = "0x" + crctable[y]; - crc = (crc >>> 8) ^ x; - } - return (crc ^ (-1)) >>> 0; - } - - addr = "mailto:" + addr; - var chars = []; - var i; - for(i = 0; i < addr.length; i++) { - chars.push(addr.charAt(i)); - } - var seed = Math.floor(Math.abs(_crc32(addr) / addr.length)); // # Deterministic seed. - - for(i = 0; i < chars.length; i++) { - var c = chars[i]; - var ord = c.charCodeAt(0); - // Ignore non-ascii chars. - if(ord < 128) { - var r = (seed * (1 + i)) % 100; // Pseudo-random function. - // roughly 10% raw, 45% hex, 45% dec - // '@' *must* be encoded. I insist. - if(r > 90 && c != '@') { /* do nothing */ } - else if(r < 45) { chars[i] = '&#x' + ord.toString(16) + ';'; } - else { chars[i] = '&#' + ord.toString(10) + ';'; } - } - } - - addr = chars.join(''); - var text = chars.splice(7).join(''); // text without `mailto:` - addr = "" + text + ""; - - return addr; -}; - -/** - * Take the string $str and parse it into tokens, hashing embeded HTML, - * escaped characters and handling code spans. -*/ -Markdown_Parser.prototype.parseSpan = function(str) { - var output = ''; - - var span_re = new RegExp( - '(' + - '\\\\' + this.escape_chars_re + - '|' + - // This expression is too difficult for JS: '(?' + // comment - '|' + - '<\\?.*?\\?>|<%.*?%>' + // processing instruction - '|' + - '<[/!$]?[-a-zA-Z0-9:_]+' + // regular tags - '(?=' + - '\\s' + - '(?=[^"\'>]+|"[^"]*"|\'[^\']*\')*' + - ')?' + - '>' - )) + - ')' - ); - - while(1) { - // - // Each loop iteration seach for either the next tag, the next - // openning code span marker, or the next escaped character. - // Each token is then passed to handleSpanToken. - // - var parts = str.match(span_re); //PREG_SPLIT_DELIM_CAPTURE - if(parts) { - if(RegExp.leftContext) { - output += RegExp.leftContext; - } - // Back quote but after backslash is to be ignored. - if(RegExp.lastMatch.charAt(0) == "`" && - RegExp.leftContext.charAt(RegExp.leftContext.length - 1) == "\\" - ) { - output += RegExp.lastMatch; - str = RegExp.rightContext; - continue; - } - var r = this.handleSpanToken(RegExp.lastMatch, RegExp.rightContext); - output += r[0]; - str = r[1]; - } - else { - output += str; - break; - } - } - return output; -}; - - -/** - * Handle $token provided by parseSpan by determining its nature and - * returning the corresponding value that should replace it. -*/ -Markdown_Parser.prototype.handleSpanToken = function(token, str) { - //console.log([token, str]); - switch (token.charAt(0)) { - case "\\": - return [this.hashPart("&#" + token.charCodeAt(1) + ";"), str]; - case "`": - // Search for end marker in remaining text. - if (str.match(new RegExp('^([\\s\\S]*?[^`])' + this._php_preg_quote(token) + '(?!`)([\\s\\S]*)$', 'm'))) { - var code = RegExp.$1; - str = RegExp.$2; - var codespan = this.makeCodeSpan(code); - return [this.hashPart(codespan), str]; - } - return [token, str]; // return as text since no ending marker found. - default: - return [this.hashPart(token), str]; - } -}; - -/** - * Remove one level of line-leading tabs or spaces - */ -Markdown_Parser.prototype.outdent = function(text) { - return text.replace(new RegExp('^(\\t|[ ]{1,' + this.tab_width + '})', 'mg'), ''); -}; - - -//# String length function for detab. `_initDetab` will create a function to -//# hanlde UTF-8 if the default function does not exist. -//var $utf8_strlen = 'mb_strlen'; - -/** - * Replace tabs with the appropriate amount of space. - */ -Markdown_Parser.prototype.detab = function(text) { - // For each line we separate the line in blocks delemited by - // tab characters. Then we reconstruct every line by adding the - // appropriate number of space between each blocks. - var self = this; - return text.replace(/^.*\t.*$/mg, function(line) { - //$strlen = $this->utf8_strlen; # strlen function for UTF-8. - // Split in blocks. - var blocks = line.split("\t"); - // Add each blocks to the line. - line = blocks.shift(); // Do not add first block twice. - for(var i = 0; i < blocks.length; i++) { - var block = blocks[i]; - // Calculate amount of space, insert spaces, insert block. - var amount = self.tab_width - line.length % self.tab_width; - line += self._php_str_repeat(" ", amount) + block; - } - return line; - }); -}; - -/** - * Swap back in all the tags hashed by _HashHTMLBlocks. - */ -Markdown_Parser.prototype.unhash = function(text) { - var self = this; - return text.replace(/(.)\x1A[0-9]+\1/g, function(match) { - return self.html_hashes[match]; - }); -}; -/*-------------------------------------------------------------------------*/ - -/** - * Constructor function. Initialize the parser object. - */ -function MarkdownExtra_Parser() { - - // Prefix for footnote ids. - this.fn_id_prefix = ""; - - // Optional title attribute for footnote links and backlinks. - this.fn_link_title = MARKDOWN_FN_LINK_TITLE; - this.fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE; - - // Optional class attribute for footnote links and backlinks. - this.fn_link_class = MARKDOWN_FN_LINK_CLASS; - this.fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; - - // Predefined abbreviations. - this.predef_abbr = {}; - - // Extra variables used during extra transformations. - this.footnotes = {}; - this.footnotes_ordered = []; - this.abbr_desciptions = {}; - this.abbr_word_re = ''; - - // Give the current footnote number. - this.footnote_counter = 1; - - // ### HTML Block Parser ### - - // Tags that are always treated as block tags: - this.block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend'; - - // Tags treated as block tags only if the opening tag is alone on it's line: - this.context_block_tags_re = 'script|noscript|math|ins|del'; - - // Tags where markdown="1" default to span mode: - this.contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; - - // Tags which must not have their contents modified, no matter where - // they appear: - this.clean_tags_re = 'script|math'; - - // Tags that do not need to be closed. - this.auto_close_tags_re = 'hr|img'; - - // Redefining emphasis markers so that emphasis by underscore does not - // work in the middle of a word. - this.em_relist = [ - ['' , '(?:(^|[^\\*])(\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(_)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['*', '((?:\\S|^)[^\\*])(\\*)(?!\\*)'], - ['_', '((?:\\S|^)[^_])(_)(?![a-zA-Z0-9_])'] - ]; - this.strong_relist = [ - ['' , '(?:(^|[^\\*])(\\*\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(__)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['**', '((?:\\S|^)[^\\*])(\\*\\*)(?!\\*)'], - ['__', '((?:\\S|^)[^_])(__)(?![a-zA-Z0-9_])'] - ]; - this.em_strong_relist = [ - ['' , '(?:(^|[^\\*])(\\*\\*\\*)(?=[^\\*])|(^|[^a-zA-Z0-9_])(___)(?=[^_]))(?=\\S|$)(?![\\.,:;]\\s)'], - ['***', '((?:\\S|^)[^\\*])(\\*\\*\\*)(?!\\*)'], - ['___', '((?:\\S|^)[^_])(___)(?![a-zA-Z0-9_])'] - ]; - - // Add extra escapable characters before parent constructor - // initialize the table. - this.escape_chars += ':|'; - - // Insert extra document, block, and span transformations. - // Parent constructor will do the sorting. - this.document_gamut.push(['doFencedCodeBlocks', 5]); - this.document_gamut.push(['stripFootnotes', 15]); - this.document_gamut.push(['stripAbbreviations', 25]); - this.document_gamut.push(['appendFootnotes', 50]); - - this.block_gamut.push(['doFencedCodeBlocks', 5]); - this.block_gamut.push(['doTables', 15]); - this.block_gamut.push(['doDefLists', 45]); - - this.span_gamut.push(['doFootnotes', 5]); - this.span_gamut.push(['doAbbreviations', 70]); -} -MarkdownExtra_Parser.prototype = new Markdown_Parser(); - -/** - * Setting up Extra-specific variables. - */ -MarkdownExtra_Parser.prototype.setup = function() { - this.constructor.prototype.setup.call(this); - - this.footnotes = {}; - this.footnotes_ordered = []; - this.abbr_desciptions = {}; - this.abbr_word_re = ''; - this.footnote_counter = 1; - - for(var abbr_word in this.predef_abbr) { - var abbr_desc = this.predef_abbr[abbr_word]; - if(this.abbr_word_re != '') { - this.abbr_word_re += '|'; - } - this.abbr_word_re += this._php_preg_quote(abbr_word); // ?? str -> re? - this.abbr_desciptions[abbr_word] = this._php_trim(abbr_desc); - } -}; - -/** - * Clearing Extra-specific variables. - */ -MarkdownExtra_Parser.prototype.teardown = function() { - this.footnotes = {}; - this.footnotes_ordered = []; - this.abbr_desciptions = {}; - this.abbr_word_re = ''; - - this.constructor.prototype.teardown.call(this); -}; - - -/** - * Hashify HTML Blocks and "clean tags". - * - * We only want to do this for block-level HTML tags, such as headers, - * lists, and tables. That's because we still want to wrap

          s around - * "paragraphs" that are wrapped in non-block-level tags, such as anchors, - * phrase emphasis, and spans. The list of tags we're looking for is - * hard-coded. - * - * This works by calling _HashHTMLBlocks_InMarkdown, which then calls - * _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" - * attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back - * _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. - * These two functions are calling each other. It's recursive! - */ -MarkdownExtra_Parser.prototype.hashHTMLBlocks = function(text) { - // - // Call the HTML-in-Markdown hasher. - // - var r = this._hashHTMLBlocks_inMarkdown(text); - text = r[0]; - - return text; -}; - -/** - * Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. - * - * * $indent is the number of space to be ignored when checking for code - * blocks. This is important because if we don't take the indent into - * account, something like this (which looks right) won't work as expected: - * - *

          - *
          - * Hello World. <-- Is this a Markdown code block or text? - *
          <-- Is this a Markdown code block or a real tag? - *
          - * - * If you don't like this, just don't indent the tag on which - * you apply the markdown="1" attribute. - * - * * If $enclosing_tag_re is not empty, stops at the first unmatched closing - * tag with that name. Nested tags supported. - * - * * If $span is true, text inside must treated as span. So any double - * newline will be replaced by a single newline so that it does not create - * paragraphs. - * - * Returns an array of that form: ( processed text , remaining text ) - */ -MarkdownExtra_Parser.prototype._hashHTMLBlocks_inMarkdown = function(text, indent, enclosing_tag_re, span) { - if('undefined' === typeof indent) { indent = 0; } - if('undefined' === typeof enclosing_tag_re) { enclosing_tag_re = ''; } - if('undefined' === typeof span) { span = false; } - - if(text === '') { return ['', '']; } - - var matches; - - // Regex to check for the presense of newlines around a block tag. - var newline_before_re = /(?:^\n?|\n\n)*$/; - var newline_after_re = new RegExp( - '^' + // Start of text following the tag. - '([ ]*)?' + // Optional comment. - '[ ]*\\n' , // Must be followed by newline. - 'm' - ); - - // Regex to match any tag. - var block_tag_re = new RegExp( - '(' + // $2: Capture hole tag. - '`) - '\'.*?\'|' + // Single quotes (can contain `>`) - '.+?' + // Anything but quotes and `>`. - ')*?' + - ')?' + - '>' + // End of tag. - '|' + - '' + // HTML Comment - '|' + - '<\\?.*?\\?>|<%.*?%>' + // Processing instruction - '|' + - '' + // CData Block - '|' + - // Code span marker - '`+' + - ( !span ? // If not in span. - '|' + - // Indented code block - '(?:^[ ]*\\n|^|\\n[ ]*\\n)' + - '[ ]{' + (indent + 4) + '}[^\\n]*\\n' + - '(?=' + - '(?:[ ]{' + (indent + 4) + '}[^\\n]*|[ ]*)\\n' + - ')*' + - '|' + - // Fenced code block marker - '(?:^|\\n)' + - '[ ]{0,' + indent + '}~~~+[ ]*\\n' - : '' ) + // # End (if not is span). - ')', - 'm' - ); - - var depth = 0; // Current depth inside the tag tree. - var parsed = ""; // Parsed text that will be returned. - - // - // Loop through every tag until we find the closing tag of the parent - // or loop until reaching the end of text if no parent tag specified. - // - do { - // - // Split the text using the first $tag_match pattern found. - // Text before pattern will be first in the array, text after - // pattern will be at the end, and between will be any catches made - // by the pattern. - // - var parts_available = text.match(block_tag_re); //PREG_SPLIT_DELIM_CAPTURE - var parts; - if(!parts_available) { - parts = [text]; - } - else { - parts = [RegExp.leftContext, RegExp.lastMatch, RegExp.rightContext]; - } - - // If in Markdown span mode, add a empty-string span-level hash - // after each newline to prevent triggering any block element. - if(span) { - var _void = this.hashPart("", ':'); - var newline = _void + "\n"; - parts[0] = _void + parts[0].replace(/\n/g, newline) + _void; - } - - parsed += parts[0]; // Text before current tag. - - // If end of $text has been reached. Stop loop. - if(!parts_available) { - text = ""; - break; - } - - var tag = parts[1]; // Tag to handle. - text = parts[2]; // Remaining text after current tag. - var tag_re = this._php_preg_quote(tag); // For use in a regular expression. - - var t; - var block_text; - // - // Check for: Code span marker - // - if (tag.charAt(0) == "`") { - // Find corresponding end marker. - tag_re = this._php_preg_quote(tag); - if(matches = text.match(new RegExp('^(.+?|\\n[^\\n])*?[^`]' + tag_re + '[^`]'))) { - // End marker found: pass text unchanged until marker. - parsed += tag + matches[0]; - text = text.substr(matches[0].length); - } - else { - // Unmatched marker: just skip it. - parsed += tag; - } - } - // - // Check for: Fenced code block marker. - // - else if(tag.match(new RegExp('^\\n?[ ]{0,' + (indent + 3) * '}~'))) { - // Fenced code block marker: find matching end marker. - tag_re = this._php_preg_quote(this._php_trim(tag)); - if(matches = text.match(new RegExp('^(?>.*\\n)+?[ ]{0,' + indent + '}' + tag_re + '[ ]*\\n'))) { - // End marker found: pass text unchanged until marker. - parsed += tag + matches[0]; - text = text.substr(matches[0].length); - } - else { - // No end marker: just skip it. - parsed += tag; - } - } - // - // Check for: Indented code block. - // - else if(tag.charAt(0) == "\n" || tag.charAt(0) == " ") { - // Indented code block: pass it unchanged, will be handled - // later. - parsed += tag; - } - // - // Check for: Opening Block level tag or - // Opening Context Block tag (like ins and del) - // used as a block tag (tag is alone on it's line). - // - else if (tag.match(new RegExp('^<(?:' + this.block_tags_re + ')\\b')) || - ( - tag.match(new RegExp('^<(?:' + this.context_block_tags_re + ')\\b')) && - parsed.match(newline_before_re) && - text.match(newline_after_re) - ) - ) { - // Need to parse tag and following text using the HTML parser. - t = this._hashHTMLBlocks_inHTML(tag + text, this.hashBlock, true); - block_text = t[0]; - text = t[1]; - - // Make sure it stays outside of any paragraph by adding newlines. - parsed += "\n\n" + block_text + "\n\n"; - } - // - // Check for: Clean tag (like script, math) - // HTML Comments, processing instructions. - // - else if( - tag.match(new RegExp('^<(?:' + this.clean_tags_re + ')\\b')) || - tag.charAt(1) == '!' || tag.charAt(1) == '?' - ) { - // Need to parse tag and following text using the HTML parser. - // (don't check for markdown attribute) - t = this._hashHTMLBlocks_inHTML(tag + text, this.hashClean, false); - block_text = t[0]; - text = t[1]; - - parsed += block_text; - } - // - // Check for: Tag with same name as enclosing tag. - // - else if (enclosing_tag_re !== '' && - // Same name as enclosing tag. - tag.match(new RegExp('^= 0); - - return [parsed, text]; -}; - -/** - * Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. - * - * * Calls $hash_method to convert any blocks. - * * Stops when the first opening tag closes. - * * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. - * (it is not inside clean tags) - * - * Returns an array of that form: ( processed text , remaining text ) - */ -MarkdownExtra_Parser.prototype._hashHTMLBlocks_inHTML = function(text, hash_method, md_attr) { - if(text === '') return ['', '']; - - var matches; - - // Regex to match `markdown` attribute inside of a tag. - var markdown_attr_re = new RegExp( - '\\s*' + // Eat whitespace before the `markdown` attribute - 'markdown' + - '\\s*=\\s*' + - '(?:' + - '(["\'])' + // $1: quote delimiter - '(.*?)' + // $2: attribute value - '\\1' + // matching delimiter - '|' + - '([^\\s>]*)' + // $3: unquoted attribute value - ')' + - '()' // $4: make $3 always defined (avoid warnings) - ); - - // Regex to match any tag. - var tag_re = new RegExp( - '(' + // $2: Capture hole tag. - '`) - '\'.*?\'|' + // Single quotes (can contain `>`) - '.+?' + // Anything but quotes and `>`. - ')*?' + - ')?' + - '>' + // End of tag. - '|' + - '' + // HTML Comment - '|' + - '<\\?.*?\\?>|<%.*?%>' + // Processing instruction - '|' + - '' + // CData Block - ')' - ); - - var original_text = text; // Save original text in case of faliure. - - var depth = 0; // Current depth inside the tag tree. - var block_text = ""; // Temporary text holder for current text. - var parsed = ""; // Parsed text that will be returned. - - // - // Get the name of the starting tag. - // (This pattern makes $base_tag_name_re safe without quoting.) - // - var base_tag_name_re = ""; - if(matches = text.match(/^<([\w:$]*)\b/)) { - base_tag_name_re = matches[1]; - } - - // - // Loop through every tag until we find the corresponding closing tag. - // - do { - // - // Split the text using the first $tag_match pattern found. - // Text before pattern will be first in the array, text after - // pattern will be at the end, and between will be any catches made - // by the pattern. - // - var parts_available = text.match(tag_re); //PREG_SPLIT_DELIM_CAPTURE); - // If end of $text has been reached. Stop loop. - if(!parts_available) { - // - // End of $text reached with unbalenced tag(s). - // In that case, we return original text unchanged and pass the - // first character as filtered to prevent an infinite loop in the - // parent function. - // - return [original_text.charAt(0), original_text.substr(1)]; - } - var parts = [RegExp.leftContext, RegExp.lastMatch, RegExp.rightContext]; - - block_text += parts[0]; // Text before current tag. - var tag = parts[1]; // Tag to handle. - text = parts[2]; // Remaining text after current tag. - - // - // Check for: Auto-close tag (like
          ) - // Comments and Processing Instructions. - // - if(tag.match(new RegExp('^ 0) { - block_text = block_text.replace(new RegExp('/^[ ]{1,' + indent + '}', 'm'), ""); - } - - // Append tag content to parsed text. - if (!span_mode) { parsed += "\n\n" + block_text + "\n\n"; } - else { parsed += block_text; } - - // Start over a new block. - block_text = ""; - } - else { - block_text += tag; - } - } - - } while(depth > 0); - - // - // Hash last block text that wasn't processed inside the loop. - // - parsed += hash_method.call(this, block_text); - - return [parsed, text]; -}; - - -/** - * Called whenever a tag must be hashed when a function insert a "clean" tag - * in $text, it pass through this function and is automaticaly escaped, - * blocking invalid nested overlap. - */ -MarkdownExtra_Parser.prototype.hashClean = function(text) { - return this.hashPart(text, 'C'); -}; - - -/** - * Redefined to add id attribute support. - */ -MarkdownExtra_Parser.prototype.doHeaders = function(text) { - var self = this; - - function _doHeaders_attr(attr) { - if('undefined' === typeof attr || attr == "") { return ""; } - return " id=\"" + attr + "\""; - } - - // Setext-style headers: - // Header 1 {#header1} - // ======== - // - // Header 2 {#header2} - // -------- - - text = text.replace(new RegExp( - '(^.+?)' + // $1: Header text - '(?:[ ]+\\{\\#([-_:a-zA-Z0-9]+)\\})?' + // $2: Id attribute - '[ ]*\\n(=+|-+)[ ]*\\n+', // $3: Header footer - 'mg' - ), function(match, span, id, line) { - //console.log(match); - if(line == '-' && span.match(/^- /)) { - return match; - } - var level = line.charAt(0) == '=' ? 1 : 2; - var attr = _doHeaders_attr(id); - var block = "" + self.runSpanGamut(span) + ""; - return "\n" + self.hashBlock(block) + "\n\n"; - }); - - // atx-style headers: - // # Header 1 {#header1} - // ## Header 2 {#header2} - // ## Header 2 with closing hashes ## {#header3} - // ... - // ###### Header 6 {#header2} - - text = text.replace(new RegExp( - '^(\\#{1,6})' + // $1 = string of #\'s - '[ ]*' + - '(.+?)' + // $2 = Header text - '[ ]*' + - '\\#*' + // optional closing #\'s (not counted) - '(?:[ ]+\\{\\#([-_:a-zA-Z0-9]+)\\})?' + // id attribute - '\\n+', - 'mg' - ), function(match, hashes, span, id) { - //console.log(match); - var level = hashes.length; - var attr = _doHeaders_attr(id); - var block = "" + self.runSpanGamut(span) + ""; - return "\n" + self.hashBlock(block) + "\n\n"; - }); - - return text; -}; - -/** - * Form HTML tables. - */ -MarkdownExtra_Parser.prototype.doTables = function(text) { - var self = this; - - var less_than_tab = this.tab_width - 1; - - var _doTable_callback = function(match, head, underline, content) { - //console.log(match); - // Remove any tailing pipes for each line. - head = head.replace(/[|] *$/m, ''); - underline = underline.replace(/[|] *$/m, ''); - content = content.replace(/[|] *$/m, ''); - - var attr = []; - - // Reading alignement from header underline. - var separators = underline.split(/[ ]*[|][ ]*/); - var n; - for(n = 0; n < separators.length; n++) { - var s = separators[n]; - if (s.match(/^ *-+: *$/)) { attr[n] = ' align="right"'; } - else if (s.match(/^ *:-+: *$/)) { attr[n] = ' align="center"'; } - else if (s.match(/^ *:-+ *$/)) { attr[n] = ' align="left"'; } - else { attr[n] = ''; } - } - - // Parsing span elements, including code spans, character escapes, - // and inline HTML tags, so that pipes inside those gets ignored. - head = self.parseSpan(head); - var headers = head.split(/ *[|] */); - var col_count = headers.length; - - // Write column headers. - var text = "\n"; - text += "\n"; - text += "\n"; - for(n = 0; n < headers.length; n++) { - var header = headers[n]; - text += " " + self.runSpanGamut(self._php_trim(header)) + "\n"; - } - text += "\n"; - text += "\n"; - - // Split content by row. - var rows = self._php_trim(content, "\n").split("\n"); - - text += "\n"; - for(var i = 0; i < rows.length; i++) { - var row = rows[i]; - // Parsing span elements, including code spans, character escapes, - // and inline HTML tags, so that pipes inside those gets ignored. - row = self.parseSpan(row); - - // Split row by cell. - var row_cells = row.split(/ *[|] */, col_count); - while(row_cells.length < col_count) { row_cells.push(''); } - - text += "\n"; - for(n = 0; n < row_cells.length; n++) { - var cell = row_cells[n]; - text += " " + self.runSpanGamut(self._php_trim(cell)) + "\n"; - } - text += "\n"; - } - text += "\n"; - text += "
          "; - - return self.hashBlock(text) + "\n"; - }; - - text = this.__wrapSTXETX__(text); - - // - // Find tables with leading pipe. - // - // | Header 1 | Header 2 - // | -------- | -------- - // | Cell 1 | Cell 2 - // | Cell 3 | Cell 4 - // - text = text.replace(new RegExp( - '^' + // Start of a line - '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. - '[|]' + // Optional leading pipe (present) - '(.+)\\n' + // $1: Header row (at least one pipe) - - '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. - '[|]([ ]*[-:]+[-| :]*)\\n' + // $2: Header underline - - '(' + // $3: Cells - '(?:' + - '[ ]*' + // Allowed whitespace. - '[|].*\\n' + // Row content. - ')*' + - ')' + - '(?=\\n|\\x03)' , // Stop at final double newline. - 'mg' - ), function(match, head, underline, content) { - // Remove leading pipe for each row. - content = content.replace(/^ *[|]/m, ''); - - return _doTable_callback.call(this, match, head, underline, content); - }); - - // - // Find tables without leading pipe. - // - // Header 1 | Header 2 - // -------- | -------- - // Cell 1 | Cell 2 - // Cell 3 | Cell 4 - // - text = text.replace(new RegExp( - '^' + // Start of a line - '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. - '(\\S.*[|].*)\\n' + // $1: Header row (at least one pipe) - - '[ ]{0,' + less_than_tab + '}' + // Allowed whitespace. - '([-:]+[ ]*[|][-| :]*)\\n' + // $2: Header underline - - '(' + // $3: Cells - '(?:' + - '.*[|].*\\n' + // Row content - ')*' + - ')' + - '(?=\\n|\\x03)' , // Stop at final double newline. - 'mg' - ), _doTable_callback); - - text = this.__unwrapSTXETX__(text); - - return text; -}; - -/** - * Form HTML definition lists. - */ -MarkdownExtra_Parser.prototype.doDefLists = function(text) { - var self = this; - - var less_than_tab = this.tab_width - 1; - - // Re-usable pattern to match any entire dl list: - var whole_list_re = '(?:' + - '(' + // $1 = whole list - '(' + // $2 - '[ ]{0,' + less_than_tab + '}' + - '((?:[ \\t]*\\S.*\\n)+)' + // $3 = defined term - // [porting note] Original regex from PHP is - // (?>.*\S.*\n), which matches a line with at - // least one non-space character. Change the - // first .* to [ \t]* stops unneccessary - // backtracking hence improves performance - '\\n?' + - '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition - ')' + - '([\\s\\S]+?)' + - '(' + // $4 - '(?=\\0x03)' + // \z - '|' + - '(?=' + // [porting note] Our regex will consume leading - // newline characters so we will leave the newlines - // here for the next definition - '\\n{2,}' + - '(?=\\S)' + - '(?!' + // Negative lookahead for another term - '[ ]{0,' + less_than_tab + '}' + - '(?:\\S.*\\n)+?' + // defined term - '\\n?' + - '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition - ')' + - '(?!' + // Negative lookahead for another definition - '[ ]{0,' + less_than_tab + '}:[ ]+' + // colon starting definition - ')' + - ')' + - ')' + - ')' + - ')'; // mx - - text = this.__wrapSTXETX__(text); - text = text.replace(new RegExp( - '(\\x02\\n?|\\n\\n)' + - whole_list_re, 'mg' - ), function(match, pre, list) { - //console.log(match); - // Re-usable patterns to match list item bullets and number markers: - // [portiong note] changed to list = $2 in order to reserve previously \n\n. - - // Turn double returns into triple returns, so that we can make a - // paragraph for the last item in a list, if necessary: - var result = self._php_trim(self.processDefListItems(list)); - result = "
          \n" + result + "\n
          "; - return pre + self.hashBlock(result) + "\n\n"; - }); - text = this.__unwrapSTXETX__(text); - - return text; -}; - -/** - * Process the contents of a single definition list, splitting it - * into individual term and definition list items. - */ -MarkdownExtra_Parser.prototype.processDefListItems = function(list_str) { - var self = this; - - var less_than_tab = this.tab_width - 1; - - list_str = this.__wrapSTXETX__(list_str); - - // trim trailing blank lines: - list_str = list_str.replace(/\n{2,}(?=\\x03)/, "\n"); - - // Process definition terms. - list_str = list_str.replace(new RegExp( - '(\\x02\\n?|\\n\\n+)' + // leading line - '(' + // definition terms = $1 - '[ ]{0,' + less_than_tab + '}' + // leading whitespace - '(?![:][ ]|[ ])' + // negative lookahead for a definition - // mark (colon) or more whitespace. - '(?:\\S.*\\n)+?' + // actual term (not whitespace). - ')' + - '(?=\\n?[ ]{0,3}:[ ])' , // lookahead for following line feed - // with a definition mark. - 'mg' - ), function(match, pre, terms_str) { - // [portiong note] changed to list = $2 in order to reserve previously \n\n. - var terms = self._php_trim(terms_str).split("\n"); - var text = ''; - for (var i = 0; i < terms.length; i++) { - var term = terms[i]; - term = self.runSpanGamut(self._php_trim(term)); - text += "\n
          " + term + "
          "; - } - return text + "\n"; - }); - - // Process actual definitions. - list_str = list_str.replace(new RegExp( - '\\n(\\n+)?' + // leading line = $1 - '(' + // marker space = $2 - '[ ]{0,' + less_than_tab + '}' + // whitespace before colon - '[:][ ]+' + // definition mark (colon) - ')' + - '([\\s\\S]+?)' + // definition text = $3 - // [porting note] Maybe no trailing - // newlines in our version, changed the - // following line from \n+ to \n*. - '(?=\\n*' + // stop at next definition mark, - '(?:' + // next term or end of text - '\\n[ ]{0,' + less_than_tab + '}[:][ ]|' + // [porting note] do not match - // colon in the middle of a line - '
          |\\x03' + // \z - ')' + - ')', - 'mg' - ), function(match, leading_line, marker_space, def) { - if (leading_line || def.match(/\n{2,}/)) { - // Replace marker with the appropriate whitespace indentation - def = self._php_str_repeat(' ', marker_space.length) + def; - def = self.runBlockGamut(self.outdent(def + "\n\n")); - def = "\n" + def + "\n"; - } - else { - def = self._php_rtrim(def); - def = self.runSpanGamut(self.outdent(def)); - } - - return "\n
          " + def + "
          \n"; - }); - - list_str = this.__unwrapSTXETX__(list_str); - - return list_str; -}; - -/** - * Adding the fenced code block syntax to regular Markdown: - * - * ~~~ - * Code block - * ~~~ - */ -MarkdownExtra_Parser.prototype.doFencedCodeBlocks = function(text) { - var self = this; - - var less_than_tab = this.tab_width; - - text = this.__wrapSTXETX__(text); - text = text.replace(new RegExp( - '(?:\\n|\\x02)' + - // 1: Opening marker - '(' + - '~{3,}' + // Marker: three tilde or more. - ')' + - '[ ]*\\n' + // Whitespace and newline following marker. - // 2: Content - '(' + - '(?:' + - '(?!\\1[ ]*\\n)' + // Not a closing marker. - '.*\\n+' + - ')+' + - ')' + - // Closing marker. - '\\1[ ]*\\n', - "mg" - ), function(match, m1, codeblock) { - codeblock = self._php_htmlspecialchars_ENT_NOQUOTES(codeblock); - codeblock = codeblock.replace(/^\n+/, function(match) { - return self._php_str_repeat("
          "; - return "\n\n" + self.hashBlock(codeblock) + "\n\n"; - }); - text = this.__unwrapSTXETX__(text); - - return text; -}; - -/** - * Params: - * $text - string to process with html

          tags - */ -MarkdownExtra_Parser.prototype.formParagraphs = function(text) { - - // Strip leading and trailing lines: - text = this.__wrapSTXETX__(text); - text = text.replace(/(?:\x02)\n+|\n+(?:\x03)/g, ""); - text = this.__unwrapSTXETX__(text); - - var grafs = text.split(/\n{2,}/m); - //preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); - - // - // Wrap

          tags and unhashify HTML blocks - // - for(var i = 0; i < grafs.length; i++) { - var value = grafs[i]; - if(value == "") { - // [porting note] - // This case is replacement for PREG_SPLIT_NO_EMPTY. - continue; - } - value = this._php_trim(this.runSpanGamut(value)); - - // Check if this should be enclosed in a paragraph. - // Clean tag hashes & block tag hashes are left alone. - var is_p = !value.match(/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/); - - if (is_p) { - value = "

          " + value + "

          "; - } - grafs[i] = value; - } - - // Join grafs in one text, then unhash HTML tags. - text = grafs.join("\n\n"); - - // Finish by removing any tag hashes still present in $text. - text = this.unhash(text); - - return text; -}; - -// ### Footnotes - -/** - * Strips link definitions from text, stores the URLs and titles in - * hash references. - */ -MarkdownExtra_Parser.prototype.stripFootnotes = function(text) { - var self = this; - - var less_than_tab = this.tab_width - 1; - - // Link defs are in the form: [^id]: url "optional title" - text = text.replace(new RegExp( - '^[ ]{0,' + less_than_tab + '}\\[\\^(.+?)\\][ ]?:' + // note_id = $1 - '[ ]*' + - '\\n?' + // maybe *one* newline - '(' + // text = $2 (no blank lines allowed) - '(?:' + - '.+' + // actual text - '|' + - '\\n' + // newlines but - '(?!\\[\\^.+?\\]:\\s)' + // negative lookahead for footnote marker. - '(?!\\n+[ ]{0,3}\\S)' + // ensure line is not blank and followed - // by non-indented content - ')*' + - ')', - "mg" - ), function(match, m1, m2) { - var note_id = self.fn_id_prefix + m1; - self.footnotes[note_id] = self.outdent(m2); - return ''; //# String that will replace the block - }); - return text; -}; - -/** - * Replace footnote references in $text [^id] with a special text-token - * which will be replaced by the actual footnote marker in appendFootnotes. - */ -MarkdownExtra_Parser.prototype.doFootnotes = function(text) { - if (!this.in_anchor) { - text = text.replace(/\[\^(.+?)\]/g, "F\x1Afn:$1\x1A:"); - } - return text; -}; - -/** - * Append footnote list to text. - */ -MarkdownExtra_Parser.prototype.appendFootnotes = function(text) { - var self = this; - - var _appendFootnotes_callback = function(match, m1) { - var node_id = self.fn_id_prefix + m1; - - // Create footnote marker only if it has a corresponding footnote *and* - // the footnote hasn't been used by another marker. - if (node_id in self.footnotes) { - // Transfert footnote content to the ordered list. - self.footnotes_ordered.push([node_id, self.footnotes[node_id]]); - delete self.footnotes[node_id]; - - var num = self.footnote_counter++; - var attr = " rel=\"footnote\""; - if (self.fn_link_class != "") { - var classname = self.fn_link_class; - classname = self.encodeAttribute(classname); - attr += " class=\"" + classname + "\""; - } - if (self.fn_link_title != "") { - var title = self.fn_link_title; - title = self.encodeAttribute(title); - attr += " title=\"" + title +"\""; - } - - attr = attr.replace(/%%/g, num); - node_id = self.encodeAttribute(node_id); - - return "" + - "" + num + "" + - ""; - } - - return "[^" + m1 + "]"; - }; - - text = text.replace(/F\x1Afn:(.*?)\x1A:/g, _appendFootnotes_callback); - - if (this.footnotes_ordered.length > 0) { - text += "\n\n"; - text += "
          \n"; - text += " 0) { - var head = this.footnotes_ordered.shift(); - var note_id = head[0]; - var footnote = head[1]; - - footnote += "\n"; // Need to append newline before parsing. - footnote = this.runBlockGamut(footnote + "\n"); - footnote = footnote.replace(/F\x1Afn:(.*?)\x1A:/g, _appendFootnotes_callback); - - attr = attr.replace(/%%/g, ++num); - note_id = this.encodeAttribute(note_id); - - // Add backlink to last paragraph; create new paragraph if needed. - var backlink = ""; - if (footnote.match(/<\/p>$/)) { - footnote = footnote.substr(0, footnote.length - 4) + " " + backlink + "

          "; - } else { - footnote += "\n\n

          " + backlink + "

          "; - } - - text += "
        • \n"; - text += footnote + "\n"; - text += "
        • \n\n"; - } - - text += "\n"; - text += "
          "; - } - return text; -}; - -//### Abbreviations ### - -/** - * Strips abbreviations from text, stores titles in hash references. - */ -MarkdownExtra_Parser.prototype.stripAbbreviations = function(text) { - var self = this; - - var less_than_tab = this.tab_width - 1; - - // Link defs are in the form: [id]*: url "optional title" - text = text.replace(new RegExp( - '^[ ]{0,' + less_than_tab + '}\\*\\[(.+?)\\][ ]?:' + // abbr_id = $1 - '(.*)', // text = $2 (no blank lines allowed) - "m" - ), function(match, abbr_word, abbr_desc) { - if (self.abbr_word_re != '') { - self.abbr_word_re += '|'; - } - self.abbr_word_re += self._php_preg_quote(abbr_word); - self.abbr_desciptions[abbr_word] = self._php_trim(abbr_desc); - return ''; // String that will replace the block - }); - return text; -}; - -/** - * Find defined abbreviations in text and wrap them in elements. - */ -MarkdownExtra_Parser.prototype.doAbbreviations = function(text) { - var self = this; - - if (this.abbr_word_re) { - // cannot use the /x modifier because abbr_word_re may - // contain significant spaces: - text = text.replace(new RegExp( - '(^|[^\\w\\x1A])' + - '(' + this.abbr_word_re + ')' + - '(?![\\w\\x1A])' - ), function(match, prev, abbr) { - if (abbr in self.abbr_desciptions) { - var desc = self.abbr_desciptions[abbr]; - if (!desc || desc == "") { - return self.hashPart("" + abbr + ""); - } else { - desc = self.encodeAttribute(desc); - return self.hashPart("" + abbr + ""); - } - } else { - return match; - } - }); - } - return text; -}; - - -/** - * Export to Node.js - */ -this.Markdown = Markdown; -this.Markdown_Parser = Markdown_Parser; -this.MarkdownExtra_Parser = MarkdownExtra_Parser; - - -/*! - * jsUri - * https://github.com/derek-watson/jsUri - * - * Copyright 2012, Derek Watson - * Released under the MIT license. - * - * Includes parseUri regular expressions - * http://blog.stevenlevithan.com/archives/parseuri - * Copyright 2007, Steven Levithan - * Released under the MIT license. - * - */ - -(function(global) { - - /** - * Define forEach for older js environments - * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility - */ - if (!Array.prototype.forEach) { - Array.prototype.forEach = function(fn, scope) { - for (var i = 0, len = this.length; i < len; ++i) { - fn.call(scope || this, this[i], i, this); - } - }; - } - - /** - * unescape a query param value - * @param {string} s encoded value - * @return {string} decoded value - */ - function decode(s) { - s = decodeURIComponent(s); - s = s.replace('+', ' '); - return s; - } - - /** - * Breaks a uri string down into its individual parts - * @param {string} str uri - * @return {object} parts - */ - function parseUri(str) { - var parser = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/, - parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"], - m = parser.exec(str || ''), - parts = {}; - - parserKeys.forEach(function(key, i) { - parts[key] = m[i] || ''; - }); - return parts; - } - - /** - * Breaks a query string down into an array of key/value pairs - * @param {string} str query - * @return {array} array of arrays (key/value pairs) - */ - function parseQuery(str) { - var i, ps, p, kvp, k, v, - pairs = []; - - if (typeof(str) === 'undefined' || str === null || str === '') { - return pairs; - } - - if (str.indexOf('?') === 0) { - str = str.substring(1); - } - - ps = str.toString().split(/[&;]/); - - for (i = 0; i < ps.length; i++) { - p = ps[i]; - kvp = p.split('='); - k = kvp[0]; - v = p.indexOf('=') === -1 ? null : (kvp[1] === null ? '' : kvp[1]); - pairs.push([k, v]); - } - return pairs; - } - - /** - * Creates a new Uri object - * @constructor - * @param {string} str - */ - function Uri(str) { - this.uriParts = parseUri(str); - this.queryPairs = parseQuery(this.uriParts.query); - this.hasAuthorityPrefixUserPref = null; - } - - /** - * Define getter/setter methods - */ - ['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) { - Uri.prototype[key] = function(val) { - if (typeof val !== 'undefined') { - this.uriParts[key] = val; - } - return this.uriParts[key]; - }; - }); - - /** - * if there is no protocol, the leading // can be enabled or disabled - * @param {Boolean} val - * @return {Boolean} - */ - Uri.prototype.hasAuthorityPrefix = function(val) { - if (typeof val !== 'undefined') { - this.hasAuthorityPrefixUserPref = val; - } - - if (this.hasAuthorityPrefixUserPref === null) { - return (this.uriParts.source.indexOf('//') !== -1); - } else { - return this.hasAuthorityPrefixUserPref; - } - }; - - /** - * Serializes the internal state of the query pairs - * @param {string} [val] set a new query string - * @return {string} query string - */ - Uri.prototype.query = function(val) { - var s = '', - i, param; - - if (typeof val !== 'undefined') { - this.queryPairs = parseQuery(val); - } - - for (i = 0; i < this.queryPairs.length; i++) { - param = this.queryPairs[i]; - if (s.length > 0) { - s += '&'; - } - if (param[1] === null) { - s += param[0]; - } else { - s += param.join('='); - } - } - return s.length > 0 ? '?' + s : s; - }; - - /** - * returns the first query param value found for the key - * @param {string} key query key - * @return {string} first value found for key - */ - Uri.prototype.getQueryParamValue = function (key) { - var param, i; - for (i = 0; i < this.queryPairs.length; i++) { - param = this.queryPairs[i]; - if (decode(key) === decode(param[0])) { - return param[1]; - } - } - }; - - /** - * returns an array of query param values for the key - * @param {string} key query key - * @return {array} array of values - */ - Uri.prototype.getQueryParamValues = function (key) { - var arr = [], - i, param; - for (i = 0; i < this.queryPairs.length; i++) { - param = this.queryPairs[i]; - if (decode(key) === decode(param[0])) { - arr.push(param[1]); - } - } - return arr; - }; - - /** - * removes query parameters - * @param {string} key remove values for key - * @param {val} [val] remove a specific value, otherwise removes all - * @return {Uri} returns self for fluent chaining - */ - Uri.prototype.deleteQueryParam = function (key, val) { - var arr = [], - i, param, keyMatchesFilter, valMatchesFilter; - - for (i = 0; i < this.queryPairs.length; i++) { - - param = this.queryPairs[i]; - keyMatchesFilter = decode(param[0]) === decode(key); - valMatchesFilter = decode(param[1]) === decode(val); - - if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && !keyMatchesFilter && !valMatchesFilter)) { - arr.push(param); - } - } - - this.queryPairs = arr; - - return this; - }; - - /** - * adds a query parameter - * @param {string} key add values for key - * @param {string} val value to add - * @param {integer} [index] specific index to add the value at - * @return {Uri} returns self for fluent chaining - */ - Uri.prototype.addQueryParam = function (key, val, index) { - if (arguments.length === 3 && index !== -1) { - index = Math.min(index, this.queryPairs.length); - this.queryPairs.splice(index, 0, [key, val]); - } else if (arguments.length > 0) { - this.queryPairs.push([key, val]); - } - return this; - }; - - /** - * replaces query param values - * @param {string} key key to replace value for - * @param {string} newVal new value - * @param {string} [oldVal] replace only one specific value (otherwise replaces all) - * @return {Uri} returns self for fluent chaining - */ - Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) { - - var index = -1, - i, param; - - if (arguments.length === 3) { - for (i = 0; i < this.queryPairs.length; i++) { - param = this.queryPairs[i]; - if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) { - index = i; - break; - } - } - this.deleteQueryParam(key, oldVal).addQueryParam(key, newVal, index); - } else { - for (i = 0; i < this.queryPairs.length; i++) { - param = this.queryPairs[i]; - if (decode(param[0]) === decode(key)) { - index = i; - break; - } - } - this.deleteQueryParam(key); - this.addQueryParam(key, newVal, index); - } - return this; - }; - - /** - * Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc) - */ - ['protocol', 'hasAuthorityPrefix', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) { - var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1); - Uri.prototype[method] = function(val) { - this[key](val); - return this; - }; - }); - - /** - * Scheme name, colon and doubleslash, as required - * @return {string} http:// or possibly just // - */ - Uri.prototype.scheme = function() { - - var s = ''; - - if (this.protocol()) { - s += this.protocol(); - if (this.protocol().indexOf(':') !== this.protocol().length - 1) { - s += ':'; - } - s += '//'; - } else { - if (this.hasAuthorityPrefix() && this.host()) { - s += '//'; - } - } - - return s; - }; - - /** - * Same as Mozilla nsIURI.prePath - * @return {string} scheme://user:password@host:port - * @see https://developer.mozilla.org/en/nsIURI - */ - Uri.prototype.origin = function() { - - var s = this.scheme(); - - if (this.userInfo() && this.host()) { - s += this.userInfo(); - if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) { - s += '@'; - } - } - - if (this.host()) { - s += this.host(); - if (this.port()) { - s += ':' + this.port(); - } - } - - return s; - }; - - /** - * Serializes the internal state of the Uri object - * @return {string} - */ - Uri.prototype.toString = function() { - - var s = this.origin(); - - if (this.path()) { - s += this.path(); - } else { - if (this.host() && (this.query().toString() || this.anchor())) { - s += '/'; - } - } - if (this.query().toString()) { - if (this.query().toString().indexOf('?') !== 0) { - s += '?'; - } - s += this.query().toString(); - } - - if (this.anchor()) { - if (this.anchor().indexOf('#') !== 0) { - s += '#'; - } - s += this.anchor(); - } - - return s; - }; - - /** - * Clone a Uri object - * @return {Uri} duplicate copy of the Uri - */ - Uri.prototype.clone = function() { - return new Uri(this.toString()); - }; - - /** - * export via CommonJS, otherwise leak a global - */ - if (typeof module === 'undefined') { - global.Uri = Uri; - } else { - module.exports = Uri; - } -}(this)); - -/*jslint browser: true, devel: true, todo: true, unparam: true */ -/*global define, btoa, Markdown */ -/* - Copyright 2014 Red Hat Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ -(function (root, factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - define('strata', ['jquery', 'jsUri'], factory); - } else { - root.strata = factory(root.$, root.Uri); - } -}(this, function ($, Uri) { - 'use strict'; - - var strata = {}, - //Since we can't set the UserAgent - redhatClient = "redhat_client", - redhatClientID, - //Internal copy of user, might be useful for logging, only valid for cookie auth - authedUser = {}, - basicAuthToken = "", - portalHostname, - strataHostname, - baseAjaxParams = {}, - authAjaxParams, - checkCredentials, - checkCredentialsNoBasic, - fetchSolution, - fetchArticle, - searchArticles, - fetchCase, - fetchCaseComments, - createComment, - fetchCases, - fetchCasesCSV, - addNotifiedUser, - removeNotifiedUser, - updateCase, - filterCases, - createAttachment, - deleteAttachment, - listCaseAttachments, - getSymptomsFromText, - listGroups, - createGroup, - deleteGroup, - fetchGroup, - listProducts, - fetchProduct, - fetchProductVersions, - caseTypes, - caseSeverities, - caseStatus, - fetchSystemProfiles, - fetchSystemProfile, - createSystemProfile, - fetchAccounts, - fetchAccount, - fetchURI, - fetchEntitlements, - authHostname, - fetchAccountUsers; - - strata.version = "1.0.33"; - redhatClientID = "stratajs-" + strata.version; - - if (window.portal && window.portal.host) { - //if this is a chromed app this will work otherwise we default to prod - portalHostname = new Uri(window.portal.host).host(); - authHostname = portalHostname; - - } else { - portalHostname = 'access.redhat.com'; - authHostname = portalHostname; - } - - if(localStorage && localStorage.getItem('portalHostname')) { - portalHostname = localStorage.getItem('portalHostname'); - } - - strataHostname = new Uri('https://api.' + portalHostname); - strataHostname.addQueryParam(redhatClient, redhatClientID); - - strata.setRedhatClientID = function (id) { - redhatClientID = id; - strataHostname = new Uri(strataHostname); - strataHostname.addQueryParam(redhatClient, redhatClientID); - }; - - strata.setStrataHostname = function (hostname) { - portalHostname = hostname; - strataHostname = new Uri(portalHostname); - strataHostname.addQueryParam(redhatClient, redhatClientID); - }; - - strata.setPortalHostname = function (hostname) { - portalHostname = hostname; - authHostname = hostname; - strataHostname = new Uri('https://api.' + portalHostname); - strataHostname.addQueryParam(redhatClient, redhatClientID); - }; - - strata.setAuthHostname = function (hostname) { - authHostname = hostname; - authAjaxParams = $.extend({ - url: 'https://' + authHostname + - '/services/user/status?jsoncallback=?', - dataType: 'jsonp' - }, baseAjaxParams); - }; - - strata.getAuthInfo = function () { - return authedUser; - }; - - //Store Base64 Encoded Auth Token - basicAuthToken = localStorage.getItem("rhAuthToken"); - authedUser.login = localStorage.getItem("rhUserName"); - - strata.setCredentials = function (username, password) { - if(isASCII(username + password)){ - basicAuthToken = btoa(username + ":" + password); - localStorage.setItem("rhAuthToken", basicAuthToken); - localStorage.setItem("rhUserName", username); - authedUser.login = username; - return true; - } else{ - return false; - } - }; - - function isASCII(str) { - return /^[\x00-\x7F]*$/.test(str); - } - - strata.clearCredentials = function () { - strata.clearBasicAuth(); - strata.clearCookieAuth(); - authedUser = {}; - }; - - strata.clearBasicAuth = function () { - localStorage.setItem("rhAuthToken", ''); - localStorage.setItem("rhUserName", ''); - basicAuthToken = ""; - }; - - strata.clearCookieAuth = function () { - $("body").append(""); - window.open("https://" + authHostname + "/logout", "rhLogoutFrame"); - }; - - - //Private vars related to the connection - baseAjaxParams = { - accepts: { - jsonp: 'application/json, text/json' - }, - crossDomain: true, - type: 'GET', - method: 'GET', - beforeSend: function (xhr) { - //Include Basic Auth Credentials if available, will try SSO Cookie otherwise - xhr.setRequestHeader('X-Omit', 'WWW-Authenticate'); - if (basicAuthToken !== null) { - if (basicAuthToken.length !== 0) { - xhr.setRequestHeader('Authorization', "Basic " + basicAuthToken); - } - } - }, - headers: { - Accept: 'application/json, text/json' - }, - xhrFields: { - withCredentials: true - }, - contentType: 'application/json', - data: {}, - dataType: 'json' - }; - - authAjaxParams = $.extend({ - url: 'https://' + authHostname + - '/services/user/status?jsoncallback=?', - dataType: 'jsonp' - }, baseAjaxParams); - - //Helper Functions - //Convert Java Calendar class to something we can use - //TODO: Make this recursive - function convertDates(entry) { - //Iterate over the objects for *_date - var key; - for (key in entry) { - if (entry.hasOwnProperty(key)) { - if (/[\s\S]*_date/.test(key)) { - //Skip indexed_date, it's not a real "Date" - if (key !== "indexed_date") { - entry[key] = new Date(entry[key]); - } - } - } - } - } - - function markDownToHtml(entry) { - var html = Markdown(entry); - return html; - } - - - //Remove empty fields from object - //TODO: Make this recursive, so it could remove nested objs - function removeEmpty(entry) { - var key; - for (key in entry) { - if (entry.hasOwnProperty(key)) { - //Removes anything with length 0 - if (entry[key].length === 0) { - delete entry[key]; - } - } - } - } - - //Function to test whether we've been passed a URL or just a string/ID - function isUrl(path) { - return path.search(/^http/) >= 0; - } - - //Helper classes - //Class to describe the required Case fields - strata.Case = function () { - return { - summary: "", - description: "", - product: "", - version: "" - }; - }; - - //Class to describe required Case Comment fields - strata.CaseComment = function () { - return { - text: "", - public: true - }; - }; - - //Class to help create System Profiles - strata.SystemProfile = function () { - return { - account_number: "", - case_number: "", - deprecated: false, - //Append SystemProfileCategoryDetails Objects here - system_profile_category: [ - ] - }; - }; - - //Helper to deal with SystemProfileCategories - strata.SystemProfileCategoryDetails = function () { - return { - system_profile_category_name: "", - system_profile_category_summary: "", - //Append key, value pairs here - system_profile_category_details: [] - }; - }; - - //Example of fields that could be supplied to case filter - //Fields with length 0 will be stripped out of this obj prior to being sent - strata.CaseFilter = function () { - var groupNumbers = []; - return { - //The _date objects should be real Date objs - start_date: "", - end_date: "", - account_number: "", - include_closed: false, - include_private: false, - keyword: "", - group_numbers: groupNumbers, - addGroupNumber: function (num) { - groupNumbers.push({group_number: num}); - }, - start: 0, - count: 50, - only_ungrouped: false, - owner_sso_name: "", - product: "", - severity: "", - sort_field: "", - sort_order: "", - status: "", - type: "", - created_by_sso_name: "", - resource_type: "", - id: "", - uri: "", - view_uri: "" - }; - }; - - //PUBLIC METHODS - //User provides a loginSuccess callback to handle the response - strata.checkLogin = function (loginHandler) { - if (!$.isFunction(loginHandler)) { throw "loginHandler callback must be supplied"; } - - checkCredentials = $.extend({}, baseAjaxParams, { - url: strataHostname.clone().setPath('/rs/users') - .addQueryParam('ssoUserName', authedUser.login), - context: authedUser, - success: function (response) { - this.name = response.first_name + ' ' + response.last_name; - this.is_internal = response.is_internal; - this.org_admin = response.org_admin; - this.has_chat = response.has_chat; - this.session_id = response.session_id; - this.can_add_attachments = response.can_add_attachments; - loginHandler(true, this); - }, - error: function () { - strata.clearBasicAuth(); - loginHandler(false); - } - }); - - var loginParams = $.extend({ - context: authedUser, - success: function (response) { - //We have an SSO Cookie, check that it's still valid - if (response.authorized) { - //Copy into our private obj - authedUser = response; - //Needs to be here so authedUser.login will resolve - checkCredentialsNoBasic = $.extend({}, baseAjaxParams, { - context: authedUser, - url: strataHostname.clone().setPath('/rs/users') - .addQueryParam('ssoUserName', authedUser.login), - beforeSend: function (xhr) { - xhr.setRequestHeader('X-Omit', 'WWW-Authenticate'); - }, - //We are all good - success: function (response) { - this.sso_username = response.sso_username; - this.name = response.first_name + ' ' + response.last_name; - this.is_internal = response.is_internal; - this.org_admin = response.org_admin; - this.has_chat = response.has_chat; - this.session_id = response.session_id; - this.can_add_attachments = response.can_add_attachments; - loginHandler(true, this); - }, - //We have an SSO Cookie but it's invalid - error: function () { - strata.clearCookieAuth(); - loginHandler(false); - } - }); - //Check /rs/users?ssoUserName=sso-id - $.ajax(checkCredentialsNoBasic); - } else { - strata.clearCookieAuth(); - $.ajax(checkCredentials); - } - } - }, authAjaxParams); - - //Check if we have an SSO Cookie - $.ajax(loginParams); - }; - - //Sends data to the strata diagnostic toolchain - strata.problems = function (data, onSuccess, onFailure, limit) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (data === undefined) { data = ""; } - if (limit === undefined) { limit = 50; } - - var getSolutionsFromText = $.extend({}, baseAjaxParams, { - url: strataHostname.clone().setPath('/rs/problems') - .addQueryParam('limit', limit), - data: data, - type: 'POST', - method: 'POST', - contentType: 'text/plain', - success: function (response) { - if (response.source_or_link_or_problem[2] !== undefined && response.source_or_link_or_problem[2].source_or_link !== undefined) { - //Gets the array of solutions - var suggestedSolutions = response.source_or_link_or_problem[2].source_or_link; - onSuccess(suggestedSolutions); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(getSolutionsFromText); - }; - - //Base for solutions - strata.solutions = {}; - - //Retrieve a solution - strata.solutions.get = function (solution, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (solution === undefined) { onFailure("solution must be defined"); } - - var url; - if (isUrl(solution)) { - url = new Uri(solution); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/solutions/' + solution); - } - - fetchSolution = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - convertDates(response); - onSuccess(response); - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchSolution); - }; - - //Search for solutions - strata.solutions.search = function (keyword, onSuccess, onFailure, limit, chain) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (keyword === undefined) { keyword = ""; } - if (limit === undefined) {limit = 50; } - if (chain === undefined) {chain = false; } - - var searchSolutions = $.extend({}, baseAjaxParams, { - url: strataHostname.clone().setPath('/rs/solutions') - .addQueryParam('keyword', encodeURIComponent(keyword)) - .addQueryParam('limit', limit), - success: function (response) { - if (chain && response.solution !== undefined) { - response.solution.forEach(function (entry) { - strata.solutions.get(entry.uri, onSuccess, onFailure); - }); - } else if (response.solution !== undefined) { - response.solution.forEach(convertDates); - onSuccess(response.solution); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(searchSolutions); - }; - - //Base for articles - strata.articles = {}; - - //Retrieve an article - strata.articles.get = function (article, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (article === undefined) { onFailure("article must be defined"); } - - var url; - if (isUrl(article)) { - url = new Uri(article); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/articles/' + article); - } - - fetchArticle = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - convertDates(response); - if (response !== undefined && response.body !== undefined && response.body.html === undefined) { - response.body = markDownToHtml(response.body); - onSuccess(response); - } - else if (response !== undefined && response.body !== undefined && response.body.html !== undefined) { - onSuccess(response); - } else { - onFailure("Failed to retrieve Article " + article); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchArticle); - }; - - //Search articles - strata.articles.search = function (keyword, onSuccess, onFailure, limit, chain) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (keyword === undefined) { keyword = ""; } - if (limit === undefined) {limit = 50; } - if (chain === undefined) {chain = false; } - - var url = strataHostname.clone().setPath('/rs/articles'); - url.addQueryParam('keyword', encodeURIComponent(keyword)); - url.addQueryParam('limit', limit); - - searchArticles = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (chain && response.article !== undefined) { - response.article.forEach(function (entry) { - strata.articles.get(entry.uri, onSuccess, onFailure); - }); - } else if (response.article !== undefined) { - response.article.forEach(convertDates); - onSuccess(response.article); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(searchArticles); - }; - - //Base for cases - strata.cases = {}; - strata.cases.attachments = {}; - strata.cases.comments = {}; - strata.cases.notified_users = {}; - - //Retrieve a case - strata.cases.get = function (casenum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum); - } - - fetchCase = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response) { - convertDates(response); - onSuccess(response); - } else { - onFailure("Failed to retrieve Case: " + casenum); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchCase); - }; - - //update case comment - strata.cases.comments.update = function (casenum, comment, commentId, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/comments'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments/' + commentId); - } - - fetchCaseComments = $.extend({}, baseAjaxParams, { - url: url, - type: 'PUT', - method: 'PUT', - data: JSON.stringify(comment), - statusCode: { - 200: function(response) { - onSuccess(); - }, - 400: onFailure - } - }); - $.ajax(fetchCaseComments); - }; - - //Retrieve case comments - strata.cases.comments.get = function (casenum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/comments'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments'); - } - - fetchCaseComments = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.comment !== undefined) { - response.comment.forEach(convertDates); - onSuccess(response.comment); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchCaseComments); - }; - - //TODO: Support DRAFT comments? Only useful for internal - //Create a new case comment - strata.cases.comments.post = function (casenum, casecomment, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - if (casecomment === undefined) { onFailure("casecomment must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/comments'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/comments'); - } - - createComment = $.extend({}, baseAjaxParams, { - url: url, - data: JSON.stringify(casecomment), - type: 'POST', - method: 'POST', - success: function (response, status, xhr) { - //Created case comment data is in the XHR - var commentnum = xhr.getResponseHeader("Location"); - commentnum = commentnum.split("/").pop(); - onSuccess(commentnum); - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(createComment); - }; - - //List cases for the given user - strata.cases.list = function (onSuccess, onFailure, closed) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (closed === undefined) { closed = 'false'; } - - if (!closed) { - closed = 'false'; - } - - var url = strataHostname.clone().setPath('/rs/cases'); - url.addQueryParam('includeClosed', closed); - - fetchCases = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response['case'] !== undefined) { - response['case'].forEach(convertDates); - onSuccess(response['case']); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchCases); - }; - - //Create a new case comment - strata.cases.notified_users.add = function (casenum, ssoUserName, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - if (ssoUserName === undefined) { onFailure("ssoUserName must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/notified_users'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/notified_users'); - } - - addNotifiedUser = $.extend({}, baseAjaxParams, { - url: url, - data: '{"user": [{"ssoUsername":"' + ssoUserName + '"}]}', - type: 'POST', - method: 'POST', - headers: { - Accept: "text/plain" - }, - dataType: 'text', - success: onSuccess, - statusCode: { - 201: onSuccess, - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(addNotifiedUser); - }; - - strata.cases.notified_users.remove = function (casenum, ssoUserName, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - if (ssoUserName === undefined) { onFailure("ssoUserName must be defined"); } - - var url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/notified_users/' + ssoUserName); - - removeNotifiedUser = $.extend({}, baseAjaxParams, { - url: url, - type: 'DELETE', - method: 'DELETE', - contentType: 'text/plain', - headers: { - Accept: "text/plain" - }, - dataType: 'text', - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(removeNotifiedUser); - }; - - //List cases in CSV for the given user, this casues a download to occur - strata.cases.csv = function (onSuccess, onFailure) { - var url = strataHostname.clone().setPath('/rs/cases'); - - fetchCasesCSV = $.extend({}, baseAjaxParams, { - headers: { - Accept: "text/csv" - }, - url: url, - contentType: 'text/csv', - dataType: 'text', - success: function(data, response, status) { - var uri = 'data:text/csv;charset=UTF-8,' + encodeURIComponent(data); - window.location = uri; - onSuccess(); - }, - error: function (xhr, response, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, response, status); - } - }); - $.ajax(fetchCasesCSV); - }; - - //Filter cases - strata.cases.filter = function (casefilter, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casefilter === undefined) { onFailure("casefilter must be defined"); } - - var url = strataHostname.clone().setPath('/rs/cases/filter'); - - //Remove any 0 length fields - removeEmpty(casefilter); - - filterCases = $.extend({}, baseAjaxParams, { - url: url, - data: JSON.stringify(casefilter), - type: 'POST', - method: 'POST', - success: function (response) { - if (response['case'] !== undefined) { - response['case'].forEach(convertDates); - onSuccess(response['case']); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(filterCases); - }; - - //Create a new case - strata.cases.post = function (casedata, onSuccess, onFailure) { - //Default parameter value - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casedata === undefined) { onFailure("casedata must be defined"); } - - var url = strataHostname.clone().setPath('/rs/cases'); - - createAttachment = $.extend({}, baseAjaxParams, { - url: url, - data: JSON.stringify(casedata), - type: 'POST', - method: 'POST', - success: function (response, status, xhr) { - //Created case data is in the XHR - var casenum = xhr.getResponseHeader("Location"); - casenum = casenum.split("/").pop(); - onSuccess(casenum); - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(createAttachment); - }; - - //Update a case - strata.cases.put = function (casenum, casedata, onSuccess, onFailure) { - //Default parameter value - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - if (casedata === undefined) { onFailure("casedata must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum); - } - - var successCallback = function() { - onSuccess(); - }; - - updateCase = $.extend({}, baseAjaxParams, { - url: url, - data: JSON.stringify(casedata), - type: 'PUT', - method: 'PUT', - statusCode: { - 200: successCallback, - 202: successCallback, - 400: onFailure - }, - success: function (response) { - onSuccess(response); - } - }); - $.ajax(updateCase); - }; - - - //List case attachments - strata.cases.attachments.list = function (casenum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/attachments'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/attachments'); - } - - listCaseAttachments = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.attachment === undefined) { - onSuccess([]); - } else { - response.attachment.forEach(convertDates); - onSuccess(response.attachment); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(listCaseAttachments); - }; - - //POST an attachment - //data MUST be MULTIPART/FORM-DATA - strata.cases.attachments.post = function (data, casenum, onSuccess, onFailure) { - //Default parameter value - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (data === undefined) { onFailure("data must be defined"); } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum + '/attachments'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/cases/' + casenum + '/attachments'); - } - - createAttachment = $.extend({}, baseAjaxParams, { - url: url, - data: data, - type: 'POST', - method: 'POST', - processData: false, - contentType: false, - cache: false, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(createAttachment); - }; - - strata.cases.attachments.remove = function (attachmentId, casenum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (attachmentId === undefined) { onFailure("attachmentId must be defined"); } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url = - strataHostname.clone().setPath( - '/rs/cases/' + casenum + '/attachments/' + attachmentId - ); - deleteAttachment = $.extend({}, baseAjaxParams, { - url: url, - type: 'DELETE', - method: 'DELETE', - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(deleteAttachment); - }; - - //Base for symptoms - strata.symptoms = {}; - - //Symptom Extractor - strata.symptoms.extractor = function (data, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (data === undefined) { onFailure("data must be defined"); } - - var url = strataHostname.clone().setPath('/rs/symptoms/extractor'); - - getSymptomsFromText = $.extend({}, baseAjaxParams, { - url: url, - data: data, - type: 'POST', - method: 'POST', - contentType: 'text/plain', - success: function (response) { - if (response.extracted_symptom !== undefined) { - onSuccess(response.extracted_symptom); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(getSymptomsFromText); - }; - - //Base for groups - strata.groups = {}; - - //List groups for this user - strata.groups.list = function (onSuccess, onFailure, ssoUserName) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - if (ssoUserName === undefined) { - var url = strataHostname.clone().setPath('/rs/groups'); - } else { - var url = strataHostname.clone().setPath('/rs/groups/contact/' + ssoUserName); - } - - listGroups = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.group !== undefined) { - onSuccess(response.group); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(listGroups); - }; - - //Create a group - strata.groups.create = function (groupName, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (groupName === undefined) { onFailure("groupName must be defined"); } - - var url = strataHostname.clone().setPath('/rs/groups'); - url.addQueryParam(redhatClient, redhatClientID); - - var throwError = function(xhr, response, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, response, status); - }; - - createGroup = $.extend({}, baseAjaxParams, { - url: url, - type: 'POST', - method: 'POST', - data: '{"name": "' + groupName + '"}', - success: onSuccess, - statusCode: { - 201: function(response) { - var locationHeader = response.getResponseHeader('Location'); - var groupNumber = - locationHeader.slice(locationHeader.lastIndexOf('/') + 1); - onSuccess(groupNumber); - }, - 400: throwError, - 500: throwError - } - }); - $.ajax(createGroup); - }; - - //Delete a group - strata.groups.remove = function (groupnum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (groupnum === undefined) { onFailure("groupnum must be defined"); } - - var url = strataHostname.clone().setPath('/rs/groups/' + groupnum); - url.addQueryParam(redhatClient, redhatClientID); - - var throwError = function(xhr, response, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, response, status); - }; - - deleteGroup = $.extend({}, baseAjaxParams, { - url: url, - type: 'DELETE', - method: 'DELETE', - success: onSuccess, - statusCode: { - 200: function(response) { - onSuccess(); - }, - 400: throwError, - 500: throwError - } - }); - $.ajax(deleteGroup); - }; - - //Retrieve a group - strata.groups.get = function (groupnum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (groupnum === undefined) { onFailure("groupnum must be defined"); } - - var url; - if (isUrl(groupnum)) { - url = new Uri(groupnum); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/groups/' + groupnum); - } - - fetchGroup = $.extend({}, baseAjaxParams, { - url: url, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchGroup); - }; - - //Base for products - strata.products = {}; - - //List products for this user - strata.products.list = function (onSuccess, onFailure, ssoUserName) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - if (ssoUserName === undefined) { - var url = strataHostname.clone().setPath('/rs/products'); - } else { - var url = strataHostname.clone().setPath('/rs/products/contact/' + ssoUserName); - } - - - listProducts = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.product !== undefined) { - onSuccess(response.product); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(listProducts); - }; - - //Retrieve a product - strata.products.get = function (code, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (code === undefined) { onFailure("code must be defined"); } - - var url; - if (isUrl(code)) { - url = new Uri(code); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/products/' + code); - } - - fetchProduct = $.extend({}, baseAjaxParams, { - url: url, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchProduct); - }; - - //Retrieve versions for a product - strata.products.versions = function (code, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (code === undefined) { onFailure("code must be defined"); } - - var url; - if (isUrl(code)) { - url = new Uri(code + '/versions'); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/products/' + code + '/versions'); - } - - fetchProductVersions = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.version !== undefined) { - onSuccess(response.version); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchProductVersions); - }; - - //Base for values - strata.values = {}; - strata.values.cases = {}; - - //Retrieve the case types - strata.values.cases.types = function (onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - var url = strataHostname.clone().setPath('/rs/values/case/types'); - - caseTypes = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.value !== undefined) { - onSuccess(response.value); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(caseTypes); - }; - - //Retrieve the case severities - strata.values.cases.severity = function (onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - var url = strataHostname.clone().setPath('/rs/values/case/severity'); - - caseSeverities = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.value !== undefined) { - onSuccess(response.value); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(caseSeverities); - }; - - //Retrieve the case statuses - strata.values.cases.status = function (onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - var url = strataHostname.clone().setPath('/rs/values/case/status'); - - caseStatus = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.value !== undefined) { - onSuccess(response.value); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(caseStatus); - }; - - //Base for System Profiles - strata.systemProfiles = {}; - - //List system profiles - strata.systemProfiles.list = function (onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - var url = strataHostname.clone().setPath('/rs/system_profiles'); - - fetchSystemProfiles = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.system_profile !== undefined) { - onSuccess(response.system_profile); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchSystemProfiles); - }; - - //Get a specific system_profile, either by hash or casenum - //Case can return an array, hash will return a single result - strata.systemProfiles.get = function (casenum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (casenum === undefined) { onFailure("casenum must be defined"); } - - var url; - if (isUrl(casenum)) { - url = new Uri(casenum); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/system_profiles/' + casenum); - } - - fetchSystemProfile = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if ($.isArray(response.system_profile)) { - onSuccess(response.system_profile); - } else { - onSuccess(response); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchSystemProfile); - }; - //TODO: Create helper class to Handle list + filtering - - //Create a new System Profile - strata.systemProfiles.post = function (systemprofile, onSuccess, onFailure) { - //Default parameter value - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (systemprofile === undefined) { onFailure("systemprofile must be defined"); } - - var url = strataHostname.clone().setPath('/rs/system_profiles'); - - createSystemProfile = $.extend({}, baseAjaxParams, { - url: url, - data: JSON.stringify(systemprofile), - type: 'POST', - method: 'POST', - success: function (response, status, xhr) { - //Created case data is in the XHR - var hash = xhr.getResponseHeader("Location"); - hash = hash.split("/").pop(); - onSuccess(hash); - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(createSystemProfile); - }; - - strata.accounts = {}; - - //List Accounts for the given user - strata.accounts.list = function (onSuccess, onFailure, closed) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (closed === undefined) { closed = false; } - - var url = strataHostname.clone().setPath('/rs/accounts'); - - fetchAccounts = $.extend({}, baseAjaxParams, { - url: url, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchAccounts); - }; - - //Get an Account - strata.accounts.get = function (accountnum, onSuccess, onFailure) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (accountnum === undefined) { onFailure("accountnum must be defined"); } - - var url; - if (isUrl(accountnum)) { - url = new Uri(accountnum); - url.addQueryParam(redhatClient, redhatClientID); - } else { - url = strataHostname.clone().setPath('/rs/accounts/' + accountnum); - } - - fetchAccount = $.extend({}, baseAjaxParams, { - url: url, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchAccount); - }; - - //Get an Accounts Users - strata.accounts.users = function (accountnum, onSuccess, onFailure, group) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (accountnum === undefined) { onFailure("accountnum must be defined"); } - - var url; - if (isUrl(accountnum)) { - url = new Uri(accountnum); - url.addQueryParam(redhatClient, redhatClientID); - } else if (group === undefined) { - url = strataHostname.clone().setPath('/rs/accounts/' + accountnum + "/users"); - } else { - url = strataHostname.clone() - .setPath('/rs/accounts/' + accountnum + "/groups/" + group + "/users"); - } - - fetchAccountUsers = $.extend({}, baseAjaxParams, { - url: url, - success: function (response) { - if (response.user !== undefined) { - onSuccess(response.user); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchAccountUsers); - }; - - strata.entitlements = {}; - strata.entitlements.get = function (showAll, onSuccess, onFailure, ssoUserName) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - - if (ssoUserName === undefined) { - var url = strataHostname.clone().setPath('/rs/entitlements?showAll=' + showAll.toString()); - } else { - var url = strataHostname.clone().setPath('/rs/entitlements/contact/' + ssoUserName + '?showAll=' + showAll.toString()); - } - - fetchEntitlements = $.extend({}, baseAjaxParams, { - url: url, - success: onSuccess, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchEntitlements); - }; - - //Helper function to "diagnose" text, chains problems and solutions calls - //This will call 'onSuccess' for each solution - strata.diagnose = function (data, onSuccess, onFailure, limit) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (data === undefined) { onFailure("data must be defined"); } - if (limit === undefined) { limit = 50; } - - //Call problems, send that list to get solutions to get each one - strata.problems(data, function (response) { - response.forEach(function (entry) { - strata.solutions.get(entry.uri, onSuccess, onFailure); - }); - }, onFailure, limit); - }; - - strata.search = function (keyword, onSuccess, onFailure, limit, chain) { - if (!$.isFunction(onSuccess)) { throw "onSuccess callback must be a function"; } - if (!$.isFunction(onFailure)) { throw "onFailure callback must be a function"; } - if (keyword === undefined) { keyword = ""; } - if (limit === undefined) {limit = 50; } - if (chain === undefined) {chain = false; } - - var searchStrata = $.extend({}, baseAjaxParams, { - url: strataHostname.clone().setPath('/rs/search') - .addQueryParam('keyword', encodeURIComponent(keyword)) - .addQueryParam('contentType', 'article,solution') - .addQueryParam('limit', limit), - success: function (response) { - if (chain && response.search_result !== undefined) { - response.search_result.forEach(function (entry) { - strata.utils.getURI(entry.uri, entry.resource_type, onSuccess, onFailure); - }); - } else if (response.search_result !== undefined) { - onSuccess(response.search_result); - } else { - onSuccess([]); - } - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(searchStrata); - }; - - strata.utils = {}; - - //Get selected text from the browser, this should work on - //Chrome and FF. Have not tested anything else - strata.utils.getSelectedText = function () { - var t = ''; - if (window.getSelection) { - t = window.getSelection(); - } else if (document.getSelection) { - t = document.getSelection(); - } else if (document.selection) { - t = document.selection.createRange().text; - } - return t.toString(); - }; - - strata.utils.getURI = function (uri, resourceType, onSuccess, onFailure) { - fetchURI = $.extend({}, baseAjaxParams, { - url: uri, - success: function (response) { - convertDates(response); - onSuccess(resourceType, response); - }, - error: function (xhr, reponse, status) { - onFailure("Error " + xhr.status + " " + xhr.statusText, xhr, reponse, status); - } - }); - $.ajax(fetchURI); - }; - - return strata; -})); - -/** - * @license AngularJS v1.2.1 - * (c) 2010-2012 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -var $resourceMinErr = angular.$$minErr('$resource'); - -// Helper functions and regex to lookup a dotted path on an object -// stopping at undefined/null. The path must be composed of ASCII -// identifiers (just like $parse) -var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; - -function isValidDottedPath(path) { - return (path != null && path !== '' && path !== 'hasOwnProperty' && - MEMBER_NAME_REGEX.test('.' + path)); -} - -function lookupDottedPath(obj, path) { - if (!isValidDottedPath(path)) { - throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); - } - var keys = path.split('.'); - for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { - var key = keys[i]; - obj = (obj !== null) ? obj[key] : undefined; - } - return obj; -} - -/** - * @ngdoc overview - * @name ngResource - * @description - * - * # ngResource - * - * The `ngResource` module provides interaction support with RESTful services - * via the $resource service. - * - * {@installModule resource} - * - *
          - * - * See {@link ngResource.$resource `$resource`} for usage. - */ - -/** - * @ngdoc object - * @name ngResource.$resource - * @requires $http - * - * @description - * A factory which creates a resource object that lets you interact with - * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. - * - * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link ng.$http $http} service. - * - * Requires the {@link ngResource `ngResource`} module to be installed. - * - * @param {string} url A parametrized URL template with parameters prefixed by `:` as in - * `/user/:username`. If you are using a URL with a port number (e.g. - * `http://example.com:8080/api`), it will be respected. - * - * If you are using a url with a suffix, just add the suffix, like this: - * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` - * or even `$resource('http://example.com/resource/:resource_id.:format')` - * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be - * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you - * can escape it with `/\.`. - * - * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in - * `actions` methods. If any of the parameter value is a function, it will be executed every time - * when a param value needs to be obtained for a request (unless the param was overridden). - * - * Each key value in the parameter object is first bound to url template if present and then any - * excess keys are appended to the url search query after the `?`. - * - * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in - * URL `/path/greet?salutation=Hello`. - * - * If the parameter value is prefixed with `@` then the value of that parameter is extracted from - * the data object (useful for non-GET operations). - * - * @param {Object.=} actions Hash with declaration of custom action that should extend the - * default set of resource actions. The declaration should be created in the format of {@link - * ng.$http#usage_parameters $http.config}: - * - * {action1: {method:?, params:?, isArray:?, headers:?, ...}, - * action2: {method:?, params:?, isArray:?, headers:?, ...}, - * ...} - * - * Where: - * - * - **`action`** – {string} – The name of action. This name becomes the name of the method on - * your resource object. - * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, - * `DELETE`, and `JSONP`. - * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of - * the parameter value is a function, it will be executed every time when a param value needs to - * be obtained for a request (unless the param was overridden). - * - **`url`** – {string} – action specific `url` override. The url templating is supported just - * like for the resource-level urls. - * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, - * see `returns` section. - * - **`transformRequest`** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * - **`transformResponse`** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that - * should abort the request when resolved. - * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 - * requests with credentials} for more information. - * - **`responseType`** - `{string}` - see {@link - * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. - * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - - * `response` and `responseError`. Both `response` and `responseError` interceptors get called - * with `http response` object. See {@link ng.$http $http interceptors}. - * - * @returns {Object} A resource "class" object with methods for the default set of resource actions - * optionally extended with custom `actions`. The default set contains these actions: - * - * { 'get': {method:'GET'}, - * 'save': {method:'POST'}, - * 'query': {method:'GET', isArray:true}, - * 'remove': {method:'DELETE'}, - * 'delete': {method:'DELETE'} }; - * - * Calling these methods invoke an {@link ng.$http} with the specified http method, - * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class. The actions `save`, `remove` and `delete` are available on it - * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, - * read, update, delete) on server-side data like this: - *
          -        var User = $resource('/user/:userId', {userId:'@id'});
          -        var user = User.get({userId:123}, function() {
          -          user.abc = true;
          -          user.$save();
          -        });
          -     
          - * - * It is important to realize that invoking a $resource object method immediately returns an - * empty reference (object or array depending on `isArray`). Once the data is returned from the - * server the existing reference is populated with the actual data. This is a useful trick since - * usually the resource is assigned to a model which is then rendered by the view. Having an empty - * object results in no rendering, once the data arrives from the server then the object is - * populated with the data and the view automatically re-renders itself showing the new data. This - * means that in most cases one never has to write a callback function for the action methods. - * - * The action methods on the class object or instance object can be invoked with the following - * parameters: - * - * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` - * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` - * - non-GET instance actions: `instance.$action([parameters], [success], [error])` - * - * Success callback is called with (value, responseHeaders) arguments. Error callback is called - * with (httpResponse) argument. - * - * Class actions return empty instance (with additional properties below). - * Instance actions return promise of the action. - * - * The Resource instances and collection have these additional properties: - * - * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this - * instance or collection. - * - * On success, the promise is resolved with the same resource instance or collection object, - * updated with data from server. This makes it easy to use in - * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view - * rendering until the resource(s) are loaded. - * - * On failure, the promise is resolved with the {@link ng.$http http response} object, without - * the `resource` property. - * - * - `$resolved`: `true` after first server interaction is completed (either with success or - * rejection), `false` before that. Knowing if the Resource has been resolved is useful in - * data-binding. - * - * @example - * - * # Credit card resource - * - *
          -     // Define CreditCard class
          -     var CreditCard = $resource('/user/:userId/card/:cardId',
          -      {userId:123, cardId:'@id'}, {
          -       charge: {method:'POST', params:{charge:true}}
          -      });
          -
          -     // We can retrieve a collection from the server
          -     var cards = CreditCard.query(function() {
          -       // GET: /user/123/card
          -       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
          -
          -       var card = cards[0];
          -       // each item is an instance of CreditCard
          -       expect(card instanceof CreditCard).toEqual(true);
          -       card.name = "J. Smith";
          -       // non GET methods are mapped onto the instances
          -       card.$save();
          -       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
          -       // server returns: {id:456, number:'1234', name: 'J. Smith'};
          -
          -       // our custom method is mapped as well.
          -       card.$charge({amount:9.99});
          -       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
          -     });
          -
          -     // we can create an instance as well
          -     var newCard = new CreditCard({number:'0123'});
          -     newCard.name = "Mike Smith";
          -     newCard.$save();
          -     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
          -     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
          -     expect(newCard.id).toEqual(789);
          - * 
          - * - * The object returned from this function execution is a resource "class" which has "static" method - * for each action in the definition. - * - * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and - * `headers`. - * When the data is returned from the server then the object is an instance of the resource type and - * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD - * operations (create, read, update, delete) on server-side data. - -
          -     var User = $resource('/user/:userId', {userId:'@id'});
          -     var user = User.get({userId:123}, function() {
          -       user.abc = true;
          -       user.$save();
          -     });
          -   
          - * - * It's worth noting that the success callback for `get`, `query` and other methods gets passed - * in the response that came from the server as well as $http header getter function, so one - * could rewrite the above example and get access to http headers as: - * -
          -     var User = $resource('/user/:userId', {userId:'@id'});
          -     User.get({userId:123}, function(u, getResponseHeaders){
          -       u.abc = true;
          -       u.$save(function(u, putResponseHeaders) {
          -         //u => saved user object
          -         //putResponseHeaders => $http header getter
          -       });
          -     });
          -   
          - */ -angular.module('ngResource', ['ng']). - factory('$resource', ['$http', '$q', function($http, $q) { - - var DEFAULT_ACTIONS = { - 'get': {method:'GET'}, - 'save': {method:'POST'}, - 'query': {method:'GET', isArray:true}, - 'remove': {method:'DELETE'}, - 'delete': {method:'DELETE'} - }; - var noop = angular.noop, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - isFunction = angular.isFunction; - - /** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path - * segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); - } - - - /** - * This method is intended for encoding *key* or *value* parts of query component. We need a - * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't - * have to be encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } - - function Route(template, defaults) { - this.template = template; - this.defaults = defaults || {}; - this.urlParams = {}; - } - - Route.prototype = { - setUrlParams: function(config, params, actionUrl) { - var self = this, - url = actionUrl || self.template, - val, - encodedVal; - - var urlParams = self.urlParams = {}; - forEach(url.split(/\W/), function(param){ - if (param === 'hasOwnProperty') { - throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); - } - if (!(new RegExp("^\\d+$").test(param)) && param && - (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { - urlParams[param] = true; - } - }); - url = url.replace(/\\:/g, ':'); - - params = params || {}; - forEach(self.urlParams, function(_, urlParam){ - val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; - if (angular.isDefined(val) && val !== null) { - encodedVal = encodeUriSegment(val); - url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); - } else { - url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, - leadingSlashes, tail) { - if (tail.charAt(0) == '/') { - return tail; - } else { - return leadingSlashes + tail; - } - }); - } - }); - - // strip trailing slashes and set the url - url = url.replace(/\/+$/, ''); - // then replace collapse `/.` if found in the last URL path segment before the query - // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` - url = url.replace(/\/\.(?=\w+($|\?))/, '.'); - // replace escaped `/\.` with `/.` - config.url = url.replace(/\/\\\./, '/.'); - - - // set params - delegate param encoding to $http - forEach(params, function(value, key){ - if (!self.urlParams[key]) { - config.params = config.params || {}; - config.params[key] = value; - } - }); - } - }; - - - function resourceFactory(url, paramDefaults, actions) { - var route = new Route(url); - - actions = extend({}, DEFAULT_ACTIONS, actions); - - function extractParams(data, actionParams){ - var ids = {}; - actionParams = extend({}, paramDefaults, actionParams); - forEach(actionParams, function(value, key){ - if (isFunction(value)) { value = value(); } - ids[key] = value && value.charAt && value.charAt(0) == '@' ? - lookupDottedPath(data, value.substr(1)) : value; - }); - return ids; - } - - function defaultResponseInterceptor(response) { - return response.resource; - } - - function Resource(value){ - copy(value || {}, this); - } - - forEach(actions, function(action, name) { - var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); - - Resource[name] = function(a1, a2, a3, a4) { - var params = {}, data, success, error; - - /* jshint -W086 */ /* (purposefully fall through case statements) */ - switch(arguments.length) { - case 4: - error = a4; - success = a3; - //fallthrough - case 3: - case 2: - if (isFunction(a2)) { - if (isFunction(a1)) { - success = a1; - error = a2; - break; - } - - success = a2; - error = a3; - //fallthrough - } else { - params = a1; - data = a2; - success = a3; - break; - } - case 1: - if (isFunction(a1)) success = a1; - else if (hasBody) data = a1; - else params = a1; - break; - case 0: break; - default: - throw $resourceMinErr('badargs', - "Expected up to 4 arguments [params, data, success, error], got {0} arguments", - arguments.length); - } - /* jshint +W086 */ /* (purposefully fall through case statements) */ - - var isInstanceCall = data instanceof Resource; - var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); - var httpConfig = {}; - var responseInterceptor = action.interceptor && action.interceptor.response || - defaultResponseInterceptor; - var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || - undefined; - - forEach(action, function(value, key) { - if (key != 'params' && key != 'isArray' && key != 'interceptor') { - httpConfig[key] = copy(value); - } - }); - - if (hasBody) httpConfig.data = data; - route.setUrlParams(httpConfig, - extend({}, extractParams(data, action.params || {}), params), - action.url); - - var promise = $http(httpConfig).then(function(response) { - var data = response.data, - promise = value.$promise; - - if (data) { - // Need to convert action.isArray to boolean in case it is undefined - // jshint -W018 - if ( angular.isArray(data) !== (!!action.isArray) ) { - throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + - 'response to contain an {0} but got an {1}', - action.isArray?'array':'object', angular.isArray(data)?'array':'object'); - } - // jshint +W018 - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - value.push(new Resource(item)); - }); - } else { - copy(data, value); - value.$promise = promise; - } - } - - value.$resolved = true; - - response.resource = value; - - return response; - }, function(response) { - value.$resolved = true; - - (error||noop)(response); - - return $q.reject(response); - }); - - promise = promise.then( - function(response) { - var value = responseInterceptor(response); - (success||noop)(value, response.headers); - return value; - }, - responseErrorInterceptor); - - if (!isInstanceCall) { - // we are creating instance / collection - // - set the initial promise - // - return the instance / collection - value.$promise = promise; - value.$resolved = false; - - return value; - } - - // instance call - return promise; - }; - - - Resource.prototype['$' + name] = function(params, success, error) { - if (isFunction(params)) { - error = success; success = params; params = {}; - } - var result = Resource[name](params, this, success, error); - return result.$promise || result; - }; - }); - - Resource.bind = function(additionalParamDefaults){ - return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; - - return Resource; - } - - return resourceFactory; - }]); - - -})(window, window.angular); - -/** - * @license AngularJS v1.2.1 - * (c) 2010-2012 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -var $sanitizeMinErr = angular.$$minErr('$sanitize'); - -/** - * @ngdoc overview - * @name ngSanitize - * @description - * - * # ngSanitize - * - * The `ngSanitize` module provides functionality to sanitize HTML. - * - * {@installModule sanitize} - * - *
          - * - * See {@link ngSanitize.$sanitize `$sanitize`} for usage. - */ - -/* - * HTML Parser By Misko Hevery (misko@hevery.com) - * based on: HTML Parser By John Resig (ejohn.org) - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - */ - - -/** - * @ngdoc service - * @name ngSanitize.$sanitize - * @function - * - * @description - * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are - * then serialized back to properly escaped html string. This means that no unsafe input can make - * it into the returned string, however, since our parser is more strict than a typical browser - * parser, it's possible that some obscure input, which would be recognized as valid HTML by a - * browser, won't make it through the sanitizer. - * - * @param {string} html Html input. - * @returns {string} Sanitized html. - * - * @example - - - -
          - Snippet: - - - - - - - - - - - - - - - - - - - - - - - - - -
          DirectiveHowSourceRendered
          ng-bind-htmlAutomatically uses $sanitize
          <div ng-bind-html="snippet">
          </div>
          ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value -
          <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
          -</div>
          -
          ng-bindAutomatically escapes
          <div ng-bind="snippet">
          </div>
          -
          -
          - - it('should sanitize the html snippet by default', function() { - expect(using('#bind-html-with-sanitize').element('div').html()). - toBe('

          an html\nclick here\nsnippet

          '); - }); - - it('should inline raw snippet if bound to a trusted value', function() { - expect(using('#bind-html-with-trust').element("div").html()). - toBe("

          an html\n" + - "click here\n" + - "snippet

          "); - }); - - it('should escape snippet without any filter', function() { - expect(using('#bind-default').element('div').html()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); - - it('should update', function() { - input('snippet').enter('new text'); - expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new text'); - expect(using('#bind-html-with-trust').element('div').html()).toBe( - 'new text'); - expect(using('#bind-default').element('div').html()).toBe( - "new <b onclick=\"alert(1)\">text</b>"); - }); -
          -
          - */ -var $sanitize = function(html) { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf)); - return buf.join(''); -}; - - -// Regular Expressions for parsing tags and attributes -var START_TAG_REGEXP = - /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, - END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, - ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, - BEGIN_TAG_REGEXP = /^/g, - DOCTYPE_REGEXP = /]*?)>/i, - CDATA_REGEXP = //g, - URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i, - // Match everything outside of normal chars and " (quote character) - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; - - -// Good source of info about elements and attributes -// http://dev.w3.org/html5/spec/Overview.html#semantics -// http://simon.html5.org/html-elements - -// Safe Void Elements - HTML5 -// http://dev.w3.org/html5/spec/Overview.html#void-elements -var voidElements = makeMap("area,br,col,hr,img,wbr"); - -// Elements that you can, intentionally, leave open (and which close themselves) -// http://dev.w3.org/html5/spec/Overview.html#optional-tags -var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), - optionalEndTagInlineElements = makeMap("rp,rt"), - optionalEndTagElements = angular.extend({}, - optionalEndTagInlineElements, - optionalEndTagBlockElements); - -// Safe Block Elements - HTML5 -var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + - "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + - "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); - -// Inline Elements - HTML5 -var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + - "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + - "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); - - -// Special Elements (can contain anything) -var specialElements = makeMap("script,style"); - -var validElements = angular.extend({}, - voidElements, - blockElements, - inlineElements, - optionalEndTagElements); - -//Attributes that have href and hence need to be sanitized -var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); -var validAttrs = angular.extend({}, uriAttrs, makeMap( - 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ - 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ - 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ - 'scope,scrolling,shape,span,start,summary,target,title,type,'+ - 'valign,value,vspace,width')); - -function makeMap(str) { - var obj = {}, items = str.split(','), i; - for (i = 0; i < items.length; i++) obj[items[i]] = true; - return obj; -} - - -/** - * @example - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * @param {string} html string - * @param {object} handler - */ -function htmlParser( html, handler ) { - var index, chars, match, stack = [], last = html; - stack.last = function() { return stack[ stack.length - 1 ]; }; - - while ( html ) { - chars = true; - - // Make sure we're not in a script or style element - if ( !stack.last() || !specialElements[ stack.last() ] ) { - - // Comment - if ( html.indexOf("", index) === index) { - if (handler.comment) handler.comment( html.substring( 4, index ) ); - html = html.substring( index + 3 ); - chars = false; - } - // DOCTYPE - } else if ( DOCTYPE_REGEXP.test(html) ) { - match = html.match( DOCTYPE_REGEXP ); - - if ( match ) { - html = html.replace( match[0] , ''); - chars = false; - } - // end tag - } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { - match = html.match( END_TAG_REGEXP ); - - if ( match ) { - html = html.substring( match[0].length ); - match[0].replace( END_TAG_REGEXP, parseEndTag ); - chars = false; - } - - // start tag - } else if ( BEGIN_TAG_REGEXP.test(html) ) { - match = html.match( START_TAG_REGEXP ); - - if ( match ) { - html = html.substring( match[0].length ); - match[0].replace( START_TAG_REGEXP, parseStartTag ); - chars = false; - } - } - - if ( chars ) { - index = html.indexOf("<"); - - var text = index < 0 ? html : html.substring( 0, index ); - html = index < 0 ? "" : html.substring( index ); - - if (handler.chars) handler.chars( decodeEntities(text) ); - } - - } else { - html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), - function(all, text){ - text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); - - if (handler.chars) handler.chars( decodeEntities(text) ); - - return ""; - }); - - parseEndTag( "", stack.last() ); - } - - if ( html == last ) { - throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + - "of html: {0}", html); - } - last = html; - } - - // Clean up any remaining tags - parseEndTag(); - - function parseStartTag( tag, tagName, rest, unary ) { - tagName = angular.lowercase(tagName); - if ( blockElements[ tagName ] ) { - while ( stack.last() && inlineElements[ stack.last() ] ) { - parseEndTag( "", stack.last() ); - } - } - - if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { - parseEndTag( "", tagName ); - } - - unary = voidElements[ tagName ] || !!unary; - - if ( !unary ) - stack.push( tagName ); - - var attrs = {}; - - rest.replace(ATTR_REGEXP, - function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { - var value = doubleQuotedValue - || singleQuotedValue - || unquotedValue - || ''; - - attrs[name] = decodeEntities(value); - }); - if (handler.start) handler.start( tagName, attrs, unary ); - } - - function parseEndTag( tag, tagName ) { - var pos = 0, i; - tagName = angular.lowercase(tagName); - if ( tagName ) - // Find the closest opened tag of the same type - for ( pos = stack.length - 1; pos >= 0; pos-- ) - if ( stack[ pos ] == tagName ) - break; - - if ( pos >= 0 ) { - // Close all the open elements, up the stack - for ( i = stack.length - 1; i >= pos; i-- ) - if (handler.end) handler.end( stack[ i ] ); - - // Remove the open elements from the stack - stack.length = pos; - } - } -} - -/** - * decodes all entities into regular string - * @param value - * @returns {string} A string with decoded entities. - */ -var hiddenPre=document.createElement("pre"); -function decodeEntities(value) { - hiddenPre.innerHTML=value.replace(//g, '>'); -} - -/** - * create an HTML/XML writer which writes to buffer - * @param {Array} buf use buf.jain('') to get out sanitized html string - * @returns {object} in the form of { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * } - */ -function htmlSanitizeWriter(buf){ - var ignore = false; - var out = angular.bind(buf, buf.push); - return { - start: function(tag, attrs, unary){ - tag = angular.lowercase(tag); - if (!ignore && specialElements[tag]) { - ignore = tag; - } - if (!ignore && validElements[tag] === true) { - out('<'); - out(tag); - angular.forEach(attrs, function(value, key){ - var lkey=angular.lowercase(key); - if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { - out(' '); - out(key); - out('="'); - out(encodeEntities(value)); - out('"'); - } - }); - out(unary ? '/>' : '>'); - } - }, - end: function(tag){ - tag = angular.lowercase(tag); - if (!ignore && validElements[tag] === true) { - out(''); - } - if (tag == ignore) { - ignore = false; - } - }, - chars: function(chars){ - if (!ignore) { - out(encodeEntities(chars)); - } - } - }; -} - - -// define ngSanitize module and register $sanitize service -angular.module('ngSanitize', []).value('$sanitize', $sanitize); - -/* global htmlSanitizeWriter: false */ - -/** - * @ngdoc filter - * @name ngSanitize.filter:linky - * @function - * - * @description - * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and - * plain email address links. - * - * Requires the {@link ngSanitize `ngSanitize`} module to be installed. - * - * @param {string} text Input text. - * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. - * @returns {string} Html-linkified text. - * - * @usage - - * - * @example - - - -
          - Snippet: - - - - - - - - - - - - - - - - - - - - - -
          FilterSourceRendered
          linky filter -
          <div ng-bind-html="snippet | linky">
          </div>
          -
          -
          -
          linky target -
          <div ng-bind-html="snippetWithTarget | linky:'_blank'">
          </div>
          -
          -
          -
          no filter
          <div ng-bind="snippet">
          </div>
          - - - it('should linkify the snippet with urls', function() { - expect(using('#linky-filter').binding('snippet | linky')). - toBe('Pretty text with some links: ' + - 'http://angularjs.org/, ' + - 'us@somewhere.org, ' + - 'another@somewhere.org, ' + - 'and one more: ftp://127.0.0.1/.'); - }); - - it ('should not linkify snippet without the linky filter', function() { - expect(using('#escaped-html').binding('snippet')). - toBe("Pretty text with some links:\n" + - "http://angularjs.org/,\n" + - "mailto:us@somewhere.org,\n" + - "another@somewhere.org,\n" + - "and one more: ftp://127.0.0.1/."); - }); - - it('should update', function() { - input('snippet').enter('new http://link.'); - expect(using('#linky-filter').binding('snippet | linky')). - toBe('new http://link.'); - expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); - }); - - it('should work with the target property', function() { - expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")). - toBe('http://angularjs.org/'); - }); - - - */ -angular.module('ngSanitize').filter('linky', function() { - var LINKY_URL_REGEXP = - /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, - MAILTO_REGEXP = /^mailto:/; - - return function(text, target) { - if (!text) return text; - var match; - var raw = text; - var html = []; - // TODO(vojta): use $sanitize instead - var writer = htmlSanitizeWriter(html); - var url; - var i; - var properties = {}; - if (angular.isDefined(target)) { - properties.target = target; - } - while ((match = raw.match(LINKY_URL_REGEXP))) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/mailto then assume mailto - if (match[2] == match[3]) url = 'mailto:' + url; - i = match.index; - writer.chars(raw.substr(0, i)); - properties.href = url; - writer.start('a', properties); - writer.chars(match[0].replace(MAILTO_REGEXP, '')); - writer.end('a'); - raw = raw.substring(i + match[0].length); - } - writer.chars(raw); - return html.join(''); - }; -}); - - -})(window, window.angular); - -/** - * @license AngularJS v1.2.1 - * (c) 2010-2012 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; - -/** - * @ngdoc overview - * @name ngRoute - * @description - * - * # ngRoute - * - * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. - * - * {@installModule route} - * - *
          - */ - /* global -ngRouteModule */ -var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider); - -/** - * @ngdoc object - * @name ngRoute.$routeProvider - * @function - * - * @description - * - * Used for configuring routes. See {@link ngRoute.$route $route} for an example. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - */ -function $RouteProvider(){ - function inherit(parent, extra) { - return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); - } - - var routes = {}; - - /** - * @ngdoc method - * @name ngRoute.$routeProvider#when - * @methodOf ngRoute.$routeProvider - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * * `path` can contain named groups starting with a colon (`:name`). All characters up - * to the next slash are matched and stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain named groups starting with a colon and ending with a star (`:name*`). - * All characters are eagerly stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain optional named groups with a question mark (`:name?`). - * - * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match - * `/color/brown/largecode/code/with/slashs/edit` and extract: - * - * * `color: brown` - * * `largecode: code/with/slashs`. - * - * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{(string|function()=}` – Controller fn that should be associated with - * newly created scope or the name of a {@link angular.Module#controller registered - * controller} if passed as a string. - * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be - * published to scope under the `controllerAs` name. - * - `template` – `{string=|function()=}` – html template as a string or a function that - * returns an html template as a string which should be used by {@link - * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. - * This property takes precedence over `templateUrl`. - * - * If `template` is a function, it will be called with the following parameters: - * - * - `{Array.}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html - * template that should be used by {@link ngRoute.directive:ngView ngView}. - * - * If `templateUrl` is a function, it will be called with the following parameters: - * - * - `{Array.}` - route parameters extracted from the current - * `$location.path()` by applying the current route - * - * - `resolve` - `{Object.=}` - An optional map of dependencies which should - * be injected into the controller. If any of these dependencies are promises, the router - * will wait for them all to be resolved or one to be rejected before the controller is - * instantiated. - * If all the promises are resolved successfully, the values of the resolved promises are - * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is - * fired. If any of the promises are rejected the - * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object - * is: - * - * - `key` – `{string}`: a name of a dependency to be injected into the controller. - * - `factory` - `{string|function}`: If `string` then it is an alias for a service. - * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} - * and the return value is treated as the dependency. If the result is a promise, it is - * resolved before its value is injected into the controller. Be aware that - * `ngRoute.$routeParams` will still refer to the previous route within these resolve - * functions. Use `$route.current.params` to access the new route parameters, instead. - * - * - `redirectTo` – {(string|function())=} – value to update - * {@link ng.$location $location} path with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.}` - route parameters extracted from the current - * `$location.path()` by applying the current route templateUrl. - * - `{string}` - current `$location.path()` - * - `{Object}` - current `$location.search()` - * - * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.path()` and `$location.search()`. - * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` - * or `$location.hash()` changes. - * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. - * - * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive - * - * If the option is set to `true`, then the particular route can be matched without being - * case sensitive - * - * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. - */ - this.when = function(path, route) { - routes[path] = angular.extend( - {reloadOnSearch: true}, - route, - path && pathRegExp(path, route) - ); - - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length-1] == '/') - ? path.substr(0, path.length-1) - : path +'/'; - - routes[redirectPath] = angular.extend( - {redirectTo: path}, - pathRegExp(redirectPath, route) - ); - } - - return this; - }; - - /** - * @param path {string} path - * @param opts {Object} options - * @return {?Object} - * - * @description - * Normalizes the given path, returning a regular expression - * and the original path. - * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. - */ - function pathRegExp(path, opts) { - var insensitive = opts.caseInsensitiveMatch, - ret = { - originalPath: path, - regexp: path - }, - keys = ret.keys = []; - - path = path - .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; - keys.push({ name: key, optional: !!optional }); - slash = slash || ''; - return '' - + (optional ? '' : slash) - + '(?:' - + (optional ? slash : '') - + (star && '(.+?)' || '([^/]+)') - + (optional || '') - + ')' - + (optional || ''); - }) - .replace(/([\/$\*])/g, '\\$1'); - - ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); - return ret; - } - - /** - * @ngdoc method - * @name ngRoute.$routeProvider#otherwise - * @methodOf ngRoute.$routeProvider - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object} params Mapping information to be assigned to `$route.current`. - * @returns {Object} self - */ - this.otherwise = function(params) { - this.when(null, params); - return this; - }; - - - this.$get = ['$rootScope', - '$location', - '$routeParams', - '$q', - '$injector', - '$http', - '$templateCache', - '$sce', - function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { - - /** - * @ngdoc object - * @name ngRoute.$route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Array.} routes Array of all configured routes. - * - * @description - * `$route` is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with the - * {@link ngRoute.directive:ngView `ngView`} directive and the - * {@link ngRoute.$routeParams `$routeParams`} service. - * - * @example - This example shows how changing the URL hash causes the `$route` to match a route against the - URL, and the `ngView` pulls in the partial. - - Note that this example is using {@link ng.directive:script inlined templates} - to get it working on jsfiddle as well. - - - -
          - Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
          - -
          -
          - -
          $location.path() = {{$location.path()}}
          -
          $route.current.templateUrl = {{$route.current.templateUrl}}
          -
          $route.current.params = {{$route.current.params}}
          -
          $route.current.scope.name = {{$route.current.scope.name}}
          -
          $routeParams = {{$routeParams}}
          -
          -
          - - - controller: {{name}}
          - Book Id: {{params.bookId}}
          -
          - - - controller: {{name}}
          - Book Id: {{params.bookId}}
          - Chapter Id: {{params.chapterId}} -
          - - - angular.module('ngViewExample', ['ngRoute']) - - .config(function($routeProvider, $locationProvider) { - $routeProvider.when('/Book/:bookId', { - templateUrl: 'book.html', - controller: BookCntl, - resolve: { - // I will cause a 1 second delay - delay: function($q, $timeout) { - var delay = $q.defer(); - $timeout(delay.resolve, 1000); - return delay.promise; - } - } - }); - $routeProvider.when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: ChapterCntl - }); - - // configure html5 to get links working on jsfiddle - $locationProvider.html5Mode(true); - }); - - function MainCntl($scope, $route, $routeParams, $location) { - $scope.$route = $route; - $scope.$location = $location; - $scope.$routeParams = $routeParams; - } - - function BookCntl($scope, $routeParams) { - $scope.name = "BookCntl"; - $scope.params = $routeParams; - } - - function ChapterCntl($scope, $routeParams) { - $scope.name = "ChapterCntl"; - $scope.params = $routeParams; - } - - - - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - sleep(2); // promises are not part of scenario waiting - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
          - */ - - /** - * @ngdoc event - * @name ngRoute.$route#$routeChangeStart - * @eventOf ngRoute.$route - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occurs. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ - - /** - * @ngdoc event - * @name ngRoute.$route#$routeChangeSuccess - * @eventOf ngRoute.$route - * @eventType broadcast on root scope - * @description - * Broadcasted after a route dependencies are resolved. - * {@link ngRoute.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is - * first route entered. - */ - - /** - * @ngdoc event - * @name ngRoute.$route#$routeChangeError - * @eventOf ngRoute.$route - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ - - /** - * @ngdoc event - * @name ngRoute.$route#$routeUpdate - * @eventOf ngRoute.$route - * @eventType broadcast on root scope - * @description - * - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - */ - - var forceReload = false, - $route = { - routes: routes, - - /** - * @ngdoc method - * @name ngRoute.$route#reload - * @methodOf ngRoute.$route - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ngRoute.directive:ngView ngView} - * creates new scope, reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(updateRoute); - } - }; - - $rootScope.$on('$locationChangeSuccess', updateRoute); - - return $route; - - ///////////////////////////////////////////////////// - - /** - * @param on {string} current url - * @param route {Object} route regexp to match the url against - * @return {?Object} - * - * @description - * Check if the route matches the current url. - * - * Inspired by match in - * visionmedia/express/lib/router/router.js. - */ - function switchRouteMatcher(on, route) { - var keys = route.keys, - params = {}; - - if (!route.regexp) return null; - - var m = route.regexp.exec(on); - if (!m) return null; - - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; - - var val = 'string' == typeof m[i] - ? decodeURIComponent(m[i]) - : m[i]; - - if (key && val) { - params[key.name] = val; - } - } - return params; - } - - function updateRoute() { - var next = parseRoute(), - last = $route.current; - - if (next && last && next.$$route === last.$$route - && angular.equals(next.pathParams, last.pathParams) - && !next.reloadOnSearch && !forceReload) { - last.params = next.params; - angular.copy(last.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', last); - } else if (next || last) { - forceReload = false; - $rootScope.$broadcast('$routeChangeStart', next, last); - $route.current = next; - if (next) { - if (next.redirectTo) { - if (angular.isString(next.redirectTo)) { - $location.path(interpolate(next.redirectTo, next.params)).search(next.params) - .replace(); - } else { - $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } - - $q.when(next). - then(function() { - if (next) { - var locals = angular.extend({}, next.resolve), - template, templateUrl; - - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : $injector.invoke(value); - }); - - if (angular.isDefined(template = next.template)) { - if (angular.isFunction(template)) { - template = template(next.params); - } - } else if (angular.isDefined(templateUrl = next.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(next.params); - } - templateUrl = $sce.getTrustedResourceUrl(templateUrl); - if (angular.isDefined(templateUrl)) { - next.loadedTemplateUrl = templateUrl; - template = $http.get(templateUrl, {cache: $templateCache}). - then(function(response) { return response.data; }); - } - } - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - }). - // after route change - then(function(locals) { - if (next == $route.current) { - if (next) { - next.locals = locals; - angular.copy(next.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', next, last); - } - }, function(error) { - if (next == $route.current) { - $rootScope.$broadcast('$routeChangeError', next, last, error); - } - }); - } - } - - - /** - * @returns the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - angular.forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), route))) { - match = inherit(route, { - params: angular.extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } - - /** - * @returns interpolation of the redirect path with the parameters - */ - function interpolate(string, params) { - var result = []; - angular.forEach((string||'').split(':'), function(segment, i) { - if (i === 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; -} - -ngRouteModule.provider('$routeParams', $RouteParamsProvider); - - -/** - * @ngdoc object - * @name ngRoute.$routeParams - * @requires $route - * - * @description - * The `$routeParams` service allows you to retrieve the current set of route parameters. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * The route parameters are a combination of {@link ng.$location `$location`}'s - * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. - * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * Note that the `$routeParams` are only updated *after* a route change completes successfully. - * This means that you cannot rely on `$routeParams` being correct in route resolve functions. - * Instead you can use `$route.current.params` to access the new route's parameters. - * - * @example - *
          - *  // Given:
          - *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
          - *  // Route: /Chapter/:chapterId/Section/:sectionId
          - *  //
          - *  // Then
          - *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
          - * 
          - */ -function $RouteParamsProvider() { - this.$get = function() { return {}; }; -} - -ngRouteModule.directive('ngView', ngViewFactory); - -/** - * @ngdoc directive - * @name ngRoute.directive:ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * @example - - -
          - Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
          - -
          -
          -
          -
          - -
          $location.path() = {{main.$location.path()}}
          -
          $route.current.templateUrl = {{main.$route.current.templateUrl}}
          -
          $route.current.params = {{main.$route.current.params}}
          -
          $route.current.scope.name = {{main.$route.current.scope.name}}
          -
          $routeParams = {{main.$routeParams}}
          -
          -
          - - -
          - controller: {{book.name}}
          - Book Id: {{book.params.bookId}}
          -
          -
          - - -
          - controller: {{chapter.name}}
          - Book Id: {{chapter.params.bookId}}
          - Chapter Id: {{chapter.params.chapterId}} -
          -
          - - - .view-animate-container { - position:relative; - height:100px!important; - position:relative; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } - - .view-animate { - padding:10px; - } - - .view-animate.ng-enter, .view-animate.ng-leave { - -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; - - display:block; - width:100%; - border-left:1px solid black; - - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - padding:10px; - } - - .view-animate.ng-enter { - left:100%; - } - .view-animate.ng-enter.ng-enter-active { - left:0; - } - .view-animate.ng-leave.ng-leave-active { - left:-100%; - } - - - - angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], - function($routeProvider, $locationProvider) { - $routeProvider.when('/Book/:bookId', { - templateUrl: 'book.html', - controller: BookCntl, - controllerAs: 'book' - }); - $routeProvider.when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: ChapterCntl, - controllerAs: 'chapter' - }); - - // configure html5 to get links working on jsfiddle - $locationProvider.html5Mode(true); - }); - - function MainCntl($route, $routeParams, $location) { - this.$route = $route; - this.$location = $location; - this.$routeParams = $routeParams; - } - - function BookCntl($routeParams) { - this.name = "BookCntl"; - this.params = $routeParams; - } - - function ChapterCntl($routeParams) { - this.name = "ChapterCntl"; - this.params = $routeParams; - } - - - - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
          - */ - - -/** - * @ngdoc event - * @name ngRoute.directive:ngView#$viewContentLoaded - * @eventOf ngRoute.directive:ngView - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ -ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; -function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { - return { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - link: function(scope, $element, attr, ctrl, $transclude) { - var currentScope, - currentElement, - autoScrollExp = attr.autoscroll, - onloadExp = attr.onload || ''; - - scope.$on('$routeChangeSuccess', update); - update(); - - function cleanupLastView() { - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if(currentElement) { - $animate.leave(currentElement); - currentElement = null; - } - } - - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; - - if (template) { - var newScope = scope.$new(); - $transclude(newScope, function(clone) { - clone.html(template); - $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { - if (angular.isDefined(autoScrollExp) - && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }); - - cleanupLastView(); - - var link = $compile(clone.contents()), - current = $route.current; - - currentScope = current.scope = newScope; - currentElement = clone; - - if (current.controller) { - locals.$scope = currentScope; - var controller = $controller(current.controller, locals); - if (current.controllerAs) { - currentScope[current.controllerAs] = controller; - } - clone.data('$ngControllerController', controller); - clone.children().data('$ngControllerController', controller); - } - - link(currentScope); - currentScope.$emit('$viewContentLoaded'); - currentScope.$eval(onloadExp); - }); - } else { - cleanupLastView(); - } - } - } - }; -} - - -})(window, window.angular); - -/** - * State-based routing for AngularJS - * @version v0.2.8 - * @link http://angular-ui.github.com/ - * @license MIT License, http://www.opensource.org/licenses/MIT - */ - -/* commonjs package manager support (eg componentjs) */ -if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ - module.exports = 'ui.router'; -} - -(function (window, angular, undefined) { -/*jshint globalstrict:true*/ -/*global angular:false*/ -'use strict'; - -var isDefined = angular.isDefined, - isFunction = angular.isFunction, - isString = angular.isString, - isObject = angular.isObject, - isArray = angular.isArray, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy; - -function inherit(parent, extra) { - return extend(new (extend(function() {}, { prototype: parent }))(), extra); -} - -function merge(dst) { - forEach(arguments, function(obj) { - if (obj !== dst) { - forEach(obj, function(value, key) { - if (!dst.hasOwnProperty(key)) dst[key] = value; - }); - } - }); - return dst; -} - -/** - * Finds the common ancestor path between two states. - * - * @param {Object} first The first state. - * @param {Object} second The second state. - * @return {Array} Returns an array of state names in descending order, not including the root. - */ -function ancestors(first, second) { - var path = []; - - for (var n in first.path) { - if (first.path[n] !== second.path[n]) break; - path.push(first.path[n]); - } - return path; -} - -/** - * IE8-safe wrapper for `Object.keys()`. - * - * @param {Object} object A JavaScript object. - * @return {Array} Returns the keys of the object as an array. - */ -function keys(object) { - if (Object.keys) { - return Object.keys(object); - } - var result = []; - - angular.forEach(object, function(val, key) { - result.push(key); - }); - return result; -} - -/** - * IE8-safe wrapper for `Array.prototype.indexOf()`. - * - * @param {Array} array A JavaScript array. - * @param {*} value A value to search the array for. - * @return {Number} Returns the array index value of `value`, or `-1` if not present. - */ -function arraySearch(array, value) { - if (Array.prototype.indexOf) { - return array.indexOf(value, Number(arguments[2]) || 0); - } - var len = array.length >>> 0, from = Number(arguments[2]) || 0; - from = (from < 0) ? Math.ceil(from) : Math.floor(from); - - if (from < 0) from += len; - - for (; from < len; from++) { - if (from in array && array[from] === value) return from; - } - return -1; -} - -/** - * Merges a set of parameters with all parameters inherited between the common parents of the - * current state and a given destination state. - * - * @param {Object} currentParams The value of the current state parameters ($stateParams). - * @param {Object} newParams The set of parameters which will be composited with inherited params. - * @param {Object} $current Internal definition of object representing the current state. - * @param {Object} $to Internal definition of object representing state to transition to. - */ -function inheritParams(currentParams, newParams, $current, $to) { - var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = []; - - for (var i in parents) { - if (!parents[i].params || !parents[i].params.length) continue; - parentParams = parents[i].params; - - for (var j in parentParams) { - if (arraySearch(inheritList, parentParams[j]) >= 0) continue; - inheritList.push(parentParams[j]); - inherited[parentParams[j]] = currentParams[parentParams[j]]; - } - } - return extend({}, inherited, newParams); -} - -/** - * Normalizes a set of values to string or `null`, filtering them by a list of keys. - * - * @param {Array} keys The list of keys to normalize/return. - * @param {Object} values An object hash of values to normalize. - * @return {Object} Returns an object hash of normalized string values. - */ -function normalize(keys, values) { - var normalized = {}; - - forEach(keys, function (name) { - var value = values[name]; - normalized[name] = (value != null) ? String(value) : null; - }); - return normalized; -} - -/** - * Performs a non-strict comparison of the subset of two objects, defined by a list of keys. - * - * @param {Object} a The first object. - * @param {Object} b The second object. - * @param {Array} keys The list of keys within each object to compare. If the list is empty or not specified, - * it defaults to the list of keys in `a`. - * @return {Boolean} Returns `true` if the keys match, otherwise `false`. - */ -function equalForKeys(a, b, keys) { - if (!keys) { - keys = []; - for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility - } - - for (var i=0; i - * $resolve.study(invocables)(locals, parent, self) - * - * is equivalent to - *
          -   * $resolve.resolve(invocables, locals, parent, self)
          -   * 
          - * but the former is more efficient (in fact `resolve` just calls `study` - * internally). - * - * @param {object} invocables Invocable objects - * @return {function} a function to pass in locals, parent and self - */ - this.study = function (invocables) { - if (!isObject(invocables)) throw new Error("'invocables' must be an object"); - - // Perform a topological sort of invocables to build an ordered plan - var plan = [], cycle = [], visited = {}; - function visit(value, key) { - if (visited[key] === VISIT_DONE) return; - - cycle.push(key); - if (visited[key] === VISIT_IN_PROGRESS) { - cycle.splice(0, cycle.indexOf(key)); - throw new Error("Cyclic dependency: " + cycle.join(" -> ")); - } - visited[key] = VISIT_IN_PROGRESS; - - if (isString(value)) { - plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES); - } else { - var params = $injector.annotate(value); - forEach(params, function (param) { - if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param); - }); - plan.push(key, value, params); - } - - cycle.pop(); - visited[key] = VISIT_DONE; - } - forEach(invocables, visit); - invocables = cycle = visited = null; // plan is all that's required - - function isResolve(value) { - return isObject(value) && value.then && value.$$promises; - } - - return function (locals, parent, self) { - if (isResolve(locals) && self === undefined) { - self = parent; parent = locals; locals = null; - } - if (!locals) locals = NO_LOCALS; - else if (!isObject(locals)) { - throw new Error("'locals' must be an object"); - } - if (!parent) parent = NO_PARENT; - else if (!isResolve(parent)) { - throw new Error("'parent' must be a promise returned by $resolve.resolve()"); - } - - // To complete the overall resolution, we have to wait for the parent - // promise and for the promise for each invokable in our plan. - var resolution = $q.defer(), - result = resolution.promise, - promises = result.$$promises = {}, - values = extend({}, locals), - wait = 1 + plan.length/3, - merged = false; - - function done() { - // Merge parent values we haven't got yet and publish our own $$values - if (!--wait) { - if (!merged) merge(values, parent.$$values); - result.$$values = values; - result.$$promises = true; // keep for isResolve() - resolution.resolve(values); - } - } - - function fail(reason) { - result.$$failure = reason; - resolution.reject(reason); - } - - // Short-circuit if parent has already failed - if (isDefined(parent.$$failure)) { - fail(parent.$$failure); - return result; - } - - // Merge parent values if the parent has already resolved, or merge - // parent promises and wait if the parent resolve is still in progress. - if (parent.$$values) { - merged = merge(values, parent.$$values); - done(); - } else { - extend(promises, parent.$$promises); - parent.then(done, fail); - } - - // Process each invocable in the plan, but ignore any where a local of the same name exists. - for (var i=0, ii=plan.length; i} The template html as a string, or a promise - * for that string. - */ - this.fromUrl = function (url, params) { - if (isFunction(url)) url = url(params); - if (url == null) return null; - else return $http - .get(url, { cache: $templateCache }) - .then(function(response) { return response.data; }); - }; - - /** - * @ngdoc function - * @name ui.router.util.$templateFactory#fromUrl - * @methodOf ui.router.util.$templateFactory - * - * @description - * Creates a template by invoking an injectable provider function. - * - * @param {Function} provider Function to invoke via `$injector.invoke` - * @param {Object} params Parameters for the template. - * @param {Object} locals Locals to pass to `invoke`. Defaults to - * `{ params: params }`. - * @return {string|Promise.} The template html as a string, or a promise - * for that string. - */ - this.fromProvider = function (provider, params, locals) { - return $injector.invoke(provider, null, locals || { params: params }); - }; -} - -angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); - -/** - * Matches URLs against patterns and extracts named parameters from the path or the search - * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list - * of search parameters. Multiple search parameter names are separated by '&'. Search parameters - * do not influence whether or not a URL is matched, but their values are passed through into - * the matched parameters returned by {@link UrlMatcher#exec exec}. - * - * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace - * syntax, which optionally allows a regular expression for the parameter to be specified: - * - * * ':' name - colon placeholder - * * '*' name - catch-all placeholder - * * '{' name '}' - curly placeholder - * * '{' name ':' regexp '}' - curly placeholder with regexp. Should the regexp itself contain - * curly braces, they must be in matched pairs or escaped with a backslash. - * - * Parameter names may contain only word characters (latin letters, digits, and underscore) and - * must be unique within the pattern (across both path and search parameters). For colon - * placeholders or curly placeholders without an explicit regexp, a path parameter matches any - * number of characters other than '/'. For catch-all placeholders the path parameter matches - * any number of characters. - * - * ### Examples - * - * * '/hello/' - Matches only if the path is exactly '/hello/'. There is no special treatment for - * trailing slashes, and patterns have to match the entire path, not just a prefix. - * * '/user/:id' - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or - * '/user/bob/details'. The second path segment will be captured as the parameter 'id'. - * * '/user/{id}' - Same as the previous example, but using curly brace syntax. - * * '/user/{id:[^/]*}' - Same as the previous example. - * * '/user/{id:[0-9a-fA-F]{1,8}}' - Similar to the previous example, but only matches if the id - * parameter consists of 1 to 8 hex digits. - * * '/files/{path:.*}' - Matches any URL starting with '/files/' and captures the rest of the - * path into the parameter 'path'. - * * '/files/*path' - ditto. - * - * @constructor - * @param {string} pattern the pattern to compile into a matcher. - * - * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any - * URL matching this matcher (i.e. any string for which {@link UrlMatcher#exec exec()} returns - * non-null) will start with this prefix. - */ -function UrlMatcher(pattern) { - - // Find all placeholders and create a compiled pattern, using either classic or curly syntax: - // '*' name - // ':' name - // '{' name '}' - // '{' name ':' regexp '}' - // The regular expression is somewhat complicated due to the need to allow curly braces - // inside the regular expression. The placeholder regexp breaks down as follows: - // ([:*])(\w+) classic placeholder ($1 / $2) - // \{(\w+)(?:\:( ... ))?\} curly brace placeholder ($3) with optional regexp ... ($4) - // (?: ... | ... | ... )+ the regexp consists of any number of atoms, an atom being either - // [^{}\\]+ - anything other than curly braces or backslash - // \\. - a backslash escape - // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms - var placeholder = /([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g, - names = {}, compiled = '^', last = 0, m, - segments = this.segments = [], - params = this.params = []; - - function addParameter(id) { - if (!/^\w+(-+\w+)*$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'"); - if (names[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'"); - names[id] = true; - params.push(id); - } - - function quoteRegExp(string) { - return string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&"); - } - - this.source = pattern; - - // Split into static segments separated by path parameter placeholders. - // The number of segments is always 1 more than the number of parameters. - var id, regexp, segment; - while ((m = placeholder.exec(pattern))) { - id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null - regexp = m[4] || (m[1] == '*' ? '.*' : '[^/]*'); - segment = pattern.substring(last, m.index); - if (segment.indexOf('?') >= 0) break; // we're into the search part - compiled += quoteRegExp(segment) + '(' + regexp + ')'; - addParameter(id); - segments.push(segment); - last = placeholder.lastIndex; - } - segment = pattern.substring(last); - - // Find any search parameter names and remove them from the last segment - var i = segment.indexOf('?'); - if (i >= 0) { - var search = this.sourceSearch = segment.substring(i); - segment = segment.substring(0, i); - this.sourcePath = pattern.substring(0, last+i); - - // Allow parameters to be separated by '?' as well as '&' to make concat() easier - forEach(search.substring(1).split(/[&?]/), addParameter); - } else { - this.sourcePath = pattern; - this.sourceSearch = ''; - } - - compiled += quoteRegExp(segment) + '$'; - segments.push(segment); - this.regexp = new RegExp(compiled); - this.prefix = segments[0]; -} - -/** - * Returns a new matcher for a pattern constructed by appending the path part and adding the - * search parameters of the specified pattern to this pattern. The current pattern is not - * modified. This can be understood as creating a pattern for URLs that are relative to (or - * suffixes of) the current pattern. - * - * ### Example - * The following two matchers are equivalent: - * ``` - * new UrlMatcher('/user/{id}?q').concat('/details?date'); - * new UrlMatcher('/user/{id}/details?q&date'); - * ``` - * - * @param {string} pattern The pattern to append. - * @return {UrlMatcher} A matcher for the concatenated pattern. - */ -UrlMatcher.prototype.concat = function (pattern) { - // Because order of search parameters is irrelevant, we can add our own search - // parameters to the end of the new pattern. Parse the new pattern by itself - // and then join the bits together, but it's much easier to do this on a string level. - return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch); -}; - -UrlMatcher.prototype.toString = function () { - return this.source; -}; - -/** - * Tests the specified path against this matcher, and returns an object containing the captured - * parameter values, or null if the path does not match. The returned object contains the values - * of any search parameters that are mentioned in the pattern, but their value may be null if - * they are not present in `searchParams`. This means that search parameters are always treated - * as optional. - * - * ### Example - * ``` - * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', { x:'1', q:'hello' }); - * // returns { id:'bob', q:'hello', r:null } - * ``` - * - * @param {string} path The URL path to match, e.g. `$location.path()`. - * @param {Object} searchParams URL search parameters, e.g. `$location.search()`. - * @return {Object} The captured parameter values. - */ -UrlMatcher.prototype.exec = function (path, searchParams) { - var m = this.regexp.exec(path); - if (!m) return null; - - var params = this.params, nTotal = params.length, - nPath = this.segments.length-1, - values = {}, i; - - if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'"); - - for (i=0; i} An array of parameter names. Must be treated as read-only. If the - * pattern has no parameters, an empty array is returned. - */ -UrlMatcher.prototype.parameters = function () { - return this.params; -}; - -/** - * Creates a URL that matches this pattern by substituting the specified values - * for the path and search parameters. Null values for path parameters are - * treated as empty strings. - * - * ### Example - * ``` - * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' }); - * // returns '/user/bob?q=yes' - * ``` - * - * @param {Object} values the values to substitute for the parameters in this pattern. - * @return {string} the formatted URL (path and optionally search part). - */ -UrlMatcher.prototype.format = function (values) { - var segments = this.segments, params = this.params; - if (!values) return segments.join(''); - - var nPath = segments.length-1, nTotal = params.length, - result = segments[0], i, search, value; - - for (i=0; i - * var app = angular.module('app', ['ui.router.router']); - * - * app.config(function ($urlRouterProvider) { - * // Here's an example of how you might allow case insensitive urls - * $urlRouterProvider.rule(function ($injector, $location) { - * var path = $location.path(), - * normalized = path.toLowerCase(); - * - * if (path !== normalized) { - * return normalized; - * } - * }); - * }); - * - * - * @param {object} rule Handler function that takes `$injector` and `$location` - * services as arguments. You can use them to return a valid path as a string. - * - * @return {object} $urlRouterProvider - $urlRouterProvider instance - */ - this.rule = - function (rule) { - if (!isFunction(rule)) throw new Error("'rule' must be a function"); - rules.push(rule); - return this; - }; - - /** - * @ngdoc object - * @name ui.router.router.$urlRouterProvider#otherwise - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Defines a path that is used when an invalied route is requested. - * - * @example - *
          -   * var app = angular.module('app', ['ui.router.router']);
          -   *
          -   * app.config(function ($urlRouterProvider) {
          -   *   // if the path doesn't match any of the urls you configured
          -   *   // otherwise will take care of routing the user to the
          -   *   // specified url
          -   *   $urlRouterProvider.otherwise('/index');
          -   *
          -   *   // Example of using function rule as param
          -   *   $urlRouterProvider.otherwise(function ($injector, $location) {
          -   *     ...
          -   *   });
          -   * });
          -   * 
          - * - * @param {string|object} rule The url path you want to redirect to or a function - * rule that returns the url path. The function version is passed two params: - * `$injector` and `$location` services. - * - * @return {object} $urlRouterProvider - $urlRouterProvider instance - */ - this.otherwise = - function (rule) { - if (isString(rule)) { - var redirect = rule; - rule = function () { return redirect; }; - } - else if (!isFunction(rule)) throw new Error("'rule' must be a function"); - otherwise = rule; - return this; - }; - - - function handleIfMatch($injector, handler, match) { - if (!match) return false; - var result = $injector.invoke(handler, handler, { $match: match }); - return isDefined(result) ? result : true; - } - - /** - * @ngdoc function - * @name ui.router.router.$urlRouterProvider#when - * @methodOf ui.router.router.$urlRouterProvider - * - * @description - * Registers a handler for a given url matching. if handle is a string, it is - * treated as a redirect, and is interpolated according to the syyntax of match - * (i.e. like String.replace() for RegExp, or like a UrlMatcher pattern otherwise). - * - * If the handler is a function, it is injectable. It gets invoked if `$location` - * matches. You have the option of inject the match object as `$match`. - * - * The handler can return - * - * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter` - * will continue trying to find another one that matches. - * - **string** which is treated as a redirect and passed to `$location.url()` - * - **void** or any **truthy** value tells `$urlRouter` that the url was handled. - * - * @example - *
          -   * var app = angular.module('app', ['ui.router.router']);
          -   *
          -   * app.config(function ($urlRouterProvider) {
          -   *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
          -   *     if ($state.$current.navigable !== state ||
          -   *         !equalForKeys($match, $stateParams) {
          -   *      $state.transitionTo(state, $match, false);
          -   *     }
          -   *   });
          -   * });
          -   * 
          - * - * @param {string|object} what The incoming path that you want to redirect. - * @param {string|object} handler The path you want to redirect your user to. - */ - this.when = - function (what, handler) { - var redirect, handlerIsString = isString(handler); - if (isString(what)) what = $urlMatcherFactory.compile(what); - - if (!handlerIsString && !isFunction(handler) && !isArray(handler)) - throw new Error("invalid 'handler' in when()"); - - var strategies = { - matcher: function (what, handler) { - if (handlerIsString) { - redirect = $urlMatcherFactory.compile(handler); - handler = ['$match', function ($match) { return redirect.format($match); }]; - } - return extend(function ($injector, $location) { - return handleIfMatch($injector, handler, what.exec($location.path(), $location.search())); - }, { - prefix: isString(what.prefix) ? what.prefix : '' - }); - }, - regex: function (what, handler) { - if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky"); - - if (handlerIsString) { - redirect = handler; - handler = ['$match', function ($match) { return interpolate(redirect, $match); }]; - } - return extend(function ($injector, $location) { - return handleIfMatch($injector, handler, what.exec($location.path())); - }, { - prefix: regExpPrefix(what) - }); - } - }; - - var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp }; - - for (var n in check) { - if (check[n]) { - return this.rule(strategies[n](what, handler)); - } - } - - throw new Error("invalid 'what' in when()"); - }; - - /** - * @ngdoc object - * @name ui.router.router.$urlRouter - * - * @requires $location - * @requires $rootScope - * @requires $injector - * - * @description - * - */ - this.$get = - [ '$location', '$rootScope', '$injector', - function ($location, $rootScope, $injector) { - // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree - function update(evt) { - if (evt && evt.defaultPrevented) return; - function check(rule) { - var handled = rule($injector, $location); - if (handled) { - if (isString(handled)) $location.replace().url(handled); - return true; - } - return false; - } - var n=rules.length, i; - for (i=0; i - * angular.module('app', ['ui.router']); - * .run(function($rootScope, $urlRouter) { - * $rootScope.$on('$locationChangeSuccess', function(evt) { - * // Halt state change from even starting - * evt.preventDefault(); - * // Perform custom logic - * var meetsRequirement = ... - * // Continue with the update and state transition if logic allows - * if (meetsRequirement) $urlRouter.sync(); - * }); - * }); - * - */ - sync: function () { - update(); - } - }; - }]; -} - -angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider); - -/** - * @ngdoc object - * @name ui.router.state.$stateProvider - * - * @requires ui.router.router.$urlRouterProvider - * @requires ui.router.util.$urlMatcherFactoryProvider - * @requires $locationProvider - * - * @description - * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely - * on state. - * - * A state corresponds to a "place" in the application in terms of the overall UI and - * navigation. A state describes (via the controller / template / view properties) what - * the UI looks like and does at that place. - * - * States often have things in common, and the primary way of factoring out these - * commonalities in this model is via the state hierarchy, i.e. parent/child states aka - * nested states. - * - * The `$stateProvider` provides interfaces to declare these states for your app. - */ -$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider', '$locationProvider']; -function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $locationProvider) { - - var root, states = {}, $state, queue = {}, abstractKey = 'abstract'; - - // Builds state properties from definition passed to registerState() - var stateBuilder = { - - // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined. - // state.children = []; - // if (parent) parent.children.push(state); - parent: function(state) { - if (isDefined(state.parent) && state.parent) return findState(state.parent); - // regex matches any valid composite state name - // would match "contact.list" but not "contacts" - var compositeName = /^(.+)\.[^.]+$/.exec(state.name); - return compositeName ? findState(compositeName[1]) : root; - }, - - // inherit 'data' from parent and override by own values (if any) - data: function(state) { - if (state.parent && state.parent.data) { - state.data = state.self.data = extend({}, state.parent.data, state.data); - } - return state.data; - }, - - // Build a URLMatcher if necessary, either via a relative or absolute URL - url: function(state) { - var url = state.url; - - if (isString(url)) { - if (url.charAt(0) == '^') { - return $urlMatcherFactory.compile(url.substring(1)); - } - return (state.parent.navigable || root).url.concat(url); - } - - if ($urlMatcherFactory.isMatcher(url) || url == null) { - return url; - } - throw new Error("Invalid url '" + url + "' in state '" + state + "'"); - }, - - // Keep track of the closest ancestor state that has a URL (i.e. is navigable) - navigable: function(state) { - return state.url ? state : (state.parent ? state.parent.navigable : null); - }, - - // Derive parameters for this state and ensure they're a super-set of parent's parameters - params: function(state) { - if (!state.params) { - return state.url ? state.url.parameters() : state.parent.params; - } - if (!isArray(state.params)) throw new Error("Invalid params in state '" + state + "'"); - if (state.url) throw new Error("Both params and url specicified in state '" + state + "'"); - return state.params; - }, - - // If there is no explicit multi-view configuration, make one up so we don't have - // to handle both cases in the view directive later. Note that having an explicit - // 'views' property will mean the default unnamed view properties are ignored. This - // is also a good time to resolve view names to absolute names, so everything is a - // straight lookup at link time. - views: function(state) { - var views = {}; - - forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) { - if (name.indexOf('@') < 0) name += '@' + state.parent.name; - views[name] = view; - }); - return views; - }, - - ownParams: function(state) { - if (!state.parent) { - return state.params; - } - var paramNames = {}; forEach(state.params, function (p) { paramNames[p] = true; }); - - forEach(state.parent.params, function (p) { - if (!paramNames[p]) { - throw new Error("Missing required parameter '" + p + "' in state '" + state.name + "'"); - } - paramNames[p] = false; - }); - var ownParams = []; - - forEach(paramNames, function (own, p) { - if (own) ownParams.push(p); - }); - return ownParams; - }, - - // Keep a full path from the root down to this state as this is needed for state activation. - path: function(state) { - return state.parent ? state.parent.path.concat(state) : []; // exclude root from path - }, - - // Speed up $state.contains() as it's used a lot - includes: function(state) { - var includes = state.parent ? extend({}, state.parent.includes) : {}; - includes[state.name] = true; - return includes; - }, - - $delegates: {} - }; - - function isRelative(stateName) { - return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0; - } - - function findState(stateOrName, base) { - var isStr = isString(stateOrName), - name = isStr ? stateOrName : stateOrName.name, - path = isRelative(name); - - if (path) { - if (!base) throw new Error("No reference point given for path '" + name + "'"); - var rel = name.split("."), i = 0, pathLength = rel.length, current = base; - - for (; i < pathLength; i++) { - if (rel[i] === "" && i === 0) { - current = base; - continue; - } - if (rel[i] === "^") { - if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'"); - current = current.parent; - continue; - } - break; - } - rel = rel.slice(i).join("."); - name = current.name + (current.name && rel ? "." : "") + rel; - } - var state = states[name]; - - if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) { - return state; - } - return undefined; - } - - function queueState(parentName, state) { - if (!queue[parentName]) { - queue[parentName] = []; - } - queue[parentName].push(state); - } - - function registerState(state) { - // Wrap a new object around the state so we can store our private details easily. - state = inherit(state, { - self: state, - resolve: state.resolve || {}, - toString: function() { return this.name; } - }); - - var name = state.name; - if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name"); - if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined"); - - // Get parent name - var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.')) - : (isString(state.parent)) ? state.parent - : ''; - - // If parent is not registered yet, add state to queue and register later - if (parentName && !states[parentName]) { - return queueState(parentName, state.self); - } - - for (var key in stateBuilder) { - if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]); - } - states[name] = state; - - // Register the state in the global state list and with $urlRouter if necessary. - if (!state[abstractKey] && state.url) { - $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) { - if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) { - $state.transitionTo(state, $match, { location: false }); - } - }]); - } - - // Register any queued children - if (queue[name]) { - for (var i = 0; i < queue[name].length; i++) { - registerState(queue[name][i]); - } - } - - return state; - } - - - // Implicit root state that is always active - root = registerState({ - name: '', - url: '^', - views: null, - 'abstract': true - }); - root.navigable = null; - - - /** - * @ngdoc function - * @name ui.router.state.$stateProvider#decorator - * @methodOf ui.router.state.$stateProvider - * - * @description - * Allows you to extend (carefully) or override (at your own peril) the - * `stateBuilder` object used internally by `$stateProvider`. This can be used - * to add custom functionality to ui-router, for example inferring templateUrl - * based on the state name. - * - * When passing only a name, it returns the current (original or decorated) builder - * function that matches `name`. - * - * The builder functions that can be decorated are listed below. Though not all - * necessarily have a good use case for decoration, that is up to you to decide. - * - * In addition, users can attach custom decorators, which will generate new - * properties within the state's internal definition. There is currently no clear - * use-case for this beyond accessing internal states (i.e. $state.$current), - * however, expect this to become increasingly relevant as we introduce additional - * meta-programming features. - * - * **Warning**: Decorators should not be interdependent because the order of - * execution of the builder functions in nondeterministic. Builder functions - * should only be dependent on the state definition object and super function. - * - * - * Existing builder functions and current return values: - * - * - parent - `{object}` - returns the parent state object. - * - data - `{object}` - returns state data, including any inherited data that is not - * overridden by own values (if any). - * - url - `{object}` - returns a UrlMatcher or null. - * - navigable - returns closest ancestor state that has a URL (aka is - * navigable). - * - params - `{object}` - returns an array of state params that are ensured to - * be a super-set of parent's params. - * - views - `{object}` - returns a views object where each key is an absolute view - * name (i.e. "viewName@stateName") and each value is the config object - * (template, controller) for the view. Even when you don't use the views object - * explicitly on a state config, one is still created for you internally. - * So by decorating this builder function you have access to decorating template - * and controller properties. - * - ownParams - `{object}` - returns an array of params that belong to the state, - * not including any params defined by ancestor states. - * - path - `{string}` - returns the full path from the root down to this state. - * Needed for state activation. - * - includes - `{object}` - returns an object that includes every state that - * would pass a '$state.includes()' test. - * - * @example - *
          -   * // Override the internal 'views' builder with a function that takes the state
          -   * // definition, and a reference to the internal function being overridden:
          -   * $stateProvider.decorator('views', function ($state, parent) {
          -   *   var result = {},
          -   *       views = parent(state);
          -   *
          -   *   angular.forEach(view, function (config, name) {
          -   *     var autoName = (state.name + '.' + name).replace('.', '/');
          -   *     config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
          -   *     result[name] = config;
          -   *   });
          -   *   return result;
          -   * });
          -   *
          -   * $stateProvider.state('home', {
          -   *   views: {
          -   *     'contact.list': { controller: 'ListController' },
          -   *     'contact.item': { controller: 'ItemController' }
          -   *   }
          -   * });
          -   *
          -   * // ...
          -   *
          -   * $state.go('home');
          -   * // Auto-populates list and item views with /partials/home/contact/list.html,
          -   * // and /partials/home/contact/item.html, respectively.
          -   * 
          - * - * @param {string} name The name of the builder function to decorate. - * @param {object} func A function that is responsible for decorating the original - * builder function. The function receives two parameters: - * - * - `{object}` - state - The state config object. - * - `{object}` - super - The original builder function. - * - * @return {object} $stateProvider - $stateProvider instance - */ - this.decorator = decorator; - function decorator(name, func) { - /*jshint validthis: true */ - if (isString(name) && !isDefined(func)) { - return stateBuilder[name]; - } - if (!isFunction(func) || !isString(name)) { - return this; - } - if (stateBuilder[name] && !stateBuilder.$delegates[name]) { - stateBuilder.$delegates[name] = stateBuilder[name]; - } - stateBuilder[name] = func; - return this; - } - - /** - * @ngdoc function - * @name ui.router.state.$stateProvider#state - * @methodOf ui.router.state.$stateProvider - * - * @description - * Registers a state configuration under a given state name. The stateConfig object - * has the following acceptable properties. - * - * - [`template`, `templateUrl`, `templateProvider`] - There are three ways to setup - * your templates. - * - * - `{string|object}` - template - String HTML content, or function that returns an HTML - * string. - * - `{string}` - templateUrl - String URL path to template file OR function, - * that returns URL path string. - * - `{object}` - templateProvider - Provider function that returns HTML content - * string. - * - * - [`controller`, `controllerProvider`] - A controller paired to the state. You can - * either use a controller, or a controller provider. - * - * - `{string|object}` - controller - Controller function or controller name. - * - `{object}` - controllerProvider - Injectable provider function that returns - * the actual controller or string. - * - * - `{object}` - resolve - A map of dependencies which should be injected into the - * controller. - * - * - `{string}` - url - A url with optional parameters. When a state is navigated or - * transitioned to, the `$stateParams` service will be populated with any - * parameters that were passed. - * - * - `{object}` - params - An array of parameter names or regular expressions. Only - * use this within a state if you are not using url. Otherwise you can specify your - * parameters within the url. When a state is navigated or transitioned to, the - * $stateParams service will be populated with any parameters that were passed. - * - * - `{object}` - views - Use the views property to set up multiple views. - * If you don't need multiple views within a single state this property is not - * needed. Tip: remember that often nested views are more useful and powerful - * than multiple sibling views. - * - * - `{boolean}` - abstract - An abstract state will never be directly activated, - * but can provide inherited properties to its common children states. - * - * - `{object}` - onEnter - Callback function for when a state is entered. Good way - * to trigger an action or dispatch an event, such as opening a dialog. - * - * - `{object}` - onExit - Callback function for when a state is exited. Good way to - * trigger an action or dispatch an event, such as opening a dialog. - * - * - `{object}` - data - Arbitrary data object, useful for custom configuration. - * - * @example - *
          -   * // The state() method takes a unique stateName (String) and a stateConfig (Object)
          -   * $stateProvider.state(stateName, stateConfig);
          -   *
          -   * // stateName can be a single top-level name (must be unique).
          -   * $stateProvider.state("home", {});
          -   *
          -   * // Or it can be a nested state name. This state is a child of the above "home" state.
          -   * $stateProvider.state("home.newest", {});
          -   *
          -   * // Nest states as deeply as needed.
          -   * $stateProvider.state("home.newest.abc.xyz.inception", {});
          -   *
          -   * // state() returns $stateProvider, so you can chain state declarations.
          -   * $stateProvider
          -   *   .state("home", {})
          -   *   .state("about", {})
          -   *   .state("contacts", {});
          -   * 
          - * - * @param {string} name A unique state name, e.g. "home", "about", "contacts". - * To create a parent/child state use a dot, e.g. "about.sales", "home.newest". - * @param {object} definition State configuratino object. - */ - this.state = state; - function state(name, definition) { - /*jshint validthis: true */ - if (isObject(name)) definition = name; - else definition.name = name; - registerState(definition); - return this; - } - - /** - * @ngdoc object - * @name ui.router.state.$state - * - * @requires $rootScope - * @requires $q - * @requires ui.router.state.$view - * @requires $injector - * @requires ui.router.util.$resolve - * @requires ui.router.state.$stateParams - * - * @property {object} params A param object, e.g. {sectionId: section.id)}, that - * you'd like to test against the current active state. - * @property {object} current A reference to the state's config object. However - * you passed it in. Useful for accessing custom data. - * @property {object} transition Currently pending transition. A promise that'll - * resolve or reject. - * - * @description - * `$state` service is responsible for representing states as well as transitioning - * between them. It also provides interfaces to ask for current state or even states - * you're coming from. - */ - // $urlRouter is injected just to ensure it gets instantiated - this.$get = $get; - $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$location', '$urlRouter']; - function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $location, $urlRouter) { - - var TransitionSuperseded = $q.reject(new Error('transition superseded')); - var TransitionPrevented = $q.reject(new Error('transition prevented')); - var TransitionAborted = $q.reject(new Error('transition aborted')); - var TransitionFailed = $q.reject(new Error('transition failed')); - var currentLocation = $location.url(); - - function syncUrl() { - if ($location.url() !== currentLocation) { - $location.url(currentLocation); - $location.replace(); - } - } - - root.locals = { resolve: null, globals: { $stateParams: {} } }; - $state = { - params: {}, - current: root.self, - $current: root, - transition: null - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#reload - * @methodOf ui.router.state.$state - * - * @description - * Reloads the current state by re-transitioning to it. - * - * @example - *
          -     * var app angular.module('app', ['ui.router.state']);
          -     *
          -     * app.controller('ctrl', function ($state) {
          -     *   $state.reload();
          -     * });
          -     * 
          - */ - $state.reload = function reload() { - $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: false }); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#go - * @methodOf ui.router.state.$state - * - * @description - * Convenience method for transitioning to a new state. `$state.go` calls - * `$state.transitionTo` internally but automatically sets options to - * `{ location: true, inherit: true, relative: $state.$current, notify: true }`. - * This allows you to easily use an absolute or relative to path and specify - * only the parameters you'd like to update (while letting unspecified parameters - * inherit from the current state. - * - * Some examples: - * - * - `$state.go('contact.detail')` - will go to the `contact.detail` state - * - `$state.go('^')` - will go to a parent state - * - `$state.go('^.sibling')` - will go to a sibling state - * - `$state.go('.child.grandchild')` - will go to grandchild state - * - * @example - *
          -     * var app = angular.module('app', ['ui.router.state']);
          -     *
          -     * app.controller('ctrl', function ($scope, $state) {
          -     *   $scope.changeState = function () {
          -     *     $state.go('contact.detail');
          -     *   };
          -     * });
          -     * 
          - * - * @param {string} to Absolute State Name or Relative State Path. - * @param {object} params A map of the parameters that will be sent to the state, - * will populate $stateParams. - * @param {object} options If Object is passed, object is an options hash. - */ - $state.go = function go(to, params, options) { - return this.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options)); - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#transitionTo - * @methodOf ui.router.state.$state - * - * @description - * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go} - * uses `transitionTo` internally. `$state.go` is recommended in most situations. - * - * @example - *
          -     * var app = angular.module('app', ['ui.router.state']);
          -     *
          -     * app.controller('ctrl', function ($scope, $state) {
          -     *   $scope.changeState = function () {
          -     *     $state.transitionTo('contact.detail');
          -     *   };
          -     * });
          -     * 
          - * - * @param {string} to Absolute State Name or Relative State Path. - * @param {object} params A map of the parameters that will be sent to the state, - * will populate $stateParams. - * @param {object} options If Object is passed, object is an options hash. - */ - $state.transitionTo = function transitionTo(to, toParams, options) { - toParams = toParams || {}; - options = extend({ - location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false - }, options || {}); - - var from = $state.$current, fromParams = $state.params, fromPath = from.path; - var evt, toState = findState(to, options.relative); - - if (!isDefined(toState)) { - // Broadcast not found event and abort the transition if prevented - var redirect = { to: to, toParams: toParams, options: options }; - evt = $rootScope.$broadcast('$stateNotFound', redirect, from.self, fromParams); - if (evt.defaultPrevented) { - syncUrl(); - return TransitionAborted; - } - - // Allow the handler to return a promise to defer state lookup retry - if (evt.retry) { - if (options.$retry) { - syncUrl(); - return TransitionFailed; - } - var retryTransition = $state.transition = $q.when(evt.retry); - retryTransition.then(function() { - if (retryTransition !== $state.transition) return TransitionSuperseded; - redirect.options.$retry = true; - return $state.transitionTo(redirect.to, redirect.toParams, redirect.options); - }, function() { - return TransitionAborted; - }); - syncUrl(); - return retryTransition; - } - - // Always retry once if the $stateNotFound was not prevented - // (handles either redirect changed or state lazy-definition) - to = redirect.to; - toParams = redirect.toParams; - options = redirect.options; - toState = findState(to, options.relative); - if (!isDefined(toState)) { - if (options.relative) throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'"); - throw new Error("No such state '" + to + "'"); - } - } - if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'"); - if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState); - to = toState; - - var toPath = to.path; - - // Starting from the root of the path, keep all levels that haven't changed - var keep, state, locals = root.locals, toLocals = []; - for (keep = 0, state = toPath[keep]; - state && state === fromPath[keep] && equalForKeys(toParams, fromParams, state.ownParams) && !options.reload; - keep++, state = toPath[keep]) { - locals = toLocals[keep] = state.locals; - } - - // If we're going to the same state and all locals are kept, we've got nothing to do. - // But clear 'transition', as we still want to cancel any other pending transitions. - // TODO: We may not want to bump 'transition' if we're called from a location change that we've initiated ourselves, - // because we might accidentally abort a legitimate transition initiated from code? - if (shouldTriggerReload(to, from, locals, options) ) { - if ( to.self.reloadOnSearch !== false ) - syncUrl(); - $state.transition = null; - return $q.when($state.current); - } - - // Normalize/filter parameters before we pass them to event handlers etc. - toParams = normalize(to.params, toParams || {}); - - // Broadcast start event and cancel the transition if requested - if (options.notify) { - evt = $rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams); - if (evt.defaultPrevented) { - syncUrl(); - return TransitionPrevented; - } - } - - // Resolve locals for the remaining states, but don't update any global state just - // yet -- if anything fails to resolve the current state needs to remain untouched. - // We also set up an inheritance chain for the locals here. This allows the view directive - // to quickly look up the correct definition for each view in the current state. Even - // though we create the locals object itself outside resolveState(), it is initially - // empty and gets filled asynchronously. We need to keep track of the promise for the - // (fully resolved) current locals, and pass this down the chain. - var resolved = $q.when(locals); - for (var l=keep; l=keep; l--) { - exiting = fromPath[l]; - if (exiting.self.onExit) { - $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals); - } - exiting.locals = null; - } - - // Enter 'to' states not kept - for (l=keep; l - * $state.is('contact.details.item'); // returns true - * $state.is(contactDetailItemStateObject); // returns true - * - * // everything else would return false - * - * - * @param {string|object} stateName The state name or state object you'd like to check. - * @param {object} params A param object, e.g. `{sectionId: section.id}`, that you'd like - * to test against the current active state. - * @returns {boolean} Returns true or false whether its the state or not. - */ - $state.is = function is(stateOrName, params) { - var state = findState(stateOrName); - - if (!isDefined(state)) { - return undefined; - } - - if ($state.$current !== state) { - return false; - } - - return isDefined(params) && params !== null ? angular.equals($stateParams, params) : true; - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#includes - * @methodOf ui.router.state.$state - * - * @description - * A method to determine if the current active state is equal to or is the child of the - * state stateName. If any params are passed then they will be tested for a match as well. - * Not all the parameters need to be passed, just the ones you'd like to test for equality. - * - * @example - *
          -     * $state.includes("contacts"); // returns true
          -     * $state.includes("contacts.details"); // returns true
          -     * $state.includes("contacts.details.item"); // returns true
          -     * $state.includes("contacts.list"); // returns false
          -     * $state.includes("about"); // returns false
          -     * 
          - * - * @param {string} stateOrName A partial name to be searched for within the current state name. - * @param {object} params A param object, e.g. `{sectionId: section.id}`, - * that you'd like to test against the current active state. - * @returns {boolean} True or false - */ - $state.includes = function includes(stateOrName, params) { - var state = findState(stateOrName); - if (!isDefined(state)) { - return undefined; - } - - if (!isDefined($state.$current.includes[state.name])) { - return false; - } - - var validParams = true; - angular.forEach(params, function(value, key) { - if (!isDefined($stateParams[key]) || $stateParams[key] !== value) { - validParams = false; - } - }); - return validParams; - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#href - * @methodOf ui.router.state.$state - * - * @description - * A url generation method that returns the compiled url for the given state populated with the given params. - * - * @example - *
          -     * expect($state.href("about.person", { person: "bob" })).toEqual("/about/bob");
          -     * 
          - * - * @param {string|object} stateOrName The state name or state object you'd like to generate a url from. - * @param {object} params An object of parameter values to fill the state's required parameters. - * @returns {string} url - */ - $state.href = function href(stateOrName, params, options) { - options = extend({ lossy: true, inherit: false, absolute: false, relative: $state.$current }, options || {}); - var state = findState(stateOrName, options.relative); - if (!isDefined(state)) return null; - - params = inheritParams($stateParams, params || {}, $state.$current, state); - var nav = (state && options.lossy) ? state.navigable : state; - var url = (nav && nav.url) ? nav.url.format(normalize(state.params, params || {})) : null; - if (!$locationProvider.html5Mode() && url) { - url = "#" + $locationProvider.hashPrefix() + url; - } - if (options.absolute && url) { - url = $location.protocol() + '://' + - $location.host() + - ($location.port() == 80 || $location.port() == 443 ? '' : ':' + $location.port()) + - (!$locationProvider.html5Mode() && url ? '/' : '') + - url; - } - return url; - }; - - /** - * @ngdoc function - * @name ui.router.state.$state#get - * @methodOf ui.router.state.$state - * - * @description - * Returns the state configuration object for any state by passing the name - * as a string. Without any arguments it'll return a array of all configured - * state objects. - * - * @param {string|object} stateOrName The name of the state for which you'd like - * to get the original state configuration object for. - * @returns {object} State configuration object or array of all objects. - */ - $state.get = function (stateOrName, context) { - if (!isDefined(stateOrName)) { - var list = []; - forEach(states, function(state) { list.push(state.self); }); - return list; - } - var state = findState(stateOrName, context); - return (state && state.self) ? state.self : null; - }; - - function resolveState(state, params, paramsAreFiltered, inherited, dst) { - // Make a restricted $stateParams with only the parameters that apply to this state if - // necessary. In addition to being available to the controller and onEnter/onExit callbacks, - // we also need $stateParams to be available for any $injector calls we make during the - // dependency resolution process. - var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params, params); - var locals = { $stateParams: $stateParams }; - - // Resolve 'global' dependencies for the state, i.e. those not specific to a view. - // We're also including $stateParams in this; that way the parameters are restricted - // to the set that should be visible to the state, and are independent of when we update - // the global $state and $stateParams values. - dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state); - var promises = [ dst.resolve.then(function (globals) { - dst.globals = globals; - }) ]; - if (inherited) promises.push(inherited); - - // Resolve template and dependencies for all views. - forEach(state.views, function (view, name) { - var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {}); - injectables.$template = [ function () { - return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: false }) || ''; - }]; - - promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) { - // References to the controller (only instantiated at link time) - if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) { - var injectLocals = angular.extend({}, injectables, locals); - result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals); - } else { - result.$$controller = view.controller; - } - // Provide access to the state itself for internal use - result.$$state = state; - dst[name] = result; - })); - }); - - // Wait for all the promises and then return the activation object - return $q.all(promises).then(function (values) { - return dst; - }); - } - - return $state; - } - - function shouldTriggerReload(to, from, locals, options) { - if ( to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false)) ) { - return true; - } - } -} - -angular.module('ui.router.state') - .value('$stateParams', {}) - .provider('$state', $StateProvider); - - -$ViewProvider.$inject = []; -function $ViewProvider() { - - this.$get = $get; - /** - * @ngdoc object - * @name ui.router.state.$view - * - * @requires ui.router.util.$templateFactory - * @requires $rootScope - * - * @description - * - */ - $get.$inject = ['$rootScope', '$templateFactory']; - function $get( $rootScope, $templateFactory) { - return { - // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... }) - /** - * @ngdoc function - * @name ui.router.state.$view#load - * @methodOf ui.router.state.$view - * - * @description - * - * @param {string} name name - * @param {object} options option object. - */ - load: function load(name, options) { - var result, defaults = { - template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {} - }; - options = extend(defaults, options); - - if (options.view) { - result = $templateFactory.fromConfig(options.view, options.params, options.locals); - } - if (result && options.notify) { - $rootScope.$broadcast('$viewContentLoading', options); - } - return result; - } - }; - } -} - -angular.module('ui.router.state').provider('$view', $ViewProvider); - -/** - * @ngdoc object - * @name ui.router.state.$uiViewScroll - * - * @requires $anchorScroll - * @requires $timeout - * - * @description - * When called with a jqLite element, it scrolls the element into view (after a - * `$timeout` so the DOM has time to refresh). - * - * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor, - * this can be enabled by calling `$uiViewScrollProvider.useAnchorScroll()`. - */ -function $ViewScrollProvider() { - - var useAnchorScroll = false; - - this.useAnchorScroll = function () { - useAnchorScroll = true; - }; - - this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) { - if (useAnchorScroll) { - return $anchorScroll; - } - - return function ($element) { - $timeout(function () { - $element[0].scrollIntoView(); - }, 0, false); - }; - }]; -} - -angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider); - -/** - * @ngdoc directive - * @name ui.router.state.diretive.ui-view - * - * @requires ui.router.state.$state - * @requires $compile - * @requires $controller - * @requires $injector - * - * @restrict ECA - * - * @description - * The ui-view directive tells $state where to place your templates. - * A view can be unnamed or named. - * - * @param {string} ui-view A view name. - */ -$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$uiViewScroll', '$document']; -function $ViewDirective( $state, $compile, $controller, $injector, $uiViewScroll, $document) { - - function getService() { - return ($injector.has) ? function(service) { - return $injector.has(service) ? $injector.get(service) : null; - } : function(service) { - try { - return $injector.get(service); - } catch (e) { - return null; - } - }; - } - - var viewIsUpdating = false, - service = getService(), - $animator = service('$animator'), - $animate = service('$animate'); - - // Returns a set of DOM manipulation functions based on whether animation - // should be performed - function getRenderer(element, attrs, scope) { - var statics = function() { - return { - leave: function (element) { element.remove(); }, - enter: function (element, parent, anchor) { anchor.after(element); } - }; - }; - - if ($animate) { - return function(shouldAnimate) { - return !shouldAnimate ? statics() : { - enter: function(element, parent, anchor) { $animate.enter(element, null, anchor); }, - leave: function(element) { $animate.leave(element, function() { element.remove(); }); } - }; - }; - } - - if ($animator) { - var animate = $animator && $animator(scope, attrs); - - return function(shouldAnimate) { - return !shouldAnimate ? statics() : { - enter: function(element, parent, anchor) { animate.enter(element, parent); }, - leave: function(element) { animate.leave(element.contents(), element); } - }; - }; - } - - return statics; - } - - var directive = { - restrict: 'ECA', - compile: function (element, attrs) { - var initial = element.html(), - isDefault = true, - anchor = angular.element($document[0].createComment(' ui-view-anchor ')), - parentEl = element.parent(); - - element.prepend(anchor); - - return function ($scope) { - var inherited = parentEl.inheritedData('$uiView'); - - var currentScope, currentEl, viewLocals, - name = attrs[directive.name] || attrs.name || '', - onloadExp = attrs.onload || '', - autoscrollExp = attrs.autoscroll, - renderer = getRenderer(element, attrs, $scope); - - if (name.indexOf('@') < 0) name = name + '@' + (inherited ? inherited.state.name : ''); - var view = { name: name, state: null }; - - var eventHook = function () { - if (viewIsUpdating) return; - viewIsUpdating = true; - - try { updateView(true); } catch (e) { - viewIsUpdating = false; - throw e; - } - viewIsUpdating = false; - }; - - $scope.$on('$stateChangeSuccess', eventHook); - $scope.$on('$viewContentLoading', eventHook); - - updateView(false); - - function cleanupLastView() { - if (currentEl) { - renderer(true).leave(currentEl); - currentEl = null; - } - - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - } - - function updateView(shouldAnimate) { - var locals = $state.$current && $state.$current.locals[name]; - - if (isDefault) { - isDefault = false; - element.replaceWith(anchor); - } - - if (!locals) { - cleanupLastView(); - currentEl = element.clone(); - currentEl.html(initial); - renderer(shouldAnimate).enter(currentEl, parentEl, anchor); - - currentScope = $scope.$new(); - $compile(currentEl.contents())(currentScope); - return; - } - - if (locals === viewLocals) return; // nothing to do - - cleanupLastView(); - - currentEl = element.clone(); - currentEl.html(locals.$template ? locals.$template : initial); - renderer(true).enter(currentEl, parentEl, anchor); - - currentEl.data('$uiView', view); - - viewLocals = locals; - view.state = locals.$$state; - - var link = $compile(currentEl.contents()); - - currentScope = $scope.$new(); - - if (locals.$$controller) { - locals.$scope = currentScope; - var controller = $controller(locals.$$controller, locals); - currentEl.children().data('$ngControllerController', controller); - } - - link(currentScope); - - currentScope.$emit('$viewContentLoaded'); - if (onloadExp) currentScope.$eval(onloadExp); - - if (!angular.isDefined(autoscrollExp) || !autoscrollExp || $scope.$eval(autoscrollExp)) { - $uiViewScroll(currentEl); - } - } - }; - } - }; - - return directive; -} - -angular.module('ui.router.state').directive('uiView', $ViewDirective); - -function parseStateRef(ref) { - var parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/); - if (!parsed || parsed.length !== 4) throw new Error("Invalid state ref '" + ref + "'"); - return { state: parsed[1], paramExpr: parsed[3] || null }; -} - -function stateContext(el) { - var stateData = el.parent().inheritedData('$uiView'); - - if (stateData && stateData.state && stateData.state.name) { - return stateData.state; - } -} - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-sref - * - * @requires ui.router.state.$state - * @requires $timeout - * - * @restrict A - * - * @description - * A directive that binds a link (`` tag) to a state. If the state has an associated - * URL, the directive will automatically generate & update the `href` attribute via - * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking - * the link will trigger a state transition with optional parameters. - * - * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be - * handled natively by the browser. - * - * You can also use relative state paths within ui-sref, just like the relative - * paths passed to `$state.go()`. You just need to be aware that the path is relative - * to the state that the link lives in, in other words the state that loaded the - * template containing the link. - * - * @example - *
          - * Home | About
          - *
          - * 
          - * 
          - * - * @param {string} ui-sref 'stateName' can be any valid absolute or relative state - */ -$StateRefDirective.$inject = ['$state', '$timeout']; -function $StateRefDirective($state, $timeout) { - return { - restrict: 'A', - require: '?^uiSrefActive', - link: function(scope, element, attrs, uiSrefActive) { - var ref = parseStateRef(attrs.uiSref); - var params = null, url = null, base = stateContext(element) || $state.$current; - var isForm = element[0].nodeName === "FORM"; - var attr = isForm ? "action" : "href", nav = true; - - var update = function(newVal) { - if (newVal) params = newVal; - if (!nav) return; - - var newHref = $state.href(ref.state, params, { relative: base }); - - if (uiSrefActive) { - uiSrefActive.$$setStateInfo(ref.state, params); - } - if (!newHref) { - nav = false; - return false; - } - element[0][attr] = newHref; - }; - - if (ref.paramExpr) { - scope.$watch(ref.paramExpr, function(newVal, oldVal) { - if (newVal !== params) update(newVal); - }, true); - params = scope.$eval(ref.paramExpr); - } - update(); - - if (isForm) return; - - element.bind("click", function(e) { - var button = e.which || e.button; - if ((button === 0 || button == 1) && !e.ctrlKey && !e.metaKey && !e.shiftKey && !element.attr('target')) { - // HACK: This is to allow ng-clicks to be processed before the transition is initiated: - $timeout(function() { - $state.go(ref.state, params, { relative: base }); - }); - e.preventDefault(); - } - }); - } - }; -} - -/** - * @ngdoc directive - * @name ui.router.state.directive:ui-sref-active - * - * @requires ui.router.state.$state - * @requires ui.router.state.$stateParams - * @requires $interpolate - * - * @restrict A - * - * @description - * A directive working alongside ui-sref to add classes to an element when the - * related ui-sref directive's state is active, and removing them when it is inactive. - * The primary use-case is to simplify the special appearance of navigation menus - * relying on `ui-sref`, by having the "active" state's menu button appear different, - * distinguishing it from the inactive menu items. - * - * @example - *
          - * 
          - * 
          - */ -$StateActiveDirective.$inject = ['$state', '$stateParams', '$interpolate']; -function $StateActiveDirective($state, $stateParams, $interpolate) { - return { - restrict: "A", - controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { - var state, params, activeClass; - - // There probably isn't much point in $observing this - activeClass = $interpolate($attrs.uiSrefActive || '', false)($scope); - - // Allow uiSref to communicate with uiSrefActive - this.$$setStateInfo = function(newState, newParams) { - state = $state.get(newState, stateContext($element)); - params = newParams; - update(); - }; - - $scope.$on('$stateChangeSuccess', update); - - // Update route state - function update() { - if ($state.$current.self === state && matchesParams()) { - $element.addClass(activeClass); - } else { - $element.removeClass(activeClass); - } - } - - function matchesParams() { - return !params || equalForKeys(params, $stateParams); - } - }] - }; -} - -angular.module('ui.router.state') - .directive('uiSref', $StateRefDirective) - .directive('uiSrefActive', $StateActiveDirective); - -/** - * @ngdoc filter - * @name ui.router.state.filter:isState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#is $state.is("stateName")}. - */ -$IsStateFilter.$inject = ['$state']; -function $IsStateFilter($state) { - return function(state) { - return $state.is(state); - }; -} - -/** - * @ngdoc filter - * @name ui.router.state.filter:includeByState - * - * @requires ui.router.state.$state - * - * @description - * Translates to {@link ui.router.state.$state#includes $state.includes()}. - */ -$IncludedByStateFilter.$inject = ['$state']; -function $IncludedByStateFilter($state) { - return function(state) { - return $state.includes(state); - }; -} - -angular.module('ui.router.state') - .filter('isState', $IsStateFilter) - .filter('includedByState', $IncludedByStateFilter); - -/** - * @ngdoc object - * @name ui.router.compat.$routeProvider - * - * @requires ui.router.state.$stateProvider - * @requires ui.router.router.$urlRouterProvider - * - * @description - * `$routeProvider` of the `ui.router.compat` module overwrites the existing - * `routeProvider` from the core. This is done to provide compatibility between - * the UI Router and the core router. - * - * It also provides a `when()` method to register routes that map to certain urls. - * Behind the scenes it actually delegates either to - * {@link ui.router.router.$urlRouterProvider $urlRouterProvider} or to the - * {@link ui.router.state.$stateProvider $stateProvider} to postprocess the given - * router definition object. - */ -$RouteProvider.$inject = ['$stateProvider', '$urlRouterProvider']; -function $RouteProvider( $stateProvider, $urlRouterProvider) { - - var routes = []; - - onEnterRoute.$inject = ['$$state']; - function onEnterRoute( $$state) { - /*jshint validthis: true */ - this.locals = $$state.locals.globals; - this.params = this.locals.$stateParams; - } - - function onExitRoute() { - /*jshint validthis: true */ - this.locals = null; - this.params = null; - } - - this.when = when; - /** - * @ngdoc function - * @name ui.router.compat.$routeProvider#when - * @methodOf ui.router.compat.$routeProvider - * - * @description - * Registers a route with a given route definition object. The route definition - * object has the same interface the angular core route definition object has. - * - * @example - *
          -   * var app = angular.module('app', ['ui.router.compat']);
          -   *
          -   * app.config(function ($routeProvider) {
          -   *   $routeProvider.when('home', {
          -   *     controller: function () { ... },
          -   *     templateUrl: 'path/to/template'
          -   *   });
          -   * });
          -   * 
          - * - * @param {string} url URL as string - * @param {object} route Route definition object - * - * @return {object} $routeProvider - $routeProvider instance - */ - function when(url, route) { - /*jshint validthis: true */ - if (route.redirectTo != null) { - // Redirect, configure directly on $urlRouterProvider - var redirect = route.redirectTo, handler; - if (isString(redirect)) { - handler = redirect; // leave $urlRouterProvider to handle - } else if (isFunction(redirect)) { - // Adapt to $urlRouterProvider API - handler = function (params, $location) { - return redirect(params, $location.path(), $location.search()); - }; - } else { - throw new Error("Invalid 'redirectTo' in when()"); - } - $urlRouterProvider.when(url, handler); - } else { - // Regular route, configure as state - $stateProvider.state(inherit(route, { - parent: null, - name: 'route:' + encodeURIComponent(url), - url: url, - onEnter: onEnterRoute, - onExit: onExitRoute - })); - } - routes.push(route); - return this; - } - - /** - * @ngdoc object - * @name ui.router.compat.$route - * - * @requires ui.router.state.$state - * @requires $rootScope - * @requires $routeParams - * - * @property {object} routes - Array of registered routes. - * @property {object} params - Current route params as object. - * @property {string} current - Name of the current route. - * - * @description - * The `$route` service provides interfaces to access defined routes. It also let's - * you access route params through `$routeParams` service, so you have fully - * control over all the stuff you would actually get from angular's core `$route` - * service. - */ - this.$get = $get; - $get.$inject = ['$state', '$rootScope', '$routeParams']; - function $get( $state, $rootScope, $routeParams) { - - var $route = { - routes: routes, - params: $routeParams, - current: undefined - }; - - function stateAsRoute(state) { - return (state.name !== '') ? state : undefined; - } - - $rootScope.$on('$stateChangeStart', function (ev, to, toParams, from, fromParams) { - $rootScope.$broadcast('$routeChangeStart', stateAsRoute(to), stateAsRoute(from)); - }); - - $rootScope.$on('$stateChangeSuccess', function (ev, to, toParams, from, fromParams) { - $route.current = stateAsRoute(to); - $rootScope.$broadcast('$routeChangeSuccess', stateAsRoute(to), stateAsRoute(from)); - copy(toParams, $route.params); - }); - - $rootScope.$on('$stateChangeError', function (ev, to, toParams, from, fromParams, error) { - $rootScope.$broadcast('$routeChangeError', stateAsRoute(to), stateAsRoute(from), error); - }); - - return $route; - } -} - -angular.module('ui.router.compat') - .provider('$route', $RouteProvider) - .directive('ngView', $ViewDirective); -})(window, window.angular); -/* - * angular-ui-bootstrap - * http://angular-ui.github.io/bootstrap/ - - * Version: 0.10.0 - 2014-01-13 - * License: MIT - */ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); -angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); -angular.module('ui.bootstrap.transition', []) - -/** - * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. - * @param {DOMElement} element The DOMElement that will be animated. - * @param {string|object|function} trigger The thing that will cause the transition to start: - * - As a string, it represents the css class to be added to the element. - * - As an object, it represents a hash of style attributes to be applied to the element. - * - As a function, it represents a function to be called that will cause the transition to occur. - * @return {Promise} A promise that is resolved when the transition finishes. - */ -.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { - - var $transition = function(element, trigger, options) { - options = options || {}; - var deferred = $q.defer(); - var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; - - var transitionEndHandler = function(event) { - $rootScope.$apply(function() { - element.unbind(endEventName, transitionEndHandler); - deferred.resolve(element); - }); - }; - - if (endEventName) { - element.bind(endEventName, transitionEndHandler); - } - - // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur - $timeout(function() { - if ( angular.isString(trigger) ) { - element.addClass(trigger); - } else if ( angular.isFunction(trigger) ) { - trigger(element); - } else if ( angular.isObject(trigger) ) { - element.css(trigger); - } - //If browser does not support transitions, instantly resolve - if ( !endEventName ) { - deferred.resolve(element); - } - }); - - // Add our custom cancel function to the promise that is returned - // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, - // i.e. it will therefore never raise a transitionEnd event for that transition - deferred.promise.cancel = function() { - if ( endEventName ) { - element.unbind(endEventName, transitionEndHandler); - } - deferred.reject('Transition cancelled'); - }; - - return deferred.promise; - }; - - // Work out the name of the transitionEnd event - var transElement = document.createElement('trans'); - var transitionEndEventNames = { - 'WebkitTransition': 'webkitTransitionEnd', - 'MozTransition': 'transitionend', - 'OTransition': 'oTransitionEnd', - 'transition': 'transitionend' - }; - var animationEndEventNames = { - 'WebkitTransition': 'webkitAnimationEnd', - 'MozTransition': 'animationend', - 'OTransition': 'oAnimationEnd', - 'transition': 'animationend' - }; - function findEndEventName(endEventNames) { - for (var name in endEventNames){ - if (transElement.style[name] !== undefined) { - return endEventNames[name]; - } - } - } - $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); - $transition.animationEndEventName = findEndEventName(animationEndEventNames); - return $transition; -}]); - -angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) - - .directive('collapse', ['$transition', function ($transition, $timeout) { - - return { - link: function (scope, element, attrs) { - - var initialAnimSkip = true; - var currentTransition; - - function doTransition(change) { - var newTransition = $transition(element, change); - if (currentTransition) { - currentTransition.cancel(); - } - currentTransition = newTransition; - newTransition.then(newTransitionDone, newTransitionDone); - return newTransition; - - function newTransitionDone() { - // Make sure it's this transition, otherwise, leave it alone. - if (currentTransition === newTransition) { - currentTransition = undefined; - } - } - } - - function expand() { - if (initialAnimSkip) { - initialAnimSkip = false; - expandDone(); - } else { - element.removeClass('collapse').addClass('collapsing'); - doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); - } - } - - function expandDone() { - element.removeClass('collapsing'); - element.addClass('collapse in'); - element.css({height: 'auto'}); - } - - function collapse() { - if (initialAnimSkip) { - initialAnimSkip = false; - collapseDone(); - element.css({height: 0}); - } else { - // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value - element.css({ height: element[0].scrollHeight + 'px' }); - //trigger reflow so a browser realizes that height was updated from auto to a specific value - var x = element[0].offsetWidth; - - element.removeClass('collapse in').addClass('collapsing'); - - doTransition({ height: 0 }).then(collapseDone); - } - } - - function collapseDone() { - element.removeClass('collapsing'); - element.addClass('collapse'); - } - - scope.$watch(attrs.collapse, function (shouldCollapse) { - if (shouldCollapse) { - collapse(); - } else { - expand(); - } - }); - } - }; - }]); - -angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) - -.constant('accordionConfig', { - closeOthers: true -}) - -.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { - - // This array keeps track of the accordion groups - this.groups = []; - - // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to - this.closeOthers = function(openGroup) { - var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; - if ( closeOthers ) { - angular.forEach(this.groups, function (group) { - if ( group !== openGroup ) { - group.isOpen = false; - } - }); - } - }; - - // This is called from the accordion-group directive to add itself to the accordion - this.addGroup = function(groupScope) { - var that = this; - this.groups.push(groupScope); - - groupScope.$on('$destroy', function (event) { - that.removeGroup(groupScope); - }); - }; - - // This is called from the accordion-group directive when to remove itself - this.removeGroup = function(group) { - var index = this.groups.indexOf(group); - if ( index !== -1 ) { - this.groups.splice(this.groups.indexOf(group), 1); - } - }; - -}]) - -// The accordion directive simply sets up the directive controller -// and adds an accordion CSS class to itself element. -.directive('accordion', function () { - return { - restrict:'EA', - controller:'AccordionController', - transclude: true, - replace: false, - templateUrl: 'template/accordion/accordion.html' - }; -}) - -// The accordion-group directive indicates a block of html that will expand and collapse in an accordion -.directive('accordionGroup', ['$parse', function($parse) { - return { - require:'^accordion', // We need this directive to be inside an accordion - restrict:'EA', - transclude:true, // It transcludes the contents of the directive into the template - replace: true, // The element containing the directive will be replaced with the template - templateUrl:'template/accordion/accordion-group.html', - scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope - controller: function() { - this.setHeading = function(element) { - this.heading = element; - }; - }, - link: function(scope, element, attrs, accordionCtrl) { - var getIsOpen, setIsOpen; - - accordionCtrl.addGroup(scope); - - scope.isOpen = false; - - if ( attrs.isOpen ) { - getIsOpen = $parse(attrs.isOpen); - setIsOpen = getIsOpen.assign; - - scope.$parent.$watch(getIsOpen, function(value) { - scope.isOpen = !!value; - }); - } - - scope.$watch('isOpen', function(value) { - if ( value ) { - accordionCtrl.closeOthers(scope); - } - if ( setIsOpen ) { - setIsOpen(scope.$parent, value); - } - }); - } - }; -}]) - -// Use accordion-heading below an accordion-group to provide a heading containing HTML -// -// Heading containing HTML - -// -.directive('accordionHeading', function() { - return { - restrict: 'EA', - transclude: true, // Grab the contents to be used as the heading - template: '', // In effect remove this element! - replace: true, - require: '^accordionGroup', - compile: function(element, attr, transclude) { - return function link(scope, element, attr, accordionGroupCtrl) { - // Pass the heading to the accordion-group controller - // so that it can be transcluded into the right place in the template - // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] - accordionGroupCtrl.setHeading(transclude(scope, function() {})); - }; - } - }; -}) - -// Use in the accordion-group template to indicate where you want the heading to be transcluded -// You must provide the property on the accordion-group controller that will hold the transcluded element -//
          -// -// ... -//
          -.directive('accordionTransclude', function() { - return { - require: '^accordionGroup', - link: function(scope, element, attr, controller) { - scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { - if ( heading ) { - element.html(''); - element.append(heading); - } - }); - } - }; -}); - -angular.module("ui.bootstrap.alert", []) - -.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { - $scope.closeable = 'close' in $attrs; -}]) - -.directive('alert', function () { - return { - restrict:'EA', - controller:'AlertController', - templateUrl:'template/alert/alert.html', - transclude:true, - replace:true, - scope: { - type: '=', - close: '&' - } - }; -}); - -angular.module('ui.bootstrap.bindHtml', []) - - .directive('bindHtmlUnsafe', function () { - return function (scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); - scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); - }; - }); -angular.module('ui.bootstrap.buttons', []) - -.constant('buttonConfig', { - activeClass: 'active', - toggleEvent: 'click' -}) - -.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { - this.activeClass = buttonConfig.activeClass || 'active'; - this.toggleEvent = buttonConfig.toggleEvent || 'click'; -}]) - -.directive('btnRadio', function () { - return { - require: ['btnRadio', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { - var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); - }; - - //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - if (!element.hasClass(buttonsCtrl.activeClass)) { - scope.$apply(function () { - ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio)); - ngModelCtrl.$render(); - }); - } - }); - } - }; -}) - -.directive('btnCheckbox', function () { - return { - require: ['btnCheckbox', 'ngModel'], - controller: 'ButtonsController', - link: function (scope, element, attrs, ctrls) { - var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; - - function getTrueValue() { - return getCheckboxValue(attrs.btnCheckboxTrue, true); - } - - function getFalseValue() { - return getCheckboxValue(attrs.btnCheckboxFalse, false); - } - - function getCheckboxValue(attributeValue, defaultValue) { - var val = scope.$eval(attributeValue); - return angular.isDefined(val) ? val : defaultValue; - } - - //model -> UI - ngModelCtrl.$render = function () { - element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); - }; - - //ui->model - element.bind(buttonsCtrl.toggleEvent, function () { - scope.$apply(function () { - ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); - ngModelCtrl.$render(); - }); - }); - } - }; -}); - -/** -* @ngdoc overview -* @name ui.bootstrap.carousel -* -* @description -* AngularJS version of an image carousel. -* -*/ -angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) -.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) { - var self = this, - slides = self.slides = [], - currentIndex = -1, - currentTimeout, isPlaying; - self.currentSlide = null; - - var destroyed = false; - /* direction: "prev" or "next" */ - self.select = function(nextSlide, direction) { - var nextIndex = slides.indexOf(nextSlide); - //Decide direction if it's not given - if (direction === undefined) { - direction = nextIndex > currentIndex ? "next" : "prev"; - } - if (nextSlide && nextSlide !== self.currentSlide) { - if ($scope.$currentTransition) { - $scope.$currentTransition.cancel(); - //Timeout so ng-class in template has time to fix classes for finished slide - $timeout(goNext); - } else { - goNext(); - } - } - function goNext() { - // Scope has been destroyed, stop here. - if (destroyed) { return; } - //If we have a slide to transition from and we have a transition type and we're allowed, go - if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { - //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime - nextSlide.$element.addClass(direction); - var reflow = nextSlide.$element[0].offsetWidth; //force reflow - - //Set all other slides to stop doing their stuff for the new transition - angular.forEach(slides, function(slide) { - angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); - }); - angular.extend(nextSlide, {direction: direction, active: true, entering: true}); - angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); - - $scope.$currentTransition = $transition(nextSlide.$element, {}); - //We have to create new pointers inside a closure since next & current will change - (function(next,current) { - $scope.$currentTransition.then( - function(){ transitionDone(next, current); }, - function(){ transitionDone(next, current); } - ); - }(nextSlide, self.currentSlide)); - } else { - transitionDone(nextSlide, self.currentSlide); - } - self.currentSlide = nextSlide; - currentIndex = nextIndex; - //every time you change slides, reset the timer - restartTimer(); - } - function transitionDone(next, current) { - angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); - angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); - $scope.$currentTransition = null; - } - }; - $scope.$on('$destroy', function () { - destroyed = true; - }); - - /* Allow outside people to call indexOf on slides array */ - self.indexOfSlide = function(slide) { - return slides.indexOf(slide); - }; - - $scope.next = function() { - var newIndex = (currentIndex + 1) % slides.length; - - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'next'); - } - }; - - $scope.prev = function() { - var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; - - //Prevent this user-triggered transition from occurring if there is already one in progress - if (!$scope.$currentTransition) { - return self.select(slides[newIndex], 'prev'); - } - }; - - $scope.select = function(slide) { - self.select(slide); - }; - - $scope.isActive = function(slide) { - return self.currentSlide === slide; - }; - - $scope.slides = function() { - return slides; - }; - - $scope.$watch('interval', restartTimer); - $scope.$on('$destroy', resetTimer); - - function restartTimer() { - resetTimer(); - var interval = +$scope.interval; - if (!isNaN(interval) && interval>=0) { - currentTimeout = $timeout(timerFn, interval); - } - } - - function resetTimer() { - if (currentTimeout) { - $timeout.cancel(currentTimeout); - currentTimeout = null; - } - } - - function timerFn() { - if (isPlaying) { - $scope.next(); - restartTimer(); - } else { - $scope.pause(); - } - } - - $scope.play = function() { - if (!isPlaying) { - isPlaying = true; - restartTimer(); - } - }; - $scope.pause = function() { - if (!$scope.noPause) { - isPlaying = false; - resetTimer(); - } - }; - - self.addSlide = function(slide, element) { - slide.$element = element; - slides.push(slide); - //if this is the first slide or the slide is set to active, select it - if(slides.length === 1 || slide.active) { - self.select(slides[slides.length-1]); - if (slides.length == 1) { - $scope.play(); - } - } else { - slide.active = false; - } - }; - - self.removeSlide = function(slide) { - //get the index of the slide inside the carousel - var index = slides.indexOf(slide); - slides.splice(index, 1); - if (slides.length > 0 && slide.active) { - if (index >= slides.length) { - self.select(slides[index-1]); - } else { - self.select(slides[index]); - } - } else if (currentIndex > index) { - currentIndex--; - } - }; - -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.carousel.directive:carousel - * @restrict EA - * - * @description - * Carousel is the outer container for a set of image 'slides' to showcase. - * - * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. - * @param {boolean=} noTransition Whether to disable transitions on the carousel. - * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). - * - * @example - - - - - - - - - - - - - - - .carousel-indicators { - top: auto; - bottom: 15px; - } - - - */ -.directive('carousel', [function() { - return { - restrict: 'EA', - transclude: true, - replace: true, - controller: 'CarouselController', - require: 'carousel', - templateUrl: 'template/carousel/carousel.html', - scope: { - interval: '=', - noTransition: '=', - noPause: '=' - } - }; -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.carousel.directive:slide - * @restrict EA - * - * @description - * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. - * - * @param {boolean=} active Model binding, whether or not this slide is currently active. - * - * @example - - -
          - - - - - - -
          -
          -
            -
          • - - {{$index}}: {{slide.text}} -
          • -
          - Add Slide -
          -
          - Interval, in milliseconds: -
          Enter a negative number to stop the interval. -
          -
          -
          -
          - -function CarouselDemoCtrl($scope) { - $scope.myInterval = 5000; - var slides = $scope.slides = []; - $scope.addSlide = function() { - var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150); - slides.push({ - image: 'http://placekitten.com/' + newWidth + '/200', - text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' ' - ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4] - }); - }; - for (var i=0; i<4; i++) $scope.addSlide(); -} - - - .carousel-indicators { - top: auto; - bottom: 15px; - } - -
          -*/ - -.directive('slide', ['$parse', function($parse) { - return { - require: '^carousel', - restrict: 'EA', - transclude: true, - replace: true, - templateUrl: 'template/carousel/slide.html', - scope: { - }, - link: function (scope, element, attrs, carouselCtrl) { - //Set up optional 'active' = binding - if (attrs.active) { - var getActive = $parse(attrs.active); - var setActive = getActive.assign; - var lastValue = scope.active = getActive(scope.$parent); - scope.$watch(function parentActiveWatch() { - var parentActive = getActive(scope.$parent); - - if (parentActive !== scope.active) { - // we are out of sync and need to copy - if (parentActive !== lastValue) { - // parent changed and it has precedence - lastValue = scope.active = parentActive; - } else { - // if the parent can be assigned then do so - setActive(scope.$parent, parentActive = lastValue = scope.active); - } - } - return parentActive; - }); - } - - carouselCtrl.addSlide(scope, element); - //when the scope is destroyed then remove the slide from the current slides array - scope.$on('$destroy', function() { - carouselCtrl.removeSlide(scope); - }); - - scope.$watch('active', function(active) { - if (active) { - carouselCtrl.select(scope); - } - }); - } - }; -}]); - -angular.module('ui.bootstrap.position', []) - -/** - * A set of utility methods that can be use to retrieve position of DOM elements. - * It is meant to be used where we need to absolute-position DOM elements in - * relation to other, existing elements (this is the case for tooltips, popovers, - * typeahead suggestions etc.). - */ - .factory('$position', ['$document', '$window', function ($document, $window) { - - function getStyle(el, cssprop) { - if (el.currentStyle) { //IE - return el.currentStyle[cssprop]; - } else if ($window.getComputedStyle) { - return $window.getComputedStyle(el)[cssprop]; - } - // finally try and get inline style - return el.style[cssprop]; - } - - /** - * Checks if a given element is statically positioned - * @param element - raw DOM element - */ - function isStaticPositioned(element) { - return (getStyle(element, "position") || 'static' ) === 'static'; - } - - /** - * returns the closest, non-statically positioned parentOffset of a given element - * @param element - */ - var parentOffsetEl = function (element) { - var docDomEl = $document[0]; - var offsetParent = element.offsetParent || docDomEl; - while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent || docDomEl; - }; - - return { - /** - * Provides read-only equivalent of jQuery's position function: - * http://api.jquery.com/position/ - */ - position: function (element) { - var elBCR = this.offset(element); - var offsetParentBCR = { top: 0, left: 0 }; - var offsetParentEl = parentOffsetEl(element[0]); - if (offsetParentEl != $document[0]) { - offsetParentBCR = this.offset(angular.element(offsetParentEl)); - offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; - offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; - } - - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: elBCR.top - offsetParentBCR.top, - left: elBCR.left - offsetParentBCR.left - }; - }, - - /** - * Provides read-only equivalent of jQuery's offset function: - * http://api.jquery.com/offset/ - */ - offset: function (element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: boundingClientRect.width || element.prop('offsetWidth'), - height: boundingClientRect.height || element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) - }; - } - }; - }]); - -angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position']) - -.constant('datepickerConfig', { - dayFormat: 'dd', - monthFormat: 'MMMM', - yearFormat: 'yyyy', - dayHeaderFormat: 'EEE', - dayTitleFormat: 'MMMM yyyy', - monthTitleFormat: 'yyyy', - showWeeks: true, - startingDay: 0, - yearRange: 20, - minDate: null, - maxDate: null -}) - -.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) { - var format = { - day: getValue($attrs.dayFormat, dtConfig.dayFormat), - month: getValue($attrs.monthFormat, dtConfig.monthFormat), - year: getValue($attrs.yearFormat, dtConfig.yearFormat), - dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat), - dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat), - monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat) - }, - startingDay = getValue($attrs.startingDay, dtConfig.startingDay), - yearRange = getValue($attrs.yearRange, dtConfig.yearRange); - - this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null; - this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null; - - function getValue(value, defaultValue) { - return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue; - } - - function getDaysInMonth( year, month ) { - return new Date(year, month, 0).getDate(); - } - - function getDates(startDate, n) { - var dates = new Array(n); - var current = startDate, i = 0; - while (i < n) { - dates[i++] = new Date(current); - current.setDate( current.getDate() + 1 ); - } - return dates; - } - - function makeDate(date, format, isSelected, isSecondary) { - return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary }; - } - - this.modes = [ - { - name: 'day', - getVisibleDates: function(date, selected) { - var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1); - var difference = startingDay - firstDayOfMonth.getDay(), - numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, - firstDate = new Date(firstDayOfMonth), numDates = 0; - - if ( numDisplayedFromPreviousMonth > 0 ) { - firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); - numDates += numDisplayedFromPreviousMonth; // Previous - } - numDates += getDaysInMonth(year, month + 1); // Current - numDates += (7 - numDates % 7) % 7; // Next - - var days = getDates(firstDate, numDates), labels = new Array(7); - for (var i = 0; i < numDates; i ++) { - var dt = new Date(days[i]); - days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month); - } - for (var j = 0; j < 7; j++) { - labels[j] = dateFilter(days[j].date, format.dayHeader); - } - return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels }; - }, - compare: function(date1, date2) { - return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); - }, - split: 7, - step: { months: 1 } - }, - { - name: 'month', - getVisibleDates: function(date, selected) { - var months = new Array(12), year = date.getFullYear(); - for ( var i = 0; i < 12; i++ ) { - var dt = new Date(year, i, 1); - months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year)); - } - return { objects: months, title: dateFilter(date, format.monthTitle) }; - }, - compare: function(date1, date2) { - return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); - }, - split: 3, - step: { years: 1 } - }, - { - name: 'year', - getVisibleDates: function(date, selected) { - var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1; - for ( var i = 0; i < yearRange; i++ ) { - var dt = new Date(startYear + i, 0, 1); - years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear())); - } - return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') }; - }, - compare: function(date1, date2) { - return date1.getFullYear() - date2.getFullYear(); - }, - split: 5, - step: { years: yearRange } - } - ]; - - this.isDisabled = function(date, mode) { - var currentMode = this.modes[mode || 0]; - return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name}))); - }; -}]) - -.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/datepicker/datepicker.html', - scope: { - dateDisabled: '&' - }, - require: ['datepicker', '?^ngModel'], - controller: 'DatepickerController', - link: function(scope, element, attrs, ctrls) { - var datepickerCtrl = ctrls[0], ngModel = ctrls[1]; - - if (!ngModel) { - return; // do nothing if no ng-model - } - - // Configuration parameters - var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks; - - if (attrs.showWeeks) { - scope.$parent.$watch($parse(attrs.showWeeks), function(value) { - showWeeks = !! value; - updateShowWeekNumbers(); - }); - } else { - updateShowWeekNumbers(); - } - - if (attrs.min) { - scope.$parent.$watch($parse(attrs.min), function(value) { - datepickerCtrl.minDate = value ? new Date(value) : null; - refill(); - }); - } - if (attrs.max) { - scope.$parent.$watch($parse(attrs.max), function(value) { - datepickerCtrl.maxDate = value ? new Date(value) : null; - refill(); - }); - } - - function updateShowWeekNumbers() { - scope.showWeekNumbers = mode === 0 && showWeeks; - } - - // Split array into smaller arrays - function split(arr, size) { - var arrays = []; - while (arr.length > 0) { - arrays.push(arr.splice(0, size)); - } - return arrays; - } - - function refill( updateSelected ) { - var date = null, valid = true; - - if ( ngModel.$modelValue ) { - date = new Date( ngModel.$modelValue ); - - if ( isNaN(date) ) { - valid = false; - $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); - } else if ( updateSelected ) { - selected = date; - } - } - ngModel.$setValidity('date', valid); - - var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date); - angular.forEach(data.objects, function(obj) { - obj.disabled = datepickerCtrl.isDisabled(obj.date, mode); - }); - - ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date))); - - scope.rows = split(data.objects, currentMode.split); - scope.labels = data.labels || []; - scope.title = data.title; - } - - function setMode(value) { - mode = value; - updateShowWeekNumbers(); - refill(); - } - - ngModel.$render = function() { - refill( true ); - }; - - scope.select = function( date ) { - if ( mode === 0 ) { - var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); - dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); - ngModel.$setViewValue( dt ); - refill( true ); - } else { - selected = date; - setMode( mode - 1 ); - } - }; - scope.move = function(direction) { - var step = datepickerCtrl.modes[mode].step; - selected.setMonth( selected.getMonth() + direction * (step.months || 0) ); - selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) ); - refill(); - }; - scope.toggleMode = function() { - setMode( (mode + 1) % datepickerCtrl.modes.length ); - }; - scope.getWeekNumber = function(row) { - return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null; - }; - - function getISO8601WeekNumber(date) { - var checkDate = new Date(date); - checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday - var time = checkDate.getTime(); - checkDate.setMonth(0); // Compare with Jan 1 - checkDate.setDate(1); - return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; - } - } - }; -}]) - -.constant('datepickerPopupConfig', { - dateFormat: 'yyyy-MM-dd', - currentText: 'Today', - toggleWeeksText: 'Weeks', - clearText: 'Clear', - closeText: 'Done', - closeOnDateSelection: true, - appendToBody: false, - showButtonBar: true -}) - -.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig', -function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) { - return { - restrict: 'EA', - require: 'ngModel', - link: function(originalScope, element, attrs, ngModel) { - var scope = originalScope.$new(), // create a child scope so we are not polluting original one - dateFormat, - closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, - appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; - - attrs.$observe('datepickerPopup', function(value) { - dateFormat = value || datepickerPopupConfig.dateFormat; - ngModel.$render(); - }); - - scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; - - originalScope.$on('$destroy', function() { - $popup.remove(); - scope.$destroy(); - }); - - attrs.$observe('currentText', function(text) { - scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText; - }); - attrs.$observe('toggleWeeksText', function(text) { - scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText; - }); - attrs.$observe('clearText', function(text) { - scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText; - }); - attrs.$observe('closeText', function(text) { - scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText; - }); - - var getIsOpen, setIsOpen; - if ( attrs.isOpen ) { - getIsOpen = $parse(attrs.isOpen); - setIsOpen = getIsOpen.assign; - - originalScope.$watch(getIsOpen, function updateOpen(value) { - scope.isOpen = !! value; - }); - } - scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state - - function setOpen( value ) { - if (setIsOpen) { - setIsOpen(originalScope, !!value); - } else { - scope.isOpen = !!value; - } - } - - var documentClickBind = function(event) { - if (scope.isOpen && event.target !== element[0]) { - scope.$apply(function() { - setOpen(false); - }); - } - }; - - var elementFocusBind = function() { - scope.$apply(function() { - setOpen( true ); - }); - }; - - // popup element used to display calendar - var popupEl = angular.element('
          '); - popupEl.attr({ - 'ng-model': 'date', - 'ng-change': 'dateSelection()' - }); - var datepickerEl = angular.element(popupEl.children()[0]), - datepickerOptions = {}; - if (attrs.datepickerOptions) { - datepickerOptions = originalScope.$eval(attrs.datepickerOptions); - datepickerEl.attr(angular.extend({}, datepickerOptions)); - } - - // TODO: reverse from dateFilter string to Date object - function parseDate(viewValue) { - if (!viewValue) { - ngModel.$setValidity('date', true); - return null; - } else if (angular.isDate(viewValue)) { - ngModel.$setValidity('date', true); - return viewValue; - } else if (angular.isString(viewValue)) { - var date = new Date(viewValue); - if (isNaN(date)) { - ngModel.$setValidity('date', false); - return undefined; - } else { - ngModel.$setValidity('date', true); - return date; - } - } else { - ngModel.$setValidity('date', false); - return undefined; - } - } - ngModel.$parsers.unshift(parseDate); - - // Inner change - scope.dateSelection = function(dt) { - if (angular.isDefined(dt)) { - scope.date = dt; - } - ngModel.$setViewValue(scope.date); - ngModel.$render(); - - if (closeOnDateSelection) { - setOpen( false ); - } - }; - - element.bind('input change keyup', function() { - scope.$apply(function() { - scope.date = ngModel.$modelValue; - }); - }); - - // Outter change - ngModel.$render = function() { - var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; - element.val(date); - scope.date = ngModel.$modelValue; - }; - - function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) { - if (attribute) { - originalScope.$watch($parse(attribute), function(value){ - scope[scopeProperty] = value; - }); - datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty); - } - } - addWatchableAttribute(attrs.min, 'min'); - addWatchableAttribute(attrs.max, 'max'); - if (attrs.showWeeks) { - addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks'); - } else { - scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks; - datepickerEl.attr('show-weeks', 'showWeeks'); - } - if (attrs.dateDisabled) { - datepickerEl.attr('date-disabled', attrs.dateDisabled); - } - - function updatePosition() { - scope.position = appendToBody ? $position.offset(element) : $position.position(element); - scope.position.top = scope.position.top + element.prop('offsetHeight'); - } - - var documentBindingInitialized = false, elementFocusInitialized = false; - scope.$watch('isOpen', function(value) { - if (value) { - updatePosition(); - $document.bind('click', documentClickBind); - if(elementFocusInitialized) { - element.unbind('focus', elementFocusBind); - } - element[0].focus(); - documentBindingInitialized = true; - } else { - if(documentBindingInitialized) { - $document.unbind('click', documentClickBind); - } - element.bind('focus', elementFocusBind); - elementFocusInitialized = true; - } - - if ( setIsOpen ) { - setIsOpen(originalScope, value); - } - }); - - scope.today = function() { - scope.dateSelection(new Date()); - }; - scope.clear = function() { - scope.dateSelection(null); - }; - - var $popup = $compile(popupEl)(scope); - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - } - }; -}]) - -.directive('datepickerPopupWrap', function() { - return { - restrict:'EA', - replace: true, - transclude: true, - templateUrl: 'template/datepicker/popup.html', - link:function (scope, element, attrs) { - element.bind('click', function(event) { - event.preventDefault(); - event.stopPropagation(); - }); - } - }; -}); - -/* - * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js - * @restrict class or attribute - * @example: - - */ - -angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) { - var openElement = null, - closeMenu = angular.noop; - return { - restrict: 'CA', - link: function(scope, element, attrs) { - scope.$watch('$location.path', function() { closeMenu(); }); - element.parent().bind('click', function() { closeMenu(); }); - element.bind('click', function (event) { - - var elementWasOpen = (element === openElement); - - event.preventDefault(); - event.stopPropagation(); - - if (!!openElement) { - closeMenu(); - } - - if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { - element.parent().addClass('open'); - openElement = element; - closeMenu = function (event) { - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - $document.unbind('click', closeMenu); - element.parent().removeClass('open'); - closeMenu = angular.noop; - openElement = null; - }; - $document.bind('click', closeMenu); - } - }); - } - }; -}]); - -angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) - -/** - * A helper, internal data structure that acts as a map but also allows getting / removing - * elements in the LIFO order - */ - .factory('$$stackedMap', function () { - return { - createNew: function () { - var stack = []; - - return { - add: function (key, value) { - stack.push({ - key: key, - value: value - }); - }, - get: function (key) { - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - return stack[i]; - } - } - }, - keys: function() { - var keys = []; - for (var i = 0; i < stack.length; i++) { - keys.push(stack[i].key); - } - return keys; - }, - top: function () { - return stack[stack.length - 1]; - }, - remove: function (key) { - var idx = -1; - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - idx = i; - break; - } - } - return stack.splice(idx, 1)[0]; - }, - removeTop: function () { - return stack.splice(stack.length - 1, 1)[0]; - }, - length: function () { - return stack.length; - } - }; - } - }; - }) - -/** - * A helper directive for the $modal service. It creates a backdrop element. - */ - .directive('modalBackdrop', ['$timeout', function ($timeout) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/modal/backdrop.html', - link: function (scope) { - - scope.animate = false; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - } - }; - }]) - - .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { - return { - restrict: 'EA', - scope: { - index: '@', - animate: '=' - }, - replace: true, - transclude: true, - templateUrl: 'template/modal/window.html', - link: function (scope, element, attrs) { - scope.windowClass = attrs.windowClass || ''; - - $timeout(function () { - // trigger CSS transitions - scope.animate = true; - // focus a freshly-opened modal - element[0].focus(); - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { - evt.preventDefault(); - evt.stopPropagation(); - $modalStack.dismiss(modal.key, 'backdrop click'); - } - }; - } - }; - }]) - - .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', - function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { - - var OPENED_MODAL_CLASS = 'modal-open'; - - var backdropDomEl, backdropScope; - var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; - - function backdropIndex() { - var topBackdropIndex = -1; - var opened = openedWindows.keys(); - for (var i = 0; i < opened.length; i++) { - if (openedWindows.get(opened[i]).value.backdrop) { - topBackdropIndex = i; - } - } - return topBackdropIndex; - } - - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ - if (backdropScope) { - backdropScope.index = newBackdropIndex; - } - }); - - function removeModalWindow(modalInstance) { - - var body = $document.find('body').eq(0); - var modalWindow = openedWindows.get(modalInstance).value; - - //clean up the stack - openedWindows.remove(modalInstance); - - //remove window DOM element - removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop); - body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); - } - - function checkRemoveBackdrop() { - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - var backdropScopeRef = backdropScope; - removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { - backdropScopeRef.$destroy(); - backdropScopeRef = null; - }); - backdropDomEl = undefined; - backdropScope = undefined; - } - } - - function removeAfterAnimate(domEl, scope, emulateTime, done) { - // Closing animation - scope.animate = false; - - var transitionEndEventName = $transition.transitionEndEventName; - if (transitionEndEventName) { - // transition out - var timeout = $timeout(afterAnimating, emulateTime); - - domEl.bind(transitionEndEventName, function () { - $timeout.cancel(timeout); - afterAnimating(); - scope.$apply(); - }); - } else { - // Ensure this call is async - $timeout(afterAnimating, 0); - } - - function afterAnimating() { - if (afterAnimating.done) { - return; - } - afterAnimating.done = true; - - domEl.remove(); - if (done) { - done(); - } - } - } - - $document.bind('keydown', function (evt) { - var modal; - - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key); - }); - } - } - }); - - $modalStack.open = function (modalInstance, modal) { - - openedWindows.add(modalInstance, { - deferred: modal.deferred, - modalScope: modal.scope, - backdrop: modal.backdrop, - keyboard: modal.keyboard - }); - - var body = $document.find('body').eq(0), - currBackdropIndex = backdropIndex(); - - if (currBackdropIndex >= 0 && !backdropDomEl) { - backdropScope = $rootScope.$new(true); - backdropScope.index = currBackdropIndex; - backdropDomEl = $compile('
          ')(backdropScope); - body.append(backdropDomEl); - } - - var angularDomEl = angular.element('
          '); - angularDomEl.attr('window-class', modal.windowClass); - angularDomEl.attr('index', openedWindows.length() - 1); - angularDomEl.attr('animate', 'animate'); - angularDomEl.html(modal.content); - - var modalDomEl = $compile(angularDomEl)(modal.scope); - openedWindows.top().value.modalDomEl = modalDomEl; - body.append(modalDomEl); - body.addClass(OPENED_MODAL_CLASS); - }; - - $modalStack.close = function (modalInstance, result) { - var modalWindow = openedWindows.get(modalInstance).value; - if (modalWindow) { - modalWindow.deferred.resolve(result); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismiss = function (modalInstance, reason) { - var modalWindow = openedWindows.get(modalInstance).value; - if (modalWindow) { - modalWindow.deferred.reject(reason); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismissAll = function (reason) { - var topModal = this.getTop(); - while (topModal) { - this.dismiss(topModal.key, reason); - topModal = this.getTop(); - } - }; - - $modalStack.getTop = function () { - return openedWindows.top(); - }; - - return $modalStack; - }]) - - .provider('$modal', function () { - - var $modalProvider = { - options: { - backdrop: true, //can be also false or 'static' - keyboard: true - }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - - var $modal = {}; - - function getTemplatePromise(options) { - return options.template ? $q.when(options.template) : - $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { - return result.data; - }); - } - - function getResolvePromises(resolves) { - var promisesArr = []; - angular.forEach(resolves, function (value, key) { - if (angular.isFunction(value) || angular.isArray(value)) { - promisesArr.push($q.when($injector.invoke(value))); - } - }); - return promisesArr; - } - - $modal.open = function (modalOptions) { - - var modalResultDeferred = $q.defer(); - var modalOpenedDeferred = $q.defer(); - - //prepare an instance of a modal to be injected into controllers and returned to a caller - var modalInstance = { - result: modalResultDeferred.promise, - opened: modalOpenedDeferred.promise, - close: function (result) { - $modalStack.close(modalInstance, result); - }, - dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); - } - }; - - //merge and clean up options - modalOptions = angular.extend({}, $modalProvider.options, modalOptions); - modalOptions.resolve = modalOptions.resolve || {}; - - //verify options - if (!modalOptions.template && !modalOptions.templateUrl) { - throw new Error('One of template or templateUrl options is required.'); - } - - var templateAndResolvePromise = - $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); - - - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { - - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; - - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; - }); - - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - } - - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - windowClass: modalOptions.windowClass - }); - - }, function resolveError(reason) { - modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); - }); - - return modalInstance; - }; - - return $modal; - }] - }; - - return $modalProvider; - }); - -angular.module('ui.bootstrap.pagination', []) - -.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) { - var self = this, - setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; - - this.init = function(defaultItemsPerPage) { - if ($attrs.itemsPerPage) { - $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { - self.itemsPerPage = parseInt(value, 10); - $scope.totalPages = self.calculateTotalPages(); - }); - } else { - this.itemsPerPage = defaultItemsPerPage; - } - }; - - this.noPrevious = function() { - return this.page === 1; - }; - this.noNext = function() { - return this.page === $scope.totalPages; - }; - - this.isActive = function(page) { - return this.page === page; - }; - - this.calculateTotalPages = function() { - var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); - return Math.max(totalPages || 0, 1); - }; - - this.getAttributeValue = function(attribute, defaultValue, interpolate) { - return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue; - }; - - this.render = function() { - this.page = parseInt($scope.page, 10) || 1; - if (this.page > 0 && this.page <= $scope.totalPages) { - $scope.pages = this.getPages(this.page, $scope.totalPages); - } - }; - - $scope.selectPage = function(page) { - if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) { - $scope.page = page; - $scope.onSelectPage({ page: page }); - } - }; - - $scope.$watch('page', function() { - self.render(); - }); - - $scope.$watch('totalItems', function() { - $scope.totalPages = self.calculateTotalPages(); - }); - - $scope.$watch('totalPages', function(value) { - setNumPages($scope.$parent, value); // Readonly variable - - if ( self.page > value ) { - $scope.selectPage(value); - } else { - self.render(); - } - }); -}]) - -.constant('paginationConfig', { - itemsPerPage: 10, - boundaryLinks: false, - directionLinks: true, - firstText: 'First', - previousText: 'Previous', - nextText: 'Next', - lastText: 'Last', - rotate: true -}) - -.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) { - return { - restrict: 'EA', - scope: { - page: '=', - totalItems: '=', - onSelectPage:' &' - }, - controller: 'PaginationController', - templateUrl: 'template/pagination/pagination.html', - replace: true, - link: function(scope, element, attrs, paginationCtrl) { - - // Setup configuration parameters - var maxSize, - boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ), - directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ), - firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true), - previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), - nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), - lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true), - rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate); - - paginationCtrl.init(config.itemsPerPage); - - if (attrs.maxSize) { - scope.$parent.$watch($parse(attrs.maxSize), function(value) { - maxSize = parseInt(value, 10); - paginationCtrl.render(); - }); - } - - // Create page object used in template - function makePage(number, text, isActive, isDisabled) { - return { - number: number, - text: text, - active: isActive, - disabled: isDisabled - }; - } - - paginationCtrl.getPages = function(currentPage, totalPages) { - var pages = []; - - // Default page limits - var startPage = 1, endPage = totalPages; - var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); - - // recompute if maxSize - if ( isMaxSized ) { - if ( rotate ) { - // Current page is displayed in the middle of the visible ones - startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); - endPage = startPage + maxSize - 1; - - // Adjust if limit is exceeded - if (endPage > totalPages) { - endPage = totalPages; - startPage = endPage - maxSize + 1; - } - } else { - // Visible pages are paginated with maxSize - startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; - - // Adjust last page if limit is exceeded - endPage = Math.min(startPage + maxSize - 1, totalPages); - } - } - - // Add page number links - for (var number = startPage; number <= endPage; number++) { - var page = makePage(number, number, paginationCtrl.isActive(number), false); - pages.push(page); - } - - // Add links to move between page sets - if ( isMaxSized && ! rotate ) { - if ( startPage > 1 ) { - var previousPageSet = makePage(startPage - 1, '...', false, false); - pages.unshift(previousPageSet); - } - - if ( endPage < totalPages ) { - var nextPageSet = makePage(endPage + 1, '...', false, false); - pages.push(nextPageSet); - } - } - - // Add previous & next links - if (directionLinks) { - var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious()); - pages.unshift(previousPage); - - var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext()); - pages.push(nextPage); - } - - // Add first & last links - if (boundaryLinks) { - var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious()); - pages.unshift(firstPage); - - var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext()); - pages.push(lastPage); - } - - return pages; - }; - } - }; -}]) - -.constant('pagerConfig', { - itemsPerPage: 10, - previousText: '« Previous', - nextText: 'Next »', - align: true -}) - -.directive('pager', ['pagerConfig', function(config) { - return { - restrict: 'EA', - scope: { - page: '=', - totalItems: '=', - onSelectPage:' &' - }, - controller: 'PaginationController', - templateUrl: 'template/pagination/pager.html', - replace: true, - link: function(scope, element, attrs, paginationCtrl) { - - // Setup configuration parameters - var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true), - nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true), - align = paginationCtrl.getAttributeValue(attrs.align, config.align); - - paginationCtrl.init(config.itemsPerPage); - - // Create page object used in template - function makePage(number, text, isDisabled, isPrevious, isNext) { - return { - number: number, - text: text, - disabled: isDisabled, - previous: ( align && isPrevious ), - next: ( align && isNext ) - }; - } - - paginationCtrl.getPages = function(currentPage) { - return [ - makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false), - makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true) - ]; - }; - } - }; -}]); - -/** - * The following features are still outstanding: animation as a - * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html tooltips, and selector delegation. - */ -angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) - -/** - * The $tooltip service creates tooltip- and popover-like directives as well as - * houses global options for them. - */ -.provider( '$tooltip', function () { - // The default options tooltip and popover. - var defaultOptions = { - placement: 'top', - animation: true, - popupDelay: 0 - }; - - // Default hide triggers for each show trigger - var triggerMap = { - 'mouseenter': 'mouseleave', - 'click': 'click', - 'focus': 'blur' - }; - - // The options specified to the provider globally. - var globalOptions = {}; - - /** - * `options({})` allows global configuration of all tooltips in the - * application. - * - * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { - * // place tooltips left instead of top by default - * $tooltipProvider.options( { placement: 'left' } ); - * }); - */ - this.options = function( value ) { - angular.extend( globalOptions, value ); - }; - - /** - * This allows you to extend the set of trigger mappings available. E.g.: - * - * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); - */ - this.setTriggers = function setTriggers ( triggers ) { - angular.extend( triggerMap, triggers ); - }; - - /** - * This is a helper function for translating camel-case to snake-case. - */ - function snake_case(name){ - var regexp = /[A-Z]/g; - var separator = '-'; - return name.replace(regexp, function(letter, pos) { - return (pos ? separator : '') + letter.toLowerCase(); - }); - } - - /** - * Returns the actual instance of the $tooltip service. - * TODO support multiple triggers - */ - this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { - return function $tooltip ( type, prefix, defaultTriggerShow ) { - var options = angular.extend( {}, defaultOptions, globalOptions ); - - /** - * Returns an object of show and hide triggers. - * - * If a trigger is supplied, - * it is used to show the tooltip; otherwise, it will use the `trigger` - * option passed to the `$tooltipProvider.options` method; else it will - * default to the trigger supplied to this directive factory. - * - * The hide trigger is based on the show trigger. If the `trigger` option - * was passed to the `$tooltipProvider.options` method, it will use the - * mapped trigger from `triggerMap` or the passed trigger if the map is - * undefined; otherwise, it uses the `triggerMap` value of the show - * trigger; else it will just use the show trigger. - */ - function getTriggers ( trigger ) { - var show = trigger || options.trigger || defaultTriggerShow; - var hide = triggerMap[show] || show; - return { - show: show, - hide: hide - }; - } - - var directiveName = snake_case( type ); - - var startSym = $interpolate.startSymbol(); - var endSym = $interpolate.endSymbol(); - var template = - '
          '+ - '
          '; - - return { - restrict: 'EA', - scope: true, - compile: function (tElem, tAttrs) { - var tooltipLinker = $compile( template ); - - return function link ( scope, element, attrs ) { - var tooltip; - var transitionTimeout; - var popupTimeout; - var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; - var triggers = getTriggers( undefined ); - var hasRegisteredTriggers = false; - var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); - - var positionTooltip = function (){ - var position, - ttWidth, - ttHeight, - ttPosition; - // Get the position of the directive element. - position = appendToBody ? $position.offset( element ) : $position.position( element ); - - // Get the height and width of the tooltip so we can center it. - ttWidth = tooltip.prop( 'offsetWidth' ); - ttHeight = tooltip.prop( 'offsetHeight' ); - - // Calculate the tooltip's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: position.top + position.height / 2 - ttHeight / 2, - left: position.left + position.width - }; - break; - case 'bottom': - ttPosition = { - top: position.top + position.height, - left: position.left + position.width / 2 - ttWidth / 2 - }; - break; - case 'left': - ttPosition = { - top: position.top + position.height / 2 - ttHeight / 2, - left: position.left - ttWidth - }; - break; - default: - ttPosition = { - top: position.top - ttHeight, - left: position.left + position.width / 2 - ttWidth / 2 - }; - break; - } - - ttPosition.top += 'px'; - ttPosition.left += 'px'; - - // Now set the calculated positioning. - tooltip.css( ttPosition ); - - }; - - // By default, the tooltip is not open. - // TODO add ability to start tooltip opened - scope.tt_isOpen = false; - - function toggleTooltipBind () { - if ( ! scope.tt_isOpen ) { - showTooltipBind(); - } else { - hideTooltipBind(); - } - } - - // Show the tooltip with delay if specified, otherwise show it immediately - function showTooltipBind() { - if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { - return; - } - if ( scope.tt_popupDelay ) { - popupTimeout = $timeout( show, scope.tt_popupDelay, false ); - popupTimeout.then(function(reposition){reposition();}); - } else { - show()(); - } - } - - function hideTooltipBind () { - scope.$apply(function () { - hide(); - }); - } - - // Show the tooltip popup element. - function show() { - - - // Don't show empty tooltips. - if ( ! scope.tt_content ) { - return angular.noop; - } - - createTooltip(); - - // If there is a pending remove transition, we must cancel it, lest the - // tooltip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - if ( appendToBody ) { - $document.find( 'body' ).append( tooltip ); - } else { - element.after( tooltip ); - } - - positionTooltip(); - - // And show the tooltip. - scope.tt_isOpen = true; - scope.$digest(); // digest required as $apply is not called - - // Return positioning function as promise callback for correct - // positioning after draw. - return positionTooltip; - } - - // Hide the tooltip popup element. - function hide() { - // First things first: we don't show it anymore. - scope.tt_isOpen = false; - - //if tooltip is going to be shown after delay, we must cancel this - $timeout.cancel( popupTimeout ); - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( scope.tt_animation ) { - transitionTimeout = $timeout(removeTooltip, 500); - } else { - removeTooltip(); - } - } - - function createTooltip() { - // There can only be one tooltip element per directive shown at once. - if (tooltip) { - removeTooltip(); - } - tooltip = tooltipLinker(scope, function () {}); - - // Get contents rendered into the tooltip - scope.$digest(); - } - - function removeTooltip() { - if (tooltip) { - tooltip.remove(); - tooltip = null; - } - } - - /** - * Observe the relevant attributes. - */ - attrs.$observe( type, function ( val ) { - scope.tt_content = val; - - if (!val && scope.tt_isOpen ) { - hide(); - } - }); - - attrs.$observe( prefix+'Title', function ( val ) { - scope.tt_title = val; - }); - - attrs.$observe( prefix+'Placement', function ( val ) { - scope.tt_placement = angular.isDefined( val ) ? val : options.placement; - }); - - attrs.$observe( prefix+'PopupDelay', function ( val ) { - var delay = parseInt( val, 10 ); - scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; - }); - - var unregisterTriggers = function() { - if (hasRegisteredTriggers) { - element.unbind( triggers.show, showTooltipBind ); - element.unbind( triggers.hide, hideTooltipBind ); - } - }; - - attrs.$observe( prefix+'Trigger', function ( val ) { - unregisterTriggers(); - - triggers = getTriggers( val ); - - if ( triggers.show === triggers.hide ) { - element.bind( triggers.show, toggleTooltipBind ); - } else { - element.bind( triggers.show, showTooltipBind ); - element.bind( triggers.hide, hideTooltipBind ); - } - - hasRegisteredTriggers = true; - }); - - var animation = scope.$eval(attrs[prefix + 'Animation']); - scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; - - attrs.$observe( prefix+'AppendToBody', function ( val ) { - appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; - }); - - // if a tooltip is attached to we need to remove it on - // location change as its parent scope will probably not be destroyed - // by the change. - if ( appendToBody ) { - scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { - if ( scope.tt_isOpen ) { - hide(); - } - }); - } - - // Make sure tooltip is destroyed and removed. - scope.$on('$destroy', function onDestroyTooltip() { - $timeout.cancel( transitionTimeout ); - $timeout.cancel( popupTimeout ); - unregisterTriggers(); - removeTooltip(); - }); - }; - } - }; - }; - }]; -}) - -.directive( 'tooltipPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' - }; -}) - -.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); -}]) - -.directive( 'tooltipHtmlUnsafePopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' - }; -}) - -.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); -}]); - -/** - * The following features are still outstanding: popup delay, animation as a - * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html popovers, and selector delegatation. - */ -angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) - -.directive( 'popoverPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/popover/popover.html' - }; -}) - -.directive( 'popover', [ '$tooltip', function ( $tooltip ) { - return $tooltip( 'popover', 'popover', 'click' ); -}]); - -angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition']) - -.constant('progressConfig', { - animate: true, - max: 100 -}) - -.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) { - var self = this, - bars = [], - max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max, - animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; - - this.addBar = function(bar, element) { - var oldValue = 0, index = bar.$parent.$index; - if ( angular.isDefined(index) && bars[index] ) { - oldValue = bars[index].value; - } - bars.push(bar); - - this.update(element, bar.value, oldValue); - - bar.$watch('value', function(value, oldValue) { - if (value !== oldValue) { - self.update(element, value, oldValue); - } - }); - - bar.$on('$destroy', function() { - self.removeBar(bar); - }); - }; - - // Update bar element width - this.update = function(element, newValue, oldValue) { - var percent = this.getPercentage(newValue); - - if (animate) { - element.css('width', this.getPercentage(oldValue) + '%'); - $transition(element, {width: percent + '%'}); - } else { - element.css({'transition': 'none', 'width': percent + '%'}); - } - }; - - this.removeBar = function(bar) { - bars.splice(bars.indexOf(bar), 1); - }; - - this.getPercentage = function(value) { - return Math.round(100 * value / max); - }; -}]) - -.directive('progress', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - require: 'progress', - scope: {}, - template: '
          ' - //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2 - }; -}) - -.directive('bar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - require: '^progress', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/bar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, element); - } - }; -}) - -.directive('progressbar', function() { - return { - restrict: 'EA', - replace: true, - transclude: true, - controller: 'ProgressController', - scope: { - value: '=', - type: '@' - }, - templateUrl: 'template/progressbar/progressbar.html', - link: function(scope, element, attrs, progressCtrl) { - progressCtrl.addBar(scope, angular.element(element.children()[0])); - } - }; -}); -angular.module('ui.bootstrap.rating', []) - -.constant('ratingConfig', { - max: 5, - stateOn: null, - stateOff: null -}) - -.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) { - - this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max; - this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; - this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; - - this.createRateObjects = function(states) { - var defaultOptions = { - stateOn: this.stateOn, - stateOff: this.stateOff - }; - - for (var i = 0, n = states.length; i < n; i++) { - states[i] = angular.extend({ index: i }, defaultOptions, states[i]); - } - return states; - }; - - // Get objects used in template - $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange)); - - $scope.rate = function(value) { - if ( $scope.value !== value && !$scope.readonly ) { - $scope.value = value; - } - }; - - $scope.enter = function(value) { - if ( ! $scope.readonly ) { - $scope.val = value; - } - $scope.onHover({value: value}); - }; - - $scope.reset = function() { - $scope.val = angular.copy($scope.value); - $scope.onLeave(); - }; - - $scope.$watch('value', function(value) { - $scope.val = value; - }); - - $scope.readonly = false; - if ($attrs.readonly) { - $scope.$parent.$watch($parse($attrs.readonly), function(value) { - $scope.readonly = !!value; - }); - } -}]) - -.directive('rating', function() { - return { - restrict: 'EA', - scope: { - value: '=', - onHover: '&', - onLeave: '&' - }, - controller: 'RatingController', - templateUrl: 'template/rating/rating.html', - replace: true - }; -}); - -/** - * @ngdoc overview - * @name ui.bootstrap.tabs - * - * @description - * AngularJS version of the tabs directive. - */ - -angular.module('ui.bootstrap.tabs', []) - -.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { - var ctrl = this, - tabs = ctrl.tabs = $scope.tabs = []; - - ctrl.select = function(tab) { - angular.forEach(tabs, function(tab) { - tab.active = false; - }); - tab.active = true; - }; - - ctrl.addTab = function addTab(tab) { - tabs.push(tab); - if (tabs.length === 1 || tab.active) { - ctrl.select(tab); - } - }; - - ctrl.removeTab = function removeTab(tab) { - var index = tabs.indexOf(tab); - //Select a new tab if the tab to be removed is selected - if (tab.active && tabs.length > 1) { - //If this is the last tab, select the previous tab. else, the next tab. - var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; - ctrl.select(tabs[newActiveIndex]); - } - tabs.splice(index, 1); - }; -}]) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabset - * @restrict EA - * - * @description - * Tabset is the outer container for the tabs directive - * - * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. - * @param {boolean=} justified Whether or not to use justified styling for the tabs. - * - * @example - - - - First Content! - Second Content! - -
          - - First Vertical Content! - Second Vertical Content! - - - First Justified Content! - Second Justified Content! - -
          -
          - */ -.directive('tabset', function() { - return { - restrict: 'EA', - transclude: true, - replace: true, - scope: {}, - controller: 'TabsetController', - templateUrl: 'template/tabs/tabset.html', - link: function(scope, element, attrs) { - scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; - scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; - scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs'; - } - }; -}) - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tab - * @restrict EA - * - * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. - * @param {string=} select An expression to evaluate when the tab is selected. - * @param {boolean=} active A binding, telling whether or not this tab is selected. - * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. - * - * @description - * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. - * - * @example - - -
          - - -
          - - First Tab - - Alert me! - Second Tab, with alert callback and html heading! - - - {{item.content}} - - -
          -
          - - function TabsDemoCtrl($scope) { - $scope.items = [ - { title:"Dynamic Title 1", content:"Dynamic Item 0" }, - { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } - ]; - - $scope.alertMe = function() { - setTimeout(function() { - alert("You've selected the alert tab!"); - }); - }; - }; - -
          - */ - -/** - * @ngdoc directive - * @name ui.bootstrap.tabs.directive:tabHeading - * @restrict EA - * - * @description - * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. - * - * @example - - - - - HTML in my titles?! - And some content, too! - - - Icon heading?!? - That's right. - - - - - */ -.directive('tab', ['$parse', function($parse) { - return { - require: '^tabset', - restrict: 'EA', - replace: true, - templateUrl: 'template/tabs/tab.html', - transclude: true, - scope: { - heading: '@', - onSelect: '&select', //This callback is called in contentHeadingTransclude - //once it inserts the tab's content into the dom - onDeselect: '&deselect' - }, - controller: function() { - //Empty controller so other directives can require being 'under' a tab - }, - compile: function(elm, attrs, transclude) { - return function postLink(scope, elm, attrs, tabsetCtrl) { - var getActive, setActive; - if (attrs.active) { - getActive = $parse(attrs.active); - setActive = getActive.assign; - scope.$parent.$watch(getActive, function updateActive(value, oldVal) { - // Avoid re-initializing scope.active as it is already initialized - // below. (watcher is called async during init with value === - // oldVal) - if (value !== oldVal) { - scope.active = !!value; - } - }); - scope.active = getActive(scope.$parent); - } else { - setActive = getActive = angular.noop; - } - - scope.$watch('active', function(active) { - // Note this watcher also initializes and assigns scope.active to the - // attrs.active expression. - setActive(scope.$parent, active); - if (active) { - tabsetCtrl.select(scope); - scope.onSelect(); - } else { - scope.onDeselect(); - } - }); - - scope.disabled = false; - if ( attrs.disabled ) { - scope.$parent.$watch($parse(attrs.disabled), function(value) { - scope.disabled = !! value; - }); - } - - scope.select = function() { - if ( ! scope.disabled ) { - scope.active = true; - } - }; - - tabsetCtrl.addTab(scope); - scope.$on('$destroy', function() { - tabsetCtrl.removeTab(scope); - }); - - - //We need to transclude later, once the content container is ready. - //when this link happens, we're inside a tab heading. - scope.$transcludeFn = transclude; - }; - } - }; -}]) - -.directive('tabHeadingTransclude', [function() { - return { - restrict: 'A', - require: '^tab', - link: function(scope, elm, attrs, tabCtrl) { - scope.$watch('headingElement', function updateHeadingElement(heading) { - if (heading) { - elm.html(''); - elm.append(heading); - } - }); - } - }; -}]) - -.directive('tabContentTransclude', function() { - return { - restrict: 'A', - require: '^tabset', - link: function(scope, elm, attrs) { - var tab = scope.$eval(attrs.tabContentTransclude); - - //Now our tab is ready to be transcluded: both the tab heading area - //and the tab content area are loaded. Transclude 'em both. - tab.$transcludeFn(tab.$parent, function(contents) { - angular.forEach(contents, function(node) { - if (isTabHeading(node)) { - //Let tabHeadingTransclude know. - tab.headingElement = node; - } else { - elm.append(node); - } - }); - }); - } - }; - function isTabHeading(node) { - return node.tagName && ( - node.hasAttribute('tab-heading') || - node.hasAttribute('data-tab-heading') || - node.tagName.toLowerCase() === 'tab-heading' || - node.tagName.toLowerCase() === 'data-tab-heading' - ); - } -}) - -; - -angular.module('ui.bootstrap.timepicker', []) - -.constant('timepickerConfig', { - hourStep: 1, - minuteStep: 1, - showMeridian: true, - meridians: null, - readonlyInput: false, - mousewheel: true -}) - -.directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) { - return { - restrict: 'EA', - require:'?^ngModel', - replace: true, - scope: {}, - templateUrl: 'template/timepicker/timepicker.html', - link: function(scope, element, attrs, ngModel) { - if ( !ngModel ) { - return; // do nothing if no ng-model - } - - var selected = new Date(), - meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; - - var hourStep = timepickerConfig.hourStep; - if (attrs.hourStep) { - scope.$parent.$watch($parse(attrs.hourStep), function(value) { - hourStep = parseInt(value, 10); - }); - } - - var minuteStep = timepickerConfig.minuteStep; - if (attrs.minuteStep) { - scope.$parent.$watch($parse(attrs.minuteStep), function(value) { - minuteStep = parseInt(value, 10); - }); - } - - // 12H / 24H mode - scope.showMeridian = timepickerConfig.showMeridian; - if (attrs.showMeridian) { - scope.$parent.$watch($parse(attrs.showMeridian), function(value) { - scope.showMeridian = !!value; - - if ( ngModel.$error.time ) { - // Evaluate from template - var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); - if (angular.isDefined( hours ) && angular.isDefined( minutes )) { - selected.setHours( hours ); - refresh(); - } - } else { - updateTemplate(); - } - }); - } - - // Get scope.hours in 24H mode if valid - function getHoursFromTemplate ( ) { - var hours = parseInt( scope.hours, 10 ); - var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); - if ( !valid ) { - return undefined; - } - - if ( scope.showMeridian ) { - if ( hours === 12 ) { - hours = 0; - } - if ( scope.meridian === meridians[1] ) { - hours = hours + 12; - } - } - return hours; - } - - function getMinutesFromTemplate() { - var minutes = parseInt(scope.minutes, 10); - return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; - } - - function pad( value ) { - return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; - } - - // Input elements - var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1); - - // Respond on mousewheel spin - var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel; - if ( mousewheel ) { - - var isScrollingUp = function(e) { - if (e.originalEvent) { - e = e.originalEvent; - } - //pick correct delta variable depending on event - var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; - return (e.detail || delta > 0); - }; - - hoursInputEl.bind('mousewheel wheel', function(e) { - scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() ); - e.preventDefault(); - }); - - minutesInputEl.bind('mousewheel wheel', function(e) { - scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() ); - e.preventDefault(); - }); - } - - scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput; - if ( ! scope.readonlyInput ) { - - var invalidate = function(invalidHours, invalidMinutes) { - ngModel.$setViewValue( null ); - ngModel.$setValidity('time', false); - if (angular.isDefined(invalidHours)) { - scope.invalidHours = invalidHours; - } - if (angular.isDefined(invalidMinutes)) { - scope.invalidMinutes = invalidMinutes; - } - }; - - scope.updateHours = function() { - var hours = getHoursFromTemplate(); - - if ( angular.isDefined(hours) ) { - selected.setHours( hours ); - refresh( 'h' ); - } else { - invalidate(true); - } - }; - - hoursInputEl.bind('blur', function(e) { - if ( !scope.validHours && scope.hours < 10) { - scope.$apply( function() { - scope.hours = pad( scope.hours ); - }); - } - }); - - scope.updateMinutes = function() { - var minutes = getMinutesFromTemplate(); - - if ( angular.isDefined(minutes) ) { - selected.setMinutes( minutes ); - refresh( 'm' ); - } else { - invalidate(undefined, true); - } - }; - - minutesInputEl.bind('blur', function(e) { - if ( !scope.invalidMinutes && scope.minutes < 10 ) { - scope.$apply( function() { - scope.minutes = pad( scope.minutes ); - }); - } - }); - } else { - scope.updateHours = angular.noop; - scope.updateMinutes = angular.noop; - } - - ngModel.$render = function() { - var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null; - - if ( isNaN(date) ) { - ngModel.$setValidity('time', false); - $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); - } else { - if ( date ) { - selected = date; - } - makeValid(); - updateTemplate(); - } - }; - - // Call internally when we know that model is valid. - function refresh( keyboardChange ) { - makeValid(); - ngModel.$setViewValue( new Date(selected) ); - updateTemplate( keyboardChange ); - } - - function makeValid() { - ngModel.$setValidity('time', true); - scope.invalidHours = false; - scope.invalidMinutes = false; - } - - function updateTemplate( keyboardChange ) { - var hours = selected.getHours(), minutes = selected.getMinutes(); - - if ( scope.showMeridian ) { - hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system - } - scope.hours = keyboardChange === 'h' ? hours : pad(hours); - scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); - scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; - } - - function addMinutes( minutes ) { - var dt = new Date( selected.getTime() + minutes * 60000 ); - selected.setHours( dt.getHours(), dt.getMinutes() ); - refresh(); - } - - scope.incrementHours = function() { - addMinutes( hourStep * 60 ); - }; - scope.decrementHours = function() { - addMinutes( - hourStep * 60 ); - }; - scope.incrementMinutes = function() { - addMinutes( minuteStep ); - }; - scope.decrementMinutes = function() { - addMinutes( - minuteStep ); - }; - scope.toggleMeridian = function() { - addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); - }; - } - }; -}]); - -angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) - -/** - * A helper service that can parse typeahead's syntax (string provided by users) - * Extracted to a separate service for ease of unit testing - */ - .factory('typeaheadParser', ['$parse', function ($parse) { - - // 00000111000000000000022200000000000000003333333333333330000000000044000 - var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; - - return { - parse:function (input) { - - var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source; - if (!match) { - throw new Error( - "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + - " but got '" + input + "'."); - } - - return { - itemName:match[3], - source:$parse(match[4]), - viewMapper:$parse(match[2] || match[1]), - modelMapper:$parse(match[1]) - }; - } - }; -}]) - - .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', - function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { - - var HOT_KEYS = [9, 13, 27, 38, 40]; - - return { - require:'ngModel', - link:function (originalScope, element, attrs, modelCtrl) { - - //SUPPORTED ATTRIBUTES (OPTIONS) - - //minimal no of characters that needs to be entered before typeahead kicks-in - var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; - - //minimal wait time after last character typed before typehead kicks-in - var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; - - //should it restrict model values to the ones selected from the popup only? - var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; - - //binding to a variable that indicates if matches are being retrieved asynchronously - var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; - - //a callback executed when a match is selected - var onSelectCallback = $parse(attrs.typeaheadOnSelect); - - var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; - - var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false; - - //INTERNAL VARIABLES - - //model setter executed upon match selection - var $setModelValue = $parse(attrs.ngModel).assign; - - //expressions used by typeahead - var parserResult = typeaheadParser.parse(attrs.typeahead); - - var hasFocus; - - //pop-up element used to display matches - var popUpEl = angular.element('
          '); - popUpEl.attr({ - matches: 'matches', - active: 'activeIdx', - select: 'select(activeIdx)', - query: 'query', - position: 'position' - }); - //custom item template - if (angular.isDefined(attrs.typeaheadTemplateUrl)) { - popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); - } - - //create a child scope for the typeahead directive so we are not polluting original scope - //with typeahead-specific data (matches, query etc.) - var scope = originalScope.$new(); - originalScope.$on('$destroy', function(){ - scope.$destroy(); - }); - - var resetMatches = function() { - scope.matches = []; - scope.activeIdx = -1; - }; - - var getMatchesAsync = function(inputValue) { - - var locals = {$viewValue: inputValue}; - isLoadingSetter(originalScope, true); - $q.when(parserResult.source(originalScope, locals)).then(function(matches) { - - //it might happen that several async queries were in progress if a user were typing fast - //but we are interested only in responses that correspond to the current view value - if (inputValue === modelCtrl.$viewValue && hasFocus) { - if (matches.length > 0) { - - scope.activeIdx = 0; - scope.matches.length = 0; - - //transform labels - for(var i=0; i= minSearch) { - if (waitTime > 0) { - if (timeoutPromise) { - $timeout.cancel(timeoutPromise);//cancel previous timeout - } - timeoutPromise = $timeout(function () { - getMatchesAsync(inputValue); - }, waitTime); - } else { - getMatchesAsync(inputValue); - } - } else { - isLoadingSetter(originalScope, false); - resetMatches(); - } - - if (isEditable) { - return inputValue; - } else { - if (!inputValue) { - // Reset in case user had typed something previously. - modelCtrl.$setValidity('editable', true); - return inputValue; - } else { - modelCtrl.$setValidity('editable', false); - return undefined; - } - } - }); - - modelCtrl.$formatters.push(function (modelValue) { - - var candidateViewValue, emptyViewValue; - var locals = {}; - - if (inputFormatter) { - - locals['$model'] = modelValue; - return inputFormatter(originalScope, locals); - - } else { - - //it might happen that we don't have enough info to properly render input value - //we need to check for this situation and simply return model value if we can't apply custom formatting - locals[parserResult.itemName] = modelValue; - candidateViewValue = parserResult.viewMapper(originalScope, locals); - locals[parserResult.itemName] = undefined; - emptyViewValue = parserResult.viewMapper(originalScope, locals); - - return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; - } - }); - - scope.select = function (activeIdx) { - //called from within the $digest() cycle - var locals = {}; - var model, item; - - locals[parserResult.itemName] = item = scope.matches[activeIdx].model; - model = parserResult.modelMapper(originalScope, locals); - $setModelValue(originalScope, model); - modelCtrl.$setValidity('editable', true); - - onSelectCallback(originalScope, { - $item: item, - $model: model, - $label: parserResult.viewMapper(originalScope, locals) - }); - - resetMatches(); - - //return focus to the input element if a mach was selected via a mouse click event - element[0].focus(); - }; - - //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) - element.bind('keydown', function (evt) { - - //typeahead is open and an "interesting" key was pressed - if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { - return; - } - - evt.preventDefault(); - - if (evt.which === 40) { - scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; - scope.$digest(); - - } else if (evt.which === 38) { - scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; - scope.$digest(); - - } else if (evt.which === 13 || evt.which === 9) { - scope.$apply(function () { - scope.select(scope.activeIdx); - }); - - } else if (evt.which === 27) { - evt.stopPropagation(); - - resetMatches(); - scope.$digest(); - } - }); - - element.bind('blur', function (evt) { - hasFocus = false; - }); - - // Keep reference to click handler to unbind it. - var dismissClickHandler = function (evt) { - if (element[0] !== evt.target) { - resetMatches(); - scope.$digest(); - } - }; - - $document.bind('click', dismissClickHandler); - - originalScope.$on('$destroy', function(){ - $document.unbind('click', dismissClickHandler); - }); - - var $popup = $compile(popUpEl)(scope); - if ( appendToBody ) { - $document.find('body').append($popup); - } else { - element.after($popup); - } - } - }; - -}]) - - .directive('typeaheadPopup', function () { - return { - restrict:'EA', - scope:{ - matches:'=', - query:'=', - active:'=', - position:'=', - select:'&' - }, - replace:true, - templateUrl:'template/typeahead/typeahead-popup.html', - link:function (scope, element, attrs) { - - scope.templateUrl = attrs.templateUrl; - - scope.isOpen = function () { - return scope.matches.length > 0; - }; - - scope.isActive = function (matchIdx) { - return scope.active == matchIdx; - }; - - scope.selectActive = function (matchIdx) { - scope.active = matchIdx; - }; - - scope.selectMatch = function (activeIdx) { - scope.select({activeIdx:activeIdx}); - }; - } - }; - }) - - .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { - return { - restrict:'EA', - scope:{ - index:'=', - match:'=', - query:'=' - }, - link:function (scope, element, attrs) { - var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; - $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ - element.replaceWith($compile(tplContent.trim())(scope)); - }); - } - }; - }]) - - .filter('typeaheadHighlight', function() { - - function escapeRegexp(queryToEscape) { - return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - } - - return function(matchItem, query) { - return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; - }; - }); -angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/accordion/accordion-group.html", - "
          \n" + - "
          \n" + - "

          \n" + - " {{heading}}\n" + - "

          \n" + - "
          \n" + - "
          \n" + - "
          \n" + - "
          \n" + - "
          "); -}]); - -angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/accordion/accordion.html", - "
          "); -}]); - -angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/alert/alert.html", - "
          \n" + - " \n" + - "
          \n" + - "
          \n" + - ""); -}]); - -angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/carousel/carousel.html", - "
          \n" + - "
            1\">\n" + - "
          1. \n" + - "
          \n" + - "
          \n" + - " 1\">\n" + - " 1\">\n" + - "
          \n" + - ""); -}]); - -angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/carousel/slide.html", - "
          \n" + - ""); -}]); - -angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/datepicker.html", - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " 0\" class=\"h6\">\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
          #{{label}}
          {{ getWeekNumber(row) }}\n" + - " \n" + - "
          \n" + - ""); -}]); - -angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/datepicker/popup.html", - "
            \n" + - "
          • \n" + - "
          • \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
          • \n" + - "
          \n" + - ""); -}]); - -angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/backdrop.html", - "
          "); -}]); - -angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/window.html", - "
          \n" + - "
          \n" + - "
          "); -}]); - -angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/pagination/pager.html", - ""); -}]); - -angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/pagination/pagination.html", - ""); -}]); - -angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", - "
          \n" + - "
          \n" + - "
          \n" + - "
          \n" + - ""); -}]); - -angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tooltip/tooltip-popup.html", - "
          \n" + - "
          \n" + - "
          \n" + - "
          \n" + - ""); -}]); - -angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/popover/popover.html", - "
          \n" + - "
          \n" + - "\n" + - "
          \n" + - "

          \n" + - "
          \n" + - "
          \n" + - "
          \n" + - ""); -}]); - -angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/bar.html", - "
          "); -}]); - -angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/progress.html", - "
          "); -}]); - -angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/progressbar/progressbar.html", - "
          "); -}]); - -angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/rating/rating.html", - "\n" + - " \n" + - ""); -}]); - -angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tab.html", - "
        • \n" + - " {{heading}}\n" + - "
        • \n" + - ""); -}]); - -angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tabset-titles.html", - "
            \n" + - "
          \n" + - ""); -}]); - -angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/tabs/tabset.html", - "\n" + - "
          \n" + - "
            \n" + - "
            \n" + - "
            \n" + - "
            \n" + - "
            \n" + - "
            \n" + - ""); -}]); - -angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/timepicker/timepicker.html", - "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
             
            \n" + - " \n" + - " :\n" + - " \n" + - "
             
            \n" + - ""); -}]); - -angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/typeahead/typeahead-match.html", - ""); -}]); - -angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/typeahead/typeahead-popup.html", - "
              \n" + - "
            • \n" + - "
              \n" + - "
            • \n" + - "
            "); -}]); - -/* - @license Angular Treeview version 0.1.6 - ⓒ 2013 AHN JAE-HA http://github.com/eu81273/angular.treeview - License: MIT - - - [TREE attribute] - angular-treeview: the treeview directive - tree-id : each tree's unique id. - tree-model : the tree model on $scope. - node-id : each node's id - node-label : each node's label - node-children: each node's children - -
            -
            -*/ - -(function ( angular ) { - 'use strict'; - - angular.module( 'angularTreeview', [] ).directive( 'treeModel', ['$compile', function( $compile ) { - return { - restrict: 'A', - link: function ( scope, element, attrs ) { - //tree id - var treeId = attrs.treeId; - - //tree model - var treeModel = attrs.treeModel; - - //node id - var nodeId = attrs.nodeId || 'id'; - - //node label - var nodeLabel = attrs.nodeLabel || 'label'; - - //children - var nodeChildren = attrs.nodeChildren || 'children'; - - //tree template - var template = - '
              ' + - '
            • ' + - '' + - '' + - ' ' + - '{{node.' + nodeLabel + '}}' + - '
              ' + - '
            • ' + - '
            '; - - - //check tree id, tree model - if( treeId && treeModel ) { - - //root node - if( attrs.angularTreeview ) { - - //create tree object if not exists - scope[treeId] = scope[treeId] || {}; - - //if node head clicks, - scope[treeId].selectNodeHead = scope[treeId].selectNodeHead || function( selectedNode ){ - - //Collapse or Expand - selectedNode.collapsed = !selectedNode.collapsed; - }; - - //if node label clicks, - scope[treeId].selectNodeLabel = scope[treeId].selectNodeLabel || function( selectedNode ){ - - //remove highlight from previous node - if( scope[treeId].currentNode && scope[treeId].currentNode.selected ) { - scope[treeId].currentNode.selected = undefined; - } - - //set highlight to selected node - selectedNode.selected = 'selected'; - - //set currentNode - scope[treeId].currentNode = selectedNode; - }; - } - - //Rendering template. - element.html('').append( $compile( template )( scope ) ); - } - } - }; - }]); -})( angular ); - -(function(angular, factory) { - 'use strict'; - - if (typeof define === 'function' && define.amd) { - define(['angular'], function(angular) { - return factory(angular); - }); - } else { - return factory(angular); - } -}(angular || null, function(angular) { - 'use strict'; -/** - * ngTable: Table + Angular JS - * - * @author Vitalii Savchuk - * @url https://github.com/esvit/ng-table/ - * @license New BSD License - */ - -/** - * @ngdoc module - * @name ngTable - * @description ngTable: Table + Angular JS - * @example - - - - - - - - -
            {{user.name}}{{user.age}}
            -
            -
            - */ -var app = angular.module('ngTable', []); -/** - * ngTable: Table + Angular JS - * - * @author Vitalii Savchuk - * @url https://github.com/esvit/ng-table/ - * @license New BSD License - */ - -/** - * @ngdoc service - * @name ngTable.factory:ngTableParams - * @description Parameters manager for ngTable - */ -app.factory('ngTableParams', ['$q', '$log', function ($q, $log) { - var isNumber = function (n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }; - var ngTableParams = function (baseParameters, baseSettings) { - var self = this, - log = function () { - if (settings.debugMode && $log.debug) { - $log.debug.apply(this, arguments); - } - }; - - this.data = []; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#parameters - * @methodOf ngTable.factory:ngTableParams - * @description Set new parameters or get current parameters - * - * @param {string} newParameters New parameters - * @param {string} parseParamsFromUrl Flag if parse parameters like in url - * @returns {Object} Current parameters or `this` - */ - this.parameters = function (newParameters, parseParamsFromUrl) { - parseParamsFromUrl = parseParamsFromUrl || false; - if (angular.isDefined(newParameters)) { - for (var key in newParameters) { - var value = newParameters[key]; - if (parseParamsFromUrl && key.indexOf('[') >= 0) { - var keys = key.split(/\[(.*)\]/).reverse() - var lastKey = ''; - for (var i = 0, len = keys.length; i < len; i++) { - var name = keys[i]; - if (name !== '') { - var v = value; - value = {}; - value[lastKey = name] = (isNumber(v) ? parseFloat(v) : v); - } - } - if (lastKey === 'sorting') { - params[lastKey] = {}; - } - params[lastKey] = angular.extend(params[lastKey] || {}, value[lastKey]); - } else { - params[key] = (isNumber(newParameters[key]) ? parseFloat(newParameters[key]) : newParameters[key]); - } - } - log('ngTable: set parameters', params); - return this; - } - return params; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#settings - * @methodOf ngTable.factory:ngTableParams - * @description Set new settings for table - * - * @param {string} newSettings New settings or undefined - * @returns {Object} Current settings or `this` - */ - this.settings = function (newSettings) { - if (angular.isDefined(newSettings)) { - if (angular.isArray(newSettings.data)) { - //auto-set the total from passed in data - newSettings.total = newSettings.data.length; - } - settings = angular.extend(settings, newSettings); - log('ngTable: set settings', settings); - return this; - } - return settings; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#page - * @methodOf ngTable.factory:ngTableParams - * @description If parameter page not set return current page else set current page - * - * @param {string} page Page number - * @returns {Object|Number} Current page or `this` - */ - this.page = function (page) { - return angular.isDefined(page) ? this.parameters({'page': page}) : params.page; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#total - * @methodOf ngTable.factory:ngTableParams - * @description If parameter total not set return current quantity else set quantity - * - * @param {string} total Total quantity of items - * @returns {Object|Number} Current page or `this` - */ - this.total = function (total) { - return angular.isDefined(total) ? this.settings({'total': total}) : settings.total; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#count - * @methodOf ngTable.factory:ngTableParams - * @description If parameter count not set return current count per page else set count per page - * - * @param {string} count Count per number - * @returns {Object|Number} Count per page or `this` - */ - this.count = function (count) { - // reset to first page because can be blank page - return angular.isDefined(count) ? this.parameters({'count': count, 'page': 1}) : params.count; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#filter - * @methodOf ngTable.factory:ngTableParams - * @description If parameter page not set return current filter else set current filter - * - * @param {string} filter New filter - * @returns {Object} Current filter or `this` - */ - this.filter = function (filter) { - return angular.isDefined(filter) ? this.parameters({'filter': filter}) : params.filter; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#sorting - * @methodOf ngTable.factory:ngTableParams - * @description If 'sorting' parameter is not set, return current sorting. Otherwise set current sorting. - * - * @param {string} sorting New sorting - * @returns {Object} Current sorting or `this` - */ - this.sorting = function (sorting) { - if (arguments.length == 2) { - var sortArray = {}; - sortArray[sorting] = arguments[1]; - this.parameters({'sorting': sortArray}); - return this; - } - return angular.isDefined(sorting) ? this.parameters({'sorting': sorting}) : params.sorting; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#isSortBy - * @methodOf ngTable.factory:ngTableParams - * @description Checks sort field - * - * @param {string} field Field name - * @param {string} direction Direction of sorting 'asc' or 'desc' - * @returns {Array} Return true if field sorted by direction - */ - this.isSortBy = function (field, direction) { - return angular.isDefined(params.sorting[field]) && params.sorting[field] == direction; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#orderBy - * @methodOf ngTable.factory:ngTableParams - * @description Return object of sorting parameters for angular filter - * - * @returns {Array} Array like: [ '-name', '+age' ] - */ - this.orderBy = function () { - var sorting = []; - for (var column in params.sorting) { - sorting.push((params.sorting[column] === "asc" ? "+" : "-") + column); - } - return sorting; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#getData - * @methodOf ngTable.factory:ngTableParams - * @description Called when updated some of parameters for get new data - * - * @param {Object} $defer promise object - * @param {Object} params New parameters - */ - this.getData = function ($defer, params) { - if (angular.isArray(this.data) && angular.isObject(params)) { - $defer.resolve(this.data.slice((params.page() - 1) * params.count(), params.page() * params.count())); - } else { - $defer.resolve([]); - } - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#getGroups - * @methodOf ngTable.factory:ngTableParams - * @description Return groups for table grouping - */ - this.getGroups = function ($defer, column) { - var defer = $q.defer(); - - defer.promise.then(function (data) { - var groups = {}; - angular.forEach(data, function (item) { - var groupName = angular.isFunction(column) ? column(item) : item[column]; - - groups[groupName] = groups[groupName] || { - data: [] - }; - groups[groupName]['value'] = groupName; - groups[groupName].data.push(item); - }); - var result = []; - for (var i in groups) { - result.push(groups[i]); - } - log('ngTable: refresh groups', result); - $defer.resolve(result); - }); - this.getData(defer, self); - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#generatePagesArray - * @methodOf ngTable.factory:ngTableParams - * @description Generate array of pages - * - * @param {boolean} currentPage which page must be active - * @param {boolean} totalItems Total quantity of items - * @param {boolean} pageSize Quantity of items on page - * @returns {Array} Array of pages - */ - this.generatePagesArray = function (currentPage, totalItems, pageSize) { - var maxBlocks, maxPage, maxPivotPages, minPage, numPages, pages; - maxBlocks = 11; - pages = []; - numPages = Math.ceil(totalItems / pageSize); - if (numPages > 1) { - pages.push({ - type: 'prev', - number: Math.max(1, currentPage - 1), - active: currentPage > 1 - }); - pages.push({ - type: 'first', - number: 1, - active: currentPage > 1 - }); - maxPivotPages = Math.round((maxBlocks - 5) / 2); - minPage = Math.max(2, currentPage - maxPivotPages); - maxPage = Math.min(numPages - 1, currentPage + maxPivotPages * 2 - (currentPage - minPage)); - minPage = Math.max(2, minPage - (maxPivotPages * 2 - (maxPage - minPage))); - var i = minPage; - while (i <= maxPage) { - if ((i === minPage && i !== 2) || (i === maxPage && i !== numPages - 1)) { - pages.push({ - type: 'more', - active: false - }); - } else { - pages.push({ - type: 'page', - number: i, - active: currentPage !== i - }); - } - i++; - } - pages.push({ - type: 'last', - number: numPages, - active: currentPage !== numPages - }); - pages.push({ - type: 'next', - number: Math.min(numPages, currentPage + 1), - active: currentPage < numPages - }); - } - return pages; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#url - * @methodOf ngTable.factory:ngTableParams - * @description Return groups for table grouping - * - * @param {boolean} asString flag indicates return array of string or object - * @returns {Array} If asString = true will be return array of url string parameters else key-value object - */ - this.url = function (asString) { - asString = asString || false; - var pairs = (asString ? [] : {}); - for (var key in params) { - if (params.hasOwnProperty(key)) { - var item = params[key], - name = encodeURIComponent(key); - if (typeof item === "object") { - for (var subkey in item) { - if (!angular.isUndefined(item[subkey]) && item[subkey] !== "") { - var pname = name + "[" + encodeURIComponent(subkey) + "]"; - if (asString) { - pairs.push(pname + "=" + item[subkey]); - } else { - pairs[pname] = item[subkey]; - } - } - } - } else if (!angular.isFunction(item) && !angular.isUndefined(item) && item !== "") { - if (asString) { - pairs.push(name + "=" + encodeURIComponent(item)); - } else { - pairs[name] = encodeURIComponent(item); - } - } - } - } - return pairs; - }; - - /** - * @ngdoc method - * @name ngTable.factory:ngTableParams#reload - * @methodOf ngTable.factory:ngTableParams - * @description Reload table data - */ - this.reload = function () { - var $defer = $q.defer(), - self = this; - - settings.$loading = true; - if (settings.groupBy) { - settings.getGroups($defer, settings.groupBy, this); - } else { - settings.getData($defer, this); - } - log('ngTable: reload data'); - $defer.promise.then(function (data) { - settings.$loading = false; - log('ngTable: current scope', settings.$scope); - if (settings.groupBy) { - self.data = settings.$scope.$groups = data; - } else { - self.data = settings.$scope.$data = data; - } - settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count()); - settings.$scope.$emit('ngTableAfterReloadData'); - }); - }; - - this.reloadPages = function () { - var self = this; - settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count()); - }; - - var params = this.$params = { - page: 1, - count: 1, - filter: {}, - sorting: {}, - group: {}, - groupBy: null - }; - var settings = { - $scope: null, // set by ngTable controller - $loading: false, - data: null, //allows data to be set when table is initialized - total: 0, - defaultSort: 'desc', - filterDelay: 750, - counts: [10, 25, 50, 100], - getGroups: this.getGroups, - getData: this.getData - }; - - this.settings(baseSettings); - this.parameters(baseParameters, true); - return this; - }; - return ngTableParams; -}]); - -/** - * ngTable: Table + Angular JS - * - * @author Vitalii Savchuk - * @url https://github.com/esvit/ng-table/ - * @license New BSD License - */ - -/** - * @ngdoc object - * @name ngTable.directive:ngTable.ngTableController - * - * @description - * Each {@link ngTable.directive:ngTable ngTable} directive creates an instance of `ngTableController` - */ -var ngTableController = ['$scope', 'ngTableParams', '$timeout', function ($scope, ngTableParams, $timeout) { - $scope.$loading = false; - - if (!$scope.params) { - $scope.params = new ngTableParams(); - } - $scope.params.settings().$scope = $scope; - - var delayFilter = (function () { - var timer = 0; - return function (callback, ms) { - $timeout.cancel(timer); - timer = $timeout(callback, ms); - }; - })(); - - $scope.$watch('params.$params', function (newParams, oldParams) { - $scope.params.settings().$scope = $scope; - - if (!angular.equals(newParams.filter, oldParams.filter)) { - delayFilter(function () { - $scope.params.$params.page = 1; - $scope.params.reload(); - }, $scope.params.settings().filterDelay); - } else { - $scope.params.reload(); - } - }, true); - - $scope.sortBy = function (column, event) { - var parsedSortable = $scope.parse(column.sortable); - if (!parsedSortable) { - return; - } - var defaultSort = $scope.params.settings().defaultSort; - var inverseSort = (defaultSort === 'asc' ? 'desc' : 'asc'); - var sorting = $scope.params.sorting() && $scope.params.sorting()[parsedSortable] && ($scope.params.sorting()[parsedSortable] === defaultSort); - var sortingParams = (event.ctrlKey || event.metaKey) ? $scope.params.sorting() : {}; - sortingParams[parsedSortable] = (sorting ? inverseSort : defaultSort); - $scope.params.parameters({ - sorting: sortingParams - }); - }; -}]; -/** - * ngTable: Table + Angular JS - * - * @author Vitalii Savchuk - * @url https://github.com/esvit/ng-table/ - * @license New BSD License - */ - -/** - * @ngdoc directive - * @name ngTable.directive:ngTable - * @restrict A - * - * @description - * Directive that instantiates {@link ngTable.directive:ngTable.ngTableController ngTableController}. - */ -app.directive('ngTable', ['$compile', '$q', '$parse', - function ($compile, $q, $parse) { - 'use strict'; - - return { - restrict: 'A', - priority: 1001, - scope: true, - controller: ngTableController, - compile: function (element) { - var columns = [], i = 0, row = null; - - // custom header - var thead = element.find('thead'); - - // IE 8 fix :not(.ng-table-group) selector - angular.forEach(angular.element(element.find('tr')), function (tr) { - tr = angular.element(tr); - if (!tr.hasClass('ng-table-group') && !row) { - row = tr; - } - }); - if (!row) { - return; - } - angular.forEach(row.find('td'), function (item) { - var el = angular.element(item); - if (el.attr('ignore-cell') && 'true' === el.attr('ignore-cell')) { - return; - } - var parsedAttribute = function (attr, defaultValue) { - return function (scope) { - return $parse(el.attr('x-data-' + attr) || el.attr('data-' + attr) || el.attr(attr))(scope, { - $columns: columns - }) || defaultValue; - }; - }; - - var parsedTitle = parsedAttribute('title', ' '), - headerTemplateURL = parsedAttribute('header', false), - filter = parsedAttribute('filter', false)(), - filterTemplateURL = false, - filterName = false; - - if (filter && filter.$$name) { - filterName = filter.$$name; - delete filter.$$name; - } - if (filter && filter.templateURL) { - filterTemplateURL = filter.templateURL; - delete filter.templateURL; - } - - el.attr('data-title-text', parsedTitle()); // this used in responsive table - columns.push({ - id: i++, - title: parsedTitle, - sortable: parsedAttribute('sortable', false), - 'class': el.attr('x-data-header-class') || el.attr('data-header-class') || el.attr('header-class'), - filter: filter, - filterTemplateURL: filterTemplateURL, - filterName: filterName, - headerTemplateURL: headerTemplateURL, - filterData: (el.attr("filter-data") ? el.attr("filter-data") : null), - show: (el.attr("ng-show") ? function (scope) { - return $parse(el.attr("ng-show"))(scope); - } : function () { - return true; - }) - }); - }); - return function (scope, element, attrs) { - scope.$loading = false; - scope.$columns = columns; - - scope.$watch(attrs.ngTable, (function (params) { - if (angular.isUndefined(params)) { - return; - } - scope.paramsModel = $parse(attrs.ngTable); - scope.params = params; - }), true); - scope.parse = function (text) { - return angular.isDefined(text) ? text(scope) : ''; - }; - if (attrs.showFilter) { - scope.$parent.$watch(attrs.showFilter, function (value) { - scope.show_filter = value; - }); - } - angular.forEach(columns, function (column) { - var def; - if (!column.filterData) { - return; - } - def = $parse(column.filterData)(scope, { - $column: column - }); - if (!(angular.isObject(def) && angular.isObject(def.promise))) { - throw new Error('Function ' + column.filterData + ' must be instance of $q.defer()'); - } - delete column.filterData; - return def.promise.then(function (data) { - if (!angular.isArray(data)) { - data = []; - } - data.unshift({ - title: '-', - id: '' - }); - column.data = data; - }); - }); - if (!element.hasClass('ng-table')) { - scope.templates = { - header: (attrs.templateHeader ? attrs.templateHeader : 'ng-table/header.html'), - pagination: (attrs.templatePagination ? attrs.templatePagination : 'ng-table/pager.html') - }; - var headerTemplate = thead.length > 0 ? thead : angular.element(document.createElement('thead')).attr('ng-include', 'templates.header'); - var paginationTemplate = angular.element(document.createElement('div')).attr({ - 'ng-table-pagination': 'params', - 'template-url': 'templates.pagination' - }); - - element.find('thead').remove(); - - element.addClass('ng-table') - .prepend(headerTemplate) - .after(paginationTemplate); - - $compile(headerTemplate)(scope); - $compile(paginationTemplate)(scope); - } - }; - } - } - } -]); - -/** - * ngTable: Table + Angular JS - * - * @author Vitalii Savchuk - * @url https://github.com/esvit/ng-table/ - * @license New BSD License - */ - -/** - * @ngdoc directive - * @name ngTable.directive:ngTablePagination - * @restrict A - */ -app.directive('ngTablePagination', ['$compile', - function ($compile) { - 'use strict'; - - return { - restrict: 'A', - scope: { - 'params': '=ngTablePagination', - 'templateUrl': '=' - }, - replace: false, - link: function (scope, element, attrs) { - - scope.params.settings().$scope.$on('ngTableAfterReloadData', function () { - scope.pages = scope.params.generatePagesArray(scope.params.page(), scope.params.total(), scope.params.count()); - }, true); - - scope.$watch('templateUrl', function(templateUrl) { - if (angular.isUndefined(templateUrl)) { - return; - } - var template = angular.element(document.createElement('div')) - template.attr({ - 'ng-include': 'templateUrl' - }); - element.append(template); - $compile(template)(scope); - }); - } - }; - } -]); - -angular.module('ngTable').run(['$templateCache', function ($templateCache) { - $templateCache.put('ng-table/filters/select-multiple.html', ''); - $templateCache.put('ng-table/filters/select.html', ''); - $templateCache.put('ng-table/filters/text.html', ''); - $templateCache.put('ng-table/header.html', '
            '); - $templateCache.put('ng-table/pager.html', ' '); -}]); - return app; -})); -angular.module("gettext",[]),angular.module("gettext").constant("gettext",function(a){return a}),angular.module("gettext").factory("gettextCatalog",["gettextPlurals","$http","$cacheFactory",function(a,b,c){var d,e=function(a){return d.debug&&d.currentLanguage!==d.baseLanguage?"[MISSING]: "+a:a};return d={debug:!1,strings:{},baseLanguage:"en",currentLanguage:"en",cache:c("strings"),setStrings:function(a,b){var c,d;this.strings[a]||(this.strings[a]={});for(c in b)d=b[c],this.strings[a][c]="string"==typeof d?[d]:d},getStringForm:function(a,b){var c=this.strings[this.currentLanguage]||{},d=c[a]||[];return d[b]},getString:function(a){return this.getStringForm(a,0)||e(a)},getPlural:function(b,c,d){var f=a(this.currentLanguage,b);return this.getStringForm(c,f)||e(1===b?c:d)},loadRemote:function(a){return b({method:"GET",url:a,cache:d.cache}).success(function(a){for(var b in a)d.setStrings(b,a[b])})}}}]),angular.module("gettext").directive("translate",["gettextCatalog","$interpolate","$parse","$compile",function(a,b,c,d){var e=function(){return String.prototype.trim?function(a){return"string"==typeof a?a.trim():a}:function(a){return"string"==typeof a?a.replace(/^\s*/,"").replace(/\s*$/,""):a}}();return{transclude:"element",priority:499,compile:function(f,g,h){return function(f,i){var j=function(a,b,c){if(!a)throw new Error("You should add a "+b+" attribute whenever you add a "+c+" attribute.")};if(j(!g.translatePlural||g.translateN,"translate-n","translate-plural"),j(!g.translateN||g.translatePlural,"translate-plural","translate-n"),g.ngIf)throw new Error("You should not combine translate with ng-if, this will lead to problems.");if(g.ngSwitchWhen)throw new Error("You should not combine translate with ng-switch-when, this will lead to problems.");var k=c(g.translateN),l=null;h(f,function(c){var h=e(c.html());return c.removeAttr("translate"),i.replaceWith(c),f.$watch(function(){var e,i=c.html();g.translatePlural?(f=l||(l=f.$new()),f.$count=k(f),e=a.getPlural(f.$count,h,g.translatePlural)):e=a.getString(h);var j=b(e)(f);return i!==j?(c.html(j),void 0!==g.translateCompile&&d(c.contents())(f),c):void 0})})}}}}]),angular.module("gettext").filter("translate",["gettextCatalog","$interpolate","$parse",function(a){return function(b){return a.getString(b)}}]),angular.module("gettext").factory("gettextPlurals",function(){return function(a,b){switch(a){case"ay":case"bo":case"cgg":case"dz":case"fa":case"id":case"ja":case"jbo":case"ka":case"kk":case"km":case"ko":case"ky":case"lo":case"ms":case"my":case"sah":case"su":case"th":case"tt":case"ug":case"vi":case"wo":case"zh":return 0;case"is":return b%10!=1||b%100==11?1:0;case"jv":return 0!=b?1:0;case"mk":return 1==b||b%10==1?0:1;case"ach":case"ak":case"am":case"arn":case"br":case"fil":case"fr":case"gun":case"ln":case"mfe":case"mg":case"mi":case"oc":case"pt_BR":case"tg":case"ti":case"tr":case"uz":case"wa":case"zh":return b>1?1:0;case"lv":return b%10==1&&b%100!=11?0:0!=b?1:2;case"lt":return b%10==1&&b%100!=11?0:b%10>=2&&(10>b%100||b%100>=20)?1:2;case"be":case"bs":case"hr":case"ru":case"sr":case"uk":return b%10==1&&b%100!=11?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"mnk":return 0==b?0:1==b?1:2;case"ro":return 1==b?0:0==b||b%100>0&&20>b%100?1:2;case"pl":return 1==b?0:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?1:2;case"cs":case"sk":return 1==b?0:b>=2&&4>=b?1:2;case"sl":return b%100==1?1:b%100==2?2:b%100==3||b%100==4?3:0;case"mt":return 1==b?0:0==b||b%100>1&&11>b%100?1:b%100>10&&20>b%100?2:3;case"gd":return 1==b||11==b?0:2==b||12==b?1:b>2&&20>b?2:3;case"cy":return 1==b?0:2==b?1:8!=b&&11!=b?2:3;case"kw":return 1==b?0:2==b?1:3==b?2:3;case"ga":return 1==b?0:2==b?1:7>b?2:11>b?3:4;case"ar":return 0==b?0:1==b?1:2==b?2:b%100>=3&&10>=b%100?3:b%100>=11?4:5;default:return 1!=b?1:0}}}); -/** - * Enhanced Select2 Dropmenus - * - * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2 - * This change is so that you do not have to do an additional query yourself on top of Select2's own query - * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation - */ -angular.module('ui.select2', []).value('uiSelect2Config', {}).directive('uiSelect2', ['uiSelect2Config', '$timeout', function (uiSelect2Config, $timeout) { - var options = {}; - if (uiSelect2Config) { - angular.extend(options, uiSelect2Config); - } - return { - require: 'ngModel', - priority: 1, - compile: function (tElm, tAttrs) { - var watch, - repeatOption, - repeatAttr, - isSelect = tElm.is('select'), - isMultiple = angular.isDefined(tAttrs.multiple); - - // Enable watching of the options dataset if in use - if (tElm.is('select')) { - repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]'); - - if (repeatOption.length) { - repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat'); - watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop(); - } - } - - return function (scope, elm, attrs, controller) { - // instance-specific options - var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2)); - - /* - Convert from Select2 view-model to Angular view-model. - */ - var convertToAngularModel = function(select2_data) { - var model; - if (opts.simple_tags) { - model = []; - angular.forEach(select2_data, function(value, index) { - model.push(value.id); - }); - } else { - model = select2_data; - } - return model; - }; - - /* - Convert from Angular view-model to Select2 view-model. - */ - var convertToSelect2Model = function(angular_data) { - var model = []; - if (!angular_data) { - return model; - } - - if (opts.simple_tags) { - model = []; - angular.forEach( - angular_data, - function(value, index) { - model.push({'id': value, 'text': value}); - }); - } else { - model = angular_data; - } - return model; - }; - - if (isSelect) { - // Use element."); - } - }); - } - - opts = $.extend({}, { - populateResults: function(container, results, query) { - var populate, id=this.opts.id, liveRegion=this.liveRegion; - - populate=function(results, container, depth) { - - var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted; - - results = opts.sortResults(results, container, query); - - for (i = 0, l = results.length; i < l; i = i + 1) { - - result=results[i]; - - disabled = (result.disabled === true); - selectable = (!disabled) && (id(result) !== undefined); - - compound=result.children && result.children.length > 0; - - node=$("
          • "); - node.addClass("select2-results-dept-"+depth); - node.addClass("select2-result"); - node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable"); - if (disabled) { node.addClass("select2-disabled"); } - if (compound) { node.addClass("select2-result-with-children"); } - node.addClass(self.opts.formatResultCssClass(result)); - node.attr("role", "presentation"); - - label=$(document.createElement("div")); - label.addClass("select2-result-label"); - label.attr("id", "select2-result-label-" + nextUid()); - label.attr("role", "option"); - - formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup); - if (formatted!==undefined) { - label.html(formatted); - node.append(label); - } - - - if (compound) { - - innerContainer=$("
              "); - innerContainer.addClass("select2-result-sub"); - populate(result.children, innerContainer, depth+1); - node.append(innerContainer); - } - - node.data("select2-data", result); - container.append(node); - } - - liveRegion.text(opts.formatMatches(results.length)); - }; - - populate(results, container, 0); - } - }, $.fn.select2.defaults, opts); - - if (typeof(opts.id) !== "function") { - idKey = opts.id; - opts.id = function (e) { return e[idKey]; }; - } - - if ($.isArray(opts.element.data("select2Tags"))) { - if ("tags" in opts) { - throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id"); - } - opts.tags=opts.element.data("select2Tags"); - } - - if (select) { - opts.query = this.bind(function (query) { - var data = { results: [], more: false }, - term = query.term, - children, placeholderOption, process; - - process=function(element, collection) { - var group; - if (element.is("option")) { - if (query.matcher(term, element.text(), element)) { - collection.push(self.optionToData(element)); - } - } else if (element.is("optgroup")) { - group=self.optionToData(element); - element.children().each2(function(i, elm) { process(elm, group.children); }); - if (group.children.length>0) { - collection.push(group); - } - } - }; - - children=element.children(); - - // ignore the placeholder option if there is one - if (this.getPlaceholder() !== undefined && children.length > 0) { - placeholderOption = this.getPlaceholderOption(); - if (placeholderOption) { - children=children.not(placeholderOption); - } - } - - children.each2(function(i, elm) { process(elm, data.results); }); - - query.callback(data); - }); - // this is needed because inside val() we construct choices from options and there id is hardcoded - opts.id=function(e) { return e.id; }; - } else { - if (!("query" in opts)) { - - if ("ajax" in opts) { - ajaxUrl = opts.element.data("ajax-url"); - if (ajaxUrl && ajaxUrl.length > 0) { - opts.ajax.url = ajaxUrl; - } - opts.query = ajax.call(opts.element, opts.ajax); - } else if ("data" in opts) { - opts.query = local(opts.data); - } else if ("tags" in opts) { - opts.query = tags(opts.tags); - if (opts.createSearchChoice === undefined) { - opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; }; - } - if (opts.initSelection === undefined) { - opts.initSelection = function (element, callback) { - var data = []; - $(splitVal(element.val(), opts.separator)).each(function () { - var obj = { id: this, text: this }, - tags = opts.tags; - if ($.isFunction(tags)) tags=tags(); - $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } }); - data.push(obj); - }); - - callback(data); - }; - } - } - } - } - if (typeof(opts.query) !== "function") { - throw "query function not defined for Select2 " + opts.element.attr("id"); - } - - if (opts.createSearchChoicePosition === 'top') { - opts.createSearchChoicePosition = function(list, item) { list.unshift(item); }; - } - else if (opts.createSearchChoicePosition === 'bottom') { - opts.createSearchChoicePosition = function(list, item) { list.push(item); }; - } - else if (typeof(opts.createSearchChoicePosition) !== "function") { - throw "invalid createSearchChoicePosition option must be 'top', 'bottom' or a custom function"; - } - - return opts; - }, - - /** - * Monitor the original element for changes and update select2 accordingly - */ - // abstract - monitorSource: function () { - var el = this.opts.element, sync, observer; - - el.on("change.select2", this.bind(function (e) { - if (this.opts.element.data("select2-change-triggered") !== true) { - this.initSelection(); - } - })); - - sync = this.bind(function () { - - // sync enabled state - var disabled = el.prop("disabled"); - if (disabled === undefined) disabled = false; - this.enable(!disabled); - - var readonly = el.prop("readonly"); - if (readonly === undefined) readonly = false; - this.readonly(readonly); - - syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); - this.container.addClass(evaluate(this.opts.containerCssClass)); - - syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); - this.dropdown.addClass(evaluate(this.opts.dropdownCssClass)); - - }); - - // IE8-10 (IE9/10 won't fire propertyChange via attachEventListener) - if (el.length && el[0].attachEvent) { - el.each(function() { - this.attachEvent("onpropertychange", sync); - }); - } - - // safari, chrome, firefox, IE11 - observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver; - if (observer !== undefined) { - if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } - this.propertyObserver = new observer(function (mutations) { - mutations.forEach(sync); - }); - this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false }); - } - }, - - // abstract - triggerSelect: function(data) { - var evt = $.Event("select2-selecting", { val: this.id(data), object: data }); - this.opts.element.trigger(evt); - return !evt.isDefaultPrevented(); - }, - - /** - * Triggers the change event on the source element - */ - // abstract - triggerChange: function (details) { - - details = details || {}; - details= $.extend({}, details, { type: "change", val: this.val() }); - // prevents recursive triggering - this.opts.element.data("select2-change-triggered", true); - this.opts.element.trigger(details); - this.opts.element.data("select2-change-triggered", false); - - // some validation frameworks ignore the change event and listen instead to keyup, click for selects - // so here we trigger the click event manually - this.opts.element.click(); - - // ValidationEngine ignores the change event and listens instead to blur - // so here we trigger the blur event manually if so desired - if (this.opts.blurOnChange) - this.opts.element.blur(); - }, - - //abstract - isInterfaceEnabled: function() - { - return this.enabledInterface === true; - }, - - // abstract - enableInterface: function() { - var enabled = this._enabled && !this._readonly, - disabled = !enabled; - - if (enabled === this.enabledInterface) return false; - - this.container.toggleClass("select2-container-disabled", disabled); - this.close(); - this.enabledInterface = enabled; - - return true; - }, - - // abstract - enable: function(enabled) { - if (enabled === undefined) enabled = true; - if (this._enabled === enabled) return; - this._enabled = enabled; - - this.opts.element.prop("disabled", !enabled); - this.enableInterface(); - }, - - // abstract - disable: function() { - this.enable(false); - }, - - // abstract - readonly: function(enabled) { - if (enabled === undefined) enabled = false; - if (this._readonly === enabled) return; - this._readonly = enabled; - - this.opts.element.prop("readonly", enabled); - this.enableInterface(); - }, - - // abstract - opened: function () { - return this.container.hasClass("select2-dropdown-open"); - }, - - // abstract - positionDropdown: function() { - var $dropdown = this.dropdown, - offset = this.container.offset(), - height = this.container.outerHeight(false), - width = this.container.outerWidth(false), - dropHeight = $dropdown.outerHeight(false), - $window = $(window), - windowWidth = $window.width(), - windowHeight = $window.height(), - viewPortRight = $window.scrollLeft() + windowWidth, - viewportBottom = $window.scrollTop() + windowHeight, - dropTop = offset.top + height, - dropLeft = offset.left, - enoughRoomBelow = dropTop + dropHeight <= viewportBottom, - enoughRoomAbove = (offset.top - dropHeight) >= $window.scrollTop(), - dropWidth = $dropdown.outerWidth(false), - enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight, - aboveNow = $dropdown.hasClass("select2-drop-above"), - bodyOffset, - above, - changeDirection, - css, - resultsListNode; - - // always prefer the current above/below alignment, unless there is not enough room - if (aboveNow) { - above = true; - if (!enoughRoomAbove && enoughRoomBelow) { - changeDirection = true; - above = false; - } - } else { - above = false; - if (!enoughRoomBelow && enoughRoomAbove) { - changeDirection = true; - above = true; - } - } - - //if we are changing direction we need to get positions when dropdown is hidden; - if (changeDirection) { - $dropdown.hide(); - offset = this.container.offset(); - height = this.container.outerHeight(false); - width = this.container.outerWidth(false); - dropHeight = $dropdown.outerHeight(false); - viewPortRight = $window.scrollLeft() + windowWidth; - viewportBottom = $window.scrollTop() + windowHeight; - dropTop = offset.top + height; - dropLeft = offset.left; - dropWidth = $dropdown.outerWidth(false); - enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight; - $dropdown.show(); - - // fix so the cursor does not move to the left within the search-textbox in IE - this.focusSearch(); - } - - if (this.opts.dropdownAutoWidth) { - resultsListNode = $('.select2-results', $dropdown)[0]; - $dropdown.addClass('select2-drop-auto-width'); - $dropdown.css('width', ''); - // Add scrollbar width to dropdown if vertical scrollbar is present - dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width); - dropWidth > width ? width = dropWidth : dropWidth = width; - dropHeight = $dropdown.outerHeight(false); - enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight; - } - else { - this.container.removeClass('select2-drop-auto-width'); - } - - //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow); - //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body.scrollTop(), "enough?", enoughRoomAbove); - - // fix positioning when body has an offset and is not position: static - if (this.body.css('position') !== 'static') { - bodyOffset = this.body.offset(); - dropTop -= bodyOffset.top; - dropLeft -= bodyOffset.left; - } - - if (!enoughRoomOnRight) { - dropLeft = offset.left + this.container.outerWidth(false) - dropWidth; - } - - css = { - left: dropLeft, - width: width - }; - - if (above) { - css.top = offset.top - dropHeight; - css.bottom = 'auto'; - this.container.addClass("select2-drop-above"); - $dropdown.addClass("select2-drop-above"); - } - else { - css.top = dropTop; - css.bottom = 'auto'; - this.container.removeClass("select2-drop-above"); - $dropdown.removeClass("select2-drop-above"); - } - css = $.extend(css, evaluate(this.opts.dropdownCss)); - - $dropdown.css(css); - }, - - // abstract - shouldOpen: function() { - var event; - - if (this.opened()) return false; - - if (this._enabled === false || this._readonly === true) return false; - - event = $.Event("select2-opening"); - this.opts.element.trigger(event); - return !event.isDefaultPrevented(); - }, - - // abstract - clearDropdownAlignmentPreference: function() { - // clear the classes used to figure out the preference of where the dropdown should be opened - this.container.removeClass("select2-drop-above"); - this.dropdown.removeClass("select2-drop-above"); - }, - - /** - * Opens the dropdown - * - * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example, - * the dropdown is already open, or if the 'open' event listener on the element called preventDefault(). - */ - // abstract - open: function () { - - if (!this.shouldOpen()) return false; - - this.opening(); - - return true; - }, - - /** - * Performs the opening of the dropdown - */ - // abstract - opening: function() { - var cid = this.containerEventName, - scroll = "scroll." + cid, - resize = "resize."+cid, - orient = "orientationchange."+cid, - mask; - - this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); - - this.clearDropdownAlignmentPreference(); - - if(this.dropdown[0] !== this.body.children().last()[0]) { - this.dropdown.detach().appendTo(this.body); - } - - // create the dropdown mask if doesn't already exist - mask = $("#select2-drop-mask"); - if (mask.length == 0) { - mask = $(document.createElement("div")); - mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask"); - mask.hide(); - mask.appendTo(this.body); - mask.on("mousedown touchstart click", function (e) { - // Prevent IE from generating a click event on the body - reinsertElement(mask); - - var dropdown = $("#select2-drop"), self; - if (dropdown.length > 0) { - self=dropdown.data("select2"); - if (self.opts.selectOnBlur) { - self.selectHighlighted({noFocus: true}); - } - self.close(); - e.preventDefault(); - e.stopPropagation(); - } - }); - } - - // ensure the mask is always right before the dropdown - if (this.dropdown.prev()[0] !== mask[0]) { - this.dropdown.before(mask); - } - - // move the global id to the correct dropdown - $("#select2-drop").removeAttr("id"); - this.dropdown.attr("id", "select2-drop"); - - // show the elements - mask.show(); - - this.positionDropdown(); - this.dropdown.show(); - this.positionDropdown(); - - this.dropdown.addClass("select2-drop-active"); - - // attach listeners to events that can change the position of the container and thus require - // the position of the dropdown to be updated as well so it does not come unglued from the container - var that = this; - this.container.parents().add(window).each(function () { - $(this).on(resize+" "+scroll+" "+orient, function (e) { - if (that.opened()) that.positionDropdown(); - }); - }); - - - }, - - // abstract - close: function () { - if (!this.opened()) return; - - var cid = this.containerEventName, - scroll = "scroll." + cid, - resize = "resize."+cid, - orient = "orientationchange."+cid; - - // unbind event listeners - this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); }); - - this.clearDropdownAlignmentPreference(); - - $("#select2-drop-mask").hide(); - this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id - this.dropdown.hide(); - this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"); - this.results.empty(); - - - this.clearSearch(); - this.search.removeClass("select2-active"); - this.opts.element.trigger($.Event("select2-close")); - }, - - /** - * Opens control, sets input value, and updates results. - */ - // abstract - externalSearch: function (term) { - this.open(); - this.search.val(term); - this.updateResults(false); - }, - - // abstract - clearSearch: function () { - - }, - - //abstract - getMaximumSelectionSize: function() { - return evaluate(this.opts.maximumSelectionSize); - }, - - // abstract - ensureHighlightVisible: function () { - var results = this.results, children, index, child, hb, rb, y, more; - - index = this.highlight(); - - if (index < 0) return; - - if (index == 0) { - - // if the first element is highlighted scroll all the way to the top, - // that way any unselectable headers above it will also be scrolled - // into view - - results.scrollTop(0); - return; - } - - children = this.findHighlightableChoices().find('.select2-result-label'); - - child = $(children[index]); - - hb = child.offset().top + child.outerHeight(true); - - // if this is the last child lets also make sure select2-more-results is visible - if (index === children.length - 1) { - more = results.find("li.select2-more-results"); - if (more.length > 0) { - hb = more.offset().top + more.outerHeight(true); - } - } - - rb = results.offset().top + results.outerHeight(true); - if (hb > rb) { - results.scrollTop(results.scrollTop() + (hb - rb)); - } - y = child.offset().top - results.offset().top; - - // make sure the top of the element is visible - if (y < 0 && child.css('display') != 'none' ) { - results.scrollTop(results.scrollTop() + y); // y is negative - } - }, - - // abstract - findHighlightableChoices: function() { - return this.results.find(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)"); - }, - - // abstract - moveHighlight: function (delta) { - var choices = this.findHighlightableChoices(), - index = this.highlight(); - - while (index > -1 && index < choices.length) { - index += delta; - var choice = $(choices[index]); - if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) { - this.highlight(index); - break; - } - } - }, - - // abstract - highlight: function (index) { - var choices = this.findHighlightableChoices(), - choice, - data; - - if (arguments.length === 0) { - return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); - } - - if (index >= choices.length) index = choices.length - 1; - if (index < 0) index = 0; - - this.removeHighlight(); - - choice = $(choices[index]); - choice.addClass("select2-highlighted"); - - // ensure assistive technology can determine the active choice - this.search.attr("aria-activedescendant", choice.find(".select2-result-label").attr("id")); - - this.ensureHighlightVisible(); - - this.liveRegion.text(choice.text()); - - data = choice.data("select2-data"); - if (data) { - this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data }); - } - }, - - removeHighlight: function() { - this.results.find(".select2-highlighted").removeClass("select2-highlighted"); - }, - - touchMoved: function() { - this._touchMoved = true; - }, - - clearTouchMoved: function() { - this._touchMoved = false; - }, - - // abstract - countSelectableResults: function() { - return this.findHighlightableChoices().length; - }, - - // abstract - highlightUnderEvent: function (event) { - var el = $(event.target).closest(".select2-result-selectable"); - if (el.length > 0 && !el.is(".select2-highlighted")) { - var choices = this.findHighlightableChoices(); - this.highlight(choices.index(el)); - } else if (el.length == 0) { - // if we are over an unselectable item remove all highlights - this.removeHighlight(); - } - }, - - // abstract - loadMoreIfNeeded: function () { - var results = this.results, - more = results.find("li.select2-more-results"), - below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible - page = this.resultsPage + 1, - self=this, - term=this.search.val(), - context=this.context; - - if (more.length === 0) return; - below = more.offset().top - results.offset().top - results.height(); - - if (below <= this.opts.loadMorePadding) { - more.addClass("select2-active"); - this.opts.query({ - element: this.opts.element, - term: term, - page: page, - context: context, - matcher: this.opts.matcher, - callback: this.bind(function (data) { - - // ignore a response if the select2 has been closed before it was received - if (!self.opened()) return; - - - self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context}); - self.postprocessResults(data, false, false); - - if (data.more===true) { - more.detach().appendTo(results).text(evaluate(self.opts.formatLoadMore, page+1)); - window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); - } else { - more.remove(); - } - self.positionDropdown(); - self.resultsPage = page; - self.context = data.context; - this.opts.element.trigger({ type: "select2-loaded", items: data }); - })}); - } - }, - - /** - * Default tokenizer function which does nothing - */ - tokenize: function() { - - }, - - /** - * @param initial whether or not this is the call to this method right after the dropdown has been opened - */ - // abstract - updateResults: function (initial) { - var search = this.search, - results = this.results, - opts = this.opts, - data, - self = this, - input, - term = search.val(), - lastTerm = $.data(this.container, "select2-last-term"), - // sequence number used to drop out-of-order responses - queryNumber; - - // prevent duplicate queries against the same term - if (initial !== true && lastTerm && equal(term, lastTerm)) return; - - $.data(this.container, "select2-last-term", term); - - // if the search is currently hidden we do not alter the results - if (initial !== true && (this.showSearchInput === false || !this.opened())) { - return; - } - - function postRender() { - search.removeClass("select2-active"); - self.positionDropdown(); - if (results.find('.select2-no-results,.select2-selection-limit,.select2-searching').length) { - self.liveRegion.text(results.text()); - } - else { - self.liveRegion.text(self.opts.formatMatches(results.find('.select2-result-selectable').length)); - } - } - - function render(html) { - results.html(html); - postRender(); - } - - queryNumber = ++this.queryCount; - - var maxSelSize = this.getMaximumSelectionSize(); - if (maxSelSize >=1) { - data = this.data(); - if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) { - render("
            • " + evaluate(opts.formatSelectionTooBig, maxSelSize) + "
            • "); - return; - } - } - - if (search.val().length < opts.minimumInputLength) { - if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) { - render("
            • " + evaluate(opts.formatInputTooShort, search.val(), opts.minimumInputLength) + "
            • "); - } else { - render(""); - } - if (initial && this.showSearch) this.showSearch(true); - return; - } - - if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) { - if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) { - render("
            • " + evaluate(opts.formatInputTooLong, search.val(), opts.maximumInputLength) + "
            • "); - } else { - render(""); - } - return; - } - - if (opts.formatSearching && this.findHighlightableChoices().length === 0) { - render("
            • " + evaluate(opts.formatSearching) + "
            • "); - } - - search.addClass("select2-active"); - - this.removeHighlight(); - - // give the tokenizer a chance to pre-process the input - input = this.tokenize(); - if (input != undefined && input != null) { - search.val(input); - } - - this.resultsPage = 1; - - opts.query({ - element: opts.element, - term: search.val(), - page: this.resultsPage, - context: null, - matcher: opts.matcher, - callback: this.bind(function (data) { - var def; // default choice - - // ignore old responses - if (queryNumber != this.queryCount) { - return; - } - - // ignore a response if the select2 has been closed before it was received - if (!this.opened()) { - this.search.removeClass("select2-active"); - return; - } - - // save context, if any - this.context = (data.context===undefined) ? null : data.context; - // create a default choice and prepend it to the list - if (this.opts.createSearchChoice && search.val() !== "") { - def = this.opts.createSearchChoice.call(self, search.val(), data.results); - if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) { - if ($(data.results).filter( - function () { - return equal(self.id(this), self.id(def)); - }).length === 0) { - this.opts.createSearchChoicePosition(data.results, def); - } - } - } - - if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) { - render("
            • " + evaluate(opts.formatNoMatches, search.val()) + "
            • "); - return; - } - - results.empty(); - self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null}); - - if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) { - results.append("
            • " + self.opts.escapeMarkup(evaluate(opts.formatLoadMore, this.resultsPage)) + "
            • "); - window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); - } - - this.postprocessResults(data, initial); - - postRender(); - - this.opts.element.trigger({ type: "select2-loaded", items: data }); - })}); - }, - - // abstract - cancel: function () { - this.close(); - }, - - // abstract - blur: function () { - // if selectOnBlur == true, select the currently highlighted option - if (this.opts.selectOnBlur) - this.selectHighlighted({noFocus: true}); - - this.close(); - this.container.removeClass("select2-container-active"); - // synonymous to .is(':focus'), which is available in jquery >= 1.6 - if (this.search[0] === document.activeElement) { this.search.blur(); } - this.clearSearch(); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - }, - - // abstract - focusSearch: function () { - focus(this.search); - }, - - // abstract - selectHighlighted: function (options) { - if (this._touchMoved) { - this.clearTouchMoved(); - return; - } - var index=this.highlight(), - highlighted=this.results.find(".select2-highlighted"), - data = highlighted.closest('.select2-result').data("select2-data"); - - if (data) { - this.highlight(index); - this.onSelect(data, options); - } else if (options && options.noFocus) { - this.close(); - } - }, - - // abstract - getPlaceholder: function () { - var placeholderOption; - return this.opts.element.attr("placeholder") || - this.opts.element.attr("data-placeholder") || // jquery 1.4 compat - this.opts.element.data("placeholder") || - this.opts.placeholder || - ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined); - }, - - // abstract - getPlaceholderOption: function() { - if (this.select) { - var firstOption = this.select.children('option').first(); - if (this.opts.placeholderOption !== undefined ) { - //Determine the placeholder option based on the specified placeholderOption setting - return (this.opts.placeholderOption === "first" && firstOption) || - (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select)); - } else if ($.trim(firstOption.text()) === "" && firstOption.val() === "") { - //No explicit placeholder option specified, use the first if it's blank - return firstOption; - } - } - }, - - /** - * Get the desired width for the container element. This is - * derived first from option `width` passed to select2, then - * the inline 'style' on the original element, and finally - * falls back to the jQuery calculated element width. - */ - // abstract - initContainerWidth: function () { - function resolveContainerWidth() { - var style, attrs, matches, i, l, attr; - - if (this.opts.width === "off") { - return null; - } else if (this.opts.width === "element"){ - return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'; - } else if (this.opts.width === "copy" || this.opts.width === "resolve") { - // check if there is inline style on the element that contains width - style = this.opts.element.attr('style'); - if (style !== undefined) { - attrs = style.split(';'); - for (i = 0, l = attrs.length; i < l; i = i + 1) { - attr = attrs[i].replace(/\s/g, ''); - matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i); - if (matches !== null && matches.length >= 1) - return matches[1]; - } - } - - if (this.opts.width === "resolve") { - // next check if css('width') can resolve a width that is percent based, this is sometimes possible - // when attached to input type=hidden or elements hidden via css - style = this.opts.element.css('width'); - if (style.indexOf("%") > 0) return style; - - // finally, fallback on the calculated width of the element - return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'); - } - - return null; - } else if ($.isFunction(this.opts.width)) { - return this.opts.width(); - } else { - return this.opts.width; - } - }; - - var width = resolveContainerWidth.call(this); - if (width !== null) { - this.container.css("width", width); - } - } - }); - - SingleSelect2 = clazz(AbstractSelect2, { - - // single - - createContainer: function () { - var container = $(document.createElement("div")).attr({ - "class": "select2-container" - }).html([ - "", - "  ", - " ", - "", - "", - "", - "
              ", - " ", - "
                ", - "
              ", - "
              "].join("")); - return container; - }, - - // single - enableInterface: function() { - if (this.parent.enableInterface.apply(this, arguments)) { - this.focusser.prop("disabled", !this.isInterfaceEnabled()); - } - }, - - // single - opening: function () { - var el, range, len; - - if (this.opts.minimumResultsForSearch >= 0) { - this.showSearch(true); - } - - this.parent.opening.apply(this, arguments); - - if (this.showSearchInput !== false) { - // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range - // all other browsers handle this just fine - - this.search.val(this.focusser.val()); - } - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - // move the cursor to the end after focussing, otherwise it will be at the beginning and - // new text will appear *before* focusser.val() - el = this.search.get(0); - if (el.createTextRange) { - range = el.createTextRange(); - range.collapse(false); - range.select(); - } else if (el.setSelectionRange) { - len = this.search.val().length; - el.setSelectionRange(len, len); - } - } - - // initializes search's value with nextSearchTerm (if defined by user) - // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter - if(this.search.val() === "") { - if(this.nextSearchTerm != undefined){ - this.search.val(this.nextSearchTerm); - this.search.select(); - } - } - - this.focusser.prop("disabled", true).val(""); - this.updateResults(true); - this.opts.element.trigger($.Event("select2-open")); - }, - - // single - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - - this.focusser.prop("disabled", false); - - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - }, - - // single - focus: function () { - if (this.opened()) { - this.close(); - } else { - this.focusser.prop("disabled", false); - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - } - }, - - // single - isFocused: function () { - return this.container.hasClass("select2-container-active"); - }, - - // single - cancel: function () { - this.parent.cancel.apply(this, arguments); - this.focusser.prop("disabled", false); - - if (this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - }, - - // single - destroy: function() { - $("label[for='" + this.focusser.attr('id') + "']") - .attr('for', this.opts.element.attr("id")); - this.parent.destroy.apply(this, arguments); - - cleanupJQueryElements.call(this, - "selection", - "focusser" - ); - }, - - // single - initContainer: function () { - - var selection, - container = this.container, - dropdown = this.dropdown, - idSuffix = nextUid(), - elementLabel; - - if (this.opts.minimumResultsForSearch < 0) { - this.showSearch(false); - } else { - this.showSearch(true); - } - - this.selection = selection = container.find(".select2-choice"); - - this.focusser = container.find(".select2-focusser"); - - // add aria associations - selection.find(".select2-chosen").attr("id", "select2-chosen-"+idSuffix); - this.focusser.attr("aria-labelledby", "select2-chosen-"+idSuffix); - this.results.attr("id", "select2-results-"+idSuffix); - this.search.attr("aria-owns", "select2-results-"+idSuffix); - - // rewrite labels from original element to focusser - this.focusser.attr("id", "s2id_autogen"+idSuffix); - - elementLabel = $("label[for='" + this.opts.element.attr("id") + "']"); - - this.focusser.prev() - .text(elementLabel.text()) - .attr('for', this.focusser.attr('id')); - - // Ensure the original element retains an accessible name - var originalTitle = this.opts.element.attr("title"); - this.opts.element.attr("title", (originalTitle || elementLabel.text())); - - this.focusser.attr("tabindex", this.elementTabIndex); - - // write label for search field using the label from the focusser element - this.search.attr("id", this.focusser.attr('id') + '_search'); - - this.search.prev() - .text($("label[for='" + this.focusser.attr('id') + "']").text()) - .attr('for', this.search.attr('id')); - - this.search.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - return; - } - - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.TAB: - this.selectHighlighted({noFocus: true}); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - })); - - this.search.on("blur", this.bind(function(e) { - // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown. - // without this the search field loses focus which is annoying - if (document.activeElement === this.body.get(0)) { - window.setTimeout(this.bind(function() { - if (this.opened()) { - this.search.focus(); - } - }), 0); - } - })); - - this.focusser.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { - return; - } - - if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { - killEvent(e); - return; - } - - if (e.which == KEY.DOWN || e.which == KEY.UP - || (e.which == KEY.ENTER && this.opts.openOnEnter)) { - - if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return; - - this.open(); - killEvent(e); - return; - } - - if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { - if (this.opts.allowClear) { - this.clear(); - } - killEvent(e); - return; - } - })); - - - installKeyUpChangeEvent(this.focusser); - this.focusser.on("keyup-change input", this.bind(function(e) { - if (this.opts.minimumResultsForSearch >= 0) { - e.stopPropagation(); - if (this.opened()) return; - this.open(); - } - })); - - selection.on("mousedown touchstart", "abbr", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - this.clear(); - killEventImmediately(e); - this.close(); - this.selection.focus(); - })); - - selection.on("mousedown touchstart", this.bind(function (e) { - // Prevent IE from generating a click event on the body - reinsertElement(selection); - - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - - if (this.opened()) { - this.close(); - } else if (this.isInterfaceEnabled()) { - this.open(); - } - - killEvent(e); - })); - - dropdown.on("mousedown touchstart", this.bind(function() { - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - } - })); - - selection.on("focus", this.bind(function(e) { - killEvent(e); - })); - - this.focusser.on("focus", this.bind(function(){ - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - })).on("blur", this.bind(function() { - if (!this.opened()) { - this.container.removeClass("select2-container-active"); - this.opts.element.trigger($.Event("select2-blur")); - } - })); - this.search.on("focus", this.bind(function(){ - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - })); - - this.initContainerWidth(); - this.opts.element.addClass("select2-offscreen"); - this.setPlaceholder(); - - }, - - // single - clear: function(triggerChange) { - var data=this.selection.data("select2-data"); - if (data) { // guard against queued quick consecutive clicks - var evt = $.Event("select2-clearing"); - this.opts.element.trigger(evt); - if (evt.isDefaultPrevented()) { - return; - } - var placeholderOption = this.getPlaceholderOption(); - this.opts.element.val(placeholderOption ? placeholderOption.val() : ""); - this.selection.find(".select2-chosen").empty(); - this.selection.removeData("select2-data"); - this.setPlaceholder(); - - if (triggerChange !== false){ - this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); - this.triggerChange({removed:data}); - } - } - }, - - /** - * Sets selection based on source element's value - */ - // single - initSelection: function () { - var selected; - if (this.isPlaceholderOptionSelected()) { - this.updateSelection(null); - this.close(); - this.setPlaceholder(); - } else { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(selected){ - if (selected !== undefined && selected !== null) { - self.updateSelection(selected); - self.close(); - self.setPlaceholder(); - self.nextSearchTerm = self.opts.nextSearchTerm(selected, self.search.val()); - } - }); - } - }, - - isPlaceholderOptionSelected: function() { - var placeholderOption; - if (this.getPlaceholder() === undefined) return false; // no placeholder specified so no option should be considered - return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected")) - || (this.opts.element.val() === "") - || (this.opts.element.val() === undefined) - || (this.opts.element.val() === null); - }, - - // single - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments), - self=this; - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install the selection initializer - opts.initSelection = function (element, callback) { - var selected = element.find("option").filter(function() { return this.selected && !this.disabled }); - // a single select box always has a value, no need to null check 'selected' - callback(self.optionToData(selected)); - }; - } else if ("data" in opts) { - // install default initSelection when applied to hidden input and data is local - opts.initSelection = opts.initSelection || function (element, callback) { - var id = element.val(); - //search in data by id, storing the actual matching item - var match = null; - opts.query({ - matcher: function(term, text, el){ - var is_match = equal(id, opts.id(el)); - if (is_match) { - match = el; - } - return is_match; - }, - callback: !$.isFunction(callback) ? $.noop : function() { - callback(match); - } - }); - }; - } - - return opts; - }, - - // single - getPlaceholder: function() { - // if a placeholder is specified on a single select without a valid placeholder option ignore it - if (this.select) { - if (this.getPlaceholderOption() === undefined) { - return undefined; - } - } - - return this.parent.getPlaceholder.apply(this, arguments); - }, - - // single - setPlaceholder: function () { - var placeholder = this.getPlaceholder(); - - if (this.isPlaceholderOptionSelected() && placeholder !== undefined) { - - // check for a placeholder option if attached to a select - if (this.select && this.getPlaceholderOption() === undefined) return; - - this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder)); - - this.selection.addClass("select2-default"); - - this.container.removeClass("select2-allowclear"); - } - }, - - // single - postprocessResults: function (data, initial, noHighlightUpdate) { - var selected = 0, self = this, showSearchInput = true; - - // find the selected element in the result list - - this.findHighlightableChoices().each2(function (i, elm) { - if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { - selected = i; - return false; - } - }); - - // and highlight it - if (noHighlightUpdate !== false) { - if (initial === true && selected >= 0) { - this.highlight(selected); - } else { - this.highlight(0); - } - } - - // hide the search box if this is the first we got the results and there are enough of them for search - - if (initial === true) { - var min = this.opts.minimumResultsForSearch; - if (min >= 0) { - this.showSearch(countResults(data.results) >= min); - } - } - }, - - // single - showSearch: function(showSearchInput) { - if (this.showSearchInput === showSearchInput) return; - - this.showSearchInput = showSearchInput; - - this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput); - this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput); - //add "select2-with-searchbox" to the container if search box is shown - $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput); - }, - - // single - onSelect: function (data, options) { - - if (!this.triggerSelect(data)) { return; } - - var old = this.opts.element.val(), - oldData = this.data(); - - this.opts.element.val(this.id(data)); - this.updateSelection(data); - - this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data }); - - this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val()); - this.close(); - - if ((!options || !options.noFocus) && this.opts.shouldFocusInput(this)) { - this.focusser.focus(); - } - - if (!equal(old, this.id(data))) { - this.triggerChange({ added: data, removed: oldData }); - } - }, - - // single - updateSelection: function (data) { - - var container=this.selection.find(".select2-chosen"), formatted, cssClass; - - this.selection.data("select2-data", data); - - container.empty(); - if (data !== null) { - formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup); - } - if (formatted !== undefined) { - container.append(formatted); - } - cssClass=this.opts.formatSelectionCssClass(data, container); - if (cssClass !== undefined) { - container.addClass(cssClass); - } - - this.selection.removeClass("select2-default"); - - if (this.opts.allowClear && this.getPlaceholder() !== undefined) { - this.container.addClass("select2-allowclear"); - } - }, - - // single - val: function () { - var val, - triggerChange = false, - data = null, - self = this, - oldData = this.data(); - - if (arguments.length === 0) { - return this.opts.element.val(); - } - - val = arguments[0]; - - if (arguments.length > 1) { - triggerChange = arguments[1]; - } - - if (this.select) { - this.select - .val(val) - .find("option").filter(function() { return this.selected }).each2(function (i, elm) { - data = self.optionToData(elm); - return false; - }); - this.updateSelection(data); - this.setPlaceholder(); - if (triggerChange) { - this.triggerChange({added: data, removed:oldData}); - } - } else { - // val is an id. !val is true for [undefined,null,'',0] - 0 is legal - if (!val && val !== 0) { - this.clear(triggerChange); - return; - } - if (this.opts.initSelection === undefined) { - throw new Error("cannot call val() if initSelection() is not defined"); - } - this.opts.element.val(val); - this.opts.initSelection(this.opts.element, function(data){ - self.opts.element.val(!data ? "" : self.id(data)); - self.updateSelection(data); - self.setPlaceholder(); - if (triggerChange) { - self.triggerChange({added: data, removed:oldData}); - } - }); - } - }, - - // single - clearSearch: function () { - this.search.val(""); - this.focusser.val(""); - }, - - // single - data: function(value) { - var data, - triggerChange = false; - - if (arguments.length === 0) { - data = this.selection.data("select2-data"); - if (data == undefined) data = null; - return data; - } else { - if (arguments.length > 1) { - triggerChange = arguments[1]; - } - if (!value) { - this.clear(triggerChange); - } else { - data = this.data(); - this.opts.element.val(!value ? "" : this.id(value)); - this.updateSelection(value); - if (triggerChange) { - this.triggerChange({added: value, removed:data}); - } - } - } - } - }); - - MultiSelect2 = clazz(AbstractSelect2, { - - // multi - createContainer: function () { - var container = $(document.createElement("div")).attr({ - "class": "select2-container select2-container-multi" - }).html([ - "
                ", - "
              • ", - " ", - " ", - "
              • ", - "
              ", - "
              ", - "
                ", - "
              ", - "
              "].join("")); - return container; - }, - - // multi - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments), - self=this; - - // TODO validate placeholder is a string if specified - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install the selection initializer - opts.initSelection = function (element, callback) { - - var data = []; - - element.find("option").filter(function() { return this.selected && !this.disabled }).each2(function (i, elm) { - data.push(self.optionToData(elm)); - }); - callback(data); - }; - } else if ("data" in opts) { - // install default initSelection when applied to hidden input and data is local - opts.initSelection = opts.initSelection || function (element, callback) { - var ids = splitVal(element.val(), opts.separator); - //search in data by array of ids, storing matching items in a list - var matches = []; - opts.query({ - matcher: function(term, text, el){ - var is_match = $.grep(ids, function(id) { - return equal(id, opts.id(el)); - }).length; - if (is_match) { - matches.push(el); - } - return is_match; - }, - callback: !$.isFunction(callback) ? $.noop : function() { - // reorder matches based on the order they appear in the ids array because right now - // they are in the order in which they appear in data array - var ordered = []; - for (var i = 0; i < ids.length; i++) { - var id = ids[i]; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - if (equal(id, opts.id(match))) { - ordered.push(match); - matches.splice(j, 1); - break; - } - } - } - callback(ordered); - } - }); - }; - } - - return opts; - }, - - // multi - selectChoice: function (choice) { - - var selected = this.container.find(".select2-search-choice-focus"); - if (selected.length && choice && choice[0] == selected[0]) { - - } else { - if (selected.length) { - this.opts.element.trigger("choice-deselected", selected); - } - selected.removeClass("select2-search-choice-focus"); - if (choice && choice.length) { - this.close(); - choice.addClass("select2-search-choice-focus"); - this.opts.element.trigger("choice-selected", choice); - } - } - }, - - // multi - destroy: function() { - $("label[for='" + this.search.attr('id') + "']") - .attr('for', this.opts.element.attr("id")); - this.parent.destroy.apply(this, arguments); - - cleanupJQueryElements.call(this, - "searchContainer", - "selection" - ); - }, - - // multi - initContainer: function () { - - var selector = ".select2-choices", selection; - - this.searchContainer = this.container.find(".select2-search-field"); - this.selection = selection = this.container.find(selector); - - var _this = this; - this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) { - //killEvent(e); - _this.search[0].focus(); - _this.selectChoice($(this)); - }); - - // rewrite labels from original element to focusser - this.search.attr("id", "s2id_autogen"+nextUid()); - - this.search.prev() - .text($("label[for='" + this.opts.element.attr("id") + "']").text()) - .attr('for', this.search.attr('id')); - - this.search.on("input paste", this.bind(function() { - if (!this.isInterfaceEnabled()) return; - if (!this.opened()) { - this.open(); - } - })); - - this.search.attr("tabindex", this.elementTabIndex); - - this.keydowns = 0; - this.search.on("keydown", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - ++this.keydowns; - var selected = selection.find(".select2-search-choice-focus"); - var prev = selected.prev(".select2-search-choice:not(.select2-locked)"); - var next = selected.next(".select2-search-choice:not(.select2-locked)"); - var pos = getCursorInfo(this.search); - - if (selected.length && - (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) { - var selectedChoice = selected; - if (e.which == KEY.LEFT && prev.length) { - selectedChoice = prev; - } - else if (e.which == KEY.RIGHT) { - selectedChoice = next.length ? next : null; - } - else if (e.which === KEY.BACKSPACE) { - if (this.unselect(selected.first())) { - this.search.width(10); - selectedChoice = prev.length ? prev : next; - } - } else if (e.which == KEY.DELETE) { - if (this.unselect(selected.first())) { - this.search.width(10); - selectedChoice = next.length ? next : null; - } - } else if (e.which == KEY.ENTER) { - selectedChoice = null; - } - - this.selectChoice(selectedChoice); - killEvent(e); - if (!selectedChoice || !selectedChoice.length) { - this.open(); - } - return; - } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1) - || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) { - - this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last()); - killEvent(e); - return; - } else { - this.selectChoice(null); - } - - if (this.opened()) { - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.TAB: - this.selectHighlighted({noFocus:true}); - this.close(); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - } - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) - || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { - return; - } - - if (e.which === KEY.ENTER) { - if (this.opts.openOnEnter === false) { - return; - } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { - return; - } - } - - this.open(); - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - } - - if (e.which === KEY.ENTER) { - // prevent form from being submitted - killEvent(e); - } - - })); - - this.search.on("keyup", this.bind(function (e) { - this.keydowns = 0; - this.resizeSearch(); - }) - ); - - this.search.on("blur", this.bind(function(e) { - this.container.removeClass("select2-container-active"); - this.search.removeClass("select2-focused"); - this.selectChoice(null); - if (!this.opened()) this.clearSearch(); - e.stopImmediatePropagation(); - this.opts.element.trigger($.Event("select2-blur")); - })); - - this.container.on("click", selector, this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - if ($(e.target).closest(".select2-search-choice").length > 0) { - // clicked inside a select2 search choice, do not open - return; - } - this.selectChoice(null); - this.clearPlaceholder(); - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.open(); - this.focusSearch(); - e.preventDefault(); - })); - - this.container.on("focus", selector, this.bind(function () { - if (!this.isInterfaceEnabled()) return; - if (!this.container.hasClass("select2-container-active")) { - this.opts.element.trigger($.Event("select2-focus")); - } - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - this.clearPlaceholder(); - })); - - this.initContainerWidth(); - this.opts.element.addClass("select2-offscreen"); - - // set the placeholder if necessary - this.clearSearch(); - }, - - // multi - enableInterface: function() { - if (this.parent.enableInterface.apply(this, arguments)) { - this.search.prop("disabled", !this.isInterfaceEnabled()); - } - }, - - // multi - initSelection: function () { - var data; - if (this.opts.element.val() === "" && this.opts.element.text() === "") { - this.updateSelection([]); - this.close(); - // set the placeholder if necessary - this.clearSearch(); - } - if (this.select || this.opts.element.val() !== "") { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(data){ - if (data !== undefined && data !== null) { - self.updateSelection(data); - self.close(); - // set the placeholder if necessary - self.clearSearch(); - } - }); - } - }, - - // multi - clearSearch: function () { - var placeholder = this.getPlaceholder(), - maxWidth = this.getMaxSearchWidth(); - - if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { - this.search.val(placeholder).addClass("select2-default"); - // stretch the search box to full width of the container so as much of the placeholder is visible as possible - // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944 - this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width")); - } else { - this.search.val("").width(10); - } - }, - - // multi - clearPlaceholder: function () { - if (this.search.hasClass("select2-default")) { - this.search.val("").removeClass("select2-default"); - } - }, - - // multi - opening: function () { - this.clearPlaceholder(); // should be done before super so placeholder is not used to search - this.resizeSearch(); - - this.parent.opening.apply(this, arguments); - - this.focusSearch(); - - // initializes search's value with nextSearchTerm (if defined by user) - // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter - if(this.search.val() === "") { - if(this.nextSearchTerm != undefined){ - this.search.val(this.nextSearchTerm); - this.search.select(); - } - } - - this.updateResults(true); - if (this.opts.shouldFocusInput(this)) { - this.search.focus(); - } - this.opts.element.trigger($.Event("select2-open")); - }, - - // multi - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - }, - - // multi - focus: function () { - this.close(); - this.search.focus(); - }, - - // multi - isFocused: function () { - return this.search.hasClass("select2-focused"); - }, - - // multi - updateSelection: function (data) { - var ids = [], filtered = [], self = this; - - // filter out duplicates - $(data).each(function () { - if (indexOf(self.id(this), ids) < 0) { - ids.push(self.id(this)); - filtered.push(this); - } - }); - data = filtered; - - this.selection.find(".select2-search-choice").remove(); - $(data).each(function () { - self.addSelectedChoice(this); - }); - self.postprocessResults(); - }, - - // multi - tokenize: function() { - var input = this.search.val(); - input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts); - if (input != null && input != undefined) { - this.search.val(input); - if (input.length > 0) { - this.open(); - } - } - - }, - - // multi - onSelect: function (data, options) { - - if (!this.triggerSelect(data)) { return; } - - this.addSelectedChoice(data); - - this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); - - // keep track of the search's value before it gets cleared - this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val()); - - this.clearSearch(); - this.updateResults(); - - if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true); - - if (this.opts.closeOnSelect) { - this.close(); - this.search.width(10); - } else { - if (this.countSelectableResults()>0) { - this.search.width(10); - this.resizeSearch(); - if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) { - // if we reached max selection size repaint the results so choices - // are replaced with the max selection reached message - this.updateResults(true); - } else { - // initializes search's value with nextSearchTerm and update search result - if(this.nextSearchTerm != undefined){ - this.search.val(this.nextSearchTerm); - this.updateResults(); - this.search.select(); - } - } - this.positionDropdown(); - } else { - // if nothing left to select close - this.close(); - this.search.width(10); - } - } - - // since its not possible to select an element that has already been - // added we do not need to check if this is a new element before firing change - this.triggerChange({ added: data }); - - if (!options || !options.noFocus) - this.focusSearch(); - }, - - // multi - cancel: function () { - this.close(); - this.focusSearch(); - }, - - addSelectedChoice: function (data) { - var enableChoice = !data.locked, - enabledItem = $( - "
            • " + - "
              " + - " " + - "
            • "), - disabledItem = $( - "
            • " + - "
              " + - "
            • "); - var choice = enableChoice ? enabledItem : disabledItem, - id = this.id(data), - val = this.getVal(), - formatted, - cssClass; - - formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup); - if (formatted != undefined) { - choice.find("div").replaceWith("
              "+formatted+"
              "); - } - cssClass=this.opts.formatSelectionCssClass(data, choice.find("div")); - if (cssClass != undefined) { - choice.addClass(cssClass); - } - - if(enableChoice){ - choice.find(".select2-search-choice-close") - .on("mousedown", killEvent) - .on("click dblclick", this.bind(function (e) { - if (!this.isInterfaceEnabled()) return; - - this.unselect($(e.target)); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - killEvent(e); - this.close(); - this.focusSearch(); - })).on("focus", this.bind(function () { - if (!this.isInterfaceEnabled()) return; - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - })); - } - - choice.data("select2-data", data); - choice.insertBefore(this.searchContainer); - - val.push(id); - this.setVal(val); - }, - - // multi - unselect: function (selected) { - var val = this.getVal(), - data, - index; - selected = selected.closest(".select2-search-choice"); - - if (selected.length === 0) { - throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; - } - - data = selected.data("select2-data"); - - if (!data) { - // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued - // and invoked on an element already removed - return; - } - - var evt = $.Event("select2-removing"); - evt.val = this.id(data); - evt.choice = data; - this.opts.element.trigger(evt); - - if (evt.isDefaultPrevented()) { - return false; - } - - while((index = indexOf(this.id(data), val)) >= 0) { - val.splice(index, 1); - this.setVal(val); - if (this.select) this.postprocessResults(); - } - - selected.remove(); - - this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); - this.triggerChange({ removed: data }); - - return true; - }, - - // multi - postprocessResults: function (data, initial, noHighlightUpdate) { - var val = this.getVal(), - choices = this.results.find(".select2-result"), - compound = this.results.find(".select2-result-with-children"), - self = this; - - choices.each2(function (i, choice) { - var id = self.id(choice.data("select2-data")); - if (indexOf(id, val) >= 0) { - choice.addClass("select2-selected"); - // mark all children of the selected parent as selected - choice.find(".select2-result-selectable").addClass("select2-selected"); - } - }); - - compound.each2(function(i, choice) { - // hide an optgroup if it doesn't have any selectable children - if (!choice.is('.select2-result-selectable') - && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { - choice.addClass("select2-selected"); - } - }); - - if (this.highlight() == -1 && noHighlightUpdate !== false){ - self.highlight(0); - } - - //If all results are chosen render formatNoMatches - if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){ - if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) { - if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) { - this.results.append("
            • " + evaluate(self.opts.formatNoMatches, self.search.val()) + "
            • "); - } - } - } - - }, - - // multi - getMaxSearchWidth: function() { - return this.selection.width() - getSideBorderPadding(this.search); - }, - - // multi - resizeSearch: function () { - var minimumWidth, left, maxWidth, containerLeft, searchWidth, - sideBorderPadding = getSideBorderPadding(this.search); - - minimumWidth = measureTextWidth(this.search) + 10; - - left = this.search.offset().left; - - maxWidth = this.selection.width(); - containerLeft = this.selection.offset().left; - - searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; - - if (searchWidth < minimumWidth) { - searchWidth = maxWidth - sideBorderPadding; - } - - if (searchWidth < 40) { - searchWidth = maxWidth - sideBorderPadding; - } - - if (searchWidth <= 0) { - searchWidth = minimumWidth; - } - - this.search.width(Math.floor(searchWidth)); - }, - - // multi - getVal: function () { - var val; - if (this.select) { - val = this.select.val(); - return val === null ? [] : val; - } else { - val = this.opts.element.val(); - return splitVal(val, this.opts.separator); - } - }, - - // multi - setVal: function (val) { - var unique; - if (this.select) { - this.select.val(val); - } else { - unique = []; - // filter out duplicates - $(val).each(function () { - if (indexOf(this, unique) < 0) unique.push(this); - }); - this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); - } - }, - - // multi - buildChangeDetails: function (old, current) { - var current = current.slice(0), - old = old.slice(0); - - // remove intersection from each array - for (var i = 0; i < current.length; i++) { - for (var j = 0; j < old.length; j++) { - if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) { - current.splice(i, 1); - if(i>0){ - i--; - } - old.splice(j, 1); - j--; - } - } - } - - return {added: current, removed: old}; - }, - - - // multi - val: function (val, triggerChange) { - var oldData, self=this; - - if (arguments.length === 0) { - return this.getVal(); - } - - oldData=this.data(); - if (!oldData.length) oldData=[]; - - // val is an id. !val is true for [undefined,null,'',0] - 0 is legal - if (!val && val !== 0) { - this.opts.element.val(""); - this.updateSelection([]); - this.clearSearch(); - if (triggerChange) { - this.triggerChange({added: this.data(), removed: oldData}); - } - return; - } - - // val is a list of ids - this.setVal(val); - - if (this.select) { - this.opts.initSelection(this.select, this.bind(this.updateSelection)); - if (triggerChange) { - this.triggerChange(this.buildChangeDetails(oldData, this.data())); - } - } else { - if (this.opts.initSelection === undefined) { - throw new Error("val() cannot be called if initSelection() is not defined"); - } - - this.opts.initSelection(this.opts.element, function(data){ - var ids=$.map(data, self.id); - self.setVal(ids); - self.updateSelection(data); - self.clearSearch(); - if (triggerChange) { - self.triggerChange(self.buildChangeDetails(oldData, self.data())); - } - }); - } - this.clearSearch(); - }, - - // multi - onSortStart: function() { - if (this.select) { - throw new Error("Sorting of elements is not supported when attached to instead."); - } - - // collapse search field into 0 width so its container can be collapsed as well - this.search.width(0); - // hide the container - this.searchContainer.hide(); - }, - - // multi - onSortEnd:function() { - - var val=[], self=this; - - // show search and move it to the end of the list - this.searchContainer.show(); - // make sure the search container is the last item in the list - this.searchContainer.appendTo(this.searchContainer.parent()); - // since we collapsed the width in dragStarted, we resize it here - this.resizeSearch(); - - // update selection - this.selection.find(".select2-search-choice").each(function() { - val.push(self.opts.id($(this).data("select2-data"))); - }); - this.setVal(val); - this.triggerChange(); - }, - - // multi - data: function(values, triggerChange) { - var self=this, ids, old; - if (arguments.length === 0) { - return this.selection - .children(".select2-search-choice") - .map(function() { return $(this).data("select2-data"); }) - .get(); - } else { - old = this.data(); - if (!values) { values = []; } - ids = $.map(values, function(e) { return self.opts.id(e); }); - this.setVal(ids); - this.updateSelection(values); - this.clearSearch(); - if (triggerChange) { - this.triggerChange(this.buildChangeDetails(old, this.data())); - } - } - } - }); - - $.fn.select2 = function () { - - var args = Array.prototype.slice.call(arguments, 0), - opts, - select2, - method, value, multiple, - allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"], - valueMethods = ["opened", "isFocused", "container", "dropdown"], - propertyMethods = ["val", "data"], - methodsMap = { search: "externalSearch" }; - - this.each(function () { - if (args.length === 0 || typeof(args[0]) === "object") { - opts = args.length === 0 ? {} : $.extend({}, args[0]); - opts.element = $(this); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - multiple = opts.element.prop("multiple"); - } else { - multiple = opts.multiple || false; - if ("tags" in opts) {opts.multiple = multiple = true;} - } - - select2 = multiple ? new window.Select2["class"].multi() : new window.Select2["class"].single(); - select2.init(opts); - } else if (typeof(args[0]) === "string") { - - if (indexOf(args[0], allowedMethods) < 0) { - throw "Unknown method: " + args[0]; - } - - value = undefined; - select2 = $(this).data("select2"); - if (select2 === undefined) return; - - method=args[0]; - - if (method === "container") { - value = select2.container; - } else if (method === "dropdown") { - value = select2.dropdown; - } else { - if (methodsMap[method]) method = methodsMap[method]; - - value = select2[method].apply(select2, args.slice(1)); - } - if (indexOf(args[0], valueMethods) >= 0 - || (indexOf(args[0], propertyMethods) >= 0 && args.length == 1)) { - return false; // abort the iteration, ready to return first matched value - } - } else { - throw "Invalid arguments to select2 plugin: " + args; - } - }); - return (value === undefined) ? this : value; - }; - - // plugin defaults, accessible to users - $.fn.select2.defaults = { - width: "copy", - loadMorePadding: 0, - closeOnSelect: true, - openOnEnter: true, - containerCss: {}, - dropdownCss: {}, - containerCssClass: "", - dropdownCssClass: "", - formatResult: function(result, container, query, escapeMarkup) { - var markup=[]; - markMatch(result.text, query.term, markup, escapeMarkup); - return markup.join(""); - }, - formatSelection: function (data, container, escapeMarkup) { - return data ? escapeMarkup(data.text) : undefined; - }, - sortResults: function (results, container, query) { - return results; - }, - formatResultCssClass: function(data) {return data.css;}, - formatSelectionCssClass: function(data, container) {return undefined;}, - formatMatches: function (matches) { return matches + " results are available, use up and down arrow keys to navigate."; }, - formatNoMatches: function () { return "No matches found"; }, - formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " or more character" + (n == 1? "" : "s"); }, - formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); }, - formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, - formatLoadMore: function (pageNumber) { return "Loading more results…"; }, - formatSearching: function () { return "Searching…"; }, - minimumResultsForSearch: 0, - minimumInputLength: 0, - maximumInputLength: null, - maximumSelectionSize: 0, - id: function (e) { return e == undefined ? null : e.id; }, - matcher: function(term, text) { - return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0; - }, - separator: ",", - tokenSeparators: [], - tokenizer: defaultTokenizer, - escapeMarkup: defaultEscapeMarkup, - blurOnChange: false, - selectOnBlur: false, - adaptContainerCssClass: function(c) { return c; }, - adaptDropdownCssClass: function(c) { return null; }, - nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; }, - searchInputPlaceholder: '', - createSearchChoicePosition: 'top', - shouldFocusInput: function (instance) { - // Attempt to detect touch devices - var supportsTouchEvents = (('ontouchstart' in window) || - (navigator.msMaxTouchPoints > 0)); - - // Only devices which support touch events should be special cased - if (!supportsTouchEvents) { - return true; - } - - // Never focus the input if search is disabled - if (instance.opts.minimumResultsForSearch < 0) { - return false; - } - - return true; - } - }; - - $.fn.select2.ajaxDefaults = { - transport: $.ajax, - params: { - type: "GET", - cache: false, - dataType: "json" - } - }; - - // exports - window.Select2 = { - query: { - ajax: ajax, - local: local, - tags: tags - }, util: { - debounce: debounce, - markMatch: markMatch, - escapeMarkup: defaultEscapeMarkup, - stripDiacritics: stripDiacritics - }, "class": { - "abstract": AbstractSelect2, - "single": SingleSelect2, - "multi": MultiSelect2 - } - }; - -}(jQuery)); - angular.module('gettext').run(['gettextCatalog', function (gettextCatalog) { /* jshint -W100 */ + gettextCatalog.setStrings('de', {"24x7 Contact:":"24x7 Kontakt:","24x7 Support:":"24x7 Support:"," Open a New Support Case":" Neues Support-Ticket erstellen"," Select Log":" Protokoll auswählen","Account Name:":"Accountname:","Account Number:":"Accountnummer:","Account:":"Account:","Add":"Hinzufügen","Add Comment":"Kommentar hinzufügen","Advanced Mission Critical":"Advanced Mission Critical","Alternate Case ID:":"Alternative Ticket-ID:","Attach Foreman logs:":"Foreman-Protokolle anhängen:","Attach local file":"Lokale Datei anhängen","Attached":"Angehängt","Attached By":"Angehängt durch","Attached Files":"Angehängte Dateien","Attachments":"Anhänge","Attachments:":"Anhänge:","Available Log Files":"Verfügbare Protokolldateien","Bugzilla Number":"Bugzilla-Nummer","Bugzilla Tickets":"Bugzilla-Tickets","Cancel":"Abbrechen","Case Discussion":"Ticketdiskussion","Case Group:":"Ticketgruppe:","Case Type:":"Tickettyp:","Chat offline":"Offline chatten","Chat with support":"Mit Support-Team chatten","Choose File(s) To Attach:":"Wählen Sie die anzuhängenden Dateien:","Close messages":"Nachrichten schließen","Comment:":"Kommentar:","Create Case Group":"Ticketgruppe erstellen","Create New Case Group":"Neue Ticketgruppe erstellen","Delete":"Löschen","Delete Group":"Gruppe löschen","Deleting attachment:":"Anhang löschen:","Description":"Beschreibung","Description:":"Beschreibung:","Details":"Details","Diagnose":"Diagnose","Draft saved":"Entwurf gespeichert","Email Notification Recipients":"Empfänger von E-Mail-Benachrichtigungen","Environment":"Umgebung","Export All as CSV":"Alle als CSV exportieren","Exporting CSV...":"CSV wird exportiert...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"Dateinamen sind auf 80 Zeichen beschränkt. Maximale Dateigröße für Anhänge zum Web-Upload ist 250 MB. Für größere Dateien bitte FTP verwenden (dropbox.redhat.com).","Filename":"Dateiname","Files to Attach":"Anzuhängende Dateien","First Name":"Vorname","Group":"Gruppe","Group:":"Gruppe:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Wenn das Problem schwerwiegender ist oder dem Ticket eine höhere Priorität zugeordnet werden soll, fügen Sie bitte einen ausführlichen Kommentar hinzu. Das Ticket wird anschließend von einem Support-Manager überprüft.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Falls Sie dennoch ein Support-Ticket einreichen möchten, klicken Sie auf \"Neues Support-Ticket erstellen\". In das Ticket werden Informationen aus dem Protokoll eingefügt, das Sie zuvor ausgewählt haben.","Is Public:":"Ist öffentlich:","Last Name":"Nachname","Last Updated:":"Zuletzt aktualisiert:","Learn more":"Mehr dazu","Log File Viewer":"Protokollbetrachter","Log In":"Anmelden","Log Out":"Abmelden","Logged into the Red Hat Customer Portal as":"Beim Red Hat Kundenportal angemeldet als","My Account":"Mein Account","Name":"Name","Next":"Weiter","No attachments added":"Keine Anhänge hinzugefügt","No cases found with given filters.":"Keine Tickets gefunden mit diesen Filtern.","No cases found with given search criteria.":"Keine Tickets gefunden mit diesen Suchkriterien.","No groups found.":"Keine Gruppen gefunden.","No linked bugzillas":"Keine verlinkten Bugzillas","Not Logged into the Red Hat Customer Portal":"Nicht beim Red Hat Kundenportal angemeldet","Note:":"Hinweis:","Notes:":"Hinweise:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"Nachdem Sie Ihre Protokolldatei ausgewählt haben, können Sie einen beliebigen Teil dieser Protokolldatei untersuchen, indem Sie auf die Schaltfläche \"Red Hat Diagnose\" klicken. Daraufhin werden die relevanten Artikel und Lösungen aus der Red Hat Knowledgebase angezeigt.","Open a New Support Case":"Neues Support-Ticket erstellen","Opened:":"Erstellt:","Owner":"Besitzer","Owner:":"Besitzer:","Password":"Passwort","Previous":"Zurück","Product":"Produkt","Product Version:":"Produktversion:","Product:":"Produkt:","Recommendations":"Empfehlungen","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access erleichtert Ihnen die Problemdiagnose, Selbsthilfe und Kommunikation mit uns über das Red Hat Kundenportal. Um auf die Ressourcen im Red Hat Kundenportal zugreifen zu können, müssen Sie Ihre gültige Anmeldedaten für das Kundenportal angeben.","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Die Anmeldedaten für das Red Hat Kundenportal unterscheiden sich von den Anmeldedaten für dieses Produkt.","Red Hat Diagnose":"Red Hat Diagnose","Red Hat Login":"Red Hat Login","Red Hat Owner:":"Red Hat Besitzer:","Request Management Escalation":"Management-Eskalation beantragen","Resolution":"Lösung","Save":"Speichern","Save Group":"Gruppe speichern","Saving draft...":"Entwurf wird gespeichert...","Search":"Suchen","Searching...":"Suchen...","Select File":"Datei wählen","Server File(s) To Attach:":"Anzuhängende Server-Dateien:","Severity":" Schweregrad","Severity:":"Schweregrad:","Sign in":"Anmelden","Sign into the Red Hat Customer Portal":"Melden Sie sich beim Red Hat Kundenportal an","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Navigieren Sie zu einer Protokolldatei und wählen Sie diese aus der Liste links aus, klicken Sie anschließend auf die Schaltfläche \"Datei wählen\".","Size":"Größe","Status":"Status","Status:":"Status:","Submit":"Absenden","Submit Request":"Anfrage absenden","Successfully deleted attachment:":"Anhang erfolgreich gelöscht:","Successfully uploaded attachment":"Anhang erfolgreich hochgeladen","Summary of Request":"Zusammenfassung der Anfrage","Summary:":"Zusammenfassung:","Support Level:":"Support-Level:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"Mithilfe des Protokollbetrachters können Sie sowohl Protokolldateien von Applikationen untersuchen, als auch ein Support-Ticket bei Red Hat Global Support Services einreichen.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Diese Release ist veraltet, bitte werfen Sie einen Blick auf die empfohlenen FAQ, bevor Sie ein Ticket öffnen.","To view a recommendation, click on it.":"Klicken Sie auf eine Empfehlung, um sie anzusehen.","Type":"Typ","Unauthorized.":"Nicht autorisiert.","Update Details":"Details aktualisieren","Updated:":"Aktualisiert:","Upload Attachments":"Anhänge hochladen","User Name":"Benutzername","View full article in new window":"Vollständigen Artikel in neuem Fenster anzeigen","Would you like a Red Hat support manager to contact you regarding this case?":"Möchten Sie, dass sich ein Red Hat Support-Manager bezüglich dieses Tickets mit Ihnen in Verbindung setzt?","You have used 0% of the 32KB maximum description size.":"Sie haben 0 % der maximalen Beschreibungsgröße von 32 KB verwendet.","handpicked":"manuell ausgewählt","to case":"nach Ticket"}); + gettextCatalog.setStrings('es', {"24x7 Contact:":"Contacto 24x7: ","24x7 Support:":"Soporte 24x7::"," Open a New Support Case":" Abrir un nuevo caso de soporte"," Select Log":" Seleccionar registro","Account Name:":"Nombre de cuenta","Account Number:":"Número de cuenta","Account:":"Cuenta:","Add":"Añadir ","Add Comment":"Agregar un comentario","Advanced Mission Critical":"Misión crítica avanzada","Alternate Case ID:":"ID de caso alternativo: ","Attach Foreman logs:":"Adjuntar registros Foreman:","Attach local file":"Adjuntar archivos locales","Attached":"Adjuntado","Attached By":"Adjuntado por","Attached Files":"Archivos adjuntos","Attachments":"Adjuntos","Attachments:":"Adjuntos: ","Available Log Files":"Archivos de registro disponibles","Bugzilla Number":"Número de Bugzilla","Bugzilla Tickets":"Tiquetes de Bugzilla","Cancel":"Cancelar","Case Discussion":"Discusión del caso","Case Group:":"Grupo de caso:","Case Type:":"Tipo de caso","Chat offline":"Chat desconectado","Chat with support":"Chat sin soporte","Choose File(s) To Attach:":"Elegir archivo(s) a adjuntar:","Close messages":"Cerrar mensajes","Comment:":"Comentario:","Create Case Group":"Crear un grupo de casos","Create New Case Group":"Crear un nuevo grupo de casos","Delete":"Borrar","Delete Group":"Borrar grupo","Deleting attachment:":"Borrar adjunto: ","Description":"Descripción","Description:":"Descripción","Details":"Información","Diagnose":"Diagnóstico","Draft saved":"Borrar guardado","Email Notification Recipients":"Destinatarios de notificación por correo-e","Environment":"Entorno","Export All as CSV":"Exportar todos como CSV","Exporting CSV...":"Exportando CSV..","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"Los nombres de los archivos tienen un límite de 80 caracteres. El tamaño máximo para adjuntos de archivos cargados a la red es de 250 MB. Para archivos más grandes, por favor expórtelos en FTP a dropbox.redhat.com.","Filename":"Nombre de archivo","Files to Attach":"Archivos a adjuntar","First Name":"Nombre","Group":"Grupo","Group:":"Grupo:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Si siente que el problema se ha vuelto más grave o que su caso debiera tener mayor prioridad, por favor, proporcione un comentario detallado y su caso será revisado por un gerente de soporte.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Si usted aún considera que debe abrir un caso de soporte, seleccione 'Abrir un nuevo caso de soporte'. El caso será prellenado con la porción del registro anteriormente seleccionado.","Is Public:":"Es público:","Last Name":"Apellido","Last Updated:":"Última actualización","Learn more":"Aprender más","Log File Viewer":"Archivo de archivo de registro","Log In":"Ingresar","Log Out":"Salir ","Logged into the Red Hat Customer Portal as":"Ingresó al Portal del cliente de Red Hat como","My Account":"Mi cuenta","Name":"Nombre","Next":"Siguiente","No attachments added":"No se han agregado adjuntos","No cases found with given filters.":"No se encontraron casos con los filtros proporcionados. ","No cases found with given search criteria.":"No se encontraron casos con dichos criterios.","No groups found.":"No se encontraron grupos.","No linked bugzillas":"No hay Buzillas vinculados","Not Logged into the Red Hat Customer Portal":"No ha ingresado al Portal del cliente de Red Hat","Note:":"Nota:","Notes:":"Notas:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"Una vez que haya seleccionado su archivo de registro puede diagnosticar cualquier parte del archivo de registro y hacer clic en el botón 'Diagnóstico Red Hat'. Esto ayudará a desplegar los artículos importantes y soluciones de nuestra base de conocimientos.","Open a New Support Case":"Abrir un nuevo caso de soporte","Opened:":"Abierto: ","Owner":"Propietario","Owner:":"Propietario:","Password":"Contraseña","Previous":"Anterior","Product":"Producto","Product Version:":"Versión del producto","Product:":"Producto:","Recommendations":"Recomendaciones","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"El acceso de Red Hat le facilita la resolución de problemas, diagnostica problemas y se encarga con nosotros a través del Portal del cliente de Red Hat. Para acceder a los recursos de Portal del cliente de Red Hat, deberá validar las credenciales del portal. ","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Las credenciales del Portal del cliente de Red Hat difieren de las credenciales utilizadas para ingresar dentro de este producto.","Red Hat Diagnose":"Diagnosis de Red Hat","Red Hat Login":"Nombre de usuario de Red Hat","Red Hat Owner:":"Propietario Red Hat: ","Request Management Escalation":"Solicitar escalamiento administrativo","Resolution":"Resolución","Save":"Guardar","Save Group":"Guardar grupo","Saving draft...":"Guardando borrador...","Search":"Búsqueda","Searching...":"Buscando...","Select File":"Seleccionar archivo","Server File(s) To Attach:":"Archivo(s) de servidor a adjuntar: ","Severity":"Gravedad","Severity:":"Gravedad: ","Sign in":"Iniciar sesión","Sign into the Red Hat Customer Portal":"Ingresar al Portal del cliente Red Hat","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Navegue y seleccione un archivo de registro de la lista a la izquierda y haga clic en el botón 'Seleccionar archivo'.","Size":"Tamaño","Status":"Estatus","Status:":"Estatus:","Submit":"Enviar","Submit Request":"Enviar solicitud","Successfully deleted attachment:":"Ha borrado los adjuntos con éxito.","Successfully uploaded attachment":"Ha cargado el adjunto con éxito","Summary of Request":"Resumen de solicitud","Summary:":"Resumen:","Support Level:":"Nivel de soporte:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"El visor del archivo de registro ofrece la capacidad de diagnosticar registros como también radicar un caso de soporte en Red Hat Global Support Services.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Este lanzamiento ha sido retirado, por favor consulte las preguntas frecuentes antes de llenar el caso.","To view a recommendation, click on it.":"Para ver una recomendación, haga clic en ella.","Type":"Tipo","Unauthorized.":"No autorizado.","Update Details":"Actualizar información","Updated:":"Actualizada:","Upload Attachments":"Cargar adjuntos","User Name":"Nombre de usuario","View full article in new window":"Ver artículo completo en una nueva ventana","Would you like a Red Hat support manager to contact you regarding this case?":"¿Desearía que un gerente de soporte de Red Hat lo contacte para este caso? ","You have used 0% of the 32KB maximum description size.":"Ha utilizado 0% del tamaño de máximo de descripción 32KB.","handpicked":"Seleccionado a mano","to case":"para caso"}); + gettextCatalog.setStrings('fr', {"24x7 Contact:":"Contact 24h/24 et 7j/7 :","24x7 Support:":"Assistance 24h/24 et 7j/7 :"," Open a New Support Case":" Ouvrir un nouveau dossier d'assistance"," Select Log":" Sélectionner un journal","Account Name:":"Nom du compte :","Account Number:":"Numéro de compte :","Account:":"Compte :","Add":"Ajouter","Add Comment":"Ajouter un commentaire","Advanced Mission Critical":"Fonctions avancées essentielles","Alternate Case ID:":"ID alternative du dossier :","Attach Foreman logs:":"Joindre des journaux Foreman :","Attach local file":"Joindre un fichier local","Attached":"Joint","Attached By":"Pièce jointe par :","Attached Files":"Fichiers joints","Attachments":"Pièces jointes","Attachments:":"Pièces jointes :","Available Log Files":"Fichiers journaux disponibles","Bugzilla Number":"Numéro sur Bugzilla","Bugzilla Tickets":"Tickets sur Bugzilla","Cancel":"Annuler","Case Discussion":"Discussion du dossier","Case Group:":"Groupe du dossier :","Case Type:":"Type de dossier :","Chat offline":"Discuter hors-ligne","Chat with support":"Discuter avec le support technique","Choose File(s) To Attach:":"Choisir le(s) fichier(s) à joindre :","Close messages":"Fermer les messages","Comment:":"Commentaire :","Create Case Group":"Créer un groupe de dossiers","Create New Case Group":"Créer un nouveau groupe de dossiers","Delete":"Supprimer","Delete Group":"Supprimer le groupe","Deleting attachment:":"Supprimer la pièce jointe :","Description":"Description","Description:":"Description :","Details":"Détails","Diagnose":"Diagnostiquer","Draft saved":"Brouillon enregistré","Email Notification Recipients":"Envoyer un courrier électronique aux destinataires des notifications","Environment":"Environnement","Export All as CSV":"Tout exporter en CSV","Exporting CSV...":"Export CRV en cours...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"Les noms de fichiers sont limités à 80 caractères. La taille de fichier maximale pour les pièces jointes téléversées par le web est de 250 Mo. Pour les fichiers plus importants, veuillez utiliser FTP (dropbox.redhat.com).","Filename":"Nom du fichier","Files to Attach":"Fichiers à joindre","First Name":"Prénom","Group":"Groupe","Group:":"Groupe :","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Si vous estimez que votre problème s'est aggravé ou qu'il devrait bénéficier d'un niveau de priorité plus élevé, saisissez un commentaire détaillé afin qu'un responsable d'assistance se charge de votre dossier.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Si vous souhaitez tout de même ouvrir un nouveau dossier d'assistance, veuillez sélectionner « Ouvrir un nouveau dossier d'assistance ». Le dossier sera pré-rempli avec la portion du journal précédemment sélectionnée.","Is Public:":"Publique :","Last Name":"Nom de famille","Last Updated:":"Dernière mise à jour :","Learn more":"En savoir plus","Log File Viewer":"Visionneur de fichiers journaux","Log In":"Connexion","Log Out":"Déconnexion","Logged into the Red Hat Customer Portal as":"Connecté sur le Portail Client Red Hat en tant que","My Account":"Mon compte","Name":"Nom","Next":"Suivant","No attachments added":"Aucune pièce jointe nécessaire","No cases found with given filters.":"Aucun dossier trouvé avec les filtres donnés.","No cases found with given search criteria.":"Aucun dossier trouvé avec les critères de recherche donnés.","No groups found.":"Aucun groupe trouvé.","No linked bugzillas":"Aucun bugzilla lié","Not Logged into the Red Hat Customer Portal":"Non connecté sur le Portail Client Red Hat","Note:":"Remarque :","Notes:":"Remarques :","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"Une fois le fichier journal sélectionné, vous pourrez diagnostiquer n'importe quelle partie de celui-ci et cliquer sur le bouton « Diagnostique Red Hat ». Ceci affichera les articles et les solutions correspondants de la base de connaissances Red Hat.","Open a New Support Case":"Ouvrir un nouveau dossier d'assistance","Opened:":"Ouvert  :","Owner":"Propriétaire","Owner:":"Propriétaire :","Password":"Mot de passe","Previous":"Précédent","Product":"Produit","Product Version:":"Version du produit :","Product:":"Produit :","Recommendations":"Recommandations","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access vous permet de résoudre vos problèmes vous-même, de diagnostiquer les problèmes, et de nous contacter plus facilement, le tout via le Portail Client. Pour accéder aux ressources du Portail Client, vous devrez saisir des informations d'identification valides sur le portail.","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Les informations d'identification du Portail Client Red Hat diffèrent des informations d'identifications utilisées pour se connecter à ce produit.","Red Hat Diagnose":"Diagnostique Red Hat","Red Hat Login":"Connexion Red Hat","Red Hat Owner:":"Propriéraire Red Hat :","Request Management Escalation":"Demander la remontée d'un problème","Resolution":"Résolution","Save":"Enregistrer","Save Group":"Enregistrer le groupe","Saving draft...":"Enregistrement du brouillon...","Search":"Rechercher","Searching...":"Recherche en cours...","Select File":"Sélectionner le fichier","Server File(s) To Attach:":"Fichier(s) du serveur à joindre :","Severity":"Sévérité","Severity:":"Sévérité :","Sign in":"Connexion","Sign into the Red Hat Customer Portal":"Connexion au Portail Client Red Hat","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Veuillez simplement vous rendre sur la liste à gauche, puis sélectionnez un fichier journal et cliquez sur le bouton « Sélectionner le fichier ».","Size":"Taille","Status":"Statut","Status:":"Statut :","Submit":"Valider","Submit Request":"Valider la requête","Successfully deleted attachment:":"Suppression de la pièce jointe réussie :","Successfully uploaded attachment":"Pièce jointe attachée","Summary of Request":"Résumé de la requête","Summary:":"Résumé :","Support Level:":"Niveau d'assistance :","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"Le visionneur de fichiers journaux offre la possibilité de diagnostiquer les journaux d'applications ainsi que d'ouvrir un dossier d'assistance avec le service de support technique de Red Hat, « Red Hat Global Support Services » (Red Hat GSS).","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Cette version est désormais obsolète, veuillez consulter la FAQ recommandée avant d'ouvrir un dossier.","To view a recommendation, click on it.":"Pour afficher une recommandation, veuillez cliquer dessus.","Type":"Type","Unauthorized.":"Non autorisé.","Update Details":"Mise à jour des détails","Updated:":"Mis à jour :","Upload Attachments":"Téléverser les pièces jointes","User Name":"Nom d'utilisateur","View full article in new window":"Afficher l'article complet dans une nouvelle fenêtre","Would you like a Red Hat support manager to contact you regarding this case?":"Souhaitez-vous qu'un responsable d'assistance Red Hat vous contacte au sujet de votre dossier ?","You have used 0% of the 32KB maximum description size.":"Vous avez utilisé 0% des 32 Ko correspondants à la taille maximale de la description.","handpicked":"sélection personnelle","to case":"pour le dossier"}); + gettextCatalog.setStrings('it', {"24x7 Contact:":"Contatto 24x7:","24x7 Support:":"Supporto 24x7:"," Open a New Support Case":" Apri un nuovo caso con il supporto tecnico"," Select Log":" Seleziona il log","Account Name:":"Nome account:","Account Number:":"Numero account:","Account:":"Account:","Add":"Aggiungi","Add Comment":"Aggiungi commento","Advanced Mission Critical":"Advanced Mission Critical","Alternate Case ID:":"ID alternativo del caso:","Attach Foreman logs:":"Allega log di Foreman:","Attach local file":"Allega un file locale","Attached":"Allegato","Attached By":"Allegato da","Attached Files":"File allegati","Attachments":"Allegati","Attachments:":"Allegati:","Available Log Files":"File di log disponibili","Bugzilla Number":"Numero bugzilla ","Bugzilla Tickets":"Ticket di bugzilla","Cancel":"Cancella","Case Discussion":"Discussione del caso","Case Group:":"Gruppo casi:","Case Type:":"Tipo di caso:","Chat offline":"Avvia una chat offline","Chat with support":"Avvia una chat con il supporto","Choose File(s) To Attach:":"Seleziona i fila da allegare:","Close messages":"Chiudi i messaggi","Comment:":"Commento:","Create Case Group":"Crea un gruppo casi","Create New Case Group":"Crea un nuovo gruppo casi","Delete":"Cancella","Delete Group":"Elimina gruppo","Deleting attachment:":"Elimina allegato:","Description":"Descrizione","Description:":"Descrizione:","Details":"Dettagli","Diagnose":"Diagnosi","Draft saved":"Bozza salvata","Email Notification Recipients":"Destinatari delle notifiche email","Environment":"Ambiente","Export All as CSV":"Esporta tutti come CSV","Exporting CSV...":"Esportazione CSV in corso...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"I nomi dei file sono limitati a 80 caratteri. La dimensione massima del file come allegato è di 250MB. Per file più grandi usare FTP per dropbox.redhat.com. ","Filename":"Nome del file","Files to Attach":"File da allegare","First Name":"Nome","Group":"Gruppo","Group:":"Gruppo:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Se ritieni che il problema si sia aggravato o che il caso debba essere classificato con una priorità più elevata, fornisci un commento dettagliato, e il caso in questione verrà esaminato da un responsabile del supporto.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Se desideri ancora aprire un nuovo caso con il supporto, seleziona ' Apri un nuovo caso con il supporto'. Così facendo il nuovo caso verrà popolato con la sezione del log precedentemente selezionato.","Is Public:":"È pubblico:","Last Name":"Cognome","Last Updated:":"Ultimo aggiornamento:","Learn more":"Maggiori informazioni","Log File Viewer":"Visualizzatore file di log","Log In":"Accedi","Log Out":"Esci","Logged into the Red Hat Customer Portal as":"Accesso eseguito nel Portale clienti di Red Hat come","My Account":"Account","Name":"Nome","Next":"Successivo","No attachments added":"Nessun allegato aggiunto","No cases found with given filters.":"Nessun caso trovato con il filtro usato.","No cases found with given search criteria.":"Nessun caso trovato con i criteri di ricerca specificati.","No groups found.":"Nessun gruppo trovato.","No linked bugzillas":"Nessun bugzilla collegato","Not Logged into the Red Hat Customer Portal":"Accesso non eseguito nel Portale clienti di Red Hat come","Note:":"Nota Bene:","Notes:":"Nota Bene:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"Dopo aver selezionato il file di log sarà possibile eseguire la diagnosi di qualsiasi parte del file selezionando il pulsante 'Diagnostica Red Hat'. Così facendo potrai visualizzare le soluzioni e gli articoli rilevanti del Red Hat Knowledge base.","Open a New Support Case":"Apri un nuovo caso con il supporto tecnico","Opened:":"Aperto: ","Owner":"Proprietario","Owner:":"Proprietario:","Password":"Password","Previous":"Precedente","Product":"Prodotto","Product Version:":"Versione del prodotto:","Product:":"Prodotto:","Recommendations":"Consigli","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access facilita la soluzione dei problemi, il processo di diagnosi e l'interazione con Red Hat tramite il Portale clienti. Per accedere alle risorse del Portale clienti è necessario inserire le credenziali valide.","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Le credenziali del Portale clienti di Red Hat differiscono da quelle usate per l'accesso a questo prodotto.","Red Hat Diagnose":"Diagnostica Red Hat","Red Hat Login":"Login di Red Hat","Red Hat Owner:":"Proprietario Red Hat:","Request Management Escalation":"Richiesta aumento prioritá livello Manager","Resolution":"Risoluzione","Save":"Salva","Save Group":"Salva gruppo","Saving draft...":"Salvataggio bozza...","Search":"Cerca","Searching...":"Ricerca in corso...","Select File":"Seleziona un file","Server File(s) To Attach:":"File del server da allegare:","Severity":"Severity","Severity:":"Severity:","Sign in":"Accedi","Sign into the Red Hat Customer Portal":"Accedi al Portale clienti di Red Hat","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Vai alla ricerca e seleziona un file di log dall'elenco sulla sinistra e fare clic sul pulsante 'Seleziona file'.","Size":"Dimensione","Status":"Stato","Status:":"Stato:","Submit":"Invia","Submit Request":"Invia una richiesta","Successfully deleted attachment:":"Allegato rimosso con successo:","Successfully uploaded attachment":"Allegato caricato con successo","Summary of Request":"Sommario della richiesta","Summary:":"Sommario:","Support Level:":"Livello di supporto:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"Il visualizzatore del file di log permette di eseguire una diagnosi dei log dell'applicazione, e di aprire un caso per il supporto con il Red Hat Global Support Services.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Questa versione è stata rimossa, consultare le FAQ prima di completare una richiesta di supporto","To view a recommendation, click on it.":"Selezionare il consiglio desiderato per poterlo visualizzare.","Type":"Tipo","Unauthorized.":"Non autorizzato.","Update Details":"Aggiorna le informazioni","Updated:":"Aggiornato:","Upload Attachments":"Carica allegati","User Name":"Nome utente","View full article in new window":"Visualizza tutto l'articolo in una nuova finestra","Would you like a Red Hat support manager to contact you regarding this case?":"Desideri che un responsabile per il supporto di Red Hat ti contatti con riferimento a questo caso?","You have used 0% of the 32KB maximum description size.":"Hai usato 0% dei 32 KB stabiliti come dimensione massima per le descrizioni.","handpicked":"scelto","to case":"per il caso"}); + gettextCatalog.setStrings('ja', {"24x7 Contact:":"24x7 のお問い合わせ先:","24x7 Support:":"24x7 サポート:"," Open a New Support Case":" サポートケースを新規作成"," Select Log":" ログの選択","Account Name:":"アカウント名:","Account Number:":"アカウント番号:","Account:":"アカウント:","Add":"追加","Add Comment":"コメントの追加","Advanced Mission Critical":"Advanced Mission Critical","Alternate Case ID:":"代替ケース ID:","Attach Foreman logs:":"Foreman のログを添付:","Attach local file":"ローカルファイルを添付します","Attached":"添付","Attached By":"作成者","Attached Files":"添付ファイル","Attachments":"添付ファイル","Attachments:":"添付ファイル:","Available Log Files":"利用可能なログファイル","Bugzilla Number":"Bugzilla 番号","Bugzilla Tickets":"Bugzilla チケット","Cancel":"キャンセル","Case Discussion":"ケースコメント","Case Group:":"ケースグループ:","Case Type:":"ケースタイプ:","Chat offline":"オフラインのチャット","Chat with support":"サポート担当者とチャットする","Choose File(s) To Attach:":"添付するファイルの選択:","Close messages":"メッセージを閉じる","Comment:":"コメント:","Create Case Group":"ケースグループの作成","Create New Case Group":"ケースグループの新規作成","Delete":"削除","Delete Group":"グループの削除","Deleting attachment:":"添付ファイルの削除:","Description":"詳細","Description:":"詳細:","Details":"詳細","Diagnose":"診断","Draft saved":"ドラフトを保存しました","Email Notification Recipients":"電子メール通知の受信者","Environment":"環境","Export All as CSV":"すべてを CSV でエクスポート","Exporting CSV...":"CSV をエクスポート中...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"ファイル名は 80 文字未満にしてください。Web にアップロードする添付ファイルの最大サイズは 250 MBです。それ以上の場合は、FTP で dropbox.redhat.com にアップロードしてください。","Filename":"ファイル名","Files to Attach":"添付するファイル","First Name":"氏名 (名)","Group":"グループ","Group:":"グループ:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"問題がより深刻になったか、またはケースの優先度を高くする必要があると思われる場合は、詳細なコメントを入力してください。サポートマネージャーがケースを確認します。","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"サポートケースをまだ作成する必要がある場合には、「サポートケースを新規作成」を選択します。ケースにはログのすでに選択されている部分が事前設定されます。","Is Public:":"公開:","Last Name":"氏名 (姓):","Last Updated:":"最終更新日時:","Learn more":"詳細情報","Log File Viewer":"ログファイルビューアー","Log In":"ログイン","Log Out":"ログアウト","Logged into the Red Hat Customer Portal as":"Red Hat カスタマーポータルにログイン中: ","My Account":"自分のアカウント","Name":"名前","Next":"次へ","No attachments added":"添付ファイルが追加されていません","No cases found with given filters.":"指定のフィルターでケースが見つかりませんでした。","No cases found with given search criteria.":"指定の検索条件でケースが見つかりませんでした。","No groups found.":"グループが見つかりませんでした。","No linked bugzillas":"リンクされた Bugzilla がありません","Not Logged into the Red Hat Customer Portal":"Red Hat カスタマーポータルにログインしていません","Note:":"注意:","Notes:":"注意:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"ログファイルを選択すると、ファイルのすべての部分を診断することができます。また「Red Hat 診断」ボタンをクリックすると、Red Hat ナレッジベースの関連記事やソリューションが表示されます。","Open a New Support Case":"サポートケースを新規作成","Opened:":"作成日時:","Owner":"担当者","Owner:":"担当者:","Password":"パスワード","Previous":"前へ","Product":"製品","Product Version:":"製品バージョン:","Product:":"製品:","Recommendations":"推奨項目","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access をご利用いただくと、問題をご自分で解決したり、問題の診断を行ったり、また Red Hat カスタマーポータル経由で Red Hat にお問い合わせいただいたりすることがより簡単になります。Red Hat カスタマーポータルの各種リソースにアクセスするには、有効なポータルの資格情報を入力する必要があります。","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Red Hat カスタマーポータルの資格情報は、この製品にログインするために使用される資格情報とは異なります。","Red Hat Diagnose":"Red Hat 診断","Red Hat Login":"Red Hat ログイン","Red Hat Owner:":"Red Hat 担当者:","Request Management Escalation":"マネージメントエスカレーションをリクエスト","Resolution":"解決方法","Save":"保存","Save Group":"グループの保存","Saving draft...":"下書きの保存中...","Search":"検索","Searching...":"検索中...","Select File":"ファイルの選択","Server File(s) To Attach:":"添付するサーバーファイル:","Severity":"重大度","Severity:":"重大度:","Sign in":"サインイン","Sign into the Red Hat Customer Portal":"Red Hat カスタマーポータルにサインインします","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"左側にある一覧に移動し、一覧からログファイルを選択して「ファイルの選択」ボタンをクリックします。","Size":"サイズ","Status":"ステータス","Status:":"ステータス:","Submit":"送信","Submit Request":"リクエストの送信","Successfully deleted attachment:":"添付ファイルが正しく削除されました:","Successfully uploaded attachment":"添付ファイルが正しくアップロードされました:","Summary of Request":"リクエストの要約","Summary:":"概要:","Support Level:":"サポートレベル:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"ログファイルビューアーは、Red Hat グローバルサポートサービス (GSS) にサポートケースを報告する機能と共に、アプリケーションのログを診断する機能を提供します。","This release is now retired, please refer to the recommended FAQ prior to filing a case":"本リリースは現在使用中止になっています。ケースを作成する前に、推奨されている FAQ を参照してください。","To view a recommendation, click on it.":"推奨項目を閲覧するには、その項目をクリックします。","Type":"タイプ","Unauthorized.":"承認されていません。","Update Details":"更新の詳細","Updated:":"更新日:","Upload Attachments":"添付ファイルのアップロード","User Name":"ユーザー名","View full article in new window":"記事の全文を新規ウィンドウで表示する","Would you like a Red Hat support manager to contact you regarding this case?":"このケースについて、Red Hat のサポートマネージャーからの連絡を希望されますか?","You have used 0% of the 32KB maximum description size.":"コメントの最大サイズ 32KB の 0% を使用しています。","handpicked":"選択済み","to case":"対象のケース"}); + gettextCatalog.setStrings('ko', {"24x7 Contact:":"24x7 연락처:","24x7 Support:":"24x7 지원:"," Open a New Support Case":" 새 기술 문의 생성 "," Select Log":" 로그 선택 ","Account Name:":"계정 이름:","Account Number:":"계정 번호:","Account:":"계정:","Add":"추가 ","Add Comment":"코멘트 추가","Advanced Mission Critical":"고급 미션 크리티컬","Alternate Case ID:":"대체 기술 문의 ID:","Attach Foreman logs:":"Foreman 로그 첨부:","Attach local file":"로컬 파일 첨부 ","Attached":"첨부됨 ","Attached By":"첨부자","Attached Files":"첨부된 파일 ","Attachments":"첨부 파일","Attachments:":"첨부 파일: ","Available Log Files":"사용 가능한 로그 파일 ","Bugzilla Number":"Bugzilla 번호 ","Bugzilla Tickets":"Bugzilla 티켓 ","Cancel":"취소 ","Case Discussion":"기술 문의 토론 ","Case Group:":"기술문의 그룹:","Case Type:":"기술문의 유형: ","Chat offline":"오프라인 채팅 ","Chat with support":"지원 담당자와 채팅","Choose File(s) To Attach:":"첨부할 파일 선택:","Close messages":"메세지 종료 ","Comment:":"코멘트: ","Create Case Group":"기술문의 그룹 생성","Create New Case Group":"새 기술문의 그룹 생성","Delete":"삭제 ","Delete Group":"그룹 삭제","Deleting attachment:":"첨부 파일 삭제 중: ","Description":"설명 ","Description:":"설명: ","Details":"상세 정보 ","Diagnose":"진단 ","Draft saved":"초안 저장됨","Email Notification Recipients":"이메일 알림 수신자 ","Environment":"환경 ","Export All as CSV":"CSV로 모두 내보내기 ","Exporting CSV...":"CSV 내보내기...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"파일명은 80자 미만이어야 합니다. 웹에 업로드될 최대 첨부 파일크기는 250 MB입니다. 더 큰 파일은 FTP를(dropbox.redhat.com) 이용해 주시기 바랍니다. ","Filename":"파일명","Files to Attach":"첨부할 파일 ","First Name":"이름","Group":"그룹","Group:":"그룹:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"문제가 더 심각해지거나 기술 문의 사항의 우선 순위가 더 높아야 한다고 생각되는 경우, 자세한 설명을 제공하면 지원 관리자가 검토할 것입니다.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"기술 문의 사항을 아직 열어 두어야 할 경우 '새 기술 문의 생성'을 선택합니다. 기술 문의는 이전에 선택한 로그의 일부로 사전 작성됩니다. ","Is Public:":"공개:","Last Name":"성","Last Updated:":"마지막 업데이트:","Learn more":"자세한 정보","Log File Viewer":"로그 파일 뷰어 ","Log In":"로그인","Log Out":"로그 아웃 ","Logged into the Red Hat Customer Portal as":"Red Hat 고객 포털로 로그인 ","My Account":"내 계정","Name":"이름 ","Next":"다음","No attachments added":"추가된 첨부 파일이 없습니다 ","No cases found with given filters.":"지정된 필터에 맞는 기술 문의 사항이 없습니다.","No cases found with given search criteria.":"지정된 검색 조건에 맞는 기술 문의 사항이 없습니다. ","No groups found.":"그룹을 찾을 수 없습니다.","No linked bugzillas":"링크된 Bugzilla가 없습니다 ","Not Logged into the Red Hat Customer Portal":"Red Hat 고객 포털에 로그인되어 있지 않습니다 ","Note:":"알림: ","Notes:":"알림: ","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"로그 파일을 선택하면 로그 파일의 모든 부분을 진단할 수 있으므로 'Red Hat 진단' 버튼을 클릭합니다. 그러면 Red Hat 지식 기반에서 관련 기사 및 솔루션이 표시됩니다. ","Open a New Support Case":"새 기술문의 생성","Opened:":"생성일: ","Owner":"소유자 ","Owner:":"소유자:","Password":"암호 ","Previous":"이전","Product":"제품","Product Version:":"제품 버전:","Product:":"제품:","Recommendations":"권장 사항 ","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access를 사용하여 문제를 직접 해결하고, 문제를 진단하거나 Red Hat 고객 포털을 통해 보다 쉽게 지원 문의할 수 있습니다. Red Hat 고객 포털 리소스에 액세스하려면 유효한 포털 인증을 입력해야 합니다. ","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Red Hat 고객 포털 인증은 제품에 로그인하는데 사용되는 인증과 다릅니다.","Red Hat Diagnose":"Red Hat 진단","Red Hat Login":"Red Hat 로그인","Red Hat Owner:":"Red Hat 소유자:","Request Management Escalation":"관리 에스컬레이션 요청","Resolution":"해결 방법 ","Save":"저장","Save Group":"그룹 저장","Saving draft...":"초안 저장 중...","Search":"검색 ","Searching...":"검색 중.. ","Select File":"파일 선택 ","Server File(s) To Attach:":"첨부할 서버 파일: ","Severity":"심각성 ","Severity:":"심각성:","Sign in":"로그인 ","Sign into the Red Hat Customer Portal":"Red Hat 고객 포털로 로그인 ","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"왼쪽에 있는 목록으로 이동하여 로그 파일을 선택한 후 '파일 선택' 버튼을 클릭합니다.","Size":"크기 ","Status":"상태","Status:":"상태: ","Submit":"제출","Submit Request":"요청 제출","Successfully deleted attachment:":"첨부 파일을 성공적으로 삭제했습니다:","Successfully uploaded attachment":"첨부 파일을 성공적으로 업로드했습니다 ","Summary of Request":"요청 요약 ","Summary:":"요약: ","Support Level:":"지원 수준: ","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"로그 파일 뷰어는 Red Hat 글로벌 지원 서비스 (GSS)를 통해 기술 지원 사항을 저장할 수 있는 기능과 함께 애플리케이션을 진단할 수 있는 기능을 제공합니다.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"이 릴리즈는 현재 만료되었습니다. 기술 문의를 작성하기 전 FAQ를 참조하십시오.","To view a recommendation, click on it.":"권장 사항을 확인하려면 해당 항목을 클릭합니다.","Type":"유형 ","Unauthorized.":"인증되지 않았습니다.","Update Details":"업데이트 상세 정보 ","Updated:":"업데이트된 날짜:","Upload Attachments":"첨부 파일 업로드 ","User Name":"사용자 이름","View full article in new window":"새 창에서 전체 기사 보기","Would you like a Red Hat support manager to contact you regarding this case?":"이 기술 문의와 관련하여 Red Hat 지원 관리자가 귀하에게 연락하도록 하시겠습니까?","You have used 0% of the 32KB maximum description size.":"최대 설명 크기인 32KB 중 0%가 사용되었습니다. ","handpicked":"엄선","to case":"기술 문의 사항 "}); + gettextCatalog.setStrings('pt-BR', {"24x7 Contact:":"Contato 24x7:","24x7 Support:":"Suporte 24x7:"," Open a New Support Case":" Abra um Novo Caso de Suporte"," Select Log":" Selecione o Log","Account Name:":"Nome da Conta:","Account Number:":"Número da Conta:","Account:":"Conta:","Add":"Adicionar","Add Comment":"Adicionar Comentário","Advanced Mission Critical":"Missão Crítica Avançada","Alternate Case ID:":"Alternar ID de Caso:","Attach Foreman logs:":"Anexar logs do Foreman:","Attach local file":"Anexar arquivo local:","Attached":"Anexado","Attached By":"Anexado por","Attached Files":"Arquivos anexados","Attachments":"Anexos","Attachments:":"Anexos:","Available Log Files":"Arquivos de Log Disponíveis:","Bugzilla Number":"Número do Bugzilla","Bugzilla Tickets":"Tiquetes do Bugzilla","Cancel":"Cancelar","Case Discussion":"Discussão do Caso","Case Group:":"Grupo do Caso:","Case Type:":"Tipo do Caso:","Chat offline":"Bate papo offline","Chat with support":"Bate-papo com o suporte","Choose File(s) To Attach:":"Escolha Arquivo(s) para Anexar:","Close messages":"Fechar Mensagens","Comment:":"Comentário:","Create Case Group":"Criar Grupo de Caso","Create New Case Group":"Criar novo Grupo de Caso","Delete":"Remover","Delete Group":"Excluir Grupo","Deleting attachment:":"remover Anexo:","Description":"Descrição","Description:":"Descrição:","Details":"Detalhes","Diagnose":"Diagnóstico","Draft saved":"Rascunho Salvo ","Email Notification Recipients":"Destinatários de notificação por e-mail","Environment":"Ambiente","Export All as CSV":"Exportar todos os CSV","Exporting CSV...":"Exportando CSV...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"O nome dos arquivos são limitados a 80 caracteres. Tamanho máximo de arquivo é 250 Mb. Para arquivos maiores que 250Mb por favor utilize o FTP (dropbox.redhat.com). ","Filename":"Nome do Arquivo","Files to Attach":"Anexar Arquivos à","First Name":"Primeiro Nome","Group":"Grupo","Group:":"Grupo:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Caso sinta que o problema ficou mais grave ou que o caso deveria ter maior prioridade, forneça um comentário detalhado, e o caso será analisado por um gerente de suporte.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Caso você ainda deseje abrir um caso de suporte, selecione 'Abrir um Novo Caso de Suporte'. O caso será pré-populado com a porção do log anteriormente selecionada.","Is Public:":"É Público:","Last Name":"Sobrenome","Last Updated:":"Úlltima Atualização:","Learn more":"Saiba mais","Log File Viewer":"Visualizador de Arquivos de Log","Log In":"Logon","Log Out":"Sair","Logged into the Red Hat Customer Portal as":"Autenticado no Red Hat Customer Portal como","My Account":"Minha conta","Name":"Nome","Next":"Próximo","No attachments added":"Não foi adicionado nenhum anexo","No cases found with given filters.":"Nenhum caso foi encontrado com os filtros especificados","No cases found with given search criteria.":"Nenhum caso encontrado com critério de busca especificado","No groups found.":"Nenhum grupo encontrado","No linked bugzillas":"Nenhum bugzilla conectado","Not Logged into the Red Hat Customer Portal":"Não foi autenticado no Red Hat Customer Portal","Note:":"Nota:","Notes:":"Notas:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"Depois que você selecionou o arquivo de log, você poderá diagnosticar qualquer parte do arquivo de log e clicar no botão 'Red Hat Diagnose'. Isto irá exibir artigos relevantes e soluções de nossa base do Red Hat Knowledge.","Open a New Support Case":"Abrir um novo Caso de Suporte","Opened:":"Aberto: ","Owner":"Proprietário","Owner:":"Proprietário:","Password":"Senha","Previous":"Anterior","Product":"Produto","Product Version:":"Versão do Produto:","Product:":"Produto:","Recommendations":"Recomendações","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"O Red Hat Access facilita a solução de problemas, diagnóstico de problemas pra você e nos une via Red Hat Customer Portal. Para acessar os recursos do Portal do Consumidor Red Hat, você precisa inserir as credenciais do portal válidas.","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"As credenciais do Portal do Consumidor, diferem das credenciais usadas para se autenticar neste produto.","Red Hat Diagnose":"Diagnóstico da Red Hat","Red Hat Login":"Red Hat Login","Red Hat Owner:":"Proprietário da Red Hat:","Request Management Escalation":"Requisita a escalação de gerenciamento","Resolution":"Resolução","Save":"Salvar","Save Group":"Salvar Grupo","Saving draft...":"Salvar Rascunho...","Search":"Buscar","Searching...":"Buscando... ","Select File":"Selecionar Arquivo","Server File(s) To Attach:":"Arquivo(s) do Servidor para Anexar:","Severity":"Gravidade","Severity:":"Gravidade:","Sign in":"Entrar","Sign into the Red Hat Customer Portal":"Entrar no Red Hat Portal do Cliente","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Simplesmente navegue até lá e selecione um arquivo de log a partir da lista à esquerda e clique no botão 'Selecionar Arquivo'.","Size":"Tamanho","Status":"Estado","Status:":"Estado:","Submit":"Enviar","Submit Request":"Enviar Solicitação","Successfully deleted attachment:":"Anexos removidos com sucesso:","Successfully uploaded attachment":"Anexo atualizado com sucesso","Summary of Request":"Sumário de Solicitações","Summary:":"Sumário","Support Level:":"Nível de Suporte","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"O visualizador de arquivo de log fornece a habilidade para diagnosticar logs de aplicativo assim como arquivar caso de suporte com os Serviços da Red Hat Global Support.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Este lançamento está obsoleto, por favor consulte o FAQ recomendado antes de arquivar um caso","To view a recommendation, click on it.":"Para visualizar recomendação, clique nela;","Type":"Tipo","Unauthorized.":"Desautorizado.","Update Details":"Atualizar Detalhes","Updated:":"Atualizado:","Upload Attachments":"Atualizar Anexos","User Name":"Nome de Usuário","View full article in new window":"Visualizar artigo completo na nova janela","Would you like a Red Hat support manager to contact you regarding this case?":"Você deseja que o gerente de suporte da Red Hat entre em contato para discutir sobre este caso?","You have used 0% of the 32KB maximum description size.":"Você já usou 0% do tamanho da descrição máximo de 32 KB.","handpicked":"escolhido manualmente","to case":"para o caso"}); + gettextCatalog.setStrings('zh-Hans', {"24x7 Contact:":"24x7 联系方法:","24x7 Support:":"24x7 支持:"," Open a New Support Case":" 创建一个新支持案例"," Select Log":" 选择日志","Account Name:":"帐户名称:","Account Number:":"帐户号:","Account:":"账户:","Add":"添加","Add Comment":"添加评论","Advanced Mission Critical":"高级关键任务","Alternate Case ID:":"备用案例 ID:","Attach Foreman logs:":"附加 Foreman 日志:","Attach local file":"附加本地文件","Attached":"附加","Attached By":"附件上传者","Attached Files":"附加的文件","Attachments":"附件","Attachments:":"附件:","Available Log Files":"有效的日志文件","Bugzilla Number":"Bugzilla 号","Bugzilla Tickets":"Bugzilla 报告","Cancel":"取消","Case Discussion":"支持案例讨论","Case Group:":"支持案例组:","Case Type:":"支持案例类别:","Chat offline":"离线互动咨询","Chat with support":"互动咨询支持","Choose File(s) To Attach:":"选择要被附加的文件:","Close messages":"关闭信息","Comment:":"评论:","Create Case Group":"创建案例组","Create New Case Group":"创建新的案例组","Delete":"删除","Delete Group":"删除组","Deleting attachment:":"删除附件:","Description":"描述","Description:":"描述:","Details":"详细信息","Diagnose":"诊断","Draft saved":"保存草稿","Email Notification Recipients":"电子邮件通知接收者","Environment":"环境","Export All as CSV":"将所有导出为 CSV","Exporting CSV...":"导出 CSV...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"文件名不能超过 80 个字符,网页上传文件大小不能超过 250 MB。请使用 FTP 将大文件上传到 dropbox.redhat.com。","Filename":"文件名","Files to Attach":"要附加的文件","First Name":"名","Group":"组","Group:":"组:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"如果您的问题愈发严重或案例应具有更高优先级,请提供详细信息,支持经理将会复核该案例。","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"如果您仍然希望创建一个新的支持案例,请选择“创建新支持案例”。系统将会根据您所选择的日志自动生成案例中的部分内容。","Is Public:":"是否公开:","Last Name":"姓","Last Updated:":"最新更新:","Learn more":"了解更多信息","Log File Viewer":"日志文件查看器","Log In":"登录","Log Out":"登出","Logged into the Red Hat Customer Portal as":"以以下身份登录到红帽客户门户网站","My Account":"我的帐号","Name":"名称","Next":"下一步","No attachments added":"无添加的附件","No cases found with given filters.":"无匹配的支持案例。","No cases found with given search criteria.":"无匹配的支持案例。","No groups found.":"没有找到组。","No linked bugzillas":"没有相关的 bugzilla 报告","Not Logged into the Red Hat Customer Portal":"没有登录到红帽用户门户网站","Note:":"备注:","Notes:":"备注:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"在选择了您的日志文件后,点“红帽诊断”按钮可以根据您的日志文件对系统进行诊断,红帽知识库中的相应条目将会被显示。","Open a New Support Case":"创建一个新的支持案例","Opened:":"创建:","Owner":"所有者","Owner:":"所有者:","Password":"密码","Previous":"上一步","Product":"产品","Product Version:":"产品版本:","Product:":"产品:","Recommendations":"建议","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"通过使用 Red Hat Access,您可以方便地通过红帽用户门户网站来自助解决问题、诊断问题并和红帽进行联系。您需要有效的账户来访问红帽用户门户网站的资源。","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"红帽用户门户网站的用户帐号和登录到其它产品的用户帐号不同。","Red Hat Diagnose":"红帽诊断","Red Hat Login":"红帽登录","Red Hat Owner:":"红帽负责人:","Request Management Escalation":"请求管理升级","Resolution":"解决方案","Save":"保存","Save Group":"保存组","Saving draft...":"保存草稿...","Search":"搜索","Searching...":"搜索...","Select File":"选择文件","Server File(s) To Attach:":"附加的服务器文件:","Severity":"严重性","Severity:":"严重性:","Sign in":"登录","Sign into the Red Hat Customer Portal":"登录到红帽用户门户网站","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"从左面的列表中查找并选择一个日志文件,点“选择文件”按钮。","Size":"大小","Status":"状态","Status:":"状态:","Submit":"提交","Submit Request":"提交请求","Successfully deleted attachment:":"成功删除的附件:","Successfully uploaded attachment":"成功上传的附件","Summary of Request":"请求概述","Summary:":"概述:","Support Level:":"支持级别:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"日志文件查看器可以对应用程序的日志文件进行诊断,并可以在红帽全球支持服务系统中创建一个支持案例。","This release is now retired, please refer to the recommended FAQ prior to filing a case":"这个版本已经不被支持,请在创建案例前参考我们推荐的 FAQ。","To view a recommendation, click on it.":"点一个建议项来查看它的内容。","Type":"类型","Unauthorized.":"未授权。","Update Details":"更新详情","Updated:":"更新:","Upload Attachments":"上传附件","User Name":"用户名","View full article in new window":"在新窗口中查看全文","Would you like a Red Hat support manager to contact you regarding this case?":"您是否希望红帽支持经理就此支持案例与您联系?","You have used 0% of the 32KB maximum description size.":"您已经使用了最大描述大小限制(32 KB)中的 0%。","handpicked":"精选","to case":"案例"}); + gettextCatalog.setStrings('ru', {"24x7 Contact:":"Круглосуточная связь:","24x7 Support:":"Круглосуточная поддержка:"," Open a New Support Case":" Открыть новый запрос"," Select Log":" Выберите журнал","Account Name:":"Имя учетной записи:","Account Number:":"Номер учетной записи:","Account:":"Учетная запись:","Add":"Добавить","Add Comment":"Добавить комментарий","Advanced Mission Critical":"Программа критического обслуживания","Alternate Case ID:":"Дополнительный идентификатор запроса:","Attach Foreman logs:":"Добавить журналы Foreman:","Attach local file":"Добавить локальный файл","Attached":"Добавлено","Attached By":"Добавил ","Attached Files":"Добавленные файлы","Attachments":"Вложения","Attachments:":"Вложения:","Available Log Files":"Доступные журналы","Bugzilla Number":"Номер Bugzilla","Bugzilla Tickets":"Отчеты Bugzilla","Cancel":"Отмена","Case Discussion":"Обсуждение запроса","Case Group:":"Группа:","Case Type:":"Тип запроса:","Chat offline":"Оффлайн чат","Chat with support":"Чат с поддержкой","Choose File(s) To Attach:":"Выберите файлы:","Close messages":"Закрыть сообщения","Comment:":"Комментарий:","Create Case Group":"Создать группу","Create New Case Group":"Создать новую группу запросов","Delete":"Удалить","Delete Group":"Удалить группу","Deleting attachment:":"Удаление вложения:","Description":"Описание","Description:":"Описание:","Details":"Свойства","Diagnose":"Диагностика","Draft saved":"Черновик сохранен","Email Notification Recipients":"Получатели уведомлений","Environment":"Окружение","Export All as CSV":"Экспорт в CSV","Exporting CSV...":"Экспорт в CSV...","File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.":"Имена файлов не могут содержать больше 80 знаков, а размер вложения не должен превышать 250 МБ. Для больших файлов рекомендуется использовать dropbox.redhat.com.","Filename":"Имя файла","Files to Attach":"Файлы для добавления","First Name":"Имя","Group":"Группа","Group:":"Группа:","If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.":"Если вы считаете, что ваш запрос должен иметь высокий приоритет, объясните причину в комментариях.","In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.":"Если вы все же хотите открыть запрос, нажмите «Открыть новый запрос». Данные из выбранного журнала будут автоматически добавлены в отчет.","Is Public:":"Общий доступ:","Last Name":"Фамилия","Last Updated:":"Обновлен:","Learn more":"Узнать больше","Log File Viewer":"Просмотр журналов","Log In":"Вход","Log Out":"Выход","Logged into the Red Hat Customer Portal as":"Пользователь:","My Account":"Моя учетная запись","Name":"Имя","Next":"Вперед","No attachments added":"Нет вложений","No cases found with given filters.":"Нет запросов.","No cases found with given search criteria.":"Нет запросов.","No groups found.":"Нет групп.","No linked bugzillas":"Нет запросов Bugzilla","Not Logged into the Red Hat Customer Portal":"Не авторизован на портале пользователей","Note:":"Примечание:","Notes:":"Примечания:","Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.":"После выбора журнала можно провести его диагностику, нажав кнопку «Диагностика Red Hat». Будут показаны подходящие статьи и решения из базы данных Red Hat.","Open a New Support Case":"Открыть новый запрос","Opened:":"Открыт: ","Owner":"Владелец","Owner:":"Владелец:","Password":"Пароль","Previous":"Назад","Product":"Продукт","Product Version:":"Версия:","Product:":"Продукт:","Recommendations":"Рекомендации","Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.":"Red Hat Access облегчает самостоятельную диагностику конфликтов и предоставляет каналы связи с Red Hat через портал пользователей. Для доступа к ресурсам надо будет авторизоваться на портале.","Red Hat Customer Portal credentials differ from the credentials used to log into this product.":"Имя пользователя и пароль доступа к порталу отличаются от имени и пароля, связанных с продуктом.","Red Hat Diagnose":"Диагностика Red Hat","Red Hat Login":"Учетная запись Red Hat","Red Hat Owner:":"Владелец Red Hat:","Request Management Escalation":"Связаться с менеджером","Resolution":"Решение","Save":"Сохранить","Save Group":"Сохранить группу","Saving draft...":"Сохранение черновика...","Search":"Поиск","Searching...":"Поиск...","Select File":"Выбрать файл","Server File(s) To Attach:":"Файлы на сервере:","Severity":"Степень","Severity:":"Степень:","Sign in":"Вход","Sign into the Red Hat Customer Portal":"Авторизация на портале пользователей Red Hat","Simply navigate to and select a log file from the list on the left and click the 'Select File' button.":"Выберите журнал из списка и нажмите «Выбрать файл».","Size":"Размер","Status":"Статус","Status:":"Статус:","Submit":"Отправить","Submit Request":"Отправить запрос","Successfully deleted attachment:":"Вложение удалено:","Successfully uploaded attachment":"Вложение добавлено:","Summary of Request":"Сводка запроса","Summary:":"Сводка:","Support Level:":"Уровень поддержки:","The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.":"В окне просмотра журналов можно проверить журналы программ и создать запрос обслуживания в службе поддержки Red Hat.","This release is now retired, please refer to the recommended FAQ prior to filing a case":"Жизненный цикл этого выпуска подошел к концу. Прежде чем создать запрос, ознакомьтесь с секцией вопросов и ответов.","To view a recommendation, click on it.":"Щелкните на рекомендации для ее просмотра.","Type":"Тип","Unauthorized.":"Не авторизован.","Update Details":"Обновить свойства","Updated:":"Обновлено:","Upload Attachments":"Добавить вложения","User Name":"Имя пользователя","View full article in new window":"Открыть статью в новом окне","Would you like a Red Hat support manager to contact you regarding this case?":"Вы согласны, чтобы менеджер службы поддержки Red Hat с вами связался?","You have used 0% of the 32KB maximum description size.":"Для описания использовано 0% из 32 КБ.","handpicked":"подобрано","to case":"к запросу"}); /* jshint +W100 */ }]); 'use strict'; angular.module('RedhatAccess.cases', [ - 'ui.router', - 'ui.bootstrap', - 'ui.select2', - 'ngTable', - 'RedhatAccess.template', - 'RedhatAccess.security', - 'RedhatAccess.search', - 'RedhatAccess.ui-utils', - 'RedhatAccess.common', - 'RedhatAccess.header' -]) -.constant('CASE_EVENTS', { - received: 'case-received' -}) -.constant('CHAT_SUPPORT', { - enableChat: false, - chatButtonToken: '573A0000000GmiP', - chatLiveAgentUrlPrefix: 'https://d.la8cs.salesforceliveagent.com/chat', - chatInitHashOne: '572A0000000GmiP', - chatInitHashTwo: '00DK000000W3mDA', - chatIframeHackUrlPrefix:'https://qa-rogsstest.cs9.force.com/chatHidden' - -// chatButtonToken - '573A0000000GmiP' -// deploymentJS - 'https://c.la8cs.salesforceliveagent.com/content/g/js/31.0/deployment.js' -// chatLiveAgentUrlPrefix - 'https://d.la8cs.salesforceliveagent.com/chat' -// chatInitHashOne - '572A0000000GmiP' -// chatInitHashTwo - '00DK000000W3mDA' -// chatIframeHackUrlPrefix - 'https://qa-rogsstest.cs9.force.com/chatHidden' - -}) -.constant('ENTITLEMENTS', { - standard: 'STANDARD', - premium: 'PREMIUM', - defaults: 'DEFAULT' -}) -.constant('STATUS', { - open: 'open', - closed: 'closed', - both: 'both' -}) -.value('NEW_DEFAULTS', { - 'product': '', - 'version': '' -}) -.value('GLOBAL_CASE_CONFIG', { - 'showRecommendations': true, - 'showAttachments': true -}) -.value('NEW_CASE_CONFIG', { - 'showRecommendations': true, - 'showAttachments': true, - 'showServerSideAttachments': true -}) -.value('EDIT_CASE_CONFIG', { - 'showDetails': true, - 'showDescription': true, - 'showBugzillas': true, - 'showAttachments': true, - 'showRecommendations': true, - 'showComments': true, - 'showServerSideAttachments': true, - 'showEmailNotifications': true -}) -.config([ - '$stateProvider', - function ($stateProvider) { - - $stateProvider.state('compact', { - url: '/case/compact?sessionId', - templateUrl: 'cases/views/compact.html' - }); - - $stateProvider.state('compact.edit', { - url: '/{id:[0-9]{1,8}}', - templateUrl: 'cases/views/compactEdit.html', - controller: 'CompactEdit' - }); - - $stateProvider.state('edit', { - url: '/case/{id:[0-9]{1,8}}', - templateUrl: 'cases/views/edit.html', - controller: 'Edit' - }); - - $stateProvider.state('new', { - url: '/case/new', - templateUrl: 'cases/views/new.html', - controller: 'New' - }); - - $stateProvider.state('list', { - url: '/case/list', - templateUrl: 'cases/views/list.html', - controller: 'List' - }); - - $stateProvider.state('searchCases', { - url: '/case/search', - templateUrl: 'cases/views/search.html', - controller: 'Search' - }); - - $stateProvider.state('group', { - url: '/case/group', - controller: 'Group', - templateUrl: 'cases/views/group.html' - }); - } + 'ui.router', + 'ui.bootstrap', + 'localytics.directives', + 'ngTable', + 'RedhatAccess.template', + 'RedhatAccess.security', + 'RedhatAccess.search', + 'RedhatAccess.ui-utils', + 'RedhatAccess.common', + 'RedhatAccess.header', + 'angularFileUpload' +]).constant('CASE_EVENTS', { + received: 'case-received' +}).constant('CHAT_SUPPORT', { + enableChat: false, + chatButtonToken: '573A0000000GmiP', + chatLiveAgentUrlPrefix: 'https://d.la1w1.salesforceliveagent.com/chat', + chatInitHashOne: '572A0000000GmiP', + chatInitHashTwo: '00DA0000000HxWH', + chatIframeHackUrlPrefix: 'https://rogsstest.force.com/chatHidden' +}).constant('STATUS', { + open: 'open', + closed: 'closed', + both: 'both' +}).value('NEW_DEFAULTS', { + 'product': '', + 'version': '' +}).value('GLOBAL_CASE_CONFIG', { + 'showRecommendations': true, + 'showAttachments': true +}).value('NEW_CASE_CONFIG', { + 'showRecommendations': true, + 'showAttachments': true, + 'showServerSideAttachments': true, + 'productSortListFile': '/productSortList.txt', + 'isPCM': false +}).value('EDIT_CASE_CONFIG', { + 'showDetails': true, + 'showDescription': true, + 'showBugzillas': true, + 'showAttachments': true, + 'showRecommendations': true, + 'showComments': true, + 'showServerSideAttachments': true, + 'showEmailNotifications': true, + 'isPCM': false +}).config([ + '$stateProvider', + function ($stateProvider) { + $stateProvider.state('compact', { + url: '/case/compact?sessionId', + templateUrl: 'cases/views/compact.html' + }); + $stateProvider.state('compact.edit', { + url: '/{id:[0-9]{1,8}}', + templateUrl: 'cases/views/compactEdit.html', + controller: 'CompactEdit' + }); + $stateProvider.state('edit', { + url: '/case/{id:[0-9]{1,8}}?commentId', + templateUrl: 'cases/views/edit.html', + controller: 'Edit', + reloadOnSearch: false + }); + $stateProvider.state('new', { + url: '/case/new', + templateUrl: 'cases/views/new.html', + controller: 'New' + }); + $stateProvider.state('list', { + url: '/case/list', + templateUrl: 'cases/views/list.html', + controller: 'List' + }); + $stateProvider.state('searchCases', { + url: '/case/search', + templateUrl: 'cases/views/search.html', + controller: 'Search' + }); + $stateProvider.state('group', { + url: '/case/group', + controller: 'Group', + templateUrl: 'cases/views/group.html' + }); + $stateProvider.state('defaultGroup', { + url: '/case/group/default', + controller: 'DefaultGroup', + templateUrl: 'cases/views/defaultGroup.html' + }); + $stateProvider.state('editGroup', { + url: '/case/group/{groupNumber}', + controller: 'EditGroup', + templateUrl: 'cases/views/editGroup.html' + }); + } ]); - +/*global angular */ 'use strict'; - /*global $ */ - -angular.module('RedhatAccess.common', ['RedhatAccess.ui-utils']); +/*global $ */ +angular.module('RedhatAccess.common', [ + 'RedhatAccess.ui-utils', + 'jmdobry.angular-cache' +]).config(["$angularCacheFactoryProvider", function($angularCacheFactoryProvider) { + +}]).constant('RESOURCE_TYPES', { + article: 'Article', + solution: 'Solution' +}).factory('configurationService', [ + '$q', + function($q) { + var defer = $q.defer(); + var service = { + setConfig: function(config) { + defer.resolve(config); + }, + getConfig: function() { + return defer.promise; + } + }; + return service; + } +]); 'use strict'; - /*global $ */ - -angular.module('RedhatAccess.header', []) - .value('TITLE_VIEW_CONFIG', { +/*global $ */ +angular.module('RedhatAccess.header', []).value('TITLE_VIEW_CONFIG', { show: 'false', titlePrefix: 'Red Hat Access: ', searchTitle: 'Search', @@ -17744,541 +150,1476 @@ angular.module('RedhatAccess.header', []) newCaseTitle: 'New Support Case', searchCaseTitle: 'Search Support Cases', logViewerTitle: 'Log', - manageGroupsTitle: 'Manage Case Groups' - }) - .controller('TitleViewCtrl', ['TITLE_VIEW_CONFIG', '$scope', - function(TITLE_VIEW_CONFIG, $scope) { - $scope.showTitle = TITLE_VIEW_CONFIG.show; - $scope.titlePrefix = TITLE_VIEW_CONFIG.titlePrefix; - $scope.getPageTitle = function() { - switch ($scope.page) { - case 'search': - return TITLE_VIEW_CONFIG.searchTitle; - case 'caseList': - return TITLE_VIEW_CONFIG.caseListTitle; - case 'caseView': - return TITLE_VIEW_CONFIG.caseViewTitle; - case 'newCase': - return TITLE_VIEW_CONFIG.newCaseTitle; - case 'logViewer': - return TITLE_VIEW_CONFIG.logViewerTitle; - case 'searchCase': - return TITLE_VIEW_CONFIG.searchCaseTitle; - case 'manageGroups': - return TITLE_VIEW_CONFIG.manageGroupsTitle; - default: - return ''; - } - }; + manageGroupsTitle: 'Manage Case Groups', + editGroupTitle: 'Edit Case Group', + defaultGroup: 'Manage Default Case Groups' +}).controller('TitleViewCtrl', [ + 'TITLE_VIEW_CONFIG', + '$scope', + function (TITLE_VIEW_CONFIG, $scope) { + $scope.showTitle = TITLE_VIEW_CONFIG.show; + $scope.titlePrefix = TITLE_VIEW_CONFIG.titlePrefix; + $scope.getPageTitle = function () { + switch ($scope.page) { + case 'search': + return TITLE_VIEW_CONFIG.searchTitle; + case 'caseList': + return TITLE_VIEW_CONFIG.caseListTitle; + case 'caseView': + return TITLE_VIEW_CONFIG.caseViewTitle; + case 'newCase': + return TITLE_VIEW_CONFIG.newCaseTitle; + case 'logViewer': + return TITLE_VIEW_CONFIG.logViewerTitle; + case 'searchCase': + return TITLE_VIEW_CONFIG.searchCaseTitle; + case 'manageGroups': + return TITLE_VIEW_CONFIG.manageGroupsTitle; + case 'editGroup': + return TITLE_VIEW_CONFIG.editGroupTitle; + default: + return ''; + } + }; } - ]) - .directive('rhaTitletemplate', - function() { - return { +]).directive('rhaTitletemplate', function () { + return { restrict: 'AE', - scope: { - page: '@' - }, + scope: { page: '@' }, templateUrl: 'common/views/title.html', controller: 'TitleViewCtrl' - }; - }) - .service('AlertService', ['$filter', 'AUTH_EVENTS', '$rootScope', 'RHAUtils', - function($filter, AUTH_EVENTS, $rootScope, RHAUtils) { - var ALERT_TYPES = { - DANGER: 'danger', - SUCCESS: 'success', - WARNING: 'warning' - }; - - this.alerts = []; //array of {message: 'some alert', type: ''} objects - - this.clearAlerts = function() { + }; +}).service('AlertService', [ + '$filter', + 'AUTH_EVENTS', + '$rootScope', + 'RHAUtils', + function ($filter, AUTH_EVENTS, $rootScope, RHAUtils) { + var ALERT_TYPES = { + DANGER: 'danger', + SUCCESS: 'success', + WARNING: 'warning' + }; this.alerts = []; - }; - - this.addAlert = function(alert) { - this.alerts.push(alert); - }; - - this.removeAlert = function(alert) { - this.alerts.splice(this.alerts.indexOf(alert), 1); - }; - - this.addDangerMessage = function(message) { - return this.addMessage(message, ALERT_TYPES.DANGER); - }; - - this.addSuccessMessage = function(message) { - return this.addMessage(message, ALERT_TYPES.SUCCESS); - }; - - this.addWarningMessage = function(message) { - return this.addMessage(message, ALERT_TYPES.WARNING); - }; - - this.addMessage = function(message, type) { - var alert = { - message: message, - type: type === null ? 'warning' : type + //array of {message: 'some alert', type: ''} objects + this.clearAlerts = function () { + this.alerts = []; }; - this.addAlert(alert); - - $('body,html').animate({ - scrollTop: $('body').offset().top - }, 100); - - //Angular adds a unique hash to each alert during data binding, - //so the returned alert will be unique even if the - //message and type are identical. - return alert; - }; - - this.getErrors = function() { - var errors = $filter('filter')(this.alerts, { - type: ALERT_TYPES.DANGER - }); - - if (errors === null) { - errors = []; - } - - return errors; - }; - - this.addStrataErrorMessage = function(error) { - if (RHAUtils.isNotEmpty(error)) { - var existingMessage = - $filter('filter')(this.alerts, { - type: ALERT_TYPES.DANGER, - message: error.message - }); - - if (existingMessage.length < 1) { - this.addDangerMessage(error.message); - } - } - }; - - $rootScope.$on(AUTH_EVENTS.logoutSuccess, angular.bind(this, - function() { - this.clearAlerts(); - this.addMessage('You have successfully logged out of the Red Hat Customer Portal.'); + this.addAlert = function (alert) { + this.alerts.push(alert); + }; + this.removeAlert = function (alert) { + this.alerts.splice(this.alerts.indexOf(alert), 1); + }; + this.addDangerMessage = function (message) { + return this.addMessage(message, ALERT_TYPES.DANGER); + }; + this.addSuccessMessage = function (message) { + return this.addMessage(message, ALERT_TYPES.SUCCESS); + }; + this.addWarningMessage = function (message) { + return this.addMessage(message, ALERT_TYPES.WARNING); + }; + this.addMessage = function (message, type) { + var alert = { + message: message, + type: type === null ? 'warning' : type + }; + this.addAlert(alert); + $('body,html').animate({ scrollTop: $('body').offset().top }, 100); + //Angular adds a unique hash to each alert during data binding, + //so the returned alert will be unique even if the + //message and type are identical. + return alert; + }; + this.getErrors = function () { + var errors = $filter('filter')(this.alerts, { type: ALERT_TYPES.DANGER }); + if (errors === null) { + errors = []; + } + return errors; + }; + this.addStrataErrorMessage = function (error) { + if (RHAUtils.isNotEmpty(error)) { + var errorText=error.message; + if (error.xhr && error.xhr.responseText){ + errorText = errorText.concat(' Message: ' + error.xhr.responseText); + } + var existingMessage = $filter('filter')(this.alerts, { + type: ALERT_TYPES.DANGER, + message: errorText, + }); + if (existingMessage.length < 1) { + this.addDangerMessage(errorText); + } + } + }; + $rootScope.$on(AUTH_EVENTS.logoutSuccess, angular.bind(this, function () { + this.clearAlerts(); + this.addMessage('You have successfully logged out of the Red Hat Customer Portal.'); })); - $rootScope.$on(AUTH_EVENTS.loginSuccess, angular.bind(this, - function() { - this.clearAlerts(); + $rootScope.$on(AUTH_EVENTS.loginSuccess, angular.bind(this, function () { + this.clearAlerts(); })); - } - ]) - .directive('rhaAlert', - function() { - return { +]).directive('rhaAlert', function () { + return { templateUrl: 'common/views/alert.html', restrict: 'A', controller: 'AlertController' - }; - }) - .controller('AlertController', ['$scope', 'AlertService', - function($scope, AlertService) { - $scope.AlertService = AlertService; - - $scope.closeable = true; - - $scope.closeAlert = function(index) { - AlertService.alerts.splice(index, 1); - }; + }; +}).controller('AlertController', [ + '$scope', + 'AlertService', + function ($scope, AlertService) { + $scope.AlertService = AlertService; + $scope.closeable = true; + $scope.closeAlert = function (index) { + AlertService.alerts.splice(index, 1); + }; + $scope.dismissAlerts = function () { + AlertService.clearAlerts(); + }; + } +]).directive('rhaHeader', function () { + return { + templateUrl: 'common/views/header.html', + restrict: 'A', + scope: { page: '@' }, + controller: 'HeaderController' + }; +}).controller('HeaderController', [ + '$scope', + 'AlertService', + function ($scope, AlertService) { + /** + * For some reason the rhaAlert directive's controller is not binding to the view. + * Hijacking rhaAlert's parent controller (HeaderController) works + * until a real solution is found. + */ + $scope.AlertService = AlertService; + $scope.closeable = true; + $scope.closeAlert = function (index) { + AlertService.alerts.splice(index, 1); + }; + $scope.dismissAlerts = function () { + AlertService.clearAlerts(); + }; + } +]); - $scope.dismissAlerts = function() { - AlertService.clearAlerts(); - }; +'use strict'; +/*jshint unused:vars */ +var app = angular.module('RedhatAccess.ui-utils', ['gettext']); +//this is an example controller to provide tree data +// app.controller('TreeViewSelectorCtrl', ['$scope', 'TreeViewSelectorData', +// function($scope, TreeViewSelectorData) { +// $scope.name = 'Attachments'; +// $scope.attachmentTree = []; +// TreeViewSelectorData.getTree('attachments').then( +// function(tree) { +// $scope.attachmentTree = tree; +// }, +// function() { +// }); +// } +// ]); +app.service('RHAUtils', function () { + /** + * Generic function to decide if a simple object should be considered nothing + */ + this.isEmpty = function (object) { + if (object === undefined || object === null || object === '' || object.length === 0 || object === {}) { + return true; + } + return false; + }; + this.isNotEmpty = function (object) { + return !this.isEmpty(object); + }; +}); +//Wrapper service for translations +app.service('translate', [ + 'gettextCatalog', + function (gettextCatalog) { + return function (str) { + return gettextCatalog.getString(str); + }; + } +]); +app.filter('trust_html', ['$sce', function($sce){ + return function(text) { + return $sce.trustAsHtml(text); + }; +}]); +app.directive('rhaChoicetree', function () { + return { + template: '
              ', + replace: true, + transclude: true, + restrict: 'A', + scope: { + tree: '=ngModel', + rhaDisabled: '=' + } + }; +}); +app.directive('optionsDisabled', ["$parse", function($parse) { + var disableOptions = function(scope, attr, element, data, fnDisableIfTrue) { + // refresh the disabled options in the select element. + $('option[value!="?"]', element).each(function(i, e) { + var locals = {}; + locals[attr] = data[i]; + $(this).attr('disabled', fnDisableIfTrue(scope, locals)); + }); + }; + return { + priority: 0, + link: function(scope, element, attrs, ctrl) { + // parse expression and build array of disabled options + var expElements = attrs.optionsDisabled.match(/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/); + var fnDisableIfTrue = $parse(expElements[1]); + var options = expElements[3]; + scope.$watch(options, function(newValue, oldValue) { + if(newValue) { + disableOptions(scope, expElements[2], element, newValue, fnDisableIfTrue); + } + }, true); + } + }; +}]); +app.directive('rhaChoice', ["$compile", function ($compile) { + return { + restrict: 'A', + templateUrl: 'common/views/treenode.html', + link: function (scope, elm) { + scope.choiceClicked = function (choice) { + choice.checked = !choice.checked; + function checkChildren(c) { + angular.forEach(c.children, function (c) { + c.checked = choice.checked; + checkChildren(c); + }); + } + checkChildren(choice); + }; + if (scope.choice.children.length > 0) { + var childChoice = $compile('
              ')(scope); + elm.append(childChoice); + } + } + }; +}]); +app.factory('TreeViewSelectorData', [ + '$http', + '$q', + 'TreeViewSelectorUtils', + function ($http, $q, TreeViewSelectorUtils) { + var service = { + getTree: function (dataUrl, sessionId) { + var defer = $q.defer(); + var tmpUrl = dataUrl; + if (sessionId) { + tmpUrl = tmpUrl + '?sessionId=' + encodeURIComponent(sessionId); + } + $http({ + method: 'GET', + url: tmpUrl + }).success(function (data, status, headers, config) { + var tree = []; + TreeViewSelectorUtils.parseTreeList(tree, data); + defer.resolve(tree); + }).error(function (data, status, headers, config) { + defer.reject({}); + }); + return defer.promise; + } + }; + return service; + } +]); +app.factory('TreeViewSelectorUtils', function () { + var parseTreeNode = function (splitPath, tree, fullFilePath) { + if (splitPath[0] !== undefined) { + if (splitPath[0] !== '') { + var node = splitPath[0]; + var match = false; + var index = 0; + for (var i = 0; i < tree.length; i++) { + if (tree[i].name === node) { + match = true; + index = i; + break; + } + } + if (!match) { + var nodeObj = {}; + nodeObj.checked = isLeafChecked(node); + nodeObj.name = removeParams(node); + if (splitPath.length === 1) { + nodeObj.fullPath = removeParams(fullFilePath); + } + nodeObj.children = []; + tree.push(nodeObj); + index = tree.length - 1; + } + splitPath.shift(); + parseTreeNode(splitPath, tree[index].children, fullFilePath); + } else { + splitPath.shift(); + parseTreeNode(splitPath, tree, fullFilePath); + } + } + }; + var removeParams = function (path) { + if (path) { + var split = path.split('?'); + return split[0]; + } + return path; + }; + var isLeafChecked = function (path) { + if (path) { + var split = path.split('?'); + if (split[1]) { + var params = split[1].split('&'); + for (var i = 0; i < params.length; i++) { + if (params[i].indexOf('checked=true') !== -1) { + return true; + } + } + } + } + return false; + }; + var hasSelectedLeaves = function (tree) { + for (var i = 0; i < tree.length; i++) { + if (tree[i] !== undefined) { + if (tree[i].children.length === 0) { + //we only check leaf nodes + if (tree[i].checked === true) { + return true; + } + } else { + if (hasSelectedLeaves(tree[i].children)) { + return true; + } + } + } + } + return false; + }; + var getSelectedNames = function (tree, container) { + for (var i = 0; i < tree.length; i++) { + if (tree[i] !== undefined) { + if (tree[i].children.length === 0) { + if (tree[i].checked === true) { + container.push(tree[i].fullPath); + } + } else { + getSelectedNames(tree[i].children, container); + } + } + } + }; + var service = { + parseTreeList: function (tree, data) { + var files = data.split('\n'); + for (var i = 0; i < files.length; i++) { + var file = files[i]; + var splitPath = file.split('/'); + parseTreeNode(splitPath, tree, file); + } + }, + hasSelections: function (tree) { + return hasSelectedLeaves(tree); + }, + getSelectedLeaves: function (tree) { + if (tree === undefined) { + return []; + } + var container = []; + getSelectedNames(tree, container); + return container; + } + }; + return service; +}); +app.directive('rhaResizable', [ + '$window', + '$timeout', + function ($window) { + var link = function (scope, element, attrs) { + scope.onResizeFunction = function () { + var distanceToTop = element[0].getBoundingClientRect().top; + var height = $window.innerHeight - distanceToTop; + element.css('height', height); + }; + angular.element($window).bind('resize', function () { + scope.onResizeFunction(); //scope.$apply(); + }); + angular.element($window).bind('click', function () { + scope.onResizeFunction(); //scope.$apply(); + }); + if (attrs.rhaDomReady !== undefined) { + scope.$watch('rhaDomReady', function (newValue) { + if (newValue) { + scope.onResizeFunction(); + } + }); + } else { + scope.onResizeFunction(); + } + }; + return { + restrict: 'A', + scope: { rhaDomReady: '=' }, + link: link + }; } - ]) - .directive('rhaHeader', - function() { - return { - templateUrl: 'common/views/header.html', - restrict: 'A', - scope: { - page: '@' - }, - controller: 'HeaderController' - }; - }) - .controller('HeaderController', ['$scope', 'AlertService', - function($scope, AlertService) { - /** - * For some reason the rhaAlert directive's controller is not binding to the view. - * Hijacking rhaAlert's parent controller (HeaderController) works - * until a real solution is found. - */ - $scope.AlertService = AlertService; - - $scope.closeable = true; - - $scope.closeAlert = function(index) { - AlertService.alerts.splice(index, 1); - }; +]); - $scope.dismissAlerts = function() { - AlertService.clearAlerts(); - }; +//var testURL = 'http://localhost:8080/LogCollector/'; +// angular module +'use strict'; +angular.module('RedhatAccess.logViewer', [ + 'angularTreeview', + 'ui.bootstrap', + 'RedhatAccess.search', + 'RedhatAccess.header' +]).config([ + '$stateProvider', + function ($stateProvider) { + $stateProvider.state('logviewer', { + url: '/logviewer', + templateUrl: 'log_viewer/views/log_viewer.html' + }); } - ]).factory('configurationService', ['$q', - function($q) { - var defer = $q.defer(); - var service = { - setConfig: function(config) { - defer.resolve(config); - }, - getConfig: function() { - return defer.promise; +]).constant('LOGVIEWER_EVENTS', { allTabsClosed: 'allTabsClosed' }).value('hideMachinesDropdown', { value: false }); +function returnNode(splitPath, tree, fullFilePath) { + if (splitPath[0] !== undefined) { + if (splitPath[0] !== '') { + var node = splitPath[0]; + var match = false; + var index = 0; + for (var i in tree) { + if (tree[i].roleName === node) { + match = true; + index = i; + break; + } + } + if (!match) { + var object = {}; + object.roleName = node; + object.roleId = node; + if (splitPath.length === 1) { + object.fullPath = fullFilePath; + } + object.children = []; + tree.push(object); + index = tree.length - 1; + } + splitPath.shift(); + returnNode(splitPath, tree[index].children, fullFilePath); + } else { + splitPath.shift(); + returnNode(splitPath, tree, fullFilePath); } - }; - return service; } - ]); +} +function parseList(tree, data) { + var files = data.split('\n'); + for (var i in files) { + var file = files[i]; + var splitPath = file.split('/'); + returnNode(splitPath, tree, file); + } +} +/*jshint camelcase: false */ 'use strict'; /*jshint unused:vars */ - -var app = angular.module('RedhatAccess.ui-utils', ['gettext']); - -//this is an example controller to provide tree data -// app.controller('TreeViewSelectorCtrl', ['$scope', 'TreeViewSelectorData', -// function($scope, TreeViewSelectorData) { -// $scope.name = 'Attachments'; -// $scope.attachmentTree = []; -// TreeViewSelectorData.getTree('attachments').then( -// function(tree) { -// $scope.attachmentTree = tree; -// }, -// function() { -// }); -// } -// ]); -app.service('RHAUtils', - function() { - /** - * Generic function to decide if a simple object should be considered nothing - */ - this.isEmpty = function(object) { - if (object === undefined || object === null || object === '' || object.length === 0 || object === {}) { - return true; - } else { - return false - } - } - - this.isNotEmpty = function(object) { - return !this.isEmpty(object); +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search', [ + 'ui.router', + 'RedhatAccess.template', + 'RedhatAccess.security', + 'ui.bootstrap', + 'ngSanitize', + 'RedhatAccess.ui-utils', + 'RedhatAccess.common', + 'RedhatAccess.header' +]).constant('SEARCH_PARAMS', { limit: 10 }).value('SEARCH_CONFIG', { + openCaseRef: '#/case/new', + showOpenCaseBtn: true +}).config([ + '$stateProvider', + function ($stateProvider) { + $stateProvider.state('search', { + url: '/search', + controller: 'SearchController', + templateUrl: 'search/views/search.html' + }).state('search_accordion', { + url: '/search2', + controller: 'SearchController', + templateUrl: 'search/views/accordion_search.html' + }); } - } -); - -//Wrapper service for translations -app.service('translate', ['gettextCatalog', - function (gettextCatalog) { - return function (str) { - return gettextCatalog.getString(str); - }; - } ]); - -app.directive('rhaChoicetree', function () { - return { - template: '
              ', - replace: true, - transclude: true, - restrict: 'A', - scope: { - tree: '=ngModel', - rhaDisabled: '=' - } - }; +'use strict'; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.security', [ + 'ui.bootstrap', + 'RedhatAccess.template', + 'ui.router', + 'RedhatAccess.common', + 'RedhatAccess.header' +]).constant('AUTH_EVENTS', { + loginSuccess: 'auth-login-success', + loginFailed: 'auth-login-failed', + logoutSuccess: 'auth-logout-success', + sessionTimeout: 'auth-session-timeout', + notAuthenticated: 'auth-not-authenticated', + notAuthorized: 'auth-not-authorized', + sessionIdChanged: 'sid-changed' +}).value('LOGIN_VIEW_CONFIG', { verbose: true }).value('SECURITY_CONFIG', { + displayLoginStatus: true, + autoCheckLogin: true, + loginURL: '', + logoutURL: '', + forceLogin: false }); - -app.directive('rhaChoice', ["$compile", function ($compile) { - return { - restrict: 'A', - templateUrl: 'common/views/treenode.html', - link: function (scope, elm) { - scope.choiceClicked = function (choice) { - choice.checked = !choice.checked; - - function checkChildren(c) { - angular.forEach(c.children, function (c) { - c.checked = choice.checked; - checkChildren(c); - }); +'use strict'; +/*global navigator, strata, angular*/ +/*jshint camelcase: false */ +/*jshint bitwise: false */ +/*jshint unused:vars */ +angular.module('RedhatAccess.common').factory('strataService', [ + '$q', + 'translate', + 'RHAUtils', + '$angularCacheFactory', + 'RESOURCE_TYPES', + function ($q, translate, RHAUtils, $angularCacheFactory, RESOURCE_TYPES) { + $angularCacheFactory('strataCache', { + capacity: 1000, + maxAge: 900000, + deleteOnExpire: 'aggressive', + recycleFreq: 60000, + cacheFlushInterval: 3600000, + storageMode: 'sessionStorage', + verifyIntegrity: true + }); + var ie8 = false; + if (navigator.appVersion.indexOf('MSIE 8.') !== -1) { + ie8 = true; + } + var strataCache; + if (!ie8) { + strataCache = $angularCacheFactory.get('strataCache'); + $(window).unload(function () { + strataCache.destroy(); + }); } - checkChildren(choice); - }; - if (scope.choice.children.length > 0) { - var childChoice = $compile('
              ')(scope); - elm.append(childChoice); - } - } - }; -}]); - - + var errorHandler = function (message, xhr, response, status) { + var translatedMsg = message; + switch (status) { + case 'Unauthorized': + translatedMsg = translate('Unauthorized.'); + break; // case n: + // code block + // break; + } + this.reject({ + message: translatedMsg, + xhr: xhr, + response: response, + status: status + }); + }; + var service = { + authentication: { + checkLogin: function () { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('auth')) { + strata.addAccountNumber(strataCache.get('auth').account_number); + deferred.resolve(strataCache.get('auth')); + } else { + strata.checkLogin(function (result, authedUser) { + if (result) { + service.accounts.list().then(function (accountNumber) { + service.accounts.get(accountNumber).then(function (account) { + authedUser.account = account; + strata.addAccountNumber(account.number); + if (!ie8) { + strataCache.put('auth', authedUser); + } + deferred.resolve(authedUser); + }); + }, function (error) { + //TODO revisit this behavior + authedUser.account = undefined; + deferred.resolve(authedUser); + }); + } else { + var error = {message: 'Unauthorized.'}; + deferred.reject(error); + } + }); + } + return deferred.promise; + }, + setCredentials: function (username, password) { + return strata.setCredentials(username, password); + }, + logout: function () { + if (!ie8) { + strataCache.removeAll(); + } + strata.clearCredentials(); + } + }, + entitlements: { + get: function (showAll, ssoUserName) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('entitlements' + ssoUserName)) { + deferred.resolve(strataCache.get('entitlements' + ssoUserName)); + } else { + strata.entitlements.get(showAll, function (entitlements) { + if (!ie8) { + strataCache.put('entitlements' + ssoUserName, entitlements); + } + deferred.resolve(entitlements); + }, angular.bind(deferred, errorHandler), ssoUserName); + } + return deferred.promise; + } + }, + problems: function (data, max) { + var deferred = $q.defer(); + strata.problems(data, function (solutions) { + deferred.resolve(solutions); + }, angular.bind(deferred, errorHandler), max); + return deferred.promise; + }, + recommendations: function (data, max, highlight, highlightTags) { + var deferred = $q.defer(); + strata.recommendations(data, function (recommendations) { + deferred.resolve(recommendations); + }, angular.bind(deferred, errorHandler), max, highlight, highlightTags); + return deferred.promise; + }, + solutions: { + get: function (uri) { + var deferred = $q.defer(); + var splitUri = uri.split('/'); + uri = splitUri[splitUri.length - 1]; + if (!ie8 && strataCache.get('solution' + uri)) { + deferred.resolve(strataCache.get('solution' + uri)); + } else { + strata.solutions.get(uri, function (solution) { + solution.resource_type = RESOURCE_TYPES.solution; //Needed upstream + if (!ie8) { + strataCache.put('solution' + uri, solution); + } + deferred.resolve(solution); + }, function () { + //workaround for 502 from strata + //If the deferred is rejected then the parent $q.all() + //based deferred will fail. Since we don't need every + //recommendation just send back undefined + //and the caller can ignore the missing solution details. + deferred.resolve(); + }); + } + return deferred.promise; + } + }, + search: function (searchString, max) { + var resultsDeferred = $q.defer(); + var deferreds = []; + strata.search( + searchString, + function (entries) { + //retrieve details for each solution + if (entries !== undefined) { + entries.forEach(function (entry) { + var deferred = $q.defer(); + deferreds.push(deferred.promise); + var cacheMiss = true; + if (entry.resource_type === RESOURCE_TYPES.solution) { + if (!ie8 && strataCache.get('solution' + entry.uri)) { + deferred.resolve(strataCache.get('solution' + entry.uri)); + cacheMiss = false; + } -app.factory('TreeViewSelectorData', ['$http', '$q', 'TreeViewSelectorUtils', - function ($http, $q, TreeViewSelectorUtils) { - var service = { - getTree: function (dataUrl, sessionId) { - var defer = $q.defer(); - var tmpUrl = dataUrl; - if(sessionId){ - tmpUrl = tmpUrl + '?sessionId=' + encodeURIComponent(sessionId) + } + // else if (entry.resource_type === RESOURCE_TYPES.article) { + // if (strataCache.get('article' + entry.uri)) { + // deferred.resolve(strataCache.get('article' + entry.uri)); + // cacheMiss = false; + // } + // } + if (cacheMiss) { + strata.utils.getURI(entry.uri, entry.resource_type, function (type, info) { + if (info !== undefined) { + info.resource_type = type; + if (!ie8 && (type === RESOURCE_TYPES.solution)) { + strataCache.put('solution' + entry.uri, info); + } + } + deferred.resolve(info); + }, function (error) { + deferred.resolve(); + }); + } + }); + } + $q.all(deferreds).then( + function (results) { + results.forEach(function (result) { + if (result !== undefined) { + results.push(result); + } + }); + resultsDeferred.resolve(results); + }, + angular.bind(resultsDeferred, errorHandler)); + }, + angular.bind(resultsDeferred, errorHandler), + max, + false); + return resultsDeferred.promise; + }, + products: { + list: function (ssoUserName) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('products' + ssoUserName)) { + deferred.resolve(strataCache.get('products' + ssoUserName)); + } else { + strata.products.list(function (response) { + if (!ie8) { + strataCache.put('products' + ssoUserName, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler), ssoUserName); + } + return deferred.promise; + }, + versions: function (productCode) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('versions-' + productCode)) { + deferred.resolve(strataCache.get('versions-' + productCode)); + } else { + strata.products.versions(productCode, function (response) { + if (!ie8) { + strataCache.put('versions-' + productCode, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + get: function (productCode) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('product' + productCode)) { + deferred.resolve(strataCache.get('product' + productCode)); + } else { + strata.products.get(productCode, function (response) { + if (!ie8) { + strataCache.put('product' + productCode, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + } + }, + groups: { + get: function (groupNum, ssoUserName) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('groups' + ssoUserName)) { + deferred.resolve(strataCache.get('groups' + ssoUserName)); + } else { + strata.groups.get(groupNum, function (response) { + if (!ie8) { + strataCache.put('groups' + ssoUserName, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler), ssoUserName); + } + return deferred.promise; + }, + list: function (ssoUserName) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('groups' + ssoUserName)) { + deferred.resolve(strataCache.get('groups' + ssoUserName)); + } else { + strata.groups.list(function (response) { + if (!ie8) { + strataCache.put('groups' + ssoUserName, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler), ssoUserName); + } + return deferred.promise; + }, + remove: function (groupNum) { + var deferred = $q.defer(); + strata.groups.remove(groupNum, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + create: function (groupName) { + var deferred = $q.defer(); + strata.groups.create(groupName, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + update: function(groupName, groupnum){ + var deferred = $q.defer(); + strata.groups.update(groupName, groupnum, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + createDefault: function(group){ + var deferred = $q.defer(); + strata.groups.createDefault(group, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + groupUsers: { + update: function(users, accountId, groupnum){ + var deferred = $q.defer(); + strata.groupUsers.update(users, accountId, groupnum, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + accounts: { + get: function (accountNumber) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('account' + accountNumber)) { + deferred.resolve(strataCache.get('account' + accountNumber)); + } else { + strata.accounts.get(accountNumber, function (response) { + if (!ie8) { + strataCache.put('account' + accountNumber, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + users: function (accountNumber, group) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('users' + accountNumber + group)) { + deferred.resolve(strataCache.get('users' + accountNumber + group)); + } else { + strata.accounts.users(accountNumber, function (response) { + if (!ie8) { + strataCache.put('users' + accountNumber + group, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler), group); + } + return deferred.promise; + }, + list: function () { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('account')) { + deferred.resolve(strataCache.get('account')); + } else { + strata.accounts.list(function (response) { + if (!ie8) { + strataCache.put('account', response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + } + }, + cases: { + csv: function () { + var deferred = $q.defer(); + strata.cases.csv(function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + attachments: { + list: function (id) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('attachments' + id)) { + deferred.resolve(strataCache.get('attachments' + id)); + } else { + strata.cases.attachments.list(id, function (response) { + if (!ie8) { + strataCache.put('attachments' + id, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + post: function (attachment, caseNumber) { + var deferred = $q.defer(); + strata.cases.attachments.post(attachment, caseNumber, function (response, code, xhr) { + if (!ie8) { + strataCache.remove('attachments' + caseNumber); + } + deferred.resolve(xhr.getResponseHeader('Location')); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + remove: function (id, caseNumber) { + var deferred = $q.defer(); + strata.cases.attachments.remove(id, caseNumber, function (response) { + if (!ie8) { + strataCache.remove('attachments' + caseNumber); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + comments: { + get: function (id) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('comments' + id)) { + deferred.resolve(strataCache.get('comments' + id)); + } else { + strata.cases.comments.get(id, function (response) { + if (!ie8) { + strataCache.put('comments' + id, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + post: function (caseNumber, text, isPublic, isDraft) { + var deferred = $q.defer(); + strata.cases.comments.post(caseNumber, { + 'text': text, + 'draft': isDraft === true ? 'true' : 'false', + 'public': isPublic === true ? 'true' : 'false' + }, function (response) { + if (!ie8) { + strataCache.remove('comments' + caseNumber); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + put: function (caseNumber, text, isDraft, isPublic, comment_id) { + var deferred = $q.defer(); + strata.cases.comments.update(caseNumber, { + 'text': text, + 'draft': isDraft === true ? 'true' : 'false', + 'public': isPublic === true ? 'true' : 'false', + 'caseNumber': caseNumber, + 'id': comment_id + }, comment_id, function (response) { + if (!ie8) { + strataCache.remove('comments' + caseNumber); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + notified_users: { + add: function (caseNumber, ssoUserName) { + var deferred = $q.defer(); + strata.cases.notified_users.add(caseNumber, ssoUserName, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + remove: function (caseNumber, ssoUserName) { + var deferred = $q.defer(); + strata.cases.notified_users.remove(caseNumber, ssoUserName, function (response) { + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + get: function (id) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('case' + id)) { + deferred.resolve([ + strataCache.get('case' + id), + true + ]); + } else { + strata.cases.get(id, function (response) { + if (!ie8) { + strataCache.put('case' + id, response); + } + deferred.resolve([ + response, + false + ]); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + filter: function (params) { + var deferred = $q.defer(); + if (RHAUtils.isEmpty(params)) { + params = {}; + } + if (RHAUtils.isEmpty(params.count)) { + params.count = 50; + } + if (!ie8 && strataCache.get('filter' + JSON.stringify(params))) { + deferred.resolve(strataCache.get('filter' + JSON.stringify(params))); + } else { + strata.cases.filter(params, function (response) { + if (!ie8) { + strataCache.put('filter' + JSON.stringify(params), response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + post: function (caseJSON) { + var deferred = $q.defer(); + strata.cases.post(caseJSON, function (caseNumber) { + //Remove any case filters that are cached + if (!ie8) { + for (var k in strataCache.keySet()) { + if (~k.indexOf('filter')) { + strataCache.remove(k); + } + } + } + deferred.resolve(caseNumber); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + }, + put: function (caseNumber, caseJSON) { + var deferred = $q.defer(); + strata.cases.put(caseNumber, caseJSON, function (response) { + if (!ie8) { + strataCache.remove('case' + caseNumber); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + return deferred.promise; + } + }, + values: { + cases: { + severity: function () { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('severities')) { + deferred.resolve(strataCache.get('severities')); + } else { + strata.values.cases.severity(function (response) { + if (!ie8) { + strataCache.put('severities', response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + status: function () { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('statuses')) { + deferred.resolve(strataCache.get('statuses')); + } else { + strata.values.cases.status(function (response) { + if (!ie8) { + strataCache.put('statuses', response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + }, + types: function () { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('types')) { + deferred.resolve(strataCache.get('types')); + } else { + strata.values.cases.types(function (response) { + if (!ie8) { + strataCache.put('types', response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + } + } + }, + users: { + get: function (userId) { + var deferred = $q.defer(); + if (!ie8 && strataCache.get('userId' + userId)) { + deferred.resolve(strataCache.get('userId' + userId)); + } else { + strata.users.get(function (response) { + if (!ie8) { + strataCache.put('userId' + userId, response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler), userId); + } + return deferred.promise; + }, + chatSession: { + post: function(){ + var deferred = $q.defer(); + if (!ie8 && strataCache.get('chatSession')) { + deferred.resolve(strataCache.get('chatSession')); + } else { + strata.users.chatSession.get(function (response) { + if (!ie8) { + strataCache.put('chatSession', response); + } + deferred.resolve(response); + }, angular.bind(deferred, errorHandler)); + } + return deferred.promise; + } + } + } + }; + return service; + } +]); +'use strict'; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.security').controller('SecurityController', [ + '$scope', + '$rootScope', + 'securityService', + 'SECURITY_CONFIG', + function ($scope, $rootScope, securityService, SECURITY_CONFIG) { + $scope.securityService = securityService; + if (SECURITY_CONFIG.autoCheckLogin) { + securityService.validateLogin(SECURITY_CONFIG.forceLogin); } - $http({ - method: 'GET', - url: tmpUrl - }).success(function (data, status, headers, config) { - var tree = []; - TreeViewSelectorUtils.parseTreeList(tree, data); - defer.resolve(tree); - }).error(function (data, status, headers, config) { - defer.reject({}); - }); - return defer.promise; - } - }; - return service; - } + $scope.displayLoginStatus = function () { + return SECURITY_CONFIG.displayLoginStatus; + }; + } ]); -app.factory('TreeViewSelectorUtils', - function () { - var parseTreeNode = function (splitPath, tree, fullFilePath) { - if (splitPath[0] !== undefined) { - if (splitPath[0] !== '') { - var node = splitPath[0]; - var match = false; - var index = 0; - for (var i = 0; i < tree.length; i++) { - if (tree[i].name === node) { - match = true; - index = i; - break; - } - } - if (!match) { - var nodeObj = {}; - nodeObj.checked = isLeafChecked(node); - nodeObj.name = removeParams(node); - if (splitPath.length === 1) { - nodeObj.fullPath = removeParams(fullFilePath); - } - nodeObj.children = []; - tree.push(nodeObj); - index = tree.length - 1; - } - splitPath.shift(); - parseTreeNode(splitPath, tree[index].children, fullFilePath); - } else { - splitPath.shift(); - parseTreeNode(splitPath, tree, fullFilePath); - } - } - }; - - var removeParams = function (path) { - if (path) { - var split = path.split('?'); - return split[0]; - } - return path; +'use strict'; +angular.module('RedhatAccess.security').directive('rhaLoginstatus', function () { + return { + restrict: 'AE', + scope: false, + templateUrl: 'security/views/login_status.html' }; - - var isLeafChecked = function (path) { - if (path) { - var split = path.split('?'); - if (split[1]) { - var params = split[1].split('&'); - for (var i = 0; i < params.length; i++) { - if (params[i].indexOf('checked=true') !== -1) { - return true; +}); +'use strict'; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.security').factory('securityService', [ + '$rootScope', + '$modal', + 'AUTH_EVENTS', + '$q', + 'LOGIN_VIEW_CONFIG', + 'SECURITY_CONFIG', + 'strataService', + 'AlertService', + 'RHAUtils', + function($rootScope, $modal, AUTH_EVENTS, $q, LOGIN_VIEW_CONFIG, SECURITY_CONFIG, strataService, AlertService, RHAUtils) { + var service = { + loginStatus: { + isLoggedIn: false, + verifying: false, + userAllowedToManageCases: false, + authedUser: {} + }, + loginURL: SECURITY_CONFIG.loginURL, + logoutURL: SECURITY_CONFIG.logoutURL, + setLoginStatus: function(isLoggedIn, verifying, authedUser) { + service.loginStatus.isLoggedIn = isLoggedIn; + service.loginStatus.verifying = verifying; + service.loginStatus.authedUser = authedUser; + service.userAllowedToManageCases(); + }, + clearLoginStatus: function() { + service.loginStatus.isLoggedIn = false; + service.loginStatus.verifying = false; + service.loginStatus.userAllowedToManageCases = false; + service.loginStatus.authedUser = {}; + }, + setAccount: function(accountJSON) { + service.loginStatus.account = accountJSON; + }, + modalDefaults: { + backdrop: 'static', + keyboard: true, + modalFade: true, + templateUrl: 'security/views/login_form.html', + windowClass: 'rha-login-modal' + }, + modalOptions: { + closeButtonText: 'Close', + actionButtonText: 'OK', + headerText: 'Proceed?', + bodyText: 'Perform this action?', + backdrop: 'static' + }, + userAllowedToManageCases: function() { + var canManage = false; + if(service.loginStatus.authedUser.rights !== undefined){ + for(var i = 0; i < service.loginStatus.authedUser.rights.right.length; i++){ + if(service.loginStatus.authedUser.rights.right[i].name === 'portal_manage_cases' && service.loginStatus.authedUser.rights.right[i].has_access === true){ + canManage = true; + break; + } + } + } + service.loginStatus.userAllowedToManageCases = canManage; + }, + userAllowedToManageEmailNotifications: function(user) { + if (RHAUtils.isNotEmpty(service.loginStatus.authedUser.account) && RHAUtils.isNotEmpty(service.loginStatus.authedUser.account) && service.loginStatus.authedUser.org_admin) { + return true; + } else { + return false; + } + }, + userAllowedToManageGroups: function(user) { + if (RHAUtils.isNotEmpty(service.loginStatus.authedUser.account) && RHAUtils.isNotEmpty(service.loginStatus.authedUser.account) && (!service.loginStatus.authedUser.account.has_group_acls || service.loginStatus.authedUser.account.has_group_acls && service.loginStatus.authedUser.org_admin)) { + return true; + } else { + return false; + } + }, + getBasicAuthToken: function() { + var defer = $q.defer(); + var token = localStorage.getItem('rhAuthToken'); + if (token !== undefined && token !== '') { + defer.resolve(token); + return defer.promise; + } else { + service.login().then(function(authedUser) { + defer.resolve(localStorage.getItem('rhAuthToken')); + }, function(error) { + defer.resolve(error); + }); + return defer.promise; + } + }, + loggingIn: false, + initLoginStatus: function() { + service.loggingIn = true; + var defer = $q.defer(); + var wasLoggedIn = service.loginStatus.isLoggedIn; + service.loginStatus.verifying = true; + strataService.authentication.checkLogin().then(angular.bind(this, function(authedUser) { + service.setAccount(authedUser.account); + service.setLoginStatus(true, false, authedUser); + service.loggingIn = false; + //We don't want to resend the AUTH_EVENTS.loginSuccess if we are already logged in + if (wasLoggedIn === false) { + $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); + } + defer.resolve(authedUser.loggedInUser); + }), angular.bind(this, function(error) { + service.clearLoginStatus(); + AlertService.addStrataErrorMessage(error); + service.loggingIn = false; + defer.reject(error); + })); + return defer.promise; + }, + validateLogin: function(forceLogin) { + var defer = $q.defer(); + //var that = this; + if (!forceLogin) { + service.initLoginStatus().then(function(username) { + defer.resolve(username); + }, function(error) { + defer.reject(error); + }); + return defer.promise; + } else { + service.initLoginStatus().then(function(username) { + defer.resolve(username); + }, function(error) { + service.login().then(function(authedUser) { + defer.resolve(authedUser.loggedInUser); + }, function(error) { + defer.reject(error); + }); + }); + return defer.promise; + } + }, + login: function() { + return service.showLogin(service.modalDefaults, service.modalOptions); + }, + logout: function() { + strataService.authentication.logout(); + service.clearLoginStatus(); + $rootScope.$broadcast(AUTH_EVENTS.logoutSuccess); + }, + showLogin: function(customModalDefaults, customModalOptions) { + //var that = this; + //Create temp objects to work with since we're in a singleton service + var tempModalDefaults = {}; + var tempModalOptions = {}; + //Map angular-ui modal custom defaults to modal defaults defined in service + angular.extend(tempModalDefaults, service.modalDefaults, customModalDefaults); + //Map modal.html $scope custom properties to defaults defined in service + angular.extend(tempModalOptions, service.modalOptions, customModalOptions); + if (!tempModalDefaults.controller) { + tempModalDefaults.controller = [ + '$scope', + '$modalInstance', + function($scope, $modalInstance) { + $scope.user = { + user: null, + password: null + }; + $scope.status = { + authenticating: false + }; + $scope.useVerboseLoginView = LOGIN_VIEW_CONFIG.verbose; + $scope.modalOptions = tempModalOptions; + $scope.modalOptions.ok = function(result) { + //Hack below is needed to handle autofill issues + //@see https://github.com/angular/angular.js/issues/1460 + //BEGIN HACK + $scope.status.authenticating = true; + $scope.user.user = $('#rha-login-user-id').val(); + $scope.user.password = $('#rha-login-password').val(); + //END HACK + var resp = strataService.authentication.setCredentials($scope.user.user, $scope.user.password); + if (resp) { + service.initLoginStatus().then( + function(authedUser) { + $scope.user.password = ''; + $scope.authError = null; + try { + $modalInstance.close(authedUser); + } catch (err) {} + $scope.status.authenticating = false; + }, + function(error) { + if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') { + $scope.$apply(function() { + $scope.authError = 'Login Failed!'; + }); + } else { + $scope.authError = 'Login Failed!'; + } + $scope.status.authenticating = false; + } + ); + }else { + $scope.authError = 'Login Failed!'; + $scope.status.authenticating = false; + } + }; + $scope.modalOptions.close = function() { + $scope.status.authenticating = false; + $modalInstance.dismiss('User Canceled Login'); + }; + } + ]; + } + return $modal.open(tempModalDefaults).result; } - } - } - } - return false; - }; - - var hasSelectedLeaves = function (tree) { + }; + return service; + } +]); - for (var i = 0; i < tree.length; i++) { - if (tree[i] !== undefined) { - if (tree[i].children.length === 0) { - //we only check leaf nodes - if (tree[i].checked === true) { - return true; - } - } else { - if (hasSelectedLeaves(tree[i].children)) { - return true; +'use strict'; +/*jshint unused:vars, camelcase:false */ +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').controller('SearchController', [ + '$scope', + '$location', + 'SearchResultsService', + 'SEARCH_CONFIG', + 'securityService', + 'AlertService', + function ($scope, $location, SearchResultsService, SEARCH_CONFIG, securityService, AlertService) { + $scope.SearchResultsService = SearchResultsService; + $scope.results = SearchResultsService.results; + $scope.selectedSolution = SearchResultsService.currentSelection; + $scope.searchInProgress = SearchResultsService.searchInProgress; + $scope.currentSearchData = SearchResultsService.currentSearchData; + $scope.itemsPerPage = 3; + $scope.maxPagerSize = 5; + $scope.selectPage = function (pageNum) { + var start = $scope.itemsPerPage * (pageNum - 1); + var end = start + $scope.itemsPerPage; + end = end > SearchResultsService.results.length ? SearchResultsService.results.length : end; + $scope.results = SearchResultsService.results.slice(start, end); + }; + $scope.getOpenCaseRef = function () { + if (SEARCH_CONFIG.openCaseRef !== undefined) { + //TODO data may be complex type - need to normalize to string in future + return SEARCH_CONFIG.openCaseRef + '?data=' + SearchResultsService.currentSearchData.data; + } else { + return '#/case/new?data=' + SearchResultsService.currentSearchData.data; } - } - } - } - return false; - }; - - var getSelectedNames = function (tree, container) { - for (var i = 0; i < tree.length; i++) { - if (tree[i] !== undefined) { - if (tree[i].children.length === 0) { - if (tree[i].checked === true) { - container.push(tree[i].fullPath); + }; + $scope.solutionSelected = function (index) { + var response = $scope.results[index]; + SearchResultsService.setSelected(response, index); + }; + $scope.search = function (searchStr, limit) { + SearchResultsService.search(searchStr, limit); + }; + $scope.diagnose = function (data, limit) { + SearchResultsService.diagnose(data, limit); + }; + $scope.triggerAnalytics = function ($event) { + if (this.isopen && window.chrometwo_require !== undefined && $location.path() === '/case/new') { + chrometwo_require(['analytics/main'], function (analytics) { + analytics.trigger('OpenSupportCaseRecommendationClick', $event); + }); } - } else { - getSelectedNames(tree[i].children, container); - } - } - } - }; - - var service = { - parseTreeList: function (tree, data) { - var files = data.split('\n'); - for (var i = 0; i < files.length; i++) { - var file = files[i]; - var splitPath = file.split('/'); - parseTreeNode(splitPath, tree, file); - } - }, - hasSelections: function (tree) { - return hasSelectedLeaves(tree); - }, - getSelectedLeaves: function (tree) { - if (tree === undefined) { - return []; - } - var container = []; - getSelectedNames(tree, container); - return container; - } - }; - return service; - }); - -app.directive('rhaResizable', [ - '$window', - '$timeout', - function ($window) { - - var link = function (scope, element, attrs) { - - scope.onResizeFunction = function () { - var distanceToTop = element[0].getBoundingClientRect().top; - var height = $window.innerHeight - distanceToTop; - element.css('height', height); - }; - - angular.element($window).bind( - 'resize', - function () { - scope.onResizeFunction(); - //scope.$apply(); - } - ); - angular.element($window).bind( - 'click', - function () { - scope.onResizeFunction(); - //scope.$apply(); - } - ); - - if (attrs.rhaDomReady !== undefined) { - scope.$watch('rhaDomReady', function (newValue) { - if (newValue) { - scope.onResizeFunction(); - } + }; + $scope.$watch(function () { + return SearchResultsService.currentSelection; + }, function (newVal) { + $scope.selectedSolution = newVal; }); - } else { - scope.onResizeFunction(); - } - - - }; - - return { - restrict: 'A', - scope: { - rhaDomReady: '=' - }, - link: link - }; - } + } ]); -//var testURL = 'http://localhost:8080/LogCollector/'; -// angular module -angular.module('RedhatAccess.logViewer', - [ 'angularTreeview', 'ui.bootstrap', 'RedhatAccess.search', 'RedhatAccess.header']) -.config([ '$stateProvider', function($stateProvider) { - $stateProvider.state('logviewer', { - url : "/logviewer", - templateUrl : 'log_viewer/views/log_viewer.html' - }) -}]) -.constant('LOGVIEWER_EVENTS', { - allTabsClosed: 'allTabsClosed' - }) -.value('hideMachinesDropdown', {value:false}); - -function parseList(tree, data) { - var files = data.split("\n"); - for ( var i in files) { - var file = files[i]; - var splitPath = file.split("/"); - returnNode(splitPath, tree, file); - } -} - -function returnNode(splitPath, tree, fullFilePath) { - if (splitPath[0] != null) { - if (splitPath[0] != "") { - var node = splitPath[0]; - var match = false; - var index = 0; - for ( var i in tree) { - if (tree[i].roleName == node) { - match = true; - index = i; - break; - } - } - if (!match) { - var object = new Object(); - object.roleName = node; - object.roleId = node; - if (splitPath.length == 1) { - object.fullPath = fullFilePath; - } - object.children = new Array(); - tree.push(object); - index = tree.length - 1; - } - - splitPath.shift(); - returnNode(splitPath, tree[index].children, fullFilePath); - } else { - splitPath.shift(); - returnNode(splitPath, tree, fullFilePath); - } - } -} /*jshint camelcase: false */ 'use strict'; -/*global strata */ /*jshint unused:vars */ - /** * @ngdoc module * @name @@ -18286,1711 +1627,958 @@ function returnNode(splitPath, tree, fullFilePath) { * @description * */ -angular.module('RedhatAccess.search', [ - 'ui.router', - 'RedhatAccess.template', - 'RedhatAccess.security', - 'ui.bootstrap', - 'ngSanitize', - 'RedhatAccess.ui-utils', - 'RedhatAccess.common', - 'RedhatAccess.header' -]) - .constant('RESOURCE_TYPES', { - article: 'Article', - solution: 'Solution', - - }) - .constant('SEARCH_PARAMS', { - limit: 10 - - }) - .value('SEARCH_CONFIG', { - openCaseRef: null, - showOpenCaseBtn: true - }) - .config(['$stateProvider', - function ($stateProvider) { - $stateProvider.state('search', { - url: '/search', - controller: 'SearchController', - templateUrl: 'search/views/search.html' - }).state('search_accordion', { //TEMPORARY - url: '/search2', - controller: 'SearchController', - templateUrl: 'search/views/accordion_search.html' - - }); - } - ]) - .controller('SearchController', ['$scope', - '$location', 'SearchResultsService', 'SEARCH_CONFIG', 'securityService', 'AlertService', - function ($scope, $location, SearchResultsService, SEARCH_CONFIG, securityService, AlertService) { - $scope.results = SearchResultsService.results; - $scope.selectedSolution = SearchResultsService.currentSelection; - $scope.searchInProgress = SearchResultsService.searchInProgress; - $scope.currentSearchData = SearchResultsService.currentSearchData; - - - - $scope.getOpenCaseRef = function () { - if (SEARCH_CONFIG.openCaseRef !== null) { - //TODO data may be complex type - need to normalize to string in future - return SEARCH_CONFIG.openCaseRef + '?data=' + SearchResultsService.currentSearchData.data; - } else { - return '#/case/new?data=' + SearchResultsService.currentSearchData.data; - } - }; - - $scope.solutionSelected = function (index) { - var response = $scope.results[index]; - SearchResultsService.setSelected(response, index); - - }; - - $scope.search = function (searchStr, limit) { - - SearchResultsService.search(searchStr, limit); - - }; - - $scope.diagnose = function (data, limit) { - - SearchResultsService.diagnose(data, limit); - }; - - $scope.triggerAnalytics = function($event) { - if(this.isopen && window.portal && $location.path() === '/case/new'){ - chrometwo_require(['analytics/main'], function (analytics) { - analytics.trigger('OpenSupportCaseRecommendationClick', $event); - }); - } - }; - $scope.$watch(function () { - return SearchResultsService.currentSelection; - }, - function (newVal) { - $scope.selectedSolution = newVal; - } - ); - - } - ]) - .directive('rhaAccordionsearchresults', ['SEARCH_CONFIG', +angular.module('RedhatAccess.search').directive('rhaAccordionsearchresults', [ + 'SEARCH_CONFIG', function (SEARCH_CONFIG) { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/accordion_search_results.html', - link: function (scope, element, attr) { - scope.showOpenCaseBtn = function () { - if (SEARCH_CONFIG.showOpenCaseBtn && (attr && attr.opencase === 'true')) { - return true; - } else { - return false; + return { + restrict: 'AE', + scope: false, + templateUrl: 'search/views/accordion_search_results.html', + link: function (scope, element, attr) { + scope.showOpenCaseBtn = function () { + if (SEARCH_CONFIG.showOpenCaseBtn && (attr && attr.opencase === 'true')) { + return true; + } else { + return false; + } + }; } - }; - } - }; + }; } - ]) - .directive('rhaListsearchresults', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/list_search_results.html' - }; - }) - .directive('rhaSearchform', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/search_form.html' - }; - }) - .directive('rhaStandardsearch', function () { +]); + +/*jshint camelcase: false */ +'use strict'; +/*jshint unused:vars */ +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').directive('rhaListsearchresults', function () { return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/standard_search.html' - }; - }) - .directive('rhaResultdetaildisplay', ['RESOURCE_TYPES', - function (RESOURCE_TYPES) { - return { restrict: 'AE', - scope: { - result: '=' - }, - link: function (scope, element, attr) { - scope.isSolution = function () { - if (scope.result !== undefined && scope.result.resource_type !== undefined) { - if (scope.result.resource_type === RESOURCE_TYPES.solution) { - return true; - } else { - return false; - } - } - return false; - }; - scope.isArticle = function () { - if (scope.result !== undefined && scope.result.resource_type !== undefined) { - if (scope.result.resource_type === RESOURCE_TYPES.article) { - return true; - } else { - return false; - } - } - return false; - }; - scope.getSolutionResolution = function () { - var resolution_html = ''; - if (scope.result.resolution !== undefined) { - resolution_html = scope.result.resolution.html; - } - return resolution_html; - }; - - scope.getArticleHtml = function () { - if (scope.result === undefined) { - return ''; - } - if (scope.result.body !== undefined) { - if (scope.result.body.html !== undefined) { - //this is for newer version of strata - return scope.result.body.html; - } else { - //handle old markdown format - return scope.result.body; - } - } else { - return ''; - } - }; - - }, - templateUrl: 'search/views/resultDetail.html' - }; - } - ]) - .factory('SearchResultsService', ['$q', '$rootScope', 'AUTH_EVENTS', 'RESOURCE_TYPES', 'SEARCH_PARAMS', 'AlertService', 'securityService', - - function ($q, $rootScope, AUTH_EVENTS, RESOURCE_TYPES, SEARCH_PARAMS, AlertService, securityService) { - var searchArticlesOrSolutions = function (searchString, limit) { - //var that = this; - if ((limit === undefined) || (limit < 1)) { - limit = SEARCH_PARAMS.limit; - } - service.clear(); - AlertService.clearAlerts(); + scope: false, + templateUrl: 'search/views/list_search_results.html' + }; +}); - service.setCurrentSearchData(searchString, 'search'); - var deferreds = []; - strata.search( - searchString, - function (entries) { - //retrieve details for each solution - if (entries !== undefined) { - if (entries.length === 0) { - AlertService.addSuccessMessage('No recommendations found.'); - } - entries.forEach(function (entry) { - var deferred = $q.defer(); - deferreds.push(deferred.promise); - strata.utils.getURI( - entry.uri, - entry.resource_type, - function (type, info) { - if (info !== undefined) { - info.resource_type = type; +/*jshint camelcase: false */ +'use strict'; +/*jshint unused:vars */ +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').directive('rhaResultdetaildisplay', [ + 'RESOURCE_TYPES', + function (RESOURCE_TYPES) { + return { + restrict: 'AE', + scope: { result: '=' }, + link: function (scope, element, attr) { + scope.isSolution = function () { + if (scope.result !== undefined && scope.result.resource_type !== undefined) { + if (scope.result.resource_type === RESOURCE_TYPES.solution) { + return true; + } else { + return false; + } + } + return false; + }; + scope.isArticle = function () { + if (scope.result !== undefined && scope.result.resource_type !== undefined) { + if (scope.result.resource_type === RESOURCE_TYPES.article) { + return true; + } else { + return false; + } + } + return false; + }; + scope.getSolutionResolution = function () { + var resolutionHtml = ''; + if (scope.result.resolution !== undefined) { + resolutionHtml = scope.result.resolution.html; + } + return resolutionHtml; + }; + scope.getArticleHtml = function () { + if (scope.result === undefined) { + return ''; + } + if (scope.result.body !== undefined) { + if (scope.result.body.html !== undefined) { + //this is for newer version of strata + return scope.result.body.html; + } else { + //handle old markdown format + return scope.result.body; + } + } else { + return ''; } - deferred.resolve(info); - }, - function (error) { - deferred.resolve(); - }); - }); - } else { - AlertService.addSuccessMessage('No recommendations found.'); - } - $q.all(deferreds).then( - function (results) { - results.forEach(function (result) { - if (result !== undefined) { - service.add(result); - } - }); - service.searchInProgress.value = false; - }, - function (error) { - service.searchInProgress.value = false; - } - ); - }, - function (error) { - $rootScope.$apply(function () { - service.searchInProgress.value = false; - AlertService.addDangerMessage(error); - }); - }, - limit, - false - ); - }; - var searchProblems = function (data, limit) { - if ((limit === undefined) || (limit < 1)) { - limit = SEARCH_PARAMS.limit; - } - service.clear(); - AlertService.clearAlerts(); - var deferreds = []; - service.searchInProgress.value = true; - service.setCurrentSearchData(data, 'diagnose'); - strata.problems( - data, - function (solutions) { - //retrieve details for each solution - if (solutions !== undefined) { - if (solutions.length === 0) { - AlertService.addSuccessMessage('No solutions found.'); - } - - solutions.forEach(function (solution) { - var deferred = $q.defer(); - deferreds.push(deferred.promise); - strata.solutions.get( - solution.uri, - function (solution) { - deferred.resolve(solution); - }, - function (error) { - deferred.resolve(); - }); - }); - } else { - AlertService.addSuccessMessage('No solutions found.'); - } - $q.all(deferreds).then( - function (solutions) { - solutions.forEach(function (solution) { - if (solution !== undefined) { - solution.resource_type = RESOURCE_TYPES.solution; - service.add(solution); - } - }); - service.searchInProgress.value = false; - }, - function (error) { - service.searchInProgress.value = false; - } - ); - }, - - function (error) { - $rootScope.$apply(function () { - service.searchInProgress.value = false; - AlertService.addDangerMessage(error); - }); - }, - limit - ); - }; - var service = { - results: [], - currentSelection: { - data: {}, - index: -1 - }, - searchInProgress: { - value: false - }, - currentSearchData: { - data: '', - method: '' - }, - - add: function (result) { - this.results.push(result); - }, - clear: function () { - this.results.length = 0; - this.setSelected({}, -1); - this.setCurrentSearchData('', ''); - - }, - setSelected: function (selection, index) { - this.currentSelection.data = selection; - this.currentSelection.index = index; - }, - setCurrentSearchData: function (data, method) { - this.currentSearchData.data = data; - this.currentSearchData.method = method; - }, - search: function (searchString, limit) { - this.searchInProgress.value = true; - var that = this; - securityService.validateLogin(true).then( - function (authedUser) { - searchArticlesOrSolutions(searchString, limit); - }, - function (error) { - that.searchInProgress.value = false; - AlertService.addDangerMessage('You must be logged in to use this functionality.'); - }); + }; - }, - diagnose: function (data, limit) { - this.searchInProgress.value = true; - var that = this; - securityService.validateLogin(true).then( - function (authedUser) { - searchProblems(data, limit); + scope.resultClickCapture = function(event, result) { + var target = event.target || event.srcElement; // srcElement === IE + var isAnchor = target && (target.nodeName && target.nodeName.toLowerCase() === 'a'); + if (!isAnchor) { + // Don't care - bail. + return; + } + var hashRegex = /^#.*/, + absoluteRegex = /^http(s?):\/\//; + // target.href adds base uri - have to use getAttr('href') + var href = target.getAttribute('href'); + if (hashRegex.test(href)) { + // Found a hash href (starts with #) + event.preventDefault(); + var targetElem = angular.element(target.hash), + scrollElem = angular.element('#rha-solution-display'); + if (targetElem.length && scrollElem.length) { + // We have a target element, and a scroll host + // set scroll of top of target element + scrollElem.scrollTop(targetElem.position().top); + } else if (targetElem.length) { + // No scroll host, just use native method that will scroll + // the window + targetElem[0].scrollIntoView(); + } + } else if (!absoluteRegex.test(href)) { + // Found a relative href (does NOT start with http or https) + event.preventDefault(); + if(href.indexOf('/') === 0) { + // Absolute url relative to result view_uri + // This is a very hacky way to get the base url from a url + var parser = document.createElement('a'); + parser.href = result.view_uri; + target.href = parser.origin + href; + } else { + // Relative url relative to result view_uri + target.href = result.view_uri + href; + } + // Re-click target with newly constructed href + target.click(); + } + }; }, - function (error) { - that.searchInProgress.value = false; - AlertService.addDangerMessage('You must be logged in to use this functionality.'); - }); - - } - }; - - $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { - service.clear.apply(service); - }); - - return service; + templateUrl: 'search/views/resultDetail.html' + }; } - ]); +]); + +/*jshint camelcase: false */ 'use strict'; -/*global strata,$*/ /*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.security', ['ui.bootstrap', 'RedhatAccess.template', 'ui.router', 'RedhatAccess.common', 'RedhatAccess.header']) - .constant('AUTH_EVENTS', { - loginSuccess: 'auth-login-success', - loginFailed: 'auth-login-failed', - logoutSuccess: 'auth-logout-success', - sessionTimeout: 'auth-session-timeout', - notAuthenticated: 'auth-not-authenticated', - notAuthorized: 'auth-not-authorized', - sessionIdChanged: 'sid-changed' - }) - .directive('rhaLoginstatus', function () { +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').directive('rhaSearchform', function () { return { - restrict: 'AE', - scope: false, - templateUrl: 'security/login_status.html' + restrict: 'AE', + scope: false, + templateUrl: 'search/views/search_form.html' }; - }) - .controller('SecurityController', ['$scope', '$rootScope', 'securityService', 'SECURITY_CONFIG', - function ($scope, $rootScope, securityService, SECURITY_CONFIG) { - $scope.securityService = securityService; - if (SECURITY_CONFIG.autoCheckLogin) { - securityService.validateLogin(SECURITY_CONFIG.forceLogin); - } - $scope.displayLoginStatus = function () { - return SECURITY_CONFIG.displayLoginStatus; +}); - }; - } - ]) - .value('LOGIN_VIEW_CONFIG', { - verbose: true, - }) - .value('SECURITY_CONFIG', { - displayLoginStatus: true, - autoCheckLogin: true, - loginURL: '', - logoutURL: '', - forceLogin: false, +/*jshint camelcase: false */ +'use strict'; +/*jshint unused:vars */ +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').directive('rhaStandardsearch', function () { + return { + restrict: 'AE', + scope: false, + templateUrl: 'search/views/standard_search.html' + }; +}); - }) - .service('securityService', [ +/*jshint camelcase: false */ +'use strict'; +/*jshint unused:vars */ +/** + * @ngdoc module + * @name + * + * @description + * + */ +angular.module('RedhatAccess.search').factory('SearchResultsService', [ + '$q', '$rootScope', - '$modal', 'AUTH_EVENTS', - '$q', - 'LOGIN_VIEW_CONFIG', - 'SECURITY_CONFIG', - 'strataService', + 'RESOURCE_TYPES', + 'SEARCH_PARAMS', 'AlertService', - 'RHAUtils', - function ( - $rootScope, - $modal, - AUTH_EVENTS, - $q, - LOGIN_VIEW_CONFIG, - SECURITY_CONFIG, - strataService, - AlertService, - RHAUtils) { - - this.loginStatus = { - isLoggedIn: false, - loggedInUser: '', - verifying: false, - isInternal: false, - orgAdmin: false, - hasChat: false, - sessionId: '', - canAddAttachments: false, - ssoName: '' - }; - - this.loginURL = SECURITY_CONFIG.loginURL; - this.logoutURL = SECURITY_CONFIG.logoutURL; - - this.setLoginStatus = function ( - isLoggedIn, - userName, - verifying, - isInternal, - orgAdmin, - hasChat, - sessionId, - canAddAttachments, - ssoName) { - this.loginStatus.isLoggedIn = isLoggedIn; - this.loginStatus.loggedInUser = userName; - this.loginStatus.verifying = verifying; - this.loginStatus.isInternal = isInternal; - this.loginStatus.orgAdmin = orgAdmin; - this.loginStatus.hasChat = hasChat; - this.loginStatus.sessionId = sessionId; - this.loginStatus.canAddAttachments = canAddAttachments; - this.loginStatus.ssoName = ssoName; - }; - - this.clearLoginStatus = function () { - this.loginStatus.isLoggedIn = false; - this.loginStatus.loggedInUser = ''; - this.loginStatus.verifying = false; - this.loginStatus.isInternal = false; - this.loginStatus.orgAdmin = false; - this.loginStatus.hasChat = false; - this.loginStatus.sessionId = ''; - this.loginStatus.canAddAttachments = false; - this.loginStatus.account = {}; - this.loginStatus.ssoName = ''; - - }; - - this.setAccount = function (accountJSON) { - this.loginStatus.account = accountJSON; - }; - - var modalDefaults = { - backdrop: 'static', - keyboard: true, - modalFade: true, - templateUrl: 'security/login_form.html', - windowClass: 'rha-login-modal' - }; - - var modalOptions = { - closeButtonText: 'Close', - actionButtonText: 'OK', - headerText: 'Proceed?', - bodyText: 'Perform this action?', - backdrop: 'static' - - }; - - - - this.userAllowedToManageEmailNotifications = function (user) { - if ((RHAUtils.isNotEmpty(this.loginStatus.account) && RHAUtils.isNotEmpty(this.loginStatus.account)) && - ((this.loginStatus.orgAdmin))) { - return true; - } else { - return false; - } - }; - - this.userAllowedToManageGroups = function (user) { - if ((RHAUtils.isNotEmpty(this.loginStatus.account) && RHAUtils.isNotEmpty(this.loginStatus.account)) && - ((!this.loginStatus.account.has_group_acls || this.loginStatus.account.has_group_acls && this.loginStatus.orgAdmin))) { - return true; - } else { - return false; - } - }; - - this.getBasicAuthToken = function () { - var defer = $q.defer(); - var token = localStorage.getItem('rhAuthToken'); - if (token !== undefined && token !== '') { - defer.resolve(token); - return defer.promise; - } else { - this.login().then( - function (authedUser) { - defer.resolve(localStorage.getItem('rhAuthToken')); - }, - function (error) { - defer.resolve(error); - }); - return defer.promise; - } - }; - - this.loggingIn = false; - - this.initLoginStatus = function () { - this.loggingIn = true; - - var defer = $q.defer(); - var that = this; - var wasLoggedIn = this.loginStatus.isLoggedIn; - var currentSid = this.loginStatus.sessionId; - this.loginStatus.verifying = true; - strata.checkLogin( - angular.bind(this, function (result, authedUser) { - if (result) { - var sidChanged = (currentSid !== authedUser.session_id); - strataService.accounts.list().then( - angular.bind(this, function (accountNumber) { - strataService.accounts.get(accountNumber).then( - angular.bind(this, function (account) { - that.setAccount(account); - that.setLoginStatus( - true, - authedUser.name, - false, - authedUser.is_internal, - authedUser.org_admin, - authedUser.has_chat, - authedUser.session_id, - authedUser.can_add_attachments, - authedUser.login); - //console.log(that.loginStatus); - //console.log(authedUser); - this.loggingIn = false; - //We don't want to resend the AUTH_EVENTS.loginSuccess if we are already logged in - if (wasLoggedIn === false) { - $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); - } - if (sidChanged) { - $rootScope.$broadcast(AUTH_EVENTS.sessionIdChanged); - } - defer.resolve(authedUser.name); - }) - ); - }), - angular.bind(this, function (error) { - AlertService.addStrataErrorMessage(error); - this.loggingIn = false; - defer.resolve(authedUser.name); - }) - ); - } else { - this.loggingIn = false; - that.clearLoginStatus(); - defer.reject(''); - } - }) - ); - return defer.promise; - }; - - this.validateLogin = function (forceLogin) { - var defer = $q.defer(); - var that = this; - if (!forceLogin) { - this.initLoginStatus().then( - function (username) { - defer.resolve(username); - }, - function (error) { - defer.reject(error); + 'securityService', + 'strataService', + function ($q, $rootScope, AUTH_EVENTS, RESOURCE_TYPES, SEARCH_PARAMS, AlertService, securityService, strataService) { + var searchArticlesOrSolutions = function (searchString, limit) { + //var that = this; + if (limit === undefined || limit < 1) { + limit = SEARCH_PARAMS.limit; } - ); - return defer.promise; - } else { - this.initLoginStatus().then( - function (username) { - defer.resolve(username); - }, - function (error) { - that.login().then( - function (authedUser) { - defer.resolve(authedUser.name); - }, - function (error) { - defer.reject(error); + service.clear(); + AlertService.clearAlerts(); + service.setCurrentSearchData(searchString, 'search'); + strataService.search(searchString, limit).then( + function (results) { + if (results.length === 0) { + AlertService.addSuccessMessage('No solutions found.'); + } + results.forEach(function (result) { + if (result !== undefined) { + service.add(result); + } + }); + service.searchInProgress.value = false; + }, function (error) { + service.searchInProgress.value = false; }); + }; + var searchProblems = function (data, limit) { + if (limit === undefined || limit < 1) { + limit = SEARCH_PARAMS.limit; } - ); - return defer.promise; - } - }; - - this.login = function () { - var that = this; - var result = this.showLogin(modalDefaults, modalOptions); - result.then( - function (authedUser) { - that.setLoginStatus(true, authedUser.name, false, authedUser.is_internal); - }, - function (error) { - that.clearLoginStatus(); - }); - return result; // pass on the promise - }; - - this.logout = function () { - strata.clearCredentials(); - this.clearLoginStatus(); - $rootScope.$broadcast(AUTH_EVENTS.logoutSuccess); - }; - - this.getLoggedInUserName = function () { - return strata.getAuthInfo().name; - }; - - this.showLogin = function (customModalDefaults, customModalOptions) { - var that = this; - //Create temp objects to work with since we're in a singleton service - var tempModalDefaults = {}; - var tempModalOptions = {}; - //Map angular-ui modal custom defaults to modal defaults defined in service - angular.extend(tempModalDefaults, modalDefaults, customModalDefaults); - //Map modal.html $scope custom properties to defaults defined in service - angular.extend(tempModalOptions, modalOptions, customModalOptions); - if (!tempModalDefaults.controller) { - tempModalDefaults.controller = ['$scope', '$modalInstance', - function ($scope, $modalInstance) { - $scope.user = { - user: null, - password: null - }; - $scope.useVerboseLoginView = LOGIN_VIEW_CONFIG.verbose; - $scope.modalOptions = tempModalOptions; - $scope.modalOptions.ok = function (result) { - //Hack below is needed to handle autofill issues - //@see https://github.com/angular/angular.js/issues/1460 - //BEGIN HACK - $scope.user.user = $('#rha-login-user-id').val(); - $scope.user.password = $('#rha-login-password').val(); - //END HACK - strata.setCredentials($scope.user.user, $scope.user.password, - function (passed, authedUser) { - if (passed) { - $scope.user.password = ''; - $scope.authError = null; - try { - $modalInstance.close(authedUser); - } catch (err) {} - that.setLoginStatus( - true, - authedUser.name, - false, - authedUser.is_internal, - authedUser.org_admin, - authedUser.has_chat, - authedUser.session_id, - authedUser.can_add_attachments, - authedUser.login); - $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); - } else { - // alert("Login failed!"); - $rootScope.$broadcast(AUTH_EVENTS.loginFailed); - if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { - $scope.$apply(function () { - $scope.authError = 'Login Failed!'; + service.clear(); + AlertService.clearAlerts(); + var deferreds = []; + service.searchInProgress.value = true; + service.setCurrentSearchData(data, 'diagnose'); + strataService.problems(data, limit).then( + function (solutions) { + //retrieve details for each solution + if (solutions !== undefined) { + if (solutions.length === 0) { + AlertService.addSuccessMessage('No solutions found.'); + } + solutions.forEach(function (solution) { + var deferred = $q.defer(); + deferreds.push(deferred.promise); + strataService.solutions.get(solution.uri).then( + function (solution) { + deferred.resolve(solution); + }, + function (error) { + deferred.resolve(); + }); }); - } else { - $scope.authError = 'Login Failed!'; - } + } else { + AlertService.addSuccessMessage('No solutions found.'); } - }); - - }; - $scope.modalOptions.close = function () { - $modalInstance.dismiss(); - }; - } - ]; - } - - return $modal.open(tempModalDefaults).result; - }; - - } - ]); -'use strict'; -/*global strata*/ -/*jshint camelcase: false */ -/*jshint unused:vars */ - -angular.module('RedhatAccess.common') - .factory('strataService', ['$q', 'translate', 'RHAUtils', - function ($q, translate, RHAUtils) { - - var errorHandler = function (message, xhr, response, status) { - - var translatedMsg = message; - - switch (status) { - case 'Unauthorized': - translatedMsg = translate('Unauthorized.'); - break; - // case n: - // code block - // break; - } - this.reject({ - message: translatedMsg, - xhr: xhr, - response: response, - status: status - }); - }; - - return { - entitlements: { - //entitlements.get - get: function(showAll, ssoUserName) { - var deferred = $q.defer(); - - strata.entitlements.get( - showAll, - function (entitlements) { - deferred.resolve(entitlements); - }, - angular.bind(deferred, errorHandler), - ssoUserName - ); - - return deferred.promise; - } - }, - problems: function (data, max) { - var deferred = $q.defer(); - - strata.problems( - data, - function (solutions) { - deferred.resolve(solutions); - }, - angular.bind(deferred, errorHandler), - max - ); - - return deferred.promise; - }, - solutions: { - //solutions.get - get: function (uri) { - var deferred = $q.defer(); - - strata.solutions.get( - uri, - function (solution) { - deferred.resolve(solution); - }, - function () { - //workaround for 502 from strata - //If the deferred is rejected then the parent $q.all() - //based deferred will fail. Since we don't need every - //recommendation just send back undefined - //and the caller can ignore the missing solution details. - deferred.resolve(); - } - ); - - return deferred.promise; - } - }, - products: { - //products.list - list: function () { - var deferred = $q.defer(); - - strata.products.list( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //products.versions - versions: function (productCode) { - var deferred = $q.defer(); - - strata.products.versions( - productCode, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - } - }, - groups: { - //groups.list - list: function (ssoUserName) { - var deferred = $q.defer(); - - strata.groups.list( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler), - ssoUserName - ); - - return deferred.promise; - }, - //groups.remove - remove: function(groupNum) { - var deferred = $q.defer(); - - strata.groups.remove( - groupNum, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //groups.create - create: function(groupName) { - var deferred = $q.defer(); - - strata.groups.create( - groupName, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - } - }, - accounts: { - //accounts.get - get: function(accountNumber) { - var deferred = $q.defer(); - - strata.accounts.get( - accountNumber, - function(response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //accounts.users - users: function(accountNumber, group) { - var deferred = $q.defer(); - - strata.accounts.users( - accountNumber, - function(response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler), - group - ); - - return deferred.promise; - }, - //accounts.list - list: function() { - var deferred = $q.defer(); - - strata.accounts.list( - function(response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - } - }, - cases: { - //cases.csv - csv: function() { - var deferred = $q.defer(); - - strata.cases.csv( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - attachments: { - //cases.attachments.list - list: function (id) { - var deferred = $q.defer(); - - strata.cases.attachments.list( - id, - function (response) { - deferred.resolve(response); + $q.all(deferreds).then(function (solutions) { + solutions.forEach(function (solution) { + if (solution !== undefined) { + service.add(solution); + } + }); + service.searchInProgress.value = false; + }, function (error) { + service.searchInProgress.value = false; + }); }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; + function (error) { + service.searchInProgress.value = false; + AlertService.addDangerMessage(error); + }); + }; + var service = { + results: [], + currentSelection: { + data: {}, + index: -1 }, - //cases.attachments.post - post: function (attachment, caseNumber) { - var deferred = $q.defer(); - - strata.cases.attachments.post( - attachment, - caseNumber, - function (response, code, xhr) { - deferred.resolve(xhr.getResponseHeader('Location')); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; + searchInProgress: { + value: false }, - //cases.attachments.remove - remove: function (id, caseNumber) { - - var deferred = $q.defer(); - - strata.cases.attachments.remove( - id, - caseNumber, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; + currentSearchData: { + data: '', + method: '' + }, + add: function (result) { + this.results.push(result); + }, + clear: function () { + this.results.length = 0; + this.setSelected({}, -1); + this.setCurrentSearchData('', ''); + }, + setSelected: function (selection, index) { + this.currentSelection.data = selection; + this.currentSelection.index = index; + }, + setCurrentSearchData: function (data, method) { + this.currentSearchData.data = data; + this.currentSearchData.method = method; + }, + search: function (searchString, limit) { + this.searchInProgress.value = true; + var that = this; + securityService.validateLogin(true).then(function (authedUser) { + searchArticlesOrSolutions(searchString, limit); + }, function (error) { + that.searchInProgress.value = false; + AlertService.addDangerMessage('You must be logged in to use this functionality.'); + }); + }, + diagnose: function (data, limit) { + this.searchInProgress.value = true; + var that = this; + securityService.validateLogin(true).then(function (authedUser) { + searchProblems(data, limit); + }, function (error) { + that.searchInProgress.value = false; + AlertService.addDangerMessage('You must be logged in to use this functionality.'); + }); + } + }; + $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + service.clear.apply(service); + }); + return service; + } +]); +'use strict'; +/*global $ */ +/*jshint expr: true, camelcase: false, newcap: false */ +angular.module('RedhatAccess.cases').controller('EditGroup', [ + '$scope', + '$rootScope', + 'strataService', + 'AlertService', + '$filter', + 'ngTableParams', + 'GroupUserService', + 'SearchBoxService', + '$location', + 'securityService', + 'RHAUtils', + 'AUTH_EVENTS', + function ($scope, $rootScope, strataService, AlertService, $filter, ngTableParams, GroupUserService, SearchBoxService, $location, securityService, RHAUtils, AUTH_EVENTS) { + $scope.GroupUserService = GroupUserService; + $scope.listEmpty = false; + $scope.selectedGroup = {}; + $scope.usersOnScreen = []; + $scope.usersOnAccount = []; + $scope.accountNumber = null; + $scope.isUsersPrestine = true; + $scope.isGroupPrestine = true; + + var reloadTable = false; + var tableBuilt = false; + var buildTable = function () { + $scope.tableParams = new ngTableParams({ + page: 1, + count: 10, + sorting: { sso_username: 'asc' } + }, { + total: $scope.usersOnAccount.length, + getData: function ($defer, params) { + var orderedData = $filter('filter')($scope.usersOnAccount, SearchBoxService.searchTerm); + orderedData = params.sorting() ? $filter('orderBy')(orderedData, params.orderBy()) : orderedData; + orderedData.length < 1 ? $scope.listEmpty = true : $scope.listEmpty = false; + var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); + $scope.tableParams.total(orderedData.length); + $scope.usersOnScreen = pageData; + $defer.resolve(pageData); + } + }); + $scope.tableParams.settings().$scope = $scope; + GroupUserService.reloadTable = function () { + $scope.tableParams.reload(); + }; + tableBuilt = true; + }; + $scope.init = function() { + if(securityService.userAllowedToManageGroups()){ + var loc = $location.url().split('/'); + $scope.accountNumber = securityService.loginStatus.authedUser.account_number; + strataService.groups.get(loc[3]).then(function (group) { + $scope.selectedGroup = group; + strataService.accounts.users($scope.accountNumber, $scope.selectedGroup.number).then(function (users) { + $scope.usersOnAccount = users; + buildTable(); + $scope.usersLoading = false; + if(reloadTable){ + //GroupUserService.reloadTable(); + reloadTable = false; + } + }, function (error) { + $scope.usersLoading = false; + AlertService.addStrataErrorMessage(error); + }); + }, function (error) { + $scope.usersLoading = false; + AlertService.addStrataErrorMessage(error); + }); + }else{ + $scope.usersLoading = false; + AlertService.addStrataErrorMessage('User does not have proper credentials to manage case groups.'); } - }, - comments: { - //cases.comments.get - get: function (id) { - var deferred = $q.defer(); - - strata.cases.comments.get( - id, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //cases.comments.post - post: function (case_number, text, isDraft) { - var deferred = $q.defer(); - - strata.cases.comments.post( - case_number, - { - 'text': text, - 'draft': isDraft === true ? 'true' : 'false' - }, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //cases.comments.put - put: function(case_number, text, isDraft, comment_id) { - var deferred = $q.defer(); - - strata.cases.comments.update( - case_number, - { - 'text': text, - 'draft': isDraft === true ? 'true' : 'false', - 'caseNumber': case_number, - 'id': comment_id - }, - comment_id, - function(response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; + }; + $scope.saveGroup = function () { + if(!$scope.isGroupPrestine){ + strataService.groups.update($scope.selectedGroup.name, $scope.selectedGroup.number).then(function (response) { + AlertService.addSuccessMessage('Case group successfully updated.'); + $scope.isGroupPrestine = true; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - }, - notified_users: { - add: function(caseNumber, ssoUserName) { - var deferred = $q.defer(); - - strata.cases.notified_users.add( - caseNumber, - ssoUserName, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - remove: function(caseNumber, ssoUserName) { - var deferred = $q.defer(); - - strata.cases.notified_users.remove( - caseNumber, - ssoUserName, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; + if(!$scope.isUsersPrestine){ + strataService.groupUsers.update($scope.usersOnAccount, $scope.accountNumber, $scope.selectedGroup.number).then(function(response) { + $scope.isUsersPrestine = true; + AlertService.addSuccessMessage('Case users successfully updated.'); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - }, - //cases.get - get: function (id) { - var deferred = $q.defer(); - - strata.cases.get( - id, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); + }; - return deferred.promise; - }, - //cases.filter - filter: function (params) { - var deferred = $q.defer(); - if (RHAUtils.isEmpty(params)) { - params = {}; + $scope.onMasterReadCheckboxClicked = function (masterReadSelected) { + for(var i = 0; i < $scope.usersOnAccount.length; i++){ + $scope.usersOnAccount[i].access = masterReadSelected; } - if (RHAUtils.isEmpty(params.count)) { - params.count = 50; + $scope.isUsersPrestine = false; + }; + + $scope.onMasterWriteCheckboxClicked = function (masterWriteSelected) { + for(var i = 0; i < $scope.usersOnAccount.length; i++){ + $scope.usersOnAccount[i].write = masterWriteSelected; } + $scope.isUsersPrestine = false; + }; - strata.cases.filter( - params, - function (allCases) { - deferred.resolve(allCases); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //cases.post - post: function (caseJSON) { - var deferred = $q.defer(); - - strata.cases.post( - caseJSON, - function (caseNumber) { - deferred.resolve(caseNumber); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - }, - //cases.put - put: function (case_number, caseJSON) { - var deferred = $q.defer(); - - strata.cases.put( - case_number, - caseJSON, - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); - - return deferred.promise; - } - }, - values: { - cases: { - //values.cases.severity - severity: function () { - var deferred = $q.defer(); - - strata.values.cases.severity( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); + $scope.writeAccessToggle = function(user){ + if(user.write && !user.access){ + user.access = true; + } + $scope.isUsersPrestine = false; + }; - return deferred.promise; - }, - //values.cases.status - status: function () { - var deferred = $q.defer(); + $scope.cancel = function(){ + $location.path('/case/group'); + }; - strata.values.cases.status( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); + $scope.toggleUsersPrestine = function(){ + $scope.isUsersPrestine = false; + }; - return deferred.promise; - }, - //values.cases.types - types: function () { - var deferred = $q.defer(); + $scope.toggleGroupPrestine = function(){ + $scope.isGroupPrestine = false; + }; - strata.values.cases.types( - function (response) { - deferred.resolve(response); - }, - angular.bind(deferred, errorHandler) - ); + $scope.usersLoading = true; + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); - return deferred.promise; - } - } + } else { + $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + }); } - }; - } - ]); - -'use strict'; - -angular.module('RedhatAccess.cases') -.controller('AccountSelect', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - 'RHAUtils', - function ($scope, strataService, AlertService, CaseService, RHAUtils) { - - $scope.CaseService = CaseService; - $scope.selectUserAccount = function() { - $scope.loadingAccountNumber = true; - strataService.accounts.list().then( - function(response) { - $scope.loadingAccountNumber = false; - CaseService.account.number = response; - $scope.populateAccountSpecificFields(); - }, - function(error) { - $scope.loadingAccountNumber = false; - AlertService.addStrataErrorMessage(error); + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + $scope.selectedGroup = {}; + $scope.usersOnScreen = []; + $scope.usersOnAccount = []; + $scope.accountNumber = null; + reloadTable = true; }); - }; - $scope.alertInstance = null; - $scope.populateAccountSpecificFields = function() { - if (RHAUtils.isNotEmpty(CaseService.account.number)) { - strataService.accounts.get(CaseService.account.number).then( - function() { - if (RHAUtils.isNotEmpty($scope.alertInstance)) { - AlertService.removeAlert($scope.alertInstance); - } - CaseService.populateUsers(); - }, - function() { - if (RHAUtils.isNotEmpty($scope.alertInstance)) { - AlertService.removeAlert($scope.alertInstance); - } - $scope.alertInstance = AlertService.addWarningMessage('Account not found.'); + $scope.$on('$destroy', function () { + $scope.authEventLogoutSuccess(); + }); + } +]); +'use strict'; +angular.module('RedhatAccess.cases').controller('AccountSelect', [ + '$scope', + 'strataService', + 'AlertService', + 'CaseService', + 'RHAUtils', + function ($scope, strataService, AlertService, CaseService, RHAUtils) { + $scope.CaseService = CaseService; + $scope.selectUserAccount = function () { + $scope.loadingAccountNumber = true; + strataService.accounts.list().then(function (response) { + $scope.loadingAccountNumber = false; + CaseService.account.number = response; + $scope.populateAccountSpecificFields(); + }, function (error) { + $scope.loadingAccountNumber = false; + AlertService.addStrataErrorMessage(error); + }); + }; + $scope.alertInstance = null; + $scope.populateAccountSpecificFields = function () { + if (RHAUtils.isNotEmpty(CaseService.account.number)) { + strataService.accounts.get(CaseService.account.number).then(function () { + if (RHAUtils.isNotEmpty($scope.alertInstance)) { + AlertService.removeAlert($scope.alertInstance); + } + CaseService.populateUsers(); + }, function () { + if (RHAUtils.isNotEmpty($scope.alertInstance)) { + AlertService.removeAlert($scope.alertInstance); + } + $scope.alertInstance = AlertService.addWarningMessage('Account not found.'); + CaseService.users = []; + }); } - ); - } - }; - } + }; + } ]); - 'use strict'; -/*global $ */ - - -angular.module('RedhatAccess.cases') -.controller('AddCommentSection', [ - '$scope', - 'strataService', - 'CaseService', - 'AlertService', - '$timeout', - 'RHAUtils', - function ($scope, strataService, CaseService, AlertService, $timeout, RHAUtils) { - $scope.CaseService = CaseService; - - $scope.addingComment = false; - $scope.addComment = function() { - $scope.addingComment = true; - - var onSuccess = function(response) { - if(RHAUtils.isNotEmpty($scope.saveDraftPromise)) { - $timeout.cancel($scope.saveDraftPromise); - } - - CaseService.commentText = ''; - - //TODO: find better way than hard code - if(CaseService.kase.status.name === "Closed"){ - CaseService.kase.status.name = "Waiting on Red Hat"; - } - - CaseService.populateComments(CaseService.kase.case_number).then( - function(comments) { - $scope.addingComment = false; - $scope.savingDraft = false; - $scope.draftSaved = false; - CaseService.draftComment = undefined; - CaseService.refreshComments(); - } - ); - }; - - var onError = function(error) { - AlertService.addStrataErrorMessage(error); +/*global $, draftComment*/ +/*jshint camelcase: false, expr: true*/ +angular.module('RedhatAccess.cases').controller('AddCommentSection', [ + '$scope', + 'strataService', + 'CaseService', + 'AlertService', + 'securityService', + '$timeout', + 'RHAUtils', + function ($scope, strataService, CaseService, AlertService, securityService, $timeout, RHAUtils) { + $scope.CaseService = CaseService; + $scope.securityService = securityService; $scope.addingComment = false; - }; - - if(RHAUtils.isNotEmpty(CaseService.draftComment)) { - strataService.cases.comments.put( - CaseService.kase.case_number, - CaseService.commentText, - false, - CaseService.draftComment.id) - .then(onSuccess, onError); - } else { - strataService.cases.comments.post( - CaseService.kase.case_number, - CaseService.commentText) - .then(onSuccess, onError); - } - }; - - $scope.saveDraftPromise; - $scope.onNewCommentKeypress = function() { - if(RHAUtils.isNotEmpty(CaseService.commentText) && !$scope.addingComment) { - $timeout.cancel($scope.saveDraftPromise); - $scope.saveDraftPromise = $timeout(function() { - if (!$scope.addingComment) { - $scope.saveDraft(); - } - }, - 5000); - } - }; - - $scope.saveDraft = function() { - $scope.savingDraft = true; + $scope.progressCount = 0; + $scope.maxCommentLength = '32000'; + + $scope.addComment = function () { + $scope.addingComment = true; + if (!securityService.loginStatus.authedUser.is_internal) { + CaseService.isCommentPublic = true; + } + var onSuccess = function (response) { + if (RHAUtils.isNotEmpty($scope.saveDraftPromise)) { + $timeout.cancel($scope.saveDraftPromise); + } + CaseService.commentText = ''; + CaseService.disableAddComment = true; + //TODO: find better way than hard code + if (!securityService.loginStatus.authedUser.is_internal && CaseService.kase.status.name === 'Closed') { + var status = { name: 'Waiting on Red Hat' }; + CaseService.kase.status = status; + } + + if(securityService.loginStatus.authedUser.is_internal){ + if (CaseService.kase.status.name === 'Waiting on Red Hat') { + var status = { name: 'Waiting on Customer' }; + CaseService.kase.status = status; + } + }else { + if (CaseService.kase.status.name === 'Waiting on Customer') { + var status = { name: 'Waiting on Red Hat' }; + CaseService.kase.status = status; + } + } + - var onSuccess = function(commentId) { - $scope.savingDraft = false; - $scope.draftSaved = true; + CaseService.populateComments(CaseService.kase.case_number).then(function (comments) { + $scope.addingComment = false; + $scope.savingDraft = false; + $scope.draftSaved = false; + CaseService.draftComment = undefined; + }); + $scope.progressCount = 0; - CaseService.draftComment = { - "text": CaseService.commentText, - "id": RHAUtils.isNotEmpty(commentId) ? commentId : draftComment.id, - "draft": true, - "case_number": CaseService.kase.case_number + if(securityService.loginStatus.authedUser.sso_username !== undefined && CaseService.updatedNotifiedUsers.indexOf(securityService.loginStatus.authedUser.sso_username) === -1){ + strataService.cases.notified_users.add(CaseService.kase.case_number, securityService.loginStatus.authedUser.sso_username).then(function () { + CaseService.updatedNotifiedUsers.push(securityService.loginStatus.authedUser.sso_username); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + } + + }; + var onError = function (error) { + AlertService.addStrataErrorMessage(error); + $scope.addingComment = false; + $scope.progressCount = 0; + }; + if (RHAUtils.isNotEmpty(CaseService.draftComment)) { + strataService.cases.comments.put(CaseService.kase.case_number, CaseService.commentText, false, CaseService.isCommentPublic, CaseService.draftComment.id).then(onSuccess, onError); + } else { + strataService.cases.comments.post(CaseService.kase.case_number, CaseService.commentText, CaseService.isCommentPublic, false).then(onSuccess, onError); + } }; - }; - - var onFailure = function(error) { - AlertService.addStrataErrorMessage(error); - $scope.savingDraft = false; - } - - if (RHAUtils.isNotEmpty(CaseService.draftComment)) { - //draft update - strataService.cases.comments.put( - CaseService.kase.case_number, - CaseService.commentText, - true, - CaseService.draftComment.id) - .then(onSuccess, onFailure); - } else { - //initial draft save - strataService.cases.comments.post( - CaseService.kase.case_number, - CaseService.commentText, - true) - .then(onSuccess, onFailure); - } - }; - } + $scope.saveDraftPromise; + $scope.onNewCommentKeypress = function () { + if (RHAUtils.isNotEmpty(CaseService.commentText) && !$scope.addingComment) { + CaseService.disableAddComment = false; + $timeout.cancel($scope.saveDraftPromise); + $scope.saveDraftPromise = $timeout(function () { + if (!$scope.addingComment && CaseService.commentText !== '') { + $scope.saveDraft(); + } + }, 5000); + } else if (RHAUtils.isEmpty(CaseService.commentText)) { + CaseService.disableAddComment = true; + } + }; + $scope.$watch('CaseService.commentText', function() { + $scope.maxCharacterCheck(); + }); + $scope.maxCharacterCheck = function() { + if (CaseService.commentText !== undefined && $scope.maxCommentLength > CaseService.commentText.length) { + var count = CaseService.commentText.length * 100 / $scope.maxCommentLength ; + parseInt(count); + $scope.progressCount = Math.round(count * 100) / 100; + } + }; + $scope.saveDraft = function () { + $scope.savingDraft = true; + if (!securityService.loginStatus.authedUser.is_internal) { + CaseService.isCommentPublic = true; + } + var onSuccess = function (commentId) { + $scope.savingDraft = false; + $scope.draftSaved = true; + CaseService.draftComment = { + 'text': CaseService.commentText, + 'id': RHAUtils.isNotEmpty(commentId) ? commentId : CaseService.draftComment.id, + 'draft': true, + 'public': CaseService.isCommentPublic, + 'case_number': CaseService.kase.case_number + }; + }; + var onFailure = function (error) { + AlertService.addStrataErrorMessage(error); + $scope.savingDraft = false; + }; + if (RHAUtils.isNotEmpty(CaseService.draftComment)) { + //draft update + strataService.cases.comments.put(CaseService.kase.case_number, CaseService.commentText, true, CaseService.isCommentPublic, CaseService.draftComment.id).then(onSuccess, onFailure); + } else { + //initial draft save + strataService.cases.comments.post(CaseService.kase.case_number, CaseService.commentText, CaseService.isCommentPublic, true).then(onSuccess, onFailure); + } + }; + } ]); 'use strict'; /*global $ */ - - -angular.module('RedhatAccess.cases') -.controller('AttachLocalFile', [ - '$scope', - 'AttachmentsService', - 'securityService', - function ($scope, AttachmentsService, securityService) { - $scope.NO_FILE_CHOSEN = 'No file chosen'; - $scope.fileDescription = ''; - - $scope.clearSelectedFile = function() { - $scope.fileName = $scope.NO_FILE_CHOSEN; - $scope.fileDescription = ''; - }; - - $scope.addFile = function() { - /*jshint camelcase: false */ - var data = new FormData(); - data.append('file', $scope.fileObj); - data.append('description', $scope.fileDescription); - - AttachmentsService.addNewAttachment({ - file_name: $scope.fileName, - description: $scope.fileDescription, - length: $scope.fileSize, - created_by: securityService.loginStatus.loggedInUser, - created_date: new Date().getTime(), - file: data - }); - - $scope.clearSelectedFile(); - }; - - $scope.getFile = function() { - $('#fileUploader').click(); - }; - - $scope.selectFile = function() { - $scope.fileObj = $('#fileUploader')[0].files[0]; - $scope.fileSize = $scope.fileObj.size; - $scope.fileName = $scope.fileObj.name; - $scope.$apply(); - }; - - $scope.clearSelectedFile(); - } +angular.module('RedhatAccess.cases').controller('AttachLocalFile', [ + '$scope', + '$sce', + 'RHAUtils', + 'AlertService', + 'AttachmentsService', + 'securityService', + function ($scope, $sce, RHAUtils,AlertService, AttachmentsService, securityService) { + $scope.AttachmentsService = AttachmentsService; + $scope.NO_FILE_CHOSEN = 'No file chosen'; + $scope.fileDescription = ''; + var maxFileSize = 250000000; + + $scope.parseArtifactHtml = function () { + var parsedHtml = ''; + if (RHAUtils.isNotEmpty(AttachmentsService.suggestedArtifact.description)) { + var rawHtml = AttachmentsService.suggestedArtifact.description.toString(); + parsedHtml = $sce.trustAsHtml(rawHtml); + } + return parsedHtml; + }; + $scope.clearSelectedFile = function () { + $scope.fileName = $scope.NO_FILE_CHOSEN; + $scope.fileDescription = ''; + }; + $scope.addFile = function () { + /*jshint camelcase: false */ + var data = new FormData(); + data.append('file', $scope.fileObj); + data.append('description', $scope.fileDescription); + AttachmentsService.addNewAttachment({ + file_name: $scope.fileName, + description: $scope.fileDescription, + length: $scope.fileSize, + created_by: securityService.loginStatus.authedUser.loggedInUser, + created_date: new Date().getTime(), + file: data + }); + $scope.clearSelectedFile(); + }; + $scope.getFile = function () { + $('#fileUploader').click(); + }; + $scope.selectFile = function () { + if($('#fileUploader')[0].files[0].size < maxFileSize){ + $scope.fileObj = $('#fileUploader')[0].files[0]; + $scope.fileSize = $scope.fileObj.size; + $scope.fileName = $scope.fileObj.name; + $scope.$apply(); + } else { + AlertService.addDangerMessage($('#fileUploader')[0].files[0].name + ' cannot be attached because it is larger the 250MB. Please FTP large files to dropbox.redhat.com.'); + } + $('#fileUploader')[0].value = ''; + }; + $scope.clearSelectedFile(); + } ]); - 'use strict'; /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') - .controller('AttachmentsSection', [ +angular.module('RedhatAccess.cases').controller('AttachmentsSection', [ '$scope', + '$upload', 'AttachmentsService', 'CaseService', 'TreeViewSelectorUtils', 'EDIT_CASE_CONFIG', - function ( - $scope, - AttachmentsService, - CaseService, - TreeViewSelectorUtils, - EDIT_CASE_CONFIG) { + function ($scope, $upload, AttachmentsService, CaseService, TreeViewSelectorUtils, EDIT_CASE_CONFIG) { + $scope.rhaDisabled = !EDIT_CASE_CONFIG.showAttachments; + $scope.showServerSideAttachments = EDIT_CASE_CONFIG.showServerSideAttachments; + $scope.isPCM = EDIT_CASE_CONFIG.isPCM; + $scope.ie8 = window.ie8; + $scope.AttachmentsService = AttachmentsService; + $scope.CaseService = CaseService; + $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; + $scope.doUpdate = function () { + $scope.updatingAttachments = true; + AttachmentsService.updateAttachments(CaseService.kase.case_number).then(function () { + $scope.updatingAttachments = false; + }, function (error) { + $scope.updatingAttachments = false; + }); + }; - $scope.rhaDisabled = !EDIT_CASE_CONFIG.showAttachments; - $scope.showServerSideAttachments = EDIT_CASE_CONFIG.showServerSideAttachments; - $scope.AttachmentsService = AttachmentsService; - $scope.CaseService = CaseService; - $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; + $scope.onFileSelect = function($files) { + //$files: an array of files selected, each file has name, size, and type. + for (var i = 0; i < $files.length; i++) { + var file = $files[i]; + $scope.upload = $upload.upload({ + url: 'https://access.devgssci.devlab.phx1.redhat.com/rs/cases/' + '01699842' + '/attachments', + method: 'POST', + //headers: {'header-key': 'header-value'}, + withCredentials: true, + data: {myObj: $scope.myModelObj}, + file: file, // or list of files ($files) for html5 only + //fileName: 'doc.jpg' or ['1.jpg', '2.jpg', ...] // to modify the name of the file(s) + // customize file formData name ('Content-Disposition'), server side file variable name. + //fileFormDataName: myFile, //or a list of names for multiple files (html5). Default is 'file' + // customize how data is added to formData. See #40#issuecomment-28612000 for sample code + //formDataAppender: function(formData, key, val){} + }).progress(function(evt) { + console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total)); + }).success(function(data, status, headers, config) { + // file is uploaded successfully + console.log(data); + }); + //.error(...) + //.then(success, error, progress); + // access or attach event listeners to the underlying XMLHttpRequest. + //.xhr(function(xhr){xhr.upload.addEventListener(...)}) + } + }; - $scope.doUpdate = function () { - $scope.updatingAttachments = true; - AttachmentsService.updateAttachments(CaseService.kase.case_number).then( - function () { - $scope.updatingAttachments = false; - }, - function (error) { - $scope.updatingAttachments = false; - }); - }; } - ]); - +]); 'use strict'; -angular.module('RedhatAccess.cases') - .controller('BackEndAttachmentsCtrl', ['$scope', '$location', 'TreeViewSelectorData', 'AttachmentsService', 'NEW_CASE_CONFIG', 'EDIT_CASE_CONFIG', +angular.module('RedhatAccess.cases').controller('BackEndAttachmentsCtrl', [ + '$scope', + '$location', + 'TreeViewSelectorData', + 'AttachmentsService', + 'NEW_CASE_CONFIG', + 'EDIT_CASE_CONFIG', function ($scope, $location, TreeViewSelectorData, AttachmentsService, NEW_CASE_CONFIG, EDIT_CASE_CONFIG) { - $scope.name = 'Attachments'; - $scope.attachmentTree = []; - - var newCase = false; - var editCase = false; - - if($location.path().indexOf('new') > -1 ){ - newCase = true; - } else{ - editCase = true; - } - if (!$scope.rhaDisabled && newCase && NEW_CASE_CONFIG.showServerSideAttachments || !$scope.rhaDisabled && editCase && EDIT_CASE_CONFIG.showServerSideAttachments) { - var sessionId = $location.search().sessionId; - TreeViewSelectorData.getTree('attachments', sessionId).then( - function (tree) { - $scope.attachmentTree = tree; - AttachmentsService.updateBackEndAttachments(tree); - }, - function () { - }); - } + $scope.name = 'Attachments'; + $scope.attachmentTree = []; + var newCase = false; + var editCase = false; + if ($location.path().indexOf('new') > -1) { + newCase = true; + } else { + editCase = true; + } + if (!$scope.rhaDisabled && newCase && NEW_CASE_CONFIG.showServerSideAttachments || !$scope.rhaDisabled && editCase && EDIT_CASE_CONFIG.showServerSideAttachments) { + var sessionId = $location.search().sessionId; + TreeViewSelectorData.getTree('attachments', sessionId).then(function (tree) { + $scope.attachmentTree = tree; + AttachmentsService.updateBackEndAttachments(tree); + }, function () { + }); + } } - ]); - +]); 'use strict'; - +/*jshint camelcase: false, expr: true*/ //Saleforce hack--- //we have to monitor stuff on the window object //because the liveagent code generated by Salesforce is not //designed for angularjs. //We create fake buttons that we give to the salesforce api so we can track //chat availability without having to write a complete rest client. -window.fakeOnlineButton = { - style: { - display: 'none' - } -}; - -window.fakeOfflineButton = { - style: { - display: 'none' - } -}; +window.fakeOnlineButton = { style: { display: 'none' } }; +window.fakeOfflineButton = { style: { display: 'none' } }; // - -angular.module('RedhatAccess.cases') - .controller('ChatButton', [ +angular.module('RedhatAccess.cases').controller('ChatButton', [ '$scope', 'CaseService', 'securityService', + 'strataService', + 'AlertService', 'CHAT_SUPPORT', - 'securityService', 'AUTH_EVENTS', '$rootScope', '$sce', '$http', '$interval', - function ($scope, CaseService, SecurityService, CHAT_SUPPORT, securityService, AUTH_EVENTS, $rootScope, $sce, $http, $interval) { - - - $scope.securityService = securityService; - if (window.chatInitialized === undefined) { - window.chatInitialized = false; - } - - - $scope.checkChatButtonStates = function () { - $scope.chatAvailable = window.fakeOnlineButton.style.display !== 'none'; - }; - - $scope.timer = null; - - - $scope.chatHackUrl = $sce.trustAsResourceUrl(CHAT_SUPPORT.chatIframeHackUrlPrefix); - - $scope.setChatIframeHackUrl = function () { - var url = CHAT_SUPPORT.chatIframeHackUrlPrefix + '?sessionId=' + securityService.loginStatus.sessionId + '&ssoName=' + securityService.loginStatus.ssoName; - $scope.chatHackUrl = $sce.trustAsResourceUrl(url); - - }; - - - $scope.enableChat = function () { - $scope.showChat = securityService.loginStatus.isLoggedIn && securityService.loginStatus.hasChat && CHAT_SUPPORT.enableChat; - return $scope.showChat; - - }; - - $scope.showChat = false; // determines whether we should show buttons at all - - $scope.chatAvailable = false; //Availability of chat as determined by live agent, toggles chat buttons - - $scope.initializeChat = function () { - if (!$scope.enableChat() || window.chatInitialized === true) { - //function should only be called when chat is enabled, and only once per page load - return; - } - if (!window._laq) { - window._laq = []; - } - window._laq.push( - function () { - liveagent.showWhenOnline( - CHAT_SUPPORT.chatButtonToken, - window.fakeOnlineButton); - - liveagent.showWhenOffline( - CHAT_SUPPORT.chatButtonToken, - window.fakeOfflineButton); - } - ); - //var chatToken = securityService.loginStatus.sessionId; - var ssoName = securityService.loginStatus.ssoName; - var name = securityService.loginStatus.loggedInUser; - //var currentCaseNumber; - var accountNumber = securityService.loginStatus.account.number; - - // if (currentCaseNumber) { - // liveagent - // .addCustomDetail('Case Number', currentCaseNumber) - // .map('Case', 'CaseNumber', false, false, false) - // .saveToTranscript('CaseNumber__c'); - // } - - // if (chatToken) { - // liveagent - // .addCustomDetail('Session ID', chatToken) - // .map('Contact', 'SessionId__c', false, false, false); - // } - - liveagent - .addCustomDetail('Contact Login', ssoName) - .map('Contact', 'SSO_Username__c', true, true, true) - .saveToTranscript('SSO_Username__c'); - //liveagent - // .addCustomDetail('Contact E-mail', email) - // .map('Contact', 'Email', false, false, false); - liveagent - .addCustomDetail('Account Number', accountNumber) - .map('Account', 'AccountNumber', true, true, true); - liveagent.setName(name); - liveagent.addCustomDetail('Name', name); - liveagent.setChatWindowHeight('552'); - //liveagent.enableLogging(); - - liveagent.init( - CHAT_SUPPORT.chatLiveAgentUrlPrefix, - CHAT_SUPPORT.chatInitHashOne, - CHAT_SUPPORT.chatInitHashTwo); - window.chatInitialized = true; - }; - - $scope.openChatWindow = function () { - liveagent.startChat(CHAT_SUPPORT.chatButtonToken); - }; - - - - $scope.init = function () { - - if ($scope.enableChat()) { - $scope.setChatIframeHackUrl(); - $scope.timer = $interval($scope.checkChatButtonStates, 5000); - $scope.initializeChat(); - - } - }; - - $scope.$on( - '$destroy', - function () { - //we cancel timer each time scope is destroyed - //it will be restarted via init on state change to a page that has a chat buttom - $interval.cancel($scope.timer); + function ($scope, CaseService, securityService, strataService, AlertService, CHAT_SUPPORT, AUTH_EVENTS, $rootScope, $sce, $http, $interval) { + $scope.securityService = securityService; + if (window.chatInitialized === undefined) { + window.chatInitialized = false; } - ); - - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } else { - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); + $scope.checkChatButtonStates = function () { + $scope.chatAvailable = window.fakeOnlineButton.style.display !== 'none'; + }; + $scope.timer = null; + $scope.chatHackUrl = $sce.trustAsResourceUrl(CHAT_SUPPORT.chatIframeHackUrlPrefix); + $scope.setChatIframeHackUrl = function () { + strataService.users.chatSession.post().then(angular.bind(this, function (sessionId) { + var url = CHAT_SUPPORT.chatIframeHackUrlPrefix + '?sessionId=' + sessionId + '&ssoName=' + securityService.loginStatus.authedUser.sso_username; + $scope.chatHackUrl = $sce.trustAsResourceUrl(url); + }), function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + $scope.enableChat = function () { + $scope.showChat = securityService.loginStatus.isLoggedIn && securityService.loginStatus.authedUser.has_chat && CHAT_SUPPORT.enableChat; + return $scope.showChat; + }; + $scope.showChat = false; + // determines whether we should show buttons at all + $scope.chatAvailable = false; + //Availability of chat as determined by live agent, toggles chat buttons + $scope.initializeChat = function () { + if (!$scope.enableChat() || window.chatInitialized === true) { + //function should only be called when chat is enabled, and only once per page load + return; + } + if (!window._laq) { + window._laq = []; + } + window._laq.push(function () { + liveagent.showWhenOnline(CHAT_SUPPORT.chatButtonToken, window.fakeOnlineButton); + liveagent.showWhenOffline(CHAT_SUPPORT.chatButtonToken, window.fakeOfflineButton); + }); + //var chatToken = securityService.loginStatus.sessionId; + var ssoName = securityService.loginStatus.authedUser.sso_username; + var name = securityService.loginStatus.authedUser.loggedInUser; + //var currentCaseNumber; + var accountNumber = securityService.loginStatus.authedUser.account_number; + // if (currentCaseNumber) { + // liveagent + // .addCustomDetail('Case Number', currentCaseNumber) + // .map('Case', 'CaseNumber', false, false, false) + // .saveToTranscript('CaseNumber__c'); + // } + // if (chatToken) { + // liveagent + // .addCustomDetail('Session ID', chatToken) + // .map('Contact', 'SessionId__c', false, false, false); + // } + liveagent.addCustomDetail('Contact Login', ssoName).map('Contact', 'SSO_Username__c', true, true, true).saveToTranscript('SSO_Username__c'); + //liveagent + // .addCustomDetail('Contact E-mail', email) + // .map('Contact', 'Email', false, false, false); + liveagent.addCustomDetail('Account Number', accountNumber).map('Account', 'AccountNumber', true, true, true); + liveagent.setName(name); + liveagent.addCustomDetail('Name', name); + liveagent.setChatWindowHeight('552'); + //liveagent.enableLogging(); + liveagent.init(CHAT_SUPPORT.chatLiveAgentUrlPrefix, CHAT_SUPPORT.chatInitHashOne, CHAT_SUPPORT.chatInitHashTwo); + window.chatInitialized = true; + }; + $scope.openChatWindow = function () { + liveagent.startChat(CHAT_SUPPORT.chatButtonToken); + }; + $scope.init = function () { + if ($scope.enableChat()) { + $scope.setChatIframeHackUrl(); + $scope.timer = $interval($scope.checkChatButtonStates, 5000); + $scope.initializeChat(); + } + }; + $scope.$on('$destroy', function () { + //we cancel timer each time scope is destroyed + //it will be restarted via init on state change to a page that has a chat buttom + $interval.cancel($scope.timer); }); - } - - $rootScope.$on(AUTH_EVENTS.sessionIdChanged, function () { - if ($scope.enableChat()) { - $scope.setChatIframeHackUrl(); + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); + } else { + $scope.authEventLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + }); + $scope.$on('$destroy', function () { + $scope.authEventLoginSuccess(); + }); } - }); + $scope.$on('$destroy', function () { + window._laq = null; + }); } - ]); -'use strict'; - /*jshint unused:vars */ - /*jshint camelcase: false */ - -angular.module('RedhatAccess.cases') -.controller('CommentsSection', [ - '$scope', - 'CaseService', - 'strataService', - '$stateParams', - 'AlertService', - '$timeout', - '$modal', - 'RHAUtils', - function( - $scope, - CaseService, - strataService, - $stateParams, - AlertService, - $timeout, - $modal, - RHAUtils) { - $scope.CaseService = CaseService; - - CaseService.refreshComments = function() { - $scope.selectPage(1); - }; - - CaseService.populateComments($stateParams.id).then( - function(comments) { - if (RHAUtils.isNotEmpty(comments)) { - CaseService.refreshComments(); - } - } - ); - - $scope.itemsPerPage = 4; - $scope.maxPagerSize = 3; - - $scope.selectPage = function(pageNum) { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > CaseService.comments.length ? - CaseService.comments.length : end; - - $scope.commentsOnScreen = - CaseService.comments.slice(start, end); - }; - - $scope.requestManagementEscalation = function() { - $modal.open({ - templateUrl: 'cases/views/requestManagementEscalationModal.html', - controller: 'RequestManagementEscalationModal' - }); - }; +]); - if (RHAUtils.isNotEmpty(CaseService.comments)) { - CaseService.refreshComments(); +'use strict'; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').controller('CommentsSection', [ + '$scope', + 'CaseService', + 'strataService', + '$stateParams', + 'AlertService', + '$modal', + '$location', + '$anchorScroll', + 'RHAUtils', + function ($scope, CaseService, strataService, $stateParams, AlertService, $modal, $location, $anchorScroll, RHAUtils) { + $scope.CaseService = CaseService; + $scope.ie8 = window.ie8; + $scope.ie9 = window.ie9; + + CaseService.populateComments($stateParams.id).then(function (comments) { + $scope.$on('rhaCaseSettled', function() { + $scope.$evalAsync(function() { + CaseService.scrollToComment($location.search().commentId); + }); + }); + }); + $scope.requestManagementEscalation = function () { + $modal.open({ + templateUrl: 'cases/views/requestManagementEscalationModal.html', + controller: 'RequestManagementEscalationModal' + }); + }; } - } ]); 'use strict'; - -angular.module('RedhatAccess.cases') - .controller('CompactCaseList', [ +angular.module('RedhatAccess.cases').controller('CompactCaseList', [ '$scope', '$stateParams', 'strataService', @@ -20003,70 +2591,40 @@ angular.module('RedhatAccess.cases') 'SearchBoxService', 'RHAUtils', '$filter', - function ( - $scope, - $stateParams, - strataService, - CaseService, - $rootScope, - AUTH_EVENTS, - securityService, - SearchCaseService, - AlertService, - SearchBoxService, - RHAUtils, - $filter) { - - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.selectedCaseIndex = -1; - $scope.SearchCaseService = SearchCaseService; - - $scope.selectCase = function ($index) { - if ($scope.selectedCaseIndex !== $index) { - $scope.selectedCaseIndex = $index; - } - }; - - $scope.domReady = false; //used to notify resizable directive that the page has loaded - - SearchBoxService.doSearch = - CaseService.onSelectChanged = - CaseService.onOwnerSelectChanged = - CaseService.onGroupSelectChanged = function () { - SearchCaseService.doFilter().then( - function () { - if (RHAUtils.isNotEmpty($stateParams.id) && $scope.selectedCaseIndex === -1) { - var selectedCase = - $filter('filter')( - SearchCaseService.cases, { - 'case_number': $stateParams.id - }); - $scope.selectedCaseIndex = SearchCaseService.cases.indexOf(selectedCase[0]); - } - - $scope.domReady = true; + function ($scope, $stateParams, strataService, CaseService, $rootScope, AUTH_EVENTS, securityService, SearchCaseService, AlertService, SearchBoxService, RHAUtils, $filter) { + $scope.securityService = securityService; + $scope.CaseService = CaseService; + $scope.selectedCaseIndex = -1; + $scope.SearchCaseService = SearchCaseService; + $scope.selectCase = function ($index) { + if ($scope.selectedCaseIndex !== $index) { + $scope.selectedCaseIndex = $index; } - ); - }; - - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateGroups(); - SearchBoxService.doSearch(); - } - - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateGroups(); - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); - + }; + $scope.domReady = false; + //used to notify resizable directive that the page has loaded + SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { + SearchCaseService.doFilter().then(function () { + if (RHAUtils.isNotEmpty($stateParams.id) && $scope.selectedCaseIndex === -1) { + var selectedCase = $filter('filter')(SearchCaseService.cases, { 'case_number': $stateParams.id }); + $scope.selectedCaseIndex = SearchCaseService.cases.indexOf(selectedCase[0]); + } + $scope.domReady = true; + }); + }; + if (securityService.loginStatus.isLoggedIn) { + CaseService.populateGroups(); + SearchBoxService.doSearch(); + } + $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + CaseService.populateGroups(); + SearchBoxService.doSearch(); + AlertService.clearAlerts(); + }); } - ]); +]); 'use strict'; - -angular.module('RedhatAccess.cases') - .controller('CompactEdit', [ +angular.module('RedhatAccess.cases').controller('CompactEdit', [ '$scope', 'strataService', '$stateParams', @@ -20077,356 +2635,382 @@ angular.module('RedhatAccess.cases') 'CASE_EVENTS', 'securityService', 'AlertService', - function ( - $scope, - strataService, - $stateParams, - CaseService, - AttachmentsService, - $rootScope, - AUTH_EVENTS, - CASE_EVENTS, - securityService, - AlertService) { - - $scope.securityService = securityService; - - $scope.caseLoading = true; - $scope.domReady = false; - - $scope.init = function () { - strataService.cases.get($stateParams.id).then( - function (caseJSON) { - CaseService.defineCase(caseJSON); - $rootScope.$broadcast(CASE_EVENTS.received); - $scope.caseLoading = false; - - if (caseJSON.product !== null && caseJSON.product.name !== null) { - strataService.products.versions(caseJSON.product.name).then( - function (versions) { - CaseService.versions = versions; - }, - function (error) { - AlertService.addStrataErrorMessage(error); + function ($scope, strataService, $stateParams, CaseService, AttachmentsService, $rootScope, AUTH_EVENTS, CASE_EVENTS, securityService, AlertService) { + $scope.securityService = securityService; + $scope.caseLoading = true; + $scope.domReady = false; + $scope.init = function () { + strataService.cases.get($stateParams.id).then(function (resp) { + var caseJSON = resp[0]; + var cacheHit = resp[1]; + if (!cacheHit) { + CaseService.defineCase(caseJSON); + } else { + CaseService.kase = caseJSON; + } + $rootScope.$broadcast(CASE_EVENTS.received); + $scope.caseLoading = false; + if (caseJSON.product !== undefined && caseJSON.product.name !== undefined) { + strataService.products.versions(caseJSON.product.name).then(function (versions) { + CaseService.versions = versions; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - ); - } - $scope.domReady = true; - } - ); - - strataService.cases.attachments.list($stateParams.id).then( - function (attachmentsJSON) { - AttachmentsService.defineOriginalAttachments(attachmentsJSON); - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; - - - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); - AlertService.clearAlerts(); - }); + $scope.domReady = true; + }); + strataService.cases.attachments.list($stateParams.id).then(function (attachmentsJSON) { + AttachmentsService.defineOriginalAttachments(attachmentsJSON); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); + } + $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + AlertService.clearAlerts(); + }); } - ]); +]); 'use strict'; /*global $ */ - -angular.module('RedhatAccess.cases') -.controller('CreateGroupButton', [ - '$scope', - '$modal', - function ($scope, $modal) { - $scope.openCreateGroupDialog = function() { - $modal.open({ - templateUrl: 'cases/views/createGroupModal.html', - controller: 'CreateGroupModal' - }); +angular.module('RedhatAccess.cases').controller('CreateGroupButton', [ + '$scope', + '$modal', + function ($scope, $modal) { + $scope.openCreateGroupDialog = function () { + $modal.open({ + templateUrl: 'cases/views/createGroupModal.html', + controller: 'CreateGroupModal' + }); + }; + } +]); +'use strict'; +/*global $ */ +angular.module('RedhatAccess.cases').controller('CreateGroupModal', [ + '$scope', + '$modalInstance', + 'strataService', + 'AlertService', + 'CaseService', + 'GroupService', + function ($scope, $modalInstance, strataService, AlertService, CaseService, GroupService) { + $scope.createGroup = function () { + AlertService.addWarningMessage('Creating group ' + this.groupName + '...'); + $modalInstance.close(); + strataService.groups.create(this.groupName).then(angular.bind(this, function (success) { + CaseService.groups.push({ + name: this.groupName, + number: success + }); + AlertService.clearAlerts(); + AlertService.addSuccessMessage('Successfully created group ' + this.groupName); + GroupService.reloadTable(); + }), function (error) { + AlertService.clearAlerts(); + AlertService.addStrataErrorMessage(error); + }); + }; + $scope.closeModal = function () { + $modalInstance.close(); + }; + $scope.onGroupNameKeyPress = function ($event) { + if ($event.keyCode === 13) { + angular.bind(this, $scope.createGroup)(); + } + }; } - } ]); - 'use strict'; /*global $ */ +/*jshint expr: true, camelcase: false, newcap: false */ +angular.module('RedhatAccess.cases').controller('DefaultGroup', [ + '$scope', + '$rootScope', + 'strataService', + 'AlertService', + '$location', + 'securityService', + 'AUTH_EVENTS', + function ($scope, $rootScope, strataService, AlertService, $location, securityService, AUTH_EVENTS) { + $scope.securityService = securityService; + $scope.listEmpty = false; + $scope.selectedGroup = {}; + $scope.selectedUser = ''; + $scope.usersOnAccount = []; + $scope.account = null; + $scope.groups = []; + $scope.ssoName = null; + $scope.groupsLoading = false; + $scope.usersLoading = false; + $scope.usersLoaded = false; + $scope.usersAndGroupsFinishedLoading = false; + + $scope.init = function() { + if(securityService.userAllowedToManageGroups()){ + $scope.groupsLoading = true; + var loc = $location.url().split('/'); + $scope.ssoName = securityService.loginStatus.authedUser.sso_username; + $scope.account = securityService.loginStatus.account; + strataService.groups.list($scope.ssoName).then(function (groups) { + $scope.groupsLoading = false; + $scope.groups = groups; + $scope.groups.sort(function(a, b){ + if(a.name < b.name) { return -1; } + if(a.name > b.name) { return 1; } + return 0; + }); + }, function (error) { + $scope.groupsLoading = false; + AlertService.addStrataErrorMessage(error); + }); -angular.module('RedhatAccess.cases') -.controller('CreateGroupModal', [ - '$scope', - '$modalInstance', - 'strataService', - 'AlertService', - 'CaseService', - 'GroupService', - function ($scope, $modalInstance, strataService, AlertService, CaseService, GroupService) { + $scope.usersLoading = true; + strataService.accounts.users($scope.account.number, $scope.selectedGroup.number).then(function (users) { + $scope.usersLoading = false; + $scope.usersOnAccount = users; + $scope.usersLoaded = true; + $scope.usersOnAccount.sort(function(a, b){ + if(a.sso_username < b.sso_username) { return -1; } + if(a.sso_username > b.sso_username) { return 1; } + return 0; + }); + }, function (error) { + $scope.usersLoading = false; + AlertService.addStrataErrorMessage(error); + }); + }else{ + $scope.usersLoading = false; + $scope.groupsLoading = false; + AlertService.addStrataErrorMessage('User does not have proper credentials to manage default groups.'); + } + }; + + $scope.validatePage = function () { + if ($scope.selectedGroup.name !== undefined && $scope.selectedUser.sso_username !== undefined) { + $scope.usersAndGroupsFinishedLoading = true; + } else { + $scope.usersAndGroupsFinishedLoading = false; + } + }; - $scope.createGroup = function() { - AlertService.addWarningMessage('Creating group ' + this.groupName + '...'); - $modalInstance.close(); + $scope.setDefaultGroup = function () { + //Remove old group is_default + var tmpGroup = { + name: $scope.selectedGroup.name, + number: $scope.selectedGroup.number, + isDefault: true, + contactSsoName: $scope.selectedUser.sso_username + }; + strataService.groups.createDefault(tmpGroup).then(function () { + AlertService.addSuccessMessage('Successfully set ' + tmpGroup.name + ' as ' + $scope.selectedUser.sso_username + '\'s default group.'); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; - strataService.groups.create(this.groupName).then( - angular.bind(this, function(success) { - CaseService.groups.push({ - name: this.groupName, - number: success - }); + $scope.back = function(){ + $location.path('/case/group'); + }; - AlertService.clearAlerts(); - AlertService.addSuccessMessage('Successfully created group ' + this.groupName); - GroupService.reloadTable(); - }), - function(error) { - AlertService.clearAlerts(); - AlertService.addStrataErrorMessage(error); - } - ); + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); - }; + } + $scope.authEventLogin = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + }); - $scope.closeModal = function() { - $modalInstance.close(); - }; + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + $scope.selectedGroup = {}; + $scope.usersOnScreen = []; + $scope.usersOnAccount = []; + $scope.accountNumber = null; + }); - $scope.onGroupNameKeyPress = function($event) { - if ($event.keyCode === 13) { - angular.bind(this, $scope.createGroup)(); - } + $scope.$on('$destroy', function () { + $scope.authEventLogoutSuccess(); + $scope.authEventLogin(); + }); } - } ]); - 'use strict'; /*global $ */ - -angular.module('RedhatAccess.cases') -.controller('DeleteGroupButton', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - '$q', - '$filter', - 'GroupService', - function ($scope, strataService, AlertService, CaseService, $q, $filter, GroupService) { - - $scope.deleteGroups = function() { - var promises = []; - angular.forEach(CaseService.groups, function(group, index) { - if (group.selected) { - var promise = strataService.groups.remove(group.number); - promise.then( - function(success) { - var groups = - $filter('filter')( - CaseService.groups, - function(g) { - if (g.number !== group.number) { - return true; - } else { - return false; - } - }); - CaseService.groups = groups; - GroupService.reloadTable(); - AlertService.addSuccessMessage('Successfully deleted group ' + group.name); - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(promise); - } - }); - - AlertService.addWarningMessage('Deleting groups...'); - var parentPromise = $q.all(promises); - parentPromise.then( - function(success) { - AlertService.clearAlerts(); - AlertService.addSuccessMessage('Successfully deleted groups.'); - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); +angular.module('RedhatAccess.cases').controller('DeleteGroupButton', [ + '$scope', + 'strataService', + 'AlertService', + 'CaseService', + '$q', + '$filter', + 'GroupService', + function ($scope, strataService, AlertService, CaseService, $q, $filter, GroupService) { + $scope.GroupService = GroupService; + $scope.deleteGroups = function () { + var promises = []; + angular.forEach(CaseService.groups, function (group, index) { + if (group.selected) { + var promise = strataService.groups.remove(group.number); + promise.then(function (success) { + var groups = $filter('filter')(CaseService.groups, function (g) { + if (g.number !== group.number) { + return true; + } else { + return false; + } + }); + CaseService.groups = groups; + GroupService.reloadTable(); + AlertService.addSuccessMessage('Successfully deleted group ' + group.name); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(promise); + } + }); + AlertService.addWarningMessage('Deleting groups...'); + var parentPromise = $q.all(promises); + parentPromise.then(function (success) { + AlertService.clearAlerts(); + AlertService.addSuccessMessage('Successfully deleted groups.'); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; } - } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('DescriptionSection', [ - '$scope', - 'CaseService', - function( - $scope, - CaseService) { - - $scope.CaseService = CaseService; - } +angular.module('RedhatAccess.cases').controller('DescriptionSection', [ + '$scope', + 'CaseService', + function ($scope, CaseService) { + $scope.CaseService = CaseService; + } ]); - 'use strict'; - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.controller('DetailsSection', [ - '$scope', - 'strataService', - 'CaseService', - '$rootScope', - 'AUTH_EVENTS', - 'CASE_EVENTS', - 'AlertService', - 'RHAUtils', - function( - $scope, - strataService, - CaseService, - $rootScope, - AUTH_EVENTS, - CASE_EVENTS, - AlertService, - RHAUtils) { - - $scope.CaseService = CaseService; - - $scope.init = function() { - if (!$scope.compact) { - - strataService.values.cases.types().then( - function(response) { - $scope.caseTypes = response; - }, - function(error) { - AlertService.addStrataErrorMessage(error); +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').controller('DetailsSection', [ + '$scope', + 'strataService', + 'CaseService', + '$rootScope', + 'AUTH_EVENTS', + 'CASE_EVENTS', + 'AlertService', + 'RHAUtils', + function ($scope, strataService, CaseService, $rootScope, AUTH_EVENTS, CASE_EVENTS, AlertService, RHAUtils) { + $scope.CaseService = CaseService; + $scope.init = function () { + if (!$scope.compact) { + strataService.values.cases.types().then(function (response) { + $scope.caseTypes = response; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + strataService.groups.list(CaseService.kase.contact_sso_username).then(function (response) { + $scope.groups = response; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - ); - - strataService.groups.list(CaseService.kase.contact_sso_username).then( - function(response) { - $scope.groups = response; - }, - function(error) { - AlertService.addStrataErrorMessage(error); + strataService.values.cases.status().then(function (response) { + $scope.statuses = response; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + strataService.values.cases.severity().then(function (response) { + CaseService.severities = response; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + strataService.products.list().then(function (response) { + $scope.products = response; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + $scope.updatingDetails = false; + $scope.updateCase = function () { + $scope.updatingDetails = true; + var caseJSON = {}; + if (CaseService.kase !== undefined) { + if (CaseService.kase.type !== undefined) { + caseJSON.type = CaseService.kase.type.name; + } + if (CaseService.kase.severity !== undefined) { + caseJSON.severity = CaseService.kase.severity.name; + } + if (CaseService.kase.status !== undefined) { + caseJSON.status = CaseService.kase.status.name; + } + if (CaseService.kase.alternate_id !== undefined) { + caseJSON.alternateId = CaseService.kase.alternate_id; + } + if (CaseService.kase.product !== undefined) { + caseJSON.product = CaseService.kase.product.name; + } + if (CaseService.kase.version !== undefined) { + caseJSON.version = CaseService.kase.version; + } + if (CaseService.kase.summary !== undefined) { + caseJSON.summary = CaseService.kase.summary; + } + if (CaseService.kase.group !== null && CaseService.kase.group !== undefined && CaseService.kase.group.number !== undefined) { + caseJSON.folderNumber = CaseService.kase.group.number; + } else { + caseJSON.folderNumber = ''; + } + if (RHAUtils.isNotEmpty(CaseService.kase.fts)) { + caseJSON.fts = CaseService.kase.fts; + if (!CaseService.kase.fts) { + caseJSON.contactInfo24X7 = ''; + } + } + if (CaseService.kase.fts && RHAUtils.isNotEmpty(CaseService.kase.contact_info24_x7)) { + caseJSON.contactInfo24X7 = CaseService.kase.contact_info24_x7; + } + if (CaseService.kase.notes !== null) { + caseJSON.notes = CaseService.kase.notes; + } + strataService.cases.put(CaseService.kase.case_number, caseJSON).then(function () { + $scope.caseDetails.$setPristine(); + $scope.updatingDetails = false; + if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') { + $scope.$apply(); + } + }, function (error) { + AlertService.addStrataErrorMessage(error); + $scope.updatingDetails = false; + $scope.$apply(); + }); } - ); - } - - strataService.values.cases.status().then( - function(response) { - $scope.statuses = response; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - - strataService.values.cases.severity().then( - function(response) { - CaseService.severities = response; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - - strataService.products.list().then( - function(response) { - $scope.products = response; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; - - $scope.updatingDetails = false; - $scope.updateCase = function() { - $scope.updatingDetails = true; - - var caseJSON = {}; - if (CaseService.kase != null) { - if (CaseService.kase.type != null) { - caseJSON.type = CaseService.kase.type.name; - } - if (CaseService.kase.severity != null) { - caseJSON.severity = CaseService.kase.severity.name; - } - if (CaseService.kase.status != null) { - caseJSON.status = CaseService.kase.status.name; - } - if (CaseService.kase.alternate_id != null) { - caseJSON.alternateId = CaseService.kase.alternate_id; - } - if (CaseService.kase.product != null) { - caseJSON.product = CaseService.kase.product.name; - } - if (CaseService.kase.version != null) { - caseJSON.version = CaseService.kase.version; - } - if (CaseService.kase.summary != null) { - caseJSON.summary = CaseService.kase.summary; - } - if (CaseService.kase.group != null) { - caseJSON.folderNumber = CaseService.kase.group.number; - } - if (RHAUtils.isNotEmpty(CaseService.kase.fts)) { - caseJSON.fts = CaseService.kase.fts; - if (!CaseService.kase.fts) { - caseJSON.contactInfo24X7 = ''; - } - } - if (CaseService.kase.fts && RHAUtils.isNotEmpty(CaseService.kase.contact_info24_x7)) { - caseJSON.contactInfo24X7 = CaseService.kase.contact_info24_x7; + }; + $scope.getProductVersions = function () { + CaseService.versions = []; + strataService.products.versions(CaseService.kase.product.code).then(function (versions) { + CaseService.versions = versions; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + if (CaseService.caseDataReady) { + $scope.init(); } - - strataService.cases.put(CaseService.kase.case_number, caseJSON).then( - function() { - $scope.caseDetails.$setPristine(); - $scope.updatingDetails = false; - if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') { - $scope.$apply(); - } - }, - function(error) { - AlertService.addStrataErrorMessage(error); - $scope.updatingDetails = false; - $scope.$apply(); - } - ); - } - }; - - $scope.getProductVersions = function() { - CaseService.versions = []; - - strataService.products.versions(CaseService.kase.product.code).then( - function(versions){ - CaseService.versions = versions; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; - - $rootScope.$on(CASE_EVENTS.received, function() { - $scope.init(); - AlertService.clearAlerts(); - }); - } + $scope.caseEventDeregister = $rootScope.$on(CASE_EVENTS.received, function () { + $scope.init(); + AlertService.clearAlerts(); + }); + $scope.$on('$destroy', function () { + $scope.caseEventDeregister(); + }); + } ]); - 'use strict'; /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') - .controller('Edit', [ +angular.module('RedhatAccess.cases').controller('Edit', [ '$scope', '$stateParams', '$filter', @@ -20439,127 +3023,124 @@ angular.module('RedhatAccess.cases') 'AUTH_EVENTS', 'AlertService', 'securityService', - 'EDIT_CASE_CONFIG', - 'RHAUtils', - 'CASE_EVENTS', - function ( - $scope, - $stateParams, - $filter, - $q, - AttachmentsService, - CaseService, - strataService, - RecommendationsService, - $rootScope, - AUTH_EVENTS, - AlertService, - securityService, - EDIT_CASE_CONFIG, - RHAUtils, - CASE_EVENTS) { - - $scope.EDIT_CASE_CONFIG = EDIT_CASE_CONFIG; - $scope.securityService = securityService; - $scope.AttachmentsService = AttachmentsService; - $scope.CaseService = CaseService; - CaseService.clearCase(); - - $scope.init = function () { - $scope.caseLoading = true; - $scope.recommendationsLoading = true; - - strataService.cases.get($stateParams.id).then( - function (caseJSON) { - CaseService.defineCase(caseJSON); - $rootScope.$broadcast(CASE_EVENTS.received); - $scope.caseLoading = false; - - if ('product' in caseJSON && 'name' in caseJSON.product && caseJSON.product.name) { - strataService.products.versions(caseJSON.product.name).then( - function (versions) { - CaseService.versions = versions; - }, - function (error) { - AlertService.addStrataErrorMessage(error); + 'EDIT_CASE_CONFIG', + 'RHAUtils', + 'CASE_EVENTS', + function ($scope, $stateParams, $filter, $q, AttachmentsService, CaseService, strataService, RecommendationsService, $rootScope, AUTH_EVENTS, AlertService, securityService, EDIT_CASE_CONFIG, RHAUtils, CASE_EVENTS) { + $scope.EDIT_CASE_CONFIG = EDIT_CASE_CONFIG; + $scope.securityService = securityService; + $scope.AttachmentsService = AttachmentsService; + $scope.CaseService = CaseService; + CaseService.clearCase(); + $scope.loading = {}; + $scope.init = function () { + $scope.loading.kase = true; + $scope.recommendationsLoading = true; + strataService.cases.get($stateParams.id).then(function (resp) { + var caseJSON = resp[0]; + var cacheHit = resp[1]; + if (!cacheHit) { + CaseService.defineCase(caseJSON); + } else { + CaseService.setCase(caseJSON); + } + $rootScope.$broadcast(CASE_EVENTS.received); + $scope.loading.kase = false; + if ('product' in caseJSON && 'name' in caseJSON.product && caseJSON.product.name) { + strataService.products.versions(caseJSON.product.name).then(function (versions) { + CaseService.versions = versions; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - ); - } - - if (caseJSON.account_number !== undefined) { - strataService.accounts.get(caseJSON.account_number).then( - function (account) { - CaseService.defineAccount(account); - }, - function (error) { - AlertService.addStrataErrorMessage(error); + if (caseJSON.account_number !== undefined) { + strataService.accounts.get(caseJSON.account_number).then(function (account) { + CaseService.defineAccount(account); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); } - ); - } - - if (EDIT_CASE_CONFIG.showRecommendations) { - RecommendationsService.populatePinnedRecommendations().then( - function () { - $scope.recommendationsLoading = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); + if (EDIT_CASE_CONFIG.showRecommendations) { + var pinnedDfd = RecommendationsService.populatePinnedRecommendations().then(angular.noop, function (error) { + AlertService.addStrataErrorMessage(error); + }); + var reccomendDfd = RecommendationsService.populateRecommendations(12); + $q.all([pinnedDfd, reccomendDfd]).then(function(){ + $scope.recommendationsLoading = false; + }); } - ); - - RecommendationsService.populateRecommendations(12).then( - function () { - $scope.recommendationsLoading = false; + if (EDIT_CASE_CONFIG.showEmailNotifications && !cacheHit) { + CaseService.defineNotifiedUsers(); } - ); + }); + if (EDIT_CASE_CONFIG.showAttachments) { + $scope.loading.attachments = true; + strataService.cases.attachments.list($stateParams.id).then(function (attachmentsJSON) { + AttachmentsService.defineOriginalAttachments(attachmentsJSON); + $scope.loading.attachments= false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + $scope.loading.attachments= false; + }); } - - if (EDIT_CASE_CONFIG.showEmailNotifications) { - CaseService.defineNotifiedUsers(); + if (EDIT_CASE_CONFIG.showComments) { + $scope.loading.comments = true; + strataService.cases.comments.get($stateParams.id).then(function (commentsJSON) { + $scope.comments = commentsJSON; + $scope.loading.comments = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + $scope.loading.comments = false; + }); } - } - ); + }; + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); + } + $scope.authLoginEvent = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + AlertService.clearAlerts(); + }); - if (EDIT_CASE_CONFIG.showAttachments) { - $scope.attachmentsLoading = true; - strataService.cases.attachments.list($stateParams.id).then( - function (attachmentsJSON) { - AttachmentsService.defineOriginalAttachments(attachmentsJSON); - $scope.attachmentsLoading = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); + var caseSettled = function() { + $scope.$broadcast('rhaCaseSettled'); + }; + + $scope.loadingWatcher = $scope.$watch('loading', function(loadingObj){ + if($.isEmptyObject(loadingObj)) { + return; } - ); - } + var allLoaded = true; + for (var key in loadingObj) { + if(loadingObj[key] !== false) { + allLoaded = false; + } + } + if(allLoaded) { + caseSettled(); + } + }, true); - if (EDIT_CASE_CONFIG.showComments) { - $scope.commentsLoading = true; - strataService.cases.comments.get($stateParams.id).then( - function (commentsJSON) { - $scope.comments = commentsJSON; - $scope.commentsLoading = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); + $scope.loadingRecWatcher = $scope.$watch('recommendationsLoading', function(newVal) { + if(newVal === false) { + caseSettled(); } - ); - } - }; + }); - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); - AlertService.clearAlerts(); - }); + $scope.$on('$destroy', function () { + // Clean up listeners + CaseService.clearCase(); + $scope.authLoginEvent(); + $scope.loadingWatcher(); + $scope.loadingRecWatcher(); + }); } - ]); -'use strict'; +]); -angular.module('RedhatAccess.cases') - .controller('EmailNotifySelect', [ +/*global angular*/ +/*jshint camelcase: false*/ +'use strict'; +angular.module('RedhatAccess.cases').controller('EmailNotifySelect', [ '$scope', '$rootScope', 'CaseService', @@ -20571,240 +3152,245 @@ angular.module('RedhatAccess.cases') 'EDIT_CASE_CONFIG', 'AUTH_EVENTS', function ($scope, $rootScope, CaseService, securityService, AlertService, strataService, $filter, RHAUtils, EDIT_CASE_CONFIG, AUTH_EVENTS) { - - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.showEmailNotifications = EDIT_CASE_CONFIG.showEmailNotifications; - - $scope.updateNotifyUsers = function () { - if (!angular.equals(CaseService.updatedNotifiedUsers, CaseService.originalNotifiedUsers)) { - - angular.forEach(CaseService.originalNotifiedUsers, function (origUser) { - var updatedUser = $filter('filter')(CaseService.updatedNotifiedUsers, origUser); - - if (RHAUtils.isEmpty(updatedUser)) { - $scope.updatingList = true; - strataService.cases.notified_users.remove(CaseService.kase.case_number, origUser).then( - function () { - $scope.updatingList = false; - CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; - }, - function (error) { - $scope.updatingList = false; - AlertService.addStrataErrorMessage(error); + $scope.securityService = securityService; + $scope.CaseService = CaseService; + $scope.showEmailNotifications = EDIT_CASE_CONFIG.showEmailNotifications; + $scope.updateNotifyUsers = function () { + if (!angular.equals(CaseService.updatedNotifiedUsers, CaseService.originalNotifiedUsers)) { + angular.forEach(CaseService.originalNotifiedUsers, function (origUser) { + var updatedUser = $filter('filter')(CaseService.updatedNotifiedUsers, origUser); + if (RHAUtils.isEmpty(updatedUser)) { + $scope.updatingList = true; + strataService.cases.notified_users.remove(CaseService.kase.case_number, origUser).then(function () { + $scope.updatingList = false; + CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; + }, function (error) { + $scope.updatingList = false; + AlertService.addStrataErrorMessage(error); + }); + } }); - } - }); - - angular.forEach(CaseService.updatedNotifiedUsers, function (updatedUser) { - var originalUser = $filter('filter')(CaseService.originalNotifiedUsers, updatedUser); - - if (RHAUtils.isEmpty(originalUser)) { - $scope.updatingList = true; - strataService.cases.notified_users.add(CaseService.kase.case_number, updatedUser).then( - function () { - CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; - $scope.updatingList = false; - }, - function (error) { - $scope.updatingList = false; - AlertService.addStrataErrorMessage(error); + angular.forEach(CaseService.updatedNotifiedUsers, function (updatedUser) { + var originalUser = $filter('filter')(CaseService.originalNotifiedUsers, updatedUser); + if (RHAUtils.isEmpty(originalUser)) { + $scope.updatingList = true; + strataService.cases.notified_users.add(CaseService.kase.case_number, updatedUser).then(function () { + CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; + $scope.updatingList = false; + }, function (error) { + $scope.updatingList = false; + AlertService.addStrataErrorMessage(error); + }); + } }); } - }); + }; + if (securityService.loginStatus.isLoggedIn) { + CaseService.populateUsers(); } - }; - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateUsers(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateUsers(); - }); + $scope.authEventDeregister = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + CaseService.populateUsers(); + }); + $scope.$on('$destroy', function () { + $scope.authEventDeregister(); + }); } - ]); -'use strict'; - -angular.module('RedhatAccess.cases') -.controller('EntitlementSelect', [ - '$scope', - 'strataService', - 'AlertService', - '$filter', - 'RHAUtils', - 'CaseService', - function ($scope, strataService, AlertService, $filter, RHAUtils, CaseService) { - - $scope.CaseService = CaseService; - CaseService.populateEntitlements(); - } ]); 'use strict'; - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.controller('ExportCSVButton', [ - '$scope', - 'strataService', - 'AlertService', - function( - $scope, - strataService, - AlertService) { - - $scope.exporting = false; - - $scope.exports = function() { - $scope.exporting = true; - strataService.cases.csv().then( - function(response) { - $scope.exporting = false; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; - } +angular.module('RedhatAccess.cases').controller('EntitlementSelect', [ + '$scope', + 'strataService', + 'AlertService', + '$filter', + 'RHAUtils', + 'CaseService', + function ($scope, strataService, AlertService, $filter, RHAUtils, CaseService) { + $scope.CaseService = CaseService; + CaseService.populateEntitlements(); + } ]); - 'use strict'; - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.controller('Group', [ - '$scope', - 'securityService', - 'SearchBoxService', - 'GroupService', - function( - $scope, - securityService, - SearchBoxService, - GroupService) { - - $scope.securityService = securityService; - - SearchBoxService.onChange = SearchBoxService.doSearch = SearchBoxService.onKeyPress = function() { - GroupService.reloadTable(); - }; - } +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').controller('ExportCSVButton', [ + '$scope', + 'strataService', + 'AlertService', + function ($scope, strataService, AlertService) { + $scope.ie8 = window.ie8; + $scope.ie9 = window.ie9; + $scope.exporting = false; + $scope.exports = function () { + $scope.exporting = true; + strataService.cases.csv().then(function (response) { + $scope.exporting = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + } +]); +'use strict'; +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').controller('Group', [ + '$scope', + '$location', + 'securityService', + 'SearchBoxService', + 'GroupService', + function ($scope, $location, securityService, SearchBoxService, GroupService) { + $scope.securityService = securityService; + $scope.onChange = SearchBoxService.onChange = SearchBoxService.doSearch = SearchBoxService.onKeyPress = function () { + GroupService.reloadTable(); + }; + $scope.$on('$destroy', function () { + $scope.onChange(); + }); + $scope.defaultCaseGroup = function(){ + $location.path('/case/group/default'); + }; + } ]); - 'use strict'; /*global $ */ +/*jshint expr: true, camelcase: false, newcap: false*/ +angular.module('RedhatAccess.cases').controller('GroupList', [ + '$rootScope', + '$scope', + 'strataService', + 'AlertService', + 'CaseService', + '$filter', + 'ngTableParams', + 'GroupService', + 'securityService', + 'SearchBoxService', + 'AUTH_EVENTS', + function ($rootScope, $scope, strataService, AlertService, CaseService, $filter, ngTableParams, GroupService, securityService, SearchBoxService, AUTH_EVENTS) { + $scope.CaseService = CaseService; + $scope.GroupService = GroupService; + $scope.listEmpty = false; + $scope.groupsOnScreen = []; + $scope.canManageGroups = false; + var reloadTable = false; + var tableBuilt = false; + $scope.groupsLoading = true; + var buildTable = function () { + $scope.tableParams = new ngTableParams({ + page: 1, + count: 10, + sorting: { name: 'asc' } + }, { + total: CaseService.groups.length, + getData: function ($defer, params) { + var orderedData = $filter('filter')(CaseService.groups, SearchBoxService.searchTerm); + orderedData = params.sorting() ? $filter('orderBy')(orderedData, params.orderBy()) : orderedData; + orderedData.length < 1 ? $scope.listEmpty = true : $scope.listEmpty = false; + var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); + $scope.tableParams.total(orderedData.length); + GroupService.groupsOnScreen = pageData; + $defer.resolve(pageData); + } + }); + $scope.tableParams.settings().$scope = $scope; + GroupService.reloadTable = function () { + $scope.tableParams.reload(); + }; + tableBuilt = true; + }; + + $scope.onMasterCheckboxClicked = function () { + for (var i = 0; i < GroupService.groupsOnScreen.length; i++) { + if (this.masterSelected) { + GroupService.groupsOnScreen[i].selected = true; + GroupService.disableDeleteGroup = false; + } else { + GroupService.groupsOnScreen[i].selected = false; + GroupService.disableDeleteGroup = true; + } + } + }; + CaseService.clearCase(); -angular.module('RedhatAccess.cases') -.controller('GroupList', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - '$filter', - 'ngTableParams', - 'GroupService', - 'SearchBoxService', - function ($scope, strataService, AlertService, CaseService, $filter, ngTableParams, GroupService, SearchBoxService) { - - $scope.CaseService = CaseService; - $scope.GroupService = GroupService; - $scope.listEmpty = false; - $scope.groupsOnScreen = []; - - var tableBuilt = false; - var buildTable = function() { - $scope.tableParams = new ngTableParams({ - page: 1, - count: 10, - sorting: { - name: 'asc' - } - }, { - total: CaseService.groups.length, - getData: function($defer, params) { - var orderedData = $filter('filter')(CaseService.groups, SearchBoxService.searchTerm); + $scope.init = function() { + strataService.groups.list().then(function (groups) { + CaseService.groups = groups; + $scope.canManageGroups = securityService.loginStatus.account.has_group_acls && securityService.loginStatus.authedUser.org_admin; + $scope.groupsLoading = false; + buildTable(); + if(reloadTable){ + //GroupService.reloadTable(); + reloadTable = false; + } + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; - orderedData = params.sorting() ? - $filter('orderBy')(orderedData, params.orderBy()) : orderedData; - - orderedData.length < 1 ? $scope.listEmpty = true : $scope.listEmpty = false; + $scope.onGroupSelected = function() { + var disableDeleteGroup = true; + for (var i = 0; i < GroupService.groupsOnScreen.length; i++) { + if (GroupService.groupsOnScreen[i].selected === true) { + disableDeleteGroup = false; + break; + } + } + GroupService.disableDeleteGroup = disableDeleteGroup; + }; - var pageData = orderedData.slice( - (params.page() - 1) * params.count(), params.page() * params.count()); + if (securityService.loginStatus.isLoggedIn) { + $scope.init(); - $scope.tableParams.total(orderedData.length); - GroupService.groupsOnScreen = pageData; - $defer.resolve(pageData); } - }); - - GroupService.reloadTable = function() { - $scope.tableParams.reload(); - }; - tableBuilt = true; - }; + $scope.authEventLogin = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.init(); + }); - $scope.groupsLoading = true; - strataService.groups.list().then( - function(groups) { - CaseService.groups = groups; - buildTable(); - $scope.groupsLoading = false; - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + CaseService.clearCase(); + $scope.groupsOnScreen = []; + GroupService.groupsOnScreen = []; + reloadTable = true; + }); - $scope.onMasterCheckboxClicked = function() { - for(var i = 0; i < GroupService.groupsOnScreen.length; i++) { - if (this.masterSelected) { - GroupService.groupsOnScreen[i].selected = true; - } else { - GroupService.groupsOnScreen[i].selected = false; - } - }; + $scope.$on('$destroy', function () { + CaseService.clearCase(); + $scope.authEventLogoutSuccess(); + $scope.authEventLogin(); + }); } - - CaseService.clearCase(); - } ]); +/*jshint camelcase: false */ 'use strict'; - -angular.module('RedhatAccess.cases') -.constant('CASE_GROUPS', { - manage: 'manage', - ungrouped: 'ungrouped' -}) -.controller('GroupSelect', [ - '$scope', - 'securityService', - 'SearchCaseService', - 'CaseService', - 'strataService', - 'AlertService', - 'CASE_GROUPS', - function ( - $scope, - securityService, - SearchCaseService, - CaseService, - strataService, - AlertService, - CASE_GROUPS) { - - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - $scope.CASE_GROUPS = CASE_GROUPS; - - CaseService.populateGroups(); - } +angular.module('RedhatAccess.cases').constant('CASE_GROUPS', { + manage: 'manage', + ungrouped: 'ungrouped' +}).controller('GroupSelect', [ + '$scope', + 'securityService', + 'SearchCaseService', + 'CaseService', + 'strataService', + 'AlertService', + 'CASE_GROUPS', + 'AUTH_EVENTS', + function ($scope, securityService, SearchCaseService, CaseService, strataService, AlertService, CASE_GROUPS, AUTH_EVENTS) { + $scope.securityService = securityService; + $scope.SearchCaseService = SearchCaseService; + $scope.CaseService = CaseService; + $scope.CASE_GROUPS = CASE_GROUPS; + + $scope.setSearchOptions = function (showsearchoptions) { + CaseService.showsearchoptions = showsearchoptions; + CaseService.buildGroupOptions(); + }; + } ]); 'use strict'; /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') - .controller('List', [ +angular.module('RedhatAccess.cases').controller('List', [ '$scope', '$filter', 'ngTableParams', @@ -20815,163 +3401,152 @@ angular.module('RedhatAccess.cases') 'CaseService', 'AUTH_EVENTS', 'SearchBoxService', - function ($scope, - $filter, - ngTableParams, - securityService, - AlertService, - $rootScope, - SearchCaseService, - CaseService, - AUTH_EVENTS, - SearchBoxService) { - $scope.SearchCaseService = SearchCaseService; - $scope.securityService = securityService; - $scope.AlertService = AlertService; - AlertService.clearAlerts(); - - var tableBuilt = false; - - var buildTable = function () { - /*jshint newcap: false*/ - $scope.tableParams = new ngTableParams({ - page: 1, - count: 10, - sorting: { - last_modified_date: 'desc' - } - }, { - total: SearchCaseService.cases.length, - getData: function ($defer, params) { - if(!SearchCaseService.allCasesDownloaded && ((params.count() * params.page()) / SearchCaseService.total >= .8)){ - SearchCaseService.doFilter().then( - function () { - $scope.tableParams.reload(); - var orderedData = params.sorting() ? - $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; - - var pageData = orderedData.slice( - (params.page() - 1) * params.count(), params.page() * params.count()); - - $scope.tableParams.total(orderedData.length); - $defer.resolve(pageData); + 'NEW_CASE_CONFIG', + function ($scope, $filter, ngTableParams, securityService, AlertService, $rootScope, SearchCaseService, CaseService, AUTH_EVENTS, SearchBoxService, NEW_CASE_CONFIG) { + $scope.SearchCaseService = SearchCaseService; + $scope.securityService = securityService; + $scope.AlertService = AlertService; + $scope.NEW_CASE_CONFIG = NEW_CASE_CONFIG; + AlertService.clearAlerts(); + var tableBuilt = false; + var buildTable = function () { + /*jshint newcap: false*/ + $scope.tableParams = new ngTableParams({ + page: 1, + count: 10, + sorting: { last_modified_date: 'desc' } + }, { + total: SearchCaseService.totalCases, + getData: function ($defer, params) { + if (!SearchCaseService.allCasesDownloaded && params.count() * params.page() >= SearchCaseService.count) { + SearchCaseService.doFilter().then(function () { + $scope.tableParams.reload(); + var orderedData = params.sorting() ? $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; + var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); + $scope.tableParams.total(SearchCaseService.totalCases); + $defer.resolve(pageData); + }); + } else { + var orderedData = params.sorting() ? $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; + var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); + $scope.tableParams.total(SearchCaseService.totalCases); + $defer.resolve(pageData); + } } - ); + }); + tableBuilt = true; + }; + SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { + SearchCaseService.clearPagination(); + if(CaseService.groups.length === 0){ + CaseService.populateGroups().then(function (){ + SearchCaseService.doFilter().then(function () { + if (!tableBuilt) { + buildTable(); + } else { + $scope.tableParams.reload(); + } + }); + }); } else { - var orderedData = params.sorting() ? - $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; - - var pageData = orderedData.slice( - (params.page() - 1) * params.count(), params.page() * params.count()); - - $scope.tableParams.total(orderedData.length); - $defer.resolve(pageData); - } - } - }); - - tableBuilt = true; - }; - - SearchBoxService.doSearch = - CaseService.onSelectChanged = - CaseService.onOwnerSelectChanged = - CaseService.onGroupSelectChanged = function () { - SearchCaseService.doFilter().then( - function () { - if (!tableBuilt) { - buildTable(); - } else { - $scope.tableParams.reload(); - } + //CaseService.buildGroupOptions(); + SearchCaseService.doFilter().then(function () { + if (!tableBuilt) { + buildTable(); + } else { + $scope.tableParams.reload(); + } + }); } - ); - }; - - /** + + }; + /** * Callback after user login. Load the cases and clear alerts */ + if (securityService.loginStatus.isLoggedIn && securityService.loginStatus.userAllowedToManageCases) { + SearchCaseService.clear(); + SearchBoxService.doSearch(); + } + $scope.listAuthEventDeregister = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + if(securityService.loginStatus.userAllowedToManageCases){ + SearchBoxService.doSearch(); + AlertService.clearAlerts(); + } + }); - if (securityService.loginStatus.isLoggedIn) { - SearchCaseService.clear(); - SearchBoxService.doSearch(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + CaseService.clearCase(); + SearchCaseService.clear(); + }); + + $scope.$on('$destroy', function () { + $scope.listAuthEventDeregister(); + CaseService.clearCase(); + }); } - ]); +]); 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('ListAttachments', [ - '$scope', - 'AttachmentsService', - function ($scope, AttachmentsService) { - - $scope.AttachmentsService = AttachmentsService; - } +angular.module('RedhatAccess.cases').controller('ListAttachments', [ + '$scope', + 'AttachmentsService', + function ($scope, AttachmentsService) { + $scope.AttachmentsService = AttachmentsService; + } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('ListBugzillas', [ - '$scope', - 'CaseService', - 'securityService', - function ( - $scope, - CaseService, - securityService) { - - $scope.CaseService = CaseService; - $scope.securityService = securityService; - } - +angular.module('RedhatAccess.cases').controller('ListBugzillas', [ + '$scope', + 'CaseService', + 'securityService', + function ($scope, CaseService, securityService) { + $scope.CaseService = CaseService; + $scope.securityService = securityService; + } ]); 'use strict'; - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.controller('ListFilter', [ - '$scope', - 'STATUS', - 'CaseService', - 'securityService', - function ($scope, - STATUS, - CaseService, - securityService) { - - $scope.securityService = securityService; - CaseService.status = STATUS.both; - } +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').controller('ListFilter', [ + '$scope', + 'STATUS', + 'CaseService', + 'securityService', + function ($scope, STATUS, CaseService, securityService) { + $scope.securityService = securityService; + CaseService.status = STATUS.both; + $scope.showsearchoptions = CaseService.showsearchoptions; + $scope.setSearchOptions = function (showsearchoptions) { + CaseService.showsearchoptions = showsearchoptions; + if(CaseService.groups.length === 0){ + CaseService.populateGroups().then(function (){ + CaseService.buildGroupOptions(); + }); + } else{ + CaseService.buildGroupOptions(); + } + }; + } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') - .controller('ListNewAttachments', [ +angular.module('RedhatAccess.cases').controller('ListNewAttachments', [ '$scope', 'AttachmentsService', 'TreeViewSelectorUtils', - function($scope, AttachmentsService, TreeViewSelectorUtils) { - $scope.AttachmentsService = AttachmentsService; - $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; - - $scope.removeLocalAttachment = function($index) { - AttachmentsService.removeUpdatedAttachment($index); - }; + function ($scope, AttachmentsService, TreeViewSelectorUtils) { + $scope.AttachmentsService = AttachmentsService; + $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; + $scope.removeLocalAttachment = function ($index) { + AttachmentsService.removeUpdatedAttachment($index); + }; } - ]); +]); 'use strict'; - -angular.module('RedhatAccess.cases') - .controller('New', [ +/*jshint camelcase: false*/ +angular.module('RedhatAccess.cases').controller('New', [ '$scope', '$state', '$q', + '$timeout', + '$sanitize', 'SearchResultsService', 'AttachmentsService', 'strataService', @@ -20985,565 +3560,576 @@ angular.module('RedhatAccess.cases') 'RHAUtils', 'NEW_DEFAULTS', 'NEW_CASE_CONFIG', - function ($scope, - $state, - $q, - SearchResultsService, - AttachmentsService, - strataService, - RecommendationsService, - CaseService, - AlertService, - securityService, - $rootScope, - AUTH_EVENTS, - $location, - RHAUtils, - NEW_DEFAULTS, - NEW_CASE_CONFIG) { - - $scope.NEW_CASE_CONFIG = NEW_CASE_CONFIG; - $scope.versions = []; - $scope.versionDisabled = true; - $scope.versionLoading = false; - $scope.incomplete = true; - $scope.submitProgress = 0; - AttachmentsService.clear(); - CaseService.clearCase(); - RecommendationsService.clear(); - SearchResultsService.clear(); - AlertService.clearAlerts(); - - $scope.CaseService = CaseService; - $scope.RecommendationsService = RecommendationsService; - $scope.securityService = securityService; - - $scope.getRecommendations = function () { - if ($scope.NEW_CASE_CONFIG.showRecommendations) { - SearchResultsService.searchInProgress.value = true; - RecommendationsService.populateRecommendations(5).then( - function () { - SearchResultsService.clear(); + '$http', + function ($scope, $state, $q, $timeout, $sanitize, SearchResultsService, AttachmentsService, strataService, RecommendationsService, CaseService, AlertService, securityService, $rootScope, AUTH_EVENTS, $location, RHAUtils, NEW_DEFAULTS, NEW_CASE_CONFIG, $http) { + $scope.NEW_CASE_CONFIG = NEW_CASE_CONFIG; + $scope.versions = []; + $scope.versionDisabled = true; + $scope.versionLoading = false; + $scope.incomplete = true; + $scope.submitProgress = 0; + AttachmentsService.clear(); + CaseService.clearCase(); + RecommendationsService.clear(); + SearchResultsService.clear(); + AlertService.clearAlerts(); + $scope.CaseService = CaseService; + $scope.RecommendationsService = RecommendationsService; + $scope.securityService = securityService; + + // Instantiate these variables outside the watch + var waiting = false; + $scope.$watch('CaseService.kase.description + CaseService.kase.summary', function () { + if (!waiting){ + waiting = true; + $timeout(function() { + waiting = false; + $scope.getRecommendations(); + }, 500); // delay 500 ms + } + }); - RecommendationsService.recommendations.forEach( - function (recommendation) { - SearchResultsService.add(recommendation); + $scope.getRecommendations = function () { + if ($scope.NEW_CASE_CONFIG.showRecommendations) { + SearchResultsService.searchInProgress.value = true; + var numRecommendations = 5; + if($scope.NEW_CASE_CONFIG.isPCM){ + numRecommendations = 30; + RecommendationsService.populatePCMRecommendations(numRecommendations).then(function () { + SearchResultsService.clear(); + RecommendationsService.recommendations.forEach(function (recommendation) { + try { + recommendation.abstract = $sanitize(recommendation.abstract); + } + catch(err) { + recommendation.abstract = ''; + } + SearchResultsService.add(recommendation); + }); + SearchResultsService.searchInProgress.value = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + SearchResultsService.searchInProgress.value = false; + }); + } else { + RecommendationsService.populateRecommendations(numRecommendations).then(function () { + SearchResultsService.clear(); + RecommendationsService.recommendations.forEach(function (recommendation) { + SearchResultsService.add(recommendation); + }); + SearchResultsService.searchInProgress.value = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + SearchResultsService.searchInProgress.value = false; + }); } - ); - SearchResultsService.searchInProgress.value = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); } - ); - } - }; - - CaseService.onOwnerSelectChanged = function () { - if (CaseService.owner != null) { - CaseService.populateEntitlements(CaseService.owner); - CaseService.populateGroups(CaseService.owner); - } - - CaseService.validateNewCasePage1(); - }; - - /** - * Populate the selects - */ - $scope.initSelects = function () { - $scope.productsLoading = true; - strataService.products.list().then( - function (products) { - $scope.products = products; - $scope.productsLoading = false; - - if (RHAUtils.isNotEmpty(NEW_DEFAULTS.product)) { - CaseService.kase.product = { - name: NEW_DEFAULTS.product, - code: NEW_DEFAULTS.product - }; - $scope.getRecommendations(); - $scope.getProductVersions(CaseService.kase.product); + }; + CaseService.onOwnerSelectChanged = function () { + if (CaseService.owner !== undefined) { + CaseService.populateEntitlements(CaseService.owner); + CaseService.populateGroups(CaseService.owner); } - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); + CaseService.validateNewCasePage1(); + }; - $scope.severitiesLoading = true; - strataService.values.cases.severity().then( - function (severities) { - CaseService.severities = severities; - CaseService.kase.severity = severities[severities.length - 1]; - $scope.severitiesLoading = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); + /** + * Add the top sorted products to list + */ + $scope.buildProductOptions = function(originalProductList) { + var productOptions = []; + var productSortList = []; + if($scope.NEW_CASE_CONFIG.isPCM){ + $http.get($scope.NEW_CASE_CONFIG.productSortListFile).then(function (response) { + if (response.status === 200 && response.data !== undefined) { + productSortList = response.data.split(','); + + for(var i = 0; i < productSortList.length; i++) { + for (var j = 0 ; j < originalProductList.length ; j++) { + if (productSortList[i] === originalProductList[j].code) { + var sortProduct = productSortList[i]; + productOptions.push({ + value: sortProduct, + label: sortProduct + }); + break; + } + } + } - $scope.groupsLoading = true; - strataService.groups.list().then( - function (groups) { - /*jshint camelcase: false*/ - CaseService.groups = groups; - for (var i = 0; i < groups.length; i++) { - if (groups[i].is_default) { - CaseService.kase.group = groups[i]; - break; - } - } - $scope.groupsLoading = false; - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; + var sep = '────────────────────────────────────────'; + if (productOptions.length > 0) { + productOptions.push({ + isDisabled: true, + label: sep + }); + } - $scope.initDescription = function () { - var searchObject = $location.search(); + angular.forEach(originalProductList, function(product){ + productOptions.push({ + value: product.code, + label: product.name + }); + }, this); - var setDesc = function (desc) { - CaseService.kase.description = desc; - $scope.getRecommendations(); + $scope.products = productOptions; + } else { + angular.forEach(originalProductList, function(product){ + productOptions.push({ + value: product.code, + label: product.name + }); + }, this); + $scope.products = productOptions; + } + }); + } else { + angular.forEach(originalProductList, function(product){ + productOptions.push({ + value: product.code, + label: product.name + }); + }, this); + $scope.products = productOptions; + } }; - if (searchObject.data) { - setDesc(searchObject.data); - } else { - //angular does not handle params before hashbang - //@see https://github.com/angular/angular.js/issues/6172 - var queryParamsStr = window.location.search.substring(1); - var parameters = queryParamsStr.split('&'); - for (var i = 0; i < parameters.length; i++) { - var parameterName = parameters[i].split('='); - if (parameterName[0] === 'data') { - setDesc(decodeURIComponent(parameterName[1])); + /** + * Populate the selects + */ + $scope.initSelects = function () { + CaseService.clearCase(); + $scope.productsLoading = true; + strataService.products.list(securityService.loginStatus.authedUser.sso_username).then(function (products) { + $scope.buildProductOptions(products); + $scope.productsLoading = false; + if (RHAUtils.isNotEmpty(NEW_DEFAULTS.product)) { + for(var i = 0; i < $scope.products.length; i++){ + if($scope.products[i].label === NEW_DEFAULTS.product){ + CaseService.kase.product = $scope.products[i].value; + break; + } + } + $scope.getRecommendations(); + $scope.getProductVersions(CaseService.kase.product); + } + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + $scope.severitiesLoading = true; + strataService.values.cases.severity().then(function (severities) { + CaseService.severities = severities; + CaseService.kase.severity = severities[severities.length - 1]; + $scope.severitiesLoading = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + $scope.groupsLoading = true; + CaseService.populateGroups().then(function (groups) { + $scope.groupsLoading = false; + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + }; + $scope.initDescription = function () { + var searchObject = $location.search(); + var setDesc = function (desc) { + CaseService.kase.description = desc; + $scope.getRecommendations(); + }; + if (searchObject.data) { + setDesc(searchObject.data); + } else { + //angular does not handle params before hashbang + //@see https://github.com/angular/angular.js/issues/6172 + var queryParamsStr = window.location.search.substring(1); + var parameters = queryParamsStr.split('&'); + for (var i = 0; i < parameters.length; i++) { + var parameterName = parameters[i].split('='); + if (parameterName[0] === 'data') { + setDesc(decodeURIComponent(parameterName[1])); + } + } } - } + }; + if (securityService.loginStatus.isLoggedIn) { + $scope.initSelects(); + $scope.initDescription(); } - }; - - if (securityService.loginStatus.isLoggedIn) { - $scope.initSelects(); - $scope.initDescription(); - } - - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.initSelects(); - $scope.initDescription(); - AlertService.clearAlerts(); - }); - + $scope.authLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + $scope.initSelects(); + $scope.initDescription(); + AlertService.clearAlerts(); + RecommendationsService.failureCount = 0; + }); - /** + $scope.$on('$destroy', function () { + $scope.authLoginSuccess(); + }); + /** * Retrieve product's versions from strata * * @param product */ - $scope.getProductVersions = function (product) { - CaseService.kase.version = ''; - $scope.versionDisabled = true; - $scope.versionLoading = true; + $scope.getProductVersions = function (product) { + CaseService.kase.version = ''; + $scope.versionDisabled = true; + $scope.versionLoading = true; + strataService.products.versions(product).then(function (response) { + $scope.versions = response; + CaseService.validateNewCasePage1(); + $scope.versionDisabled = false; + $scope.versionLoading = false; + if (RHAUtils.isNotEmpty(NEW_DEFAULTS.version)) { + CaseService.kase.version = NEW_DEFAULTS.version; + $scope.getRecommendations(); + } + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); - strataService.products.versions(product.code).then( - function (response) { - $scope.versions = response; - CaseService.validateNewCasePage1(); - $scope.versionDisabled = false; - $scope.versionLoading = false; + //Retrieve the product detail, basically finding the attachment artifact + $scope.fetchProductDetail(product); + }; - if (RHAUtils.isNotEmpty(NEW_DEFAULTS.version)) { - CaseService.kase.version = NEW_DEFAULTS.version; - $scope.getRecommendations(); - } - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; + /** + * Fetch the product details for the selected product + **/ + $scope.fetchProductDetail = function (productCode) { + AttachmentsService.suggestedArtifact = {}; + strataService.products.get(productCode).then(angular.bind(this, function (product) { + if (product !== undefined && product.suggested_artifacts !== undefined && product.suggested_artifacts.suggested_artifact !== undefined) { + if (product.suggested_artifacts.suggested_artifact.length > 0) { + var description = product.suggested_artifacts.suggested_artifact[0].description; + if (description.indexOf(' -1) { + description = description.replace(" 0 || AttachmentsService.hasBackEndSelections()) && - NEW_CASE_CONFIG.showAttachments) { - - AttachmentsService.updateAttachments(caseNumber).then( - function () { - redirectToCase(caseNumber); + /** + * Create the case with attachments + */ + $scope.doSubmit = function ($event) { + if (window.chrometwo_require !== undefined) { + chrometwo_require(['analytics/main'], function (analytics) { + analytics.trigger('OpenSupportCaseSubmit', $event); }); - } else { - redirectToCase(caseNumber); } - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; + /*jshint camelcase: false */ + var caseJSON = { + 'product': CaseService.kase.product, + 'version': CaseService.kase.version, + 'summary': CaseService.kase.summary, + 'description': CaseService.kase.description, + 'severity': CaseService.kase.severity.name + }; + if (RHAUtils.isNotEmpty(CaseService.group)) { + caseJSON.folderNumber = CaseService.group; + } + if (RHAUtils.isNotEmpty(CaseService.entitlement)) { + caseJSON.entitlement = {}; + caseJSON.entitlement.sla = CaseService.entitlement; + } + if (RHAUtils.isNotEmpty(CaseService.account)) { + caseJSON.accountNumber = CaseService.account.number; + } + if (CaseService.fts) { + caseJSON.fts = true; + if (CaseService.fts_contact) { + caseJSON.contactInfo24X7 = CaseService.fts_contact; + } + } + if (RHAUtils.isNotEmpty(CaseService.owner)) { + caseJSON.contactSsoUsername = CaseService.owner; + } + $scope.submittingCase = true; + AlertService.addWarningMessage('Creating case...'); + var redirectToCase = function (caseNumber) { + $state.go('edit', { id: caseNumber }); + AlertService.clearAlerts(); + $scope.submittingCase = false; + }; + strataService.cases.post(caseJSON).then(function (caseNumber) { + AlertService.clearAlerts(); + AlertService.addSuccessMessage('Successfully created case number ' + caseNumber); + if ((AttachmentsService.updatedAttachments.length > 0 || AttachmentsService.hasBackEndSelections()) && NEW_CASE_CONFIG.showAttachments) { + AttachmentsService.updateAttachments(caseNumber).then(function () { + redirectToCase(caseNumber); + }, function (error) { + AlertService.addStrataErrorMessage(error); + $scope.submittingCase = false; + }); + } else { + redirectToCase(caseNumber); + } + }, function (error) { + AlertService.addStrataErrorMessage(error); + $scope.submittingCase = false; + }); + }; + $scope.gotoPage(1); - $scope.gotoPage(1); + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + CaseService.clearCase(); + }); + $scope.$on('$destroy', function () { + CaseService.clearCase(); + }); } - ]); -'use strict'; +]); -angular.module('RedhatAccess.cases') - .controller('OwnerSelect', [ +/*global angular*/ +'use strict'; +angular.module('RedhatAccess.cases').controller('OwnerSelect', [ '$scope', '$rootScope', 'securityService', 'AUTH_EVENTS', 'SearchCaseService', 'CaseService', - function ( - $scope, - $rootScope, - securityService, - AUTH_EVENTS, - SearchCaseService, - CaseService) { - - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateUsers(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateUsers(); - }); + function ($scope, $rootScope, securityService, AUTH_EVENTS, SearchCaseService, CaseService) { + $scope.securityService = securityService; + $scope.SearchCaseService = SearchCaseService; + $scope.CaseService = CaseService; } - ]); -'use strict'; - -angular.module('RedhatAccess.cases') -.controller('ProductSelect', [ - '$scope', - 'securityService', - 'SearchCaseService', - 'CaseService', - 'strataService', - 'AlertService', - function ( - $scope, - securityService, - SearchCaseService, - CaseService, - strataService, - AlertService) { - - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - - $scope.productsLoading = true; - strataService.products.list().then( - function(products) { - $scope.productsLoading = false; - CaseService.products = products; - }, - function(error) { - $scope.productsLoading = false; - AlertService.addStrataErrorMessage(error); - }) - } ]); 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('RecommendationsSection', [ - 'RecommendationsService', - '$scope', - 'strataService', - 'CaseService', - 'AlertService', - function( - RecommendationsService, - $scope, - strataService, - CaseService, - AlertService) { - - $scope.RecommendationsService = RecommendationsService; - - $scope.recommendationsPerPage = 4; - $scope.maxRecommendationsSize = 10; - - $scope.selectRecommendationsPage = function(pageNum) { - //filter out pinned recommendations - angular.forEach(RecommendationsService.pinnedRecommendations, - function(pinnedRec) { - angular.forEach(RecommendationsService.recommendations, - function(rec, index) { - if (angular.equals(rec.id, pinnedRec.id)) { - RecommendationsService.recommendations.splice(index, 1); - } - } - ); - } - ); - - angular.forEach(RecommendationsService.handPickedRecommendations, - function(handPickedRec) { - angular.forEach(RecommendationsService.recommendations, - function(rec, index) { - if (angular.equals(rec.id, handPickedRec.id)) { - RecommendationsService.recommendations.splice(index, 1); - } - } - ); - } - ); - - var recommendations = RecommendationsService.pinnedRecommendations.concat( - RecommendationsService.recommendations); - recommendations = RecommendationsService.handPickedRecommendations.concat( - recommendations); - var start = $scope.recommendationsPerPage * (pageNum - 1); - var end = start + $scope.recommendationsPerPage; - end = end > recommendations.length ? recommendations.length : end; - $scope.recommendationsOnScreen = recommendations.slice(start, end); - }; - - $scope.currentRecPin; - $scope.pinRecommendation = function(recommendation, $index, $event) { - $scope.currentRecPin = recommendation; - $scope.currentRecPin.pinning = true; - - var doPut = function(linked) { - var recJSON = { - recommendations: { - recommendation: [ - { - linked: linked.toString(), - resourceId: recommendation.id, - resourceType: 'Solution' - } - ] - } +/*jshint camelcase: false, expr: true*/ +angular.module('RedhatAccess.cases').controller('PcmRecommendationsController', [ + '$scope', + '$location', + 'SearchResultsService', + 'SEARCH_CONFIG', + 'securityService', + 'AlertService', + function ($scope, $location, SearchResultsService, SEARCH_CONFIG, securityService, AlertService) { + $scope.SearchResultsService = SearchResultsService; + $scope.results = {}; + $scope.selectedSolution = SearchResultsService.currentSelection; + $scope.searchInProgress = SearchResultsService.searchInProgress; + $scope.currentSearchData = SearchResultsService.currentSearchData; + $scope.itemsPerPage = 3; + $scope.maxPagerSize = 5; + $scope.currentPage = 1; + $scope.selectPage = function (pageNum) { + + var start = $scope.itemsPerPage * (pageNum - 1); + var end = start + $scope.itemsPerPage; + end = end > SearchResultsService.results.length ? SearchResultsService.results.length : end; + $scope.results = SearchResultsService.results.slice(start, end); }; - - strataService.cases.put(CaseService.kase.case_number, recJSON).then( - function(response) { - if (!$scope.currentRecPin.pinned) { - //not currently pinned, so add it to the pinned list - RecommendationsService.pinnedRecommendations.push($scope.currentRecPin); - } else { - //currently pinned, so remove from pinned list - angular.forEach(RecommendationsService.pinnedRecommendations, - function(rec, index) { - if (rec.id === $scope.currentRecPin.id) { - RecommendationsService.pinnedRecommendations.splice(index, 1); - } - }); - - //add the de-pinned rec to the top of the list - //this allows the user to still view the rec, or re-pin it - RecommendationsService.recommendations.splice(0, 0, $scope.currentRecPin); - } - - $scope.currentRecPin.pinning = false; - $scope.currentRecPin.pinned = !$scope.currentRecPin.pinned; - $scope.selectPageOne(); - }, - function(error) { - $scope.currentRecPin.pinning = false; - AlertService.addStrataErrorMessage(error); + $scope.triggerAnalytics = function ($event) { + if (this.isopen && window.chrometwo_require !== undefined && $location.path() === '/case/new') { + chrometwo_require(['analytics/main'], function (analytics) { + analytics.trigger('OpenSupportCaseRecommendationClick', $event); + }); } - ); - } - - recommendation.pinned ? doPut(false) : doPut(true); - }; + }; + $scope.$watch(function () { + return SearchResultsService.currentSelection; + }, function (newVal) { + $scope.selectedSolution = newVal; + }); - $scope.selectPageOne = function() { - $scope.selectRecommendationsPage(1); - $scope.currentRecommendationPage = 1; - }; + $scope.$watch(function () { + return SearchResultsService.results; + }, function () { + $scope.currentPage = 1; + $scope.selectPage($scope.currentPage); + }, true); + } +]); - $scope.triggerAnalytics = function($event) { - if(window.portal){ - chrometwo_require(['analytics/main'], function (analytics) { - analytics.trigger('OpenSupportCaseRecommendationClick', $event); +'use strict'; +angular.module('RedhatAccess.cases').controller('ProductSelect', [ + '$scope', + 'securityService', + 'SearchCaseService', + 'CaseService', + 'strataService', + 'AlertService', + function ($scope, securityService, SearchCaseService, CaseService, strataService, AlertService) { + $scope.securityService = securityService; + $scope.SearchCaseService = SearchCaseService; + $scope.CaseService = CaseService; + $scope.productsLoading = true; + strataService.products.list().then(function (products) { + $scope.productsLoading = false; + CaseService.products = products; + }, function (error) { + $scope.productsLoading = false; + AlertService.addStrataErrorMessage(error); }); - } } - - RecommendationsService.setPopulateCallback($scope.selectPageOne); - } +]); +'use strict'; +/*jshint camelcase: false*/ +angular.module('RedhatAccess.cases').controller('RecommendationsSection', [ + 'RecommendationsService', + '$scope', + 'strataService', + 'CaseService', + 'AlertService', + function (RecommendationsService, $scope, strataService, CaseService, AlertService) { + $scope.RecommendationsService = RecommendationsService; + $scope.currentRecPin = {}; + $scope.pinRecommendation = function (recommendation, $index, $event) { + $scope.currentRecPin = recommendation; + $scope.currentRecPin.pinning = true; + var doPut = function (linked) { + var recJSON = { + recommendations: { + recommendation: [{ + linked: linked.toString(), + resourceId: recommendation.id, + resourceType: 'Solution' + }] + } + }; + strataService.cases.put(CaseService.kase.case_number, recJSON).then(function (response) { + if (!$scope.currentRecPin.pinned) { + //not currently pinned, so add it to the pinned list + RecommendationsService.pinnedRecommendations.push($scope.currentRecPin); + } else { + //currently pinned, so remove from pinned list + angular.forEach(RecommendationsService.pinnedRecommendations, function (rec, index) { + if (rec.id === $scope.currentRecPin.id) { + RecommendationsService.pinnedRecommendations.splice(index, 1); + } + }); + //add the de-pinned rec to the top of the list + //this allows the user to still view the rec, or re-pin it + RecommendationsService.recommendations.splice(0, 0, $scope.currentRecPin); + } + $scope.currentRecPin.pinning = false; + $scope.currentRecPin.pinned = !$scope.currentRecPin.pinned; + RecommendationsService.selectPage(1); + }, function (error) { + $scope.currentRecPin.pinning = false; + AlertService.addStrataErrorMessage(error); + }); + }; + if (recommendation.pinned) { + doPut(false); + } else { + doPut(true); + } + }; + $scope.triggerAnalytics = function ($event) { + if (window.chrometwo_require !== undefined) { + chrometwo_require(['analytics/main'], function (analytics) { + analytics.trigger('CaseViewRecommendationClick', $event); + }); + } + }; + } ]); 'use strict'; /*global $ */ - - -angular.module('RedhatAccess.cases') -.controller('RequestManagementEscalationModal', [ - '$scope', - '$modalInstance', - 'AlertService', - 'CaseService', - 'strataService', - '$q', - '$stateParams', - function ($scope, $modalInstance, AlertService, CaseService, strataService, $q, $stateParams) { - - $scope.CaseService = CaseService; - $scope.commentText = CaseService.commentText; - - $scope.submittingRequest = false; - $scope.submitRequestClick = angular.bind($scope, function(commentText) { - $scope.submittingRequest = true; - var promises = []; - - var fullComment = 'Request Management Escalation: ' + commentText; - var postComment; - if (CaseService.draftComment) { - postComment = strataService.cases.comments.put(CaseService.kase.case_number, fullComment, false, CaseService.draftComment.id); - } else { - postComment = strataService.cases.comments.post(CaseService.kase.case_number, fullComment, false); - } - postComment.then( - function(response) { - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(postComment); - - var caseJSON = { - 'escalated': true - }; - var updateCase = strataService.cases.put(CaseService.kase.case_number, caseJSON); - updateCase.then( - function(response) { - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(updateCase); - - var masterPromise = $q.all(promises); - masterPromise.then( - function(response) { - CaseService.populateComments($stateParams.id).then( - function(comments) { - CaseService.refreshComments(); - $scope.closeModal(); - $scope.submittingRequest = false; - } - ); - }, - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - return masterPromise; - }); - - $scope.closeModal = function() { - $modalInstance.close(); - }; - } +/*jshint camelcase: false*/ +angular.module('RedhatAccess.cases').controller('RequestManagementEscalationModal', [ + '$scope', + '$modalInstance', + 'AlertService', + 'CaseService', + 'strataService', + '$q', + '$stateParams', + 'RHAUtils', + function ($scope, $modalInstance, AlertService, CaseService, strataService, $q, $stateParams, RHAUtils) { + $scope.CaseService = CaseService; + $scope.submittingRequest = false; + $scope.disableSubmitRequest = true; + $scope.submitRequestClick = angular.bind($scope, function (commentText) { + $scope.submittingRequest = true; + var promises = []; + var fullComment = 'Request Management Escalation: ' + commentText; + var postComment; + if (CaseService.draftComment) { + postComment = strataService.cases.comments.put(CaseService.kase.case_number, fullComment, false, CaseService.draftComment.id); + } else { + postComment = strataService.cases.comments.post(CaseService.kase.case_number, fullComment, true, false); + } + postComment.then(function (response) { + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(postComment); + var caseJSON = { 'escalated': true }; + var updateCase = strataService.cases.put(CaseService.kase.case_number, caseJSON); + updateCase.then(function (response) { + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(updateCase); + var masterPromise = $q.all(promises); + masterPromise.then(function (response) { + CaseService.populateComments($stateParams.id).then(function (comments) { + $scope.closeModal(); + $scope.submittingRequest = false; + }); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + return masterPromise; + }); + $scope.closeModal = function () { + CaseService.escalationCommentText = undefined; + $modalInstance.close(); + }; + $scope.onNewEscalationComment = function () { + if (RHAUtils.isNotEmpty(CaseService.escalationCommentText) && !$scope.submittingRequest) { + $scope.disableSubmitRequest = false; + } else if (RHAUtils.isEmpty(CaseService.escalationCommentText)) { + $scope.disableSubmitRequest = true; + } + }; + } ]); 'use strict'; - -angular.module('RedhatAccess.cases') - .controller('Search', [ +angular.module('RedhatAccess.cases').controller('Search', [ '$scope', '$rootScope', 'AUTH_EVENTS', @@ -21553,686 +4139,581 @@ angular.module('RedhatAccess.cases') 'STATUS', 'SearchBoxService', 'AlertService', - function ( - $scope, - $rootScope, - AUTH_EVENTS, - securityService, - SearchCaseService, - CaseService, - STATUS, - SearchBoxService, - AlertService) { - - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - - $scope.itemsPerPage = 10; - $scope.maxPagerSize = 5; - - $scope.selectPage = function (pageNum) { - if(!SearchCaseService.allCasesDownloaded && (($scope.itemsPerPage * pageNum) / SearchCaseService.total >= .8)){ - SearchCaseService.doFilter().then( - function () { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > SearchCaseService.cases.length ? - SearchCaseService.cases.length : end; - - $scope.casesOnScreen = - SearchCaseService.cases.slice(start, end); + function ($scope, $rootScope, AUTH_EVENTS, securityService, SearchCaseService, CaseService, STATUS, SearchBoxService, AlertService) { + $scope.securityService = securityService; + $scope.SearchCaseService = SearchCaseService; + $scope.CaseService = CaseService; + $scope.itemsPerPage = 10; + $scope.maxPagerSize = 5; + $scope.selectPage = function (pageNum) { + if (!SearchCaseService.allCasesDownloaded && $scope.itemsPerPage * pageNum / SearchCaseService.total >= 0.8) { + SearchCaseService.doFilter().then(function () { + var start = $scope.itemsPerPage * (pageNum - 1); + var end = start + $scope.itemsPerPage; + end = end > SearchCaseService.cases.length ? SearchCaseService.cases.length : end; + $scope.casesOnScreen = SearchCaseService.cases.slice(start, end); + }); + } else { + var start = $scope.itemsPerPage * (pageNum - 1); + var end = start + $scope.itemsPerPage; + end = end > SearchCaseService.cases.length ? SearchCaseService.cases.length : end; + $scope.casesOnScreen = SearchCaseService.cases.slice(start, end); } - ); - } else { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > SearchCaseService.cases.length ? - SearchCaseService.cases.length : end; - - $scope.casesOnScreen = - SearchCaseService.cases.slice(start, end); + }; + SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { + SearchCaseService.clearPagination(); + SearchCaseService.doFilter().then(function () { + $scope.selectPage(1); + }); + }; + if (securityService.loginStatus.isLoggedIn) { + CaseService.clearCase(); + SearchCaseService.clear(); + SearchBoxService.doSearch(); } - }; - - SearchBoxService.doSearch = - CaseService.onSelectChanged = - CaseService.onOwnerSelectChanged = - CaseService.onGroupSelectChanged = function () { - SearchCaseService.doFilter().then( - function () { - $scope.selectPage(1); - } - ); - }; - - if (securityService.loginStatus.isLoggedIn) { - CaseService.clearCase(); - SearchCaseService.clear(); - SearchBoxService.doSearch(); - } - - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); - - - - $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { - CaseService.clearCase(); - SearchCaseService.clear(); - }); + $scope.authEventLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + SearchBoxService.doSearch(); + AlertService.clearAlerts(); + }); + $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { + CaseService.clearCase(); + SearchCaseService.clear(); + }); + $scope.$on('$destroy', function () { + $scope.authEventLoginSuccess(); + $scope.authEventLogoutSuccess(); + }); + } +]); +'use strict'; +angular.module('RedhatAccess.cases').controller('SearchBox', [ + '$scope', + 'SearchBoxService', + 'securityService', + function ($scope, SearchBoxService, securityService) { + $scope.securityService = securityService; + $scope.SearchBoxService = SearchBoxService; + $scope.onFilterKeyPress = function ($event) { + if ($event.keyCode === 13) { + SearchBoxService.doSearch(); + } else if (angular.isFunction(SearchBoxService.onKeyPress)) { + SearchBoxService.onKeyPress(); + } + }; } - ]); -'use strict'; - -angular.module('RedhatAccess.cases') -.controller('SearchBox', [ - '$scope', - 'SearchBoxService', - 'securityService', - function ( - $scope, - SearchBoxService, - securityService) { - - $scope.securityService = securityService; - $scope.SearchBoxService = SearchBoxService; - - $scope.onFilterKeyPress = function($event) { - if ($event.keyCode === 13) { - SearchBoxService.doSearch(); - } else if (angular.isFunction(SearchBoxService.onKeyPress)){ - SearchBoxService.onKeyPress(); - } - }; - } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('SeveritySelect', [ - '$scope', - 'securityService', - 'strataService', - 'CaseService', - 'AlertService', - function ( - $scope, - securityService, - strataService, - CaseService, - AlertService) { - - $scope.securityService = securityService; - $scope.CaseService = CaseService; - - $scope.severitiesLoading = true; - strataService.values.cases.severity().then( - function(severities) { - $scope.severitiesLoading = false; - CaseService.severities = severities; - }, - function(error) { - $scope.severitiesLoading = false; - AlertService.addStrataErrorMessage(error); - }); - } +angular.module('RedhatAccess.cases').controller('SeveritySelect', [ + '$scope', + 'securityService', + 'strataService', + 'CaseService', + 'AlertService', + function ($scope, securityService, strataService, CaseService, AlertService) { + $scope.securityService = securityService; + $scope.CaseService = CaseService; + $scope.severitiesLoading = true; + strataService.values.cases.severity().then(function (severities) { + $scope.severitiesLoading = false; + CaseService.severities = severities; + }, function (error) { + $scope.severitiesLoading = false; + AlertService.addStrataErrorMessage(error); + }); + } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('StatusSelect', [ - '$scope', - 'securityService', - 'CaseService', - 'STATUS', - function ( - $scope, - securityService, - CaseService, - STATUS) { - - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.STATUS = STATUS; - - $scope.statuses = [ - { - name: 'Open and Closed', - value: STATUS.both - }, - { - name: 'Open', - value: STATUS.open - }, - { - name: 'Closed', - value: STATUS.closed - } - ]; - } +angular.module('RedhatAccess.cases').controller('StatusSelect', [ + '$scope', + 'securityService', + 'CaseService', + 'STATUS', + function ($scope, securityService, CaseService, STATUS) { + $scope.securityService = securityService; + $scope.CaseService = CaseService; + $scope.STATUS = STATUS; + $scope.statuses = [ + { + name: 'Open and Closed', + value: STATUS.both + }, + { + name: 'Open', + value: STATUS.open + }, + { + name: 'Closed', + value: STATUS.closed + } + ]; + } ]); 'use strict'; - -angular.module('RedhatAccess.cases') -.controller('TypeSelect', [ - '$scope', - 'securityService', - 'CaseService', - 'strataService', - 'AlertService', - function ( - $scope, - securityService, - CaseService, - strataService, - AlertService) { - - $scope.securityService = securityService; - $scope.CaseService = CaseService; - - $scope.typesLoading = true; - strataService.values.cases.types().then( - function(types) { - $scope.typesLoading = false; - CaseService.types = types; - }, - function(error) { - $scope.typesLoading = false; - AlertService.addStrataErrorMessage(error); - }) - } +angular.module('RedhatAccess.cases').controller('TypeSelect', [ + '$scope', + 'securityService', + 'CaseService', + 'strataService', + 'AlertService', + function ($scope, securityService, CaseService, strataService, AlertService) { + $scope.securityService = securityService; + $scope.CaseService = CaseService; + $scope.typesLoading = true; + strataService.values.cases.types().then(function (types) { + $scope.typesLoading = false; + CaseService.types = types; + }, function (error) { + $scope.typesLoading = false; + AlertService.addStrataErrorMessage(error); + }); + } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaAccountselect', function () { - return { - templateUrl: 'cases/views/accountSelect.html', - restrict: 'A', - controller: 'AccountSelect' - }; +angular.module('RedhatAccess.cases').directive('rhaAccountselect', function () { + return { + templateUrl: 'cases/views/accountSelect.html', + restrict: 'A', + controller: 'AccountSelect' + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaAddcommentsection', function () { - return { - templateUrl: 'cases/views/addCommentSection.html', - restrict: 'A', - controller: 'AddCommentSection' - }; +angular.module('RedhatAccess.cases').directive('rhaAddcommentsection', function () { + return { + templateUrl: 'cases/views/addCommentSection.html', + restrict: 'A', + controller: 'AddCommentSection' + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaAttachlocalfile', function () { - return { - templateUrl: 'cases/views/attachLocalFile.html', - restrict: 'A', - controller: 'AttachLocalFile', - scope: { - disabled: '=' - } - }; +angular.module('RedhatAccess.cases').directive('rhaAttachlocalfile', function () { + return { + templateUrl: 'cases/views/attachLocalFile.html', + restrict: 'A', + controller: 'AttachLocalFile', + scope: { disabled: '=' } + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaAttachproductlogs', function () { - return { - templateUrl: 'cases/views/attachProductLogs.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaAttachproductlogs', function () { + return { + templateUrl: 'cases/views/attachProductLogs.html', + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaCaseattachments', function () { - return { - templateUrl: 'cases/views/attachmentsSection.html', - restrict: 'A', - controller: 'AttachmentsSection', - scope: { - loading: '=' - }, - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaCaseattachments', function () { + return { + templateUrl: 'cases/views/attachmentsSection.html', + restrict: 'A', + controller: 'AttachmentsSection', + scope: { loading: '=' }, + link: function postLink(scope, element, attrs) { + } + }; }); - 'use strict'; /*jshint unused:vars */ +angular.module('RedhatAccess.cases').directive('rhaChatbutton', function () { + return { + scope: {}, + templateUrl: 'cases/views/chatButton.html', + restrict: 'A', + controller: 'ChatButton', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; +}); -angular.module('RedhatAccess.cases') - .directive('rhaChatbutton', function () { - return { - scope: {}, - templateUrl: 'cases/views/chatButton.html', - restrict: 'A', - controller: 'ChatButton' - }; - }); 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') - .directive('rhaCasecomments', function() { +angular.module('RedhatAccess.cases').directive('rhaCasecomments', ['$location','$anchorScroll' ,function ($location, $anchorScroll) { return { - templateUrl: 'cases/views/commentsSection.html', - controller: 'CommentsSection', - scope: { - loading: '=' - }, - restrict: 'A', - link: function postLink(scope, element, attrs) {} + templateUrl: 'cases/views/commentsSection.html', + controller: 'CommentsSection', + scope: { loading: '=' }, + restrict: 'A', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + scope.commentReply = function(id) { + var text = $('#'+id+' .pcmTextBlock').text(); + var person = $('#'+id+' .personNameBlock').text(); + var originalText = $('#case-comment-box').val(); + var lines = text.split(/\n/); + text = '(In reply to ' + person + ')\n'; + for (var i = 0, max = lines.length; i < max; i++) { + text = text + '> '+ lines[i] + '\n'; + } + if (originalText.trim() !== '') { + text = '\n' + text; + } + $('#case-comment-box').val($('#case-comment-box').val()+text).keyup(); + + //Copying the code from the link to comment method + var old = $location.hash(); + $location.hash('case-comment-box'); + $anchorScroll(); + $location.hash(old); + $location.search('commentBox', 'commentBox'); + }; + } }; - }); +}]); 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaCompactcaselist', function () { - return { - templateUrl: 'cases/views/compactCaseList.html', - controller: 'CompactCaseList', - scope: { - }, - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaCompactcaselist', function () { + return { + templateUrl: 'cases/views/compactCaseList.html', + controller: 'CompactCaseList', + scope: {}, + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); - - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaCreategroupbutton', function () { - return { - templateUrl: 'cases/views/createGroupButton.html', - restrict: 'A', - controller: 'CreateGroupButton' - }; +angular.module('RedhatAccess.cases').directive('rhaCreategroupbutton', function () { + return { + templateUrl: 'cases/views/createGroupButton.html', + restrict: 'A', + controller: 'CreateGroupButton' + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaDeletegroupbutton', function () { - return { - templateUrl: 'cases/views/deleteGroupButton.html', - restrict: 'A', - controller: 'DeleteGroupButton' - }; +angular.module('RedhatAccess.cases').directive('rhaDeletegroupbutton', function () { + return { + templateUrl: 'cases/views/deleteGroupButton.html', + restrict: 'A', + controller: 'DeleteGroupButton' + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') - .directive('rhaCasedescription', function() { +angular.module('RedhatAccess.cases').directive('rhaCasedescription', function () { return { - templateUrl: 'cases/views/descriptionSection.html', - restrict: 'A', - scope: { - loading: '=' - }, - controller: 'DescriptionSection', - link: function postLink(scope, element, attrs) {} + templateUrl: 'cases/views/descriptionSection.html', + restrict: 'A', + scope: { loading: '=' }, + controller: 'DescriptionSection', + link: function postLink(scope, element, attrs) { + } }; - }); - +}); 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaCasedetails', function () { - return { - templateUrl: 'cases/views/detailsSection.html', - controller: 'DetailsSection', - scope: { - compact: '=', - loading: '=' - }, - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaCasedetails', function () { + return { + templateUrl: 'cases/views/detailsSection.html', + controller: 'DetailsSection', + scope: { + compact: '=', + loading: '=' + }, + restrict: 'A', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); - +/*global angular*/ 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaEmailnotifyselect', function () { - return { - templateUrl: 'cases/views/emailNotifySelect.html', - restrict: 'A', - controller: 'EmailNotifySelect' - }; +angular.module('RedhatAccess.cases').directive('rhaEmailnotifyselect', function () { + return { + templateUrl: 'cases/views/emailNotifySelect.html', + restrict: 'A', + transclude: true, + controller: 'EmailNotifySelect', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaEntitlementselect', function () { - return { - templateUrl: 'cases/views/entitlementSelect.html', - restrict: 'A', - controller: 'EntitlementSelect' - }; +angular.module('RedhatAccess.cases').directive('rhaEntitlementselect', function () { + return { + templateUrl: 'cases/views/entitlementSelect.html', + restrict: 'A', + controller: 'EntitlementSelect' + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaExportcsvbutton', function () { - return { - templateUrl: 'cases/views/exportCSVButton.html', - restrict: 'A', - controller: 'ExportCSVButton' - }; +angular.module('RedhatAccess.cases').directive('rhaExportcsvbutton', function () { + return { + templateUrl: 'cases/views/exportCSVButton.html', + restrict: 'A', + controller: 'ExportCSVButton' + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaGrouplist', function () { - return { - templateUrl: 'cases/views/groupList.html', - restrict: 'A', - controller: 'GroupList' - }; +angular.module('RedhatAccess.cases').directive('rhaGrouplist', function () { + return { + templateUrl: 'cases/views/groupList.html', + restrict: 'A', + controller: 'GroupList', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaGroupselect', function () { - return { - templateUrl: 'cases/views/groupSelect.html', - restrict: 'A', - controller: 'GroupSelect', - scope: { - onchange: '&', - showsearchoptions: '=' - } - }; +angular.module('RedhatAccess.cases').directive('rhaGroupselect', function () { + return { + templateUrl: 'cases/views/groupSelect.html', + restrict: 'A', + controller: 'GroupSelect', + scope: { + onchange: '&' + } + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaListattachments', function () { - return { - templateUrl: 'cases/views/listAttachments.html', - restrict: 'A', - controller: 'ListAttachments', - scope: { - disabled: '=' - } - }; +angular.module('RedhatAccess.cases').directive('rhaListattachments', function () { + return { + templateUrl: 'cases/views/listAttachments.html', + restrict: 'A', + controller: 'ListAttachments', + scope: { disabled: '=' } + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaListbugzillas', function () { - return { - templateUrl: 'cases/views/listBugzillas.html', - restrict: 'A', - controller: 'ListBugzillas', - scope: { - loading: '=' - }, - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaListbugzillas', function () { + return { + templateUrl: 'cases/views/listBugzillas.html', + restrict: 'A', + controller: 'ListBugzillas', + scope: { loading: '=' }, + link: function postLink(scope, element, attrs) { + } + }; }); +/*global angular*/ 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaListfilter', function () { - return { - templateUrl: 'cases/views/listFilter.html', - restrict: 'A', - controller: 'ListFilter', - scope: { - prefilter: '=', - postfilter: '=' - }, - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaListfilter', function () { + return { + templateUrl: 'cases/views/listFilter.html', + restrict: 'A', + controller: 'ListFilter', + scope: { + prefilter: '=', + postfilter: '=' + }, + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); - 'use strict'; - -angular.module('RedhatAccess.cases') - .directive('rhaListnewattachments', function() { +angular.module('RedhatAccess.cases').directive('rhaListnewattachments', function () { return { - templateUrl: 'cases/views/listNewAttachments.html', - restrict: 'A', - controller: 'ListNewAttachments' + templateUrl: 'cases/views/listNewAttachments.html', + restrict: 'A', + controller: 'ListNewAttachments' }; - }); +}); 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaOnchange', function () { - return { - restrict: 'A', - link: function (scope, element, attrs) { - element.bind('change', element.scope()[attrs.rhaOnChange]); - } - }; +angular.module('RedhatAccess.cases').directive('rhaNewrecommendations', function () { + return { + templateUrl: 'cases/views/newRecommendationsSection.html', + restrict: 'A', + controller: 'PcmRecommendationsController', + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaOwnerselect', function () { - return { - templateUrl: 'cases/views/ownerSelect.html', - restrict: 'A', - controller: 'OwnerSelect', - scope: { - onchange: '&' - } - }; +angular.module('RedhatAccess.cases').directive('rhaOnchange', function () { + return { + restrict: 'A', + link: function (scope, element, attrs) { + element.bind('change', element.scope()[attrs.rhaOnchange]); + } + }; }); - 'use strict'; +/*global angular*/ /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaPageheader', function () { - return { - templateUrl: 'cases/views/pageHeader.html', - restrict: 'A', - scope: { - title: '=title' - }, - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaOwnerselect', function () { + return { + templateUrl: 'cases/views/ownerSelect.html', + restrict: 'A', + controller: 'OwnerSelect', + scope: { onchange: '&' }, + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaProductselect', function () { - return { - templateUrl: 'cases/views/productSelect.html', - restrict: 'A', - controller: 'ProductSelect', - scope: { - onchange: '&' - } - }; +angular.module('RedhatAccess.cases').directive('rhaPageheader', function () { + return { + templateUrl: 'cases/views/pageHeader.html', + restrict: 'A', + scope: { title: '=title' }, + link: function postLink(scope, element, attrs) { + } + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaCaserecommendations', function () { - return { - templateUrl: 'cases/views/recommendationsSection.html', - restrict: 'A', - controller: 'RecommendationsSection', - scope: { - loading: '=' - }, - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.cases').directive('rhaProductselect', function () { + return { + templateUrl: 'cases/views/productSelect.html', + restrict: 'A', + controller: 'ProductSelect', + scope: { onchange: '&' } + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaSearchbox', function () { - return { - templateUrl: 'cases/views/searchBox.html', - restrict: 'A', - controller: 'SearchBox', - scope: { - placeholder: '=' - } - }; +angular.module('RedhatAccess.cases').directive('rhaCaserecommendations', function () { + return { + templateUrl: 'cases/views/recommendationsSection.html', + restrict: 'A', + controller: 'RecommendationsSection', + transclude: true, + scope: { loading: '=' }, + link: function postLink(scope, element, attrs) { + scope.$on('$destroy', function () { + element.remove(); + }); + } + }; }); 'use strict'; - -angular.module('RedhatAccess.cases') -.directive('rhaCasesearchresult', function () { - return { - templateUrl: 'cases/views/searchResult.html', - restrict: 'A', - scope: { - theCase: '=case' - } - }; +/*jshint unused:vars */ +angular.module('RedhatAccess.cases').directive('rhaSearchbox', function () { + return { + templateUrl: 'cases/views/searchBox.html', + restrict: 'A', + controller: 'SearchBox', + scope: { placeholder: '=' } + }; +}); +'use strict'; +angular.module('RedhatAccess.cases').directive('rhaCasesearchresult', function () { + return { + templateUrl: 'cases/views/searchResult.html', + restrict: 'A', + scope: { theCase: '=case' } + }; }); - 'use strict'; /*jshint unused:vars */ - -angular.module('RedhatAccess.cases') -.directive('rhaSelectloadingindicator', function () { - return { - templateUrl: 'cases/views/selectLoadingIndicator.html', - restrict: 'A', - transclude: true, - scope: { - loading: '=', - type: '@' - } - }; +angular.module('RedhatAccess.cases').directive('rhaSelectloadingindicator', function () { + return { + templateUrl: 'cases/views/selectLoadingIndicator.html', + restrict: 'A', + transclude: true, + scope: { + loading: '=', + type: '@' + } + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaSeverityselect', function () { - return { - templateUrl: 'cases/views/severitySelect.html', - restrict: 'A', - controller: 'SeveritySelect', - scope: { - onchange: '&' - } - }; +angular.module('RedhatAccess.cases').directive('rhaSeverityselect', function () { + return { + templateUrl: 'cases/views/severitySelect.html', + restrict: 'A', + controller: 'SeveritySelect', + scope: { onchange: '&' } + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaStatusselect', function () { - return { - templateUrl: 'cases/views/statusSelect.html', - restrict: 'A', - controller: 'StatusSelect', - scope: { - onchange: '&' - } - }; +angular.module('RedhatAccess.cases').directive('rhaStatusselect', function () { + return { + templateUrl: 'cases/views/statusSelect.html', + restrict: 'A', + controller: 'StatusSelect', + scope: { onchange: '&' } + }; }); - 'use strict'; /*jshint unused:vars */ -angular.module('RedhatAccess.cases') -.directive('rhaTypeselect', function () { - return { - templateUrl: 'cases/views/typeSelect.html', - restrict: 'A', - controller: 'TypeSelect', - scope: { - onchange: '&' - } - }; +angular.module('RedhatAccess.cases').directive('rhaTypeselect', function () { + return { + templateUrl: 'cases/views/typeSelect.html', + restrict: 'A', + controller: 'TypeSelect', + scope: { onchange: '&' } + }; +}); +'use strict'; +angular.module('RedhatAccess.cases').filter('bytes', function () { + return function (bytes, precision) { + if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) { + return '-'; + } + if (typeof precision === 'undefined') { + precision = 1; + } + var units = [ + 'bytes', + 'kB', + 'MB', + 'GB', + 'TB', + 'PB' + ], number = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; + }; }); - 'use strict'; -angular.module('RedhatAccess.cases') - .filter('bytes', function() { - return function(bytes, precision) { - if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) { - return '-'; - } - if (typeof precision === 'undefined') { - precision = 1; - } - var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], - number = Math.floor(Math.log(bytes) / Math.log(1024)); - return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; +angular.module('RedhatAccess.cases').filter('recommendationsResolution', function () { + return function (text) { + var shortText = ''; + var maxTextLength = 150; + if (text !== undefined && text.length > maxTextLength) { + shortText = text.substr(0, maxTextLength); + // var lastSpace = shortText.lastIndexOf(' '); + // shortText = shortText.substr(0, lastSpace); + shortText = shortText.concat('...'); + } else { + shortText = text; + } + return shortText; }; - }); -'use strict'; - -angular.module('RedhatAccess.cases') -.filter('recommendationsResolution', function() { - return function(text) { - var shortText = ''; - var maxTextLength = 150; - - if (text != null && text.length > maxTextLength) { - shortText = text.substr(0, maxTextLength); - // var lastSpace = shortText.lastIndexOf(' '); - // shortText = shortText.substr(0, lastSpace); - shortText = shortText.concat('...'); - } else { - shortText = text; - } - - return shortText; - }; }); - 'use strict'; /*jshint camelcase: false */ - -angular.module('RedhatAccess.cases') - .service('AttachmentsService', [ +angular.module('RedhatAccess.cases').service('AttachmentsService', [ '$filter', '$q', 'strataService', @@ -22243,606 +4724,656 @@ angular.module('RedhatAccess.cases') 'CaseService', 'translate', function ($filter, $q, strataService, TreeViewSelectorUtils, $http, securityService, AlertService, CaseService, translate) { - this.originalAttachments = []; - this.updatedAttachments = []; - - this.backendAttachments = []; - - this.clear = function () { this.originalAttachments = []; this.updatedAttachments = []; this.backendAttachments = []; - }; - - this.updateBackEndAttachments = function (selected) { - this.backendAttachments = selected; - }; - - this.hasBackEndSelections = function () { - return TreeViewSelectorUtils.hasSelections(this.backendAttachments); - }; - - this.removeUpdatedAttachment = function ($index) { - this.updatedAttachments.splice($index, 1); - }; - - this.removeOriginalAttachment = function ($index) { - var attachment = this.originalAttachments[$index]; - var progressMessage = - AlertService.addWarningMessage( - translate('Deleting attachment:')+ ' ' + attachment.file_name + ' - ' + attachment.uuid); - - strataService.cases.attachments.remove(attachment.uuid, CaseService.kase.case_number).then( - angular.bind(this, function () { - AlertService.removeAlert(progressMessage); - AlertService.addSuccessMessage( - translate('Successfully deleted attachment:') + ' ' + attachment.file_name + ' - ' + attachment.uuid); - this.originalAttachments.splice($index, 1); - }), - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - }; - - this.addNewAttachment = function (attachment) { - this.updatedAttachments.push(attachment); - }; - - this.defineOriginalAttachments = function (attachments) { - if (!angular.isArray(attachments)) { - this.originalAttachments = []; - } else { - this.originalAttachments = attachments; - } - }; - - this.postBackEndAttachments = function (caseId) { - var selectedFiles = TreeViewSelectorUtils.getSelectedLeaves(this.backendAttachments); - return securityService.getBasicAuthToken().then( - function (auth) { - /*jshint unused:false */ - //we post each attachment separately - var promises = []; - angular.forEach(selectedFiles, function (file) { - var jsonData = { - authToken: auth, - attachment: file, - caseNum: caseId - }; - var deferred = $q.defer(); - $http.post('attachments', jsonData).success(function (data, status, headers, config) { - deferred.resolve(data); - AlertService.addSuccessMessage( - translate('Successfully uploaded attachment') + ' '+ - jsonData.attachment + ' '+translate('to case') + ' ' + caseId); - }).error(function (data, status, headers, config) { - var error_msg = ''; - switch (status) { - case 401: - error_msg = ' : Unauthorised.'; - break; - case 409: - error_msg = ' : Invalid username/password.'; - break; - case 500: - error_msg = ' : Internal server error'; - break; - } - AlertService.addDangerMessage( - 'Failed to upload attachment ' + - jsonData.attachment + ' to case ' + caseId + error_msg); - deferred.reject(data); - }); - promises.push(deferred.promise); + this.suggestedArtifact = {}; + this.clear = function () { + this.originalAttachments = []; + this.updatedAttachments = []; + this.backendAttachments = []; + }; + this.updateBackEndAttachments = function (selected) { + this.backendAttachments = selected; + }; + this.hasBackEndSelections = function () { + return TreeViewSelectorUtils.hasSelections(this.backendAttachments); + }; + this.removeUpdatedAttachment = function ($index) { + this.updatedAttachments.splice($index, 1); + }; + this.removeOriginalAttachment = function ($index) { + var attachment = this.originalAttachments[$index]; + var progressMessage = AlertService.addWarningMessage(translate('Deleting attachment:') + ' ' + attachment.file_name); + strataService.cases.attachments.remove(attachment.uuid, CaseService.kase.case_number).then(angular.bind(this, function () { + AlertService.removeAlert(progressMessage); + AlertService.addSuccessMessage(translate('Successfully deleted attachment:') + ' ' + attachment.file_name); + this.originalAttachments.splice($index, 1); + }), function (error) { + AlertService.addStrataErrorMessage(error); }); - return $q.all(promises); - }); - }; - - this.updateAttachments = function (caseId) { - var hasLocalAttachments = !angular.equals(this.originalAttachments, this.updatedAttachments); - var hasServerAttachments = this.hasBackEndSelections; - if (hasLocalAttachments || hasServerAttachments) { - var promises = []; - var updatedAttachments = this.updatedAttachments; - if (hasServerAttachments) { - promises.push(this.postBackEndAttachments(caseId)); - } - if (hasLocalAttachments) { - //find new attachments - angular.forEach(updatedAttachments, function (attachment) { - if (!attachment.hasOwnProperty('uuid')) { - var promise = strataService.cases.attachments.post( - attachment.file, - caseId - ); - promise.then( - function (uri) { - attachment.uri = uri; - attachment.uuid = uri.slice(uri.lastIndexOf('/') + 1); - AlertService.addSuccessMessage( - 'Successfully uploaded attachment ' + - attachment.file_name + ' to case ' + caseId); - }, - function (error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(promise); - } + }; + this.addNewAttachment = function (attachment) { + this.updatedAttachments.push(attachment); + }; + this.defineOriginalAttachments = function (attachments) { + if (!angular.isArray(attachments)) { + this.originalAttachments = []; + } else { + this.originalAttachments = attachments; + } + }; + this.postBackEndAttachments = function (caseId) { + var selectedFiles = TreeViewSelectorUtils.getSelectedLeaves(this.backendAttachments); + return securityService.getBasicAuthToken().then(function (auth) { + /*jshint unused:false */ + //we post each attachment separately + var promises = []; + angular.forEach(selectedFiles, function (file) { + var jsonData = { + authToken: auth, + attachment: file, + caseNum: caseId + }; + var deferred = $q.defer(); + $http.post('attachments', jsonData).success(function (data, status, headers, config) { + deferred.resolve(data); + AlertService.addSuccessMessage(translate('Successfully uploaded attachment') + ' ' + jsonData.attachment + ' ' + translate('to case') + ' ' + caseId); + }).error(function (data, status, headers, config) { + var errorMsg = ''; + switch (status) { + case 401: + errorMsg = ' : Unauthorised.'; + break; + case 409: + errorMsg = ' : Invalid username/password.'; + break; + case 500: + errorMsg = ' : Internal server error'; + break; + } + AlertService.addDangerMessage('Failed to upload attachment ' + jsonData.attachment + ' to case ' + caseId + errorMsg); + deferred.reject(data); + }); + promises.push(deferred.promise); + }); + return $q.all(promises); }); - } - - var uploadingAlert = AlertService.addWarningMessage('Uploading attachments...'); - var parentPromise = $q.all(promises); - parentPromise.then( - angular.bind(this, function () { - this.originalAttachments = - this.originalAttachments.concat(this.updatedAttachments); - this.updatedAttachments = []; - AlertService.removeAlert(uploadingAlert); - }), - function (error) { - AlertService.addStrataErrorMessage(error); - AlertService.removeAlert(uploadingAlert); + }; + this.updateAttachments = function (caseId) { + var hasServerAttachments = this.hasBackEndSelections(); + var hasLocalAttachments = !angular.equals(this.updatedAttachments.length, 0); + if (hasLocalAttachments || hasServerAttachments) { + var promises = []; + var updatedAttachments = this.updatedAttachments; + if (hasServerAttachments) { + promises.push(this.postBackEndAttachments(caseId)); + } + if (hasLocalAttachments) { + //find new attachments + angular.forEach(updatedAttachments, function (attachment) { + if (!attachment.hasOwnProperty('uuid')) { + var promise = strataService.cases.attachments.post(attachment.file, caseId); + promise.then(function (uri) { + attachment.uri = uri; + attachment.uuid = uri.slice(uri.lastIndexOf('/') + 1); + AlertService.addSuccessMessage('Successfully uploaded attachment ' + attachment.file_name + ' to case ' + caseId); + }, function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(promise); + } + }); + } + var uploadingAlert = AlertService.addWarningMessage('Uploading attachments...'); + var parentPromise = $q.all(promises); + parentPromise.then(angular.bind(this, function () { + this.originalAttachments = this.originalAttachments.concat(this.updatedAttachments); + this.updatedAttachments = []; + AlertService.removeAlert(uploadingAlert); + }), function (error) { + AlertService.addStrataErrorMessage(error); + AlertService.removeAlert(uploadingAlert); + }); + return parentPromise; } - ); - - return parentPromise; - } - }; + }; } - ]); - -'use strict'; - -angular.module('RedhatAccess.cases') -.service('CaseListService', [ - function () { - this.cases = []; - - this.defineCases = function(cases) { - this.cases = cases; - }; - } ]); - 'use strict'; - -angular.module('RedhatAccess.cases') - .service('CaseService', [ +angular.module('RedhatAccess.cases').service('CaseListService', [function () { + this.cases = []; + this.defineCases = function (cases) { + this.cases = cases; + }; + }]); +'use strict'; +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').constant('CASE_GROUPS', { + manage: 'manage', + ungrouped: 'ungrouped' +}).service('CaseService', [ 'strataService', 'AlertService', - 'ENTITLEMENTS', 'RHAUtils', 'securityService', '$q', + '$timeout', '$filter', - function(strataService, AlertService, ENTITLEMENTS, RHAUtils, securityService, $q, $filter) { - this. - kase = {}; - this.versions = []; - this.products = []; - //this.statuses = []; - this.severities = []; - this.groups = []; - this.users = []; - this.comments = []; - - this.originalNotifiedUsers = []; - this.updatedNotifiedUsers = []; - - this.account = {}; - - this.draftComment = ''; - this.commentText = ''; - this.status = ''; - this.severity = ''; - this.type = ''; - this.group = ''; - this.owner = ''; - this.product = ''; - this.bugzillaList = {}; - - this.onSelectChanged = null; - this.onOwnerSelectChanged = null; - this.onGroupSelectChanged = null; - /** + function (strataService, AlertService, RHAUtils, securityService, $q, $timeout, $filter) { + this.kase = {}; + this.caseDataReady = false; + this.isCommentPublic = false; + this.versions = []; + this.products = []; + //this.statuses = []; + this.severities = []; + this.groups = []; + this.users = []; + this.comments = []; + this.originalNotifiedUsers = []; + this.updatedNotifiedUsers = []; + this.account = {}; + this.draftComment = {}; + this.commentText = ''; + this.escalationCommentText = ''; + this.status = ''; + this.severity = ''; + this.type = ''; + this.group = ''; + this.owner = ''; + this.product = ''; + this.bugzillaList = {}; + this.onSelectChanged = null; + this.onOwnerSelectChanged = null; + this.onGroupSelectChanged = null; + this.groupOptions = []; + this.showsearchoptions = false; + this.disableAddComment = true; + /** * Add the necessary wrapper objects needed to properly display the data. * * @param rawCase */ - this.defineCase = function(rawCase) { - /*jshint camelcase: false */ - rawCase.severity = { - 'name': rawCase.severity + this.defineCase = function (rawCase) { + /*jshint camelcase: false */ + rawCase.severity = { 'name': rawCase.severity }; + rawCase.status = { 'name': rawCase.status }; + rawCase.product = { 'name': rawCase.product }; + rawCase.group = { 'number': rawCase.folder_number }; + rawCase.type = { 'name': rawCase.type }; + this.kase = rawCase; + this.bugzillaList = rawCase.bugzillas; + this.caseDataReady = true; }; - rawCase.status = { - 'name': rawCase.status + this.setCase = function (jsonCase) { + this.kase = jsonCase; + this.bugzillaList = jsonCase.bugzillas; + this.caseDataReady = true; }; - rawCase.product = { - 'name': rawCase.product + this.defineAccount = function (account) { + this.account = account; }; - rawCase.group = { - 'number': rawCase.folder_number + this.defineNotifiedUsers = function () { + /*jshint camelcase: false */ + this.updatedNotifiedUsers.push(this.kase.contact_sso_username); + //hide the X button for the case owner + $('#rha-emailnotifyselect').on('change', angular.bind(this, function () { + $('rha-emailnotifyselect .chosen-choices li:contains("' + this.kase.contact_sso_username + '") a').css('display', 'none'); + $('rha-emailnotifyselect .chosen-choices li:contains("' + this.kase.contact_sso_username + '")').css('padding-left', '5px'); + })); + if (RHAUtils.isNotEmpty(this.kase.notified_users)) { + angular.forEach(this.kase.notified_users.link, angular.bind(this, function (user) { + this.originalNotifiedUsers.push(user.sso_username); + })); + this.updatedNotifiedUsers = this.updatedNotifiedUsers.concat(this.originalNotifiedUsers); + } }; - rawCase.type = { - 'name': rawCase.type + this.getGroups = function () { + return this.groups; }; - - this. - kase = rawCase; - - this.bugzillaList = rawCase.bugzillas; - - }; - - this.defineAccount = function(account) { - this.account = account; - }; - - this.defineNotifiedUsers = function() { - /*jshint camelcase: false */ - this.updatedNotifiedUsers.push(this.kase.contact_sso_username); - - //hide the X button for the case owner - $('#rha-emailnotifyselect').on('change', angular.bind(this, function() { - $('rha-emailnotifyselect .select2-choices li:contains("' + this.kase.contact_sso_username + '") a').css('display', 'none'); - $('rha-emailnotifyselect .select2-choices li:contains("' + this.kase.contact_sso_username + '")').css('padding-left', '5px'); - })); - - if (RHAUtils.isNotEmpty(this.kase.notified_users)) { - - angular.forEach(this.kase.notified_users.link, - angular.bind(this, function(user) { - this.originalNotifiedUsers.push(user.sso_username); - }) - ); - this.updatedNotifiedUsers = - this.updatedNotifiedUsers.concat(this.originalNotifiedUsers); - } - }; - - this.getGroups = function() { - return this.groups; - }; - - this.clearCase = function() { - this. - kase = {}; - - this.versions = []; - this.products = []; - this.statuses = []; - this.severities = []; - this.groups = []; - this.account = {}; - this.comments = []; - this.bugzillaList = {}; - - this.draftComment = undefined; - this.commentText = undefined; - this.status = undefined; - this.severity = undefined; - this.type = undefined; - this.group = undefined; - this.owner = undefined; - this.product = undefined; - }; - - this.groupsLoading = false; - - this.populateGroups = function(ssoUsername) { - this.groupsLoading = true; - strataService.groups.list(ssoUsername).then( - angular.bind(this, function(groups) { - this.groups = groups; - this.groupsLoading = false; - }), - angular.bind(this, function(error) { - this.groupsLoading = false; - AlertService.addStrataErrorMessage(error); - }) - ); - }; - - this.usersLoading = false; - - /** + this.clearCase = function () { + this.caseDataReady = false; + this.isCommentPublic = false; + this.kase = {}; + this.versions = []; + this.products = []; + this.statuses = []; + this.severities = []; + this.groups = []; + this.account = {}; + this.comments = []; + this.bugzillaList = {}; + this.draftComment = undefined; + this.commentText = undefined; + this.escalationCommentText = undefined; + this.status = undefined; + this.severity = undefined; + this.type = undefined; + this.group = undefined; + this.owner = undefined; + this.product = undefined; + this.originalNotifiedUsers = []; + this.updatedNotifiedUsers = []; + this.groupOptions = []; + }; + this.groupsLoading = false; + this.populateGroups = function (ssoUsername) { + var that = this; + var deferred = $q.defer(); + this.groupsLoading = true; + var username = ssoUsername; + if(username === undefined){ + username = securityService.loginStatus.authedUser.sso_username; + } + strataService.groups.list(ssoUsername).then(angular.bind(this, function (groups) { + that.groups = groups; + that.group = ''; + that.buildGroupOptions(that); + that.groupsLoading = false; + deferred.resolve(groups); + }), angular.bind(this, function (error) { + that.groupsLoading = false; + AlertService.addStrataErrorMessage(error); + deferred.reject(); + })); + return deferred.promise; + }; + this.usersLoading = false; + /** * Intended to be called only after user is logged in and has account details * See securityService. */ - this.populateUsers = angular.bind(this, function () { - var promise = null; - - if (securityService.loginStatus.orgAdmin) { - this.usersLoading = true; - - var accountNumber = - RHAUtils.isEmpty(this.account.number) ? - securityService.loginStatus.account.number : this.account.number; + this.populateUsers = angular.bind(this, function () { + var promise = null; + if (securityService.loginStatus.authedUser.org_admin) { + this.usersLoading = true; + var accountNumber = RHAUtils.isEmpty(this.account.number) ? securityService.loginStatus.authedUser.account_number : this.account.number; + promise = strataService.accounts.users(accountNumber); + promise.then(angular.bind(this, function (users) { + angular.forEach(users, function(user){ + if(user.sso_username === securityService.loginStatus.authedUser.sso_username) { + this.owner = user.sso_username; + } + }, this); + this.usersLoading = false; + this.users = users; + }), angular.bind(this, function (error) { + this.users = []; + this.usersLoading = false; + AlertService.addStrataErrorMessage(error); + })); + } else { + var deferred = $q.defer(); + promise = deferred.promise; + deferred.resolve(); + var tmp= {'sso_username': securityService.loginStatus.authedUser.sso_username}; + this.users.push(tmp); + } + return promise; + }); - promise = strataService.accounts.users(accountNumber); - promise.then( - angular.bind(this, function(users) { - this.usersLoading = false; - this.users = users; - }), - angular.bind(this, function(error) { - this.usersLoading = false; - this.users = []; + this.scrollToComment = function(commentID) { + if(!commentID) { + return; + } + var commentElem = document.getElementById(commentID); + if(commentElem) { + commentElem.scrollIntoView(true); + } + }; + this.populateComments = function (caseNumber) { + var promise = strataService.cases.comments.get(caseNumber); + promise.then(angular.bind(this, function (comments) { + //pull out the draft comment + angular.forEach(comments, angular.bind(this, function (comment, index) { + if (comment.draft === true) { + this.draftComment = comment; + this.commentText = comment.text; + this.isCommentPublic = comment.public; + if (RHAUtils.isNotEmpty(this.commentText)) { + this.disableAddComment = false; + } else if (RHAUtils.isEmpty(this.commentText)) { + this.disableAddComment = true; + } + comments.slice(index, index + 1); + } + })); + this.comments = comments; + }), function (error) { AlertService.addStrataErrorMessage(error); - }) - ); - } else { - var deferred = $q.defer(); - promise = deferred.promise; - deferred.resolve(); - - this.users = []; - } - - return promise; - }); - - this.refreshComments = null; - - this.populateComments = function(caseNumber) { - var promise = strataService.cases.comments.get(caseNumber); - - promise.then( - angular.bind(this, function(comments) { - //pull out the draft comment - angular.forEach(comments, angular.bind(this, function(comment, index) { - if (comment.draft === true) { - this.draftComment = comment; - this.commentText = comment.text; - comments.slice(index, index + 1); + }); + return promise; + }; + this.entitlementsLoading = false; + this.populateEntitlements = function (ssoUserName) { + this.entitlementsLoading = true; + strataService.entitlements.get(false, ssoUserName).then(angular.bind(this, function (entitlementsResponse) { + // if the user has any premium or standard level entitlement, then allow them + // to select it, regardless of the product. + // TODO: strata should respond with a filtered list given a product. + // Adding the query param ?product=$PRODUCT does not work. + var uniqueEntitlements = function (a) { + return a.reduce(function (p, c) { + if (p.indexOf(c.sla) < 0) { + p.push(c.sla); + } + return p; + }, []); + }; + var entitlements = uniqueEntitlements(entitlementsResponse.entitlement); + var unknownIndex = entitlements.indexOf('UNKNOWN'); + if (unknownIndex > -1) { + entitlements.splice(unknownIndex, 1); + } + this.entitlements = entitlements; + this.entitlementsLoading = false; + }), angular.bind(this, function (error) { + AlertService.addStrataErrorMessage(error); + })); + }; + this.showFts = function () { + if (RHAUtils.isNotEmpty(this.severities)) { + if (this.entitlement === 'PREMIUM' || this.entitlement === 'AMC' || RHAUtils.isNotEmpty(this.kase.entitlement) && (this.kase.entitlement.sla === 'PREMIUM' || this.kase.entitlement.sla === 'AMC')) { + return true; } - })); - - this.comments = comments; - }), - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - - return promise; - }; - - this.entitlementsLoading = false; - this.populateEntitlements = function(ssoUserName) { - this.entitlementsLoading = true; - strataService.entitlements.get(false, ssoUserName).then( - angular.bind(this, function(entitlementsResponse) { - // if the user has any premium or standard level entitlement, then allow them - // to select it, regardless of the product. - // TODO: strata should respond with a filtered list given a product. - // Adding the query param ?product=$PRODUCT does not work. - var premiumSupport = $filter('filter')(entitlementsResponse.entitlement, {'sla': ENTITLEMENTS.premium}); - var standardSupport = $filter('filter')(entitlementsResponse.entitlement, {'sla': ENTITLEMENTS.standard}); - - var entitlements = []; - if (RHAUtils.isNotEmpty(premiumSupport)) { - entitlements.push(ENTITLEMENTS.premium); } - if (RHAUtils.isNotEmpty(standardSupport)) { - entitlements.push(ENTITLEMENTS.standard); + return false; + }; + this.newCasePage1Incomplete = true; + this.validateNewCasePage1 = function () { + if (RHAUtils.isEmpty(this.kase.product) || RHAUtils.isEmpty(this.kase.version) || RHAUtils.isEmpty(this.kase.summary) || RHAUtils.isEmpty(this.kase.description)) { + this.newCasePage1Incomplete = true; + } else { + this.newCasePage1Incomplete = false; } - - if (entitlements.length === 0) { - entitlements.push(ENTITLEMENTS.defaults); + }; + this.showVersionSunset = function () { + if (RHAUtils.isNotEmpty(this.kase.product) && RHAUtils.isNotEmpty(this.kase.version)) { + if ((this.kase.version).toLowerCase().indexOf('- eol') > -1) { + return true; + } } + return false; + }; - this.entitlements = entitlements; - this.entitlementsLoading = false; - }), - angular.bind(this, function(error) { - AlertService.addStrataErrorMessage(error); - }) - ); - }; + this.buildGroupOptions = function() { + this.groupOptions = []; + var sep = '────────────────────────────────────────'; + this.groups.sort(function(a, b){ + if(a.name < b.name) { return -1; } + if(a.name > b.name) { return 1; } + return 0; + }); - this.showFts = function() { - if (RHAUtils.isNotEmpty(this.severities) && angular.equals(this.kase.severity, this.severities[0])) { - if (this.entitlement === ENTITLEMENTS.premium || - (RHAUtils.isNotEmpty(this.kase.entitlement) && - this.kase.entitlement.sla === ENTITLEMENTS.premium)) { - return true; - } - } - return false; - }; + var defaultGroup = ''; + if (this.showsearchoptions === true) { + this.groupOptions.push({ + value: '', + label: 'All Groups' + }, { + value: 'ungrouped', + label: 'Ungrouped Cases' + }, { + isDisabled: true, + label: sep + }); + } else { + this.groupOptions.push({ + value: '', + label: 'Ungrouped Case' + }); + } - this.newCasePage1Incomplete = true; - this.validateNewCasePage1 = function () { - if (RHAUtils.isEmpty(this.kase.product) || - RHAUtils.isEmpty(this.kase.version) || - RHAUtils.isEmpty(this.kase.summary) || - RHAUtils.isEmpty(this.kase.description) || - (securityService.loginStatus.isInternal && RHAUtils.isEmpty(this.owner))) { - this.newCasePage1Incomplete = true; - } else { - this.newCasePage1Incomplete = false; - } - }; + angular.forEach(this.groups, function(group){ + this.groupOptions.push({ + value: group.number, + label: group.name + }); + if(group.is_default) { + this.kase.group = group.number; + this.group = group.number; + } + }, this); + if (this.showsearchoptions === true) { + this.groupOptions.push({ + isDisabled: true, + label: sep + }, { + value: 'manage', + label: 'Manage Case Groups' + }); + } + }; } - ]); +]); 'use strict'; - /*jshint unused:vars */ - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.service('GroupService', [ - 'strataService', - function (strataService) { - - this.reloadTable; - this.groupsOnScreen = []; - } +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').service('GroupService', [ + 'strataService', + function (strataService) { + this.reloadTable = {}; + this.groupsOnScreen = []; + this.disableDeleteGroup = true; + } ]); 'use strict'; - /*jshint unused:vars */ - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.service('RecommendationsService', [ - 'strataService', - 'CaseService', - '$q', - function (strataService, CaseService, $q) { - - this.recommendations = []; - this.pinnedRecommendations = []; - this.handPickedRecommendations = []; - this.populateCallback = function() {}; - - var currentData = {}; - this.loadingRecommendations = false; - - var setCurrentData = function () { - currentData = { - product: CaseService.kase.product, - version: CaseService.kase.version, - summary: CaseService.kase.summary, - description: CaseService.kase.description - }; - }; - setCurrentData(); - - this.clear = function() { - this.recommendations = []; - }; - - this.setPopulateCallback = function(callback) { - this.populateCallback = callback; - }; - - this.populatePinnedRecommendations = function() { - var promises = []; - - if (CaseService.kase.recommendations) { - //Push any pinned recommendations to the front of the array - if (CaseService.kase.recommendations.recommendation) { - var pinnedRecsPromises = []; - angular.forEach(CaseService.kase.recommendations.recommendation, - angular.bind(this, function(rec) { - if (rec.pinned_at) { - var promise = - strataService.solutions.get(rec.solution_url).then( - angular.bind(this, function(solution) { - solution.pinned = true; - this.pinnedRecommendations.push(solution); - }), - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(promise); - } else if (rec.linked){ - var promise = - strataService.solutions.get(rec.solution_url).then( - angular.bind(this, function(solution) { - //solution.pinned = true; - solution.handPicked = true; - this.handPickedRecommendations.push(solution); - }), - function(error) { - AlertService.addStrataErrorMessage(error); - } - ); - promises.push(promise); - } - }) - ); - } - } - - var masterPromise = $q.all(promises); - masterPromise.then(angular.bind(this, function() {this.populateCallback();})); - return masterPromise; - }; - - this.failureCount = 0; - this.populateRecommendations = function (max) { - - var masterDeferred = $q.defer(); - - masterDeferred.promise.then(this.populateCallback); - - var newData = { - product: CaseService.kase.product, - version: CaseService.kase.version, - summary: CaseService.kase.summary, - description: CaseService.kase.description - }; - - if ((!angular.equals(currentData, newData) && !this.loadingRecommendations) || - (this.recommendations.length < 1 && this.failureCount < 10)) { - this.loadingRecommendations = true; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').service('GroupUserService', [ + 'strataService', + function (strataService) { + this.reloadTable = {}; + this.groupsOnScreen = []; + } +]); +'use strict'; +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').service('RecommendationsService', [ + 'strataService', + 'CaseService', + 'AlertService', + '$q', + function (strataService, CaseService, AlertService, $q) { + this.recommendations = []; + this.pinnedRecommendations = []; + this.handPickedRecommendations = []; + var currentData = { + product: null, + version: null, + summary: null, + description: null + }; + this.loadingRecommendations = false; + var setCurrentData = function () { + currentData = { + product: CaseService.kase.product, + version: CaseService.kase.version, + summary: CaseService.kase.summary, + description: CaseService.kase.description + }; + }; setCurrentData(); - var deferreds = []; - - strataService.problems(currentData, max).then( - angular.bind(this, function(solutions) { - //retrieve details for each solution - solutions.forEach(function (solution) { - var deferred = strataService.solutions.get(solution.uri); - deferreds.push(deferred); - }); - - $q.all(deferreds).then( - angular.bind(this, function (solutions) { - this.recommendations = []; - - solutions.forEach(angular.bind(this, function (solution) { - if (solution !== undefined) { - solution.resource_type = 'Solution'; - this.recommendations.push(solution); - } + this.clear = function () { + this.recommendations = []; + }; + this.pageSize = 4; + this.maxSize = 10; + this.recommendationsOnScreen = []; + this.selectPage = function (pageNum) { + //filter out pinned recommendations + angular.forEach(this.pinnedRecommendations, angular.bind(this, function (pinnedRec) { + angular.forEach(this.recommendations, angular.bind(this, function (rec, index) { + if (angular.equals(rec.id, pinnedRec.id)) { + this.recommendations.splice(index, 1); + } + })); + })); + angular.forEach(this.handPickedRecommendations, angular.bind(this, function (handPickedRec) { + angular.forEach(this.recommendations, angular.bind(this, function (rec, index) { + if (angular.equals(rec.id, handPickedRec.id)) { + this.recommendations.splice(index, 1); + } + })); + })); + var recommendations = this.pinnedRecommendations.concat(this.recommendations); + recommendations = this.handPickedRecommendations.concat(recommendations); + var start = this.pageSize * (pageNum - 1); + var end = start + this.pageSize; + end = end > recommendations.length ? recommendations.length : end; + this.recommendationsOnScreen = recommendations.slice(start, end); + this.currentPage = pageNum; + }; + this.populatePinnedRecommendations = function () { + var promises = []; + if (CaseService.kase.recommendations) { + //Push any pinned recommendations to the front of the array + if (CaseService.kase.recommendations.recommendation) { + var promise = {}; + angular.forEach(CaseService.kase.recommendations.recommendation, angular.bind(this, function (rec) { + if (rec.pinned_at) { + promise = strataService.solutions.get(rec.resource_id).then(angular.bind(this, function (solution) { + solution.pinned = true; + this.pinnedRecommendations.push(solution); + }), function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(promise); + } else if (rec.linked) { + promise = strataService.solutions.get(rec.resource_id).then(angular.bind(this, function (solution) { + //solution.pinned = true; + solution.handPicked = true; + this.handPickedRecommendations.push(solution); + }), function (error) { + AlertService.addStrataErrorMessage(error); + }); + promises.push(promise); + } + })); + } + } + var masterPromise = $q.all(promises); + masterPromise.then(angular.bind(this, function () { + this.selectPage(1); + })); + return masterPromise; + }; + this.failureCount = 0; + this.populateRecommendations = function (max) { + var masterDeferred = $q.defer(); + masterDeferred.promise.then(angular.bind(this, function() {this.selectPage(1);})); + var productName; + if(CaseService.kase.product !== undefined && CaseService.kase.product.name !== undefined){ + productName = CaseService.kase.product.name; + } + var newData = { + product: productName, + version: CaseService.kase.version, + summary: CaseService.kase.summary, + description: CaseService.kase.description + }; + if ((newData.product !== undefined || newData.version !== undefined || newData.summary !== undefined || newData.description !== undefined || (!angular.equals(currentData, newData) && !this.loadingRecommendations || this.recommendations.length < 1)) && this.failureCount < 10) { + this.loadingRecommendations = true; + setCurrentData(); + var deferreds = []; + strataService.recommendations(currentData, max).then(angular.bind(this, function (solutions) { + //retrieve details for each solution + solutions.forEach(function (solution) { + var deferred = strataService.solutions.get(solution.resource_uri); + deferreds.push(deferred); + }); + $q.all(deferreds).then(angular.bind(this, function (solutions) { + this.recommendations = []; + solutions.forEach(angular.bind(this, function (solution) { + if (solution !== undefined) { + solution.resource_type = 'Solution'; + this.recommendations.push(solution); + } + })); + this.loadingRecommendations = false; + masterDeferred.resolve(); + }), angular.bind(this, function (error) { + this.loadingRecommendations = false; + masterDeferred.resolve(); })); + }), angular.bind(this, function (error) { this.loadingRecommendations = false; - masterDeferred.resolve(); - }), - angular.bind(this, function (error) { + masterDeferred.reject(); + this.failureCount++; + this.populateRecommendations(12); + })); + } else { + masterDeferred.resolve(); + } + return masterDeferred.promise; + }; + this.populatePCMRecommendations = function (max) { + var masterDeferred = $q.defer(); + masterDeferred.promise.then(angular.bind(this, function() {this.selectPage(1);})); + var productName; + if(CaseService.kase.product !== undefined && CaseService.kase.product.name !== undefined){ + productName = CaseService.kase.product.name; + } + var newData = { + product: productName, + version: CaseService.kase.version, + summary: CaseService.kase.summary, + description: CaseService.kase.description + }; + if ((newData.product !== undefined || newData.version !== undefined || newData.summary !== undefined || newData.description !== undefined || (!angular.equals(currentData, newData) && !this.loadingRecommendations || this.recommendations.length < 1)) && this.failureCount < 10) { + this.loadingRecommendations = true; + setCurrentData(); + var deferreds = []; + strataService.recommendations(currentData, max, true, '%3Cstrong%3E%2C%3C%2Fstrong%3E').then(angular.bind(this, function (solutions) { + //retrieve details for each solution + solutions.forEach(function (solution) { + //var deferred = strataService.solutions.get(solution.resource_uri); + deferreds.push(solution); + }); + $q.all(deferreds).then(angular.bind(this, function (solutions) { + this.recommendations = []; + solutions.forEach(angular.bind(this, function (solution) { + if (solution !== undefined) { + solution.resource_type = 'Solution'; + this.recommendations.push(solution); + } + })); + this.loadingRecommendations = false; + masterDeferred.resolve(); + }), angular.bind(this, function (error) { + this.loadingRecommendations = false; + masterDeferred.resolve(); + })); + }), angular.bind(this, function (error) { this.loadingRecommendations = false; - masterDeferred.resolve(); - }) - ); - }), - angular.bind(this, function(error) { - masterDeferred.reject(); - this.failureCount++; - this.populateRecommendations(12); - }) - ); - } else { - masterDeferred.resolve(); - } - - return masterDeferred.promise; - }; - } + masterDeferred.reject(); + this.failureCount++; + this.populateRecommendations(12); + })); + } else { + masterDeferred.resolve(); + } + return masterDeferred.promise; + }; + } ]); 'use strict'; - /*jshint unused:vars */ - /*jshint camelcase: false */ -angular.module('RedhatAccess.cases') -.service('SearchBoxService', [ - function () { - this.doSearch; - this.searchTerm; - this.onKeyPress; - } -]); +/*jshint unused:vars */ +/*jshint camelcase: false */ +angular.module('RedhatAccess.cases').service('SearchBoxService', [function () { + this.doSearch = {}; + this.searchTerm = undefined; + this.onKeyPress = {}; + }]); +/*jshint camelcase: false*/ 'use strict'; - -angular.module('RedhatAccess.cases') - .service('SearchCaseService', [ +angular.module('RedhatAccess.cases').service('SearchCaseService', [ 'CaseService', 'strataService', 'AlertService', @@ -22854,625 +5385,571 @@ angular.module('RedhatAccess.cases') '$rootScope', 'SearchBoxService', 'securityService', - function ( - CaseService, - strataService, - AlertService, - STATUS, - CASE_GROUPS, - AUTH_EVENTS, - $q, - $state, - $rootScope, - SearchBoxService, - securityService) { - - this.cases = []; - this.searching = false; - this.prefilter; - this.postfilter; - this.start = 0; - this.count = 100; - this.total = 0; - this.allCasesDownloaded = false; - - var getIncludeClosed = function () { - if (CaseService.status === STATUS.open) { - return false; - } else if (CaseService.status === STATUS.closed) { - return true; - } else if (CaseService.status === STATUS.both) { - return true; - } - - return true; - }; - - this.clear = function () { + function (CaseService, strataService, AlertService, STATUS, CASE_GROUPS, AUTH_EVENTS, $q, $state, $rootScope, SearchBoxService, securityService) { this.cases = []; - this.oldParams = {}; - SearchBoxService.searchTerm = ''; + this.totalCases = 0; + this.searching = true; + this.prefilter = {}; + this.postfilter = {}; this.start = 0; + this.count = 100; this.total = 0; this.allCasesDownloaded = false; - }; - - this.oldParams = {}; - this.doFilter = function () { - if (angular.isFunction(this.prefilter)) { - this.prefilter(); - } - - var params = { - include_closed: getIncludeClosed(), - count: this.count, - }; - params.start = this.start; - - var isObjectNothing = function (object) { - if (object === '' || object === undefined || object === null) { + var getIncludeClosed = function () { + if (CaseService.status === STATUS.open) { + return false; + } else if (CaseService.status === STATUS.closed) { + return true; + } else if (CaseService.status === STATUS.both) { + return true; + } return true; - } else { - return false; - } }; - - if (!isObjectNothing(SearchBoxService.searchTerm)) { - params.keyword = SearchBoxService.searchTerm; - } - - if (CaseService.group === CASE_GROUPS.manage) { - $state.go('group'); - } else if (CaseService.group === CASE_GROUPS.ungrouped) { - params.only_ungrouped = true; - } else if (!isObjectNothing(CaseService.group)) { - params.group_numbers = { - group_number: [CaseService.group] - }; - } - - if (CaseService.status === STATUS.closed) { - params.status = STATUS.closed; - } - - if (!isObjectNothing(CaseService.product)) { - params.product = CaseService.product; - } - - if (!isObjectNothing(CaseService.owner)) { - params.owner_ssoname = CaseService.owner; - } - - if (!isObjectNothing(CaseService.type)) { - params.type = CaseService.type; - } - - if (!isObjectNothing(CaseService.severity)) { - params.severity = CaseService.severity; - } - - this.searching = true; - - //TODO: hack to get around onchange() firing at page load for each select. - //Need to prevent initial onchange() event instead of handling here. - var promises = []; - if (!angular.equals(params, this.oldParams)) { - this.oldParams = params; - var deferred = $q.defer(); - var that = this; - var cases = null; - if (securityService.loginStatus.isLoggedIn) { - if (securityService.loginStatus.ssoName && securityService.loginStatus.isInternal) { - params.owner_ssoname = securityService.loginStatus.ssoName; + this.clear = function () { + this.cases = []; + this.oldParams = {}; + SearchBoxService.searchTerm = ''; + this.start = 0; + this.total = 0; + this.totalCases = 0; + this.allCasesDownloaded = false; + this.prefilter = {}; + this.postfilter = {}; + this.searching = true; + }; + this.clearPagination = function () { + this.start = 0; + this.total = 0; + this.allCasesDownloaded = false; + this.cases = []; + }; + this.oldParams = {}; + this.doFilter = function () { + if (angular.isFunction(this.prefilter)) { + this.prefilter(); } - cases = strataService.cases.filter(params).then( - angular.bind(that, function (cases) { - if(cases.length < that.count){ - that.allCasesDownloaded = true; - } - that.cases = that.cases.concat(cases); - that.searching = false; - that.start = that.start + that.count; - that.total = that.total + that.count; - - if (angular.isFunction(that.postFilter)) { - that.postFilter(); + if(this.start > 0){ + this.count = this.totalCases - this.start; + } + var params = { + count: this.count, + include_closed: getIncludeClosed(), + }; + params.start = this.start; + var isObjectNothing = function (object) { + if (object === '' || object === undefined || object === null) { + return true; + } else { + return false; } - }), - angular.bind(that, function (error) { - AlertService.addStrataErrorMessage(error); - that.searching = false; - }) - ); - deferred.resolve(cases); - } else { - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - if (securityService.loginStatus.ssoName && securityService.loginStatus.isInternal) { - params.owner_ssoname = securityService.loginStatus.ssoName; - } - cases = strataService.cases.filter(params).then( - angular.bind(that, function (cases) { - if(cases.length < that.count){ - that.allCasesDownloaded = true; - } - that.cases = that.cases.concat(cases); - that.searching = false; - that.start = that.start + that.count; - that.total = that.total + that.count; + }; + if (!isObjectNothing(SearchBoxService.searchTerm)) { + params.keyword = SearchBoxService.searchTerm; + } + if (CaseService.group === CASE_GROUPS.manage) { + $state.go('group'); + } else if (CaseService.group === CASE_GROUPS.ungrouped) { + params.only_ungrouped = true; + } else if (!isObjectNothing(CaseService.group)) { + params.group_numbers = { group_number: [CaseService.group] }; + } + if (CaseService.status === STATUS.closed) { + params.status = STATUS.closed; + } + if (!isObjectNothing(CaseService.product)) { + params.product = CaseService.product; + } + if (!isObjectNothing(CaseService.owner)) { + params.owner_ssoname = CaseService.owner; + } + if (!isObjectNothing(CaseService.type)) { + params.type = CaseService.type; + } + if (!isObjectNothing(CaseService.severity)) { + params.severity = CaseService.severity; + } + this.searching = true; + //TODO: hack to get around onchange() firing at page load for each select. + //Need to prevent initial onchange() event instead of handling here. + var promises = []; + var deferred = $q.defer(); + if (!angular.equals(params, this.oldParams)) { + this.oldParams = params; + var that = this; + var cases = null; + if (securityService.loginStatus.isLoggedIn) { + if (securityService.loginStatus.authedUser.sso_username && securityService.loginStatus.authedUser.is_internal) { + params.owner_ssoname = securityService.loginStatus.authedUser.sso_username; + } + cases = strataService.cases.filter(params).then(angular.bind(that, function (response) { + if(response['case'] === undefined ){ + that.totalCases = 0; + that.total = 0; + } else { + that.totalCases = response.total_count; + if (response['case'] !== undefined && response['case'].length + that.total >= that.totalCases) { + that.allCasesDownloaded = true; + } + that.cases = that.cases.concat(response['case']); + that.start = that.start + that.count; + if (response['case'] !== undefined){ + that.total = that.total + response['case'].length; + } + if (angular.isFunction(that.postFilter)) { + that.postFilter(); + } + } + that.searching = false; + }), angular.bind(that, function (error) { + AlertService.addStrataErrorMessage(error); + that.searching = false; + })); + deferred.resolve(cases); + } else { + $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { + if (securityService.loginStatus.authedUser.sso_username && securityService.loginStatus.authedUser.is_internal) { + params.owner_ssoname = securityService.loginStatus.authedUser.sso_username; + } + cases = strataService.cases.filter(params).then(angular.bind(that, function (response) { + that.totalCases = response.total_count; + + that.cases = that.cases.concat(response['case']); + that.searching = false; + that.start = that.start + that.count; + that.total = that.total + response['case'].length; + if (that.total >= that.totalCases) { + that.allCasesDownloaded = true; + } + if (angular.isFunction(that.postFilter)) { + that.postFilter(); + } + }), angular.bind(that, function (error) { + AlertService.addStrataErrorMessage(error); + that.searching = false; + })); + deferred.resolve(cases); + }); + } + promises.push(deferred.promise); + } else { + deferred.resolve(); + promises.push(deferred.promise); + } + return $q.all(promises); + }; + } +]); - if (angular.isFunction(that.postFilter)) { - that.postFilter(); - } - }), - angular.bind(that, function (error) { - AlertService.addStrataErrorMessage(error); - that.searching = false; - }) - ); - deferred.resolve(cases); +'use strict'; +angular.module('RedhatAccess.logViewer').controller('AccordionDemoCtrl', [ + '$scope', + 'accordian', + function ($scope, accordian) { + $scope.oneAtATime = true; + $scope.groups = accordian.getGroups(); + } +]); +/*global parseList*/ +'use strict'; +angular.module('RedhatAccess.logViewer').controller('DropdownCtrl', [ + '$scope', + '$http', + '$location', + 'files', + 'hideMachinesDropdown', + 'AlertService', + function ($scope, $http, $location, files, hideMachinesDropdown, AlertService) { + $scope.machinesDropdownText = 'Please Select the Machine'; + $scope.items = []; + $scope.hideDropdown = hideMachinesDropdown.value; + $scope.loading = false; + var sessionId = $location.search().sessionId; + $scope.getMachines = function () { + $http({ + method: 'GET', + url: 'machines?sessionId=' + encodeURIComponent(sessionId) + }).success(function (data, status, headers, config) { + $scope.items = data; + }).error(function (data, status, headers, config) { + AlertService.addDangerMessage(data); + }); + }; + $scope.machineSelected = function () { + $scope.loading = true; + var sessionId = $location.search().sessionId; + var userId = $location.search().userId; + files.selectedHost = this.choice; + $scope.machinesDropdownText = this.choice; + $http({ + method: 'GET', + url: 'logs?machine=' + files.selectedHost + '&sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) + }).success(function (data, status, headers, config) { + $scope.loading = false; + var tree = []; + parseList(tree, data); + files.setFileList(tree); + }).error(function (data, status, headers, config) { + $scope.loading = false; + AlertService.addDangerMessage(data); }); - } - promises.push(deferred.promise); + }; + if ($scope.hideDropdown) { + $scope.machineSelected(); } else { - var deferred = $q.defer(); - deferred.resolve(); - promises.push(deferred.promise); + $scope.getMachines(); } - return $q.all(promises); - }; } - ]); -'use strict'; - -angular.module('RedhatAccess.logViewer') -.controller('AccordionDemoCtrl', [ - '$scope', - 'accordian', - function($scope, accordian) { - $scope.oneAtATime = true; - $scope.groups = accordian.getGroups(); -}]); -'use strict'; - -angular.module('RedhatAccess.logViewer') -.controller('DropdownCtrl', [ - '$scope', - '$http', - '$location', - 'files', - 'hideMachinesDropdown', - 'AlertService', - function($scope, $http, $location, files, hideMachinesDropdown, AlertService) { - $scope.machinesDropdownText = "Please Select the Machine"; - $scope.items = []; - $scope.hideDropdown = hideMachinesDropdown.value; - $scope.loading = false; - var sessionId = $location.search().sessionId; - - $scope.getMachines = function() { - $http({ - method : 'GET', - url : 'machines?sessionId=' + encodeURIComponent(sessionId) - }).success(function(data, status, headers, config) { - $scope.items = data; - }).error(function(data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - }; - $scope.machineSelected = function() { - $scope.loading = true; - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - files.selectedHost = this.choice; - $scope.machinesDropdownText = this.choice; - $http( - { - method : 'GET', - url : 'logs?machine=' + files.selectedHost - + '&sessionId=' + encodeURIComponent(sessionId) - + '&userId=' + encodeURIComponent(userId) - }).success(function(data, status, headers, config) { - $scope.loading = false; - var tree = new Array(); - parseList(tree, data); - files.setFileList(tree); - }).error(function(data, status, headers, config) { - $scope.loading = false; - AlertService.addDangerMessage(data); - }); - }; - if($scope.hideDropdown){ - $scope.machineSelected(); - } else{ - $scope.getMachines(); - } -}]); -'use strict'; - -angular.module('RedhatAccess.logViewer') -.controller('TabsDemoCtrl', [ - '$rootScope', - '$scope', - '$http', - '$location', - 'files', - 'accordian', - 'SearchResultsService', - 'securityService', - 'AlertService', - 'LOGVIEWER_EVENTS', - function($rootScope,$scope, $http, $location, files, accordian, SearchResultsService, securityService, AlertService,LOGVIEWER_EVENTS) { - $scope.tabs = []; - $scope.isLoading = false; - $scope.$watch(function() { - return files.getFileClicked().check; - }, function() { - if(files.getFileClicked().check && files.selectedFile != null){ - var tab = new Object(); - if(files.selectedHost != null){ - tab.longTitle = files.selectedHost + ":" - } else { - tab.longTitle = new String(); - } - tab.longTitle = tab.longTitle.concat(files.selectedFile); - var splitFileName = files.selectedFile.split("/"); - var fileName = splitFileName[splitFileName.length - 1]; - - if(files.selectedHost != null){ - tab.shortTitle = files.selectedHost + ":" - } else { - tab.shortTitle = new String(); - } - tab.shortTitle = tab.shortTitle.concat(fileName); - tab.active = true; - $scope.tabs.push(tab); - $scope.isLoading = true; - files.setActiveTab(tab); - files.setFileClicked(false); - } - }); - $scope.$watch(function() { - return files.file; - }, function() { - if (files.file != null && files.activeTab != null) { - files.activeTab.content = files.file; - $scope.isLoading = false; - files.file = null; - } - }); - $scope.$watch(function() { - return SearchResultsService.searchInProgress.value; - }, function() { - if (SearchResultsService.searchInProgress.value == true) { - $scope.$parent.isDisabled = true; - } else if(SearchResultsService.searchInProgress.value == false && $scope.$parent.textSelected == true){ - $scope.$parent.isDisabled = false; - } - }); - $scope.removeTab = function(index) { - $scope.tabs.splice(index, 1); - if ($scope.tabs.length < 1){ - $rootScope.$broadcast(LOGVIEWER_EVENTS.allTabsClosed); - } - }; - - $scope.checked = false; // This will be - // binded using the - // ps-open attribute - - $scope.diagnoseText = function() { - //$scope.isDisabled = true; - var text = strata.utils.getSelectedText(); - securityService.validateLogin(true). - then( function(){ - //Removed in refactor, no loger exists. Think it hides tool tip?? - //this.tt_isOpen = false; - if (!$scope.$parent.solutionsToggle) { - $scope.$parent.solutionsToggle = !$scope.$parent.solutionsToggle; - } - - if (text != "") { - $scope.checked = !$scope.checked; - SearchResultsService.diagnose(text, 5); - } - }); - // this.tt_isOpen = false; - // if (!$scope.$parent.solutionsToggle) { - // $scope.$parent.solutionsToggle = !$scope.$parent.solutionsToggle; - // } - // var text = strata.utils.getSelectedText(); - // if (text != "") { - // $scope.checked = !$scope.checked; - // SearchResultsService.diagnose(text, 5); - // } - //$scope.sleep(5000, $scope.checkTextSelection); - }; - - - $scope.refreshTab = function(index){ - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - var fileNameForRefresh = this.$parent.tab.longTitle; - var hostForRefresh = null; - var splitNameForRefresh = fileNameForRefresh.split(":"); - if(splitNameForRefresh[0] && splitNameForRefresh[1]){ - hostForRefresh = splitNameForRefresh[0]; - fileNameForRefresh = splitNameForRefresh[1]; - $http( - { - method : 'GET', - url : 'logs?sessionId=' - + encodeURIComponent(sessionId) + '&userId=' - + encodeURIComponent(userId) + '&path=' - + fileNameForRefresh+ '&machine=' - + hostForRefresh - }).success(function(data, status, headers, config) { - $scope.tabs[index].content = data; - }).error(function(data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - } - }; -}]); -'use strict'; - -angular.module('RedhatAccess.logViewer') -.controller('fileController', [ - '$scope', - 'files', - function($scope, files) { - $scope.roleList = ''; +]); - $scope.$watch(function() { - return $scope.mytree.currentNode; - }, function() { - if ($scope.mytree.currentNode != null - && $scope.mytree.currentNode.fullPath != null) { - files.setSelectedFile($scope.mytree.currentNode.fullPath); - files.setRetrieveFileButtonIsDisabled(false); - } else { - files.setRetrieveFileButtonIsDisabled(true); - } - }); - $scope.$watch(function() { - return files.fileList; - }, function() { - $scope.roleList = files.fileList; - }); -}]); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.controller('logViewerController', [ - '$scope', - 'SearchResultsService', - function($scope, SearchResultsService) { - $scope.isDisabled = true; - $scope.textSelected = false; - $scope.enableDiagnoseButton = function(){ - //Gotta wait for text to "unselect" - $scope.sleep(1, $scope.checkTextSelection); - }; - $scope.checkTextSelection = function(){ - if(strata.utils.getSelectedText()){ - $scope.textSelected = true; - if(SearchResultsService.searchInProgress.value){ - $scope.isDisabled = true; - } else { - $scope.isDisabled = false; - } - } else{ - $scope.textSelected = false; - $scope.isDisabled = true; - } - $scope.$apply(); - }; - - $scope.sleep = function(millis, callback) { - setTimeout(function() - { callback(); } - , millis); - }; -}]); +angular.module('RedhatAccess.logViewer').controller('TabsDemoCtrl', [ + '$rootScope', + '$scope', + '$http', + '$location', + 'files', + 'accordian', + 'SearchResultsService', + 'securityService', + 'AlertService', + 'LOGVIEWER_EVENTS', + function ($rootScope, $scope, $http, $location, files, accordian, SearchResultsService, securityService, AlertService, LOGVIEWER_EVENTS) { + $scope.tabs = []; + $scope.isLoading = false; + $scope.$watch(function () { + return files.getFileClicked().check; + }, function () { + if (files.getFileClicked().check && files.selectedFile !== undefined) { + var tab = {}; + if (files.selectedHost !== undefined) { + tab.longTitle = files.selectedHost + ':'; + } else { + tab.longTitle = ''; + } + tab.longTitle = tab.longTitle.concat(files.selectedFile); + var splitFileName = files.selectedFile.split('/'); + var fileName = splitFileName[splitFileName.length - 1]; + if (files.selectedHost !== undefined) { + tab.shortTitle = files.selectedHost + ':'; + } else { + tab.shortTitle = ''; + } + tab.shortTitle = tab.shortTitle.concat(fileName); + tab.active = true; + $scope.tabs.push(tab); + $scope.isLoading = true; + files.setActiveTab(tab); + files.setFileClicked(false); + } + }); + $scope.$watch(function () { + return files.file; + }, function () { + if (files.file && files.activeTab) { + files.activeTab.content = files.file; + $scope.isLoading = false; + files.file = undefined; + } + }); + $scope.$watch(function () { + return SearchResultsService.searchInProgress.value; + }, function () { + if (SearchResultsService.searchInProgress.value === true) { + $scope.$parent.isDisabled = true; + } else if (SearchResultsService.searchInProgress.value === false && $scope.$parent.textSelected === true) { + $scope.$parent.isDisabled = false; + } + }); + $scope.removeTab = function (index) { + $scope.tabs.splice(index, 1); + if ($scope.tabs.length < 1) { + $rootScope.$broadcast(LOGVIEWER_EVENTS.allTabsClosed); + } + }; + $scope.checked = false; + // This will be + // binded using the + // ps-open attribute + $scope.diagnoseText = function () { + //$scope.isDisabled = true; + var text = strata.utils.getSelectedText(); + securityService.validateLogin(true).then(function () { + //Removed in refactor, no loger exists. Think it hides tool tip?? + //this.tt_isOpen = false; + if (!$scope.$parent.showSolutions) { + $scope.$parent.showSolutions = !$scope.$parent.showSolutions; + } + if (text !== '') { + $scope.checked = !$scope.checked; + SearchResultsService.diagnose(text, 5); + } + }); // this.tt_isOpen = false; + // if (!$scope.$parent.solutionsToggle) { + // $scope.$parent.solutionsToggle = !$scope.$parent.solutionsToggle; + // } + // var text = strata.utils.getSelectedText(); + // if (text != "") { + // $scope.checked = !$scope.checked; + // SearchResultsService.diagnose(text, 5); + // } + //$scope.sleep(5000, $scope.checkTextSelection); + }; + $scope.refreshTab = function (index) { + var sessionId = $location.search().sessionId; + var userId = $location.search().userId; + var fileNameForRefresh = this.$parent.tab.longTitle; + var hostForRefresh = null; + var splitNameForRefresh = fileNameForRefresh.split(':'); + if (splitNameForRefresh[0] && splitNameForRefresh[1]) { + $scope.isLoading = true; + hostForRefresh = splitNameForRefresh[0]; + fileNameForRefresh = splitNameForRefresh[1]; + $http({ + method: 'GET', + url: 'logs?sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) + '&path=' + fileNameForRefresh + '&machine=' + hostForRefresh + }).success(function (data, status, headers, config) { + $scope.isLoading = false; + $scope.tabs[index].content = data; + }).error(function (data, status, headers, config) { + $scope.isLoading = false; + AlertService.addDangerMessage(data); + }); + } + }; + } +]); 'use strict'; +angular.module('RedhatAccess.logViewer').controller('fileController', [ + '$scope', + '$rootScope', + '$http', + '$location', + 'files', + 'AlertService', + 'LOGVIEWER_EVENTS', + function ($scope, $rootScope, $http, $location, files, AlertService, LOGVIEWER_EVENTS) { + $scope.roleList = ''; + $scope.retrieveFileButtonIsDisabled = files.getRetrieveFileButtonIsDisabled(); + $scope.$watch(function () { + return $scope.mytree.currentNode; + }, function () { + if ($scope.mytree.currentNode !== undefined && $scope.mytree.currentNode.fullPath !== undefined) { + files.setSelectedFile($scope.mytree.currentNode.fullPath); + files.setRetrieveFileButtonIsDisabled(false); + } else { + files.setRetrieveFileButtonIsDisabled(true); + } + }); + $scope.$watch(function () { + return files.fileList; + }, function () { + $scope.roleList = files.fileList; + }); -angular.module('RedhatAccess.logViewer') -.controller('selectFileButton', [ - '$scope', - '$rootScope', - '$http', - '$location', - 'files', - 'AlertService', - 'LOGVIEWER_EVENTS', - function($scope,$rootScope, $http, $location, - files, AlertService, LOGVIEWER_EVENTS) { - $scope.retrieveFileButtonIsDisabled = files.getRetrieveFileButtonIsDisabled(); - - $scope.fileSelected = function() { - files.setFileClicked(true); - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; - $http( - { - method : 'GET', - url : 'logs?sessionId=' - + encodeURIComponent(sessionId) + '&userId=' - + encodeURIComponent(userId) + '&path=' - + files.selectedFile + '&machine=' - + files.selectedHost - }).success(function(data, status, headers, config) { - files.file = data; - }).error(function(data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - }; + $scope.selectItem = function(){ + if(files.selectedFile !== undefined && !files.getRetrieveFileButtonIsDisabled()){ + $scope.fileSelected(); + } + }; - $rootScope.$on(LOGVIEWER_EVENTS.allTabsClosed, function() { - $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; + $scope.fileSelected = function () { + files.setFileClicked(true); + var sessionId = $location.search().sessionId; + var userId = $location.search().userId; + $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; + $http({ + method: 'GET', + url: 'logs?sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) + '&path=' + files.selectedFile + '&machine=' + files.selectedHost + }).success(function (data, status, headers, config) { + if(data !== ""){ + files.file = data; + } else { + files.file = " "; + } + }).error(function (data, status, headers, config) { + AlertService.addDangerMessage(data); + }); + }; + $rootScope.$on(LOGVIEWER_EVENTS.allTabsClosed, function () { + $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; }); -}]); -'use strict'; + } +]); -angular.module('RedhatAccess.logViewer') -.directive('rhaFilldown', [ - '$window', - '$timeout', - function($window, $timeout) { - return { - restrict: 'A', - link: function postLink(scope, element, attrs) { - scope.onResizeFunction = function() { - var distanceToTop = element[0].getBoundingClientRect().top; - var height = $window.innerHeight - distanceToTop - 21; - if(element[0].id == 'fileList'){ - height -= 34; - } - return scope.windowHeight = height; - }; - // This might be overkill?? - //scope.onResizeFunction(); - angular.element($window).bind('resize', function() { - scope.onResizeFunction(); - scope.$apply(); - }); - angular.element($window).bind('click', function() { - scope.onResizeFunction(); - scope.$apply(); - }); - $timeout(scope.onResizeFunction, 100); - // $(window).load(function(){ - // scope.onResizeFunction(); - // scope.$apply(); - // }); - // scope.$on('$viewContentLoaded', function() { - // scope.onResizeFunction(); - // //scope.$apply(); - // }); - } - } -} +'use strict'; +angular.module('RedhatAccess.logViewer').controller('logViewerController', [ + '$scope', + 'SearchResultsService', + function ($scope, SearchResultsService) { + $scope.isDisabled = true; + $scope.textSelected = false; + $scope.showSolutions = false; + $scope.enableDiagnoseButton = function () { + //Gotta wait for text to "unselect" + $scope.sleep(1, $scope.checkTextSelection); + }; + $scope.checkTextSelection = function () { + if (strata.utils.getSelectedText()) { + $scope.textSelected = true; + if (SearchResultsService.searchInProgress.value) { + $scope.isDisabled = true; + } else { + $scope.isDisabled = false; + } + } else { + $scope.textSelected = false; + $scope.isDisabled = true; + } + $scope.$apply(); + }; + $scope.sleep = function (millis, callback) { + setTimeout(function () { + callback(); + }, millis); + }; + $scope.toggleSolutions = function () { + $scope.showSolutions = !$scope.showSolutions; + }; + } ]); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.directive('rhaLogtabs', function () { - return { - templateUrl: 'log_viewer/views/logTabs.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { +angular.module('RedhatAccess.logViewer').directive('rhaFilldown', [ + '$window', + '$timeout', + function ($window, $timeout) { + return { + restrict: 'A', + link: function postLink(scope, element) { + scope.onResizeFunction = function () { + var distanceToTop = element[0].getBoundingClientRect().top; + var height = $window.innerHeight - distanceToTop - 21; + if (element[0].id === 'fileList') { + height -= 34; + } + scope.windowHeight = height; + return scope.windowHeight; + }; + // This might be overkill?? + //scope.onResizeFunction(); + angular.element($window).bind('resize', function () { + scope.onResizeFunction(); + scope.$apply(); + }); + angular.element($window).bind('click', function () { + scope.onResizeFunction(); + scope.$apply(); + }); + $timeout(scope.onResizeFunction, 100); // $(window).load(function(){ + // scope.onResizeFunction(); + // scope.$apply(); + // }); + // scope.$on('$viewContentLoaded', function() { + // scope.onResizeFunction(); + // //scope.$apply(); + // }); + } + }; } - }; +]); +'use strict'; +angular.module('RedhatAccess.logViewer').directive('rhaLogtabs', function () { + return { + templateUrl: 'log_viewer/views/logTabs.html', + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.directive('rhaLogsinstructionpane', function () { - return { - templateUrl: 'log_viewer/views/logsInstructionPane.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.logViewer').directive('rhaLogsinstructionpane', function () { + return { + templateUrl: 'log_viewer/views/logsInstructionPane.html', + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.directive('rhaNavsidebar', function () { - return { - templateUrl: 'log_viewer/views/navSideBar.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.logViewer').directive('rhaNavsidebar', function () { + return { + templateUrl: 'log_viewer/views/navSideBar.html', + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.directive('rhaRecommendations', function () { - return { - templateUrl: 'log_viewer/views/recommendations.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; +angular.module('RedhatAccess.logViewer').directive('rhaRecommendations', function () { + return { + templateUrl: 'log_viewer/views/recommendations.html', + restrict: 'A', + link: function postLink(scope, element, attrs) { + } + }; }); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.service('accordian', function() { - var groups = new Array(); - return { - getGroups : function() { - return groups; - }, - addGroup : function(group) { - groups.push(group); - }, - clearGroups : function() { - groups = ''; - } - }; +angular.module('RedhatAccess.logViewer').service('accordian', function () { + var groups = []; + return { + getGroups: function () { + return groups; + }, + addGroup: function (group) { + groups.push(group); + }, + clearGroups: function () { + groups = ''; + } + }; }); 'use strict'; - -angular.module('RedhatAccess.logViewer') -.factory('files', function() { - var fileList = ''; - var selectedFile = ''; - var selectedHost = ''; - var file = ''; - var retrieveFileButtonIsDisabled = {check : true}; - var fileClicked = {check : false}; - var activeTab = null; - return { - getFileList : function() { - return fileList; - }, - - setFileList : function(fileList) { - this.fileList = fileList; - }, - getSelectedFile : function() { - return selectedFile; - }, - - setSelectedFile : function(selectedFile) { - this.selectedFile = selectedFile; - }, - getFile : function() { - return file; - }, - - setFile : function(file) { - this.file = file; - }, - - setRetrieveFileButtonIsDisabled : function(isDisabled){ - retrieveFileButtonIsDisabled.check = isDisabled; - }, - - getRetrieveFileButtonIsDisabled : function() { - return retrieveFileButtonIsDisabled; - }, - setFileClicked : function(isClicked){ - fileClicked.check = isClicked; - }, - - getFileClicked : function() { - return fileClicked; - }, - setActiveTab : function(activeTab){ - this.activeTab = activeTab; - }, - - getActiveTab : function() { - return activeTab; - } - }; +angular.module('RedhatAccess.logViewer').factory('files', function () { + var fileList = ''; + var selectedFile = ''; + var file = ''; + var retrieveFileButtonIsDisabled = { check: true }; + var fileClicked = { check: false }; + var activeTab = null; + return { + getFileList: function () { + return fileList; + }, + setFileList: function (fileList) { + this.fileList = fileList; + }, + getSelectedFile: function () { + return selectedFile; + }, + setSelectedFile: function (selectedFile) { + this.selectedFile = selectedFile; + }, + getFile: function () { + return file; + }, + setFile: function (file) { + this.file = file; + }, + setRetrieveFileButtonIsDisabled: function (isDisabled) { + retrieveFileButtonIsDisabled.check = isDisabled; + }, + getRetrieveFileButtonIsDisabled: function () { + return retrieveFileButtonIsDisabled.check; + }, + setFileClicked: function (isClicked) { + fileClicked.check = isClicked; + }, + getFileClicked: function () { + return fileClicked; + }, + setActiveTab: function (activeTab) { + this.activeTab = activeTab; + }, + getActiveTab: function () { + return activeTab; + } + }; }); -angular.module('RedhatAccess.template', ['common/views/alert.html', 'common/views/header.html', 'common/views/title.html', 'common/views/treenode.html', 'common/views/treeview-selector.html', 'security/login_form.html', 'security/login_status.html', 'search/views/accordion_search.html', 'search/views/accordion_search_results.html', 'search/views/list_search_results.html', 'search/views/resultDetail.html', 'search/views/search.html', 'search/views/search_form.html', 'search/views/standard_search.html', 'cases/views/accountSelect.html', 'cases/views/addCommentSection.html', 'cases/views/attachLocalFile.html', 'cases/views/attachProductLogs.html', 'cases/views/attachmentsSection.html', 'cases/views/chatButton.html', 'cases/views/commentsSection.html', 'cases/views/compact.html', 'cases/views/compactCaseList.html', 'cases/views/compactEdit.html', 'cases/views/createGroupButton.html', 'cases/views/createGroupModal.html', 'cases/views/deleteGroupButton.html', 'cases/views/descriptionSection.html', 'cases/views/detailsSection.html', 'cases/views/edit.html', 'cases/views/emailNotifySelect.html', 'cases/views/entitlementSelect.html', 'cases/views/exportCSVButton.html', 'cases/views/group.html', 'cases/views/groupList.html', 'cases/views/groupSelect.html', 'cases/views/list.html', 'cases/views/listAttachments.html', 'cases/views/listBugzillas.html', 'cases/views/listFilter.html', 'cases/views/listNewAttachments.html', 'cases/views/new.html', 'cases/views/ownerSelect.html', 'cases/views/productSelect.html', 'cases/views/recommendationsSection.html', 'cases/views/requestManagementEscalationModal.html', 'cases/views/search.html', 'cases/views/searchBox.html', 'cases/views/searchResult.html', 'cases/views/selectLoadingIndicator.html', 'cases/views/severitySelect.html', 'cases/views/statusSelect.html', 'cases/views/typeSelect.html', 'log_viewer/views/logTabs.html', 'log_viewer/views/log_viewer.html', 'log_viewer/views/logsInstructionPane.html', 'log_viewer/views/navSideBar.html', 'log_viewer/views/recommendations.html']); +angular.module('RedhatAccess.template', ['common/views/alert.html', 'common/views/header.html', 'common/views/title.html', 'common/views/treenode.html', 'common/views/treeview-selector.html', 'security/views/login_form.html', 'security/views/login_status.html', 'search/views/accordion_search.html', 'search/views/accordion_search_results.html', 'search/views/list_search_results.html', 'search/views/resultDetail.html', 'search/views/search.html', 'search/views/search_form.html', 'search/views/standard_search.html', 'cases/views/accountSelect.html', 'cases/views/addCommentSection.html', 'cases/views/attachLocalFile.html', 'cases/views/attachProductLogs.html', 'cases/views/attachmentsSection.html', 'cases/views/chatButton.html', 'cases/views/commentsSection.html', 'cases/views/compact.html', 'cases/views/compactCaseList.html', 'cases/views/compactEdit.html', 'cases/views/createGroupButton.html', 'cases/views/createGroupModal.html', 'cases/views/defaultGroup.html', 'cases/views/deleteGroupButton.html', 'cases/views/descriptionSection.html', 'cases/views/detailsSection.html', 'cases/views/edit.html', 'cases/views/editGroup.html', 'cases/views/emailNotifySelect.html', 'cases/views/entitlementSelect.html', 'cases/views/exportCSVButton.html', 'cases/views/group.html', 'cases/views/groupList.html', 'cases/views/groupSelect.html', 'cases/views/list.html', 'cases/views/listAttachments.html', 'cases/views/listBugzillas.html', 'cases/views/listFilter.html', 'cases/views/listNewAttachments.html', 'cases/views/new.html', 'cases/views/newRecommendationsSection.html', 'cases/views/ownerSelect.html', 'cases/views/productSelect.html', 'cases/views/recommendationsSection.html', 'cases/views/requestManagementEscalationModal.html', 'cases/views/search.html', 'cases/views/searchBox.html', 'cases/views/searchResult.html', 'cases/views/selectLoadingIndicator.html', 'cases/views/severitySelect.html', 'cases/views/statusSelect.html', 'cases/views/typeSelect.html', 'log_viewer/views/logTabs.html', 'log_viewer/views/log_viewer.html', 'log_viewer/views/logsInstructionPane.html', 'log_viewer/views/navSideBar.html', 'log_viewer/views/recommendations.html']); angular.module("common/views/alert.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("common/views/alert.html", @@ -23544,8 +6021,8 @@ angular.module("common/views/treeview-selector.html", []).run(["$templateCache", ""); }]); -angular.module("security/login_form.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("security/login_form.html", +angular.module("security/views/login_form.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("security/views/login_form.html", "
              \n" + "

              \n" + " Sign into the Red Hat Customer Portal\n" + @@ -23584,31 +6061,37 @@ angular.module("security/login_form.html", []).run(["$templateCache", function($ "
              \n" + " \n" + " \n" + - " \n" + + " \n" + " \n" + "
              \n" + "

              \n" + ""); }]); -angular.module("security/login_status.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("security/login_status.html", +angular.module("security/views/login_status.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("security/views/login_status.html", "
              \n" + - " {{'Logged into the Red Hat Customer Portal as'|translate}} {{securityService.loginStatus.loggedInUser}}  | \n" + - " \n" + - " Log out\n" + + " {{'Logged into the Red Hat Customer Portal as'|translate}} {{securityService.loginStatus.authedUser.loggedInUser}}  | \n" + + " \n" + + " {{'Log Out'|translate}}\n" + + " \n" + + " 0\" ng-show=\"!securityService.loginStatus.verifying\">\n" + + " {{'Log Out'|translate}}\n" + " \n" + - " 0\">\n" + - " Log out\n" + + " \n" + + " {{'Log Out'|translate}}\n" + " \n" + "\n" + " {{'Not Logged into the Red Hat Customer Portal'|translate}} | \n" + - " \n" + + " \n" + " {{'Log In'|translate}}\n" + " \n" + - " 0\">\n" + + " 0\" ng-show=\"!securityService.loginStatus.verifying\">\n" + " {{'Log In'|translate}}\n" + " \n" + + " \n" + + " {{'Log In'|translate}}\n" + + " \n" + "\n" + "\n" + "
              \n" + @@ -23647,15 +6130,15 @@ angular.module("search/views/accordion_search_results.html", []).run(["$template "\n" + "
              \n" + " \n" + - " \n" + - " \n" + - " \n" + + "
              \n" + + "
              \n" + + "
              \n" + " \n" + - "  {{result.title}}\n" + - " \n" + + "  {{result.title}}\n" + + "
              \n" + "
              \n" + - " \n" + - " \n" + + "
              \n" + + "
              \n" + " \n" + "
              \n" + ""); @@ -23673,35 +6156,36 @@ angular.module("search/views/list_search_results.html", []).run(["$templateCache " \n" + "
              \n" + "
              \n" + - " {{ result.title }}\n" + + " {{ result.title }}\n" + "
              \n" + " \n" + "\n" + - "
              \n" + + "
              \n" + "
              \n" + " {{'To view a recommendation, click on it.'|translate}}\n" + "
              \n" + "
              \n" + "
              \n" + "
              \n" + - "
              "); + "
              \n" + + ""); }]); angular.module("search/views/resultDetail.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("search/views/resultDetail.html", - "
              \n" + + "
              \n" + "
              \n" + "

              Environment

              \n" + "
              \n" + "

              Issue

              \n" + - "
              \n" + + "
              \n" + "
              \n" + "

              Resolution

              \n" + "
              \n" + - "
              \n" + + "
              \n" + "
              \n" + "
              \n" + - "
              \n" + + "
              \n" + "
              \n" + "
              \n" + "\n" + @@ -23752,17 +6236,17 @@ angular.module("search/views/standard_search.html", []).run(["$templateCache", f angular.module("cases/views/accountSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/accountSelect.html", - "
               
              "); + "
               
              "); }]); angular.module("cases/views/addCommentSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/addCommentSection.html", - "
              {{'You have used 0% of the 32KB maximum description size.'|translate}}{{'Saving draft...'|translate}}{{'Draft saved'|translate}}
              "); + "
              {{'You have used'|translate}}{{progressCount}} %{{'of the 32KB maximum description size.'|translate}}
              {{'Is Public:'|translate}}
              {{'Saving draft...'|translate}}{{'Draft saved'|translate}}
              "); }]); angular.module("cases/views/attachLocalFile.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/attachLocalFile.html", - "
              {{fileName}}
              {{'File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.'|translate}} (More info)
              "); + "
              {{fileName}}
              {{'File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.'|translate}} (More info)
              "); }]); angular.module("cases/views/attachProductLogs.html", []).run(["$templateCache", function($templateCache) { @@ -23772,17 +6256,17 @@ angular.module("cases/views/attachProductLogs.html", []).run(["$templateCache", angular.module("cases/views/attachmentsSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/attachmentsSection.html", - "

              Attachments

              {{'Server File(s) To Attach:'|translate}}
              "); + "

              Attachments

              {{'Server File(s) To Attach:'|translate}}
              "); }]); angular.module("cases/views/chatButton.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/chatButton.html", - "
              "); + ""); }]); angular.module("cases/views/commentsSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/commentsSection.html", - "

              Case Discussion

              {{'Would you like a Red Hat support manager to contact you regarding this case?'|translate}}
              {{comment.created_by}}
              {{comment.created_date | date:'mediumDate'}}
              {{comment.created_date | date:'h:mm:ss a Z'}}
              {{comment.text}}
              "); + "

              Case Discussion

              {{'Would you like a Red Hat support manager to contact you regarding this case?'|translate}}
              {{comment.created_by}}
              {{comment.created_date | date:'mediumDate'}}
              {{comment.created_date | date:'h:mm:ss a Z'}}
              Private
              {{ comment.text}}
              {{'Reply'|translate}}
              "); }]); angular.module("cases/views/compact.html", []).run(["$templateCache", function($templateCache) { @@ -23816,7 +6300,7 @@ angular.module("cases/views/compactCaseList.html", []).run(["$templateCache", fu angular.module("cases/views/compactEdit.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/compactEdit.html", - "
              "); + "
              "); }]); angular.module("cases/views/createGroupButton.html", []).run(["$templateCache", function($templateCache) { @@ -23826,77 +6310,87 @@ angular.module("cases/views/createGroupButton.html", []).run(["$templateCache", angular.module("cases/views/createGroupModal.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/createGroupModal.html", - "

              Create Case Group

              "); + "

              Create Case Group

              "); +}]); + +angular.module("cases/views/defaultGroup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("cases/views/defaultGroup.html", + "
              {{'User does not have permissions to set default case group.'|translate}}
              "); }]); angular.module("cases/views/deleteGroupButton.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/deleteGroupButton.html", - ""); + ""); }]); angular.module("cases/views/descriptionSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/descriptionSection.html", - "

              Description

              {{CaseService.kase.created_by}}
              {{CaseService.kase.description}}
              "); + "

              Description

              {{CaseService.kase.created_by}}
              {{CaseService.kase.description}}
              "); }]); angular.module("cases/views/detailsSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/detailsSection.html", - "

              Case {{CaseService.kase.case_number}}

              Details

              {{'Case Type:'|translate}}
              {{'Severity:'|translate}}
              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              {{'Status:'|translate}}
              {{'Alternate ID:'|translate}}
              {{'Product:'|translate}}
              {{'Product Version:'|translate}}
              {{'Support Level:'|translate}}{{CaseService.kase.entitlement.sla}}
              {{'Owner:'|translate}}{{CaseService.kase.contact_name}}
              {{'Red Hat Owner:'|translate}}{{CaseService.kase.owner}}
              {{'Group:'|translate}}
              {{'Opened:'|translate}}
              {{CaseService.kase.created_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.created_by}}
              {{'Last Updated:'|translate}}
              {{CaseService.kase.last_modified_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.last_modified_by}}
              {{'Account Number:'|translate}}{{CaseService.kase.account_number}}
              {{'Account Name:'|translate}}{{CaseService.account.name}}
              "); + "

              Case {{CaseService.kase.case_number}} {{'Advanced Mission Critical'|translate}}

              Details

              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              {{CaseService.kase.entitlement.sla}}
              {{CaseService.kase.contact_name}} <{{CaseService.kase.contact_sso_username }}>
              {{CaseService.kase.owner}}
              {{CaseService.kase.created_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.created_by}}
              {{CaseService.kase.last_modified_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.last_modified_by}}
              {{CaseService.kase.account_number}}
              {{CaseService.account.name}}
              "); }]); angular.module("cases/views/edit.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/edit.html", - "
              "); + "
              "); +}]); + +angular.module("cases/views/editGroup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("cases/views/editGroup.html", + "
              {{'User Name'|translate}}
              {{'First Name'|translate}}
              {{'Last Name'|translate}}
              {{user.sso_username}}{{user.first_name}}{{user.last_name}}
              "); }]); angular.module("cases/views/emailNotifySelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/emailNotifySelect.html", - "

              Email Notification Recipients

              {{user}}
              "); + "

              Email Notification Recipients

              "); }]); angular.module("cases/views/entitlementSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/entitlementSelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/exportCSVButton.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/exportCSVButton.html", - "
              {{'Exporting CSV...'|translate}}
              "); + "
              {{'Exporting CSV...'|translate}}
              "); }]); angular.module("cases/views/group.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/group.html", - "
              "); + "
              "); }]); angular.module("cases/views/groupList.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/groupList.html", - "
              {{'No groups found.'|translate}}
              {{'Name'|translate}}
              {{group.name}}
              "); + "
              {{'No groups found.'|translate}}
              {{'Name'|translate}}
              {{group.name}}

              {{group.name}}

              "); }]); angular.module("cases/views/groupSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/groupSelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/list.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/list.html", - "
              {{'No cases found with given filters.'|translate}}
              {{case.case_number}}{{case.summary}}{{case.product}} / {{case.version}}{{case.status}}{{case.severity}}{{case.contact_name}}{{case.created_date | date:'longDate'}}{{case.last_modified_date | date:'longDate'}}
              "); + "
              {{'User does not have permissions to manage cases.'|translate}}
              {{'No cases found with given filters.'|translate}}
              {{case.case_number}}{{case.summary}}{{case.product}} / {{case.version}}{{case.status}}{{case.severity}}{{case.contact_name}}{{case.created_date | date:'longDate'}}{{case.last_modified_date | date:'longDate'}}
              "); }]); angular.module("cases/views/listAttachments.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/listAttachments.html", - "
              {{'No attachments added'|translate}}
              {{'Attached Files'|translate}}
              {{'Filename'|translate}}{{'Description'|translate}}{{'Size'|translate}}{{'Attached'|translate}}{{'Attached By'|translate}}{{'Delete'|translate}}
              {{attachment.file_name}}
              {{attachment.file_name}}
              {{attachment.description}}{{attachment.length | bytes}}{{attachment.created_date | date:'medium'}}{{attachment.created_by}}
              {{'Delete'|translate}}
              {{'Delete'|translate}}
              "); + "
              {{'No attachments added'|translate}}
              {{'Attached Files'|translate}}
              {{'Filename'|translate}}{{'Description'|translate}}{{'Size'|translate}}{{'Attached'|translate}}{{'Attached By'|translate}}{{'Delete'|translate}}
              {{attachment.file_name}}
              {{attachment.file_name}}
              {{attachment.description}}{{attachment.length | bytes}}{{attachment.created_date | date:'medium'}}{{attachment.created_by}}
              {{'Delete'|translate}}
              {{'Delete'|translate}}
              "); }]); angular.module("cases/views/listBugzillas.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/listBugzillas.html", - "

              {{'Bugzilla Tickets'|translate}}

              {{'No linked bugzillas'|translate}}
              {{'Bugzilla Number'|translate}}{{'Summary of Request'|translate}}
              {{bugzilla.bugzilla_number}}{{bugzilla.summary}}
              "); + "

              {{'Bugzilla Tickets'|translate}}

              {{'No linked bugzillas'|translate}}
              {{'Bugzilla Number'|translate}}{{'Summary of Request'|translate}}
              {{bugzilla.bugzilla_number}}{{bugzilla.summary}}
              "); }]); angular.module("cases/views/listFilter.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/listFilter.html", - "
              "); + "
              "); }]); angular.module("cases/views/listNewAttachments.html", []).run(["$templateCache", function($templateCache) { @@ -23906,27 +6400,32 @@ angular.module("cases/views/listNewAttachments.html", []).run(["$templateCache", angular.module("cases/views/new.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/new.html", - "
              {{'Account:'|translate}}
              {{'Owner:'|translate}}
              {{'Product:'|translate}}
              {{'Product Version:'|translate}}
              {{'Summary:'|translate}}
              {{'Description:'|translate}}
              {{CaseService.kase.product.name}} {{CaseService.kase.version}}
              {{CaseService.kase.summary}}
              {{CaseService.kase.description}}
              Support Level:
              {{CaseService.entitlements[0]}}
              {{'Severity:'|translate}}
              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              {{'Case Group:'|translate}}
              {{'Attachments:'|translate}}
              Server File(s) To Attach:
              "); + "
              {{'This release is now retired, please refer to the recommended FAQ prior to filing a case'|translate}}
              {{CaseService.kase.product.name}} {{CaseService.kase.version}}
              {{CaseService.kase.summary}}
              {{CaseService.kase.description}}
              {{CaseService.entitlements[0]}}
              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              Server File(s) To Attach:
              "); +}]); + +angular.module("cases/views/newRecommendationsSection.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("cases/views/newRecommendationsSection.html", + "

              Red Hat Access Recommendations

              "); }]); angular.module("cases/views/ownerSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/ownerSelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/productSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/productSelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/recommendationsSection.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/recommendationsSection.html", - "

              Recommendations

               
              {{recommendation.title}}
              handpicked
              {{recommendation.resolution.text | recommendationsResolution}}
              {{'View full article in new window'|translate}}
              "); + "

              Recommendations

               
              {{recommendation.title}}
              handpicked
              {{recommendation.resolution.text | recommendationsResolution}}
              {{'View full article in new window'|translate}}
              "); }]); angular.module("cases/views/requestManagementEscalationModal.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/requestManagementEscalationModal.html", - "

              Request Management Escalation

              {{'If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.'|translate}}{{'Learn more'|translate}}
              {{'Comment:'|translate}}
              "); + "

              Request Management Escalation

              {{'If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.'|translate}}{{'Learn more'|translate}}
              {{'Comment:'|translate}}
              "); }]); angular.module("cases/views/search.html", []).run(["$templateCache", function($templateCache) { @@ -23936,7 +6435,7 @@ angular.module("cases/views/search.html", []).run(["$templateCache", function($t angular.module("cases/views/searchBox.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/searchBox.html", - "
              "); + "
              "); }]); angular.module("cases/views/searchResult.html", []).run(["$templateCache", function($templateCache) { @@ -23951,17 +6450,17 @@ angular.module("cases/views/selectLoadingIndicator.html", []).run(["$templateCac angular.module("cases/views/severitySelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/severitySelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/statusSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/statusSelect.html", - "
              "); + "
              "); }]); angular.module("cases/views/typeSelect.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("cases/views/typeSelect.html", - "
              "); + "
              "); }]); angular.module("log_viewer/views/logTabs.html", []).run(["$templateCache", function($templateCache) { @@ -23995,7 +6494,8 @@ angular.module("log_viewer/views/logTabs.html", []).run(["$templateCache", funct "
              \n" + "
              \n" + " \n" + - ""); + "\n" + + ""); }]); angular.module("log_viewer/views/log_viewer.html", []).run(["$templateCache", function($templateCache) { @@ -24078,12 +6578,14 @@ angular.module("log_viewer/views/navSideBar.html", []).run(["$templateCache", fu " \n" + " \n" + " \n" + - "
              \n" + - "
              \n" + + "
              \n" + + "
              \n" + + "
              \n" + + "
              \n" + "
              \n" + + " \n" + "
              \n" + - " \n" + "
              \n" + "
              \n" + " \n" + @@ -24094,11 +6596,11 @@ angular.module("log_viewer/views/navSideBar.html", []).run(["$templateCache", fu angular.module("log_viewer/views/recommendations.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("log_viewer/views/recommendations.html", - "
              \n" + - "
              \n" + + ""); }]); diff --git a/assets/redhat_access_angular_ui.no-deps.js b/assets/redhat_access_angular_ui.no-deps.js deleted file mode 100644 index acf5755..0000000 --- a/assets/redhat_access_angular_ui.no-deps.js +++ /dev/null @@ -1,5368 +0,0 @@ -/*! redhat_access_angular_ui - v0.9.9 - 2014-09-01 - * Copyright (c) 2014 ; - * Licensed - */ -angular.module('gettext').run(['gettextCatalog', function (gettextCatalog) { -/* jshint -W100 */ -/* jshint +W100 */ -}]); -'use strict'; -angular.module('RedhatAccess.cases', [ - 'ui.router', - 'ui.bootstrap', - 'ui.select2', - 'ngTable', - 'RedhatAccess.template', - 'RedhatAccess.security', - 'RedhatAccess.search', - 'RedhatAccess.ui-utils', - 'RedhatAccess.common', - 'RedhatAccess.header' -]).constant('CASE_EVENTS', { received: 'case-received' }).constant('CHAT_SUPPORT', { - enableChat: false, - chatButtonToken: '573A0000000GmiP', - chatLiveAgentUrlPrefix: 'https://d.la8cs.salesforceliveagent.com/chat', - chatInitHashOne: '572A0000000GmiP', - chatInitHashTwo: '00DK000000W3mDA', - chatIframeHackUrlPrefix: 'https://qa-rogsstest.cs9.force.com/chatHidden' -}).constant('STATUS', { - open: 'open', - closed: 'closed', - both: 'both' -}).value('NEW_DEFAULTS', { - 'product': '', - 'version': '' -}).value('GLOBAL_CASE_CONFIG', { - 'showRecommendations': true, - 'showAttachments': true -}).value('NEW_CASE_CONFIG', { - 'showRecommendations': true, - 'showAttachments': true, - 'showServerSideAttachments': true -}).value('EDIT_CASE_CONFIG', { - 'showDetails': true, - 'showDescription': true, - 'showBugzillas': true, - 'showAttachments': true, - 'showRecommendations': true, - 'showComments': true, - 'showServerSideAttachments': true, - 'showEmailNotifications': true -}).config([ - '$stateProvider', - function ($stateProvider) { - $stateProvider.state('compact', { - url: '/case/compact?sessionId', - templateUrl: 'cases/views/compact.html' - }); - $stateProvider.state('compact.edit', { - url: '/{id:[0-9]{1,8}}', - templateUrl: 'cases/views/compactEdit.html', - controller: 'CompactEdit' - }); - $stateProvider.state('edit', { - url: '/case/{id:[0-9]{1,8}}', - templateUrl: 'cases/views/edit.html', - controller: 'Edit' - }); - $stateProvider.state('new', { - url: '/case/new', - templateUrl: 'cases/views/new.html', - controller: 'New' - }); - $stateProvider.state('list', { - url: '/case/list', - templateUrl: 'cases/views/list.html', - controller: 'List' - }); - $stateProvider.state('searchCases', { - url: '/case/search', - templateUrl: 'cases/views/search.html', - controller: 'Search' - }); - $stateProvider.state('group', { - url: '/case/group', - controller: 'Group', - templateUrl: 'cases/views/group.html' - }); - } -]); -/*global angular */ -'use strict'; -/*global $ */ -angular.module('RedhatAccess.common', [ - 'RedhatAccess.ui-utils', - 'jmdobry.angular-cache' -]).config(["$angularCacheFactoryProvider", function ($angularCacheFactoryProvider) { -}]); -'use strict'; -/*global $ */ -angular.module('RedhatAccess.header', []).value('TITLE_VIEW_CONFIG', { - show: 'false', - titlePrefix: 'Red Hat Access: ', - searchTitle: 'Search', - caseListTitle: 'Support Cases', - caseViewTitle: 'View/Modify Case', - newCaseTitle: 'New Support Case', - searchCaseTitle: 'Search Support Cases', - logViewerTitle: 'Log', - manageGroupsTitle: 'Manage Case Groups' -}).controller('TitleViewCtrl', [ - 'TITLE_VIEW_CONFIG', - '$scope', - function (TITLE_VIEW_CONFIG, $scope) { - $scope.showTitle = TITLE_VIEW_CONFIG.show; - $scope.titlePrefix = TITLE_VIEW_CONFIG.titlePrefix; - $scope.getPageTitle = function () { - switch ($scope.page) { - case 'search': - return TITLE_VIEW_CONFIG.searchTitle; - case 'caseList': - return TITLE_VIEW_CONFIG.caseListTitle; - case 'caseView': - return TITLE_VIEW_CONFIG.caseViewTitle; - case 'newCase': - return TITLE_VIEW_CONFIG.newCaseTitle; - case 'logViewer': - return TITLE_VIEW_CONFIG.logViewerTitle; - case 'searchCase': - return TITLE_VIEW_CONFIG.searchCaseTitle; - case 'manageGroups': - return TITLE_VIEW_CONFIG.manageGroupsTitle; - default: - return ''; - } - }; - } -]).directive('rhaTitletemplate', function () { - return { - restrict: 'AE', - scope: { page: '@' }, - templateUrl: 'common/views/title.html', - controller: 'TitleViewCtrl' - }; -}).service('AlertService', [ - '$filter', - 'AUTH_EVENTS', - '$rootScope', - 'RHAUtils', - function ($filter, AUTH_EVENTS, $rootScope, RHAUtils) { - var ALERT_TYPES = { - DANGER: 'danger', - SUCCESS: 'success', - WARNING: 'warning' - }; - this.alerts = []; - //array of {message: 'some alert', type: ''} objects - this.clearAlerts = function () { - this.alerts = []; - }; - this.addAlert = function (alert) { - this.alerts.push(alert); - }; - this.removeAlert = function (alert) { - this.alerts.splice(this.alerts.indexOf(alert), 1); - }; - this.addDangerMessage = function (message) { - return this.addMessage(message, ALERT_TYPES.DANGER); - }; - this.addSuccessMessage = function (message) { - return this.addMessage(message, ALERT_TYPES.SUCCESS); - }; - this.addWarningMessage = function (message) { - return this.addMessage(message, ALERT_TYPES.WARNING); - }; - this.addMessage = function (message, type) { - var alert = { - message: message, - type: type === null ? 'warning' : type - }; - this.addAlert(alert); - $('body,html').animate({ scrollTop: $('body').offset().top }, 100); - //Angular adds a unique hash to each alert during data binding, - //so the returned alert will be unique even if the - //message and type are identical. - return alert; - }; - this.getErrors = function () { - var errors = $filter('filter')(this.alerts, { type: ALERT_TYPES.DANGER }); - if (errors === null) { - errors = []; - } - return errors; - }; - this.addStrataErrorMessage = function (error) { - if (RHAUtils.isNotEmpty(error)) { - var existingMessage = $filter('filter')(this.alerts, { - type: ALERT_TYPES.DANGER, - message: error.message - }); - if (existingMessage.length < 1) { - this.addDangerMessage(error.message); - } - } - }; - $rootScope.$on(AUTH_EVENTS.logoutSuccess, angular.bind(this, function () { - this.clearAlerts(); - this.addMessage('You have successfully logged out of the Red Hat Customer Portal.'); - })); - $rootScope.$on(AUTH_EVENTS.loginSuccess, angular.bind(this, function () { - this.clearAlerts(); - })); - } -]).directive('rhaAlert', function () { - return { - templateUrl: 'common/views/alert.html', - restrict: 'A', - controller: 'AlertController' - }; -}).controller('AlertController', [ - '$scope', - 'AlertService', - function ($scope, AlertService) { - $scope.AlertService = AlertService; - $scope.closeable = true; - $scope.closeAlert = function (index) { - AlertService.alerts.splice(index, 1); - }; - $scope.dismissAlerts = function () { - AlertService.clearAlerts(); - }; - } -]).directive('rhaHeader', function () { - return { - templateUrl: 'common/views/header.html', - restrict: 'A', - scope: { page: '@' }, - controller: 'HeaderController' - }; -}).controller('HeaderController', [ - '$scope', - 'AlertService', - function ($scope, AlertService) { - /** - * For some reason the rhaAlert directive's controller is not binding to the view. - * Hijacking rhaAlert's parent controller (HeaderController) works - * until a real solution is found. - */ - $scope.AlertService = AlertService; - $scope.closeable = true; - $scope.closeAlert = function (index) { - AlertService.alerts.splice(index, 1); - }; - $scope.dismissAlerts = function () { - AlertService.clearAlerts(); - }; - } -]).factory('configurationService', [ - '$q', - function ($q) { - var defer = $q.defer(); - var service = { - setConfig: function (config) { - defer.resolve(config); - }, - getConfig: function () { - return defer.promise; - } - }; - return service; - } -]); -'use strict'; -/*jshint unused:vars */ -var app = angular.module('RedhatAccess.ui-utils', ['gettext']); -//this is an example controller to provide tree data -// app.controller('TreeViewSelectorCtrl', ['$scope', 'TreeViewSelectorData', -// function($scope, TreeViewSelectorData) { -// $scope.name = 'Attachments'; -// $scope.attachmentTree = []; -// TreeViewSelectorData.getTree('attachments').then( -// function(tree) { -// $scope.attachmentTree = tree; -// }, -// function() { -// }); -// } -// ]); -app.service('RHAUtils', function () { - /** - * Generic function to decide if a simple object should be considered nothing - */ - this.isEmpty = function (object) { - if (object === undefined || object === null || object === '' || object.length === 0 || object === {}) { - return true; - } - return false; - }; - this.isNotEmpty = function (object) { - return !this.isEmpty(object); - }; -}); -//Wrapper service for translations -app.service('translate', [ - 'gettextCatalog', - function (gettextCatalog) { - return function (str) { - return gettextCatalog.getString(str); - }; - } -]); -app.directive('rhaChoicetree', function () { - return { - template: '
              ', - replace: true, - transclude: true, - restrict: 'A', - scope: { - tree: '=ngModel', - rhaDisabled: '=' - } - }; -}); -app.directive('rhaChoice', ["$compile", function ($compile) { - return { - restrict: 'A', - templateUrl: 'common/views/treenode.html', - link: function (scope, elm) { - scope.choiceClicked = function (choice) { - choice.checked = !choice.checked; - function checkChildren(c) { - angular.forEach(c.children, function (c) { - c.checked = choice.checked; - checkChildren(c); - }); - } - checkChildren(choice); - }; - if (scope.choice.children.length > 0) { - var childChoice = $compile('
              ')(scope); - elm.append(childChoice); - } - } - }; -}]); -app.factory('TreeViewSelectorData', [ - '$http', - '$q', - 'TreeViewSelectorUtils', - function ($http, $q, TreeViewSelectorUtils) { - var service = { - getTree: function (dataUrl, sessionId) { - var defer = $q.defer(); - var tmpUrl = dataUrl; - if (sessionId) { - tmpUrl = tmpUrl + '?sessionId=' + encodeURIComponent(sessionId); - } - $http({ - method: 'GET', - url: tmpUrl - }).success(function (data, status, headers, config) { - var tree = []; - TreeViewSelectorUtils.parseTreeList(tree, data); - defer.resolve(tree); - }).error(function (data, status, headers, config) { - defer.reject({}); - }); - return defer.promise; - } - }; - return service; - } -]); -app.factory('TreeViewSelectorUtils', function () { - var parseTreeNode = function (splitPath, tree, fullFilePath) { - if (splitPath[0] !== undefined) { - if (splitPath[0] !== '') { - var node = splitPath[0]; - var match = false; - var index = 0; - for (var i = 0; i < tree.length; i++) { - if (tree[i].name === node) { - match = true; - index = i; - break; - } - } - if (!match) { - var nodeObj = {}; - nodeObj.checked = isLeafChecked(node); - nodeObj.name = removeParams(node); - if (splitPath.length === 1) { - nodeObj.fullPath = removeParams(fullFilePath); - } - nodeObj.children = []; - tree.push(nodeObj); - index = tree.length - 1; - } - splitPath.shift(); - parseTreeNode(splitPath, tree[index].children, fullFilePath); - } else { - splitPath.shift(); - parseTreeNode(splitPath, tree, fullFilePath); - } - } - }; - var removeParams = function (path) { - if (path) { - var split = path.split('?'); - return split[0]; - } - return path; - }; - var isLeafChecked = function (path) { - if (path) { - var split = path.split('?'); - if (split[1]) { - var params = split[1].split('&'); - for (var i = 0; i < params.length; i++) { - if (params[i].indexOf('checked=true') !== -1) { - return true; - } - } - } - } - return false; - }; - var hasSelectedLeaves = function (tree) { - for (var i = 0; i < tree.length; i++) { - if (tree[i] !== undefined) { - if (tree[i].children.length === 0) { - //we only check leaf nodes - if (tree[i].checked === true) { - return true; - } - } else { - if (hasSelectedLeaves(tree[i].children)) { - return true; - } - } - } - } - return false; - }; - var getSelectedNames = function (tree, container) { - for (var i = 0; i < tree.length; i++) { - if (tree[i] !== undefined) { - if (tree[i].children.length === 0) { - if (tree[i].checked === true) { - container.push(tree[i].fullPath); - } - } else { - getSelectedNames(tree[i].children, container); - } - } - } - }; - var service = { - parseTreeList: function (tree, data) { - var files = data.split('\n'); - for (var i = 0; i < files.length; i++) { - var file = files[i]; - var splitPath = file.split('/'); - parseTreeNode(splitPath, tree, file); - } - }, - hasSelections: function (tree) { - return hasSelectedLeaves(tree); - }, - getSelectedLeaves: function (tree) { - if (tree === undefined) { - return []; - } - var container = []; - getSelectedNames(tree, container); - return container; - } - }; - return service; -}); -app.directive('rhaResizable', [ - '$window', - '$timeout', - function ($window) { - var link = function (scope, element, attrs) { - scope.onResizeFunction = function () { - var distanceToTop = element[0].getBoundingClientRect().top; - var height = $window.innerHeight - distanceToTop; - element.css('height', height); - }; - angular.element($window).bind('resize', function () { - scope.onResizeFunction(); //scope.$apply(); - }); - angular.element($window).bind('click', function () { - scope.onResizeFunction(); //scope.$apply(); - }); - if (attrs.rhaDomReady !== undefined) { - scope.$watch('rhaDomReady', function (newValue) { - if (newValue) { - scope.onResizeFunction(); - } - }); - } else { - scope.onResizeFunction(); - } - }; - return { - restrict: 'A', - scope: { rhaDomReady: '=' }, - link: link - }; - } -]); -//var testURL = 'http://localhost:8080/LogCollector/'; -// angular module -'use strict'; -angular.module('RedhatAccess.logViewer', [ - 'angularTreeview', - 'ui.bootstrap', - 'RedhatAccess.search', - 'RedhatAccess.header' -]).config([ - '$stateProvider', - function ($stateProvider) { - $stateProvider.state('logviewer', { - url: '/logviewer', - templateUrl: 'log_viewer/views/log_viewer.html' - }); - } -]).constant('LOGVIEWER_EVENTS', { allTabsClosed: 'allTabsClosed' }).value('hideMachinesDropdown', { value: false }); -function returnNode(splitPath, tree, fullFilePath) { - if (splitPath[0] !== undefined) { - if (splitPath[0] !== '') { - var node = splitPath[0]; - var match = false; - var index = 0; - for (var i in tree) { - if (tree[i].roleName === node) { - match = true; - index = i; - break; - } - } - if (!match) { - var object = {}; - object.roleName = node; - object.roleId = node; - if (splitPath.length === 1) { - object.fullPath = fullFilePath; - } - object.children = []; - tree.push(object); - index = tree.length - 1; - } - splitPath.shift(); - returnNode(splitPath, tree[index].children, fullFilePath); - } else { - splitPath.shift(); - returnNode(splitPath, tree, fullFilePath); - } - } -} -function parseList(tree, data) { - var files = data.split('\n'); - for (var i in files) { - var file = files[i]; - var splitPath = file.split('/'); - returnNode(splitPath, tree, file); - } -} - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search', [ - 'ui.router', - 'RedhatAccess.template', - 'RedhatAccess.security', - 'ui.bootstrap', - 'ngSanitize', - 'RedhatAccess.ui-utils', - 'RedhatAccess.common', - 'RedhatAccess.header' -]).constant('RESOURCE_TYPES', { - article: 'Article', - solution: 'Solution' -}).constant('SEARCH_PARAMS', { limit: 10 }).value('SEARCH_CONFIG', { - openCaseRef: '#/case/new', - showOpenCaseBtn: true -}).config([ - '$stateProvider', - function ($stateProvider) { - $stateProvider.state('search', { - url: '/search', - controller: 'SearchController', - templateUrl: 'search/views/search.html' - }).state('search_accordion', { - url: '/search2', - controller: 'SearchController', - templateUrl: 'search/views/accordion_search.html' - }); - } -]); -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.security', [ - 'ui.bootstrap', - 'RedhatAccess.template', - 'ui.router', - 'RedhatAccess.common', - 'RedhatAccess.header' -]).constant('AUTH_EVENTS', { - loginSuccess: 'auth-login-success', - loginFailed: 'auth-login-failed', - logoutSuccess: 'auth-logout-success', - sessionTimeout: 'auth-session-timeout', - notAuthenticated: 'auth-not-authenticated', - notAuthorized: 'auth-not-authorized', - sessionIdChanged: 'sid-changed' -}).value('LOGIN_VIEW_CONFIG', { verbose: true }).value('SECURITY_CONFIG', { - displayLoginStatus: true, - autoCheckLogin: true, - loginURL: '', - logoutURL: '', - forceLogin: false -}); -'use strict'; -/*global strata, angular*/ -/*jshint camelcase: false */ -/*jshint bitwise: false */ -/*jshint unused:vars */ -angular.module('RedhatAccess.common').factory('strataService', [ - '$q', - 'translate', - 'RHAUtils', - '$angularCacheFactory', - function ($q, translate, RHAUtils, $angularCacheFactory) { - $angularCacheFactory('strataCache', { - capacity: 1000, - maxAge: 900000, - deleteOnExpire: 'aggressive', - recycleFreq: 60000, - cacheFlushInterval: 3600000, - storageMode: 'sessionStorage', - verifyIntegrity: true - }); - var strataCache = $angularCacheFactory.get('strataCache'); - var errorHandler = function (message, xhr, response, status) { - var translatedMsg = message; - switch (status) { - case 'Unauthorized': - translatedMsg = translate('Unauthorized.'); - break; // case n: - // code block - // break; - } - this.reject({ - message: translatedMsg, - xhr: xhr, - response: response, - status: status - }); - }; - var service = { - authentication: { - checkLogin: function () { - var deferred = $q.defer(); - if (strataCache.get('auth')) { - deferred.resolve(strataCache.get('auth')); - } else { - strata.checkLogin(function (result, authedUser) { - if (result) { - service.accounts.list().then(function (accountNumber) { - service.accounts.get(accountNumber).then(function (account) { - authedUser.account = account; - strataCache.put('auth', authedUser); - deferred.resolve(authedUser); - }); - }, function (error) { - //TODO revisit this behavior - authedUser.account = undefined; - deferred.resolve(authedUser); - }); - } else { - deferred.reject('Unauthorized.'); - } - }); - } - return deferred.promise; - }, - setCredentials: function (username, password) { - return strata.setCredentials(username,password); - }, - logout: function (){ - strataCache.removeAll(); - strata.clearCredentials(); - } - }, - entitlements: { - get: function (showAll, ssoUserName) { - var deferred = $q.defer(); - if (strataCache.get('entitlements' + ssoUserName)) { - deferred.resolve(strataCache.get('entitlements' + ssoUserName)); - } else { - strata.entitlements.get(showAll, function (entitlements) { - strataCache.put('entitlements' + ssoUserName, entitlements); - deferred.resolve(entitlements); - }, angular.bind(deferred, errorHandler), ssoUserName); - } - return deferred.promise; - } - }, - problems: function (data, max) { - var deferred = $q.defer(); - strata.problems(data, function (solutions) { - deferred.resolve(solutions); - }, angular.bind(deferred, errorHandler), max); - return deferred.promise; - }, - solutions: { - get: function (uri) { - var deferred = $q.defer(); - if (strataCache.get('solution' + uri)) { - deferred.resolve(strataCache.get('solution' + uri)); - } else { - strata.solutions.get(uri, function (solution) { - strataCache.put('solution' + uri, solution); - deferred.resolve(solution); - }, function () { - //workaround for 502 from strata - //If the deferred is rejected then the parent $q.all() - //based deferred will fail. Since we don't need every - //recommendation just send back undefined - //and the caller can ignore the missing solution details. - deferred.resolve(); - }); - } - return deferred.promise; - } - }, - products: { - list: function (ssoUserName) { - var deferred = $q.defer(); - if (strataCache.get('products' + ssoUserName)) { - deferred.resolve(strataCache.get('products' + ssoUserName)); - } else { - strata.products.list(function (response) { - strataCache.put('products' + ssoUserName, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler), ssoUserName); - } - return deferred.promise; - }, - versions: function (productCode) { - var deferred = $q.defer(); - if (strataCache.get('versions-' + productCode)) { - deferred.resolve(strataCache.get('versions-' + productCode)); - } else { - strata.products.versions(productCode, function (response) { - strataCache.put('versions-' + productCode, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - } - }, - groups: { - list: function (ssoUserName) { - var deferred = $q.defer(); - if (strataCache.get('groups' + ssoUserName)) { - deferred.resolve(strataCache.get('groups' + ssoUserName)); - } else { - strata.groups.list(function (response) { - strataCache.put('groups' + ssoUserName, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler), ssoUserName); - } - return deferred.promise; - }, - remove: function (groupNum) { - var deferred = $q.defer(); - strata.groups.remove(groupNum, function (response) { - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - create: function (groupName) { - var deferred = $q.defer(); - strata.groups.create(groupName, function (response) { - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - } - }, - accounts: { - get: function (accountNumber) { - var deferred = $q.defer(); - if (strataCache.get('account' + accountNumber)) { - deferred.resolve(strataCache.get('account' + accountNumber)); - } else { - strata.accounts.get(accountNumber, function (response) { - strataCache.put('account' + accountNumber, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - users: function (accountNumber, group) { - var deferred = $q.defer(); - if (strataCache.get('users' + accountNumber + group)) { - deferred.resolve(strataCache.get('users' + accountNumber + group)); - } else { - strata.accounts.users(accountNumber, function (response) { - strataCache.put('users' + accountNumber + group, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler), group); - } - return deferred.promise; - }, - list: function () { - var deferred = $q.defer(); - if (strataCache.get('account')) { - deferred.resolve(strataCache.get('account')); - } else { - strata.accounts.list(function (response) { - strataCache.put('account', response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - } - }, - cases: { - csv: function () { - var deferred = $q.defer(); - strata.cases.csv(function (response) { - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - attachments: { - list: function (id) { - var deferred = $q.defer(); - if (strataCache.get('attachments' + id)) { - deferred.resolve(strataCache.get('attachments' + id)); - } else { - strata.cases.attachments.list(id, function (response) { - strataCache.put('attachments' + id, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - post: function (attachment, caseNumber) { - var deferred = $q.defer(); - strata.cases.attachments.post(attachment, caseNumber, function (response, code, xhr) { - strataCache.remove('attachments' + caseNumber); - deferred.resolve(xhr.getResponseHeader('Location')); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - remove: function (id, caseNumber) { - var deferred = $q.defer(); - strata.cases.attachments.remove(id, caseNumber, function (response) { - strataCache.remove('attachments' + caseNumber); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - } - }, - comments: { - get: function (id) { - var deferred = $q.defer(); - if (strataCache.get('comments' + id)) { - deferred.resolve(strataCache.get('comments' + id)); - } else { - strata.cases.comments.get(id, function (response) { - strataCache.put('comments' + id, response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - post: function (caseNumber, text, isDraft) { - var deferred = $q.defer(); - strata.cases.comments.post(caseNumber, { - 'text': text, - 'draft': isDraft === true ? 'true' : 'false' - }, function (response) { - strataCache.remove('comments' + caseNumber); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - put: function (caseNumber, text, isDraft, comment_id) { - var deferred = $q.defer(); - strata.cases.comments.update(caseNumber, { - 'text': text, - 'draft': isDraft === true ? 'true' : 'false', - 'caseNumber': caseNumber, - 'id': comment_id - }, comment_id, function (response) { - strataCache.remove('comments' + caseNumber); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - } - }, - notified_users: { - add: function (caseNumber, ssoUserName) { - var deferred = $q.defer(); - strata.cases.notified_users.add(caseNumber, ssoUserName, function (response) { - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - remove: function (caseNumber, ssoUserName) { - var deferred = $q.defer(); - strata.cases.notified_users.remove(caseNumber, ssoUserName, function (response) { - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - } - }, - get: function (id) { - var deferred = $q.defer(); - if (strataCache.get('case' + id)) { - deferred.resolve([ - strataCache.get('case' + id), - true - ]); - } else { - strata.cases.get(id, function (response) { - strataCache.put('case' + id, response); - deferred.resolve([ - response, - false - ]); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - filter: function (params) { - var deferred = $q.defer(); - if (RHAUtils.isEmpty(params)) { - params = {}; - } - if (RHAUtils.isEmpty(params.count)) { - params.count = 50; - } - if (strataCache.get('filter' + JSON.stringify(params))) { - deferred.resolve(strataCache.get('filter' + JSON.stringify(params))); - } else { - strata.cases.filter(params, function (allCases) { - strataCache.put('filter' + JSON.stringify(params), allCases); - deferred.resolve(allCases); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - post: function (caseJSON) { - var deferred = $q.defer(); - strata.cases.post(caseJSON, function (caseNumber) { - //Remove any case filters that are cached - for (var k in strataCache.keySet()) { - if (~k.indexOf('filter')) { - strataCache.remove(k); - } - } - deferred.resolve(caseNumber); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - }, - put: function (caseNumber, caseJSON) { - var deferred = $q.defer(); - strata.cases.put(caseNumber, caseJSON, function (response) { - strataCache.remove('case' + caseNumber); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - return deferred.promise; - } - }, - values: { - cases: { - severity: function () { - var deferred = $q.defer(); - if (strataCache.get('severities')) { - deferred.resolve(strataCache.get('severities')); - } else { - strata.values.cases.severity(function (response) { - strataCache.put('severities', response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - status: function () { - var deferred = $q.defer(); - if (strataCache.get('statuses')) { - deferred.resolve(strataCache.get('statuses')); - } else { - strata.values.cases.status(function (response) { - strataCache.put('statuses', response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - }, - types: function () { - var deferred = $q.defer(); - if (strataCache.get('types')) { - deferred.resolve(strataCache.get('types')); - } else { - strata.values.cases.types(function (response) { - strataCache.put('types', response); - deferred.resolve(response); - }, angular.bind(deferred, errorHandler)); - } - return deferred.promise; - } - } - } - }; - return service; - } -]); - -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.security').controller('SecurityController', [ - '$scope', - '$rootScope', - 'securityService', - 'SECURITY_CONFIG', - function ($scope, $rootScope, securityService, SECURITY_CONFIG) { - $scope.securityService = securityService; - if (SECURITY_CONFIG.autoCheckLogin) { - securityService.validateLogin(SECURITY_CONFIG.forceLogin); - } - $scope.displayLoginStatus = function () { - return SECURITY_CONFIG.displayLoginStatus; - }; - } -]); - -'use strict'; -angular.module('RedhatAccess.security').directive('rhaLoginstatus', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'security/views/login_status.html' - }; -}); -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.security').factory('securityService', [ - '$rootScope', - '$modal', - 'AUTH_EVENTS', - '$q', - 'LOGIN_VIEW_CONFIG', - 'SECURITY_CONFIG', - 'strataService', - 'AlertService', - 'RHAUtils', - function($rootScope, $modal, AUTH_EVENTS, $q, LOGIN_VIEW_CONFIG, SECURITY_CONFIG, strataService, AlertService, RHAUtils) { - var service = { - loginStatus: { - isLoggedIn: false, - loggedInUser: '', - verifying: false, - isInternal: false, - orgAdmin: false, - hasChat: false, - sessionId: '', - canAddAttachments: false, - ssoName: '' - }, - loginURL: SECURITY_CONFIG.loginURL, - logoutURL: SECURITY_CONFIG.logoutURL, - setLoginStatus: function(isLoggedIn, userName, verifying, isInternal, orgAdmin, hasChat, sessionId, canAddAttachments, ssoName) { - service.loginStatus.isLoggedIn = isLoggedIn; - service.loginStatus.loggedInUser = userName; - service.loginStatus.verifying = verifying; - service.loginStatus.isInternal = isInternal; - if (orgAdmin !== null) { - service.loginStatus.orgAdmin = orgAdmin; - } - if (hasChat !== null) { - service.loginStatus.hasChat = hasChat; - } - if (sessionId !== null) { - service.loginStatus.sessionId = sessionId; - } - if (canAddAttachments !== null) { - service.loginStatus.canAddAttachments = canAddAttachments; - } - if (ssoName !== null) { - service.loginStatus.ssoName = ssoName; - } - }, - clearLoginStatus: function() { - service.loginStatus.isLoggedIn = false; - service.loginStatus.loggedInUser = ''; - service.loginStatus.verifying = false; - service.loginStatus.isInternal = false; - service.loginStatus.orgAdmin = false; - service.loginStatus.hasChat = false; - service.loginStatus.sessionId = ''; - service.loginStatus.canAddAttachments = false; - service.loginStatus.account = {}; - service.loginStatus.ssoName = ''; - }, - setAccount: function(accountJSON) { - service.loginStatus.account = accountJSON; - }, - modalDefaults: { - backdrop: 'static', - keyboard: true, - modalFade: true, - templateUrl: 'security/views/login_form.html', - windowClass: 'rha-login-modal' - }, - modalOptions: { - closeButtonText: 'Close', - actionButtonText: 'OK', - headerText: 'Proceed?', - bodyText: 'Perform this action?', - backdrop: 'static' - }, - userAllowedToManageEmailNotifications: function(user) { - if (RHAUtils.isNotEmpty(service.loginStatus.account) && RHAUtils.isNotEmpty(service.loginStatus.account) && service.loginStatus.orgAdmin) { - return true; - } else { - return false; - } - }, - userAllowedToManageGroups: function(user) { - if (RHAUtils.isNotEmpty(service.loginStatus.account) && RHAUtils.isNotEmpty(service.loginStatus.account) && (!service.loginStatus.account.has_group_acls || service.loginStatus.account.has_group_acls && service.loginStatus.orgAdmin)) { - return true; - } else { - return false; - } - }, - getBasicAuthToken: function() { - var defer = $q.defer(); - var token = localStorage.getItem('rhAuthToken'); - if (token !== undefined && token !== '') { - defer.resolve(token); - return defer.promise; - } else { - service.login().then(function(authedUser) { - defer.resolve(localStorage.getItem('rhAuthToken')); - }, function(error) { - defer.resolve(error); - }); - return defer.promise; - } - }, - loggingIn: false, - initLoginStatus: function() { - service.loggingIn = true; - var defer = $q.defer(); - var wasLoggedIn = service.loginStatus.isLoggedIn; - var currentSid = service.loginStatus.sessionId; - service.loginStatus.verifying = true; - strataService.authentication.checkLogin().then(angular.bind(this, function(authedUser) { - var sidChanged = currentSid !== authedUser.session_id; - service.setAccount(authedUser.account); - service.setLoginStatus(true, authedUser.name, false, authedUser.is_internal, authedUser.org_admin, authedUser.has_chat, authedUser.session_id, authedUser.can_add_attachments, authedUser.login); - service.loggingIn = false; - //We don't want to resend the AUTH_EVENTS.loginSuccess if we are already logged in - if (wasLoggedIn === false) { - $rootScope.$broadcast(AUTH_EVENTS.loginSuccess); - } - if (sidChanged) { - $rootScope.$broadcast(AUTH_EVENTS.sessionIdChanged); - } - defer.resolve(authedUser.name); - }), angular.bind(this, function(error) { - service.clearLoginStatus(); - AlertService.addDangerMessage(error); - service.loggingIn = false; - defer.reject(error); - })); - return defer.promise; - }, - validateLogin: function(forceLogin) { - var defer = $q.defer(); - //var that = this; - if (!forceLogin) { - service.initLoginStatus().then(function(username) { - defer.resolve(username); - }, function(error) { - defer.reject(error); - }); - return defer.promise; - } else { - service.initLoginStatus().then(function(username) { - defer.resolve(username); - }, function(error) { - service.login().then(function(authedUser) { - defer.resolve(authedUser.name); - }, function(error) { - defer.reject(error); - }); - }); - return defer.promise; - } - }, - login: function() { - return service.showLogin(service.modalDefaults, service.modalOptions); - }, - logout: function() { - strataService.authentication.logout(); - service.clearLoginStatus(); - $rootScope.$broadcast(AUTH_EVENTS.logoutSuccess); - }, - showLogin: function(customModalDefaults, customModalOptions) { - //var that = this; - //Create temp objects to work with since we're in a singleton service - var tempModalDefaults = {}; - var tempModalOptions = {}; - //Map angular-ui modal custom defaults to modal defaults defined in service - angular.extend(tempModalDefaults, service.modalDefaults, customModalDefaults); - //Map modal.html $scope custom properties to defaults defined in service - angular.extend(tempModalOptions, service.modalOptions, customModalOptions); - if (!tempModalDefaults.controller) { - tempModalDefaults.controller = [ - '$scope', - '$modalInstance', - function($scope, $modalInstance) { - $scope.user = { - user: null, - password: null - }; - $scope.useVerboseLoginView = LOGIN_VIEW_CONFIG.verbose; - $scope.modalOptions = tempModalOptions; - $scope.modalOptions.ok = function(result) { - //Hack below is needed to handle autofill issues - //@see https://github.com/angular/angular.js/issues/1460 - //BEGIN HACK - $scope.user.user = $('#rha-login-user-id').val(); - $scope.user.password = $('#rha-login-password').val(); - //END HACK - var resp = strataService.authentication.setCredentials($scope.user.user, $scope.user.password); - if (resp) { - service.initLoginStatus().then( - function(authedUser) { - $scope.user.password = ''; - $scope.authError = null; - try { - $modalInstance.close(authedUser); - } catch (err) {} - }, - function(error) { - if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') { - $scope.$apply(function() { - $scope.authError = 'Login Failed!'; - }); - } else { - $scope.authError = 'Login Failed!'; - } - } - ); - }else { - $scope.authError = 'Login Failed!'; - } - }; - $scope.modalOptions.close = function() { - $modalInstance.dismiss('User Canceled Login'); - }; - } - ]; - } - return $modal.open(tempModalDefaults).result; - }, - }; - return service; - } -]); -'use strict'; -/*jshint unused:vars, camelcase:false */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').controller('SearchController', [ - '$scope', - '$location', - 'SearchResultsService', - 'SEARCH_CONFIG', - 'securityService', - 'AlertService', - function ($scope, $location, SearchResultsService, SEARCH_CONFIG, securityService, AlertService) { - $scope.results = SearchResultsService.results; - $scope.selectedSolution = SearchResultsService.currentSelection; - $scope.searchInProgress = SearchResultsService.searchInProgress; - $scope.currentSearchData = SearchResultsService.currentSearchData; - $scope.getOpenCaseRef = function () { - if (SEARCH_CONFIG.openCaseRef !== undefined) { - //TODO data may be complex type - need to normalize to string in future - return SEARCH_CONFIG.openCaseRef + '?data=' + SearchResultsService.currentSearchData.data; - } else { - return '#/case/new?data=' + SearchResultsService.currentSearchData.data; - } - }; - $scope.solutionSelected = function (index) { - var response = $scope.results[index]; - SearchResultsService.setSelected(response, index); - }; - $scope.search = function (searchStr, limit) { - SearchResultsService.search(searchStr, limit); - }; - $scope.diagnose = function (data, limit) { - SearchResultsService.diagnose(data, limit); - }; - $scope.triggerAnalytics = function ($event) { - if (this.isopen && window.chrometwo_require !== undefined && $location.path() === '/case/new') { - chrometwo_require(['analytics/main'], function (analytics) { - analytics.trigger('OpenSupportCaseRecommendationClick', $event); - }); - } - }; - $scope.$watch(function () { - return SearchResultsService.currentSelection; - }, function (newVal) { - $scope.selectedSolution = newVal; - }); - } -]); - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').directive('rhaAccordionsearchresults', [ - 'SEARCH_CONFIG', - function (SEARCH_CONFIG) { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/accordion_search_results.html', - link: function (scope, element, attr) { - scope.showOpenCaseBtn = function () { - if (SEARCH_CONFIG.showOpenCaseBtn && (attr && attr.opencase === 'true')) { - return true; - } else { - return false; - } - }; - } - }; - } -]); - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').directive('rhaListsearchresults', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/list_search_results.html' - }; -}); - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').directive('rhaResultdetaildisplay', [ - 'RESOURCE_TYPES', - function (RESOURCE_TYPES) { - return { - restrict: 'AE', - scope: { result: '=' }, - link: function (scope, element, attr) { - scope.isSolution = function () { - if (scope.result !== undefined && scope.result.resource_type !== undefined) { - if (scope.result.resource_type === RESOURCE_TYPES.solution) { - return true; - } else { - return false; - } - } - return false; - }; - scope.isArticle = function () { - if (scope.result !== undefined && scope.result.resource_type !== undefined) { - if (scope.result.resource_type === RESOURCE_TYPES.article) { - return true; - } else { - return false; - } - } - return false; - }; - scope.getSolutionResolution = function () { - var resolutionHtml = ''; - if (scope.result.resolution !== undefined) { - resolutionHtml = scope.result.resolution.html; - } - return resolutionHtml; - }; - scope.getArticleHtml = function () { - if (scope.result === undefined) { - return ''; - } - if (scope.result.body !== undefined) { - if (scope.result.body.html !== undefined) { - //this is for newer version of strata - return scope.result.body.html; - } else { - //handle old markdown format - return scope.result.body; - } - } else { - return ''; - } - }; - }, - templateUrl: 'search/views/resultDetail.html' - }; - } -]); - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').directive('rhaSearchform', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/search_form.html' - }; -}); - -/*jshint camelcase: false */ -'use strict'; -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').directive('rhaStandardsearch', function () { - return { - restrict: 'AE', - scope: false, - templateUrl: 'search/views/standard_search.html' - }; -}); - -/*jshint camelcase: false */ -'use strict'; -/*global strata */ -/*jshint unused:vars */ -/** - * @ngdoc module - * @name - * - * @description - * - */ -angular.module('RedhatAccess.search').factory('SearchResultsService', [ - '$q', - '$rootScope', - 'AUTH_EVENTS', - 'RESOURCE_TYPES', - 'SEARCH_PARAMS', - 'AlertService', - 'securityService', - function ($q, $rootScope, AUTH_EVENTS, RESOURCE_TYPES, SEARCH_PARAMS, AlertService, securityService) { - var searchArticlesOrSolutions = function (searchString, limit) { - //var that = this; - if (limit === undefined || limit < 1) { - limit = SEARCH_PARAMS.limit; - } - service.clear(); - AlertService.clearAlerts(); - service.setCurrentSearchData(searchString, 'search'); - var deferreds = []; - strata.search(searchString, function (entries) { - //retrieve details for each solution - if (entries !== undefined) { - if (entries.length === 0) { - AlertService.addSuccessMessage('No recommendations found.'); - } - entries.forEach(function (entry) { - var deferred = $q.defer(); - deferreds.push(deferred.promise); - strata.utils.getURI(entry.uri, entry.resource_type, function (type, info) { - if (info !== undefined) { - info.resource_type = type; - } - deferred.resolve(info); - }, function (error) { - deferred.resolve(); - }); - }); - } else { - AlertService.addSuccessMessage('No recommendations found.'); - } - $q.all(deferreds).then(function (results) { - results.forEach(function (result) { - if (result !== undefined) { - service.add(result); - } - }); - service.searchInProgress.value = false; - }, function (error) { - service.searchInProgress.value = false; - }); - }, function (error) { - $rootScope.$apply(function () { - service.searchInProgress.value = false; - AlertService.addDangerMessage(error); - }); - }, limit, false); - }; - var searchProblems = function (data, limit) { - if (limit === undefined || limit < 1) { - limit = SEARCH_PARAMS.limit; - } - service.clear(); - AlertService.clearAlerts(); - var deferreds = []; - service.searchInProgress.value = true; - service.setCurrentSearchData(data, 'diagnose'); - strata.problems(data, function (solutions) { - //retrieve details for each solution - if (solutions !== undefined) { - if (solutions.length === 0) { - AlertService.addSuccessMessage('No solutions found.'); - } - solutions.forEach(function (solution) { - var deferred = $q.defer(); - deferreds.push(deferred.promise); - strata.solutions.get(solution.uri, function (solution) { - deferred.resolve(solution); - }, function (error) { - deferred.resolve(); - }); - }); - } else { - AlertService.addSuccessMessage('No solutions found.'); - } - $q.all(deferreds).then(function (solutions) { - solutions.forEach(function (solution) { - if (solution !== undefined) { - solution.resource_type = RESOURCE_TYPES.solution; - service.add(solution); - } - }); - service.searchInProgress.value = false; - }, function (error) { - service.searchInProgress.value = false; - }); - }, function (error) { - $rootScope.$apply(function () { - service.searchInProgress.value = false; - AlertService.addDangerMessage(error); - }); - }, limit); - }; - var service = { - results: [], - currentSelection: { - data: {}, - index: -1 - }, - searchInProgress: { value: false }, - currentSearchData: { - data: '', - method: '' - }, - add: function (result) { - this.results.push(result); - }, - clear: function () { - this.results.length = 0; - this.setSelected({}, -1); - this.setCurrentSearchData('', ''); - }, - setSelected: function (selection, index) { - this.currentSelection.data = selection; - this.currentSelection.index = index; - }, - setCurrentSearchData: function (data, method) { - this.currentSearchData.data = data; - this.currentSearchData.method = method; - }, - search: function (searchString, limit) { - this.searchInProgress.value = true; - var that = this; - securityService.validateLogin(true).then(function (authedUser) { - searchArticlesOrSolutions(searchString, limit); - }, function (error) { - that.searchInProgress.value = false; - AlertService.addDangerMessage('You must be logged in to use this functionality.'); - }); - }, - diagnose: function (data, limit) { - this.searchInProgress.value = true; - var that = this; - securityService.validateLogin(true).then(function (authedUser) { - searchProblems(data, limit); - }, function (error) { - that.searchInProgress.value = false; - AlertService.addDangerMessage('You must be logged in to use this functionality.'); - }); - } - }; - $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { - service.clear.apply(service); - }); - return service; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('AccountSelect', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - 'RHAUtils', - function ($scope, strataService, AlertService, CaseService, RHAUtils) { - $scope.CaseService = CaseService; - $scope.selectUserAccount = function () { - $scope.loadingAccountNumber = true; - strataService.accounts.list().then(function (response) { - $scope.loadingAccountNumber = false; - CaseService.account.number = response; - $scope.populateAccountSpecificFields(); - }, function (error) { - $scope.loadingAccountNumber = false; - AlertService.addStrataErrorMessage(error); - }); - }; - $scope.alertInstance = null; - $scope.populateAccountSpecificFields = function () { - if (RHAUtils.isNotEmpty(CaseService.account.number)) { - strataService.accounts.get(CaseService.account.number).then(function () { - if (RHAUtils.isNotEmpty($scope.alertInstance)) { - AlertService.removeAlert($scope.alertInstance); - } - CaseService.populateUsers(); - }, function () { - if (RHAUtils.isNotEmpty($scope.alertInstance)) { - AlertService.removeAlert($scope.alertInstance); - } - $scope.alertInstance = AlertService.addWarningMessage('Account not found.'); - }); - } - }; - } -]); -'use strict'; -/*global $, draftComment*/ -/*jshint camelcase: false, expr: true*/ -angular.module('RedhatAccess.cases').controller('AddCommentSection', [ - '$scope', - 'strataService', - 'CaseService', - 'AlertService', - '$timeout', - 'RHAUtils', - function ($scope, strataService, CaseService, AlertService, $timeout, RHAUtils) { - $scope.CaseService = CaseService; - $scope.addingComment = false; - $scope.addComment = function () { - $scope.addingComment = true; - var onSuccess = function (response) { - if (RHAUtils.isNotEmpty($scope.saveDraftPromise)) { - $timeout.cancel($scope.saveDraftPromise); - } - CaseService.commentText = ''; - //TODO: find better way than hard code - if (CaseService.kase.status.name === 'Closed') { - var status = { name: 'Waiting on Red Hat' }; - CaseService.kase.status = status; - } - CaseService.populateComments(CaseService.kase.case_number).then(function (comments) { - $scope.addingComment = false; - $scope.savingDraft = false; - $scope.draftSaved = false; - CaseService.draftComment = undefined; - CaseService.refreshComments(); - }); - }; - var onError = function (error) { - AlertService.addStrataErrorMessage(error); - $scope.addingComment = false; - }; - if (RHAUtils.isNotEmpty(CaseService.draftComment)) { - strataService.cases.comments.put(CaseService.kase.case_number, CaseService.commentText, false, CaseService.draftComment.id).then(onSuccess, onError); - } else { - strataService.cases.comments.post(CaseService.kase.case_number, CaseService.commentText).then(onSuccess, onError); - } - }; - $scope.saveDraftPromise; - $scope.onNewCommentKeypress = function () { - if (RHAUtils.isNotEmpty(CaseService.commentText) && !$scope.addingComment) { - $timeout.cancel($scope.saveDraftPromise); - $scope.saveDraftPromise = $timeout(function () { - if (!$scope.addingComment) { - $scope.saveDraft(); - } - }, 5000); - } - }; - $scope.saveDraft = function () { - $scope.savingDraft = true; - var onSuccess = function (commentId) { - $scope.savingDraft = false; - $scope.draftSaved = true; - CaseService.draftComment = { - 'text': CaseService.commentText, - 'id': RHAUtils.isNotEmpty(commentId) ? commentId : draftComment.id, - 'draft': true, - 'case_number': CaseService.kase.case_number - }; - }; - var onFailure = function (error) { - AlertService.addStrataErrorMessage(error); - $scope.savingDraft = false; - }; - if (RHAUtils.isNotEmpty(CaseService.draftComment)) { - //draft update - strataService.cases.comments.put(CaseService.kase.case_number, CaseService.commentText, true, CaseService.draftComment.id).then(onSuccess, onFailure); - } else { - //initial draft save - strataService.cases.comments.post(CaseService.kase.case_number, CaseService.commentText, true).then(onSuccess, onFailure); - } - }; - } -]); - -'use strict'; -/*global $ */ -angular.module('RedhatAccess.cases').controller('AttachLocalFile', [ - '$scope', - 'AttachmentsService', - 'securityService', - function ($scope, AttachmentsService, securityService) { - $scope.NO_FILE_CHOSEN = 'No file chosen'; - $scope.fileDescription = ''; - $scope.clearSelectedFile = function () { - $scope.fileName = $scope.NO_FILE_CHOSEN; - $scope.fileDescription = ''; - }; - $scope.addFile = function () { - /*jshint camelcase: false */ - var data = new FormData(); - data.append('file', $scope.fileObj); - data.append('description', $scope.fileDescription); - AttachmentsService.addNewAttachment({ - file_name: $scope.fileName, - description: $scope.fileDescription, - length: $scope.fileSize, - created_by: securityService.loginStatus.loggedInUser, - created_date: new Date().getTime(), - file: data - }); - $scope.clearSelectedFile(); - }; - $scope.getFile = function () { - $('#fileUploader').click(); - }; - $scope.selectFile = function () { - $scope.fileObj = $('#fileUploader')[0].files[0]; - $scope.fileSize = $scope.fileObj.size; - $scope.fileName = $scope.fileObj.name; - $scope.$apply(); - $('#fileUploader')[0].value = ''; - }; - $scope.clearSelectedFile(); - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('AttachmentsSection', [ - '$scope', - 'AttachmentsService', - 'CaseService', - 'TreeViewSelectorUtils', - 'EDIT_CASE_CONFIG', - function ($scope, AttachmentsService, CaseService, TreeViewSelectorUtils, EDIT_CASE_CONFIG) { - $scope.rhaDisabled = !EDIT_CASE_CONFIG.showAttachments; - $scope.showServerSideAttachments = EDIT_CASE_CONFIG.showServerSideAttachments; - $scope.AttachmentsService = AttachmentsService; - $scope.CaseService = CaseService; - $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; - $scope.doUpdate = function () { - $scope.updatingAttachments = true; - AttachmentsService.updateAttachments(CaseService.kase.case_number).then(function () { - $scope.updatingAttachments = false; - }, function (error) { - $scope.updatingAttachments = false; - }); - }; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('BackEndAttachmentsCtrl', [ - '$scope', - '$location', - 'TreeViewSelectorData', - 'AttachmentsService', - 'NEW_CASE_CONFIG', - 'EDIT_CASE_CONFIG', - function ($scope, $location, TreeViewSelectorData, AttachmentsService, NEW_CASE_CONFIG, EDIT_CASE_CONFIG) { - $scope.name = 'Attachments'; - $scope.attachmentTree = []; - var newCase = false; - var editCase = false; - if ($location.path().indexOf('new') > -1) { - newCase = true; - } else { - editCase = true; - } - if (!$scope.rhaDisabled && newCase && NEW_CASE_CONFIG.showServerSideAttachments || !$scope.rhaDisabled && editCase && EDIT_CASE_CONFIG.showServerSideAttachments) { - var sessionId = $location.search().sessionId; - TreeViewSelectorData.getTree('attachments', sessionId).then(function (tree) { - $scope.attachmentTree = tree; - AttachmentsService.updateBackEndAttachments(tree); - }, function () { - }); - } - } -]); -'use strict'; -//Saleforce hack--- -//we have to monitor stuff on the window object -//because the liveagent code generated by Salesforce is not -//designed for angularjs. -//We create fake buttons that we give to the salesforce api so we can track -//chat availability without having to write a complete rest client. -window.fakeOnlineButton = { style: { display: 'none' } }; -window.fakeOfflineButton = { style: { display: 'none' } }; -// -angular.module('RedhatAccess.cases').controller('ChatButton', [ - '$scope', - 'CaseService', - 'securityService', - 'CHAT_SUPPORT', - 'securityService', - 'AUTH_EVENTS', - '$rootScope', - '$sce', - '$http', - '$interval', - function ($scope, CaseService, SecurityService, CHAT_SUPPORT, securityService, AUTH_EVENTS, $rootScope, $sce, $http, $interval) { - $scope.securityService = securityService; - if (window.chatInitialized === undefined) { - window.chatInitialized = false; - } - $scope.checkChatButtonStates = function () { - $scope.chatAvailable = window.fakeOnlineButton.style.display !== 'none'; - }; - $scope.timer = null; - $scope.chatHackUrl = $sce.trustAsResourceUrl(CHAT_SUPPORT.chatIframeHackUrlPrefix); - $scope.setChatIframeHackUrl = function () { - var url = CHAT_SUPPORT.chatIframeHackUrlPrefix + '?sessionId=' + securityService.loginStatus.sessionId + '&ssoName=' + securityService.loginStatus.ssoName; - $scope.chatHackUrl = $sce.trustAsResourceUrl(url); - }; - $scope.enableChat = function () { - $scope.showChat = securityService.loginStatus.isLoggedIn && securityService.loginStatus.hasChat && CHAT_SUPPORT.enableChat; - return $scope.showChat; - }; - $scope.showChat = false; - // determines whether we should show buttons at all - $scope.chatAvailable = false; - //Availability of chat as determined by live agent, toggles chat buttons - $scope.initializeChat = function () { - if (!$scope.enableChat() || window.chatInitialized === true) { - //function should only be called when chat is enabled, and only once per page load - return; - } - if (!window._laq) { - window._laq = []; - } - window._laq.push(function () { - liveagent.showWhenOnline(CHAT_SUPPORT.chatButtonToken, window.fakeOnlineButton); - liveagent.showWhenOffline(CHAT_SUPPORT.chatButtonToken, window.fakeOfflineButton); - }); - //var chatToken = securityService.loginStatus.sessionId; - var ssoName = securityService.loginStatus.ssoName; - var name = securityService.loginStatus.loggedInUser; - //var currentCaseNumber; - var accountNumber = securityService.loginStatus.account.number; - // if (currentCaseNumber) { - // liveagent - // .addCustomDetail('Case Number', currentCaseNumber) - // .map('Case', 'CaseNumber', false, false, false) - // .saveToTranscript('CaseNumber__c'); - // } - // if (chatToken) { - // liveagent - // .addCustomDetail('Session ID', chatToken) - // .map('Contact', 'SessionId__c', false, false, false); - // } - liveagent.addCustomDetail('Contact Login', ssoName).map('Contact', 'SSO_Username__c', true, true, true).saveToTranscript('SSO_Username__c'); - //liveagent - // .addCustomDetail('Contact E-mail', email) - // .map('Contact', 'Email', false, false, false); - liveagent.addCustomDetail('Account Number', accountNumber).map('Account', 'AccountNumber', true, true, true); - liveagent.setName(name); - liveagent.addCustomDetail('Name', name); - liveagent.setChatWindowHeight('552'); - //liveagent.enableLogging(); - liveagent.init(CHAT_SUPPORT.chatLiveAgentUrlPrefix, CHAT_SUPPORT.chatInitHashOne, CHAT_SUPPORT.chatInitHashTwo); - window.chatInitialized = true; - }; - $scope.openChatWindow = function () { - liveagent.startChat(CHAT_SUPPORT.chatButtonToken); - }; - $scope.init = function () { - if ($scope.enableChat()) { - $scope.setChatIframeHackUrl(); - $scope.timer = $interval($scope.checkChatButtonStates, 5000); - $scope.initializeChat(); - } - }; - $scope.$on('$destroy', function () { - //we cancel timer each time scope is destroyed - //it will be restarted via init on state change to a page that has a chat buttom - $interval.cancel($scope.timer); - }); - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } else { - $scope.authEventLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); - }); - $scope.$on('$destroy', function () { - $scope.authEventLoginSuccess(); - }); - } - $scope.authEventSessionChanged = $rootScope.$on(AUTH_EVENTS.sessionIdChanged, function () { - if ($scope.enableChat()) { - $scope.setChatIframeHackUrl(); - } - }); - - $scope.$on('$destroy', function () { - $scope.authEventSessionChanged(); - }); - } -]); - -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('CommentsSection', [ - '$scope', - 'CaseService', - 'strataService', - '$stateParams', - 'AlertService', - '$timeout', - '$modal', - 'RHAUtils', - function ($scope, CaseService, strataService, $stateParams, AlertService, $timeout, $modal, RHAUtils) { - $scope.CaseService = CaseService; - CaseService.refreshComments = function () { - $scope.selectPage(1); - }; - CaseService.populateComments($stateParams.id).then(function (comments) { - if (RHAUtils.isNotEmpty(comments)) { - CaseService.refreshComments(); - } - }); - $scope.itemsPerPage = 4; - $scope.maxPagerSize = 3; - $scope.selectPage = function (pageNum) { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > CaseService.comments.length ? CaseService.comments.length : end; - $scope.commentsOnScreen = CaseService.comments.slice(start, end); - }; - $scope.requestManagementEscalation = function () { - $modal.open({ - templateUrl: 'cases/views/requestManagementEscalationModal.html', - controller: 'RequestManagementEscalationModal' - }); - }; - if (RHAUtils.isNotEmpty(CaseService.comments)) { - CaseService.refreshComments(); - } - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('CompactCaseList', [ - '$scope', - '$stateParams', - 'strataService', - 'CaseService', - '$rootScope', - 'AUTH_EVENTS', - 'securityService', - 'SearchCaseService', - 'AlertService', - 'SearchBoxService', - 'RHAUtils', - '$filter', - function ($scope, $stateParams, strataService, CaseService, $rootScope, AUTH_EVENTS, securityService, SearchCaseService, AlertService, SearchBoxService, RHAUtils, $filter) { - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.selectedCaseIndex = -1; - $scope.SearchCaseService = SearchCaseService; - $scope.selectCase = function ($index) { - if ($scope.selectedCaseIndex !== $index) { - $scope.selectedCaseIndex = $index; - } - }; - $scope.domReady = false; - //used to notify resizable directive that the page has loaded - SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { - SearchCaseService.doFilter().then(function () { - if (RHAUtils.isNotEmpty($stateParams.id) && $scope.selectedCaseIndex === -1) { - var selectedCase = $filter('filter')(SearchCaseService.cases, { 'case_number': $stateParams.id }); - $scope.selectedCaseIndex = SearchCaseService.cases.indexOf(selectedCase[0]); - } - $scope.domReady = true; - }); - }; - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateGroups(); - SearchBoxService.doSearch(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateGroups(); - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('CompactEdit', [ - '$scope', - 'strataService', - '$stateParams', - 'CaseService', - 'AttachmentsService', - '$rootScope', - 'AUTH_EVENTS', - 'CASE_EVENTS', - 'securityService', - 'AlertService', - function ($scope, strataService, $stateParams, CaseService, AttachmentsService, $rootScope, AUTH_EVENTS, CASE_EVENTS, securityService, AlertService) { - $scope.securityService = securityService; - $scope.caseLoading = true; - $scope.domReady = false; - $scope.init = function () { - strataService.cases.get($stateParams.id).then(function (resp) { - var caseJSON = resp[0]; - var cacheHit = resp[1]; - if (!cacheHit) { - CaseService.defineCase(caseJSON); - } else { - CaseService.kase = caseJSON; - } - $rootScope.$broadcast(CASE_EVENTS.received); - $scope.caseLoading = false; - if (caseJSON.product !== undefined && caseJSON.product.name !== undefined) { - strataService.products.versions(caseJSON.product.name).then(function (versions) { - CaseService.versions = versions; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - $scope.domReady = true; - }); - strataService.cases.attachments.list($stateParams.id).then(function (attachmentsJSON) { - AttachmentsService.defineOriginalAttachments(attachmentsJSON); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); - AlertService.clearAlerts(); - }); - } -]); -'use strict'; -/*global $ */ -angular.module('RedhatAccess.cases').controller('CreateGroupButton', [ - '$scope', - '$modal', - function ($scope, $modal) { - $scope.openCreateGroupDialog = function () { - $modal.open({ - templateUrl: 'cases/views/createGroupModal.html', - controller: 'CreateGroupModal' - }); - }; - } -]); -'use strict'; -/*global $ */ -angular.module('RedhatAccess.cases').controller('CreateGroupModal', [ - '$scope', - '$modalInstance', - 'strataService', - 'AlertService', - 'CaseService', - 'GroupService', - function ($scope, $modalInstance, strataService, AlertService, CaseService, GroupService) { - $scope.createGroup = function () { - AlertService.addWarningMessage('Creating group ' + this.groupName + '...'); - $modalInstance.close(); - strataService.groups.create(this.groupName).then(angular.bind(this, function (success) { - CaseService.groups.push({ - name: this.groupName, - number: success - }); - AlertService.clearAlerts(); - AlertService.addSuccessMessage('Successfully created group ' + this.groupName); - GroupService.reloadTable(); - }), function (error) { - AlertService.clearAlerts(); - AlertService.addStrataErrorMessage(error); - }); - }; - $scope.closeModal = function () { - $modalInstance.close(); - }; - $scope.onGroupNameKeyPress = function ($event) { - if ($event.keyCode === 13) { - angular.bind(this, $scope.createGroup)(); - } - }; - } -]); -'use strict'; -/*global $ */ -angular.module('RedhatAccess.cases').controller('DeleteGroupButton', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - '$q', - '$filter', - 'GroupService', - function ($scope, strataService, AlertService, CaseService, $q, $filter, GroupService) { - $scope.deleteGroups = function () { - var promises = []; - angular.forEach(CaseService.groups, function (group, index) { - if (group.selected) { - var promise = strataService.groups.remove(group.number); - promise.then(function (success) { - var groups = $filter('filter')(CaseService.groups, function (g) { - if (g.number !== group.number) { - return true; - } else { - return false; - } - }); - CaseService.groups = groups; - GroupService.reloadTable(); - AlertService.addSuccessMessage('Successfully deleted group ' + group.name); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(promise); - } - }); - AlertService.addWarningMessage('Deleting groups...'); - var parentPromise = $q.all(promises); - parentPromise.then(function (success) { - AlertService.clearAlerts(); - AlertService.addSuccessMessage('Successfully deleted groups.'); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('DescriptionSection', [ - '$scope', - 'CaseService', - function ($scope, CaseService) { - $scope.CaseService = CaseService; - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('DetailsSection', [ - '$scope', - 'strataService', - 'CaseService', - '$rootScope', - 'AUTH_EVENTS', - 'CASE_EVENTS', - 'AlertService', - 'RHAUtils', - function ($scope, strataService, CaseService, $rootScope, AUTH_EVENTS, CASE_EVENTS, AlertService, RHAUtils) { - $scope.CaseService = CaseService; - $scope.init = function () { - if (!$scope.compact) { - strataService.values.cases.types().then(function (response) { - $scope.caseTypes = response; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - strataService.groups.list(CaseService.kase.contact_sso_username).then(function (response) { - $scope.groups = response; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - strataService.values.cases.status().then(function (response) { - $scope.statuses = response; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - strataService.values.cases.severity().then(function (response) { - CaseService.severities = response; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - strataService.products.list().then(function (response) { - $scope.products = response; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - $scope.updatingDetails = false; - $scope.updateCase = function () { - $scope.updatingDetails = true; - var caseJSON = {}; - if (CaseService.kase !== undefined) { - if (CaseService.kase.type !== undefined) { - caseJSON.type = CaseService.kase.type.name; - } - if (CaseService.kase.severity !== undefined) { - caseJSON.severity = CaseService.kase.severity.name; - } - if (CaseService.kase.status !== undefined) { - caseJSON.status = CaseService.kase.status.name; - } - if (CaseService.kase.alternate_id !== undefined) { - caseJSON.alternateId = CaseService.kase.alternate_id; - } - if (CaseService.kase.product !== undefined) { - caseJSON.product = CaseService.kase.product.name; - } - if (CaseService.kase.version !== undefined) { - caseJSON.version = CaseService.kase.version; - } - if (CaseService.kase.summary !== undefined) { - caseJSON.summary = CaseService.kase.summary; - } - if (CaseService.kase.group !== undefined) { - caseJSON.folderNumber = CaseService.kase.group.number; - } - if (RHAUtils.isNotEmpty(CaseService.kase.fts)) { - caseJSON.fts = CaseService.kase.fts; - if (!CaseService.kase.fts) { - caseJSON.contactInfo24X7 = ''; - } - } - if (CaseService.kase.fts && RHAUtils.isNotEmpty(CaseService.kase.contact_info24_x7)) { - caseJSON.contactInfo24X7 = CaseService.kase.contact_info24_x7; - } - if (CaseService.kase.notes !== null) { - caseJSON.notes = CaseService.kase.notes; - } - strataService.cases.put(CaseService.kase.case_number, caseJSON).then(function () { - $scope.caseDetails.$setPristine(); - $scope.updatingDetails = false; - if ($scope.$root.$$phase !== '$apply' && $scope.$root.$$phase !== '$digest') { - $scope.$apply(); - } - }, function (error) { - AlertService.addStrataErrorMessage(error); - $scope.updatingDetails = false; - $scope.$apply(); - }); - } - }; - $scope.getProductVersions = function () { - CaseService.versions = []; - strataService.products.versions(CaseService.kase.product.code).then(function (versions) { - CaseService.versions = versions; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - if (CaseService.caseDataReady) { - $scope.init(); - } - $scope.caseEventDeregister = $rootScope.$on(CASE_EVENTS.received, function () { - $scope.init(); - AlertService.clearAlerts(); - }); - $scope.$on('$destroy', function () { - $scope.caseEventDeregister(); - }); - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('Edit', [ - '$scope', - '$stateParams', - '$filter', - '$q', - 'AttachmentsService', - 'CaseService', - 'strataService', - 'RecommendationsService', - '$rootScope', - 'AUTH_EVENTS', - 'AlertService', - 'securityService', - 'EDIT_CASE_CONFIG', - 'RHAUtils', - 'CASE_EVENTS', - function ($scope, $stateParams, $filter, $q, AttachmentsService, CaseService, strataService, RecommendationsService, $rootScope, AUTH_EVENTS, AlertService, securityService, EDIT_CASE_CONFIG, RHAUtils, CASE_EVENTS) { - $scope.EDIT_CASE_CONFIG = EDIT_CASE_CONFIG; - $scope.securityService = securityService; - $scope.AttachmentsService = AttachmentsService; - $scope.CaseService = CaseService; - CaseService.clearCase(); - $scope.init = function () { - $scope.caseLoading = true; - $scope.recommendationsLoading = true; - strataService.cases.get($stateParams.id).then(function (resp) { - var caseJSON = resp[0]; - var cacheHit = resp[1]; - if (!cacheHit) { - CaseService.defineCase(caseJSON); - } else { - CaseService.setCase(caseJSON); - } - $rootScope.$broadcast(CASE_EVENTS.received); - $scope.caseLoading = false; - if ('product' in caseJSON && 'name' in caseJSON.product && caseJSON.product.name) { - strataService.products.versions(caseJSON.product.name).then(function (versions) { - CaseService.versions = versions; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - if (caseJSON.account_number !== undefined) { - strataService.accounts.get(caseJSON.account_number).then(function (account) { - CaseService.defineAccount(account); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - if (EDIT_CASE_CONFIG.showRecommendations) { - RecommendationsService.populatePinnedRecommendations().then(function () { - $scope.recommendationsLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - RecommendationsService.populateRecommendations(12).then(function () { - $scope.recommendationsLoading = false; - }); - } - if (EDIT_CASE_CONFIG.showEmailNotifications && !cacheHit) { - CaseService.defineNotifiedUsers(); - } - }); - if (EDIT_CASE_CONFIG.showAttachments) { - $scope.attachmentsLoading = true; - strataService.cases.attachments.list($stateParams.id).then(function (attachmentsJSON) { - AttachmentsService.defineOriginalAttachments(attachmentsJSON); - $scope.attachmentsLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - if (EDIT_CASE_CONFIG.showComments) { - $scope.commentsLoading = true; - strataService.cases.comments.get($stateParams.id).then(function (commentsJSON) { - $scope.comments = commentsJSON; - $scope.commentsLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - }; - if (securityService.loginStatus.isLoggedIn) { - $scope.init(); - } - $scope.authLoginEvent = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.init(); - AlertService.clearAlerts(); - }); - - $scope.$on('$destroy', function () { - $scope.authLoginEvent(); - }); - } -]); - -/*global angular*/ -/*jshint camelcase: false*/ -'use strict'; -angular.module('RedhatAccess.cases').controller('EmailNotifySelect', [ - '$scope', - '$rootScope', - 'CaseService', - 'securityService', - 'AlertService', - 'strataService', - '$filter', - 'RHAUtils', - 'EDIT_CASE_CONFIG', - 'AUTH_EVENTS', - function ($scope, $rootScope, CaseService, securityService, AlertService, strataService, $filter, RHAUtils, EDIT_CASE_CONFIG, AUTH_EVENTS) { - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.showEmailNotifications = EDIT_CASE_CONFIG.showEmailNotifications; - $scope.updateNotifyUsers = function () { - if (!angular.equals(CaseService.updatedNotifiedUsers, CaseService.originalNotifiedUsers)) { - angular.forEach(CaseService.originalNotifiedUsers, function (origUser) { - var updatedUser = $filter('filter')(CaseService.updatedNotifiedUsers, origUser); - if (RHAUtils.isEmpty(updatedUser)) { - $scope.updatingList = true; - strataService.cases.notified_users.remove(CaseService.kase.case_number, origUser).then(function () { - $scope.updatingList = false; - CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; - }, function (error) { - $scope.updatingList = false; - AlertService.addStrataErrorMessage(error); - }); - } - }); - angular.forEach(CaseService.updatedNotifiedUsers, function (updatedUser) { - var originalUser = $filter('filter')(CaseService.originalNotifiedUsers, updatedUser); - if (RHAUtils.isEmpty(originalUser)) { - $scope.updatingList = true; - strataService.cases.notified_users.add(CaseService.kase.case_number, updatedUser).then(function () { - CaseService.originalNotifiedUsers = CaseService.updatedNotifiedUsers; - $scope.updatingList = false; - }, function (error) { - $scope.updatingList = false; - AlertService.addStrataErrorMessage(error); - }); - } - }); - } - }; - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateUsers(); - } - $scope.authEventDeregister = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateUsers(); - }); - $scope.$on('$destroy', function () { - $scope.authEventDeregister(); - }); - } -]); - -'use strict'; -angular.module('RedhatAccess.cases').controller('EntitlementSelect', [ - '$scope', - 'strataService', - 'AlertService', - '$filter', - 'RHAUtils', - 'CaseService', - function ($scope, strataService, AlertService, $filter, RHAUtils, CaseService) { - $scope.CaseService = CaseService; - CaseService.populateEntitlements(); - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('ExportCSVButton', [ - '$scope', - 'strataService', - 'AlertService', - function ($scope, strataService, AlertService) { - $scope.exporting = false; - $scope.exports = function () { - $scope.exporting = true; - strataService.cases.csv().then(function (response) { - $scope.exporting = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('Group', [ - '$scope', - 'securityService', - 'SearchBoxService', - 'GroupService', - function ($scope, securityService, SearchBoxService, GroupService) { - $scope.securityService = securityService; - SearchBoxService.onChange = SearchBoxService.doSearch = SearchBoxService.onKeyPress = function () { - GroupService.reloadTable(); - }; - } -]); -'use strict'; -/*global $ */ -/*jshint expr: true, newcap: false*/ -angular.module('RedhatAccess.cases').controller('GroupList', [ - '$scope', - 'strataService', - 'AlertService', - 'CaseService', - '$filter', - 'ngTableParams', - 'GroupService', - 'SearchBoxService', - function ($scope, strataService, AlertService, CaseService, $filter, ngTableParams, GroupService, SearchBoxService) { - $scope.CaseService = CaseService; - $scope.GroupService = GroupService; - $scope.listEmpty = false; - $scope.groupsOnScreen = []; - var tableBuilt = false; - var buildTable = function () { - $scope.tableParams = new ngTableParams({ - page: 1, - count: 10, - sorting: { name: 'asc' } - }, { - total: CaseService.groups.length, - getData: function ($defer, params) { - var orderedData = $filter('filter')(CaseService.groups, SearchBoxService.searchTerm); - orderedData = params.sorting() ? $filter('orderBy')(orderedData, params.orderBy()) : orderedData; - orderedData.length < 1 ? $scope.listEmpty = true : $scope.listEmpty = false; - var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); - $scope.tableParams.total(orderedData.length); - GroupService.groupsOnScreen = pageData; - $defer.resolve(pageData); - } - }); - GroupService.reloadTable = function () { - $scope.tableParams.reload(); - }; - tableBuilt = true; - }; - $scope.groupsLoading = true; - strataService.groups.list().then(function (groups) { - CaseService.groups = groups; - buildTable(); - $scope.groupsLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - $scope.onMasterCheckboxClicked = function () { - for (var i = 0; i < GroupService.groupsOnScreen.length; i++) { - if (this.masterSelected) { - GroupService.groupsOnScreen[i].selected = true; - } else { - GroupService.groupsOnScreen[i].selected = false; - } - } - }; - CaseService.clearCase(); - } -]); - -'use strict'; -angular.module('RedhatAccess.cases').constant('CASE_GROUPS', { - manage: 'manage', - ungrouped: 'ungrouped' -}).controller('GroupSelect', [ - '$scope', - 'securityService', - 'SearchCaseService', - 'CaseService', - 'strataService', - 'AlertService', - 'CASE_GROUPS', - function ($scope, securityService, SearchCaseService, CaseService, strataService, AlertService, CASE_GROUPS) { - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - $scope.CASE_GROUPS = CASE_GROUPS; - CaseService.populateGroups(); - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('List', [ - '$scope', - '$filter', - 'ngTableParams', - 'securityService', - 'AlertService', - '$rootScope', - 'SearchCaseService', - 'CaseService', - 'AUTH_EVENTS', - 'SearchBoxService', - function ($scope, $filter, ngTableParams, securityService, AlertService, $rootScope, SearchCaseService, CaseService, AUTH_EVENTS, SearchBoxService) { - $scope.SearchCaseService = SearchCaseService; - $scope.securityService = securityService; - $scope.AlertService = AlertService; - AlertService.clearAlerts(); - var tableBuilt = false; - var buildTable = function () { - /*jshint newcap: false*/ - $scope.tableParams = new ngTableParams({ - page: 1, - count: 10, - sorting: { last_modified_date: 'desc' } - }, { - total: SearchCaseService.cases.length, - getData: function ($defer, params) { - if (!SearchCaseService.allCasesDownloaded && params.count() === params.page()) { - SearchCaseService.doFilter().then(function () { - $scope.tableParams.reload(); - var orderedData = params.sorting() ? $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; - var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); - $scope.tableParams.total(orderedData.length); - $defer.resolve(pageData); - }); - } else { - var orderedData = params.sorting() ? $filter('orderBy')(SearchCaseService.cases, params.orderBy()) : SearchCaseService.cases; - var pageData = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()); - $scope.tableParams.total(orderedData.length); - $defer.resolve(pageData); - } - } - }); - tableBuilt = true; - }; - SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { - SearchCaseService.clearPagination(); - SearchCaseService.doFilter().then(function () { - if (!tableBuilt) { - buildTable(); - } else { - $scope.tableParams.reload(); - } - }); - }; - /** - * Callback after user login. Load the cases and clear alerts - */ - if (securityService.loginStatus.isLoggedIn) { - SearchCaseService.clear(); - SearchBoxService.doSearch(); - } - $scope.listAuthEventDeregister = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); - $scope.$on('$destroy', function () { - $scope.listAuthEventDeregister(); - }); - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('ListAttachments', [ - '$scope', - 'AttachmentsService', - function ($scope, AttachmentsService) { - $scope.AttachmentsService = AttachmentsService; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('ListBugzillas', [ - '$scope', - 'CaseService', - 'securityService', - function ($scope, CaseService, securityService) { - $scope.CaseService = CaseService; - $scope.securityService = securityService; - } -]); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').controller('ListFilter', [ - '$scope', - 'STATUS', - 'CaseService', - 'securityService', - function ($scope, STATUS, CaseService, securityService) { - $scope.securityService = securityService; - CaseService.status = STATUS.both; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('ListNewAttachments', [ - '$scope', - 'AttachmentsService', - 'TreeViewSelectorUtils', - function ($scope, AttachmentsService, TreeViewSelectorUtils) { - $scope.AttachmentsService = AttachmentsService; - $scope.TreeViewSelectorUtils = TreeViewSelectorUtils; - $scope.removeLocalAttachment = function ($index) { - AttachmentsService.removeUpdatedAttachment($index); - }; - } -]); -'use strict'; -/*jshint camelcase: false*/ -angular.module('RedhatAccess.cases').controller('New', [ - '$scope', - '$state', - '$q', - 'SearchResultsService', - 'AttachmentsService', - 'strataService', - 'RecommendationsService', - 'CaseService', - 'AlertService', - 'securityService', - '$rootScope', - 'AUTH_EVENTS', - '$location', - 'RHAUtils', - 'NEW_DEFAULTS', - 'NEW_CASE_CONFIG', - function ($scope, $state, $q, SearchResultsService, AttachmentsService, strataService, RecommendationsService, CaseService, AlertService, securityService, $rootScope, AUTH_EVENTS, $location, RHAUtils, NEW_DEFAULTS, NEW_CASE_CONFIG) { - $scope.NEW_CASE_CONFIG = NEW_CASE_CONFIG; - $scope.versions = []; - $scope.versionDisabled = true; - $scope.versionLoading = false; - $scope.incomplete = true; - $scope.submitProgress = 0; - AttachmentsService.clear(); - CaseService.clearCase(); - RecommendationsService.clear(); - SearchResultsService.clear(); - AlertService.clearAlerts(); - $scope.CaseService = CaseService; - $scope.RecommendationsService = RecommendationsService; - $scope.securityService = securityService; - $scope.getRecommendations = function () { - if ($scope.NEW_CASE_CONFIG.showRecommendations) { - SearchResultsService.searchInProgress.value = true; - RecommendationsService.populateRecommendations(5).then(function () { - SearchResultsService.clear(); - RecommendationsService.recommendations.forEach(function (recommendation) { - SearchResultsService.add(recommendation); - }); - SearchResultsService.searchInProgress.value = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - } - }; - CaseService.onOwnerSelectChanged = function () { - if (CaseService.owner !== undefined) { - CaseService.populateEntitlements(CaseService.owner); - CaseService.populateGroups(CaseService.owner); - } - CaseService.validateNewCasePage1(); - }; - /** - * Populate the selects - */ - $scope.initSelects = function () { - $scope.productsLoading = true; - strataService.products.list(securityService.loginStatus.ssoName).then(function (products) { - $scope.products = products; - $scope.productsLoading = false; - if (RHAUtils.isNotEmpty(NEW_DEFAULTS.product)) { - CaseService.kase.product = { - name: NEW_DEFAULTS.product, - code: NEW_DEFAULTS.product - }; - $scope.getRecommendations(); - $scope.getProductVersions(CaseService.kase.product); - } - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - $scope.severitiesLoading = true; - strataService.values.cases.severity().then(function (severities) { - CaseService.severities = severities; - CaseService.kase.severity = severities[severities.length - 1]; - $scope.severitiesLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - $scope.groupsLoading = true; - strataService.groups.list().then(function (groups) { - /*jshint camelcase: false*/ - CaseService.groups = groups; - for (var i = 0; i < groups.length; i++) { - if (groups[i].is_default) { - CaseService.kase.group = groups[i]; - break; - } - } - $scope.groupsLoading = false; - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - $scope.initDescription = function () { - var searchObject = $location.search(); - var setDesc = function (desc) { - CaseService.kase.description = desc; - $scope.getRecommendations(); - }; - if (searchObject.data) { - setDesc(searchObject.data); - } else { - //angular does not handle params before hashbang - //@see https://github.com/angular/angular.js/issues/6172 - var queryParamsStr = window.location.search.substring(1); - var parameters = queryParamsStr.split('&'); - for (var i = 0; i < parameters.length; i++) { - var parameterName = parameters[i].split('='); - if (parameterName[0] === 'data') { - setDesc(decodeURIComponent(parameterName[1])); - } - } - } - }; - if (securityService.loginStatus.isLoggedIn) { - $scope.initSelects(); - $scope.initDescription(); - } - $scope.authLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - $scope.initSelects(); - $scope.initDescription(); - AlertService.clearAlerts(); - }); - - $scope.$on('$destroy', function () { - $scope.authLoginSuccess(); - }); - /** - * Retrieve product's versions from strata - * - * @param product - */ - $scope.getProductVersions = function (product) { - CaseService.kase.version = ''; - $scope.versionDisabled = true; - $scope.versionLoading = true; - strataService.products.versions(product.code).then(function (response) { - $scope.versions = response; - CaseService.validateNewCasePage1(); - $scope.versionDisabled = false; - $scope.versionLoading = false; - if (RHAUtils.isNotEmpty(NEW_DEFAULTS.version)) { - CaseService.kase.version = NEW_DEFAULTS.version; - $scope.getRecommendations(); - } - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - /** - * Go to a page in the wizard - * - * @param page - */ - $scope.gotoPage = function (page) { - $scope.isPage1 = page === 1 ? true : false; - $scope.isPage2 = page === 2 ? true : false; - }; - /** - * Navigate forward in the wizard - */ - $scope.doNext = function () { - $scope.gotoPage(2); - }; - /** - * Navigate back in the wizard - */ - $scope.doPrevious = function () { - $scope.gotoPage(1); - }; - $scope.submittingCase = false; - /** - * Create the case with attachments - */ - $scope.doSubmit = function ($event) { - if (window.chrometwo_require !== undefined) { - chrometwo_require(['analytics/main'], function (analytics) { - analytics.trigger('OpenSupportCaseSubmit', $event); - }); - } - /*jshint camelcase: false */ - var caseJSON = { - 'product': CaseService.kase.product.code, - 'version': CaseService.kase.version, - 'summary': CaseService.kase.summary, - 'description': CaseService.kase.description, - 'severity': CaseService.kase.severity.name - }; - if (RHAUtils.isNotEmpty(CaseService.group)) { - caseJSON.folderNumber = CaseService.group; - } - if (RHAUtils.isNotEmpty(CaseService.entitlement)) { - caseJSON.entitlement = {}; - caseJSON.entitlement.sla = CaseService.entitlement; - } - if (RHAUtils.isNotEmpty(CaseService.account)) { - caseJSON.accountNumber = CaseService.account.number; - } - if (CaseService.fts) { - caseJSON.fts = true; - if (CaseService.fts_contact) { - caseJSON.contactInfo24X7 = CaseService.fts_contact; - } - } - if (RHAUtils.isNotEmpty(CaseService.owner)) { - caseJSON.contactSsoUsername = CaseService.owner; - } - $scope.submittingCase = true; - AlertService.addWarningMessage('Creating case...'); - var redirectToCase = function (caseNumber) { - $state.go('edit', { id: caseNumber }); - AlertService.clearAlerts(); - $scope.submittingCase = false; - }; - strataService.cases.post(caseJSON).then(function (caseNumber) { - AlertService.clearAlerts(); - AlertService.addSuccessMessage('Successfully created case number ' + caseNumber); - if ((AttachmentsService.updatedAttachments.length > 0 || AttachmentsService.hasBackEndSelections()) && NEW_CASE_CONFIG.showAttachments) { - AttachmentsService.updateAttachments(caseNumber).then(function () { - redirectToCase(caseNumber); - }); - } else { - redirectToCase(caseNumber); - } - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - $scope.gotoPage(1); - } -]); - -/*global angular*/ -'use strict'; -angular.module('RedhatAccess.cases').controller('OwnerSelect', [ - '$scope', - '$rootScope', - 'securityService', - 'AUTH_EVENTS', - 'SearchCaseService', - 'CaseService', - function ($scope, $rootScope, securityService, AUTH_EVENTS, SearchCaseService, CaseService) { - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - if (securityService.loginStatus.isLoggedIn) { - CaseService.populateUsers(); - } - $scope.authLoginEvent = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - CaseService.populateUsers(); - }); - $scope.$on('$destroy', function () { - $scope.authLoginEvent(); - }); - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('ProductSelect', [ - '$scope', - 'securityService', - 'SearchCaseService', - 'CaseService', - 'strataService', - 'AlertService', - function ($scope, securityService, SearchCaseService, CaseService, strataService, AlertService) { - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - $scope.productsLoading = true; - strataService.products.list().then(function (products) { - $scope.productsLoading = false; - CaseService.products = products; - }, function (error) { - $scope.productsLoading = false; - AlertService.addStrataErrorMessage(error); - }); - } -]); -'use strict'; -/*jshint camelcase: false*/ -angular.module('RedhatAccess.cases').controller('RecommendationsSection', [ - 'RecommendationsService', - '$scope', - 'strataService', - 'CaseService', - 'AlertService', - function (RecommendationsService, $scope, strataService, CaseService, AlertService) { - $scope.RecommendationsService = RecommendationsService; - $scope.recommendationsPerPage = 4; - $scope.maxRecommendationsSize = 10; - $scope.selectRecommendationsPage = function (pageNum) { - //filter out pinned recommendations - angular.forEach(RecommendationsService.pinnedRecommendations, function (pinnedRec) { - angular.forEach(RecommendationsService.recommendations, function (rec, index) { - if (angular.equals(rec.id, pinnedRec.id)) { - RecommendationsService.recommendations.splice(index, 1); - } - }); - }); - angular.forEach(RecommendationsService.handPickedRecommendations, function (handPickedRec) { - angular.forEach(RecommendationsService.recommendations, function (rec, index) { - if (angular.equals(rec.id, handPickedRec.id)) { - RecommendationsService.recommendations.splice(index, 1); - } - }); - }); - var recommendations = RecommendationsService.pinnedRecommendations.concat(RecommendationsService.recommendations); - recommendations = RecommendationsService.handPickedRecommendations.concat(recommendations); - var start = $scope.recommendationsPerPage * (pageNum - 1); - var end = start + $scope.recommendationsPerPage; - end = end > recommendations.length ? recommendations.length : end; - $scope.recommendationsOnScreen = recommendations.slice(start, end); - }; - $scope.currentRecPin = {}; - $scope.pinRecommendation = function (recommendation, $index, $event) { - $scope.currentRecPin = recommendation; - $scope.currentRecPin.pinning = true; - var doPut = function (linked) { - var recJSON = { - recommendations: { - recommendation: [{ - linked: linked.toString(), - resourceId: recommendation.id, - resourceType: 'Solution' - }] - } - }; - strataService.cases.put(CaseService.kase.case_number, recJSON).then(function (response) { - if (!$scope.currentRecPin.pinned) { - //not currently pinned, so add it to the pinned list - RecommendationsService.pinnedRecommendations.push($scope.currentRecPin); - } else { - //currently pinned, so remove from pinned list - angular.forEach(RecommendationsService.pinnedRecommendations, function (rec, index) { - if (rec.id === $scope.currentRecPin.id) { - RecommendationsService.pinnedRecommendations.splice(index, 1); - } - }); - //add the de-pinned rec to the top of the list - //this allows the user to still view the rec, or re-pin it - RecommendationsService.recommendations.splice(0, 0, $scope.currentRecPin); - } - $scope.currentRecPin.pinning = false; - $scope.currentRecPin.pinned = !$scope.currentRecPin.pinned; - $scope.selectPageOne(); - }, function (error) { - $scope.currentRecPin.pinning = false; - AlertService.addStrataErrorMessage(error); - }); - }; - if (recommendation.pinned) { - doPut(false); - } else { - doPut(true); - } - }; - $scope.selectPageOne = function () { - $scope.selectRecommendationsPage(1); - $scope.currentRecommendationPage = 1; - }; - $scope.triggerAnalytics = function ($event) { - if (window.chrometwo_require !== undefined) { - chrometwo_require(['analytics/main'], function (analytics) { - analytics.trigger('CaseViewRecommendationClick', $event); - }); - } - }; - RecommendationsService.setPopulateCallback($scope.selectPageOne); - } -]); - -'use strict'; -/*global $ */ -/*jshint camelcase: false*/ -angular.module('RedhatAccess.cases').controller('RequestManagementEscalationModal', [ - '$scope', - '$modalInstance', - 'AlertService', - 'CaseService', - 'strataService', - '$q', - '$stateParams', - function ($scope, $modalInstance, AlertService, CaseService, strataService, $q, $stateParams) { - $scope.CaseService = CaseService; - $scope.commentText = CaseService.commentText; - $scope.submittingRequest = false; - $scope.submitRequestClick = angular.bind($scope, function (commentText) { - $scope.submittingRequest = true; - var promises = []; - var fullComment = 'Request Management Escalation: ' + commentText; - var postComment; - if (CaseService.draftComment) { - postComment = strataService.cases.comments.put(CaseService.kase.case_number, fullComment, false, CaseService.draftComment.id); - } else { - postComment = strataService.cases.comments.post(CaseService.kase.case_number, fullComment, false); - } - postComment.then(function (response) { - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(postComment); - var caseJSON = { 'escalated': true }; - var updateCase = strataService.cases.put(CaseService.kase.case_number, caseJSON); - updateCase.then(function (response) { - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(updateCase); - var masterPromise = $q.all(promises); - masterPromise.then(function (response) { - CaseService.populateComments($stateParams.id).then(function (comments) { - CaseService.refreshComments(); - $scope.closeModal(); - $scope.submittingRequest = false; - }); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - return masterPromise; - }); - $scope.closeModal = function () { - $modalInstance.close(); - }; - } -]); - -'use strict'; -angular.module('RedhatAccess.cases').controller('Search', [ - '$scope', - '$rootScope', - 'AUTH_EVENTS', - 'securityService', - 'SearchCaseService', - 'CaseService', - 'STATUS', - 'SearchBoxService', - 'AlertService', - function ($scope, $rootScope, AUTH_EVENTS, securityService, SearchCaseService, CaseService, STATUS, SearchBoxService, AlertService) { - $scope.securityService = securityService; - $scope.SearchCaseService = SearchCaseService; - $scope.CaseService = CaseService; - $scope.itemsPerPage = 10; - $scope.maxPagerSize = 5; - $scope.selectPage = function (pageNum) { - if (!SearchCaseService.allCasesDownloaded && $scope.itemsPerPage * pageNum / SearchCaseService.total >= 0.8) { - SearchCaseService.doFilter().then(function () { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > SearchCaseService.cases.length ? SearchCaseService.cases.length : end; - $scope.casesOnScreen = SearchCaseService.cases.slice(start, end); - }); - } else { - var start = $scope.itemsPerPage * (pageNum - 1); - var end = start + $scope.itemsPerPage; - end = end > SearchCaseService.cases.length ? SearchCaseService.cases.length : end; - $scope.casesOnScreen = SearchCaseService.cases.slice(start, end); - } - }; - SearchBoxService.doSearch = CaseService.onSelectChanged = CaseService.onOwnerSelectChanged = CaseService.onGroupSelectChanged = function () { - SearchCaseService.clearPagination(); - SearchCaseService.doFilter().then(function () { - $scope.selectPage(1); - }); - }; - if (securityService.loginStatus.isLoggedIn) { - CaseService.clearCase(); - SearchCaseService.clear(); - SearchBoxService.doSearch(); - } - $scope.authEventLoginSuccess = $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - SearchBoxService.doSearch(); - AlertService.clearAlerts(); - }); - $scope.authEventLogoutSuccess = $rootScope.$on(AUTH_EVENTS.logoutSuccess, function () { - CaseService.clearCase(); - SearchCaseService.clear(); - }); - - $scope.$on('$destroy', function () { - $scope.authEventLoginSuccess(); - $scope.authEventLogoutSuccess(); - }); - } -]); - -'use strict'; -angular.module('RedhatAccess.cases').controller('SearchBox', [ - '$scope', - 'SearchBoxService', - 'securityService', - function ($scope, SearchBoxService, securityService) { - $scope.securityService = securityService; - $scope.SearchBoxService = SearchBoxService; - $scope.onFilterKeyPress = function ($event) { - if ($event.keyCode === 13) { - SearchBoxService.doSearch(); - } else if (angular.isFunction(SearchBoxService.onKeyPress)) { - SearchBoxService.onKeyPress(); - } - }; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('SeveritySelect', [ - '$scope', - 'securityService', - 'strataService', - 'CaseService', - 'AlertService', - function ($scope, securityService, strataService, CaseService, AlertService) { - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.severitiesLoading = true; - strataService.values.cases.severity().then(function (severities) { - $scope.severitiesLoading = false; - CaseService.severities = severities; - }, function (error) { - $scope.severitiesLoading = false; - AlertService.addStrataErrorMessage(error); - }); - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('StatusSelect', [ - '$scope', - 'securityService', - 'CaseService', - 'STATUS', - function ($scope, securityService, CaseService, STATUS) { - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.STATUS = STATUS; - $scope.statuses = [ - { - name: 'Open and Closed', - value: STATUS.both - }, - { - name: 'Open', - value: STATUS.open - }, - { - name: 'Closed', - value: STATUS.closed - } - ]; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').controller('TypeSelect', [ - '$scope', - 'securityService', - 'CaseService', - 'strataService', - 'AlertService', - function ($scope, securityService, CaseService, strataService, AlertService) { - $scope.securityService = securityService; - $scope.CaseService = CaseService; - $scope.typesLoading = true; - strataService.values.cases.types().then(function (types) { - $scope.typesLoading = false; - CaseService.types = types; - }, function (error) { - $scope.typesLoading = false; - AlertService.addStrataErrorMessage(error); - }); - } -]); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaAccountselect', function () { - return { - templateUrl: 'cases/views/accountSelect.html', - restrict: 'A', - controller: 'AccountSelect' - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaAddcommentsection', function () { - return { - templateUrl: 'cases/views/addCommentSection.html', - restrict: 'A', - controller: 'AddCommentSection' - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaAttachlocalfile', function () { - return { - templateUrl: 'cases/views/attachLocalFile.html', - restrict: 'A', - controller: 'AttachLocalFile', - scope: { disabled: '=' } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaAttachproductlogs', function () { - return { - templateUrl: 'cases/views/attachProductLogs.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCaseattachments', function () { - return { - templateUrl: 'cases/views/attachmentsSection.html', - restrict: 'A', - controller: 'AttachmentsSection', - scope: { loading: '=' }, - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaChatbutton', function () { - return { - scope: {}, - templateUrl: 'cases/views/chatButton.html', - restrict: 'A', - controller: 'ChatButton', - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); - -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCasecomments', function () { - return { - templateUrl: 'cases/views/commentsSection.html', - controller: 'CommentsSection', - scope: { loading: '=' }, - restrict: 'A', - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); - -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCompactcaselist', function () { - return { - templateUrl: 'cases/views/compactCaseList.html', - controller: 'CompactCaseList', - scope: {}, - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCreategroupbutton', function () { - return { - templateUrl: 'cases/views/createGroupButton.html', - restrict: 'A', - controller: 'CreateGroupButton' - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaDeletegroupbutton', function () { - return { - templateUrl: 'cases/views/deleteGroupButton.html', - restrict: 'A', - controller: 'DeleteGroupButton' - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCasedescription', function () { - return { - templateUrl: 'cases/views/descriptionSection.html', - restrict: 'A', - scope: { loading: '=' }, - controller: 'DescriptionSection', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCasedetails', function () { - return { - templateUrl: 'cases/views/detailsSection.html', - controller: 'DetailsSection', - scope: { - compact: '=', - loading: '=' - }, - restrict: 'A', - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); -/*global angular*/ -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaEmailnotifyselect', function () { - return { - templateUrl: 'cases/views/emailNotifySelect.html', - restrict: 'A', - transclude: true, - controller: 'EmailNotifySelect', - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); - -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaEntitlementselect', function () { - return { - templateUrl: 'cases/views/entitlementSelect.html', - restrict: 'A', - controller: 'EntitlementSelect' - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaExportcsvbutton', function () { - return { - templateUrl: 'cases/views/exportCSVButton.html', - restrict: 'A', - controller: 'ExportCSVButton' - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaGrouplist', function () { - return { - templateUrl: 'cases/views/groupList.html', - restrict: 'A', - controller: 'GroupList' - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaGroupselect', function () { - return { - templateUrl: 'cases/views/groupSelect.html', - restrict: 'A', - controller: 'GroupSelect', - scope: { - onchange: '&', - showsearchoptions: '=' - } - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaListattachments', function () { - return { - templateUrl: 'cases/views/listAttachments.html', - restrict: 'A', - controller: 'ListAttachments', - scope: { disabled: '=' } - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaListbugzillas', function () { - return { - templateUrl: 'cases/views/listBugzillas.html', - restrict: 'A', - controller: 'ListBugzillas', - scope: { loading: '=' }, - link: function postLink(scope, element, attrs) { - } - }; -}); -/*global angular*/ -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaListfilter', function () { - return { - templateUrl: 'cases/views/listFilter.html', - restrict: 'A', - controller: 'ListFilter', - scope: { - prefilter: '=', - postfilter: '=' - }, - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaListnewattachments', function () { - return { - templateUrl: 'cases/views/listNewAttachments.html', - restrict: 'A', - controller: 'ListNewAttachments' - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaOnchange', function () { - return { - restrict: 'A', - link: function (scope, element, attrs) { - element.bind('change', element.scope()[attrs.rhaOnchange]); - } - }; -}); -'use strict'; -/*global angular*/ -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaOwnerselect', function () { - return { - templateUrl: 'cases/views/ownerSelect.html', - restrict: 'A', - controller: 'OwnerSelect', - scope: { onchange: '&' }, - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaPageheader', function () { - return { - templateUrl: 'cases/views/pageHeader.html', - restrict: 'A', - scope: { title: '=title' }, - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaProductselect', function () { - return { - templateUrl: 'cases/views/productSelect.html', - restrict: 'A', - controller: 'ProductSelect', - scope: { onchange: '&' } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaCaserecommendations', function () { - return { - templateUrl: 'cases/views/recommendationsSection.html', - restrict: 'A', - controller: 'RecommendationsSection', - transclude: true, - scope: { loading: '=' }, - link: function postLink(scope, element, attrs) { - scope.$on('$destroy', function () { - element.remove(); - }); - } - }; -}); - -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaSearchbox', function () { - return { - templateUrl: 'cases/views/searchBox.html', - restrict: 'A', - controller: 'SearchBox', - scope: { placeholder: '=' } - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').directive('rhaCasesearchresult', function () { - return { - templateUrl: 'cases/views/searchResult.html', - restrict: 'A', - scope: { theCase: '=case' } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaSelectloadingindicator', function () { - return { - templateUrl: 'cases/views/selectLoadingIndicator.html', - restrict: 'A', - transclude: true, - scope: { - loading: '=', - type: '@' - } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaSeverityselect', function () { - return { - templateUrl: 'cases/views/severitySelect.html', - restrict: 'A', - controller: 'SeveritySelect', - scope: { onchange: '&' } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaStatusselect', function () { - return { - templateUrl: 'cases/views/statusSelect.html', - restrict: 'A', - controller: 'StatusSelect', - scope: { onchange: '&' } - }; -}); -'use strict'; -/*jshint unused:vars */ -angular.module('RedhatAccess.cases').directive('rhaTypeselect', function () { - return { - templateUrl: 'cases/views/typeSelect.html', - restrict: 'A', - controller: 'TypeSelect', - scope: { onchange: '&' } - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').filter('bytes', function () { - return function (bytes, precision) { - if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) { - return '-'; - } - if (typeof precision === 'undefined') { - precision = 1; - } - var units = [ - 'bytes', - 'kB', - 'MB', - 'GB', - 'TB', - 'PB' - ], number = Math.floor(Math.log(bytes) / Math.log(1024)); - return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; - }; -}); -'use strict'; -angular.module('RedhatAccess.cases').filter('recommendationsResolution', function () { - return function (text) { - var shortText = ''; - var maxTextLength = 150; - if (text !== undefined && text.length > maxTextLength) { - shortText = text.substr(0, maxTextLength); - // var lastSpace = shortText.lastIndexOf(' '); - // shortText = shortText.substr(0, lastSpace); - shortText = shortText.concat('...'); - } else { - shortText = text; - } - return shortText; - }; -}); -'use strict'; -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').service('AttachmentsService', [ - '$filter', - '$q', - 'strataService', - 'TreeViewSelectorUtils', - '$http', - 'securityService', - 'AlertService', - 'CaseService', - 'translate', - function ($filter, $q, strataService, TreeViewSelectorUtils, $http, securityService, AlertService, CaseService, translate) { - this.originalAttachments = []; - this.updatedAttachments = []; - this.backendAttachments = []; - this.clear = function () { - this.originalAttachments = []; - this.updatedAttachments = []; - this.backendAttachments = []; - }; - this.updateBackEndAttachments = function (selected) { - this.backendAttachments = selected; - }; - this.hasBackEndSelections = function () { - return TreeViewSelectorUtils.hasSelections(this.backendAttachments); - }; - this.removeUpdatedAttachment = function ($index) { - this.updatedAttachments.splice($index, 1); - }; - this.removeOriginalAttachment = function ($index) { - var attachment = this.originalAttachments[$index]; - var progressMessage = AlertService.addWarningMessage(translate('Deleting attachment:') + ' ' + attachment.file_name + ' - ' + attachment.uuid); - strataService.cases.attachments.remove(attachment.uuid, CaseService.kase.case_number).then(angular.bind(this, function () { - AlertService.removeAlert(progressMessage); - AlertService.addSuccessMessage(translate('Successfully deleted attachment:') + ' ' + attachment.file_name + ' - ' + attachment.uuid); - this.originalAttachments.splice($index, 1); - }), function (error) { - AlertService.addStrataErrorMessage(error); - }); - }; - this.addNewAttachment = function (attachment) { - this.updatedAttachments.push(attachment); - }; - this.defineOriginalAttachments = function (attachments) { - if (!angular.isArray(attachments)) { - this.originalAttachments = []; - } else { - this.originalAttachments = attachments; - } - }; - this.postBackEndAttachments = function (caseId) { - var selectedFiles = TreeViewSelectorUtils.getSelectedLeaves(this.backendAttachments); - return securityService.getBasicAuthToken().then(function (auth) { - /*jshint unused:false */ - //we post each attachment separately - var promises = []; - angular.forEach(selectedFiles, function (file) { - var jsonData = { - authToken: auth, - attachment: file, - caseNum: caseId - }; - var deferred = $q.defer(); - $http.post('attachments', jsonData).success(function (data, status, headers, config) { - deferred.resolve(data); - AlertService.addSuccessMessage(translate('Successfully uploaded attachment') + ' ' + jsonData.attachment + ' ' + translate('to case') + ' ' + caseId); - }).error(function (data, status, headers, config) { - var errorMsg = ''; - switch (status) { - case 401: - errorMsg = ' : Unauthorised.'; - break; - case 409: - errorMsg = ' : Invalid username/password.'; - break; - case 500: - errorMsg = ' : Internal server error'; - break; - } - AlertService.addDangerMessage('Failed to upload attachment ' + jsonData.attachment + ' to case ' + caseId + errorMsg); - deferred.reject(data); - }); - promises.push(deferred.promise); - }); - return $q.all(promises); - }); - }; - this.updateAttachments = function (caseId) { - var hasServerAttachments = this.hasBackEndSelections(); - var hasLocalAttachments = !angular.equals(this.updatedAttachments.length, 0); - if (hasLocalAttachments || hasServerAttachments) { - var promises = []; - var updatedAttachments = this.updatedAttachments; - if (hasServerAttachments) { - promises.push(this.postBackEndAttachments(caseId)); - } - if (hasLocalAttachments) { - //find new attachments - angular.forEach(updatedAttachments, function (attachment) { - if (!attachment.hasOwnProperty('uuid')) { - var promise = strataService.cases.attachments.post(attachment.file, caseId); - promise.then(function (uri) { - attachment.uri = uri; - attachment.uuid = uri.slice(uri.lastIndexOf('/') + 1); - AlertService.addSuccessMessage('Successfully uploaded attachment ' + attachment.file_name + ' to case ' + caseId); - }, function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(promise); - } - }); - } - var uploadingAlert = AlertService.addWarningMessage('Uploading attachments...'); - var parentPromise = $q.all(promises); - parentPromise.then(angular.bind(this, function () { - this.originalAttachments = this.originalAttachments.concat(this.updatedAttachments); - this.updatedAttachments = []; - AlertService.removeAlert(uploadingAlert); - }), function (error) { - AlertService.addStrataErrorMessage(error); - AlertService.removeAlert(uploadingAlert); - }); - return parentPromise; - } - }; - } -]); -'use strict'; -angular.module('RedhatAccess.cases').service('CaseListService', [function () { - this.cases = []; - this.defineCases = function (cases) { - this.cases = cases; - }; - }]); -'use strict'; -angular.module('RedhatAccess.cases').service('CaseService', [ - 'strataService', - 'AlertService', - 'RHAUtils', - 'securityService', - '$q', - '$filter', - function (strataService, AlertService, RHAUtils, securityService, $q, $filter) { - this.kase = {}; - this.caseDataReady = false; - this.versions = []; - this.products = []; - //this.statuses = []; - this.severities = []; - this.groups = []; - this.users = []; - this.comments = []; - this.originalNotifiedUsers = []; - this.updatedNotifiedUsers = []; - this.account = {}; - this.draftComment = ''; - this.commentText = ''; - this.status = ''; - this.severity = ''; - this.type = ''; - this.group = ''; - this.owner = ''; - this.product = ''; - this.bugzillaList = {}; - this.onSelectChanged = null; - this.onOwnerSelectChanged = null; - this.onGroupSelectChanged = null; - /** - * Add the necessary wrapper objects needed to properly display the data. - * - * @param rawCase - */ - this.defineCase = function (rawCase) { - /*jshint camelcase: false */ - rawCase.severity = { 'name': rawCase.severity }; - rawCase.status = { 'name': rawCase.status }; - rawCase.product = { 'name': rawCase.product }; - rawCase.group = { 'number': rawCase.folder_number }; - rawCase.type = { 'name': rawCase.type }; - this.kase = rawCase; - this.bugzillaList = rawCase.bugzillas; - this.caseDataReady = true; - }; - this.setCase = function (jsonCase) { - this.kase = jsonCase; - this.bugzillaList = jsonCase.bugzillas; - this.caseDataReady = true; - }; - this.defineAccount = function (account) { - this.account = account; - }; - this.defineNotifiedUsers = function () { - /*jshint camelcase: false */ - this.updatedNotifiedUsers.push(this.kase.contact_sso_username); - //hide the X button for the case owner - $('#rha-emailnotifyselect').on('change', angular.bind(this, function () { - $('rha-emailnotifyselect .select2-choices li:contains("' + this.kase.contact_sso_username + '") a').css('display', 'none'); - $('rha-emailnotifyselect .select2-choices li:contains("' + this.kase.contact_sso_username + '")').css('padding-left', '5px'); - })); - if (RHAUtils.isNotEmpty(this.kase.notified_users)) { - angular.forEach(this.kase.notified_users.link, angular.bind(this, function (user) { - this.originalNotifiedUsers.push(user.sso_username); - })); - this.updatedNotifiedUsers = this.updatedNotifiedUsers.concat(this.originalNotifiedUsers); - } - }; - this.getGroups = function () { - return this.groups; - }; - this.clearCase = function () { - this.caseDataReady = false; - this.kase = {}; - this.versions = []; - this.products = []; - this.statuses = []; - this.severities = []; - this.groups = []; - this.account = {}; - this.comments = []; - this.bugzillaList = {}; - this.draftComment = undefined; - this.commentText = undefined; - this.status = undefined; - this.severity = undefined; - this.type = undefined; - this.group = undefined; - this.owner = undefined; - this.product = undefined; - }; - this.groupsLoading = false; - this.populateGroups = function (ssoUsername) { - this.groupsLoading = true; - strataService.groups.list(ssoUsername).then(angular.bind(this, function (groups) { - this.groups = groups; - this.groupsLoading = false; - }), angular.bind(this, function (error) { - this.groupsLoading = false; - AlertService.addStrataErrorMessage(error); - })); - }; - this.usersLoading = false; - /** - * Intended to be called only after user is logged in and has account details - * See securityService. - */ - this.populateUsers = angular.bind(this, function () { - var promise = null; - if (securityService.loginStatus.orgAdmin) { - this.usersLoading = true; - var accountNumber = RHAUtils.isEmpty(this.account.number) ? securityService.loginStatus.account.number : this.account.number; - promise = strataService.accounts.users(accountNumber); - promise.then(angular.bind(this, function (users) { - this.usersLoading = false; - this.users = users; - }), angular.bind(this, function (error) { - this.usersLoading = false; - this.users = []; - AlertService.addStrataErrorMessage(error); - })); - } else { - var deferred = $q.defer(); - promise = deferred.promise; - deferred.resolve(); - this.users = []; - } - return promise; - }); - this.refreshComments = null; - this.populateComments = function (caseNumber) { - var promise = strataService.cases.comments.get(caseNumber); - promise.then(angular.bind(this, function (comments) { - //pull out the draft comment - angular.forEach(comments, angular.bind(this, function (comment, index) { - if (comment.draft === true) { - this.draftComment = comment; - this.commentText = comment.text; - comments.slice(index, index + 1); - } - })); - this.comments = comments; - }), function (error) { - AlertService.addStrataErrorMessage(error); - }); - return promise; - }; - this.entitlementsLoading = false; - this.populateEntitlements = function (ssoUserName) { - this.entitlementsLoading = true; - strataService.entitlements.get(false, ssoUserName).then(angular.bind(this, function (entitlementsResponse) { - // if the user has any premium or standard level entitlement, then allow them - // to select it, regardless of the product. - // TODO: strata should respond with a filtered list given a product. - // Adding the query param ?product=$PRODUCT does not work. - var uniqueEntitlements = function (a) { - return a.reduce(function (p, c) { - if (p.indexOf(c.sla) < 0) { - p.push(c.sla); - } - return p; - }, []); - }; - var entitlements = uniqueEntitlements(entitlementsResponse.entitlement); - var unknownIndex = entitlements.indexOf('UNKNOWN'); - if (unknownIndex > -1) { - entitlements.splice(unknownIndex, 1); - } - this.entitlements = entitlements; - this.entitlementsLoading = false; - }), angular.bind(this, function (error) { - AlertService.addStrataErrorMessage(error); - })); - }; - this.showFts = function () { - if (RHAUtils.isNotEmpty(this.severities)) { - if (this.entitlement === 'PREMIUM' || this.entitlement === 'AMC' || RHAUtils.isNotEmpty(this.kase.entitlement) && (this.kase.entitlement.sla === 'PREMIUM' || this.kase.entitlement.sla === 'AMC')) { - return true; - } - } - return false; - }; - this.newCasePage1Incomplete = true; - this.validateNewCasePage1 = function () { - if (RHAUtils.isEmpty(this.kase.product) || RHAUtils.isEmpty(this.kase.version) || RHAUtils.isEmpty(this.kase.summary) || RHAUtils.isEmpty(this.kase.description) || securityService.loginStatus.isInternal && RHAUtils.isEmpty(this.owner)) { - this.newCasePage1Incomplete = true; - } else { - this.newCasePage1Incomplete = false; - } - }; - } -]); -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').service('GroupService', [ - 'strataService', - function (strataService) { - this.reloadTable = {}; - this.groupsOnScreen = []; - } -]); - -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').service('RecommendationsService', [ - 'strataService', - 'CaseService', - 'AlertService', - '$q', - function (strataService, CaseService, AlertService, $q) { - this.recommendations = []; - this.pinnedRecommendations = []; - this.handPickedRecommendations = []; - this.populateCallback = function () { - }; - var currentData = { - product: null, - version: null, - summary: null, - description: null - }; - this.loadingRecommendations = false; - var setCurrentData = function () { - currentData = { - product: CaseService.kase.product, - version: CaseService.kase.version, - summary: CaseService.kase.summary, - description: CaseService.kase.description - }; - }; - setCurrentData(); - this.clear = function () { - this.recommendations = []; - }; - this.setPopulateCallback = function (callback) { - this.populateCallback = callback; - }; - this.populatePinnedRecommendations = function () { - var promises = []; - if (CaseService.kase.recommendations) { - //Push any pinned recommendations to the front of the array - if (CaseService.kase.recommendations.recommendation) { - var promise = {}; - angular.forEach(CaseService.kase.recommendations.recommendation, angular.bind(this, function (rec) { - if (rec.pinned_at) { - promise = strataService.solutions.get(rec.resource_id).then(angular.bind(this, function (solution) { - solution.pinned = true; - this.pinnedRecommendations.push(solution); - }), function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(promise); - } else if (rec.linked) { - promise = strataService.solutions.get(rec.resource_id).then(angular.bind(this, function (solution) { - //solution.pinned = true; - solution.handPicked = true; - this.handPickedRecommendations.push(solution); - }), function (error) { - AlertService.addStrataErrorMessage(error); - }); - promises.push(promise); - } - })); - } - } - var masterPromise = $q.all(promises); - masterPromise.then(angular.bind(this, function () { - this.populateCallback(); - })); - return masterPromise; - }; - this.failureCount = 0; - this.populateRecommendations = function (max) { - var masterDeferred = $q.defer(); - masterDeferred.promise.then(this.populateCallback); - var newData = { - product: CaseService.kase.product, - version: CaseService.kase.version, - summary: CaseService.kase.summary, - description: CaseService.kase.description - }; - if (newData.product !== undefined && newData.version !== undefined && newData.summary !== undefined && newData.description !== undefined && (!angular.equals(currentData, newData) && !this.loadingRecommendations || this.recommendations.length < 1 && this.failureCount < 10)) { - this.loadingRecommendations = true; - setCurrentData(); - var deferreds = []; - strataService.problems(currentData, max).then(angular.bind(this, function (solutions) { - //retrieve details for each solution - solutions.forEach(function (solution) { - var splitUri = solution.uri.split('/'); - var deferred = strataService.solutions.get(splitUri[splitUri.length - 1]); - deferreds.push(deferred); - }); - $q.all(deferreds).then(angular.bind(this, function (solutions) { - this.recommendations = []; - solutions.forEach(angular.bind(this, function (solution) { - if (solution !== undefined) { - solution.resource_type = 'Solution'; - this.recommendations.push(solution); - } - })); - this.loadingRecommendations = false; - masterDeferred.resolve(); - }), angular.bind(this, function (error) { - this.loadingRecommendations = false; - masterDeferred.resolve(); - })); - }), angular.bind(this, function (error) { - masterDeferred.reject(); - this.failureCount++; - this.populateRecommendations(12); - })); - } else { - masterDeferred.resolve(); - } - return masterDeferred.promise; - }; - } -]); - -'use strict'; -/*jshint unused:vars */ -/*jshint camelcase: false */ -angular.module('RedhatAccess.cases').service('SearchBoxService', [function () { - this.doSearch = {}; - this.searchTerm = undefined; - this.onKeyPress = {}; - }]); - -/*jshint camelcase: false*/ -'use strict'; -angular.module('RedhatAccess.cases').service('SearchCaseService', [ - 'CaseService', - 'strataService', - 'AlertService', - 'STATUS', - 'CASE_GROUPS', - 'AUTH_EVENTS', - '$q', - '$state', - '$rootScope', - 'SearchBoxService', - 'securityService', - function (CaseService, strataService, AlertService, STATUS, CASE_GROUPS, AUTH_EVENTS, $q, $state, $rootScope, SearchBoxService, securityService) { - this.cases = []; - this.searching = false; - this.prefilter = {}; - this.postfilter = {}; - this.start = 0; - this.count = 100; - this.total = 0; - this.allCasesDownloaded = false; - var getIncludeClosed = function () { - if (CaseService.status === STATUS.open) { - return false; - } else if (CaseService.status === STATUS.closed) { - return true; - } else if (CaseService.status === STATUS.both) { - return true; - } - return true; - }; - this.clear = function () { - this.cases = []; - this.oldParams = {}; - SearchBoxService.searchTerm = ''; - this.start = 0; - this.total = 0; - this.allCasesDownloaded = false; - }; - this.clearPagination = function () { - this.start = 0; - this.total = 0; - this.allCasesDownloaded = false; - this.cases = []; - }; - this.oldParams = {}; - this.doFilter = function () { - if (angular.isFunction(this.prefilter)) { - this.prefilter(); - } - var params = { - include_closed: getIncludeClosed(), - count: this.count - }; - params.start = this.start; - var isObjectNothing = function (object) { - if (object === '' || object === undefined || object === null) { - return true; - } else { - return false; - } - }; - if (!isObjectNothing(SearchBoxService.searchTerm)) { - params.keyword = SearchBoxService.searchTerm; - } - if (CaseService.group === CASE_GROUPS.manage) { - $state.go('group'); - } else if (CaseService.group === CASE_GROUPS.ungrouped) { - params.only_ungrouped = true; - } else if (!isObjectNothing(CaseService.group)) { - params.group_numbers = { group_number: [CaseService.group] }; - } - if (CaseService.status === STATUS.closed) { - params.status = STATUS.closed; - } - if (!isObjectNothing(CaseService.product)) { - params.product = CaseService.product; - } - if (!isObjectNothing(CaseService.owner)) { - params.owner_ssoname = CaseService.owner; - } - if (!isObjectNothing(CaseService.type)) { - params.type = CaseService.type; - } - if (!isObjectNothing(CaseService.severity)) { - params.severity = CaseService.severity; - } - this.searching = true; - //TODO: hack to get around onchange() firing at page load for each select. - //Need to prevent initial onchange() event instead of handling here. - var promises = []; - var deferred = $q.defer(); - if (!angular.equals(params, this.oldParams)) { - this.oldParams = params; - var that = this; - var cases = null; - if (securityService.loginStatus.isLoggedIn) { - if (securityService.loginStatus.ssoName && securityService.loginStatus.isInternal) { - params.owner_ssoname = securityService.loginStatus.ssoName; - } - cases = strataService.cases.filter(params).then(angular.bind(that, function (cases) { - if (cases.length < that.count) { - that.allCasesDownloaded = true; - } - that.cases = that.cases.concat(cases); - that.searching = false; - that.start = that.start + that.count; - that.total = that.total + that.count; - if (angular.isFunction(that.postFilter)) { - that.postFilter(); - } - }), angular.bind(that, function (error) { - AlertService.addStrataErrorMessage(error); - that.searching = false; - })); - deferred.resolve(cases); - } else { - $rootScope.$on(AUTH_EVENTS.loginSuccess, function () { - if (securityService.loginStatus.ssoName && securityService.loginStatus.isInternal) { - params.owner_ssoname = securityService.loginStatus.ssoName; - } - cases = strataService.cases.filter(params).then(angular.bind(that, function (cases) { - if (cases.length < that.count) { - that.allCasesDownloaded = true; - } - that.cases = that.cases.concat(cases); - that.searching = false; - that.start = that.start + that.count; - that.total = that.total + that.count; - if (angular.isFunction(that.postFilter)) { - that.postFilter(); - } - }), angular.bind(that, function (error) { - AlertService.addStrataErrorMessage(error); - that.searching = false; - })); - deferred.resolve(cases); - }); - } - promises.push(deferred.promise); - } else { - deferred.resolve(); - promises.push(deferred.promise); - } - return $q.all(promises); - }; - } -]); - -'use strict'; -angular.module('RedhatAccess.logViewer').controller('AccordionDemoCtrl', [ - '$scope', - 'accordian', - function ($scope, accordian) { - $scope.oneAtATime = true; - $scope.groups = accordian.getGroups(); - } -]); -/*global parseList*/ -'use strict'; -angular.module('RedhatAccess.logViewer').controller('DropdownCtrl', [ - '$scope', - '$http', - '$location', - 'files', - 'hideMachinesDropdown', - 'AlertService', - function ($scope, $http, $location, files, hideMachinesDropdown, AlertService) { - $scope.machinesDropdownText = 'Please Select the Machine'; - $scope.items = []; - $scope.hideDropdown = hideMachinesDropdown.value; - $scope.loading = false; - var sessionId = $location.search().sessionId; - $scope.getMachines = function () { - $http({ - method: 'GET', - url: 'machines?sessionId=' + encodeURIComponent(sessionId) - }).success(function (data, status, headers, config) { - $scope.items = data; - }).error(function (data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - }; - $scope.machineSelected = function () { - $scope.loading = true; - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - files.selectedHost = this.choice; - $scope.machinesDropdownText = this.choice; - $http({ - method: 'GET', - url: 'logs?machine=' + files.selectedHost + '&sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) - }).success(function (data, status, headers, config) { - $scope.loading = false; - var tree = []; - parseList(tree, data); - files.setFileList(tree); - }).error(function (data, status, headers, config) { - $scope.loading = false; - AlertService.addDangerMessage(data); - }); - }; - if ($scope.hideDropdown) { - $scope.machineSelected(); - } else { - $scope.getMachines(); - } - } -]); - -'use strict'; -angular.module('RedhatAccess.logViewer').controller('TabsDemoCtrl', [ - '$rootScope', - '$scope', - '$http', - '$location', - 'files', - 'accordian', - 'SearchResultsService', - 'securityService', - 'AlertService', - 'LOGVIEWER_EVENTS', - function ($rootScope, $scope, $http, $location, files, accordian, SearchResultsService, securityService, AlertService, LOGVIEWER_EVENTS) { - $scope.tabs = []; - $scope.isLoading = false; - $scope.$watch(function () { - return files.getFileClicked().check; - }, function () { - if (files.getFileClicked().check && files.selectedFile !== undefined) { - var tab = {}; - if (files.selectedHost !== undefined) { - tab.longTitle = files.selectedHost + ':'; - } else { - tab.longTitle = ''; - } - tab.longTitle = tab.longTitle.concat(files.selectedFile); - var splitFileName = files.selectedFile.split('/'); - var fileName = splitFileName[splitFileName.length - 1]; - if (files.selectedHost !== undefined) { - tab.shortTitle = files.selectedHost + ':'; - } else { - tab.shortTitle = ''; - } - tab.shortTitle = tab.shortTitle.concat(fileName); - tab.active = true; - $scope.tabs.push(tab); - $scope.isLoading = true; - files.setActiveTab(tab); - files.setFileClicked(false); - } - }); - $scope.$watch(function () { - return files.file; - }, function () { - if (files.file && files.activeTab) { - files.activeTab.content = files.file; - $scope.isLoading = false; - files.file = undefined; - } - }); - $scope.$watch(function () { - return SearchResultsService.searchInProgress.value; - }, function () { - if (SearchResultsService.searchInProgress.value === true) { - $scope.$parent.isDisabled = true; - } else if (SearchResultsService.searchInProgress.value === false && $scope.$parent.textSelected === true) { - $scope.$parent.isDisabled = false; - } - }); - $scope.removeTab = function (index) { - $scope.tabs.splice(index, 1); - if ($scope.tabs.length < 1) { - $rootScope.$broadcast(LOGVIEWER_EVENTS.allTabsClosed); - } - }; - $scope.checked = false; - // This will be - // binded using the - // ps-open attribute - $scope.diagnoseText = function () { - //$scope.isDisabled = true; - var text = strata.utils.getSelectedText(); - securityService.validateLogin(true).then(function () { - //Removed in refactor, no loger exists. Think it hides tool tip?? - //this.tt_isOpen = false; - if (!$scope.$parent.solutionsToggle) { - $scope.$parent.solutionsToggle = !$scope.$parent.solutionsToggle; - } - if (text !== '') { - $scope.checked = !$scope.checked; - SearchResultsService.diagnose(text, 5); - } - }); // this.tt_isOpen = false; - // if (!$scope.$parent.solutionsToggle) { - // $scope.$parent.solutionsToggle = !$scope.$parent.solutionsToggle; - // } - // var text = strata.utils.getSelectedText(); - // if (text != "") { - // $scope.checked = !$scope.checked; - // SearchResultsService.diagnose(text, 5); - // } - //$scope.sleep(5000, $scope.checkTextSelection); - }; - $scope.refreshTab = function (index) { - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - var fileNameForRefresh = this.$parent.tab.longTitle; - var hostForRefresh = null; - var splitNameForRefresh = fileNameForRefresh.split(':'); - if (splitNameForRefresh[0] && splitNameForRefresh[1]) { - hostForRefresh = splitNameForRefresh[0]; - fileNameForRefresh = splitNameForRefresh[1]; - $http({ - method: 'GET', - url: 'logs?sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) + '&path=' + fileNameForRefresh + '&machine=' + hostForRefresh - }).success(function (data, status, headers, config) { - $scope.tabs[index].content = data; - }).error(function (data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - } - }; - } -]); -'use strict'; -angular.module('RedhatAccess.logViewer').controller('fileController', [ - '$scope', - 'files', - function ($scope, files) { - $scope.roleList = ''; - $scope.$watch(function () { - return $scope.mytree.currentNode; - }, function () { - if ($scope.mytree.currentNode !== undefined && $scope.mytree.currentNode.fullPath !== undefined) { - files.setSelectedFile($scope.mytree.currentNode.fullPath); - files.setRetrieveFileButtonIsDisabled(false); - } else { - files.setRetrieveFileButtonIsDisabled(true); - } - }); - $scope.$watch(function () { - return files.fileList; - }, function () { - $scope.roleList = files.fileList; - }); - } -]); -'use strict'; -angular.module('RedhatAccess.logViewer').controller('logViewerController', [ - '$scope', - 'SearchResultsService', - function ($scope, SearchResultsService) { - $scope.isDisabled = true; - $scope.textSelected = false; - $scope.enableDiagnoseButton = function () { - //Gotta wait for text to "unselect" - $scope.sleep(1, $scope.checkTextSelection); - }; - $scope.checkTextSelection = function () { - if (strata.utils.getSelectedText()) { - $scope.textSelected = true; - if (SearchResultsService.searchInProgress.value) { - $scope.isDisabled = true; - } else { - $scope.isDisabled = false; - } - } else { - $scope.textSelected = false; - $scope.isDisabled = true; - } - $scope.$apply(); - }; - $scope.sleep = function (millis, callback) { - setTimeout(function () { - callback(); - }, millis); - }; - } -]); -'use strict'; -angular.module('RedhatAccess.logViewer').controller('selectFileButton', [ - '$scope', - '$rootScope', - '$http', - '$location', - 'files', - 'AlertService', - 'LOGVIEWER_EVENTS', - function ($scope, $rootScope, $http, $location, files, AlertService, LOGVIEWER_EVENTS) { - $scope.retrieveFileButtonIsDisabled = files.getRetrieveFileButtonIsDisabled(); - $scope.fileSelected = function () { - files.setFileClicked(true); - var sessionId = $location.search().sessionId; - var userId = $location.search().userId; - $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; - $http({ - method: 'GET', - url: 'logs?sessionId=' + encodeURIComponent(sessionId) + '&userId=' + encodeURIComponent(userId) + '&path=' + files.selectedFile + '&machine=' + files.selectedHost - }).success(function (data, status, headers, config) { - files.file = data; - }).error(function (data, status, headers, config) { - AlertService.addDangerMessage(data); - }); - }; - $rootScope.$on(LOGVIEWER_EVENTS.allTabsClosed, function () { - $scope.$parent.$parent.sidePaneToggle = !$scope.$parent.$parent.sidePaneToggle; - }); - } -]); -'use strict'; -angular.module('RedhatAccess.logViewer').directive('rhaFilldown', [ - '$window', - '$timeout', - function ($window, $timeout) { - return { - restrict: 'A', - link: function postLink(scope, element) { - scope.onResizeFunction = function () { - var distanceToTop = element[0].getBoundingClientRect().top; - var height = $window.innerHeight - distanceToTop - 21; - if (element[0].id === 'fileList') { - height -= 34; - } - scope.windowHeight = height; - return scope.windowHeight; - }; - // This might be overkill?? - //scope.onResizeFunction(); - angular.element($window).bind('resize', function () { - scope.onResizeFunction(); - scope.$apply(); - }); - angular.element($window).bind('click', function () { - scope.onResizeFunction(); - scope.$apply(); - }); - $timeout(scope.onResizeFunction, 100); // $(window).load(function(){ - // scope.onResizeFunction(); - // scope.$apply(); - // }); - // scope.$on('$viewContentLoaded', function() { - // scope.onResizeFunction(); - // //scope.$apply(); - // }); - } - }; - } -]); -'use strict'; -angular.module('RedhatAccess.logViewer').directive('rhaLogtabs', function () { - return { - templateUrl: 'log_viewer/views/logTabs.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -angular.module('RedhatAccess.logViewer').directive('rhaLogsinstructionpane', function () { - return { - templateUrl: 'log_viewer/views/logsInstructionPane.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -angular.module('RedhatAccess.logViewer').directive('rhaNavsidebar', function () { - return { - templateUrl: 'log_viewer/views/navSideBar.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -angular.module('RedhatAccess.logViewer').directive('rhaRecommendations', function () { - return { - templateUrl: 'log_viewer/views/recommendations.html', - restrict: 'A', - link: function postLink(scope, element, attrs) { - } - }; -}); -'use strict'; -angular.module('RedhatAccess.logViewer').service('accordian', function () { - var groups = []; - return { - getGroups: function () { - return groups; - }, - addGroup: function (group) { - groups.push(group); - }, - clearGroups: function () { - groups = ''; - } - }; -}); -'use strict'; -angular.module('RedhatAccess.logViewer').factory('files', function () { - var fileList = ''; - var selectedFile = ''; - var file = ''; - var retrieveFileButtonIsDisabled = { check: true }; - var fileClicked = { check: false }; - var activeTab = null; - return { - getFileList: function () { - return fileList; - }, - setFileList: function (fileList) { - this.fileList = fileList; - }, - getSelectedFile: function () { - return selectedFile; - }, - setSelectedFile: function (selectedFile) { - this.selectedFile = selectedFile; - }, - getFile: function () { - return file; - }, - setFile: function (file) { - this.file = file; - }, - setRetrieveFileButtonIsDisabled: function (isDisabled) { - retrieveFileButtonIsDisabled.check = isDisabled; - }, - getRetrieveFileButtonIsDisabled: function () { - return retrieveFileButtonIsDisabled; - }, - setFileClicked: function (isClicked) { - fileClicked.check = isClicked; - }, - getFileClicked: function () { - return fileClicked; - }, - setActiveTab: function (activeTab) { - this.activeTab = activeTab; - }, - getActiveTab: function () { - return activeTab; - } - }; -}); -angular.module('RedhatAccess.template', ['common/views/alert.html', 'common/views/header.html', 'common/views/title.html', 'common/views/treenode.html', 'common/views/treeview-selector.html', 'security/views/login_form.html', 'security/views/login_status.html', 'search/views/accordion_search.html', 'search/views/accordion_search_results.html', 'search/views/list_search_results.html', 'search/views/resultDetail.html', 'search/views/search.html', 'search/views/search_form.html', 'search/views/standard_search.html', 'cases/views/accountSelect.html', 'cases/views/addCommentSection.html', 'cases/views/attachLocalFile.html', 'cases/views/attachProductLogs.html', 'cases/views/attachmentsSection.html', 'cases/views/chatButton.html', 'cases/views/commentsSection.html', 'cases/views/compact.html', 'cases/views/compactCaseList.html', 'cases/views/compactEdit.html', 'cases/views/createGroupButton.html', 'cases/views/createGroupModal.html', 'cases/views/deleteGroupButton.html', 'cases/views/descriptionSection.html', 'cases/views/detailsSection.html', 'cases/views/edit.html', 'cases/views/emailNotifySelect.html', 'cases/views/entitlementSelect.html', 'cases/views/exportCSVButton.html', 'cases/views/group.html', 'cases/views/groupList.html', 'cases/views/groupSelect.html', 'cases/views/list.html', 'cases/views/listAttachments.html', 'cases/views/listBugzillas.html', 'cases/views/listFilter.html', 'cases/views/listNewAttachments.html', 'cases/views/new.html', 'cases/views/ownerSelect.html', 'cases/views/productSelect.html', 'cases/views/recommendationsSection.html', 'cases/views/requestManagementEscalationModal.html', 'cases/views/search.html', 'cases/views/searchBox.html', 'cases/views/searchResult.html', 'cases/views/selectLoadingIndicator.html', 'cases/views/severitySelect.html', 'cases/views/statusSelect.html', 'cases/views/typeSelect.html', 'log_viewer/views/logTabs.html', 'log_viewer/views/log_viewer.html', 'log_viewer/views/logsInstructionPane.html', 'log_viewer/views/navSideBar.html', 'log_viewer/views/recommendations.html']); - -angular.module("common/views/alert.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("common/views/alert.html", - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              {{alert.message}}
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("common/views/header.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("common/views/header.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("common/views/title.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("common/views/title.html", - "

              {{titlePrefix}}{{getPageTitle()}}

              \n" + - ""); -}]); - -angular.module("common/views/treenode.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("common/views/treenode.html", - "
            • \n" + - "
              \n" + - " 0\" ng-click=\"choice.collapsed = !choice.collapsed\">\n" + - " \n" + - " 0\" ng-class=\"folder\">{{choice.name}}\n" + - " \n" + - " \n" + - " {{choice.name}}\n" + - " \n" + - "
              \n" + - "
            • "); -}]); - -angular.module("common/views/treeview-selector.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("common/views/treeview-selector.html", - "
              \n" + - "
              {{'Choose File(s) To Attach:'|translate}}
              \n" + - " \n" + - "
              {{attachmentTree| json}}
              \n" + - "
              "); -}]); - -angular.module("security/views/login_form.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("security/views/login_form.html", - "
              \n" + - "

              \n" + - " Sign into the Red Hat Customer Portal\n" + - "

              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - " {{'Red Hat Access makes it easy for you to self-solve issues, diagnose problems, and engage with us via the Red Hat Customer Portal. To access Red Hat Customer Portal resources, you must enter valid portal credentials.'|translate}}\n" + - "
              \n" + - "\n" + - "
              \n" + - " {{authError}}\n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " {{'Note:'|translate}}\n" + - "  {{'Red Hat Customer Portal credentials differ from the credentials used to log into this product.'|translate}}\n" + - "
              \n" + - "\n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              "); -}]); - -angular.module("security/views/login_status.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("security/views/login_status.html", - "
              \n" + - " {{'Logged into the Red Hat Customer Portal as'|translate}} {{securityService.loginStatus.loggedInUser}}  | \n" + - " \n" + - " Log out\n" + - " \n" + - " 0\">\n" + - " Log out\n" + - " \n" + - "\n" + - " {{'Not Logged into the Red Hat Customer Portal'|translate}} | \n" + - " \n" + - " {{'Log In'|translate}}\n" + - " \n" + - " 0\">\n" + - " {{'Log In'|translate}}\n" + - " \n" + - "\n" + - "\n" + - "
              \n" + - ""); -}]); - -angular.module("search/views/accordion_search.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/accordion_search.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("search/views/accordion_search_results.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/accordion_search_results.html", - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "

              Recommendations

              \n" + - "
              \n" + - " \n" + - "  \n" + - " \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "  {{result.title}}\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - ""); -}]); - -angular.module("search/views/list_search_results.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/list_search_results.html", - "
              \n" + - "
              \n" + - " \n" + - "\n" + - "
              \n" + - "

              \n" + - " Recommendations\n" + - "

              \n" + - "
              \n" + - "
              \n" + - " {{ result.title }}\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " {{'To view a recommendation, click on it.'|translate}}\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              "); -}]); - -angular.module("search/views/resultDetail.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/resultDetail.html", - "
              \n" + - "
              \n" + - "

              Environment

              \n" + - "
              \n" + - "

              Issue

              \n" + - "
              \n" + - "
              \n" + - "

              Resolution

              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "\n" + - ""); -}]); - -angular.module("search/views/search.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/search.html", - "
              \n" + - ""); -}]); - -angular.module("search/views/search_form.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/search_form.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "  \n" + - " \n" + - "
              \n" + - "\n" + - "
              \n" + - "
              "); -}]); - -angular.module("search/views/standard_search.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("search/views/standard_search.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("cases/views/accountSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/accountSelect.html", - "
               
              "); -}]); - -angular.module("cases/views/addCommentSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/addCommentSection.html", - "
              {{'You have used 0% of the 32KB maximum description size.'|translate}}{{'Saving draft...'|translate}}{{'Draft saved'|translate}}
              "); -}]); - -angular.module("cases/views/attachLocalFile.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/attachLocalFile.html", - "
              {{fileName}}
              {{'File names must be less than 80 characters. Maximum file size for web-uploaded attachments is 250 MB. Please FTP larger files to dropbox.redhat.com.'|translate}} (More info)
              "); -}]); - -angular.module("cases/views/attachProductLogs.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/attachProductLogs.html", - "
              {{'Attach Foreman logs:'|translate}}
              "); -}]); - -angular.module("cases/views/attachmentsSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/attachmentsSection.html", - "

              Attachments

              {{'Server File(s) To Attach:'|translate}}
              "); -}]); - -angular.module("cases/views/chatButton.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/chatButton.html", - "
              "); -}]); - -angular.module("cases/views/commentsSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/commentsSection.html", - "

              Case Discussion

              {{'Would you like a Red Hat support manager to contact you regarding this case?'|translate}}
              {{comment.created_by}}
              {{comment.created_date | date:'mediumDate'}}
              {{comment.created_date | date:'h:mm:ss a Z'}}
              "); -}]); - -angular.module("cases/views/compact.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/compact.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("cases/views/compactCaseList.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/compactCaseList.html", - "
              {{'No cases found with given filters.'|translate}}
              "); -}]); - -angular.module("cases/views/compactEdit.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/compactEdit.html", - "
              "); -}]); - -angular.module("cases/views/createGroupButton.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/createGroupButton.html", - ""); -}]); - -angular.module("cases/views/createGroupModal.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/createGroupModal.html", - "

              Create Case Group

              "); -}]); - -angular.module("cases/views/deleteGroupButton.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/deleteGroupButton.html", - ""); -}]); - -angular.module("cases/views/descriptionSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/descriptionSection.html", - "

              Description

              {{CaseService.kase.created_by}}
              {{CaseService.kase.description}}
              "); -}]); - -angular.module("cases/views/detailsSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/detailsSection.html", - "

              Case {{CaseService.kase.case_number}}

              Details

              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              {{CaseService.kase.entitlement.sla}}
              {{CaseService.kase.contact_name}}
              {{CaseService.kase.owner}}
              {{CaseService.kase.created_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.created_by}}
              {{CaseService.kase.last_modified_date | date:'MMM d, y h:mm:ss a Z'}}
              {{CaseService.kase.last_modified_by}}
              {{CaseService.kase.account_number}}
              {{CaseService.account.name}}
              "); -}]); - -angular.module("cases/views/edit.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/edit.html", - "
              "); -}]); - -angular.module("cases/views/emailNotifySelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/emailNotifySelect.html", - "

              Email Notification Recipients

              {{user}}
              "); -}]); - -angular.module("cases/views/entitlementSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/entitlementSelect.html", - "
              "); -}]); - -angular.module("cases/views/exportCSVButton.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/exportCSVButton.html", - "
              {{'Exporting CSV...'|translate}}
              "); -}]); - -angular.module("cases/views/group.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/group.html", - "
              "); -}]); - -angular.module("cases/views/groupList.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/groupList.html", - "
              {{'No groups found.'|translate}}
              {{'Name'|translate}}
              {{group.name}}
              "); -}]); - -angular.module("cases/views/groupSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/groupSelect.html", - "
              "); -}]); - -angular.module("cases/views/list.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/list.html", - "
              {{'No cases found with given filters.'|translate}}
              {{case.case_number}}{{case.summary}}{{case.product}} / {{case.version}}{{case.status}}{{case.severity}}{{case.contact_name}}{{case.created_date | date:'longDate'}}{{case.last_modified_date | date:'longDate'}}
              "); -}]); - -angular.module("cases/views/listAttachments.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/listAttachments.html", - "
              {{'No attachments added'|translate}}
              {{'Attached Files'|translate}}
              {{'Filename'|translate}}{{'Description'|translate}}{{'Size'|translate}}{{'Attached'|translate}}{{'Attached By'|translate}}{{'Delete'|translate}}
              {{attachment.file_name}}
              {{attachment.file_name}}
              {{attachment.description}}{{attachment.length | bytes}}{{attachment.created_date | date:'medium'}}{{attachment.created_by}}
              {{'Delete'|translate}}
              {{'Delete'|translate}}
              "); -}]); - -angular.module("cases/views/listBugzillas.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/listBugzillas.html", - "

              {{'Bugzilla Tickets'|translate}}

              {{'No linked bugzillas'|translate}}
              {{'Bugzilla Number'|translate}}{{'Summary of Request'|translate}}
              {{bugzilla.bugzilla_number}}{{bugzilla.summary}}
              "); -}]); - -angular.module("cases/views/listFilter.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/listFilter.html", - "
              "); -}]); - -angular.module("cases/views/listNewAttachments.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/listNewAttachments.html", - "
              {{'Files to Attach'|translate}}
              • {{attachment.file_name}} ({{attachment.length | bytes}}) - {{attachment.description}}
              • {{attachment}}
              "); -}]); - -angular.module("cases/views/new.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/new.html", - "
              {{CaseService.kase.product.name}} {{CaseService.kase.version}}
              {{CaseService.kase.summary}}
              {{CaseService.kase.description}}
              {{CaseService.entitlements[0]}}
              {{'24x7 Support:'|translate}}
              {{'24x7 Contact:'|translate}}
              Server File(s) To Attach:
              "); -}]); - -angular.module("cases/views/ownerSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/ownerSelect.html", - "
              "); -}]); - -angular.module("cases/views/productSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/productSelect.html", - "
              "); -}]); - -angular.module("cases/views/recommendationsSection.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/recommendationsSection.html", - "

              Recommendations

               
              {{recommendation.title}}
              handpicked
              {{recommendation.resolution.text | recommendationsResolution}}
              {{'View full article in new window'|translate}}
              "); -}]); - -angular.module("cases/views/requestManagementEscalationModal.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/requestManagementEscalationModal.html", - "

              Request Management Escalation

              {{'If you feel the issue has become more severe or the case should be a higher priority, please provide a detailed comment, and the case will be reviewed by a support manager.'|translate}}{{'Learn more'|translate}}
              {{'Comment:'|translate}}
              "); -}]); - -angular.module("cases/views/search.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/search.html", - "

              Searching...

              {{'No cases found with given search criteria.'|translate}}
              "); -}]); - -angular.module("cases/views/searchBox.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/searchBox.html", - "
              "); -}]); - -angular.module("cases/views/searchResult.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/searchResult.html", - "
              {{'Updated:'|translate}}{{theCase.last_modified_date | date: 'medium'}}
              {{'Status:'|translate}}{{theCase.status}}
              {{'Severity:'|translate}}{{theCase.severity}}
              {{theCase.comments.comment[0].text}}
              {{theCase.comments.comment[0].created_by}}
              {{theCase.comments.comment[0].created_date | date: 'medium'}}
              "); -}]); - -angular.module("cases/views/selectLoadingIndicator.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/selectLoadingIndicator.html", - "
              "); -}]); - -angular.module("cases/views/severitySelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/severitySelect.html", - "
              "); -}]); - -angular.module("cases/views/statusSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/statusSelect.html", - "
              "); -}]); - -angular.module("cases/views/typeSelect.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("cases/views/typeSelect.html", - "
              "); -}]); - -angular.module("log_viewer/views/logTabs.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("log_viewer/views/logTabs.html", - "
              \n" + - "
              \n" + - "
              {{tab.shortTitle}}\n" + - " \n" + - " \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - "

              {{tab.longTitle}}

              \n" + - "
              \n" + - " \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - "\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "\n" + - "
              {{tab.content}}
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - ""); -}]); - -angular.module("log_viewer/views/log_viewer.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("log_viewer/views/log_viewer.html", - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              "); -}]); - -angular.module("log_viewer/views/logsInstructionPane.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("log_viewer/views/logsInstructionPane.html", - "
              0\" rha-filldown ng-style=\"{ height: windowHeight }\" style=\"overflow:auto\">\n" + - "
              \n" + - "
              \n" + - "

              Log File Viewer

              \n" + - "

              \n" + - "

              The log file viewer gives the ability to diagnose application logs as well as file a support case with Red Hat Global Support Services.\n" + - "

              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "

              \n" + - "  Select Log

              \n" + - "

              \n" + - " Simply navigate to and select a log file from the list on the left and click the 'Select File' button.

              \n" + - "\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "

              \n" + - "  {{'Diagnose'|translate}}\n" + - "

              \n" + - "

              Once you have selected your log file then you may diagnose any part of the log file and clicking the 'Red Hat Diagnose' button. This will then display relevant articles and solutons from our Red Hat Knowledge base.

              \n" + - "\n" + - "
              \n" + - "
              \n" + - "
              \n" + - "

              \n" + - "  Open a New Support Case\n" + - "

              \n" + - "

              In the event that you would still like to open a support case, select 'Open a New Support Case'. The case will be pre-populated with the portion of the log previously selected.

              \n" + - "\n" + - "
              \n" + - "
              \n" + - "\n" + - "
              "); -}]); - -angular.module("log_viewer/views/navSideBar.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("log_viewer/views/navSideBar.html", - "
              \n" + - "
              \n" + - "
              \n" + - "

              Available Log Files

              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - "\n" + - " \n" + - " \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - "
              \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - "
              "); -}]); - -angular.module("log_viewer/views/recommendations.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("log_viewer/views/recommendations.html", - "
              \n" + - "
              \n" + - "
              \n" + - " \n" + - " \n" + - " \n" + - "
              "); -}]); diff --git a/assets/styles/redhat_access_angular_ui-deps-embedded-images.css b/assets/styles/redhat_access_angular_ui-deps-embedded-images.css new file mode 100644 index 0000000..9dca137 --- /dev/null +++ b/assets/styles/redhat_access_angular_ui-deps-embedded-images.css @@ -0,0 +1 @@ +div[data-angular-treeview]{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;font-family:Tahoma;font-size:13px;color:#555;text-decoration:none}div[data-tree-model] ul{margin:0;padding:0;list-style:none;border:0;overflow:hidden}div[data-tree-model] li{position:relative;padding:0 0 0 20px;line-height:20px}div[data-tree-model] li .expanded{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAJBJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX9EUgsSIwVgNQOdjxcBwghswP0MFLAAThLHxYbA6mBfA/oIJoinEhmGaUQwgRzPcABAGeYMYDFMPwygc0vF/BgDd66LkDQj2XgAAAABJRU5ErkJggg==);background-repeat:no-repeat}div[data-tree-model] li .collapsed{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAIhJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX7EoxobRw4U6BszPUAEHHDEYrBbdALC/sCjGhrGGAUgQ7DQsGtAxVgNAGOQ0YjBMPQyjcEjH/xkAhEKsbVNNI1sAAAAASUVORK5CYII=);background-repeat:no-repeat}div[data-tree-model] li .normal{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAOCAYAAADwikbvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAHZJREFUOE9j+P//Pxiv7HH8jw/D1CFjOAOqACu4srcGqwFEa8ZmAEma0Q3A1JxbgIrRAH7NBABezT3bF8JxfU08WCwrJwlMgwD1bYbZguxXkBjIVmSboepwaCYA8GoGRQeyvy07S8AYBmhnMyGAUzOxGKLnPwMABE2jDc+cnOsAAAAASUVORK5CYII=);background-repeat:no-repeat}div[data-tree-model] li i,div[data-tree-model] li span{cursor:pointer}div[data-tree-model] li .selected{background-color:#adf;font-weight:700;padding:1px 5px}.ng-table th{text-align:center;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-table th.sortable{cursor:pointer}.ng-table th.sortable div{padding-right:18px;position:relative}.ng-table th.sortable div:after,.ng-table th.sortable div:before{content:"";border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:visible;right:8px;top:50%;position:absolute;opacity:.3;margin-top:-4px}.ng-table th.sortable div:before{margin-top:2px;border-bottom:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000}.ng-table th.sortable div:hover:after,.ng-table th.sortable div:hover:before{opacity:1;visibility:visible}.ng-table th.sortable.sort-asc,.ng-table th.sortable.sort-desc{background-color:rgba(141,192,219,.25);text-shadow:0 1px 1px rgba(255,255,255,.75)}.ng-table th.sortable.sort-asc div:after,.ng-table th.sortable.sort-desc div:after{margin-top:-2px}.ng-table th.sortable.sort-asc div:before,.ng-table th.sortable.sort-desc div:before{visibility:hidden}.ng-table th.sortable.sort-asc div:after,.ng-table th.sortable.sort-asc div:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:.6;-moz-opacity:.6;opacity:.6}.ng-table th.sortable.sort-desc div:after{border-bottom:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:.6;-moz-opacity:.6;opacity:.6}.ng-table th.filter .input-filter{margin:0;display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-table+.pagination{margin-top:0}@media only screen and (max-width:800px){.ng-table-responsive{border-bottom:1px solid #999}.ng-table-responsive tr{border-top:1px solid #999;border-left:1px solid #999;border-right:1px solid #999}.ng-table-responsive td:before{position:absolute;padding:8px;left:0;top:0;width:50%;white-space:nowrap;text-align:left;font-weight:700}.ng-table-responsive thead tr th{text-align:left}.ng-table-responsive thead tr.ng-table-filters th{padding:0}.ng-table-responsive thead tr.ng-table-filters th form>div{padding:8px}.ng-table-responsive td{border:0;border-bottom:1px solid #eee;position:relative;padding-left:50%;white-space:normal;text-align:left}.ng-table-responsive td:before{content:attr(data-title-text)}.ng-table-responsive,.ng-table-responsive tbody,.ng-table-responsive td,.ng-table-responsive th,.ng-table-responsive thead,.ng-table-responsive tr{display:block}}.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;zoom:1;*display:inline;-webkit-user-select:none;-moz-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;left:-9999px;z-index:1010;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;border:1px solid #aaa;border-top:0;background:#fff;box-shadow:0 4px 5px rgba(0,0,0,.15)}.chosen-container.chosen-with-drop .chosen-drop{left:0}.chosen-container a{cursor:pointer}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 0 0 8px;height:23px;border:1px solid #aaa;border-radius:5px;background:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),color-stop(100%,#f4f4f4));background:-webkit-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-moz-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-o-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;right:26px;display:block;width:12px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover,.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1px 0;padding:4px 20px 4px 5px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px,-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px,-webkit-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px,-moz-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px,-o-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat 100% -20px,linear-gradient(#eee 1%,#fff 15%);font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:-webkit-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-moz-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-o-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;width:100%;height:auto!important;height:1%;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background-image:-webkit-linear-gradient(#eee 1%,#fff 15%);background-image:-moz-linear-gradient(#eee 1%,#fff 15%);background-image:-o-linear-gradient(#eee 1%,#fff 15%);background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:5px;height:15px;outline:0;border:0!important;background:transparent!important;box-shadow:none;color:#666;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 20px 3px 5px;border:1px solid #aaa;border-radius:3px;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-clip:padding-box;box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:12px;height:12px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#eee),color-stop(80%,#fff));background-image:-webkit-linear-gradient(#eee 20%,#fff 80%);background-image:-moz-linear-gradient(#eee 20%,#fff 80%);background-image:-o-linear-gradient(#eee 20%,#fff 80%);background-image:linear-gradient(#eee 20%,#fff 80%);box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-left:0;background:0 0}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close,.chosen-disabled .chosen-single{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{overflow:visible;padding:0 8px 0 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:38px}.chosen-rtl .chosen-single div{right:auto;left:3px}.chosen-rtl .chosen-single abbr{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:3px 5px 3px 19px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:4px}.chosen-rtl .chosen-drop,.chosen-rtl.chosen-container-single-nosearch .chosen-search{left:9999px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div{border-right:0}.chosen-rtl .chosen-search input[type=text]{padding:4px 5px 4px 20px;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px,-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px,-webkit-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px,-moz-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px,-o-linear-gradient(#eee 1%,#fff 15%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAlCAYAAAAN8srVAAACTUlEQVR42u3Wv2sTcRiA8VPBxUKwEAxU3NxPIoFAl1bIkkmwYKAKRbqbRSWQCGJ+rMUibjo4FARBl0AgUIh/QXFxFIpKJHAQKA56r0/hDbyEK5VrDH2hBx+ud+Ga9+G+uSQQkVOv0+lMZNBFHoFRwABZb0F9CCITVdRjQd9b0CoOTNSGiRkidBWkljGGINb9CCECd0FqE7GJqkxeMxccK8UbJzppUPGIO5SfR9DCjINsTIR1RDbKXvAakuB9yqAsvuLaDIN6Jqag5/IaIxjYCxaxDzFGyKUMegdBb4ZBGfQmMUaIXeSmLyhDjHspl9wdiPHgJEGlUumf2UGml96HlJ+hRQwhRoSleQfZgfawlDJoB5KgO4OgDLrIT4UUMEA2xdNpro/t6aA+BJGJKuqxoJ9ikLmzQas4MFEbJmYIHz99GNRaxhiCWPcjhAjcBalNxCaqgsBrUPGIO5T3GGRjIqwjslHegnompqDn8hojGHgLyqA3iTFC7CLnLOh4Z0Gn3FnQf2O3ZrN5iZ9aVw81Go3zQfLmI4iIx/gBUXvtdnvNXZDGbEMI2Gf/BFsQPXffVRADr+jgn1hylwPdOL6Bn7w2brVaV9wEMfALBheGDu3QGvVQ79RtT0FvGDyu1WoXE4JWNKjiack916HXEoJecT7GLTdBLLXrDPwbEX+Xq9XqucPHNzFVzv3B93q9fsHbU+4uhAhh/wXfIMaWqyBdXjfxluE/63fQM/Yt8/je9hQ0vdnQpybqJRZcB2nUI4J+QVB2H6RRHzUoTPo/fwGr9gNcek8bXAAAAABJRU5ErkJggg==) no-repeat -30px -20px,linear-gradient(#eee 1%,#fff 15%);direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background-position:6px 2px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background-position:-12px 2px}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:144dpi){.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span,.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container-single .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b,.chosen-rtl .chosen-search input[type=text]{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGgAAABKCAMAAABgpuGuAAAAzFBMVEX///8AAACIiIiIiIiIiIhGRkZGRkZGRkaIiIiHh4eHh4eGhoaAgICGhoaHh4dGRkaHh4eHh4eIiIiHh4eIiIiHh4eIiIiHh4eHh4eHh4eHh4eHh4eAgICHh4eHh4eAgICFhYWIiIiHh4eHh4eHh4eIiIiEhISIiIiIiIiIiIiIiIiHh4d0dHSGhoaHh4eDg4NVVVWDg4OHh4eIiIiAgICHh4eHh4eAgICIiIiHh4eIiIiHh4eIiIiHh4eGhoaHh4eHh4eIiIiIiIhGRkYymc+gAAAAQnRSTlMAAP7wMDDwYGCg/VAQcIDz4CDz0PxAz7D1wPv5CGChFEX64t2QHh2N3Jaa2wsTgiEDKYjYDGaZBO8Rqd+LREqM5n7NGqdwAAACCUlEQVR4Xu3V127jMBCFYZ2h5KJiW5a7UzbJ9t57Hb3/O60Ik+JFsMLCwrkI4P+KV/oAYjSMOAEjE8MVm1HECiNVL8VGlSY1jpW8w5OMeil2RxIUe6k9kCA4YD7nOhG8RHYiWCk4YEKI5wdnHuPuQPyr4w8Df7xhm0xgI/2wASpVSwsdvYJm2jbrgraqWwsdvVSXAVp2QJk2ZQCOfiaw9s4a/4bymYVmOXD0w4fSzaIpO6CJ2nTyH1Cfj6BUV9kHwuFa0AFtPbTtBS0ttOyAMm3L+kB2HtbogG79Ap0Bw0ECVzIYBgilMaWH+odhXTeSc+p62LFeetU4VvKOlTgNai8l7kiCEi+1BxIEB0ynXCeCl8hOBCsFB0wIyfTgTBPcEYh/dfxh4I83/4flryD+UmU9E4Q6Hj5Cp06dOoWmvKhURFZFjjYGlBlVC4l+zpjQuahIulikljrnQZmqXN18ePDwz+O9qGQsKDcqm/tnaHrxdCNichJUiOwf4dDrJzvRggStJH32HK6za9GKBKlKgbaXqQgNSl8F6N6CCb3pgFhX95Z3dZXKu/dwXV6nsiJBhcrVx09u6C6I450bkc3FpXW+fN2I7nPaChKV3bfvP37++r0TVR3zlqqKpIelqrYx85kQm+o+SKSHb2WhqsjHTiJBITiJDQWJDAWpIkNBIkJBIkOhSnUShf4C9DyJBLzMYSsAAAAASUVORK5CYII=)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}} \ No newline at end of file diff --git a/assets/styles/redhat_access_angular_ui-embedded-images.css b/assets/styles/redhat_access_angular_ui-embedded-images.css index 65d2e97..a844e61 100644 --- a/assets/styles/redhat_access_angular_ui-embedded-images.css +++ b/assets/styles/redhat_access_angular_ui-embedded-images.css @@ -1 +1 @@ -div[data-angular-treeview]{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;font-family:Tahoma;font-size:13px;color:#555;text-decoration:none}div[data-tree-model] ul{margin:0;padding:0;list-style:none;border:0;overflow:hidden}div[data-tree-model] li{position:relative;padding:0 0 0 20px;line-height:20px}div[data-tree-model] li .expanded{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAJBJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX9EUgsSIwVgNQOdjxcBwghswP0MFLAAThLHxYbA6mBfA/oIJoinEhmGaUQwgRzPcABAGeYMYDFMPwygc0vF/BgDd66LkDQj2XgAAAABJRU5ErkJggg==);background-repeat:no-repeat}div[data-tree-model] li .collapsed{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAIhJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX7EoxobRw4U6BszPUAEHHDEYrBbdALC/sCjGhrGGAUgQ7DQsGtAxVgNAGOQ0YjBMPQyjcEjH/xkAhEKsbVNNI1sAAAAASUVORK5CYII=);background-repeat:no-repeat}div[data-tree-model] li .normal{padding:1px 10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAOCAYAAADwikbvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAHZJREFUOE9j+P//Pxiv7HH8jw/D1CFjOAOqACu4srcGqwFEa8ZmAEma0Q3A1JxbgIrRAH7NBABezT3bF8JxfU08WCwrJwlMgwD1bYbZguxXkBjIVmSboepwaCYA8GoGRQeyvy07S8AYBmhnMyGAUzOxGKLnPwMABE2jDc+cnOsAAAAASUVORK5CYII=);background-repeat:no-repeat}div[data-tree-model] li i,div[data-tree-model] li span{cursor:pointer}div[data-tree-model] li .selected{background-color:#adf;font-weight:700;padding:1px 5px}.ng-table th{text-align:center;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ng-table th.sortable{cursor:pointer}.ng-table th.sortable div{padding-right:18px;position:relative}.ng-table th.sortable div:after,.ng-table th.sortable div:before{content:"";border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:visible;right:8px;top:50%;position:absolute;opacity:.3;margin-top:-4px}.ng-table th.sortable div:before{margin-top:2px;border-bottom:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000}.ng-table th.sortable div:hover:after,.ng-table th.sortable div:hover:before{opacity:1;visibility:visible}.ng-table th.sortable.sort-asc,.ng-table th.sortable.sort-desc{background-color:rgba(141,192,219,.25);text-shadow:0 1px 1px rgba(255,255,255,.75)}.ng-table th.sortable.sort-asc div:after,.ng-table th.sortable.sort-desc div:after{margin-top:-2px}.ng-table th.sortable.sort-asc div:before,.ng-table th.sortable.sort-desc div:before{visibility:hidden}.ng-table th.sortable.sort-asc div:after,.ng-table th.sortable.sort-asc div:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:.6;-moz-opacity:.6;opacity:.6}.ng-table th.sortable.sort-desc div:after{border-bottom:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:.6;-moz-opacity:.6;opacity:.6}.ng-table th.filter .input-filter{margin:0;display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ng-table+.pagination{margin-top:0}@media only screen and (max-width:800px){.ng-table-responsive{border-bottom:1px solid #999}.ng-table-responsive tr{border-top:1px solid #999;border-left:1px solid #999;border-right:1px solid #999}.ng-table-responsive td:before{position:absolute;padding:8px;left:0;top:0;width:50%;white-space:nowrap;text-align:left;font-weight:700}.ng-table-responsive thead tr th{text-align:left}.ng-table-responsive thead tr.ng-table-filters th{padding:0}.ng-table-responsive thead tr.ng-table-filters th form>div{padding:8px}.ng-table-responsive td{border:0;border-bottom:1px solid #eee;position:relative;padding-left:50%;white-space:normal;text-align:left}.ng-table-responsive td:before{content:attr(data-title-text)}.ng-table-responsive,.ng-table-responsive tbody,.ng-table-responsive td,.ng-table-responsive th,.ng-table-responsive thead,.ng-table-responsive tr{display:block}}.select2-container{margin:0;position:relative;display:inline-block;zoom:1;*display:inline;vertical-align:middle}.select2-container,.select2-drop,.select2-search,.select2-search input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.select2-container .select2-choice{display:block;height:26px;padding:0 0 0 8px;overflow:hidden;position:relative;border:1px solid #aaa;white-space:nowrap;line-height:26px;color:#444;text-decoration:none;border-radius:4px;background-clip:padding-box;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#fff;background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#eee),color-stop(0.5,#fff));background-image:-webkit-linear-gradient(center bottom,#eee 0,#fff 50%);background-image:-moz-linear-gradient(center bottom,#eee 0,#fff 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);background-image:linear-gradient(to top,#eee 0,#fff 50%)}.select2-container.select2-drop-above .select2-choice{border-bottom-color:#aaa;border-radius:0 0 4px 4px;background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#eee),color-stop(0.9,#fff));background-image:-webkit-linear-gradient(center bottom,#eee 0,#fff 90%);background-image:-moz-linear-gradient(center bottom,#eee 0,#fff 90%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0);background-image:linear-gradient(to bottom,#eee 0,#fff 90%)}.select2-container.select2-allowclear .select2-choice .select2-chosen{margin-right:42px}.select2-container .select2-choice>.select2-chosen{margin-right:26px;display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;float:none;width:auto}.select2-container .select2-choice abbr{display:none;width:12px;height:12px;position:absolute;right:24px;top:8px;font-size:1px;text-decoration:none;border:0;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) right top no-repeat;cursor:pointer;outline:0}.select2-container.select2-allowclear .select2-choice abbr{display:inline-block}.select2-container .select2-choice abbr:hover{background-position:right -11px;cursor:pointer}.select2-drop-mask{border:0;margin:0;padding:0;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:9998;background-color:#fff;filter:alpha(opacity=0)}.select2-drop{width:100%;margin-top:-1px;position:absolute;z-index:9999;top:100%;background:#fff;color:#000;border:1px solid #aaa;border-top:0;border-radius:0 0 4px 4px;-webkit-box-shadow:0 4px 5px rgba(0,0,0,.15);box-shadow:0 4px 5px rgba(0,0,0,.15)}.select2-drop.select2-drop-above{margin-top:1px;border-top:1px solid #aaa;border-bottom:0;border-radius:4px 4px 0 0;-webkit-box-shadow:0 -4px 5px rgba(0,0,0,.15);box-shadow:0 -4px 5px rgba(0,0,0,.15)}.select2-drop-active{border:1px solid #5897fb;border-top:0}.select2-drop.select2-drop-above.select2-drop-active{border-top:1px solid #5897fb}.select2-drop-auto-width{border-top:1px solid #aaa;width:auto}.select2-drop-auto-width .select2-search{padding-top:4px}.select2-container .select2-choice .select2-arrow{display:inline-block;width:18px;height:100%;position:absolute;right:0;top:0;border-left:1px solid #aaa;border-radius:0 4px 4px 0;background:#ccc;background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#ccc),color-stop(0.6,#eee));background-image:-webkit-linear-gradient(center bottom,#ccc 0,#eee 60%);background-image:-moz-linear-gradient(center bottom,#ccc 0,#eee 60%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#cccccc', GradientType=0);background-image:linear-gradient(to top,#ccc 0,#eee 60%)}.select2-container .select2-choice .select2-arrow b{display:block;width:100%;height:100%;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 0 1px}.select2-search{display:inline-block;width:100%;min-height:26px;margin:0;padding-left:4px;padding-right:4px;position:relative;z-index:10000;white-space:nowrap}.select2-search input{width:100%;height:auto!important;min-height:26px;padding:4px 20px 4px 5px;margin:0;outline:0;font-family:sans-serif;font-size:1em;border:1px solid #aaa;border-radius:0;-webkit-box-shadow:none;box-shadow:none;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 100% -22px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 100% -22px,-webkit-gradient(linear,left bottom,left top,color-stop(0.85,#fff),color-stop(0.99,#eee));background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 100% -22px,-webkit-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 100% -22px,-moz-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) no-repeat 100% -22px,linear-gradient(to bottom,#fff 85%,#eee 99%) 0 0}.select2-drop.select2-drop-above .select2-search input{margin-top:4px}.select2-search input.select2-active{background:#fff url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%;background:url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%,-webkit-gradient(linear,left bottom,left top,color-stop(0.85,#fff),color-stop(0.99,#eee));background:url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%,-webkit-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%,-moz-linear-gradient(center bottom,#fff 85%,#eee 99%);background:url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%,linear-gradient(to bottom,#fff 85%,#eee 99%) 0 0}.select2-container-active .select2-choice,.select2-container-active .select2-choices{border:1px solid #5897fb;outline:0;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.select2-dropdown-open .select2-choice{border-bottom-color:transparent;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;border-bottom-left-radius:0;border-bottom-right-radius:0;background-color:#eee;background-image:-webkit-gradient(linear,left bottom,left top,color-stop(0,#fff),color-stop(0.5,#eee));background-image:-webkit-linear-gradient(center bottom,#fff 0,#eee 50%);background-image:-moz-linear-gradient(center bottom,#fff 0,#eee 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);background-image:linear-gradient(to top,#fff 0,#eee 50%)}.select2-dropdown-open.select2-drop-above .select2-choice,.select2-dropdown-open.select2-drop-above .select2-choices{border:1px solid #5897fb;border-top-color:transparent;background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(0.5,#eee));background-image:-webkit-linear-gradient(center top,#fff 0,#eee 50%);background-image:-moz-linear-gradient(center top,#fff 0,#eee 50%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0);background-image:linear-gradient(to bottom,#fff 0,#eee 50%)}.select2-dropdown-open .select2-choice .select2-arrow{background:0 0;border-left:0;filter:none}.select2-dropdown-open .select2-choice .select2-arrow b{background-position:-18px 1px}.select2-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.select2-results{max-height:200px;padding:0 0 0 4px;margin:4px 4px 4px 0;position:relative;overflow-x:hidden;overflow-y:auto;-webkit-tap-highlight-color:rgba(0,0,0,0)}.select2-results ul.select2-result-sub{margin:0;padding-left:0}.select2-results li{list-style:none;display:list-item;background-image:none}.select2-results li.select2-result-with-children>.select2-result-label{font-weight:700}.select2-results .select2-result-label{padding:3px 7px 4px;margin:0;cursor:pointer;min-height:1em;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select2-results-dept-1 .select2-result-label{padding-left:20px}.select2-results-dept-2 .select2-result-label{padding-left:40px}.select2-results-dept-3 .select2-result-label{padding-left:60px}.select2-results-dept-4 .select2-result-label{padding-left:80px}.select2-results-dept-5 .select2-result-label{padding-left:100px}.select2-results-dept-6 .select2-result-label{padding-left:110px}.select2-results-dept-7 .select2-result-label{padding-left:120px}.select2-results .select2-highlighted{background:#3875d7;color:#fff}.select2-results li em{background:#feffde;font-style:normal}.select2-results .select2-highlighted em{background:0 0}.select2-results .select2-highlighted ul{background:#fff;color:#000}.select2-results .select2-no-results,.select2-results .select2-searching,.select2-results .select2-selection-limit{background:#f4f4f4;display:list-item;padding-left:5px}.select2-results .select2-disabled.select2-highlighted{color:#666;background:#f4f4f4;display:list-item;cursor:default}.select2-results .select2-disabled{background:#f4f4f4;display:list-item;cursor:default}.select2-results .select2-selected{display:none}.select2-more-results.select2-active{background:#f4f4f4 url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%}.select2-more-results{background:#f4f4f4;display:list-item}.select2-container.select2-container-disabled .select2-choice{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default}.select2-container.select2-container-disabled .select2-choice .select2-arrow{background-color:#f4f4f4;background-image:none;border-left:0}.select2-container.select2-container-disabled .select2-choice abbr{display:none}.select2-container-multi .select2-choices{height:auto!important;height:1%;margin:0;padding:0;position:relative;border:1px solid #aaa;cursor:text;overflow:hidden;background-color:#fff;background-image:-webkit-gradient(linear,0 0,0 100%,color-stop(1%,#eee),color-stop(15%,#fff));background-image:-webkit-linear-gradient(top,#eee 1%,#fff 15%);background-image:-moz-linear-gradient(top,#eee 1%,#fff 15%);background-image:linear-gradient(to bottom,#eee 1%,#fff 15%)}.select2-locked{padding:3px 5px!important}.select2-container-multi .select2-choices{min-height:26px}.select2-container-multi.select2-container-active .select2-choices{border:1px solid #5897fb;outline:0;-webkit-box-shadow:0 0 5px rgba(0,0,0,.3);box-shadow:0 0 5px rgba(0,0,0,.3)}.select2-container-multi .select2-choices li{float:left;list-style:none}html[dir=rtl] .select2-container-multi .select2-choices li{float:right}.select2-container-multi .select2-choices .select2-search-field{margin:0;padding:0;white-space:nowrap}.select2-container-multi .select2-choices .select2-search-field input{padding:5px;margin:1px 0;font-family:sans-serif;font-size:100%;color:#666;outline:0;border:0;-webkit-box-shadow:none;box-shadow:none;background:transparent!important}.select2-container-multi .select2-choices .select2-search-field input.select2-active{background:#fff url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==) no-repeat 100%!important}.select2-default{color:#999!important}.select2-container-multi .select2-choices .select2-search-choice{padding:3px 5px 3px 18px;margin:3px 0 3px 5px;position:relative;line-height:13px;color:#333;cursor:default;border:1px solid #aaa;border-radius:3px;-webkit-box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);background-clip:padding-box;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#e4e4e4;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(to top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%)}html[dir=rtl] .select2-container-multi .select2-choices .select2-search-choice{margin-left:0;margin-right:5px}.select2-container-multi .select2-choices .select2-search-choice .select2-chosen{cursor:default}.select2-container-multi .select2-choices .select2-search-choice-focus{background:#d4d4d4}.select2-search-choice-close{display:block;width:12px;height:13px;position:absolute;right:3px;top:4px;font-size:1px;outline:0;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAAoCAYAAACiu5n/AAACLElEQVR42u3Zz0sUYRzH8bUISoyF1i5iXSooyYgOEXapZNYNojwU/aAfUAT9A4YhUgdxt1To0KFIBCMIvEcUEXntUtivpYuUhYFIdDBMmD69he/hObgsbSnb13ngdZjZhX3eO8/MDrMpSctKErwsg//HUSgU7uNYsB3hHla4CybqEoRPaMJGFCEMewxuxnsIk5iALPqg1yVdj9eQGUdjiuE1eAs+QOYztrsMJqwFk8EyHguW95klD+ZD08gsYvBFCBPYgHXBOT1UNpg3ncQpnAicRbrCCQ3j8SIf5QvYEWxvxnlb0mWDr0MIvcOaCiayC78gRKmlH+WDbaIjkJnDzgq/+VHIvMWqag3ehBkIAxXGdkAIDVRlsE24H9//4ty9hju4Hej710c5m83WYging32HMYjMnwSvx75UlQ+iOiDEaEMLZiA8dPc7TFQDnkGYxQ8Iz9Hs8k4riqIa4l5ApojVbm8tiduPL5CZRs5lMGFH8DNYxo+C5d3tMfgohJeow0qMQujxuqRb0RBsZ3DA2ZIuP5LgJDgJToKr4ZHOWjTOy+fzNa6DiezCFGReod1lMGF3IYzjMm5B5rirYIJyEJ4iHezfjW+YRr2n4EHE2LrAa1cg5DwFj2DWLlKljn67p+B+CIdKPAaOsddTcBOEKbTZvjp0Qvjo8Sp9DjJFfIVMjBsef4f34AHeYAxX0VfqMbDnfw97IXMTta6DLbobcxBa3Qdb9BPE2LZQ8G98530ecQi/2QAAAABJRU5ErkJggg==) right top no-repeat}html[dir=rtl] .select2-search-choice-close{right:auto;left:3px}.select2-container-multi .select2-search-choice-close{left:3px}.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover,.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close{background-position:right -11px}.select2-container-multi.select2-container-disabled .select2-choices{background-color:#f4f4f4;background-image:none;border:1px solid #ddd;cursor:default}.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice{padding:3px 5px;border:1px solid #ddd;background-image:none;background-color:#f4f4f4}.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close{display:none;background:0 0}.select2-result-selectable .select2-match,.select2-result-unselectable .select2-match{text-decoration:underline}.select2-offscreen,.select2-offscreen:focus{clip:rect(0 0 0 0)!important;width:1px!important;height:1px!important;border:0!important;margin:0!important;padding:0!important;overflow:hidden!important;position:absolute!important;outline:0!important;left:0!important;top:0!important}.select2-display-none{display:none}.select2-measure-scrollbar{position:absolute;top:-10000px;left:-10000px;width:100px;height:100px;overflow:scroll}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-resolution:2dppx){.select2-container .select2-choice .select2-arrow b,.select2-container .select2-choice abbr,.select2-search input,.select2-search-choice-close{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABQCAYAAADSm7GJAAADFElEQVR42u2dsW4TQRBAI0ERCYpDpAUdJX/hAlxQ3SekovYXIIvKEiBRIUF1lHT+BP+Br0TCCCsFLW5cmCS3jKWNNFrdZu+EOG7sd9Irkl0p8r3s7Mzs5XLinIMD5uhvAIIBwYBgaMnNNZvNyj0nkUvPQbAdtDjnCSU3zkGwEbS4iOToHATbE6wptVwEGyUhcaW/JkTbT7JcCpIse4K7SC4pk4wRXreE5ZUMUwezgh03lT0YyKKBOhjoZHUi1oCf7mkYohd9ACVFrj50HgxzmtQifKwF15L1fxC8UD9/EQxzHtxC8KiD4FHPNWMhuIACwd33h3kLuXOZ2mc4yyLRZS1kCG6H3uc2Mbl+LO9Z8FRwEaYINnpDdWKVIEdwC/QVC4l97nk6sUqwQHA3wbGEa9Sj4CCxSlIguHtZMg8Tq/4Edy/bLNXB4/G4FKJ1sJ7zTwTrhMuTU3f+NVqc84SSG+bEJR99a3BoaHERybE5HDYYFKwptVwEGyUhcRX5PufBRoiH4Tg80WFMcBfJPJNljfC6JSzzVCUrGMHswUAWDdTBQCcLOveiCdEBnCYBb9kBBAOCAcGAYEDw0XP0NwDBgGBAMCAYEAwIHvD7QzJhIlSCE2rF0o9lav4eBBt5JWHR8EfzdYATfgkFgg2g5J4LdSD1WrjyXDeIPkfwsNErV6/Y38J34aXwWHgkvBJWwi74RSgQPGD8nrtRwrbCe+G0YX9+KHzyc2rPRsgQPFzBEyVrJ7xLvNTsjvBBuFQreYLg4Qpeqv32m3BP+YxJPhUulOAKwQMl2HsnymNK8mudeCF44IK9rCcdBD8XrhBsS/BTBBOibwTPCNEGCFqSX4X7LeSeCRdK8BLBwy6TdIPjo3A3kUF/pkyy1+ioPVsv8KxB7gPhi7BVcndCpqYheKCtSt1+vBR+CG+EZ8IL4a3wU69cRYlgC4cN4UFD/LDBNVAi2NZxYa0Ixe5ikhFs58B/2SC48mOZUMYkI/jw/61diWDzgtOSEWxdcFpyhWDTgtOSEWxccCgZwfZJ9akrJXiKYEDwMfAHMSYobVemsdsAAAAASUVORK5CYII=)!important;background-repeat:no-repeat!important;background-size:60px 40px!important}.select2-search input{background-position:100% -21px!important}}li.rha-treeselector-node{list-style-type:none}.rha-treeselector-node .icon{display:block;float:left;width:16px;height:16px}.rha-treeselector-node .expanded{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAJBJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX9EUgsSIwVgNQOdjxcBwghswP0MFLAAThLHxYbA6mBfA/oIJoinEhmGaUQwgRzPcABAGeYMYDFMPwygc0vF/BgDd66LkDQj2XgAAAABJRU5ErkJggg==)}.rha-treeselector-node .collapsed{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAIhJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX7EoxobRw4U6BszPUAEHHDEYrBbdALC/sCjGhrGGAUgQ7DQsGtAxVgNAGOQ0YjBMPQyjcEjH/xkAhEKsbVNNI1sAAAAASUVORK5CYII=)}.rha-treeselector-node .label{color:#000;font-size:100%;font-weight:400;padding-left:2px}input[type=checkbox]{margin:4px 4px 0}#rha-login-modal-body{padding:0 30px;max-height:auto}.rha-login-modal div.modal-dialog{width:600px;padding:10px}.rha-login-modal .modal-header{padding-left:30px}.rha-login-modal .form-group{margin-bottom:5px}.form-horizontal .control-label{padding-top:1px}#rha-login-modal-buttons{margin-bottom:10px}.rha-search-spinner{padding:1px;background-image:url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjc3N1dXV9jY2OTk5Ly8vB8fHwUFBQAAAAAAAAAAACH5BAkKAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOw==);background-repeat:no-repeat;display:inline-block;width:32px;height:32px;margin-bottom:5px}.panel-group .panel-title>a:before{content:none}th{font-weight:400;color:#777}.details-table>tbody>tr>td,.details-table>tbody>tr>th{padding:8px 0;border-top:0}.rha-section-header{padding:0;border-bottom:solid 1px;border-color:grey}.rha-bold{font-weight:700}.rha-create-field{padding-bottom:8px}.rha-bottom-border{margin-bottom:14px;padding-bottom:7px;border-bottom:1px solid #ccc}.rha-create-case-section{padding:10px 0}.rha-col-no-padding{padding-left:0;padding-right:0}.rha-details-table-header{width:40%}.rha-side-padding{padding-left:15px;padding-right:15px}div#redhat-access-case .has-feedback .form-control-feedback{right:10px;top:0}div#redhat-access-case .form-group{margin-bottom:0}div#redhat-access-case .table>tbody>tr>th{vertical-align:middle}div#redhat-access-case .glyphicon-asterisk{color:#428bca}div#redhat-access-case a{cursor:pointer}div#redhat-access-case div .panel-body{overflow:auto!important}.rha-hidden{display:none}.container-offset{padding:0 15px}div#rha-case-details .table{margin-bottom:0}div.server-attach-header{font-weight:700;margin-bottom:5px}div#redhat-access-compact-list .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0;border-top:0}div#redhat-access-compact-list .list-group-item:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}div#redhat-access-compact-list .list-group-item{border-right:0}div#rha-case-search-result .detail-name{color:#999}div#rha-case-search-result .status{font-weight:700}div#rha-case-search-result .redhat{color:#7ab800}div#rha-case-search-result .customer{color:#c00}div#rha-case-search-result .closed{color:#666}div#rha-case-search label{font-weight:400;color:#999}div#rha-case-search-result .comment-tip{position:absolute;left:40px;bottom:0;display:block;width:17px;height:42px;clip:rect(21px,17px,42px,0);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAqCAIAAADasifDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCOTAyMTVBM0U3NDYxMUUyOUVGRThGOEIzMTM3MTIxRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCOTAyMTVBNEU3NDYxMUUyOUVGRThGOEIzMTM3MTIxRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI5MDIxNUExRTc0NjExRTI5RUZFOEY4QjMxMzcxMjFFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI5MDIxNUEyRTc0NjExRTI5RUZFOEY4QjMxMzcxMjFFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+J7WibQAAAR1JREFUeNqclEEKgzAQRXUQF4qIC93oAbz/ObyCG3Ej6koQEdFOaWmtJpOfDigk+Jm8mDynqqphGA64+Hsqy7JpmmVZHLgoDMM8z+u63vcdzfCTZVkcx9zNIsNVFAW/x3G0yHClaQqCfTM4GJ0HIBhdxggY3aeMYIqMEYyUszIY6RYggJHAqgOTMjowkn+FEoyMJ+UORsihvIBBmTMYEbl8XcFr07bttm2e5zmHTb3M4bITeHOiKAK78QqfDuH0uq7WDum6Dgd7OyRJkr7vrR3i+/40TdYO4VYI2I9DuBsCdnUIAqZwiBFM7RAZTO0QGUzrEAFMcogOzOAQJZjBIUows0PuYJBDLmCoQ85gqEM+YNYOmec5CIJ/HPIQYADsQlhEct808gAAAABJRU5ErkJggg==) no-repeat 0 0}div#rha-case-search-result .well{border:1px solid #ccc;background-color:#efefef}div#rha-case-search-result .comment-user{position:absolute;left:70px;bottom:-32px;padding-left:40px}div#rha-case-search-result .comment-user div{position:static;display:block}div#rha-case-search-result .avatar{position:absolute;left:0;top:1px;width:30px;height:30px;border:1px solid #ccc;overflow:hidden;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRDQxNkYwOEU3NDcxMUUyOUVGRThGOEIzMTM3MTIxRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRDQxNkYwOUU3NDcxMUUyOUVGRThGOEIzMTM3MTIxRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJENDE2RjA2RTc0NzExRTI5RUZFOEY4QjMxMzcxMjFFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJENDE2RjA3RTc0NzExRTI5RUZFOEY4QjMxMzcxMjFFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+q7O7dgAAAU9JREFUeNrUlq+zRUAUx++ap0iKQhEoBEVSJP+6QqAoAgJFIVAE7zvXG+/OXpZZV7gnCNb5zDnfPT+QeZ4fN5jwuMfu4v4cfjGOY1EUfd9P0yRJkqIomqaJosj2Imx9y7KM45h6Cajv+7Isc+rQtu07FIbAwzDEk5Ob5zlDnLquObld1zFOh2Hg5CKo76nfpaq4sxEY4r56EkKoD6qq+oAO72XObo1drvI0hqdlWZzxep63d6SqqmmanFxkCv897qU620wWk0HX9UtcIBzHoZJwXfcDfUHNLXDZk+x4Ti6TF3VKtYD6NPYU3uZiQmZZhufhrdq2vdmWNBehJUnSNM3JfgHdMAzQWVzgoihaBzZ69+S2huLYIK+y/HturpzzBmgQBKsmwpp+mqZXBiOyRK50neHe2fvqjOGe1xXzpwPiZe+VkwahF5XJl/1H/QowAARDrjf0ewDSAAAAAElFTkSuQmCC) no-repeat 0 0}div#rha-select-loading-indicator .bootstrap{height:34px}div#rha-select-loading-indicator .select2{height:26px}div#rha-case-search-result .hascomment{padding-bottom:40px}div#rha-case-search-result .hover:hover{background-color:#f5f5f5}div#rha-case-search .select2-search-choice-close{display:block!important;top:6px!important}div#rha-case-search .select2-chosen{margin-right:40px!important}.select2-disabled{background:#fff!important;color:#ccc}.pagination{margin-top:0!important;margin-bottom:0!important}div#rha-create-group-modal label{font-weight:400}div#rha-recommendation-section .not-pinned{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 0;height:16px;width:15px;display:block}div#rha-recommendation-section .pinned{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 0;height:16px;width:30px;display:block;position:absolute;top:0;left:-16px;overflow:hidden;clip:rect(0,30px,16px,15px)}div#rha-recommendation-section .hand-picked{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 -27px;color:#078700;font-variant:small-caps;letter-spacing:1px;margin:0 0 0 3px;padding:0 0 0 13px}.rha-search-spinner-sm{padding:1px;background-image:url(data:image/gif;base64,R0lGODlhCwALAPcAAP////////////////f39/X19fXz9fPz8/Px8+/z7/Hx8e/v7+3x7e3t7evr6+vp6+np6efn5+Xl5ePj4+Hh4d/h39/f393d3dvb29vZ29nZ2dfZ19fX19XX1dXV1dPT09PR08zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMXFxcPDw8HBwb/Bv8G/wb+/v729vbu/u7+9v7u7u7m5ube3t7W1tbOzs7Gxsa+vr62trautq6urq6mpqaenp6WlpaOjo6GjoaGhoZmZmZmZmZmZmZmZmZmZmZmZmZmZmZGRkY+Pj42NjYuLi4eLh4mJiYeHh4WFhYODg4GBgX5+fnx8fHp6enh4eHZ2dnJ2cnR0dHJycnBwcG5ubmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZl5eXlxcXFpaWlhYWFZaVlZYVlZWVlRUVFJSUlBQUE5OTkxMTEpKSkhKSEhISEZGRkRGREREREJCQkBAQD5APj4+Pjw8PDg8ODo6OjY6NjMzMzMzMzMzMzMzMzMzMzMzMzMzMywwLCwuLCwsLCoqKigoKCYmJiIiIiAgIB4eHhwcHBoaGhgYGBYWFhQUFBISEhQSFAwMDAoKCgAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBwCdACwAAAAACwALAAAIcAA7dWpwYEKdDwITdqqRJ4SmFwQICFRAo8EfP5VeKOgkccEbPFm66IgAYMECAA4yeHECQCCBAwAAXAnkBYPCAyYdYLiiI6ECCjEBSImTYwWBCBY02CQAMYqSGyxsEDgpMESUBkoKhLBwM6KNAhwFBgQAIfkEBQcAnQAsAAAAAAoACwAACF0AOwkU6MaCggkDO8X5IJCPkoSB4CSyUykCgE5nOjGJsiJNjQKdDlDoMmTgxU5K3DxxkDChEBgDCTQQOKRLJxCdGiiAMFOgEBsvOIRIiAEIgRydLkRICACAB4EXAwIAIfkEBQcAnQAsAAAAAAsACwAACGMAO3VC0AkCFgkCE3aCcSZDJA9phiQMUWCPlzlpHFnIQaGTFjVCmICo4iUPIw8CpdhIyMVLg04+tgxRoBCAwh4qFCYk0KNJiAudCuj80OmGCgkQIOikQAPABQALXiok0AmowoAAIfkEBQcAnQAsAAAAAAsACwAACGcAOwkUKEVgg4ECp3T6g8GLkIEaAMSpcqaLoggvHHRqIsVHDw1RqrzpFEEgExgDryRqUeGGkhoEBs6opAkRyRkfBkp44SIEABU5OBzsREBBTAAWOnmgoADhQAgcOi1wKhAAAKlXBwYEACH5BAUHAJ0ALAAAAAALAAoAAAhhADsJ7DThjEAJAzvR8BJCU4g/QwaqOJAIzSM7mDgMTNNGSZQQXDrVcdQJQIQqOQZuqdSJwwsrQgAMHJKpk6YGFzpZGPiGpEAPCTvhePRHwcAIEQQSmHBFY6cFFAgMLCAwIAAh+QQFBwCdACwAAAAACwALAAAIawA7CezkgIcDAgcGdsKgYwIWCVReDJwAYEiPIUrsFBjI4kYIFhF4CFHjpkGnBSw4DBRyRkMECRgkKLygBtKZAg0oLBgIxU0nDQQlHARwxUugORFOCmxAQAskDkNkCoQAoBOBQEsUao1gQWBAACH5BAUHAJ0ALAAAAAALAAsAAAhhADsJFBhCwcCDKhwo6TRExcAGnXKouPGDS6cQAilowPCBAAsdZywKtOBgYI4tAhcsUEBgoAQugbocHDjECwcLBxfA6MTlDxqIKUNI+hPogY+DB0JokuEnyMxOGwJxSDAwIAAh+QQFBwCdACwAAAAACwALAAAIYAA7CRToYaBBCBo62WjAI4TBAiEwqLDRpJOHAp0gRGggQeANKlAOCHSgYCCMJxYMGnQApU1FgQQG8sCSUmCBG14+KGGix4tBRoH+vPmjUsgiFp1C2Olh8AIFCnpSduwUEAAh+QQFBwCdACwAAAAACwALAAAIXwA7CRToYKDBBQUxHFBBwWCnCBAkhLgB4ILABRc7cQgxBIjDgSF4GCQAYCCAIVuEACAABcvAGUoeVsgD6cwVCj2GwLHSicISCn/svPFipxOMAwMBgZjkYc1HBp3OOAwIACH5BAUHAJ0ALAAAAQALAAoAAAhgADsJFLigEwcIAxMqoBCCgEAAnRocEOjgAg8YnSKECEFhoIcXCP9owoRjIAEbSnCAWFEnoQohAhvI+VNlSKcaPLQoESjEQSc0XqykIRBiIA88GgJ1usIioQSBWwQqEBgQACH5BAUHAJ0ALAAAAQALAAoAAAheADsJHNjJwQKBDmx0KoBQQicInSYo6lODgMAIHzQMzKNmYAQMAul0SqSCoIocHzqFOMNkYAgbEA666cQECIQXNZb4eNijk50uUIZ06ZRSIAw3FtpcWEKw08FOUZoGBAAh+QQJBwCdACwAAAAACwALAAAIYAA7dYrgQKDBg0MCHVxIIdIWAAYXdIKgx46XMxAHFuzEYY0XgwogCPQySY4HgwAkYLhwgcNHgxpWSOzUpdMQGgRChMhRo9MBGwe8SBnSYwgACwZDcOm0pVOPDwsFDlkYEAA7);background-repeat:no-repeat;display:inline-block;width:11px;height:11px}.col-fluid{position:relative;min-height:1px;overflow:auto}.rha-navsidebar{display:block}.rha-navsidebar.showMe{width:15px;padding-left:0}.hideable-side-bar{display:block}.hideable-side-bar.showMe{display:none}.solutions{width:15px;padding-right:0}.solutions.showMe{width:50%;display:block}.left-side-glyphicon{position:absolute;top:50%;left:100%;margin-top:-7px;margin-left:-15px}.left-side-glyphicon.showMe{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}#diagnoseButton{float:right}.panel-body{overflow:auto}.right-side-glyphicon{top:50%;position:absolute;right:100%;margin-top:-7px;margin-right:-15px;display:block}.right-side-glyphicon.showMe{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.fileList{overflow:auto;padding-right:0}.no-line-wrap{word-wrap:normal;white-space:pre;overflow:auto}.resizeable-solution-view{display:none;overflow:auto;padding:0 15px}.btn-group,.resizeable-solution-view.showMe{display:block}.btn-group.hideMe{display:none}.dropdown-toggle{border-radius:3px!important;float:none!important}.machines-spinner{display:none}.machines-spinner.showMe{display:block}.tabs-spinner{display:none}.tabs-spinner.showMe{display:inline-block}.rha-search-spinner{margin-bottom:0!important}.file-list-title{display:none}.file-list-title.showMe{display:block}.rha-logsinstructionpane{padding-left:20px;padding-right:20px} \ No newline at end of file +.margin-0{margin:0}.margin-s{margin:.33em}.margin-m{margin:.66em}.margin-l{margin:1em}.margin-t-0{margin-top:0}.margin-r-0{margin-right:0}.margin-b-0{margin-bottom:0}.margin-l-0{margin-left:0}.margin-t-s{margin-top:.33em}.margin-b-s{margin-bottom:.33em}.margin-r-s{margin-right:.33em}.margin-l-s{margin-left:.33em}.margin-t-m{margin-top:.66em}.margin-b-m{margin-bottom:.66em}.margin-r-m{margin-right:.66em}.margin-l-m{margin-left:.66em}.margin-t-l{margin-top:1em}.margin-b-l{margin-bottom:1em}.margin-r-l{margin-right:1em}.margin-l-l{margin-left:1em}.pad-0{padding:0}.pad-s{padding:.33em}.pad-m{padding:.66em}.pad-l{padding:1em}.pad-t-0{padding-top:0}.pad-r-0{padding-right:0}.pad-b-0{padding-bottom:0}.pad-l-0{padding-left:0}.pad-t-s{padding-top:.33em}.pad-b-s{padding-bottom:.33em}.pad-r-s{padding-right:.33em}.pad-l-s{padding-left:.33em}.pad-t-m{padding-top:.66em}.pad-b-m{padding-bottom:.66em}.pad-r-m{padding-right:.66em}.pad-l-m{padding-left:.66em}.pad-t-l{padding-top:1em}.pad-r-l{padding-right:1em}.pad-b-l{padding-bottom:1em}.pad-l-l{padding-left:1em}.pad-s-x{padding-left:.33em;padding-right:.33em}.pad-m-x{padding-left:.66em;padding-right:.66em}.pad-l-x{padding-left:1em;padding-right:1em}.pad-s-y{padding-top:.33em;padding-bottom:.33em}.pad-m-y{padding-top:.66em;padding-bottom:.5em}.pad-l-y{padding-top:1em;padding-bottom:1em}@media (min-width:992px){.portal-content-area #main.container{width:95%!important}}li.rha-treeselector-node{list-style-type:none}.rha-treeselector-node .icon{display:block;float:left;width:16px;height:16px}.rha-treeselector-node .expanded{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAOw4AAA7DAZBCL/sAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAJBJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX9EUgsSIwVgNQOdjxcBwghswP0MFLAAThLHxYbA6mBfA/oIJoinEhmGaUQwgRzPcABAGeYMYDFMPwygc0vF/BgDd66LkDQj2XgAAAABJRU5ErkJggg==)}.rha-treeselector-node .collapsed{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAYAAAAmL5yKAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAIhJREFUOE9j+P//P0UYqyApGIVzuN3kPzJGlsOF4Yy1xbpAChVAxVA0oGMwAbbt0TwEvtT8/8GuCjBGdxVYLVYDiAS4DUB2AR48PUoe0wCwX7EoxobRw4U6BszPUAEHHDEYrBbdALC/sCjGhrGGAUgQ7DQsGtAxVgNAGOQ0YjBMPQyjcEjH/xkAhEKsbVNNI1sAAAAASUVORK5CYII=)}.rha-treeselector-node .label{color:#000;font-size:100%;font-weight:400;padding-left:2px}input[type=checkbox]{margin:4px 4px 0}#rha-login-modal-body{padding:0 30px;max-height:auto}.rha-login-modal div.modal-dialog{width:600px;padding:10px}.rha-login-modal .modal-header{padding-left:30px}.rha-login-modal .form-group{margin-bottom:5px}.form-horizontal .control-label{padding-top:1px}#rha-login-modal-buttons{margin-bottom:10px}.rha-search-spinner{padding:1px;background-image:url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjc3N1dXV9jY2OTk5Ly8vB8fHwUFBQAAAAAAAAAAACH5BAkKAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOw==);background-repeat:no-repeat;display:inline-block;width:32px;height:32px;margin-bottom:5px}.panel-group .panel-title>a:before{content:none}th{font-weight:400;color:#777}.details-table>tbody>tr>td,.details-table>tbody>tr>th{padding:8px 0;border-top:0}.rha-section-header{padding:0;border-bottom:solid 1px;border-color:grey}.rha-bold{font-weight:700}.rha-create-field{padding-bottom:8px}.rha-bottom-border{margin-bottom:14px;padding-bottom:7px;border-bottom:1px solid #ccc}.rha-create-case-section{padding:10px 0}.rha-col-no-padding{padding-left:0;padding-right:0}.rha-details-table-header{width:40%}.rha-side-padding{padding-left:15px;padding-right:15px}div#redhat-access-case .has-feedback .form-control-feedback{right:10px;top:0}div#redhat-access-case .form-group{margin-bottom:0}div#redhat-access-case .table>tbody>tr>th{vertical-align:middle}div#redhat-access-case .glyphicon-asterisk{color:#428bca}div#redhat-access-case a{cursor:pointer}div#redhat-access-case div .panel-body{overflow:auto!important}.rha-hidden{display:none}.container-offset{padding:0 15px}div#rha-case-details .table{margin-bottom:0}div.server-attach-header{font-weight:700;margin-bottom:5px}div#redhat-access-compact-list .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0;border-top:0}div#redhat-access-compact-list .list-group-item:last-child{border-bottom-right-radius:0;border-bottom-left-radius:0}div#redhat-access-compact-list .list-group-item{border-right:0}div#rha-case-search-result .detail-name{color:#999}div#rha-case-search-result .status{font-weight:700}div#rha-case-search-result .redhat{color:#7ab800}div#rha-case-search-result .customer{color:#c00}div#rha-case-search-result .closed{color:#666}div#rha-case-search label{font-weight:400;color:#999}div#rha-case-search-result .comment-tip{position:absolute;left:40px;bottom:0;display:block;width:17px;height:42px;clip:rect(21px,17px,42px,0);background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAqCAIAAADasifDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCOTAyMTVBM0U3NDYxMUUyOUVGRThGOEIzMTM3MTIxRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCOTAyMTVBNEU3NDYxMUUyOUVGRThGOEIzMTM3MTIxRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI5MDIxNUExRTc0NjExRTI5RUZFOEY4QjMxMzcxMjFFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI5MDIxNUEyRTc0NjExRTI5RUZFOEY4QjMxMzcxMjFFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+J7WibQAAAR1JREFUeNqclEEKgzAQRXUQF4qIC93oAbz/ObyCG3Ej6koQEdFOaWmtJpOfDigk+Jm8mDynqqphGA64+Hsqy7JpmmVZHLgoDMM8z+u63vcdzfCTZVkcx9zNIsNVFAW/x3G0yHClaQqCfTM4GJ0HIBhdxggY3aeMYIqMEYyUszIY6RYggJHAqgOTMjowkn+FEoyMJ+UORsihvIBBmTMYEbl8XcFr07bttm2e5zmHTb3M4bITeHOiKAK78QqfDuH0uq7WDum6Dgd7OyRJkr7vrR3i+/40TdYO4VYI2I9DuBsCdnUIAqZwiBFM7RAZTO0QGUzrEAFMcogOzOAQJZjBIUows0PuYJBDLmCoQ85gqEM+YNYOmec5CIJ/HPIQYADsQlhEct808gAAAABJRU5ErkJggg==) no-repeat 0 0}div#rha-case-search-result .well{border:1px solid #ccc;background-color:#efefef}div#rha-case-search-result .comment-user{position:absolute;left:70px;bottom:-32px;padding-left:40px}div#rha-case-search-result .comment-user div{position:static;display:block}div#rha-case-search-result .avatar{position:absolute;left:0;top:1px;width:30px;height:30px;border:1px solid #ccc;overflow:hidden;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAdCAIAAADZ8fBYAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNS4xIE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCRDQxNkYwOEU3NDcxMUUyOUVGRThGOEIzMTM3MTIxRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCRDQxNkYwOUU3NDcxMUUyOUVGRThGOEIzMTM3MTIxRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkJENDE2RjA2RTc0NzExRTI5RUZFOEY4QjMxMzcxMjFFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkJENDE2RjA3RTc0NzExRTI5RUZFOEY4QjMxMzcxMjFFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+q7O7dgAAAU9JREFUeNrUlq+zRUAUx++ap0iKQhEoBEVSJP+6QqAoAgJFIVAE7zvXG+/OXpZZV7gnCNb5zDnfPT+QeZ4fN5jwuMfu4v4cfjGOY1EUfd9P0yRJkqIomqaJosj2Imx9y7KM45h6Cajv+7Isc+rQtu07FIbAwzDEk5Ob5zlDnLquObld1zFOh2Hg5CKo76nfpaq4sxEY4r56EkKoD6qq+oAO72XObo1drvI0hqdlWZzxep63d6SqqmmanFxkCv897qU620wWk0HX9UtcIBzHoZJwXfcDfUHNLXDZk+x4Ti6TF3VKtYD6NPYU3uZiQmZZhufhrdq2vdmWNBehJUnSNM3JfgHdMAzQWVzgoihaBzZ69+S2huLYIK+y/HturpzzBmgQBKsmwpp+mqZXBiOyRK50neHe2fvqjOGe1xXzpwPiZe+VkwahF5XJl/1H/QowAARDrjf0ewDSAAAAAElFTkSuQmCC) no-repeat 0 0}div#rha-select-loading-indicator .bootstrap{height:34px}div#rha-select-loading-indicator .select2{height:26px}div#rha-case-search-result .hascomment{padding-bottom:40px}div#rha-case-search-result .hover:hover{background-color:#f5f5f5}div#rha-case-search .select2-search-choice-close{display:block!important;top:6px!important}div#rha-case-search .select2-chosen{margin-right:40px!important}.select2-disabled{background:#fff!important;color:#ccc}.pagination{margin-top:0!important;margin-bottom:0!important}div#rha-create-group-modal label{font-weight:400}div#rha-recommendation-section .not-pinned{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 0;height:16px;width:15px;display:block}div#rha-recommendation-section .pinned{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 0;height:16px;width:30px;display:block;position:absolute;top:0;left:-16px;overflow:hidden;clip:rect(0,30px,16px,15px)}div#rha-recommendation-section .hand-picked{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAmCAYAAAA2h+4OAAAFiElEQVR42u3YfUhVZwDH8Ydu3nQTzN6zVTPkXu/1XUxLI5+aDJJp083JzbVlS9AYQdtAcr6l1/d0Wl4t09QGrrHBsqK53dom9k9CbMYO9Fcw5goGltAfEjzXs99zOA65q92Xc+na5Xng48tzz/WIX57znCORZVlRWloaXFJSsuHgwYPr8/PzV/A5TzmNUNgGWyHE6TWXP8va1KSFx797QUGBSzjuhVE+lJWVvX706NFMhKFFRUU0JydnN6U0wtswX6emVkxkZv4+kZ4ujScmSvbIyKn60ND9PgoTBuXwHdyDcegAo4/ChMBH0AmFnoTBMWlwDCwQ7sG5s6BZfe9aPkeKi4vX8yBHjhyhWC00Ly+P7tmzh6akpNDExMQwT8MMx8YemNi5U+LGk5Kkn6KiJPuaNdLY8uVSGSEmjWHKG5qb2ZmeHtY3MMAGL15k/YODrOfcOdZy6hTjgTSGWQUSyItcAx24ev+nYIMP1K/tsNmN83bAGHymfv0QokhhYWGCxWKh+Exzc3MpH8nJyTQhIYHGxcVFexrmstH4zS9xcdLPJpN0c+tWyR4eLn1PiPQtNBJSoyFMaXNbG7swPMx6z55lTS0trBFaEeRcfz8bQqQvTp9W4mgI0wTyMxyC/3uvAUYgaNFcEXS6OGcC3AH9ornjcIVkZ2en7Nu3j2ZlZdH09HSalJRE4+PjaUxMDDWbzTGehrkSETF2c+NGyb5qlTSm10uXEeRLOA/NhJz3MkwYVgoPoLh2/bpDVsejx4/lkUuXHFhByupRV877Xoa5+ZwwZ1yE2Q9VTnOr4QcX5zwMfU5zG2CaZGRkmFJTU3kQZZXExsbyIDQ6Oprb7GmYkeDg3jGdThpFiBHogy44BccIOexlmHK+Gs729SmXLR7k9uTkfFtHB5t59Ej+68GD+a7ubuW1HqwmHH/LyzCDzwnziYswu6DTae5NuODinNlwxWnuPbhFECQEQXbhsqWsEpPJRI1GIzUYDDvwWedpmDZCDAhyZwghuqENrFBNyMTbhAR7GWbytM3GeJw+rJjfpqbmexGprqGBzczMyI+xalrb21lHVxez9fbySxyPs8mLMFEw6xTlHoS6scf0wnFIgxyYhGQ3zmuHNsiCD+Ep7FZeRJRXECUeUXZjlShhYJ23d2WVhJhbCfkK7vIotVBEyBsa7sr+RhTlMoU/OgAubX9OT8/Pzc3J2HccfK6ptZW1d3Yy7EU8zA4vwnCRcAYmoBXCgLgRZgWUgA3qIMbN84ZAFYzBEGzn8/85EHEMPA4YvAnjPD4m5ASi2PBAk6khzL0W/sdubGT1WCWc/cYNZZ/pHxhwKHOqZsRRV4z5pX+OWQwrJwh7TAbs8EUYDD28A1qeY7r55l9bV8dqVNhTHKNXrzo+r65W5wDqEQ/Hc8pl+GX1zEncALwGFF7VGkYdRrDASi/DxAKrqq1llTU1ih/tdmXFYMN3KHMq7Ds8Sg3/GQEXBrfLy7DvpMEWH4VZBm+BWcNzTEOd1coqKiufq/rkSR5lAEhAhlHjrIYkH4XhIwKyNIThOviKqKiqYuUVFf86gSi19fULUbYHdBg1TgIE+SgMH3tBryEMZ4FxYCet1oU95T6UA+ECPgyeb0JhnQ/DhMMWjWEW6CEZ1jrNB34YNc5KH4bhY53mMK4Ffhj8h3mZj8PoRBiPwmjn7hBh3AvjDyKMG2EEEUYQYUQYQYQRYQQRRhBhXiYkeMgTIuRSCvMuJIowSyuMEZ7AfQgTYZZGGD38CjLMglGEWRphekCGp7BXXMpefBgdmJ2iFIKsKhObv3/CHIJZ2AkEItXvZWgXd2X+C/MHyPAEMhftK6OgE2H8F+aAGkVe5C6EiucY/2/+kXAbZHgIm8QDpn853x5bIVU8+fvfP4LehDZ9XyuiAAAAAElFTkSuQmCC) no-repeat 0 -27px;color:#078700;font-variant:small-caps;letter-spacing:1px;margin:0 0 0 3px;padding:0 0 0 13px}.clickable{cursor:pointer}.rha-comment-link{margin-top:10px}.rha-search-spinner-sm{padding:1px;background-image:url(data:image/gif;base64,R0lGODlhCwALAPcAAP////////////////f39/X19fXz9fPz8/Px8+/z7/Hx8e/v7+3x7e3t7evr6+vp6+np6efn5+Xl5ePj4+Hh4d/h39/f393d3dvb29vZ29nZ2dfZ19fX19XX1dXV1dPT09PR08zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMXFxcPDw8HBwb/Bv8G/wb+/v729vbu/u7+9v7u7u7m5ube3t7W1tbOzs7Gxsa+vr62trautq6urq6mpqaenp6WlpaOjo6GjoaGhoZmZmZmZmZmZmZmZmZmZmZmZmZmZmZGRkY+Pj42NjYuLi4eLh4mJiYeHh4WFhYODg4GBgX5+fnx8fHp6enh4eHZ2dnJ2cnR0dHJycnBwcG5ubmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZl5eXlxcXFpaWlhYWFZaVlZYVlZWVlRUVFJSUlBQUE5OTkxMTEpKSkhKSEhISEZGRkRGREREREJCQkBAQD5APj4+Pjw8PDg8ODo6OjY6NjMzMzMzMzMzMzMzMzMzMzMzMzMzMywwLCwuLCwsLCoqKigoKCYmJiIiIiAgIB4eHhwcHBoaGhgYGBYWFhQUFBISEhQSFAwMDAoKCgAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBwCdACwAAAAACwALAAAIcAA7dWpwYEKdDwITdqqRJ4SmFwQICFRAo8EfP5VeKOgkccEbPFm66IgAYMECAA4yeHECQCCBAwAAXAnkBYPCAyYdYLiiI6ECCjEBSImTYwWBCBY02CQAMYqSGyxsEDgpMESUBkoKhLBwM6KNAhwFBgQAIfkEBQcAnQAsAAAAAAoACwAACF0AOwkU6MaCggkDO8X5IJCPkoSB4CSyUykCgE5nOjGJsiJNjQKdDlDoMmTgxU5K3DxxkDChEBgDCTQQOKRLJxCdGiiAMFOgEBsvOIRIiAEIgRydLkRICACAB4EXAwIAIfkEBQcAnQAsAAAAAAsACwAACGMAO3VC0AkCFgkCE3aCcSZDJA9phiQMUWCPlzlpHFnIQaGTFjVCmICo4iUPIw8CpdhIyMVLg04+tgxRoBCAwh4qFCYk0KNJiAudCuj80OmGCgkQIOikQAPABQALXiok0AmowoAAIfkEBQcAnQAsAAAAAAsACwAACGcAOwkUKEVgg4ECp3T6g8GLkIEaAMSpcqaLoggvHHRqIsVHDw1RqrzpFEEgExgDryRqUeGGkhoEBs6opAkRyRkfBkp44SIEABU5OBzsREBBTAAWOnmgoADhQAgcOi1wKhAAAKlXBwYEACH5BAUHAJ0ALAAAAAALAAoAAAhhADsJ7DThjEAJAzvR8BJCU4g/QwaqOJAIzSM7mDgMTNNGSZQQXDrVcdQJQIQqOQZuqdSJwwsrQgAMHJKpk6YGFzpZGPiGpEAPCTvhePRHwcAIEQQSmHBFY6cFFAgMLCAwIAAh+QQFBwCdACwAAAAACwALAAAIawA7CezkgIcDAgcGdsKgYwIWCVReDJwAYEiPIUrsFBjI4kYIFhF4CFHjpkGnBSw4DBRyRkMECRgkKLygBtKZAg0oLBgIxU0nDQQlHARwxUugORFOCmxAQAskDkNkCoQAoBOBQEsUao1gQWBAACH5BAUHAJ0ALAAAAAALAAsAAAhhADsJFBhCwcCDKhwo6TRExcAGnXKouPGDS6cQAilowPCBAAsdZywKtOBgYI4tAhcsUEBgoAQugbocHDjECwcLBxfA6MTlDxqIKUNI+hPogY+DB0JokuEnyMxOGwJxSDAwIAAh+QQFBwCdACwAAAAACwALAAAIYAA7CRToYaBBCBo62WjAI4TBAiEwqLDRpJOHAp0gRGggQeANKlAOCHSgYCCMJxYMGnQApU1FgQQG8sCSUmCBG14+KGGix4tBRoH+vPmjUsgiFp1C2Olh8AIFCnpSduwUEAAh+QQFBwCdACwAAAAACwALAAAIXwA7CRToYKDBBQUxHFBBwWCnCBAkhLgB4ILABRc7cQgxBIjDgSF4GCQAYCCAIVuEACAABcvAGUoeVsgD6cwVCj2GwLHSicISCn/svPFipxOMAwMBgZjkYc1HBp3OOAwIACH5BAUHAJ0ALAAAAQALAAoAAAhgADsJFLigEwcIAxMqoBCCgEAAnRocEOjgAg8YnSKECEFhoIcXCP9owoRjIAEbSnCAWFEnoQohAhvI+VNlSKcaPLQoESjEQSc0XqykIRBiIA88GgJ1usIioQSBWwQqEBgQACH5BAUHAJ0ALAAAAQALAAoAAAheADsJHNjJwQKBDmx0KoBQQicInSYo6lODgMAIHzQMzKNmYAQMAul0SqSCoIocHzqFOMNkYAgbEA666cQECIQXNZb4eNijk50uUIZ06ZRSIAw3FtpcWEKw08FOUZoGBAAh+QQJBwCdACwAAAAACwALAAAIYAA7dYrgQKDBg0MCHVxIIdIWAAYXdIKgx46XMxAHFuzEYY0XgwogCPQySY4HgwAkYLhwgcNHgxpWSOzUpdMQGgRChMhRo9MBGwe8SBnSYwgACwZDcOm0pVOPDwsFDlkYEAA7);background-repeat:no-repeat;display:inline-block;width:11px;height:11px}.pcmTextBlock{color:#222;font-family:"Courier New",Courier,"DejaVu Sans Mono",monospace;font-size:1em;word-wrap:break-word;white-space:-moz-pre-wrap;white-space:pre-wrap;word-break:break-all}.amc{background:#f32827;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;color:#fff;display:inline-block;font-size:46.15%;line-height:100%;margin:0 0 0 4px;padding:4px 7px;vertical-align:5px}.versionSunsetMessage{margin-top:1em;white-space:normal;font-size:.846em}.recommendations-inner{position:relative;padding:10px 0}.snippet{word-wrap:break-word}.recommendations{display:none;list-style:none;margin:0!important;padding:0}.recommendations li .meta{padding-top:.25em;font-size:.923em}.recommendations_products,.recommendations_tags{display:inline-block;*display:inline;margin:0 4px .5em 0;padding:0 8px;color:#666;background:#fff;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;font-size:.917em;zoom:1;border:1px solid #dedede}.recommendations li .icon-solution{position:relative;float:left;margin-left:-20px}.private{background-color:#fde5e5;color:#c92b22;display:inline-block}.group-select-help{position:absolute;right:0;padding-left:5px;padding-top:9px}.group-select{margin-right:20px}.progressBarWrap{position:relative;display:inline-block;*display:inline;margin:0 5px;width:100px;height:1.2em;background:#fff;vertical-align:middle;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;overflow:hidden}.progressBar{position:absolute;left:0;top:0;height:100%;width:0;background:#83bce7}.progressCount{position:absolute;left:0;top:0;width:100%;text-align:center;color:#333;line-height:1.2em}.commentReply{position:absolute;right:20px;bottom:0;left:auto;top:auto;color:#4e9fdd;padding:3px 8px;border-top-left-radius:5px;border-bottom-right-radius:5px;border:1px solid #ccc;background:#fff}.commentReply:hover{text-decoration:none;color:#06c;background:#fff}.center{text-align:center}.fun{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA+CAYAAABgFuiwAAAE9ElEQVR42u1aWUgkVxR10hqnyQwaJz3OJJ3FxAmZCYHoR9B8JAQ0SEAi8TMIih+iRgwEJP6IgmgiiCJ+TEACOm5oFnEXbQPKKO6iqO24YFBxxcZl3KJ1c2/zhGe9LqeEFsZHXTiCvqpT99Rd3q2yPABAANoNhBfiFuJ1hC/CjDDRGn+shunm0WnE48047iP8EbcRnmxNMLUgE3Piflpa2letra0Zdru9fnR09ElNTc0PYWFhn+CaHzl7sTD9PC8Q5MmOexATE/NdaWlpYX9//z9o9UVFRRlBQUFf4NpbJFpTGLuIf0dHR8b6+rodXJiiKKeTk5N/43HvIV5DvCIQuo+HnH2/srKyYGFh4V9XPKenp0pvb28HHvcpywaTOmVM5MzY2Fgl6DCHw7FQUVHxPXOKM7fxeJKorq6uRj08q6urK/n5+ckkTp2/PhjeX4CZXqfYHfe6Ap67tbW1j4GZXnEUOT4tveLj4z/b3993gMp2dnYA0wk2Nzfh4OBAIGPpdIc5I/CwtAOMIDQ2NoLNZoPFxcUX8XhHRkZ+jdfeVfPMzc1BZ2cntLS0wNTUlMDT09Njw/OtZw3lNuZpkSp3nQ7MzMycw9bWllArSUlJn7PUEXiOjo6gpKQEMjMzz6G7u/sinjsNDQ1l/Prx8TE0NTVBWVkZFBYWQmJiIoSEhACmn1Bz0dHR3yDHqyTsLhZnD3C2sbHBxAgQIodO/MxqROBpa2sjIQKysrK4yAk8AePj42NqHhJUXFzsPBcjCr6+vmAymShKwBndyF/Pas16eHi4wy/Oz89rCSPR6jT6k+0vAk9eXh4TI4DSSYvn0XM0fi05ORkSEhKckYqKigI/Pz/qeE6kpqaq07Gd7XUeb5+cnBzzi7Ozs5rC1tbWgDPK+za21wg82dnZmsLq6uq0eD7+D41fCw4OhsDAQLBYLCTmHGJjY4G3kZGRp/j3N0nYveXl5VF+EX/XFLa9vQ2cUUPIpfpywUM1oSlseHhYi+cDvM4zfi08PJwJEUE1zFt1dfVj1og8fAcGBn5XFb2rqLnsaDk5OaxYBR7qqK6iRs5Qw9DiuYc19Re/NjExAWazWRAVGhoKmCXAW0pKCu2LNz3oB4458YCmFkeRoxaLTYGcdHZLleN2FvYbWjy0VZSXl0Nubi41AGr7xH0Rzy0cl35S8+BIBhEREeDj4wMBAQHOetvd3QXeaELB8z908rBxxoK5+QQuaTQ2sZnQnTwmxLuYmvWX5aHxi+qUH6k8EdaVlZVBvSQ0NtH4xM1n7uShlHyE5TCpl4fGLxrDyAf1EExk1sHBwVIQjd8sn9PYxJwRpnM38pgRD9vb2+tA22hfPcQS+I2J8hamey5yb1RVVcX29fWVLC0tDZMDmMvr2Dxs5Eh6evqXZ5O0xmPLpXm0SNhNeqegoODH5ubmP6anp+0kBCcgx9DQUD/Nk3Fxcd9SwzmLlCiMgdXKTXZRf4SVPfNYWDv24h82NUw3j3CW9rOdP4vKQ8RHVIesrXsLPEyYIiMMYddOmCKnGcIMYYawqxZG7wkkhCHs+gnDJ1BFQkgsDF8IKRLCEGYIe2mE4SO7IiEkFoYvLxUJYQi7fsLwf1qKhJBYGL5ZVSSEIcwQ9tIIww9OFAkhsTD8pEKREBIL29vbUySEIcwQZgi7amH4iYIiISQWht/eKhLCEGYIM4RdMf4HVwQWkkTiTYMAAAAASUVORK5CYII=) no-repeat;display:block;height:18px;margin:0 0 0 -9px;position:absolute;bottom:-9px;left:50%;width:18px}.no-fun{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAA+CAYAAABgFuiwAAAE9ElEQVR42u1aWUgkVxR10hqnyQwaJz3OJJ3FxAmZCYHoR9B8JAQ0SEAi8TMIih+iRgwEJP6IgmgiiCJ+TEACOm5oFnEXbQPKKO6iqO24YFBxxcZl3KJ1c2/zhGe9LqeEFsZHXTiCvqpT99Rd3q2yPABAANoNhBfiFuJ1hC/CjDDRGn+shunm0WnE48047iP8EbcRnmxNMLUgE3Piflpa2letra0Zdru9fnR09ElNTc0PYWFhn+CaHzl7sTD9PC8Q5MmOexATE/NdaWlpYX9//z9o9UVFRRlBQUFf4NpbJFpTGLuIf0dHR8b6+rodXJiiKKeTk5N/43HvIV5DvCIQuo+HnH2/srKyYGFh4V9XPKenp0pvb28HHvcpywaTOmVM5MzY2Fgl6DCHw7FQUVHxPXOKM7fxeJKorq6uRj08q6urK/n5+ckkTp2/PhjeX4CZXqfYHfe6Ap67tbW1j4GZXnEUOT4tveLj4z/b3993gMp2dnYA0wk2Nzfh4OBAIGPpdIc5I/CwtAOMIDQ2NoLNZoPFxcUX8XhHRkZ+jdfeVfPMzc1BZ2cntLS0wNTUlMDT09Njw/OtZw3lNuZpkSp3nQ7MzMycw9bWllArSUlJn7PUEXiOjo6gpKQEMjMzz6G7u/sinjsNDQ1l/Prx8TE0NTVBWVkZFBYWQmJiIoSEhACmn1Bz0dHR3yDHqyTsLhZnD3C2sbHBxAgQIodO/MxqROBpa2sjIQKysrK4yAk8AePj42NqHhJUXFzsPBcjCr6+vmAymShKwBndyF/Pas16eHi4wy/Oz89rCSPR6jT6k+0vAk9eXh4TI4DSSYvn0XM0fi05ORkSEhKckYqKigI/Pz/qeE6kpqaq07Gd7XUeb5+cnBzzi7Ozs5rC1tbWgDPK+za21wg82dnZmsLq6uq0eD7+D41fCw4OhsDAQLBYLCTmHGJjY4G3kZGRp/j3N0nYveXl5VF+EX/XFLa9vQ2cUUPIpfpywUM1oSlseHhYi+cDvM4zfi08PJwJEUE1zFt1dfVj1og8fAcGBn5XFb2rqLnsaDk5OaxYBR7qqK6iRs5Qw9DiuYc19Re/NjExAWazWRAVGhoKmCXAW0pKCu2LNz3oB4458YCmFkeRoxaLTYGcdHZLleN2FvYbWjy0VZSXl0Nubi41AGr7xH0Rzy0cl35S8+BIBhEREeDj4wMBAQHOetvd3QXeaELB8z908rBxxoK5+QQuaTQ2sZnQnTwmxLuYmvWX5aHxi+qUH6k8EdaVlZVBvSQ0NtH4xM1n7uShlHyE5TCpl4fGLxrDyAf1EExk1sHBwVIQjd8sn9PYxJwRpnM38pgRD9vb2+tA22hfPcQS+I2J8hamey5yb1RVVcX29fWVLC0tDZMDmMvr2Dxs5Eh6evqXZ5O0xmPLpXm0SNhNeqegoODH5ubmP6anp+0kBCcgx9DQUD/Nk3Fxcd9SwzmLlCiMgdXKTXZRf4SVPfNYWDv24h82NUw3j3CW9rOdP4vKQ8RHVIesrXsLPEyYIiMMYddOmCKnGcIMYYawqxZG7wkkhCHs+gnDJ1BFQkgsDF8IKRLCEGYIe2mE4SO7IiEkFoYvLxUJYQi7fsLwf1qKhJBYGL5ZVSSEIcwQ9tIIww9OFAkhsTD8pEKREBIL29vbUySEIcwQZgi7amH4iYIiISQWht/eKhLCEGYIM4RdMf4HVwQWkkTiTYMAAAAASUVORK5CYII=) no-repeat;display:block;height:18px;margin:0 0 0 -9px;position:absolute;bottom:-9px;left:50%;width:18px;background-position:-36px 0}.full-border{padding-bottom:10px;border:1px solid #ccc}.partial-border{border-left:1px solid #ccc;border-bottom:1px solid #ccc;border-right:1px solid #ccc}.divider{width:10px;height:auto;display:inline-block}.col-fluid{position:relative;min-height:1px;overflow:auto}.rha-navsidebar{display:block}.rha-navsidebar.showMe{width:15px;padding-left:0}.hideable-side-bar{display:block}.hideable-side-bar.showMe{display:none}.solutions{width:15px;padding-right:0}.solutions.showMe{width:50%;display:block}.left-side-glyphicon{position:absolute;top:50%;left:100%;margin-top:-7px;margin-left:-15px}.left-side-glyphicon.showMe{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}#diagnoseButton{float:right}.panel-body{overflow:auto}.right-side-glyphicon{top:50%;position:absolute;right:100%;margin-top:-7px;margin-right:-15px;display:block}.right-side-glyphicon.showMe{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.fileList{overflow:auto;padding-right:0}.no-line-wrap{word-wrap:normal;white-space:pre;overflow:auto}.resizeable-solution-view{display:none;overflow:auto;padding:0 15px}.btn-group,.resizeable-solution-view.showMe{display:block}.btn-group.hideMe{display:none}.dropdown-toggle{border-radius:3px!important;float:none!important}.machines-spinner{display:none}.machines-spinner.showMe{display:block}.tabs-spinner{display:none}.tabs-spinner.showMe{display:inline-block}.rha-search-spinner{margin-bottom:0!important}.file-list-title{display:none}.file-list-title.showMe{display:block}.rha-logsinstructionpane{padding-left:20px;padding-right:20px} \ No newline at end of file diff --git a/rhaccess.js b/rhaccess.js index d7c07b1..10c85d4 100644 --- a/rhaccess.js +++ b/rhaccess.js @@ -220,15 +220,15 @@ rhaccess.RHAApp = declare([], { window.strata.setRedhatClientID('redhat_access_plugin_ipa_7.1'); window.angular.module('RedhatAccess', [ - 'ngSanitize', - 'ui.select2', - 'RedhatAccess.header', - 'RedhatAccess.template', - 'RedhatAccess.cases', - 'RedhatAccess.security', - 'RedhatAccess.search', - 'RedhatAccess.logViewer', - 'RedhatAccess.ui-utils' + 'ngSanitize', + 'localytics.directives', + 'RedhatAccess.header', + 'RedhatAccess.template', + 'RedhatAccess.cases', + 'RedhatAccess.security', + 'RedhatAccess.search', + 'RedhatAccess.logViewer', + 'RedhatAccess.ui-utils' ]). config(['$provide', function ($provide) { -- 1.9.3