diff --git a/backend/Gemfile b/backend/Gemfile index 309c0bb..1c76afc 100644 --- a/backend/Gemfile +++ b/backend/Gemfile @@ -35,6 +35,7 @@ gem "thruster", require: false # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible gem "rack-cors" gem 'jwt' +gem 'sprockets-rails' group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem diff --git a/backend/Gemfile.lock b/backend/Gemfile.lock index d23b681..6ffef1d 100644 --- a/backend/Gemfile.lock +++ b/backend/Gemfile.lock @@ -276,6 +276,13 @@ GEM fugit (~> 1.11.0) railties (>= 7.1) thor (~> 1.3.1) + sprockets (4.2.1) + concurrent-ruby (~> 1.0) + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) + sprockets (>= 3.0.0) sqlite3 (2.6.0-aarch64-linux-gnu) sqlite3 (2.6.0-aarch64-linux-musl) sqlite3 (2.6.0-arm-linux-gnu) @@ -338,6 +345,7 @@ DEPENDENCIES solid_cable solid_cache solid_queue + sprockets-rails sqlite3 (>= 2.1) thruster tzinfo-data diff --git a/backend/app/assets/config/manifest.js b/backend/app/assets/config/manifest.js new file mode 100644 index 0000000..b16e53d --- /dev/null +++ b/backend/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/backend/app/assets/images/.gitkeep b/backend/app/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/assets/javascripts/.gitkeep b/backend/app/assets/javascripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/assets/stylesheets/.gitkeep b/backend/app/assets/stylesheets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/public/assets/.sprockets-manifest-bc72bf50aa9693fc0bfc25057f1a5282.json b/backend/public/assets/.sprockets-manifest-bc72bf50aa9693fc0bfc25057f1a5282.json new file mode 100644 index 0000000..83d14f4 --- /dev/null +++ b/backend/public/assets/.sprockets-manifest-bc72bf50aa9693fc0bfc25057f1a5282.json @@ -0,0 +1 @@ +{"files":{"manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js":{"logical_path":"manifest.js","mtime":"2025-02-26T00:16:38+09:00","size":3,"digest":"6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167","integrity":"sha256-ajz1GSNU9xYVrFEDSz6Xwg7amWQ/yvW75tQa1ZvRIWc="},"actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js":{"logical_path":"actiontext.js","mtime":"2025-02-26T00:16:38+09:00","size":30872,"digest":"9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d","integrity":"sha256-nMv2gkUdQ5YR/loEjlUVfFYDiEDrtKgpJDfrVf0xyD0="},"actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js":{"logical_path":"actiontext.esm.js","mtime":"2025-02-26T00:16:38+09:00","size":29011,"digest":"6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c","integrity":"sha256-Z5DU2vVFjNbrjeLVbnVKNAyuupCcspUqNftHiVVJPYw="},"trix-488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc.js":{"logical_path":"trix.js","mtime":"2025-02-26T00:16:38+09:00","size":512990,"digest":"488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc","integrity":"sha256-SIrIi1Dt7oqrc92CbplnB17tkAChP9E8vMF3RfhmUMw="},"trix-4c7f56767699ae6c08f21004c9dc23a1b76b0734b0fca61d4534900ee0fef211.css":{"logical_path":"trix.css","mtime":"2025-02-26T00:16:38+09:00","size":20027,"digest":"4c7f56767699ae6c08f21004c9dc23a1b76b0734b0fca61d4534900ee0fef211","integrity":"sha256-TH9WdnaZrmwI8hAEydwjobdrBzSw/KYdRTSQDuD+8hE="},"activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js":{"logical_path":"activestorage.js","mtime":"2025-02-26T00:16:38+09:00","size":29420,"digest":"cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2","integrity":"sha256-zyKkl9GVbo8NPsUPRdaJYaoNE47gYoAsNq44TvsmCqI="},"activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js":{"logical_path":"activestorage.esm.js","mtime":"2025-02-26T00:16:38+09:00","size":27262,"digest":"8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485","integrity":"sha256-i7AoIo5iLFBQGz6yacvkBEpG2ge1ZakOWubyIdwG5IU="},"actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js":{"logical_path":"actioncable.js","mtime":"2025-02-26T00:16:38+09:00","size":16474,"digest":"1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e","integrity":"sha256-GiOf+WZEvI/WJvh6dBdbTufq/SAfUDQDSkM8ItjE3D4="},"actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js":{"logical_path":"actioncable.esm.js","mtime":"2025-02-26T00:16:38+09:00","size":14813,"digest":"555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0","integrity":"sha256-VVZ55E8Rm+en5UEh/KC+sKWDLykzwaHtzQUVu6+tIuA="}},"assets":{"manifest.js":"manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js","actiontext.js":"actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js","actiontext.esm.js":"actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js","trix.js":"trix-488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc.js","trix.css":"trix-4c7f56767699ae6c08f21004c9dc23a1b76b0734b0fca61d4534900ee0fef211.css","activestorage.js":"activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js","activestorage.esm.js":"activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js","actioncable.js":"actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js","actioncable.esm.js":"actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js"}} \ No newline at end of file diff --git a/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js b/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js new file mode 100644 index 0000000..5fc9943 --- /dev/null +++ b/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js @@ -0,0 +1,510 @@ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, + factory(global.ActionCable = {})); +})(this, (function(exports) { + "use strict"; + var adapters = { + logger: typeof console !== "undefined" ? console : undefined, + WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined + }; + var logger = { + log(...messages) { + if (this.enabled) { + messages.push(Date.now()); + adapters.logger.log("[ActionCable]", ...messages); + } + } + }; + const now = () => (new Date).getTime(); + const secondsSince = time => (now() - time) / 1e3; + class ConnectionMonitor { + constructor(connection) { + this.visibilityDidChange = this.visibilityDidChange.bind(this); + this.connection = connection; + this.reconnectAttempts = 0; + } + start() { + if (!this.isRunning()) { + this.startedAt = now(); + delete this.stoppedAt; + this.startPolling(); + addEventListener("visibilitychange", this.visibilityDidChange); + logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`); + } + } + stop() { + if (this.isRunning()) { + this.stoppedAt = now(); + this.stopPolling(); + removeEventListener("visibilitychange", this.visibilityDidChange); + logger.log("ConnectionMonitor stopped"); + } + } + isRunning() { + return this.startedAt && !this.stoppedAt; + } + recordMessage() { + this.pingedAt = now(); + } + recordConnect() { + this.reconnectAttempts = 0; + delete this.disconnectedAt; + logger.log("ConnectionMonitor recorded connect"); + } + recordDisconnect() { + this.disconnectedAt = now(); + logger.log("ConnectionMonitor recorded disconnect"); + } + startPolling() { + this.stopPolling(); + this.poll(); + } + stopPolling() { + clearTimeout(this.pollTimeout); + } + poll() { + this.pollTimeout = setTimeout((() => { + this.reconnectIfStale(); + this.poll(); + }), this.getPollInterval()); + } + getPollInterval() { + const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor; + const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10)); + const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate; + const jitter = jitterMax * Math.random(); + return staleThreshold * 1e3 * backoff * (1 + jitter); + } + reconnectIfStale() { + if (this.connectionIsStale()) { + logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`); + this.reconnectAttempts++; + if (this.disconnectedRecently()) { + logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`); + } else { + logger.log("ConnectionMonitor reopening"); + this.connection.reopen(); + } + } + } + get refreshedAt() { + return this.pingedAt ? this.pingedAt : this.startedAt; + } + connectionIsStale() { + return secondsSince(this.refreshedAt) > this.constructor.staleThreshold; + } + disconnectedRecently() { + return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; + } + visibilityDidChange() { + if (document.visibilityState === "visible") { + setTimeout((() => { + if (this.connectionIsStale() || !this.connection.isOpen()) { + logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`); + this.connection.reopen(); + } + }), 200); + } + } + } + ConnectionMonitor.staleThreshold = 6; + ConnectionMonitor.reconnectionBackoffRate = .15; + var INTERNAL = { + message_types: { + welcome: "welcome", + disconnect: "disconnect", + ping: "ping", + confirmation: "confirm_subscription", + rejection: "reject_subscription" + }, + disconnect_reasons: { + unauthorized: "unauthorized", + invalid_request: "invalid_request", + server_restart: "server_restart", + remote: "remote" + }, + default_mount_path: "/cable", + protocols: [ "actioncable-v1-json", "actioncable-unsupported" ] + }; + const {message_types: message_types, protocols: protocols} = INTERNAL; + const supportedProtocols = protocols.slice(0, protocols.length - 1); + const indexOf = [].indexOf; + class Connection { + constructor(consumer) { + this.open = this.open.bind(this); + this.consumer = consumer; + this.subscriptions = this.consumer.subscriptions; + this.monitor = new ConnectionMonitor(this); + this.disconnected = true; + } + send(data) { + if (this.isOpen()) { + this.webSocket.send(JSON.stringify(data)); + return true; + } else { + return false; + } + } + open() { + if (this.isActive()) { + logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`); + return false; + } else { + const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ]; + logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`); + if (this.webSocket) { + this.uninstallEventHandlers(); + } + this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols); + this.installEventHandlers(); + this.monitor.start(); + return true; + } + } + close({allowReconnect: allowReconnect} = { + allowReconnect: true + }) { + if (!allowReconnect) { + this.monitor.stop(); + } + if (this.isOpen()) { + return this.webSocket.close(); + } + } + reopen() { + logger.log(`Reopening WebSocket, current state is ${this.getState()}`); + if (this.isActive()) { + try { + return this.close(); + } catch (error) { + logger.log("Failed to reopen WebSocket", error); + } finally { + logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`); + setTimeout(this.open, this.constructor.reopenDelay); + } + } else { + return this.open(); + } + } + getProtocol() { + if (this.webSocket) { + return this.webSocket.protocol; + } + } + isOpen() { + return this.isState("open"); + } + isActive() { + return this.isState("open", "connecting"); + } + triedToReconnect() { + return this.monitor.reconnectAttempts > 0; + } + isProtocolSupported() { + return indexOf.call(supportedProtocols, this.getProtocol()) >= 0; + } + isState(...states) { + return indexOf.call(states, this.getState()) >= 0; + } + getState() { + if (this.webSocket) { + for (let state in adapters.WebSocket) { + if (adapters.WebSocket[state] === this.webSocket.readyState) { + return state.toLowerCase(); + } + } + } + return null; + } + installEventHandlers() { + for (let eventName in this.events) { + const handler = this.events[eventName].bind(this); + this.webSocket[`on${eventName}`] = handler; + } + } + uninstallEventHandlers() { + for (let eventName in this.events) { + this.webSocket[`on${eventName}`] = function() {}; + } + } + } + Connection.reopenDelay = 500; + Connection.prototype.events = { + message(event) { + if (!this.isProtocolSupported()) { + return; + } + const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data); + this.monitor.recordMessage(); + switch (type) { + case message_types.welcome: + if (this.triedToReconnect()) { + this.reconnectAttempted = true; + } + this.monitor.recordConnect(); + return this.subscriptions.reload(); + + case message_types.disconnect: + logger.log(`Disconnecting. Reason: ${reason}`); + return this.close({ + allowReconnect: reconnect + }); + + case message_types.ping: + return null; + + case message_types.confirmation: + this.subscriptions.confirmSubscription(identifier); + if (this.reconnectAttempted) { + this.reconnectAttempted = false; + return this.subscriptions.notify(identifier, "connected", { + reconnected: true + }); + } else { + return this.subscriptions.notify(identifier, "connected", { + reconnected: false + }); + } + + case message_types.rejection: + return this.subscriptions.reject(identifier); + + default: + return this.subscriptions.notify(identifier, "received", message); + } + }, + open() { + logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`); + this.disconnected = false; + if (!this.isProtocolSupported()) { + logger.log("Protocol is unsupported. Stopping monitor and disconnecting."); + return this.close({ + allowReconnect: false + }); + } + }, + close(event) { + logger.log("WebSocket onclose event"); + if (this.disconnected) { + return; + } + this.disconnected = true; + this.monitor.recordDisconnect(); + return this.subscriptions.notifyAll("disconnected", { + willAttemptReconnect: this.monitor.isRunning() + }); + }, + error() { + logger.log("WebSocket onerror event"); + } + }; + const extend = function(object, properties) { + if (properties != null) { + for (let key in properties) { + const value = properties[key]; + object[key] = value; + } + } + return object; + }; + class Subscription { + constructor(consumer, params = {}, mixin) { + this.consumer = consumer; + this.identifier = JSON.stringify(params); + extend(this, mixin); + } + perform(action, data = {}) { + data.action = action; + return this.send(data); + } + send(data) { + return this.consumer.send({ + command: "message", + identifier: this.identifier, + data: JSON.stringify(data) + }); + } + unsubscribe() { + return this.consumer.subscriptions.remove(this); + } + } + class SubscriptionGuarantor { + constructor(subscriptions) { + this.subscriptions = subscriptions; + this.pendingSubscriptions = []; + } + guarantee(subscription) { + if (this.pendingSubscriptions.indexOf(subscription) == -1) { + logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`); + this.pendingSubscriptions.push(subscription); + } else { + logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`); + } + this.startGuaranteeing(); + } + forget(subscription) { + logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`); + this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription)); + } + startGuaranteeing() { + this.stopGuaranteeing(); + this.retrySubscribing(); + } + stopGuaranteeing() { + clearTimeout(this.retryTimeout); + } + retrySubscribing() { + this.retryTimeout = setTimeout((() => { + if (this.subscriptions && typeof this.subscriptions.subscribe === "function") { + this.pendingSubscriptions.map((subscription => { + logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`); + this.subscriptions.subscribe(subscription); + })); + } + }), 500); + } + } + class Subscriptions { + constructor(consumer) { + this.consumer = consumer; + this.guarantor = new SubscriptionGuarantor(this); + this.subscriptions = []; + } + create(channelName, mixin) { + const channel = channelName; + const params = typeof channel === "object" ? channel : { + channel: channel + }; + const subscription = new Subscription(this.consumer, params, mixin); + return this.add(subscription); + } + add(subscription) { + this.subscriptions.push(subscription); + this.consumer.ensureActiveConnection(); + this.notify(subscription, "initialized"); + this.subscribe(subscription); + return subscription; + } + remove(subscription) { + this.forget(subscription); + if (!this.findAll(subscription.identifier).length) { + this.sendCommand(subscription, "unsubscribe"); + } + return subscription; + } + reject(identifier) { + return this.findAll(identifier).map((subscription => { + this.forget(subscription); + this.notify(subscription, "rejected"); + return subscription; + })); + } + forget(subscription) { + this.guarantor.forget(subscription); + this.subscriptions = this.subscriptions.filter((s => s !== subscription)); + return subscription; + } + findAll(identifier) { + return this.subscriptions.filter((s => s.identifier === identifier)); + } + reload() { + return this.subscriptions.map((subscription => this.subscribe(subscription))); + } + notifyAll(callbackName, ...args) { + return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args))); + } + notify(subscription, callbackName, ...args) { + let subscriptions; + if (typeof subscription === "string") { + subscriptions = this.findAll(subscription); + } else { + subscriptions = [ subscription ]; + } + return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined)); + } + subscribe(subscription) { + if (this.sendCommand(subscription, "subscribe")) { + this.guarantor.guarantee(subscription); + } + } + confirmSubscription(identifier) { + logger.log(`Subscription confirmed ${identifier}`); + this.findAll(identifier).map((subscription => this.guarantor.forget(subscription))); + } + sendCommand(subscription, command) { + const {identifier: identifier} = subscription; + return this.consumer.send({ + command: command, + identifier: identifier + }); + } + } + class Consumer { + constructor(url) { + this._url = url; + this.subscriptions = new Subscriptions(this); + this.connection = new Connection(this); + this.subprotocols = []; + } + get url() { + return createWebSocketURL(this._url); + } + send(data) { + return this.connection.send(data); + } + connect() { + return this.connection.open(); + } + disconnect() { + return this.connection.close({ + allowReconnect: false + }); + } + ensureActiveConnection() { + if (!this.connection.isActive()) { + return this.connection.open(); + } + } + addSubProtocol(subprotocol) { + this.subprotocols = [ ...this.subprotocols, subprotocol ]; + } + } + function createWebSocketURL(url) { + if (typeof url === "function") { + url = url(); + } + if (url && !/^wss?:/i.test(url)) { + const a = document.createElement("a"); + a.href = url; + a.href = a.href; + a.protocol = a.protocol.replace("http", "ws"); + return a.href; + } else { + return url; + } + } + function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) { + return new Consumer(url); + } + function getConfig(name) { + const element = document.head.querySelector(`meta[name='action-cable-${name}']`); + if (element) { + return element.getAttribute("content"); + } + } + exports.Connection = Connection; + exports.ConnectionMonitor = ConnectionMonitor; + exports.Consumer = Consumer; + exports.INTERNAL = INTERNAL; + exports.Subscription = Subscription; + exports.SubscriptionGuarantor = SubscriptionGuarantor; + exports.Subscriptions = Subscriptions; + exports.adapters = adapters; + exports.createConsumer = createConsumer; + exports.createWebSocketURL = createWebSocketURL; + exports.getConfig = getConfig; + exports.logger = logger; + Object.defineProperty(exports, "__esModule", { + value: true + }); +})); diff --git a/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js.gz b/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js.gz new file mode 100644 index 0000000..8ffa46b Binary files /dev/null and b/backend/public/assets/actioncable-1a239ff96644bc8fd626f87a74175b4ee7eafd201f5034034a433c22d8c4dc3e.js.gz differ diff --git a/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js b/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js new file mode 100644 index 0000000..1832009 --- /dev/null +++ b/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js @@ -0,0 +1,512 @@ +var adapters = { + logger: typeof console !== "undefined" ? console : undefined, + WebSocket: typeof WebSocket !== "undefined" ? WebSocket : undefined +}; + +var logger = { + log(...messages) { + if (this.enabled) { + messages.push(Date.now()); + adapters.logger.log("[ActionCable]", ...messages); + } + } +}; + +const now = () => (new Date).getTime(); + +const secondsSince = time => (now() - time) / 1e3; + +class ConnectionMonitor { + constructor(connection) { + this.visibilityDidChange = this.visibilityDidChange.bind(this); + this.connection = connection; + this.reconnectAttempts = 0; + } + start() { + if (!this.isRunning()) { + this.startedAt = now(); + delete this.stoppedAt; + this.startPolling(); + addEventListener("visibilitychange", this.visibilityDidChange); + logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`); + } + } + stop() { + if (this.isRunning()) { + this.stoppedAt = now(); + this.stopPolling(); + removeEventListener("visibilitychange", this.visibilityDidChange); + logger.log("ConnectionMonitor stopped"); + } + } + isRunning() { + return this.startedAt && !this.stoppedAt; + } + recordMessage() { + this.pingedAt = now(); + } + recordConnect() { + this.reconnectAttempts = 0; + delete this.disconnectedAt; + logger.log("ConnectionMonitor recorded connect"); + } + recordDisconnect() { + this.disconnectedAt = now(); + logger.log("ConnectionMonitor recorded disconnect"); + } + startPolling() { + this.stopPolling(); + this.poll(); + } + stopPolling() { + clearTimeout(this.pollTimeout); + } + poll() { + this.pollTimeout = setTimeout((() => { + this.reconnectIfStale(); + this.poll(); + }), this.getPollInterval()); + } + getPollInterval() { + const {staleThreshold: staleThreshold, reconnectionBackoffRate: reconnectionBackoffRate} = this.constructor; + const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10)); + const jitterMax = this.reconnectAttempts === 0 ? 1 : reconnectionBackoffRate; + const jitter = jitterMax * Math.random(); + return staleThreshold * 1e3 * backoff * (1 + jitter); + } + reconnectIfStale() { + if (this.connectionIsStale()) { + logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`); + this.reconnectAttempts++; + if (this.disconnectedRecently()) { + logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`); + } else { + logger.log("ConnectionMonitor reopening"); + this.connection.reopen(); + } + } + } + get refreshedAt() { + return this.pingedAt ? this.pingedAt : this.startedAt; + } + connectionIsStale() { + return secondsSince(this.refreshedAt) > this.constructor.staleThreshold; + } + disconnectedRecently() { + return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; + } + visibilityDidChange() { + if (document.visibilityState === "visible") { + setTimeout((() => { + if (this.connectionIsStale() || !this.connection.isOpen()) { + logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`); + this.connection.reopen(); + } + }), 200); + } + } +} + +ConnectionMonitor.staleThreshold = 6; + +ConnectionMonitor.reconnectionBackoffRate = .15; + +var INTERNAL = { + message_types: { + welcome: "welcome", + disconnect: "disconnect", + ping: "ping", + confirmation: "confirm_subscription", + rejection: "reject_subscription" + }, + disconnect_reasons: { + unauthorized: "unauthorized", + invalid_request: "invalid_request", + server_restart: "server_restart", + remote: "remote" + }, + default_mount_path: "/cable", + protocols: [ "actioncable-v1-json", "actioncable-unsupported" ] +}; + +const {message_types: message_types, protocols: protocols} = INTERNAL; + +const supportedProtocols = protocols.slice(0, protocols.length - 1); + +const indexOf = [].indexOf; + +class Connection { + constructor(consumer) { + this.open = this.open.bind(this); + this.consumer = consumer; + this.subscriptions = this.consumer.subscriptions; + this.monitor = new ConnectionMonitor(this); + this.disconnected = true; + } + send(data) { + if (this.isOpen()) { + this.webSocket.send(JSON.stringify(data)); + return true; + } else { + return false; + } + } + open() { + if (this.isActive()) { + logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`); + return false; + } else { + const socketProtocols = [ ...protocols, ...this.consumer.subprotocols || [] ]; + logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`); + if (this.webSocket) { + this.uninstallEventHandlers(); + } + this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols); + this.installEventHandlers(); + this.monitor.start(); + return true; + } + } + close({allowReconnect: allowReconnect} = { + allowReconnect: true + }) { + if (!allowReconnect) { + this.monitor.stop(); + } + if (this.isOpen()) { + return this.webSocket.close(); + } + } + reopen() { + logger.log(`Reopening WebSocket, current state is ${this.getState()}`); + if (this.isActive()) { + try { + return this.close(); + } catch (error) { + logger.log("Failed to reopen WebSocket", error); + } finally { + logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`); + setTimeout(this.open, this.constructor.reopenDelay); + } + } else { + return this.open(); + } + } + getProtocol() { + if (this.webSocket) { + return this.webSocket.protocol; + } + } + isOpen() { + return this.isState("open"); + } + isActive() { + return this.isState("open", "connecting"); + } + triedToReconnect() { + return this.monitor.reconnectAttempts > 0; + } + isProtocolSupported() { + return indexOf.call(supportedProtocols, this.getProtocol()) >= 0; + } + isState(...states) { + return indexOf.call(states, this.getState()) >= 0; + } + getState() { + if (this.webSocket) { + for (let state in adapters.WebSocket) { + if (adapters.WebSocket[state] === this.webSocket.readyState) { + return state.toLowerCase(); + } + } + } + return null; + } + installEventHandlers() { + for (let eventName in this.events) { + const handler = this.events[eventName].bind(this); + this.webSocket[`on${eventName}`] = handler; + } + } + uninstallEventHandlers() { + for (let eventName in this.events) { + this.webSocket[`on${eventName}`] = function() {}; + } + } +} + +Connection.reopenDelay = 500; + +Connection.prototype.events = { + message(event) { + if (!this.isProtocolSupported()) { + return; + } + const {identifier: identifier, message: message, reason: reason, reconnect: reconnect, type: type} = JSON.parse(event.data); + this.monitor.recordMessage(); + switch (type) { + case message_types.welcome: + if (this.triedToReconnect()) { + this.reconnectAttempted = true; + } + this.monitor.recordConnect(); + return this.subscriptions.reload(); + + case message_types.disconnect: + logger.log(`Disconnecting. Reason: ${reason}`); + return this.close({ + allowReconnect: reconnect + }); + + case message_types.ping: + return null; + + case message_types.confirmation: + this.subscriptions.confirmSubscription(identifier); + if (this.reconnectAttempted) { + this.reconnectAttempted = false; + return this.subscriptions.notify(identifier, "connected", { + reconnected: true + }); + } else { + return this.subscriptions.notify(identifier, "connected", { + reconnected: false + }); + } + + case message_types.rejection: + return this.subscriptions.reject(identifier); + + default: + return this.subscriptions.notify(identifier, "received", message); + } + }, + open() { + logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`); + this.disconnected = false; + if (!this.isProtocolSupported()) { + logger.log("Protocol is unsupported. Stopping monitor and disconnecting."); + return this.close({ + allowReconnect: false + }); + } + }, + close(event) { + logger.log("WebSocket onclose event"); + if (this.disconnected) { + return; + } + this.disconnected = true; + this.monitor.recordDisconnect(); + return this.subscriptions.notifyAll("disconnected", { + willAttemptReconnect: this.monitor.isRunning() + }); + }, + error() { + logger.log("WebSocket onerror event"); + } +}; + +const extend = function(object, properties) { + if (properties != null) { + for (let key in properties) { + const value = properties[key]; + object[key] = value; + } + } + return object; +}; + +class Subscription { + constructor(consumer, params = {}, mixin) { + this.consumer = consumer; + this.identifier = JSON.stringify(params); + extend(this, mixin); + } + perform(action, data = {}) { + data.action = action; + return this.send(data); + } + send(data) { + return this.consumer.send({ + command: "message", + identifier: this.identifier, + data: JSON.stringify(data) + }); + } + unsubscribe() { + return this.consumer.subscriptions.remove(this); + } +} + +class SubscriptionGuarantor { + constructor(subscriptions) { + this.subscriptions = subscriptions; + this.pendingSubscriptions = []; + } + guarantee(subscription) { + if (this.pendingSubscriptions.indexOf(subscription) == -1) { + logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`); + this.pendingSubscriptions.push(subscription); + } else { + logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`); + } + this.startGuaranteeing(); + } + forget(subscription) { + logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`); + this.pendingSubscriptions = this.pendingSubscriptions.filter((s => s !== subscription)); + } + startGuaranteeing() { + this.stopGuaranteeing(); + this.retrySubscribing(); + } + stopGuaranteeing() { + clearTimeout(this.retryTimeout); + } + retrySubscribing() { + this.retryTimeout = setTimeout((() => { + if (this.subscriptions && typeof this.subscriptions.subscribe === "function") { + this.pendingSubscriptions.map((subscription => { + logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`); + this.subscriptions.subscribe(subscription); + })); + } + }), 500); + } +} + +class Subscriptions { + constructor(consumer) { + this.consumer = consumer; + this.guarantor = new SubscriptionGuarantor(this); + this.subscriptions = []; + } + create(channelName, mixin) { + const channel = channelName; + const params = typeof channel === "object" ? channel : { + channel: channel + }; + const subscription = new Subscription(this.consumer, params, mixin); + return this.add(subscription); + } + add(subscription) { + this.subscriptions.push(subscription); + this.consumer.ensureActiveConnection(); + this.notify(subscription, "initialized"); + this.subscribe(subscription); + return subscription; + } + remove(subscription) { + this.forget(subscription); + if (!this.findAll(subscription.identifier).length) { + this.sendCommand(subscription, "unsubscribe"); + } + return subscription; + } + reject(identifier) { + return this.findAll(identifier).map((subscription => { + this.forget(subscription); + this.notify(subscription, "rejected"); + return subscription; + })); + } + forget(subscription) { + this.guarantor.forget(subscription); + this.subscriptions = this.subscriptions.filter((s => s !== subscription)); + return subscription; + } + findAll(identifier) { + return this.subscriptions.filter((s => s.identifier === identifier)); + } + reload() { + return this.subscriptions.map((subscription => this.subscribe(subscription))); + } + notifyAll(callbackName, ...args) { + return this.subscriptions.map((subscription => this.notify(subscription, callbackName, ...args))); + } + notify(subscription, callbackName, ...args) { + let subscriptions; + if (typeof subscription === "string") { + subscriptions = this.findAll(subscription); + } else { + subscriptions = [ subscription ]; + } + return subscriptions.map((subscription => typeof subscription[callbackName] === "function" ? subscription[callbackName](...args) : undefined)); + } + subscribe(subscription) { + if (this.sendCommand(subscription, "subscribe")) { + this.guarantor.guarantee(subscription); + } + } + confirmSubscription(identifier) { + logger.log(`Subscription confirmed ${identifier}`); + this.findAll(identifier).map((subscription => this.guarantor.forget(subscription))); + } + sendCommand(subscription, command) { + const {identifier: identifier} = subscription; + return this.consumer.send({ + command: command, + identifier: identifier + }); + } +} + +class Consumer { + constructor(url) { + this._url = url; + this.subscriptions = new Subscriptions(this); + this.connection = new Connection(this); + this.subprotocols = []; + } + get url() { + return createWebSocketURL(this._url); + } + send(data) { + return this.connection.send(data); + } + connect() { + return this.connection.open(); + } + disconnect() { + return this.connection.close({ + allowReconnect: false + }); + } + ensureActiveConnection() { + if (!this.connection.isActive()) { + return this.connection.open(); + } + } + addSubProtocol(subprotocol) { + this.subprotocols = [ ...this.subprotocols, subprotocol ]; + } +} + +function createWebSocketURL(url) { + if (typeof url === "function") { + url = url(); + } + if (url && !/^wss?:/i.test(url)) { + const a = document.createElement("a"); + a.href = url; + a.href = a.href; + a.protocol = a.protocol.replace("http", "ws"); + return a.href; + } else { + return url; + } +} + +function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) { + return new Consumer(url); +} + +function getConfig(name) { + const element = document.head.querySelector(`meta[name='action-cable-${name}']`); + if (element) { + return element.getAttribute("content"); + } +} + +export { Connection, ConnectionMonitor, Consumer, INTERNAL, Subscription, SubscriptionGuarantor, Subscriptions, adapters, createConsumer, createWebSocketURL, getConfig, logger }; diff --git a/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js.gz b/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js.gz new file mode 100644 index 0000000..62de4cb Binary files /dev/null and b/backend/public/assets/actioncable.esm-555679e44f119be7a7e54121fca0beb0a5832f2933c1a1edcd0515bbafad22e0.js.gz differ diff --git a/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js b/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js new file mode 100644 index 0000000..e61f03c --- /dev/null +++ b/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js @@ -0,0 +1,884 @@ +(function(factory) { + typeof define === "function" && define.amd ? define(factory) : factory(); +})((function() { + "use strict"; + 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 button = event.target.closest("button, input"); + if (button && button.type === "submit" && button.form) { + submitButtonsByForm.set(button.form, button); + } + } + 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)); + this.dispatch("start"); + } + directUploadWillStoreFileWithXHR(xhr) { + xhr.upload.addEventListener("progress", (event => { + const progress = event.loaded / event.total * 100; + this.attachment.setUploadProgress(progress); + if (progress) { + this.dispatch("progress", { + progress: progress + }); + } + })); + } + directUploadDidComplete(error, attributes) { + if (error) { + this.dispatchError(error); + } else { + this.attachment.setAttributes({ + sgid: attributes.attachable_sgid, + url: this.createBlobUrl(attributes.signed_id, attributes.filename) + }); + this.dispatch("end"); + } + } + createBlobUrl(signedId, filename) { + return this.blobUrlTemplate.replace(":signed_id", signedId).replace(":filename", encodeURIComponent(filename)); + } + dispatch(name, detail = {}) { + detail.attachment = this.attachment; + return dispatchEvent(this.element, `direct-upload:${name}`, { + detail: detail + }); + } + dispatchError(error) { + const event = this.dispatch("error", { + error: error + }); + if (!event.defaultPrevented) { + alert(error); + } + } + 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/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js.gz b/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js.gz new file mode 100644 index 0000000..393a007 Binary files /dev/null and b/backend/public/assets/actiontext-9ccbf682451d439611fe5a048e55157c56038840ebb4a8292437eb55fd31c83d.js.gz differ diff --git a/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js b/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js new file mode 100644 index 0000000..7902835 --- /dev/null +++ b/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js @@ -0,0 +1,911 @@ +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 button = event.target.closest("button, input"); + if (button && button.type === "submit" && button.form) { + submitButtonsByForm.set(button.form, button); + } +} + +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)); + this.dispatch("start"); + } + directUploadWillStoreFileWithXHR(xhr) { + xhr.upload.addEventListener("progress", (event => { + const progress = event.loaded / event.total * 100; + this.attachment.setUploadProgress(progress); + if (progress) { + this.dispatch("progress", { + progress: progress + }); + } + })); + } + directUploadDidComplete(error, attributes) { + if (error) { + this.dispatchError(error); + } else { + this.attachment.setAttributes({ + sgid: attributes.attachable_sgid, + url: this.createBlobUrl(attributes.signed_id, attributes.filename) + }); + this.dispatch("end"); + } + } + createBlobUrl(signedId, filename) { + return this.blobUrlTemplate.replace(":signed_id", signedId).replace(":filename", encodeURIComponent(filename)); + } + dispatch(name, detail = {}) { + detail.attachment = this.attachment; + return dispatchEvent(this.element, `direct-upload:${name}`, { + detail: detail + }); + } + dispatchError(error) { + const event = this.dispatch("error", { + error: error + }); + if (!event.defaultPrevented) { + alert(error); + } + } + 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/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js.gz b/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js.gz new file mode 100644 index 0000000..8339b17 Binary files /dev/null and b/backend/public/assets/actiontext.esm-6790d4daf5458cd6eb8de2d56e754a340caeba909cb2952a35fb478955493d8c.js.gz differ diff --git a/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js b/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js new file mode 100644 index 0000000..754c727 --- /dev/null +++ b/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js @@ -0,0 +1,830 @@ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, + factory(global.ActiveStorage = {})); +})(this, (function(exports) { + "use strict"; + 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 button = event.target.closest("button, input"); + if (button && button.type === "submit" && button.form) { + submitButtonsByForm.set(button.form, button); + } + } + 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); + exports.DirectUpload = DirectUpload; + exports.DirectUploadController = DirectUploadController; + exports.DirectUploadsController = DirectUploadsController; + exports.dispatchEvent = dispatchEvent; + exports.start = start; + Object.defineProperty(exports, "__esModule", { + value: true + }); +})); diff --git a/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js.gz b/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js.gz new file mode 100644 index 0000000..41703fb Binary files /dev/null and b/backend/public/assets/activestorage-cf22a497d1956e8f0d3ec50f45d68961aa0d138ee062802c36ae384efb260aa2.js.gz differ diff --git a/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js b/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js new file mode 100644 index 0000000..1ac9835 --- /dev/null +++ b/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js @@ -0,0 +1,848 @@ +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 button = event.target.closest("button, input"); + if (button && button.type === "submit" && button.form) { + submitButtonsByForm.set(button.form, button); + } +} + +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); + +export { DirectUpload, DirectUploadController, DirectUploadsController, dispatchEvent, start }; diff --git a/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js.gz b/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js.gz new file mode 100644 index 0000000..074d5a2 Binary files /dev/null and b/backend/public/assets/activestorage.esm-8bb028228e622c50501b3eb269cbe4044a46da07b565a90e5ae6f221dc06e485.js.gz differ diff --git a/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js b/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js @@ -0,0 +1,3 @@ + + + diff --git a/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js.gz b/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js.gz new file mode 100644 index 0000000..a8e8ffe Binary files /dev/null and b/backend/public/assets/manifest-6a3cf5192354f71615ac51034b3e97c20eda99643fcaf5bbe6d41ad59bd12167.js.gz differ diff --git a/backend/public/assets/trix-488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc.js b/backend/public/assets/trix-488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc.js new file mode 100644 index 0000000..1cd185f --- /dev/null +++ b/backend/public/assets/trix-488ac88b50edee8aab73dd826e9967075eed9000a13fd13cbcc17745f86650cc.js @@ -0,0 +1,13720 @@ +/* +Trix 2.1.10 +Copyright © 2024 37signals, LLC + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Trix = factory()); +})(this, (function () { 'use strict'; + + var name = "trix"; + var version = "2.1.10"; + var description = "A rich text editor for everyday writing"; + var main = "dist/trix.umd.min.js"; + var module = "dist/trix.esm.min.js"; + var style = "dist/trix.css"; + var files = [ + "dist/*.css", + "dist/*.js", + "dist/*.map", + "src/{inspector,trix}/*.js" + ]; + var repository = { + type: "git", + url: "git+https://github.com/basecamp/trix.git" + }; + var keywords = [ + "rich text", + "wysiwyg", + "editor" + ]; + var author = "37signals, LLC"; + var license = "MIT"; + var bugs = { + url: "https://github.com/basecamp/trix/issues" + }; + var homepage = "https://trix-editor.org/"; + var devDependencies = { + "@babel/core": "^7.16.0", + "@babel/preset-env": "^7.16.4", + "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-commonjs": "^22.0.2", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.3.0", + "@web/dev-server": "^0.1.34", + "babel-eslint": "^10.1.0", + concurrently: "^7.4.0", + eslint: "^7.32.0", + esm: "^3.2.25", + karma: "6.4.1", + "karma-chrome-launcher": "3.2.0", + "karma-qunit": "^4.1.2", + "karma-sauce-launcher": "^4.3.6", + "node-sass": "^7.0.1", + qunit: "2.19.1", + rangy: "^1.3.0", + rollup: "^2.56.3", + "rollup-plugin-includepaths": "^0.2.4", + "rollup-plugin-terser": "^7.0.2", + svgo: "^2.8.0", + webdriverio: "^7.19.5" + }; + var resolutions = { + webdriverio: "^7.19.5" + }; + var scripts = { + "build-css": "node-sass --functions=./assets/trix/stylesheets/functions assets/trix.scss dist/trix.css", + "build-js": "rollup -c", + "build-assets": "cp -f assets/*.html dist/", + build: "yarn run build-js && yarn run build-css && yarn run build-assets", + watch: "rollup -c -w", + lint: "eslint .", + pretest: "yarn run lint && yarn run build", + test: "karma start", + prerelease: "yarn version && yarn test", + release: "npm adduser && npm publish", + postrelease: "git push && git push --tags", + dev: "web-dev-server --app-index index.html --root-dir dist --node-resolve --open", + start: "yarn build-assets && concurrently --kill-others --names js,css,dev-server 'yarn watch' 'yarn build-css --watch' 'yarn dev'" + }; + var dependencies = { + dompurify: "^3.2.3" + }; + var _package = { + name: name, + version: version, + description: description, + main: main, + module: module, + style: style, + files: files, + repository: repository, + keywords: keywords, + author: author, + license: license, + bugs: bugs, + homepage: homepage, + devDependencies: devDependencies, + resolutions: resolutions, + scripts: scripts, + dependencies: dependencies + }; + + const attachmentSelector = "[data-trix-attachment]"; + const attachments = { + preview: { + presentation: "gallery", + caption: { + name: true, + size: true + } + }, + file: { + caption: { + size: true + } + } + }; + + const attributes = { + default: { + tagName: "div", + parse: false + }, + quote: { + tagName: "blockquote", + nestable: true + }, + heading1: { + tagName: "h1", + terminal: true, + breakOnReturn: true, + group: false + }, + code: { + tagName: "pre", + terminal: true, + htmlAttributes: ["language"], + text: { + plaintext: true + } + }, + bulletList: { + tagName: "ul", + parse: false + }, + bullet: { + tagName: "li", + listAttribute: "bulletList", + group: false, + nestable: true, + test(element) { + return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName; + } + }, + numberList: { + tagName: "ol", + parse: false + }, + number: { + tagName: "li", + listAttribute: "numberList", + group: false, + nestable: true, + test(element) { + return tagName$1(element.parentNode) === attributes[this.listAttribute].tagName; + } + }, + attachmentGallery: { + tagName: "div", + exclusive: true, + terminal: true, + parse: false, + group: false + } + }; + const tagName$1 = element => { + var _element$tagName; + return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase(); + }; + + const androidVersionMatch = navigator.userAgent.match(/android\s([0-9]+.*Chrome)/i); + const androidVersion = androidVersionMatch && parseInt(androidVersionMatch[1]); + var browser$1 = { + // Android emits composition events when moving the cursor through existing text + // Introduced in Chrome 65: https://bugs.chromium.org/p/chromium/issues/detail?id=764439#c9 + composesExistingText: /Android.*Chrome/.test(navigator.userAgent), + // Android 13, especially on Samsung keyboards, emits extra compositionend and beforeinput events + // that can make the input handler lose the current selection or enter an infinite input -> render -> input + // loop. + recentAndroid: androidVersion && androidVersion > 12, + samsungAndroid: androidVersion && navigator.userAgent.match(/Android.*SM-/), + // IE 11 activates resizing handles on editable elements that have "layout" + forcesObjectResizing: /Trident.*rv:11/.test(navigator.userAgent), + // https://www.w3.org/TR/input-events-1/ + https://www.w3.org/TR/input-events-2/ + supportsInputEvents: typeof InputEvent !== "undefined" && ["data", "getTargetRanges", "inputType"].every(prop => prop in InputEvent.prototype) + }; + + var css$3 = { + attachment: "attachment", + attachmentCaption: "attachment__caption", + attachmentCaptionEditor: "attachment__caption-editor", + attachmentMetadata: "attachment__metadata", + attachmentMetadataContainer: "attachment__metadata-container", + attachmentName: "attachment__name", + attachmentProgress: "attachment__progress", + attachmentSize: "attachment__size", + attachmentToolbar: "attachment__toolbar", + attachmentGallery: "attachment-gallery" + }; + + var lang$1 = { + attachFiles: "Attach Files", + bold: "Bold", + bullets: "Bullets", + byte: "Byte", + bytes: "Bytes", + captionPlaceholder: "Add a caption…", + code: "Code", + heading1: "Heading", + indent: "Increase Level", + italic: "Italic", + link: "Link", + numbers: "Numbers", + outdent: "Decrease Level", + quote: "Quote", + redo: "Redo", + remove: "Remove", + strike: "Strikethrough", + undo: "Undo", + unlink: "Unlink", + url: "URL", + urlPlaceholder: "Enter a URL…", + GB: "GB", + KB: "KB", + MB: "MB", + PB: "PB", + TB: "TB" + }; + + /* eslint-disable + no-case-declarations, + */ + const sizes = [lang$1.bytes, lang$1.KB, lang$1.MB, lang$1.GB, lang$1.TB, lang$1.PB]; + var file_size_formatting = { + prefix: "IEC", + precision: 2, + formatter(number) { + switch (number) { + case 0: + return "0 ".concat(lang$1.bytes); + case 1: + return "1 ".concat(lang$1.byte); + default: + let base; + if (this.prefix === "SI") { + base = 1000; + } else if (this.prefix === "IEC") { + base = 1024; + } + const exp = Math.floor(Math.log(number) / Math.log(base)); + const humanSize = number / Math.pow(base, exp); + const string = humanSize.toFixed(this.precision); + const withoutInsignificantZeros = string.replace(/0*$/, "").replace(/\.$/, ""); + return "".concat(withoutInsignificantZeros, " ").concat(sizes[exp]); + } + } + }; + + const ZERO_WIDTH_SPACE = "\uFEFF"; + const NON_BREAKING_SPACE = "\u00A0"; + const OBJECT_REPLACEMENT_CHARACTER = "\uFFFC"; + + const extend = function (properties) { + for (const key in properties) { + const value = properties[key]; + this[key] = value; + } + return this; + }; + + const html$2 = document.documentElement; + const match = html$2.matches; + const handleEvent = function (eventName) { + let { + onElement, + matchingSelector, + withCallback, + inPhase, + preventDefault, + times + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const element = onElement ? onElement : html$2; + const selector = matchingSelector; + const useCapture = inPhase === "capturing"; + const handler = function (event) { + if (times != null && --times === 0) { + handler.destroy(); + } + const target = findClosestElementFromNode(event.target, { + matchingSelector: selector + }); + if (target != null) { + withCallback === null || withCallback === void 0 || withCallback.call(target, event, target); + if (preventDefault) { + event.preventDefault(); + } + } + }; + handler.destroy = () => element.removeEventListener(eventName, handler, useCapture); + element.addEventListener(eventName, handler, useCapture); + return handler; + }; + const handleEventOnce = function (eventName) { + let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + options.times = 1; + return handleEvent(eventName, options); + }; + const triggerEvent = function (eventName) { + let { + onElement, + bubbles, + cancelable, + attributes + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const element = onElement != null ? onElement : html$2; + bubbles = bubbles !== false; + cancelable = cancelable !== false; + const event = document.createEvent("Events"); + event.initEvent(eventName, bubbles, cancelable); + if (attributes != null) { + extend.call(event, attributes); + } + return element.dispatchEvent(event); + }; + const elementMatchesSelector = function (element, selector) { + if ((element === null || element === void 0 ? void 0 : element.nodeType) === 1) { + return match.call(element, selector); + } + }; + const findClosestElementFromNode = function (node) { + let { + matchingSelector, + untilNode + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + while (node && node.nodeType !== Node.ELEMENT_NODE) { + node = node.parentNode; + } + if (node == null) { + return; + } + if (matchingSelector != null) { + if (node.closest && untilNode == null) { + return node.closest(matchingSelector); + } else { + while (node && node !== untilNode) { + if (elementMatchesSelector(node, matchingSelector)) { + return node; + } + node = node.parentNode; + } + } + } else { + return node; + } + }; + const findInnerElement = function (element) { + while ((_element = element) !== null && _element !== void 0 && _element.firstElementChild) { + var _element; + element = element.firstElementChild; + } + return element; + }; + const innerElementIsActive = element => document.activeElement !== element && elementContainsNode(element, document.activeElement); + const elementContainsNode = function (element, node) { + if (!element || !node) { + return; + } + while (node) { + if (node === element) { + return true; + } + node = node.parentNode; + } + }; + const findNodeFromContainerAndOffset = function (container, offset) { + if (!container) { + return; + } + if (container.nodeType === Node.TEXT_NODE) { + return container; + } else if (offset === 0) { + return container.firstChild != null ? container.firstChild : container; + } else { + return container.childNodes.item(offset - 1); + } + }; + const findElementFromContainerAndOffset = function (container, offset) { + const node = findNodeFromContainerAndOffset(container, offset); + return findClosestElementFromNode(node); + }; + const findChildIndexOfNode = function (node) { + var _node; + if (!((_node = node) !== null && _node !== void 0 && _node.parentNode)) { + return; + } + let childIndex = 0; + node = node.previousSibling; + while (node) { + childIndex++; + node = node.previousSibling; + } + return childIndex; + }; + const removeNode = node => { + var _node$parentNode; + return node === null || node === void 0 || (_node$parentNode = node.parentNode) === null || _node$parentNode === void 0 ? void 0 : _node$parentNode.removeChild(node); + }; + const walkTree = function (tree) { + let { + onlyNodesOfType, + usingFilter, + expandEntityReferences + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + const whatToShow = (() => { + switch (onlyNodesOfType) { + case "element": + return NodeFilter.SHOW_ELEMENT; + case "text": + return NodeFilter.SHOW_TEXT; + case "comment": + return NodeFilter.SHOW_COMMENT; + default: + return NodeFilter.SHOW_ALL; + } + })(); + return document.createTreeWalker(tree, whatToShow, usingFilter != null ? usingFilter : null, expandEntityReferences === true); + }; + const tagName = element => { + var _element$tagName; + return element === null || element === void 0 || (_element$tagName = element.tagName) === null || _element$tagName === void 0 ? void 0 : _element$tagName.toLowerCase(); + }; + const makeElement = function (tag) { + let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + let key, value; + if (typeof tag === "object") { + options = tag; + tag = options.tagName; + } else { + options = { + attributes: options + }; + } + const element = document.createElement(tag); + if (options.editable != null) { + if (options.attributes == null) { + options.attributes = {}; + } + options.attributes.contenteditable = options.editable; + } + if (options.attributes) { + for (key in options.attributes) { + value = options.attributes[key]; + element.setAttribute(key, value); + } + } + if (options.style) { + for (key in options.style) { + value = options.style[key]; + element.style[key] = value; + } + } + if (options.data) { + for (key in options.data) { + value = options.data[key]; + element.dataset[key] = value; + } + } + if (options.className) { + options.className.split(" ").forEach(className => { + element.classList.add(className); + }); + } + if (options.textContent) { + element.textContent = options.textContent; + } + if (options.childNodes) { + [].concat(options.childNodes).forEach(childNode => { + element.appendChild(childNode); + }); + } + return element; + }; + let blockTagNames = undefined; + const getBlockTagNames = function () { + if (blockTagNames != null) { + return blockTagNames; + } + blockTagNames = []; + for (const key in attributes) { + const attributes$1 = attributes[key]; + if (attributes$1.tagName) { + blockTagNames.push(attributes$1.tagName); + } + } + return blockTagNames; + }; + const nodeIsBlockContainer = node => nodeIsBlockStartComment(node === null || node === void 0 ? void 0 : node.firstChild); + const nodeProbablyIsBlockContainer = function (node) { + return getBlockTagNames().includes(tagName(node)) && !getBlockTagNames().includes(tagName(node.firstChild)); + }; + const nodeIsBlockStart = function (node) { + let { + strict + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { + strict: true + }; + if (strict) { + return nodeIsBlockStartComment(node); + } else { + return nodeIsBlockStartComment(node) || !nodeIsBlockStartComment(node.firstChild) && nodeProbablyIsBlockContainer(node); + } + }; + const nodeIsBlockStartComment = node => nodeIsCommentNode(node) && (node === null || node === void 0 ? void 0 : node.data) === "block"; + const nodeIsCommentNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.COMMENT_NODE; + const nodeIsCursorTarget = function (node) { + let { + name + } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + if (!node) { + return; + } + if (nodeIsTextNode(node)) { + if (node.data === ZERO_WIDTH_SPACE) { + if (name) { + return node.parentNode.dataset.trixCursorTarget === name; + } else { + return true; + } + } + } else { + return nodeIsCursorTarget(node.firstChild); + } + }; + const nodeIsAttachmentElement = node => elementMatchesSelector(node, attachmentSelector); + const nodeIsEmptyTextNode = node => nodeIsTextNode(node) && (node === null || node === void 0 ? void 0 : node.data) === ""; + const nodeIsTextNode = node => (node === null || node === void 0 ? void 0 : node.nodeType) === Node.TEXT_NODE; + + const input = { + level2Enabled: true, + getLevel() { + if (this.level2Enabled && browser$1.supportsInputEvents) { + return 2; + } else { + return 0; + } + }, + pickFiles(callback) { + const input = makeElement("input", { + type: "file", + multiple: true, + hidden: true, + id: this.fileInputId + }); + input.addEventListener("change", () => { + callback(input.files); + removeNode(input); + }); + removeNode(document.getElementById(this.fileInputId)); + document.body.appendChild(input); + input.click(); + } + }; + + var key_names = { + 8: "backspace", + 9: "tab", + 13: "return", + 27: "escape", + 37: "left", + 39: "right", + 46: "delete", + 68: "d", + 72: "h", + 79: "o" + }; + + var parser = { + removeBlankTableCells: false, + tableCellSeparator: " | ", + tableRowSeparator: "\n" + }; + + var text_attributes = { + bold: { + tagName: "strong", + inheritable: true, + parser(element) { + const style = window.getComputedStyle(element); + return style.fontWeight === "bold" || style.fontWeight >= 600; + } + }, + italic: { + tagName: "em", + inheritable: true, + parser(element) { + const style = window.getComputedStyle(element); + return style.fontStyle === "italic"; + } + }, + href: { + groupTagName: "a", + parser(element) { + const matchingSelector = "a:not(".concat(attachmentSelector, ")"); + const link = element.closest(matchingSelector); + if (link) { + return link.getAttribute("href"); + } + } + }, + strike: { + tagName: "del", + inheritable: true + }, + frozen: { + style: { + backgroundColor: "highlight" + } + } + }; + + var toolbar = { + getDefaultHTML() { + return "
\n\n