"module.exports = function() {\n\tthrow new Error(\"define cannot be used indirect\");\n};\n",
"/**\n * Copyright (C) 2018-2020 The Software Heritage developers\n * See the AUTHORS file at the top-level directory of this distribution\n * License: GNU Affero General Public License version 3, or any later version\n * See top-level LICENSE file for more information\n */\n\n// utility functions\n\nexport function handleFetchError(response) {\n if (!response.ok) {\n throw response;\n }\n return response;\n}\n\nexport function handleFetchErrors(responses) {\n for (let i = 0; i < responses.length; ++i) {\n if (!responses[i].ok) {\n throw responses[i];\n }\n }\n return responses;\n}\n\nexport function staticAsset(asset) {\n return `${__STATIC__}${asset}`;\n}\n\nexport function csrfPost(url, headers = {}, body = null) {\n headers['X-CSRFToken'] = Cookies.get('csrftoken');\n return fetch(url, {\n credentials: 'include',\n headers: headers,\n method: 'POST',\n body: body\n });\n}\n\nexport function isGitRepoUrl(url, domain) {\n let endOfPattern = '\\\\/[\\\\w\\\\.-]+\\\\/?(?!=.git)(?:\\\\.git(?:\\\\/?|\\\\#[\\\\w\\\\.\\\\-_]+)?)?$';\n let pattern = `(?:git|https?|git@)(?:\\\\:\\\\/\\\\/)?${domain}[/|:][A-Za-z0-9-/]+?` + endOfPattern;\n let re = new RegExp(pattern);\n return re.test(url);\n};\n\nexport function removeUrlFragment() {\n history.replaceState('', document.title, window.location.pathname + window.location.search);\n}\n\nexport function selectText(startNode, endNode) {\n let selection = window.getSelection();\n selection.removeAllRanges();\n let range = document.createRange();\n range.setStart(startNode, 0);\n if (endNode.nodeName !== '#text') {\n range.setEnd(endNode, endNode.childNodes.length);\n } else {\n range.setEnd(endNode, endNode.textContent.length);\n }\n selection.addRange(range);\n}\n\nexport function htmlAlert(type, message, closable = false) {\n let closeButton = '';\n let extraClasses = '';\n if (closable) {\n closeButton =\n `<button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>`;\n extraClasses = 'alert-dismissible';\n }\n return `<div class=\"alert alert-${type} ${extraClasses}\" role=\"alert\">${message}${closeButton}</div>`;\n}\n",
"/**\n * Copyright (C) 2019 The Software Heritage developers\n * See the AUTHORS file at the top-level directory of this distribution\n * License: GNU Affero General Public License version 3, or any later version\n * See top-level LICENSE file for more information\n */\n\nimport {staticAsset} from 'utils/functions';\n\n// Constants defining Bootstrap Breakpoints\nexport const BREAKPOINT_SM = 768;\nexport const BREAKPOINT_MD = 992;\nexport const BREAKPOINT_LG = 1200;\n\nexport const swhSpinnerSrc = staticAsset('img/swh-spinner.gif');\n",
"/*!\n * validate.js 0.13.1\n *\n * (c) 2013-2019 Nicklas Ansman, 2013 Wrapp\n * Validate.js may be freely distributed under the MIT license.\n * For all details and documentation:\n * http://validatejs.org/\n */\n\n(function(exports, module, define) {\n\"use strict\";\n\n // The main function that calls the validators specified by the constraints.\n // The options are the following:\n // - format (string) - An option that controls how the returned value is formatted\n // * flat - Returns a flat array of just the error messages\n // * grouped - Returns the messages grouped by attribute (default)\n // * detailed - Returns an array of the raw validation data\n // - fullMessages (boolean) - If `true` (default) the attribute name is prepended to the error.\n //\n // Please note that the options are also passed to each validator.\n var validate = function(attributes, constraints, options) {\n options = v.extend({}, v.options, options);\n\n var results = v.runValidations(attributes, constraints, options)\n , attr\n , validator;\n\n if (results.some(function(r) { return v.isPromise(r.error); })) {\n throw new Error(\"Use validate.async if you want support for promises\");\n }\n return validate.processValidationResults(results, options);\n };\n\n var v = validate;\n\n // Copies over attributes from one or more sources to a single destination.\n // Very much similar to underscore's extend.\n // The first argument is the target object and the remaining arguments will be\n // used as sources.\n v.extend = function(obj) {\n [].slice.call(arguments, 1).forEach(function(source) {\n for (var attr in source) {\n obj[attr] = source[attr];\n }\n });\n return obj;\n };\n\n v.extend(validate, {\n // This is the version of the library as a semver.\n // The toString function will allow it to be coerced into a string\n version: {\n major: 0,\n minor: 13,\n patch: 1,\n metadata: null,\n toString: function() {\n var version = v.format(\"%{major}.%{minor}.%{patch}\", v.version);\n if (!v.isEmpty(v.version.metadata)) {\n version += \"+\" + v.version.metadata;\n }\n return version;\n }\n },\n\n // Below is the dependencies that are used in validate.js\n\n // The constructor of the Promise implementation.\n // If you are using Q.js, RSVP or any other A+ compatible implementation\n // override this attribute to be the constructor of that promise.\n // Since jQuery promises aren't A+ compatible they won't work.\n Promise: typeof Promise !== \"undefined\" ? Promise : /* istanbul ignore next */ null,\n\n EMPTY_STRING_REGEXP: /^\\s*$/,\n\n // Runs the validators specified by the constraints object.\n // Will return an array of the format:\n // [{attribute: \"<attribute name>\", error: \"<validation result>\"}, ...]\n runValidations: function(attributes, constraints, options) {\n var results = []\n , attr\n , validatorName\n , value\n , validators\n , validator\n , validatorOptions\n , error;\n\n if (v.isDomElement(attributes) || v.isJqueryElement(attributes)) {\n attributes = v.collectFormValues(attributes);\n }\n\n // Loops through each constraints, finds the correct validator and run it.\n for (attr in constraints) {\n value = v.getDeepObjectValue(attributes, attr);\n // This allows the constraints for an attribute to be a function.\n // The function will be called with the value, attribute name, the complete dict of\n // attributes as well as the options and constraints passed in.\n // This is useful when you want to have different\n // validations depending on the attribute value.\n validators = v.result(constraints[attr], value, attributes, attr, options, constraints);\n\n for (validatorName in validators) {\n validator = v.validators[validatorName];\n\n if (!validator) {\n error = v.format(\"Unknown validator %{name}\", {name: validatorName});\n throw new Error(error);\n }\n\n validatorOptions = validators[validatorName];\n // This allows the options to be a function. The function will be\n // called with the value, attribute name, the complete dict of\n // attributes as well as the options and constraints passed in.\n // This is useful when you want to have different\n // validations depending on the attribute value.\n validatorOptions = v.result(validatorOptions, value, attributes, attr, options, constraints);\n if (!validatorOptions) {\n continue;\n }\n results.push({\n attribute: attr,\n value: value,\n validator: validatorName,\n globalOptions: options,\n attributes: attributes,\n options: validatorOptions,\n error: validator.call(validator,\n value,\n validatorOptions,\n attr,\n attributes,\n options)\n });\n }\n }\n\n return results;\n },\n\n // Takes the output from runValidations and converts it to the correct\n // output format.\n processValidationResults: function(errors, options) {\n errors = v.pruneEmptyErrors(errors, options);\n errors = v.expandMultipleErrors(errors, options);\n errors = v.convertErrorMessages(errors, options);\n\n var format = options.format || \"grouped\";\n\n if (typeof v.formatters[format] === 'function') {\n errors = v.formatters[format](errors);\n } else {\n throw new Error(v.format(\"Unknown format %{format}\", options));\n }\n\n return v.isEmpty(errors) ? undefined : errors;\n },\n\n // Runs the validations with support for promises.\n // This function will return a promise that is settled when all the\n // validation promises have been completed.\n // It can be called even if no validations returned a promise.\n async: function(attributes, constraints, options) {\n options = v.extend({}, v.async.options, options);\n\n var WrapErrors = options.wrapErrors || function(errors) {\n return errors;\n };\n\n // Removes unknown attributes\n if (options.cleanAttributes !== false) {\n attributes = v.cleanAttributes(attributes, constraints);\n }\n\n var results = v.runValidations(attributes, constraints, options);\n\n return new v.Promise(function(resolve, reject) {\n v.waitForResults(results).then(function() {\n var errors = v.processValidationResults(results, options);\n if (errors) {\n reject(new WrapErrors(errors, options, attributes, constraints));\n } else {\n resolve(attributes);\n }\n }, function(err) {\n reject(err);\n });\n });\n },\n\n single: function(value, constraints, options) {\n options = v.extend({}, v.single.options, options, {\n format: \"flat\",\n fullMessages: false\n });\n return v({single: value}, {single: constraints}, options);\n },\n\n // Returns a promise that is resolved when all promises in the results array\n // are settled. The promise returned from this function is always resolved,\n // never rejected.\n // This function modifies the input argument, it replaces the promises\n // with the value returned from the promise.\n waitForResults: function(results) {\n // Create a sequence of all the results starting with a resolved promise.\n return results.reduce(function(memo, result) {\n // If this result isn't a promise skip it in the sequence.\n if (!v.isPromise(result.error)) {\n return memo;\n }\n\n return memo.then(function() {\n return result.error.then(function(error) {\n result.error = error || null;\n });\n });\n }, new v.Promise(function(r) { r(); })); // A resolved promise\n },\n\n // If the given argument is a call: function the and: function return the value\n // otherwise just return the value. Additional arguments will be passed as\n // arguments to the function.\n // Example:\n // ```\n // result('foo') // 'foo'\n // result(Math.max, 1, 2) // 2\n // ```\n result: function(value) {\n var args = [].slice.call(arguments, 1);\n if (typeof value === 'function') {\n value = value.apply(null, args);\n }\n return value;\n },\n\n // Checks if the value is a number. This function does not consider NaN a\n // number like many other `isNumber` functions do.\n isNumber: function(value) {\n return typeof value === 'number' && !isNaN(value);\n },\n\n // Returns false if the object is not a function\n isFunction: function(value) {\n return typeof value === 'function';\n },\n\n // A simple check to verify that the value is an integer. Uses `isNumber`\n // and a simple modulo check.\n isInteger: function(value) {\n return v.isNumber(value) && value % 1 === 0;\n },\n\n // Checks if the value is a boolean\n isBoolean: function(value) {\n return typeof value === 'boolean';\n },\n\n // Uses the `Object` function to check if the given argument is an object.\n isObject: function(obj) {\n return obj === Object(obj);\n },\n\n // Simply checks if the object is an instance of a date\n isDate: function(obj) {\n return obj instanceof Date;\n },\n\n // Returns false if the object is `null` of `undefined`\n isDefined: function(obj) {\n return obj !== null && obj !== undefined;\n },\n\n // Checks if the given argument is a promise. Anything with a `then`\n // function is considered a promise.\n isPromise: function(p) {\n return !!p && v.isFunction(p.then);\n },\n\n isJqueryElement: function(o) {\n return o && v.isString(o.jquery);\n },\n\n isDomElement: function(o) {\n if (!o) {\n return false;\n }\n\n if (!o.querySelectorAll || !o.querySelector) {\n return false;\n }\n\n if (v.isObject(document) && o === document) {\n return true;\n }\n\n // http://stackoverflow.com/a/384380/699304\n /* istanbul ignore else */\n if (typeof HTMLElement === \"object\") {\n return o instanceof HTMLElement;\n } else {\n return o &&\n typeof o === \"object\" &&\n o !== null &&\n o.nodeType === 1 &&\n typeof o.nodeName === \"string\";\n }\n },\n\n isEmpty: function(value) {\n var attr;\n\n // Null and undefined are empty\n if (!v.isDefined(value)) {\n return true;\n }\n\n // functions are non empty\n if (v.isFunction(value)) {\n return false;\n }\n\n // Whitespace only strings are empty\n if (v.isString(value)) {\n return v.EMPTY_STRING_REGEXP.test(value);\n }\n\n // For arrays we use the length property\n if (v.isArray(value)) {\n return value.length === 0;\n }\n\n // Dates have no attributes but aren't empty\n if (v.isDate(value)) {\n return false;\n }\n\n // If we find at least one property we consider it non empty\n if (v.isObject(value)) {\n for (attr in value) {\n return false;\n }\n return true;\n }\n\n return false;\n },\n\n // Formats the specified strings with the given values like so:\n // ```\n // format(\"Foo: %{foo}\", {foo: \"bar\"}) // \"Foo bar\"\n // ```\n // If you want to write %{...} without having it replaced simply\n // prefix it with % like this `Foo: %%{foo}` and it will be returned\n // as `\"Foo: %{foo}\"`\n format: v.extend(function(str, vals) {\n if (!v.isString(str)) {\n return str;\n }\n return str.replace(v.format.FORMAT_REGEXP, function(m0, m1, m2) {\n if (m1 === '%') {\n return \"%{\" + m2 + \"}\";\n } else {\n return String(vals[m2]);\n }\n });\n }, {\n // Finds %{key} style patterns in the given string\n FORMAT_REGEXP: /(%?)%\\{([^\\}]+)\\}/g\n }),\n\n // \"Prettifies\" the given string.\n // Prettifying means replacing [.\\_-] with spaces as well as splitting\n // camel case words.\n prettify: function(str) {\n if (v.isNumber(str)) {\n // If there are more than 2 decimals round it to two\n if ((str * 100) % 1 === 0) {\n return \"\" + str;\n } else {\n return parseFloat(Math.round(str * 100) / 100).toFixed(2);\n }\n }\n\n if (v.isArray(str)) {\n return str.map(function(s) { return v.prettify(s); }).join(\", \");\n }\n\n if (v.isObject(str)) {\n if (!v.isDefined(str.toString)) {\n return JSON.stringify(str);\n }\n\n return str.toString();\n }\n\n // Ensure the string is actually a string\n str = \"\" + str;\n\n return str\n // Splits keys separated by periods\n .replace(/([^\\s])\\.([^\\s])/g, '$1 $2')\n // Removes backslashes\n .replace(/\\\\+/g, '')\n // Replaces - and - with space\n .replace(/[_-]/g, ' ')\n // Splits camel cased words\n .replace(/([a-z])([A-Z])/g, function(m0, m1, m2) {\n return \"\" + m1 + \"\" + m2.toLowerCase();\n })\n .toLowerCase();\n },\n\n stringifyValue: function(value, options) {\n var prettify = options && options.prettify || v.prettify;\n return prettify(value);\n },\n\n isString: function(value) {\n return typeof value === 'string';\n },\n\n isArray: function(value) {\n return {}.toString.call(value) === '[object Array]';\n },\n\n // Checks if the object is a hash, which is equivalent to an object that\n // is neither an array nor a function.\n isHash: function(value) {\n return v.isObject(value) && !v.isArray(value) && !v.isFunction(value);\n },\n\n contains: function(obj, value) {\n if (!v.isDefined(obj)) {\n return false;\n }\n if (v.isArray(obj)) {\n return obj.indexOf(value) !== -1;\n }\n return value in obj;\n },\n\n unique: function(array) {\n if (!v.isArray(array)) {\n return array;\n }\n return array.filter(function(el, index, array) {\n return array.indexOf(el) == index;\n });\n },\n\n forEachKeyInKeypath: function(object, keypath, callback) {\n if (!v.isString(keypath)) {\n return undefined;\n }\n\n var key = \"\"\n , i\n , escape = false;\n\n for (i = 0; i < keypath.length; ++i) {\n switch (keypath[i]) {\n case '.':\n if (escape) {\n escape = false;\n key += '.';\n } else {\n object = callback(object, key, false);\n key = \"\";\n }\n break;\n\n case '\\\\':\n if (escape) {\n escape = false;\n key += '\\\\';\n } else {\n escape = true;\n }\n break;\n\n default:\n escape = false;\n key += keypath[i];\n break;\n }\n }\n\n return callback(object, key, true);\n },\n\n getDeepObjectValue: function(obj, keypath) {\n if (!v.isObject(obj)) {\n return undefined;\n }\n\n return v.forEachKeyInKeypath(obj, keypath, function(obj, key) {\n if (v.isObject(obj)) {\n return obj[key];\n }\n });\n },\n\n // This returns an object with all the values of the form.\n // It uses the input name as key and the value as value\n // So for example this:\n // <input type=\"text\" name=\"email\" value=\"foo@bar.com\" />\n // would return:\n // {email: \"foo@bar.com\"}\n collectFormValues: function(form, options) {\n var values = {}\n , i\n , j\n , input\n , inputs\n , option\n , value;\n\n if (v.isJqueryElement(form)) {\n form = form[0];\n }\n\n if (!form) {\n return values;\n }\n\n options = options || {};\n\n inputs = form.querySelectorAll(\"input[name], textarea[name]\");\n for (i = 0; i < inputs.length; ++i) {\n input = inputs.item(i);\n\n if (v.isDefined(input.getAttribute(\"data-ignored\"))) {\n continue;\n }\n\n var name = input.name.replace(/\\./g, \"\\\\\\\\.\");\n value = v.sanitizeFormValue(input.value, options);\n if (input.type === \"number\") {\n value = value ? +value : null;\n } else if (input.type === \"checkbox\") {\n if (input.attributes.value) {\n if (!input.checked) {\n value = values[name] || null;\n }\n } else {\n value = input.checked;\n }\n } else if (input.type === \"radio\") {\n if (!input.checked) {\n value = values[name] || null;\n }\n }\n values[name] = value;\n }\n\n inputs = form.querySelectorAll(\"select[name]\");\n for (i = 0; i < inputs.length; ++i) {\n input = inputs.item(i);\n if (v.isDefined(input.getAttribute(\"data-ignored\"))) {\n continue;\n }\n\n if (input.multiple) {\n value = [];\n for (j in input.options) {\n option = input.options[j];\n if (option && option.selected) {\n value.push(v.sanitizeFormValue(option.value, options));\n }\n }\n } else {\n var _val = typeof input.options[input.selectedIndex] !== 'undefined' ? input.options[input.selectedIndex].value : /* istanbul ignore next */ '';\n value = v.sanitizeFormValue(_val, options);\n }\n values[input.name] = value;\n }\n\n return values;\n },\n\n sanitizeFormValue: function(value, options) {\n if (options.trim && v.isString(value)) {\n value = value.trim();\n }\n\n if (options.nullify !== false && value === \"\") {\n return null;\n }\n return value;\n },\n\n capitalize: function(str) {\n if (!v.isString(str)) {\n return str;\n }\n return str[0].toUpperCase() + str.slice(1);\n },\n\n // Remove all errors who's error attribute is empty (null or undefined)\n pruneEmptyErrors: function(errors) {\n return errors.filter(function(error) {\n return !v.isEmpty(error.error);\n });\n },\n\n // In\n // [{error: [\"err1\", \"err2\"], ...}]\n // Out\n // [{error: \"err1\", ...}, {error: \"err2\", ...}]\n //\n // All attributes in an error with multiple messages are duplicated\n // when expanding the errors.\n expandMultipleErrors: function(errors) {\n var ret = [];\n errors.forEach(function(error) {\n // Removes errors without a message\n if (v.isArray(error.error)) {\n error.error.forEach(function(msg) {\n ret.push(v.extend({}, error, {error: msg}));\n });\n } else {\n ret.push(error);\n }\n });\n return ret;\n },\n\n // Converts the error mesages by prepending the attribute name unless the\n // message is prefixed by ^\n convertErrorMessages: function(errors, options) {\n options = options || {};\n\n var ret = []\n , prettify = options.prettify || v.prettify;\n errors.forEach(function(errorInfo) {\n var error = v.result(errorInfo.error,\n errorInfo.value,\n errorInfo.attribute,\n errorInfo.options,\n errorInfo.attributes,\n errorInfo.globalOptions);\n\n if (!v.isString(error)) {\n ret.push(errorInfo);\n return;\n }\n\n if (error[0] === '^') {\n error = error.slice(1);\n } else if (options.fullMessages !== false) {\n error = v.capitalize(prettify(errorInfo.attribute)) + \"\" + error;\n }\n error = error.replace(/\\\\\\^/g, \"^\");\n error = v.format(error, {\n value: v.stringifyValue(errorInfo.value, options)\n });\n ret.push(v.extend({}, errorInfo, {error: error}));\n });\n return ret;\n },\n\n // In:\n // [{attribute: \"<attributeName>\", ...}]\n // Out:\n // {\"<attributeName>\": [{attribute: \"<attributeName>\", ...}]}\n groupErrorsByAttribute: function(errors) {\n var ret = {};\n errors.forEach(function(error) {\n var list = ret[error.attribute];\n if (list) {\n list.push(error);\n } else {\n ret[error.attribute] = [error];\n }\n });\n return ret;\n },\n\n // In:\n // [{error: \"<message 1>\", ...}, {error: \"<message 2>\", ...}]\n // Out:\n // [\"<message 1>\", \"<message 2>\"]\n flattenErrorsToArray: function(errors) {\n return errors\n .map(function(error) { return error.error; })\n .filter(function(value, index, self) {\n return self.indexOf(value) === index;\n });\n },\n\n cleanAttributes: function(attributes, whitelist) {\n function whitelistCreator(obj, key, last) {\n if (v.isObject(obj[key])) {\n return obj[key];\n }\n return (obj[key] = last ? true : {});\n }\n\n function buildObjectWhitelist(whitelist) {\n var ow = {}\n , lastObject\n , attr;\n for (attr in whitelist) {\n if (!whitelist[attr]) {\n continue;\n }\n v.forEachKeyInKeypath(ow, attr, whitelistCreator);\n }\n return ow;\n }\n\n function cleanRecursive(attributes, whitelist) {\n if (!v.isObject(attributes)) {\n return attributes;\n }\n\n var ret = v.extend({}, attributes)\n , w\n , attribute;\n\n for (attribute in attributes) {\n w = whitelist[attribute];\n\n if (v.isObject(w)) {\n ret[attribute] = cleanRecursive(ret[attribute], w);\n } else if (!w) {\n delete ret[attribute];\n }\n }\n return ret;\n }\n\n if (!v.isObject(whitelist) || !v.isObject(attributes)) {\n return {};\n }\n\n whitelist = buildObjectWhitelist(whitelist);\n return cleanRecursive(attributes, whitelist);\n },\n\n exposeModule: function(validate, root, exports, module, define) {\n if (exports) {\n if (module && module.exports) {\n exports = module.exports = validate;\n }\n exports.validate = validate;\n } else {\n root.validate = validate;\n if (validate.isFunction(define) && define.amd) {\n define([], function () { return validate; });\n }\n }\n },\n\n warn: function(msg) {\n if (typeof console !== \"undefined\" && console.warn) {\n console.warn(\"[validate.js] \" + msg);\n }\n },\n\n error: function(msg) {\n if (typeof console !== \"undefined\" && console.error) {\n console.error(\"[validate.js] \" + msg);\n }\n }\n });\n\n validate.validators = {\n // Presence validates that the value isn't empty\n presence: function(value, options) {\n options = v.extend({}, this.options, options);\n if (options.allowEmpty !== false ? !v.isDefined(value) : v.isEmpty(value)) {\n return options.message || this.message || \"can't be blank\";\n }\n },\n length: function(value, options, attribute) {\n // Empty values are allowed\n if (!v.isDefined(value)) {\n return;\n }\n\n options = v.extend({}, this.options, options);\n\n var is = options.is\n , maximum = options.maximum\n , minimum = options.minimum\n , tokenizer = options.tokenizer || function(val) { return val; }\n , err\n , errors = [];\n\n value = tokenizer(value);\n var length = value.length;\n if(!v.isNumber(length)) {\n return options.message || this.notValid || \"has an incorrect length\";\n }\n\n // Is checks\n if (v.isNumber(is) && length !== is) {\n err = options.wrongLength ||\n this.wrongLength ||\n\"is the wrong length (should be %{count} characters)\";\n errors.push(v.format(err, {count: is}));\n }\n\n if (v.isNumber(minimum) && length < minimum) {\n err = options.tooShort ||\n this.tooShort ||\n\"is too short (minimum is %{count} characters)\";\n errors.push(v.format(err, {count: minimum}));\n }\n\n if (v.isNumber(maximum) && length > maximum) {\n err = options.tooLong ||\n this.tooLong ||\n\"is too long (maximum is %{count} characters)\";\n errors.push(v.format(err, {count: maximum}));\n }\n\n if (errors.length > 0) {\n return options.message || errors;\n }\n },\n numericality: function(value, options, attribute, attributes, globalOptions) {\n // Empty values are fine\n if (!v.isDefined(value)) {\n return;\n }\n\n options = v.extend({}, this.options, options);\n\n var errors = []\n , name\n , count\n , checks = {\n greaterThan: function(v, c) { return v > c; },\n greaterThanOrEqualTo: function(v, c) { return v >= c; },\n equalTo: function(v, c) { return v === c; },\n lessThan: function(v, c) { return v < c; },\n lessThanOrEqualTo: function(v, c) { return v <= c; },\n divisibleBy: function(v, c) { return v % c === 0; }\n }\n , prettify = options.prettify ||\n (globalOptions && globalOptions.prettify) ||\n v.prettify;\n\n // Strict will check that it is a valid looking number\n if (v.isString(value) && options.strict) {\n var pattern = \"^-?(0|[1-9]\\\\d*)\";\n if (!options.onlyInteger) {\n pattern += \"(\\\\.\\\\d+)?\";\n }\n pattern += \"$\";\n\n if (!(new RegExp(pattern).test(value))) {\n return options.message ||\n options.notValid ||\n this.notValid ||\n this.message ||\n\"must be a valid number\";\n }\n }\n\n // Coerce the value to a number unless we're being strict.\n if (options.noStrings !== true && v.isString(value) && !v.isEmpty(value)) {\n value = +value;\n }\n\n // If it's not a number we shouldn't continue since it will compare it.\n if (!v.isNumber(value)) {\n return options.message ||\n options.notValid ||\n this.notValid ||\n this.message ||\n\"is not a number\";\n }\n\n // Same logic as above, sort of. Don't bother with comparisons if this\n // doesn't pass.\n if (options.onlyInteger && !v.isInteger(value)) {\n return options.message ||\n options.notInteger ||\n this.notInteger ||\n this.message ||\n\"must be an integer\";\n }\n\n for (name in checks) {\n count = options[name];\n if (v.isNumber(count) && !checks[name](value, count)) {\n // This picks the default message if specified\n // For example the greaterThan check uses the message from\n // this.notGreaterThan so we capitalize the name and prepend \"not\"\n var key = \"not\" + v.capitalize(name);\n var msg = options[key] ||\n this[key] ||\n this.message ||\n\"must be %{type} %{count}\";\n\n errors.push(v.format(msg, {\n count: count,\n type: prettify(name)\n }));\n }\n }\n\n if (options.odd && value % 2 !== 1) {\n errors.push(options.notOdd ||\n this.notOdd ||\n this.message ||\n\"must be odd\");\n }\n if (options.even && value % 2 !== 0) {\n errors.push(options.notEven ||\n this.notEven ||\n this.message ||\n\"must be even\");\n }\n\n if (errors.length) {\n return options.message || errors;\n }\n },\n datetime: v.extend(function(value, options) {\n if (!v.isFunction(this.parse) || !v.isFunction(this.format)) {\n throw new Error(\"Both the parse and format functions needs to be set to use the datetime/date validator\");\n }\n\n // Empty values are fine\n if (!v.isDefined(value)) {\n return;\n }\n\n options = v.extend({}, this.options, options);\n\n var err\n , errors = []\n , earliest = options.earliest ? this.parse(options.earliest, options) : NaN\n , latest = options.latest ? this.parse(options.latest, options) : NaN;\n\n value = this.parse(value, options);\n\n // 86400000 is the number of milliseconds in a day, this is used to remove\n // the time from the date\n if (isNaN(value) || options.dateOnly && value % 86400000 !== 0) {\n err = options.notValid ||\n options.message ||\n this.notValid ||\n\"must be a valid date\";\n return v.format(err, {value: arguments[0]});\n }\n\n if (!isNaN(earliest) && value < earliest) {\n err = options.tooEarly ||\n options.message ||\n this.tooEarly ||\n\"must be no earlier than %{date}\";\n err = v.format(err, {\n value: this.format(value, options),\n date: this.format(earliest, options)\n });\n errors.push(err);\n }\n\n if (!isNaN(latest) && value > latest) {\n err = options.tooLate ||\n options.message ||\n this.tooLate ||\n\"must be no later than %{date}\";\n err = v.format(err, {\n date: this.format(latest, options),\n value: this.format(value, options)\n });\n errors.push(err);\n }\n\n if (errors.length) {\n return v.unique(errors);\n }\n }, {\n parse: null,\n format: null\n }),\n date: function(value, options) {\n options = v.extend({}, options, {dateOnly: true});\n return v.validators.datetime.call(v.validators.datetime, value, options);\n },\n format: function(value, options) {\n if (v.isString(options) || (options instanceof RegExp)) {\n options = {pattern: options};\n }\n\n options = v.extend({}, this.options, options);\n\n var message = options.message || this.message || \"is invalid\"\n , pattern = options.pattern\n , match;\n\n // Empty values are allowed\n if (!v.isDefined(value)) {\n return;\n }\n if (!v.isString(value)) {\n return message;\n }\n\n if (v.isString(pattern)) {\n pattern = new RegExp(options.pattern, options.flags);\n }\n match = pattern.exec(value);\n if (!match || match[0].length != value.length) {\n return message;\n }\n },\n inclusion: function(value, options) {\n // Empty values are fine\n if (!v.isDefined(value)) {\n return;\n }\n if (v.isArray(options)) {\n options = {within: options};\n }\n options = v.extend({}, this.options, options);\n if (v.contains(options.within, value)) {\n return;\n }\n var message = options.message ||\n this.message ||\n\"^%{value} is not included in the list\";\n return v.format(message, {value: value});\n },\n exclusion: function(value, options) {\n // Empty values are fine\n if (!v.isDefined(value)) {\n return;\n }\n if (v.isArray(options)) {\n options = {within: options};\n }\n options = v.extend({}, this.options, options);\n if (!v.contains(options.within, value)) {\n return;\n }\n var message = options.message || this.message || \"^%{value} is restricted\";\n if (v.isString(options.within[value])) {\n value = options.within[value];\n }\n return v.format(message, {value: value});\n },\n email: v.extend(function(value, options) {\n options = v.extend({}, this.options, options);\n var message = options.message || this.message || \"is not a valid email\";\n // Empty values are fine\n if (!v.isDefined(value)) {\n return;\n }\n if (!v.isString(value)) {\n return message;\n }\n if (!this.PATTERN.exec(value)) {\n return message;\n }\n }, {\n PATTERN: /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$/i\n }),\n equality: function(value, options, attribute, attributes, globalOptions) {\n if (!v.isDefined(value)) {\n return;\n }\n\n if (v.isString(options)) {\n options = {attribute: options};\n }\n options = v.extend({}, this.options, options);\n var message = options.message ||\n this.message ||\n\"is not equal to %{attribute}\";\n\n if (v.isEmpty(options.attribute) || !v.isString(options.attribute)) {\n throw new Error(\"The attribute must be a non empty string\");\n }\n\n var otherValue = v.getDeepObjectValue(attributes, options.attribute)\n , comparator = options.comparator || function(v1, v2) {\n return v1 === v2;\n }\n , prettify = options.prettify ||\n (globalOptions && globalOptions.prettify) ||\n v.prettify;\n\n if (!comparator(value, otherValue, options, attribute, attributes)) {\n return v.format(message, {attribute: prettify(options.attribute)});\n }\n },\n // A URL validator that is used to validate URLs with the ability to\n // restrict schemes and some domains.\n url: function(value, options) {\n if (!v.isDefined(value)) {\n return;\n }\n\n options = v.extend({}, this.options, options);\n\n var message = options.message || this.message || \"is not a valid url\"\n , schemes = options.schemes || this.schemes || ['http', 'https']\n , allowLocal = options.allowLocal || this.allowLocal || false\n , allowDataUrl = options.allowDataUrl || this.allowDataUrl || false;\n if (!v.isString(value)) {\n return message;\n }\n\n // https://gist.github.com/dperini/729294\n var regex =\n\"^\" +\n // protocol identifier\n\"(?:(?:\" + schemes.join(\"|\") + \")://)\" +\n // user:pass authentication\n\"(?:\\\\S+(?::\\\\S*)?@)?\" +\n\"(?:\";\n\n var tld = \"(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,}))\";\n\n if (allowLocal) {\n tld += \"?\";\n } else {\n regex +=\n // IP address exclusion\n // private & local networks\n\"(?!(?:10|127)(?:\\\\.\\\\d{1,3}){3})\" +\n\"(?!(?:169\\\\.254|192\\\\.168)(?:\\\\.\\\\d{1,3}){2})\" +\n\"(?!172\\\\.(?:1[6-9]|2\\\\d|3[0-1])(?:\\\\.\\\\d{1,3}){2})\";\n }\n\n regex +=\n // IP address dotted notation octets\n // excludes loopback network 0.0.0.0\n // excludes reserved space >= 224.0.0.0\n // excludes network & broacast addresses\n // (first & last IP address of each class)\n\"(?:[1-9]\\\\d?|1\\\\d\\\\d|2[01]\\\\d|22[0-3])\" +\n\"(?:\\\\.(?:1?\\\\d{1,2}|2[0-4]\\\\d|25[0-5])){2}\" +\n\"(?:\\\\.(?:[1-9]\\\\d?|1\\\\d\\\\d|2[0-4]\\\\d|25[0-4]))\" +\n\"|\" +\n // host name\n\"(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)\" +\n // domain name\n\"(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*\" +\n tld +\n\")\" +\n // port number\n\"(?::\\\\d{2,5})?\" +\n // resource path\n\"(?:[/?#]\\\\S*)?\" +\n\"$\";\n\n if (allowDataUrl) {\n // RFC 2397\n var mediaType = \"\\\\w+\\\\/[-+.\\\\w]+(?:;[\\\\w=]+)*\";\n var urlchar = \"[A-Za-z0-9-_.!~\\\\*'();\\\\/?:@&=+$,%]*\";\n var dataurl = \"data:(?:\"+mediaType+\")?(?:;base64)?,\"+urlchar;\n regex = \"(?:\"+regex+\")|(?:^\"+dataurl+\"$)\";\n }\n\n var PATTERN = new RegExp(regex, 'i');\n if (!PATTERN.exec(value)) {\n return message;\n }\n },\n type: v.extend(function(value, originalOptions, attribute, attributes, globalOptions) {\n if (v.isString(originalOptions)) {\n originalOptions = {type: originalOptions};\n }\n\n if (!v.isDefined(value)) {\n return;\n }\n\n var options = v.extend({}, this.options, originalOptions);\n\n var type = options.type;\n if (!v.isDefined(type)) {\n throw new Error(\"No type was specified\");\n }\n\n var check;\n if (v.isFunction(type)) {\n check = type;\n } else {\n check = this.types[type];\n }\n\n if (!v.isFunction(check)) {\n throw new Error(\"validate.validators.type.types.\" + type + \" must be a function.\");\n }\n\n if (!check(value, options, attribute, attributes, globalOptions)) {\n var message = originalOptions.message ||\n this.messages[type] ||\n this.message ||\n options.message ||\n (v.isFunction(type) ? \"must be of the correct type\" : \"must be of type %{type}\");\n\n if (v.isFunction(message)) {\n message = message(value, originalOptions, attribute, attributes, globalOptions);\n }\n\n return v.format(message, {attribute: v.prettify(attribute), type: type});\n }\n }, {\n types: {\n object: function(value) {\n return v.isObject(value) && !v.isArray(value);\n },\n array: v.isArray,\n integer: v.isInteger,\n number: v.isNumber,\n string: v.isString,\n date: v.isDate,\n boolean: v.isBoolean\n },\n messages: {}\n })\n };\n\n validate.formatters = {\n detailed: function(errors) {return errors;},\n flat: v.flattenErrorsToArray,\n grouped: function(errors) {\n var attr;\n\n errors = v.groupErrorsByAttribute(errors);\n for (attr in errors) {\n errors[attr] = v.flattenErrorsToArray(errors[attr]);\n }\n return errors;\n },\n constraint: function(errors) {\n var attr;\n errors = v.groupErrorsByAttribute(errors);\n for (attr in errors) {\n errors[attr] = errors[attr].map(function(result) {\n return result.validator;\n }).sort();\n }\n return errors;\n }\n };\n\n validate.exposeModule(validate, this, exports, module, define);\n}).call(this,\n typeof exports !== 'undefined' ? /* istanbul ignore next */ exports : null,\n typeof module !== 'undefined' ? /* istanbul ignore next */ module : null,\n typeof define !== 'undefined' ? /* istanbul ignore next */ define : null);\n",
"/**\n * Copyright (C) 2018-2020 The Software Heritage developers\n * See the AUTHORS file at the top-level directory of this distribution\n * License: GNU Affero General Public License version 3, or any later version\n * See top-level LICENSE file for more information\n */\n\nimport {handleFetchError, csrfPost, isGitRepoUrl, htmlAlert, removeUrlFragment} from 'utils/functions';\nimport {swhSpinnerSrc} from 'utils/constants';\nimport {validate} from 'validate.js';\n\nlet saveRequestsTable;\n\nfunction originSaveRequest(originType, originUrl,\n acceptedCallback, pendingCallback, errorCallback) {\n let addSaveOriginRequestUrl = Urls.origin_save_request(originType, originUrl);\n let headers = {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n };\n $('.swh-processing-save-request').css('display', 'block');\n csrfPost(addSaveOriginRequestUrl, headers)\n .then(handleFetchError)\n .then(response => response.json())\n .then(data => {\n $('.swh-processing-save-request').css('display', 'none');\n if (data.save_request_status === 'accepted') {\n acceptedCallback();\n } else {\n pendingCallback();\n }\n })\n .catch(response => {\n $('.swh-processing-save-request').css('display', 'none');\n response.json().then(errorData => {\n errorCallback(response.status, errorData);\n });\n });\n}\n\nexport function initOriginSave() {\n\n $(document).ready(() => {\n\n $.fn.dataTable.ext.errMode = 'none';\n\n fetch(Urls.origin_save_types_list())\n .then(response => response.json())\n .then(data => {\n for (let originType of data) {\n $('#swh-input-visit-type').append(`<option value=\"${originType}\">${originType}</option>`);\n }\n });\n\n saveRequestsTable = $('#swh-origin-save-requests')\n .on('error.dt', (e, settings, techNote, message) => {\n $('#swh-origin-save-request-list-error').text('An error occurred while retrieving the save requests list');\n console.log(message);\n })\n .DataTable({\n serverSide: true,\n processing: true,\n language: {\n processing: `<img src=\"${swhSpinnerSrc}\"></img>`\n },\n ajax: Urls.origin_save_requests_list('all'),\n searchDelay: 1000,\n columns: [\n {\n data: 'save_request_date',\n name: 'request_date',\n render: (data, type, row) => {\n if (type === 'display') {\n let date = new Date(data);\n return date.toLocaleString();\n }\n return data;\n }\n },\n {\n data: 'visit_type',\n name: 'visit_type'\n },\n {\n data: 'origin_url',\n name: 'origin_url',\n render: (data, type, row) => {\n if (type === 'display') {\n let html = '';\n const sanitizedURL = $.fn.dataTable.render.text().display(data);\n if (row.save_task_status === 'succeed') {\n let browseOriginUrl = `${Urls.browse_origin()}?origin_url=${sanitizedURL}`;\n browseOriginUrl += `&timestamp=${encodeURIComponent(row.visit_date)}`;\n html += `<a href=\"${browseOriginUrl}\">${sanitizedURL}</a>`;\n } else {\n html += sanitizedURL;\n }\n html += ` <a href=\"${sanitizedURL}\"><i class=\"mdi mdi-open-in-new\" aria-hidden=\"true\"></i></a>`;\n return html;\n }\n return data;\n }\n },\n {\n data: 'save_request_status',\n name: 'status'\n },\n {\n data: 'save_task_status',\n name: 'loading_task_status'\n },\n {\n name: 'info',\n render: (data, type, row) => {\n if (row.save_task_status === 'succeed' || row.save_task_status === 'failed') {\n return `<i class=\"mdi mdi-information-outline swh-save-request-info\" ` +\n 'aria-hidden=\"true\" style=\"cursor: pointer\"' +\n `onclick=\"swh.save.displaySaveRequestInfo(event, ${row.id})\"></i>`;\n } else {\n return '';\n }\n }\n },\n {\n render: (data, type, row) => {\n if (row.save_request_status === 'accepted') {\n const saveAgainButton =\n '<button class=\"btn btn-default btn-sm swh-save-origin-again\" type=\"button\" ' +\n `onclick=\"swh.save.fillSaveRequestFormAndScroll(` +\n `'${row.visit_type}', '${row.origin_url}');\">` +\n '<i class=\"mdi mdi-camera mdi-fw\" aria-hidden=\"true\"></i>' +\n 'Save again</button>';\n return saveAgainButton;\n } else {\n return '';\n }\n }\n }\n ],\n scrollY: '50vh',\n scrollCollapse: true,\n order: [[0, 'desc']],\n responsive: {\n details: {\n type: 'none'\n }\n }\n });\n\n swh.webapp.addJumpToPagePopoverToDataTable(saveRequestsTable);\n\n $('#swh-origin-save-requests-list-tab').on('shown.bs.tab', () => {\n saveRequestsTable.draw();\n window.location.hash = '#requests';\n });\n\n $('#swh-origin-save-request-help-tab').on('shown.bs.tab', () => {\n removeUrlFragment();\n $('.swh-save-request-info').popover('dispose');\n });\n\n let saveRequestAcceptedAlert = htmlAlert(\n 'success',\n 'The \"save code now\" request has been accepted and will be processed as soon as possible.',\n true\n );\n\n let saveRequestPendingAlert = htmlAlert(\n 'warning',\n 'The \"save code now\" request has been put in pending state and may be accepted for processing after manual review.',\n true\n );\n\n let saveRequestRateLimitedAlert = htmlAlert(\n 'danger',\n 'The rate limit for \"save code now\" requests has been reached. Please try again later.',\n true\n );\n\n let saveRequestUnknownErrorAlert = htmlAlert(\n 'danger',\n 'An unexpected error happened when submitting the \"save code now request\".',\n true\n );\n\n $('#swh-save-origin-form').submit(event => {\n event.preventDefault();\n event.stopPropagation();\n $('.alert').alert('close');\n if (event.target.checkValidity()) {\n $(event.target).removeClass('was-validated');\n let originType = $('#swh-input-visit-type').val();\n let originUrl = $('#swh-input-origin-url').val();\n\n originSaveRequest(originType, originUrl,\n () => $('#swh-origin-save-request-status').html(saveRequestAcceptedAlert),\n () => $('#swh-origin-save-request-status').html(saveRequestPendingAlert),\n (statusCode, errorData) => {\n $('#swh-origin-save-request-status').css('color', 'red');\n if (statusCode === 403) {\n const errorAlert = htmlAlert('danger', `Error: ${errorData['detail']}`);\n $('#swh-origin-save-request-status').html(errorAlert);\n } else if (statusCode === 429) {\n $('#swh-origin-save-request-status').html(saveRequestRateLimitedAlert);\n } else {\n $('#swh-origin-save-request-status').html(saveRequestUnknownErrorAlert);\n }\n });\n } else {\n $(event.target).addClass('was-validated');\n }\n });\n\n $('#swh-show-origin-save-requests-list').on('click', (event) => {\n event.preventDefault();\n $('.nav-tabs a[href=\"#swh-origin-save-requests-list\"]').tab('show');\n });\n\n $('#swh-input-origin-url').on('input', function(event) {\n let originUrl = $(this).val().trim();\n $(this).val(originUrl);\n $('#swh-input-visit-type option').each(function() {\n let val = $(this).val();\n if (val && originUrl.includes(val)) {\n $(this).prop('selected', true);\n }\n });\n });\n\n if (window.location.hash === '#requests') {\n $('.nav-tabs a[href=\"#swh-origin-save-requests-list\"]').tab('show');\n }\n\n });\n\n}\n\nexport function validateSaveOriginUrl(input) {\n let originUrl = input.value.trim();\n let validUrl = validate({website: originUrl}, {\n website: {\n url: {\n schemes: ['http', 'https', 'svn', 'git']\n }\n }\n }) === undefined;\n let originType = $('#swh-input-visit-type').val();\n if (originType === 'git' && validUrl) {\n // additional checks for well known code hosting providers\n let githubIdx = originUrl.indexOf('://github.com');\n let gitlabIdx = originUrl.indexOf('://gitlab.');\n let gitSfIdx = originUrl.indexOf('://git.code.sf.net');\n let bitbucketIdx = originUrl.indexOf('://bitbucket.org');\n if (githubIdx !== -1 && githubIdx <= 5) {\n validUrl = isGitRepoUrl(originUrl, 'github.com');\n } else if (gitlabIdx !== -1 && gitlabIdx <= 5) {\n let startIdx = gitlabIdx + 3;\n let idx = originUrl.indexOf('/', startIdx);\n if (idx !== -1) {\n let gitlabDomain = originUrl.substr(startIdx, idx - startIdx);\n validUrl = isGitRepoUrl(originUrl, gitlabDomain);\n } else {\n validUrl = false;\n }\n } else if (gitSfIdx !== -1 && gitSfIdx <= 5) {\n validUrl = isGitRepoUrl(originUrl, 'git.code.sf.net/p');\n } else if (bitbucketIdx !== -1 && bitbucketIdx <= 5) {\n validUrl = isGitRepoUrl(originUrl, 'bitbucket.org');\n }\n }\n if (validUrl) {\n input.setCustomValidity('');\n } else {\n input.setCustomValidity('The origin url is not valid or does not reference a code repository');\n }\n}\n\nexport function initTakeNewSnapshot() {\n\n let newSnapshotRequestAcceptedAlert = htmlAlert(\n 'success',\n 'The \"take new snapshot\" request has been accepted and will be processed as soon as possible.',\n true\n );\n\n let newSnapshotRequestPendingAlert = htmlAlert(\n 'warning',\n 'The \"take new snapshot\" request has been put in pending state and may be accepted for processing after manual review.',\n true\n );\n\n let newSnapshotRequestRateLimitAlert = htmlAlert(\n 'danger',\n 'The rate limit for \"take new snapshot\" requests has been reached. Please try again later.',\n true\n );\n\n let newSnapshotRequestUnknownErrorAlert = htmlAlert(\n 'danger',\n 'An unexpected error happened when submitting the \"save code now request\".',\n true\n );\n\n $(document).ready(() => {\n $('#swh-take-new-snapshot-form').submit(event => {\n event.preventDefault();\n event.stopPropagation();\n\n let originType = $('#swh-input-visit-type').val();\n let originUrl = $('#swh-input-origin-url').val();\n\n originSaveRequest(originType, originUrl,\n () => $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestAcceptedAlert),\n () => $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestPendingAlert),\n (statusCode, errorData) => {\n $('#swh-take-new-snapshot-request-status').css('color', 'red');\n if (statusCode === 403) {\n const errorAlert = htmlAlert('danger', `Error: ${errorData['detail']}`, true);\n $('#swh-take-new-snapshot-request-status').html(errorAlert);\n } else if (statusCode === 429) {\n $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestRateLimitAlert);\n } else {\n $('#swh-take-new-snapshot-request-status').html(newSnapshotRequestUnknownErrorAlert);\n }\n });\n });\n });\n}\n\nexport function displaySaveRequestInfo(event, saveRequestId) {\n event.stopPropagation();\n const saveRequestTaskInfoUrl = Urls.origin_save_task_info(saveRequestId);\n // close popover when clicking again on the info icon\n if ($(event.target).data('bs.popover')) {\n $(event.target).popover('dispose');\n return;\n }\n $('.swh-save-request-info').popover('dispose');\n $(event.target).popover({\n animation: false,\n boundary: 'viewport',\n container: 'body',\n title: 'Save request task information ' +\n '<i style=\"cursor: pointer; position: absolute; right: 1rem;\" ' +\n `class=\"mdi mdi-close swh-save-request-info-close\"></i>`,\n content: `<div class=\"swh-popover swh-save-request-info-popover\">\n <div class=\"text-center\">\n <img src=${swhSpinnerSrc}></img>\n <p>Fetching task information ...</p>\n </div>\n </div>`,\n html: true,\n placement: 'left',\n sanitizeFn: swh.webapp.filterXSS\n });\n\n $(event.target).on('shown.bs.popover', function() {\n const popoverId = $(this).attr('aria-describedby');\n $(`#${popoverId} .mdi-close`).click(() => {\n $(this).popover('dispose');\n });\n });\n\n $(event.target).popover('show');\n fetch(saveRequestTaskInfoUrl)\n .then(response => response.json())\n .then(saveRequestTaskInfo => {\n let content;\n if ($.isEmptyObject(saveRequestTaskInfo)) {\n content = 'Not available';\n } else {\n let saveRequestInfo = [];\n if (saveRequestTaskInfo.type) {\n saveRequestInfo.push({\n key: 'Task type',\n value: saveRequestTaskInfo.type\n });\n }\n if (saveRequestTaskInfo.arguments) {\n saveRequestInfo.push({\n key: 'Task arguments',\n value: JSON.stringify(saveRequestTaskInfo.arguments, null, 2)\n });\n }\n if (saveRequestTaskInfo.id) {\n saveRequestInfo.push({\n key: 'Task id',\n value: saveRequestTaskInfo.id\n });\n }\n if (saveRequestTaskInfo.backend_id) {\n saveRequestInfo.push({\n key: 'Task backend id',\n value: saveRequestTaskInfo.backend_id\n });\n }\n if (saveRequestTaskInfo.scheduled) {\n saveRequestInfo.push({\n key: 'Task scheduling date',\n value: new Date(saveRequestTaskInfo.scheduled).toLocaleString()\n });\n }\n if (saveRequestTaskInfo.started) {\n saveRequestInfo.push({\n key: 'Task start date',\n value: new Date(saveRequestTaskInfo.started).toLocaleString()\n });\n }\n if (saveRequestTaskInfo.ended) {\n saveRequestInfo.push({\n key: 'Task termination date',\n value: new Date(saveRequestTaskInfo.ended).toLocaleString()\n });\n }\n if (saveRequestTaskInfo.duration) {\n saveRequestInfo.push({\n key: 'Task duration',\n value: saveRequestTaskInfo.duration + ' seconds'\n });\n }\n if (saveRequestTaskInfo.worker) {\n saveRequestInfo.push({\n key: 'Task executor',\n value: saveRequestTaskInfo.worker\n });\n }\n if (saveRequestTaskInfo.message) {\n saveRequestInfo.push({\n key: 'Task log',\n value: saveRequestTaskInfo.message\n });\n }\n content = '<table class=\"table\"><tbody>';\n for (let info of saveRequestInfo) {\n content +=\n `<tr>\n <th class=\"swh-metadata-table-row swh-metadata-table-key\">${info.key}</th>\n <td class=\"swh-metadata-table-row swh-metadata-table-value\">\n <pre>${info.value}</pre>\n </td>\n </tr>`;\n }\n content += '</tbody></table>';\n }\n $('.swh-popover').html(content);\n $(event.target).popover('update');\n });\n}\n\nexport function fillSaveRequestFormAndScroll(visitType, originUrl) {\n $('#swh-input-origin-url').val(originUrl);\n let originTypeFound = false;\n $('#swh-input-visit-type option').each(function() {\n let val = $(this).val();\n if (val && originUrl.includes(val)) {\n $(this).prop('selected', true);\n originTypeFound = true;\n }\n });\n if (!originTypeFound) {\n $('#swh-input-visit-type option').each(function() {\n let val = $(this).val();\n if (val === visitType) {\n $(this).prop('selected', true);\n }\n });\n }\n window.scrollTo(0, 0);\n}\n"