summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathijs van Veluw <[email protected]>2024-05-25 15:39:36 +0200
committerGitHub <[email protected]>2024-05-25 15:39:36 +0200
commitf05398a6b36891e6e979a39937f8f8eaa6360d52 (patch)
tree60cb2786b60293b4e108c5eb18a8c12b68525e72
parent9555ac7bb84c213221b157448f39a0b3fa521dbc (diff)
downloadvaultwarden-f05398a6b36891e6e979a39937f8f8eaa6360d52.tar.gz
vaultwarden-f05398a6b36891e6e979a39937f8f8eaa6360d52.zip
Update admin interface dependencies (#4581)
- Updated JS/CSS dependencies - Fixed a small issue regarding DNS IP detection fixes #3946 fixes #3947
-rw-r--r--src/api/web.rs2
-rw-r--r--src/static/scripts/admin_diagnostics.js6
-rw-r--r--src/static/scripts/bootstrap.bundle.js17
-rw-r--r--src/static/scripts/bootstrap.css53
-rw-r--r--src/static/scripts/datatables.css18
-rw-r--r--src/static/scripts/datatables.js1079
-rw-r--r--src/static/scripts/jdenticon-3.3.0.js (renamed from src/static/scripts/jdenticon.js)2851
-rw-r--r--src/static/templates/admin/organizations.hbs2
-rw-r--r--src/static/templates/admin/users.hbs2
9 files changed, 1831 insertions, 2199 deletions
diff --git a/src/api/web.rs b/src/api/web.rs
index 67248c83..6983719b 100644
--- a/src/api/web.rs
+++ b/src/api/web.rs
@@ -170,7 +170,7 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro
}
"bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))),
"bootstrap.bundle.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap.bundle.js"))),
- "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
+ "jdenticon-3.3.0.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon-3.3.0.js"))),
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
"jquery-3.7.1.slim.js" => {
diff --git a/src/static/scripts/admin_diagnostics.js b/src/static/scripts/admin_diagnostics.js
index a079c0aa..9f2aca66 100644
--- a/src/static/scripts/admin_diagnostics.js
+++ b/src/static/scripts/admin_diagnostics.js
@@ -21,7 +21,11 @@ const browserUTC = `${year}-${month}-${day} ${hour}:${minute}:${seconds} UTC`;
// ================================
// Check if the output is a valid IP
-const isValidIp = value => (/^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(value) ? true : false);
+function isValidIp(ip) {
+ const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ const ipv6Regex = /^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|((?:[a-fA-F0-9]{1,4}:){1,7}:|:(:[a-fA-F0-9]{1,4}){1,7}|[a-fA-F0-9]{1,4}:((:[a-fA-F0-9]{1,4}){1,6}))$/;
+ return ipv4Regex.test(ip) || ipv6Regex.test(ip);
+}
function checkVersions(platform, installed, latest, commit=null) {
if (installed === "-" || latest === "-") {
diff --git a/src/static/scripts/bootstrap.bundle.js b/src/static/scripts/bootstrap.bundle.js
index 491038c2..6294dff3 100644
--- a/src/static/scripts/bootstrap.bundle.js
+++ b/src/static/scripts/bootstrap.bundle.js
@@ -1,6 +1,6 @@
/*!
- * Bootstrap v5.3.2 (https://getbootstrap.com/)
- * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Bootstrap v5.3.3 (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(function (global, factory) {
@@ -210,7 +210,6 @@
const reflow = element => {
element.offsetHeight; // eslint-disable-line no-unused-expressions
};
-
const getjQuery = () => {
if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
return window.jQuery;
@@ -648,7 +647,7 @@
* Constants
*/
- const VERSION = '5.3.2';
+ const VERSION = '5.3.3';
/**
* Class definition
@@ -729,9 +728,9 @@
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
hrefAttribute = `#${hrefAttribute.split('#')[1]}`;
}
- selector = hrefAttribute && hrefAttribute !== '#' ? parseSelector(hrefAttribute.trim()) : null;
+ selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;
}
- return selector;
+ return selector ? selector.split(',').map(sel => parseSelector(sel)).join(',') : null;
};
const SelectorEngine = {
find(selector, element = document.documentElement) {
@@ -3916,7 +3915,6 @@
// if false, we use the backdrop helper without adding any element to the dom
rootElement: 'body' // give the choice to place backdrop under different elements
};
-
const DefaultType$8 = {
className: 'string',
clickCallback: '(function|null)',
@@ -4041,7 +4039,6 @@
autofocus: true,
trapElement: null // The element to trap focus inside of
};
-
const DefaultType$7 = {
autofocus: 'boolean',
trapElement: 'element'
@@ -4768,7 +4765,10 @@
br: [],
col: [],
code: [],
+ dd: [],
div: [],
+ dl: [],
+ dt: [],
em: [],
hr: [],
h1: [],
@@ -6311,3 +6311,4 @@
return index_umd;
}));
+//# sourceMappingURL=bootstrap.bundle.js.map
diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css
index 32ea6e9c..b7ab57f2 100644
--- a/src/static/scripts/bootstrap.css
+++ b/src/static/scripts/bootstrap.css
@@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
- * Bootstrap v5.3.2 (https://getbootstrap.com/)
- * Copyright 2011-2023 The Bootstrap Authors
+ * Bootstrap v5.3.3 (https://getbootstrap.com/)
+ * Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@@ -3042,6 +3042,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
+.btn-check:checked:focus-visible + .btn {
+ box-shadow: var(--bs-btn-focus-box-shadow);
+}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@@ -4573,12 +4576,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
- --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
- --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
- --bs-accordion-btn-focus-border-color: #86b7fe;
+ --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23052c65' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@@ -4636,7 +4638,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
- border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@@ -4654,7 +4655,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
-.accordion-item:first-of-type .accordion-button {
+.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@@ -4665,11 +4666,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
-.accordion-item:last-of-type .accordion-button.collapsed {
+.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
-.accordion-item:last-of-type .accordion-collapse {
+.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@@ -4678,21 +4679,21 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
-.accordion-flush .accordion-collapse {
- border-width: 0;
-}
-.accordion-flush .accordion-item {
+.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
-.accordion-flush .accordion-item:first-child {
+.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
-.accordion-flush .accordion-item:last-child {
+.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
-.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
+.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
+ border-radius: 0;
+}
+.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
@@ -5578,7 +5579,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
- justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@@ -6144,20 +6144,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
-/* rtl:options: {
- "autoRename": true,
- "stringMap":[ {
- "name" : "prev-next",
- "search" : "prev",
- "replace" : "next"
- } ]
-} */
.carousel-control-prev-icon {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
- background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@@ -6777,14 +6769,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
- justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
- margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
- margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
- margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
+ margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {
@@ -12064,3 +12053,5 @@ textarea.form-control-lg {
display: none !important;
}
}
+
+/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file
diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css
index 4c93a7d5..83e4f44b 100644
--- a/src/static/scripts/datatables.css
+++ b/src/static/scripts/datatables.css
@@ -4,10 +4,10 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
- * https://datatables.net/download/#bs5/dt-2.0.0
+ * https://datatables.net/download/#bs5/dt-2.0.7
*
* Included libraries:
- * DataTables 2.0.0
+ * DataTables 2.0.7
*/
@charset "UTF-8";
@@ -347,7 +347,7 @@ table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
box-shadow: none;
}
table.table.dataTable > :not(caption) > * > * {
- background-color: transparent;
+ background-color: var(--bs-table-bg);
}
table.table.dataTable > tbody > tr {
background-color: transparent;
@@ -463,10 +463,18 @@ div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
justify-content: center !important;
}
}
-table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc {
padding-right: 20px;
}
-table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):before, table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):after {
+table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order,
+table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order {
right: 5px;
}
diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js
index f67d3f28..88d0b627 100644
--- a/src/static/scripts/datatables.js
+++ b/src/static/scripts/datatables.js
@@ -4,20 +4,20 @@
*
* To rebuild or modify this file with the latest versions of the included
* software please visit:
- * https://datatables.net/download/#bs5/dt-2.0.0
+ * https://datatables.net/download/#bs5/dt-2.0.7
*
* Included libraries:
- * DataTables 2.0.0
+ * DataTables 2.0.7
*/
-/*! DataTables 2.0.0
+/*! DataTables 2.0.7
* © SpryMedia Ltd - datatables.net/license
*/
/**
* @summary DataTables
* @description Paginate, search and order HTML tables
- * @version 2.0.0
+ * @version 2.0.7
* @author SpryMedia Ltd
* @contact www.datatables.net
* @copyright SpryMedia Ltd.
@@ -329,7 +329,7 @@
_fnCamelToHungarian( defaults.oLanguage, json );
$.extend( true, oLanguage, json, oSettings.oInit.oLanguage );
- _fnCallbackFire( oSettings, null, 'i18n', [oSettings]);
+ _fnCallbackFire( oSettings, null, 'i18n', [oSettings], true);
_fnInitialise( oSettings );
},
error: function () {
@@ -563,7 +563,7 @@
*
* @type string
*/
- build:"bs5/dt-2.0.0",
+ builder: "bs5/dt-2.0.7",
/**
@@ -1108,7 +1108,8 @@
var _re_dic = {};
var _re_new_lines = /[\r\n\u2028]/g;
- var _re_html = /<.*?>/g;
+ var _re_html = /<([^>]*>)/g;
+ var _max_str_len = Math.pow(2, 28);
// This is not strict ISO8601 - Date.parse() is quite lax, although
// implementations differ between browsers.
@@ -1296,10 +1297,24 @@
};
// Replaceable function in api.util
- var _stripHtml = function ( d ) {
- return d
- .replace( _re_html, '' ) // Complete tags
- .replace(/<script/i, ''); // Safety for incomplete script tag
+ var _stripHtml = function (input) {
+ // Irrelevant check to workaround CodeQL's false positive on the regex
+ if (input.length > _max_str_len) {
+ throw new Error('Exceeded max str len');
+ }
+
+ var previous;
+
+ input = input.replace(_re_html, ''); // Complete tags
+
+ // Safety for incomplete script tag - use do / while to ensure that
+ // we get all instances
+ do {
+ previous = input;
+ input = input.replace(/<script/i, '');
+ } while (input !== previous);
+
+ return previous;
};
// Replaceable function in api.util
@@ -2014,7 +2029,7 @@
"mData": oDefaults.mData ? oDefaults.mData : iCol,
idx: iCol,
searchFixed: {},
- colEl: $('<col>')
+ colEl: $('<col>').attr('data-dt-column', iCol)
} );
oSettings.aoColumns.push( oCol );
@@ -2367,7 +2382,7 @@
*/
function _columnAutoClass(container, colIdx, className) {
container.forEach(function (row) {
- if (row[colIdx].unique) {
+ if (row[colIdx] && row[colIdx].unique) {
_addClass(row[colIdx].cell, className);
}
});
@@ -2441,24 +2456,32 @@
else if ( typeof target === 'string' )
{
for ( k=0, kLen=columns.length ; k<kLen ; k++ ) {
- if (target.indexOf(':name') !== -1) {
+ if (target === '_all') {
+ // Apply to all columns
+ fn( k, def );
+ }
+ else if (target.indexOf(':name') !== -1) {
+ // Column selector
if (columns[k].sName === target.replace(':name', '')) {
fn( k, def );
}
}
else {
+ // Cell selector
headerLayout.forEach(function (row) {
- var cell = $(row[k].cell);
-
- // Legacy support. Note that it means that we don't support
- // an element name selector only, since they are treated as
- // class names for 1.x compat.
- if (target.match(/^[a-z][\w-]*$/i)) {
- target = '.' + target;
- }
-
- if (target === '_all' || cell.is( target )) {
- fn( k, def );
+ if (row[k]) {
+ var cell = $(row[k].cell);
+
+ // Legacy support. Note that it means that we don't support
+ // an element name selector only, since they are treated as
+ // class names for 1.x compat.
+ if (target.match(/^[a-z][\w-]*$/i)) {
+ target = '.' + target;
+ }
+
+ if (cell.is( target )) {
+ fn( k, def );
+ }
}
});
}
@@ -2634,9 +2657,15 @@
type = 'sort';
}
+ var row = settings.aoData[rowIdx];
+
+ if (! row) {
+ return undefined;
+ }
+
var draw = settings.iDraw;
var col = settings.aoColumns[colIdx];
- var rowData = settings.aoData[rowIdx]._aData;
+ var rowData = row._aData;
var defaultContent = col.sDefaultContent;
var cellData = col.fnGetData( rowData, type, {
settings: settings,
@@ -3042,7 +3071,7 @@
for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
{
oCol = oSettings.aoColumns[i];
- create = nTrIn ? false : true;
+ create = nTrIn && anTds[i] ? false : true;
nTd = create ? document.createElement( oCol.sCellType ) : anTds[i];
@@ -3071,11 +3100,11 @@
}
// Visibility - add or remove as required
- if ( oCol.bVisible && ! nTrIn )
+ if ( oCol.bVisible && create )
{
nTr.appendChild( nTd );
}
- else if ( ! oCol.bVisible && nTrIn )
+ else if ( ! oCol.bVisible && ! create )
{
nTd.parentNode.removeChild( nTd );
}
@@ -3159,14 +3188,23 @@
}
// If no cells yet and we have content for them, then create
- if ( $('th, td', target).length === 0 && (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) ) {
- row = $('<tr/>')
- .appendTo( target );
+ if (side === 'header' || _pluck(settings.aoColumns, titleProp).join('')) {
+ row = $('tr', target);
- for ( i=0, ien=columns.length ; i<ien ; i++ ) {
- $('<th/>')
- .html( columns[i][titleProp] || '' )
- .appendTo( row );
+ // Add a row if needed
+ if (! row.length) {
+ row = $('<tr/>').appendTo(target)
+ }
+
+ // Add the number of cells needed to make up to the number of columns
+ if (row.length === 1) {
+ var cells = $('td, th', row);
+
+ for ( i=cells.length, ien=columns.length ; i<ien ; i++ ) {
+ $('<th/>')
+ .html( columns[i][titleProp] || '' )
+ .appendTo( row );
+ }
}
}
@@ -3263,11 +3301,15 @@
colspan++;
}
+ var titleSpan = $('span.dt-column-title', cell);
+
structure[row][column] = {
cell: cell,
colspan: colspan,
rowspan: rowspan,
- title: $('span.dt-column-title', cell).html()
+ title: titleSpan.length
+ ? titleSpan.html()
+ : $(cell).html()
};
}
}
@@ -3408,8 +3450,15 @@
_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
- body.children().detach();
- body.append( $(anRows) );
+ // replaceChildren is faster, but only became widespread in 2020,
+ // so a fall back in jQuery is provided for older browsers.
+ if (body[0].replaceChildren) {
+ body[0].replaceChildren.apply(body[0], anRows);
+ }
+ else {
+ body.children().detach();
+ body.append( $(anRows) );
+ }
// Empty table needs a specific class
$(oSettings.nTableWrapper).toggleClass('dt-empty-footer', $('tr', oSettings.nTFoot).length === 0);
@@ -3474,8 +3523,10 @@
var zero = oLang.sZeroRecords;
var dataSrc = _fnDataSource( settings );
- if ( settings.iDraw <= 1 && (dataSrc === 'ajax' || dataSrc === 'ssp') )
- {
+ if (
+ (settings.iDraw < 1 && dataSrc === 'ssp') ||
+ (settings.iDraw <= 1 && dataSrc === 'ajax')
+ ) {
zero = oLang.sLoadingRecords;
}
else if ( oLang.sEmptyTable && settings.fnRecordsTotal() === 0 )
@@ -3521,30 +3572,34 @@
'full' :
splitPos[1].toLowerCase();
var group = groups[ splitPos[0] ];
-
- // Transform to an object with a contents property
- if ( $.isPlainObject( val ) ) {
- // Already a group from a previous pass
- if (val.contents) {
- group[ align ] = val;
+ var groupRun = function (contents, innerVal) {
+ // If it is an object, then there can be multiple features contained in it
+ if ( $.isPlainObject( innerVal ) ) {
+ Object.keys(innerVal).map(function (key) {
+ contents.push( {
+ feature: key,
+ opts: innerVal[key]
+ });
+ });
}
else {
- // For objects, each property becomes an entry in the contents
- // array for this insert position
- group[ align ] = {
- contents: Object.keys(val).map(function (key) {
- return {
- feature: key,
- opts: val[key]
- };
- })
- };
+ contents.push(innerVal);
+ }
+ }
+
+ // Transform to an object with a contents property
+ if (! group[align] || ! group[align].contents) {
+ group[align] = { contents: [] };
+ }
+
+ // Allow for an array or just a single object
+ if ( Array.isArray(val)) {
+ for (var i=0 ; i<val.length ; i++) {
+ groupRun(group[align].contents, val[i]);
}
}
else {
- group[ align ] = {
- contents: val
- };
+ groupRun(group[ align ].contents, val);
}
// And make contents an array
@@ -3671,15 +3726,15 @@
settings.nTableWrapper = insert[0];
- var top = _layoutArray( settings, settings.layout, 'top' );
- var bottom = _layoutArray( settings, settings.layout, 'bottom' );
- var renderer = _fnRenderer( settings, 'layout' );
-
if (settings.sDom) {
// Legacy
_fnLayoutDom(settings, settings.sDom, insert);
}
else {
+ var top = _layoutArray( settings, settings.layout, 'top' );
+ var bottom = _layoutArray( settings, settings.layout, 'bottom' );
+ var renderer = _fnRenderer( settings, 'layout' );
+
// Everything above - the renderer will actually insert the contents into the document
top.forEach(function (item) {
renderer( settings, insert, item );
@@ -3865,7 +3920,7 @@
}
if (! columnDef.sTitle && unique) {
- columnDef.sTitle = cell.innerHTML.replace( /<.*?>/g, "" );
+ columnDef.sTitle = _stripHtml(cell.innerHTML);
columnDef.autoTitle = true;
}
}
@@ -4046,7 +4101,12 @@
oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
}
else if (ajax.url === '') {
- callback({});
+ // No url, so don't load any data. Just apply an empty data array
+ // to the object for the callback.
+ var empty = {};
+
+ DataTable.util.set(ajax.dataSrc)(empty, []);
+ callback(empty);
}
else {
// Object to extend the base settings
@@ -4374,6 +4434,7 @@
}
var i = 0;
+ var matched = [];
// Search term can be a function, regex or string - if a string we apply our
// smart filtering regex (assuming the options require that)
@@ -4385,18 +4446,22 @@
: _fnFilterCreateSearch( input, options );
// Then for each row, does the test pass. If not, lop the row from the array
- while (i < searchRows.length) {
+ for (i=0 ; i<searchRows.length ; i++) {
var row = settings.aoData[ searchRows[i] ];
var data = column === undefined
? row._sFilterRow
: row._aFilterData[ column ];
- if ( (searchFunc && ! searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && ! rpSearch.test(data)) ) {
- searchRows.splice(i, 1);
- i--;
+ if ( (searchFunc && searchFunc(data, row._aData, searchRows[i], column)) || (rpSearch && rpSearch.test(data)) ) {
+ matched.push(searchRows[i]);
}
+ }
- i++;
+ // Mutate the searchRows array
+ searchRows.length = matched.length;
+
+ for (i=0 ; i<matched.length ; i++) {
+ searchRows[i] = matched[i];
}
}
@@ -4425,6 +4490,9 @@
search = search.toString();
}
+ // Remove diacritics if normalize is set up to do so
+ search = _normalize(search);
+
if (options.exact) {
return new RegExp(
'^'+_fnEscapeRegex(search)+'$',
@@ -4478,7 +4546,7 @@
word = '';
}
- return word.replace('"', '');
+ return word.replace(/"/g, '');
} );
var match = not.length
@@ -4761,6 +4829,7 @@
function _processingHtml ( settings )
{
var table = settings.nTable;
+ var scrolling = settings.oScroll.sX !== '' || settings.oScroll.sY !== '';
if ( settings.oFeatures.bProcessing ) {
var n = $('<div/>', {
@@ -4769,9 +4838,16 @@
'role': 'status'
} )
.html( settings.oLanguage.sProcessing )
- .append('<div><div></div><div></div><div></div><div></div></div>')
- .insertBefore( table );
-
+ .append('<div><div></div><div></div><div></div><div></div></div>');
+
+ // Different positioning depending on if scrolling is enabled or not
+ if (scrolling) {
+ n.prependTo( $('div.dt-scroll', settings.nTableWrapper) );
+ }
+ else {
+ n.insertBefore( table );
+ }
+
$(table).on( 'processing.dt.DT', function (e, s, show) {
n.css( 'display', show ? 'block' : 'none' );
} );
@@ -5010,20 +5086,27 @@
// uses a cell which has a longer string, but isn't the widest! For example
// "Chief Executive Officer (CEO)" is the longest string in the demo, but
// "Systems Administrator" is actually the widest string since it doesn't collapse.
+ // Note the use of translating into a column index to get the `col` element. This
+ // is because of Responsive which might remove `col` elements, knocking the alignment
+ // of the indexes out.
if (settings.aiDisplay.length) {
// Get the column sizes from the first row in the table
- var colSizes = table.find('tbody tr').eq(0).find('th, td').map(function () {
- return $(this).outerWidth();
+ var colSizes = table.children('tbody').eq(0).children('tr').eq(0).children('th, td').map(function (vis) {
+ return {
+ idx: _fnVisibleToColumnIndex(settings, vis),
+ width: $(this).outerWidth()
+ }
});
// Check against what the colgroup > col is set to and correct if needed
- $('col', settings.colgroup).each(function (i) {
- var colWidth = this.style.width.replace('px', '');
+ for (var i=0 ; i<colSizes.length ; i++) {
+ var colEl = settings.aoColumns[ colSizes[i].idx ].colEl[0];
+ var colWidth = colEl.style.width.replace('px', '');
- if (colWidth !== colSizes[i]) {
- this.style.width = colSizes[i] + 'px';
+ if (colWidth !== colSizes[i].width) {
+ colEl.style.width = colSizes[i].width + 'px';
}
- });
+ }
}
// 3. Copy the colgroup over to the header and footer
@@ -5045,12 +5128,12 @@
// the content of the cell so that the width applied to the header and body
// both match, but we want to hide it completely.
$('th, td', headerCopy).each(function () {
- $(this).children().wrapAll('<div class="dt-scroll-sizing">');
+ $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
});
if ( footer ) {
$('th, td', footerCopy).each(function () {
- $(this).children().wrapAll('<div class="dt-scroll-sizing">');
+ $(this.childNodes).wrapAll('<div class="dt-scroll-sizing">');
});
}
@@ -5405,30 +5488,44 @@
function _fnSortAttachListener(settings, node, selector, column, callback) {
_fnBindAction( node, selector, function (e) {
+ var run = false;
var columns = column === undefined
? _fnColumnsFromHeader( e.target )
: [column];
if ( columns.length ) {
- _fnProcessingDisplay( settings, true );
+ for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
+ var ret = _fnSortAdd( settings, columns[i], i, e.shiftKey );
- // Allow the processing display to show
- setTimeout( function () {
- for ( var i=0, ien=columns.length ; i<ien ; i++ ) {
- var append = e.shiftKey || i > 0;
-
- _fnSortAdd( settings, columns[i], append );
+ if (ret !== false) {
+ run = true;
+ }
+
+ // If the first entry is no sort, then subsequent
+ // sort columns are ignored
+ if (settings.aaSorting.length === 1 && settings.aaSorting[0][1] === '') {
+ break;
}
+ }
- _fnSort( settings );
- _fnSortDisplay( settings );
- _fnReDraw( settings, false, false );
- _fnProcessingDisplay( settings, false );
+ if (run) {
+ _fnProcessingDisplay( settings, true );
- if (callback) {
- callback();
- }
- }, 0);
+ // Allow the processing display to show
+ setTimeout( function () {
+ _fnSort( settings );
+ _fnSortDisplay( settings, settings.aiDisplay );
+
+ // Sort processing done - redraw has its own processing display
+ _fnProcessingDisplay( settings, false );
+
+ _fnReDraw( settings, false, false );
+
+ if (callback) {
+ callback();
+ }
+ }, 0);
+ }
}
} );
}
@@ -5437,12 +5534,29 @@
* Sort the display array to match the master's order
* @param {*} settings
*/
- function _fnSortDisplay(settings) {
- var display = settings.aiDisplay;
+ function _fnSortDisplay(settings, display) {
+ if (display.length < 2) {
+ return;
+ }
+
var master = settings.aiDisplayMaster;
+ var masterMap = {};
+ var map = {};
+ var i;
+
+ // Rather than needing an `indexOf` on master array, we can create a map
+ for (i=0 ; i<master.length ; i++) {
+ masterMap[master[i]] = i;
+ }
+
+ // And then cache what would be the indexOf fom the display
+ for (i=0 ; i<display.length ; i++) {
+ map[display[i]] = masterMap[display[i]];
+ }
- display.sort(function(a, b){
- return master.indexOf(a) - master.indexOf(b);
+ display.sort(function(a, b){
+ // Short version of this function is simply `master.indexOf(a) - master.indexOf(b);`
+ return map[a] - map[b];
});
}
@@ -5693,12 +5807,12 @@
* @param {object} settings dataTables settings object
* @param {node} attachTo node to attach the handler to
* @param {int} colIdx column sorting index
- * @param {boolean} [append=false] Append the requested sort to the existing
- * sort if true (i.e. multi-column sort)
+ * @param {int} addIndex Counter
+ * @param {boolean} [shift=false] Shift click add
* @param {function} [callback] callback function
* @memberof DataTable#oApi
*/
- function _fnSortAdd ( settings, colIdx, append )
+ function _fnSortAdd ( settings, colIdx, addIndex, shift )
{
var col = settings.aoColumns[ colIdx ];
var sorting = settings.aaSorting;
@@ -5718,7 +5832,7 @@
};
if ( ! col.bSortable ) {
- return;
+ return false;
}
// Convert to 2D array if needed
@@ -5727,7 +5841,7 @@
}
// If appending the sort then we are multi-column sorting
- if ( append && settings.oFeatures.bSortMulti ) {
+ if ( (shift || addIndex) && settings.oFeatures.bSortMulti ) {
// Are we already doing some kind of sort on this column?
var sortIdx = _pluck(sorting, '0').indexOf(colIdx);
@@ -5747,11 +5861,18 @@
sorting[sortIdx]._idx = nextSortIdx;
}
}
- else {
- // No sort on this column yet
+ else if (shift) {
+ // No sort on this column yet, being added by shift click
+ // add it as itself
sorting.push( [ colIdx, asSorting[0], 0 ] );
sorting[sorting.length-1]._idx = 0;
}
+ else {
+ // No sort on this column yet, being added from a colspan
+ // so add with same direction as first column
+ sorting.push( [ colIdx, sorting[0][1], 0 ] );
+ sorting[sorting.length-1]._idx = 0;
+ }
}
else if ( sorting.length && sorting[0][0] == colIdx ) {
// Single column - already sorting on this column, modify the sort
@@ -6785,6 +6906,10 @@
for ( i=0, ien=ext.length ; i<ien ; i++ ) {
struct = ext[i];
+ if (struct.name === '__proto__') {
+ continue;
+ }
+
// Value
obj[ struct.name ] = struct.type === 'function' ?
_api_scope( scope, struct.val, struct ) :
@@ -7447,16 +7572,7 @@
order = opts.order, // applied, current, index (original - compatibility with 1.9)
page = opts.page; // all, current
- if ( _fnDataSource( settings ) == 'ssp' ) {
- // In server-side processing mode, most options are irrelevant since
- // rows not shown don't exist and the index order is the applied order
- // Removed is a special case - for consistency just return an empty
- // array
- return search === 'removed' ?
- [] :
- _range( 0, displayMaster.length );
- }
- else if ( page == 'current' ) {
+ if ( page == 'current' ) {
// Current page implies that order=current and filter=applied, since it is
// fairly senseless otherwise, regardless of what order and search actually
// are
@@ -7638,11 +7754,7 @@
var matched = _selector_run( 'row', selector, run, settings, opts );
if (opts.order === 'current' || opts.order === 'applied') {
- var master = settings.aiDisplayMaster;
-
- matched.sort(function(a, b) {
- return master.indexOf(a) - master.indexOf(b);
- });
+ _fnSortDisplay(settings, matched);
}
return matched;
@@ -7729,11 +7841,6 @@
settings.aiDisplayMaster.splice(idx, 1);
}
- idx = settings.aiDisplay.indexOf(row);
- if (idx !== -1) {
- settings.aiDisplay.splice(idx, 1);
- }
-
// For server-side processing tables - subtract the deleted row from the count
if ( settings._iRecordsDisplay > 0 ) {
settings._iRecordsDisplay--;
@@ -7823,9 +7930,15 @@
_api_register( 'row().node()', function () {
var ctx = this.context;
- return ctx.length && this.length && this[0].length ?
- ctx[0].aoData[ this[0] ].nTr || null :
- null;
+ if (ctx.length && this.length && this[0].length) {
+ var row = ctx[0].aoData[ this[0] ];
+
+ if (row && row.nTr) {
+ return row.nTr;
+ }
+ }
+
+ return null;
} );
@@ -7883,8 +7996,10 @@
{
if ( state && state.childRows ) {
api
- .rows( state.childRows.map(function (id){
- return id.replace(/:/g, '\\:')
+ .rows( state.childRows.map(function (id) {
+ // Escape any `:` characters from the row id. Accounts for
+ // already escaped characters.
+ return id.replace(/([^:\\]*(?:\\.[^:\\]*)*):/g, "$1\\:");
}) )
.every( function () {
_fnCallbackFire( api.settings()[0], null, 'requestChild', [ this ] )
@@ -8036,7 +8151,7 @@
for ( var i=0, ien=data.length ; i<ien ; i++ ) {
row = data[i];
- if ( row._details ) {
+ if ( row && row._details ) {
row._details.each(function () {
var el = $(this).children('td');
@@ -8055,7 +8170,7 @@
}
for ( var i=0, ien=data.length ; i<ien ; i++ ) {
- if ( data[i]._details ) {
+ if ( data[i] && data[i]._details ) {
__details_remove( api, i );
}
}
@@ -8077,9 +8192,9 @@
if ( data === undefined ) {
// get
- return ctx.length && this.length ?
- ctx[0].aoData[ this[0] ]._details :
- undefined;
+ return ctx.length && this.length && ctx[0].aoData[ this[0] ]
+ ? ctx[0].aoData[ this[0] ]._details
+ : undefined;
}
else if ( data === true ) {
// show
@@ -8165,6 +8280,17 @@
};
+ var __column_header = function ( settings, column, row ) {
+ var header = settings.aoHeader;
+ var target = row !== undefined
+ ? row
+ : settings.bSortCellsTop // legacy support
+ ? 0
+ : header.length - 1;
+
+ return header[target][column].cell;
+ };
+
var __column_selector = function ( settings, selector, opts )
{
var
@@ -8197,7 +8323,8 @@
return columns.map(function (col, idx) {
return s(
idx,
- __columnData( settings, idx, 0, 0, rows )
+ __columnData( settings, idx, 0, 0, rows ),
+ __column_header( settings, idx )
) ? idx : null;
});
}
@@ -8342,15 +8469,8 @@
} );
_api_registerPlural( 'columns().header()', 'column().header()', function ( row ) {
- return this.iterator( 'column', function ( settings, column ) {
- var header = settings.aoHeader;
- var target = row !== undefined
- ? row
- : settings.bSortCellsTop // legacy support
- ? 0
- : header.length - 1;
-
- return header[target][column].cell;
+ return this.iterator( 'column', function (settings, column) {
+ return __column_header(settings, column, row);
}, 1 );
} );
@@ -9402,6 +9522,8 @@
jqTable.append( tfoot );
}
+ settings.colgroup.remove();
+
settings.aaSorting = [];
settings.aaSortingFixed = [];
_fnSortingClasses( settings );
@@ -9501,7 +9623,7 @@
* @type string
* @default Version number
*/
- DataTable.version = "2.0.0";
+ DataTable.version = "2.0.7";
/**
* Private data store, containing all of the settings objects that are
@@ -10356,24 +10478,24 @@
*/
"oPaginate": {
/**
- * Label and character for first page button
+ * Label and character for first page button («)
*/
- "sFirst": "«",
+ "sFirst": "\u00AB",
/**
- * Last page button
+ * Last page button (»)
*/
- "sLast": "»",
+ "sLast": "\u00BB",
/**
- * Next page button
+ * Next page button (›)
*/
- "sNext": "›",
+ "sNext": "\u203A",
/**
- * Previous page button
+ * Previous page button (‹)
*/
- "sPrevious": "‹",
+ "sPrevious": "\u2039",
},
/**
@@ -11602,568 +11724,6 @@
*/
- /**
- * DataTables extensions
- *
- * This namespace acts as a collection area for plug-ins that can be used to
- * extend DataTables capabilities. Indeed many of the build in methods
- * use this method to provide their own capabilities (sorting methods for
- * example).
- *
- * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
- * reasons
- *
- * @namespace
- */
- DataTable.ext = _ext = {
- /**
- * Buttons. For use with the Buttons extension for DataTables. This is
- * defined here so other extensions can define buttons regardless of load
- * order. It is _not_ used by DataTables core.
- *
- * @type object
- * @default {}
- */
- buttons: {},
-
-
- /**
- * Element class names
- *
- * @type object
- * @default {}
- */
- classes: {},
-
-
- /**
- * DataTables build type (expanded by the download builder)
- *
- * @type string
- */
- build:"bs5/dt-2.0.0",
-
-
- /**
- * Error reporting.
- *
- * How should DataTables report an error. Can take the value 'alert',
- * 'throw', 'none' or a function.
- *
- * @type string|function
- * @default alert
- */
- errMode: "alert",
-
-
- /**
- * Legacy so v1 plug-ins don't throw js errors on load
- */
- feature: [],
-
- /**
- * Feature plug-ins.
- *
- * This is an object of callbacks which provide the features for DataTables
- * to be initialised via the `layout` option.
- */
- features: {},
-
-
- /**
- * Row searching.
- *
- * This method of searching is complimentary to the default type based
- * searching, and a lot more comprehensive as it allows you complete control
- * over the searching logic. Each element in this array is a function
- * (parameters described below) that is called for every row in the table,
- * and your logic decides if it should be included in the searching data set
- * or not.
- *
- * Searching functions have the following input parameters:
- *
- * 1. `{object}` DataTables settings object: see
- * {@link DataTable.models.oSettings}
- * 2. `{array|object}` Data for the row to be processed (same as the
- * original format that was passed in as the data source, or an array
- * from a DOM data source
- * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
- * can be useful to retrieve the `TR` element if you need DOM interaction.
- *
- * And the following return is expected:
- *
- * * {boolean} Include the row in the searched result set (true) or not
- * (false)
- *
- * Note that as with the main search ability in DataTables, technically this
- * is "filtering", since it is subtractive. However, for consistency in
- * naming we call it searching here.
- *
- * @type array
- * @default []
- *
- * @example
- * // The following example shows custom search being applied to the
- * // fourth column (i.e. the data[3] index) based on two input values
- * // from the end-user, matching the data in a certain range.
- * $.fn.dataTable.ext.search.push(
- * function( settings, data, dataIndex ) {
- * var min = document.getElementById('min').value * 1;
- * var max = document.getElementById('max').value * 1;
- * var version = data[3] == "-" ? 0 : data[3]*1;
- *
- * if ( min == "" && max == "" ) {
- * return true;
- * }
- * else if ( min == "" && version < max ) {
- * return true;
- * }
- * else if ( min < version && "" == max ) {
- * return true;
- * }
- * else if ( min < version && version < max ) {
- * return true;
- * }
- * return false;
- * }
- * );
- */
- search: [],
-
-
- /**
- * Selector extensions
- *
- * The `selector` option can be used to extend the options available for the
- * selector modifier options (`selector-modifier` object data type) that
- * each of the three built in selector types offer (row, column and cell +
- * their plural counterparts). For example the Select extension uses this
- * mechanism to provide an option to select only rows, columns and cells
- * that have been marked as selected by the end user (`{selected: true}`),
- * which can be used in conjunction with the existing built in selector
- * options.
- *
- * Each property is an array to which functions can be pushed. The functions
- * take three attributes:
- *
- * * Settings object for the host table
- * * Options object (`selector-modifier` object type)
- * * Array of selected item indexes
- *
- * The return is an array of the resulting item indexes after the custom
- * selector has been applied.
- *
- * @type object
- */
- selector: {
- cell: [],
- column: [],
- row: []
- },
-
-
- /**
- * Legacy configuration options. Enable and disable legacy options that
- * are available in DataTables.
- *
- * @type object
- */
- legacy: {
- /**
- * Enable / disable DataTables 1.9 compatible server-side processing
- * requests
- *
- * @type boolean
- * @default null
- */
- ajax: null
- },
-
-
- /**
- * Pagination plug-in methods.
- *
- * Each entry in this object is a function and defines which buttons should
- * be shown by the pagination rendering method that is used for the table:
- * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
- * buttons are displayed in the document, while the functions here tell it
- * what buttons to display. This is done by returning an array of button
- * descriptions (what each button will do).
- *
- * Pagination types (the four built in options and any additional plug-in
- * options defined here) can be used through the `paginationType`
- * initialisation parameter.
- *
- * The functions defined take two parameters:
- *
- * 1. `{int} page` The current page index
- * 2. `{int} pages` The number of pages in the table
- *
- * Each function is expected to return an array where each element of the
- * array can be one of:
- *
- * * `first` - Jump to first page when activated
- * * `last` - Jump to last page when activated
- * * `previous` - Show previous page when activated
- * * `next` - Show next page when activated
- * * `{int}` - Show page of the index given
- * * `{array}` - A nested array containing the above elements to add a
- * containing 'DIV' element (might be useful for styling).
- *
- * Note that DataTables v1.9- used this object slightly differently whereby
- * an object with two functions would be defined for each plug-in. That
- * ability is still supported by DataTables 1.10+ to provide backwards
- * compatibility, but this option of use is now decremented and no longer
- * documented in DataTables 1.10+.
- *
- * @type object
- * @default {}
- *
- * @example
- * // Show previous, next and current page buttons only
- * $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
- * return [ 'previous', page, 'next' ];
- * };
- */
- pager: {},
-
-
- renderer: {
- pageButton: {},
- header: {}
- },
-
-
- /**
- * Ordering plug-ins - custom data source
- *
- * The extension options for ordering of data available here is complimentary
- * to the default type based ordering that DataTables typically uses. It
- * allows much greater control over the the data that is being used to
- * order a column, but is necessarily therefore more complex.
- *
- * This type of ordering is useful if you want to do ordering based on data
- * live from the DOM (for example the contents of an 'input' element) rather
- * than just the static string that DataTables knows of.
- *
- * The way these plug-ins work is that you create an array of the values you
- * wish to be ordering for the column in question and then return that
- * array. The data in the array much be in the index order of the rows in
- * the table (not the currently ordering order!). Which order data gathering
- * function is run here depends on the `dt-init columns.orderDataType`
- * parameter that is used for the column (if any).
- *
- * The functions defined take two parameters:
- *
- * 1. `{object}` DataTables settings object: see
- * {@link DataTable.models.oSettings}
- * 2. `{int}` Target column index
- *
- * Each function is expected to return an array:
- *
- * * `{array}` Data for the column to be ordering upon
- *
- * @type array
- *
- * @example
- * // Ordering using `input` node values
- * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col )
- * {
- * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
- * return $('input', td).val();
- * } );
- * }
- */
- order: {},
-
-
- /**
- * Type based plug-ins.
- *
- * Each column in DataTables has a type assigned to it, either by automatic
- * detection or by direct assignment using the `type` option for the column.
- * The type of a column will effect how it is ordering and search (plug-ins
- * can also make use of the column type if required).
- *
- * @namespace
- */
- type: {
- /**
- * Automatic column class assignment
- */
- className: {},
-
- /**
- * Type detection functions.
- *
- * The functions defined in this object are used to automatically detect
- * a column's type, making initialisation of DataTables super easy, even
- * when complex data is in the table.
- *
- * The functions defined take two parameters:
- *
- * 1. `{*}` Data from the column cell to be analysed
- * 2. `{settings}` DataTables settings object. This can be used to
- * perform context specific type detection - for example detection
- * based on language settings such as using a comma for a decimal
- * place. Generally speaking the options from the settings will not
- * be required
- *
- * Each function is expected to return:
- *
- * * `{string|null}` Data type detected, or null if unknown (and thus
- * pass it on to the other type detection functions.
- *
- * @type array
- *
- * @example
- * // Currency type detection plug-in:
- * $.fn.dataTable.ext.type.detect.push(
- * function ( data, settings ) {
- * // Check the numeric part
- * if ( ! data.substring(1).match(/[0-9]/) ) {
- * return null;
- * }
- *
- * // Check prefixed by currency
- * if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
- * return 'currency';
- * }
- * return null;
- * }
- * );
- */
- detect: [],
-
- /**
- * Automatic renderer assignment
- */
- render: {},
-
-
- /**
- * Type based search formatting.
- *
- * The type based searching functions can be used to pre-format the
- * data to be search on. For example, it can be used to strip HTML
- * tags or to de-format telephone numbers for numeric only searching.
- *
- * Note that is a search is not defined for a column of a given type,
- * no search formatting will be performed.
- *
- * Pre-processing of searching data plug-ins - When you assign the sType
- * for a column (or have it automatically detected for you by DataTables
- * or a type detection plug-in), you will typically be using this for
- * custom sorting, but it can also be used to provide custom searching
- * by allowing you to pre-processing the data and returning the data in
- * the format that should be searched upon. This is done by adding
- * functions this object with a parameter name which matches the sType
- * for that target column. This is the corollary of <i>afnSortData</i>
- * for searching data.
- *
- * The functions defined take a single parameter:
- *
- * 1. `{*}` Data from the column cell to be prepared for searching
- *
- * Each function is expected to return:
- *
- * * `{string|null}` Formatted string that will be used for the searching.
- *
- * @type object
- * @default {}
- *
- * @example
- * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
- * return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
- * }
- */
- search: {},
-
-
- /**
- * Type based ordering.
- *
- * The column type tells DataTables what ordering to apply to the table
- * when a column is sorted upon. The order for each type that is defined,
- * is defined by the functions available in this object.
- *
- * Each ordering option can be described by three properties added to
- * this object:
- *
- * * `{type}-pre` - Pre-formatting function
- * * `{type}-asc` - Ascending order function
- * * `{type}-desc` - Descending order function
- *
- * All three can be used together, only `{type}-pre` or only
- * `{type}-asc` and `{type}-desc` together. It is generally recommended
- * that only `{type}-pre` is used, as this provides the optimal
- * implementation in terms of speed, although the others are provided
- * for compatibility with existing Javascript sort functions.
- *
- * `{type}-pre`: Functions defined take a single parameter:
- *
- * 1. `{*}` Data from the column cell to be prepared for ordering
- *
- * And return:
- *
- * * `{*}` Data to be sorted upon
- *
- * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
- * functions, taking two parameters:
- *
- * 1. `{*}` Data to compare to the second parameter
- * 2. `{*}` Data to compare to the first parameter
- *
- * And returning:
- *
- * * `{*}` Ordering match: <0 if first parameter should be sorted lower
- * than the second parameter, ===0 if the two parameters are equal and
- * >0 if the first parameter should be sorted height than the second
- * parameter.
- *
- * @type object
- * @default {}
- *
- * @example
- * // Numeric ordering of formatted numbers with a pre-formatter
- * $.extend( $.fn.dataTable.ext.type.order, {
- * "string-pre": function(x) {
- * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
- * return parseFloat( a );
- * }
- * } );
- *
- * @example
- * // Case-sensitive string ordering, with no pre-formatting method
- * $.extend( $.fn.dataTable.ext.order, {
- * "string-case-asc": function(x,y) {
- * return ((x < y) ? -1 : ((x > y) ? 1 : 0));
- * },
- * "string-case-desc": function(x,y) {
- * return ((x < y) ? 1 : ((x > y) ? -1 : 0));
- * }
- * } );
- */
- order: {}
- },
-
- /**
- * Unique DataTables instance counter
- *
- * @type int
- * @private
- */
- _unique: 0,
-
-
- //
- // Depreciated
- // The following properties are retained for backwards compatibility only.
- // The should not be used in new projects and will be removed in a future
- // version
- //
-
- /**
- * Version check function.
- * @type function
- * @depreciated Since 1.10
- */
- fnVersionCheck: DataTable.fnVersionCheck,
-
-
- /**
- * Index for what 'this' index API functions should use
- * @type int
- * @deprecated Since v1.10
- */
- iApiIndex: 0,
-
-
- /**
- * Software version
- * @type string
- * @deprecated Since v1.10
- */
- sVersion: DataTable.version
- };
-
-
- //
- // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
- //
- $.extend( _ext, {
- afnFiltering: _ext.search,
- aTypes: _ext.type.detect,
- ofnSearch: _ext.type.search,
- oSort: _ext.type.order,
- afnSortData: _ext.order,
- aoFeatures: _ext.feature,
- oStdClasses: _ext.classes,
- oPagination: _ext.pager
- } );
-
-
- $.extend( DataTable.ext.classes, {
- container: 'dt-container',
- empty: {
- row: 'dt-empty'
- },
- info: {
- container: 'dt-info'
- },
- length: {
- container: 'dt-length',
- select: 'dt-input'
- },
- order: {
- canAsc: 'dt-orderable-asc',
- canDesc: 'dt-orderable-desc',
- isAsc: 'dt-ordering-asc',
- isDesc: 'dt-ordering-desc',
- none: 'dt-orderable-none',
- position: 'sorting_'
- },
- processing: {
- container: 'dt-processing'
- },
- scrolling: {
- body: 'dt-scroll-body',
- container: 'dt-scroll',
- footer: {
- self: 'dt-scroll-foot',
- inner: 'dt-scroll-footInner'
- },
- header: {
- self: 'dt-scroll-head',
- inner: 'dt-scroll-headInner'
- }
- },
- search: {
- container: 'dt-search',
- input: 'dt-input'
- },
- table: 'dataTable',
- tbody: {
- cell: '',
- row: ''
- },
- thead: {
- cell: '',
- row: ''
- },
- tfoot: {
- cell: '',
- row: ''
- },
- paging: {
- active: 'current',
- button: 'dt-paging-button',
- container: 'dt-paging',
- disabled: 'disabled'
- }
- } );
-
-
var extPagination = DataTable.ext.pager;
// Paging buttons configuration
@@ -12246,7 +11806,7 @@
} );
// Common function to remove new lines, strip HTML and diacritic control
- var _filterString = function (stripHtml, diacritics) {
+ var _filterString = function (stripHtml, normalize) {
return function (str) {
if (_empty(str) || typeof str !== 'string') {
return str;
@@ -12258,8 +11818,8 @@
str = _stripHtml(str);
}
- if (diacritics) {
- str = _normalize(str, true);
+ if (normalize) {
+ str = _normalize(str, false);
}
return str;
@@ -12637,7 +12197,7 @@
};
// prop is optional
- if (! val) {
+ if (val === undefined) {
val = prop;
prop = null;
}
@@ -12903,9 +12463,9 @@
var ariaType = '';
var indexes = columns.indexes();
var sortDirs = columns.orderable(true).flatten();
- var orderedColumns = sorting.map( function (val) {
+ var orderedColumns = ',' + sorting.map( function (val) {
return val.col;
- } ).join(',');
+ } ).join(',') + ',';
cell
.removeClass(
@@ -12916,7 +12476,7 @@
.toggleClass( orderClasses.canAsc, orderable && sortDirs.includes('asc') )
.toggleClass( orderClasses.canDesc, orderable && sortDirs.includes('desc') );
- var sortIdx = orderedColumns.indexOf( indexes.toArray().join(',') );
+ var sortIdx = orderedColumns.indexOf( ',' + indexes.toArray().join(',') + ',' );
if ( sortIdx !== -1 ) {
// Get the ordering direction for the columns under this cell
@@ -12931,7 +12491,7 @@
}
// The ARIA spec says that only one column should be marked with aria-sort
- if ( sortIdx === 0 && orderedColumns.length === indexes.count() ) {
+ if ( sortIdx === 0 ) {
var firstSort = sorting[0];
var sortOrder = col.asSorting;
@@ -13027,7 +12587,7 @@
});
// For the first info display in the table, we add a callback and aria information.
- if (! $('#' + tid+'_info', settings.nWrapper).length) {
+ if (! settings._infoEl) {
n.attr({
'aria-live': 'polite',
id: tid+'_info',
@@ -13036,6 +12596,8 @@
// Table is described by our info div
$(settings.nTable).attr( 'aria-describedby', tid+'_info' );
+
+ settings._infoEl = n;
}
return n;
@@ -13193,7 +12755,7 @@
// opts
// - type - button configuration
- // - numbers - number of buttons to show - must be odd
+ // - buttons - number of buttons to show - must be odd
DataTable.feature.register( 'paging', function ( settings, opts ) {
// Don't show the paging input if the table doesn't have paging enabled
if (! settings.oFeatures.bPaginate) {
@@ -13201,9 +12763,15 @@
}
opts = $.extend({
- numbers: DataTable.ext.pager.numbers_length,
- type: settings.sPaginationType
- }, opts)
+ buttons: DataTable.ext.pager.numbers_length,
+ type: settings.sPaginationType,
+ boundaryNumbers: true
+ }, opts);
+
+ // To be removed in 2.1
+ if (opts.numbers) {
+ opts.buttons = opts.numbers;
+ }
var host = $('<div/>').addClass( settings.oClasses.paging.container + ' paging_' + opts.type );
var draw = function () {
@@ -13235,7 +12803,7 @@
buttons = plugin()
.map(function (val) {
return val === 'numbers'
- ? _pagingNumbers(page, pages, opts.numbers)
+ ? _pagingNumbers(page, pages, opts.buttons, opts.boundaryNumbers)
: val;
})
.flat();
@@ -13296,7 +12864,7 @@
if (
buttonEls.length && // any buttons
opts.numbers > 1 && // prevent infinite
- $(host).outerHeight() >= ($(buttonEls[0]).outerHeight() * 2) - 10
+ $(host).height() >= ($(buttonEls[0]).outerHeight() * 2) - 10
) {
_pagingDraw(settings, host, $.extend({}, opts, { numbers: opts.numbers - 2 }));
}
@@ -13377,12 +12945,15 @@
* @param {*} page Current page
* @param {*} pages Total number of pages
* @param {*} buttons Target number of number buttons
+ * @param {boolean} addFirstLast Indicate if page 1 and end should be included
* @returns Buttons to show
*/
- function _pagingNumbers ( page, pages, buttons ) {
+ function _pagingNumbers ( page, pages, buttons, addFirstLast ) {
var
numbers = [],
- half = Math.floor(buttons / 2);
+ half = Math.floor(buttons / 2),
+ before = addFirstLast ? 2 : 1,
+ after = addFirstLast ? 1 : 0;
if ( pages <= buttons ) {
numbers = _range(0, pages);
@@ -13405,17 +12976,30 @@
}
}
else if ( page <= half ) {
- numbers = _range(0, buttons-2);
- numbers.push('ellipsis', pages-1);
+ numbers = _range(0, buttons-before);
+ numbers.push('ellipsis');
+
+ if (addFirstLast) {
+ numbers.push(pages-1);
+ }
}
else if ( page >= pages - 1 - half ) {
- numbers = _range(pages-(buttons-2), pages);
- numbers.unshift(0, 'ellipsis');
+ numbers = _range(pages-(buttons-before), pages);
+ numbers.unshift('ellipsis');
+
+ if (addFirstLast) {
+ numbers.unshift(0);
+ }
}
else {
- numbers = _range(page-half+2, page+half-1);
- numbers.push('ellipsis', pages-1);
- numbers.unshift(0, 'ellipsis');
+ numbers = _range(page-half+before, page+half-after);
+ numbers.push('ellipsis');
+ numbers.unshift('ellipsis');
+
+ if (addFirstLast) {
+ numbers.push(pages-1);
+ numbers.unshift(0);
+ }
}
return numbers;
@@ -13583,7 +13167,7 @@
/*! DataTables Bootstrap 5 integration
- * 2020 SpryMedia Ltd - datatables.net/license
+ * © SpryMedia Ltd - datatables.net/license
*/
(function( factory ){
@@ -13634,8 +13218,7 @@ var DataTable = $.fn.dataTable;
/**
- * DataTables integration for Bootstrap 5. This requires Bootstrap 5 and
- * DataTables 2 or newer.
+ * DataTables integration for Bootstrap 5.
*
* This file sets the defaults and adds options to DataTables to style its
* controls using Bootstrap. See https://datatables.net/manual/styling/bootstrap
diff --git a/src/static/scripts/jdenticon.js b/src/static/scripts/jdenticon-3.3.0.js
index 1144d379..b862ac1e 100644
--- a/src/static/scripts/jdenticon.js
+++ b/src/static/scripts/jdenticon-3.3.0.js
@@ -1,1462 +1,1507 @@
-/**
- * Jdenticon 3.2.0
- * http://jdenticon.com
- *
- * Built: 2022-08-07T11:23:11.640Z
- *
- * MIT License
- *
- * Copyright (c) 2014-2021 Daniel Mester Pirttijärvi
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-(function (umdGlobal, factory) {
- var jdenticon = factory(umdGlobal);
-
- // Node.js
- if (typeof module !== "undefined" && "exports" in module) {
- module["exports"] = jdenticon;
- }
- // RequireJS
- else if (typeof define === "function" && define["amd"]) {
- define([], function () { return jdenticon; });
- }
- // No module loader
- else {
- umdGlobal["jdenticon"] = jdenticon;
- }
-})(typeof self !== "undefined" ? self : this, function (umdGlobal) {
+/**
+ * Jdenticon 3.3.0
+ * http://jdenticon.com
+ *
+ * Built: 2024-05-10T09:48:41.921Z
+ *
+ * MIT License
+ *
+ * Copyright (c) 2014-2024 Daniel Mester Pirttijärvi
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+(function (umdGlobal, factory) {
+ var jdenticon = factory(umdGlobal);
+
+ // Node.js
+ if (typeof module !== "undefined" && "exports" in module) {
+ module["exports"] = jdenticon;
+ }
+ // RequireJS
+ else if (typeof define === "function" && define["amd"]) {
+ define([], function () { return jdenticon; });
+ }
+ // No module loader
+ else {
+ umdGlobal["jdenticon"] = jdenticon;
+ }
+})(typeof self !== "undefined" ? self : this, function (umdGlobal) {
'use strict';
-/**
- * Parses a substring of the hash as a number.
- * @param {number} startPosition
- * @param {number=} octets
- */
-function parseHex(hash, startPosition, octets) {
- return parseInt(hash.substr(startPosition, octets), 16);
-}
-
-function decToHex(v) {
- v |= 0; // Ensure integer value
- return v < 0 ? "00" :
- v < 16 ? "0" + v.toString(16) :
- v < 256 ? v.toString(16) :
- "ff";
-}
-
-function hueToRgb(m1, m2, h) {
- h = h < 0 ? h + 6 : h > 6 ? h - 6 : h;
- return decToHex(255 * (
- h < 1 ? m1 + (m2 - m1) * h :
- h < 3 ? m2 :
- h < 4 ? m1 + (m2 - m1) * (4 - h) :
- m1));
+/**
+ * Parses a substring of the hash as a number.
+ * @param {number} startPosition
+ * @param {number=} octets
+ */
+function parseHex(hash, startPosition, octets) {
+ return parseInt(hash.substr(startPosition, octets), 16);
}
-/**
- * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
- * @returns {string}
- */
-function parseColor(color) {
- if (/^#[0-9a-f]{3,8}$/i.test(color)) {
- var result;
- var colorLength = color.length;
-
- if (colorLength < 6) {
- var r = color[1],
- g = color[2],
- b = color[3],
- a = color[4] || "";
- result = "#" + r + r + g + g + b + b + a + a;
- }
- if (colorLength == 7 || colorLength > 8) {
- result = color;
- }
-
- return result;
- }
-}
-
-/**
- * Converts a hexadecimal color to a CSS3 compatible color.
- * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
- * @returns {string}
- */
-function toCss3Color(hexColor) {
- var a = parseHex(hexColor, 7, 2);
- var result;
-
- if (isNaN(a)) {
- result = hexColor;
- } else {
- var r = parseHex(hexColor, 1, 2),
- g = parseHex(hexColor, 3, 2),
- b = parseHex(hexColor, 5, 2);
- result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
- }
-
- return result;
+function decToHex(v) {
+ v |= 0; // Ensure integer value
+ return v < 0 ? "00" :
+ v < 16 ? "0" + v.toString(16) :
+ v < 256 ? v.toString(16) :
+ "ff";
+}
+
+function hueToRgb(m1, m2, h) {
+ h = h < 0 ? h + 6 : h > 6 ? h - 6 : h;
+ return decToHex(255 * (
+ h < 1 ? m1 + (m2 - m1) * h :
+ h < 3 ? m2 :
+ h < 4 ? m1 + (m2 - m1) * (4 - h) :
+ m1));
+}
+
+/**
+ * @param {string} color Color value to parse. Currently hexadecimal strings on the format #rgb[a] and #rrggbb[aa] are supported.
+ * @returns {string}
+ */
+function parseColor(color) {
+ if (/^#[0-9a-f]{3,8}$/i.test(color)) {
+ var result;
+ var colorLength = color.length;
+
+ if (colorLength < 6) {
+ var r = color[1],
+ g = color[2],
+ b = color[3],
+ a = color[4] || "";
+ result = "#" + r + r + g + g + b + b + a + a;
+ }
+ if (colorLength == 7 || colorLength > 8) {
+ result = color;
+ }
+
+ return result;
+ }
+}
+
+/**
+ * Converts a hexadecimal color to a CSS3 compatible color.
+ * @param {string} hexColor Color on the format "#RRGGBB" or "#RRGGBBAA"
+ * @returns {string}
+ */
+function toCss3Color(hexColor) {
+ var a = parseHex(hexColor, 7, 2);
+ var result;
+
+ if (isNaN(a)) {
+ result = hexColor;
+ } else {
+ var r = parseHex(hexColor, 1, 2),
+ g = parseHex(hexColor, 3, 2),
+ b = parseHex(hexColor, 5, 2);
+ result = "rgba(" + r + "," + g + "," + b + "," + (a / 255).toFixed(2) + ")";
+ }
+
+ return result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color.
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function hsl(hue, saturation, lightness) {
+ // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
+ var result;
+
+ if (saturation == 0) {
+ var partialHex = decToHex(lightness * 255);
+ result = partialHex + partialHex + partialHex;
+ }
+ else {
+ var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
+ m1 = lightness * 2 - m2;
+ result =
+ hueToRgb(m1, m2, hue * 6 + 2) +
+ hueToRgb(m1, m2, hue * 6) +
+ hueToRgb(m1, m2, hue * 6 - 2);
+ }
+
+ return "#" + result;
+}
+
+/**
+ * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
+ * @param {number} hue Hue in range [0, 1]
+ * @param {number} saturation Saturation in range [0, 1]
+ * @param {number} lightness Lightness in range [0, 1]
+ * @returns {string}
+ */
+function correctedHsl(hue, saturation, lightness) {
+ // The corrector specifies the perceived middle lightness for each hue
+ var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
+ corrector = correctors[(hue * 6 + 0.5) | 0];
+
+ // Adjust the input lightness relative to the corrector
+ lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
+
+ return hsl(hue, saturation, lightness);
}
-/**
- * Converts an HSL color to a hexadecimal RGB color.
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function hsl(hue, saturation, lightness) {
- // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
- var result;
-
- if (saturation == 0) {
- var partialHex = decToHex(lightness * 255);
- result = partialHex + partialHex + partialHex;
- }
- else {
- var m2 = lightness <= 0.5 ? lightness * (saturation + 1) : lightness + saturation - lightness * saturation,
- m1 = lightness * 2 - m2;
- result =
- hueToRgb(m1, m2, hue * 6 + 2) +
- hueToRgb(m1, m2, hue * 6) +
- hueToRgb(m1, m2, hue * 6 - 2);
- }
-
- return "#" + result;
-}
-
-/**
- * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues
- * @param {number} hue Hue in range [0, 1]
- * @param {number} saturation Saturation in range [0, 1]
- * @param {number} lightness Lightness in range [0, 1]
- * @returns {string}
- */
-function correctedHsl(hue, saturation, lightness) {
- // The corrector specifies the perceived middle lightness for each hue
- var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ],
- corrector = correctors[(hue * 6 + 0.5) | 0];
-
- // Adjust the input lightness relative to the corrector
- lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2;
-
- return hsl(hue, saturation, lightness);
-}
-
-/* global umdGlobal */
-
-// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
-// backward compatibility.
+/* global umdGlobal */
+
+// In the future we can replace `GLOBAL` with `globalThis`, but for now use the old school global detection for
+// backward compatibility.
var GLOBAL = umdGlobal;
-/**
- * @typedef {Object} ParsedConfiguration
- * @property {number} colorSaturation
- * @property {number} grayscaleSaturation
- * @property {string} backColor
- * @property {number} iconPadding
- * @property {function(number):number} hue
- * @property {function(number):number} colorLightness
- * @property {function(number):number} grayscaleLightness
- */
-
-var CONFIG_PROPERTIES = {
- G/*GLOBAL*/: "jdenticon_config",
- n/*MODULE*/: "config",
-};
-
-var rootConfigurationHolder = {};
-
-/**
- * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
- * when it is being used.
- * @param {!Object} rootObject
- */
-function defineConfigProperty(rootObject) {
- rootConfigurationHolder = rootObject;
+/**
+ * @typedef {Object} ParsedConfiguration
+ * @property {number} colorSaturation
+ * @property {number} grayscaleSaturation
+ * @property {string} backColor
+ * @property {number} iconPadding
+ * @property {function(number):number} hue
+ * @property {function(number):number} colorLightness
+ * @property {function(number):number} grayscaleLightness
+ */
+
+var CONFIG_PROPERTIES = {
+ G/*GLOBAL*/: "jdenticon_config",
+ n/*MODULE*/: "config",
+};
+
+var rootConfigurationHolder = {};
+
+/**
+ * Defines the deprecated `config` property on the root Jdenticon object without printing a warning in the console
+ * when it is being used.
+ * @param {!Object} rootObject
+ */
+function defineConfigProperty(rootObject) {
+ rootConfigurationHolder = rootObject;
+}
+
+/**
+ * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
+ * @param {Object} newConfiguration - New configuration object.
+ */
+function configure(newConfiguration) {
+ if (arguments.length) {
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
+ }
+ return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
+}
+
+/**
+ * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
+ * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
+ * local configuration overrides the global configuration in it entirety. This parameter can for backward
+ * compatibility also contain a padding value. A padding value only overrides the global padding, not the
+ * entire global configuration.
+ * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
+ * explicitly to the API method.
+ * @returns {ParsedConfiguration}
+ */
+function getConfiguration(paddingOrLocalConfig, defaultPadding) {
+ var configObject =
+ typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
+ rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
+ GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
+ { },
+
+ lightnessConfig = configObject["lightness"] || { },
+
+ // In versions < 2.1.0 there was no grayscale saturation -
+ // saturation was the color saturation.
+ saturation = configObject["saturation"] || { },
+ colorSaturation = "color" in saturation ? saturation["color"] : saturation,
+ grayscaleSaturation = saturation["grayscale"],
+
+ backColor = configObject["backColor"],
+ padding = configObject["padding"];
+
+ /**
+ * Creates a lightness range.
+ */
+ function lightness(configName, defaultRange) {
+ var range = lightnessConfig[configName];
+
+ // Check if the lightness range is an array-like object. This way we ensure the
+ // array contain two values at the same time.
+ if (!(range && range.length > 1)) {
+ range = defaultRange;
+ }
+
+ /**
+ * Gets a lightness relative the specified value in the specified lightness range.
+ */
+ return function (value) {
+ value = range[0] + value * (range[1] - range[0]);
+ return value < 0 ? 0 : value > 1 ? 1 : value;
+ };
+ }
+
+ /**
+ * Gets a hue allowed by the configured hue restriction,
+ * provided the originally computed hue.
+ */
+ function hueFunction(originalHue) {
+ var hueConfig = configObject["hues"];
+ var hue;
+
+ // Check if 'hues' is an array-like object. This way we also ensure that
+ // the array is not empty, which would mean no hue restriction.
+ if (hueConfig && hueConfig.length > 0) {
+ // originalHue is in the range [0, 1]
+ // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
+ hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
+ }
+
+ return typeof hue == "number" ?
+
+ // A hue was specified. We need to convert the hue from
+ // degrees on any turn - e.g. 746° is a perfectly valid hue -
+ // to turns in the range [0, 1).
+ ((((hue / 360) % 1) + 1) % 1) :
+
+ // No hue configured => use original hue
+ originalHue;
+ }
+
+ return {
+ X/*hue*/: hueFunction,
+ p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
+ H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
+ q/*colorLightness*/: lightness("color", [0.4, 0.8]),
+ I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
+ J/*backColor*/: parseColor(backColor),
+ Y/*iconPadding*/:
+ typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
+ typeof padding == "number" ? padding :
+ defaultPadding
+ }
}
-/**
- * Sets a new icon style configuration. The new configuration is not merged with the previous one. *
- * @param {Object} newConfiguration - New configuration object.
- */
-function configure(newConfiguration) {
- if (arguments.length) {
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] = newConfiguration;
- }
- return rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/];
+var ICON_TYPE_SVG = 1;
+
+var ICON_TYPE_CANVAS = 2;
+
+var ATTRIBUTES = {
+ t/*HASH*/: "data-jdenticon-hash",
+ o/*VALUE*/: "data-jdenticon-value"
+};
+
+var IS_RENDERED_PROPERTY = "jdenticonRendered";
+
+var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
+
+var documentQuerySelectorAll = /** @type {!Function} */ (
+ typeof document !== "undefined" && document.querySelectorAll.bind(document));
+
+function getIdenticonType(el) {
+ if (el) {
+ var tagName = el["tagName"];
+
+ if (/^svg$/i.test(tagName)) {
+ return ICON_TYPE_SVG;
+ }
+
+ if (/^canvas$/i.test(tagName) && "getContext" in el) {
+ return ICON_TYPE_CANVAS;
+ }
+ }
+}
+
+function whenDocumentIsReady(/** @type {Function} */ callback) {
+ function loadedHandler() {
+ document.removeEventListener("DOMContentLoaded", loadedHandler);
+ window.removeEventListener("load", loadedHandler);
+ setTimeout(callback, 0); // Give scripts a chance to run
+ }
+
+ if (typeof document !== "undefined" &&
+ typeof window !== "undefined" &&
+ typeof setTimeout !== "undefined"
+ ) {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", loadedHandler);
+ window.addEventListener("load", loadedHandler);
+ } else {
+ // Document already loaded. The load events above likely won't be raised
+ setTimeout(callback, 0);
+ }
+ }
}
-/**
- * Gets the normalized current Jdenticon color configuration. Missing fields have default values.
- * @param {Object|number|undefined} paddingOrLocalConfig - Configuration passed to the called API method. A
- * local configuration overrides the global configuration in it entirety. This parameter can for backward
- * compatibility also contain a padding value. A padding value only overrides the global padding, not the
- * entire global configuration.
- * @param {number} defaultPadding - Padding used if no padding is specified in neither the configuration nor
- * explicitly to the API method.
- * @returns {ParsedConfiguration}
- */
-function getConfiguration(paddingOrLocalConfig, defaultPadding) {
- var configObject =
- typeof paddingOrLocalConfig == "object" && paddingOrLocalConfig ||
- rootConfigurationHolder[CONFIG_PROPERTIES.n/*MODULE*/] ||
- GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
- { },
-
- lightnessConfig = configObject["lightness"] || { },
-
- // In versions < 2.1.0 there was no grayscale saturation -
- // saturation was the color saturation.
- saturation = configObject["saturation"] || { },
- colorSaturation = "color" in saturation ? saturation["color"] : saturation,
- grayscaleSaturation = saturation["grayscale"],
-
- backColor = configObject["backColor"],
- padding = configObject["padding"];
-
- /**
- * Creates a lightness range.
- */
- function lightness(configName, defaultRange) {
- var range = lightnessConfig[configName];
-
- // Check if the lightness range is an array-like object. This way we ensure the
- // array contain two values at the same time.
- if (!(range && range.length > 1)) {
- range = defaultRange;
- }
-
- /**
- * Gets a lightness relative the specified value in the specified lightness range.
- */
- return function (value) {
- value = range[0] + value * (range[1] - range[0]);
- return value < 0 ? 0 : value > 1 ? 1 : value;
- };
- }
-
- /**
- * Gets a hue allowed by the configured hue restriction,
- * provided the originally computed hue.
- */
- function hueFunction(originalHue) {
- var hueConfig = configObject["hues"];
- var hue;
-
- // Check if 'hues' is an array-like object. This way we also ensure that
- // the array is not empty, which would mean no hue restriction.
- if (hueConfig && hueConfig.length > 0) {
- // originalHue is in the range [0, 1]
- // Multiply with 0.999 to change the range to [0, 1) and then truncate the index.
- hue = hueConfig[0 | (0.999 * originalHue * hueConfig.length)];
- }
-
- return typeof hue == "number" ?
-
- // A hue was specified. We need to convert the hue from
- // degrees on any turn - e.g. 746° is a perfectly valid hue -
- // to turns in the range [0, 1).
- ((((hue / 360) % 1) + 1) % 1) :
-
- // No hue configured => use original hue
- originalHue;
- }
-
- return {
- X/*hue*/: hueFunction,
- p/*colorSaturation*/: typeof colorSaturation == "number" ? colorSaturation : 0.5,
- H/*grayscaleSaturation*/: typeof grayscaleSaturation == "number" ? grayscaleSaturation : 0,
- q/*colorLightness*/: lightness("color", [0.4, 0.8]),
- I/*grayscaleLightness*/: lightness("grayscale", [0.3, 0.9]),
- J/*backColor*/: parseColor(backColor),
- Y/*iconPadding*/:
- typeof paddingOrLocalConfig == "number" ? paddingOrLocalConfig :
- typeof padding == "number" ? padding :
- defaultPadding
- }
-}
-
-var ICON_TYPE_SVG = 1;
-
-var ICON_TYPE_CANVAS = 2;
-
-var ATTRIBUTES = {
- t/*HASH*/: "data-jdenticon-hash",
- o/*VALUE*/: "data-jdenticon-value"
-};
-
-var ICON_SELECTOR = "[" + ATTRIBUTES.t/*HASH*/ +"],[" + ATTRIBUTES.o/*VALUE*/ +"]";
-
-var documentQuerySelectorAll = /** @type {!Function} */ (
- typeof document !== "undefined" && document.querySelectorAll.bind(document));
-
-function getIdenticonType(el) {
- if (el) {
- var tagName = el["tagName"];
-
- if (/^svg$/i.test(tagName)) {
- return ICON_TYPE_SVG;
- }
-
- if (/^canvas$/i.test(tagName) && "getContext" in el) {
- return ICON_TYPE_CANVAS;
- }
- }
+function observer(updateCallback) {
+ if (typeof MutationObserver != "undefined") {
+ var mutationObserver = new MutationObserver(function onmutation(mutations) {
+ for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
+ var mutation = mutations[mutationIndex];
+ var addedNodes = mutation.addedNodes;
+
+ for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
+ var addedNode = addedNodes[addedNodeIndex];
+
+ // Skip other types of nodes than element nodes, since they might not support
+ // the querySelectorAll method => runtime error.
+ if (addedNode.nodeType == 1) {
+ if (getIdenticonType(addedNode)) {
+ updateCallback(addedNode);
+ }
+ else {
+ var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
+ for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
+ updateCallback(icons[iconIndex]);
+ }
+ }
+ }
+ }
+
+ if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
+ updateCallback(mutation.target);
+ }
+ }
+ });
+
+ mutationObserver.observe(document.body, {
+ "childList": true,
+ "attributes": true,
+ "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
+ "subtree": true,
+ });
+ }
}
-function observer(updateCallback) {
- if (typeof MutationObserver != "undefined") {
- var mutationObserver = new MutationObserver(function onmutation(mutations) {
- for (var mutationIndex = 0; mutationIndex < mutations.length; mutationIndex++) {
- var mutation = mutations[mutationIndex];
- var addedNodes = mutation.addedNodes;
-
- for (var addedNodeIndex = 0; addedNodes && addedNodeIndex < addedNodes.length; addedNodeIndex++) {
- var addedNode = addedNodes[addedNodeIndex];
-
- // Skip other types of nodes than element nodes, since they might not support
- // the querySelectorAll method => runtime error.
- if (addedNode.nodeType == 1) {
- if (getIdenticonType(addedNode)) {
- updateCallback(addedNode);
- }
- else {
- var icons = /** @type {Element} */(addedNode).querySelectorAll(ICON_SELECTOR);
- for (var iconIndex = 0; iconIndex < icons.length; iconIndex++) {
- updateCallback(icons[iconIndex]);
- }
- }
- }
- }
-
- if (mutation.type == "attributes" && getIdenticonType(mutation.target)) {
- updateCallback(mutation.target);
- }
- }
- });
-
- mutationObserver.observe(document.body, {
- "childList": true,
- "attributes": true,
- "attributeFilter": [ATTRIBUTES.o/*VALUE*/, ATTRIBUTES.t/*HASH*/, "width", "height"],
- "subtree": true,
- });
- }
+/**
+ * Represents a point.
+ */
+function Point(x, y) {
+ this.x = x;
+ this.y = y;
}
-/**
- * Represents a point.
- */
-function Point(x, y) {
- this.x = x;
- this.y = y;
-}
-
-/**
- * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself,
- * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly.
- */
-function Transform(x, y, size, rotation) {
- this.u/*_x*/ = x;
- this.v/*_y*/ = y;
- this.K/*_size*/ = size;
- this.Z/*_rotation*/ = rotation;
-}
-
-/**
- * Transforms the specified point based on the translation and rotation specification for this Transform.
- * @param {number} x x-coordinate
- * @param {number} y y-coordinate
- * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
- * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
- */
-Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
- var right = this.u/*_x*/ + this.K/*_size*/,
- bottom = this.v/*_y*/ + this.K/*_size*/,
- rotation = this.Z/*_rotation*/;
- return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
- rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
- rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
- new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
-};
-
+/**
+ * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself,
+ * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly.
+ */
+function Transform(x, y, size, rotation) {
+ this.u/*_x*/ = x;
+ this.v/*_y*/ = y;
+ this.K/*_size*/ = size;
+ this.Z/*_rotation*/ = rotation;
+}
+
+/**
+ * Transforms the specified point based on the translation and rotation specification for this Transform.
+ * @param {number} x x-coordinate
+ * @param {number} y y-coordinate
+ * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
+ * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle.
+ */
+Transform.prototype.L/*transformIconPoint*/ = function transformIconPoint (x, y, w, h) {
+ var right = this.u/*_x*/ + this.K/*_size*/,
+ bottom = this.v/*_y*/ + this.K/*_size*/,
+ rotation = this.Z/*_rotation*/;
+ return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) :
+ rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) :
+ rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) :
+ new Point(this.u/*_x*/ + x, this.v/*_y*/ + y);
+};
+
var NO_TRANSFORM = new Transform(0, 0, 0, 0);
-
-
-/**
- * Provides helper functions for rendering common basic shapes.
- */
-function Graphics(renderer) {
- /**
- * @type {Renderer}
- * @private
- */
- this.M/*_renderer*/ = renderer;
-
- /**
- * @type {Transform}
- */
- this.A/*currentTransform*/ = NO_TRANSFORM;
-}
-var Graphics__prototype = Graphics.prototype;
-
-/**
- * Adds a polygon to the underlying renderer.
- * @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ]
- * @param {boolean=} invert Specifies if the polygon will be inverted.
- */
+
+
+/**
+ * Provides helper functions for rendering common basic shapes.
+ */
+function Graphics(renderer) {
+ /**
+ * @type {Renderer}
+ * @private
+ */
+ this.M/*_renderer*/ = renderer;
+
+ /**
+ * @type {Transform}
+ */
+ this.A/*currentTransform*/ = NO_TRANSFORM;
+}
+var Graphics__prototype = Graphics.prototype;
+
+/**
+ * Adds a polygon to the underlying renderer.
+ * @param {Array<number>} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ]
+ * @param {boolean=} invert Specifies if the polygon will be inverted.
+ */
Graphics__prototype.g/*addPolygon*/ = function addPolygon (points, invert) {
var this$1 = this;
-
- var di = invert ? -2 : 2,
- transformedPoints = [];
-
- for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
- transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
- }
-
- this.M/*_renderer*/.g/*addPolygon*/(transformedPoints);
+
+ var di = invert ? -2 : 2,
+ transformedPoints = [];
+
+ for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) {
+ transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1]));
+ }
+
+ this.M/*_renderer*/.g/*addPolygon*/(transformedPoints);
+};
+
+/**
+ * Adds a polygon to the underlying renderer.
+ * Source: http://stackoverflow.com/a/2173084
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse.
+ * @param {number} size The size of the ellipse.
+ * @param {boolean=} invert Specifies if the ellipse will be inverted.
+ */
+Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
+ var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
+ this.M/*_renderer*/.h/*addCircle*/(p, size, invert);
+};
+
+/**
+ * Adds a rectangle to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle.
+ * @param {number} w The width of the rectangle.
+ * @param {number} h The height of the rectangle.
+ * @param {boolean=} invert Specifies if the rectangle will be inverted.
+ */
+Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x, y,
+ x + w, y,
+ x + w, y + h,
+ x, y + h
+ ], invert);
+};
+
+/**
+ * Adds a right triangle to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle.
+ * @param {number} w The width of the triangle.
+ * @param {number} h The height of the triangle.
+ * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle.
+ * @param {boolean=} invert Specifies if the triangle will be inverted.
+ */
+Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) {
+ var points = [
+ x + w, y,
+ x + w, y + h,
+ x, y + h,
+ x, y
+ ];
+ points.splice(((r || 0) % 4) * 2, 2);
+ this.g/*addPolygon*/(points, invert);
+};
+
+/**
+ * Adds a rhombus to the underlying renderer.
+ * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus.
+ * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus.
+ * @param {number} w The width of the rhombus.
+ * @param {number} h The height of the rhombus.
+ * @param {boolean=} invert Specifies if the rhombus will be inverted.
+ */
+Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
+ this.g/*addPolygon*/([
+ x + w / 2, y,
+ x + w, y + h / 2,
+ x + w / 2, y + h,
+ x, y + h / 2
+ ], invert);
};
-
-/**
- * Adds a polygon to the underlying renderer.
- * Source: http://stackoverflow.com/a/2173084
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse.
- * @param {number} size The size of the ellipse.
- * @param {boolean=} invert Specifies if the ellipse will be inverted.
- */
-Graphics__prototype.h/*addCircle*/ = function addCircle (x, y, size, invert) {
- var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size);
- this.M/*_renderer*/.h/*addCircle*/(p, size, invert);
-};
-
-/**
- * Adds a rectangle to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle.
- * @param {number} w The width of the rectangle.
- * @param {number} h The height of the rectangle.
- * @param {boolean=} invert Specifies if the rectangle will be inverted.
- */
-Graphics__prototype.i/*addRectangle*/ = function addRectangle (x, y, w, h, invert) {
- this.g/*addPolygon*/([
- x, y,
- x + w, y,
- x + w, y + h,
- x, y + h
- ], invert);
-};
-
-/**
- * Adds a right triangle to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle.
- * @param {number} w The width of the triangle.
- * @param {number} h The height of the triangle.
- * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle.
- * @param {boolean=} invert Specifies if the triangle will be inverted.
- */
-Graphics__prototype.j/*addTriangle*/ = function addTriangle (x, y, w, h, r, invert) {
- var points = [
- x + w, y,
- x + w, y + h,
- x, y + h,
- x, y
- ];
- points.splice(((r || 0) % 4) * 2, 2);
- this.g/*addPolygon*/(points, invert);
-};
-
-/**
- * Adds a rhombus to the underlying renderer.
- * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus.
- * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus.
- * @param {number} w The width of the rhombus.
- * @param {number} h The height of the rhombus.
- * @param {boolean=} invert Specifies if the rhombus will be inverted.
- */
-Graphics__prototype.N/*addRhombus*/ = function addRhombus (x, y, w, h, invert) {
- this.g/*addPolygon*/([
- x + w / 2, y,
- x + w, y + h / 2,
- x + w / 2, y + h,
- x, y + h / 2
- ], invert);
-};
-
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- * @param {number} positionIndex
- */
-function centerShape(index, g, cell, positionIndex) {
- index = index % 14;
-
- var k, m, w, h, inner, outer;
-
- !index ? (
- k = cell * 0.42,
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell - k * 2,
- cell - k, cell,
- 0, cell
- ])) :
-
- index == 1 ? (
- w = 0 | (cell * 0.5),
- h = 0 | (cell * 0.8),
-
- g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
-
- index == 2 ? (
- w = 0 | (cell / 3),
- g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
-
- index == 3 ? (
- inner = cell * 0.1,
- // Use fixed outer border widths in small icons to ensure the border is drawn
- outer =
- cell < 6 ? 1 :
- cell < 8 ? 2 :
- (0 | (cell * 0.25)),
-
- inner =
- inner > 1 ? (0 | inner) : // large icon => truncate decimals
- inner > 0.5 ? 1 : // medium size icon => fixed width
- inner, // small icon => anti-aliased border
-
- g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
-
- index == 4 ? (
- m = 0 | (cell * 0.15),
- w = 0 | (cell * 0.5),
- g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
-
- index == 5 ? (
- inner = cell * 0.1,
- outer = inner * 4,
- // Align edge to nearest pixel in large icons
- outer > 3 && (outer = 0 | outer),
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.g/*addPolygon*/([
- outer, outer,
- cell - inner, outer,
- outer + (cell - outer - inner) / 2, cell - inner
- ], true)) :
-
- index == 6 ?
- g.g/*addPolygon*/([
- 0, 0,
- cell, 0,
- cell, cell * 0.7,
- cell * 0.4, cell * 0.4,
- cell * 0.7, cell,
- 0, cell
- ]) :
-
- index == 7 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 8 ? (
- g.i/*addRectangle*/(0, 0, cell, cell / 2),
- g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
-
- index == 9 ? (
- inner = cell * 0.14,
- // Use fixed outer border widths in small icons to ensure the border is drawn
- outer =
- cell < 4 ? 1 :
- cell < 6 ? 2 :
- (0 | (cell * 0.35)),
-
- inner =
- cell < 8 ? inner : // small icon => anti-aliased border
- (0 | inner), // large icon => truncate decimals
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
-
- index == 10 ? (
- inner = cell * 0.12,
- outer = inner * 3,
-
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
-
- index == 11 ?
- g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
-
- index == 12 ? (
- m = cell * 0.25,
- g.i/*addRectangle*/(0, 0, cell, cell),
- g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
-
- // 13
- (
- !positionIndex && (
- m = cell * 0.4, w = cell * 1.2,
- g.h/*addCircle*/(m, m, w)
- )
- );
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ * @param {number} positionIndex
+ */
+function centerShape(index, g, cell, positionIndex) {
+ index = index % 14;
+
+ var k, m, w, h, inner, outer;
+
+ !index ? (
+ k = cell * 0.42,
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell - k * 2,
+ cell - k, cell,
+ 0, cell
+ ])) :
+
+ index == 1 ? (
+ w = 0 | (cell * 0.5),
+ h = 0 | (cell * 0.8),
+
+ g.j/*addTriangle*/(cell - w, 0, w, h, 2)) :
+
+ index == 2 ? (
+ w = 0 | (cell / 3),
+ g.i/*addRectangle*/(w, w, cell - w, cell - w)) :
+
+ index == 3 ? (
+ inner = cell * 0.1,
+ // Use fixed outer border widths in small icons to ensure the border is drawn
+ outer =
+ cell < 6 ? 1 :
+ cell < 8 ? 2 :
+ (0 | (cell * 0.25)),
+
+ inner =
+ inner > 1 ? (0 | inner) : // large icon => truncate decimals
+ inner > 0.5 ? 1 : // medium size icon => fixed width
+ inner, // small icon => anti-aliased border
+
+ g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) :
+
+ index == 4 ? (
+ m = 0 | (cell * 0.15),
+ w = 0 | (cell * 0.5),
+ g.h/*addCircle*/(cell - w - m, cell - w - m, w)) :
+
+ index == 5 ? (
+ inner = cell * 0.1,
+ outer = inner * 4,
+
+ // Align edge to nearest pixel in large icons
+ outer > 3 && (outer = 0 | outer),
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.g/*addPolygon*/([
+ outer, outer,
+ cell - inner, outer,
+ outer + (cell - outer - inner) / 2, cell - inner
+ ], true)) :
+
+ index == 6 ?
+ g.g/*addPolygon*/([
+ 0, 0,
+ cell, 0,
+ cell, cell * 0.7,
+ cell * 0.4, cell * 0.4,
+ cell * 0.7, cell,
+ 0, cell
+ ]) :
+
+ index == 7 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 8 ? (
+ g.i/*addRectangle*/(0, 0, cell, cell / 2),
+ g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2),
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) :
+
+ index == 9 ? (
+ inner = cell * 0.14,
+ // Use fixed outer border widths in small icons to ensure the border is drawn
+ outer =
+ cell < 4 ? 1 :
+ cell < 6 ? 2 :
+ (0 | (cell * 0.35)),
+
+ inner =
+ cell < 8 ? inner : // small icon => anti-aliased border
+ (0 | inner), // large icon => truncate decimals
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) :
+
+ index == 10 ? (
+ inner = cell * 0.12,
+ outer = inner * 3,
+
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) :
+
+ index == 11 ?
+ g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) :
+
+ index == 12 ? (
+ m = cell * 0.25,
+ g.i/*addRectangle*/(0, 0, cell, cell),
+ g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) :
+
+ // 13
+ (
+ !positionIndex && (
+ m = cell * 0.4, w = cell * 1.2,
+ g.h/*addCircle*/(m, m, w)
+ )
+ );
+}
+
+/**
+ * @param {number} index
+ * @param {Graphics} g
+ * @param {number} cell
+ */
+function outerShape(index, g, cell) {
+ index = index % 4;
+
+ var m;
+
+ !index ?
+ g.j/*addTriangle*/(0, 0, cell, cell, 0) :
+
+ index == 1 ?
+ g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
+
+ index == 2 ?
+ g.N/*addRhombus*/(0, 0, cell, cell) :
+
+ // 3
+ (
+ m = cell / 6,
+ g.h/*addCircle*/(m, m, cell - 2 * m)
+ );
}
-/**
- * @param {number} index
- * @param {Graphics} g
- * @param {number} cell
- */
-function outerShape(index, g, cell) {
- index = index % 4;
-
- var m;
-
- !index ?
- g.j/*addTriangle*/(0, 0, cell, cell, 0) :
-
- index == 1 ?
- g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) :
-
- index == 2 ?
- g.N/*addRhombus*/(0, 0, cell, cell) :
-
- // 3
- (
- m = cell / 6,
- g.h/*addCircle*/(m, m, cell - 2 * m)
- );
+/**
+ * Gets a set of identicon color candidates for a specified hue and config.
+ * @param {number} hue
+ * @param {ParsedConfiguration} config
+ */
+function colorTheme(hue, config) {
+ hue = config.X/*hue*/(hue);
+ return [
+ // Dark gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
+ // Mid color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
+ // Light gray
+ correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
+ // Light color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
+ // Dark color
+ correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
+ ];
}
-/**
- * Gets a set of identicon color candidates for a specified hue and config.
- * @param {number} hue
- * @param {ParsedConfiguration} config
- */
-function colorTheme(hue, config) {
- hue = config.X/*hue*/(hue);
- return [
- // Dark gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)),
- // Mid color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)),
- // Light gray
- correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)),
- // Light color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)),
- // Dark color
- correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0))
- ];
+/**
+ * Draws an identicon to a specified renderer.
+ * @param {Renderer} renderer
+ * @param {string} hash
+ * @param {Object|number=} config
+ */
+function iconGenerator(renderer, hash, config) {
+ var parsedConfig = getConfiguration(config, 0.08);
+
+ // Set background color
+ if (parsedConfig.J/*backColor*/) {
+ renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
+ }
+
+ // Calculate padding and round to nearest integer
+ var size = renderer.k/*iconSize*/;
+ var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
+ size -= padding * 2;
+
+ var graphics = new Graphics(renderer);
+
+ // Calculate cell size and ensure it is an integer
+ var cell = 0 | (size / 4);
+
+ // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon
+ var x = 0 | (padding + size / 2 - cell * 2);
+ var y = 0 | (padding + size / 2 - cell * 2);
+
+ function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
+ var shapeIndex = parseHex(hash, index, 1);
+ var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
+
+ renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
+
+ for (var i = 0; i < positions.length; i++) {
+ graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
+ shapes(shapeIndex, graphics, cell, i);
+ }
+
+ renderer.P/*endShape*/();
+ }
+
+ // AVAILABLE COLORS
+ var hue = parseHex(hash, -7) / 0xfffffff,
+
+ // Available colors for this icon
+ availableColors = colorTheme(hue, parsedConfig),
+
+ // The index of the selected colors
+ selectedColorIndexes = [];
+
+ var index;
+
+ function isDuplicate(values) {
+ if (values.indexOf(index) >= 0) {
+ for (var i = 0; i < values.length; i++) {
+ if (selectedColorIndexes.indexOf(values[i]) >= 0) {
+ return true;
+ }
+ }
+ }
+ }
+
+ for (var i = 0; i < 3; i++) {
+ index = parseHex(hash, 8 + i, 1) % availableColors.length;
+ if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo
+ isDuplicate([2, 3])) { // Disallow light gray and light color combo
+ index = 1;
+ }
+ selectedColorIndexes.push(index);
+ }
+
+ // ACTUAL RENDERING
+ // Sides
+ renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
+ // Corners
+ renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
+ // Center
+ renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
+
+ renderer.finish();
}
-/**
- * Draws an identicon to a specified renderer.
- * @param {Renderer} renderer
- * @param {string} hash
- * @param {Object|number=} config
- */
-function iconGenerator(renderer, hash, config) {
- var parsedConfig = getConfiguration(config, 0.08);
-
- // Set background color
- if (parsedConfig.J/*backColor*/) {
- renderer.m/*setBackground*/(parsedConfig.J/*backColor*/);
- }
-
- // Calculate padding and round to nearest integer
- var size = renderer.k/*iconSize*/;
- var padding = (0.5 + size * parsedConfig.Y/*iconPadding*/) | 0;
- size -= padding * 2;
-
- var graphics = new Graphics(renderer);
-
- // Calculate cell size and ensure it is an integer
- var cell = 0 | (size / 4);
-
- // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon
- var x = 0 | (padding + size / 2 - cell * 2);
- var y = 0 | (padding + size / 2 - cell * 2);
-
- function renderShape(colorIndex, shapes, index, rotationIndex, positions) {
- var shapeIndex = parseHex(hash, index, 1);
- var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0;
-
- renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]);
-
- for (var i = 0; i < positions.length; i++) {
- graphics.A/*currentTransform*/ = new Transform(x + positions[i][0] * cell, y + positions[i][1] * cell, cell, r++ % 4);
- shapes(shapeIndex, graphics, cell, i);
- }
-
- renderer.P/*endShape*/();
- }
-
- // AVAILABLE COLORS
- var hue = parseHex(hash, -7) / 0xfffffff,
-
- // Available colors for this icon
- availableColors = colorTheme(hue, parsedConfig),
-
- // The index of the selected colors
- selectedColorIndexes = [];
-
- var index;
-
- function isDuplicate(values) {
- if (values.indexOf(index) >= 0) {
- for (var i = 0; i < values.length; i++) {
- if (selectedColorIndexes.indexOf(values[i]) >= 0) {
- return true;
- }
- }
- }
- }
-
- for (var i = 0; i < 3; i++) {
- index = parseHex(hash, 8 + i, 1) % availableColors.length;
- if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo
- isDuplicate([2, 3])) { // Disallow light gray and light color combo
- index = 1;
- }
- selectedColorIndexes.push(index);
- }
-
- // ACTUAL RENDERING
- // Sides
- renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]);
- // Corners
- renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]);
- // Center
- renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]);
-
- renderer.finish();
+/**
+ * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
+ *
+ * This function is optimized for minimal code size and rather short messages.
+ *
+ * @param {string} message
+ */
+function sha1(message) {
+ var HASH_SIZE_HALF_BYTES = 40;
+ var BLOCK_SIZE_WORDS = 16;
+
+ // Variables
+ // `var` is used to be able to minimize the number of `var` keywords.
+ var i = 0,
+ f = 0,
+
+ // Use `encodeURI` to UTF8 encode the message without any additional libraries
+ // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
+ // since `unescape` is deprecated.
+ urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
+
+ // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
+ data = [],
+ dataSize,
+
+ hashBuffer = [],
+
+ a = 0x67452301,
+ b = 0xefcdab89,
+ c = ~a,
+ d = ~b,
+ e = 0xc3d2e1f0,
+ hash = [a, b, c, d, e],
+
+ blockStartIndex = 0,
+ hexHash = "";
+
+ /**
+ * Rotates the value a specified number of bits to the left.
+ * @param {number} value Value to rotate
+ * @param {number} shift Bit count to shift.
+ */
+ function rotl(value, shift) {
+ return (value << shift) | (value >>> (32 - shift));
+ }
+
+ // Message data
+ for ( ; i < urlEncodedMessage.length; f++) {
+ data[f >> 2] = data[f >> 2] |
+ (
+ (
+ urlEncodedMessage[i] == "%"
+ // Percent encoded byte
+ ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
+ // Unencoded byte
+ : urlEncodedMessage.charCodeAt(i++)
+ )
+
+ // Read bytes in reverse order (big endian words)
+ << ((3 - (f & 3)) * 8)
+ );
+ }
+
+ // f is now the length of the utf8 encoded message
+ // 7 = 8 bytes (64 bit) for message size, -1 to round down
+ // >> 6 = integer division with block size
+ dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
+
+ // Message size in bits.
+ // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
+ // significant 32 bits are set. -8 is for the '1' bit padding byte.
+ data[dataSize - 1] = f * 8 - 8;
+
+ // Compute hash
+ for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
+ for (i = 0; i < 80; i++) {
+ f = rotl(a, 5) + e + (
+ // Ch
+ i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
+
+ // Parity
+ i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
+
+ // Maj
+ i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
+
+ // Parity
+ (b ^ c ^ d) + 0xca62c1d6
+ ) + (
+ hashBuffer[i] = i < BLOCK_SIZE_WORDS
+ // Bitwise OR is used to coerse `undefined` to 0
+ ? (data[blockStartIndex + i] | 0)
+ : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
+ );
+
+ e = d;
+ d = c;
+ c = rotl(b, 30);
+ b = a;
+ a = f;
+ }
+
+ hash[0] = a = ((hash[0] + a) | 0);
+ hash[1] = b = ((hash[1] + b) | 0);
+ hash[2] = c = ((hash[2] + c) | 0);
+ hash[3] = d = ((hash[3] + d) | 0);
+ hash[4] = e = ((hash[4] + e) | 0);
+ }
+
+ // Format hex hash
+ for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
+ hexHash += (
+ (
+ // Get word (2^3 half-bytes per word)
+ hash[i >> 3] >>>
+
+ // Append half-bytes in reverse order
+ ((7 - (i & 7)) * 4)
+ )
+ // Clamp to half-byte
+ & 0xf
+ ).toString(16);
+ }
+
+ return hexHash;
}
-/**
- * Computes a SHA1 hash for any value and returns it as a hexadecimal string.
- *
- * This function is optimized for minimal code size and rather short messages.
- *
- * @param {string} message
- */
-function sha1(message) {
- var HASH_SIZE_HALF_BYTES = 40;
- var BLOCK_SIZE_WORDS = 16;
-
- // Variables
- // `var` is used to be able to minimize the number of `var` keywords.
- var i = 0,
- f = 0,
-
- // Use `encodeURI` to UTF8 encode the message without any additional libraries
- // We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
- // since `unescape` is deprecated.
- urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
-
- // This can be changed to a preallocated Uint32Array array for greater performance and larger code size
- data = [],
- dataSize,
-
- hashBuffer = [],
-
- a = 0x67452301,
- b = 0xefcdab89,
- c = ~a,
- d = ~b,
- e = 0xc3d2e1f0,
- hash = [a, b, c, d, e],
-
- blockStartIndex = 0,
- hexHash = "";
-
- /**
- * Rotates the value a specified number of bits to the left.
- * @param {number} value Value to rotate
- * @param {number} shift Bit count to shift.
- */
- function rotl(value, shift) {
- return (value << shift) | (value >>> (32 - shift));
- }
-
- // Message data
- for ( ; i < urlEncodedMessage.length; f++) {
- data[f >> 2] = data[f >> 2] |
- (
- (
- urlEncodedMessage[i] == "%"
- // Percent encoded byte
- ? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
- // Unencoded byte
- : urlEncodedMessage.charCodeAt(i++)
- )
-
- // Read bytes in reverse order (big endian words)
- << ((3 - (f & 3)) * 8)
- );
- }
-
- // f is now the length of the utf8 encoded message
- // 7 = 8 bytes (64 bit) for message size, -1 to round down
- // >> 6 = integer division with block size
- dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
-
- // Message size in bits.
- // SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
- // significant 32 bits are set. -8 is for the '1' bit padding byte.
- data[dataSize - 1] = f * 8 - 8;
-
- // Compute hash
- for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
- for (i = 0; i < 80; i++) {
- f = rotl(a, 5) + e + (
- // Ch
- i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
-
- // Parity
- i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
-
- // Maj
- i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
-
- // Parity
- (b ^ c ^ d) + 0xca62c1d6
- ) + (
- hashBuffer[i] = i < BLOCK_SIZE_WORDS
- // Bitwise OR is used to coerse `undefined` to 0
- ? (data[blockStartIndex + i] | 0)
- : rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
- );
-
- e = d;
- d = c;
- c = rotl(b, 30);
- b = a;
- a = f;
- }
-
- hash[0] = a = ((hash[0] + a) | 0);
- hash[1] = b = ((hash[1] + b) | 0);
- hash[2] = c = ((hash[2] + c) | 0);
- hash[3] = d = ((hash[3] + d) | 0);
- hash[4] = e = ((hash[4] + e) | 0);
- }
-
- // Format hex hash
- for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
- hexHash += (
- (
- // Get word (2^3 half-bytes per word)
- hash[i >> 3] >>>
-
- // Append half-bytes in reverse order
- ((7 - (i & 7)) * 4)
- )
- // Clamp to half-byte
- & 0xf
- ).toString(16);
- }
-
- return hexHash;
-}
-
-/**
- * Inputs a value that might be a valid hash string for Jdenticon and returns it
- * if it is determined valid, otherwise a falsy value is returned.
- */
-function isValidHash(hashCandidate) {
- return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
-}
-
-/**
- * Computes a hash for the specified value. Currently SHA1 is used. This function
- * always returns a valid hash.
- */
-function computeHash(value) {
- return sha1(value == null ? "" : "" + value);
-}
-
-
-
-/**
- * Renderer redirecting drawing commands to a canvas context.
- * @implements {Renderer}
- */
-function CanvasRenderer(ctx, iconSize) {
- var canvas = ctx.canvas;
- var width = canvas.width;
- var height = canvas.height;
-
- ctx.save();
-
- if (!iconSize) {
- iconSize = Math.min(width, height);
-
- ctx.translate(
- ((width - iconSize) / 2) | 0,
- ((height - iconSize) / 2) | 0);
- }
-
- /**
- * @private
- */
- this.l/*_ctx*/ = ctx;
- this.k/*iconSize*/ = iconSize;
-
- ctx.clearRect(0, 0, iconSize, iconSize);
+/**
+ * Inputs a value that might be a valid hash string for Jdenticon and returns it
+ * if it is determined valid, otherwise a falsy value is returned.
+ */
+function isValidHash(hashCandidate) {
+ return /^[0-9a-f]{11,}$/i.test(hashCandidate) && hashCandidate;
+}
+
+/**
+ * Computes a hash for the specified value. Currently SHA1 is used. This function
+ * always returns a valid hash.
+ */
+function computeHash(value) {
+ return sha1(value == null ? "" : "" + value);
}
-var CanvasRenderer__prototype = CanvasRenderer.prototype;
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var ctx = this.l/*_ctx*/;
- var iconSize = this.k/*iconSize*/;
-
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.fillRect(0, 0, iconSize, iconSize);
-};
-
-/**
- * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
- * @param {string} fillColor Fill color on format #rrggbb[aa].
- */
-CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
- var ctx = this.l/*_ctx*/;
- ctx.fillStyle = toCss3Color(fillColor);
- ctx.beginPath();
-};
-
-/**
- * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
- */
-CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
- this.l/*_ctx*/.fill();
-};
-
-/**
- * Adds a polygon to the rendering queue.
- * @param points An array of Point objects.
- */
-CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var ctx = this.l/*_ctx*/;
- ctx.moveTo(points[0].x, points[0].y);
- for (var i = 1; i < points.length; i++) {
- ctx.lineTo(points[i].x, points[i].y);
- }
- ctx.closePath();
-};
-
-/**
- * Adds a circle to the rendering queue.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var ctx = this.l/*_ctx*/,
- radius = diameter / 2;
- ctx.moveTo(point.x + radius, point.y + radius);
- ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
- ctx.closePath();
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
-CanvasRenderer__prototype.finish = function finish () {
- this.l/*_ctx*/.restore();
+
+
+/**
+ * Renderer redirecting drawing commands to a canvas context.
+ * @implements {Renderer}
+ */
+function CanvasRenderer(ctx, iconSize) {
+ var canvas = ctx.canvas;
+ var width = canvas.width;
+ var height = canvas.height;
+
+ ctx.save();
+
+ if (!iconSize) {
+ iconSize = Math.min(width, height);
+
+ ctx.translate(
+ ((width - iconSize) / 2) | 0,
+ ((height - iconSize) / 2) | 0);
+ }
+
+ /**
+ * @private
+ */
+ this.l/*_ctx*/ = ctx;
+ this.k/*iconSize*/ = iconSize;
+
+ ctx.clearRect(0, 0, iconSize, iconSize);
+}
+var CanvasRenderer__prototype = CanvasRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ var iconSize = this.k/*iconSize*/;
+
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.fillRect(0, 0, iconSize, iconSize);
+};
+
+/**
+ * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
+ * @param {string} fillColor Fill color on format #rrggbb[aa].
+ */
+CanvasRenderer__prototype.O/*beginShape*/ = function beginShape (fillColor) {
+ var ctx = this.l/*_ctx*/;
+ ctx.fillStyle = toCss3Color(fillColor);
+ ctx.beginPath();
+};
+
+/**
+ * Marks the end of the currently drawn shape. This causes the queued paths to be rendered on the canvas.
+ */
+CanvasRenderer__prototype.P/*endShape*/ = function endShape () {
+ this.l/*_ctx*/.fill();
+};
+
+/**
+ * Adds a polygon to the rendering queue.
+ * @param points An array of Point objects.
+ */
+CanvasRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var ctx = this.l/*_ctx*/;
+ ctx.moveTo(points[0].x, points[0].y);
+ for (var i = 1; i < points.length; i++) {
+ ctx.lineTo(points[i].x, points[i].y);
+ }
+ ctx.closePath();
+};
+
+/**
+ * Adds a circle to the rendering queue.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+CanvasRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var ctx = this.l/*_ctx*/,
+ radius = diameter / 2;
+ ctx.moveTo(point.x + radius, point.y + radius);
+ ctx.arc(point.x + radius, point.y + radius, radius, 0, Math.PI * 2, counterClockwise);
+ ctx.closePath();
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
+CanvasRenderer__prototype.finish = function finish () {
+ this.l/*_ctx*/.restore();
};
-/**
- * Draws an identicon to a context.
- * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
- * @param {number} size - Icon size in pixels.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function drawIcon(ctx, hashOrValue, size, config) {
- if (!ctx) {
- throw new Error("No canvas specified.");
- }
-
- iconGenerator(new CanvasRenderer(ctx, size),
- isValidHash(hashOrValue) || computeHash(hashOrValue),
- config);
+/**
+ * Draws an identicon to a context.
+ * @param {CanvasRenderingContext2D} ctx - Canvas context on which the icon will be drawn at location (0, 0).
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function drawIcon(ctx, hashOrValue, size, config) {
+ if (!ctx) {
+ throw new Error("No canvas specified.");
+ }
+
+ iconGenerator(new CanvasRenderer(ctx, size),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+
+ var canvas = ctx.canvas;
+ if (canvas) {
+ canvas[IS_RENDERED_PROPERTY] = true;
+ }
}
-/**
- * Prepares a measure to be used as a measure in an SVG path, by
- * rounding the measure to a single decimal. This reduces the file
- * size of the generated SVG with more than 50% in some cases.
- */
-function svgValue(value) {
- return ((value * 10 + 0.5) | 0) / 10;
-}
-
-/**
- * Represents an SVG path element.
- */
-function SvgPath() {
- /**
- * This property holds the data string (path.d) of the SVG path.
- * @type {string}
- */
- this.B/*dataString*/ = "";
-}
-var SvgPath__prototype = SvgPath.prototype;
-
-/**
- * Adds a polygon with the current fill color to the SVG path.
- * @param points An array of Point objects.
- */
-SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
- var dataString = "";
- for (var i = 0; i < points.length; i++) {
- dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
- }
- this.B/*dataString*/ += dataString + "Z";
-};
-
-/**
- * Adds a circle with the current fill color to the SVG path.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- var sweepFlag = counterClockwise ? 0 : 1,
- svgRadius = svgValue(diameter / 2),
- svgDiameter = svgValue(diameter),
- svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
-
- this.B/*dataString*/ +=
- "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
- svgArc + svgDiameter + ",0" +
- svgArc + (-svgDiameter) + ",0";
+/**
+ * Prepares a measure to be used as a measure in an SVG path, by
+ * rounding the measure to a single decimal. This reduces the file
+ * size of the generated SVG with more than 50% in some cases.
+ */
+function svgValue(value) {
+ return ((value * 10 + 0.5) | 0) / 10;
+}
+
+/**
+ * Represents an SVG path element.
+ */
+function SvgPath() {
+ /**
+ * This property holds the data string (path.d) of the SVG path.
+ * @type {string}
+ */
+ this.B/*dataString*/ = "";
+}
+var SvgPath__prototype = SvgPath.prototype;
+
+/**
+ * Adds a polygon with the current fill color to the SVG path.
+ * @param points An array of Point objects.
+ */
+SvgPath__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ var dataString = "";
+ for (var i = 0; i < points.length; i++) {
+ dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y);
+ }
+ this.B/*dataString*/ += dataString + "Z";
+};
+
+/**
+ * Adds a circle with the current fill color to the SVG path.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+SvgPath__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ var sweepFlag = counterClockwise ? 0 : 1,
+ svgRadius = svgValue(diameter / 2),
+ svgDiameter = svgValue(diameter),
+ svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " ";
+
+ this.B/*dataString*/ +=
+ "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) +
+ svgArc + svgDiameter + ",0" +
+ svgArc + (-svgDiameter) + ",0";
};
-
-
-/**
- * Renderer producing SVG output.
- * @implements {Renderer}
- */
-function SvgRenderer(target) {
- /**
- * @type {SvgPath}
- * @private
- */
- this.C/*_path*/;
-
- /**
- * @type {Object.<string,SvgPath>}
- * @private
- */
- this.D/*_pathsByColor*/ = { };
-
- /**
- * @type {SvgElement|SvgWriter}
- * @private
- */
- this.R/*_target*/ = target;
-
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = target.k/*iconSize*/;
-}
-var SvgRenderer__prototype = SvgRenderer.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb[aa].
- */
-SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
- var match = /^(#......)(..)?/.exec(fillColor),
- opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
- this.R/*_target*/.m/*setBackground*/(match[1], opacity);
-};
-
-/**
- * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
- * @param {string} color Fill color on format #xxxxxx.
- */
-SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
- this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
-};
-
-/**
- * Marks the end of the currently drawn shape.
- */
-SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
-
-/**
- * Adds a polygon with the current fill color to the SVG.
- * @param points An array of Point objects.
- */
-SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
- this.C/*_path*/.g/*addPolygon*/(points);
-};
-
-/**
- * Adds a circle with the current fill color to the SVG.
- * @param {Point} point The upper left corner of the circle bounding box.
- * @param {number} diameter The diameter of the circle.
- * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
- */
-SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
- this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
-};
-
-/**
- * Called when the icon has been completely drawn.
- */
+
+
+/**
+ * Renderer producing SVG output.
+ * @implements {Renderer}
+ */
+function SvgRenderer(target) {
+ /**
+ * @type {SvgPath}
+ * @private
+ */
+ this.C/*_path*/;
+
+ /**
+ * @type {Object.<string,SvgPath>}
+ * @private
+ */
+ this.D/*_pathsByColor*/ = { };
+
+ /**
+ * @type {SvgElement|SvgWriter}
+ * @private
+ */
+ this.R/*_target*/ = target;
+
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = target.k/*iconSize*/;
+}
+var SvgRenderer__prototype = SvgRenderer.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb[aa].
+ */
+SvgRenderer__prototype.m/*setBackground*/ = function setBackground (fillColor) {
+ var match = /^(#......)(..)?/.exec(fillColor),
+ opacity = match[2] ? parseHex(match[2], 0) / 255 : 1;
+ this.R/*_target*/.m/*setBackground*/(match[1], opacity);
+};
+
+/**
+ * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape.
+ * @param {string} color Fill color on format #xxxxxx.
+ */
+SvgRenderer__prototype.O/*beginShape*/ = function beginShape (color) {
+ this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath());
+};
+
+/**
+ * Marks the end of the currently drawn shape.
+ */
+SvgRenderer__prototype.P/*endShape*/ = function endShape () { };
+
+/**
+ * Adds a polygon with the current fill color to the SVG.
+ * @param points An array of Point objects.
+ */
+SvgRenderer__prototype.g/*addPolygon*/ = function addPolygon (points) {
+ this.C/*_path*/.g/*addPolygon*/(points);
+};
+
+/**
+ * Adds a circle with the current fill color to the SVG.
+ * @param {Point} point The upper left corner of the circle bounding box.
+ * @param {number} diameter The diameter of the circle.
+ * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path).
+ */
+SvgRenderer__prototype.h/*addCircle*/ = function addCircle (point, diameter, counterClockwise) {
+ this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise);
+};
+
+/**
+ * Called when the icon has been completely drawn.
+ */
SvgRenderer__prototype.finish = function finish () {
var this$1 = this;
-
- var pathsByColor = this.D/*_pathsByColor*/;
- for (var color in pathsByColor) {
- // hasOwnProperty cannot be shadowed in pathsByColor
- // eslint-disable-next-line no-prototype-builtins
- if (pathsByColor.hasOwnProperty(color)) {
- this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
- }
- }
+
+ var pathsByColor = this.D/*_pathsByColor*/;
+ for (var color in pathsByColor) {
+ // hasOwnProperty cannot be shadowed in pathsByColor
+ // eslint-disable-next-line no-prototype-builtins
+ if (pathsByColor.hasOwnProperty(color)) {
+ this$1.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/);
+ }
+ }
};
-var SVG_CONSTANTS = {
- T/*XMLNS*/: "http://www.w3.org/2000/svg",
- U/*WIDTH*/: "width",
- V/*HEIGHT*/: "height",
+var SVG_CONSTANTS = {
+ T/*XMLNS*/: "http://www.w3.org/2000/svg",
+ U/*WIDTH*/: "width",
+ V/*HEIGHT*/: "height",
};
-/**
- * Renderer producing SVG output.
- */
-function SvgWriter(iconSize) {
- /**
- * @type {number}
- */
- this.k/*iconSize*/ = iconSize;
-
- /**
- * @type {string}
- * @private
- */
- this.F/*_s*/ =
- '<svg xmlns="' + SVG_CONSTANTS.T/*XMLNS*/ + '" width="' +
- iconSize + '" height="' + iconSize + '" viewBox="0 0 ' +
- iconSize + ' ' + iconSize + '">';
-}
-var SvgWriter__prototype = SvgWriter.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb.
- * @param {number} opacity Opacity in the range [0.0, 1.0].
- */
-SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
- if (opacity) {
- this.F/*_s*/ += '<rect width="100%" height="100%" fill="' +
- fillColor + '" opacity="' + opacity.toFixed(2) + '"/>';
- }
+/**
+ * Renderer producing SVG output.
+ */
+function SvgWriter(iconSize) {
+ /**
+ * @type {number}
+ */
+ this.k/*iconSize*/ = iconSize;
+
+ /**
+ * @type {string}
+ * @private
+ */
+ this.F/*_s*/ =
+ '<svg xmlns="' + SVG_CONSTANTS.T/*XMLNS*/ + '" width="' +
+ iconSize + '" height="' + iconSize + '" viewBox="0 0 ' +
+ iconSize + ' ' + iconSize + '">';
+}
+var SvgWriter__prototype = SvgWriter.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb.
+ * @param {number} opacity Opacity in the range [0.0, 1.0].
+ */
+SvgWriter__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
+ if (opacity) {
+ this.F/*_s*/ += '<rect width="100%" height="100%" fill="' +
+ fillColor + '" opacity="' + opacity.toFixed(2) + '"/>';
+ }
+};
+
+/**
+ * Writes a path to the SVG string.
+ * @param {string} color Fill color on format #rrggbb.
+ * @param {string} dataString The SVG path data string.
+ */
+SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
+ this.F/*_s*/ += '<path fill="' + color + '" d="' + dataString + '"/>';
+};
+
+/**
+ * Gets the rendered image as an SVG string.
+ */
+SvgWriter__prototype.toString = function toString () {
+ return this.F/*_s*/ + "</svg>";
};
-/**
- * Writes a path to the SVG string.
- * @param {string} color Fill color on format #rrggbb.
- * @param {string} dataString The SVG path data string.
- */
-SvgWriter__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
- this.F/*_s*/ += '<path fill="' + color + '" d="' + dataString + '"/>';
-};
-
-/**
- * Gets the rendered image as an SVG string.
- */
-SvgWriter__prototype.toString = function toString () {
- return this.F/*_s*/ + "</svg>";
-};
-
-/**
- * Draws an identicon as an SVG string.
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
- * @param {number} size - Icon size in pixels.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- * @returns {string} SVG string
- */
-function toSvg(hashOrValue, size, config) {
- var writer = new SvgWriter(size);
- iconGenerator(new SvgRenderer(writer),
- isValidHash(hashOrValue) || computeHash(hashOrValue),
- config);
- return writer.toString();
+/**
+ * Draws an identicon as an SVG string.
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
+ * @param {number} size - Icon size in pixels.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ * @returns {string} SVG string
+ */
+function toSvg(hashOrValue, size, config) {
+ var writer = new SvgWriter(size);
+ iconGenerator(new SvgRenderer(writer),
+ isValidHash(hashOrValue) || computeHash(hashOrValue),
+ config);
+ return writer.toString();
}
-/**
- * Creates a new element and adds it to the specified parent.
- * @param {Element} parentNode
- * @param {string} name
- * @param {...(string|number)} keyValuePairs
- */
+/**
+ * Creates a new element and adds it to the specified parent.
+ * @param {Element} parentNode
+ * @param {string} name
+ * @param {...(string|number)} keyValuePairs
+ */
function SvgElement_append(parentNode, name) {
var keyValuePairs = [], len = arguments.length - 2;
while ( len-- > 0 ) keyValuePairs[ len ] = arguments[ len + 2 ];
-
- var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
-
- for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
- el.setAttribute(
- /** @type {string} */(keyValuePairs[i]),
- /** @type {string} */(keyValuePairs[i + 1])
- );
- }
-
- parentNode.appendChild(el);
-}
-
-
-/**
- * Renderer producing SVG output.
- */
-function SvgElement(element) {
- // Don't use the clientWidth and clientHeight properties on SVG elements
- // since Firefox won't serve a proper value of these properties on SVG
- // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
- // Instead use 100px as a hardcoded size (the svg viewBox will rescale
- // the icon to the correct dimensions)
- var iconSize = this.k/*iconSize*/ = Math.min(
- (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
- (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
- );
-
- /**
- * @type {Element}
- * @private
- */
- this.W/*_el*/ = element;
-
- // Clear current SVG child elements
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
-
- // Set viewBox attribute to ensure the svg scales nicely.
- element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
- element.setAttribute("preserveAspectRatio", "xMidYMid meet");
-}
-var SvgElement__prototype = SvgElement.prototype;
-
-/**
- * Fills the background with the specified color.
- * @param {string} fillColor Fill color on the format #rrggbb.
- * @param {number} opacity Opacity in the range [0.0, 1.0].
- */
-SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
- if (opacity) {
- SvgElement_append(this.W/*_el*/, "rect",
- SVG_CONSTANTS.U/*WIDTH*/, "100%",
- SVG_CONSTANTS.V/*HEIGHT*/, "100%",
- "fill", fillColor,
- "opacity", opacity);
- }
+
+ var el = document.createElementNS(SVG_CONSTANTS.T/*XMLNS*/, name);
+
+ for (var i = 0; i + 1 < keyValuePairs.length; i += 2) {
+ el.setAttribute(
+ /** @type {string} */(keyValuePairs[i]),
+ /** @type {string} */(keyValuePairs[i + 1])
+ );
+ }
+
+ parentNode.appendChild(el);
+}
+
+
+/**
+ * Renderer producing SVG output.
+ */
+function SvgElement(element) {
+ // Don't use the clientWidth and clientHeight properties on SVG elements
+ // since Firefox won't serve a proper value of these properties on SVG
+ // elements (https://bugzilla.mozilla.org/show_bug.cgi?id=874811)
+ // Instead use 100px as a hardcoded size (the svg viewBox will rescale
+ // the icon to the correct dimensions)
+ var iconSize = this.k/*iconSize*/ = Math.min(
+ (Number(element.getAttribute(SVG_CONSTANTS.U/*WIDTH*/)) || 100),
+ (Number(element.getAttribute(SVG_CONSTANTS.V/*HEIGHT*/)) || 100)
+ );
+
+ /**
+ * @type {Element}
+ * @private
+ */
+ this.W/*_el*/ = element;
+
+ // Clear current SVG child elements
+ while (element.firstChild) {
+ element.removeChild(element.firstChild);
+ }
+
+ // Set viewBox attribute to ensure the svg scales nicely.
+ element.setAttribute("viewBox", "0 0 " + iconSize + " " + iconSize);
+ element.setAttribute("preserveAspectRatio", "xMidYMid meet");
+}
+var SvgElement__prototype = SvgElement.prototype;
+
+/**
+ * Fills the background with the specified color.
+ * @param {string} fillColor Fill color on the format #rrggbb.
+ * @param {number} opacity Opacity in the range [0.0, 1.0].
+ */
+SvgElement__prototype.m/*setBackground*/ = function setBackground (fillColor, opacity) {
+ if (opacity) {
+ SvgElement_append(this.W/*_el*/, "rect",
+ SVG_CONSTANTS.U/*WIDTH*/, "100%",
+ SVG_CONSTANTS.V/*HEIGHT*/, "100%",
+ "fill", fillColor,
+ "opacity", opacity);
+ }
+};
+
+/**
+ * Appends a path to the SVG element.
+ * @param {string} color Fill color on format #xxxxxx.
+ * @param {string} dataString The SVG path data string.
+ */
+SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
+ SvgElement_append(this.W/*_el*/, "path",
+ "fill", color,
+ "d", dataString);
};
-/**
- * Appends a path to the SVG element.
- * @param {string} color Fill color on format #xxxxxx.
- * @param {string} dataString The SVG path data string.
- */
-SvgElement__prototype.S/*appendPath*/ = function appendPath (color, dataString) {
- SvgElement_append(this.W/*_el*/, "path",
- "fill", color,
- "d", dataString);
-};
-
-/**
- * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
- */
-function updateAll() {
- if (documentQuerySelectorAll) {
- update(ICON_SELECTOR);
- }
-}
-
-/**
- * Updates the identicon in the specified `<canvas>` or `<svg>` elements.
- * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
- * `<svg>` or `<canvas>`, or a CSS selector to such an element.
- * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
- * `data-jdenticon-value` attribute will be evaluated.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
- * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function update(el, hashOrValue, config) {
- renderDomElement(el, hashOrValue, config, function (el, iconType) {
- if (iconType) {
- return iconType == ICON_TYPE_SVG ?
- new SvgRenderer(new SvgElement(el)) :
- new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
- }
- });
-}
-
-/**
- * Updates the identicon in the specified canvas or svg elements.
- * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
- * `<svg>` or `<canvas>`, or a CSS selector to such an element.
- * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
- * `data-jdenticon-value` attribute will be evaluated.
- * @param {Object|number|undefined} config
- * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer.
- */
-function renderDomElement(el, hashOrValue, config, rendererFactory) {
- if (typeof el === "string") {
- if (documentQuerySelectorAll) {
- var elements = documentQuerySelectorAll(el);
- for (var i = 0; i < elements.length; i++) {
- renderDomElement(elements[i], hashOrValue, config, rendererFactory);
- }
- }
- return;
- }
-
- // Hash selection. The result from getValidHash or computeHash is
- // accepted as a valid hash.
- var hash =
- // 1. Explicit valid hash
- isValidHash(hashOrValue) ||
-
- // 2. Explicit value (`!= null` catches both null and undefined)
- hashOrValue != null && computeHash(hashOrValue) ||
-
- // 3. `data-jdenticon-hash` attribute
- isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) ||
-
- // 4. `data-jdenticon-value` attribute.
- // We want to treat an empty attribute as an empty value.
- // Some browsers return empty string even if the attribute
- // is not specified, so use hasAttribute to determine if
- // the attribute is specified.
- el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/));
-
- if (!hash) {
- // No hash specified. Don't render an icon.
- return;
- }
-
- var renderer = rendererFactory(el, getIdenticonType(el));
- if (renderer) {
- // Draw icon
- iconGenerator(renderer, hash, config);
- }
-}
-
-/**
- * Renders an identicon for all matching supported elements.
- *
- * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not
- * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be
- * evaluated.
- * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global
- * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
- * specified in place of a configuration object.
- */
-function jdenticonJqueryPlugin(hashOrValue, config) {
- this["each"](function (index, el) {
- update(el, hashOrValue, config);
- });
- return this;
+/**
+ * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute.
+ */
+function updateAll() {
+ if (documentQuerySelectorAll) {
+ update(ICON_SELECTOR);
+ }
+}
+
+/**
+ * Updates all canvas elements with the `data-jdenticon-hash` or `data-jdenticon-value` attribute that have not already
+ * been rendered.
+ */
+function updateAllConditional() {
+ if (documentQuerySelectorAll) {
+ /** @type {NodeListOf<HTMLElement>} */
+ var elements = documentQuerySelectorAll(ICON_SELECTOR);
+
+ for (var i = 0; i < elements.length; i++) {
+ var el = elements[i];
+ if (!el[IS_RENDERED_PROPERTY]) {
+ update(el);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the identicon in the specified `<canvas>` or `<svg>` elements.
+ * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
+ * `<svg>` or `<canvas>`, or a CSS selector to such an element.
+ * @param {*=} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
+ * `data-jdenticon-value` attribute will be evaluated.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any
+ * global configuration in its entirety. For backward compability a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function update(el, hashOrValue, config) {
+ renderDomElement(el, hashOrValue, config, function (el, iconType) {
+ if (iconType) {
+ return iconType == ICON_TYPE_SVG ?
+ new SvgRenderer(new SvgElement(el)) :
+ new CanvasRenderer(/** @type {HTMLCanvasElement} */(el).getContext("2d"));
+ }
+ });
+}
+
+/**
+ * Updates the identicon in the specified canvas or svg elements.
+ * @param {(string|Element)} el - Specifies the container in which the icon is rendered as a DOM element of the type
+ * `<svg>` or `<canvas>`, or a CSS selector to such an element.
+ * @param {*} hashOrValue - Optional hash or value to be rendered. If not specified, the `data-jdenticon-hash` or
+ * `data-jdenticon-value` attribute will be evaluated.
+ * @param {Object|number|undefined} config
+ * @param {function(Element,number):Renderer} rendererFactory - Factory function for creating an icon renderer.
+ */
+function renderDomElement(el, hashOrValue, config, rendererFactory) {
+ if (typeof el === "string") {
+ if (documentQuerySelectorAll) {
+ var elements = documentQuerySelectorAll(el);
+ for (var i = 0; i < elements.length; i++) {
+ renderDomElement(elements[i], hashOrValue, config, rendererFactory);
+ }
+ }
+ return;
+ }
+
+ // Hash selection. The result from getValidHash or computeHash is
+ // accepted as a valid hash.
+ var hash =
+ // 1. Explicit valid hash
+ isValidHash(hashOrValue) ||
+
+ // 2. Explicit value (`!= null` catches both null and undefined)
+ hashOrValue != null && computeHash(hashOrValue) ||
+
+ // 3. `data-jdenticon-hash` attribute
+ isValidHash(el.getAttribute(ATTRIBUTES.t/*HASH*/)) ||
+
+ // 4. `data-jdenticon-value` attribute.
+ // We want to treat an empty attribute as an empty value.
+ // Some browsers return empty string even if the attribute
+ // is not specified, so use hasAttribute to determine if
+ // the attribute is specified.
+ el.hasAttribute(ATTRIBUTES.o/*VALUE*/) && computeHash(el.getAttribute(ATTRIBUTES.o/*VALUE*/));
+
+ if (!hash) {
+ // No hash specified. Don't render an icon.
+ return;
+ }
+
+ var renderer = rendererFactory(el, getIdenticonType(el));
+ if (renderer) {
+ // Draw icon
+ iconGenerator(renderer, hash, config);
+ el[IS_RENDERED_PROPERTY] = true;
+ }
}
-// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js
-
-var jdenticon = updateAll;
-
-defineConfigProperty(jdenticon);
-
-// Export public API
-jdenticon["configure"] = configure;
-jdenticon["drawIcon"] = drawIcon;
-jdenticon["toSvg"] = toSvg;
-jdenticon["update"] = update;
-jdenticon["updateCanvas"] = update;
-jdenticon["updateSvg"] = update;
-
-/**
- * Specifies the version of the Jdenticon package in use.
- * @type {string}
- */
-jdenticon["version"] = "3.2.0";
-
-/**
- * Specifies which bundle of Jdenticon that is used.
- * @type {string}
- */
-jdenticon["bundle"] = "browser-umd";
-
-// Basic jQuery plugin
-var jQuery = GLOBAL["jQuery"];
-if (jQuery) {
- jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin;
-}
-
-/**
- * This function is called once upon page load.
- */
-function jdenticonStartup() {
- var replaceMode = (
- jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] ||
- GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
- { }
- )["replaceMode"];
-
- if (replaceMode != "never") {
- updateAll();
-
- if (replaceMode == "observe") {
- observer(update);
- }
- }
-}
-
-// Schedule to render all identicons on the page once it has been loaded.
-if (typeof setTimeout === "function") {
- setTimeout(jdenticonStartup, 0);
+/**
+ * Renders an identicon for all matching supported elements.
+ *
+ * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. If not
+ * specified the `data-jdenticon-hash` and `data-jdenticon-value` attributes of each element will be
+ * evaluated.
+ * @param {Object|number=} config - Optional configuration. If specified, this configuration object overrides any global
+ * configuration in its entirety. For backward compatibility a padding value in the range [0.0, 0.5) can be
+ * specified in place of a configuration object.
+ */
+function jdenticonJqueryPlugin(hashOrValue, config) {
+ this["each"](function (index, el) {
+ update(el, hashOrValue, config);
+ });
+ return this;
}
+// This file is compiled to dist/jdenticon.js and dist/jdenticon.min.js
+
+var jdenticon = updateAll;
+
+defineConfigProperty(jdenticon);
+
+// Export public API
+jdenticon["configure"] = configure;
+jdenticon["drawIcon"] = drawIcon;
+jdenticon["toSvg"] = toSvg;
+jdenticon["update"] = update;
+jdenticon["updateCanvas"] = update;
+jdenticon["updateSvg"] = update;
+
+/**
+ * Specifies the version of the Jdenticon package in use.
+ * @type {string}
+ */
+jdenticon["version"] = "3.3.0";
+
+/**
+ * Specifies which bundle of Jdenticon that is used.
+ * @type {string}
+ */
+jdenticon["bundle"] = "browser-umd";
+
+// Basic jQuery plugin
+var jQuery = GLOBAL["jQuery"];
+if (jQuery) {
+ jQuery["fn"]["jdenticon"] = jdenticonJqueryPlugin;
+}
+
+/**
+ * This function is called once upon page load.
+ */
+function jdenticonStartup() {
+ var replaceMode = (
+ jdenticon[CONFIG_PROPERTIES.n/*MODULE*/] ||
+ GLOBAL[CONFIG_PROPERTIES.G/*GLOBAL*/] ||
+ { }
+ )["replaceMode"];
+
+ if (replaceMode != "never") {
+ updateAllConditional();
+
+ if (replaceMode == "observe") {
+ observer(update);
+ }
+ }
+}
+
+// Schedule to render all identicons on the page once it has been loaded.
+whenDocumentIsReady(jdenticonStartup);
+
return jdenticon;
-
+
}); \ No newline at end of file
diff --git a/src/static/templates/admin/organizations.hbs b/src/static/templates/admin/organizations.hbs
index ab442e8f..a5676994 100644
--- a/src/static/templates/admin/organizations.hbs
+++ b/src/static/templates/admin/organizations.hbs
@@ -62,4 +62,4 @@
<script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
-<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
+<script src="{{urlpath}}/vw_static/jdenticon-3.3.0.js"></script>
diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs
index d1fd3e5d..b8552bd7 100644
--- a/src/static/templates/admin/users.hbs
+++ b/src/static/templates/admin/users.hbs
@@ -143,4 +143,4 @@
<script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
<script src="{{urlpath}}/vw_static/datatables.js"></script>
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
-<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
+<script src="{{urlpath}}/vw_static/jdenticon-3.3.0.js"></script>