@@ -35,6 +35,7 @@ gem "thruster", require: false | |||||
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible | ||||
gem "rack-cors" | gem "rack-cors" | ||||
gem 'jwt' | gem 'jwt' | ||||
gem 'sprockets-rails' | |||||
group :development, :test do | group :development, :test do | ||||
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem | ||||
@@ -276,6 +276,13 @@ GEM | |||||
fugit (~> 1.11.0) | fugit (~> 1.11.0) | ||||
railties (>= 7.1) | railties (>= 7.1) | ||||
thor (~> 1.3.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-gnu) | ||||
sqlite3 (2.6.0-aarch64-linux-musl) | sqlite3 (2.6.0-aarch64-linux-musl) | ||||
sqlite3 (2.6.0-arm-linux-gnu) | sqlite3 (2.6.0-arm-linux-gnu) | ||||
@@ -338,6 +345,7 @@ DEPENDENCIES | |||||
solid_cable | solid_cable | ||||
solid_cache | solid_cache | ||||
solid_queue | solid_queue | ||||
sprockets-rails | |||||
sqlite3 (>= 2.1) | sqlite3 (>= 2.1) | ||||
thruster | thruster | ||||
tzinfo-data | 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%; } |