From 2dbcb3df33bdaab5364009dcb93d33222790e17a Mon Sep 17 00:00:00 2001 From: Matias Grunberg Date: Mon, 6 Nov 2023 23:55:06 -0300 Subject: [PATCH] fix using actiontext.js in sprocket actiontext.js is compiled as ESM bundle instead of UMD bundle. This leads to issues when trying to use ActionText with sprockets because the ESM bundle declares variables like they are scoped to the file but sprockets will see them as scoped globally. This is a problem, in particular, if you want to mix actiontext with turbo-rails. The problem got introduced in https://github.com/rails/rails/pull/46447. I traced valid compilation back to https://github.com/rails/rails/pull/42895. This commit mimic changes made in https://github.com/rails/rails/pull/42895 to ActiveStorage: Retains app/assets/javascripts/actiontext.js as a UMD package for backwards compatibility with bundling in the asset pipeline, but also adds app/assets/javascripts/actiontext.esm.js for use with ESM via importmap in the browser. --- actiontext/CHANGELOG.md | 8 + .../app/assets/javascripts/.gitattributes | 1 + .../app/assets/javascripts/actiontext.esm.js | 889 +++++++++ .../app/assets/javascripts/actiontext.js | 1665 ++++++++--------- actiontext/lib/action_text/engine.rb | 2 +- .../action_text/install/install_generator.rb | 2 +- actiontext/package.json | 2 +- actiontext/rollup.config.js | 47 +- actiontext/test/dummy/config/importmap.rb | 2 +- actiontext/test/javascript_package_test.rb | 1 + 10 files changed, 1754 insertions(+), 865 deletions(-) create mode 100644 actiontext/app/assets/javascripts/actiontext.esm.js diff --git a/actiontext/CHANGELOG.md b/actiontext/CHANGELOG.md index 34ab27646f..13011bff23 100644 --- a/actiontext/CHANGELOG.md +++ b/actiontext/CHANGELOG.md @@ -1,3 +1,11 @@ +* Compile ESM package that can be used directly in the browser as actiontext.esm.js + + *Matias Grunberg* + +* Fix using actiontext.js with Sprockets + + *Matias Grunberg* + * Upgrade Trix to 2.0.7 *Hartley McGuire* diff --git a/actiontext/app/assets/javascripts/.gitattributes b/actiontext/app/assets/javascripts/.gitattributes index d8f019e9e2..65a3ad3f8b 100644 --- a/actiontext/app/assets/javascripts/.gitattributes +++ b/actiontext/app/assets/javascripts/.gitattributes @@ -1,2 +1,3 @@ actiontext.js linguist-generated +actiontext.esm.js linguist-generated trix.js linguist-vendored diff --git a/actiontext/app/assets/javascripts/actiontext.esm.js b/actiontext/app/assets/javascripts/actiontext.esm.js new file mode 100644 index 0000000000..2a3fad67a3 --- /dev/null +++ b/actiontext/app/assets/javascripts/actiontext.esm.js @@ -0,0 +1,889 @@ +var sparkMd5 = { + exports: {} +}; + +(function(module, exports) { + (function(factory) { + { + module.exports = factory(); + } + })((function(undefined$1) { + var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ]; + function md5cycle(x, k) { + var a = x[0], b = x[1], c = x[2], d = x[3]; + a += (b & c | ~b & d) + k[0] - 680876936 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[1] - 389564586 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[2] + 606105819 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[3] - 1044525330 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[4] - 176418897 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[5] + 1200080426 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[6] - 1473231341 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[7] - 45705983 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[8] + 1770035416 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[9] - 1958414417 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[10] - 42063 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[11] - 1990404162 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[12] + 1804603682 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[13] - 40341101 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[14] - 1502002290 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[15] + 1236535329 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & d | c & ~d) + k[1] - 165796510 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[6] - 1069501632 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[11] + 643717713 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[0] - 373897302 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[5] - 701558691 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[10] + 38016083 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[15] - 660478335 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[4] - 405537848 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[9] + 568446438 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[14] - 1019803690 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[3] - 187363961 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[8] + 1163531501 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[13] - 1444681467 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[2] - 51403784 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[7] + 1735328473 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[12] - 1926607734 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b ^ c ^ d) + k[5] - 378558 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[8] - 2022574463 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[11] + 1839030562 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[14] - 35309556 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[1] - 1530992060 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[4] + 1272893353 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[7] - 155497632 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[10] - 1094730640 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[13] + 681279174 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[0] - 358537222 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[3] - 722521979 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[6] + 76029189 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[9] - 640364487 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[12] - 421815835 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[15] + 530742520 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[2] - 995338651 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; + b = (b << 21 | b >>> 11) + c | 0; + x[0] = a + x[0] | 0; + x[1] = b + x[1] | 0; + x[2] = c + x[2] | 0; + x[3] = d + x[3] | 0; + } + function md5blk(s) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + function md5blk_array(a) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + } + function md51(s) { + var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(state, tail); + return state; + } + function md51_array(a) { + var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0); + length = a.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(state, tail); + return state; + } + function rhex(n) { + var s = "", j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15]; + } + return s; + } + function hex(x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(""); + } + if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ; + if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) { + (function() { + function clamp(val, length) { + val = val | 0 || 0; + if (val < 0) { + return Math.max(val + length, 0); + } + return Math.min(val, length); + } + ArrayBuffer.prototype.slice = function(from, to) { + var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray; + if (to !== undefined$1) { + end = clamp(to, length); + } + if (begin > end) { + return new ArrayBuffer(0); + } + num = end - begin; + target = new ArrayBuffer(num); + targetArray = new Uint8Array(target); + sourceArray = new Uint8Array(this, begin, num); + targetArray.set(sourceArray); + return target; + }; + })(); + } + function toUtf8(str) { + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + return str; + } + function utf8Str2ArrayBuffer(str, returnUInt8Array) { + var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i; + for (i = 0; i < length; i += 1) { + arr[i] = str.charCodeAt(i); + } + return returnUInt8Array ? arr : buff; + } + function arrayBuffer2Utf8Str(buff) { + return String.fromCharCode.apply(null, new Uint8Array(buff)); + } + function concatenateArrayBuffers(first, second, returnUInt8Array) { + var result = new Uint8Array(first.byteLength + second.byteLength); + result.set(new Uint8Array(first)); + result.set(new Uint8Array(second), first.byteLength); + return returnUInt8Array ? result : result.buffer; + } + function hexToBinaryString(hex) { + var bytes = [], length = hex.length, x; + for (x = 0; x < length - 1; x += 2) { + bytes.push(parseInt(hex.substr(x, 2), 16)); + } + return String.fromCharCode.apply(String, bytes); + } + function SparkMD5() { + this.reset(); + } + SparkMD5.prototype.append = function(str) { + this.appendBinary(toUtf8(str)); + return this; + }; + SparkMD5.prototype.appendBinary = function(contents) { + this._buff += contents; + this._length += contents.length; + var length = this._buff.length, i; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); + } + this._buff = this._buff.substring(i - 64); + return this; + }; + SparkMD5.prototype.end = function(raw) { + 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; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.prototype.reset = function() { + this._buff = ""; + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.prototype.getState = function() { + return { + buff: this._buff, + length: this._length, + hash: this._hash.slice() + }; + }; + SparkMD5.prototype.setState = function(state) { + this._buff = state.buff; + this._length = state.length; + this._hash = state.hash; + return this; + }; + SparkMD5.prototype.destroy = function() { + delete this._hash; + delete this._buff; + delete this._length; + }; + SparkMD5.prototype._finish = function(tail, length) { + var i = length, tmp, lo, hi; + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(this._hash, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(this._hash, tail); + }; + SparkMD5.hash = function(str, raw) { + return SparkMD5.hashBinary(toUtf8(str), raw); + }; + SparkMD5.hashBinary = function(content, raw) { + var hash = md51(content), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + SparkMD5.ArrayBuffer = function() { + this.reset(); + }; + SparkMD5.ArrayBuffer.prototype.append = function(arr) { + var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i; + this._length += arr.byteLength; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); + } + this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); + return this; + }; + SparkMD5.ArrayBuffer.prototype.end = function(raw) { + 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; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.ArrayBuffer.prototype.reset = function() { + this._buff = new Uint8Array(0); + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.ArrayBuffer.prototype.getState = function() { + var state = SparkMD5.prototype.getState.call(this); + state.buff = arrayBuffer2Utf8Str(state.buff); + return state; + }; + SparkMD5.ArrayBuffer.prototype.setState = function(state) { + state.buff = utf8Str2ArrayBuffer(state.buff, true); + return SparkMD5.prototype.setState.call(this, state); + }; + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + SparkMD5.ArrayBuffer.hash = function(arr, raw) { + var hash = md51_array(new Uint8Array(arr)), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + return SparkMD5; + })); +})(sparkMd5); + +var SparkMD5 = sparkMd5.exports; + +const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; + +class FileChecksum { + static create(file, callback) { + const instance = new FileChecksum(file); + instance.create(callback); + } + constructor(file) { + this.file = file; + this.chunkSize = 2097152; + this.chunkCount = Math.ceil(this.file.size / this.chunkSize); + this.chunkIndex = 0; + } + create(callback) { + this.callback = callback; + this.md5Buffer = new SparkMD5.ArrayBuffer; + this.fileReader = new FileReader; + this.fileReader.addEventListener("load", (event => this.fileReaderDidLoad(event))); + this.fileReader.addEventListener("error", (event => this.fileReaderDidError(event))); + this.readNextChunk(); + } + fileReaderDidLoad(event) { + this.md5Buffer.append(event.target.result); + if (!this.readNextChunk()) { + const binaryDigest = this.md5Buffer.end(true); + const base64digest = btoa(binaryDigest); + this.callback(null, base64digest); + } + } + fileReaderDidError(event) { + this.callback(`Error reading ${this.file.name}`); + } + readNextChunk() { + if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) { + const start = this.chunkIndex * this.chunkSize; + const end = Math.min(start + this.chunkSize, this.file.size); + const bytes = fileSlice.call(this.file, start, end); + this.fileReader.readAsArrayBuffer(bytes); + this.chunkIndex++; + return true; + } else { + return false; + } + } +} + +function getMetaValue(name) { + const element = findElement(document.head, `meta[name="${name}"]`); + if (element) { + return element.getAttribute("content"); + } +} + +function findElements(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + const elements = root.querySelectorAll(selector); + return toArray(elements); +} + +function findElement(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + return root.querySelector(selector); +} + +function dispatchEvent(element, type, eventInit = {}) { + const {disabled: disabled} = element; + const {bubbles: bubbles, cancelable: cancelable, detail: detail} = eventInit; + const event = document.createEvent("Event"); + event.initEvent(type, bubbles || true, cancelable || true); + event.detail = detail || {}; + try { + element.disabled = false; + element.dispatchEvent(event); + } finally { + element.disabled = disabled; + } + return event; +} + +function toArray(value) { + if (Array.isArray(value)) { + return value; + } else if (Array.from) { + return Array.from(value); + } else { + return [].slice.call(value); + } +} + +class BlobRecord { + constructor(file, checksum, url, customHeaders = {}) { + this.file = file; + this.attributes = { + filename: file.name, + content_type: file.type || "application/octet-stream", + byte_size: file.size, + checksum: checksum + }; + this.xhr = new XMLHttpRequest; + this.xhr.open("POST", url, true); + this.xhr.responseType = "json"; + this.xhr.setRequestHeader("Content-Type", "application/json"); + this.xhr.setRequestHeader("Accept", "application/json"); + this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + Object.keys(customHeaders).forEach((headerKey => { + this.xhr.setRequestHeader(headerKey, customHeaders[headerKey]); + })); + const csrfToken = getMetaValue("csrf-token"); + if (csrfToken != undefined) { + this.xhr.setRequestHeader("X-CSRF-Token", csrfToken); + } + this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); + this.xhr.addEventListener("error", (event => this.requestDidError(event))); + } + get status() { + return this.xhr.status; + } + get response() { + const {responseType: responseType, response: response} = this.xhr; + if (responseType == "json") { + return response; + } else { + return JSON.parse(response); + } + } + create(callback) { + this.callback = callback; + this.xhr.send(JSON.stringify({ + blob: this.attributes + })); + } + requestDidLoad(event) { + if (this.status >= 200 && this.status < 300) { + const {response: response} = this; + const {direct_upload: direct_upload} = response; + delete response.direct_upload; + this.attributes = response; + this.directUploadData = direct_upload; + this.callback(null, this.toJSON()); + } else { + this.requestDidError(event); + } + } + requestDidError(event) { + this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`); + } + toJSON() { + const result = {}; + for (const key in this.attributes) { + result[key] = this.attributes[key]; + } + return result; + } +} + +class BlobUpload { + constructor(blob) { + this.blob = blob; + this.file = blob.file; + const {url: url, headers: headers} = blob.directUploadData; + this.xhr = new XMLHttpRequest; + this.xhr.open("PUT", url, true); + this.xhr.responseType = "text"; + for (const key in headers) { + this.xhr.setRequestHeader(key, headers[key]); + } + this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); + this.xhr.addEventListener("error", (event => this.requestDidError(event))); + } + create(callback) { + this.callback = callback; + this.xhr.send(this.file.slice()); + } + requestDidLoad(event) { + const {status: status, response: response} = this.xhr; + if (status >= 200 && status < 300) { + this.callback(null, response); + } else { + this.requestDidError(event); + } + } + requestDidError(event) { + this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`); + } +} + +let id = 0; + +class DirectUpload { + constructor(file, url, delegate, customHeaders = {}) { + this.id = ++id; + this.file = file; + this.url = url; + this.delegate = delegate; + this.customHeaders = customHeaders; + } + create(callback) { + FileChecksum.create(this.file, ((error, checksum) => { + if (error) { + callback(error); + return; + } + const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders); + notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr); + blob.create((error => { + if (error) { + callback(error); + } else { + const upload = new BlobUpload(blob); + notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr); + upload.create((error => { + if (error) { + callback(error); + } else { + callback(null, blob.toJSON()); + } + })); + } + })); + })); + } +} + +function notify(object, methodName, ...messages) { + if (object && typeof object[methodName] == "function") { + return object[methodName](...messages); + } +} + +class DirectUploadController { + constructor(input, file) { + this.input = input; + this.file = file; + this.directUpload = new DirectUpload(this.file, this.url, this); + this.dispatch("initialize"); + } + start(callback) { + const hiddenInput = document.createElement("input"); + hiddenInput.type = "hidden"; + hiddenInput.name = this.input.name; + this.input.insertAdjacentElement("beforebegin", hiddenInput); + this.dispatch("start"); + this.directUpload.create(((error, attributes) => { + if (error) { + hiddenInput.parentNode.removeChild(hiddenInput); + this.dispatchError(error); + } else { + hiddenInput.value = attributes.signed_id; + } + this.dispatch("end"); + callback(error); + })); + } + uploadRequestDidProgress(event) { + const progress = event.loaded / event.total * 100; + if (progress) { + this.dispatch("progress", { + progress: progress + }); + } + } + get url() { + return this.input.getAttribute("data-direct-upload-url"); + } + dispatch(name, detail = {}) { + detail.file = this.file; + detail.id = this.directUpload.id; + return dispatchEvent(this.input, `direct-upload:${name}`, { + detail: detail + }); + } + dispatchError(error) { + const event = this.dispatch("error", { + error: error + }); + if (!event.defaultPrevented) { + alert(error); + } + } + directUploadWillCreateBlobWithXHR(xhr) { + this.dispatch("before-blob-request", { + xhr: xhr + }); + } + directUploadWillStoreFileWithXHR(xhr) { + this.dispatch("before-storage-request", { + xhr: xhr + }); + xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event))); + } +} + +const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])"; + +class DirectUploadsController { + constructor(form) { + this.form = form; + this.inputs = findElements(form, inputSelector).filter((input => input.files.length)); + } + start(callback) { + const controllers = this.createDirectUploadControllers(); + const startNextController = () => { + const controller = controllers.shift(); + if (controller) { + controller.start((error => { + if (error) { + callback(error); + this.dispatch("end"); + } else { + startNextController(); + } + })); + } else { + callback(); + this.dispatch("end"); + } + }; + this.dispatch("start"); + startNextController(); + } + createDirectUploadControllers() { + const controllers = []; + this.inputs.forEach((input => { + toArray(input.files).forEach((file => { + const controller = new DirectUploadController(input, file); + controllers.push(controller); + })); + })); + return controllers; + } + dispatch(name, detail = {}) { + return dispatchEvent(this.form, `direct-uploads:${name}`, { + detail: detail + }); + } +} + +const processingAttribute = "data-direct-uploads-processing"; + +const submitButtonsByForm = new WeakMap; + +let started = false; + +function start() { + if (!started) { + started = true; + document.addEventListener("click", didClick, true); + document.addEventListener("submit", didSubmitForm, true); + document.addEventListener("ajax:before", didSubmitRemoteElement); + } +} + +function didClick(event) { + const {target: target} = event; + if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target); + } +} + +function didSubmitForm(event) { + handleFormSubmissionEvent(event); +} + +function didSubmitRemoteElement(event) { + if (event.target.tagName == "FORM") { + handleFormSubmissionEvent(event); + } +} + +function handleFormSubmissionEvent(event) { + const form = event.target; + if (form.hasAttribute(processingAttribute)) { + event.preventDefault(); + return; + } + const controller = new DirectUploadsController(form); + const {inputs: inputs} = controller; + if (inputs.length) { + event.preventDefault(); + form.setAttribute(processingAttribute, ""); + inputs.forEach(disable); + controller.start((error => { + form.removeAttribute(processingAttribute); + if (error) { + inputs.forEach(enable); + } else { + submitForm(form); + } + })); + } +} + +function submitForm(form) { + let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]"); + if (button) { + const {disabled: disabled} = button; + button.disabled = false; + button.focus(); + button.click(); + button.disabled = disabled; + } else { + button = document.createElement("input"); + button.type = "submit"; + button.style.display = "none"; + form.appendChild(button); + button.click(); + form.removeChild(button); + } + submitButtonsByForm.delete(form); +} + +function disable(input) { + input.disabled = true; +} + +function enable(input) { + input.disabled = false; +} + +function autostart() { + if (window.ActiveStorage) { + start(); + } +} + +setTimeout(autostart, 1); + +class AttachmentUpload { + constructor(attachment, element) { + this.attachment = attachment; + this.element = element; + this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this); + } + start() { + this.directUpload.create(this.directUploadDidComplete.bind(this)); + } + directUploadWillStoreFileWithXHR(xhr) { + xhr.upload.addEventListener("progress", (event => { + const progress = event.loaded / event.total * 100; + this.attachment.setUploadProgress(progress); + })); + } + directUploadDidComplete(error, attributes) { + if (error) { + throw new Error(`Direct upload failed: ${error}`); + } + this.attachment.setAttributes({ + sgid: attributes.attachable_sgid, + url: this.createBlobUrl(attributes.signed_id, attributes.filename) + }); + } + createBlobUrl(signedId, filename) { + return this.blobUrlTemplate.replace(":signed_id", signedId).replace(":filename", encodeURIComponent(filename)); + } + get directUploadUrl() { + return this.element.dataset.directUploadUrl; + } + get blobUrlTemplate() { + return this.element.dataset.blobUrlTemplate; + } +} + +addEventListener("trix-attachment-add", (event => { + const {attachment: attachment, target: target} = event; + if (attachment.file) { + const upload = new AttachmentUpload(attachment, target); + upload.start(); + } +})); diff --git a/actiontext/app/assets/javascripts/actiontext.js b/actiontext/app/assets/javascripts/actiontext.js index d4b0b1f50f..a89f5ec3ef 100644 --- a/actiontext/app/assets/javascripts/actiontext.js +++ b/actiontext/app/assets/javascripts/actiontext.js @@ -1,899 +1,862 @@ -var sparkMd5 = { - exports: {} -}; - -(function(module, exports) { - (function(factory) { - { - module.exports = factory(); - } - })((function(undefined$1) { - var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ]; - function md5cycle(x, k) { - var a = x[0], b = x[1], c = x[2], d = x[3]; - a += (b & c | ~b & d) + k[0] - 680876936 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[1] - 389564586 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[2] + 606105819 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[3] - 1044525330 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[4] - 176418897 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[5] + 1200080426 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[6] - 1473231341 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[7] - 45705983 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[8] + 1770035416 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[9] - 1958414417 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[10] - 42063 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[11] - 1990404162 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[12] + 1804603682 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[13] - 40341101 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[14] - 1502002290 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[15] + 1236535329 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & d | c & ~d) + k[1] - 165796510 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[6] - 1069501632 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[11] + 643717713 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[0] - 373897302 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[5] - 701558691 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[10] + 38016083 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[15] - 660478335 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[4] - 405537848 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[9] + 568446438 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[14] - 1019803690 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[3] - 187363961 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[8] + 1163531501 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[13] - 1444681467 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[2] - 51403784 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[7] + 1735328473 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[12] - 1926607734 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b ^ c ^ d) + k[5] - 378558 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[8] - 2022574463 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[11] + 1839030562 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[14] - 35309556 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[1] - 1530992060 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[4] + 1272893353 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[7] - 155497632 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[10] - 1094730640 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[13] + 681279174 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[0] - 358537222 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[3] - 722521979 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[6] + 76029189 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[9] - 640364487 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[12] - 421815835 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[15] + 530742520 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[2] - 995338651 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; - b = (b << 21 | b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; - b = (b << 21 | b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; - b = (b << 21 | b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; - b = (b << 21 | b >>> 11) + c | 0; - x[0] = a + x[0] | 0; - x[1] = b + x[1] | 0; - x[2] = c + x[2] | 0; - x[3] = d + x[3] | 0; - } - function md5blk(s) { - var md5blks = [], i; - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); +(function(factory) { + typeof define === "function" && define.amd ? define(factory) : factory(); +})((function() { + "use strict"; + var sparkMd5 = { + exports: {} + }; + (function(module, exports) { + (function(factory) { + { + module.exports = factory(); } - return md5blks; - } - function md5blk_array(a) { - var md5blks = [], i; - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + })((function(undefined$1) { + var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ]; + function md5cycle(x, k) { + var a = x[0], b = x[1], c = x[2], d = x[3]; + a += (b & c | ~b & d) + k[0] - 680876936 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[1] - 389564586 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[2] + 606105819 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[3] - 1044525330 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[4] - 176418897 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[5] + 1200080426 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[6] - 1473231341 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[7] - 45705983 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[8] + 1770035416 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[9] - 1958414417 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[10] - 42063 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[11] - 1990404162 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[12] + 1804603682 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[13] - 40341101 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[14] - 1502002290 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[15] + 1236535329 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & d | c & ~d) + k[1] - 165796510 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[6] - 1069501632 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[11] + 643717713 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[0] - 373897302 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[5] - 701558691 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[10] + 38016083 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[15] - 660478335 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[4] - 405537848 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[9] + 568446438 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[14] - 1019803690 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[3] - 187363961 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[8] + 1163531501 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[13] - 1444681467 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[2] - 51403784 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[7] + 1735328473 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[12] - 1926607734 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b ^ c ^ d) + k[5] - 378558 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[8] - 2022574463 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[11] + 1839030562 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[14] - 35309556 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[1] - 1530992060 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[4] + 1272893353 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[7] - 155497632 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[10] - 1094730640 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[13] + 681279174 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[0] - 358537222 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[3] - 722521979 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[6] + 76029189 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[9] - 640364487 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[12] - 421815835 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[15] + 530742520 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[2] - 995338651 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; + b = (b << 21 | b >>> 11) + c | 0; + x[0] = a + x[0] | 0; + x[1] = b + x[1] | 0; + x[2] = c + x[2] | 0; + x[3] = d + x[3] | 0; } - return md5blks; - } - function md51(s) { - var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); + function md5blk(s) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; } - s = s.substring(i - 64); - length = s.length; - tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + function md5blk_array(a) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; } - tail[i >> 2] |= 128 << (i % 4 << 3); - if (i > 55) { + function md51(s) { + var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; + return state; + } + function md51_array(a) { + var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); } - } - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - tail[14] = lo; - tail[15] = hi; - md5cycle(state, tail); - return state; - } - function md51_array(a) { - var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; - for (i = 64; i <= n; i += 64) { - md5cycle(state, md5blk_array(a.subarray(i - 64, i))); - } - a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0); - length = a.length; - tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= a[i] << (i % 4 << 3); - } - tail[i >> 2] |= 128 << (i % 4 << 3); - if (i > 55) { + a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0); + length = a.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; md5cycle(state, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; + return state; + } + function rhex(n) { + var s = "", j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15]; } + return s; } - tmp = n * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - tail[14] = lo; - tail[15] = hi; - md5cycle(state, tail); - return state; - } - function rhex(n) { - var s = "", j; - for (j = 0; j < 4; j += 1) { - s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15]; - } - return s; - } - function hex(x) { - var i; - for (i = 0; i < x.length; i += 1) { - x[i] = rhex(x[i]); - } - return x.join(""); - } - if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ; - if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) { - (function() { - function clamp(val, length) { - val = val | 0 || 0; - if (val < 0) { - return Math.max(val + length, 0); - } - return Math.min(val, length); + function hex(x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); } - ArrayBuffer.prototype.slice = function(from, to) { - var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray; - if (to !== undefined$1) { - end = clamp(to, length); + return x.join(""); + } + if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ; + if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) { + (function() { + function clamp(val, length) { + val = val | 0 || 0; + if (val < 0) { + return Math.max(val + length, 0); + } + return Math.min(val, length); } - if (begin > end) { - return new ArrayBuffer(0); - } - num = end - begin; - target = new ArrayBuffer(num); - targetArray = new Uint8Array(target); - sourceArray = new Uint8Array(this, begin, num); - targetArray.set(sourceArray); - return target; - }; - })(); - } - function toUtf8(str) { - if (/[\u0080-\uFFFF]/.test(str)) { - str = unescape(encodeURIComponent(str)); + ArrayBuffer.prototype.slice = function(from, to) { + var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray; + if (to !== undefined$1) { + end = clamp(to, length); + } + if (begin > end) { + return new ArrayBuffer(0); + } + num = end - begin; + target = new ArrayBuffer(num); + targetArray = new Uint8Array(target); + sourceArray = new Uint8Array(this, begin, num); + targetArray.set(sourceArray); + return target; + }; + })(); } - return str; - } - function utf8Str2ArrayBuffer(str, returnUInt8Array) { - var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i; - for (i = 0; i < length; i += 1) { - arr[i] = str.charCodeAt(i); + function toUtf8(str) { + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + return str; } - return returnUInt8Array ? arr : buff; - } - function arrayBuffer2Utf8Str(buff) { - return String.fromCharCode.apply(null, new Uint8Array(buff)); - } - function concatenateArrayBuffers(first, second, returnUInt8Array) { - var result = new Uint8Array(first.byteLength + second.byteLength); - result.set(new Uint8Array(first)); - result.set(new Uint8Array(second), first.byteLength); - return returnUInt8Array ? result : result.buffer; - } - function hexToBinaryString(hex) { - var bytes = [], length = hex.length, x; - for (x = 0; x < length - 1; x += 2) { - bytes.push(parseInt(hex.substr(x, 2), 16)); + function utf8Str2ArrayBuffer(str, returnUInt8Array) { + var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i; + for (i = 0; i < length; i += 1) { + arr[i] = str.charCodeAt(i); + } + return returnUInt8Array ? arr : buff; } - return String.fromCharCode.apply(String, bytes); - } - function SparkMD5() { - this.reset(); - } - SparkMD5.prototype.append = function(str) { - this.appendBinary(toUtf8(str)); - return this; - }; - SparkMD5.prototype.appendBinary = function(contents) { - this._buff += contents; - this._length += contents.length; - var length = this._buff.length, i; - for (i = 64; i <= length; i += 64) { - md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); + function arrayBuffer2Utf8Str(buff) { + return String.fromCharCode.apply(null, new Uint8Array(buff)); } - this._buff = this._buff.substring(i - 64); - return this; - }; - SparkMD5.prototype.end = function(raw) { - 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; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3); + function concatenateArrayBuffers(first, second, returnUInt8Array) { + var result = new Uint8Array(first.byteLength + second.byteLength); + result.set(new Uint8Array(first)); + result.set(new Uint8Array(second), first.byteLength); + return returnUInt8Array ? result : result.buffer; } - this._finish(tail, length); - ret = hex(this._hash); - if (raw) { - ret = hexToBinaryString(ret); + function hexToBinaryString(hex) { + var bytes = [], length = hex.length, x; + for (x = 0; x < length - 1; x += 2) { + bytes.push(parseInt(hex.substr(x, 2), 16)); + } + return String.fromCharCode.apply(String, bytes); } - this.reset(); - return ret; - }; - SparkMD5.prototype.reset = function() { - this._buff = ""; - this._length = 0; - this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; - return this; - }; - SparkMD5.prototype.getState = function() { - return { - buff: this._buff, - length: this._length, - hash: this._hash.slice() + function SparkMD5() { + this.reset(); + } + SparkMD5.prototype.append = function(str) { + this.appendBinary(toUtf8(str)); + return this; }; - }; - SparkMD5.prototype.setState = function(state) { - this._buff = state.buff; - this._length = state.length; - this._hash = state.hash; - return this; - }; - SparkMD5.prototype.destroy = function() { - delete this._hash; - delete this._buff; - delete this._length; - }; - SparkMD5.prototype._finish = function(tail, length) { - var i = length, tmp, lo, hi; - tail[i >> 2] |= 128 << (i % 4 << 3); - if (i > 55) { - md5cycle(this._hash, tail); - for (i = 0; i < 16; i += 1) { - tail[i] = 0; + SparkMD5.prototype.appendBinary = function(contents) { + this._buff += contents; + this._length += contents.length; + var length = this._buff.length, i; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); } - } - tmp = this._length * 8; - tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); - lo = parseInt(tmp[2], 16); - hi = parseInt(tmp[1], 16) || 0; - tail[14] = lo; - tail[15] = hi; - md5cycle(this._hash, tail); - }; - SparkMD5.hash = function(str, raw) { - return SparkMD5.hashBinary(toUtf8(str), raw); - }; - SparkMD5.hashBinary = function(content, raw) { - var hash = md51(content), ret = hex(hash); - return raw ? hexToBinaryString(ret) : ret; - }; - SparkMD5.ArrayBuffer = function() { - this.reset(); - }; - SparkMD5.ArrayBuffer.prototype.append = function(arr) { - var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i; - this._length += arr.byteLength; - for (i = 64; i <= length; i += 64) { - md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); - } - this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); - return this; - }; - SparkMD5.ArrayBuffer.prototype.end = function(raw) { - 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; - for (i = 0; i < length; i += 1) { - tail[i >> 2] |= buff[i] << (i % 4 << 3); - } - this._finish(tail, length); - ret = hex(this._hash); - if (raw) { - ret = hexToBinaryString(ret); - } - this.reset(); - return ret; - }; - SparkMD5.ArrayBuffer.prototype.reset = function() { - this._buff = new Uint8Array(0); - this._length = 0; - this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; - return this; - }; - SparkMD5.ArrayBuffer.prototype.getState = function() { - var state = SparkMD5.prototype.getState.call(this); - state.buff = arrayBuffer2Utf8Str(state.buff); - return state; - }; - SparkMD5.ArrayBuffer.prototype.setState = function(state) { - state.buff = utf8Str2ArrayBuffer(state.buff, true); - return SparkMD5.prototype.setState.call(this, state); - }; - SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; - SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; - SparkMD5.ArrayBuffer.hash = function(arr, raw) { - var hash = md51_array(new Uint8Array(arr)), ret = hex(hash); - return raw ? hexToBinaryString(ret) : ret; - }; - return SparkMD5; - })); -})(sparkMd5); - -var SparkMD5 = sparkMd5.exports; - -const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; - -class FileChecksum { - static create(file, callback) { - const instance = new FileChecksum(file); - instance.create(callback); - } - constructor(file) { - this.file = file; - this.chunkSize = 2097152; - this.chunkCount = Math.ceil(this.file.size / this.chunkSize); - this.chunkIndex = 0; - } - create(callback) { - this.callback = callback; - this.md5Buffer = new SparkMD5.ArrayBuffer; - this.fileReader = new FileReader; - this.fileReader.addEventListener("load", (event => this.fileReaderDidLoad(event))); - this.fileReader.addEventListener("error", (event => this.fileReaderDidError(event))); - this.readNextChunk(); - } - fileReaderDidLoad(event) { - this.md5Buffer.append(event.target.result); - if (!this.readNextChunk()) { - const binaryDigest = this.md5Buffer.end(true); - const base64digest = btoa(binaryDigest); - this.callback(null, base64digest); - } - } - fileReaderDidError(event) { - this.callback(`Error reading ${this.file.name}`); - } - readNextChunk() { - if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) { - const start = this.chunkIndex * this.chunkSize; - const end = Math.min(start + this.chunkSize, this.file.size); - const bytes = fileSlice.call(this.file, start, end); - this.fileReader.readAsArrayBuffer(bytes); - this.chunkIndex++; - return true; - } else { - return false; - } - } -} - -function getMetaValue(name) { - const element = findElement(document.head, `meta[name="${name}"]`); - if (element) { - return element.getAttribute("content"); - } -} - -function findElements(root, selector) { - if (typeof root == "string") { - selector = root; - root = document; - } - const elements = root.querySelectorAll(selector); - return toArray(elements); -} - -function findElement(root, selector) { - if (typeof root == "string") { - selector = root; - root = document; - } - return root.querySelector(selector); -} - -function dispatchEvent(element, type, eventInit = {}) { - const {disabled: disabled} = element; - const {bubbles: bubbles, cancelable: cancelable, detail: detail} = eventInit; - const event = document.createEvent("Event"); - event.initEvent(type, bubbles || true, cancelable || true); - event.detail = detail || {}; - try { - element.disabled = false; - element.dispatchEvent(event); - } finally { - element.disabled = disabled; - } - return event; -} - -function toArray(value) { - if (Array.isArray(value)) { - return value; - } else if (Array.from) { - return Array.from(value); - } else { - return [].slice.call(value); - } -} - -class BlobRecord { - constructor(file, checksum, url, customHeaders = {}) { - this.file = file; - this.attributes = { - filename: file.name, - content_type: file.type || "application/octet-stream", - byte_size: file.size, - checksum: checksum - }; - this.xhr = new XMLHttpRequest; - this.xhr.open("POST", url, true); - this.xhr.responseType = "json"; - this.xhr.setRequestHeader("Content-Type", "application/json"); - this.xhr.setRequestHeader("Accept", "application/json"); - this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - Object.keys(customHeaders).forEach((headerKey => { - this.xhr.setRequestHeader(headerKey, customHeaders[headerKey]); + this._buff = this._buff.substring(i - 64); + return this; + }; + SparkMD5.prototype.end = function(raw) { + 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; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.prototype.reset = function() { + this._buff = ""; + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.prototype.getState = function() { + return { + buff: this._buff, + length: this._length, + hash: this._hash.slice() + }; + }; + SparkMD5.prototype.setState = function(state) { + this._buff = state.buff; + this._length = state.length; + this._hash = state.hash; + return this; + }; + SparkMD5.prototype.destroy = function() { + delete this._hash; + delete this._buff; + delete this._length; + }; + SparkMD5.prototype._finish = function(tail, length) { + var i = length, tmp, lo, hi; + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(this._hash, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(this._hash, tail); + }; + SparkMD5.hash = function(str, raw) { + return SparkMD5.hashBinary(toUtf8(str), raw); + }; + SparkMD5.hashBinary = function(content, raw) { + var hash = md51(content), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + SparkMD5.ArrayBuffer = function() { + this.reset(); + }; + SparkMD5.ArrayBuffer.prototype.append = function(arr) { + var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i; + this._length += arr.byteLength; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); + } + this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); + return this; + }; + SparkMD5.ArrayBuffer.prototype.end = function(raw) { + 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; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.ArrayBuffer.prototype.reset = function() { + this._buff = new Uint8Array(0); + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.ArrayBuffer.prototype.getState = function() { + var state = SparkMD5.prototype.getState.call(this); + state.buff = arrayBuffer2Utf8Str(state.buff); + return state; + }; + SparkMD5.ArrayBuffer.prototype.setState = function(state) { + state.buff = utf8Str2ArrayBuffer(state.buff, true); + return SparkMD5.prototype.setState.call(this, state); + }; + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + SparkMD5.ArrayBuffer.hash = function(arr, raw) { + var hash = md51_array(new Uint8Array(arr)), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + return SparkMD5; })); - const csrfToken = getMetaValue("csrf-token"); - if (csrfToken != undefined) { - this.xhr.setRequestHeader("X-CSRF-Token", csrfToken); + })(sparkMd5); + var SparkMD5 = sparkMd5.exports; + const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; + class FileChecksum { + static create(file, callback) { + const instance = new FileChecksum(file); + instance.create(callback); } - this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); - this.xhr.addEventListener("error", (event => this.requestDidError(event))); - } - get status() { - return this.xhr.status; - } - get response() { - const {responseType: responseType, response: response} = this.xhr; - if (responseType == "json") { - return response; - } else { - return JSON.parse(response); + constructor(file) { + this.file = file; + this.chunkSize = 2097152; + this.chunkCount = Math.ceil(this.file.size / this.chunkSize); + this.chunkIndex = 0; } - } - create(callback) { - this.callback = callback; - this.xhr.send(JSON.stringify({ - blob: this.attributes - })); - } - requestDidLoad(event) { - if (this.status >= 200 && this.status < 300) { - const {response: response} = this; - const {direct_upload: direct_upload} = response; - delete response.direct_upload; - this.attributes = response; - this.directUploadData = direct_upload; - this.callback(null, this.toJSON()); - } else { - this.requestDidError(event); + create(callback) { + this.callback = callback; + this.md5Buffer = new SparkMD5.ArrayBuffer; + this.fileReader = new FileReader; + this.fileReader.addEventListener("load", (event => this.fileReaderDidLoad(event))); + this.fileReader.addEventListener("error", (event => this.fileReaderDidError(event))); + this.readNextChunk(); } - } - requestDidError(event) { - this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`); - } - toJSON() { - const result = {}; - for (const key in this.attributes) { - result[key] = this.attributes[key]; - } - return result; - } -} - -class BlobUpload { - constructor(blob) { - this.blob = blob; - this.file = blob.file; - const {url: url, headers: headers} = blob.directUploadData; - this.xhr = new XMLHttpRequest; - this.xhr.open("PUT", url, true); - this.xhr.responseType = "text"; - for (const key in headers) { - this.xhr.setRequestHeader(key, headers[key]); - } - this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); - this.xhr.addEventListener("error", (event => this.requestDidError(event))); - } - create(callback) { - this.callback = callback; - this.xhr.send(this.file.slice()); - } - requestDidLoad(event) { - const {status: status, response: response} = this.xhr; - if (status >= 200 && status < 300) { - this.callback(null, response); - } else { - this.requestDidError(event); - } - } - requestDidError(event) { - this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`); - } -} - -let id = 0; - -class DirectUpload { - constructor(file, url, delegate, customHeaders = {}) { - this.id = ++id; - this.file = file; - this.url = url; - this.delegate = delegate; - this.customHeaders = customHeaders; - } - create(callback) { - FileChecksum.create(this.file, ((error, checksum) => { - if (error) { - callback(error); - return; + fileReaderDidLoad(event) { + this.md5Buffer.append(event.target.result); + if (!this.readNextChunk()) { + const binaryDigest = this.md5Buffer.end(true); + const base64digest = btoa(binaryDigest); + this.callback(null, base64digest); } - const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders); - notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr); - blob.create((error => { + } + fileReaderDidError(event) { + this.callback(`Error reading ${this.file.name}`); + } + readNextChunk() { + if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) { + const start = this.chunkIndex * this.chunkSize; + const end = Math.min(start + this.chunkSize, this.file.size); + const bytes = fileSlice.call(this.file, start, end); + this.fileReader.readAsArrayBuffer(bytes); + this.chunkIndex++; + return true; + } else { + return false; + } + } + } + function getMetaValue(name) { + const element = findElement(document.head, `meta[name="${name}"]`); + if (element) { + return element.getAttribute("content"); + } + } + function findElements(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + const elements = root.querySelectorAll(selector); + return toArray(elements); + } + function findElement(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + return root.querySelector(selector); + } + function dispatchEvent(element, type, eventInit = {}) { + const {disabled: disabled} = element; + const {bubbles: bubbles, cancelable: cancelable, detail: detail} = eventInit; + const event = document.createEvent("Event"); + event.initEvent(type, bubbles || true, cancelable || true); + event.detail = detail || {}; + try { + element.disabled = false; + element.dispatchEvent(event); + } finally { + element.disabled = disabled; + } + return event; + } + function toArray(value) { + if (Array.isArray(value)) { + return value; + } else if (Array.from) { + return Array.from(value); + } else { + return [].slice.call(value); + } + } + class BlobRecord { + constructor(file, checksum, url, customHeaders = {}) { + this.file = file; + this.attributes = { + filename: file.name, + content_type: file.type || "application/octet-stream", + byte_size: file.size, + checksum: checksum + }; + this.xhr = new XMLHttpRequest; + this.xhr.open("POST", url, true); + this.xhr.responseType = "json"; + this.xhr.setRequestHeader("Content-Type", "application/json"); + this.xhr.setRequestHeader("Accept", "application/json"); + this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + Object.keys(customHeaders).forEach((headerKey => { + this.xhr.setRequestHeader(headerKey, customHeaders[headerKey]); + })); + const csrfToken = getMetaValue("csrf-token"); + if (csrfToken != undefined) { + this.xhr.setRequestHeader("X-CSRF-Token", csrfToken); + } + this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); + this.xhr.addEventListener("error", (event => this.requestDidError(event))); + } + get status() { + return this.xhr.status; + } + get response() { + const {responseType: responseType, response: response} = this.xhr; + if (responseType == "json") { + return response; + } else { + return JSON.parse(response); + } + } + create(callback) { + this.callback = callback; + this.xhr.send(JSON.stringify({ + blob: this.attributes + })); + } + requestDidLoad(event) { + if (this.status >= 200 && this.status < 300) { + const {response: response} = this; + const {direct_upload: direct_upload} = response; + delete response.direct_upload; + this.attributes = response; + this.directUploadData = direct_upload; + this.callback(null, this.toJSON()); + } else { + this.requestDidError(event); + } + } + requestDidError(event) { + this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`); + } + toJSON() { + const result = {}; + for (const key in this.attributes) { + result[key] = this.attributes[key]; + } + return result; + } + } + class BlobUpload { + constructor(blob) { + this.blob = blob; + this.file = blob.file; + const {url: url, headers: headers} = blob.directUploadData; + this.xhr = new XMLHttpRequest; + this.xhr.open("PUT", url, true); + this.xhr.responseType = "text"; + for (const key in headers) { + this.xhr.setRequestHeader(key, headers[key]); + } + this.xhr.addEventListener("load", (event => this.requestDidLoad(event))); + this.xhr.addEventListener("error", (event => this.requestDidError(event))); + } + create(callback) { + this.callback = callback; + this.xhr.send(this.file.slice()); + } + requestDidLoad(event) { + const {status: status, response: response} = this.xhr; + if (status >= 200 && status < 300) { + this.callback(null, response); + } else { + this.requestDidError(event); + } + } + requestDidError(event) { + this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`); + } + } + let id = 0; + class DirectUpload { + constructor(file, url, delegate, customHeaders = {}) { + this.id = ++id; + this.file = file; + this.url = url; + this.delegate = delegate; + this.customHeaders = customHeaders; + } + create(callback) { + FileChecksum.create(this.file, ((error, checksum) => { if (error) { callback(error); + return; + } + const blob = new BlobRecord(this.file, checksum, this.url, this.customHeaders); + notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr); + blob.create((error => { + if (error) { + callback(error); + } else { + const upload = new BlobUpload(blob); + notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr); + upload.create((error => { + if (error) { + callback(error); + } else { + callback(null, blob.toJSON()); + } + })); + } + })); + })); + } + } + function notify(object, methodName, ...messages) { + if (object && typeof object[methodName] == "function") { + return object[methodName](...messages); + } + } + class DirectUploadController { + constructor(input, file) { + this.input = input; + this.file = file; + this.directUpload = new DirectUpload(this.file, this.url, this); + this.dispatch("initialize"); + } + start(callback) { + const hiddenInput = document.createElement("input"); + hiddenInput.type = "hidden"; + hiddenInput.name = this.input.name; + this.input.insertAdjacentElement("beforebegin", hiddenInput); + this.dispatch("start"); + this.directUpload.create(((error, attributes) => { + if (error) { + hiddenInput.parentNode.removeChild(hiddenInput); + this.dispatchError(error); } else { - const upload = new BlobUpload(blob); - notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr); - upload.create((error => { + hiddenInput.value = attributes.signed_id; + } + this.dispatch("end"); + callback(error); + })); + } + uploadRequestDidProgress(event) { + const progress = event.loaded / event.total * 100; + if (progress) { + this.dispatch("progress", { + progress: progress + }); + } + } + get url() { + return this.input.getAttribute("data-direct-upload-url"); + } + dispatch(name, detail = {}) { + detail.file = this.file; + detail.id = this.directUpload.id; + return dispatchEvent(this.input, `direct-upload:${name}`, { + detail: detail + }); + } + dispatchError(error) { + const event = this.dispatch("error", { + error: error + }); + if (!event.defaultPrevented) { + alert(error); + } + } + directUploadWillCreateBlobWithXHR(xhr) { + this.dispatch("before-blob-request", { + xhr: xhr + }); + } + directUploadWillStoreFileWithXHR(xhr) { + this.dispatch("before-storage-request", { + xhr: xhr + }); + xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event))); + } + } + const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])"; + class DirectUploadsController { + constructor(form) { + this.form = form; + this.inputs = findElements(form, inputSelector).filter((input => input.files.length)); + } + start(callback) { + const controllers = this.createDirectUploadControllers(); + const startNextController = () => { + const controller = controllers.shift(); + if (controller) { + controller.start((error => { if (error) { callback(error); + this.dispatch("end"); } else { - callback(null, blob.toJSON()); + startNextController(); } })); + } else { + callback(); + this.dispatch("end"); } + }; + this.dispatch("start"); + startNextController(); + } + createDirectUploadControllers() { + const controllers = []; + this.inputs.forEach((input => { + toArray(input.files).forEach((file => { + const controller = new DirectUploadController(input, file); + controllers.push(controller); + })); })); - })); - } -} - -function notify(object, methodName, ...messages) { - if (object && typeof object[methodName] == "function") { - return object[methodName](...messages); - } -} - -class DirectUploadController { - constructor(input, file) { - this.input = input; - this.file = file; - this.directUpload = new DirectUpload(this.file, this.url, this); - this.dispatch("initialize"); - } - start(callback) { - const hiddenInput = document.createElement("input"); - hiddenInput.type = "hidden"; - hiddenInput.name = this.input.name; - this.input.insertAdjacentElement("beforebegin", hiddenInput); - this.dispatch("start"); - this.directUpload.create(((error, attributes) => { - if (error) { - hiddenInput.parentNode.removeChild(hiddenInput); - this.dispatchError(error); - } else { - hiddenInput.value = attributes.signed_id; - } - this.dispatch("end"); - callback(error); - })); - } - uploadRequestDidProgress(event) { - const progress = event.loaded / event.total * 100; - if (progress) { - this.dispatch("progress", { - progress: progress + return controllers; + } + dispatch(name, detail = {}) { + return dispatchEvent(this.form, `direct-uploads:${name}`, { + detail: detail }); } } - get url() { - return this.input.getAttribute("data-direct-upload-url"); - } - dispatch(name, detail = {}) { - detail.file = this.file; - detail.id = this.directUpload.id; - return dispatchEvent(this.input, `direct-upload:${name}`, { - detail: detail - }); - } - dispatchError(error) { - const event = this.dispatch("error", { - error: error - }); - if (!event.defaultPrevented) { - alert(error); + const processingAttribute = "data-direct-uploads-processing"; + const submitButtonsByForm = new WeakMap; + let started = false; + function start() { + if (!started) { + started = true; + document.addEventListener("click", didClick, true); + document.addEventListener("submit", didSubmitForm, true); + document.addEventListener("ajax:before", didSubmitRemoteElement); } } - directUploadWillCreateBlobWithXHR(xhr) { - this.dispatch("before-blob-request", { - xhr: xhr - }); + function didClick(event) { + const {target: target} = event; + if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target); + } } - directUploadWillStoreFileWithXHR(xhr) { - this.dispatch("before-storage-request", { - xhr: xhr - }); - xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event))); - } -} - -const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])"; - -class DirectUploadsController { - constructor(form) { - this.form = form; - this.inputs = findElements(form, inputSelector).filter((input => input.files.length)); - } - start(callback) { - const controllers = this.createDirectUploadControllers(); - const startNextController = () => { - const controller = controllers.shift(); - if (controller) { - controller.start((error => { - if (error) { - callback(error); - this.dispatch("end"); - } else { - startNextController(); - } - })); - } else { - callback(); - this.dispatch("end"); - } - }; - this.dispatch("start"); - startNextController(); - } - createDirectUploadControllers() { - const controllers = []; - this.inputs.forEach((input => { - toArray(input.files).forEach((file => { - const controller = new DirectUploadController(input, file); - controllers.push(controller); - })); - })); - return controllers; - } - dispatch(name, detail = {}) { - return dispatchEvent(this.form, `direct-uploads:${name}`, { - detail: detail - }); - } -} - -const processingAttribute = "data-direct-uploads-processing"; - -const submitButtonsByForm = new WeakMap; - -let started = false; - -function start() { - if (!started) { - started = true; - document.addEventListener("click", didClick, true); - document.addEventListener("submit", didSubmitForm, true); - document.addEventListener("ajax:before", didSubmitRemoteElement); - } -} - -function didClick(event) { - const {target: target} = event; - if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) { - submitButtonsByForm.set(target.form, target); - } -} - -function didSubmitForm(event) { - handleFormSubmissionEvent(event); -} - -function didSubmitRemoteElement(event) { - if (event.target.tagName == "FORM") { + function didSubmitForm(event) { handleFormSubmissionEvent(event); } -} - -function handleFormSubmissionEvent(event) { - const form = event.target; - if (form.hasAttribute(processingAttribute)) { - event.preventDefault(); - return; - } - const controller = new DirectUploadsController(form); - const {inputs: inputs} = controller; - if (inputs.length) { - event.preventDefault(); - form.setAttribute(processingAttribute, ""); - inputs.forEach(disable); - controller.start((error => { - form.removeAttribute(processingAttribute); - if (error) { - inputs.forEach(enable); - } else { - submitForm(form); - } - })); - } -} - -function submitForm(form) { - let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]"); - if (button) { - const {disabled: disabled} = button; - button.disabled = false; - button.focus(); - button.click(); - button.disabled = disabled; - } else { - button = document.createElement("input"); - button.type = "submit"; - button.style.display = "none"; - form.appendChild(button); - button.click(); - form.removeChild(button); - } - submitButtonsByForm.delete(form); -} - -function disable(input) { - input.disabled = true; -} - -function enable(input) { - input.disabled = false; -} - -function autostart() { - if (window.ActiveStorage) { - start(); - } -} - -setTimeout(autostart, 1); - -class AttachmentUpload { - constructor(attachment, element) { - this.attachment = attachment; - this.element = element; - this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this); - } - - start() { - this.directUpload.create(this.directUploadDidComplete.bind(this)); - } - - directUploadWillStoreFileWithXHR(xhr) { - xhr.upload.addEventListener("progress", event => { - const progress = event.loaded / event.total * 100; - this.attachment.setUploadProgress(progress); - }); - } - - directUploadDidComplete(error, attributes) { - if (error) { - throw new Error(`Direct upload failed: ${error}`) + function didSubmitRemoteElement(event) { + if (event.target.tagName == "FORM") { + handleFormSubmissionEvent(event); } - - this.attachment.setAttributes({ - sgid: attributes.attachable_sgid, - url: this.createBlobUrl(attributes.signed_id, attributes.filename) - }); } - - createBlobUrl(signedId, filename) { - return this.blobUrlTemplate - .replace(":signed_id", signedId) - .replace(":filename", encodeURIComponent(filename)) + function handleFormSubmissionEvent(event) { + const form = event.target; + if (form.hasAttribute(processingAttribute)) { + event.preventDefault(); + return; + } + const controller = new DirectUploadsController(form); + const {inputs: inputs} = controller; + if (inputs.length) { + event.preventDefault(); + form.setAttribute(processingAttribute, ""); + inputs.forEach(disable); + controller.start((error => { + form.removeAttribute(processingAttribute); + if (error) { + inputs.forEach(enable); + } else { + submitForm(form); + } + })); + } } - - get directUploadUrl() { - return this.element.dataset.directUploadUrl + function submitForm(form) { + let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]"); + if (button) { + const {disabled: disabled} = button; + button.disabled = false; + button.focus(); + button.click(); + button.disabled = disabled; + } else { + button = document.createElement("input"); + button.type = "submit"; + button.style.display = "none"; + form.appendChild(button); + button.click(); + form.removeChild(button); + } + submitButtonsByForm.delete(form); } - - get blobUrlTemplate() { - return this.element.dataset.blobUrlTemplate + function disable(input) { + input.disabled = true; } -} - -addEventListener("trix-attachment-add", event => { - const { attachment, target } = event; - - if (attachment.file) { - const upload = new AttachmentUpload(attachment, target); - upload.start(); + function enable(input) { + input.disabled = false; } -}); + function autostart() { + if (window.ActiveStorage) { + start(); + } + } + setTimeout(autostart, 1); + class AttachmentUpload { + constructor(attachment, element) { + this.attachment = attachment; + this.element = element; + this.directUpload = new DirectUpload(attachment.file, this.directUploadUrl, this); + } + start() { + this.directUpload.create(this.directUploadDidComplete.bind(this)); + } + directUploadWillStoreFileWithXHR(xhr) { + xhr.upload.addEventListener("progress", (event => { + const progress = event.loaded / event.total * 100; + this.attachment.setUploadProgress(progress); + })); + } + directUploadDidComplete(error, attributes) { + if (error) { + throw new Error(`Direct upload failed: ${error}`); + } + this.attachment.setAttributes({ + sgid: attributes.attachable_sgid, + url: this.createBlobUrl(attributes.signed_id, attributes.filename) + }); + } + createBlobUrl(signedId, filename) { + return this.blobUrlTemplate.replace(":signed_id", signedId).replace(":filename", encodeURIComponent(filename)); + } + get directUploadUrl() { + return this.element.dataset.directUploadUrl; + } + get blobUrlTemplate() { + return this.element.dataset.blobUrlTemplate; + } + } + addEventListener("trix-attachment-add", (event => { + const {attachment: attachment, target: target} = event; + if (attachment.file) { + const upload = new AttachmentUpload(attachment, target); + upload.start(); + } + })); +})); diff --git a/actiontext/lib/action_text/engine.rb b/actiontext/lib/action_text/engine.rb index 9273352b62..0282dc7a9b 100644 --- a/actiontext/lib/action_text/engine.rb +++ b/actiontext/lib/action_text/engine.rb @@ -32,7 +32,7 @@ class Engine < Rails::Engine initializer "action_text.asset" do if Rails.application.config.respond_to?(:assets) - Rails.application.config.assets.precompile += %w( actiontext.js trix.js trix.css ) + Rails.application.config.assets.precompile += %w( actiontext.js actiontext.esm.js trix.js trix.css ) end end diff --git a/actiontext/lib/generators/action_text/install/install_generator.rb b/actiontext/lib/generators/action_text/install/install_generator.rb index e52ce26374..b443ebf70f 100644 --- a/actiontext/lib/generators/action_text/install/install_generator.rb +++ b/actiontext/lib/generators/action_text/install/install_generator.rb @@ -29,7 +29,7 @@ def append_javascript_dependencies end if (importmap_path = destination.join("config/importmap.rb")).exist? - append_to_file importmap_path.to_s, %(pin "trix"\npin "@rails/actiontext", to: "actiontext.js"\n) + append_to_file importmap_path.to_s, %(pin "trix"\npin "@rails/actiontext", to: "actiontext.esm.js"\n) end end diff --git a/actiontext/package.json b/actiontext/package.json index 4ee836615d..49752ab02c 100644 --- a/actiontext/package.json +++ b/actiontext/package.json @@ -2,8 +2,8 @@ "name": "@rails/actiontext", "version": "7.2.0-alpha", "description": "Edit and display rich text in Rails applications", + "module": "app/assets/javascripts/actiontext.esm.js", "main": "app/assets/javascripts/actiontext.js", - "type": "module", "files": [ "app/assets/javascripts/*.js" ], diff --git a/actiontext/rollup.config.js b/actiontext/rollup.config.js index 28a2e4c740..00c19ad525 100644 --- a/actiontext/rollup.config.js +++ b/actiontext/rollup.config.js @@ -1,14 +1,41 @@ import resolve from "@rollup/plugin-node-resolve" import commonjs from "@rollup/plugin-commonjs" +import { terser } from "rollup-plugin-terser" -export default { - input: "app/javascript/actiontext/index.js", - output: { - file: "app/assets/javascripts/actiontext.js", - format: "es" - }, - plugins: [ - resolve(), - commonjs() - ] +const terserOptions = { + mangle: false, + compress: false, + format: { + beautify: true, + indent_level: 2 + } } + +export default [ + { + input: "app/javascript/actiontext/index.js", + output: { + file: "app/assets/javascripts/actiontext.js", + format: "umd", + name: "ActionText" + }, + plugins: [ + resolve(), + commonjs(), + terser(terserOptions) + ] + }, + + { + input: "app/javascript/actiontext/index.js", + output: { + file: "app/assets/javascripts/actiontext.esm.js", + format: "es" + }, + plugins: [ + resolve(), + commonjs(), + terser(terserOptions) + ] + } +] diff --git a/actiontext/test/dummy/config/importmap.rb b/actiontext/test/dummy/config/importmap.rb index fff93ec535..c2071c6baf 100644 --- a/actiontext/test/dummy/config/importmap.rb +++ b/actiontext/test/dummy/config/importmap.rb @@ -3,4 +3,4 @@ pin "application", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "trix" -pin "@rails/actiontext", to: "actiontext.js" +pin "@rails/actiontext", to: "actiontext.esm.js" diff --git a/actiontext/test/javascript_package_test.rb b/actiontext/test/javascript_package_test.rb index 26c64eafb9..e4531e29b4 100644 --- a/actiontext/test/javascript_package_test.rb +++ b/actiontext/test/javascript_package_test.rb @@ -6,6 +6,7 @@ class JavascriptPackageTest < ActiveSupport::TestCase def test_compiled_code_is_in_sync_with_source_code compiled_files = %w[ app/assets/javascripts/actiontext.js + app/assets/javascripts/actiontext.esm.js ].map do |file| Pathname(file).expand_path("#{__dir__}/..") end