@@ -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 | |||
@@ -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 | |||
@@ -0,0 +1,3 @@ | |||
//= link_tree ../images | |||
//= link_directory ../javascripts .js | |||
//= link_directory ../stylesheets .css |
@@ -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"}} |
@@ -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 | |||
}); | |||
})); |
@@ -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 }; |
@@ -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(); | |||
} | |||
})); | |||
})); |
@@ -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(); | |||
} | |||
})); |
@@ -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 | |||
}); | |||
})); |
@@ -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 }; |
@@ -0,0 +1,3 @@ | |||
@@ -0,0 +1,412 @@ | |||
trix-editor { | |||
border: 1px solid #bbb; | |||
border-radius: 3px; | |||
margin: 0; | |||
padding: 0.4em 0.6em; | |||
min-height: 5em; | |||
outline: none; } | |||
trix-toolbar * { | |||
box-sizing: border-box; } | |||
trix-toolbar .trix-button-row { | |||
display: flex; | |||
flex-wrap: nowrap; | |||
justify-content: space-between; | |||
overflow-x: auto; } | |||
trix-toolbar .trix-button-group { | |||
display: flex; | |||
margin-bottom: 10px; | |||
border: 1px solid #bbb; | |||
border-top-color: #ccc; | |||
border-bottom-color: #888; | |||
border-radius: 3px; } | |||
trix-toolbar .trix-button-group:not(:first-child) { | |||
margin-left: 1.5vw; } | |||
@media (max-width: 768px) { | |||
trix-toolbar .trix-button-group:not(:first-child) { | |||
margin-left: 0; } } | |||
trix-toolbar .trix-button-group-spacer { | |||
flex-grow: 1; } | |||
@media (max-width: 768px) { | |||
trix-toolbar .trix-button-group-spacer { | |||
display: none; } } | |||
trix-toolbar .trix-button { | |||
position: relative; | |||
float: left; | |||
color: rgba(0, 0, 0, 0.6); | |||
font-size: 0.75em; | |||
font-weight: 600; | |||
white-space: nowrap; | |||
padding: 0 0.5em; | |||
margin: 0; | |||
outline: none; | |||
border: none; | |||
border-bottom: 1px solid #ddd; | |||
border-radius: 0; | |||
background: transparent; } | |||
trix-toolbar .trix-button:not(:first-child) { | |||
border-left: 1px solid #ccc; } | |||
trix-toolbar .trix-button.trix-active { | |||
background: #cbeefa; | |||
color: black; } | |||
trix-toolbar .trix-button:not(:disabled) { | |||
cursor: pointer; } | |||
trix-toolbar .trix-button:disabled { | |||
color: rgba(0, 0, 0, 0.125); } | |||
@media (max-width: 768px) { | |||
trix-toolbar .trix-button { | |||
letter-spacing: -0.01em; | |||
padding: 0 0.3em; } } | |||
trix-toolbar .trix-button--icon { | |||
font-size: inherit; | |||
width: 2.6em; | |||
height: 1.6em; | |||
max-width: calc(0.8em + 4vw); | |||
text-indent: -9999px; } | |||
@media (max-width: 768px) { | |||
trix-toolbar .trix-button--icon { | |||
height: 2em; | |||
max-width: calc(0.8em + 3.5vw); } } | |||
trix-toolbar .trix-button--icon::before { | |||
display: inline-block; | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
bottom: 0; | |||
left: 0; | |||
opacity: 0.6; | |||
content: ""; | |||
background-position: center; | |||
background-repeat: no-repeat; | |||
background-size: contain; } | |||
@media (max-width: 768px) { | |||
trix-toolbar .trix-button--icon::before { | |||
right: 6%; | |||
left: 6%; } } | |||
trix-toolbar .trix-button--icon.trix-active::before { | |||
opacity: 1; } | |||
trix-toolbar .trix-button--icon:disabled::before { | |||
opacity: 0.125; } | |||
trix-toolbar .trix-button--icon-attach::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.5%2018V7.5c0-2.25%203-2.25%203%200V18c0%204.125-6%204.125-6%200V7.5c0-6.375%209-6.375%209%200V18%22%20stroke%3D%22%23000%22%20stroke-width%3D%222%22%20stroke-miterlimit%3D%2210%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E"); | |||
top: 8%; | |||
bottom: 4%; } | |||
trix-toolbar .trix-button--icon-bold::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6.522%2019.242a.5.5%200%200%201-.5-.5V5.35a.5.5%200%200%201%20.5-.5h5.783c1.347%200%202.46.345%203.24.982.783.64%201.216%201.562%201.216%202.683%200%201.13-.587%202.129-1.476%202.71a.35.35%200%200%200%20.049.613c1.259.56%202.101%201.742%202.101%203.22%200%201.282-.483%202.334-1.363%203.063-.876.726-2.132%201.12-3.66%201.12h-5.89ZM9.27%207.347v3.362h1.97c.766%200%201.347-.17%201.733-.464.38-.291.587-.716.587-1.27%200-.53-.183-.928-.513-1.198-.334-.273-.838-.43-1.505-.43H9.27Zm0%205.606v3.791h2.389c.832%200%201.448-.177%201.853-.497.399-.315.614-.786.614-1.423%200-.62-.22-1.077-.63-1.385-.418-.313-1.053-.486-1.905-.486H9.27Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-italic::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M9%205h6.5v2h-2.23l-2.31%2010H13v2H6v-2h2.461l2.306-10H9V5Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-link::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M18.948%205.258a4.337%204.337%200%200%200-6.108%200L11.217%206.87a.993.993%200%200%200%200%201.41c.392.39%201.027.39%201.418%200l1.623-1.613a2.323%202.323%200%200%201%203.271%200%202.29%202.29%200%200%201%200%203.251l-2.393%202.38a3.021%203.021%200%200%201-4.255%200l-.05-.049a1.007%201.007%200%200%200-1.418%200%20.993.993%200%200%200%200%201.41l.05.049a5.036%205.036%200%200%200%207.091%200l2.394-2.38a4.275%204.275%200%200%200%200-6.072Zm-13.683%2013.6a4.337%204.337%200%200%200%206.108%200l1.262-1.255a.993.993%200%200%200%200-1.41%201.007%201.007%200%200%200-1.418%200L9.954%2017.45a2.323%202.323%200%200%201-3.27%200%202.29%202.29%200%200%201%200-3.251l2.344-2.331a2.579%202.579%200%200%201%203.631%200c.392.39%201.027.39%201.419%200a.993.993%200%200%200%200-1.41%204.593%204.593%200%200%200-6.468%200l-2.345%202.33a4.275%204.275%200%200%200%200%206.072Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-strike::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M6%2014.986c.088%202.647%202.246%204.258%205.635%204.258%203.496%200%205.713-1.728%205.713-4.463%200-.275-.02-.536-.062-.781h-3.461c.398.293.573.654.573%201.123%200%201.035-1.074%201.787-2.646%201.787-1.563%200-2.773-.762-2.91-1.924H6ZM6.432%2010h3.763c-.632-.314-.914-.715-.914-1.273%200-1.045.977-1.739%202.432-1.739%201.475%200%202.52.723%202.617%201.914h2.764c-.05-2.548-2.11-4.238-5.39-4.238-3.145%200-5.392%201.719-5.392%204.316%200%20.363.04.703.12%201.02ZM4%2011a1%201%200%201%200%200%202h15a1%201%200%201%200%200-2H4Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-quote::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M4.581%208.471c.44-.5%201.056-.834%201.758-.995C8.074%207.17%209.201%207.822%2010%208.752c1.354%201.578%201.33%203.555.394%205.277-.941%201.731-2.788%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.121-.49.16-.764.294-.286.567-.566.791-.835.222-.266.413-.54.524-.815.113-.28.156-.597.026-.908-.128-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.674-2.7c0-.905.283-1.59.72-2.088Zm9.419%200c.44-.5%201.055-.834%201.758-.995%201.734-.306%202.862.346%203.66%201.276%201.355%201.578%201.33%203.555.395%205.277-.941%201.731-2.789%203.163-4.988%203.56a.622.622%200%200%201-.653-.317c-.113-.205-.122-.49.16-.764.294-.286.567-.566.791-.835.222-.266.412-.54.523-.815.114-.28.157-.597.026-.908-.127-.303-.39-.524-.72-.69a3.02%203.02%200%200%201-1.672-2.701c0-.905.283-1.59.72-2.088Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-heading-1::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21.5%207.5v-3h-12v3H14v13h3v-13h4.5ZM9%2013.5h3.5v-3h-10v3H6v7h3v-7Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-code::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3.293%2011.293a1%201%200%200%200%200%201.414l4%204a1%201%200%201%200%201.414-1.414L5.414%2012l3.293-3.293a1%201%200%200%200-1.414-1.414l-4%204Zm13.414%205.414%204-4a1%201%200%200%200%200-1.414l-4-4a1%201%200%201%200-1.414%201.414L18.586%2012l-3.293%203.293a1%201%200%200%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-bullet-list::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%207.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203ZM8%206a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-2.5-5a1.5%201.5%200%201%201-3%200%201.5%201.5%200%200%201%203%200ZM5%2019.5a1.5%201.5%200%201%200%200-3%201.5%201.5%200%200%200%200%203Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-number-list::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%204h2v4H4V5H3V4Zm5%202a1%201%200%200%201%201-1h11a1%201%200%201%201%200%202H9a1%201%200%200%201-1-1Zm1%205a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm0%206a1%201%200%201%200%200%202h11a1%201%200%201%200%200-2H9Zm-3.5-7H6v1l-1.5%202H6v1H3v-1l1.667-2H3v-1h2.5ZM3%2017v-1h3v4H3v-1h2v-.5H4v-1h1V17H3Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-undo::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M3%2014a1%201%200%200%200%201%201h6a1%201%200%201%200%200-2H6.257c2.247-2.764%205.151-3.668%207.579-3.264%202.589.432%204.739%202.356%205.174%205.405a1%201%200%200%200%201.98-.283c-.564-3.95-3.415-6.526-6.825-7.095C11.084%207.25%207.63%208.377%205%2011.39V8a1%201%200%200%200-2%200v6Zm2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-redo::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M21%2014a1%201%200%200%201-1%201h-6a1%201%200%201%201%200-2h3.743c-2.247-2.764-5.151-3.668-7.579-3.264-2.589.432-4.739%202.356-5.174%205.405a1%201%200%200%201-1.98-.283c.564-3.95%203.415-6.526%206.826-7.095%203.08-.513%206.534.614%209.164%203.626V8a1%201%200%201%201%202%200v6Zm-2-1Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-decrease-nesting-level::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-3.707-5.707a1%201%200%200%200%200%201.414l2%202a1%201%200%201%200%201.414-1.414L4.414%2012l1.293-1.293a1%201%200%200%200-1.414-1.414l-2%202Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-button--icon-increase-nesting-level::before { | |||
background-image: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20d%3D%22M5%206a1%201%200%200%201%201-1h12a1%201%200%201%201%200%202H6a1%201%200%200%201-1-1Zm4%205a1%201%200%201%200%200%202h9a1%201%200%201%200%200-2H9Zm-3%206a1%201%200%201%200%200%202h12a1%201%200%201%200%200-2H6Zm-2.293-2.293%202-2a1%201%200%200%200%200-1.414l-2-2a1%201%200%201%200-1.414%201.414L3.586%2012l-1.293%201.293a1%201%200%201%200%201.414%201.414Z%22%20fill%3D%22%23000%22%2F%3E%3C%2Fsvg%3E"); } | |||
trix-toolbar .trix-dialogs { | |||
position: relative; } | |||
trix-toolbar .trix-dialog { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
right: 0; | |||
font-size: 0.75em; | |||
padding: 15px 10px; | |||
background: #fff; | |||
box-shadow: 0 0.3em 1em #ccc; | |||
border-top: 2px solid #888; | |||
border-radius: 5px; | |||
z-index: 5; } | |||
trix-toolbar .trix-input--dialog { | |||
font-size: inherit; | |||
font-weight: normal; | |||
padding: 0.5em 0.8em; | |||
margin: 0 10px 0 0; | |||
border-radius: 3px; | |||
border: 1px solid #bbb; | |||
background-color: #fff; | |||
box-shadow: none; | |||
outline: none; | |||
-webkit-appearance: none; | |||
-moz-appearance: none; } | |||
trix-toolbar .trix-input--dialog.validate:invalid { | |||
box-shadow: #F00 0px 0px 1.5px 1px; } | |||
trix-toolbar .trix-button--dialog { | |||
font-size: inherit; | |||
padding: 0.5em; | |||
border-bottom: none; } | |||
trix-toolbar .trix-dialog--link { | |||
max-width: 600px; } | |||
trix-toolbar .trix-dialog__link-fields { | |||
display: flex; | |||
align-items: baseline; } | |||
trix-toolbar .trix-dialog__link-fields .trix-input { | |||
flex: 1; } | |||
trix-toolbar .trix-dialog__link-fields .trix-button-group { | |||
flex: 0 0 content; | |||
margin: 0; } | |||
trix-editor [data-trix-mutable]:not(.attachment__caption-editor) { | |||
-webkit-user-select: none; | |||
-moz-user-select: none; | |||
-ms-user-select: none; | |||
user-select: none; } | |||
trix-editor [data-trix-mutable]::-moz-selection, | |||
trix-editor [data-trix-cursor-target]::-moz-selection, trix-editor [data-trix-mutable] ::-moz-selection { | |||
background: none; } | |||
trix-editor [data-trix-mutable]::selection, | |||
trix-editor [data-trix-cursor-target]::selection, trix-editor [data-trix-mutable] ::selection { | |||
background: none; } | |||
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::-moz-selection { | |||
background: highlight; } | |||
trix-editor .attachment__caption-editor:focus[data-trix-mutable]::selection { | |||
background: highlight; } | |||
trix-editor [data-trix-mutable].attachment.attachment--file { | |||
box-shadow: 0 0 0 2px highlight; | |||
border-color: transparent; } | |||
trix-editor [data-trix-mutable].attachment img { | |||
box-shadow: 0 0 0 2px highlight; } | |||
trix-editor .attachment { | |||
position: relative; } | |||
trix-editor .attachment:hover { | |||
cursor: default; } | |||
trix-editor .attachment--preview .attachment__caption:hover { | |||
cursor: text; } | |||
trix-editor .attachment__progress { | |||
position: absolute; | |||
z-index: 1; | |||
height: 20px; | |||
top: calc(50% - 10px); | |||
left: 5%; | |||
width: 90%; | |||
opacity: 0.9; | |||
transition: opacity 200ms ease-in; } | |||
trix-editor .attachment__progress[value="100"] { | |||
opacity: 0; } | |||
trix-editor .attachment__caption-editor { | |||
display: inline-block; | |||
width: 100%; | |||
margin: 0; | |||
padding: 0; | |||
font-size: inherit; | |||
font-family: inherit; | |||
line-height: inherit; | |||
color: inherit; | |||
text-align: center; | |||
vertical-align: top; | |||
border: none; | |||
outline: none; | |||
-webkit-appearance: none; | |||
-moz-appearance: none; } | |||
trix-editor .attachment__toolbar { | |||
position: absolute; | |||
z-index: 1; | |||
top: -0.9em; | |||
left: 0; | |||
width: 100%; | |||
text-align: center; } | |||
trix-editor .trix-button-group { | |||
display: inline-flex; } | |||
trix-editor .trix-button { | |||
position: relative; | |||
float: left; | |||
color: #666; | |||
white-space: nowrap; | |||
font-size: 80%; | |||
padding: 0 0.8em; | |||
margin: 0; | |||
outline: none; | |||
border: none; | |||
border-radius: 0; | |||
background: transparent; } | |||
trix-editor .trix-button:not(:first-child) { | |||
border-left: 1px solid #ccc; } | |||
trix-editor .trix-button.trix-active { | |||
background: #cbeefa; } | |||
trix-editor .trix-button:not(:disabled) { | |||
cursor: pointer; } | |||
trix-editor .trix-button--remove { | |||
text-indent: -9999px; | |||
display: inline-block; | |||
padding: 0; | |||
outline: none; | |||
width: 1.8em; | |||
height: 1.8em; | |||
line-height: 1.8em; | |||
border-radius: 50%; | |||
background-color: #fff; | |||
border: 2px solid highlight; | |||
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.25); } | |||
trix-editor .trix-button--remove::before { | |||
display: inline-block; | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
bottom: 0; | |||
left: 0; | |||
opacity: 0.7; | |||
content: ""; | |||
background-image: url("data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22%2F%3E%3Cpath%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3C%2Fsvg%3E"); | |||
background-position: center; | |||
background-repeat: no-repeat; | |||
background-size: 90%; } | |||
trix-editor .trix-button--remove:hover { | |||
border-color: #333; } | |||
trix-editor .trix-button--remove:hover::before { | |||
opacity: 1; } | |||
trix-editor .attachment__metadata-container { | |||
position: relative; } | |||
trix-editor .attachment__metadata { | |||
position: absolute; | |||
left: 50%; | |||
top: 2em; | |||
transform: translate(-50%, 0); | |||
max-width: 90%; | |||
padding: 0.1em 0.6em; | |||
font-size: 0.8em; | |||
color: #fff; | |||
background-color: rgba(0, 0, 0, 0.7); | |||
border-radius: 3px; } | |||
trix-editor .attachment__metadata .attachment__name { | |||
display: inline-block; | |||
max-width: 100%; | |||
vertical-align: bottom; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; } | |||
trix-editor .attachment__metadata .attachment__size { | |||
margin-left: 0.2em; | |||
white-space: nowrap; } | |||
.trix-content { | |||
line-height: 1.5; | |||
overflow-wrap: break-word; | |||
word-break: break-word; } | |||
.trix-content * { | |||
box-sizing: border-box; | |||
margin: 0; | |||
padding: 0; } | |||
.trix-content h1 { | |||
font-size: 1.2em; | |||
line-height: 1.2; } | |||
.trix-content blockquote { | |||
border: 0 solid #ccc; | |||
border-left-width: 0.3em; | |||
margin-left: 0.3em; | |||
padding-left: 0.6em; } | |||
.trix-content [dir=rtl] blockquote, | |||
.trix-content blockquote[dir=rtl] { | |||
border-width: 0; | |||
border-right-width: 0.3em; | |||
margin-right: 0.3em; | |||
padding-right: 0.6em; } | |||
.trix-content li { | |||
margin-left: 1em; } | |||
.trix-content [dir=rtl] li { | |||
margin-right: 1em; } | |||
.trix-content pre { | |||
display: inline-block; | |||
width: 100%; | |||
vertical-align: top; | |||
font-family: monospace; | |||
font-size: 0.9em; | |||
padding: 0.5em; | |||
white-space: pre; | |||
background-color: #eee; | |||
overflow-x: auto; } | |||
.trix-content img { | |||
max-width: 100%; | |||
height: auto; } | |||
.trix-content .attachment { | |||
display: inline-block; | |||
position: relative; | |||
max-width: 100%; } | |||
.trix-content .attachment a { | |||
color: inherit; | |||
text-decoration: none; } | |||
.trix-content .attachment a:hover, .trix-content .attachment a:visited:hover { | |||
color: inherit; } | |||
.trix-content .attachment__caption { | |||
text-align: center; } | |||
.trix-content .attachment__caption .attachment__name + .attachment__size::before { | |||
content: ' \2022 '; } | |||
.trix-content .attachment--preview { | |||
width: 100%; | |||
text-align: center; } | |||
.trix-content .attachment--preview .attachment__caption { | |||
color: #666; | |||
font-size: 0.9em; | |||
line-height: 1.2; } | |||
.trix-content .attachment--file { | |||
color: #333; | |||
line-height: 1; | |||
margin: 0 2px 2px 2px; | |||
padding: 0.4em 1em; | |||
border: 1px solid #bbb; | |||
border-radius: 5px; } | |||
.trix-content .attachment-gallery { | |||
display: flex; | |||
flex-wrap: wrap; | |||
position: relative; } | |||
.trix-content .attachment-gallery .attachment { | |||
flex: 1 0 33%; | |||
padding: 0 0.5em; | |||
max-width: 33%; } | |||
.trix-content .attachment-gallery.attachment-gallery--2 .attachment, .trix-content .attachment-gallery.attachment-gallery--4 .attachment { | |||
flex-basis: 50%; | |||
max-width: 50%; } |