diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-06-26 13:40:55 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-06-26 13:40:55 +0200 |
| commit | 811bb04649365b0faaa00b1e0810bb101a4d19b1 (patch) | |
| tree | 581d52faa6e1bf21a1cdfd4da7127a0f95786bad /public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js | |
| parent | a1ddc25da0d2aa79a4d9216ef7792f26233bd38e (diff) | |
Stage 5 click-testing fixes
- Fix link_to :method → button_to for all PUT/DELETE actions
- Add button_to CSS reset to admin.css for visual consistency
- Fix admin layout: replace broken jquery/jquery_ujs pipeline refs with
admin_bundle via sprockets; add sprockets-rails, jquery-ui-rails gems
- Add app/assets/javascripts/admin_bundle.js pipeline manifest
- Fix event_information helper: use safe_join to avoid double-escaping
- Fix nodes_helper: to_s(:db) → to_fs(:db) for event times
- Fix revisions view: eliminate nested forms; diff button uses vanilla JS
to collect radio button values before POST
- Fix config/environments/development.rb and test.rb: cache_classes →
enable_reloading
- Add routing_filter_rails71_patch.rb version guard
- Move LockedByAnotherUser to own file for Zeitwerk autoloading
- Fix Globalize fallbacks via config.i18n.fallbacks in application.rb
Diffstat (limited to 'public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js')
| -rw-r--r-- | public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js | 862 |
1 files changed, 862 insertions, 0 deletions
diff --git a/public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js b/public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js new file mode 100644 index 00000000..ef283f27 --- /dev/null +++ b/public/assets/actiontext-338257afa53cc53f94018abf7a897120d1eb3ee71f39e4641b2058442c9c4dc6.js | |||
| @@ -0,0 +1,862 @@ | |||
| 1 | (function(factory) { | ||
| 2 | typeof define === "function" && define.amd ? define(factory) : factory(); | ||
| 3 | })((function() { | ||
| 4 | "use strict"; | ||
| 5 | var sparkMd5 = { | ||
| 6 | exports: {} | ||
| 7 | }; | ||
| 8 | (function(module, exports) { | ||
| 9 | (function(factory) { | ||
| 10 | { | ||
| 11 | module.exports = factory(); | ||
| 12 | } | ||
| 13 | })((function(undefined$1) { | ||
| 14 | var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ]; | ||
| 15 | function md5cycle(x, k) { | ||
| 16 | var a = x[0], b = x[1], c = x[2], d = x[3]; | ||
| 17 | a += (b & c | ~b & d) + k[0] - 680876936 | 0; | ||
| 18 | a = (a << 7 | a >>> 25) + b | 0; | ||
| 19 | d += (a & b | ~a & c) + k[1] - 389564586 | 0; | ||
| 20 | d = (d << 12 | d >>> 20) + a | 0; | ||
| 21 | c += (d & a | ~d & b) + k[2] + 606105819 | 0; | ||
| 22 | c = (c << 17 | c >>> 15) + d | 0; | ||
| 23 | b += (c & d | ~c & a) + k[3] - 1044525330 | 0; | ||
| 24 | b = (b << 22 | b >>> 10) + c | 0; | ||
| 25 | a += (b & c | ~b & d) + k[4] - 176418897 | 0; | ||
| 26 | a = (a << 7 | a >>> 25) + b | 0; | ||
| 27 | d += (a & b | ~a & c) + k[5] + 1200080426 | 0; | ||
| 28 | d = (d << 12 | d >>> 20) + a | 0; | ||
| 29 | c += (d & a | ~d & b) + k[6] - 1473231341 | 0; | ||
| 30 | c = (c << 17 | c >>> 15) + d | 0; | ||
| 31 | b += (c & d | ~c & a) + k[7] - 45705983 | 0; | ||
| 32 | b = (b << 22 | b >>> 10) + c | 0; | ||
| 33 | a += (b & c | ~b & d) + k[8] + 1770035416 | 0; | ||
| 34 | a = (a << 7 | a >>> 25) + b | 0; | ||
| 35 | d += (a & b | ~a & c) + k[9] - 1958414417 | 0; | ||
| 36 | d = (d << 12 | d >>> 20) + a | 0; | ||
| 37 | c += (d & a | ~d & b) + k[10] - 42063 | 0; | ||
| 38 | c = (c << 17 | c >>> 15) + d | 0; | ||
| 39 | b += (c & d | ~c & a) + k[11] - 1990404162 | 0; | ||
| 40 | b = (b << 22 | b >>> 10) + c | 0; | ||
| 41 | a += (b & c | ~b & d) + k[12] + 1804603682 | 0; | ||
| 42 | a = (a << 7 | a >>> 25) + b | 0; | ||
| 43 | d += (a & b | ~a & c) + k[13] - 40341101 | 0; | ||
| 44 | d = (d << 12 | d >>> 20) + a | 0; | ||
| 45 | c += (d & a | ~d & b) + k[14] - 1502002290 | 0; | ||
| 46 | c = (c << 17 | c >>> 15) + d | 0; | ||
| 47 | b += (c & d | ~c & a) + k[15] + 1236535329 | 0; | ||
| 48 | b = (b << 22 | b >>> 10) + c | 0; | ||
| 49 | a += (b & d | c & ~d) + k[1] - 165796510 | 0; | ||
| 50 | a = (a << 5 | a >>> 27) + b | 0; | ||
| 51 | d += (a & c | b & ~c) + k[6] - 1069501632 | 0; | ||
| 52 | d = (d << 9 | d >>> 23) + a | 0; | ||
| 53 | c += (d & b | a & ~b) + k[11] + 643717713 | 0; | ||
| 54 | c = (c << 14 | c >>> 18) + d | 0; | ||
| 55 | b += (c & a | d & ~a) + k[0] - 373897302 | 0; | ||
| 56 | b = (b << 20 | b >>> 12) + c | 0; | ||
| 57 | a += (b & d | c & ~d) + k[5] - 701558691 | 0; | ||
| 58 | a = (a << 5 | a >>> 27) + b | 0; | ||
| 59 | d += (a & c | b & ~c) + k[10] + 38016083 | 0; | ||
| 60 | d = (d << 9 | d >>> 23) + a | 0; | ||
| 61 | c += (d & b | a & ~b) + k[15] - 660478335 | 0; | ||
| 62 | c = (c << 14 | c >>> 18) + d | 0; | ||
| 63 | b += (c & a | d & ~a) + k[4] - 405537848 | 0; | ||
| 64 | b = (b << 20 | b >>> 12) + c | 0; | ||
| 65 | a += (b & d | c & ~d) + k[9] + 568446438 | 0; | ||
| 66 | a = (a << 5 | a >>> 27) + b | 0; | ||
| 67 | d += (a & c | b & ~c) + k[14] - 1019803690 | 0; | ||
| 68 | d = (d << 9 | d >>> 23) + a | 0; | ||
| 69 | c += (d & b | a & ~b) + k[3] - 187363961 | 0; | ||
| 70 | c = (c << 14 | c >>> 18) + d | 0; | ||
| 71 | b += (c & a | d & ~a) + k[8] + 1163531501 | 0; | ||
| 72 | b = (b << 20 | b >>> 12) + c | 0; | ||
| 73 | a += (b & d | c & ~d) + k[13] - 1444681467 | 0; | ||
| 74 | a = (a << 5 | a >>> 27) + b | 0; | ||
| 75 | d += (a & c | b & ~c) + k[2] - 51403784 | 0; | ||
| 76 | d = (d << 9 | d >>> 23) + a | 0; | ||
| 77 | c += (d & b | a & ~b) + k[7] + 1735328473 | 0; | ||
| 78 | c = (c << 14 | c >>> 18) + d | 0; | ||
| 79 | b += (c & a | d & ~a) + k[12] - 1926607734 | 0; | ||
| 80 | b = (b << 20 | b >>> 12) + c | 0; | ||
| 81 | a += (b ^ c ^ d) + k[5] - 378558 | 0; | ||
| 82 | a = (a << 4 | a >>> 28) + b | 0; | ||
| 83 | d += (a ^ b ^ c) + k[8] - 2022574463 | 0; | ||
| 84 | d = (d << 11 | d >>> 21) + a | 0; | ||
| 85 | c += (d ^ a ^ b) + k[11] + 1839030562 | 0; | ||
| 86 | c = (c << 16 | c >>> 16) + d | 0; | ||
| 87 | b += (c ^ d ^ a) + k[14] - 35309556 | 0; | ||
| 88 | b = (b << 23 | b >>> 9) + c | 0; | ||
| 89 | a += (b ^ c ^ d) + k[1] - 1530992060 | 0; | ||
| 90 | a = (a << 4 | a >>> 28) + b | 0; | ||
| 91 | d += (a ^ b ^ c) + k[4] + 1272893353 | 0; | ||
| 92 | d = (d << 11 | d >>> 21) + a | 0; | ||
| 93 | c += (d ^ a ^ b) + k[7] - 155497632 | 0; | ||
| 94 | c = (c << 16 | c >>> 16) + d | 0; | ||
| 95 | b += (c ^ d ^ a) + k[10] - 1094730640 | 0; | ||
| 96 | b = (b << 23 | b >>> 9) + c | 0; | ||
| 97 | a += (b ^ c ^ d) + k[13] + 681279174 | 0; | ||
| 98 | a = (a << 4 | a >>> 28) + b | 0; | ||
| 99 | d += (a ^ b ^ c) + k[0] - 358537222 | 0; | ||
| 100 | d = (d << 11 | d >>> 21) + a | 0; | ||
| 101 | c += (d ^ a ^ b) + k[3] - 722521979 | 0; | ||
| 102 | c = (c << 16 | c >>> 16) + d | 0; | ||
| 103 | b += (c ^ d ^ a) + k[6] + 76029189 | 0; | ||
| 104 | b = (b << 23 | b >>> 9) + c | 0; | ||
| 105 | a += (b ^ c ^ d) + k[9] - 640364487 | 0; | ||
| 106 | a = (a << 4 | a >>> 28) + b | 0; | ||
| 107 | d += (a ^ b ^ c) + k[12] - 421815835 | 0; | ||
| 108 | d = (d << 11 | d >>> 21) + a | 0; | ||
| 109 | c += (d ^ a ^ b) + k[15] + 530742520 | 0; | ||
| 110 | c = (c << 16 | c >>> 16) + d | 0; | ||
| 111 | b += (c ^ d ^ a) + k[2] - 995338651 | 0; | ||
| 112 | b = (b << 23 | b >>> 9) + c | 0; | ||
| 113 | a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; | ||
| 114 | a = (a << 6 | a >>> 26) + b | 0; | ||
| 115 | d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; | ||
| 116 | d = (d << 10 | d >>> 22) + a | 0; | ||
| 117 | c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; | ||
| 118 | c = (c << 15 | c >>> 17) + d | 0; | ||
| 119 | b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; | ||
| 120 | b = (b << 21 | b >>> 11) + c | 0; | ||
| 121 | a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; | ||
| 122 | a = (a << 6 | a >>> 26) + b | 0; | ||
| 123 | d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; | ||
| 124 | d = (d << 10 | d >>> 22) + a | 0; | ||
| 125 | c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; | ||
| 126 | c = (c << 15 | c >>> 17) + d | 0; | ||
| 127 | b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; | ||
| 128 | b = (b << 21 | b >>> 11) + c | 0; | ||
| 129 | a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; | ||
| 130 | a = (a << 6 | a >>> 26) + b | 0; | ||
| 131 | d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; | ||
| 132 | d = (d << 10 | d >>> 22) + a | 0; | ||
| 133 | c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; | ||
| 134 | c = (c << 15 | c >>> 17) + d | 0; | ||
| 135 | b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; | ||
| 136 | b = (b << 21 | b >>> 11) + c | 0; | ||
| 137 | a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; | ||
| 138 | a = (a << 6 | a >>> 26) + b | 0; | ||
| 139 | d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; | ||
| 140 | d = (d << 10 | d >>> 22) + a | 0; | ||
| 141 | c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; | ||
| 142 | c = (c << 15 | c >>> 17) + d | 0; | ||
| 143 | b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; | ||
| 144 | b = (b << 21 | b >>> 11) + c | 0; | ||
| 145 | x[0] = a + x[0] | 0; | ||
| 146 | x[1] = b + x[1] | 0; | ||
| 147 | x[2] = c + x[2] | 0; | ||
| 148 | x[3] = d + x[3] | 0; | ||
| 149 | } | ||
| 150 | function md5blk(s) { | ||
| 151 | var md5blks = [], i; | ||
| 152 | for (i = 0; i < 64; i += 4) { | ||
| 153 | md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); | ||
| 154 | } | ||
| 155 | return md5blks; | ||
| 156 | } | ||
| 157 | function md5blk_array(a) { | ||
| 158 | var md5blks = [], i; | ||
| 159 | for (i = 0; i < 64; i += 4) { | ||
| 160 | md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); | ||
| 161 | } | ||
| 162 | return md5blks; | ||
| 163 | } | ||
| 164 | function md51(s) { | ||
| 165 | var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; | ||
| 166 | for (i = 64; i <= n; i += 64) { | ||
| 167 | md5cycle(state, md5blk(s.substring(i - 64, i))); | ||
| 168 | } | ||
| 169 | s = s.substring(i - 64); | ||
| 170 | length = s.length; | ||
| 171 | tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; | ||
| 172 | for (i = 0; i < length; i += 1) { | ||
| 173 | tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); | ||
| 174 | } | ||
| 175 | tail[i >> 2] |= 128 << (i % 4 << 3); | ||
| 176 | if (i > 55) { | ||
| 177 | md5cycle(state, tail); | ||
| 178 | for (i = 0; i < 16; i += 1) { | ||
| 179 | tail[i] = 0; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | tmp = n * 8; | ||
| 183 | tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); | ||
| 184 | lo = parseInt(tmp[2], 16); | ||
| 185 | hi = parseInt(tmp[1], 16) || 0; | ||
| 186 | tail[14] = lo; | ||
| 187 | tail[15] = hi; | ||
| 188 | md5cycle(state, tail); | ||
| 189 | return state; | ||
| 190 | } | ||
| 191 | function md51_array(a) { | ||
| 192 | var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; | ||
| 193 | for (i = 64; i <= n; i += 64) { | ||
| 194 | md5cycle(state, md5blk_array(a.subarray(i - 64, i))); | ||
| 195 | } | ||
| 196 | a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0); | ||
| 197 | length = a.length; | ||
| 198 | tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; | ||
| 199 | for (i = 0; i < length; i += 1) { | ||
| 200 | tail[i >> 2] |= a[i] << (i % 4 << 3); | ||
| 201 | } | ||
| 202 | tail[i >> 2] |= 128 << (i % 4 << 3); | ||
| 203 | if (i > 55) { | ||
| 204 | md5cycle(state, tail); | ||
| 205 | for (i = 0; i < 16; i += 1) { | ||
| 206 | tail[i] = 0; | ||
| 207 | } | ||
| 208 | } | ||
| 209 | tmp = n * 8; | ||
| 210 | tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); | ||
| 211 | lo = parseInt(tmp[2], 16); | ||
| 212 | hi = parseInt(tmp[1], 16) || 0; | ||
| 213 | tail[14] = lo; | ||
| 214 | tail[15] = hi; | ||
| 215 | md5cycle(state, tail); | ||
| 216 | return state; | ||
| 217 | } | ||
| 218 | function rhex(n) { | ||
| 219 | var s = "", j; | ||
| 220 | for (j = 0; j < 4; j += 1) { | ||
| 221 | s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15]; | ||
| 222 | } | ||
| 223 | return s; | ||
| 224 | } | ||
| 225 | function hex(x) { | ||
| 226 | var i; | ||
| 227 | for (i = 0; i < x.length; i += 1) { | ||
| 228 | x[i] = rhex(x[i]); | ||
| 229 | } | ||
| 230 | return x.join(""); | ||
| 231 | } | ||
| 232 | if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ; | ||
| 233 | if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) { | ||
| 234 | (function() { | ||
| 235 | function clamp(val, length) { | ||
| 236 | val = val | 0 || 0; | ||
| 237 | if (val < 0) { | ||
| 238 | return Math.max(val + length, 0); | ||
| 239 | } | ||
| 240 | return Math.min(val, length); | ||
| 241 | } | ||
| 242 | ArrayBuffer.prototype.slice = function(from, to) { | ||
| 243 | var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray; | ||
| 244 | if (to !== undefined$1) { | ||
| 245 | end = clamp(to, length); | ||
| 246 | } | ||
| 247 | if (begin > end) { | ||
| 248 | return new ArrayBuffer(0); | ||
| 249 | } | ||
| 250 | num = end - begin; | ||
| 251 | target = new ArrayBuffer(num); | ||
| 252 | targetArray = new Uint8Array(target); | ||
| 253 | sourceArray = new Uint8Array(this, begin, num); | ||
| 254 | targetArray.set(sourceArray); | ||
| 255 | return target; | ||
| 256 | }; | ||
| 257 | })(); | ||
| 258 | } | ||
| 259 | function toUtf8(str) { | ||
| 260 | if (/[\u0080-\uFFFF]/.test(str)) { | ||
| 261 | str = unescape(encodeURIComponent(str)); | ||
| 262 | } | ||
| 263 | return str; | ||
| 264 | } | ||
| 265 | function utf8Str2ArrayBuffer(str, returnUInt8Array) { | ||
| 266 | var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i; | ||
| 267 | for (i = 0; i < length; i += 1) { | ||
| 268 | arr[i] = str.charCodeAt(i); | ||
| 269 | } | ||
| 270 | return returnUInt8Array ? arr : buff; | ||
| 271 | } | ||
| 272 | function arrayBuffer2Utf8Str(buff) { | ||
| 273 | return String.fromCharCode.apply(null, new Uint8Array(buff)); | ||
| 274 | } | ||
| 275 | function concatenateArrayBuffers(first, second, returnUInt8Array) { | ||
| 276 | var result = new Uint8Array(first.byteLength + second.byteLength); | ||
| 277 | result.set(new Uint8Array(first)); | ||
| 278 | result.set(new Uint8Array(second), first.byteLength); | ||
| 279 | return returnUInt8Array ? result : result.buffer; | ||
| 280 | } | ||
| 281 | function hexToBinaryString(hex) { | ||
| 282 | var bytes = [], length = hex.length, x; | ||
| 283 | for (x = 0; x < length - 1; x += 2) { | ||
| 284 | bytes.push(parseInt(hex.substr(x, 2), 16)); | ||
| 285 | } | ||
| 286 | return String.fromCharCode.apply(String, bytes); | ||
| 287 | } | ||
| 288 | function SparkMD5() { | ||
| 289 | this.reset(); | ||
| 290 | } | ||
| 291 | SparkMD5.prototype.append = function(str) { | ||
| 292 | this.appendBinary(toUtf8(str)); | ||
| 293 | return this; | ||
| 294 | }; | ||
| 295 | SparkMD5.prototype.appendBinary = function(contents) { | ||
| 296 | this._buff += contents; | ||
| 297 | this._length += contents.length; | ||
| 298 | var length = this._buff.length, i; | ||
| 299 | for (i = 64; i <= length; i += 64) { | ||
| 300 | md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); | ||
| 301 | } | ||
| 302 | this._buff = this._buff.substring(i - 64); | ||
| 303 | return this; | ||
| 304 | }; | ||
| 305 | SparkMD5.prototype.end = function(raw) { | ||
| 306 | var buff = this._buff, length = buff.length, i, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], ret; | ||
| 307 | for (i = 0; i < length; i += 1) { | ||
| 308 | tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3); | ||
| 309 | } | ||
| 310 | this._finish(tail, length); | ||
| 311 | ret = hex(this._hash); | ||
| 312 | if (raw) { | ||
| 313 | ret = hexToBinaryString(ret); | ||
| 314 | } | ||
| 315 | this.reset(); | ||
| 316 | return ret; | ||
| 317 | }; | ||
| 318 | SparkMD5.prototype.reset = function() { | ||
| 319 | this._buff = ""; | ||
| 320 | this._length = 0; | ||
| 321 | this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; | ||
| 322 | return this; | ||
| 323 | }; | ||
| 324 | SparkMD5.prototype.getState = function() { | ||
| 325 | return { | ||
| 326 | buff: this._buff, | ||
| 327 | length: this._length, | ||
| 328 | hash: this._hash.slice() | ||
| 329 | }; | ||
| 330 | }; | ||
| 331 | SparkMD5.prototype.setState = function(state) { | ||
| 332 | this._buff = state.buff; | ||
| 333 | this._length = state.length; | ||
| 334 | this._hash = state.hash; | ||
| 335 | return this; | ||
| 336 | }; | ||
| 337 | SparkMD5.prototype.destroy = function() { | ||
| 338 | delete this._hash; | ||
| 339 | delete this._buff; | ||
| 340 | delete this._length; | ||
| 341 | }; | ||
| 342 | SparkMD5.prototype._finish = function(tail, length) { | ||
| 343 | var i = length, tmp, lo, hi; | ||
| 344 | tail[i >> 2] |= 128 << (i % 4 << 3); | ||
| 345 | if (i > 55) { | ||
| 346 | md5cycle(this._hash, tail); | ||
| 347 | for (i = 0; i < 16; i += 1) { | ||
| 348 | tail[i] = 0; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | tmp = this._length * 8; | ||
| 352 | tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); | ||
| 353 | lo = parseInt(tmp[2], 16); | ||
| 354 | hi = parseInt(tmp[1], 16) || 0; | ||
| 355 | tail[14] = lo; | ||
| 356 | tail[15] = hi; | ||
| 357 | md5cycle(this._hash, tail); | ||
| 358 | }; | ||
| 359 | SparkMD5.hash = function(str, raw) { | ||
| 360 | return SparkMD5.hashBinary(toUtf8(str), raw); | ||
| 361 | }; | ||
| 362 | SparkMD5.hashBinary = function(content, raw) { | ||
| 363 | var hash = md51(content), ret = hex(hash); | ||
| 364 | return raw ? hexToBinaryString(ret) : ret; | ||
| 365 | }; | ||
| 366 | SparkMD5.ArrayBuffer = function() { | ||
| 367 | this.reset(); | ||
| 368 | }; | ||
| 369 | SparkMD5.ArrayBuffer.prototype.append = function(arr) { | ||
| 370 | var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i; | ||
| 371 | this._length += arr.byteLength; | ||
| 372 | for (i = 64; i <= length; i += 64) { | ||
| 373 | md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); | ||
| 374 | } | ||
| 375 | this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); | ||
| 376 | return this; | ||
| 377 | }; | ||
| 378 | SparkMD5.ArrayBuffer.prototype.end = function(raw) { | ||
| 379 | var buff = this._buff, length = buff.length, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, ret; | ||
| 380 | for (i = 0; i < length; i += 1) { | ||
| 381 | tail[i >> 2] |= buff[i] << (i % 4 << 3); | ||
| 382 | } | ||
| 383 | this._finish(tail, length); | ||
| 384 | ret = hex(this._hash); | ||
| 385 | if (raw) { | ||
| 386 | ret = hexToBinaryString(ret); | ||
| 387 | } | ||
| 388 | this.reset(); | ||
| 389 | return ret; | ||
| 390 | }; | ||
| 391 | SparkMD5.ArrayBuffer.prototype.reset = function() { | ||
| 392 | this._buff = new Uint8Array(0); | ||
| 393 | this._length = 0; | ||
| 394 | this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; | ||
| 395 | return this; | ||
| 396 | }; | ||
| 397 | SparkMD5.ArrayBuffer.prototype.getState = function() { | ||
| 398 | var state = SparkMD5.prototype.getState.call(this); | ||
| 399 | state.buff = arrayBuffer2Utf8Str(state.buff); | ||
| 400 | return state; | ||
| 401 | }; | ||
| 402 | SparkMD5.ArrayBuffer.prototype.setState = function(state) { | ||
| 403 | state.buff = utf8Str2ArrayBuffer(state.buff, true); | ||
| 404 | return SparkMD5.prototype.setState.call(this, state); | ||
| 405 | }; | ||
| 406 | SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; | ||
| 407 | SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; | ||
| 408 | SparkMD5.ArrayBuffer.hash = function(arr, raw) { | ||
| 409 | var hash = md51_array(new Uint8Array(arr)), ret = hex(hash); | ||
| 410 | return raw ? hexToBinaryString(ret) : ret; | ||
| 411 | }; | ||
| 412 | return SparkMD5; | ||
| 413 | })); | ||
| 414 | })(sparkMd5); | ||
| 415 | var SparkMD5 = sparkMd5.exports; | ||
| 416 | const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; | ||
| 417 | class FileChecksum { | ||
| 418 | static create(file, callback) { | ||
| 419 | const instance = new FileChecksum(file); | ||
| 420 | instance.create(callback); | ||
| 421 | } | ||
| 422 | constructor(file) { | ||
| 423 | this.file = file; | ||
| 424 | this.chunkSize = 2097152; | ||
| 425 | this.chunkCount = Math.ceil(this.file.size / this.chunkSize); | ||
| 426 | this.chunkIndex = 0; | ||
| 427 | } | ||
| 428 | create(callback) { | ||
| 429 | this.callback = callback; | ||
| 430 | this.md5Buffer = new SparkMD5.ArrayBuffer; | ||
| 431 | this.fileReader = new FileReader; | ||
| 432 | this.fileReader.addEventListener("load", (event => this.fileReaderDidLoad(event))); | ||
| 433 | this.fileReader.addEventListener("error", (event => this.fileReaderDidError(event))); | ||
| 434 | this.readNextChunk(); | ||
| 435 | } | ||
| 436 | fileReaderDidLoad(event) { | ||
| 437 | this.md5Buffer.append(event.target.result); | ||
| 438 | if (!this.readNextChunk()) { | ||
| 439 | const binaryDigest = this.md5Buffer.end(true); | ||
| 440 | const base64digest = btoa(binaryDigest); | ||
| 441 | this.callback(null, base64digest); | ||
| 442 | } | ||
| 443 | } | ||
| 444 | fileReaderDidError(event) { | ||
| 445 | this.callback(`Error reading ${this.file.name}`); | ||
| 446 | } | ||
| 447 | readNextChunk() { | ||
| 448 | if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) { | ||
| 449 | const start = this.chunkIndex * this.chunkSize; | ||
| 450 | const end = Math.min(start + this.chunkSize, this.file.size); | ||
| 451 | const bytes = fileSlice.call(this.file, start, end); | ||
| 452 | this.fileReader.readAsArrayBuffer(bytes); | ||
| 453 | this.chunkIndex++; | ||
| 454 | return true; | ||
| 455 | } else { | ||
| 456 | return false; | ||
| 457 | } | ||
| 458 | } | ||
| 459 | } | ||
| 460 | function getMetaValue(name) { | ||
| 461 | const element = findElement(document.head, `meta[name="${name}"]`); | ||
| 462 | if (element) { | ||
| 463 | return element.getAttribute("content"); | ||
| 464 | } | ||
| 465 | } | ||
| 466 | function findElements(root, selector) { | ||
| 467 | if (typeof root == "string") { | ||
| 468 | selector = root; | ||
| 469 | root = document; | ||
| 470 | } | ||
| 471 | const elements = root.querySelectorAll(selector); | ||
| 472 | return toArray(elements); | ||
| 473 | } | ||
| 474 | function findElement(root, selector) { | ||
| 475 | if (typeof root == "string") { | ||
| 476 | selector = root; | ||
| 477 | root = document; | ||
| 478 | } | ||
| 479 | return root.querySelector(selector); | ||
| 480 | } | ||
| 481 | function dispatchEvent(element, type, eventInit = {}) { | ||
| 482 | const {disabled: disabled} = element; | ||
| 483 | const {bubbles: bubbles, cancelable: cancelable, detail: detail} = eventInit; | ||
| 484 | const event = document.createEvent("Event"); | ||
| 485 | event.initEvent(type, bubbles || true, cancelable || true); | ||
| 486 | event.detail = detail || {}; | ||
| 487 | try { | ||
| 488 | element.disabled = false; | ||
| 489 | element.dispatchEvent(event); | ||
| 490 | } finally { | ||
| 491 | element.disabled = disabled; | ||
| 492 | } | ||
| 493 | return event; | ||
| 494 | } | ||
| 495 | function toArray(value) { | ||
| 496 | if (Array.isArray(value)) { | ||
| 497 | return value; | ||
| 498 | } else if (Array.from) { | ||
| 499 | return Array.from(value); | ||
| 500 | } else { | ||
| 501 | return [].slice.call(value); | ||
| 502 | } | ||
| 503 | } | ||
| 504 | class BlobRecord { | ||
| 505 | constructor(file, checksum, url, customHeaders = {}) { | ||
| 506 | this.file = file; | ||
| 507 | this.attributes = { | ||
| 508 | filename: file.name, | ||
| 509 | content_type: file.type || "application/octet-stream", | ||
| 510 | byte_size: file.size, | ||
| 511 | checksum: checksum | ||
| 512 | }; | ||
| 513 | this.xhr = new XMLHttpRequest; | ||
| 514 | this.xhr.open("POST", url, true); | ||
| 515 | this.xhr.responseType = "json"; | ||
| 516 | this.xhr.setRequestHeader("Content-Type", "application/json"); | ||
| 517 | this.xhr.setRequestHeader("Accept", "application/json"); | ||
| 518 | this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); | ||
| 519 | Object.keys(customHeaders).forEach((headerKey => { | ||
| 520 | this.xhr.setRequestHeader(headerKey, customHeaders[headerKey]); | ||
| 521 | })); | ||
| 522 | const csrfToken = getMetaValue("csrf-token"); | ||
| 523 | if (csrfToken != undefined) { | ||
| 524 | this.xhr.setRequestHeader("X-CSRF-Token", csrfToken); | ||
| 525 | } | ||
| 526 | this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); | ||
| 527 | this.xhr.addEventListener("error", (event => this.requestDidError(event))); | ||
| 528 | } | ||
| 529 | get status() { | ||
| 530 | return this.xhr.status; | ||
| 531 | } | ||
| 532 | get response() { | ||
| 533 | const {responseType: responseType, response: response} = this.xhr; | ||
| 534 | if (responseType == "json") { | ||
| 535 | return response; | ||
| 536 | } else { | ||
| 537 | return JSON.parse(response); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | create(callback) { | ||
| 541 | this.callback = callback; | ||
| 542 | this.xhr.send(JSON.stringify({ | ||
| 543 | blob: this.attributes | ||
| 544 | })); | ||
| 545 | } | ||
| 546 | requestDidLoad(event) { | ||
| 547 | if (this.status >= 200 && this.status < 300) { | ||
| 548 | const {response: response} = this; | ||
| 549 | const {direct_upload: direct_upload} = response; | ||
| 550 | delete response.direct_upload; | ||
| 551 | this.attributes = response; | ||
| 552 | this.directUploadData = direct_upload; | ||
| 553 | this.callback(null, this.toJSON()); | ||
| 554 | } else { | ||
| 555 | this.requestDidError(event); | ||
| 556 | } | ||
| 557 | } | ||
| 558 | requestDidError(event) { | ||
| 559 | this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`); | ||
| 560 | } | ||
| 561 | toJSON() { | ||
| 562 | const result = {}; | ||
| 563 | for (const key in this.attributes) { | ||
| 564 | result[key] = this.attributes[key]; | ||
| 565 | } | ||
| 566 | return result; | ||
| 567 | } | ||
| 568 | } | ||
| 569 | class BlobUpload { | ||
| 570 | constructor(blob) { | ||
| 571 | this.blob = blob; | ||
| 572 | this.file = blob.file; | ||
| 573 | const {url: url, headers: headers} = blob.directUploadData; | ||
| 574 | this.xhr = new XMLHttpRequest; | ||
| 575 | this.xhr.open("PUT", url, true); | ||
| 576 | this.xhr.responseType = "text"; | ||
| 577 | for (const key in headers) { | ||
| 578 | this.xhr.setRequestHeader(key, headers[key]); | ||
| 579 | } | ||
| 580 | this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); | ||
| 581 | this.xhr.addEventListener("error", (event => this.requestDidError(event))); | ||
| 582 | } | ||
| 583 | create(callback) { | ||
| 584 | this.callback = callback; | ||
| 585 | this.xhr.send(this.file.slice()); | ||
| 586 | } | ||
| 587 | requestDidLoad(event) { | ||
| 588 | const {status: status, response: response} = this.xhr; | ||
| 589 | if (status >= 200 && status < 300) { | ||
| 590 | this.callback(null, response); | ||
| 591 | } else { | ||
| 592 | this.requestDidError(event); | ||
| 593 | } | ||
| 594 | } | ||
| 595 | requestDidError(event) { | ||
| 596 | this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | let id = 0; | ||
| 600 | class DirectUpload { | ||
| 601 | constructor(file, url, delegate, customHeaders = {}) { | ||
| 602 | this.id = ++id; | ||
| 603 | this.file = file; | ||
| 604 | this.url = url; | ||
| 605 | this.delegate = delegate; | ||
| 606 | this.customHeaders = customHeaders; | ||
| 607 | } | ||
| 608 | create(callback) { | ||
| 609 | FileChecksum.create(this.file, ((error, checksum) => { | ||
| 610 | if (error) { | ||
| 611 | callback(error); | ||
| 612 | return; | ||
| 613 | } | ||
| 614 | const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders); | ||
| 615 | notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr); | ||
| 616 | blob.create((error => { | ||
| 617 | if (error) { | ||
| 618 | callback(error); | ||
| 619 | } else { | ||
| 620 | const upload = new BlobUpload(blob); | ||
| 621 | notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr); | ||
| 622 | upload.create((error => { | ||
| 623 | if (error) { | ||
| 624 | callback(error); | ||
| 625 | } else { | ||
| 626 | callback(null, blob.toJSON()); | ||
| 627 | } | ||
| 628 | })); | ||
| 629 | } | ||
| 630 | })); | ||
| 631 | })); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | function notify(object, methodName, ...messages) { | ||
| 635 | if (object && typeof object[methodName] == "function") { | ||
| 636 | return object[methodName](...messages); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | class DirectUploadController { | ||
| 640 | constructor(input, file) { | ||
| 641 | this.input = input; | ||
| 642 | this.file = file; | ||
| 643 | this.directUpload = new DirectUpload(this.file, this.url, this); | ||
| 644 | this.dispatch("initialize"); | ||
| 645 | } | ||
| 646 | start(callback) { | ||
| 647 | const hiddenInput = document.createElement("input"); | ||
| 648 | hiddenInput.type = "hidden"; | ||
| 649 | hiddenInput.name = this.input.name; | ||
| 650 | this.input.insertAdjacentElement("beforebegin", hiddenInput); | ||
| 651 | this.dispatch("start"); | ||
| 652 | this.directUpload.create(((error, attributes) => { | ||
| 653 | if (error) { | ||
| 654 | hiddenInput.parentNode.removeChild(hiddenInput); | ||
| 655 | this.dispatchError(error); | ||
| 656 | } else { | ||
| 657 | hiddenInput.value = attributes.signed_id; | ||
| 658 | } | ||
| 659 | this.dispatch("end"); | ||
| 660 | callback(error); | ||
| 661 | })); | ||
| 662 | } | ||
| 663 | uploadRequestDidProgress(event) { | ||
| 664 | const progress = event.loaded / event.total * 100; | ||
| 665 | if (progress) { | ||
| 666 | this.dispatch("progress", { | ||
| 667 | progress: progress | ||
| 668 | }); | ||
| 669 | } | ||
| 670 | } | ||
| 671 | get url() { | ||
| 672 | return this.input.getAttribute("data-direct-upload-url"); | ||
| 673 | } | ||
| 674 | dispatch(name, detail = {}) { | ||
| 675 | detail.file = this.file; | ||
| 676 | detail.id = this.directUpload.id; | ||
| 677 | return dispatchEvent(this.input, `direct-upload:${name}`, { | ||
| 678 | detail: detail | ||
| 679 | }); | ||
| 680 | } | ||
| 681 | dispatchError(error) { | ||
| 682 | const event = this.dispatch("error", { | ||
| 683 | error: error | ||
| 684 | }); | ||
| 685 | if (!event.defaultPrevented) { | ||
| 686 | alert(error); | ||
| 687 | } | ||
| 688 | } | ||
| 689 | directUploadWillCreateBlobWithXHR(xhr) { | ||
| 690 | this.dispatch("before-blob-request", { | ||
| 691 | xhr: xhr | ||
| 692 | }); | ||
| 693 | } | ||
| 694 | directUploadWillStoreFileWithXHR(xhr) { | ||
| 695 | this.dispatch("before-storage-request", { | ||
| 696 | xhr: xhr | ||
| 697 | }); | ||
| 698 | xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event))); | ||
| 699 | } | ||
| 700 | } | ||
| 701 | const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])"; | ||
| 702 | class DirectUploadsController { | ||
| 703 | constructor(form) { | ||
| 704 | this.form = form; | ||
| 705 | this.inputs = findElements(form, inputSelector).filter((input => input.files.length)); | ||
| 706 | } | ||
| 707 | start(callback) { | ||
| 708 | const controllers = this.createDirectUploadControllers(); | ||
| 709 | const startNextController = () => { | ||
| 710 | const controller = controllers.shift(); | ||
| 711 | if (controller) { | ||
| 712 | controller.start((error => { | ||
| 713 | if (error) { | ||
| 714 | callback(error); | ||
| 715 | this.dispatch("end"); | ||
| 716 | } else { | ||
| 717 | startNextController(); | ||
| 718 | } | ||
| 719 | })); | ||
| 720 | } else { | ||
| 721 | callback(); | ||
| 722 | this.dispatch("end"); | ||
| 723 | } | ||
| 724 | }; | ||
| 725 | this.dispatch("start"); | ||
| 726 | startNextController(); | ||
| 727 | } | ||
| 728 | createDirectUploadControllers() { | ||
| 729 | const controllers = []; | ||
| 730 | this.inputs.forEach((input => { | ||
| 731 | toArray(input.files).forEach((file => { | ||
| 732 | const controller = new DirectUploadController(input, file); | ||
| 733 | controllers.push(controller); | ||
| 734 | })); | ||
| 735 | })); | ||
| 736 | return controllers; | ||
| 737 | } | ||
| 738 | dispatch(name, detail = {}) { | ||
| 739 | return dispatchEvent(this.form, `direct-uploads:${name}`, { | ||
| 740 | detail: detail | ||
| 741 | }); | ||
| 742 | } | ||
| 743 | } | ||
| 744 | const processingAttribute = "data-direct-uploads-processing"; | ||
| 745 | const submitButtonsByForm = new WeakMap; | ||
| 746 | let started = false; | ||
| 747 | function start() { | ||
| 748 | if (!started) { | ||
| 749 | started = true; | ||
| 750 | document.addEventListener("click", didClick, true); | ||
| 751 | document.addEventListener("submit", didSubmitForm, true); | ||
| 752 | document.addEventListener("ajax:before", didSubmitRemoteElement); | ||
| 753 | } | ||
| 754 | } | ||
| 755 | function didClick(event) { | ||
| 756 | const button = event.target.closest("button, input"); | ||
| 757 | if (button && button.type === "submit" && button.form) { | ||
| 758 | submitButtonsByForm.set(button.form, button); | ||
| 759 | } | ||
| 760 | } | ||
| 761 | function didSubmitForm(event) { | ||
| 762 | handleFormSubmissionEvent(event); | ||
| 763 | } | ||
| 764 | function didSubmitRemoteElement(event) { | ||
| 765 | if (event.target.tagName == "FORM") { | ||
| 766 | handleFormSubmissionEvent(event); | ||
| 767 | } | ||
| 768 | } | ||
| 769 | function handleFormSubmissionEvent(event) { | ||
| 770 | const form = event.target; | ||
| 771 | if (form.hasAttribute(processingAttribute)) { | ||
| 772 | event.preventDefault(); | ||
| 773 | return; | ||
| 774 | } | ||
| 775 | const controller = new DirectUploadsController(form); | ||
| 776 | const {inputs: inputs} = controller; | ||
| 777 | if (inputs.length) { | ||
| 778 | event.preventDefault(); | ||
| 779 | form.setAttribute(processingAttribute, ""); | ||
| 780 | inputs.forEach(disable); | ||
| 781 | controller.start((error => { | ||
| 782 | form.removeAttribute(processingAttribute); | ||
| 783 | if (error) { | ||
| 784 | inputs.forEach(enable); | ||
| 785 | } else { | ||
| 786 | submitForm(form); | ||
| 787 | } | ||
| 788 | })); | ||
| 789 | } | ||
| 790 | } | ||
| 791 | function submitForm(form) { | ||
| 792 | let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]"); | ||
| 793 | if (button) { | ||
| 794 | const {disabled: disabled} = button; | ||
| 795 | button.disabled = false; | ||
| 796 | button.focus(); | ||
| 797 | button.click(); | ||
| 798 | button.disabled = disabled; | ||
| 799 | } else { | ||
| 800 | button = document.createElement("input"); | ||
| 801 | button.type = "submit"; | ||
| 802 | button.style.display = "none"; | ||
| 803 | form.appendChild(button); | ||
| 804 | button.click(); | ||
| 805 | form.removeChild(button); | ||
| 806 | } | ||
| 807 | submitButtonsByForm.delete(form); | ||
| 808 | } | ||
| 809 | function disable(input) { | ||
| 810 | input.disabled = true; | ||
| 811 | } | ||
| 812 | function enable(input) { | ||
| 813 | input.disabled = false; | ||
| 814 | } | ||
| 815 | function autostart() { | ||
| 816 | if (window.ActiveStorage) { | ||
| 817 | start(); | ||
| 818 | } | ||
| 819 | } | ||
| 820 | setTimeout(autostart, 1); | ||
| 821 | class AttachmentUpload { | ||
| 822 | constructor(attachment, element) { | ||
| 823 | this.attachment = attachment; | ||
| 824 | this.element = element; | ||
| 825 | this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this); | ||
| 826 | } | ||
| 827 | start() { | ||
| 828 | this.directUpload.create(this.directUploadDidComplete.bind(this)); | ||
| 829 | } | ||
| 830 | directUploadWillStoreFileWithXHR(xhr) { | ||
| 831 | xhr.upload.addEventListener("progress", (event => { | ||
| 832 | const progress = event.loaded / event.total * 100; | ||
| 833 | this.attachment.setUploadProgress(progress); | ||
| 834 | })); | ||
| 835 | } | ||
| 836 | directUploadDidComplete(error, attributes) { | ||
| 837 | if (error) { | ||
| 838 | throw new Error(`Direct upload failed: ${error}`); | ||
| 839 | } | ||
| 840 | this.attachment.setAttributes({ | ||
| 841 | sgid: attributes.attachable_sgid, | ||
| 842 | url: this.createBlobUrl(attributes.signed_id, attributes.filename) | ||
| 843 | }); | ||
| 844 | } | ||
| 845 | createBlobUrl(signedId, filename) { | ||
| 846 | return this.blobUrlTemplate.replace(":signed_id", signedId).replace(":filename", encodeURIComponent(filename)); | ||
| 847 | } | ||
| 848 | get directUploadUrl() { | ||
| 849 | return this.element.dataset.directUploadUrl; | ||
| 850 | } | ||
| 851 | get blobUrlTemplate() { | ||
| 852 | return this.element.dataset.blobUrlTemplate; | ||
| 853 | } | ||
| 854 | } | ||
| 855 | addEventListener("trix-attachment-add", (event => { | ||
| 856 | const {attachment: attachment, target: target} = event; | ||
| 857 | if (attachment.file) { | ||
| 858 | const upload = new AttachmentUpload(attachment, target); | ||
| 859 | upload.start(); | ||
| 860 | } | ||
| 861 | })); | ||
| 862 | })); | ||
