vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

commit 610b183cef5111ea51aaf7e85e58baf779610fc1
parent 8feed2916fee7a06e82e6df5070a9c4ee042bcd0
Author: BlackDex <black.dex@gmail.com>
Date:   Thu,  1 Dec 2022 17:18:29 +0100

Update dependencies for Rust and Admin interface.

- Updated Rust deps and one small change regarding chrono
- Updated bootstrap 5 css
- Updated datatables
- Replaced identicon.js with jdenticon.
  identicon.js is unmaintained ( https://github.com/stewartlord/identicon.js/issues/52 )
  The icon's are very different, but nice. It also doesn't need custom
  code to find and update the icons our selfs.

Diffstat:
MCargo.lock | 205++++++++++++++++++++++++++++++++++++++++---------------------------------------
MCargo.toml | 24++++++++++++------------
Msrc/api/web.rs | 2+-
Msrc/static/scripts/bootstrap.css | 199++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/static/scripts/datatables.css | 16+++++++++++-----
Msrc/static/scripts/datatables.js | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Dsrc/static/scripts/identicon.js | 205-------------------------------------------------------------------------------
Asrc/static/scripts/jdenticon.js | 1463+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/static/templates/admin/base.hbs | 7+------
Msrc/static/templates/admin/organizations.hbs | 8+-------
Msrc/static/templates/admin/users.hbs | 8+-------
11 files changed, 1757 insertions(+), 497 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -122,9 +122,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" dependencies = [ "proc-macro2", "quote", @@ -173,7 +173,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object", "rustc-demangle", ] @@ -240,9 +240,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cached" @@ -283,9 +283,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" [[package]] name = "cc" -version = "1.0.75" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ca34107f97baef6cfb231b32f36115781856b8f8208e8c580e0bcaea374842" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cfg-if" @@ -295,9 +295,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "num-integer", @@ -308,9 +308,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87b30366b6766751277791b473b674f3bf7fb75696841c784a3eb7e7fbf44ee" +checksum = "fa48fa079165080f11d7753fd0bc175b7d391f276b965fe4b55bfad67856e463" dependencies = [ "chrono", "chrono-tz-build", @@ -445,9 +445,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] @@ -484,9 +484,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" dependencies = [ "cc", "cxxbridge-flags", @@ -496,9 +496,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" dependencies = [ "cc", "codespan-reporting", @@ -511,15 +511,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" dependencies = [ "proc-macro2", "quote", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "enum-as-inner" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", "proc-macro2", @@ -768,12 +768,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] @@ -959,9 +959,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "governor" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1b4626e87b9eb1d603ed23067ba1e29ec1d0b35325a2b96c3fe1cf20871f56" +checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5" dependencies = [ "cfg-if", "dashmap", @@ -1198,9 +1198,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -1233,14 +1233,14 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" dependencies = [ "socket2", "widestring", "winapi", - "winreg 0.7.0", + "winreg", ] [[package]] @@ -1283,9 +1283,9 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.1.1" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa4b4af834c6cfd35d8763d359661b90f2e45d8f750a0849156c7f4671af09c" +checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" dependencies = [ "base64", "pem", @@ -1331,17 +1331,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libmimalloc-sys" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37567b180c1af25924b303ddf1ee4467653783440c62360beb2b322a4d93361" +checksum = "04d1c67deb83e6b75fa4fe3309e09cfeade12e7721d95322af500d3814ea60c9" dependencies = [ "cc", + "libc", ] [[package]] @@ -1472,9 +1473,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32d6a9ac92d0239d7bfa31137fb47634ac7272a3c11bcee91379ac100781670" +checksum = "9b2374e2999959a7b583e1811a1ddbf1d3a4b9496eceb9746f1192a59d871eca" dependencies = [ "libmimalloc-sys", ] @@ -1501,6 +1502,15 @@ dependencies = [ ] [[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] name = "mio" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1561,9 +1571,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", "bitflags", @@ -1686,9 +1696,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" dependencies = [ "bitflags", "cfg-if", @@ -1727,9 +1737,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" dependencies = [ "autocfg", "cc", @@ -1757,9 +1767,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", @@ -1823,9 +1833,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.1" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0" dependencies = [ "thiserror", "ucd-trie", @@ -1833,9 +1843,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.4.1" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fd9bc6500181952d34bd0b2b0163a54d794227b498be0b7afa7698d0a7b18f" +checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344" dependencies = [ "pest", "pest_generator", @@ -1843,9 +1853,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.4.1" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2610d5ac5156217b4ff8e46ddcef7cdf44b273da2ac5bca2ecbfa86a330e7c4" +checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c" dependencies = [ "pest", "pest_meta", @@ -1856,9 +1866,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.4.1" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824749bf7e21dd66b36fbe26b3f45c713879cccd4a009a917ab8e045ca8246fe" +checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20" dependencies = [ "once_cell", "pest", @@ -2055,9 +2065,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fee2dce59f7a43418e3382c766554c614e06a552d53a8f07ef499ea4b332c0f" +checksum = "20f14e071918cbeefc5edc986a7aa92c425dae244e003a35e1cdddb5ca39b5cb" [[package]] name = "r2d2" @@ -2175,9 +2185,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "async-compression", "base64", @@ -2214,7 +2224,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", + "winreg", ] [[package]] @@ -2474,9 +2484,9 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] @@ -2493,9 +2503,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -2504,9 +2514,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -2527,9 +2537,9 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ "cfg-if", "cpufeatures", @@ -2663,9 +2673,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -2792,9 +2802,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", "bytes", @@ -2812,9 +2822,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -2867,9 +2877,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", @@ -2983,9 +2993,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.21.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", "cfg-if", @@ -2997,32 +3007,32 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static", - "log", "rand", "smallvec", "thiserror", "tinyvec", "tokio", + "tracing", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.21.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" dependencies = [ "cfg-if", "futures-util", "ipconfig", "lazy_static", - "log", "lru-cache", "parking_lot", "resolv-conf", "smallvec", "thiserror", "tokio", + "tracing", "trust-dns-proto", ] @@ -3034,9 +3044,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ "base64", "byteorder", @@ -3045,7 +3055,7 @@ dependencies = [ "httparse", "log", "rand", - "sha-1", + "sha1", "thiserror", "url", "utf-8", @@ -3151,9 +3161,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom", ] @@ -3554,15 +3564,6 @@ checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] - -[[package]] -name = "winreg" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" diff --git a/Cargo.toml b/Cargo.toml @@ -55,17 +55,17 @@ num-derive = "0.3.3" rocket = { version = "0.5.0-rc.2", features = ["tls", "json"], default-features = false } # WebSockets libraries -tokio-tungstenite = "0.17.2" +tokio-tungstenite = "0.18.0" rmpv = "1.0.0" # MessagePack library dashmap = "5.4.0" # Async futures futures = "0.3.25" -tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] } +tokio = { version = "1.22.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time"] } # A generic serialization/deserialization framework -serde = { version = "1.0.147", features = ["derive"] } -serde_json = "1.0.87" +serde = { version = "1.0.148", features = ["derive"] } +serde_json = "1.0.89" # A safe, extensible ORM and Query builder diesel = { version = "2.0.2", features = ["chrono", "r2d2"] } @@ -79,11 +79,11 @@ rand = { version = "0.8.5", features = ["small_rng"] } ring = "0.16.20" # UUID generation -uuid = { version = "1.2.1", features = ["v4"] } +uuid = { version = "1.2.2", features = ["v4"] } # Date and time libraries -chrono = { version = "0.4.22", features = ["clock", "serde"], default-features = false } -chrono-tz = "0.8.0" +chrono = { version = "0.4.23", features = ["clock", "serde"], default-features = false } +chrono-tz = "0.8.1" time = "0.3.17" # Job scheduler @@ -116,13 +116,13 @@ email_address = "0.2.4" handlebars = { version = "4.3.5", features = ["dir_source"] } # HTTP client -reqwest = { version = "0.11.12", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } +reqwest = { version = "0.11.13", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns"] } # For favicon extraction from main website html5gum = "0.5.2" regex = { version = "1.7.0", features = ["std", "perf", "unicode-perl"], default-features = false } data-url = "0.2.0" -bytes = "1.2.1" +bytes = "1.3.0" cached = "0.40.0" # Used for custom short lived cookie jar during favicon extraction @@ -130,14 +130,14 @@ cookie = "0.16.1" cookie_store = "0.19.0" # Used by U2F, JWT and Postgres -openssl = "0.10.42" +openssl = "0.10.43" # CLI argument parsing pico-args = "0.5.0" # Macro ident concatenation paste = "1.0.9" -governor = "0.5.0" +governor = "0.5.1" # Capture CTRL+C ctrlc = { version = "3.2.3", features = ["termination"] } @@ -147,7 +147,7 @@ semver = "1.0.14" # Allow overriding the default memory allocator # Mainly used for the musl builds, since the default musl malloc is very slow -mimalloc = { version = "0.1.31", features = ["secure"], default-features = false, optional = true } +mimalloc = { version = "0.1.32", features = ["secure"], default-features = false, optional = true } [patch.crates-io] # Using a patched version of multer-rs (Used by Rocket) to fix attachment/send file uploads diff --git a/src/api/web.rs b/src/api/web.rs @@ -98,7 +98,7 @@ pub fn static_files(filename: String) -> Result<(ContentType, &'static [u8]), Er "vaultwarden-icon.png" => Ok((ContentType::PNG, include_bytes!("../static/images/vaultwarden-icon.png"))), "bootstrap.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/bootstrap.css"))), "bootstrap-native.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/bootstrap-native.js"))), - "identicon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/identicon.js"))), + "jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.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.6.1.slim.js" => { diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css @@ -1,6 +1,6 @@ @charset "UTF-8"; /*! - * Bootstrap v5.2.0 (https://getbootstrap.com/) + * Bootstrap v5.2.3 (https://getbootstrap.com/) * Copyright 2011-2022 The Bootstrap Authors * Copyright 2011-2022 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) @@ -2468,6 +2468,7 @@ textarea.form-control-lg { height: 100%; padding: 1rem 0.75rem; overflow: hidden; + text-align: start; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; @@ -2547,14 +2548,14 @@ textarea.form-control-lg { .input-group > .form-control:focus, .input-group > .form-select:focus, .input-group > .form-floating:focus-within { - z-index: 3; + z-index: 5; } .input-group .btn { position: relative; z-index: 2; } .input-group .btn:focus { - z-index: 3; + z-index: 5; } .input-group-text { @@ -2609,10 +2610,13 @@ textarea.form-control-lg { border-top-right-radius: 0; border-bottom-right-radius: 0; } -.input-group > :not(:first-child):not(.dropdown-menu):not(.form-floating):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback), +.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} .input-group > .form-floating:not(:first-child) > .form-control, .input-group > .form-floating:not(:first-child) > .form-select { - margin-left: -1px; border-top-left-radius: 0; border-bottom-left-radius: 0; } @@ -2699,14 +2703,11 @@ textarea.form-control-lg { margin-left: 0.5em; } -.was-validated .input-group .form-control:valid, .input-group .form-control.is-valid, -.was-validated .input-group .form-select:valid, -.input-group .form-select.is-valid { - z-index: 1; -} -.was-validated .input-group .form-control:valid:focus, .input-group .form-control.is-valid:focus, -.was-validated .input-group .form-select:valid:focus, -.input-group .form-select.is-valid:focus { +.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid, +.was-validated .input-group > .form-select:not(:focus):valid, +.input-group > .form-select:not(:focus).is-valid, +.was-validated .input-group > .form-floating:not(:focus-within):valid, +.input-group > .form-floating:not(:focus-within).is-valid { z-index: 3; } @@ -2792,15 +2793,12 @@ textarea.form-control-lg { margin-left: 0.5em; } -.was-validated .input-group .form-control:invalid, .input-group .form-control.is-invalid, -.was-validated .input-group .form-select:invalid, -.input-group .form-select.is-invalid { - z-index: 2; -} -.was-validated .input-group .form-control:invalid:focus, .input-group .form-control.is-invalid:focus, -.was-validated .input-group .form-select:invalid:focus, -.input-group .form-select.is-invalid:focus { - z-index: 3; +.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid, +.was-validated .input-group > .form-select:not(:focus):invalid, +.input-group > .form-select:not(:focus).is-invalid, +.was-validated .input-group > .form-floating:not(:focus-within):invalid, +.input-group > .form-floating:not(:focus-within).is-invalid { + z-index: 4; } .btn { @@ -2815,6 +2813,7 @@ textarea.form-control-lg { --bs-btn-border-width: 1px; --bs-btn-border-color: transparent; --bs-btn-border-radius: 0.375rem; + --bs-btn-hover-border-color: transparent; --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); --bs-btn-disabled-opacity: 0.65; --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); @@ -2847,19 +2846,29 @@ textarea.form-control-lg { background-color: var(--bs-btn-hover-bg); border-color: var(--bs-btn-hover-border-color); } -.btn-check:focus + .btn, .btn:focus { +.btn-check + .btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color); +} +.btn:focus-visible { color: var(--bs-btn-hover-color); background-color: var(--bs-btn-hover-bg); border-color: var(--bs-btn-hover-border-color); outline: 0; box-shadow: var(--bs-btn-focus-box-shadow); } -.btn-check:checked + .btn, .btn-check:active + .btn, .btn:active, .btn.active, .btn.show { +.btn-check:focus-visible + .btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show { color: var(--bs-btn-active-color); background-color: var(--bs-btn-active-bg); border-color: var(--bs-btn-active-border-color); } -.btn-check:checked + .btn:focus, .btn-check:active + .btn:focus, .btn:active:focus, .btn.active:focus, .btn.show:focus { +.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:disabled, .btn.disabled, fieldset:disabled .btn { @@ -3157,7 +3166,7 @@ textarea.form-control-lg { --bs-btn-focus-shadow-rgb: 49, 132, 253; text-decoration: underline; } -.btn-link:focus { +.btn-link:focus-visible { color: var(--bs-btn-color); } .btn-link:hover { @@ -3242,6 +3251,7 @@ textarea.form-control-lg { } .dropdown-menu { + --bs-dropdown-zindex: 1000; --bs-dropdown-min-width: 10rem; --bs-dropdown-padding-x: 0; --bs-dropdown-padding-y: 0.5rem; @@ -3268,7 +3278,7 @@ textarea.form-control-lg { --bs-dropdown-header-padding-x: 1rem; --bs-dropdown-header-padding-y: 0.5rem; position: absolute; - z-index: 1000; + z-index: var(--bs-dropdown-zindex); display: none; min-width: var(--bs-dropdown-min-width); padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); @@ -3568,7 +3578,7 @@ textarea.form-control-lg { .btn-group { border-radius: 0.375rem; } -.btn-group > .btn:not(:first-child), +.btn-group > :not(.btn-check:first-child) + .btn, .btn-group > .btn-group:not(:first-child) { margin-left: -1px; } @@ -3678,7 +3688,7 @@ textarea.form-control-lg { border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color); } .nav-tabs .nav-link { - margin-bottom: calc(var(--bs-nav-tabs-border-width) * -1); + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); background: none; border: var(--bs-nav-tabs-border-width) solid transparent; border-top-left-radius: var(--bs-nav-tabs-border-radius); @@ -3700,7 +3710,7 @@ textarea.form-control-lg { border-color: var(--bs-nav-tabs-link-active-border-color); } .nav-tabs .dropdown-menu { - margin-top: calc(var(--bs-nav-tabs-border-width) * -1); + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); border-top-left-radius: 0; border-top-right-radius: 0; } @@ -4357,7 +4367,7 @@ textarea.form-control-lg { } .accordion { - --bs-accordion-color: #000; + --bs-accordion-color: #212529; --bs-accordion-bg: #fff; --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; --bs-accordion-border-color: var(--bs-border-color); @@ -4366,9 +4376,9 @@ textarea.form-control-lg { --bs-accordion-inner-border-radius: calc(0.375rem - 1px); --bs-accordion-btn-padding-x: 1.25rem; --bs-accordion-btn-padding-y: 1rem; - --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-color: #212529; --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='var%28--bs-body-color%29'%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='%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-width: 1.25rem; --bs-accordion-btn-icon-transform: rotate(-180deg); --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; @@ -4404,7 +4414,7 @@ textarea.form-control-lg { .accordion-button:not(.collapsed) { color: var(--bs-accordion-active-color); background-color: var(--bs-accordion-active-bg); - box-shadow: inset 0 calc(var(--bs-accordion-border-width) * -1) 0 var(--bs-accordion-border-color); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color); } .accordion-button:not(.collapsed)::after { background-image: var(--bs-accordion-btn-active-icon); @@ -4487,7 +4497,7 @@ textarea.form-control-lg { .accordion-flush .accordion-item:last-child { border-bottom: 0; } -.accordion-flush .accordion-item .accordion-button { +.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed { border-radius: 0; } @@ -4753,12 +4763,6 @@ textarea.form-control-lg { color: #101214; } -@-webkit-keyframes progress-bar-stripes { - 0% { - background-position-x: 1rem; - } -} - @keyframes progress-bar-stripes { 0% { background-position-x: 1rem; @@ -4804,12 +4808,10 @@ textarea.form-control-lg { } .progress-bar-animated { - -webkit-animation: 1s linear infinite progress-bar-stripes; animation: 1s linear infinite progress-bar-stripes; } @media (prefers-reduced-motion: reduce) { .progress-bar-animated { - -webkit-animation: none; animation: none; } } @@ -4896,18 +4898,18 @@ textarea.form-control-lg { border-top-width: 0; } .list-group-item + .list-group-item.active { - margin-top: calc(var(--bs-list-group-border-width) * -1); + margin-top: calc(-1 * var(--bs-list-group-border-width)); border-top-width: var(--bs-list-group-border-width); } .list-group-horizontal { flex-direction: row; } -.list-group-horizontal > .list-group-item:first-child { +.list-group-horizontal > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } -.list-group-horizontal > .list-group-item:last-child { +.list-group-horizontal > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -4919,7 +4921,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } @@ -4927,11 +4929,11 @@ textarea.form-control-lg { .list-group-horizontal-sm { flex-direction: row; } - .list-group-horizontal-sm > .list-group-item:first-child { + .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } - .list-group-horizontal-sm > .list-group-item:last-child { + .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -4943,7 +4945,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal-sm > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } } @@ -4951,11 +4953,11 @@ textarea.form-control-lg { .list-group-horizontal-md { flex-direction: row; } - .list-group-horizontal-md > .list-group-item:first-child { + .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } - .list-group-horizontal-md > .list-group-item:last-child { + .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -4967,7 +4969,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal-md > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } } @@ -4975,11 +4977,11 @@ textarea.form-control-lg { .list-group-horizontal-lg { flex-direction: row; } - .list-group-horizontal-lg > .list-group-item:first-child { + .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } - .list-group-horizontal-lg > .list-group-item:last-child { + .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -4991,7 +4993,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal-lg > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } } @@ -4999,11 +5001,11 @@ textarea.form-control-lg { .list-group-horizontal-xl { flex-direction: row; } - .list-group-horizontal-xl > .list-group-item:first-child { + .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } - .list-group-horizontal-xl > .list-group-item:last-child { + .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -5015,7 +5017,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal-xl > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } } @@ -5023,11 +5025,11 @@ textarea.form-control-lg { .list-group-horizontal-xxl { flex-direction: row; } - .list-group-horizontal-xxl > .list-group-item:first-child { + .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) { border-bottom-left-radius: var(--bs-list-group-border-radius); border-top-right-radius: 0; } - .list-group-horizontal-xxl > .list-group-item:last-child { + .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) { border-top-right-radius: var(--bs-list-group-border-radius); border-bottom-left-radius: 0; } @@ -5039,7 +5041,7 @@ textarea.form-control-lg { border-left-width: 0; } .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { - margin-left: calc(var(--bs-list-group-border-width) * -1); + margin-left: calc(-1 * var(--bs-list-group-border-width)); border-left-width: var(--bs-list-group-border-width); } } @@ -5199,6 +5201,7 @@ textarea.form-control-lg { } .toast { + --bs-toast-zindex: 1090; --bs-toast-padding-x: 0.75rem; --bs-toast-padding-y: 0.5rem; --bs-toast-spacing: 1.5rem; @@ -5232,8 +5235,9 @@ textarea.form-control-lg { } .toast-container { + --bs-toast-zindex: 1090; position: absolute; - z-index: 1090; + z-index: var(--bs-toast-zindex); width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -5256,7 +5260,7 @@ textarea.form-control-lg { border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); } .toast-header .btn-close { - margin-right: calc(var(--bs-toast-padding-x) * -0.5); + margin-right: calc(-0.5 * var(--bs-toast-padding-x)); margin-left: var(--bs-toast-padding-x); } @@ -5383,7 +5387,7 @@ textarea.form-control-lg { } .modal-header .btn-close { padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); - margin: calc(var(--bs-modal-header-padding-y) * -0.5) calc(var(--bs-modal-header-padding-x) * -0.5) calc(var(--bs-modal-header-padding-y) * -0.5) auto; + margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto; } .modal-title { @@ -5673,7 +5677,7 @@ textarea.form-control-lg { --bs-popover-header-padding-x: 1rem; --bs-popover-header-padding-y: 0.5rem; --bs-popover-header-font-size: 1rem; - --bs-popover-header-color: var(--bs-heading-color); + --bs-popover-header-color: ; --bs-popover-header-bg: #f0f0f0; --bs-popover-body-padding-x: 1rem; --bs-popover-body-padding-y: 1rem; @@ -5720,7 +5724,7 @@ textarea.form-control-lg { } .bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow { - bottom: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); } .bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; @@ -5736,7 +5740,7 @@ textarea.form-control-lg { /* rtl:begin:ignore */ .bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow { - left: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); width: var(--bs-popover-arrow-height); height: var(--bs-popover-arrow-width); } @@ -5754,7 +5758,7 @@ textarea.form-control-lg { /* rtl:end:ignore */ .bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { - top: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); } .bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); @@ -5773,14 +5777,14 @@ textarea.form-control-lg { left: 50%; display: block; width: var(--bs-popover-arrow-width); - margin-left: calc(var(--bs-popover-arrow-width) * -0.5); + margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); content: ""; border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg); } /* rtl:begin:ignore */ .bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow { - right: calc(var(--bs-popover-arrow-height) * -1 - var(--bs-popover-border-width)); + right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); width: var(--bs-popover-arrow-height); height: var(--bs-popover-arrow-width); } @@ -5857,7 +5861,6 @@ textarea.form-control-lg { display: block; } -/* rtl:begin:ignore */ .carousel-item-next:not(.carousel-item-start), .active.carousel-item-end { transform: translateX(100%); @@ -5868,7 +5871,6 @@ textarea.form-control-lg { transform: translateX(-100%); } -/* rtl:end:ignore */ .carousel-fade .carousel-item { opacity: 0; transition-property: opacity; @@ -6030,16 +6032,9 @@ textarea.form-control-lg { height: var(--bs-spinner-height); vertical-align: var(--bs-spinner-vertical-align); border-radius: 50%; - -webkit-animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); } -@-webkit-keyframes spinner-border { - to { - transform: rotate(360deg) /* rtl:ignore */; - } -} - @keyframes spinner-border { to { transform: rotate(360deg) /* rtl:ignore */; @@ -6062,16 +6057,6 @@ textarea.form-control-lg { --bs-spinner-border-width: 0.2em; } -@-webkit-keyframes spinner-grow { - 0% { - transform: scale(0); - } - 50% { - opacity: 1; - transform: none; - } -} - @keyframes spinner-grow { 0% { transform: scale(0); @@ -6103,6 +6088,7 @@ textarea.form-control-lg { } } .offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm { + --bs-offcanvas-zindex: 1045; --bs-offcanvas-width: 400px; --bs-offcanvas-height: 30vh; --bs-offcanvas-padding-x: 1rem; @@ -6118,7 +6104,7 @@ textarea.form-control-lg { .offcanvas-sm { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6206,7 +6192,7 @@ textarea.form-control-lg { .offcanvas-md { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6294,7 +6280,7 @@ textarea.form-control-lg { .offcanvas-lg { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6382,7 +6368,7 @@ textarea.form-control-lg { .offcanvas-xl { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6470,7 +6456,7 @@ textarea.form-control-lg { .offcanvas-xxl { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6557,7 +6543,7 @@ textarea.form-control-lg { .offcanvas { position: fixed; bottom: 0; - z-index: 1045; + z-index: var(--bs-offcanvas-zindex); display: flex; flex-direction: column; max-width: 100%; @@ -6635,9 +6621,9 @@ textarea.form-control-lg { } .offcanvas-header .btn-close { padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); - margin-top: calc(var(--bs-offcanvas-padding-y) * -0.5); - margin-right: calc(var(--bs-offcanvas-padding-x) * -0.5); - margin-bottom: calc(var(--bs-offcanvas-padding-y) * -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)); } .offcanvas-title { @@ -6677,16 +6663,9 @@ textarea.form-control-lg { } .placeholder-glow .placeholder { - -webkit-animation: placeholder-glow 2s ease-in-out infinite; animation: placeholder-glow 2s ease-in-out infinite; } -@-webkit-keyframes placeholder-glow { - 50% { - opacity: 0.2; - } -} - @keyframes placeholder-glow { 50% { opacity: 0.2; @@ -6697,17 +6676,9 @@ textarea.form-control-lg { mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); -webkit-mask-size: 200% 100%; mask-size: 200% 100%; - -webkit-animation: placeholder-wave 2s linear infinite; animation: placeholder-wave 2s linear infinite; } -@-webkit-keyframes placeholder-wave { - 100% { - -webkit-mask-position: -200% 0%; - mask-position: -200% 0%; - } -} - @keyframes placeholder-wave { 100% { -webkit-mask-position: -200% 0%; diff --git 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-1.12.1 + * https://datatables.net/download/#bs5/dt-1.13.1 * * Included libraries: - * DataTables 1.12.1 + * DataTables 1.13.1 */ @charset "UTF-8"; @@ -63,7 +63,7 @@ table.dataTable thead > tr > td.sorting_desc_disabled:after { opacity: 0.125; right: 10px; line-height: 9px; - font-size: 0.9em; + font-size: 0.8em; } table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > td.sorting:before, @@ -72,7 +72,7 @@ table.dataTable thead > tr > td.sorting_desc:before, table.dataTable thead > tr > td.sorting_asc_disabled:before, table.dataTable thead > tr > td.sorting_desc_disabled:before { bottom: 50%; - content: "▴"; + content: "▲"; } table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > td.sorting:after, @@ -81,7 +81,7 @@ table.dataTable thead > tr > td.sorting_desc:after, table.dataTable thead > tr > td.sorting_asc_disabled:after, table.dataTable thead > tr > td.sorting_desc_disabled:after { top: 50%; - content: "▾"; + content: "▼"; } table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > td.sorting_asc:before, @@ -287,6 +287,9 @@ table.dataTable > tbody > tr.selected > * { box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9); color: white; } +table.dataTable > tbody > tr.selected a { + color: #090a0b; +} table.dataTable.table-striped > tbody > tr.odd > * { box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.05); } @@ -335,6 +338,9 @@ div.dataTables_wrapper div.dataTables_paginate ul.pagination { white-space: nowrap; justify-content: flex-end; } +div.dataTables_wrapper div.dt-row { + position: relative; +} div.dataTables_scrollHead table.dataTable { margin-bottom: 0 !important; diff --git 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-1.12.1 + * https://datatables.net/download/#bs5/dt-1.13.1 * * Included libraries: - * DataTables 1.12.1 + * DataTables 1.13.1 */ -/*! DataTables 1.12.1 +/*! DataTables 1.13.1 * ©2008-2022 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables - * @version 1.12.1 + * @version 1.13.1 * @author SpryMedia Ltd * @contact www.datatables.net * @copyright SpryMedia Ltd. @@ -1162,6 +1162,10 @@ $( rowOne[0] ).children('th, td').each( function (i, cell) { var col = oSettings.aoColumns[i]; + if (! col) { + _fnLog( oSettings, 0, 'Incorrect column count', 18 ); + } + if ( col.mData === i ) { var sort = a( cell, 'sort' ) || a( cell, 'order' ); var filter = a( cell, 'filter' ) || a( cell, 'search' ); @@ -3166,6 +3170,11 @@ create = nTrIn ? false : true; nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; + + if (! nTd) { + _fnLog( oSettings, 0, 'Incorrect column count', 18 ); + } + nTd._DT_CellIndex = { row: iRow, column: i @@ -3316,10 +3325,16 @@ for ( i=0, ien=cells.length ; i<ien ; i++ ) { column = columns[i]; - column.nTf = cells[i].cell; - if ( column.sClass ) { - $(column.nTf).addClass( column.sClass ); + if (column) { + column.nTf = cells[i].cell; + + if ( column.sClass ) { + $(column.nTf).addClass( column.sClass ); + } + } + else { + _fnLog( oSettings, 0, 'Incorrect column count', 18 ); } } } @@ -5079,6 +5094,10 @@ _fnDraw( settings ); } } + else { + // No change event - paging was called, but no change + _fnCallbackFire( settings, null, 'page-nc', [settings] ); + } return changed; } @@ -8334,8 +8353,12 @@ $(document).on('plugin-init.dt', function (e, context) { var api = new _Api( context ); + + const namespace = 'on-plugin-init'; + const stateSaveParamsEvent = `stateSaveParams.${namespace}`; + const destroyEvent = `destroy.${namespace}`; - api.on( 'stateSaveParams', function ( e, settings, d ) { + api.on( stateSaveParamsEvent, function ( e, settings, d ) { // This could be more compact with the API, but it is a lot faster as a simple // internal loop var idFn = settings.rowIdFn; @@ -8349,7 +8372,11 @@ } d.childRows = ids; - }) + }); + + api.on( destroyEvent, function () { + api.off(`${stateSaveParamsEvent} ${destroyEvent}`); + }); var loaded = api.state.loaded(); @@ -9670,7 +9697,7 @@ * @type string * @default Version number */ - DataTable.version = "1.12.1"; + DataTable.version = "1.13.1"; /** * Private data store, containing all of the settings objects that are @@ -14094,7 +14121,7 @@ * * @type string */ - build:"bs5/dt-1.12.1", + build:"bs5/dt-1.13.1", /** @@ -14732,7 +14759,7 @@ var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; var aria = settings.oLanguage.oAria.paginate || {}; - var btnDisplay, btnClass, counter=0; + var btnDisplay, btnClass; var attach = function( container, buttons ) { var i, ien, node, button, tabIndex; @@ -14807,7 +14834,7 @@ 'class': classes.sPageButton+' '+btnClass, 'aria-controls': settings.sTableId, 'aria-label': aria[ button ], - 'data-dt-idx': counter, + 'data-dt-idx': button, 'tabindex': tabIndex, 'id': idx === 0 && typeof button === 'string' ? settings.sTableId +'_'+ button : @@ -14819,8 +14846,6 @@ _fnBindAction( node, {action: button}, clickHandler ); - - counter++; } } } @@ -15165,7 +15190,7 @@ } } else if (window.luxon) { - dt = format + dt = format && typeof d === 'string' ? window.luxon.DateTime.fromFormat( d, format ) : window.luxon.DateTime.fromISO( d ); @@ -15587,7 +15612,7 @@ $.each( DataTable, function ( prop, val ) { $.fn.DataTable[ prop ] = val; } ); - + return DataTable; })); @@ -15596,14 +15621,6 @@ * 2020 SpryMedia Ltd - datatables.net/license */ -/** - * DataTables integration for Bootstrap 4. This requires Bootstrap 5 and - * DataTables 1.10 or newer. - * - * This file sets the defaults and adds options to DataTables to style its - * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap - * for further information. - */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD @@ -15615,16 +15632,22 @@ // CommonJS module.exports = function (root, $) { if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise root = window; } - if ( ! $ || ! $.fn.dataTable ) { - // Require DataTables, which attaches to jQuery, including - // jQuery if needed and have a $ property so we can access the - // jQuery object that is used - $ = require('datatables.net')(root, $).$; + if ( ! $ ) { + $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window + require('jquery') : + require('jquery')( root ); + } + + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); } + return factory( $, root, root.document ); }; } @@ -15637,11 +15660,21 @@ var DataTable = $.fn.dataTable; + +/** + * DataTables integration for Bootstrap 5. This requires Bootstrap 5 and + * DataTables 1.10 or newer. + * + * This file sets the defaults and adds options to DataTables to style its + * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap + * for further information. + */ + /* Set the defaults for DataTables initialisation */ $.extend( true, DataTable.defaults, { dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" + - "<'row'<'col-sm-12'tr>>" + + "<'row dt-row'<'col-sm-12'tr>>" + "<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>", renderer: 'bootstrap' } ); @@ -15663,7 +15696,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; var aria = settings.oLanguage.oAria.paginate || {}; - var btnDisplay, btnClass, counter=0; + var btnDisplay, btnClass; var attach = function( container, buttons ) { var i, ien, node, button; @@ -15732,7 +15765,7 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu 'href': '#', 'aria-controls': settings.sTableId, 'aria-label': aria[ button ], - 'data-dt-idx': counter, + 'data-dt-idx': button, 'tabindex': settings.iTabIndex, 'class': 'page-link' } ) @@ -15743,13 +15776,12 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu settings.oApi._fnBindAction( node, {action: button}, clickHandler ); - - counter++; } } } }; + var hostEl = $(host); // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame. var activeEl; @@ -15759,17 +15791,26 @@ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, bu // elements, focus is lost on the select button which is bad for // accessibility. So we want to restore focus once the draw has // completed - activeEl = $(host).find(document.activeElement).data('dt-idx'); + activeEl = hostEl.find(document.activeElement).data('dt-idx'); } catch (e) {} + var paginationEl = hostEl.children('ul.pagination'); + + if (paginationEl.length) { + paginationEl.empty(); + } + else { + paginationEl = hostEl.html('<ul/>').children('ul').addClass('pagination'); + } + attach( - $(host).empty().html('<ul class="pagination"/>').children('ul'), + paginationEl, buttons ); if ( activeEl !== undefined ) { - $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); + hostEl.find('[data-dt-idx='+activeEl+']').trigger('focus'); } }; diff --git a/src/static/scripts/identicon.js b/src/static/scripts/identicon.js @@ -1,205 +0,0 @@ -/** - * Identicon.js 2.3.3 - * http://github.com/stewartlord/identicon.js - * - * PNGLib required for PNG output - * http://www.xarg.org/download/pnglib.js - * - * Copyright 2018, Stewart Lord - * Released under the BSD license - * http://www.opensource.org/licenses/bsd-license.php - */ - -(function() { - var PNGlib; - if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - PNGlib = require('./pnglib'); - } else { - PNGlib = window.PNGlib; - } - - var Identicon = function(hash, options){ - if (typeof(hash) !== 'string' || hash.length < 15) { - throw 'A hash of at least 15 characters is required.'; - } - - this.defaults = { - background: [240, 240, 240, 255], - margin: 0.08, - size: 64, - saturation: 0.7, - brightness: 0.5, - format: 'png' - }; - - this.options = typeof(options) === 'object' ? options : this.defaults; - - // backward compatibility with old constructor (hash, size, margin) - if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; } - if (arguments[2]) { this.options.margin = arguments[2]; } - - this.hash = hash - this.background = this.options.background || this.defaults.background; - this.size = this.options.size || this.defaults.size; - this.format = this.options.format || this.defaults.format; - this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin; - - // foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness - var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff; - var saturation = this.options.saturation || this.defaults.saturation; - var brightness = this.options.brightness || this.defaults.brightness; - this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness); - }; - - Identicon.prototype = { - background: null, - foreground: null, - hash: null, - margin: null, - size: null, - format: null, - - image: function(){ - return this.isSvg() - ? new Svg(this.size, this.foreground, this.background) - : new PNGlib(this.size, this.size, 256); - }, - - render: function(){ - var image = this.image(), - size = this.size, - baseMargin = Math.floor(size * this.margin), - cell = Math.floor((size - (baseMargin * 2)) / 5), - margin = Math.floor((size - cell * 5) / 2), - bg = image.color.apply(image, this.background), - fg = image.color.apply(image, this.foreground); - - // the first 15 characters of the hash control the pixels (even/odd) - // they are drawn down the middle first, then mirrored outwards - var i, color; - for (i = 0; i < 15; i++) { - color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg; - if (i < 5) { - this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image); - } else if (i < 10) { - this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); - this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); - } else if (i < 15) { - this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); - this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); - } - } - - return image; - }, - - rectangle: function(x, y, w, h, color, image){ - if (this.isSvg()) { - image.rectangles.push({x: x, y: y, w: w, h: h, color: color}); - } else { - var i, j; - for (i = x; i < x + w; i++) { - for (j = y; j < y + h; j++) { - image.buffer[image.index(i, j)] = color; - } - } - } - }, - - // adapted from: https://gist.github.com/aemkei/1325937 - hsl2rgb: function(h, s, b){ - h *= 6; - s = [ - b += s *= b < .5 ? b : 1 - b, - b - h % 1 * s * 2, - b -= s *= 2, - b, - b + h % 1 * s, - b + s - ]; - - return[ - s[ ~~h % 6 ] * 255, // red - s[ (h|16) % 6 ] * 255, // green - s[ (h|8) % 6 ] * 255 // blue - ]; - }, - - toString: function(raw){ - // backward compatibility with old toString, default to base64 - if (raw) { - return this.render().getDump(); - } else { - return this.render().getBase64(); - } - }, - - isSvg: function(){ - return this.format.match(/svg/i) - } - }; - - var Svg = function(size, foreground, background){ - this.size = size; - this.foreground = this.color.apply(this, foreground); - this.background = this.color.apply(this, background); - this.rectangles = []; - }; - - Svg.prototype = { - size: null, - foreground: null, - background: null, - rectangles: null, - - color: function(r, g, b, a){ - var values = [r, g, b].map(Math.round); - values.push((a >= 0) && (a <= 255) ? a/255 : 1); - return 'rgba(' + values.join(',') + ')'; - }, - - getDump: function(){ - var i, - xml, - rect, - fg = this.foreground, - bg = this.background, - stroke = this.size * 0.005; - - xml = "<svg xmlns='http://www.w3.org/2000/svg'" - + " width='" + this.size + "' height='" + this.size + "'" - + " style='background-color:" + bg + ";'>" - + "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>"; - - for (i = 0; i < this.rectangles.length; i++) { - rect = this.rectangles[i]; - if (rect.color == bg) continue; - xml += "<rect " - + " x='" + rect.x + "'" - + " y='" + rect.y + "'" - + " width='" + rect.w + "'" - + " height='" + rect.h + "'" - + "/>"; - } - xml += "</g></svg>" - - return xml; - }, - - getBase64: function(){ - if ('function' === typeof btoa) { - return btoa(this.getDump()); - } else if (Buffer) { - return new Buffer(this.getDump(), 'binary').toString('base64'); - } else { - throw 'Cannot generate base64 output'; - } - } - }; - - if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - module.exports = Identicon; - } else { - window.Identicon = Identicon; - } -})(); diff --git a/src/static/scripts/jdenticon.js b/src/static/scripts/jdenticon.js @@ -0,0 +1,1462 @@ +/** + * 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) { +'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)); +} + +/** + * @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); +} + +/* 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; +} + +/** + * 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 + } +} + +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, + }); + } +} + +/** + * 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); +}; + +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. + */ +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); +}; + +/** + * 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 + */ +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)) + ]; +} + +/** + * 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; +} + +/** + * 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); +} +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); +} + +/** + * 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. + */ +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 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) + '"/>'; + } +}; + +/** + * 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(); +} + +/** + * 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); + } +}; + +/** + * 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; +} + +// 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); +} + +return jdenticon; + +}); +\ No newline at end of file diff --git a/src/static/templates/admin/base.hbs b/src/static/templates/admin/base.hbs @@ -28,7 +28,7 @@ border: var(--bs-alert-border); } </style> - <script src="{{urlpath}}/vw_static/identicon.js"></script> + <script src="{{urlpath}}/vw_static/jdenticon.js"></script> <script> 'use strict'; @@ -45,11 +45,6 @@ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } - async function identicon(email) { - const hash = await sha256(email); - const data = new Identicon(hash, { size: 48, format: 'svg' }); - return "data:image/svg+xml;base64," + data.toString(); - } function toggleVis(input_id) { const elem = document.getElementById(input_id); const type = elem.getAttribute("type"); diff --git a/src/static/templates/admin/organizations.hbs b/src/static/templates/admin/organizations.hbs @@ -16,7 +16,7 @@ {{#each page_data}} <tr> <td> - <img class="float-start me-2 rounded identicon" data-src="{{Id}}"> + <svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{Id}}"> <div class="float-start"> <strong>{{Name}}</strong> <span class="me-2">({{BillingEmail}})</span> @@ -73,12 +73,6 @@ return false; } - (async () => { - for (let e of document.querySelectorAll("img.identicon")) { - e.src = await identicon(e.dataset.src); - } - })(); - document.addEventListener("DOMContentLoaded", function() { $('#orgs-table').DataTable({ "responsive": true, diff --git a/src/static/templates/admin/users.hbs b/src/static/templates/admin/users.hbs @@ -19,7 +19,7 @@ {{#each page_data}} <tr> <td> - <img class="float-start me-2 rounded identicon" data-src="{{Email}}"> + <svg width="48" height="48" class="float-start me-2 rounded" data-jdenticon-value="{{Email}}"> <div class="float-start"> <strong>{{Name}}</strong> <span class="d-block">{{Email}}</span> @@ -206,12 +206,6 @@ "3": { "name": "Manager", "color": "green" }, }; - (async () => { - for (let e of document.querySelectorAll("img.identicon")) { - e.src = await identicon(e.dataset.src); - } - })(); - document.querySelectorAll("[data-orgtype]").forEach(function (e) { let orgtype = OrgTypes[e.dataset.orgtype]; e.style.backgroundColor = orgtype.color;