{ "version": 3, "sources": ["../node_modules/dompurify/src/utils.js", "../node_modules/dompurify/src/tags.js", "../node_modules/dompurify/src/attrs.js", "../node_modules/dompurify/src/regexp.js", "../node_modules/dompurify/src/purify.js", "common.js", "../src/common_billing.js", "../src/common.mjs"], "sourcesContent": ["const {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor,\n} = Object;\n\nlet { freeze, seal, create } = Object; // eslint-disable-line import/no-mutable-exports\nlet { apply, construct } = typeof Reflect !== 'undefined' && Reflect;\n\nif (!apply) {\n apply = function (fun, thisValue, args) {\n return fun.apply(thisValue, args);\n };\n}\n\nif (!freeze) {\n freeze = function (x) {\n return x;\n };\n}\n\nif (!seal) {\n seal = function (x) {\n return x;\n };\n}\n\nif (!construct) {\n construct = function (Func, args) {\n return new Func(...args);\n };\n}\n\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayIndexOf = unapply(Array.prototype.indexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySlice = unapply(Array.prototype.slice);\n\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\n\nconst regExpTest = unapply(RegExp.prototype.test);\n\nconst typeErrorCreate = unconstruct(TypeError);\n\nexport function unapply(func) {\n return (thisArg, ...args) => apply(func, thisArg, args);\n}\n\nexport function unconstruct(func) {\n return (...args) => construct(func, args);\n}\n\n/* Add properties to a lookup table */\nexport function addToSet(set, array, transformCaseFunc) {\n transformCaseFunc = transformCaseFunc ?? stringToLowerCase;\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n\n element = lcElement;\n }\n }\n\n set[element] = true;\n }\n\n return set;\n}\n\n/* Shallow clone an object */\nexport function clone(object) {\n const newObject = create(null);\n\n for (const [property, value] of entries(object)) {\n newObject[property] = value;\n }\n\n return newObject;\n}\n\n/* This method automatically checks if the prop is function\n * or getter and behaves accordingly. */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n\n object = getPrototypeOf(object);\n }\n\n function fallbackValue(element) {\n console.warn('fallback value for', element);\n return null;\n }\n\n return fallbackValue;\n}\n\nexport {\n // Array\n arrayForEach,\n arrayIndexOf,\n arrayPop,\n arrayPush,\n arraySlice,\n // Object\n entries,\n freeze,\n getPrototypeOf,\n getOwnPropertyDescriptor,\n isFrozen,\n setPrototypeOf,\n seal,\n // RegExp\n regExpTest,\n // String\n stringIndexOf,\n stringMatch,\n stringReplace,\n stringToLowerCase,\n stringToString,\n stringTrim,\n // Errors\n typeErrorCreate,\n // Other\n lookupGetter,\n};\n", "import { freeze } from './utils.js';\n\nexport const html = freeze([\n 'a',\n 'abbr',\n 'acronym',\n 'address',\n 'area',\n 'article',\n 'aside',\n 'audio',\n 'b',\n 'bdi',\n 'bdo',\n 'big',\n 'blink',\n 'blockquote',\n 'body',\n 'br',\n 'button',\n 'canvas',\n 'caption',\n 'center',\n 'cite',\n 'code',\n 'col',\n 'colgroup',\n 'content',\n 'data',\n 'datalist',\n 'dd',\n 'decorator',\n 'del',\n 'details',\n 'dfn',\n 'dialog',\n 'dir',\n 'div',\n 'dl',\n 'dt',\n 'element',\n 'em',\n 'fieldset',\n 'figcaption',\n 'figure',\n 'font',\n 'footer',\n 'form',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'head',\n 'header',\n 'hgroup',\n 'hr',\n 'html',\n 'i',\n 'img',\n 'input',\n 'ins',\n 'kbd',\n 'label',\n 'legend',\n 'li',\n 'main',\n 'map',\n 'mark',\n 'marquee',\n 'menu',\n 'menuitem',\n 'meter',\n 'nav',\n 'nobr',\n 'ol',\n 'optgroup',\n 'option',\n 'output',\n 'p',\n 'picture',\n 'pre',\n 'progress',\n 'q',\n 'rp',\n 'rt',\n 'ruby',\n 's',\n 'samp',\n 'section',\n 'select',\n 'shadow',\n 'small',\n 'source',\n 'spacer',\n 'span',\n 'strike',\n 'strong',\n 'style',\n 'sub',\n 'summary',\n 'sup',\n 'table',\n 'tbody',\n 'td',\n 'template',\n 'textarea',\n 'tfoot',\n 'th',\n 'thead',\n 'time',\n 'tr',\n 'track',\n 'tt',\n 'u',\n 'ul',\n 'var',\n 'video',\n 'wbr',\n]);\n\n// SVG\nexport const svg = freeze([\n 'svg',\n 'a',\n 'altglyph',\n 'altglyphdef',\n 'altglyphitem',\n 'animatecolor',\n 'animatemotion',\n 'animatetransform',\n 'circle',\n 'clippath',\n 'defs',\n 'desc',\n 'ellipse',\n 'filter',\n 'font',\n 'g',\n 'glyph',\n 'glyphref',\n 'hkern',\n 'image',\n 'line',\n 'lineargradient',\n 'marker',\n 'mask',\n 'metadata',\n 'mpath',\n 'path',\n 'pattern',\n 'polygon',\n 'polyline',\n 'radialgradient',\n 'rect',\n 'stop',\n 'style',\n 'switch',\n 'symbol',\n 'text',\n 'textpath',\n 'title',\n 'tref',\n 'tspan',\n 'view',\n 'vkern',\n]);\n\nexport const svgFilters = freeze([\n 'feBlend',\n 'feColorMatrix',\n 'feComponentTransfer',\n 'feComposite',\n 'feConvolveMatrix',\n 'feDiffuseLighting',\n 'feDisplacementMap',\n 'feDistantLight',\n 'feDropShadow',\n 'feFlood',\n 'feFuncA',\n 'feFuncB',\n 'feFuncG',\n 'feFuncR',\n 'feGaussianBlur',\n 'feImage',\n 'feMerge',\n 'feMergeNode',\n 'feMorphology',\n 'feOffset',\n 'fePointLight',\n 'feSpecularLighting',\n 'feSpotLight',\n 'feTile',\n 'feTurbulence',\n]);\n\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nexport const svgDisallowed = freeze([\n 'animate',\n 'color-profile',\n 'cursor',\n 'discard',\n 'font-face',\n 'font-face-format',\n 'font-face-name',\n 'font-face-src',\n 'font-face-uri',\n 'foreignobject',\n 'hatch',\n 'hatchpath',\n 'mesh',\n 'meshgradient',\n 'meshpatch',\n 'meshrow',\n 'missing-glyph',\n 'script',\n 'set',\n 'solidcolor',\n 'unknown',\n 'use',\n]);\n\nexport const mathMl = freeze([\n 'math',\n 'menclose',\n 'merror',\n 'mfenced',\n 'mfrac',\n 'mglyph',\n 'mi',\n 'mlabeledtr',\n 'mmultiscripts',\n 'mn',\n 'mo',\n 'mover',\n 'mpadded',\n 'mphantom',\n 'mroot',\n 'mrow',\n 'ms',\n 'mspace',\n 'msqrt',\n 'mstyle',\n 'msub',\n 'msup',\n 'msubsup',\n 'mtable',\n 'mtd',\n 'mtext',\n 'mtr',\n 'munder',\n 'munderover',\n 'mprescripts',\n]);\n\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nexport const mathMlDisallowed = freeze([\n 'maction',\n 'maligngroup',\n 'malignmark',\n 'mlongdiv',\n 'mscarries',\n 'mscarry',\n 'msgroup',\n 'mstack',\n 'msline',\n 'msrow',\n 'semantics',\n 'annotation',\n 'annotation-xml',\n 'mprescripts',\n 'none',\n]);\n\nexport const text = freeze(['#text']);\n", "import { freeze } from './utils.js';\n\nexport const html = freeze([\n 'accept',\n 'action',\n 'align',\n 'alt',\n 'autocapitalize',\n 'autocomplete',\n 'autopictureinpicture',\n 'autoplay',\n 'background',\n 'bgcolor',\n 'border',\n 'capture',\n 'cellpadding',\n 'cellspacing',\n 'checked',\n 'cite',\n 'class',\n 'clear',\n 'color',\n 'cols',\n 'colspan',\n 'controls',\n 'controlslist',\n 'coords',\n 'crossorigin',\n 'datetime',\n 'decoding',\n 'default',\n 'dir',\n 'disabled',\n 'disablepictureinpicture',\n 'disableremoteplayback',\n 'download',\n 'draggable',\n 'enctype',\n 'enterkeyhint',\n 'face',\n 'for',\n 'headers',\n 'height',\n 'hidden',\n 'high',\n 'href',\n 'hreflang',\n 'id',\n 'inputmode',\n 'integrity',\n 'ismap',\n 'kind',\n 'label',\n 'lang',\n 'list',\n 'loading',\n 'loop',\n 'low',\n 'max',\n 'maxlength',\n 'media',\n 'method',\n 'min',\n 'minlength',\n 'multiple',\n 'muted',\n 'name',\n 'nonce',\n 'noshade',\n 'novalidate',\n 'nowrap',\n 'open',\n 'optimum',\n 'pattern',\n 'placeholder',\n 'playsinline',\n 'poster',\n 'preload',\n 'pubdate',\n 'radiogroup',\n 'readonly',\n 'rel',\n 'required',\n 'rev',\n 'reversed',\n 'role',\n 'rows',\n 'rowspan',\n 'spellcheck',\n 'scope',\n 'selected',\n 'shape',\n 'size',\n 'sizes',\n 'span',\n 'srclang',\n 'start',\n 'src',\n 'srcset',\n 'step',\n 'style',\n 'summary',\n 'tabindex',\n 'title',\n 'translate',\n 'type',\n 'usemap',\n 'valign',\n 'value',\n 'width',\n 'xmlns',\n 'slot',\n]);\n\nexport const svg = freeze([\n 'accent-height',\n 'accumulate',\n 'additive',\n 'alignment-baseline',\n 'ascent',\n 'attributename',\n 'attributetype',\n 'azimuth',\n 'basefrequency',\n 'baseline-shift',\n 'begin',\n 'bias',\n 'by',\n 'class',\n 'clip',\n 'clippathunits',\n 'clip-path',\n 'clip-rule',\n 'color',\n 'color-interpolation',\n 'color-interpolation-filters',\n 'color-profile',\n 'color-rendering',\n 'cx',\n 'cy',\n 'd',\n 'dx',\n 'dy',\n 'diffuseconstant',\n 'direction',\n 'display',\n 'divisor',\n 'dur',\n 'edgemode',\n 'elevation',\n 'end',\n 'fill',\n 'fill-opacity',\n 'fill-rule',\n 'filter',\n 'filterunits',\n 'flood-color',\n 'flood-opacity',\n 'font-family',\n 'font-size',\n 'font-size-adjust',\n 'font-stretch',\n 'font-style',\n 'font-variant',\n 'font-weight',\n 'fx',\n 'fy',\n 'g1',\n 'g2',\n 'glyph-name',\n 'glyphref',\n 'gradientunits',\n 'gradienttransform',\n 'height',\n 'href',\n 'id',\n 'image-rendering',\n 'in',\n 'in2',\n 'k',\n 'k1',\n 'k2',\n 'k3',\n 'k4',\n 'kerning',\n 'keypoints',\n 'keysplines',\n 'keytimes',\n 'lang',\n 'lengthadjust',\n 'letter-spacing',\n 'kernelmatrix',\n 'kernelunitlength',\n 'lighting-color',\n 'local',\n 'marker-end',\n 'marker-mid',\n 'marker-start',\n 'markerheight',\n 'markerunits',\n 'markerwidth',\n 'maskcontentunits',\n 'maskunits',\n 'max',\n 'mask',\n 'media',\n 'method',\n 'mode',\n 'min',\n 'name',\n 'numoctaves',\n 'offset',\n 'operator',\n 'opacity',\n 'order',\n 'orient',\n 'orientation',\n 'origin',\n 'overflow',\n 'paint-order',\n 'path',\n 'pathlength',\n 'patterncontentunits',\n 'patterntransform',\n 'patternunits',\n 'points',\n 'preservealpha',\n 'preserveaspectratio',\n 'primitiveunits',\n 'r',\n 'rx',\n 'ry',\n 'radius',\n 'refx',\n 'refy',\n 'repeatcount',\n 'repeatdur',\n 'restart',\n 'result',\n 'rotate',\n 'scale',\n 'seed',\n 'shape-rendering',\n 'specularconstant',\n 'specularexponent',\n 'spreadmethod',\n 'startoffset',\n 'stddeviation',\n 'stitchtiles',\n 'stop-color',\n 'stop-opacity',\n 'stroke-dasharray',\n 'stroke-dashoffset',\n 'stroke-linecap',\n 'stroke-linejoin',\n 'stroke-miterlimit',\n 'stroke-opacity',\n 'stroke',\n 'stroke-width',\n 'style',\n 'surfacescale',\n 'systemlanguage',\n 'tabindex',\n 'targetx',\n 'targety',\n 'transform',\n 'transform-origin',\n 'text-anchor',\n 'text-decoration',\n 'text-rendering',\n 'textlength',\n 'type',\n 'u1',\n 'u2',\n 'unicode',\n 'values',\n 'viewbox',\n 'visibility',\n 'version',\n 'vert-adv-y',\n 'vert-origin-x',\n 'vert-origin-y',\n 'width',\n 'word-spacing',\n 'wrap',\n 'writing-mode',\n 'xchannelselector',\n 'ychannelselector',\n 'x',\n 'x1',\n 'x2',\n 'xmlns',\n 'y',\n 'y1',\n 'y2',\n 'z',\n 'zoomandpan',\n]);\n\nexport const mathMl = freeze([\n 'accent',\n 'accentunder',\n 'align',\n 'bevelled',\n 'close',\n 'columnsalign',\n 'columnlines',\n 'columnspan',\n 'denomalign',\n 'depth',\n 'dir',\n 'display',\n 'displaystyle',\n 'encoding',\n 'fence',\n 'frame',\n 'height',\n 'href',\n 'id',\n 'largeop',\n 'length',\n 'linethickness',\n 'lspace',\n 'lquote',\n 'mathbackground',\n 'mathcolor',\n 'mathsize',\n 'mathvariant',\n 'maxsize',\n 'minsize',\n 'movablelimits',\n 'notation',\n 'numalign',\n 'open',\n 'rowalign',\n 'rowlines',\n 'rowspacing',\n 'rowspan',\n 'rspace',\n 'rquote',\n 'scriptlevel',\n 'scriptminsize',\n 'scriptsizemultiplier',\n 'selection',\n 'separator',\n 'separators',\n 'stretchy',\n 'subscriptshift',\n 'supscriptshift',\n 'symmetric',\n 'voffset',\n 'width',\n 'xmlns',\n]);\n\nexport const xml = freeze([\n 'xlink:href',\n 'xml:id',\n 'xlink:title',\n 'xml:space',\n 'xmlns:xlink',\n]);\n", "import { seal } from './utils.js';\n\n// eslint-disable-next-line unicorn/better-regex\nexport const MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nexport const ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nexport const TMPLIT_EXPR = seal(/\\${[\\w\\W]*}/gm);\nexport const DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]/); // eslint-disable-line no-useless-escape\nexport const ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nexport const IS_ALLOWED_URI = seal(\n /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nexport const IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nexport const ATTR_WHITESPACE = seal(\n /[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nexport const DOCTYPE_NAME = seal(/^html$/i);\n", "import * as TAGS from './tags.js';\nimport * as ATTRS from './attrs.js';\nimport * as EXPRESSIONS from './regexp.js';\nimport {\n addToSet,\n clone,\n entries,\n freeze,\n arrayForEach,\n arrayPop,\n arrayPush,\n stringMatch,\n stringReplace,\n stringToLowerCase,\n stringToString,\n stringIndexOf,\n stringTrim,\n regExpTest,\n typeErrorCreate,\n lookupGetter,\n} from './utils.js';\n\nconst getGlobal = () => (typeof window === 'undefined' ? null : window);\n\n/**\n * Creates a no-op policy for internal use only.\n * Don't export this function outside this module!\n * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.\n * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types\n * are not supported or creating the policy failed).\n */\nconst _createTrustedTypesPolicy = function (trustedTypes, purifyHostElement) {\n if (\n typeof trustedTypes !== 'object' ||\n typeof trustedTypes.createPolicy !== 'function'\n ) {\n return null;\n }\n\n // Allow the callers to control the unique policy name\n // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n // Policy creation with duplicate names throws in Trusted Types.\n let suffix = null;\n const ATTR_NAME = 'data-tt-policy-suffix';\n if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n suffix = purifyHostElement.getAttribute(ATTR_NAME);\n }\n\n const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n\n try {\n return trustedTypes.createPolicy(policyName, {\n createHTML(html) {\n return html;\n },\n createScriptURL(scriptUrl) {\n return scriptUrl;\n },\n });\n } catch (_) {\n // Policy creation failed (most likely another DOMPurify script has\n // already run). Skip creating the policy, as this will only cause errors\n // if TT are enforced.\n console.warn(\n 'TrustedTypes policy ' + policyName + ' could not be created.'\n );\n return null;\n }\n};\n\nfunction createDOMPurify(window = getGlobal()) {\n const DOMPurify = (root) => createDOMPurify(root);\n\n /**\n * Version label, exposed for easier checks\n * if DOMPurify is up to date or not\n */\n DOMPurify.version = VERSION;\n\n /**\n * Array of elements that DOMPurify removed during sanitation.\n * Empty if nothing was removed.\n */\n DOMPurify.removed = [];\n\n if (!window || !window.document || window.document.nodeType !== 9) {\n // Not running in a browser, provide a factory function\n // so that you can pass your own Window\n DOMPurify.isSupported = false;\n\n return DOMPurify;\n }\n\n const originalDocument = window.document;\n const currentScript = originalDocument.currentScript;\n\n let { document } = window;\n const {\n DocumentFragment,\n HTMLTemplateElement,\n Node,\n Element,\n NodeFilter,\n NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n HTMLFormElement,\n DOMParser,\n trustedTypes,\n } = window;\n\n const ElementPrototype = Element.prototype;\n\n const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n\n // As per issue #47, the web-components registry is inherited by a\n // new document created via createHTMLDocument. As per the spec\n // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n // a new empty registry is used when creating a template contents owner\n // document, so we use that as our parent document to ensure nothing\n // is inherited.\n if (typeof HTMLTemplateElement === 'function') {\n const template = document.createElement('template');\n if (template.content && template.content.ownerDocument) {\n document = template.content.ownerDocument;\n }\n }\n\n let trustedTypesPolicy;\n let emptyHTML = '';\n\n const {\n implementation,\n createNodeIterator,\n createDocumentFragment,\n getElementsByTagName,\n } = document;\n const { importNode } = originalDocument;\n\n let hooks = {};\n\n /**\n * Expose whether this browser supports running the full DOMPurify.\n */\n DOMPurify.isSupported =\n typeof entries === 'function' &&\n typeof getParentNode === 'function' &&\n implementation &&\n implementation.createHTMLDocument !== undefined;\n\n const {\n MUSTACHE_EXPR,\n ERB_EXPR,\n TMPLIT_EXPR,\n DATA_ATTR,\n ARIA_ATTR,\n IS_SCRIPT_OR_DATA,\n ATTR_WHITESPACE,\n } = EXPRESSIONS;\n\n let { IS_ALLOWED_URI } = EXPRESSIONS;\n\n /**\n * We consider the elements and attributes below to be safe. Ideally\n * don't add any new ones but feel free to remove unwanted ones.\n */\n\n /* allowed element names */\n let ALLOWED_TAGS = null;\n const DEFAULT_ALLOWED_TAGS = addToSet({}, [\n ...TAGS.html,\n ...TAGS.svg,\n ...TAGS.svgFilters,\n ...TAGS.mathMl,\n ...TAGS.text,\n ]);\n\n /* Allowed attribute names */\n let ALLOWED_ATTR = null;\n const DEFAULT_ALLOWED_ATTR = addToSet({}, [\n ...ATTRS.html,\n ...ATTRS.svg,\n ...ATTRS.mathMl,\n ...ATTRS.xml,\n ]);\n\n /*\n * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.\n * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n */\n let CUSTOM_ELEMENT_HANDLING = Object.seal(\n Object.create(null, {\n tagNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null,\n },\n attributeNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null,\n },\n allowCustomizedBuiltInElements: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: false,\n },\n })\n );\n\n /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n let FORBID_TAGS = null;\n\n /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n let FORBID_ATTR = null;\n\n /* Decide if ARIA attributes are okay */\n let ALLOW_ARIA_ATTR = true;\n\n /* Decide if custom data attributes are okay */\n let ALLOW_DATA_ATTR = true;\n\n /* Decide if unknown protocols are okay */\n let ALLOW_UNKNOWN_PROTOCOLS = false;\n\n /* Decide if self-closing tags in attributes are allowed.\n * Usually removed due to a mXSS issue in jQuery 3.0 */\n let ALLOW_SELF_CLOSE_IN_ATTR = true;\n\n /* Output should be safe for common template engines.\n * This means, DOMPurify removes data attributes, mustaches and ERB\n */\n let SAFE_FOR_TEMPLATES = false;\n\n /* Decide if document with ... should be returned */\n let WHOLE_DOCUMENT = false;\n\n /* Track whether config is already set on this instance of DOMPurify. */\n let SET_CONFIG = false;\n\n /* Decide if all elements (e.g. style, script) must be children of\n * document.body. By default, browsers might move them to document.head */\n let FORCE_BODY = false;\n\n /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported).\n * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n */\n let RETURN_DOM = false;\n\n /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported) */\n let RETURN_DOM_FRAGMENT = false;\n\n /* Try to return a Trusted Type object instead of a string, return a string in\n * case Trusted Types are not supported */\n let RETURN_TRUSTED_TYPE = false;\n\n /* Output should be free from DOM clobbering attacks?\n * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n */\n let SANITIZE_DOM = true;\n\n /* Achieve full DOM Clobbering protection by isolating the namespace of named\n * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n *\n * HTML/DOM spec rules that enable DOM Clobbering:\n * - Named Access on Window (§7.3.3)\n * - DOM Tree Accessors (§3.1.5)\n * - Form Element Parent-Child Relations (§4.10.3)\n * - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n * - HTMLCollection (§4.2.10.2)\n *\n * Namespace isolation is implemented by prefixing `id` and `name` attributes\n * with a constant string, i.e., `user-content-`\n */\n let SANITIZE_NAMED_PROPS = false;\n const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n\n /* Keep element content when removing element? */\n let KEEP_CONTENT = true;\n\n /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n * of importing it into a new Document and returning a sanitized copy */\n let IN_PLACE = false;\n\n /* Allow usage of profiles like html, svg and mathMl */\n let USE_PROFILES = {};\n\n /* Tags to ignore content of when KEEP_CONTENT is true */\n let FORBID_CONTENTS = null;\n const DEFAULT_FORBID_CONTENTS = addToSet({}, [\n 'annotation-xml',\n 'audio',\n 'colgroup',\n 'desc',\n 'foreignobject',\n 'head',\n 'iframe',\n 'math',\n 'mi',\n 'mn',\n 'mo',\n 'ms',\n 'mtext',\n 'noembed',\n 'noframes',\n 'noscript',\n 'plaintext',\n 'script',\n 'style',\n 'svg',\n 'template',\n 'thead',\n 'title',\n 'video',\n 'xmp',\n ]);\n\n /* Tags that are safe for data: URIs */\n let DATA_URI_TAGS = null;\n const DEFAULT_DATA_URI_TAGS = addToSet({}, [\n 'audio',\n 'video',\n 'img',\n 'source',\n 'image',\n 'track',\n ]);\n\n /* Attributes safe for values like \"javascript:\" */\n let URI_SAFE_ATTRIBUTES = null;\n const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [\n 'alt',\n 'class',\n 'for',\n 'id',\n 'label',\n 'name',\n 'pattern',\n 'placeholder',\n 'role',\n 'summary',\n 'title',\n 'value',\n 'style',\n 'xmlns',\n ]);\n\n const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n /* Document namespace */\n let NAMESPACE = HTML_NAMESPACE;\n let IS_EMPTY_INPUT = false;\n\n /* Allowed XHTML+XML namespaces */\n let ALLOWED_NAMESPACES = null;\n const DEFAULT_ALLOWED_NAMESPACES = addToSet(\n {},\n [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE],\n stringToString\n );\n\n /* Parsing of strict XHTML documents */\n let PARSER_MEDIA_TYPE;\n const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];\n const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';\n let transformCaseFunc;\n\n /* Keep a reference to config to pass to hooks */\n let CONFIG = null;\n\n /* Ideally, do not touch anything below this line */\n /* ______________________________________________ */\n\n const formElement = document.createElement('form');\n\n const isRegexOrFunction = function (testValue) {\n return testValue instanceof RegExp || testValue instanceof Function;\n };\n\n /**\n * _parseConfig\n *\n * @param {Object} cfg optional config literal\n */\n // eslint-disable-next-line complexity\n const _parseConfig = function (cfg) {\n if (CONFIG && CONFIG === cfg) {\n return;\n }\n\n /* Shield configuration object from tampering */\n if (!cfg || typeof cfg !== 'object') {\n cfg = {};\n }\n\n /* Shield configuration object from prototype pollution */\n cfg = clone(cfg);\n\n PARSER_MEDIA_TYPE =\n // eslint-disable-next-line unicorn/prefer-includes\n SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1\n ? (PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE)\n : (PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE);\n\n // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.\n transformCaseFunc =\n PARSER_MEDIA_TYPE === 'application/xhtml+xml'\n ? stringToString\n : stringToLowerCase;\n\n /* Set configuration parameters */\n ALLOWED_TAGS =\n 'ALLOWED_TAGS' in cfg\n ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc)\n : DEFAULT_ALLOWED_TAGS;\n ALLOWED_ATTR =\n 'ALLOWED_ATTR' in cfg\n ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc)\n : DEFAULT_ALLOWED_ATTR;\n ALLOWED_NAMESPACES =\n 'ALLOWED_NAMESPACES' in cfg\n ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString)\n : DEFAULT_ALLOWED_NAMESPACES;\n URI_SAFE_ATTRIBUTES =\n 'ADD_URI_SAFE_ATTR' in cfg\n ? addToSet(\n clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent\n cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent\n transformCaseFunc // eslint-disable-line indent\n ) // eslint-disable-line indent\n : DEFAULT_URI_SAFE_ATTRIBUTES;\n DATA_URI_TAGS =\n 'ADD_DATA_URI_TAGS' in cfg\n ? addToSet(\n clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent\n cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent\n transformCaseFunc // eslint-disable-line indent\n ) // eslint-disable-line indent\n : DEFAULT_DATA_URI_TAGS;\n FORBID_CONTENTS =\n 'FORBID_CONTENTS' in cfg\n ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc)\n : DEFAULT_FORBID_CONTENTS;\n FORBID_TAGS =\n 'FORBID_TAGS' in cfg\n ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc)\n : {};\n FORBID_ATTR =\n 'FORBID_ATTR' in cfg\n ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc)\n : {};\n USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;\n ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true\n ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true\n ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false\n ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true\n SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false\n WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false\n RETURN_DOM = cfg.RETURN_DOM || false; // Default false\n RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false\n RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false\n FORCE_BODY = cfg.FORCE_BODY || false; // Default false\n SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true\n SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false\n KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true\n IN_PLACE = cfg.IN_PLACE || false; // Default false\n IS_ALLOWED_URI = cfg.ALLOWED_URI_REGEXP || EXPRESSIONS.IS_ALLOWED_URI;\n NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;\n CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)\n ) {\n CUSTOM_ELEMENT_HANDLING.tagNameCheck =\n cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;\n }\n\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)\n ) {\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck =\n cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;\n }\n\n if (\n cfg.CUSTOM_ELEMENT_HANDLING &&\n typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements ===\n 'boolean'\n ) {\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements =\n cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;\n }\n\n if (SAFE_FOR_TEMPLATES) {\n ALLOW_DATA_ATTR = false;\n }\n\n if (RETURN_DOM_FRAGMENT) {\n RETURN_DOM = true;\n }\n\n /* Parse profile info */\n if (USE_PROFILES) {\n ALLOWED_TAGS = addToSet({}, [...TAGS.text]);\n ALLOWED_ATTR = [];\n if (USE_PROFILES.html === true) {\n addToSet(ALLOWED_TAGS, TAGS.html);\n addToSet(ALLOWED_ATTR, ATTRS.html);\n }\n\n if (USE_PROFILES.svg === true) {\n addToSet(ALLOWED_TAGS, TAGS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n\n if (USE_PROFILES.svgFilters === true) {\n addToSet(ALLOWED_TAGS, TAGS.svgFilters);\n addToSet(ALLOWED_ATTR, ATTRS.svg);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n\n if (USE_PROFILES.mathMl === true) {\n addToSet(ALLOWED_TAGS, TAGS.mathMl);\n addToSet(ALLOWED_ATTR, ATTRS.mathMl);\n addToSet(ALLOWED_ATTR, ATTRS.xml);\n }\n }\n\n /* Merge configuration parameters */\n if (cfg.ADD_TAGS) {\n if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {\n ALLOWED_TAGS = clone(ALLOWED_TAGS);\n }\n\n addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);\n }\n\n if (cfg.ADD_ATTR) {\n if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {\n ALLOWED_ATTR = clone(ALLOWED_ATTR);\n }\n\n addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);\n }\n\n if (cfg.ADD_URI_SAFE_ATTR) {\n addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);\n }\n\n if (cfg.FORBID_CONTENTS) {\n if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {\n FORBID_CONTENTS = clone(FORBID_CONTENTS);\n }\n\n addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);\n }\n\n /* Add #text in case KEEP_CONTENT is set to true */\n if (KEEP_CONTENT) {\n ALLOWED_TAGS['#text'] = true;\n }\n\n /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n if (WHOLE_DOCUMENT) {\n addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n }\n\n /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n if (ALLOWED_TAGS.table) {\n addToSet(ALLOWED_TAGS, ['tbody']);\n delete FORBID_TAGS.tbody;\n }\n\n if (cfg.TRUSTED_TYPES_POLICY) {\n if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n throw typeErrorCreate(\n 'TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.'\n );\n }\n\n if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n throw typeErrorCreate(\n 'TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.'\n );\n }\n\n // Overwrite existing TrustedTypes policy.\n trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n\n // Sign local variables required by `sanitize`.\n emptyHTML = trustedTypesPolicy.createHTML('');\n } else {\n // Uninitialized policy, attempt to initialize the internal dompurify policy.\n if (trustedTypesPolicy === undefined) {\n trustedTypesPolicy = _createTrustedTypesPolicy(\n trustedTypes,\n currentScript\n );\n }\n\n // If creating the internal policy succeeded sign internal variables.\n if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {\n emptyHTML = trustedTypesPolicy.createHTML('');\n }\n }\n\n // Prevent further manipulation of configuration.\n // Not available in IE8, Safari 5, etc.\n if (freeze) {\n freeze(cfg);\n }\n\n CONFIG = cfg;\n };\n\n const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, [\n 'mi',\n 'mo',\n 'mn',\n 'ms',\n 'mtext',\n ]);\n\n const HTML_INTEGRATION_POINTS = addToSet({}, [\n 'foreignobject',\n 'desc',\n 'title',\n 'annotation-xml',\n ]);\n\n // Certain elements are allowed in both SVG and HTML\n // namespace. We need to specify them explicitly\n // so that they don't get erroneously deleted from\n // HTML namespace.\n const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [\n 'title',\n 'style',\n 'font',\n 'a',\n 'script',\n ]);\n\n /* Keep track of all possible SVG and MathML tags\n * so that we can perform the namespace checks\n * correctly. */\n const ALL_SVG_TAGS = addToSet({}, TAGS.svg);\n addToSet(ALL_SVG_TAGS, TAGS.svgFilters);\n addToSet(ALL_SVG_TAGS, TAGS.svgDisallowed);\n\n const ALL_MATHML_TAGS = addToSet({}, TAGS.mathMl);\n addToSet(ALL_MATHML_TAGS, TAGS.mathMlDisallowed);\n\n /**\n *\n *\n * @param {Element} element a DOM element whose namespace is being checked\n * @returns {boolean} Return false if the element has a\n * namespace that a spec-compliant parser would never\n * return. Return true otherwise.\n */\n const _checkValidNamespace = function (element) {\n let parent = getParentNode(element);\n\n // In JSDOM, if we're inside shadow DOM, then parentNode\n // can be null. We just simulate parent in this case.\n if (!parent || !parent.tagName) {\n parent = {\n namespaceURI: NAMESPACE,\n tagName: 'template',\n };\n }\n\n const tagName = stringToLowerCase(element.tagName);\n const parentTagName = stringToLowerCase(parent.tagName);\n\n if (!ALLOWED_NAMESPACES[element.namespaceURI]) {\n return false;\n }\n\n if (element.namespaceURI === SVG_NAMESPACE) {\n // The only way to switch from HTML namespace to SVG\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'svg';\n }\n\n // The only way to switch from MathML to SVG is via`\n // svg if parent is either or MathML\n // text integration points.\n if (parent.namespaceURI === MATHML_NAMESPACE) {\n return (\n tagName === 'svg' &&\n (parentTagName === 'annotation-xml' ||\n MATHML_TEXT_INTEGRATION_POINTS[parentTagName])\n );\n }\n\n // We only allow elements that are defined in SVG\n // spec. All others are disallowed in SVG namespace.\n return Boolean(ALL_SVG_TAGS[tagName]);\n }\n\n if (element.namespaceURI === MATHML_NAMESPACE) {\n // The only way to switch from HTML namespace to MathML\n // is via . If it happens via any other tag, then\n // it should be killed.\n if (parent.namespaceURI === HTML_NAMESPACE) {\n return tagName === 'math';\n }\n\n // The only way to switch from SVG to MathML is via\n // and HTML integration points\n if (parent.namespaceURI === SVG_NAMESPACE) {\n return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];\n }\n\n // We only allow elements that are defined in MathML\n // spec. All others are disallowed in MathML namespace.\n return Boolean(ALL_MATHML_TAGS[tagName]);\n }\n\n if (element.namespaceURI === HTML_NAMESPACE) {\n // The only way to switch from SVG to HTML is via\n // HTML integration points, and from MathML to HTML\n // is via MathML text integration points\n if (\n parent.namespaceURI === SVG_NAMESPACE &&\n !HTML_INTEGRATION_POINTS[parentTagName]\n ) {\n return false;\n }\n\n if (\n parent.namespaceURI === MATHML_NAMESPACE &&\n !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]\n ) {\n return false;\n }\n\n // We disallow tags that are specific for MathML\n // or SVG and should never appear in HTML namespace\n return (\n !ALL_MATHML_TAGS[tagName] &&\n (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName])\n );\n }\n\n // For XHTML and XML documents that support custom namespaces\n if (\n PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&\n ALLOWED_NAMESPACES[element.namespaceURI]\n ) {\n return true;\n }\n\n // The code should never reach this place (this means\n // that the element somehow got namespace that is not\n // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).\n // Return false just in case.\n return false;\n };\n\n /**\n * _forceRemove\n *\n * @param {Node} node a DOM node\n */\n const _forceRemove = function (node) {\n arrayPush(DOMPurify.removed, { element: node });\n try {\n // eslint-disable-next-line unicorn/prefer-dom-node-remove\n node.parentNode.removeChild(node);\n } catch (_) {\n node.remove();\n }\n };\n\n /**\n * _removeAttribute\n *\n * @param {String} name an Attribute name\n * @param {Node} node a DOM node\n */\n const _removeAttribute = function (name, node) {\n try {\n arrayPush(DOMPurify.removed, {\n attribute: node.getAttributeNode(name),\n from: node,\n });\n } catch (_) {\n arrayPush(DOMPurify.removed, {\n attribute: null,\n from: node,\n });\n }\n\n node.removeAttribute(name);\n\n // We void attribute values for unremovable \"is\"\" attributes\n if (name === 'is' && !ALLOWED_ATTR[name]) {\n if (RETURN_DOM || RETURN_DOM_FRAGMENT) {\n try {\n _forceRemove(node);\n } catch (_) {}\n } else {\n try {\n node.setAttribute(name, '');\n } catch (_) {}\n }\n }\n };\n\n /**\n * _initDocument\n *\n * @param {String} dirty a string of dirty markup\n * @return {Document} a DOM, filled with the dirty markup\n */\n const _initDocument = function (dirty) {\n /* Create a HTML document */\n let doc;\n let leadingWhitespace;\n\n if (FORCE_BODY) {\n dirty = '' + dirty;\n } else {\n /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */\n const matches = stringMatch(dirty, /^[\\r\\n\\t ]+/);\n leadingWhitespace = matches && matches[0];\n }\n\n if (\n PARSER_MEDIA_TYPE === 'application/xhtml+xml' &&\n NAMESPACE === HTML_NAMESPACE\n ) {\n // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)\n dirty =\n '' +\n dirty +\n '';\n }\n\n const dirtyPayload = trustedTypesPolicy\n ? trustedTypesPolicy.createHTML(dirty)\n : dirty;\n /*\n * Use the DOMParser API by default, fallback later if needs be\n * DOMParser not work for svg when has multiple root element.\n */\n if (NAMESPACE === HTML_NAMESPACE) {\n try {\n doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);\n } catch (_) {}\n }\n\n /* Use createHTMLDocument in case DOMParser is not available */\n if (!doc || !doc.documentElement) {\n doc = implementation.createDocument(NAMESPACE, 'template', null);\n try {\n doc.documentElement.innerHTML = IS_EMPTY_INPUT\n ? emptyHTML\n : dirtyPayload;\n } catch (_) {\n // Syntax error if dirtyPayload is invalid xml\n }\n }\n\n const body = doc.body || doc.documentElement;\n\n if (dirty && leadingWhitespace) {\n body.insertBefore(\n document.createTextNode(leadingWhitespace),\n body.childNodes[0] || null\n );\n }\n\n /* Work on whole document or just its body */\n if (NAMESPACE === HTML_NAMESPACE) {\n return getElementsByTagName.call(\n doc,\n WHOLE_DOCUMENT ? 'html' : 'body'\n )[0];\n }\n\n return WHOLE_DOCUMENT ? doc.documentElement : body;\n };\n\n /**\n * _createIterator\n *\n * @param {Document} root document/fragment to create iterator for\n * @return {Iterator} iterator instance\n */\n const _createIterator = function (root) {\n return createNodeIterator.call(\n root.ownerDocument || root,\n root,\n // eslint-disable-next-line no-bitwise\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT,\n null,\n false\n );\n };\n\n /**\n * _isClobbered\n *\n * @param {Node} elm element to check for clobbering attacks\n * @return {Boolean} true if clobbered, false if safe\n */\n const _isClobbered = function (elm) {\n return (\n elm instanceof HTMLFormElement &&\n (typeof elm.nodeName !== 'string' ||\n typeof elm.textContent !== 'string' ||\n typeof elm.removeChild !== 'function' ||\n !(elm.attributes instanceof NamedNodeMap) ||\n typeof elm.removeAttribute !== 'function' ||\n typeof elm.setAttribute !== 'function' ||\n typeof elm.namespaceURI !== 'string' ||\n typeof elm.insertBefore !== 'function' ||\n typeof elm.hasChildNodes !== 'function')\n );\n };\n\n /**\n * _isNode\n *\n * @param {Node} obj object to check whether it's a DOM node\n * @return {Boolean} true is object is a DOM node\n */\n const _isNode = function (object) {\n return typeof Node === 'object'\n ? object instanceof Node\n : object &&\n typeof object === 'object' &&\n typeof object.nodeType === 'number' &&\n typeof object.nodeName === 'string';\n };\n\n /**\n * _executeHook\n * Execute user configurable hooks\n *\n * @param {String} entryPoint Name of the hook's entry point\n * @param {Node} currentNode node to work on with the hook\n * @param {Object} data additional hook parameters\n */\n const _executeHook = function (entryPoint, currentNode, data) {\n if (!hooks[entryPoint]) {\n return;\n }\n\n arrayForEach(hooks[entryPoint], (hook) => {\n hook.call(DOMPurify, currentNode, data, CONFIG);\n });\n };\n\n /**\n * _sanitizeElements\n *\n * @protect nodeName\n * @protect textContent\n * @protect removeChild\n *\n * @param {Node} currentNode to check for permission to exist\n * @return {Boolean} true if node was killed, false if left alive\n */\n const _sanitizeElements = function (currentNode) {\n let content;\n\n /* Execute a hook if present */\n _executeHook('beforeSanitizeElements', currentNode, null);\n\n /* Check if element is clobbered or can clobber */\n if (_isClobbered(currentNode)) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Now let's check the element's type and name */\n const tagName = transformCaseFunc(currentNode.nodeName);\n\n /* Execute a hook if present */\n _executeHook('uponSanitizeElement', currentNode, {\n tagName,\n allowedTags: ALLOWED_TAGS,\n });\n\n /* Detect mXSS attempts abusing namespace confusion */\n if (\n currentNode.hasChildNodes() &&\n !_isNode(currentNode.firstElementChild) &&\n (!_isNode(currentNode.content) ||\n !_isNode(currentNode.content.firstElementChild)) &&\n regExpTest(/<[/\\w]/g, currentNode.innerHTML) &&\n regExpTest(/<[/\\w]/g, currentNode.textContent)\n ) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Remove element if anything forbids its presence */\n if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n /* Check if we have a custom element to handle */\n if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {\n if (\n CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)\n )\n return false;\n if (\n CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)\n )\n return false;\n }\n\n /* Keep content except for bad-listed elements */\n if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {\n const parentNode = getParentNode(currentNode) || currentNode.parentNode;\n const childNodes = getChildNodes(currentNode) || currentNode.childNodes;\n\n if (childNodes && parentNode) {\n const childCount = childNodes.length;\n\n for (let i = childCount - 1; i >= 0; --i) {\n parentNode.insertBefore(\n cloneNode(childNodes[i], true),\n getNextSibling(currentNode)\n );\n }\n }\n }\n\n _forceRemove(currentNode);\n return true;\n }\n\n /* Check whether element has a valid namespace */\n if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Make sure that older browsers don't get fallback-tag mXSS */\n if (\n (tagName === 'noscript' ||\n tagName === 'noembed' ||\n tagName === 'noframes') &&\n regExpTest(/<\\/no(script|embed|frames)/i, currentNode.innerHTML)\n ) {\n _forceRemove(currentNode);\n return true;\n }\n\n /* Sanitize element content to be template-safe */\n if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {\n /* Get the element's text content */\n content = currentNode.textContent;\n content = stringReplace(content, MUSTACHE_EXPR, ' ');\n content = stringReplace(content, ERB_EXPR, ' ');\n content = stringReplace(content, TMPLIT_EXPR, ' ');\n if (currentNode.textContent !== content) {\n arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });\n currentNode.textContent = content;\n }\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeElements', currentNode, null);\n\n return false;\n };\n\n /**\n * _isValidAttribute\n *\n * @param {string} lcTag Lowercase tag name of containing element.\n * @param {string} lcName Lowercase attribute name.\n * @param {string} value Attribute value.\n * @return {Boolean} Returns true if `value` is valid, otherwise false.\n */\n // eslint-disable-next-line complexity\n const _isValidAttribute = function (lcTag, lcName, value) {\n /* Make sure attribute cannot clobber */\n if (\n SANITIZE_DOM &&\n (lcName === 'id' || lcName === 'name') &&\n (value in document || value in formElement)\n ) {\n return false;\n }\n\n /* Allow valid data-* attributes: At least one character after \"-\"\n (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)\n XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)\n We don't need to check the value; it's always URI safe. */\n if (\n ALLOW_DATA_ATTR &&\n !FORBID_ATTR[lcName] &&\n regExpTest(DATA_ATTR, lcName)\n ) {\n // This attribute is safe\n } else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) {\n // This attribute is safe\n /* Otherwise, check the name is permitted */\n } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {\n if (\n // First condition does a very basic check if a) it's basically a valid custom element tagname AND\n // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck\n (_basicCustomElementTest(lcTag) &&\n ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag)) ||\n (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag))) &&\n ((CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName)) ||\n (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)))) ||\n // Alternative, second condition checks if it's an `is`-attribute, AND\n // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck\n (lcName === 'is' &&\n CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements &&\n ((CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp &&\n regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value)) ||\n (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function &&\n CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))))\n ) {\n // If user has supplied a regexp or function in CUSTOM_ELEMENT_HANDLING.tagNameCheck, we need to also allow derived custom elements using the same tagName test.\n // Additionally, we need to allow attributes passing the CUSTOM_ELEMENT_HANDLING.attributeNameCheck user has configured, as custom elements can define these at their own discretion.\n } else {\n return false;\n }\n /* Check value is safe. First, is attr inert? If so, is safe */\n } else if (URI_SAFE_ATTRIBUTES[lcName]) {\n // This attribute is safe\n /* Check no script, data or unknown possibly unsafe URI\n unless we know URI values are safe for that attribute */\n } else if (\n regExpTest(IS_ALLOWED_URI, stringReplace(value, ATTR_WHITESPACE, ''))\n ) {\n // This attribute is safe\n /* Keep image data URIs alive if src/xlink:href is allowed */\n /* Further prevent gadget XSS for dynamically built script tags */\n } else if (\n (lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') &&\n lcTag !== 'script' &&\n stringIndexOf(value, 'data:') === 0 &&\n DATA_URI_TAGS[lcTag]\n ) {\n // This attribute is safe\n /* Allow unknown protocols: This provides support for links that\n are handled by protocol handlers which may be unknown ahead of\n time, e.g. fb:, spotify: */\n } else if (\n ALLOW_UNKNOWN_PROTOCOLS &&\n !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))\n ) {\n // This attribute is safe\n /* Check for binary attributes */\n } else if (value) {\n return false;\n } else {\n // Binary attributes are safe at this point\n /* Anything else, presume unsafe, do not add it back */\n }\n\n return true;\n };\n\n /**\n * _basicCustomElementCheck\n * checks if at least one dash is included in tagName, and it's not the first char\n * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name\n * @param {string} tagName name of the tag of the node to sanitize\n */\n const _basicCustomElementTest = function (tagName) {\n return tagName.indexOf('-') > 0;\n };\n\n /**\n * _sanitizeAttributes\n *\n * @protect attributes\n * @protect nodeName\n * @protect removeAttribute\n * @protect setAttribute\n *\n * @param {Node} currentNode to sanitize\n */\n const _sanitizeAttributes = function (currentNode) {\n let attr;\n let value;\n let lcName;\n let l;\n /* Execute a hook if present */\n _executeHook('beforeSanitizeAttributes', currentNode, null);\n\n const { attributes } = currentNode;\n\n /* Check if we have attributes; if not we might have a text node */\n if (!attributes) {\n return;\n }\n\n const hookEvent = {\n attrName: '',\n attrValue: '',\n keepAttr: true,\n allowedAttributes: ALLOWED_ATTR,\n };\n l = attributes.length;\n\n /* Go backwards over all attributes; safely remove bad ones */\n while (l--) {\n attr = attributes[l];\n const { name, namespaceURI } = attr;\n value = name === 'value' ? attr.value : stringTrim(attr.value);\n lcName = transformCaseFunc(name);\n\n /* Execute a hook if present */\n hookEvent.attrName = lcName;\n hookEvent.attrValue = value;\n hookEvent.keepAttr = true;\n hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set\n _executeHook('uponSanitizeAttribute', currentNode, hookEvent);\n value = hookEvent.attrValue;\n /* Did the hooks approve of the attribute? */\n if (hookEvent.forceKeepAttr) {\n continue;\n }\n\n /* Remove attribute */\n _removeAttribute(name, currentNode);\n\n /* Did the hooks approve of the attribute? */\n if (!hookEvent.keepAttr) {\n continue;\n }\n\n /* Work around a security issue in jQuery 3.0 */\n if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\\/>/i, value)) {\n _removeAttribute(name, currentNode);\n continue;\n }\n\n /* Sanitize attribute content to be template-safe */\n if (SAFE_FOR_TEMPLATES) {\n value = stringReplace(value, MUSTACHE_EXPR, ' ');\n value = stringReplace(value, ERB_EXPR, ' ');\n value = stringReplace(value, TMPLIT_EXPR, ' ');\n }\n\n /* Is `value` valid for this attribute? */\n const lcTag = transformCaseFunc(currentNode.nodeName);\n if (!_isValidAttribute(lcTag, lcName, value)) {\n continue;\n }\n\n /* Full DOM Clobbering protection via namespace isolation,\n * Prefix id and name attributes with `user-content-`\n */\n if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {\n // Remove the attribute with this value\n _removeAttribute(name, currentNode);\n\n // Prefix the value and later re-create the attribute with the sanitized value\n value = SANITIZE_NAMED_PROPS_PREFIX + value;\n }\n\n /* Handle attributes that require Trusted Types */\n if (\n trustedTypesPolicy &&\n typeof trustedTypes === 'object' &&\n typeof trustedTypes.getAttributeType === 'function'\n ) {\n if (namespaceURI) {\n /* Namespaces are not yet supported, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293 */\n } else {\n switch (trustedTypes.getAttributeType(lcTag, lcName)) {\n case 'TrustedHTML': {\n value = trustedTypesPolicy.createHTML(value);\n break;\n }\n\n case 'TrustedScriptURL': {\n value = trustedTypesPolicy.createScriptURL(value);\n break;\n }\n\n default: {\n break;\n }\n }\n }\n }\n\n /* Handle invalid data-* attribute set by try-catching it */\n try {\n if (namespaceURI) {\n currentNode.setAttributeNS(namespaceURI, name, value);\n } else {\n /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. \"x-schema\". */\n currentNode.setAttribute(name, value);\n }\n\n arrayPop(DOMPurify.removed);\n } catch (_) {}\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeAttributes', currentNode, null);\n };\n\n /**\n * _sanitizeShadowDOM\n *\n * @param {DocumentFragment} fragment to iterate over recursively\n */\n const _sanitizeShadowDOM = function (fragment) {\n let shadowNode;\n const shadowIterator = _createIterator(fragment);\n\n /* Execute a hook if present */\n _executeHook('beforeSanitizeShadowDOM', fragment, null);\n\n while ((shadowNode = shadowIterator.nextNode())) {\n /* Execute a hook if present */\n _executeHook('uponSanitizeShadowNode', shadowNode, null);\n\n /* Sanitize tags and elements */\n if (_sanitizeElements(shadowNode)) {\n continue;\n }\n\n /* Deep shadow DOM detected */\n if (shadowNode.content instanceof DocumentFragment) {\n _sanitizeShadowDOM(shadowNode.content);\n }\n\n /* Check attributes, sanitize if necessary */\n _sanitizeAttributes(shadowNode);\n }\n\n /* Execute a hook if present */\n _executeHook('afterSanitizeShadowDOM', fragment, null);\n };\n\n /**\n * Sanitize\n * Public method providing core sanitation functionality\n *\n * @param {String|Node} dirty string or DOM node\n * @param {Object} configuration object\n */\n // eslint-disable-next-line complexity\n DOMPurify.sanitize = function (dirty, cfg = {}) {\n let body;\n let importedNode;\n let currentNode;\n let returnNode;\n /* Make sure we have a string to sanitize.\n DO NOT return early, as this will return the wrong type if\n the user has requested a DOM object rather than a string */\n IS_EMPTY_INPUT = !dirty;\n if (IS_EMPTY_INPUT) {\n dirty = '';\n }\n\n /* Stringify, in case dirty is an object */\n if (typeof dirty !== 'string' && !_isNode(dirty)) {\n if (typeof dirty.toString === 'function') {\n dirty = dirty.toString();\n if (typeof dirty !== 'string') {\n throw typeErrorCreate('dirty is not a string, aborting');\n }\n } else {\n throw typeErrorCreate('toString is not a function');\n }\n }\n\n /* Return dirty HTML if DOMPurify cannot run */\n if (!DOMPurify.isSupported) {\n return dirty;\n }\n\n /* Assign config vars */\n if (!SET_CONFIG) {\n _parseConfig(cfg);\n }\n\n /* Clean up removed elements */\n DOMPurify.removed = [];\n\n /* Check if dirty is correctly typed for IN_PLACE */\n if (typeof dirty === 'string') {\n IN_PLACE = false;\n }\n\n if (IN_PLACE) {\n /* Do some early pre-sanitization to avoid unsafe root nodes */\n if (dirty.nodeName) {\n const tagName = transformCaseFunc(dirty.nodeName);\n if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {\n throw typeErrorCreate(\n 'root node is forbidden and cannot be sanitized in-place'\n );\n }\n }\n } else if (dirty instanceof Node) {\n /* If dirty is a DOM element, append to an empty document to avoid\n elements being stripped by the parser */\n body = _initDocument('');\n importedNode = body.ownerDocument.importNode(dirty, true);\n if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {\n /* Node is already a body, use as is */\n body = importedNode;\n } else if (importedNode.nodeName === 'HTML') {\n body = importedNode;\n } else {\n // eslint-disable-next-line unicorn/prefer-dom-node-append\n body.appendChild(importedNode);\n }\n } else {\n /* Exit directly if we have nothing to do */\n if (\n !RETURN_DOM &&\n !SAFE_FOR_TEMPLATES &&\n !WHOLE_DOCUMENT &&\n // eslint-disable-next-line unicorn/prefer-includes\n dirty.indexOf('<') === -1\n ) {\n return trustedTypesPolicy && RETURN_TRUSTED_TYPE\n ? trustedTypesPolicy.createHTML(dirty)\n : dirty;\n }\n\n /* Initialize the document to work on */\n body = _initDocument(dirty);\n\n /* Check we have a DOM node from the data */\n if (!body) {\n return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';\n }\n }\n\n /* Remove first element node (ours) if FORCE_BODY is set */\n if (body && FORCE_BODY) {\n _forceRemove(body.firstChild);\n }\n\n /* Get node iterator */\n const nodeIterator = _createIterator(IN_PLACE ? dirty : body);\n\n /* Now start iterating over the created document */\n while ((currentNode = nodeIterator.nextNode())) {\n /* Sanitize tags and elements */\n if (_sanitizeElements(currentNode)) {\n continue;\n }\n\n /* Shadow DOM detected, sanitize it */\n if (currentNode.content instanceof DocumentFragment) {\n _sanitizeShadowDOM(currentNode.content);\n }\n\n /* Check attributes, sanitize if necessary */\n _sanitizeAttributes(currentNode);\n }\n\n /* If we sanitized `dirty` in-place, return it. */\n if (IN_PLACE) {\n return dirty;\n }\n\n /* Return sanitized string or DOM */\n if (RETURN_DOM) {\n if (RETURN_DOM_FRAGMENT) {\n returnNode = createDocumentFragment.call(body.ownerDocument);\n\n while (body.firstChild) {\n // eslint-disable-next-line unicorn/prefer-dom-node-append\n returnNode.appendChild(body.firstChild);\n }\n } else {\n returnNode = body;\n }\n\n if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {\n /*\n AdoptNode() is not used because internal state is not reset\n (e.g. the past names map of a HTMLFormElement), this is safe\n in theory but we would rather not risk another attack vector.\n The state that is cloned by importNode() is explicitly defined\n by the specs.\n */\n returnNode = importNode.call(originalDocument, returnNode, true);\n }\n\n return returnNode;\n }\n\n let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;\n\n /* Serialize doctype if allowed */\n if (\n WHOLE_DOCUMENT &&\n ALLOWED_TAGS['!doctype'] &&\n body.ownerDocument &&\n body.ownerDocument.doctype &&\n body.ownerDocument.doctype.name &&\n regExpTest(EXPRESSIONS.DOCTYPE_NAME, body.ownerDocument.doctype.name)\n ) {\n serializedHTML =\n '\\n' + serializedHTML;\n }\n\n /* Sanitize final string template-safe */\n if (SAFE_FOR_TEMPLATES) {\n serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR, ' ');\n serializedHTML = stringReplace(serializedHTML, ERB_EXPR, ' ');\n serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR, ' ');\n }\n\n return trustedTypesPolicy && RETURN_TRUSTED_TYPE\n ? trustedTypesPolicy.createHTML(serializedHTML)\n : serializedHTML;\n };\n\n /**\n * Public method to set the configuration once\n * setConfig\n *\n * @param {Object} cfg configuration object\n */\n DOMPurify.setConfig = function (cfg) {\n _parseConfig(cfg);\n SET_CONFIG = true;\n };\n\n /**\n * Public method to remove the configuration\n * clearConfig\n *\n */\n DOMPurify.clearConfig = function () {\n CONFIG = null;\n SET_CONFIG = false;\n };\n\n /**\n * Public method to check if an attribute value is valid.\n * Uses last set config, if any. Otherwise, uses config defaults.\n * isValidAttribute\n *\n * @param {string} tag Tag name of containing element.\n * @param {string} attr Attribute name.\n * @param {string} value Attribute value.\n * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.\n */\n DOMPurify.isValidAttribute = function (tag, attr, value) {\n /* Initialize shared config vars if necessary. */\n if (!CONFIG) {\n _parseConfig({});\n }\n\n const lcTag = transformCaseFunc(tag);\n const lcName = transformCaseFunc(attr);\n return _isValidAttribute(lcTag, lcName, value);\n };\n\n /**\n * AddHook\n * Public method to add DOMPurify hooks\n *\n * @param {String} entryPoint entry point for the hook to add\n * @param {Function} hookFunction function to execute\n */\n DOMPurify.addHook = function (entryPoint, hookFunction) {\n if (typeof hookFunction !== 'function') {\n return;\n }\n\n hooks[entryPoint] = hooks[entryPoint] || [];\n arrayPush(hooks[entryPoint], hookFunction);\n };\n\n /**\n * RemoveHook\n * Public method to remove a DOMPurify hook at a given entryPoint\n * (pops it from the stack of hooks if more are present)\n *\n * @param {String} entryPoint entry point for the hook to remove\n * @return {Function} removed(popped) hook\n */\n DOMPurify.removeHook = function (entryPoint) {\n if (hooks[entryPoint]) {\n return arrayPop(hooks[entryPoint]);\n }\n };\n\n /**\n * RemoveHooks\n * Public method to remove all DOMPurify hooks at a given entryPoint\n *\n * @param {String} entryPoint entry point for the hooks to remove\n */\n DOMPurify.removeHooks = function (entryPoint) {\n if (hooks[entryPoint]) {\n hooks[entryPoint] = [];\n }\n };\n\n /**\n * RemoveAllHooks\n * Public method to remove all DOMPurify hooks\n *\n */\n DOMPurify.removeAllHooks = function () {\n hooks = {};\n };\n\n return DOMPurify;\n}\n\nexport default createDOMPurify();\n", "/**\n * Docket Alarm\n * Javascript Utility Functions - General to all pages.\n */\n\n// Import DOMPurify. We can use it to sanitize any potentially problematic value, such as html formatted\n// errors from the backend while still being safe from potential injection attacks\nimport * as DOMPurify from 'dompurify';\n\n// configure DOMPurify to accept only certain html elements\nDOMPurify.setConfig({ALLOWED_TAGS: ['h4', 'p', 'div', 'span']});\n\n// Make sure we have console\nif (!window.console) {\n\twindow.console = {};\n\tconsole = {};\n}\nif (!window.console.log) {\n\twindow.console.log = function () { };\n\tconsole.log = function () { };\n}\n\n// TODO: I think we can/should delete this, but haven't tested.\n// Google Analytics - analytics.js\n(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\nm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n})(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\n// Google Tag Manager\n(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\nnew Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],\nj=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=\n'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);\n})(window,document,'script','dataLayer','GTM-KNKW5TW');\n\nga('create', 'UA-2119060-7', 'auto'); // Replace with your property ID.\n\n// Hubspot Logged In User Identification.\nvar _hsq = window._hsq = window._hsq || [];\nif(user_logged_in) {\n\t_hsq.push([\"identify\",{email: user_logged_in}]);\n}\n\n// Create a method for mouseover check\n// use by doing $element.mouseIsOver();\njQuery.fn.mouseIsOver = function () {\n return $(this).parent().find($(this).selector + \":hover\").length > 0;\n};\n\n/**\n * Track an event on Google Analytics.\n * @param category The event Category, a string\n * @param action\n * @param label\n */\nfunction analytics_track(category, action, label) {\n\tif(window.location.origin.search(\"://localhost:8080\") != -1) {\n\t\t// Don't track localhost activity.\n\t\treturn\n\t}\n\tga('send', 'event', category, action, label);\n}\nfunction analytics_dimension(logged_in, org_type, billing_type, paywall_expired) {\n\tga('set', {\n\t\t'dimension1': logged_in && logged_in.length ? \"true\": \"false\",\n\t\t'dimension2': (org_type || '').toString(),\n\t\t'dimension3': (billing_type || '').toString(),\n\t\t'dimension4': paywall_expired.toString(),\n\t});\n\t// Send ipMeta data if we're on production.\n\tif(/localhost:\\d+/.test(window.location.host) == false) {\n\t\tga('require', 'ipMeta', {\n\t\t\tapiKey: 'f9b0200b34ede3d1b4bf5db5d70f57c468c60278e117520204bc549e4e265b1c',\n\t\t\tserviceProvider: 'dimension1',\n\t\t\tnetworkDomain: 'dimension2',\n\t\t\tnetworkType: 'dimension3',\n\t\t});\n\t\tga('ipMeta:loadNetworkFields');\n\t}\n\tga('send', 'pageview');\n}\n\n//////////////////\n// Utility Functions\n\n// Strings\nString.prototype.strip = function() {\n\treturn this.replace(/^\\s+|\\s+$/g,\"\");\n};\nString.prototype.replaceAll = function(to_find, with_this) {\n return this.replace(new RegExp(to_find, 'g'), with_this);\n};\nif (typeof String.prototype.startsWith != 'function') {\n String.prototype.startsWith = function (str) {\n return this.slice(0, str.length) == str;\n };\n}\nif (typeof String.prototype.endsWith != 'function') {\n String.prototype.endsWith = function(suffix) {\n return this.indexOf(suffix, this.length - suffix.length) !== -1;\n };\n}\n\n// Basic string hashing function taken from an SO post.\nfunction gen_string_hash(s) {\n\tvar hash = 0, i, chr, len;\n\tif(s.length == 0){\n\t\treturn hash;\n\t}\n\tfor(i = 0, len = s.length; i < len; i++) {\n\t\tchr = s.charCodeAt(i);\n\t\thash = hash * 31 + chr;\n\t\thash |= 0; // Convert to 32 bit integer\n\t}\n\treturn hash;\n}\n\n// Capitalizes a string\nfunction capitalize(str) {\n\treturn str.replace(/\\w\\S*/g, function _rep_cap(txt){\n\t\treturn txt.charAt(0).toUpperCase() + \n\t\t\ttxt.substr(1).toLowerCase();\n\t\t});\n}\n\n/**\n * Abbreviate the given words based on a simple heuristic.\n **/\nfunction get_abbreviation(t) {\n\t// Split up spaces and punctuation.\n\treturn t.split(/[\\s;:,]+/ig).map(function (e) {\n\t\t// Remove stopwords altogether.\n\t\tif(e.search(/^(a|an|are|as|at|be|but|by|for|if|in|into|is|it|of|on|such|that|the|their|then|there|these|they|this|to|was|with|or|and|will|)?$/ig) == 0) {\n\t\t\treturn \"\";\n\t\t}\n\t\t// Find first vowel.\n\t\tvar vowel = e.search(/[aeiouy]/ig);\n\t\t// Find consonant after vowel.\n\t\tvar consonant = e.substring(vowel).search(/[^aeiouy]/ig);\n\t\treturn consonant == -1 ? e:\n\t\t\tvowel + consonant + 2 >= e.length ? e:\n\t\t\te.substring(0, vowel + consonant + 1) + \".\";\n\t}).filter(function (e) {\n\t\treturn e && e.length;\n\t}).join(\" \");\n}\n\nfunction plural_have(n) {\n\tvar n_s = number_with_commas(n || 0);\n\treturn n_s.toString() + (n == 1 ? \" has\" : \" have\");\n}\n\n/**\n * @returns The plural of the input text if n != 1.\n */\nfunction plural(n, text) {\n\tif(n == 1) {\n\t\t// Not plural, don't pluralize.\n\t\treturn text;\n\t}\n\t// We can probably do a lot better, see:\n\t// https://web2.uvcs.uvic.ca/elc/studyzone/330/grammar/irrplu.htm\n\t// https://stackoverflow.com/questions/18902608/generating-the-plural-form-of-a-noun\n\t// Not sure why \"ing\" is excluded, adding back in \"proceeding_s_\".\n\tif(text.endsWith(\"ed\") || (text.endsWith(\"ing\") && !text.endsWith(\"ceeding\"))) {\n\t\treturn text;\n\t}\n\t// For: bankruptcies, \"entry-->entries\", reply\n\tif(text.endsWith(\"cy\") || text.endsWith(\"party\") || text.endsWith(\"ntry\") || text.endsWith(\"eply\")) {\n\t\treturn text.slice(0, -1) + \"ies\";\n\t}\n\treturn text + (text.endsWith(\"s\") ? \"'\" : \"s\");\n}\nfunction plural_n(n, text) {\n\tvar n_s = number_with_commas(n || 0);\n\treturn n_s.toString() + \" \" + plural(n, text);\n}\n\n\nfunction escape_html(m) {\n if(typeof m == \"number\" || typeof m == \"boolean\") {\n return m.toString();\n } else if(m === null || m === undefined) {\n \tconsole.warn(\"Trying to escape 'null' html.\");\n\t\treturn null;\n\t}\n\treturn m.replace(/&/gm, \"&\"\n\t\t).replace(/\"/gm,\""\").replace(/'/gm, \"'\"\n\t\t).replace(/ 0 ? \" ago\" : \" from now\";\n\tt = Math.abs(t);\n\treturn t < 10 ? \"just now\":\n\t\tt < 50 ? Math.floor(t).toString() + \" seconds\" + sign:\n\t\tt < 120 ? \"a minute\" + sign:\n\t\tt < 60*60 ? Math.floor(t/60).toString() + \" minutes\" + sign:\n\t\tt < 2*60*60 ? \"an hour\" + sign:\n\t\tt < 22*60*60 ? Math.floor(t/60/60).toString() + \" hours\" + sign:\n\t\tt < 2*24*60*60 ? \"a day\" + sign:\n\t\tt < 30*24*60*60 ? Math.floor(t/60/60/24).toString() + \" days\" + sign:\n\t\t\"more than a month\" + sign;\n}\n\nfunction humanize_time_utc(t) {\n\tif(!t) {\n\t\treturn '';\n\t}\n\tif(typeof t == 'string') {\n\t\tt = new Date(t);\n\t}\n\tvar t_time = t.getTime();\n\tif(isNaN(t_time)) {\n\t\treturn '';\n\t}\n\t// UTC to local date time correction.\n t = new Date(t_time - t.getTimezoneOffset()*60*1000);\n return humanize_time(t);\n}\n\n/**\n * By comparing a time value with the current time, we would return human readable time.\n * For eg, while comparing a time value with current date, this function could return values from\n * Year all the way to Seconds. Like \"1 year, 1 month ago\" OR \"1 week, 5 days from now\".\n * The humanize_time function above only returns 1 interval. like \"1 year ago\" or \"1 month from now\"\n * @param value: The datetime value that we want to evaluate\n * @param interval_limit: number of time intervals that we want to return\n * @returns {string|*}\n */\nfunction humanize_time_granular(value, interval_limit){\n\tvar t_c = [\n\t\t{\n\t\t\t\"interval\": \"year\",\n\t\t\t\"calc\": 60 * 60 * 24 * 365\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"month\",\n\t\t\t\"calc\": 60 * 60 * 24 * 30\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"week\",\n\t\t\t\"calc\": 60 * 60 * 24 * 7\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"day\",\n\t\t\t\"calc\": 60 * 60 * 24\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"hour\",\n\t\t\t\"calc\": 60 * 60\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"minute\",\n\t\t\t\"calc\": 60\n\t\t},\n\t\t{\n\t\t\t\"interval\": \"second\",\n\t\t\t\"calc\": 1\n\t\t}\n\t];\n\n\tif (isNaN(Date.parse(value))){\n\t\treturn value;\n\t}\n\tvar date_val = Date.parse(value);\n\tvar date_now = Date.now();\n\tvar delta = date_now - date_val;\n\n\t//The time interval is in past or in future?\n\tvar sign = delta > 0 ? \" ago\" : \" from now\";\n\tdelta = Math.abs(delta);\n\n\t//Convert delta from milliseconds to seconds\n\tdelta = delta / 1000;\n\tvar calculated_time = [];\n\tvar time_val = \"\";\n\t/*\n\tDividing the time delta with time intervals from year to minute and pushing the intervals\n\tto the list for later use\n\t*/\n\tfor(var i =0; i < t_c.length; i ++){\n\t\tif (delta <= 0){\n\t\t\tbreak;\n\t\t}\n\t\tvar interval = t_c[i].interval;\n\t\tvar calc = t_c[i].calc;\n\t\tvar time_calc = Math.floor(delta / calc);\n\t\tif(time_calc > 0) {\n\t\t\ttime_val = time_calc + \" \" + interval + (time_calc > 1 ? \"s\" : \"\");\n\t\t\t//pushing the intervals to list\n\t\t\tcalculated_time.push(time_val);\n\t\t\tdelta = delta - (calc * time_calc);\n\t\t}\n\t}\n\t//This is where we decide how many items from the list we want to OR can display\n\t//to the user. Our default is only 2 intervals but if the time delta is very short then we\n\t//might end up showing only 1 interval\n\tvar calc_limit = Math.min(calculated_time.length, interval_limit || 2);\n\t//Slicing the array and concatinating them to create meaningful time interval\n\tvar time_interval = calculated_time.slice(0, calc_limit).join(', ') + sign;\n\treturn time_interval;\n}\n\n/**\n * Checking if a string is a number is more complex than one would think.\n * @param my_str\n * @returns {boolean} true if the string is represented as a number.\n */\nfunction is_number(my_str) {\n return !isNaN(Number(my_str)) && (Boolean(my_str) || my_str == 0);\n}\n\nfunction number_with_commas(x) {\n\t// Split up decimal.\n var parts = x.toString().split(\".\");\n\tif (typeof x != \"number\" || Math.abs(x) >= 10000) {\n\t\t// Add commas over 10k.\n\t\tparts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\n\t}\n\t// Make at least two decimals.\n\tif(parts.length > 1 && parts[1].length == 1){\n\t\tparts[1] += '0';\n\t}\n return parts.join(\".\");\n}\nfunction number_with_abbrev(x, decimals) {\n\tif(decimals === undefined) {\n\t\tdecimals = 2;\n\t}\n\tvar sign = '';\n\tif(x < 0) {\n\t\tsign = '-';\n\t\tx = -x;\n\t}\n\t// Less than 100k, give the number\n\treturn sign + (\n\t\tx < 1e5 ? number_with_commas(x):\n\t\t// Keep the same sig digits, even though we mess with the decimals.\n\t\tx < 1e6 ? (x/1e3).toFixed(decimals < 3 ? 0 : decimals - 3) + \"k\":\n\t\tx < 1e9 ? (x/1e6).toFixed(decimals) + \"M\":\n\t\t\t\t (x/1e9).toFixed(decimals) + \"B\");\n}\nfunction roundNumber(num, dec) {\n\tif(dec === undefined) {\n\t\tdec = 0;\n\t}\n\treturn Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);\n}\n\n/**\n * Return a Unique ID\n **/\nfunction uniqueid() {\n\t// The first value must be a-zA-Z.\n\tvar possible = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\tvar t = possible.charAt(Math.floor(Math.random() * possible.length));\n\t// After the first, we can include numbers.\n\tpossible += \"0123456789\";\n\tfor(var i = 0; i < 4; i++ ) {\n\t\tt += possible.charAt(Math.floor(Math.random() * possible.length));\n\t}\n\t// If this ID already exists, then try again.\n\treturn $(\"#\" + t).length ? uniqueid() : t;\n}\n\n/**\n * Should be called whenever we're inserting a user editable link directly \n * into the DOM. Prevents security issues.\n */\nfunction sanitize_dom_url(s) {\n\tif(typeof s == \"number\") {\n\t\ts = s.toString();\n\t\tconsole.log(\"sanitize_dom_url given a number: \" + s);\n\t}\n\treturn s.replace(//g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\")\n\t\t.replace(/\\?/g, \"?\");\n}\n\n// Arrays\nfunction sum_array(array, init) {\n\t// Unfortunately, it's impossible to write this as a prototype on IE8.\n\t\n\tvar total = init === undefined ? 0 : init;\n\tfor(var i = 0; i < array.length; i++) {\n\t\ttotal += array[i];\n\t}\n\treturn total;\n}\nArray.max = function( array ){\n return Math.max.apply( Math, array );\n};\n \nArray.min = function( array ){\n return Math.min.apply( Math, array );\n};\n\n/**\n * Dedup an array.\n * @param array An array of anything of the same type (e.g., can't mix ints and strings).\n * @returns {*[]} The deduplicated array in the same order.\n */\nfunction unique_array(array) {\n\tvar o = {}, a = [];\n for (var i = 0; i < array.length; i++) {\n\t\tvar v = array[i];\n\t\tvar k = v === undefined || v === null ? \"\":\n\t\t\tv.toString() + \" :: \" + typeof v;\n\t\tif(o[k] === undefined) {\n\t\t\to[k] = true;\n\t\t\ta.push(v);\n\t\t}\n\t}\n return a;\n}\n\n/**\n * Join jquery objects, and wrap them in an html element. This function\n * makes it easier to combine multiple jquery objects in an array.\n * @param inArray An array of jquery objects\n * @param cls The html class of the parent array.\n * @param tag\t The html tag of the parent array (default: span)\n * @returns {jQuery|HTMLElement}\n */\nfunction join_jquery(inArray, cls, tag) {\n\tif(!tag) {\n\t\ttag = \"span\";\n\t}\n\tvar $html = $(\"<\" + tag + \" class='\" + (cls ||'') + \"'>\").empty();\n\tfor(var i = 0; i < inArray.length; i++) {\n\t\t$html.append(inArray[i])\n\t}\n\treturn $html;\n}\n\n/**\n * Turn any text into an html object, safely escaping any characters.\n * Useful mostly to for shorthand, rather than creating giant strings.\n * @param text\tThe text you want to turn into an object.\n * @param tag\tThe HTML tag that will wrap the text (default: ).\n * @param cls\tThe HTML class of the object.\n * @param data\tA javascript dict of any \"data\" that you want stored in the HTML\n * \t\t\t\tobject. Can be retrieved with jquery .data(..) command.\n * @param attrs\tAny HTML attributes you want applied to the wrapped object.\n * @returns {string}\n */\nfunction wrap_html(text, tag, cls, data, attrs) {\n\tif(!tag) {\n\t\ttag = \"span\"\n\t}\n\tvar extra = cls ? \" class='\" + cls + \"'\" : \"\";\n\tvar k = null;\n\tif(data) {\n\t\tif(typeof data == \"object\") {\n\t\t\tfor(k in data) {\n\t\t\t\tvar val = data[k];\n\t\t\t\tif(val == null) {\n\t\t\t\t\tconsole.warn(\"wrap_html data element is null: \" + k);\n\t\t\t\t} else {\n\t\t\t\t\textra += \" data-\" + escape_html(k) + \"='\" + escape_html(val) + \"'\";\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.warn(\"Data not an object.\");\n\t\t}\n\t}\n\tif(attrs) {\n\t\tfor(k in attrs) {\n\t\t\textra += \" \" + escape_html(k) + \"='\" +\n\t\t\t\tescape_html(attrs[k]) + \"'\";\n\t\t}\n\t}\n\treturn \"<\" + tag + extra + \">\" + text + \"\";\n}\n\n/**\n * See wrap_html for documentation, this function just rearranges some\n * parameters for convenience. Use whichever is easiest.\n * @param tag\n * @param cls\n * @param text\n * @param data\n * @param attrs\n * @returns {string}\n */\nfunction html_tag(tag, cls, text, data, attrs) {\n\treturn wrap_html(text, tag, cls, data, attrs);\n}\n\n/**\n * Given a Docket Alarm search query, wrap the query in parenthesis so it\n * can be nicely used with other search query chunks. For example, given\n * the query \"Hello There\", this will return \"(Hello There)\".\n * @param q The query you want to wrap.\n * @param always Always wrap the query, even if it already has parenthesis.\n * @param wrap_char Specify a character to use for wrapping, default to parenthesis.\n */\nfunction wrap_query(q, always, wrap_char) {\n\tvar need_wrap = always || q.search(/[\\s()]/) != -1;\n\t// Wrap in parenthesis or other char, if needed.\n\treturn need_wrap ? (\n\t\twrap_char ? wrap_char + q + wrap_char : \"(\" + q + \")\"\n\t) : q;\n}\n\n/**\n * Create hierarchical html from a lists of nested dictionaries. Useful for\n * sidebars, action bars, and other items where the html is cumbersome.\n Format:\n // Must send a list of dictionaries\n create_html_from_json([{\n // Each dictionary item can have a type\n type : 'link', // can also be list, see below, horizontal-rule.\n title : 'My Case Alerts', // User displayed title\n icon : 'fas fa-bells', // Font awesome icon classes\n href : '/foo/#analytics', // A link to hyperlink to.\n klass: 'dashboard', // Any additional classes to add\n tip : 'View all of the cases.', // Powertip\n \t\ttip_class: 'class_name', // The class name of the powertip.\n \t\tgutter : { // Element that appears to the right in the gutter on hover.\n \t\t\ttext: 'Element Text',\n \t\t\ticon: 'Element Icon',\n \t\t\ttip: 'Tooltip',\n \t\t\ttag: 'a' // defaults to a.\n \t\t}\n },{\n // Nested lists can be of type \"list\"\n type : 'list',\n title : 'Support',\n // You can style nested lists the same as links\n icon : 'fas fa-question',\n // These nested items work the same way as the others.\n items : [{\n // Select Dropdown\n title : 'Quick Start Guide',\n type : 'select',\n options : [{\n k:\"select option value\",\n v:\"select option user display value\"},\n ...],\n },{\n // ADVANCED Usage, use a script instead of a link.\n type : 'link',\n title : 'Video Tutorials',\n // Instead of a link, this is a script that is run when clicked:\n // CAUTION: This executes a javascript command and may be unsafe\n // if relying on user input. Don't do anything fancy here.\n script : 'show_salesforce_chat_window'\n }}\n ]);\n **/\nfunction create_html_from_json(sidebar) {\n // We allow certain javascript functions to be run, but for security,\n // make sure only certain functions are run. This is more of a double\n // safety measure than a real\n var allowed_functions = /^show_/i;\n function create_icon_html(icon) {\n \tif(!icon) {\n \t\treturn '';\n\t\t}\n\t\tif(icon.search(/^fa[rsdbl]?\\s+fa[rsdbl]?-/) != -1) {\n\t\t\t// Handle font-awesome images.\n\t\t\treturn wrap_html(\"\", \"i\", icon + \" icon\");\n\t\t} else if(icon.search(/\\.(png|jpg)$/) != -1) {\n\t\t\t// Handle normal image icons.\n\t\t\treturn \"\";\n\t\t} else if(icon.search(/\\.svg$/) != -1) {\n\t\t\t// Handle SVG images.\n\t\t\treturn \"\";\n\t\t}\n\t\treturn '';\n\t}\n\tfunction create_tip_data(tip, tip_klass) {\n \treturn tip ? {'powertip': wrap_html(tip, \"span\", \"menutip \" +\n\t\t\t\t(tip_klass || ''))} : {};\n\t}\n function create_one(s, depth) {\n if(!s || s.hide) {\n return null;\n }\n if(s.type == 'horizontal-rule') {\n return wrap_html(\"\", \"hr\", s.klass || \"\");\n }\n // Add an optional icon.\n var icon = create_icon_html(s.icon);\n // The title is HTML, you must do your own HTML safety encoding.\n var title = s.title && s.title.length ?\n wrap_html(s.title, 'div', 'text') : \"\";\n // Titles are h3 or a by default.\n var t_tag = s.title_tag ? s.title_tag : s.type == 'header' ? \"h3\" : \"a\";\n var t_klass = \"title\";\n var count = s.count ? wrap_html(s.count, \"div\", \"count\") : \"\";\n // The gutter goes after the count / floats right.\n var gutter = s.gutter ? wrap_html(\n \tcreate_icon_html(s.gutter.icon) + (s.gutter.text||''),\n\t\t\ts.gutter.tag || \"a\",\n\t\t\t\t\"gutter \" + (s.gutter.klass || \"\"),\n\t\t\tcreate_tip_data(s.gutter.tip, s.gutter.tip_class),\n\t\t\ts.gutter.href ? {href : s.gutter.href}:null\n\t\t):\"\";\n // The title may have a tooltip.\n\t\tvar icon_title = wrap_html(icon + title, 'span',\n\t\t\t'icon_title ' + (s.icon_title_class || ''));\n\n\t\t// Create the data element.\n\t\tvar t_data = create_tip_data(s.tip, s.tip_class) || {};\n\t\tfor(var k in (s.data || {})) {\n\t\t\tt_data[k] = s.data[k]\n\t\t}\n\n var $title = icon.length || title.length ?\n $(wrap_html(icon_title + count, t_tag, t_klass, t_data)): \"\";\n var items = [];\n switch(s.type) {\n\t\t\tcase 'list':\n var opener_cls = \"opener fas fa-chevron-right\";\n if(s.href) {\n \t$title.attr(\"href\", s.href);\n \tif(s.href.search(\"#\") == -1 || s.target) {\n $title.attr(\"target\", s.target || \"_blank\");\n }\n\t\t\t\t}\n items.push($title);\n items.push(wrap_html(\"\", \"i\", opener_cls));\n items.push(join_jquery(s.items.map(function (list_item) {\n return $(\"
  • \")\n\t\t\t\t\t\t.addClass((list_item && list_item.li_klass) || '')\n\t\t\t\t\t\t.append(create_one(list_item, depth + 1));\n }), s.start ? s.start : 'closed', 'ul'));\n break;\n case 'link':\n if(s.href && $title) {\n $title.attr(\"href\", s.href);\n if(s.href.search(\"#\") == -1 || s.target) {\n $title.attr(\"target\", s.target || \"_blank\");\n }\n } else if(s.script && window[s.script] &&\n allowed_functions.test(s.script)) {\n // Allow scripts to be run.\n $title.click(function () {\n window[s.script].apply(s.arguments || []);\n })\n } else if(s.script) {\n console.error(\"Using banned script: \" + s.script.toString());\n }\n items.push($title);\n break;\n case 'select':\n \titems.push($title);\n items.push(join_jquery((s.options || []).map(function _opt(o) {\n return \"\";\n }), \"\", \"select\"));\n break;\n\t\t\tcase 'header':\n\t\t\t\titems.push($title);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tconsole.error(\"create_html_from_json Unknown type: \" + s.type);\n }\n items.push(gutter);\n return join_jquery(items, s.type + \" \" + (s.klass ||\"\"),\n \"div\");\n }\n return join_jquery(sidebar.map(function (s) {\n return create_one(s, 0);\n }), \"top\", \"div\");\n}\nvar _default_json_html_tip_settings = {\n\tsmartPlacement: false,\n\tplacement : 'ne',\n\tpopupClass : 'small_powertip',\n}\nfunction _add_collapse_expanders($object, toggle) {\n\t\tvar ptip = {\n\t\tpopupClass: 'motion_filter_tip small_powertip',\n\t\tsmartPlacement: true,\n\t}\n\t$(\"
    \").addClass(\"expanders\")\n\t\t.append($(\"\")\n\t\t\t.attr('title', 'Collapse All')\n\t\t\t.addClass(\"fad fa-chevrons-up\").click(function () {\n\t\t\t\ttoggle($object.find(\".opener:visible\"), false);\n\t\t\t})).powerTip(ptip)\n\t\t.append($(\"\")\n\t\t\t.attr('title', 'Expand All')\n\t\t\t.addClass(\"fad fa-chevrons-down\").click(function () {\n\t\t\t\ttoggle($object.find(\".opener\"), true);\n\t\t\t})).powerTip(ptip)\n\t\t// Insert directly into the JSON html Chooser. We may want to generalize this.\n\t\t.prependTo($object.find(\".top > .list\"));\n}\n/**\n * Setup load/hide toggles on object created from create_html_from_json.\n * @param $object The top level\n * @param get_track_category The google analytics category to use for tracking\n * clicks. Can be a string, or a function that returns a string. If null, no tracking.\n * @param tip_settings\n * @param add_expanders True to add expand all/collapse all.\n * @returns {(function(*): (boolean|undefined))|*} Return a function that can toggle on/off a given list.\n */\nfunction json_html_expander($object, get_track_category, tip_settings, add_expanders) {\n\tif(!tip_settings) {\n\t\ttip_settings = _default_json_html_tip_settings;\n\t}\n\t// If a string, then convert to a function.\n\tvar get_track =\n\t\t!get_track_category || typeof get_track_category == \"string\"?\n\t\t\tfunction _def_get_track() { return get_track_category; }:\n\t\t\tget_track_category;\n\n\tfunction toggle($this, force_val) {\n\t\t// We clicked on the opener or title. Find the title.\n\t\tvar $parent = $this.parent();\n\t\tvar $title = $parent.find(\"> .title\");\n\t\t// Lists can have links too, make sure we're the target.\n\t\tvar href = $this.attr(\"href\");\n\t\tif(href && href.length && $this[0].tagName == \"A\") {\n\t\t\t// Bubble up to the higher level click handler.\n\t\t\treturn true;\n\t\t}\n\t\t// Set the title to open.\n\t\t$title.toggleClass(\"open\", force_val);\n\t\t// Set the\n\t\t$parent.find(\"> ul\").toggleClass(\"closed\",\n\t\t\tforce_val === true ? false : force_val === false ? true : undefined);\n\t\tvar $op = $title.find(\".opener\");\n\t\t$op.hasClass(\"fa-plus\") || force_val == true?\n\t\t\t$op.removeClass(\"fa-plus\").addClass(\"fa-minus\"):\n\t\t$op.hasClass(\"fa-minus\") || force_val == false ?\n\t\t\t\t$op.removeClass(\"fa-minus\").addClass(\"fa-plus\"): '';\n\t\tvar track_category = get_track();\n\t\tif(track_category) {\n\t\t\tanalytics_track(track_category, \"Toggle\", $title.text());\n\t\t}\n\t}\n\tif(add_expanders) {\n\t\t_add_collapse_expanders($object, toggle)\n\t}\n\n $object.find(\".list > .title, .list > .opener\").click(function _opener_clicked() {\n return toggle($(this));\n }).end().find(\".link > .title\").click(function _title_clicked() {\n \tvar track_category = get_track();\n if(track_category) {\n analytics_track(track_category, \"Click\", $(this).text());\n }\n return true;\n }).end().find(\".title[data-powertip]\").powerTip(tip_settings).end();\n\treturn toggle;\n}\n\n/*\n* A very basic markdown renderer. Supports italic, bold, and lists.\n */\nfunction create_html_from_markdown(t) {\n\tvar re_bold = /\\*\\*([^\\s*][^*]*[^\\s*])\\*\\*(?=[^\\w]|$)/ig;\n var re_italic = /\\*([^\\s*][^*]*[^\\s*])\\*(?=[^\\w]|$)/ig;\n\tvar re_headings = /(^|\\r?\\n)(#+)\\s+([^\\r\\n]*)/ig;\n\tvar re_list = /(^|\\r?\\n)([ \\t]*)([\\-*])\\s+([^\\r\\n]*)/ig;\n\n\tt = t.replace(re_bold, \"$1\");\n\tt = t.replace(re_italic, \"$1\");\n\tt = t.replace(re_headings, function _rep_headings(m, p1, p2, p3) {\n\t\tvar level = p2.length;\n\t\treturn p1 + \"\" + p3 + \"\";\n\t});\n\tt = t.replace(re_list, function _rep_list(m, p1, spaces, dot, rest) {\n\t\t// Create the list dot style.\n\t\tvar style = 'list-style-type:' + (dot=='*' ? \"disc\" : \"square\")+\";\";\n\t\t// Figure out the indentation and add the appropriate style.\n\t\tvar num_indent = (spaces.length || 0) + 1;\n\t\tstyle += 'margin-left: ' + (num_indent * 20) + \"px;\";\n\t\t// Create the list item\n\t\tvar li = wrap_html(rest, \"li\", null, null, { style : style });\n\t\t// Create the ul element.\n\t\treturn wrap_html(li, \"ul\");\n\t});\n\tt = t.replace(/\\u000A|\\n/ig, \"
    \");\n\treturn t;\n}\n\n//////////////////////////////////\n// Javascript Events\nfunction makeCustomEvent(event_identifier) {\n\t// Create the event.\n\tvar event = document.createEvent('Event');\n\t// Make the event bubble-able and cancel-able.\n\tevent.initEvent(event_identifier, true, true);\n\t// Return it\n\treturn event;\n}\nfunction onEvent(event_identifier, listener) {\n\tdocument.addEventListener(event_identifier, listener, false);\n}\nfunction dispatchEvent(event) {\n\treturn document.dispatchEvent(event);\n}\n\n/**\n * Open a URL in the browser. Accepts a true/false to open in a new tab,\n * simulating \"CTRL\" click functionality.\n * @param url\t\tThe URL you want to open.\n * @param ctrlKey\tTrue to open in new tab.\n */\nfunction open_url(url, ctrlKey) {\n\tif(ctrlKey) {\n\t\t// Support opening in new tab.\n\t\tvar win = window.open(url, '_blank');\n\t\t// null check due to aggressive pop-up blockers\n\t\tif(win) win.focus();\n\t} else {\n\t\t// Open normally.\n\t\twindow.location = url;\n\t}\n}\n\n// Debounce: If a function is called twice in a timeout, it executes only once.\nfunction debouncer( func , timeout, retval ) {\n var timeoutID = null;\n return function () {\n var scope = this , args = arguments;\n if(timeoutID) {\n \tclearTimeout( timeoutID );\n\t }\n timeoutID = setTimeout( function () {\n func.apply( scope , Array.prototype.slice.call( args ) );\n }, timeout || 200);\n\t if(retval != undefined) {\n\t\t if (typeof retval == 'function') {\n\t\t\t return retval.apply( scope , Array.prototype.slice.call(args));\n\t\t }\n\t\t return retval;\n\t }\n }\n}\n\n// Regex\nRegExp.escape= function(s) {\n return s ? s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&') : s;\n};\n\n\n// Tables\n/**\n * Create an html table\n * array: an array of objects, representing the rows.\n * headings: a list of strings representing the headings.\n * func: Function that takes (object, object_row_index) as input. Returns a\n * list of cells.\n **/\nfunction create_html_table(array, headings, func) {\n\tif(!func) {\n\t\tfunc = function (x, x_i) { return x; };\n\t}\n\tvar headingclass = headings.map(function (th) {\n\t\treturn th.toLowerCase().replace(/[^\\w_\\- ]+/ig, '')\n\t\t\t.strip()\n\t\t\t.replaceAll(\" \", \"_\");\n\t});\n\tvar table = \"\" + headings.map(function (th, th_i) {\n\t\treturn wrap_html(th, \"th\", headingclass[th_i]);\n\t}).join(\"\") + \"\";\n\tfor(var i = 0; i < array.length; i++) {\n\t\tvar td_vals = func(array[i], i);\n\t\ttable += \"\" + td_vals.map(function (td, td_i) {\n\t\t\tvar colspan = td_i == td_vals.length -1 && td_vals.length < headings.length ?\n\t\t\t\t' colspan=\"' + (headings.length - td_vals.length + 1) + '\"':'';\n\t\t\treturn \"\";\n\t\t}).join(\"\") + \"\";\n\t}\n\treturn table + \"
    \" + td + \"
    \";\n}\n\n// DOM Classes\nfunction getClassList(el) {\n\t// The classList attribute is not always available on SVG elements.\n\treturn (el.getAttribute(\"class\") || '').split(/\\s+/);\n}\nfunction addClass(el, cls) {\n\tcls = cls.strip();\n\tvar has_class = getClassList(el).filter(function (c) { \n\t\treturn c.strip() == cls; \n\t}).length > 0;\n\tif(has_class) {\n\t\treturn;\n\t}\n\tvar new_class = (el.getAttribute(\"class\") || '') + ' ' + cls;\n\treturn el.setAttribute(\"class\", new_class);\n}\nfunction removeClass(el, cls) {\n\tcls = cls.strip();\n\tvar classList = getClassList(el).filter(function (c) { \n\t\treturn c.strip() != cls; \n\t});\n\treturn el.setAttribute(\"class\", classList.join(\" \"));\n}\nfunction toggleClass(el, cls, val) {\n\tcls = cls.strip();\n\tif(val === undefined) {\n\t\tval = getClassList(el).filter(function (c) { \n\t\t\treturn c.strip() == cls; \n\t\t}).length == 0;\n\t}\n\tif (val){\n\t\taddClass(el, cls);\n\t} else {\n\t\tremoveClass(el, cls);\n\t}\n}\n\nfunction getParentElement(el) {\n\t// Parent Element is not available on SVG elements.\n\treturn el.parentElement ? el.parentElement : el.parentNode;\n}\n\n// Localstorage\n// If the browser does not support real session storage, fake it with a global.\nvar _backup_localstorage = {};\nfunction _get_store(persistTabs) {\n\t// Use the naive implementation that uses a global variable.\n\tif(typeof(Storage) === \"undefined\") {\n\t\treturn _backup_localstorage;\n\t}\n\tif(persistTabs) {\n\t\t// Use local storage that works across tabs / windows, if it exists.\n\t\ttry {\n\t\t\treturn localStorage;\n\t\t} catch (e) {\n\t\t\t// We may not have localStorage in an iFrame.\n\t\t\tconsole.info(\"No localStorage, using sessionStorage\");\n\t\t}\n\t}\n\ttry {\n\t\t// Use session storage, that persists only in this tab / window.\n\t\treturn sessionStorage;\n\t} catch (e) {\n\t\tconsole.warn(\"No Session Storage, using backup.\");\n\t}\n\treturn _backup_localstorage;\n}\n/**\n * Save a key, value pair for expire_seconds long. \n *\tkey: \t\t\t\tA string.\n * \tval: \t\t\t\tMay be an object as long as it can be stringified.\n * \texpire_seconds: \tIf null, save it for the entire session duration. \n * persistTabs:\t\tIf True, data will be saved across tabs.\n */\nfunction sessionSet(key, val, expire_seconds, persistTabs) {\n\tval = {\n\t\texpire : expire_seconds ? \n\t\t\t(new Date()).getTime() + 1000*expire_seconds : null,\n\t\tval : val\n\t};\n\tvar stringified = JSON.stringify(val);\n\tif (val && (!stringified || stringified.search('\"val\"') == -1)) {\n\t\tconsole.log(\"Could not save '\" + key + \"' in local cache.\");\n\t\treturn false;\n\t}\n\t\t\n\t// Save the value.\n\ttry {\n\t\t_get_store(persistTabs)[key] = stringified;\n\t} catch (err) {\n\t\tconsole.warn(\"Cannot save session data: \" + err);\n\t\treturn false;\n\t}\n\treturn true;\n}\nfunction sessionGet(key, persistTabs) {\n\tvar val = _get_store(persistTabs)[key];\n\tif (!val) {\n\t\treturn null;\n\t}\n\ttry {\n\t\tval = JSON.parse(val);\n\t\tif(val.expire === undefined) {\n\t\t\treturn null;\n\t\t}\n\t\tif (val.expire && (new Date()).getTime() > val.expire) {\n\t\t\t// Expired.\n\t\t\treturn null;\n\t\t}\n\t\treturn val.val;\n\t} catch(err) {\n\t\tconsole.log(\"Could not load \" + key + \": \" + err);\n\t\treturn null;\n\t}\n}\nfunction sessionClear(key, persistTabs) {\n\tif(typeof(Storage) === \"undefined\") {\n\t\tif(key) {\n\t\t\tif(_backup_localstorage[key]) {\n\t\t\t\tdelete _backup_localstorage[key];\n\t\t\t}\n\t\t} else {\n\t\t\t_backup_localstorage = {};\n\t\t}\n\t} else {\n\t\tif(key) {\n\t\t\tsessionSet(key, null, 1, persistTabs);\n\t\t} else {\n\t\t\tif(localStorage && (persistTabs || persistTabs === undefined)) {\n\t\t\t\tlocalStorage.clear();\n\t\t\t}\n\t\t\tif(sessionStorage &&\n\t\t\t\t\t(persistTabs === false || persistTabs === undefined)) {\n\t\t\t\tsessionStorage.clear();\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Live loading\nfunction load_css(link) {\n // Load fontawesome, used by custom analytics.\n $('')\n\t\t.appendTo('head')\n\t\t.attr({\n\t\t\ttype: 'text/css',\n\t\t\trel: 'stylesheet',\n\t\t\tcrossorigin: \"anonymous\",\n\t\t\thref: link\n\t});\n}\n\nvar has_drawing_js = false;\nvar _getting_drawing = false;\n/**\n * A big complex function for loading a few js files. This should be\n * re-implemented using promises.\n * @param done\n * @param failed\n */\nfunction load_drawing_js(done, failed) {\n\tif(has_drawing_js) {\n\t\tif(done) {\n\t\t\tsetTimeout(done, 0);\n\t\t}\n\t\treturn;\n\t}\n\tif(_getting_drawing) {\n\t\tfunction wait_complete() {\n\t\t\tif(_getting_drawing) {\n\t\t\t\tsetTimeout(wait_complete, 100);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif(has_drawing_js && done) {\n\t\t\t\tdone();\n\t\t\t} else if(!has_drawing_js && fail) {\n\t\t\t\tfail(this);\n\t\t\t}\n\t\t}\n\t\tsetTimeout(wait_complete, 100);\n\t\treturn;\n\t}\n\n\t_getting_drawing = true;\n\tvar files_to_get = [\n\t\t\"/site_media/d3.v3.min.js\",\n\t\t\"/site_media/d3.layout.min.js\",\n\t\t\"/site_media/rickshaw.js\",\n\t\t\"/site_media/sankey.js\", // This is only needed for sankey charts.\n\t\t\"/site_media/colorbrewer.js\",\n\t\t\"/site_media/drawing.js\"];\n\tfunction get_script(file_index) {\n\t\tvar file = files_to_get[file_index];\n\t\t$.getScript(file, function _get_script_success() {\n\t\t\tif(file_index >= files_to_get.length - 1) {\n\t\t\t\t// Got all scripts.\n\t\t\t\thas_drawing_js = true;\n\t\t\t\t_getting_drawing = false;\n\t\t\t\tif(done) {\n\t\t\t\t\tdone();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Get the next script, only after finishing this one.\n\t\t\t\tget_script(file_index+1);\n\t\t\t}\n\t\t}).fail(function ( jqxhr, settings, exception ) {\n\t\t\t_getting_drawing = false;\n\t\t\tif(failed) {\n\t\t\t\tfailed(this);\n\t\t\t}\n\t\t});\n\t}\n\t// Get the scripts through a semi-recursive function.\n\tget_script(0);\n}\n\n/////////////////////\n// Inputs and Textarea\n// Set's the cursor in an input or textarea.\n$.fn.selectRange = function(start, end) {\n if(end === undefined) {\n end = start;\n }\n return this.each(function() {\n if('selectionStart' in this) {\n this.selectionStart = start;\n this.selectionEnd = end;\n } else if(this.setSelectionRange) {\n this.setSelectionRange(start, end);\n } else if(this.createTextRange) {\n var range = this.createTextRange();\n range.collapse(true);\n range.moveEnd('character', end);\n range.moveStart('character', start);\n range.select();\n }\n });\n};\n\n// Windowing\nfunction get_window_height() {\n\t// Returns browser window height\n\treturn (typeof window.innerHeight != 'undefined' ? window.innerHeight : document.body.offsetHeight);\n}\n\nfunction getScrollbarWidth(doc) {\n\t// Return the width of a scrollbar.\n\tif(!doc) {\n\t\tdoc = document.body;\n\t}\n\tvar cached_width = doc.getAttribute('data-ScrollBarWidth');\n\tif (cached_width) {\n\t\treturn cached_width;\n\t}\n\tvar outer = document.createElement(\"div\");\n\touter.style.visibility = \"hidden\";\n\touter.style.width = \"100px\";\n\touter.style.msOverflowStyle = \"scrollbar\"; // needed for WinJS apps\n\n\tdoc.appendChild(outer);\n\n\tvar widthNoScroll = outer.offsetWidth;\n\t// force scrollbars\n\touter.style.overflow = \"scroll\";\n\n\t// add innerdiv\n\tvar inner = document.createElement(\"div\");\n\tinner.style.width = \"100%\";\n\touter.appendChild(inner); \n\n\tvar widthWithScroll = inner.offsetWidth;\n\n\t// remove divs\n\touter.parentNode.removeChild(outer);\n\tcached_width = widthNoScroll - widthWithScroll\n\tdoc.setAttribute('data-ScrollBarWidth', cached_width)\n\treturn cached_width;\n}\n\n/////////////////////\n// User Messages and Alerts.\nfunction init_powertip_defaults() {\n\t$.fn.powerTip.defaults.fadeInTime = 100;\n\t$.fn.powerTip.defaults.fadeOutTime = 50;\n\t$.fn.powerTip.defaults.closeDelay = 50;\n}\n\nvar current_loader_show_type = null;\nfunction show_success_usermsg(in_html, show_type) {\n\tanalytics_track(\"user_msg\", \"info\", show_type || in_html);\n\tshow_confirm_usermsg({\n\t\ttitle : 'Success',\n\t\tsubtitle : in_html,\n\t\thide_cancel : true,\n\t\tdialog_class : 'membership_success'\n\t});\n}\n\n/**\n * Show the user an error message.\n * @param in_html\tThe HTML you want to display to the user.\n * @param show_type\tA unique ID distinguishing this error message from others.\n * @param $target\t(Optional) If you want to display the error near a\n * \t\t\t\t\tbutton or other HTML element, you can specify it here.\n * \t\t\t\t\tOtherwise it will display at the top of the page.\n */\nfunction show_error_usermsg(in_html, show_type, $target) {\n\tshow_usermsg(in_html, $target || null, \"error\");\n\tcurrent_loader_show_type = show_type;\n}\n\n/**\n * Show a message to the user that dissappears after a set time period.\n * @param text\t\tThe HTML you want to display.\n * @param $target\t(Optional) If you want to display the error near a\n * \t\t\t\t\tbutton or other HTML element, you can specify it here.\n * \t\t\t\t\tOtherwise it will display at the top of the page.\n * @param level\t\tCan be: \"error\", \"warning\", \"info\". (default: \"info\")\n * @param timeout\t(Optional) How long to display (defaults 5 to 8 Secs).\n * @returns {jQuery|HTMLElement}\n */\nfunction show_usermsg(text, $target, level, timeout) {\n\tanalytics_track(\"user_msg\", level || \"info\", text);\n\tlevel = level ? level.toLowerCase() : 'info';\n\tvar body_tip = false;\n\tif($target && $target.parents(\"#powerTip\").length) {\n\t\t// The target is within a powerTip, we need to show a body tip.\n\t\t$target = null;\n\t}\n\tif(!$target) {\n\t\t$target = $(\"body\");\n\t\tbody_tip = true;\n\t}\n\t// A unique identifier for this instance.\n\tvar tip_id = Math.random().toString();\n\t// Wrap the tip if we doing an body tip for alignment.\n\tvar tip_html = body_tip ? \n\t\t\"Close \" +\n\t\t\"
    \" + text + \"
    \" : text;\n\tvar all_classes = 'usermsg_tip ';\n\tif(level) {\n\t\tall_classes += level + ' ';\n\t}\n\tif(body_tip) {\n\t\tall_classes += \"noarrows bodytip \";\n\t}\n\t// Timeout defaults.\n\tif(!timeout) {\n\t\t// Timeout defaults depend on the level.\n\t\ttimeout = level == 'error' ? 8000:\n\t\t\tlevel == 'warning' ? 5000:\n\t\t\tlevel == 'info' ? 5000:\n\t\t\t5000;\n\t}\n\t\n\t// Set the tip data and id.\n\t$target.data('tip_id', tip_id).data('powertip', tip_html);\n\t\n\tif(body_tip && $(\"#powerTip.bodytip\").is(\":visible\")) {\n\t\t// If a tip is already open, just change it.\n\t\t$(\"#powerTip\").html(tip_html);\n\t} else {\n\t\t// Hide the existing tooltip. WARNING: if there is a fadeOutTime,\n\t\t// it won't hide immediately, and the tooltips won't hide.\n\t\t$.powerTip.hide(null, true);\n\n\t\t// Create a new tip.\n\t\t$target.powerTip({\n\t\t\tpopupClass : all_classes,\n\t\t\tmanual: true, // Manually close it.\n\t\t\tfadeOutTime: 500,\n\t\t\tsmartPlacement: !body_tip,\n\t\t\tplacement : body_tip ? 'n' : undefined,\n\t\t\tmouseOnToPopup : true\n\t\t});\n\t\t$.powerTip.show($target);\n\t\t$.powerTip.reposition($target);\n\t}\n\t// $.powerTip.reposition($target);\n\tvar timer = setTimeout(function () {\n\t\tvar $usertip = $(\"#powerTip.usermsg_tip\");\n\t\tif($usertip.is(\":visible\") && $target.data('tip_id') == tip_id) {\n\t\t\t$usertip.hide('slow');\n\t\t\t$.powerTip.hide($target);\n\t\t}\n\t}, timeout);\n\tif(body_tip) {\n\t\tvar left_right = $target.width() > 1080 ? \n\t\t\t($target.width() - 1080) / 2 : 5;\n\t\t$(\"#powerTip\").css({\n\t\t\t'top':\"5px\",\n\t\t\t'left':left_right,\n\t\t\t'right':left_right,\n\t\t}).find(\"a.close\").click(function () {\n\t\t\t$.powerTip.destroy($target);\n\t\t\t$(\"#powerTip.usermsg_tip\").hide('slow');\n\t\t\tclearTimeout(timer);\n\t\t\treturn false;\n\t\t});\n\t}\n\t// Manually close the tip after a second.\n\treturn $target;\n}\n\n/**\n * Easily show a user a yes/no modal dialog box. But you can customize to\n * show any type of modal form.\n * @param options\tA dictionary with the following format: {\n * title: \t\tThe title you want to display on the dialog.\n * subtitle: \tThe subtitle you want to display on the dialog.\n * okay: \t\tA function that is called when the user hits \"okay\"\n * cancel:\t\tA function that is called when the user hits \"cancel\"\n * okay_msg:\tThe text of the okay button (default: \"Okay\")\n * cancel_msg:\tThe text of teh cancel button (default: \"Cancel\").\n * form:\t\tA list that you can use to add further options to\n * \t\t\t\tthe yes/no dialog. Each element in the list is a\n * \t\t\t\t\tdictionary representing a form element. It has the form: {\n * \t\t\t\t\t type: Can be: select, checkbox, password, or text.\n * \t\t\t\t\t name: The html name attribute of the form element.\n * \t\t\t\t\t label: (Optional) A label to show near the form\n * \t\t\t\t\t \t\t element, or for text types, as placeholder.\n * \t\t\t\t\t \tpowertip: Any powertip text you want to display when\n * \t\t\t\t\t \t\t\t the user hovers over the form element.\n * \t\t\t\t\t \tchecked: true/false for checkbox types.\n * \t\t\t\t\t \tselect: A list of {value:'',label:''} objects to\n * \t\t\t\t\t \t\t\t show as options for select types.\n * \t\t\t\t\t}\n * dialog_options: This function calls another more general function\n * \t\t\t\t borderless_dialog. You can pass an object in here\n * \t\t\t\t that will have further control over the dialog.\n * }\n * @returns {*}\n */\nfunction show_confirm_usermsg(options) {\n\tif(options === undefined || options === null) {\n\t\toptions = {};\n\t}\n\tvar title = options.title || \"\";\n\tvar subtitle = options.subtitle || \"\";\n\tvar okay = options.okay || function () { };\n\tvar cancel = options.cancel || function () { };\n\tvar okay_msg = options.okay_msg || \"Okay\";\n\tvar cancel_msg = options.cancel_msg || \"Cancel\";\n\tvar hide_okay = options.hide_okay ? true : false;\n\tvar hide_cancel = options.hide_cancel ? true : false;\n\tvar dialog_class = options.dialog_class;\n\tvar dialog_options = options.dialog_options || {};\n\tvar form = options.form || [];\n\tvar $warning_msg = $(\"
    \" +\n\t\t\"
    \" +\n\t\t\"
    \" +\n\t\t\"
    \" +\n\t\t\"Okay\" +\n\t\t\"Cancel
    \");\n\n\tfunction _one_widget(f) {\n\t\tvar ftype = f.type ? f.type :\n\t\t\tf.name.search(/password/ig) == -1 ? 'text' : 'password';\n\t\tvar form_html = \"
    \";\n\t\tvar name = escape_html(f.name);\n\t\tif(ftype == \"select\") {\n\t\t\tif(f.label) {\n\t\t\t\t// Give the label a class so we can style it.\n\t\t\t\tform_html += \"\";\n\t\t\t}\n\t\t\tform_html += \"\";\n\t\t} else if(ftype == 'checkbox') {\n\t\t\tvar uid = uniqueid();\n\t\t\tform_html += \"\";\n\t\t\tif(f.label) {\n\t\t\t\tform_html += \"\";\n\t\t\t}\n\t\t} else if(ftype == 'textarea') {\n\t\t\tform_html += '';\n\t\t} else if(ftype == 'link') {\n\t\t\t// Build the href value.\n\t\t\tvar href = f.href ? 'href=\"' + f.href + '\" ' : '';\n\t\t\tform_html += '' + f.a + \"\"\n\t\t} else {\n\t\t\tform_html += '';\n\t\t}\n\t\tform_html += \"
    \";\n\t\treturn form_html;\n }\n\n\t// We need a hidden submit button so \"enter\" works.\n\tvar form_html = '' +\n\t\t// Add all the widgets.\n\t\tform.map(_one_widget).join(\"\");\n\n\tif(dialog_class){\n\t\tif(!dialog_options.classes){\n\t\t\tdialog_options.classes = {};\n\t\t}\n\t\tdialog_options.classes[\"ui-dialog\"] = dialog_class;\n\t}\n\tvar $dialog = borderless_dialog($warning_msg, dialog_options);\n\t$dialog.dialog(\"open\");\n\t\n\tfunction do_close($diag, func) {\n\t\ttry {\n\t\t\tif(!$dialog.dialog(\"isOpen\"))\n\t\t\t\treturn;\n\t\t} catch(e) {\n\t\t\t// This is an old handler for a previous dialog.\n\t\t\treturn;\n\t\t}\n\t\tif(func) {\n\t\t\tvar $form = $dialog.find(\"form\");\n\t\t\tvar vals = $form.serializeArray();\n\t\t\tvar vals_d = {};\n\t\t\tfor(var v_i=0; v_i>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{return(x^1073741824^F^H)}}else{return(x^F^H)}}function r(d,F,k){return(d&F)|((~d)&k)}function q(d,F,k){return(d&k)|(F&(~k))}function p(d,F,k){return(d^F^k)}function n(d,F,k){return(F^(d|(~k)))}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F)}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F)}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F)}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F)}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H>>29;return aa}function B(x){var k=\"\",F=\"\",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F=\"0\"+G.toString(16);k=k+F.substr(F.length-2,2)}return k}function J(k){k=k.replace(/rn/g,\"n\");var d=\"\";for(var F=0;F127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)|224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128)}}}return d}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P\\n\");\n\t\t\tdata.push({\n\t\t\t\tname : 'message_html',\n\t\t\t\tvalue : html_message,\n\t\t\t});\n\t\t\t// Add a simple length. Serves to prevent bots from submitting the\n\t\t\t// form w/o at least looking at the js. For spammers that see this,\n\t\t\t// note that this messsage just goes to an internal folder. We won't\n\t\t\t// pass it on to the recepient in the email.\n\t\t\tdata.push({\n\t\t\t\tname : 'message_html_len',\n\t\t\t\tvalue : html_message.length,\n\t\t\t});\n\t\t\t\n\t\t\t$form\n\t\t\t\t.find(\".loader\").show().end()\n\t\t\t\t.find(\"input[type=submit]\").addClass(\"disabled\");\n\t\t}, \n\t\tsuccess: function(data, statusText, xhr, $form) {\n\t\t\tvar $btn = $form.find(\".loader\").hide().end()\n\t\t\t\t.find(\"input[type=submit]\").removeClass(\"disabled\");\n\t\t\t// Give the user a message\n\t\t\tif(data.success) {\n\t\t\t\tif(redirect_url && typeof redirect_url == \"function\") {\n\t\t\t\t\tredirect_url(data, $form);\n\t\t\t\t} else if(redirect_url) {\n\t\t\t\t\twindow.location = redirect_url;\n\t\t\t\t} else {\n\t\t\t\t\t$form.html(\"

    Thank you.
    We'll \" +\n\t\t\t\t\t\t\"get back to you shortly.

    \");\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tshow_error_usermsg(data.error, \"email_support\", $btn);\n\t\t\t}\n\t\t}, \n\t\ttype: \t'post', // Can override method attribute\n\t\tdataType: 'json' // 'xml', 'script', or 'json' (expected server response type) \n\t})\n\t.find(\"input\").each(function () {\n\t\t// The validation engine requires everything to have an id.\n\t\tvar $this = $(this);\n\t\tif(!$this.attr('id')) {\n\t\t\t!$this.attr('id', \"more_info_email_\" + $this.attr('name'))\n\t\t}\n\t}).end()\n\t.validationEngine({\n\t\tonValidationComplete:function(form, valid) {\n\t\t\tif(valid)\n\t\t\t\treturn true;\n\t\t\tsetTimeout(function () {\n\t\t\t\tform.validationEngine('hideAll');\n\t\t\t}, 3000);\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * Setup a jquery object so when a user clicks on it, it copies something to\n * the clipboard. Does not work on Safari mobile, but degrades nicely.\n * @param target_selector\tThe selector or jquery object for the items that\n * \t\t\t\t\t\t\tyou want to be copyable.\n * @param get_text\t\t\tA function that takes the jquery object\n * \t\t\t\t\t\t\tas input and return the text you want copied.\n * @param copied_message\tA message to display when copy completes\n * \t\t\t\t\t\t\t(default: Copied!).\n */\nfunction copy_to_clipboard(target_selector, get_text, copied_message) {\n\tvar $targets = $(target_selector);\n\tfunction show_tip($targ, text) {\n\t\t// Show a tooltip that we've completed the copy.\n\t\t$targ.data('powertip', text).powerTip({\n\t\t\tmanual: true, // We'll manually close it.\n\t\t\tfadeOutTime: 500,\n\t\t\tsmartPlacement: true,\n\t\t});\n\t\t$.powerTip.show($targ);\n\t\t// Manually close the tip after a second.\n\t\tsetTimeout(function (){ \n\t\t\t$.powerTip.hide($targ);\n\t\t\t// Remove the data so they don't see it when they hover again.\n\t\t\t$targ.data('powertip', '');\n\t\t}, 1500);\n\t}\n\t$targets.click(function () {\n\t\tvar $targ = $(this);\n\t\tvar text = get_text($targ);\n\t\tcopy_to_clipboard_inner(text, function () {\n\t\t\tshow_tip($targ,copied_message || \"Copied!\");\n\t\t}, function () {\n\t\t\tshow_tip($targ,\"Press CTRL+C to Finish Copying\")\n\t\t});\n\t\treturn false;\n\t});\n}\n\n/**\n * Browser compatible way to copy text to clipboard.\n * \t\tSee https://stackoverflow.com/a/30810322/234270\n * @param text The text you want to copy.\n * @param success Success callback function.\n * @param error Error callback function.\n */\nfunction copy_to_clipboard_inner(text, success, error) {\n\tif (navigator.clipboard) {\n\t\tnavigator.clipboard.writeText(text).then(function () {\n\t\t\tsuccess();\n\t\t}, function (err) {\n\t\t\terror(err);\n\t\t});\n\t} else {\n\t\t// Fallback method.\n\t\tvar textArea = document.createElement(\"textarea\");\n\t\ttextArea.value = text;\n\n\t\t// Avoid scrolling to bottom\n\t\ttextArea.style.top = \"0\";\n\t\ttextArea.style.left = \"0\";\n\t\ttextArea.style.position = \"fixed\";\n\n\t\tdocument.body.appendChild(textArea);\n\t\ttextArea.focus();\n\t\ttextArea.select();\n\n\t\ttry {\n\t\t\tvar successful = document.execCommand('copy');\n\t\t\tsuccessful ? success() : error(\"Did not copy.\");\n\t\t} catch (err) {\n\t\t\terror(err);\n\t\t}\n\t\tdocument.body.removeChild(textArea);\n\t}\n}\n\n\n/**\n * Find the first parent with the given tag name\n */\nfunction get_parent_with_tag(child, tag) {\n\tif(!child || child.length == 0)\n\t\treturn null;\n\ttag = tag.toLowerCase();\n\tvar parent = child.parent();\n\twhile(parent.length == 1 && parent[0].nodeName.toLowerCase() != tag)\n\t\tparent = parent.parent();\n\treturn parent ? parent : null;\n}\nfunction get_parent_with_class(child, cls) {\n\tif(!child || child.length == 0)\n\t\treturn null;\n\tvar parent = child.parent();\n\twhile(parent.length == 1 && !$(parent[0]).hasClass(cls))\n\t\tparent = parent.parent();\n\treturn parent ? parent : null;\n}\n\n///////////////////////////////////////////////////////////////////////////////\n// Custom Jqueery UI Changes\nfunction show_modal(item, title, in_width, in_height, dialogClass) {\n\tvar data = {\n\t\tmodal : true,\n\t\tresizable : false,\n\t\t/*show : 'fade', Show things faster w/o an effect*/\n\t\twidth : in_width,\n\t\theight : in_height,\n\t\tdraggable : false\n\t};\n\tif(title) {\n\t\tdata.title = title;\n\t}\n\tif(dialogClass) {\n\t\tdata.dialogClass = dialogClass;\n\t}\n\t$(item).dialog(data);\n\t// Prevent any input fields from getting focus. Jqueryui gives them \n\t// focus for some reason messing up placesholders.\n\t$(item).find('input, textarea, select').blur();\n}\n\nfunction modal_progress_start(id, title, cancel) {\n\tif(!id) id = 'modal_progress';\n\t// Create the modal dialog immediately for the file list progress bar.\n\tvar $exist = $(\"body #\" + id + \".download_progress_overlay\");\n\tif($exist.length){\n\t\t$exist\n\t\t\t.find(\".title\").text(title).end()\n\t\t\t.find(\".status\").text(\"\").end()\n\t\t\t.find(\".perc\").text(\"\").end()\n\t\t\t.find(\".error\").text(\"\").end();\n\t} else {\n\t\t$(\"body\").append(\"
    \" +\n\t\t\t\"
    \" + title + \"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"Cancel
    \");\n\t}\n\tvar $dialog = borderless_dialog(\"#\" + id).dialog(\"open\")\n\t\t.find(\".cancel\").removeClass(\"done\").off(\"click\").end();\n\tif(!cancel) {\n\t\tcancel = function () {\n\t\t\t$dialog.dialog('close');\n\t\t\treturn false;\n\t\t};\n\t}\n\treturn $dialog.find(\".cancel, .done\").click(cancel).end();\n}\n\n/**\n * Update the progress bar\n * @param id The progress bar id used in start.\n * @param perc The current progress percent (0.0 to 100.0)\n * @param stat A status message\n * @param substatus A sub status message.\n */\nfunction modal_progress_set(id, perc, stat, substatus) {\n\tif(!id) id = 'modal_progress';\n\tvar $dialog = $(\"#\" + id);\n\tif(stat != null)\n\t\t$dialog.find(\".status\").text(stat).end();\n\tif(substatus != null)\n\t\t$dialog.find(\".substatus\").text(substatus).end();\n\tif(perc != null) {\n\t\t$dialog\n\t\t\t.find(\".perc\").text(perc.toFixed(0) + \"%\").end()\n\t\t\t.find(\".progress .bar\").width(perc.toFixed(3) + \"%\").end();\n\t}\n}\n\n/**\n * Add an error message to the error list in the modal.\n * @param id\n * @param error\n */\nfunction modal_progress_error(id, error) {\n\tif(!id) id = 'modal_progress';\n\tvar $dialog = $(\"#\" + id);\n\tvar $error = $(\"
    \").html(DOMPurify.sanitize(error));\n\t$dialog.find(\".error\").append($error);\n}\n\n/**\n * Close a modal progress bar window.\n * @param id\n */\nfunction modal_progress_close(id) {\n\tif(!id) id = 'modal_progress';\n\t$(\"#\" + id).dialog('close');\n}\n\n// A rich select menu allows us to generate nicer looking drop down menus.\n$.widget( \"custom.richselectmenu\", $.ui.selectmenu, {\n _renderItem: function( ul, item ) {\n\tvar li = $( \"
  • \" );\n\tvar wrapper = $( \"
    \", { text: item.label } );\n\n\tif ( item.disabled ) {\n\t li.addClass( \"ui-state-disabled\" );\n\t}\n\n\tvar el = item.element;\n\tif(el.attr(\"data-description\").length) {\n\t\t$( \"\", {\n\t\t \"class\": \"description\"\n\t\t}).text(el.attr( \"data-description\" )).appendTo( wrapper );\n\t}\n\tif(el.attr(\"data-fas\") && el.attr(\"data-fas\").length){\n\t\t$(\"\", {\"class\": \"fas \" + el.attr(\"data-fas\")}).prependTo( wrapper );\n\t\twrapper.addClass(\"has_fas\");\n\t}\n\treturn li.append( wrapper ).appendTo( ul );\n }\n});\n\n// The standard jqueryio progress bar only increments in integers e.g., 1%, 2%,\n// etc. This can look choppy on progress bars with more than 100px wide. Below,\n// modify jqueryui widget to support fractional percents, eg. 1.123%, 8.821%.\n$.widget(\"ui.progressbar\", $.ui.progressbar, {\n _refreshValue: function() {\n \t// Call the parent class.\n this._super();\n // Get the current percentage.\n var percentage = this._percentage();\n\t\tif(percentage > 0.0 && percentage < 100.0) {\n\t\t\t// Use up to 3 decimal places for the percent.\n\t\t\tthis.valueDiv.width( percentage.toFixed( 3 ) + \"%\" );\n\t\t}\n }\n});\n\n/**\n * Download a given set of text as a file.\n * @param filename The filename that you want to download.\n * @param text The contents of the file.\n * @param mimetype (Optional) The mime type of the file.\n */\nfunction download_file(filename, text, mimetype) {\n\tif(!mimetype) {\n\t\tmimetype = 'text/plain;charset=utf-8';\n\t}\n\t// Create the element.\n\tvar element = document.createElement('a');\n\telement.setAttribute('href', 'data:' + mimetype + ',' + \n\t\tencodeURIComponent(text));\n\telement.setAttribute('download', filename);\n\telement.style.display = 'none';\n\t// Insert and click on the elemnt to download it.\n\tdocument.body.appendChild(element);\n\telement.click();\n\t// Remove the element.\n\tdocument.body.removeChild(element);\n}\n\n/**\n * Sanitize filenames\n * npmjs.com/package/sanitize-filename\n **/\nvar illegalRe = /[\\/?<>\\\\:*|\"]/g;\nvar controlRe = /[\\x00-\\x1f\\x80-\\x9f]/g;\nvar reservedRe = /^\\.+$/;\nvar windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$/i;\nfunction safe_filename(input, replacement, max_len) {\n\tif(!input) {\n\t\tthrow \"No file name given.\";\n\t}\n\tif(!max_len) {\n\t\t// Maximum length of file on windows.\n\t\tmax_len = 250;\n\t}\n\tif(!replacement) {\n\t\treplacement = '_';\n\t}\n\tvar out = input\n\t\t.replace(illegalRe, replacement)\n\t\t.replace(controlRe, replacement)\n\t\t.replace(reservedRe, replacement)\n\t\t.replace(windowsReservedRe, replacement)\n\t\t// Remove extra spaces.\n\t\t.replace(/\\s+/g, \" \");\n\tif(out.length > max_len) {\n\t\t// Cut it up by periods, try to find the extension.\n\t\tvar split = out.split(\".\");\n\t\t// Assume the extension is last.\n\t\tvar ext = split[split.length - 1];\n\t\t// Put all but the extension together\n\t\tout = split.slice(0, -1).join(\".\")\n\t\t\t// Cut the first part down so it fits into max_len\n\t\t\t.slice(0, max_len - 2 - ext.length)\n\t\t\t// Add the period and the extension itself.\n\t\t\t+ \".\" + ext;\n\t}\n\treturn out;\n}\n\n/**\n * Run a number of functions in parallel.\n * @param fns An array of functions that take a single argument, \"done\" that\n * should be called when they are done.\n * @param max_parallel The maximum number of functions to run at once.\n * @param done A function that takes no arguments and will be called when\n * all of the functions have finished processing.\n * @returns {_cancel} A function that can be called to stop processing.\n */\nfunction map_parallel(fns, max_parallel, done) {\n\tvar is_canceled = false;\n\tvar started_i = -1, num_running = 0;\n\tfunction do_idx(i) {\n\t\tif(is_canceled) {\n\t\t\treturn;\n\t\t}\n\t\t// Sanity check.\n\t\tif(i >= fns.length) {\n\t\t\tconsole.log(\"Moving past end of array.\");\n\t\t\treturn;\n\t\t}\n\t\t// Run the function.\n\t\tnum_running += 1;\n\t\tfns[i](function _done() {\n\t\t\t// Free the worker when we're done.\n\t\t\tnum_running -= 1;\n\t\t\tif(started_i < fns.length - 1) {\n\t\t\t\t// Start the next item for this worker.\n\t\t\t\tstarted_i += 1;\n\t\t\t\tdo_idx(started_i);\n\t\t\t} else if(!is_canceled && num_running <= 0) {\n\t\t\t\t// We're totally done, mark it as such.\n\t\t\t\tdone();\n\t\t\t}\n\t\t});\n\t}\n\n\t// Start up to max_parallel workers.\n\tfor(var i = 0; i < Math.min(max_parallel, fns.length); i ++) {\n\t\tstarted_i += 1;\n\t\tif(started_i >= fns.length) {\n \t// Can occur if the functions are synchronous.\n\t\t\tbreak;\n }\n\t\tdo_idx(started_i);\n\t}\n\t// Return a cancel function for it to stop.\n\treturn function _cancel() {\n\t\tis_canceled = true;\n\t}\n}\n\nfunction create_download_dialog() {\n\tvar previous_dialog = document.getElementById(\"download_files_overlay\");\n\tif(previous_dialog){\n\t\tconsole.warn(\"Found an existing dialog for batch download. \" +\n\t\t\t\"Closing it in order to avoid duplicates.\");\n\t\tborderless_dialog(previous_dialog).dialog(\"close\").remove();\n\t\tprevious_dialog.remove();\n\t}\n\t$(\"body\").append(\"
    \" +\n\t\t\"
    Batch Download
    \" +\n\t\t\"
    \" +\n\t\t\"
    \" +\n\t\t\"
    \" +\n\t\t\"Cancel
    \");\n\tvar $dialog = borderless_dialog(\"#download_files_overlay\");\n\t$dialog.dialog(\"open\");\n\treturn $dialog;\n}\n\n/**\n * A helper function for the bulk downloader. Returns a \"file\" object that can\n * be used in the bulk downloader to summarize a document.\n * @param $dialog\n * @param file_info The original file information that would normally be passed into download_File.\n * @param ai_options\n * @returns {{filename, function: function, group_key: string, group: group}}\n */\nfunction download_to_ai_task($dialog, file_info, ai_options) {\n\t// Give it a different name to prevent naming conflicts..\n\tvar renamed = file_info.filename.substring(0, file_info.filename.lastIndexOf(\".\") + 1) + \" - Summarized\";\n\n\t/**\n\t * Pretty print failure messages.\n\t * @param items\n\t * @returns {{line: null, failures: Array}}\n\t */\n\tfunction fail_info(items) {\n\t\tvar fails = {}, num = 0;\n\t\tvar fail_list = [];\n\t\titems.forEach(function _get_fails(item) {\n\t\t\tvar i = item.file;\n\t\t\tif(i.error) {\n\t\t\t\tfail_list.push(item);\n\t\t\t\tif(fails[i.error]) {\n\t\t\t\t\tfails[i.error] += 1;\n\t\t\t\t} else {\n\t\t\t\t\tfails[i.error] = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tvar deduped = Object.keys(fails);\n\t\tvar line = deduped.length == 0 ? null:\n\t\t\tdeduped.length == 1 ? [\"Failed to summarize: \" + Object.keys(fails)[0], fail_list.length]:\n\t\t\t\t[\"Failed to summarize\", fail_list.length];\n\t\treturn {\n\t\t\tline: line,\n\t\t\tfailures : fail_list\n\t\t}\n\t}\n\t// We return a \"file\" structure, this is something that is used by the download_files function below.\n\treturn {\n\t\tfilename : renamed,\n\t\toriginal_file_info : file_info,\n\t\tgroup_key : 'summary',\n\t\tfunction : function _on_doc_summarize(callback) {\n\t\t\tai_task_document_summarize(file_info, false, function(summary) {\n\t\t\t\tcallback(summary);\n\t\t\t}, function _error(error) {\n\t\t\t\tcallback({success:false, error: error});\n\t\t\t}, ai_options);\n\t\t},\n\t\tgroup : function _combine_summaries(items, callback) {\n\t\t\t$dialog\n\t\t\t\t.find(\".status\").text(\"Compiling\").end()\n\t\t\t\t.find(\".substatus\").text(\"Compiling summary for \" + plural_n(items.length, \"item\")).end();\n\t\t\tvar successes = items.filter(function (i) { return i.file.success; });\n\t\t\tvar f_info = fail_info(items);\n\t\t\tvar info = [[\"Summarized\", successes.length]];\n\t\t\tif(f_info.line) {\n\t\t\t\tinfo.push(f_info.line);\n\t\t\t}\n\t\t\tinfo.push([\"Date Generated\", (new Date()).toLocaleString()]);\n\t\t\tinfo.push([\"Source Page\", window.location.href]);\n\t\t\tfunction full_filing(o) {\n\t\t\t\tvar o_filing = o.filing;\n\t\t\t\tif(o.exhibit) {\n\t\t\t\t\t// This is an exhibit. It may be a filing+exhibit, or just an exhibit.\n\t\t\t\t\treturn o_filing ? o_filing + \"-\" + o.exhibit : o.exhibit;\n\t\t\t\t} else if(!o_filing && o.link) {\n\t\t\t\t\t// If there's a link, give an anchor tag.\n\t\t\t\t\treturn \"Document\";\n\t\t\t\t}\n\t\t\t\treturn o_filing || \"\";\n\t\t\t}\n\n\t\t\t// Extract the prompts from the successes\n\t\t\tvar prompts = [], prompt_dedup = {};\n\t\t\tsuccesses.forEach(function (i) {\n\t\t\t\tvar prompt = i.file.prompt;\n\t\t\t\tvar p_key = prompt && prompt.key ? prompt.key: \"summary\";\n\t\t\t\tvar prompt_model_key = i.file.model + p_key;\n\t\t\t\tif(prompt_dedup[prompt_model_key]) {\n\t\t\t\t\t// Increment the count if we've already seen this prompt.\n\t\t\t\t\tprompt_dedup[prompt_model_key].count += 1;\n\t\t\t\t} else {\n\t\t\t\t\t// Flatten/join if prompt is an array:\n\t\t\t\t\tvar p = prompt && prompt.template && Array.isArray(prompt.template) ?\n\t\t\t\t\t\tprompt.template.join(\"\\n \\n\"):\n\t\t\t\t\t\t// Older way:\n\t\t\t\t\t\ti.file.prompt_template ? i.file.prompt_template:\n\t\t\t\t\t\tprompt && prompt.template ? prompt.template || '':\n\t\t\t\t\t\t'';\n\t\t\t\t\tprompts.push({\n\t\t\t\t\t\tkey : p_key,\n\t\t\t\t\t\tmodel : i.file.model,\n\t\t\t\t\t\ttemplate_cell : p,\n\t\t\t\t\t\tcount : 1\n\t\t\t\t\t});\n\t\t\t\t\tprompt_dedup[prompt_model_key] = prompts[prompts.length - 1];\n\t\t\t\t}\n\t\t\t});\n\n\t\t\texport_data_to_excel(\"Filings Summary\", [{\n\t\t\t\tname : 'Summarizations',\n\t\t\t\tinfo : info,\n\t\t\t\tlisting_cols : [\"Court\", \"Docket\", \"Filing-Exhibit\", \"Link\", \"Pleading Tags\", \"Filename\",\n\t\t\t\t\t\"AI Summary\", \"Model: Task\", \"Tokens\"],\n\t\t\t\tlisting : successes.map(function (i) {\n\t\t\t\t\t// The original value has a lot of the info.\n\t\t\t\t\tvar o = i.file_info.original_file_info;\n\t\t\t\t\t// For now, we provide very simpl tags.\n\t\t\t\t\tvar tags = (o?.analyze?.parser_types || []).map(function (t) {\n\t\t\t\t\t\treturn t.type + (t.identifier ? \" - \" + t.identifier : \"\");\n\t\t\t\t\t}).join(\", \");\n\t\t\t\t\t// Be careful with the prompt. May not be a dict.\n\t\t\t\t\tvar p_key = i.file.prompt && i.file.prompt.key ? i.file.prompt.key: \"summary\";\n\t\t\t\t\tvar model_key = i.file.model + \": \" + p_key;\n\t\t\t\t\treturn [o.court, o.docket, full_filing(o), [o.link, \"Link\"],\n\t\t\t\t\t\ttags, o.filename || \"File\", i.file.result,\n\t\t\t\t\t\tmodel_key, i.file.total_tokens || \"\"];\n\t\t\t\t})\n\t\t\t}]\n\t\t\t.concat(prompts.length ? [{\n\t\t\t\tname : 'Tasks',\n\t\t\t\tinfo : [[\"Types of Tasks Performed\", prompts.length]],\n\t\t\t\tlisting_cols : [\"Model\", \"Task\", \"Template\", \"Number of Uses\"],\n\t\t\t\tlisting : prompts.map(function (p) {\n\t\t\t\t\treturn [p.model, p.key, p.template_cell, p.count];\n\t\t\t\t})\n\t\t\t}]:[]).concat(f_info.failures.length ? [{\n\t\t\t\tname : 'Documents Not Summarized',\n\t\t\t\tinfo : info,\n\t\t\t\tlisting_cols : [\"Court\", \"Docket\", \"Filing-Exhibit\", \"Link\", \"Filename\", \"Error Message\"],\n\t\t\t\tlisting : f_info.failures.map(function (i) {\n\t\t\t\t\tvar o = i.file_info.original_file_info;\n\t\t\t\t\treturn [o.court, o.docket, full_filing(o), [o.link, \"Link\"], o.filename || \"File\", i.file.error];\n\t\t\t\t})\n\t\t\t}]:[]), function (success, raw_resp) {\n\t\t\t\tif(success) {\n\t\t\t\t\tcallback(\"Summarized Filings.xlsx\", new Uint8Array(raw_resp));\n\t\t\t\t} else {\n\t\t\t\t\tshow_error_usermsg(\"Error Summarizing: \" + e);\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t}\n}\n\n/**\n * Get the options for the AI system, returns true if synchronous.\n * @param callback\n */\nfunction ai_task_options(callback) {\n\t// Pull from the cache if we have it.\n\tvar ai_options = sessionGet(\"ai_options\");\n\tif(ai_options) {\n\t\tcallback(ai_options);\n\t\treturn true;\n\t}\n\t// Otherwise, we need to get it from the server.\n\t$.getJSON(\"/ocr/ai_options.ajax\", function (resp) {\n\t\tif(resp.success) {\n\t\t\tsessionSet(\"ai_options\", resp);\n\t\t}\n\t\tcallback(resp);\n\t});\n\treturn false;\n}\n\n/**\n * Get a document's GPT summary. Not all accounts have this ability,\n * may raise an error.\n * @param file_info A data structure that contians the document's id, or court/docket/filing/exhibit.\n * @param force\n * @param callback\n * @param $error_el Either a jquery element to show errors in, or a function\n * \tto call with the error.\n * @param ai_options Additional parameters to pass to the AI system.\n */\nfunction ai_task_document_summarize(file_info, force, callback, $error_el, ai_options) {\n // Default to a summary.\n\tvar task_key = ai_options && ai_options.task || 'summary';\n if(file_info.analyze && file_info.analyze.gpt && [task_key] && !force) {\n callback(file_info.analyze.gpt[task_key]);\n return;\n }\n var doc_id = file_info.doc_id || file_info.id;\n // Specify the document by id or by court/docket/filing/exhibit.\n var data = doc_id ? {id:doc_id} : {court:file_info.court, docket:file_info.docket, filing:file_info.filing, exhibit:file_info.exhibit};\n\n if(force) {\n data.force = true;\n }\n\tfor(var k in ai_options) {\n\t\tif(ai_options[k] !== undefined && ai_options[k] !== null) {\n\t\t\tdata[k] = ai_options[k];\n\t\t}\n\t}\n\n $.post('/ocr/ai_analyze.ajax', data, null, \"json\")\n .success(function(result) {\n if(!result.success) {\n\t\t\tif(typeof $error_el == \"function\") {\n\t\t\t\t$error_el(result.error);\n\t\t\t} else {\n \tshow_usermsg(result.error, $error_el);\n\t\t\t}\n } else {\n\t\t\t// A bit of a hack, but save the result in the file_info object.\n\t\t\tif(!file_info.analyze) { file_info.analyze = {}; }\n\t\t\tif(!file_info.analyze.gpt) { file_info.analyze.gpt = {}; }\n file_info.analyze.gpt[task_key] = result;\n\n callback(result);\n }\n }).error(function () {\n\t\tif(typeof $error_el == \"function\") {\n\t\t\t$error_el(\"Error getting GPT summary\");\n\t\t} else {\n \tshow_usermsg(\"Error getting GPT summary.\", $error_el);\n\t\t}\n });\n}\n\n/**\n * Download a list of files. Automatically shows progress messages to the users.\n * Parameters allow for downloading list of files, or generating on the fly.\n * @param files can be one of two types.\n * A list of objects, each with the following properties.\n * filename: the filename to use. If no filename is specified here, you\n * must specify one in the HTTP download request as x-filename.\n * download: the download link to the file.\n * function: A function called to generate the file, instead of the download link.\n * group_key: if grouping files together, then use this key to group this file.\n * group: If grouping together, a function that will accept a list of items to group callback with a file.\n * A function which generates a list of dictionaries.\n * files($dialog, function callback(generated_files) { } )\n * If a user cancels the dialog, then $dialog.data('cancel', true)\n * @param zipfilename\tThe filename of the output zip file.\n * @param download_parallel If true, download 3 documents at once.\n * @author speedplane\n */\nfunction download_files(files, zipfilename, download_parallel) {\n\t// Create the modal dialog immediately for the file list progress bar.\n\tvar $dialog = create_download_dialog();\n\t$dialog\n\t\t.find(\".cancel, .done\").click(function () {\n\t\t\tclose_dialog();\n\t\t\t// We propogate the click if we're done, we stop it if we cancel.\n\t\t\treturn $(this).hasClass(\"done\") ? true : false;\n\t\t}).end()\n\t\t.find(\".status\").text(\"Initializing ... \").end();\n\t// Give the user the ability to cancel.\n\tvar canceled = false;\n\tfunction close_dialog() {\n\t\tcanceled = true;\n\t\t$dialog\n\t\t\t.dialog(\"close\")\n\t\t\t.remove()\n\t\t\t.data('canceled', true);\n\t}\n\tfunction add_error_msg(msg) {\n\t\t$dialog.find(\".error\").append(\"
    \" + msg + \"
    \");\n\t}\n\t\n\t// Download the final generated zip file.\n\tfunction download_zip(blob) {\n\t\t// Make sure there's a valid filename.\n\t\tzipfilename = zipfilename ? safe_filename(zipfilename) : \"download.zip\";\n\t\tif (canceled) {\n\t\t\treturn;\n\t\t} else if (typeof navigator.msSaveBlob == \"function\") {\n\t\t\t// On IE, we can automatically download the file.\n\t\t\tnavigator.msSaveBlob(blob, zipfilename);\n\t\t\tclose_dialog();\n\t\t} else {\n\t\t\t// On other browsers, the user must click. Use the cancel button.\n\t\t\tvar blob_url = URL.createObjectURL(blob);\n\t\t\tconsole.log(\"Saving zip (\" + zipfilename + \") to: \" + blob_url);\n\t\t\t$dialog\n\t\t\t\t.find(\".status\").text(\"Download complete.\").end()\n\t\t\t\t.find(\".substatus\").text(\"\").end()\n\t\t\t\t.find(\".perc\").text(\"100%\").end()\n\t\t\t\t.find(\".progress .bar\").width(\"100%\").end()\n\t\t\t\t.find('.cancel')\n\t\t\t\t\t.addClass(\"done\")\n\t\t\t\t\t.removeClass(\"cancel\")\n\t\t\t\t\t.attr('href', blob_url)\n\t\t\t\t\t.attr('download', zipfilename)\n\t\t\t\t\t.text('Save').end();\n\t\t}\n\t}\n\t\n\tvar zipWriter = null;\n\tfunction zip_initialize(callback) {\n\t\t// initialize the zip library.\n\t\t$dialog\n\t\t\t// set a status message\n\t\t\t.find(\".status\").text(\"Creating zip file.\").end()\n\t\t\t.find(\".substatus\").text(\"\");\n\t\t$.getScript(\"/site_media/zipjs/zip.js\", function () {\n\t\t\tzip.workerScriptsPath = '/site_media/zipjs/';\n\t\t\t// use a BlobWriter to store the zip into a Blob object\n\t\t\tzip.createWriter(new zip.BlobWriter(\"application/zip\"), function(writer) {\n\t\t\t\tzipWriter = writer;\n\t\t\t\tcallback();\n\t\t\t}, function _zip_create_error(error) {\n\t\t\t\tclose_dialog();\n\t\t\t\tshow_error_usermsg(error.message || error);\n\t\t\t});\n\t\t});\n\t}\n\t\n\tvar _all_filenames = {};\n\t/**\n\t * Keep track of all of the filenames and make sure they all have a\n\t * unique value.\n\t * @param filename The filename you want to uniqyify.\n\t * @returns {string}\n\t */\n\tfunction unique_filename(filename) {\n\t\t// Break the filename into an extension, make sure its a 2-array.\n\t\tvar orig_filename = filename.split(\".\");\n\t\torig_filename = orig_filename.length == 1 ? [orig_filename[0], \"\"]:\n\t\t\t[orig_filename.slice(0, -1).join(\".\"), \".\" +\n\t\t\t\torig_filename[orig_filename.length-1]];\n\t\t// Start trying stuff to append.\n\t\tvar append_i = 0;\n\t\twhile(_all_filenames[filename]) {\n\t\t\tappend_i += 1;\n\t\t\tfilename = orig_filename[0] + \"-\" + append_i + orig_filename[1];\n\t\t}\n\t\t// Make this one as used.\n\t\t_all_filenames[filename] = true;\n\t\treturn filename;\n\t}\n\n\t// Zip one File\n\tvar currently_zipping = false;\n\tfunction zip_file(filename, data, done, comment) {\n\t\tif(canceled) {\n\t\t\treturn;\n\t\t}\n\t\t// The zip web worker is not thread safe, only one request at a time.\n\t\tif (currently_zipping) {\n\t\t\t// Wait until we're done zipping.\n\t\t\tsetTimeout(function () { \n\t\t\t\tzip_file(filename, data, done, comment);\n\t\t\t}, 100);\n\t\t\treturn;\n\t\t}\n\t\tif(!filename) {\n\t\t\tconsole.error(\"No file name given, skipping: \" + data.length);\n\t\t\tdone();\n\t\t\treturn;\n\t\t}\n\t\tcurrently_zipping = true;\n\t\t// Zip up downloaded file.\n\t\tzipWriter.add(unique_filename(safe_filename(filename)), {\n\t\t\t\tsize : data.length,\n\t\t\t\t// We don't need to do anything to init.\n\t\t\t\tinit : function (callback, onerror) { callback(); },\n\t\t\t\t// Reading is just creating a subarray\n\t\t\t\treadUint8Array : function(index, length, callback, onerror) {\n\t\t\t\t\t\tcallback(new Uint8Array(data.subarray(index, index + length)));\n\t\t\t\t}\n\t\t}, function() {\n\t\t\t// onsuccess callback\n\t\t\tdone();\n\t\t\tcurrently_zipping = false; // Unlock the zip pipeline.\n\t\t}, function(currentIndex, totalIndex) {\n\t\t\t// zipWriter onprogress callback\n\t\t}, {\n\t\t\t// The options structure.\n\t\t\tcomment : comment || \"\"\n\t\t});\n\t}\n\n\tvar failed_downloads = [];\n\tvar successful_downloads = [];\n\tvar start_time = new Date();\n\tfunction remove_query (q) {\n\t\t// Remove everything after the query, and the docs folder.\n\t\treturn q.split(\"?\")[0].replace(\"/docs/\", \"/\").replace(/\\.pdf$/, \"\");\n\t}\n\tfunction get_download_summary(done) {\n\t\t// Create an array, which lists the failed downloads in human readable.\n\t\t$dialog\n\t\t\t.find(\".status\").text(\"Generating Excel summary\").end()\n\t\t\t.find(\".substatus\").text(\"\").end();\n\t\t// Generate a human readable duration.\n\t\tvar seconds = Math.floor((new Date() - start_time)/1000);\n\t\tvar duration = seconds <= 120 ? seconds + \" Seconds\":\n\t\t\tMath.floor(seconds/60) + \" Minutes \" + (seconds%60) + \" Seconds\";\n\t\tvar info = [\n\t\t\t[\"Successful Downloads\", successful_downloads.length],\n\t\t\t[\"Failed Downloads\", failed_downloads.length],\n\t\t\t[\"Download Time\", start_time],\n\t\t\t[\"Download Duration\", duration],\n\t\t\t[\"Source Page\", window.location.href]\n\t\t];\n\t\texport_data_to_excel(\"Download Summary\", [{\n\t\t\tname : 'Successful Downloads',\n\t\t\tinfo : info,\n\t\t\tlisting_cols : [\"Filename\", \"Download Link\"],\n\t\t\tlisting : successful_downloads.map(function (f) {\n return [f.filename || \"\", remove_query(f.download || \"\")];\n })\n\t\t},{\n\t\t\tname : 'Failed Downloads',\n\t\t\tinfo : info,\n\t\t\tlisting_cols : [\"Filename\", \"Error Message\", \"Download Link\"],\n\t\t\tlisting : failed_downloads.map(function (f) {\n return [f.f.filename || \"\", f.e || \"\",\n\t\t\t\t\tremove_query(f.f.download || \"\")];\n })\n\t\t}], function (success, raw_resp) {\n\t\t\tif(success) {\n\t\t\t\tdone(new Uint8Array(raw_resp));\n\t\t\t} else {\n\t\t\t\tclose_dialog();\n\t\t\t\tshow_error_usermsg(e);\n\t\t\t}\n\t\t});\n\t}\n\n\t// Download the files.\n\t// Number of parallel downloads\n\tif(!download_parallel) {\n\t\tdownload_parallel = 3;\n\t}\n\tfunction get_filename_from_xhr(xhr) {\n\t\tvar disposition = xhr.getResponseHeader('Content-Disposition');\n\t\tif (disposition) {\n\t\t\tvar filenameRegex = /filename[^;=\\n]*=((['\"]).*?\\2|[^;\\n]*)/;\n\t\t\tvar matches = filenameRegex.exec(disposition);\n\t\t\tif (matches != null && matches[1]) {\n\t\t\t return matches[1].replace(/['\"]/g, '');\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\tvar TEST_ONLY = false;\n\tvar MAX_TRIES = 5; \t// Number of times we'll try downloading each file.\n\tvar bytesDownloaded = 0;\n\t// This is the structure, initialized further below\n\tvar downloadsLeft = {files:null, groups:null, total:function () { return this.files + this.groups; }};\n\t// We allow certain files to be grouped together into a single larger file.\n\tvar grouped_files = {};\n\n\t/**\n\t * Update the status message, called after downloading each file.\n\t * Relies on files and downloadsLeft, and bytesDownloaded\n\t */\n\tfunction updateStatus(file) {\n\t\t// Calculate stats.\n\t\tvar num_files = 0, num_grouped = 0;\n\t\tfiles.forEach(function (f) {\n\t\t\tif(f.group_key) { num_grouped++; } else { num_files++; }\n\t\t});\n\t\tvar dl_files = num_files - downloadsLeft.files;\n\t\tvar dl_groups = num_grouped - downloadsLeft.groups;\n\t\tvar perc = (dl_files + dl_groups) / files.length * 100;\n\t\t// Status message.\n\t\tvar msg = \"Downloaded \" + dl_files + \" of \" + plural_n(num_files, \"file\");\n\t\tif(num_grouped) {\n\t\t\t// We have grouped files.\n\t\t\tmsg += \" and \" + dl_groups + \" of \" + plural_n(num_grouped, \"task\");\n\t\t}\n\t\tmsg += \" \" + getSize() + \".\";\n\n\t\t// Set a status message and progress bar.\n\t\tvar title = num_grouped ? \"Download and Analyze\": \"Batch Download\";\n\t\t$dialog\n\t\t\t.find(\".title\").text(title).end()\n\t\t\t.find(\".status\").text(msg).end()\n\t\t\t.find(\".perc\").text(perc.toFixed(0) + \"%\").end()\n\t\t\t.find(\".substatus\").text(\"Downloading: \" + file.filename).end()\n\t\t\t.find(\".progress .bar\").width(perc + \"%\");\n\t}\n\t// Filesize in user friendly format.\n\tfunction getSize() {\n\t\treturn bytesDownloaded > 10 * 1024 * 1024 ?\n\t\t\t\t(bytesDownloaded / 1024 / 1024).toFixed(0) + \"MB\":\n\t\t\tbytesDownloaded > 2 * 1024 * 1024 ?\n\t\t\t\t(bytesDownloaded / 1024 / 1024).toFixed(1) + \"MB\":\n\t\t\t\t(bytesDownloaded / 1024 ).toFixed(0) + \"kB\";\n\t}\n\tfunction download_file(i, num_tries) {\n\t\tif(canceled) {\n\t\t\treturn;\n\t\t} else if(i >= files.length) {\n\t\t\tconsole.log(\"Trying to download nonexistent file.\");\n\t\t\treturn;\n\t\t} else if (downloadsLeft.total() <= 0) {\n\t\t\tconsole.log(\"Negative downloads left: \" + downloadsLeft.files);\n\t\t\treturn;\n\t\t}\n\t\tvar file = files[i];\n\t\t// One day, try to refactor.\n\t\tupdateStatus(file);\n\t\t/**\n\t\t * Asynchronously merge a group of files. Returns true if complete.\n\t\t * @return {boolean} True we are grouping more files. False if complete.\n\t\t */\n\t\tfunction merge_groups() {\n\t\t\tfor(var group_key in grouped_files) {\n\t\t\t\tvar group_files = grouped_files[group_key];\n\t\t\t\t// We use the first item in the list.\n\t\t\t\tvar first = group_files[0];\n\t\t\t\tif(first.group_completed) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tvar data = first.file_info.group(grouped_files[group_key], function _callback(grouped_filename, data) {\n\t\t\t\t\t// Now zip that file up.\n\t\t\t\t\tzip_file(grouped_filename, data, function _on_zipped() {\n\t\t\t\t\t\tfirst.group_completed = true;\n\t\t\t\t\t\t// Go on to the next item.\n\t\t\t\t\t\tdo_next();\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Sets up the next download, or complete the zip file if done.\n\t\tfunction do_next(was_group) {\n\t\t\tif(was_group) {\n\t\t\t\tdownloadsLeft.groups -= 1;\n\t\t\t} else {\n\t\t\t\tdownloadsLeft.files -= 1;\n\t\t\t}\n\t\t\tif (downloadsLeft.total() <= 0) {\n\t\t\t\t// No more downloads left, build the grouped files.\n\t\t\t\tif(merge_groups()) {\n\t\t\t\t\t// If there are groups, downloadsLeft maybe negative.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// No more downloads left, build the excel file listing.\n\t\t\t\tget_download_summary(function (file_data) {\n\t\t\t\t\t// Now zip that file up.\n\t\t\t\t\tzip_file(\"Download Summary.xlsx\", file_data, function () {\n\t\t\t\t\t\t// No finish the zip and be done.\n\t\t\t\t\t\tzipWriter.close(download_zip);\n\t\t\t\t\t}, null);\n\t\t\t\t});\n\t\t\t} else if (i + download_parallel < files.length) {\n\t\t\t\t// Downloads are in parallel, so add the next for this group.\n\t\t\t\tdownload_file(i + download_parallel);\n\t\t\t}\n\t\t}\n\t\tif(TEST_ONLY) {\n\t\t\t// Create a fake file.\n\t\t\tvar fake_file = \"this is the fake file for \" + file.filename;\n\t\t\tbytesDownloaded += fake_file.length;\n\t\t\t// Add the file to the zip, and then go on to the next download.\n\t\t\tzip_file(file.filename, new TextEncoder(\"utf8\").encode(fake_file),\n\t\t\t\tdo_next);\n\t\t\treturn;\n\t\t}\n\t\tfunction on_failure(e, status) {\n\t\t\tif(!num_tries) {\n\t\t\t\tnum_tries = 1;\n\t\t\t}\n\t\t\tif (status != 403 && status != 401 && num_tries < MAX_TRIES) {\n\t\t\t\t// Try again.\n\t\t\t\tdownload_file(i, num_tries + 1);\n\t\t\t} else {\n\t\t\t\t// The download failed, try not to mess up everything.\n\t\t\t\tvar fname = files[i].filename || \n\t\t\t\t\tfiles[i].download.split(\"/\").slice(-1)[0];\n\t\t\t\tadd_error_msg(\"Download Failed - \" + e + \": \" + fname);\n\t\t\t\tfailed_downloads.push({\n\t\t\t\t\tf : files[i],\n\t\t\t\t\te : e\n });\n\t\t\t\t// Go on to the next file.\n\t\t\t\tdo_next();\n\t\t\t}\n\t\t}\n\n\t\tif (file.function) {\n\t\t\t// This is a function, so call it and get the file.\n\t\t\tfile.function(function _done(f) {\n\t\t\t\tsuccessful_downloads.push(file);\n\t\t\t\tif(file.group_key) {\n\t\t\t\t\tif(!grouped_files[file.group_key]) {\n\t\t\t\t\t\tgrouped_files[file.group_key] = [];\n\t\t\t\t\t}\n\t\t\t\t\tgrouped_files[file.group_key].push({file_info:file, file:f});\n\t\t\t\t\tdo_next(true);\n\t\t\t\t} else {\n\t\t\t\t\tzip_file(f.filename, f.data, do_next);\n\t\t\t\t}\n\t\t\t}, on_failure);\n\t\t\treturn;\n\t\t}\n\n\t\t// Download the file\n\t\tvar xhr = new XMLHttpRequest();\n\t\txhr.open('GET', file.download, true);\n\t\txhr.responseType = 'arraybuffer';\n\t\txhr.onload = function(e) {\n\t\t\tif (this.status != 200) {\n\t\t\t\t// Bad Download, build an error message.\n\t\t\t\tvar e_msg = xhr.getResponseHeader('X-Detailed-Error') || \"\";\n\t\t\t\te_msg += e_msg.length ? \" (Status: \" + this.status + \")\":\n\t\t\t\t\t\t\"Bad Status: \" + this.status;\n\t\t\t\ton_failure(e_msg, this.status);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbytesDownloaded += this.response.byteLength;\n\t\t\t\n\t\t\t// Get the filename from the server first, then from the files.\n\t\t\tvar fname = this.getResponseHeader('x-filename') ||\n\t\t\t\t\tget_filename_from_xhr(xhr) || file.filename;\n\t\t\tif(!fname) {\n\t\t\t\t// Remove everything after the query.\n\t\t\t\tfname = file.download.split(\"?\").slice(0)[0];\n\t\t\t\t// Now try to get the last soubstantive part of the URL.\n\t\t\t\twhile(fname && fname.endsWith(\"/\")) {\n\t\t\t\t\tfname = fname.slice(0, -1);\n\t\t\t\t}\n\t\t\t\tfname = fname.split(\"/\").slice(-1);\n\t\t\t}\n\t\t\tif(!fname) {\n\t\t\t\t$dialog.find(\".error\").append(\"
    No filename for \" +\n\t\t\t\t\t\t\"download: \" + file.download + \"
    \");\n\t\t\t\tfailed_downloads.push({\n\t\t\t\t\tf : file,\n\t\t\t\t\te : \"No filename\"\n\t\t\t\t});\n\t\t\t\t// Go on to the next file.\n\t\t\t\tdo_next();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Make sure the filename is trouble free. Set max length to 160\n\t\t\t// just to make sure they can fully open the files on windows.\n\t\t\t// It's the caller's job to provide user-friendly filenames.\n\t\t\tvar safe_fname = safe_filename(fname, null, 160);\n\t\t\tif(safe_fname != fname) {\n\t\t\t\tconsole.log(\"Warning: Making bad filename safe: \" + fname);\n\t\t\t}\n\t\t\tsuccessful_downloads.push(file);\n\t\t\tif(file.group_key) {\n\t\t\t\tfile.data = new Uint8Array(xhr.response);\n\t\t\t\tfile.comment = this.getResponseHeader('x-comment');\n\t\t\t\tgrouped_files[file.group_key].push({file_info:file, file:file});\n\t\t\t\tdo_next(true);\n\t\t\t} else {\n\t\t\t\t// Add the file to the zip, and then go on to the next download.\n\t\t\t\tzip_file(safe_fname, new Uint8Array(xhr.response), do_next,\n\t\t\t\t\tthis.getResponseHeader('x-comment'));\n\t\t\t}\n\t\t};\n\t\txhr.onerror = function(e) {\n\t\t\t// Another way of failing.\n\t\t\ton_failure(\"Transfer Error: \" + e);\n\t\t};\n\t\txhr.send();\n\t}\n\t\n\tfunction auto_perc() {\n\t\t// Give the sensation of a continuously moving progress bar by moving the\n\t\t// progress bar up a notch on regular intervals.\n\t\tvar total_left = downloadsLeft.total();\n\t\tif (canceled || total_left <= 0) {\n\t\t\treturn;\n\t\t}\n\t\t// The minimum the percentage bar should be at.\n\t\tvar perc_lo = (files.length - total_left) / files.length;\n\t\t// The maximum\n\t\tvar perc_hi = (files.length - total_left + 1) / files.length;\n\t\tvar perc_cur = Math.max(perc_lo, $dialog.find(\".progress .bar\").width() / \n\t\t\t$dialog.find(\".progress\").width());\n\t\t// Get a bit closer to the next one. It will never reach it.\n\t\tperc_cur += (perc_hi - perc_cur) / 5;\n\t\t$dialog.find(\".progress .bar\").width((perc_cur*100) + \"%\");\n\t\tsetTimeout(auto_perc, 150);\n\t}\n\t\n\t// Actually start the download process.\n\tfunction start_download() {\n\t\tconsole.log(\"Downloading multiple files with \" + navigator.userAgent);\n\t\tif(navigator.userAgent.search(/iPhone|iPad/) >= 0) {\n\t\t\t$dialog\n\t\t\t\t.find(\".status\").text(\"Bulk downloads are not yet \" +\n\t\t\t\t\t\"supported on iPhones or iPads.\").end()\n\t\t\t\t.find(\".substatus\").text(\"\").end()\n\t\t\t\t.find(\".perc\").text(\"\").end()\n\t\t\t\t.find(\".progress\").hide();\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// Remove duplicates. We shouldn't have any, but just in case.\n\t\tvar dnames = {}, fnames = {}, to_remove = [];\n\t\tfiles.forEach(function(f, idx) {\n\t\t\tif(f.download && f.download in dnames) {\n\t\t\t\t// These are duplicate downloads, remove them.\n\t\t\t\tto_remove.push(idx);\n\t\t\t} else if(f.filename && f.filename in fnames) {\n\t\t\t\tvar count = fnames[f.filename];\n\t\t\t\tvar filename_split = f.filename.split('.');\n\t\t\t\tvar extension = filename_split.pop();\n\t\t\t\tvar name = filename_split.join('.');\n\t\t\t\twhile(f.filename in fnames) {\n\t\t\t\t\tcount++;\n\t\t\t\t\tf.filename = name + \" (\" + (count + 1) + \").\" + extension;\n\t\t\t\t}\n\t\t\t\tfnames[f.filename] = count;\n\t\t\t\tdnames[f.download] = true;\n\t\t\t\tconsole.log(\"Renaming File: \" + f.filename);\n } else {\n\t\t\t\tdnames[f.download] = true;\n\t\t\t\tif(f.filename) {\n\t\t\t\t\tfnames[f.filename] = (fnames[f.filename] || 0) + 1;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tif(to_remove.length) {\n\t\t\tconsole.log(\"Removing \" + to_remove.length + \" duplicates.\");\n\t\t\t// Remove the files that we should remove.\n\t\t\tto_remove.reverse();\n\t\t\tto_remove.forEach(function (idx) { \n\t\t\t\tfiles.splice(idx, 1);\n\t\t\t});\n\t\t}\n\t\t\n\t\t// Initialize our file counter.\n\t\tdownloadsLeft.groups = files.filter(function (f) {return !!f.group_key; }).length;\n\t\tdownloadsLeft.files = files.length - downloadsLeft.groups;\n\t\tzip_initialize(function _on_zip_init() {\n\t\t\t// Initialize the first set of parallel downloads.\n\t\t\tfor(var j=0; j < download_parallel && j < files.length; j++) {\n\t\t\t\tdownload_file(j);\n\t\t\t}\n\t\t\t// Start the automatic progress bar mover.\n\t\t\tauto_perc();\n\t\t});\n\t}\n\t\n\tif (typeof files == \"function\") {\n\t\t// Before starting the download, we must generate the files.\n\t\tfiles($dialog, function (generated_files) {\n\t\t\tfiles = generated_files;\n\t\t\tif(files.length == 0) {\n\t\t\t\tclose_dialog();\n\t\t\t}\n\t\t\tstart_download();\n\t\t});\n\t} else {\n if(files.length == 0) {\n\t\t\tclose_dialog();\n } else {\n\t\t\tstart_download();\n\t\t}\n\t}\n}\n\n\n/**\n * A simple function that turns a form into an uploader. The form must have\n * an input[type=file] and should have a label and use the scss mixin\n * image-uploader.\n * @param $form The jquery object.\n * @param existing_image The url to an image if it's supposed to start out\n * with one.\n * @param endpoint Where to set the endpoint.\n * @param extra_data Additional data to be passed in via POST.\n */\nfunction image_uploader($form, existing_image, endpoint, extra_data) {\n\tif(!$form || !$form.length) {\n\t\treturn;\n\t}\n\tif($form[0].tagName != \"FORM\") {\n\t\tconsole.log(\"Not using a form: \" + $form[0].tagName);\n\t\treturn;\n\t}\n\t// Add the file input if it wasn't already there.\n\tif(!$form.find(\"input[type=file]\").length){\n\t\t$form.append('');\n\t}\n\t// Add an image if one doesn't already exist.\n\tif(!$form.find(\"img\").length) {\n\t\t$form.append('\"Uploader\"');\n\t}\n\t$form\n\t\t.attr('action', endpoint || '/user/image/upload')\n\t \t.attr('method', 'POST')\n\t \t.find(\"input[type=file]\").change(function () {\n\t \t\t$form.submit();\n\t \t}).end()\n\t\t // Add in the action\n\t\t.ajaxForm({\n\t\t\tbeforeSubmit: function (arr, $form) {\n\t\t\t\t$form.addClass('disabled').find(\".loader\").show();\n\t\t\t},\n\t\t\tsuccess: function (resp, statusText, xhr, $form) {\n\t\t\t\t$form.removeClass('disabled').find(\".loader\").hide().end();\n\t\t\t\tif (resp.success) {\n\t\t\t\t\tsetup_image(resp.links[0]);\n\t\t\t\t} else {\n\t\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t\t}\n\t\t\t},\n\t\t\tdataType: 'json'\n\t\t});\n\t// Add in the extra data.\n\tfor(var k in extra_data) {\n\t\t$form.append('');\n\t}\n\n function setup_image(url) {\n if(url) {\n $form.addClass('has_image').find(\"img\").attr('src', url).show();\n } else {\n $form.removeClass('has_image').find(\"img\").hide();\n }\n }\n setup_image(existing_image);\n}\n\nfunction show_support_overlay() {\n\tshow_modal($(\"#SupportOverlay\"), null, \"auto\", \"auto\", \"support_overlay\");\n}\n\nfunction init_support_overlay() {\n\t$(\"a[href='#SupportOverlay']\").click(function (ev) {\n\t\tev.preventDefault();\n\t\tshow_support_overlay();\n\t});\n\tvar $overlay = $('#SupportOverlay form, #MessageOverlay form')\n\t.ajaxForm({\n\t\tbeforeSubmit: \tfunction (data, $form, options) {\n\t\t\t$form.find(\".loader\").show();\n\t\t},\n\t\tsuccess: function(data, statusText, xhr, $form) {\n\t\t\t$form.find(\".loader\").hide();\n\n\t\t\t// Try to close the dialog\n\t\t\tvar msg_overlay = $form;\n\t\t\twhile(msg_overlay && !msg_overlay.hasClass(\"message_overlay\"))\n\t\t\t\tmsg_overlay = msg_overlay.parent();\n\t\t\tif(msg_overlay) msg_overlay.dialog(\"close\");\n\n\t\t\t// Give the user a message\n\t\t\tif(data.success)\n\t\t\t\tshow_success_usermsg(\"Successfully Sent\", \"email_support\");\n\t\t\telse\n\t\t\t\tshow_error_usermsg(data.error, \"email_support\");\n\t\t},\n\t\ttype: \t'post', // Can override method attribute\n\t\tdataType: 'json' // 'xml', 'script', or 'json' (expected server response type)\n\t})\n\t.find('input[type=submit]').button();\n}\nfunction init_forgot_password() {\n\t$(\"#ForgotPasswordOverlay form input[type=submit]\").click(function () {\n\t\t$(\"#ForgotPasswordOverlay form .loader\").show();\n\t\tvar the_email = $(\"#ForgotPasswordOverlay form input[type=text]\").val();\n\t\t$.post(\"/password_reset.ajax\", {email:the_email}, function (resp) {\n\t\t\t$(\"#ForgotPasswordOverlay form .loader\").hide();\n\t\t\tif(resp.success) {\n\t\t\t\tshow_success_usermsg(\"Your new password has been emailed to you.\");\n\t\t\t\t$(\"#ForgotPasswordOverlay\").dialog(\"close\");\n\t\t\t} else {\n\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t}\n\t\t}, \"json\");\n\n\t\treturn false;\n\t});\n}\nfunction init_hd_bd_holder() {\n\t// Set the body to at least about the size of the screen. This is a\n\t// remenant of old code that tries to get the footer down to the bottom of\n\t// the screen. If a page lacks a footer, it isn't needed: Add no_push_footer\n\t// to the bd class in the template.\n\tvar $hd_bd = $(\".hd_bd_holder\");\n\tif(!$hd_bd.hasClass(\"no_push_footer\")) {\n\t\tvar $hd = $hd_bd.find(\"#hd\");\n\t\tvar min_height = get_window_height() - ($hd.height() +\n\t\t\t$(\"#search_login_menu_spacer\").height() + $(\"#footer\").height() + 50);\n\t\t$hd_bd.find(\"#bd\").css(\"min-height\", min_height);\n\t}\n}\nfunction init_pacer_tips() {\n\t// A tooltip that explains pacer\n\tvar html = $(\"#PACER_explained\").html();\n\t$(\".PACER_tip\").powerTip({\n\t\tsmartPlacement\t: true,\n\t\tmouseOnToPopup\t: true,\n\t\tcloseDelay \t\t: 400,\n\t}).data('powertip', html);\n}\nfunction init_float_menu() {\n\t// When we scroll down, tell the float menu\n\tif($(\"#float_menu:visible\").length) {\n\t\t$(window).scroll(function() {\n\t\t if ($(document).scrollTop() > 50) {\n\t\t\t$('#float_menu, #float_menu li a').addClass('shrink');\n\t\t } else {\n\t\t\t$('#float_menu, #float_menu li a').removeClass('shrink');\n\t\t }\n\t\t});\n\t}\n}\n\nfunction common_setup() {\n\t// Prevent IE (or anyone) from caching our ajax calls\n\t$.ajaxSetup({\n\t\tcache: false,\n\t\ttimeout: 1000*65\t\t// GAE timeout is 60 seconds?\n\t});\n\t// Enable the placeholder attribute for non-html5 browsers\n\tinit_support_overlay();\n\tinit_hd_bd_holder();\n\tinit_float_menu();\n\tinit_global_dropdown();\n\t$(\":input[placeholder], textarea\").placeholder();\n\n\t// These functions don't need to run synchronously on startup.\n\tsetTimeout(function common_setup_later() {\n\t\tinit_powertip_defaults();\n\t\tinit_forgot_password();\n\t\tinit_pacer_tips();\n\t\tinit_close_buttons();\n\t\tinit_godaddy_seal();\n\t}, 0);\n}\n\n$(document).ready(function _start() {\n\tcommon_setup();\n});\n\n\n///////////\n// Support Chat\nfunction get_admin_url(user_email) {\n if(!user_email || typeof user_email != \"string\") {\n return null;\n }\n return window.location.origin + \"/msadmin/admin.html?user_detail=\" +\n encodeURIComponent(user_email);\n}\n\n// 9/2023: Salesforce being deprecated.\nvar enable_support_chat = false;\nvar sf_chat_svc = null;\n\nfunction show_salesforce_chat_window() {\n\tif(sf_chat_svc) {\n\t\tsf_chat_svc.onHelpButtonClick();\n\t}\n}\nfunction setup_salesforce_live_chat_window() {\n if(!user_logged_in || !enable_support_chat) {\n return;\n }\n var initESW = function(gslbBaseURL) {\n embedded_svc.settings.displayHelpButton = true;\n embedded_svc.settings.language = 'en';\n embedded_svc.settings.defaultMinimizedText = 'Chat with support';\n embedded_svc.settings.disabledMinimizedText = 'Support offline';\n embedded_svc.settings.offlineSupportMinimizedText = 'Contact Support';\n // Defaults to Loading\n //embedded_svc.settings.loadingText = '';\n // Sets the domain for your deployment so that visitors can navigate\n // subdomains during a chat session\n //embedded_svc.settings.storageDomain = 'yourdomain.com';\n // Settings for Live Agent\n //embedded_svc.settings.directToButtonRouting = function(prechatFormData) {\n // Dynamically changes the button ID based on what the visitor enters in the pre-chat form.\n // Returns a valid button ID.\n //};\n //Sets the auto-population of pre-chat form fields\n embedded_svc.settings.prepopulatedPrechatFields = {\n FirstName: user_first_name,\n LastName: user_last_name,\n Email: user_logged_in,\n // Subject: \"Hello\",\n };\n // Send the data as prechat fields as well.\n embedded_svc.settings.extraPrechatFormDetails = [{\n label: \"ReferrerUri\",\n value: window.location.href,\n displayToAgent: true,\n transcriptFields : [\"ReferrerUri\"],\n },{\n label: \"AdminURL\",\n value: get_admin_url(user_logged_in),\n displayToAgent: true,\n transcriptFields : [\"AdminURL\"],\n },{\n label: \"FirstName\",\n value: user_first_name,\n displayToAgent: true,\n transcriptFields : [\"FirstName\"],\n },{\n label: \"LastName\",\n value: user_last_name,\n displayToAgent: true,\n transcriptFields : [\"LastName\"],\n },{\n label: \"Email\",\n value: user_logged_in,\n displayToAgent: true,\n transcriptFields : [\"Email\"],\n }];\n //An array of button IDs, user IDs, or userId_buttonId\n //embedded_svc.settings.fallbackRouting = [];\n var tEl = document.createElement('div');\n tEl.setAttribute('class', 'salesforce_agent');\n document.body.appendChild(tEl);\n embedded_svc.settings.targetElement = tEl;\n embedded_svc.settings.enabledFeatures = ['LiveAgent'];\n embedded_svc.settings.entryFeature = 'LiveAgent';\n\n var ret = embedded_svc.init(\n 'https://fastcase.my.salesforce.com',\n 'https://fastcasesupport.force.com',\n gslbBaseURL, '00D3000000000Vb',\n 'Docket_Alarm_Outreach_Agents', {\n baseLiveAgentContentURL:\n 'https://c.la1-c1-dfw.salesforceliveagent.com/content',\n deploymentId: '57238000000CbI7',\n buttonId: '5733800000001Ds',\n baseLiveAgentURL:\n 'https://d.la1-c1-dfw.salesforceliveagent.com/chat',\n eswLiveAgentDevName:\n 'EmbeddedServiceLiveAgent_Parent04I380000008OIUEA2_1679eb7aaf7',\n isOfflineSupportEnabled: true\n }\n );\n sf_chat_svc = embedded_svc;\n };\n if(window.location.hostname == 'localhost') {\n \tconsole.log(\"Not loading Salesforce chat on localhost.\");\n \treturn;\n\t}\n $.getScript(\"https://service.force.com/embeddedservice/5.0/esw.min.js\", function () {\n initESW('https://service.force.com');\n });\n}\n\n///////////\n// Special GUI Elements\n\n/**\n * Sidebar.\n * Handles an autohiding, scrolling sidebar.\n * Returns a function that should be called on scrol and resize.\n */\nfunction setup_common_sidebar(sidebar, footer, top_elements_with_padding) {\n\tvar $sidebar = $(sidebar);\n\t// When we show the scrollbar on hover, we don't want it to shift things \n\t// around. Reduce padding on elements to make room for the scrollbar.\n\t$sidebar.hover(function() {\n\t\tif($sidebar.get(0).scrollHeight <= $sidebar.height()) {\n\t\t\treturn;\n\t\t}\n\t\tvar width = getScrollbarWidth($sidebar.parent()[0]).toString();\n\t\t$sidebar.find(top_elements_with_padding).filter(\":visible\")\n\t\t\t.css('padding-right', \"-=\" + width + \"px\");\n\t}, function () {\n\t\t$sidebar.find(top_elements_with_padding).filter(\":visible\")\n\t\t\t.css('padding-right', '');\n\t});\n\t\n\tif($(\".nosidebar:visible\").length) {\n\t\t// If there is no sidebar, tell the footer so it can modify its css.\n\t\t$(\"#footer\").addClass(\"nosidebar\");\n\t}\n\t\n\t// Handle sidebar toggling.\n\t$(\".toggle_sidebar\").click(function () { \n\t\tif($sidebar.offset().left >= 0) {\n\t\t\t$sidebar.css('left', -$sidebar.width());\n\t\t} else {\n\t\t\t$sidebar.animate({left: 0, duration:200});\n\t\t}\n\t});\n}\n\n/**\n * Actionbar\n * Automatically shrink down or hide elements in the action bar depending on \n * the overall width.\n */\nfunction adjust_actionbar_items($actionbar, $matchwidth) {\n\tif(!$actionbar) {\n\t\t$actionbar = $(\"#actionbar\");\n\t}\n\tvar total_width = $actionbar.width();\n\tvar $items = $actionbar.css('padding-left', '')\n\t\t.find(\"> div\").css(\"margin-left\", \"\");\n\t$items.find(\"span\").show();\n\t$actionbar.find(\"input:not(#hide_on_filter)\").show().css(\"width\", \"\");\n\t\n\t// Return a function which shrink the filter down to a size.\n\tfunction reduce_filter_to(width) { return function () {\n\t\tif($actionbar.find(\"input\").width() > width) {\n\t\t\t$actionbar.find(\"input\").css('width',\n\t\t\t\t$actionbar.find(\"input\").width() - 15\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\t} }\n\t// Try hiding a span label.\n\tfunction hide_span_label() {\n\t\tvar $labels = $items.find(\"span:visible:not(.nohide)\").first();\n\t\tif($labels.length) {\n\t\t\t$labels.hide();\n\t\t\treturn true;\n\t\t}\n\t}\n\t// Hide the input.\n\tfunction hide_filter_input() {\n\t\tif($actionbar.find(\"input\").is(\":visible\")) {\n\t\t\t$actionbar.find(\"input\").hide();\n\t\t\treturn true;\n\t\t}\n\t}\n\tfunction sqeeze_buttons_margin_to(min_margin) {\n\t\treturn function (){\n\t\t\tvar lefts = $items.map(function () {\n\t\t\t\treturn parseInt($(this).css(\"margin-left\")); \n\t\t\t});\n\t\t\tvar max_left = Array.max(lefts);\n\t\t\tif(max_left > min_margin) {\n\t\t\t\t$items.each(function () {\n\t\t\t\t\tif(parseInt($(this).css(\"margin-left\")) == max_left) {\n\t\t\t\t\t\t$(this).css(\"margin-left\", min_margin);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\tvar shrink_functions = [\n\t\treduce_filter_to(150),\n\t\thide_span_label,\n\t\treduce_filter_to(90),\n\t\tsqeeze_buttons_margin_to(15),\n\t\thide_filter_input,\n\t\tsqeeze_buttons_margin_to(5),\n\t\tsqeeze_buttons_margin_to(0)\n\t];\n\tvar num_shrinks = 0;\n\twhile(true) {\n\t\tvar item_widths = 0;\n\t\t$items.each(function () {\n\t\t\titem_widths += $(this).outerWidth(true); \n\t\t});\n\t\tif(item_widths < total_width) {\n\t\t\tbreak;\n\t\t}\n\t\t// Go through the various shrinking functions\n\t\tvar found = false;\n\t\tfor(var i = 0; !found && i < shrink_functions.length; i++) {\n\t\t\tfound = shrink_functions[i]();\n\t\t}\n\t\tif(!found) {\n\t\t\t// Can't shrink anymore.\n\t\t\tbreak;\n\t\t}\n\t\tnum_shrinks += 1;\n\t}\n\t// If we didn't shrink it down too much, and the action bar is far to \n\t// the left, bring it in so it's more noticeable.\n\tif(num_shrinks == 0 && $matchwidth && $matchwidth.length) {\n\t\t// The left end of the action bar.\n\t\tvar action_l = $actionbar.offset().left +\n\t\t\t\tparseInt($actionbar.css('padding-left'));\n\t\tvar match_l = $matchwidth.offset().left + \n\t\t\tparseInt($matchwidth.css('padding-left'));\n\t\t// The right end of the left most button\n\t\tvar $last_btn = $actionbar.find(\".btns_right_align\")\n\t\t\t.prevUntil(\":visible\");\n\t\t// prevUntil stops before the :visible element, go one more to be on it.\n\t\tif($last_btn && $last_btn.length)\n\t\t\t$last_btn = $last_btn.prev();\n\t\tif(!$last_btn || !$last_btn.length) {\n\t\t\t// Everything is visible.\n\t\t\t$last_btn = $actionbar.find(\".btns_right_align\").prev(\":visible\");\n\t\t}\n\t\tif($last_btn && $last_btn.length) {\n\t\t\tvar last_btn_right = $last_btn.offset().left + $last_btn.width();\n\t\t\t// Left end of the right buttons\n\t\t\tvar action_right_btns = $actionbar.find(\".btns_right_align\").offset().left;\n\t\t\tif (action_l < match_l && \n\t\t\t\tmatch_l + last_btn_right - action_l < action_right_btns) {\n\t\t\t\t$actionbar.css('padding-left', \n\t\t\t\t\t\"+=\" + (match_l - action_l).toString());\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction init_close_buttons() {\n\t$(\".close\").click(function() {\n\t\t// Find the DOM object to minimize, it must be a parent of the button\n\t\tvar to_close = $(this).parent();\n\t\twhile(to_close.length && !to_close.hasClass(\"closeable\")) {\n\t\t\tto_close = to_close.parent();\n\t\t}\n\t\tto_close.hide();\n\t\t// Close tooltips. There may be an error tip causing the user to close the dialog.\n\t\t$.powerTip.hide(null, true)\n\t});\n}\n\nvar bgiframe_inner = \" \";\nvar bgiframe = '';\n\n/**\n * Open a dialog window with many of the default options already set\n */\nfunction borderless_dialog(id, options) {\n\tif(options === undefined || options === null) {\n\t\toptions = {};\n\t}\n\t// Close any active tips when we show a dialog.\n\t$.powerTip.hide()\n\tvar position = options.position || { my: \"center\", at: \"center\", of: window };\n\treturn $(id).dialog({\n\t\tdialogClass\t: options.dialogClass || 'alert',\n\t\tautoOpen\t: options.autoOpen || false,\n\t\tresizable\t: options.resizable || false,\n\t\tdraggable\t: options.draggable || false,\n\t\tcloseOnEscape: options.closeOnEscape !== undefined ? \n\t\t\toptions.closeOnEscape : true,\n\t\twidth\t\t: options.width || \"auto\",\n\t\theight\t: options.height || \"auto\",\n\t\tshow \t\t: options.show || 'fade',\n\t\tmodal\t\t: options.modal != undefined ? options.modal : true,\n\t\tclasses\t\t: options.classes || {},\n\t\tposition \t: position,\n\t\topen\t\t: function _borderless_open(event, ui) {\n\t\t\t// Dont show the close button\n\t\t\tvar $dialog = $(this);\n\t\t\t$dialog.parent()\n\t\t\t\t.children('.ui-dialog-titlebar').hide().end()\n\t\t\t\t// Add an iframe behind the overlay to make sure it\n\t\t\t\t// can be seen above embedded PDFs.\n\t\t\t\t.parent().find(\".ui-widget-overlay\").html(bgiframe).end();\n\t\t\t// We need to reposition after opening. This is a hack.\n\t\t\tsetTimeout(function _post_open_resize() {\n\t\t\t\t$dialog.dialog(\"option\", \"position\", position);\n\t\t\t}, 40);\n\t\t\tif(options.open) {\n\t\t\t\treturn options.open(event, ui);\n\t\t\t}\n\t\t},\n\t\tfocus : function _borderless_focus(event, ui) {\n\t\t\tvar $this = $(this);\n\t\t\tvar $dialog = $this;\n\t\t\tif(!options.position) {\n $this.parent().css('position', 'fixed');\n }\n if($this.is(\":visible\")) {\n \t$dialog.dialog(\"option\", \"position\", position);\n\t\t\t}\n if(options.focus) {\n\t\t\t\treturn options.focus(event, ui);\n\t\t\t}\n\t\t}\n\t}).parent()\n\t\t.css(\"background\", \"transparent\")\n\t\t.css(\"border\", \"none\")\n\t.end();\n}\n\n// Search Name Constants.\nvar SEARCH_ALL_STR = \"All Courts\";\nvar COURT_ACRONYMS = ['PTAB', 'TTAB', 'ITC', 'Bankruptcies',\n\t'Federal Courts', 'SCOTUS', 'Trademarks', 'State Courts'\n];\n\n/**\n * Set all of the label widths to the same value within a given item\n */\nfunction update_label_widths(item) {\n\tif(!item)\n\t\titem = $(document);\n\tvar vals = $(item).hasClass(\"has_labels\") ? \n\t\t\t\t[item] : $(item).find(\".has_labels\");\n\t// Align all of the labels\n\t$(vals).each(function () {\n\t\tvar label_width = $(this).attr('data-label_width');\n\t\tlabel_width = label_width ? parseInt(label_width): 10;\n\t\tvar min_width = $(this).attr('data-min_width');\n\t\tvar max = min_width ? parseInt(min_width): 0;\n\t\t\n\t\t$(this).find(\"label\").not(\".ignore_label_width\")\n\t\t\t// Reset all the widths to the standard width\n\t\t\t.each(function () {\n\t\t\t\t$(this).css(\"width\", \"auto\");\n\t\t\t})\n\t\t\t// Find the widest label\n\t\t\t.each(function(l) { \n\t\t\t\tif ($(this).width() > max) \n\t\t\t\t\tmax = $(this).width(); \n\t\t\t})\n\t\t\t// Set all labels to the widest\n\t\t\t.each(function(l) { \n\t\t\t\t$(this).width(max+label_width); \n\t\t\t});\n\t});\n}\n\n//////////////////////////////////\n// Standardized Search Bar\n\n/**\n * Initialize the search bar. Should only be called on page-load.\n */\nfunction setup_search_bar() {\n\t// Set the
    action\n\tset_search_bar_form_action();\n\t// Parse the current URL and set the text\n\tsetup_search_bar_input_from_url();\n\t// Parse the facets.\n\tsetup_search_bar_input_from_facets();\n\t// Certain courts have submenus in the drop down.\n\tsetup_submenus();\n\n\t$(\".query_builder\")\n\t\t.find(\"input, select\")\n\t\t\t.change(update_advanced_search)\n\t\t\t.keyup(update_advanced_search).end()\n\t\t.find(\".add_filter\").click(function () {\n\t\t\tvar $qb = $(this).parents(\".query_builder\");\n\t\t\t$qb.find(\".group.hidden\").first().removeClass(\"hidden\");\n\t\t\tif(!$qb.find(\".group.hidden\").length) {\n\t\t\t\t$(this).hide();\n\t\t\t}\n\t\t\treturn false;\n\t\t}).data(\"powertip\", \"Add another search filter.\").powerTip({\n\t\t\tsmartPlacement:true\n\t\t}).end().find(\".date_cell\").datepicker({\n\t\t\tshowOn: \"button\",\n\t\t\tbuttonText: \"\",\n\t\t\tbuttonImageOnly: false,\n\t\t\tchangeMonth: true,\n\t\t\tchangeYear: true,\n\t\t\tbeforeShow: function(input, inst) {\n\t\t\t\tsetTimeout(function() {\n\t\t\t\t\tinst.dpDiv.css({\"z-index\": 1000});\n\t\t\t\t}, 0);\n\t\t\t}\n\t\t});\n\n\tvar $search_bar = $(\".search_engine_form\")\n\t\t.find(\".show_search_options\").click(function () {\n\t\t\ttoggle_advanced_search(false, $(this));\n\t\t\treturn false;\n\t\t}).powerTip({smartPlacement:true}).end()\n\t\t.find(\"input[type=text], textarea\").keypress(function (ev) {\n\t\t\t// Make sure enter submits the form (necessary for chrome b/c of\n\t\t\t// https://code.google.com/p/chromium/issues/detail?id=45549)\n\t\t\tif(ev.keyCode == 13) {\n\t\t\t\t\tget_parent_with_tag($(this), \"form\")\n\t\t\t\t\t\t// Hide the search helper once we submit.\n\t\t\t\t\t\t.find(\".search_helper\").hide().end()\n\t\t\t\t\t\t.submit();\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}).end()\n\t\t.find(\".selected_court\").click(function () {\n\t\t\t// Toggle if we select the court.\n\t\t\ttoggle_search_bar_court_dropdown(null, $(this).parent());\n\t\t\treturn false;\n\t\t}).end();\n\n\t// Add a handler for all court_dropdowns so for use outside the search bar.\n\t$(\".court_dropdown li:not(.has_submenu)\").click(function () {\n\t\t\t$('.selected_court .selected').text($(this).text());\n\t\t\tset_search_bar_form_action($(this));\n\n\t\t\tvar $form = get_parent_with_tag($(this), \"form\");\n\t\t\ttoggle_search_bar_court_dropdown(false, $form);\n\n\t\t\t// The drop-down can automatically search if requested.\n\t\t\tvar court_dropdown = get_parent_with_class($(this), \"court_dropdown\");\n\t\t\tif (court_dropdown && court_dropdown.hasClass(\"submitOnSelect\")) {\n\t\t\t\t$('.search_engine_form').submit();\n\t\t\t\t// Since we just selected this, add the selected class.\n\t\t\t\t$(this).addClass(\"selected\");\n\t\t\t}\n\t\t});\n\n\t$(\".search_btn_holder, .search_btn_holder_middle\").click(function () {\n\t\tvar form = get_parent_with_tag($(this), \"form\");\n\t\tform.submit();\n\t\treturn false;\n\t});\n\n\t// Grow the search bar when the user is typing into it.\n\tfunction check_grow($this) {\n\t\t// Calculate the width the of text that we have in the input\n\t\tvar text_width = get_parent_with_class($this, \"search_engine_form\")\n\t\t\t\t\t.find(\".widthcalc\").text($this.val()).width();\n\t\t// If it is getting near a full line, then grow it.\n\t\tvar enough_text = text_width > .8*$this.width();\n\t\t$this.toggleClass(\"grow\", enough_text);\n\t\t$search_bar.toggleClass(\"grow\", enough_text && $this.is(\":focus\"));\n\t}\n\t\n\tfunction get_court_context() {\n\t\tvar c = $(\".search_engine_form .selected_court .selected\").text();\n\t\treturn c == SEARCH_ALL_STR ? null : c;\n\t}\n\tfunction keydown_handler(ev) {\n\t\tif(ev.which == 9) { // TAB\n\t\t\t// Open up the court selector when they press tab.\n\t\t\tvar $form = get_parent_with_tag($(ev.target), \"form\");\n\t\t\tif($form && $form.find(\".inner\").length) {\n\t\t\t\ttoggle_search_bar_court_dropdown(true, $form);\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\t// The view that is shown when the user first enters the search bar.\n\tvar DEFAULT_SEARCH_VIEW = \"query_builder\";\n\n\t$search_bar\n\t\t.find(\".search_tabs_dropdown .tabs li\").click(function _click_tab() {\n\t\t\t// When the user clicks a search bar tab.\n\t\t\tvar $this = $(this);\n\t\t\tif($this.hasClass(\"disabled\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar type = $this.attr('for');\n\t\t\tsearchbar_show_dropdown($search_bar, type);\n\t\t\treturn false;\n\t\t}).end()\n\t\t.find(\"i.close\").click(function _close_search_dropdown() {\n\t\t\ttoggle_search_bar_court_dropdown(false, $search_bar);\n\t\t\treturn false;\n\t\t}).end()\n\t\t.find(\".inner textarea\")\n\t\t// Event handling for the main search bar text area.\n\t\t\t.on(\"input\", function _on_search_bar_input() {\n\t\t\t\tcheck_grow($(this));\n\t\t\t})\n\t\t\t.keydown(debouncer(get_suggest_handler(null, get_court_context,\n\t\t\t\t$search_bar.find(\".suggestions\"), null, null, keydown_handler), 300))\n\t\t\t.focus(function _textarea_focus() {\n\t\t\t\tvar $this = $(this);\n\t\t\t\tcheck_grow($this);\n\t\t\t\tvar no_dropdown = $this.prop(\"no_dropdown\");\n\t\t\t\tif (no_dropdown) {\n\t\t\t\t\t$this.removeProp(\"no_dropdown\");\n\t\t\t\t} else if($this.is(\":focus\")) {\n\t\t\t\t\tsearchbar_show_dropdown($search_bar, DEFAULT_SEARCH_VIEW);\n\t\t\t\t}\n\t\t\t}).blur(function _textarea_blur() {\n\t\t\t\tcheck_grow($(this));\n\t\t\t})\n\t\t.end()\n\t\t.find(\".suggestions\").bind('cssClassChanged', function _suggest_change() {\n\t\t\tvar $t = $(this);\n\t\t\tif($t.hasClass(\"autocomplete\") && $t.find(\".set\").length) {\n\t\t\t\tif(!searchbar_isactive_dropdown($search_bar, \"suggestions\")) {\n\t\t\t\t\tsearchbar_show_dropdown($search_bar, \"suggestions\");\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif(searchbar_isactive_dropdown($search_bar, \"suggestions\")) {\n\t\t\t\t\t$search_bar.find(\".tabs [for='suggestions']\").hide();\n\t\t\t\t\tsearchbar_show_dropdown($search_bar, DEFAULT_SEARCH_VIEW);\n\t\t\t\t}\n\t\t\t}\n\t\t}).end()\n\t\t.submit(function _check_submit_search_engine(ev) {\n\t\t\t// Make sure the query is not too long.\n\t\t\tvar f_len = 0;\n\t\t\t$(ev.target).find(\"[name]:enabled\").each(function _chk_targ_part() {\n\t\t\t\tf_len += encodeURIComponent($(this).val().toString()).length +\n\t\t\t\t\tencodeURIComponent($(this).attr('name')).length;\n\t\t\t});\n\t\t\t// The GAE limit 2048\n\t\t\tif(f_len > 1950) {\n\t\t\t\tshow_error_usermsg(\"Your query is too large (\" + \n\t\t\t\t\tf_len.toString() + \" letters). \" +\n\t\t\t\t\t\"Shorten it and search again.\");\n\t\t\t\tev.preventDefault();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t$(this).find(\".selected_court\").addClass(\"loading\");\n\t\t});\n\t\n\tvar SCROLL_DEBOUNCE = 100;\n\tvar MIN_SCROLL = 50; // Always show the scroll bar below here.\n\tvar last_scroll = 0;\n\tvar last_scroll_time = null;\n\tvar bd_div = document.getElementById('bd');\n\t$(document).scroll(function() {\n\t\tvar cur_scroll = $(document).scrollTop();\n\t\t// Don't update so often, otherwise it can cause jittery effects.\n\t\tvar body_start = bd_div ? bd_div.getBoundingClientRect().top : 0;\n\t\tif(cur_scroll > MIN_SCROLL && last_scroll_time && \n\t\t\t\tnew Date() - last_scroll_time < SCROLL_DEBOUNCE) {\n\t\t\t// But do update scroll_past_body\n\t\t\t$(\"body\").toggleClass(\"scroll_past_body\", body_start <= 0);\n\t\t\treturn;\n\t\t}\n\t\tif (cur_scroll > MIN_SCROLL && cur_scroll > last_scroll) {\n\t\t\t$(\"body\")\n\t\t\t\t.addClass(\"scroll_down\")\n\t\t\t\t.removeClass(\"scroll_up\")\n\t\t\t\t.toggleClass(\"scroll_past_body\", body_start <= 0);\n\t\t} else if (cur_scroll < last_scroll) {\n\t\t\t$(\"body\")\n\t\t\t\t.removeClass(\"scroll_down\")\n\t\t\t\t.addClass(\"scroll_up\")\n\t\t\t\t.toggleClass(\"scroll_past_body\", body_start <= 0);\n\t\t} else {\n\t\t\t$(\"body\")\n\t\t\t\t.removeClass(\"scroll_down\")\n\t\t\t\t.removeClass(\"scroll_up\")\n\t\t\t\t.toggleClass(\"scroll_past_body\", body_start <= 0);\n\t\t}\n\t\tlast_scroll = cur_scroll;\n\t\tlast_scroll_time = new Date();\n\t});\n}\n\nfunction searchbar_show_dropdown($search_bar, type) {\n\tvar $tabs = $search_bar\n\t\t.find(\".search_tabs_dropdown\")\n\t\t\t.addClass(\"shown\")\n\t\t\t.find(\".tabs li.selected\").removeClass(\"selected\").end()\n\t\t\t.find(\".tabs li[for='\" + type + \"']\").addClass(\"selected\").end()\n\t\t\t.find(\".tab\").hide().end()\n\t\t\t.find(\".tab.\" + type).show().end();\n\tif(type == 'search_helper') {\n\t} else if(type == \"query_builder\") {\n\t} else if(type == \"suggestions\") {\n\t\t$tabs.find(\".tabs [for='suggestions']\").show();\n\t} else if (type =='court_chooser') {\n\t\t// Add a re-entry checker.\n\t\tsetup_court_chooser($search_bar, function () {\n\t\t\ttoggle_ptab_submenu(true);\n\t\t\t// Add a timeout to wait until after the animation completes.\n\t\t\tsetTimeout(function () {\n\t\t\t\t$search_bar.find(\".selected_court input[type=text]\")\n\t\t\t\t\t.first().focus();\n\t\t\t}, 300);\n\t\t});\n\t} else {\n\t\tthrow (\"Unknown dropdown to show: \" + type);\n\t}\n}\nfunction searchbar_isactive_dropdown($search_bar, type) {\n\tvar $dropdown = $search_bar.find(\".search_tabs_dropdown\");\n\tif(!$dropdown.hasClass(\"shown\")) {\n\t\treturn false;\n\t}\n\tif(type) {\n\t\tvar $sel = $dropdown.find(\".tabs li.selected\");\n\t\tif(!$sel.length || $sel.attr('for') != type){\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\nfunction searchbar_hide_dropdown(force_hide, $el) {\n\tif(!$el) {\n\t\t$el = $(\".search_tabs_dropdown\")\n\t} else if(!$el.hasClass(\"search_tabs_dropdown\")) {\n\t\tif($el.parents(\".search_tabs_dropdown\").length) {\n\t\t\t$el = $el.parents(\".search_tabs_dropdown\");\n\t\t} else {\n\t\t\t$el = $(\".search_tabs_dropdown\")\n\t\t}\n\t}\n\tvar had_class = $el.hasClass(\"shown\");\n\t$el.removeClass('shown');\n\tif(had_class) {\n\t\t// After closing the dropdown, give focus back to textarea so user can\n\t\t// continue with query. But only do so if it was actually closed.\n\t\t// Add a timeout to wait until after the animation completes.\n\t\tsetTimeout(function () {\n\t\t\tif(!$el.hasClass(\"shown\") &&\n\t\t\t\t!$(\".search_sub_select\").is(\":visible\")) {\n\t\t\t\t$el.parents(\".search_engine_form\").find(\"textarea\").first().prop('no_dropdown', true).focus();\n\t\t\t}\n\t\t}, 300);\n\t}\n}\n\n\n/**\n * Get the current URL hash value.\n * @returns {any}\n */\nfunction get_url_hash() {\n\tvar hashloc = location.href.search(\"#\");\n\treturn hashloc > 0 ? location.href.substring(hashloc+1):null;\n}\n\n/**\n * Returns a keydown handler that handles autocompletes analytics requests.\n * When selected, the user will go to a analytics page of the entity. See\n * further below for a variation on this function that only makes\n * suggestions, and does not change the page when clicked.\n * @param entity if set, returns suggestions only for the given entity.\n * @param cur_court_context A function or string, that sets the context of \n\t\t\t\t\t\t\tthe suggestion, i.e., PTAB, ITC, etc.\n * @param get_suggest_div \tFunction that returns the suggestion div.\n * @param selection_required If true, at least one item will be selected.\n * @param custom_formatter \tOptional function that takes formats the suggestions\n\t\t\t\t\t\t\tinto HTML. Takes null if there is no suggestion.\n */\nvar suggest_supported = true;\nfunction get_suggest_handler(entity, court_context, get_suggest_div,\n\tselection_required, custom_formatter, keydown_handler, replace_div) {\n\tif(!get_suggest_div) {\n\t\t\tget_suggest_div = function ($this) {\n\t\t\t\treturn get_parent_with_class($this, \"search_engine_form\")\n\t\t\t\t\t.find(\".search_helper\")\n\t\t\t}\n\t}\n\tif(!replace_div) {\n\t\treplace_div = $('textarea[name=q]')\n\t}\n\t\n\treturn function handle_suggest(ev) {\n\t\tvar $this = $(this);\n\t\tvar text = $this.val();\n\t\tvar helper = typeof get_suggest_div == 'function' ?\n\t\t\tget_suggest_div($this) : get_suggest_div;\n\t\t// Try putting html in a special sub-div. Overwrite if nonexistent.\n\t\tvar $suggestions = helper.find(\".suggestions\").length ?\n\t\t\t\thelper.find(\".suggestions\") : helper;\n\t\t\n\t\t// Figure out which suggestions is selected.\n\t\t// Returns all suggestions, the selected index, and the selection.\n\t\tfunction selected() {\n\t\t\tvar $sug = helper.find(\".suggestion\");\n\t\t\tfor(var i = 0; i < $sug.length; i++) {\n\t\t\t\tif($($sug[i]).hasClass(\"selected\")) {\n\t\t\t\t\treturn {$ : $sug, selected_i : i, $selected : $($sug[i])};\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn {$ : $sug, selected_i : null, $selected : null};\n\t\t}\n\t\t\n\t\t// Allow a customer handler to override the suggestion.\n\t\tif(keydown_handler && keydown_handler(ev, selected().$selected) === false) {\n\t\t\tev.preventDefault();\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif(!text.strip().length || !suggest_supported) {\n\t\t\t// If there is nothing to search for, turn off autocomplete.\n\t\t\thelper.removeClass(\"autocomplete\");\n\t\t\t$suggestions.html(custom_formatter ? custom_formatter(null) : \"\");\n\t\t\treturn;\n\t\t}\n\t\t// Handle navigating the autocomplete\n\t\tvar sel = null;\n\t\tswitch(ev.which) {\n\t\t\tcase 38: // UP\n\t\t\t\tsel = selected();\n\t\t\t\tif(sel.$selected === null) {\n\t\t\t\t\t// Add the bottom one.\n\t\t\t\t\t$(sel.$[sel.$.length - 1]).addClass(\"selected\");\n\t\t\t\t} else {\n\t\t\t\t\tsel.$selected.removeClass(\"selected\");\n\t\t\t\t\t$(sel.$[(sel.selected_i - 1 + sel.$.length) % sel.$.length])\n\t\t\t\t\t\t.addClass(\"selected\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\tcase 40: // DOWN\n\t\t\t\tsel = selected();\n\t\t\t\tif(sel.$selected === null) {\n\t\t\t\t\t// Add the bottom one.\n\t\t\t\t\t$(sel.$[0]).addClass(\"selected\");\n\t\t\t\t} else {\n\t\t\t\t\tsel.$selected.removeClass(\"selected\");\n\t\t\t\t\t$(sel.$[(sel.selected_i+1) % sel.$.length]).addClass(\"selected\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\tcase 13: // ENTER\n\t\t\t\tsel = selected();\n\t\t\t\tsel = sel.$selected && sel.$selected.attr('href');\n\t\t\t\tif(sel && sel != \"#\") {\n\t\t\t\t\t// Go to the selected page.\n\t\t\t\t\twindow.location = sel;\n\t\t\t\t\treturn;\n\t\t\t\t} else {\n\t\t\t\t\t// This may be handled by an alternative mechanism.\n\t\t\t\t\tconsole.log(\"Ignoring b/c no suggested URL: \" + sel);\n\t\t\t\t}\n\t\t}\n\t\t\n\t\tvar cur_entity = typeof entity == 'function' ? entity() : entity;\n\t\tvar cur_court_context = typeof court_context == 'function' ? \n\t\t\tcourt_context() : court_context;\n\t\t\n\t\t// Make an ajax call to get the suggestions.\n\t\thelper.addClass(\"loading\");\n\t\t$.ajax({\n\t\t\tcache: true,\n\t\t\tdataType: \"json\",\n\t\t\turl: \"/search/getsuggest.ajax\",\n\t\t\tdata: { \n\t\t\t\ttext : text, \n\t\t\t\tentity : cur_entity, \n\t\t\t\tcourt_context : cur_court_context,\n\t\t\t\targs : get_url_hash(),\n\t\t\t},\n\t\t}).success(function(resp) {\n\t\t\thelper.removeClass(\"loading\");\n\t\t\tif (text != $this.val() || !resp.success) {\n\t\t\t\t// The search text has changed, don't process this result.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif(resp.suggestions_not_supported) {\n\t\t\t\tsuggest_supported = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Render the suggestions as usable html.\n\t\t\tvar html = custom_formatter ? custom_formatter(resp.suggestions):\n\t\t\t\tresp.suggestions.map(function (t) {\n\t\t\t\t// Each suggestions has several options, iterate and merge:\n\t\t\t\tvar options = t.options.map(function (o) {\n\t\t\t\t\tif(o.cursor) {\n\t\t\t\t\t\t// A cursor suggestion.\n\t\t\t\t\t\treturn \"\" + o.text + \"\";\n\t\t\t\t\t}\n\t\t\t\t\treturn \"\" + \n\t\t\t\t\t\to.text + \"\";\n\t\t\t\t}).join(\" \");\n\t\t\t\tvar title = t.title ? \"
    \" + t.title +\n\t\t\t\t\t\"
    \" : \"\";\n\t\t\t\treturn \"
    \" + t.name + \n\t\t\t\t\t\"
    \" + title + options +\n\t\t\t\t\t\"
    \";\n\t\t\t}).join(\" \");\n\t\t\thelper.addClass(\"autocomplete\");\n\t\t\t// If we replace the query with a suggestion, save the original.\n\t\t\tvar replaced_query = null;\n\t\t\t$suggestions\n\t\t\t\t.html(html)\n\t\t\t\t.toggleClass(\"entity_suggest\", cur_entity !== undefined)\n\t\t\t\t.find(\".suggestion\").hover(function() {\n\t\t\t\t\t// When we hover, change the selected autocomplete\n\t\t\t\t\thelper.find(\".suggestion\").removeClass(\"selected\");\n\t\t\t\t\t$(this).addClass(\"selected\");\n\t\t\t\t}).bind('cssClassChanged', function () {\n\t\t\t\t\t// When an item is selected.\n\t\t\t\t\tif($(this).hasClass('selected')) {\n\t\t\t\t\t\tvar cursor = $(this).data('cursor');\n\t\t\t\t\t\t// For cursor suggests, replace query and set cursor.\n\t\t\t\t\t\tif(cursor) {\n\t\t\t\t\t\t\t// Save the original.\n\t\t\t\t\t\t\tif(!replaced_query) {\n\t\t\t\t\t\t\t\treplaced_query = replace_div.val();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Set the query and cursor.\n\t\t\t\t\t\t\treplace_div.val($(this).text()).selectRange(cursor);\n\t\t\t\t\t\t} else if (replaced_query) {\n\t\t\t\t\t\t\t// Set the original back.\n\t\t\t\t\t\t\treplace_div.val(replaced_query);\n\t\t\t\t\t\t\treplaced_query = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tif(selection_required) {\n\t\t\t\tvar sel = selected();\n\t\t\t\tif(sel.$selected === null) {\n\t\t\t\t\t$(sel.$[0]).addClass(\"selected\");\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n\n/**\n * Add a dropdown suggester to any text input field.\n * @param $text_input \tThe text input field.\n * @param $suggest_div \tA div to store the suggestions. Use SCSS for styling:\n * \t\t\t\t\t\t\t%search_suggestions_window\n * \t\t\t\t\t\t\t%search_suggestions_window\n * @param entity\t\tWhat entity to suggest (default: \"court\"). Can be\n * \t\t\t\t\t\tparty, judge, attorney, firm, or court. Can also be a\n * \t\t\t\t\t\tfunction that takes no args and returns the entity name.\n * \t@param court_context\t(Optional) Provide a court context to narrow down\n * \t\t\t\t\t\tthe possible entities. Options are \"PTAB\", \"TTAB\",\n * \t\t\t\t\t\t\"FederalCourts\", and others in search_views.py.\n */\nfunction add_entity_suggestion_to_input($text_input, $suggest_div, entity,\n\t\t\t\t\t\t\t\t\t\tcourt_context) {\n\t// Default to showing court names.\n\tif(!entity) { entity = \"court\"; }\n\n\tfunction make_selection($input, cur_entity){\n\t\tvar val = $input.data(cur_entity);\n\t\tif(!val || !val.length) {\n\t\t\tval = $input.text();\n\t\t}\n\t\t// Be sure to trigger change.\n\t\t$text_input.val(val).trigger(\"change\");\n\t\t// When selecting a suggestion, close the court search dropdown.\n\t\t$suggest_div.hide();\n\t}\n\n\tfunction court_search_results(results) {\n\t\tif(!results) {\n\t\t\t$suggest_div.hide();\n\t\t\treturn false;\n\t\t}\n\t\t$suggest_div.show();\n\t\t// Entity might be\n\t\tvar cur_entity = typeof entity == 'function' ? entity() : entity;\n\t\t// We're only interested in the court suggestions.\n\t\tvar entities = results.filter(function(t) {\n\t\t\treturn t.entity == cur_entity;\n\t\t});\n\t\t// Check if there are no suggestions\n\t\tif(!entities || !entities.length) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar $suggestions = join_jquery(entities[0].options.map(function (t) {\n\t\t\tvar v = t.entity || '';\n\t\t\t// Each suggestions has several options, iterate and merge:\n\t\t\treturn $(\"
  • \" + t.text + \"
  • \").data(cur_entity, v);\n\t\t}), \"options\", \"ul\")\n\t\t.find(\"li\").click(function () {\n\t\t\tmake_selection($(this), cur_entity);\n\t\t\treturn false;\n\t\t}).end();\n\t\treturn $(\"
    \").append($suggestions);\n\t}\n\t$text_input.keydown(debouncer(get_suggest_handler(\n\t\tentity, court_context, $suggest_div, false, court_search_results, null, $text_input),\n\t\t100, function _debounce_retval(ev) {\n\t\t\tif(ev.which == 13) {\n\t\t\t\t// ENTER Key\n\t\t\t\t// If they press enter while making a selection, we want to\n\t\t\t\t// do the selection. But if they press enter without a selecting,\n\t\t\t\t// assume they want to submit the form.\n\t\t\t\tif($suggest_div.is(\":visible\")) {\n\t\t\t\t\t// The dropdown is visible, see if we've selected anything.\n\t\t\t\t\tvar $selected = $suggest_div.find(\".selected\");\n\t\t\t\t\tif($selected.length == 1) {\n\t\t\t\t\t\t// Make the selection.\n\t\t\t\t\t\tvar cur_entity = typeof entity == 'function' ? entity() : entity;\n\t\t\t\t\t\tmake_selection($selected, cur_entity);\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn undefined;\n\t\t\t\t} else {\n\t\t\t\t\t// Bubble up to form.\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If the user presses the enter key, we do want it to bubble up.\n\t\t\treturn undefined;\n\t})).end();\n}\n\n/**\n * Add a list of suggestions to an autocomplete.\n * @param get_val The text input field with the autocomplete.\n * @param get_suggest_div The div to add the suggestions to.\n * @param get_entity A function that gets the current entity.\n * @param court_context The court context to use.\n * @param max_entities\n * @param url\n */\nfunction add_contained_entities(get_val, get_suggest_div, get_entity,\n court_context, max_entities, url, success) {\n var text_val = get_val();\n var entity = get_entity();\n var loading_msg = wrap_html(\"Loading list of \" + plural(2, entity));\n var $suggest_div = get_suggest_div();\n $suggest_div.html(loading_msg).addClass(\"loading\");\n // Disable the form from submitting.\n var $inputs = $suggest_div.parents(\"form\").find(\"input\").prop(\"disabled\", true);\n var hard_max_entities = 50;\n if(!max_entities) {\n max_entities = 10;\n }\n\n // Use the other options to narrow.\n var params = {};\n if(url) {\n var hash = url.split(\"#\")[1] || url.split(\"?\")[1] || url;\n params = $.deparam(hash);\n }\n\n var data = {\n max_entities : max_entities,\n text : text_val,\n entity : entity,\n court_context : court_context,\n mode : 'query',\n date_filed_start: params.date_filed_start,\n date_filed_end: params.date_filed_end,\n f : params.f\n };\n $.ajax({\n cache: true,\n dataType: \"json\",\n url: \"/search/getsuggest.ajax\",\n data: $.param(data, true),\n }).success(function(resp) {\n \t$suggest_div = get_suggest_div()\n $suggest_div.removeClass(\"loading\");\n $inputs.prop(\"disabled\", false);\n if(get_val() != text_val || get_entity() != entity) {\n // out of date.\n return;\n }\n if(!resp.success || resp.suggests_not_supported) {\n $suggest_div.text(resp.error);\n return;\n }\n // Clear the existing div.\n $suggest_div.html(\"\");\n if(resp.suggestions.length != 1) {\n if(success) {\n success(resp.suggestions);\n }\n return;\n }\n var suggests = resp.suggestions[0];\n if(suggests.entity != entity) {\n return;\n }\n var items = suggests.options.map(function (opt) {\n var id = uniqueid();\n var count = opt.count ? wrap_html(number_with_abbrev(opt.count),\n \"span\", \"count\") : \"\";\n var label = wrap_html(escape_html(opt.entity) + count, \"label\",\n \"\", {}, {for : id});\n var only = suggests.options.length > 2 ?\n wrap_html(\"only\", \"a\", \"show_only\") : \"\";\n var chkbox = \"\";\n return wrap_html(chkbox + label + only, \"li\");\n }).join(\"\");\n var extra_options = '';\n // Add a checkbox allowing them to limit results to exact matches.\n if(suggests.options.length >= max_entities) {\n var load_more = max_entities < hard_max_entities ?\n wrap_html(wrap_html(\"\", \"i\", \"far fa-plus-square\") +\n \"Load More\", \"a\", \"load_more\") : \"\";\n extra_options += load_more;\n }\n if(suggests.options.length > 1) {\n var id = uniqueid();\n var limit_check = wrap_html(\n \"\" +\n wrap_html(\"Limit to exact \" + plural(2, entity) + \" above.\", \"label\", \"\", {}, {for : id}),\n \"div\", \"limit_check\");\n extra_options += limit_check;\n }\n if(extra_options.length) {\n extra_options = wrap_html(extra_options, \"div\", \"extra_options\");\n }\n\n var ul_list = wrap_html(items, 'ul');\n var title = wrap_html(\"Confirm \" + plural(2, entity) +\n \" to include in analytics:\", \"div\", \"explain\");\n $suggest_div.html(title + ul_list + extra_options).show()\n .find(\".load_more\").click(function () {\n max_entities = Math.min(max_entities*2, hard_max_entities);\n add_contained_entities(get_val, get_suggest_div, get_entity, court_context, max_entities, url, success);\n return false;\n }).end()\n .find(\".show_only\").click(function () {\n var $this = $(this);\n $suggest_div\n .find(\"input.entity_exact[type=checkbox]\").prop(\"checked\", \"\").end()\n // Default to exact search when they select show only.\n .find(\"input.only_entity_exact[type=checkbox]\").prop(\"checked\", true).end();\n $this.parents(\"li\").first().find(\"input.entity_exact[type=checkbox]\").prop(\"checked\", true);\n if(success) {\n success(resp.suggestions);\n }\n return false;\n }).end();\n if(success) {\n success(resp.suggestions);\n }\n });\n}\n\n/**\n * Given a set of checkboxes in an entity selector, return the query that should be applied.\n * @param $suggest_div The div containing the checkboxes.\n * @param orig_val Original value of query.\n * @returns {string|string|*}\n */\nfunction contained_entities_to_query($suggest_div, orig_val) {\n // Determine if they are limiting to a set of party names.\n var limit_checked = $suggest_div.find(\".limit_check input[type=checkbox]:checked\").length;\n\n // Get the checked if limiting, or the unchecked if not.\n var vals = $suggest_div.find(limit_checked ? \"li input[type=checkbox]:checked\" : \"li input[type=checkbox]:not(:checked)\").map(function () {\n return wrap_query($(this).attr('name'), false, '\"');\n }).get();\n var joined_vals = wrap_query(vals.length ? vals.join(\" OR \") : \"\", vals.length > 1);\n\n // Create the query from the joined values.\n return vals.length && limit_checked ? joined_vals:\n vals.length && !limit_checked ? orig_val + \" AND NOT \" + joined_vals:\n orig_val;\n}\n\n/**\n * Convert a url query string into an object. Does some special checks for\n * google webcache.\n * @param url_str\n * @returns {{}}\n */\nfunction url_params_to_object(url_str) {\n\tif(!url_str && document.URL.split(\"?\")[0]\n\t\t\t.search(/webcache\\.googleusercontent\\.com/g) != -1) {\n\t\t// When in google's webcache, get the actual query URL. \n\t\treturn url_params_to_object($.deparam.querystring().q);\n\t}\n\treturn url_str == null ? $.deparam.querystring() : $.deparam(url_str, true);\n}\n\n/**\n * Convert a URL into a fragment. If the URL has no fragment, returns an empty.\n * @param url_str\n * @returns {string|string}\n */\nfunction url_to_fragment(url_str) {\n\tvar hash = new URL(event.newURL).hash;\n\treturn hash ? hash.substring(1) : \"\";\n}\n\n/**\n * Convert a javascript dictionary object into a URL. The URL will have the\n * same domain and http protocol as the currently loaded page.\n * @param params\tA dictionary where k->v will turn into query parameters.\n * @param pathname\tA pathname to use. If blank, will use the current path.\n *\t\t\t\t\tTo override the protocol or hostname, you can specify it.\n * @returns {string}\tA URL.\n */\nfunction url_from_params(params, pathname) {\n // Jquery does most of the work. But set traditional to true otherwise\n\t// arrays will get messed up.\n var query_str = $.param(params, true);\n // Add the path.\n if(pathname && pathname.search(\"://\") > 0) {\n \t// The path has a host protocol already specified, so use it.\n return pathname + \"?\" + query_str; \n }\n // Add the correct protocol.\n return location.protocol + \"//\" + location.host + \n (pathname ? pathname : location.pathname) + \"?\" + query_str; \n}\n\nfunction toggle_search_bar_court_dropdown(force, parent) {\n\t// Shows and hides the search bar dropdown\n\t// First find the full search bar.\n\tvar $search_bar = $(parent || \".search_engine_form\");\n\tif (!$search_bar.hasClass(\"search_engine_form\")) {\n\t\t$search_bar = $search_bar.parents(\".search_engine_form\");\n\t\tif(!$search_bar.length) {\n\t\t\tconsole.log(\"Could not find search bar\");\n\t\t\treturn false;\n\t\t}\n\t}\n\tvar $dropdown = $search_bar.find(\".court_dropdown\");\n\tif(!$dropdown.length) {\n\t\tconsole.log(\"Cannot find dropdown\");\n\t\treturn false;\n\t}\n\t// Get the dropdown inner. We expect the HTML to be well formed.\n\tvar $inner = $search_bar.find(\".inner\");\n\tif(!$inner.length) {\n\t\tconsole.log(\"Expecting the parent of dropdown to be a search inner!\");\n\t\treturn;\n\t}\n\t\n\tvar was_visible = searchbar_isactive_dropdown($search_bar, \"court_chooser\");\n if(force === false || (was_visible && force !== true)) {\n\t\t// Hide it\n\t\tsearchbar_hide_dropdown(true, $dropdown);\n\t\treturn;\n }\n\t// Show it.\n\t$search_bar = $dropdown.parents(\".search_engine_form\");\n\tif ($dropdown.hasClass(\"court_chooser\")) {\n\t\t// Initialize the court chooser, then show.\n\t\tsetup_court_chooser($search_bar, function () {\n\t\t\tsearchbar_show_dropdown($search_bar, \"court_chooser\");\n\t\t\ttoggle_ptab_submenu(true);\n\t\t\t$dropdown.show();\n\t\t\t// Add a timeout to wait until after the animation completes.\n\t\t\tsetTimeout(function () {\n\t\t\t\t$inner.find(\".selected_court input[type=text]\").focus();\n\t\t\t}, 300);\n\t\t});\n\t} else {\n // Simply show it\n $dropdown.show();\n }\n}\n\n/**************************\n * Tree Selector\n * A data structure used to assist with selecting and unselecting objects\n * stored in a tree.\n **/\nvar TreeSelector = function _TreeSelector(tree) {\n\tthis.tree = tree;\n\tthis.selected = [];\n};\n/**\n * Return the keys of an object or values in an array ass a list\n */\nTreeSelector.prototype.get_keys = function(ar_or_obj) {\n\tif(Array.isArray(ar_or_obj)) {\n\t\treturn ar_or_obj;\n\t}\n\tvar keys = [];\n\tfor(var k in ar_or_obj) {\n\t\tkeys.push(k);\n\t}\n\treturn keys;\n};\n\n/**\n * Compare two arrays. \n * Return true if equal.\n * Return 1 if first starts with second.\n * Return -1 if second starts with first.\n */\nTreeSelector.prototype.compareArray = function(ar1, ar2) {\n\tfor(var i = 0; i < ar1.length && i < ar2.length; i++) {\n\t\tif(ar1[i] != ar2[i]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn ar1.length == ar2.length ? true : \n\t\tar1.length > ar2.length ? 1 :\n\t\tar1.length < ar2.length ? -1:\n\t\tfalse;\n};\n\n/**\n * Returns true if the given set of keys is selected.\n * Returns 1 if one of its children is selected.\n * Otherwise return false.\n **/\nTreeSelector.prototype.isSelected = function(keys) {\n\tfor(var i = this.selected.length - 1; i >= 0; i--) {\n\t\tvar cmp = this.compareArray(keys, this.selected[i]);\n\t\tif(cmp == true || cmp == 1) {\n\t\t\treturn true;\n\t\t} else if(cmp == -1) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn false;\n};\n\nTreeSelector.prototype.isIn = function(ar, array_list) {\n\tfor(var i = 0; i < array_list.length; i++) {\n\t\tif(this.compareArray(ar, array_list[i]) === true) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n};\n/**\n * Get the value at the node specified by keys.\n **/\nTreeSelector.prototype.get = function(keys) {\n\tvar groups = this.tree;\n\tfor(var i = 0; i < keys.length; i++) {\n\t\tif(Array.isArray(groups)) {\n\t\t\treturn null;\n\t\t}\n\t\tgroups = groups[keys[i]];\n\t}\n\treturn groups;\n};\n\n/**\n * Get the path to the value. Note that this is O(N).\n */\nTreeSelector.prototype.find = function(value) {\n\tfunction search(node) {\n\t\tif(Array.isArray(node)) {\n\t\t\treturn node.indexOf(value) == -1 ? null : [value];\n\t\t}\n\t\tfor(var k in node) {\n\t\t\tvar s = search(node[k]);\n\t\t\tif(s != null) {\n\t\t\t\t// We found the value.\n\t\t\t\treturn [k].concat(s);\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\treturn search(this.tree);\n};\n\n/**\n * Return an array of arrays, where each array lists the keys to a child node.\n */\nTreeSelector.prototype.children = function(keys) {\n\tvar parent_path = [];\n\tvar groups = this.tree;\n\tfor(var i = 0; i < keys.length; i++) {\n\t\tvar p = keys[i];\n\t\tif(Array.isArray(groups)) {\n\t\t\treturn [];\n\t\t} else {\n\t\t\tgroups = groups[p];\n\t\t\tparent_path.push(p);\n\t\t}\n\t}\n\t// Now add the parent path into the name.\n\treturn this.get_keys(groups).map(function (x) {\n\t\treturn parent_path.concat([x]);\n\t});\n};\n\n/**\n * Clear the selection.\n */\nTreeSelector.prototype.clear = function() {\n\tthis.selected = [];\n};\n\n/**\n * Add a new selection. If the seletion is in the tree, and caused an entire \n * set of leafnodes to fill up, then it will remove all the children and add \n * the parent.\n * If the item is not in the tree, that's okay, it can still be selected, but \n * the selection will not modify the tree state.\n */\nTreeSelector.prototype.add = function(keys) {\n\t// Remove all items that start with this item.\n\tfor(var i = this.selected.length - 1; i >= 0; i--) {\n\t\tvar cmp = this.compareArray(this.selected[i], keys);\n\t\tif(cmp == true || cmp == 1) {\n\t\t\tthis.selected.splice(i, 1);\n\t\t}\n\t}\n\t// Add this selection.\n\tthis.selected.push(keys);\n\tvar _this = this;\n\t\n\tfunction check_compact_children(parent_keys) {\n\t\t// If we added all children under a parent, then we need to remove the\n\t\t// children from the list and add the parent.\n\t\tvar group = _this.tree;\n\t\tfor(var p_i = 0; p_i < parent_keys.length; p_i++) {\n\t\t\tgroup = group[keys[p_i]];\n\t\t\tif(!group) {\n\t\t\t\t// The item is not in the tree. \n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tvar siblings = _this.get_keys(group).map(function(x) {\n\t\t\treturn parent_keys.concat([x]);\n\t\t});\n\t\tfor(var s_i = 0; s_i < siblings.length; s_i++) {\n\t\t\tif(!_this.isIn(siblings[s_i], _this.selected)) {\n\t\t\t\t// This court is not in the selected index, no need to compact.\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t// We need to remove all elements that start with parent_keys.\n\t\tfor(var i = _this.selected.length - 1; i >= 0; i--) {\n\t\t\tvar cmp = _this.compareArray(_this.selected[i], parent_keys);\n\t\t\tif(cmp === true || cmp === 1) {\n\t\t\t\t_this.selected.splice(i, 1);\n\t\t\t}\n\t\t}\n\t\t_this.selected.push(parent_keys);\n\t\t// We may need to compact again\n\t\tif(parent_keys.length) {\n\t\t\tcheck_compact_children(parent_keys.slice(0, -1));\n\t\t}\n\t}\n\tcheck_compact_children(keys.slice(0, -1));\n};\n\n/**\n * Remove the given selection.\n */\nTreeSelector.prototype.remove = function(keys) {\n\t// Remove the item and all of the children items.\n\tvar removed_exact = false, removed_partial = [];\n\tfor(var i = this.selected.length - 1; i >= 0; i--) {\n\t\tvar cmp = this.compareArray(keys, this.selected[i]);\n\t\tif(cmp === true) {\n\t\t\tthis.selected.splice(i, 1);\n\t\t\tremoved_exact = true;\n\t\t} else if(cmp == 1) {\n\t\t\tremoved_partial.push(this.selected[i]);\n\t\t\tthis.selected.splice(i, 1);\n\t\t} else if(cmp == -1){\n\t\t\tconsole.log(\"Invalid state\");\n\t\t\tthis.selected.splice(i, 1);\n\t\t}\n\t}\n\t// If removing a child caused a parent to become indeterminate, \n\t// add the other children.\n\tvar _this = this;\n\tfunction add_children(parent_court) {\n\t\tvar courts = _this.children(parent_court);\n\t\tfor(var j = 0; j < courts.length; j++) {\n\t\t\tvar cmp = _this.compareArray(keys, courts[j]);\n\t\t\tif(cmp === true) {\n\t\t\t\t; // Don't add the one we're removing.\n\t\t\t} else if(cmp == 1) {\n\t\t\t\t// Make a recursive call and add the children of this node.\n\t\t\t\tadd_children(courts[j]);\n\t\t\t} else if(cmp === false) {\n\t\t\t\t// We can add the node and only this node.\n\t\t\t\t_this.selected.push(courts[j]);\n\t\t\t}\n\t\t}\n\t}\n\tfor(var r_i = 0; r_i < removed_partial.length; r_i++) {\n\t\tadd_children(removed_partial[r_i]);\n\t}\n};\n\n/**\n * Return a list of items to include and exclude. This list can be \n * signifigantly smaller than items solely to include.\n **/\nTreeSelector.prototype.selected_simplified = function() {\n\t// Group the seleted items by their parents.\n\tvar parents = {};\n\tfor(var t_i = 0; t_i < this.selected.length; t_i++) {\n\t\tvar t = this.selected[t_i];\n\t\t// Get this parent. The root will be an empty array.\n\t\tvar p_i = JSON.stringify(t.slice(0, -1));\n\t\tif(parents[p_i]) {\n\t\t\tparents[p_i].push(t);\n\t\t} else {\n\t\t\tparents[p_i] = [t];\n\t\t}\n\t}\n\tvar selected = [];\n\t// Now go through the parents.\n\tfor(var p in parents) {\n\t\tvar parent_keys = JSON.parse(p);\n\t\tvar children = this.children(parent_keys);\n\t\t// If roots, we need to include all.\n\t\tvar are_roots = parent_keys.length == 0;\n\t\tif(are_roots || parents[p].length < children.length / 2) {\n\t\t\t// If including fewer than half the children, include them all.\n\t\t\tselected.push({\n\t\t\t\tinclude : parents[p],\n\t\t\t\texclude : [],\n\t\t\t})\n\t\t} else {\n\t\t\t// It is more efficient to include the parent and exclude subitems.\n\t\t\tselected.push({\n\t\t\t\tinclude : [parent_keys],\n\t\t\t\texclude : []\n\t\t\t});\n\t\t\tvar p_include = {};\n\t\t\t// Make a set of the items we're including.\n\t\t\tfor(var i = 0; i < parents[p].length; i++) {\n\t\t\t\tp_include[JSON.stringify(parents[p][i])] = true;\n\t\t\t}\n\t\t\t// Now iterate through all children.\n\t\t\tfor(var c_i = 0; c_i < children.length; c_i++) {\n\t\t\t\tvar c = children[c_i];\n\t\t\t\t// See if it isn't supposed to be included.\n\t\t\t\tif(!p_include[JSON.stringify(c)]) {\n\t\t\t\t\t// Exclude this one.\n\t\t\t\t\tselected[selected.length - 1].exclude.push(c);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn selected;\n};\n\n// This will be calculated below.\nvar global_courts_selected = null;\n\n/**\n * The court chooser is pretty complex, set things up here\n */\nfunction setup_court_chooser($search_bar, onComplete) {\n\t// Find the elements we'll be working with.\n\tvar $chooser = $search_bar.find(\".court_chooser\");\n\tvar $inner = $search_bar.find(\".inner\");\n\tif(!$inner || !$inner.length) {\n\t\tthrow \"No search bar specified to setup\";\n\t}\n\tif(!$chooser.length){\n\t\tthrow \"No court chooser specified to setup\";\n\t}\n\t\n\t// Prevent reentry.\n\tif($chooser.data(\"chooser-init\")) {\n\t\tif(onComplete) {\n\t\t\tonComplete($chooser);\n\t\t}\n\t\treturn;\n\t}\n\t$chooser.data(\"chooser-init\", 1);\n\n\t// We have a font set for representing states. We can use them here.\n\tvar state_fonts = {\n\t\t\"AL\": \"B\", \"AK\": \"A\", \"AZ\": \"D\", \"AR\": \"C\", \"CA\": \"E\", \"CO\": \"F\", \n\t\t\"CT\": \"G\", \"DE\": \"H\", \"DC\": \"y\", \"FL\": \"I\", \"GA\": \"J\", \"HI\": \"K\", \n\t\t\"ID\": \"M\", \"IL\": \"N\", \"IN\": \"O\", \"IA\": \"L\", \"KS\": \"P\", \"KY\": \"Q\", \n\t\t\"LA\": \"R\", \"ME\": \"U\", \"MD\": \"T\", \"MA\": \"S\", \"MI\": \"V\", \"MN\": \"W\", \n\t\t\"MS\": \"Y\", \"MO\": \"X\", \"MT\": \"Z\", \"NE\": \"c\", \"NV\": \"g\", \"NH\": \"d\", \n\t\t\"NJ\": \"e\", \"NM\": \"f\", \"NY\": \"h\", \"NC\": \"a\", \"ND\": \"b\", \"OH\": \"i\", \n\t\t\"OK\": \"j\", \"OR\": \"k\", \"PA\": \"l\", \"RI\": \"m\", \"SC\": \"n\", \"SD\": \"o\", \n\t\t\"TN\": \"p\", \"TX\": \"q\", \"UT\": \"r\", \"VT\": \"t\", \"VA\": \"s\", \"WA\": \"u\", \n\t\t\"WV\": \"w\", \"WI\": \"v\", \"WY\": \"x\", \"US\": \"z\"\n\t};\n\t/**\n\t * Get the full court list via cache or ajax asynchronously.\n\t **/\n\tfunction get_court_list(onHasCourtList) {\n\t\t// Load the state icons if we have not done so already.\n\t\tif(!$(\"link.states\").length) {\n\t\t\t$('').appendTo(\"head\");\n\t\t}\n\t\t\n\t\tvar key = \"da_court_list_3\";\n\t\tvar court_list = sessionGet(key, true);\n\t\tif(court_list) {\n\t\t\tonHasCourtList(court_list);\n\t\t} else {\n\t\t\t// Get a list of courts.\n\t\t\t$.getJSON(\"/search_dockets_params.ajax\", {}, function (resp) {\n\t\t\t\tif(resp.success) {\n\t\t\t\t\tsessionSet(key, resp, 60*30, true);\n\t\t\t\t\tonHasCourtList(resp);\t\n\t\t\t\t} else {\n\t\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n /**\n\t * When the screen is too small, we need to shrink the height of the\n\t * court selector so other parts of the chooser doesn't get cut off.\n */\n\tfunction set_court_list_height($ul_parent) {\n\t\tif(window.innerHeight > 800) {\n\t\t\treturn;\n\t\t}\n\t\tvar $cc = $search_bar.find(\".court_chooser\");\n\t\tvar search_height = $cc.height();\n\t\tvar shown_height = $cc.find(\".details:visible\").map(function () {\n\t\t\treturn $(this).height();\n\t\t}).toArray().reduce(function (t, n) {\n\t\t\treturn t + n;\n\t\t});\n\t\tvar min_height = search_height - shown_height;\n\t\tvar new_height = Math.max(80, window.innerHeight - min_height);\n\t\t$ul_parent.find(\".details > ul\").css(\"max-height\", new_height + \"px\");\n\t}\n\tfunction checkbox_to_path($checkbox, parent_path) {\n\t\tvar group = $checkbox.data('key');\n\t\tif(!group || !group.length) {\n\t\t\tthrow \"Expecting data for \" + $checkbox.attr('id');\n\t\t}\n\t\treturn parent_path.concat([group]);\n\t}\n\tfunction set_checkbox_states(t, $checkboxes, parent_path) {\n\t\tif($checkboxes === null) {\n\t\t\t// If checkboxes are null, reset all of them. Start with top level.\n\t\t\tvar $topchecks = $chooser.find(\"ul.court_list_tree > li > input\");\n\t\t\tset_checkbox_states(t, $topchecks, []);\n\t\t\t// Close the details panes to invalidate them.\n\t\t\tunexpand($chooser.find(\".expanded\"));\n\t\t\treturn;\n\t\t}\n\t\tfor(var i = 0; i < $checkboxes.length; i++) {\n\t\t\tvar $c = $($checkboxes[i]);\n\t\t\tvar path = checkbox_to_path($c, parent_path);\n\t\t\tvar cmp = t.isSelected(path);\n\t\t\tif(cmp === true) {\n\t\t\t\t// Set to checked.\n\t\t\t\t$c.prop(\"indeterminate\", false)\n\t\t\t\t\t.prop(\"checked\", true);\n\t\t\t} else if (cmp === 1) {\n\t\t\t\t// Set to indeterminate.\n\t\t\t\t$c.prop(\"checked\", false)\n\t\t\t\t\t.prop(\"indeterminate\", true);\n\t\t\t} else {\n\t\t\t\t$c.prop(\"checked\", false)\n\t\t\t\t\t.prop(\"indeterminate\", false);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get an abbreviated description of the selected courts.\n\t * selected: The selected courts.\n\t * abbreviations: A dictionary of abbreviations we can use.\n\t */\n\tfunction get_description(selected, abbreviations) {\n\t\t// Generate an inverse abbreviation list for shorter descriptions.\n\t\tvar rev_abbrev = {};\n\t\tfor(var abbrev in (abbreviations || {})) {\n\t\t\trev_abbrev[abbreviations[abbrev]] = abbrev;\n\t\t}\n\t\t// Prepare a full description.\n\t\tvar full_desc = selected.map(function (sel) {\n\t\t\t// We don't have much space, so try to use a abbreviations.\n\t\t\tvar last_abbrev = rev_abbrev[sel[sel.length-1]];\n\t\t\tif(last_abbrev) {\n\t\t\t\t// The last term can be abbreviated. Do it.\n\t\t\t\treturn last_abbrev;\n\t\t\t}\n\t\t\tvar expanded = sel.map(function (shortened) {\n\t\t\t\treturn abbreviations[shortened] || shortened;\n\t\t\t});\n\t\t\t// Special case state courts.\n\t\t\tif(expanded[0] == \"State\") {\n\t\t\t\tif(expanded.length == 2) {\n\t\t\t\t\treturn expanded[1] + \" State\";\n\t\t\t\t} else if (expanded.length >= 4) {\n\t\t\t\t\treturn expanded[expanded.length - 1];\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Special case Federal courts.\n\t\t\tif(expanded.length >= 3 && expanded[0] == \"Federal\") {\n\t\t\t\treturn expanded[expanded.length - 1];\n\t\t\t}\n\t\t\treturn expanded.join(\", \");\n\t\t}).join(\" | \");\n\t\tif(full_desc.length == 0) {\n\t\t\tfull_desc = SEARCH_ALL_STR;\n\t\t}\n\t\t\n\t\t// Get a truncated version.\n\t\tvar truncated = null;\n\t\tif(full_desc.length > 30) {\n\t\t\ttruncated = '';\n\t\t\tvar f_split = full_desc.split(\" \");\n\t\t\tfor(var i = 0; i < f_split.length; i++) {\n\t\t\t\ttruncated += f_split[i] + \" \";\n\t\t\t\tif(truncated.length > 25) {\n\t\t\t\t\ttruncated += \"...\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn {\n\t\t\tfull : full_desc,\n\t\t\ttruncated : truncated,\n\t\t\t// Prepare the short description.\n\t\t\tshorter : full_desc.length <= 10 ? full_desc:\n\t\t\t\t// If it's too long, cite the number of courts.\n\t\t\t\tselected.length + ' Court' + (selected.length == 1 ? \"\" : \"s\")\n\t\t};\n\t}\n\n\t/**\n\t * Given the selection tree, return a query to find the courts and\n\t * optionally, the base query.\n\t **/\n\tfunction get_url_and_query(t, court_list) {\n\t\t// Find all of the queries first.\n\t\tvar select_queries = [];\n\t\tfor(var s_i = 0; s_i < t.selected.length; s_i++) {\n\t\t\tvar sel = t.selected[s_i];\n\t\t\tvar qlist = court_list.queries[sel.toString()];\n\t\t\tselect_queries.push(qlist ? qlist : sel[sel.length - 1]);\n\t\t}\n\n\t\t// Try doing a url search (eg, /search/PTAB/), because it's cleaner.\n\t\t// First get all of the URLs.\n\t\tvar all_urls = select_queries.map(function (sub_qs) {\n\t\t\treturn Array.isArray(sub_qs) ? sub_qs.filter(function (q) {\n\t\t\t\treturn q.url;\n\t\t\t}) : [];\n\t\t});\n\t\tvar has_url = false;\n\t\tfor(var i = 0; i < all_urls.length; i++) {\n\t\t\tvar qs_i = all_urls[i];\n\t\t\tif(!qs_i.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfor(var j = 0; j < qs_i.length; j++) {\n\t\t\t\thas_url = qs_i[j];\n\t\t\t\tfor(var k = 0; k < all_urls.length; k++) {\n\t\t\t\t\t// Look for this query in the other selected item.\n\t\t\t\t\tif(all_urls[k].indexOf(has_url) == -1) {\n\t\t\t\t\t\thas_url = false;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif(has_url) {\n\t\t\t\t\t// We found one, we can break.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(has_url) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Now set the form status and attributes.\n\t\tvar use_query = null;\n\t\tif(has_url) {\n\t\t\t// We only use queries with a url component.\n\t\t\tuse_query = function (sq_j) { return sq_j.q && sq_j.url; };\n\t\t\thas_url = has_url.url;\n\t\t} else {\n\t\t\t// Don't use queries with a url component.\n\t\t\tuse_query = function (sq_j) { return sq_j.q && !sq_j.url; };\n\t\t}\n\n\t\tfunction get_query(keys) {\n\t\t\tvar sq = court_list.queries[keys] || keys[keys.length - 1];\n\t\t\tif(typeof sq == \"string\") {\n\t\t\t\t// Put the court name in quotes. #2455.\n\t\t\t\treturn 'court:(\"' + sq + '\")';\n\t\t\t}\n\t\t\tfor(var j = 0; j < sq.length; j++) {\n\t\t\t\tif(use_query(sq[j])) {\n\t\t\t\t\treturn \"(\" + sq[j].q + \")\";\n\t\t\t\t}\n\t\t\t}\n\t\t\t// This can occur if the key should be a url query.\n\t\t\treturn null;\n\t\t}\n\n\t\tvar query = t.selected_simplified().map(function (s) {\n\t\t\tvar q = '';\n\t\t\tif(s.include.length) {\n\t\t\t\tq += s.include.map(get_query).filter(function (sq) {\n\t\t\t\t\treturn sq && sq.length;\n\t\t\t\t}).join(\" OR \");\n\t\t\t}\n\t\t\tif(s.exclude.length) {\n\t\t\t\tvar excludes = s.exclude.map(get_query).filter(function (sq) {\n\t\t\t\t\treturn sq && sq.length;\n\t\t\t\t}).join(\" OR \");\n\t\t\t\tif(excludes.length) {\n\t\t\t\t\tif(q.length) {\n\t\t\t\t\t\tq += \" AND \";\n\t\t\t\t\t}\n\t\t\t\t\tq += \"NOT (\" + excludes + \")\";\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn q && q.length ? \"(\" + q + \")\" : \"\";\n\t\t}).filter(function (sq) { return sq && sq.length; }).join(\" OR \");\n\t\treturn {\n\t\t\turl : has_url,\n\t\t\tquery : query,\n\t\t\thash : gen_string_hash((has_url || '') + query)\n\t\t}\n\t}\n\t\n\t/**\n\t * Set the Selected Court section towards the top of the chooser.\n\t */\n\tfunction set_selected_court_states(t, $chooser, court_list) {\n\t\tvar num_courts = t.selected.length;\n\t\tif(num_courts == 1 && t.selected.length == 1 && \n\t\t\t\tt.selected[0].length == 0) {\n\t\t\tnum_courts = 0;\n\t\t}\n\t\t// Build the selected courts html section.\n\t\tvar select_html = '';\n\t\tfor(var i = 0; i < t.selected.length; i++) {\n\t\t\tvar name = t.selected[i].join(\" | \");\n\t\t\tvar key = JSON.stringify(t.selected[i]);\n\t\t\tselect_html += '
  • ' + name + \n\t\t\t\t'
  • ';\n\t\t}\n\t\t\n\t\t// Get the descriptions.\n\t\tvar desc = get_description(t.selected, court_list.abbreviations);\n\t\t$chooser\n\t\t\t.find(\".clear_all\").toggleClass(\"disabled\", num_courts == 0)\n\t\t\t.click(function() {\n\t\t\t\tt.clear();\n\t\t\t\tset_checkbox_states(t, null);\n\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\".selected_courts\")\n\t\t\t\t.toggle(num_courts > 0)\n\t\t\t\t.find(\"ul\").html(select_html).end()\n\t\t\t\t.find(\"a.close\").click(function () {\n\t\t\t\t\tvar keys = $(this).parent().data(\"jsonkey\");\n\t\t\t\t\tt.remove(keys);\n\t\t\t\t\tset_checkbox_states(t, null);\n\t\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t\t\treturn false;\n\t\t\t\t}).end()\n\t\t\t.end()\n\t\t\t// Set the court description.\n\t\t\t.parents(\".search_engine_form\")\n\t\t\t\t.find(\".selected_court .selected\").text(desc.shorter);\n\t\t\n\t\t// Get the query.\n\t\tvar query = get_url_and_query(t, court_list);\n\t\t// Set the form to the url.\n\t\tset_search_bar_form_action(query.url || SEARCH_ALL_STR, null, \n\t\t\tquery.query, desc.full);\n\t\t\n\t\t// Put the focus on the text box so if they hit enter, it closes.\n\t\tget_parent_with_tag($chooser, \"form\")\n\t\t\t.find(\".court_chooser_search\").first().focus();\n\t\t\n\t\t// Save the selected courts in the session data so \n\t\tif((query.query && query.query.length) || query.url) {\n\t\t\tvar qkey = \"court_choice_\" + query.hash.toString();\n\t\t\tsessionSet(qkey, t.selected, null, true);\n\t\t\tsessionSet(qkey + \"_description\", desc, null, true);\n\t\t}\n\t}\n\tfunction unexpand($expanded){\n\t\t$expanded.removeClass(\"expanded\");\n\t}\n\n\tvar re_num = /^(\\d*)(.*)$/;\n\tfunction compare_courts(a, b) {\n\t\t// Try to extract any numbers in a court.\n\t\tvar m_a = a.match(re_num);\n\t\tvar m_b = b.match(re_num);\n\t\t// Not sure why, but turning this into a simple return breaks sorting on Chrome.\n\t\tvar cmp =\n\t\t\t// If the digits are the same, then use normal compare\n\t\t\tm_a[1] == m_b[1] ? m_a[2].localeCompare(m_b[2]):\n\t\t\t// If they both exist, compare the numbers.\n\t\t\tm_a[1] && m_b[1] ? parseInt(m_a[1]) - parseInt(m_b[1]):\n\t\t\t// Otherwise, numbers come first.\n\t\t\tm_a[1] ? 1:\n\t\t\tm_b[1] ? -1:\n\t\t\t// We shouldn't get here.\n\t\t\t0;\n\t\treturn cmp;\n\t}\n\n\t/**\n\t * Setup the details pane within one of the courts. Only triggered if\n\t * one of the parents is clicked on by the user.\n\t **/\n\tfunction setup_details(t, court_list, $parent, path) {\n\t\t// Remove all instances of expanded.\n\t\tunexpand($parent.parent().find(\".expanded\"));\n\t\t// Start building the details.\n\t\tvar html_details = '';\n\t\tif(path.length > 1) {\n\t\t\thtml_details += \"< Back \";\n\t\t}\n\t\t// Build the breadcrumbs.\n\t\thtml_details += \"
      \";\n\n\t\t// Get the selected group and it's parent.\n\t\tvar selected_group = t.get(path);\n\t\tif(Array.isArray(selected_group)) {\n\t\t\tfor(var g_i=0; g_i < selected_group.length; g_i++) {\n\t\t\t\tvar g = selected_group[g_i];\n\t\t\t\tvar user_k = (court_list.user_facing && \n\t\t\t\t\tcourt_list.user_facing[g]) || g;\n\t\t\t\tvar g_id = uniqueid();\n\t\t\t\thtml_details += \"
    • \";\n\t\t\t}\n\t\t} else {\n\t\t\tvar keys = [];\n\t\t\tfor(var sel_k in selected_group) {\n\t\t\t\tkeys = keys.concat([sel_k]);\n\t\t\t}\n\t\t\tkeys.sort(compare_courts);\n\t\t\tfor(var k_i=0; k_i < keys.length; k_i++) {\n\t\t\t\tvar k = keys[k_i];\n\t\t\t\tvar id = uniqueid();\n\t\t\t\thtml_details += \"
    • \";\n\t\t\t}\n\t\t}\n\t\t\n\t\thtml_details += \"
    \";\n\t\tvar cur_path = path.slice();\n\t\t$parent.addClass(\"expanded\")\n\t\t\t.find(\".details\").html(html_details)\n\t\t\t.find(\".back\").click(function () {\n\t\t\t\tsetup_details(t, court_list, $parent, cur_path.slice(0, -1));\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\".crumb\").click(function () {\n\t\t\t\tvar subgroup_idx = parseInt($(this).data(\"idx\"));\n\t\t\t\tsetup_details(t, court_list, $parent, \n\t\t\t\t\tcur_path.slice(0, subgroup_idx + 1));\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\".subgrouplink\").click(function () {\n\t\t\t\tvar subgroup = $(this).parent().parent()\n\t\t\t\t\t.find(\"input\").data(\"key\");\n\t\t\t\tsetup_details(t, court_list, $parent, \n\t\t\t\t\tcur_path.concat([subgroup]));\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\"input\").change(function () {\n\t\t\t\tvar click_path = checkbox_to_path($(this), cur_path);\n\t\t\t\tif($(this).is(\":checked\")) {\n\t\t\t\t\tt.add(click_path);\n\t\t\t\t} else {\n\t\t\t\t\tt.remove(click_path);\n\t\t\t\t}\n\t\t\t\t// Reset the top level checkbox.\n\t\t\t\tset_checkbox_states(t, $parent.find(\"> input[type=checkbox]\"), []);\n\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t}).end();\n\t\t// Set the height after we've setup the UI.\n\t\tset_court_list_height($parent);\n\t\tset_checkbox_states(t, $parent.find(\".details input[type=checkbox]\"), path);\n\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t$chooser.trigger('details_changed', [path, $parent, t, court_list]);\n\t\treturn false;\n\t}\n\t\n\tvar extra_top_level_html = {\n\t\t'Federal' : 'supplemental search',\n\t\t'State' : '' +\n\t\t\t\t'supplemental search',\n\t\t'Patent' : '' + \n\t\t\t'more research options',\n\t\t'International' : '' +\n\t\t\t\t'supplemental search',\n\t};\n\t\n\tvar still_loading = true;\n\t// We may make an ajax call here. Setup a loader if it'll take time.\n\tsetTimeout(function () {\n\t\tif(still_loading) {\n\t\t\tget_parent_with_tag($chooser, \"form\")\n\t\t\t\t.find(\".selected_court\").addClass(\"loading\");\n\t\t}\n\t}, 50);\n\t\n\t// Get the court list and then perform most of the setup once we have it.\n\tget_court_list(function (court_list) {\n\t\t//////\n\t\t// Turn off the loader.\n\t\tstill_loading = false;\n\t\tget_parent_with_tag($chooser, \"form\")\n\t\t\t.find(\".selected_court\").removeClass(\"loading\");\n\t\t\n\t\t//////\n\t\t// Initialize the tree data.\n\t\t// Start by sorting the list of keys.\n\t\tvar group_list = [];\n\t\tfor(var k in court_list.grouped) {\n\t\t\tgroup_list.push(k);\n\t\t}\n\t\tgroup_list.sort();\n\t\tvar t = new TreeSelector(court_list.grouped);\n\t\t// Build the html for the main court tree.\n\t\tvar html = '';\n\t\tgroup_list.forEach(function(k) {\n\t\t\tvar id = escape_html(uniqueid());\n\t\t\tk = escape_html(k);\n\t\t\thtml += \"
  • \" +\n\t\t\t\t(extra_top_level_html[k] || '') +\n\t\t\t\t\"\" +\n\t\t\t\t\"
  • \";\n\t\t});\n\t\t// If there is global courts selected, add them here.\n\t\t$chooser\n\t\t\t.find(\"ul.court_list_tree\").html(html)\n\t\t\t.find(\"[data-powertiptarget]\").powerTip({smartPlacement:true}).end()\n\t\t\t.find(\".all\").click(function () {\n\t\t\t\tif($(this).parent().hasClass(\"expanded\")) {\n\t\t\t\t\t// Close\n\t\t\t\t\tunexpand($(this).parent());\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tvar court_group = $(this).parent().find(\"input\").data(\"key\");\n\t\t\t\tsetup_details(t, court_list, $(this).parent(), [court_group]);\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\"input\").change(function () {\n\t\t\t\tvar click_path = checkbox_to_path($(this), []);\n\t\t\t\tif($(this).is(\":checked\")) {\n\t\t\t\t\tt.add(click_path);\n\t\t\t\t} else {\n\t\t\t\t\tt.remove(click_path);\n\t\t\t\t}\n\t\t\t\tset_checkbox_states(t, \n\t\t\t\t\t$(this).parent().find(\".details input[type=checkbox]\"), \n\t\t\t\t\tclick_path);\n\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t}).end().end()\n\t\t\t.find(\".done\").click(function() {\n\t\t\t\tvar $form = get_parent_with_tag($(this), \"form\");\n\t\t\t\ttoggle_search_bar_court_dropdown(false, $form);\n\t\t\t\treturn false;\n\t\t\t}).end();\n\t\tif(global_courts_selected && global_courts_selected.length) {\n\t\t\tfor(var c_i = 0; c_i < global_courts_selected.length; c_i++) {\n\t\t\t\tt.add(global_courts_selected[c_i]);\n\t\t\t}\n\t\t\tset_checkbox_states(t, null);\n\t\t}\n\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\n\t\t//////\n\t\t// Setup the court suggestion bar.\n\t\tvar $court_list_search_results = $chooser.find(\".court_list_search\");\n\t\tfunction select_result(selected_li) {\n\t\t\tvar path = t.find(selected_li);\n\t\t\tif(!path && court_list.abbreviations[selected_li]) {\n\t\t\t\t// Try looking it up as an abbreviation.\n\t\t\t\tpath = t.find(court_list.abbreviations[selected_li]);\n\t\t\t}\n\t\t\t// Sometimes \"the\" is prepended. This is a hack, should be fixed\n\t\t\t// elsewhere. #1601.\n\t\t\tif(!path && selected_li.startsWith(\"the \")) {\n\t\t\t\tvar abrev = court_list.abbreviations[selected_li.substr(4)];\n\t\t\t\tif(abrev) {\n\t\t\t\t\tpath = t.find(abrev)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif(path) {\n\t\t\t\tt.add(path);\n\t\t\t\tset_checkbox_states(t, null);\n\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t} else if (court_list.queries[selected_li]) {\n\t\t\t\t// This court is not in our tree view, but we still \n\t\t\t\t// know how to run a query to search for it.\n\t\t\t\tt.add([selected_li]);\n\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t} else {\n\t\t\t\tconsole.log(\"Problem finding court: \" + selected_li);\n\t\t\t}\n\t\t\t$chooser.parents(\"form.search_engine_form\")\n\t\t\t\t.removeClass(\"court_suggest_mode\")\n\t\t\t\t// Clear the text area from the search.\n\t\t\t\t.find(\".court_chooser_search\").val(\"\");\n\t\t}\n\t\tfunction court_search_results(results) {\n\t\t\tvar $form = get_parent_with_tag($chooser, \"form\");\n\t\t\tif(!results) {\n\t\t\t\t$form.removeClass(\"court_suggest_mode\");\n\t\t\t\treturn '';\n\t\t\t}\n\t\t\t$form.addClass(\"court_suggest_mode\");\n\t\t\t// We're only interested in the court suggestions.\n\t\t\tvar courts = results.filter(function(t) { return t.name == 'Court' });\n\t\t\tif(!courts.length) {\n\t\t\t\treturn '';\n\t\t\t}\n\t\t\treturn $(courts[0].options.map(function (t) {\n\t\t\t\t// Each suggestions has several options, iterate and merge:\n\t\t\t\treturn \"
  • \" + t.text + \"
  • \";\n\t\t\t}).join(\"\")).click(function () {\n\t\t\t\tselect_result($(this).text());\n\t\t\t\t// When selecting a suggestion, close the court search dropdown.\n\t\t\t\t// toggle_search_bar_court_dropdown(false, $form);\n\t\t\t\treturn false;\n\t\t\t});\n\t\t}\n\t\t// Key handler for navigating through the search results.\n\t\tfunction keydown_handler(ev, $selected) {\n\t\t\tvar $form = get_parent_with_tag($(ev.target), \"form\");\n\t\t\tswitch(ev.which) {\n\t\t\t\tcase 13: // ENTER\n\t\t\t\t\tif($selected && $selected.length && $selected.is(\":visible\")) {\n\t\t\t\t\t\tselect_result($selected.text());\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\ttoggle_search_bar_court_dropdown(false, $form);\n\t\t\t\t\treturn false;\n\t\t\t\tcase 27: // ESCAPE\n\t\t\t\tcase 9: // TAB\n\t\t\t\t\ttoggle_search_bar_court_dropdown(false, $form);\n\t\t\t\t\treturn false;\n\t\t\t\tdefault:\n\t\t\t\t\t$form.addClass(\"court_suggest_mode\");\n\t\t\t}\n\t\t}\n\t\t// Install the court search suggester.\n\t\t$chooser.find(\"input[type=text].court_chooser_search\").keydown(\n\t\t\tdebouncer(get_suggest_handler(\n\t\t\t\t\"court\", null, $court_list_search_results, false,\n\t\t\t\tcourt_search_results, keydown_handler), 100,\n\t\t\t\tfunction _debounce_retval(ev) {\n\t\t\t\t\t// If the user presses the enter key, we don't want it to \n\t\t\t\t\treturn ev.which == 13 ? false : undefined;\n\t\t\t\t}));\n\t\t\n\t\t//////\n\t\t// Populate Recent Courts.\n\t\tvar MAX_RECENT_COURTS = 5;\n\t\tvar RECENT_KEY = \"court_search_recent_court_list_key\";\n\t\tvar recent_courts = sessionGet(RECENT_KEY, true);\n\t\tif(recent_courts) {\n\t\t\t// Generate html for the recent courts.\n\t\t\tvar recent_html = '';\n\t\t\tfor(var i = 0; i < recent_courts.length; i++) {\n\t\t\t\tvar obj = recent_courts[i];\n\t\t\t\tvar key = JSON.stringify(obj.selected);\n\t\t\t\tvar desc = get_description(obj.selected, \n\t\t\t\t\tcourt_list.abbreviations);\n\t\t\t\tvar tip = desc.truncated ?\n\t\t\t\t\t'data-powertip=\"' + desc.full + '\"' : '';\n\t\t\t\trecent_html += '
  • ' + (desc.truncated || desc.full) + '
  • ';\n\t\t\t}\n\t\t\t$chooser.find(\".recent_courts\").find(\"ul\").html(recent_html)\n\t\t\t\t.find(\"li a\").click(function (ev) {\n\t\t\t\t\t// Update the data.\n\t\t\t\t\tt.selected = $(ev.target).data('jsonkey');\n\t\t\t\t\t// Update the UI.\n\t\t\t\t\tset_checkbox_states(t, null);\n\t\t\t\t\tset_selected_court_states(t, $chooser, court_list);\n\t\t\t\t\t// Close the chooser.\n\t\t\t\t\tvar $form = get_parent_with_tag($(this), \"form\");\n\t\t\t\t\ttoggle_search_bar_court_dropdown(false, $form);\n\t\t\t\t\treturn false;\n\t\t\t\t}).powerTip({smartPlacement : true}).end().end().show();\n\t\t} else {\n\t\t\t$chooser.find(\".recent_courts\").hide();\n\t\t}\n\t\t// When the user submits, save the search so we can populate it later.\n\t\tget_parent_with_tag($chooser, \"form\").submit(function () {\n\t\t\tif($chooser.is(\":visible\")) {\n\t\t\t\ttoggle_search_bar_court_dropdown(false, $(this));\n\t\t\t}\n\t\t\t\n\t\t\tif(!t.selected.length) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar obj = { \n\t\t\t\tselected : t.selected,\n\t\t\t\thash : gen_string_hash(t.selected.toString()),\n\t\t\t\tdate : (new Date()).toString()\n\t\t\t};\n\t\t\t// Get a fresh copy (could have been changed in another tab.)\n\t\t\tvar recent_courts = sessionGet(RECENT_KEY, true) || [];\n\t\t\t// Remvoe this query from the existing list.\n\t\t\tfor(var i = recent_courts.length - 1; i >= 0; i--) {\n\t\t\t\tif (recent_courts[i].hash == obj.hash) {\n\t\t\t\t\trecent_courts.splice(i, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Add the court to the list.\n\t\t\trecent_courts.unshift(obj);\n\t\t\t// If there are too many, truncate.\n\t\t\tif (recent_courts.length > MAX_RECENT_COURTS) {\n\t\t\t\trecent_courts.splice(MAX_RECENT_COURTS, \n\t\t\t\t\trecent_courts.length - MAX_RECENT_COURTS);\n\t\t\t}\n\t\t\t// Save it back in the session.\n\t\t\tsessionSet(RECENT_KEY, recent_courts, 60*60*24, true);\n\t\t});\n\t\tif(onComplete) {\n\t\t\tonComplete($chooser);\n\t\t}\n\t});\n}\n\n/**\n * Convert a given court chooser to a coverage graph.\n * Requirements: d3, colorbrewer, rickshaw, font awesome 5.0.\n * Make sure they are included in the base page.\n * @param $chooser\n */\nfunction transform_court_chooser_to_coverage($chooser) {\n\tvar cols = [\"Court Name\", \"Years Covered\", \"Total Cases\", {\n\t\t\tk : \"Supported Fields\",\n\t\t\tsub_cols : [\"Judge\", \"Firm\", \"Attorney\", \"Type\", \"Status\",\n\t\t\t\t\"Contact\", \"Docs\"]\n\t\t}, {\n\t\t\tk : \"Last 7 Days\",\n\t\t\tsub_cols : [\n\t\t\t\t// \"Cases 24h\", \"Docs 24h\",\n\t\t\t\t\"Cases\", \"Docs\"\n\t\t\t]\n\t}];\n\tvar params = url_params_to_object();\n\tif(params.admin) {\n\t\tcols.push({\n k: 'Last 30 Days',\n\t\t\tsub_cols : [params.admin.search(/get/ig) >= 0 ? \"Get\" : \"Created\"]\n });\n\t}\n\tvar to_key = function _to_key(k) { return k.replaceAll(\" \", \"_\"); };\n\tvar num_cols = 0;\n\tvar first_head_r = cols.map(function _get_col(name, col_i) {\n\t\tvar has_sub = typeof name != \"string\";\n\t\tvar col_name = has_sub ? name.k : name;\n\t\tvar col_k = to_key(col_name);\n\t\tvar row_span = has_sub ? 1 : 2;\n\t\tvar col_span = has_sub ? name.sub_cols.length : 1;\n\t\tvar sorter = has_sub ? \"\" :\n\t\t\t\"
    • Ascending
    • Decending
    \";\n\t\tnum_cols += col_span;\n\t\treturn \"\" + col_name + sorter + \"\";\n\t}).join(\"\");\n\tvar second_head_r = cols.map(function _get_sec_row(name) {\n\t\tif(typeof name == \"string\") {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn name.sub_cols.map(function _sub(sname) {\n\t\t\tvar col_k = sname.replaceAll(\" \", \"_\");\n\t\t\treturn \"\" + sname + \"\";\n\t\t}).join(\"\");\n\t}).join(\"\");\n\tvar $table = $(\"\" + first_head_r + \"\" +\n\t\t\"\" + second_head_r +\n\t\t\"
    \");\n\n\n\tfunction get_query(path, court_list) {\n\t\tvar comb_path = path.join(\",\");\n\t\tvar qs = court_list.queries[comb_path];\n\t\tif(qs) {\n\t\t\tfor(var q_i=0; q_i
    \" +\n\t\t\t\t\"
    \");\n\t\t\tfunction conv_data(d) {\n\t\t\t\treturn d.buckets.map(function (d) {\n return {\n x: d.key,\n y: d.doc_count\n }\n });\n\t\t\t}\n\t\t\treturn {\n\t\t\t\t$cell : $cell,\n\t\t\t\t// After the cell is inserted, we need to call a setup function.\n\t\t\t\tafterinsert : function _afterinsert() {\n\t\t\t\t\tif(!$cell.is(\":visible\")) {\n\t\t\t\t\t\t// It disappeared before we could draw it.\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvar data_series = [];\n\t\t\t\t\t// There are two series: cases and documents\n\t\t\t\t\tif(doc_aggs[\"0\"].sub.Date) {\n\t\t\t\t\t\tdata_series.push({\n\t\t\t\t\t\t\tname : \"Cases\",\n\t\t\t\t\t\t\tclassName : \"Cases\",\n\t\t\t\t\t\t\ttooltip : \"Cases\",\n\t\t\t\t\t\t\tcolor : \"blue\",\n\t\t\t\t\t\t\tdata : conv_data(doc_aggs[\"0\"].sub.Date)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tif(doc_aggs[\"1\"].sub.Date) {\n\t\t\t\t\t\tdata_series.push({\n\t\t\t\t\t\t\tname : \"Documents\",\n\t\t\t\t\t\t\ttooltip : \"Documents\",\n\t\t\t\t\t\t\tclassName : \"Documents\",\n\t\t\t\t\t\t\tcolor : \"orange\",\n\t\t\t\t\t\t\tdata : conv_data(doc_aggs[\"1\"].sub.Date)\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tif(data_series.length) {\n\t\t\t\t\t\tvar graph = new Rickshaw.Graph({\n\t\t\t\t\t\t\twidth\t: 130,\n\t\t\t\t\t\t\theight\t: 30,\n\t\t\t\t\t\t\telement\t: $cell.find(\".graph\")[0],\n\t\t\t\t\t\t\trenderer: 'line',\n\t\t\t\t\t\t\tseries\t: data_series\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t//hover details\n\t\t\t\t\t\t var hoverDetail = new Rickshaw.Graph.HoverDetail( {\n\t\t\t\t\t\t\t graph: graph,\n\t\t\t\t\t\t\t xFormatter : function(x) {\n\t\t\t\t\t\t\t\tvar d = new Date(x);\n\t\t\t\t\t\t\t\treturn '
    ' +\n\t\t\t\t\t\t\t\t\tmonthNames[d.getUTCMonth()] +\n\t\t\t\t\t\t\t\t\t', ' + d.getUTCFullYear() + '
    ';\n\t\t\t\t\t\t\t },\n\t\t\t\t\t\t\t formatter: function(series, x, val) {\n\t\t\t\t\t\t\t\tvar swatch = '';\n\t\t\t\t\t\t\t\treturn swatch + series.tooltip + \": \" +\n\t\t\t\t\t\t\t\t\tnumber_with_commas(val) + '
    ';\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tgraph.render();\n\t\t\t\t\t\t// Initially hide all tooltips.\n\t\t\t\t\t\t$cell.find(\".detail\").addClass(\"inactive\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\tamount : function _amount(agg_key, msg) {\n\t\t\tvar count = (agg_key && (agg_key.count || agg_key.doc_count)) || 0;\n\t\t\tvar amt = number_with_abbrev(count);\n\t\t\tvar $cell = $(\"\" + amt + \"\");\n\t\t\tif(amt != count.toString() || msg) {\n\t\t\t\tif(msg) {\n\t\t\t\t\tmsg = \"There are \" + number_with_commas(count) + \" \" + msg;\n\t\t\t\t} else {\n\t\t\t\t\tmsg = number_with_commas(count);\n\t\t\t\t}\n\t\t\t\t$cell.data('powertip', msg).powerTip();\n\t\t\t}\n\t\t\treturn $cell;\n\t\t},\n\t\ticon : function _icon(resp, k, fa, val, msg) {\n\t\t\tvar count = val !== undefined ? val :\n\t\t\t\t(resp.aggs[k] && resp.aggs[k].doc_count) || 0;\n\t\t\tvar v = !!count;\n\t\t\tvar $cell = $(\"\");\n\t\t\tif(count) {\n\t\t\t\tif(!msg) {\n\t\t\t\t\tmsg = \"cases searchable on the \" + k + \" field.\"\n\t\t\t\t}\n\t\t\t\t$cell.data('powertip', \"There are \" +\n\t\t\t\t\tnumber_with_commas(count) + \" \" + msg).powerTip();\n\t\t\t}\n\t\t\treturn $cell;\n\t\t},\n\t\tloader : function _loader() {\n\t\t\treturn \" Loading\";\n\t\t},\n\t\terror : function _error(msg) {\n\t\t\treturn \" \" + (msg || \"Error\") + \"\";\n\t\t},\n\t\tviewmore : function _viewmore() {\n\t\t\treturn \"
    \" +\n\t\t\t\t\"Details
    \";\n\t\t},\n\t};\n\n\tfunction transform_details(ev, path, $parent, t, court_list) {\n\t\tvar json_requests = [];\n\t\t// Add a new table to the details view.\n\t\t$parent.find(\".details\").append($table.clone());\n\t\t// We'll be adding a number of rows to it.\n\t\tvar $tbody = $(\".details table tbody\");\n\t\t$parent.find(\"li.courts, .subgroup\").each(function _make_row() {\n\t\t\t\tvar $t = $(this);\n\t\t\t\tvar court_key = $t.find(\"input\").data(\"key\");\n\t\t\t\tvar court_full = court_list.abbreviations[court_key] || court_key;\n\t\t\t\tvar query = get_query(path.concat([court_key]), court_list);\n\t\t\t\tvar selected_group = t.get(path.concat([court_key]));\n\t\t\t\tvar $row = $(\"\" + widgets.loader() + \"\");\n\t\t\t\t$t.find(\"label a\").text(court_full).filter(\".subgrouplink\")\n\t\t\t\t\t.append(widgets.viewmore());\n \t$t.appendTo($row.find(\"td:first\"));\n \t$row = $row.appendTo($tbody);\n\t\t\t\tvar j_filter = { q : query };\n\t\t\t\tif(params.admin) {\n\t\t\t\t\tj_filter.viewargs = \"admin-\" + params.admin;\n\t\t\t\t}\n \t// Add the row to the request itself to manipulate it later.\n\t\t\t\tjson_requests.push({\n\t\t\t\t\tviews : ['Coverage'],\n\t\t\t\t\tfilter : j_filter,\n\t\t\t\t\t$row : $row\n\t\t\t\t});\n });\n\n\t\tfunction split_doc_buckets(doc_buckets) {\n\t\t\tdoc_buckets.filter(function (d) { return d.key == 0 });\n\t\t\treturn {\n\t\t\t\tdocuments : dock_buckets.length\n\t\t\t}\n\t\t}\n\n\t\tget_json_results({\n\t\t\trequests : json_requests,\n\t\t\tbase_url : \"/analytics/Coverage/\",\n\t\t\t// Called when every ajax request finishes, except for the last one.\n\t\t\ton_finished_request : function _on_finished(i, resp, num_finished) {\n\t\t\t\tvar $row = json_requests[i].$row;\n\t\t\t\t$row.find(\".loader\").remove().end();\n\t\t\t\tif(!resp || resp.error){\n\t\t\t\t\t$row.append(widgets.error(resp.error));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvar doc_aggs = agg_to_dict(resp.aggs.Documents, ['0','1']);\n\t\t\t\tvar histo = widgets.histo(doc_aggs), histo2 = null;\n\t\t\t\t// Need to use appendTo for the histograph to update the query.\n\t\t\t\thisto.$cell = histo.$cell.appendTo($row);\n\t\t\t\t// Now add the rest of the widget columns.\n\t\t\t\t$row.append(widgets.amount(doc_aggs[\"0\"]))\n\t\t\t\t\t.append(widgets.icon(resp, 'Judge', 'fa-gavel'))\n\t\t\t\t\t.append(widgets.icon(resp, 'Firm', 'fa-briefcase'))\n\t\t\t\t\t.append(widgets.icon(resp, 'Attorney', 'fa-user'))\n\t\t\t\t\t.append(widgets.icon(resp, 'Type', 'fa-table-cells-large'))\n\t\t\t\t\t.append(widgets.icon(resp, 'Status', 'fa-sync-alt'))\n\t\t\t\t\t.append(widgets.icon(resp, 'Contact', 'fa-phone',\n\t\t\t\t\t\tundefined, \"cases with attorney contact information.\"))\n\t\t\t\t\t.append(widgets.icon(resp, 'Docs', 'fa-file',\n\t\t\t\t\t\tdoc_aggs[\"1\"].count, \"OCRed and searchable documents.\"))\n\t\t\t\t\t.append(widgets.amount(doc_aggs[0].sub.Create_7d,\n\t\t\t\t\t\t\"new cases added in the last 7 days.\"))\n\t\t\t\t\t.append(widgets.amount(doc_aggs[1].sub.Create_7d,\n\t\t\t\t\t\t\"new documents added in the last 7 days.\"));\n\t\t\t\tif(params.admin) {\n\t\t\t\t\tvar admin_docs = agg_to_dict(resp.aggs.Documents_Admin, ['0','1']);\n\t\t\t\t\thisto2 = widgets.histo(admin_docs);\n\t\t\t\t\thisto2.$cell = histo2.$cell.appendTo($row);\n\t\t\t\t\thisto2.afterinsert();\n\t\t\t\t}\n\t\t\t\thisto.afterinsert();\n\t\t\t},\n\t\t\t// Called when all ajax requests finish.\n\t\t\ton_success : function _on_success(resp) {\n\n\t\t\t},\n\t\t\t// Called, when one request fails (no success messages will be sent).\n\t\t\ton_fail : function _on_fail(xhr) {\n\t\t\t\tvar error = xhr.getResponseHeader('X-Detailed-Error');\n\t\t\t\t$tbody\n\t\t\t\t\t.find(\"tr\").remove().end()\n\t\t\t\t\t.append(\"\").find(\"tr\").append(widgets.error(error));\n\t\t\t}\n\t\t});\n\t}\n\n\t$chooser.addClass(\"coverage_viewer\").on(\"details_changed\", transform_details);\n}\n\n/**\n * Sets up the specialized PTAB order searcher.\n **/\nfunction setup_ptab_order_search() {\n\tvar ptab_motions = [\n // 'Improper Communications',\n 'Final Written Decision',\n 'Motion for Additional Discovery',\n 'Motion for Extension of Time',\n 'Motion for Joinder',\n 'Motion for Observation on Cross-Examination',\n 'Motion for Page Extension',\n 'Motion for Pro Hac Vice',\n 'Motion for Protective Order',\n 'Motion for Sanctions',\n 'Motion for Supplemental Information',\n 'Motion to Accelerate',\n 'Motion to Amend Claims',\n 'Motion to Bifurcate',\n 'Motion to Compel',\n // 'Motion to Correct Clerical Error',\n 'Motion to Disqualify',\n 'Motion to Exclude',\n 'Motion to Expedite',\n 'Motion to Expunge',\n 'Motion to File Objections',\n 'Motion to Limit Claims',\n 'Motion to Reassign',\n 'Motion to Seal',\n 'Motion to Stay',\n 'Motion to Strike',\n\t\t'Motion to Terminate',\n 'Motion to Withdraw',\n 'Petition Institution',\n 'Rehearing Request',\n 'Request for Adverse Judgment',\n 'Setting Initial Conference',\n\t];\n\tvar motion_outcomes = {\n\t\t''\t: [\n\t\t\t\t{value:\"\", desc:\"Any outcome\"},\n\t\t\t\t{value:\"Granted\", desc:\"Granted\"},\n\t\t\t\t{value:\"Denied\", desc:\"Denied\"},\n\t\t\t\t{value:\"Granted and Denied\", desc:\"Granted and Denied\"},\n\t\t],\n\t\t\"Final Written Decision\" : [\n\t\t\t{value:\"\", desc:\"Any outcome\"},\n\t\t\t{value:\"Granted\",\tdesc:\"All Claims Canceled\"},\n {value:\"Denied\", \tdesc:\"No Claims Canceled\"},\n {value:\"Granted and Denied\", desc:\"Some Claims Canceled\"},\n\t\t]\n\t};\n\t// Populate the motion list\n\tvar motion_options = \"\";\n\tfor(i in ptab_motions) {\n\t\tvar m = ptab_motions[i];\n\t\tmotion_options += '';\n\t}\n $(motion_options).appendTo(\".ptab_orders select.order_type\");\n \n\t// Populate the outcome list depending on the value in the motion list.\n\tfunction update_selector($form) {\n\t\tif($form == null)\n\t\t\t$form = $(\".ptab_orders\");\n\t\t// Get the selected motion\n\t\tvar motion = $form.find(\"select[name=f]\").val();\n\t\t// If it's not a special type, then use the default \n\t\tvar outcomes = motion_outcomes[motion] != null ? \n\t\t\t\t\t\t\tmotion_outcomes[motion]:\n\t\t\t\t\t\t\tmotion_outcomes[''];\n\t\t// Build the new selections\n\t\tvar new_selections = \"\";\n\t\tfor(i in outcomes) {\n\t\t\tnew_selections += '';\n\t\t}\n\t\t$form.find(\"select.outcome_type\").html(new_selections);\n }\n\t// Bind it to a change in the selector\n $('.ptab_orders select[name=f]').change(function () {\n\t\tupdate_selector(get_parent_with_class($(this), \"ptab_orders\"));\n\t});\n\t// Run it once to do initial setup\n\tupdate_selector();\n\t\n\t// Handle form submission\n $('.ptab_orders').submit(function (ev) {\n\t\tvar $this = $(this);\n\t\t\n\t\t// Figure out the outcome type\n\t\tvar out_t = $this.find(\".outcome_type\").val();\n\t\t// Get the motion type\n var mot_t = $this.find(\"[name=f] :selected\").attr('data-motion');\n\t\t// Modify the motion type value to take into account the outcome\n\t\tvar new_value = out_t.length ? \n\t\t\t\t\t\t\t'outcome_exact-' + mot_t + \" - \" + out_t:\n\t\t\t\t\t\t\t'outcome-' + mot_t;\n\t\t$this.find(\"[name=f] :selected\").attr('value', new_value);\n return true;\n\t});\n\t\n\t// We also need to bind \n\t$(\".ptabsubmenu\").click(function () {\n\t\treturn toggle_ptab_submenu();\n\t})\n\t.powerTip({smartPlacement : true})\n\t.data('powertiptarget', \"ptabsubmenu_tip\");\n}\n\nfunction setup_submenus () {\n\t// Setup the Federal Courts direct search.\n\tsetup_fedcourts_search();\n\t// Setup the PTAB orders drop-down\n\tsetup_ptab_order_search();\n\t// Setup TTAB Orders dropdown.\n\tsetup_trademark_submenu();\n}\n\t\nfunction toggle_submenu(selector) {\n\treturn function (force_hide, $target) {\n\t\tif(force_hide || $(selector + \":visible\").length) {\n\t\t\t$(selector).hide();\n\t\t} else {\n\t\t\tupdate_label_widths($(selector).show().focus());\n\t\t}\n\t}\n}\n\nvar toggle_ptab_submenu = toggle_submenu(\".ptab_orders\");\nfunction setup_fedcourts_search() {\n\t// Bind the arrow to bring up the view\n\t$(\".fedcourtsubmenu\").powerTip({\n\t\tsmartPlacement : true,\n\t\tmouseOnToPopup\t: true\n\t}).data('powertiptarget', \"fedcourts_tip\");\n}\n\nvar toggle_trademark_submenu = toggle_submenu(\".trademark_options\");\nfunction setup_trademark_submenu() {\n\t// Bind the arrow to bring up the view\n\t$(\".trademarksubmenu\").click(function () {\n\t\ttoggle_search_bar_court_dropdown(false, $(this).parent());\n\t\treturn toggle_trademark_submenu()\n\t});\n}\n\nfunction set_search_bar_form_action($court, $doc, query, query_name) {\n\t// Set the search form's \"action\" parameter, which determines which court\n\t// and type of document to search.\n\tvar base_action = window.location.pathname;\n\tvar courttext = $court && $court.text ? $court.text() : $court;\n\tvar court_url = \"\";\n\tif (courttext != SEARCH_ALL_STR) {\n\t\tfor(var i = 0; i < COURT_ACRONYMS.length; i++) {\n\t\t\tvar a = COURT_ACRONYMS[i];\n\t\t\t// Remove spaces, and add an end slash.\n\t\t\tvar a_url = a.replaceAll(\" \", \"\") + \"/\";\n\t\t\tif(courttext == a || courttext == a_url.slice(0, -1)) {\n\t\t\t\t// If the court text matches, it gets precedence.\n\t\t\t\tcourt_url = a_url;\n\t\t\t\tbreak;\n\t\t\t} else if (base_action.search(\"/\" + a_url) > 0) {\n\t\t\t\tcourt_url = a_url;\n\t\t\t}\n\t\t}\n\t}\n\t\n\t// Figure out whether we're searching documents dockets, or both.\n\tvar docid = $doc ? $doc.attr(\"id\") : null;\n\tvar doc =\tdocid == \"documents_only\" ? \"documents/\": \n\t\t\t\tdocid == \"dockets_only\" ? \"dockets/\":\n\t\t\t\tbase_action.search(\"/documents/\") > 0 ? \"documents/\":\n\t\t\t\t// >1 b/c if it's the start of the pathname, it's the dashboard.\n\t\t\t\tbase_action.search(\"/dockets/\") > 1 ? \"dockets/\":\n\t\t\t\t\"\";\n\tvar form_base = \"/search/\" + court_url + doc; \n\tvar $search_form = $(\".search_engine_form\")\n\t\t.attr(\"action\", form_base)\n\t\t// Set the links in the search helper are also correct.\n\t\t.find(\".exampleblock a\").each(function (i, a) {\n\t\t\t\tvar $a = $(a);\n\t\t\t\t$a.attr('href', form_base + $a.attr('href').replace(\"/search/\", \"\"));\n\t\t}).end()\n\t\t.find(\".added_q\").remove().end()\n\t\t.find(\"input[name=fname_q_court_choice]\").remove().end()\n\t\t.find(\"input[name=f][value^='q_court_choice']\").remove().end();\n\t// Add \n\tif(query) {\n\t\t$search_form.append('');\n\t\tif(query_name) {\n\t\t\t$search_form.append('');\n\t\t}\n\t}\n}\n/**\n * Parse the current URL, and set the search bar's info based on it. Should\n * only be called initially when the page loads.\n **/\nfunction setup_search_bar_input_from_url() {\n\tvar base_action = document.URL.split(\"?\")[0];\n\tvar $form = $('.search_engine_form');\n\t\n\t// First set the search bar's court dropdown.\n\tvar court_dropdown = SEARCH_ALL_STR;\n\tfor(var i = 0; i < COURT_ACRONYMS.length; i++) {\n\t\tvar a = COURT_ACRONYMS[i];\n\t\tvar a_url = a.replaceAll(\" \", \"\") + \"/\";\n\t\tif(base_action.search(\"/\" + a_url) > 0) {\n\t\t\tcourt_dropdown = a;\n\t\t\tbreak;\n\t\t}\n\t}\n\t$form.find('.selected_court .selected').text(court_dropdown);\n\t \n\t$(\".court_dropdown li:contains('\" + court_dropdown + \"')\")\n\t\t.addClass(\"selected\");\n\t\n\t// The main search input.\n\tvar params = url_params_to_object();\n\tvar $textarea = $form.find('[name=q]');\n\tif(params.q) {\n\t\t$textarea.val(params.q)\n\t}\n\tfunction get_width(text) {\n\t\treturn $form.find(\".widthcalc\").text(text).width();\n\t}\n\t// Shorten the placeholder text if there isn't much room.\n\tvar text_area_width = $textarea.width();\n\t['Search by case number, title, keyword, company, patent, ...',\n\t\t'Search by case number, title, keyword, company, ...',\n\t\t'Search by case number, keyword, company, ...',\n\t\t'Search by case number, keyword, ...',\n\t\t'Search across litigation ...',\n\t\t'Search for cases ...',\n\t\t'Search ...'\n\t].forEach(function (text, i) {\n\t\tvar cur_text = $textarea.attr(\"placeholder\");\n\t\tif(get_width(cur_text) < text_area_width) {\n\t\t\t// It fits within the text area, stop iterating.\n\t\t\treturn false;\n\t\t}\n\t\t// Change the placeholder text.\n\t\t$textarea.attr(\"placeholder\", text);\n\t});\n \n\t// If we have facets, add them as hidden inputs\n\tvar hidden_input = \"\";\n\tif (typeof facets != 'undefined' && facets) {\n\t\tfor(i in facets) {\n\t\t\tvar f = facets[i];\n\t\t\tif (f.name == \"Search Type\" || f.name == 'Date filed' )\n\t\t\t\tcontinue;\n\t\t\tfor(var fci in f.counts) {\n\t\t\t\tvar fval = f.counts[fci][0];\n\t\t\t\tvar exists = f.counts[fci][3];\n\t\t\t\tif(exists && fval) {\n\t\t\t\t\tvar val = (f.is_bool ? f.facet_name + \"-\" + fval:\n\t\t\t\t\t\t\t\t\t f.facet_name + \"_exact-\" + fval);\n\t\t\t\t\thidden_input += '';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Also add their names too.\n\tfor(var k in params) {\n\t\tif(k.startsWith('fname_')) {\n\t\t\thidden_input += '';\n\t\t}\n\t}\n\t$form.append(hidden_input);\n\t\n\t// Even if no date_filed_data, setup hidden input fields for date facets.\n\t$form\n\t\t\t.find(\"input[name=date_filed_start]\")\n\t\t\t\t\t.attr(\"value\", params.date_filed_start)\n\t\t\t\t\t.prop('disabled', params.date_filed_start==null).end()\n\t\t\t.find(\"input[name=date_filed_end]\")\n\t\t\t\t\t.attr(\"value\", params.date_filed_end)\n\t\t\t\t\t.prop('disabled', params.date_filed_end==null)\n\t.find(\"input[name=order_by]\")\n\t\t.attr(\"value\", params.order_by)\n\t\t\t\t\t.prop('disabled', params.order_by==null);\n\t\t\t\t\n\t$form.find(\".search_helper li\").powerTip({\n\t\tsmartPlacement\t: true,\n\t\tmouseOnToPopup\t: false,\n\t\tintentSensitivity : 5,\n\t\tintentPollInterval : 200,\n\t});\n}\n\n/**\n * Look at the facets applied to the current page and change the advanced \n * chooser accordingly.\n **/\nfunction setup_search_bar_input_from_facets() {\n\tvar $form = $(\"form.search_engine_form\");\n\tvar $chooser = $form.find(\".court_chooser\");\n\tif(!$chooser.length) {\n\t\t// No point in parsing the url if we don't have a chooser.\n\t\treturn;\n\t}\n\t// Look for facets that start with the court choice keyword.\n\tvar facets = $.deparam.querystring().f;\n\tif(!facets) {\n\t\tfacets = [];\n\t} else if(!Array.isArray(facets)) {\n\t\tfacets = [facets];\n\t}\n\t// Determine the query.\n\tvar query = '';\n\tfor(var i = 0; i < facets.length; i++) {\n\t\t// Ignore all irrelevant facets.\n\t\tif(facets[i].startsWith('q_court_choice-')) {\n\t\t\tquery = facets[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// Determine the URL query.\n\tvar url_q = window.location.pathname.split(\"/\").filter(function (s) {\n\t\t\treturn s.length && s != 'search' && s != 'analytics'; \n\t\t});\n\turl_q = url_q.length ? url_q[0] : '';\n\t\n\t// Get the hash of this facet.\n\tvar qhash = gen_string_hash(url_q + query.substring('q_court_choice-'.length));\n\tvar qkey = \"court_choice_\" + qhash.toString();\n\tvar selected_items = sessionGet(qkey, true);\n\tif(Array.isArray(selected_items)) {\n\t\tglobal_courts_selected = selected_items;\n\t\tvar desc = sessionGet(qkey + \"_description\", true);\n\t\tif(desc) {\n\t\t\t$form.find(\".selected_court .selected\").text(desc.shorter);\n\t\t}\n\t} else {\n\t\t// Can occur if a user clicks on a link to a search.\n\t\tconsole.log(\"Could not find selected_items: \" + qkey);\n\t}\n}\n\n/**\n * Show or hide the Advanced Search Bar (the gear icon on the search bar).\n **/\nfunction toggle_advanced_search(force_hide, $link) {\n\t// query_builder is the name of the advanced search dialog.\n\tvar $search_bar = $link.parents(\".search_engine_form\");\n if(force_hide || searchbar_isactive_dropdown($search_bar, \"query_builder\")) {\n // Hide it\n\t\tsearchbar_hide_dropdown(true, $search_bar)\n } else {\n // Show it\n\t\tsearchbar_show_dropdown($search_bar, \"query_builder\")\n }\n}\n\n/**\n * Build a query based on the values entered by the user.\n */\nfunction update_advanced_search() {\n\tvar $t = $(this);\n\tvar $qb = $t.parents(\"form\");\n\tif (!$qb || !$qb.length) {\n\t\t$qb = $(\".query_builder\");\n\t}\n\tupdate_docket_documents_chk($t, $qb);\n\t// Set the query\n\t$(\".search_engine_form [name=q]\").val(query_builder_to_query($qb));\n}\n\nfunction update_docket_documents_chk($t, $qb) {\n\t// If they changed the document type, we need to validate.\n\tvar $doc_type = $qb.find(\".group.doc_type\");\n\tif ($doc_type.length) {\n\t\tvar is_checked = $t.is(\":checked\");\n\t\tswitch ($t.attr(\"id\")) {\n\t\t\tcase 'q_chk_dockets':\n\t\t\t\t// Don't need to do anything special if checking dockets.\n\t\t\t\tbreak;\n\t\t\tcase 'q_chk_documents':\n\t\t\t\t// If they uncheck documents, then uncheck all sub docs.\n\t\t\t\t$doc_type.find(\".more input[type=checkbox]\")\n\t\t\t\t\t.prop(\"checked\", is_checked);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// If they check/uncheck subdocs, may need to change documents.\n\t\t\t\tvar d_chks = $doc_type.find(\".more input\").map(function () {\n\t\t\t\t\treturn $(this).is(\":checked\");\n\t\t\t\t}).toArray();\n\t\t\t\tvar all_true = d_chks.every(function (i) { return i; });\n\t\t\t\tvar one_true = d_chks.some(function (i) { return i; });\n\t\t\t\t$doc_type.find(\"#q_chk_documents\")\n\t\t\t\t\t.prop(\"checked\", all_true)\n\t\t\t\t\t.prop(\"indeterminate\", !all_true && one_true);\n\t\t\t\tbreak;\n\t\t}\n\t\tif (!is_checked && !$doc_type.find(\"input:checked\").length) {\n\t\t\t// If none of them are checked, check all of them.\n\t\t\t$doc_type.find(\"input[type=checkbox]\").prop(\"checked\", true);\n\t\t}\n\t}\n}\n\n/**\n * Build a query based on the values entered by the user.\n * @param $qb A query builder object.\n * @returns {string}\n */\nfunction query_builder_to_query($qb) {\n\t// Get the values\n\tvar all = $qb.find(\"#all_these_words\").val();\n\tvar any = $qb.find(\"#any_these_words\").val();\n\tvar none = $qb.find(\"#none_these_words\").val();\n\tvar exact = $qb.find(\"#exact_these_words\").val().strip();\n\tvar with_a = $qb.find(\"#within_a\").val().strip();\n\tvar with_b = $qb.find(\"#within_b\").val().strip();\n\tvar with_n = $qb.find(\"#within_n\").val().strip();\n\tvar party_name = $qb.find(\"#filter_by_party\").val().strip();\n\tvar party_type = $qb.find(\"#filter_by_party_type\").val().strip();\n\tvar party_firm = $qb.find(\"#filter_by_party_firm\").val().strip();\n\tvar party_atny = $qb.find(\"#filter_by_party_attorney\").val().strip();\n\tvar party_public = $qb.find(\"#filter_by_party_public\").val().strip();\n\tvar f_atleast = $qb.find(\"#filter_by_atleast\").val().strip();\n\tvar f_filed_date_start = ($qb.find(\"#filter_date_filed_start\").val() || '').strip();\n\tvar f_filed_date_end = ($qb.find(\"#filter_date_filed_end\").val() || '').strip();\n\tvar f_filing_date_start = ($qb.find(\"#filter_date_last_filing_start\").val() || '').strip();\n\tvar f_filing_date_end = ($qb.find(\"#filter_date_last_filing_end\").val() || '').strip();\n\tvar f_filed_date = ($qb.find(\"#filter_by_filed_date\").val() || '').strip();\n\n\tvar deadline = $qb.find(\"#filter_by_deadline\").val(); // Don't strip\n\tvar deadline_date = $qb.find(\"#filter_by_deadline_date\").val().strip();\n\tvar filters = [];\n\tfor (var i=0; i<4; i++) {\n\t\tif(!$qb.find(\"#filter_by_field\" + i).length)\n\t\t\tbreak;\n\t\tvar f = $qb.find(\"#filter_by_field\" + i).val().strip();\n\t\tvar f_txt = $qb.find(\"#filter_by_field_txt\" + i).val().strip();\n\t\tif(f && f_txt && f.length && f_txt.length)\n\t\t\tfilters = filters.concat([[f, f_txt]]);\n\t}\n\n\tvar splitp = function (x) {\n\t\treturn x.strip().split(\" \").filter(function(x) { return x.length>0});\n\t};\n\tvar wrapifspace = function(x, always) {\n\t\treturn wrap_query(x, always);\n\t};\n\n\t// Construct the query\n\tvar q = [];\n\tif(all && all.length)\n\t\tq.push(wrapifspace(splitp(all).join(\" \")));\n\tif(any && any.length)\n\t\tq.push(wrapifspace(splitp(any).join(\" OR \")));\n\tif(exact && exact.length)\n\t\tq.push('\"' + exact + '\"');\n\tif(with_a && with_a.length && with_b && with_b.length) {\n\t\tvar w_q = wrapifspace(splitp(with_a).join(\" \")) + \" \" + with_n + \" \" +\n\t\t\twrapifspace(splitp(with_b).join(\" \"));\n\t\tif(q.length)\n\t\t\tw_q = \"(\" + w_q + \")\";\n\t\tq.push(w_q);\n\t}\n\tif(party_firm && !party_type && !party_public && !party_atny && !party_name) {\n\t\t// Only searching for a party.\n\t\tq.push(\"firm:\" + wrapifspace(party_firm));\n\t} else if(!party_firm && !party_type && !party_public && party_atny && !party_name) {\n\t\t// Only searching for an attorney.\n\t\tq.push(\"attorney:\" + wrapifspace(party_atny));\n\t} else if(party_type || party_public || party_firm || party_atny) {\n\t\tvar p_q = '';\n\t\tif(party_name) {\n\t\t\tp_q += \"name:\" + wrapifspace(party_name) + \" \";\n\t\t}\n\t\tif(party_firm) {\n\t\t\tif(party_atny) {\n\t\t\t\tp_q += \"firm:(name:\" + wrapifspace(party_firm) +\n\t\t\t\t\t\" attorney:\" + wrapifspace(party_atny) + \") \";\n\t\t\t} else {\n\t\t\t\tp_q += \"firm:(name:\" + wrapifspace(party_firm) + \") \";\n\t\t\t}\n\t\t} else if (party_atny) {\n\t\t\tp_q += \"firm:(attorney:\" + wrapifspace(party_atny) + \") \";\n\t\t}\n\t\tif(party_type) {\n\t\t\tp_q += \"type:\" + wrapifspace(party_type) + \" \";\n\t\t}\n\t\tif(party_public == 'yes') {\n\t\t\tp_q += \"fact:(type:Exchange) \";\n } else if(party_public == 'no') {\n\t\t\tp_q += \"not fact:(type:Exchange) \";\n }\n p_q = \"party:\" + \"(\" + p_q.strip() + \")\";\n q.push(p_q);\n\t} else if(party_name) {\n\t\tq.push(\"party:\" + wrapifspace(party_name));\n\t}\n\tif(f_atleast.length) {\n\t\tq.push(f_atleast);\n\t}\n\tif(f_filed_date.length) {\n\t\tq.push(f_filed_date);\n\t}\n\tif(f_filed_date_start) {\n\t\tq.push(\"from:\" + wrapifspace(f_filed_date_start));\n\t}\n\tif(f_filed_date_end) {\n\t\tq.push(\"to:\" + wrapifspace(f_filed_date_end));\n\t}\n\tif(f_filing_date_start) {\n\t\tif(f_filing_date_end) {\n\t\t\tq.push(\"date_last_filing:(from:\" + wrapifspace(f_filing_date_start)+\n\t\t\t\t\" to:\" + wrapifspace(f_filing_date_end) + \")\");\n\t\t} else {\n\t\t\tq.push(\"date_last_filing:(from:\" +\n\t\t\t\twrapifspace(f_filing_date_start) + \")\");\n\t\t}\n\t} else if(f_filing_date_end) {\n\t\tq.push(\"date_last_filing:(to:\" +\n\t\t\t\twrapifspace(f_filing_date_end) + \")\");\n\t}\n\n\tif(deadline.length) {\n\t\tvar d_q = deadline.strip();\n\t\tif(deadline_date) {\n\t\t\td_q += \" \" + deadline_date;\n\t\t}\n\t\td_q = d_q.strip();\n\t\td_q = d_q.length ? wrapifspace(d_q, true) : '\"\"';\n\t\tq.push(\"deadline:\" + d_q);\n\t} else if (deadline_date) {\n\t\t// Always wrap deadlines, we can get errors if not.\n\t\tq.push(\"deadline:\" + wrapifspace(deadline_date, true));\n\t}\n\t// Iterate over all filters, adding 1 by 1\n\tfor(var f_i=0; f_i 1;\n\tif(exists) {\n\t\treturn\n\t}\n\tdrop_downs.push({\n\t\t// Add the \":visible\" selector, because we only care about closing\n\t\t// visible dialogs.\n\t\tselector : saved_selector,\n\t\tshower \t : shower,\n\t\tfunc \t : func,\n\t});\n}\n\nvar debug_global_dropdown = true;\nfunction init_global_dropdown() {\n\tadd_global_dropdown(\n\t\t'.ptab_orders'\t,\n\t\t'.ptabsubmenu',\n\t\ttoggle_ptab_submenu\n\t);\n\tadd_global_dropdown(\n\t\t'.trademark_options',\n\t\t'.trademarksubmenu',\n\t\ttoggle_trademark_submenu\n\t);\n\tadd_global_dropdown(\n\t\t'.search_tabs_dropdown',\n\t\t'.query_builder, .selected_court, .search_engine_form textarea',\n\t\tsearchbar_hide_dropdown\n\t);\n\t// Any CSS that shows/hides controls can have problems on iPhone:\n\t// https://www.nczonline.net/blog/2012/07/05/ios-has-a-hover-problem/\n\t// By hiding it and resetting its state, we can \n\tadd_global_dropdown(\n\t\t'.user_submenu',\n\t\t'.user_menu .user_icon',\n\t\tfunction () { $('.user_submenu').hide().css(\"display\", \"\"); }\n\t);\n\tadd_global_dropdown(\n\t\t'#float_menu .pages ul',\n\t\t'#float_menu .pages',\n\t\tfunction () { $('#float_menu .pages').hide().css(\"display\", \"\"); }\n\t);\n\t\n\t\n\t// Hide various windows when you click outside of them\n\tfunction onHTMLClick(event) {\n\t\tvar $targ = $(event.target);\n\t\tvar $parents = $targ.parents();\n\t\t// Hide the dropdown if you click anywhere outside of it.\n\t\tfor(var i in drop_downs) {\n\t\t\tvar d = drop_downs[i];\n\t\t\tvar $dropdown = $(d.selector);\n\t\t\tif(!$dropdown || !$dropdown.length)\n\t\t\t\tcontinue;\n\t\t\tif($dropdown.css(\"visibility\") == \"hidden\")\n\t\t\t\tcontinue;\n\t\t\tvar $showers = $(d.shower);\n\t\t\tif( $parents.index($dropdown) == -1 && !$targ.is($dropdown) && \n\t\t\t\t$parents.index($showers) == -1 && !$targ.is($showers)) {\n\t\t\t\tif(debug_global_dropdown){\n\t\t\t\t\tconsole.log(\"Click outside: \" + d.selector + \", closing.\");\n\t\t\t\t}\n\t\t\t\t// We clicked outside of it.\n\t\t\t\tif(d.func) {\n\t\t\t\t\td.func(true, $dropdown);\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(\"No close function for: \" + d.selector);\n\t\t\t\t}\n\t\t\t} else if (debug_global_dropdown) {\n\t\t\t\tconsole.log(\"Click inside: \" + d.selector + \", keeping.\");\n\t\t\t}\n\t\t}\n\t}\n\t// Bind to BOTH click and touch events of non clickable elements.\n\t$('html').click(onHTMLClick).bind(\"touchend\", onHTMLClick);\n\n\t// Make sure the drop-down can be seen over a PDF\n\tif ($(\"#EmbeddedPDF\").length) {\n\t\t$(\".search_engine_form .court_dropdown\")\n\t\t\t.prepend(bgiframe).end();\n\t}\n\t\n\tsetup_search_bar();\n}\n\n//////////////////////////////////\n// Common to Docket and Search Page (e.g., exporting dockets)\n\n/**\n * Export a set of search results or a docket either as an email and provides a \n * download link to download the export.\n * data: Information identifying the search or docket to send.\n * success: Callback function that takes a URL to download the export.\n * canceled: Callback that should return true if the user cancels. Even if \n * the user cancels, the export will be emailed to them.\n * error: Error with a message parameter.\n */\nfunction export_docket_report(options) {\n var data = options.data;\n var canceled = options.canceled || function () { return false; }; \n var success = options.success;\n var error = options.error;\n\n function clean_msg(jqXHR) {\n \tvar msg = $(jqXHR.responseText).text().replace(/\\s+/ig, \" \").strip();\n \t// Sometimes we get weird messages we need to clean up. See #1070.\n \treturn msg.replace(/\\s*Bad\\s+(Response|Request)/ig, \"\");\n\t}\n\n var checks = 0;\n function check_export_ready(chk_options) {\n\t\tvar ready = chk_options.ready;\n\t\tvar notready = chk_options.notready;\n\t\tvar url = '/export_docket_report.ajax?' + $.param(data);\n\t\tchecks += 1;\n\t\t$.getJSON(url + \"&check_ready\", function (resp) {\n\t\t\tif(resp.success && resp.ready) {\n\t\t\t\t// Download the export.\n\t\t\t\tready(url);\n\t\t\t} else if (resp.success) {\n\t\t\t\t// Cannot download yet.\n\t\t\t\tnotready();\n\t\t\t} else if(error) {\n\t\t\t\t// Error\n\t\t\t\terror(resp.error);\n\t\t\t}\n\t\t})\n\t\t.fail(function (jqXHR, textStatus, errorMsg) {\n\t\t\tif(error) {\n\t\t\t\terror(\"Can't \" + (checks <= 1 ? \"start\" : \"check\") +\n\t\t\t\t\t\" export \" + textStatus + \": \" + clean_msg(jqXHR));\n\t\t\t}\n\t\t});\n\t}\n function on_ready(url) {\n if(!canceled()) {\n success(url);\n }\n }\n function on_notready() {\n if(canceled()) {\n return;\n }\n setTimeout(function () {\n check_export_ready({\n ready : on_ready,\n notready : on_notready,\n });\n }, 2000);\n }\n \n check_export_ready({\n ready : on_ready,\n notready : function () {\n // Make the post call to start the export.\n $.post(\"/export_docket_report.ajax\", data, function(resp) {\n if(resp.success) {\n on_notready();\n } else {\n error(resp.error);\n }\n }, \"json\")\n\t\t\t.fail(function (jqXHR, textStatus, errorMsg) {\n\t\t\t\tif(error) {\n\t\t\t\t\terror(\"Can't start export \" + textStatus + \": \" + clean_msg(jqXHR));\n\t\t\t\t}\n\t\t\t});\n },\n });\n}\n\n/**\n * Convert raw data into excel file.\n * See the backend code for the proper format.\n * @param filename The name of the file to export into.\n * @param pages The data to export.\n * @param raw_data If a function, then return the excel document as raw data.\n * \tto the provided async function.\n */\nfunction export_data_to_excel(filename, pages, raw_data) {\n\tvar exportable = {\n\t\tfilename : safe_filename(filename),\n\t\tpages : pages,\n\t};\n\tvar exportable_json = JSON.stringify(exportable);\n\n\tif(raw_data) {\n\t\t// Download the file\n\t\tvar xhr = new XMLHttpRequest();\n\t\txhr.open('POST', \"/export_data.ajax\", true);\n\t\txhr.responseType = 'arraybuffer';\n\t\txhr.onload = function(e) {\n\t\t\tif (this.status != 200) {\n\t\t\t\t// Bad Download\n\t\t\t\traw_data(false, e);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\traw_data(true, xhr.response);\n\t\t};\n\t\t// Another way of failing.\n\t\txhr.onerror = function(e) { on_failure(false, e); };\n\t\txhr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");\n\t\txhr.send(\"data=\" + encodeURIComponent(exportable_json));\n\t\treturn;\n\t}\n\n\t// Create a POST form to submit the data.\n\tvar form = document.createElement(\"form\");\n\tform.setAttribute(\"method\", \"POST\");\n\tform.setAttribute(\"action\", \"/export_data.ajax\");\n\tvar hiddenField = document.createElement(\"input\");\n\thiddenField.setAttribute(\"type\", \"hidden\");\n\thiddenField.setAttribute(\"name\", \"data\");\n\thiddenField.setAttribute(\"value\", exportable_json);\n\tform.appendChild(hiddenField);\n\tdocument.body.appendChild(form);\n\n\t// Submit it, and get the file back.\n\tform.submit();\n}\n\n//////////////////////////////////\n// Go Daddy Seal\nfunction init_godaddy_seal() {\n\t$(\".godaddyseal\")\n\t\t.html('\"GoDaddy')\n\t\t.click(function() {\n\t\t\tvar bgHeight = \"460\";\n\t\t\tvar bgWidth = \"593\";\n\t\t\tvar url = \"https://seal.godaddy.com/verifySeal?sealID=9LzCSAFyXWQYGU7DSvqhI3VbzHewnrfszUZP2FgzsTOKvNw94aXWcjH\";\n\t\t\twindow.open(url,'SealVerification','menubar=no,toolbar=no,personalbar=no,location=yes,status=no,resizable=yes,fullscreen=no,scrollbars=no,width=' + bgWidth + ',height=' + bgHeight);\n\t\t});\n};\n\nfunction html_escape(text) {\n // escape a string that would have possible cross-scripting embedded into it.\n return sanitize_dom_url(text);\n}\n\n// Work in progress transition to modern javascript. For now, we have many\n// global functions, but we're not making any new ones, and can refactor each in pieces.\nexport {\n\t// Google Analytics Usage tracking.\n\tanalytics_track,\n\tanalytics_dimension,\n\n\t// General Number Utility functions.\n\t// gen_string_hash,\n\tcapitalize,\n\tget_abbreviation,\n\tplural_have,\n\tplural,\n\tplural_n,\n\tescape_html,\n\thuman_list,\n\thumanize_time,\n\thumanize_time_utc,\n\thumanize_time_granular,\n\tis_number,\n\tnumber_with_commas,\n\tnumber_with_abbrev,\n\troundNumber,\n\n\tuniqueid,\n\n\tsanitize_dom_url,\n\topen_url,\n\tdebouncer,\n\n\t// Array Utils\n\tsum_array,\n\tunique_array,\n\n\tjoin_jquery,\n\twrap_html,\n\thtml_tag,\n\twrap_query,\n\n\tcreate_html_from_json,\n\tjson_html_expander,\n\tcreate_html_from_markdown,\n\tcreate_html_table,\n\n\t// Events\n\tmakeCustomEvent,\n\tonEvent,\n\tdispatchEvent,\n\n\t// Class Helpers. Pretty duplicative of jquery.\n\tgetClassList,\n\taddClass,\n\tremoveClass,\n\ttoggleClass,\n\tgetParentElement,\n\tget_parent_with_tag,\n\tget_parent_with_class,\n\n\t// Local cache\n\tsessionSet,\n\tsessionGet,\n\tsessionClear,\n\n\tload_css,\n\tload_drawing_js,\n\n\t// get_window_height,\n\t// getScrollbarWidth,\n\n\tinit_powertip_defaults,\n\n\tshow_success_usermsg,\n\tshow_error_usermsg,\n\tshow_usermsg,\n\tshow_confirm_usermsg,\n\n\tget_gravatar,\n\n\tsetup_marketing_inquiry_form,\n\n\tcopy_to_clipboard,\n\t// copy_to_clipboard_inner,\n\n\tshow_modal,\n\tmodal_progress_start,\n\tmodal_progress_set,\n\tmodal_progress_error,\n\tmodal_progress_close,\n\n\tdownload_file,\n\tsafe_filename,\n\tmap_parallel,\n\tcreate_download_dialog,\n\tdownload_files,\n\tai_task_document_summarize,\n\tai_task_options,\n\tdownload_to_ai_task,\n\n\timage_uploader,\n\n\tshow_support_overlay,\n\t// init_support_overlay,\n\t// init_forgot_password,\n\t// init_hd_bd_holder,\n\t// init_pacer_tips,\n\t// init_float_menu,\n\t// common_setup,\n\n\tget_admin_url,\n\tshow_salesforce_chat_window,\n\tsetup_salesforce_live_chat_window,\n\n\tsetup_common_sidebar,\n\tadjust_actionbar_items,\n\tinit_close_buttons,\n\n\t// Paywall Related\n\tborderless_dialog,\n\n\tupdate_label_widths,\n\n\t// Search Bar Related\n\t// setup_search_bar,\n\tsearchbar_show_dropdown,\n\tSEARCH_ALL_STR,\n\t// searchbar_isactive_dropdown,\n\t// searchbar_hide_dropdown,\n\tget_url_hash,\n\tget_suggest_handler,\n\tadd_entity_suggestion_to_input,\n\tadd_contained_entities,\n\tcontained_entities_to_query,\n\turl_params_to_object,\n\turl_to_fragment,\n\turl_from_params,\n\t// toggle_search_bar_court_dropdown,\n\tsetup_court_chooser,\n\ttransform_court_chooser_to_coverage,\n\t// setup_ptab_order_search,\n\t// setup_submenus,\n\t// toggle_submenu,\n\t// setup_fedcourts_search,\n\t// setup_trademark_submenu,\n\t// set_search_bar_form_action,\n\t// setup_search_bar_input_from_url,\n\t// setup_search_bar_input_from_facets,\n\t// toggle_advanced_search,\n\t// update_advanced_search,\n\t// update_docket_documents_chk,\n\t// query_builder_to_query,\n\n\tadd_global_dropdown,\n\tinit_global_dropdown,\n\n\texport_docket_report,\n\texport_data_to_excel,\n\n\t// init_godaddy_seal,\n\thtml_escape\n}", "/**\n * Docket Alarm\n * Javascript Utility Functions - Billing Related\n *\n * Within Scope:\n * Anything billing related when user is logged out of account\n * \t Signup / Setup Account\n * \t Paywall\n * \t Membership panels\n * \t Basic billing information pull for when user first logs in.\n *\n * Out of Scope:\n * \tBilling history\n * \tMost logged in billing features\n * \t\tException: related to logging in / signing up.\n *\n */\n\nimport {\n\tanalytics_track,\n\tborderless_dialog,\n\tcapitalize,\n\tcreate_html_table,\n\tget_parent_with_class,\n\tget_parent_with_tag,\n\tload_css,\n\n\tmakeCustomEvent,\n\tonEvent,\n\tdispatchEvent,\n\n\tsessionClear,\n\tsessionGet,\n\tsessionSet,\n\n\tsetup_marketing_inquiry_form,\n\n\tshow_confirm_usermsg,\n\tshow_error_usermsg,\n\tshow_success_usermsg,\n\tshow_usermsg,\n\n\tupdate_label_widths,\n\n\tSEARCH_ALL_STR\n} from \"../site_media/common\";\n\n/*********************************************************************\n *\n * Billing\n *\n *********************************************************************/\nvar setup_billing_overlay_callback = [];\nvar last_billing_info = null;\n\n/**\n * Parse a response error, checking if its a matter error. If it is, and the matter is visible,\n * create the error on the matter. If not, return false.\n * @param resp\n * @param $form_hint The form to show the matter_error on. If not given, will try to\n * \tfind the default matter number on the top.\n * @returns {boolean}\n */\nfunction set_matter_number_error(resp, $form_hint) {\n\tif(resp.error_type != 'billing.matter') {\n\t\treturn false;\n\t}\n\t// If not given a hint, try to find the default matter number on the top.\n\tvar $form_with_matter = $form_hint ||\n\t\t$(\".userinfoform input[name=pacerdefaultmatter]\").parents(\"form\");\n\t// If not visible, don't show error.\n\tif (!$form_with_matter.length || !$form_with_matter.is(\":visible\")) {\n\t\treturn false;\n\t}\n\t// If not scrolled into view, don't show the error.\n\tif($(window).scrollTop() > $form_with_matter.offset().top) {\n\t\treturn false;\n\t}\n\t// Show the matter number error.\n\t$form_with_matter.addClass(\"matter_error\");\n\tshow_error_usermsg(resp.error, resp.error_type, $form_with_matter);\n\treturn true;\n}\n\n/**\n * Sets up the billing pane. Also displays the free trial/account type window\n * if necessary. Also displays any pertinent billing messages anywhere.\n * Does NOT make the billing pane visible.\n * @param info\t\t\tUser information.\n * @param from_cache\tTrue if the data came from a cache (and may be stale)\n */\nfunction setup_billing_pane(info, from_cache) {\n\tif(!info) {\n\t\treturn;\n\t}\n\t// We show a different narrative depending on the tye of account they have.\n\tvar narrative_class = info.billing_type;\n\tvar $billing_pane = $(\".billing_pane\")\n\t\t.find(\".billing_narrative\").hide().end();\n\n\t// Fill in values for the pacerlogin and pacer password\n\t$(\"input[name=pacerlogin]\").val(info.pacerlogin);\n\t$(\"input[name=pacerpassword]\").val(info.pacerpassword);\n\t// Set the default matter number if it makes sense to do so.\n\t$(\"input[name=pacerdefaultmatter]\").each(function () {\n\t\t// Get the existing value.\n\t\tvar cur_val = $(this).val();\n\n\t\t// Decode into other possible values.\n\t\tvar cur_vals = {};\n\t\tcur_vals[cur_val] = true;\n\t\tif(cur_val && cur_val[0] == '{') {\n\t\t\tvar js_val = JSON.parse(cur_val);\n\t\t\tif(js_val.description) {\n\t\t\t\tcur_vals[js_val.description] = true;\n\t\t\t}\n\t\t\tcur_vals[js_val.id] = true;\n\t\t}\n\t\tif(cur_vals[info.pacerdefaultmatter]) {\n\t\t\t// No change needed.\n\t\t} else if(cur_val && cur_val != info.pacerdefaultmatter) {\n\t\t\t// There is an existing value, and it doesn't match. Happen if\n\t\t\t// billing info is out of date compared to HTML. Give a warning.\n\t\t\tif(!from_cache) {\n\t\t\t\tconsole.warn(\"PACER matter mismatch: \" + cur_val + \" != \" +\n\t\t\t\t\tinfo.pacerdefaultmatter);\n\t\t\t}\n\t\t} else {\n\t\t\t// Setting the matter number.\n\t\t\t$(this).val(info.pacerdefaultmatter);\n\t\t}\n\t});\n\n\t// Extra PACER Logins\n\tvar pacer_extra = (info.integration || {}).pacer_extra || {};\n\tvar pacer_arr = [];\n\tfor(var k in pacer_extra) {\n\t\tpacer_arr.push({name:k, info:pacer_extra[k]});\n\t}\n\tif(pacer_arr.length) {\n\t\t// Convert this object to an array.\n\t\t// Create the table.\n\t\tvar headings = ['Name', 'PACER Username', 'Default Matter', 'Action'];\n\t\tvar table = create_html_table(pacer_arr, headings, function (obj) {\n\t\t\treturn [obj.name, obj.info.pacerlogin, obj.info.pacerdefaultmatter,\n\t\t\t\t'Remove'];\n\t\t});\n\t\t$(\".extra_pacer_accounts\").html(table)\n\t\t\t.find(\".remove\").click(function () {\n\t\t\t\tvar $btn = $(this);\n\t\t\t\tvar data = {pacername_extra_remove:$btn.data('name')};\n\t\t\t\t$.post( \"/set_user_info.ajax\", data, function(resp) {\n\t\t\t\t\tif(resp.success) {\n\t\t\t\t\t\tshow_usermsg(\"Removed.\", $btn);\n\t\t\t\t\t\t// Give it a pause to let the db update.\n\t\t\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t\t\tupdate_billing_info(true);\n\t\t\t\t\t\t}, 800);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t\t\t}\n\t\t\t\t}, 'json');\n\t\t\t});\n\t} else {\n\t\t$(\".extra_pacer_accounts\").html(\"\");\n\t}\n\n\t// Turn on all powertips that have data.\n\t/// Problematic\n\t$billing_pane.find(\"[data-powertip]\").powerTip({\n\t\tsmartPlacement : true,\n\t\t// These are basic powerTips defined by attributes, style accordingly.\n\t\tpopupClass : 'tagtip'\n\t});\n\n\tvar bill_descrip = \"\";\n\t// Reset menu items\n\tdisable_pacer_submenus(true);\n\n\t// Setup the account setup\n\tvar Recur = info.user_pricing.Recur;\n\tvar products_pricing_description = ((info.custom_pricing || {}).other || {}).products_pricing_description;\n\tvar rate_value = (info.billing_type == 'personal') ?\n\t\t(Recur.flat_rate || Recur.paygo_recurring || \"None\") : \"None\";\n\tvar $flat_rate = $billing_pane.find(\"form select[name=flat_rate]\")\n\t\t.find(\"option[value=Fed]\")\n\t\t\t.toggle(Recur.Fed != null)\n\t\t\t.text(\"Flat Rate - Federal Courts ($\" + (Recur.Fed ||\"\").toString() + \"/mo.)\")\n\t\t\t.end()\n\t\t.find(\"option[value=PTOITC]\")\n\t\t\t.toggle(Recur.PTOITC != null)\n\t\t\t.text(\"Flat Rate - The PTAB and ITC ($\" + (Recur.PTOITC ||\"\").toString() + \"/mo.)\")\n\t\t\t.end()\n\t\t.find(\"option[value=All]\")\n\t\t\t.toggle(Recur.All != null)\n\t\t\t.text(\"Flat Rate - \" + SEARCH_ALL_STR + \" ($\" +\n\t\t\t\t(Recur.All ||\"\").toString() + \"/mo.)\" + ((products_pricing_description) ?\n\t\t\t\t\t\" + \" + products_pricing_description : ''))\n\t\t\t.end()\n\t\t.find(\"option[value=paygo_monthly]\")\n\t\t\t.toggle(Recur.paygo_monthly != null)\n\t\t .text(\"Pay-As-You-Go Monthly ($\" + (Recur.paygo_monthly || \"\").toString() + \"/mo.)\")\n\t\t .end()\n\t\t.val(rate_value)\n\t\t.find(\"option[value=personal]\").remove().end();\n\tif (rate_value != \"None\" || info.billing_type != 'personal') {\n\t\t// rate_value is not None or user is pacer, they shouldn't be able to see the old Pay-Go rate\n\t\t$flat_rate.find(\"option[value=None]\").remove();\n\t}\n\n\t// toggle the account type pane after all modifications\n\t$billing_pane.find(\"form.set_account_type\").show();\n\n\t// Do not show the PACER input fields if they don't need it. Note that this\n\t// only applies to paid users, not to the free trial people.\n\tvar show_pacer_info = !info.user_pricing.other.pacer_fees_billed ||\n\t\t(info.user_pricing.other.paygo_user_pacer_account &&\n\t\t\t!info.user_pricing.Recur.flat_rate);\n\t// Don't show PACER information adjustments to non-admins.\n\tif(show_pacer_info && info.billing_type == \"corporate\" &&\n\t\t!info.user_can_edit) {\n\t\tshow_pacer_info = false;\n\t}\n\t$billing_pane\n\t\t.find(\"form.setpacerinfo\").toggle(show_pacer_info).end()\n\t\t// Hide this for now, we unhide below if necessary.\n\t\t.find(\"form.setpacerinfo_freetrial\").hide();\n\n\tif(info.billing_type == 'personal') {\n\t\t// Paid Account\n\t\tbill_descrip += \"Paid Account.\";\n\t\t// Don't let them view the membership page anymore.\n\t\t$(\"a[href='#MembershipOptions']\").each(function () {\n\t\t\t// Hide the link\n\t\t\tvar p = $(this).hide().parent()[0];\n\t\t\t// And hide any list object it belongs to.\n\t\t\tif(p && p.nodeName.toLowerCase() == \"li\") {\n\t\t\t\t$(p).hide();\n\t\t\t}\n\t\t});\n\t\tif(info.next_charge_date)\n\t\t\tbill_descrip += \" Next bill due on \" + info.next_charge_date + \".\";\n\t\tvar account_type = {\n\t\t\t\tFed\t\t: \"Federal Court flat rate\",\n\t\t\t\tPTOITC\t: \"PTAB and ITC flat rate\",\n\t\t\t\tAll\t\t: \"flat rate\",\n\t\t\t paygo_monthly: \"Pay-As-You-Go Monthly\",\n\t\t\t\tPayAsGo\t: \"Pay-As-You-Go\"\n\t\t\t}[Recur.flat_rate || Recur.paygo_recurring || \"PayAsGo\"];\n\n\t\tif(last_billing_info && last_billing_info.billing_type == \"pacer\") {\n\t\t\t// Congratulate them on upgrading.\n\t\t\tshow_success_usermsg(\"Your membership has been upgraded.\");\n\t\t\tanalytics_track(\"billing\", \"account_upgraded\", account_type);\n\t\t}\n\t\ttoggle_membership_window(false);\n\t\t// In the testimonial window, don't say anything about free trials.\n\t\t$billing_pane.find(\".account_type\")\n\t\t\t.find(\"form.set_account_type input[type=submit]\").button({disabled:false}).end()\n\t\t\t.find(\".account_msg\").text(\"Your account type is set to \" + account_type + \" pricing. \");\n\t} else if(info.billing_type == 'corporate') {\n\t\tbill_descrip += \"Group billing.\";\n\t\ttoggle_membership_window(false);\n\t\t// Don't let them view the account type page.\n\t\t$(\"a[href='#MembershipOptions']\").hide();\n\t\t// Setup the account buttons\n\t\t$billing_pane.find(\".account_type\")\n\t\t\t.find(\"form.set_account_type input[type=submit]\").button({disabled:true}).end()\n\t\t\t.find(\".account_msg\").html(\n\t\t\t\t\"Warning: Your account is \" +\n\t\t\t\t\"a corporate billing account and cannot be changed here. \" +\n\t\t\t\t\"Contact support@docketalarm.com for billing inquiries.\");\n\t} else if(info.billing_type == 'pacer') {\n\t\t// During the free trial, the user may either have PACER credentials\n\t\t// entered or a credit card entered to bill court fees.\n\t\tvar cards_info = sessionGet(\"cards_info\");\n\t\tvar free_with_credit_card = cards_info && cards_info.length > 0;\n\t\t// True if they entered their pacer account details or a credit\n\t\t// card, i.e., some way to pay for court fees.\n\t\tvar free_w_pacer = free_with_credit_card || (\n\t\t\t\t\t\tinfo.pacerlogin != null &&\n\t\t\t\t\t\tinfo.pacerpassword != null &&\n\t\t\t\t\t\tinfo.pacerlogin.length > 0 &&\n\t\t\t\t\t\tinfo.pacerpassword.length > 0);\n\t\t// True when there's no pacer details, and should show the message.\n\t\tvar free_wo_pacer = !free_w_pacer && info.dontshow_pacer_msg &&\n\t\t\t!free_with_credit_card;\n\n\t\t// Figure out our previous state so we can show a message\n\t\tvar had_free_w_pacer = free_with_credit_card || (\n\t\t\t\t\tlast_billing_info != null &&\n\t\t\t\t\tlast_billing_info.pacerlogin != null &&\n\t\t\t\t\tlast_billing_info.pacerpassword != null &&\n\t\t\t\t\tlast_billing_info.pacerlogin.length > 0 &&\n\t\t\t\t\tlast_billing_info.pacerpassword.length > 0);\n\t\tvar had_free_wo_pacer = last_billing_info != null &&\n\t\t\t\t\t\t!had_free_w_pacer &&\n\t\t\t\t\t\tlast_billing_info.dontshow_pacer_msg;\n\n\t\tnarrative_class = !info.had_free_trial ? 'no_trial' :\n\t\t\t\t\t\t\t\tinfo.trial_days_left <= 0 ? 'pacer_expired':\n\t\t\t\t\t\t\t\tfree_with_credit_card ? 'pacer_creditcard':\n\t\t\t\t\t\t\t\t'pacer';\n\t\tif(info.trial_days_left <= 0 && info.had_free_trial) {\n\t\t\t// The free trial is over\n\t\t\tbill_descrip += \"Trial has expired! Upgrade your account.\";\n\t\t\t// Warn then when they log in.\n\t\t\tif(last_billing_info == null) {\n\t\t\t\tshow_error_usermsg(bill_descrip);\n\t\t\t\t// Direct them to the billing page.\n\t\t\t}\n\t\t\ttoggle_membership_window(true);\n\t\t} else if(info.trial_days_left <= 0) {\n\t\t\t// They never had a free trial\n\t\t\tbill_descrip += \"Set up your\" +\n\t\t\t\t\" account.\";\n\t\t\ttoggle_membership_window(true);\n\t\t} else if(!free_w_pacer && !free_wo_pacer) {\n\t\t\t// They have not yet selected an account. Direct them to the\n\t\t\t// account selection page.\n\t\t\tbill_descrip += \"Setup account.\";\n\t\t\tdisable_pacer_submenus(false, false);\n\t\t\t// Don't show signup page if free trial, even if not full.\n\t\t\t// toggle_membership_window(true);\n\t\t\t// Allow them to change their PACER username/password.\n\t\t\t$billing_pane.find(\"form.setpacerinfo_freetrial\").show();\n\t\t} else if(free_w_pacer || free_wo_pacer) {\n\t\t\t// A valid free trial\n\t\t\tvar free_trial_type = free_w_pacer ? \"trial\":\"unactivated account\";\n\t\t\t// Make sure free trial is selected in the dropdown.\n\t\t\t$flat_rate.append(\"\").val(\"personal\");\n\t\t\t// Show the account type link during the free trial\n\t\t\t$(\"a[href='#MembershipOptions']\").show();\n\t\t\tif(!free_with_credit_card) {\n\t\t\t\t// Allow them to change their PACER username/password.\n\t\t\t\t$billing_pane.find(\"form.setpacerinfo_freetrial\").show();\n\t\t\t}\n\t\t\tif(last_billing_info != null && (\n\t\t\t\t\t\t\t\t\thad_free_w_pacer != free_w_pacer ||\n\t\t\t\t\t\t\t\t\tfree_wo_pacer != had_free_wo_pacer)) {\n\t\t\t\t// They just created the free trial, show a message\n\t\t\t\tshow_success_usermsg(\"Your \" + free_trial_type +\n\t\t\t\t\t\t\t\t\t\" is now activated.\");\n\t\t\t}\n\n\t\t\tbill_descrip += \"\" + info.trial_days_left + \" days \" +\n\t\t\t\t\t\t\"left for \" + free_trial_type + \".\";\n\t\t\t// Close the billing page if it's open\n\t\t\ttoggle_membership_window(false);\n\t\t\t// Disable the PACER searches if necessary\n\t\t\tdisable_pacer_submenus(false, true, free_w_pacer);\n\t\t} else {\n\t\t\t// This should not happen\n\t\t\tshow_error_usermsg(\"Bad account type!\");\n\t\t}\n\t\tif(info.had_free_trial) {\n\t\t\t$(\".trial_days_left\").text(info.trial_days_left);\n\t\t\t$billing_pane.find(\".account_type\")\n\t\t\t\t.find(\"form.set_account_type input[type=submit]\").button({disabled:true}).end()\n\t\t\t\t.find(\".account_msg\").text(\"Contact support@docketalarm.com\" +\n\t\t\t\t \" to change your account type during the trial.\");\n\t\t} else {\n\t\t\t$(\".trial_days_left\").text(info.trial_days_left);\n\t\t\t$billing_pane.find(\".account_type\")\n\t\t\t\t.find(\"form.set_account_type input[type=submit]\").button({disabled:true}).end()\n\t\t\t\t.find(\".account_msg\").text(\"Add a credit card to change\" +\n\t\t\t\t \" your account.\");\n\t\t}\n\t}\n\t$billing_pane.find(\".billing_narrative.\" + narrative_class ).show().end();\n\t$(\"#current_billing\").html(bill_descrip);\n\n\tif(info.unsubscribed) {\n\t\t// We could arguably log them out automatically here. But lets not.\n\t\tshow_success_usermsg(\"Your account is not active. Please log out \" +\n\t\t\t\"and log back in to activate.\");\n\t}\n\n\tupdate_label_widths(\".billing_pane\");\n\tsetup_billing_overlay_callback.map(function (callback) {\n\t\tcallback(info);\n\t});\n\n\t// FIXME: If the user's billing type changed, then do another full update\n\t// to get an updated narrative, etc. We shouldn't need to do this though.\n\tif(last_billing_info && last_billing_info.billing_type != info.billing_type) {\n\t\tupdate_billing_info(true);\n\t}\n\n\tlast_billing_info = info;\n}\n\n/**\n * Sets up the enabled/disabled state of the sub-menus.\n *\n * reset\t\t\tTurns everything back on.\n * has_pacer\t\tIf true, everything is enabled.\n * has_account_type If false, they have not selected an account type.\n */\nfunction disable_pacer_submenus(reset, has_account_type, has_pacer) {\n\tvar court_items = \"#Federal_Courts, #Bankruptcies_Courts, #The_ITC, #The_USPTO, \";\n\tcourt_items += \"[for=Federal_Courts], [for=Bankruptcies_Courts], [for=The_ITC], [for=The_USPTO]\";\n\tif(reset || has_pacer) {\n\t\t$(\"#menu\")\n\t\t\t.find(court_items)\n\t\t\t\t.removeClass(\"disabled\")\n\t\t\t\t.find(\"a\")\n\t\t\t\t\t.powerTip('destroy').end();\n\t} else if(!has_account_type) {\n\t\t$(\"#menu\")\n\t\t\t.find(court_items)\n\t\t\t\t.addClass(\"disabled\")\n\t\t\t.find(\"a\")\n\t\t\t\t.powerTip('destroy')\n\t\t\t\t.powerTip({\n\t\t\t\t\t\t\tsmartPlacement:true,\n\t\t\t\t\t\t\tmouseOnToPopup:true\n\t\t\t\t\t}).data('powertiptarget', 'select_account_tooltip')\n\t\t\t\t.end();\n\t} else {\n\t\t$(\"#menu\")\n\t\t\t.find(\"#Federal_Courts, #Bankruptcies_Courts\")\n\t\t\t\t.addClass(\"disabled\")\n\t\t\t\t.find(\"a\")\n\t\t\t\t\t.powerTip('destroy')\n\t\t\t\t\t.powerTip({\n\t\t\t\t\t\tsmartPlacement:true,\n\t\t\t\t\t\tmouseOnToPopup:true\n\t\t\t\t\t}).data('powertiptarget', 'disabled_tooltip').end()\n\t\t\t\t.end()\n\t\t\t.find(\".submenuitem[for=Federal_Courts], .submenuitem[for=Bankruptcies_Courts]\")\n\t\t\t\t.addClass(\"disabled\").end();\n\t}\n}\n\n/**\n * Get the user's billing information, and saves it in their local session\n * storage for 10 minutes.\n * @param force true to force pulling new data.\n * @param done callback function of the form function done(user_info, synchronous, error)\n * \terror can be a string if it was an ajax error, or a dict if a http error.\n * @returns {boolean|*} the info if it was cached, false if we need to fetch.\n */\nfunction update_billing_session_info(force, done) {\n\tvar info = sessionGet(\"billing_info\", true);\n\tif(info && !force) {\n\t\tif(done) {\n\t\t\tdone(info, true);\n\t\t}\n\t\treturn info;\n\t}\n\t$.getJSON(\"/get_user_info.ajax\", function () { })\n\t.success(function(resp) {\n\t\tif(resp.success) {\n\t\t\tsessionSet(\"billing_info\", resp.user_info, 10 * 60, true);\n\t\t\tif(done) {\n\t\t\t\tdone(resp.user_info, false);\n\t\t\t}\n\t\t} else if (done) {\n\t\t\tdone(null, false, resp.error);\n\t\t}\n\t})\n\t.error(function(error) {\n\t\tif(done) {\n\t\t\tdone(null, false, error);\n\t\t}\n\t});\n\treturn false;\n}\n\n/**\n * Get the user's billing data from the server (if force==true) or\n * try to get a cached version, and then setup the billing overlay.\n *\n * The user must be logged in to use this function.\n */\nfunction update_billing_info(force) {\n\t// Group Billing isn't available on every screen (e.g., documents).\n\tif(typeof update_groupbilling_info == \"function\") {\n\t\tvar groupbilling_info = force ? null : sessionGet(\"groupbilling_info\");\n\t\tupdate_groupbilling_info(null, groupbilling_info);\n\t}\n\n\tvar info = update_billing_session_info(force, function _done(user_info, synchronous, error) {\n\t\t$('.billing_pane .loader').hide();\n\t\tif(user_info) {\n\t\t\tsetup_billing_pane(user_info, synchronous);\n\t\t\t// Only update the cards list when the billing info changes\n\t\t\tvar card_list = force ? null : sessionGet(\"cards_info\");\n\t\t\tcard_list_helpers.update_card_list(null, card_list, user_info);\n\t\t} else if(error && typeof error == \"string\") {\n\t\t\tvar hash = $.bbq.getState();\n\t\t\tif(hash.embed != undefined) {\n\t\t\t\t// In embedded mode, don't complain about user login.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar params = $.deparam(window.location.search.slice(1));\n\t\t\tif(params.login_encrypted) {\n\t\t\t\t// We're used an encrypted login, so shouldn't access user functions.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tshow_error_usermsg(error);\n\t\t} else if(error && error.status != 0 || error.readyState != 0) {\n\t\t\tshow_error_usermsg(error.status + \": \" + error.statusText);\n\t\t}\n\t});\n\tif(info == false) {\n\t\t$('.billing_pane .loader').show();\n\t}\n}\n\n/**\n *\n */\nfunction init_matter_inputs() {\n\t// Special setup for the userinfo form at the top of the\n\t$(\".actions .userinfoform input\").change(function () {\n\t\t$(this).parents(\"form\").first().submit();\n\t});\n}\n\nfunction init_billing_info_callback() {\n\t// Save The Billing Info\n\t$('form.userinfoform, .userinfoform form').ajaxForm({\n\t\tbeforeSubmit: function (arr, $form) {\n\t\t\t$form\n\t\t\t\t.find('.loader').show().end()\n\t\t\t\t.find('input[type=text]').addClass(\"changing\").end()\n\t\t\t\t// Remove any existing powertips.\n\t\t\t\t.removeClass(\"matter_error\")\n\t\t\t\t.powerTip('hide', true);\n\t\t},\n\t\tsuccess: function (resp, statusText, xhr, $form) { // post-submit callback\n\t\t\t$form.find('.loader').hide().end()\n\t\t\t\t.find('input[type=text]').removeClass(\"changing\");\n\t\t\tif (resp.success) {\n\t\t\t\tif ($form.attr('action').search(\"adminas\") > 0) {\n\t\t\t\t\t// This was an administrator changing a setting.\n\t\t\t\t\tvar $submit = $form.find(\"input[type=submit]\");\n\t\t\t\t\tshow_usermsg(\"Changed\", $submit.length ? $submit : null);\n\t\t\t\t} else {\n\t\t\t\t\tsessionSet(\"billing_info\", resp.user_info, 10 * 60, true);\n\t\t\t\t\tsessionClear(\"cards_info\");\n\t\t\t\t\tupdate_billing_info($form.hasClass(\"force_billing_update\"));\n\t\t\t\t}\n\t\t\t} else if(set_matter_number_error(resp)) {\n\n\t\t\t} else {\n\t\t\t\tshow_error_usermsg(resp.error, resp.error_type);\n\t\t\t}\n\t\t},\n\t\terror: function (dum, the_error) {\n\t\t\t$(this).find('.loader').hide();\n\t\t\tshow_error_usermsg(the_error)\n\t\t},\n\t\tdataType: 'json' // 'xml', 'script', or 'json' (expected server response type)\n\t});\n}\n\n/////////////////////////\n// Credit Cards - Adding\n\n/**\n * Setup and show a credit card form given a credit card form holder (i.e.\n * a div to place a cc form).\n * @param cc_form_holder jQuery object where the credit card form should be placed\n * @param use_placeholder If true, display the form in a slightly different\n * style using placeholder text rather than labels.\n * @param for_freetrial If true, adding the credit card should only be used\n * for the free trial, and should not be upgraded to a full account. The\n * user must already be on a free trial for it to work.\n * @param success_callback An optional callback if the card was properly added.\n * @param additional_card If true, the form will not show the account type selector\n * This should happen only when a user doesn't have any credit cards to their name\n */\nfunction setup_credit_card_form(cc_form_holder, use_placeholder,\n\t\t\t\t\t\t\t\tfor_freetrial, success_callback, additional_card) {\n\tif(!cc_form_holder || !cc_form_holder.length) {\n\t\t// No holder on page to show.\n\t\treturn;\n\t}\n\t// get the boolean value of whether a user is corporate\n\t// if they are, they shouldn't be able to see the rate picker\n\tvar is_corporate_user =\n\t\tsessionGet('billing_info', true).billing_type == 'corporate' || false;\n\tif(cc_form_holder.find(\".credit-card-form\").length > 0) {\n\t\t// Already setup, just show it\n\t\tvar $full_div_holder = cc_form_holder.parent(); // show the whole element, including the rate selector\n\t\t$full_div_holder.show();\n\t\t// we don't need the rate selector if we're adding more cards to the existing account, so hide it\n\t\tif (additional_card || is_corporate_user)\n\t\t\t$full_div_holder.find('.memholder').hide();\n\t\treturn;\n\t}\n\tvar cc_form = $(\".credit-card-form.original\").clone()\n\t\t\t.removeClass(\"original\").show()\n\t\t\t.appendTo(cc_form_holder);\n\t// parent form is the form that surrounds cc_form\n\tvar parent_form = cc_form_holder[0].tagName.toLowerCase() == \"form\" ?\n\t\t// It's the card holder itself.\n\t\t$(cc_form_holder[0]) :\n\t\t// We have to go up the parent tree.\n\t\tget_parent_with_tag(cc_form_holder, \"form\");\n\tif (!parent_form || !parent_form.length) {\n\t\tconsole.log(\"Cannot find CC's holder.\");\n\t\treturn;\n\t}\n\t// show the parent form\n\tparent_form.show();\n\tif (parent_form.prop('id') == 'billing_add_card_form' && !(additional_card || is_corporate_user)) {\n\t\t// the form is the embedded form in the account pane, we need to add the account type selector\n\t\t// find the selector form and prepend to cc_form_holder\n\t\t// this should only show up if the user has no cards to their name\n\t\t$('#membership_options_form .memholder').clone().prependTo(cc_form_holder);\n\t\tparent_form.find(\".memoption\").click(function () {\n\t\t\tparent_form.find(\".memoption\").removeClass(\"selected\");\n\t\t\t$(this).addClass(\"selected\")\n\t\t\t\t.find(\"input[type=radio]\").prop(\"checked\", true);\n\t\t});\n\n\t}\n\t// Make sure we have the Stripe script, before we start setting up the form.\n\tcard_list_helpers.get_stripe_script();\n\tif(use_placeholder) {\n\t\t// Make a more space efficient credit card form that relies on\n\t\t// placeholders to label the items.\n\t\t// instead.\n\t\tcc_form.find(\"input[type=text]\").each(function _eachinput(v) {\n\t\t\tvar $this = $(this);\n\t\t\tvar $parent = get_parent_with_class($this, \"form-row\");\n\t\t\tif($parent.find(\"input[type=text]\").length != 1){\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar $label = $parent.find(\"label\");\n\t\t\tif($label.length != 1) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Hide the label.\n\t\t\tvar label_txt = $label.hide().text();\n\t\t\t// Add the label to the placeholder text.\n\t\t\t$this.attr('placeholder', label_txt);\n\t\t});\n\t\t// We need to handle some special cases.\n\t\tcc_form.find(\".card-city\").attr(\"placeholder\", \"City\")\n\t\t\t.parent().find(\"label\").hide();\n\t\tcc_form.find(\".card-state\").attr(\"placeholder\", \"State\");\n\t\t// Move the zip into the city/state.\n\t\tcc_form.find(\"#Zip\").appendTo(cc_form.find(\".card-state\").parent())\n\t\t\t.attr(\"size\", 8);\n\t\t// Move the credit card icons up to the CC line.\n\t\tcc_form.find(\".card_icons\").appendTo(cc_form.find(\"#card1\").parent());\n\t\tcc_form.find(\".form-row.icon-row\").html(\"\");\n\t\tcc_form.find(\"#card1\").attr(\"size\", 20);\n\t\t// Move the CVC into the expiration line.\n\t\tcc_form.find(\"#cvc1\").appendTo(cc_form.find(\"#expmo1\").parent())\n\t\t\t.attr(\"size\", 8)\n\t\t\t// Hide the cvc details and make it a powertip.\n\t\t\t.data(\"powertip\", cc_form.find(\".cvc_details\").hide().text())\n\t\t\t.powerTip({smartPlacement:true});\n\n\t} else {\n\t\tupdate_label_widths(parent_form);\n\t}\n\n\tparent_form.validationEngine({\n\t\tonValidationComplete:function(form, valid) {\n\t\t\tif(valid)\n\t\t\t\treturn true;\n\t\t\tsetTimeout(function () {\n\t\t\t\tform.validationEngine('hideAll');\n\t\t\t}, 3000);\n\t\t\treturn false;\n\t\t}\n\t});\n\tcc_form.find(\".submit_button\").button({\n\t\ticons: { primary: \"ui-icon-locked\" }\n\t}).click(function _on_submit_click(event) {\n\t\tif (!is_card_address_valid(cc_form)) {\n\t\t \tshow_error_usermsg(\"Cannot add credit card. Address is missing\");\n\t\t \treturn;\n\t\t}\n\t\tif (typeof Stripe === \"undefined\") {\n\t\t\tsetTimeout(function () {\n\t\t\t\t_on_submit_click(event);\n\t\t\t}, 100);\n\t\t\treturn;\n\t\t}\n\n\t\tparent_form\n\t\t\t.find(\".submit_button\").addClass(\"changing\").button('disable').end()\n\t\t\t.find(\".loader\").show();\n\t\t// determine account type from inputs of the parent form\n\t\tvar account_type = parent_form\n\t\t\t.find(\".memoption.selected input\").val() || false;\n\t\tStripe.createToken({\n\t\t\tname : cc_form.find('.card-name').val(),\n\t\t\taddress_line1:\tcc_form.find('.card-address-1').val(),\n\t\t\taddress_line2:\tcc_form.find('.card-address-2').val(),\n\t\t\taddress_city:\tcc_form.find('.card-city').val(),\n\t\t\taddress_state:\tcc_form.find('.card-state').val(),\n\t\t\taddress_zip:\tcc_form.find('.card-zip').val(),\n\t\t\taddress_country:cc_form.find('.card-country').val(),\n\t\t\tnumber: \tcc_form.find('.card-number').val(),\n\t\t\tcvc: \t\tcc_form.find('.card-cvc').val(),\n\t\t\texp_month: \tcc_form.find('.card-expiry-month').val(),\n\t\t\texp_year: \tcc_form.find('.card-expiry-year').val()\n\t\t}, function (status, response) {\n\t\t\treturn card_list_helpers.stripeResponseHandler(parent_form, status, response,\n\t\t\t\tfor_freetrial, account_type, success_callback, function () {\n\t\t\t\t\tparent_form\n\t\t\t\t\t\t.find(\".submit_button\").removeClass(\"changing\").end()\n\t\t\t\t\t\t.find(\".loader\").hide();\n\t\t\t});\n\t\t});\n\t});\n\treturn cc_form;\n}\n/**\n * This function validates the Credit Card Billing address.\n * We need the address to determine the Sales Tax for each user\n * @param cc_form: The Credit Card form that user enters billing and cc details on\n * @returns {boolean}: Whether the address is filled or not\n */\nfunction is_card_address_valid(cc_form){\n var address_line1 = cc_form.find(\".card-address-1\").val();\n var address_city = cc_form.find(\".card-city\").val();\n var address_state = cc_form.find(\".card-state\").val();\n var address_zip = cc_form.find(\".card-zip\").val();\n var address_country = cc_form.find(\".card-country\").val();\n return !(address_line1 == \"\" || address_city == \"\" || address_state == \"\"\n || address_zip == \"\" || address_country == \"\");\n}\nvar card_list_helpers = {\n\t/**\n\t * Display the list of credit cards and handle deleting credit cards when asked.\n\t *\n\t * Not intended to be called directly. Instead, call update_billing_info, which\n\t * calls this function as necessary.\n\t */\n\tupdate_card_list : function update_card_list(base, cards_info, user_info) {\n\t\tif(!cards_info) {\n\t\t\t$.getJSON(\"/get_creditcard.ajax\", {}, function(resp) {\n\t\t\t\tif(resp.success) {\n\t\t\t\t\t// Save credit card info in a cookie so we can reuse.\n\t\t\t\t\tsessionSet(\"cards_info\", resp.cards_info, 10 * 60);\n\t\t\t\t\tcard_list_helpers.update_card_list(base, resp.cards_info, user_info);\n\t\t\t\t} else {\n\t\t\t\t\t// show_error_usermsg(resp.error);\n\t\t\t\t\tif(!base) {\n\t\t\t\t\t\tbase = $(\".billing_pane .card-list\");\n\t\t\t\t\t}\n\t\t\t\t\tbase.html(\"There was an issue contacting our credit card processor \" +\n\t\t\t\t\t\t\"and we are unnable to obtain all of your account details. \" +\n\t\t\t\t\t\t\"Let us know if your issue is urgent at support@docketalarm.com.\");\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif(!base)\n\t\t\tbase = $(\".billing_pane .card-list\");\n\t\tvar $billing_pane = get_parent_with_class(base, \"billing_pane\");\n\t\tbase.empty();\n\t\t// Don't show the card list to users that cannot edit.\n\t\tif(user_info && !user_info.user_can_edit) {\n\t\t\tif($billing_pane)\n\t\t\t\t$billing_pane.addClass(\"cannot_edit\");\n\t\t\treturn;\n\t\t}\n\t\tif($billing_pane)\n\t\t\t$billing_pane.removeClass(\"cannot_edit\");\n\t\tif(cards_info.length == 0) {\n\t\t\t// Show the credit card form.\n\t\t\tsetup_credit_card_form($(\".billing_pane .card-form-holder\"));\n\t\t\t// Hide the add card button.\n\t\t\t$(\".billing_pane .AddCard\").hide();\n\t\t\tif(last_billing_info && last_billing_info.billing_type == \"personal\") {\n\t\t\t\t$(\".billing_narrative\").hide();\n\t\t\t\t$(\".billing_narrative.no_cards\").show();\n\t\t\t}\n\t\t} else {\n\t\t\tfunction int_zero_pad(num, pad) {\n\t\t\t\tvar s = num + \"\";\n\t\t\t\twhile (s.length < pad) s = \"0\" + s;\n\t\t\t\treturn s;\n\t\t\t}\n\t\t\tfor(var i=0; i < cards_info.length; i++) {\n\t\t\t\tvar card = cards_info[i];\n\t\t\t\tvar card_img = card_list_helpers.getCardImg(card.type, true);\n\t\t\t\t$(\".credit_card_info.original\").clone().removeClass(\"original\")\n\t\t\t\t\t.find('.card_icons').attr('src',card_img).end()\n\t\t\t\t\t.find(\".cname\").text(card.name).end()\n\t\t\t\t\t.find(\".caddress\").text(card.address).end()\n\t\t\t\t\t.find(\".czip\").text(int_zero_pad(card.zip, 5)).end()\n\t\t\t\t\t.find(\".ctype\").text(card.type).end()\n\t\t\t\t\t.find(\".clast4\").text(\"xxx-\" + card.last4).end()\n\t\t\t\t\t.find(\".clastexpmonth\").text(card.exp_month).end()\n\t\t\t\t\t.find(\".clastexpyear\").text(card.exp_year).end()\n\t\t\t\t\t.find(\".delete_card\").attr('cardid', card.id).end()\n\t\t\t\t\t.find(\".make_default\").attr('cardid', card.id).end()\n\t\t\t\t\t.toggleClass(\"default_card\", card.default_card)\n\t\t\t\t\t.appendTo(base);\n\t\t\t}\n\t\t\t$(\".billing_pane\")\n\t\t\t\t.find(\"#billing_add_card_form\").hide().end()\n\t\t\t\t.find(\".AddCardTitle\").hide().end()\n\t\t\t\t.find(\".AddCard\").show();\n\n\t\t\tbase.find(\".delete_card\").click(function() {\n\t\t\t\tvar last4 = $(this).parent().parent().find(\".clast4\").text();\n\t\t\t\tif(last4 && last4.length) {\n\t\t\t\t\tlast4 = \" ending in \" + last4;\n\t\t\t\t}\n\t\t\t\tvar answer = confirm (\"Are you sure you want to remove the credit \"+\n\t\t\t\t\t\"card\" + last4 + \"?\");\n\t\t\t\tif (!answer)\n\t\t\t\t\treturn false;\n\t\t\t\tget_parent_with_class($(this), \"credit_card_info\").find(\".loader\").show();\n\t\t\t\t$.post(\"/delete_creditcard.ajax\", {id:$(this).attr('cardid')}, function(resp) {\n\t\t\t\t\t$(\".billing_pane .loader\").hide();\n\t\t\t\t\tif(resp.success) {\n\t\t\t\t\t\t// Save credit card info in a cookie so we can reuse.\n\t\t\t\t\t\tsessionSet(\"cards_info\", resp.cards_info, 10 * 60);\n\t\t\t\t\t\tcard_list_helpers.update_card_list(base, resp.cards_info, user_info);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t\t\t}\n\t\t\t\t}, \"json\");\n\t\t\t\treturn false;\n\t\t\t}).end()\n\t\t\t.find(\".make_default\").click(function() {\n\t\t\t\tget_parent_with_class($(this), \"credit_card_info\").find(\".loader\").show();\n\t\t\t\t$.post(\"/set_default_creditcard.ajax\", {id:$(this).attr('cardid')}, function(resp) {\n\t\t\t\t\t$(\".billing_pane .loader\").hide();\n\t\t\t\t\tif(resp.success) {\n\t\t\t\t\t\t// Save credit card info in a cookie so we can reuse.\n\t\t\t\t\t\tsessionSet(\"cards_info\", resp.cards_info, 10 * 60);\n\t\t\t\t\t\tcard_list_helpers.update_card_list(base, resp.cards_info, user_info);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t\t\t}\n\t\t\t\t}, \"json\");\n\t\t\t\treturn false;\n\t\t\t});\n\t\t}\n\t\t// Let everyone know we finished.\n\t\tdispatchEvent(makeCustomEvent('card_list_updated'));\n\t},\n\n\t/**\n\t * Stripe add credit card callback function. Called once the stripe ajax call\n\t * is complete and we recieved the stripe token. Continues the add credit card\n\t * process.\n\t */\n\tstripeResponseHandler: function stripeResponseHandler(parent_form, status, response, for_freetrial,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t account_type, success_callback, done)\n\t{\n\t\tif (response.error) {\n\t\t\tparent_form.find('.loader').hide();\n\t\t\tparent_form.find(\".submit_button\").button('enable');\n\t\t\t//show the errors on the form\n\t\t\tshow_error_usermsg(response.error.message);\n\t\t\treturn;\n\t\t}\n\t\tvar out_data = {\n\t\t\tstripe_token: response.id,\n\t\t\tname: response.card.name,\n\t\t\taddress: response.card.address_line1,\n\t\t\tzip: response.card.address_zip,\n\t\t\t// Newer versions of Stripe use \"brand\", not \"type\", but be compatible.\n\t\t\ttype: response.card.brand || response.card.type,\n\t\t\tlast4: response.card.last4,\n\t\t\texp_month: response.card.exp_month,\n\t\t\texp_year: response.card.exp_year\n\t\t};\n\t\tif (for_freetrial) {\n\t\t\tout_data.stay_free_trial = true;\n\t\t}\n\t\tif (account_type) {\n\t\t\tif (account_type == \"All\")\n\t\t\t\t// this denotes flat rate account type\n\t\t\t\tout_data.account_type = \"flat_rate\";\n\t\t\telse\n\t\t\t\tout_data.account_type = account_type;\n\t\t}\n\t\t$.post(\"/add_creditcard.ajax\", out_data, function (result) {\n\t\t\tparent_form\n\t\t\t\t.find('.loader').hide().end()\n\t\t\t\t.find(\".submit_button\").button('enable');\n\t\t\tif (result.success) {\n\t\t\t\tif (parent_form && parent_form.attr('action') == '/set_user_info.ajax') {\n\t\t\t\t\t// After adding the credit card, we must set the user account.\n\t\t\t\t\tparent_form.submit();\n\t\t\t\t}\n\t\t\t\tupdate_billing_info(true);\n\t\t\t\tif (success_callback) {\n\t\t\t\t\tsuccess_callback(result);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Update the billing even if there was a failure b/c we still\n\t\t\t\t// may have deleted older cards.\n\t\t\t\tupdate_billing_info(true);\n\t\t\t\t// Show the message after the call so the ajax has time to run.\n\t\t\t\tshow_error_usermsg(result.error);\n\t\t\t}\n\t\t\tif (done) {\n\t\t\t\tdone();\n\t\t\t}\n\t\t}, \"json\");\n\t},\n\n\tgetCardImg: function getCardImg(card_type, big) {\n\t\tvar base = \"/site_media/img/credit-cards/\";\n\t\tbase += big ? \"big_\" : \"\";\n\t\treturn base + ({\n\t\t\t\"Visa\" : \"visa_icon.png\",\n\t\t\t\"MasterCard\" : \"mastercard_icon.png\",\n\t\t\t\"American Express\" : \"amex_icon.png\",\n\t\t\t\"Discover\" : \"discover_icon.png\",\n\t\t\t\"Diners Club\" : \"diners_icon.png\",\n\t\t\t\"JCB\" : \"jcb_icon.png\"\n\t\t}[card_type] || \"all_icons.png\");\n\t},\n\t_has_scripts : {},\n\t_is_loading : false,\n\tget_stripe_script : function get_stripe_script(stripe_key) {\n\t\tif(!stripe_key || !stripe_key.length) {\n\t\t\tstripe_key = stripe_public_key;\n\t\t}\n\t\tif(this._has_scripts[stripe_key]) {\n\t\t\treturn;\n\t\t}\n\t\tif(this._is_loading) {\n\t\t\treturn;\n\t\t}\n\t\t// Get Stripe code asynchronously.\n\t\tif (!stripe_key || !stripe_key.length) {\n\t\t\tconsole.log(\"No stripe key, will not load stripe.\");\n\t\t\treturn;\n\t\t}\n\t\tvar t = this;\n\t\tt._is_loading = true;\n\t\t$.getScript(\"https://js.stripe.com/v1/\", function _got_stripe() {\n\t\t\tt._is_loading = false;\n\t\t\tif (typeof Stripe !== \"undefined\") {\n\t\t\t\tStripe.setPublishableKey(stripe_key);\n\t\t\t\tt._has_scripts[stripe_key] = true;\n\t\t\t}\n\t\t});\n },\n\tcheckCardNum : function checkCardNum(field, rules, i, options) {\n\t\tif (!Stripe) {\n\t\t\t// Don't validate if we can't.\n\t\t\treturn true;\n\t\t}\n\t\tif (!Stripe.validateCardNumber(field.val()))\n\t\t\treturn \"Invalid credit card number.\";\n\t\t$(\".credit-card-form img.card_icons\").attr(\"src\", card_list_helpers.getCardImg(Stripe.cardType(field.val())));\n\t},\n\tcheckDate : function checkDate(field, rules, i, options) {\n\t\tif (!Stripe) {\n\t\t\treturn true;\n\t\t}\n\t\tvar parent_form = field.parent();\n\t\tif (!Stripe.validateExpiry(\n\t\t\tparent_form.children('.card-expiry-month').val(),\n\t\t\tparent_form.children('.card-expiry-year').val()))\n\t\t\treturn \"Bad expiration date.\";\n\t},\n\tcheckCVC : function checkCVC(field, rules, i, options) {\n\t\tif (!Stripe) {\n\t\t\treturn true;\n\t\t}\n\t\tif (!Stripe.validateCVC(field.val()))\n\t\t\treturn \"Bad CVC code\";\n\t}\n}\n\n\nfunction toggle_membership_window(show) {\n\tif(show) {\n\t\tshow_membership_options_overlay();\n\t} else {\n\t\t// Do the same with the membership page if it's open.\n\t\tvar $memberoptions = $(\"#MembershipOptions\");\n\t\tif($memberoptions.is(\":visible\"))\n\t\t\t$memberoptions.dialog(\"close\");\n\t}\n}\nfunction show_membership_options_overlay(force_now) {\n\tvar cards_info = sessionGet(\"cards_info\");\n\tif(user_logged_in && !cards_info && !force_now) {\n\t\t// Card data is coming in any second, wait for it..\n\t\tsetTimeout(function () {\n\t\t\tshow_membership_options_overlay(true);\n\t\t}, 1000);\n\t\treturn;\n\t} else if (user_logged_in && cards_info === null) {\n\t\tconsole.log(\"Showing user data witout card info\");\n\t}\n\n\tvar $options_window = $(\"#MembershipOptions\");\n\tvar has_cards = cards_info && cards_info.length > 0;\n\tif (has_cards) {\n\t\t// We have a credit card on file, however our billing type is still\n\t\t// set to free trial and its expired. This is an error state and\n\t\t// should not occur, but handle it gracefully.\n\t\t// Build our html;\n\t\tvar $options = $options_window\n\t\t\t.find(\".memoption.first\").clone()\n\t\t\t.find(\"input\").remove().end();\n\t\tvar $msg = $(\"

    Would you like to use the card on file \" +\n\t\t\t\"to upgrade your account?

    \").append($options)\n\t\t\t.find(\".PACER_tip\").powerTip({\n\t\t\t\tsmartPlacement: true,\n\t\t\t\tmouseOnToPopup: true,\n\t\t\t\tcloseDelay: 400,\n\t\t\t}).data('powertip', $(\"#PACER_explained\").html()).end();\n\n\t\tif ($options_window.is(\":visible\")) {\n\t\t\t$options_window.dialog(\"close\");\n\t\t}\n\t\tshow_confirm_usermsg({\n\t\t\ttitle: last_billing_info && last_billing_info.had_free_trial ?\n\t\t\t\t\"Free Trial Expired\" : \"Upgrade Account\",\n\t\t\tsubtitle: $msg,\n\t\t\tokay_msg: \"Yes, Upgrade\",\n\t\t\tcancel_msg: \"No\",\n\t\t\tokay: function () {\n\t\t\t\t$(\"form#upgrade_account\").submit();\n\t\t\t},\n\t\t\tdialog_options: {\n\t\t\t\tdialogClass: \"UpgradeAccountMsg\"\n\t\t\t}\n\t\t});\n\t\treturn;\n\t}\n\tfunction do_show() {\n var position = {my: \"center center-2%\", at: \"center\", of: window};\n var $dialog = borderless_dialog(\"#MembershipOptions\", {\n dialogClass: \"MembershipOptionsParent\"\n })\n\t\t.dialog(\"open\")\n\t\t.find(\".card-form-holder\").each(function () {\n\t\t\tvar $this = $(this);\n\t\t\tsetup_credit_card_form($this);\n\t\t\t$this.find(\".submit_button .card_msg\").text(\"Become a Member\");\n\t\t\t$options_window.dialog(\"option\", \"position\", position);\n\t\t\t$(window).resize(function (e) {\n\t\t\t\t$options_window.dialog(\"option\", \"position\", position);\n\t\t\t});\n\t\t}).end()\n\t\t.find(\".memoption\").click(function () {\n\t\t\t$dialog.find(\".memoption\").removeClass(\"selected\");\n\t\t\t$(this).addClass(\"selected\")\n\t\t\t\t.find(\"input[type=radio]\").prop(\"checked\", true);\n\n\t\t}).find(\"[data-powertip]\").powerTip({\n\t\t\t\tsmartPlacement: true,\n\t\t\t\tpopupClass: 'small_powertip',\n\t\t\t\tcloseDelay: 400,\n\t\t\t}).end().end();\n\t}\n\tif($options_window.is(\":visible\")) {\n\t\t// Don't display if already visible.\n\t\t;\n\t} else if(typeof groupbilling_ask_join !== \"undefined\") {\n\t\t// Ask the user if they'd like to join the billing group first.\n \tgroupbilling_ask_join(null, do_show);\n\t} else {\n\t\t// Show the normal membership dialog.\n\t\tdo_show();\n\t}\n}\n\n\n$(document).ready(function _start() {\n\tinit_matter_inputs();\n\tsetup_signup_dialogs();\n setTimeout(function common_setup_later() {\n init_billing_info_callback();\n\t\t$(\".calendly_demo\").click(calendly_demo_click);\n\t\t$(\"a[href='#MembershipOptions']\").click(function () {\n\t\t\tshow_membership_options_overlay();\n\t\t\treturn false;\n\t\t});\n }, 10);\n});\n\n//----------------------------------------------------------\n//--------------------- Paywall ----------------------------\n/**\n * Show the paywall if the user has viewed too many items.\n * There may be a delay between the call and showing it.\n *\n * onshow: callback right after the paywall is shown.\n * return true if the paywall will be shown\n */\nfunction show_paywall_if_necessary(on_show) {\n\tif(!user_logged_in && paywall_expired && !override_paywall) {\n\t\tsetTimeout(function() {\n\t\t\tif(override_paywall) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvar diag = show_signup_dialog(true);\n\t\t\tif(on_show) {\n\t\t\t\ton_show(diag);\n\t\t\t}\n\t\t\t$(\"#PaywallOverlay\").show(1000)\n\t\t}, 3000);\n\t\treturn true;\n\t} else if(!user_logged_in && user_firm_name &&\n\t\t!sessionGet(\"sent_user_firm_inquiry\", true)) {\n\t\tsetTimeout(function() {\n\t\t\tvar $user_info = $(\"#UserFirmInfo\")\n\t\t\t\t.find(\".firm_name\").text(user_firm_name).end()\n\t\t\t\t.find(\"[name=organization]\").val(user_firm_name).end()\n\t\t\t\t.find(\".seemore\").click(function () {\n\t\t\t\t\t$(this).parent().parent()\n\t\t\t\t\t\t.find(\"form\").show().end()\n\t\t\t\t\t\t.find(\".message\").hide().end();\n\t\t\t\t\treturn false;\n\t\t\t\t}).end()\n\t\t\t.show(500);\n\t\t\tsetup_marketing_inquiry_form($user_info.find(\"form\"), function () {\n\t\t\t\t$user_info.find(\"form\").hide().end().find(\".thanks\").show();\n\t\t\t\tsessionSet(\"sent_user_firm_inquiry\", true, 60*60*12, true);\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\t$user_info.hide(500);\n\t\t\t\t}, 1000);\n\t\t\t});\n\t\t}, 3000);\n\t}\n\n\t// Show the fastcase dialog if necessary.\n\tif(typeof user_source != \"undefined\" &&\n\t\tuser_source && user_source.source == \"Fastcase\" &&\n\t\t!sessionGet(\"signup_overlay_closed_\" + \"NewUserOverlayFastCase\")) {\n\t\tvar diag = show_signup_dialog(false, false, false, false,\n\t\t\t\"NewUserOverlayFastCase\");\n\t\tif(on_show) {\n\t\t\ton_show(diag);\n\t\t}\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * We like to ease the user into knowing that the paywall is coming. So\n * show them a small window before we hit them with the real paywall.\n\t4/2015: Turn this off, Optimizely showed we get 15% more signups without it.\n\t3/2020: Turn it back on just for COVID-19.\n\t6/2020: Only show when they are running out, this message may hurt\n\t our google rankings\n */\nvar SHOW_PAYWALL_HELPER = true;\nfunction show_paywall_helper() {\n\t// Don't bother showing the user the window unless it helps.\n\tif(paywall_expired)\treturn;\n\tif(override_paywall) return;\n\tif(!paywall_incremented) return;\n\t// Don't show them the first few. Our Google rankings went down in\n\t// March and again in June, and this may be the culprit.\n\tvar paywall_used = paywall_total - paywall_left;\n\tif(paywall_used < 5 && paywall_left && paywall_left > 3) {\n\t\treturn;\n\t}\n\t$(\"#PaywallIncrement\")\n\t\t.find(\".paywall_used\").text(paywall_used).end()\n\t\t.find(\".paywall_total\").text(paywall_total).end()\n\t\t.find(\".paywall_left\").text(paywall_left || \"0\").end()\n\t\t.find(\".close_overlay\").click(function () {\n\t\t\t$(\"#PaywallIncrement\").hide();\n\t\t\treturn false;\n\t\t}).end()\n\t\t.show()\n\t\t.animate({ bottom: \"-10px\",}, 1000 );\n}\nif (SHOW_PAYWALL_HELPER) {\n\t$(document).ready(function() {\n\t\t// Dont' show it immediately, let the user get used to seeing the page.\n\t\tsetTimeout('show_paywall_helper();', 3000);\n\t});\n}\n\n\n/**\n * Show the signup dialog. Each parameter corresponds to an extra message:\n * \tpaywalled\tSeeing the dialog b/c they reached the paywall limit.\n * download\tSeeing the dialog b/c they're trying to download a doc.\n * tracker\t\tSeeing the dialog b/c they're trying to add a tracker.\n * Returns the dialog if it was opened. Returns null if it was not opened, and\n * the action should be continued.\n **/\nvar signup_dialog_id = 'NewUserOverlayV2';\n// Assigning this to V2 uses the new dialog. Makes it easy for Optimizely.\n// signup_dialog_id = 'NewUserOverlayV2';\nfunction show_signup_dialog(paywalled, download, tracker, expor, id) {\n\tif(user_logged_in) {\n\t\treturn null;\n\t} else if (download && !show_paywall_if_necessary()) {\n\t\t// If downloading and they're below paywall limit, allow the download.\n\t\treturn null;\n\t}\n\tvar diag = borderless_dialog('#' + (id || signup_dialog_id))\n\t\t.dialog(\"open\")\n\t\t\t.toggleClass(\"user_source\", user_source ? true : false)\n\t\t\t.width(\"auto\")\n\t\t\t// Make sure the first input field is focused.\n\t\t\t.find(\"input[name=name]\")\n\t\t\t\t.val(user_source && user_source.first_last ?\n\t\t\t\t\tuser_source.first_last : \"\").focus().end()\n\t\t\t.find(\"input[name=phone]\")\n\t\t\t\t.val(user_source && user_source.phone ?\n\t\t\t\t\tuser_source.phone : \"\").end()\n\t\t\t.find(\"input[name=email], input[name=username]\")\n\t\t\t\t.val(user_source && user_source.email ?\n\t\t\t\t\tuser_source.email : \"\").end()\n\t\t\t.find(\".first\").text(\n\t\t\t\t(user_source && user_source.first) || \"\").end()\n\t\t\t.find(\".email\").text(\n\t\t\t\t(user_source && user_source.email) || \"\").end()\n\t\t\t.find(\".phone\").text(\n\t\t\t\t(user_source && user_source.phone) || \"\").end()\n\t\t\t// Hide everything\n\t\t\t.find(\".nolimit\").hide().end()\n\t\t\t.find(\".download\").hide().end()\n\t\t\t.find(\".tracker\").hide().end()\n\t\t\t.find(\".limit\").hide().end();\n\tif(paywalled) {\n\t\tanalytics_track(\"user\", \"signup_paywall\");\n\t\tdiag\n\t\t\t.find(\".close_overlay\").hide().end()\n\t\t\t.find(\".limit\").show();\n\t\t// Also hide the close button on the signin window.\n\t\t$('#LoginOverlay .close_overlay').hide();\n\t} else if (download || expor) {\n\t\tanalytics_track(\"user\", \"signup_download\");\n\t\tdiag\n\t\t\t.find(\".close_overlay\").show().end()\n\t\t\t.find(\".download\").show();\n\t} else if (tracker) {\n analytics_track(\"user\", \"signup_tracker\");\n diag\n .find(\".close_overlay\").show().end()\n .find(\".tracker\").show();\n } else if(id) {\n\t\t// Track any special analytics popup.\n\t\tanalytics_track(\"user\", \"signup_view_\" + id);\n\t} else {\n\t\tanalytics_track(\"user\", \"signup_click\");\n\t\tdiag\n\t\t\t.find(\".close_overlay\").show().end()\n\t\t\t.find(\".limit\").hide().end()\n\t\t\t.find(\".nolimit\").show().end();\n\t}\n\t// Center the dialog now that everything is shown.\n\tvar position = { my: \"center center-2%\", at: \"center\", of: window };\n\tdiag.dialog(\"option\", \"position\", position);\n\n\t// On IE placeholders disappear on focus. Make sure they're unfocused.\n\tvar ua = window.navigator.userAgent;\n\tif(ua.indexOf(\"MSIE \") > 0 || ua.indexOf(\"Trident/\") > 0 ||\n\t\tua.indexOf(\"Edge/\") > 0) {\n\t\t$(\".ui-dialog\").focus();\n\t}\n\treturn diag;\n}\nfunction setup_signup_dialogs($obj) {\n\tif(!$obj) {\n\t\t$obj = $(\"html\");\n\t}\n\tfunction close_parent_overlay(t) {\n\t\tvar $overlay = get_parent_with_class($(t), \"ui-dialog-content\");\n\t\tif ($overlay) {\n\t\t\t// Close the overlay.\n\t\t\t$overlay.dialog(\"close\");\n\t\t\t// Record the fact that this overlay was closed.\n\t\t\tsessionSet(\"signup_overlay_closed_\" + $overlay.attr('id'), true);\n\t\t}\n\t\treturn false;\n\t}\n\n\t$obj\n\n\t.find(\".signup\").unbind(\"click\").click(function (ev) {\n\t\tif($(this).hasClass('nosignup')) {\n\t\t\treturn;\n\t\t}\n\t\tclose_parent_overlay(this);\n\t\tev.preventDefault();\n\t\tshow_signup_dialog($(this).hasClass(\"paywalled\"),\n\t\t\t\t\t\t\t$(this).hasClass(\"download\"),\n\t\t\t\t\t\t\t$(this).hasClass(\"tracker\"));\n\t\treturn false;\n\t}).end()\n\n\t.find(\".close_overlay\").click(function() {\n\t\t$(\".formError\").remove();\n\t\treturn close_parent_overlay(this);\n\t}).end()\n\n\t.find(\".signin\").unbind(\"click\").click(function (ev) {\n\t\t// Close the signup dialog if it exists.\n\t\tclose_parent_overlay(this);\n\t\tev.preventDefault();\n\t\t// user_logged_in must be defined\n\t\tif(user_logged_in) {\n\t\t\twindow.location.replace(\"/dockets/\");\n\t\t} else {\n\t\t\tborderless_dialog('div#LoginOverlay').dialog(\"open\")\n\t\t\t\t.find(\"input[name=username]\").focus();\n\t\t}\n\t}).end()\n\n\t.find(\".ForgotPassword\").unbind(\"click\").click(function() {\n\t\tclose_parent_overlay(this);\n\t\tborderless_dialog(\"#ForgotPasswordOverlay\").dialog(\"open\")\n\t\t\t.find(\"input[name=username]\").focus();\n\t\treturn false;\n\t}).end()\n\n\t.find(\"a[title]\").powerTip({\n\t\tsmartPlacement: true,\n\t\tpopupClass : 'small_powertip',\n\t}).end()\n\n\t// createlogin forms are for both logging in and new users\n\t.find('form.createlogin').ajaxForm({\n\t\tbeforeSubmit: function(arr, form) {\n\t\t\t$('form.createlogin .loader').css('visibility', 'visible');\n\t\t\t$('form.createlogin input[type=submit]').addClass('disabled');\n\t\t},\n\t\tsuccess: function (resp) {\n\t\t\t// Hide the throbber only if there is an error\n\t\t\tif(resp.success) {\n\t\t\t\tanalytics_track(\"user\", resp.created ? \"new_user\" : \"login\");\n\t\t\t\t// Put a small delay on the reload so async analytics works.\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\tif(resp.redirect) {\n\t\t\t\t\t\twindow.location = resp.redirect;\n\t\t\t\t\t} else {\n\t\t\t\t\t\twindow.location.reload(true);\n\t\t\t\t\t}\n\t\t\t\t}, 10);\n\t\t\t} else {\n\t\t\t\t$('form.createlogin .loader').css('visibility', 'hidden');\n\t\t\t\t$('form.createlogin input[type=submit]').removeClass('disabled');\n\t\t\t\tshow_error_usermsg(resp.error);\n\t\t\t}\n\t\t},\n\t\terror:\t\tfunction (dum, the_error) {\n\t\t\t$('form.createlogin .loader').css('visibility', 'hidden');\n\t\t\tshow_error_usermsg(the_error);\n\t\t\t$('form.createlogin input[type=submit]').removeClass('disabled');\n\t\t\treturn false;\n\t\t},\n\t\tdataType: 'json'\n\t}).end().find(\"#general_sso\").unbind(\"click\").click(function(ev) {\n\t\tev.preventDefault();\n\t\tuser_email = $(\"form.createlogin input[id=login_email]\").val();\n\t\tborderless_dialog(\"div#CorporateSSO\").dialog(\"open\").find(\"input[name=sso_email]\").val(user_email).focus();\n\t }).end().find(\"#general_sso_login\").unbind(\"click\").click(function(ev) {\n\t\tev.preventDefault();\n\t\tuser_email = $(\"form.createlogin input[name=username]\").val();\n\t\tborderless_dialog(\"div#CorporateSSOLogin\").dialog(\"open\").find(\"input[name=sso_email]\").val(user_email).focus();\n\t });\n}\n\nvar calendly_setup = false;\n/**\n * A click handler for calendly links. Should be used with jquery query as\n * follows: $(\"