diff --git a/app/assets/javascripts/admin/trix_direct_upload.js b/app/assets/javascripts/admin/trix_direct_upload.js
deleted file mode 100644
index f622fde380044ed1514be220a77e2adb234263b4..0000000000000000000000000000000000000000
--- a/app/assets/javascripts/admin/trix_direct_upload.js
+++ /dev/null
@@ -1,20 +0,0 @@
-(function() {
-
-  addEventListener("trix-attachment-add", function(event) {
-      var file = event.attachment.file;
-    if (file) {
-      var upload = new window.ActiveStorage.DirectUpload(file,'/rails/active_storage/direct_uploads', window);
-      upload.create((error, attributes) => {
-        if (error) {
-          return false;
-        } else {
-          return event.attachment.setAttributes({
-            url: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
-            href: `/rails/active_storage/blobs/${attributes.signed_id}/${attributes.filename}`,
-          });
-        }
-      });
-    }
-  })
-
-})();
diff --git a/app/assets/stylesheets/commons/_actiontext.sass b/app/assets/stylesheets/commons/_actiontext.sass
index 3669d72572d4c5be3e2064f5daa85601f32ab15e..266498590207b3450a25522403ccd54e7ea1bdd4 100644
--- a/app/assets/stylesheets/commons/_actiontext.sass
+++ b/app/assets/stylesheets/commons/_actiontext.sass
@@ -17,6 +17,9 @@
     h3
         font-size: 1.15rem
 
+    .attachment
+      text-align: initial
+
     .attachment-gallery
         > action-text-attachment,
         > .attachment
diff --git a/app/controllers/active_storage/direct_uploads_controller.rb b/app/controllers/active_storage/direct_uploads_controller.rb
deleted file mode 100644
index fe237d61410167e36036c91eed19c037b729974b..0000000000000000000000000000000000000000
--- a/app/controllers/active_storage/direct_uploads_controller.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-# Creates a new blob on the server side in anticipation of a direct-to-service upload from the client side.
-# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference
-# the blob that was created up front.
-class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
-  include ApplicationController::WithUniversity
-
-  def create
-    blob = ActiveStorage::Blob.create_before_direct_upload!(**blob_args)
-    blob.update_column(:university_id, current_university.id)
-    render json: direct_upload_json(blob)
-  end
-
-  private
-    def blob_args
-      params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
-    end
-
-    def direct_upload_json(blob)
-      blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
-        url: blob.service_url_for_direct_upload,
-        headers: blob.service_headers_for_direct_upload
-      })
-    end
-
-end
diff --git a/app/models/research/journal/article.rb b/app/models/research/journal/article.rb
index 20a415c655deaf08befa1c4a77cf15c2faa94b1f..0410c664d3b6e9859bf11e853a325e9043904631 100644
--- a/app/models/research/journal/article.rb
+++ b/app/models/research/journal/article.rb
@@ -10,7 +10,7 @@
 #  text                       :text
 #  title                      :string
 #  created_at                 :datetime         not null
-#  updated_at                 :date             not null
+#  updated_at                 :datetime         not null
 #  research_journal_id        :uuid             not null
 #  research_journal_volume_id :uuid
 #  university_id              :uuid             not null
diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb
new file mode 100644
index 0000000000000000000000000000000000000000..49ba357dd1d4afc63ac0bae78dbcd1a882df9e9e
--- /dev/null
+++ b/app/views/active_storage/blobs/_blob.html.erb
@@ -0,0 +1,14 @@
+<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
+  <% if blob.representable? %>
+    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
+  <% end %>
+
+  <figcaption class="attachment__caption">
+    <% if caption = blob.try(:caption) %>
+      <%= caption %>
+    <% else %>
+      <span class="attachment__name"><%= blob.filename %></span>
+      <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
+    <% end %>
+  </figcaption>
+</figure>
diff --git a/config/initializers/action_text.rb b/config/initializers/action_text.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c0e43cbd0ea266efd23ebdcf6f782b92cfb443d1
--- /dev/null
+++ b/config/initializers/action_text.rb
@@ -0,0 +1,8 @@
+Rails.configuration.to_prepare do
+  ActionText::RichText.class_eval do
+    delegate :university, :university_id, to: :record
+  end
+
+  ActionText::ContentHelper.allowed_tags += Rails.application.config.action_view.sanitized_allowed_tags
+  ActionText::ContentHelper.allowed_attributes += Rails.application.config.action_view.sanitized_allowed_attributes
+end
diff --git a/db/migrate/20211021093238_create_action_text_tables.action_text.rb b/db/migrate/20211021093238_create_action_text_tables.action_text.rb
new file mode 100644
index 0000000000000000000000000000000000000000..bba5dc5076e10fd807b859cae4e18e88f73edf3f
--- /dev/null
+++ b/db/migrate/20211021093238_create_action_text_tables.action_text.rb
@@ -0,0 +1,14 @@
+# This migration comes from action_text (originally 20180528164100)
+class CreateActionTextTables < ActiveRecord::Migration[6.0]
+  def change
+    create_table :action_text_rich_texts, id: :uuid do |t|
+      t.string     :name, null: false
+      t.text       :body, size: :long
+      t.references :record, null: false, polymorphic: true, index: false, type: :uuid
+
+      t.timestamps
+
+      t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 33335b1a6728ebcbbcefa04562c7abd95e52c004..bb4be990c1026ec5e143e9ad9659da38c4033190 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,13 +10,23 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2021_10_20_090658) do
+ActiveRecord::Schema.define(version: 2021_10_21_093238) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
   enable_extension "plpgsql"
 
-  create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "action_text_rich_texts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.string "name", null: false
+    t.text "body"
+    t.string "record_type", null: false
+    t.uuid "record_id", null: false
+    t.datetime "created_at", precision: 6, null: false
+    t.datetime "updated_at", precision: 6, null: false
+    t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
+  end
+
+  create_table "active_storage_attachments", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name", null: false
     t.string "record_type", null: false
     t.uuid "record_id", null: false
@@ -26,7 +36,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
   end
 
-  create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_blobs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "key", null: false
     t.string "filename", null: false
     t.string "content_type"
@@ -40,13 +50,13 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_active_storage_blobs_on_university_id"
   end
 
-  create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "active_storage_variant_records", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "blob_id", null: false
     t.string "variation_digest", null: false
     t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
   end
 
-  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_criterions", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.integer "number"
     t.text "name"
     t.text "description"
@@ -54,7 +64,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.datetime "updated_at", precision: 6, null: false
   end
 
-  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "administration_qualiopi_indicators", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "criterion_id", null: false
     t.integer "number"
     t.text "name"
@@ -68,7 +78,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["criterion_id"], name: "index_administration_qualiopi_indicators_on_criterion_id"
   end
 
-  create_table "communication_website_imported_media", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_media", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "identifier"
     t.jsonb "data"
     t.text "file_url"
@@ -83,7 +93,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["website_id"], name: "index_communication_website_imported_media_on_website_id"
   end
 
-  create_table "communication_website_imported_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "page_id", null: false
@@ -106,7 +116,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["website_id"], name: "index_communication_website_imported_pages_on_website_id"
   end
 
-  create_table "communication_website_imported_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.uuid "post_id", null: false
@@ -129,7 +139,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["website_id"], name: "index_communication_website_imported_posts_on_website_id"
   end
 
-  create_table "communication_website_imported_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_imported_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "website_id", null: false
     t.integer "status", default: 0
@@ -139,7 +149,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["website_id"], name: "index_communication_website_imported_websites_on_website_id"
   end
 
-  create_table "communication_website_pages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_pages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -160,7 +170,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_communication_website_pages_on_university_id"
   end
 
-  create_table "communication_website_posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_website_posts", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "communication_website_id", null: false
     t.string "title"
@@ -175,7 +185,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_communication_website_posts_on_university_id"
   end
 
-  create_table "communication_websites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "communication_websites", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.string "domain"
@@ -204,7 +214,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
-  create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "education_programs", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
     t.integer "level"
@@ -225,14 +235,14 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_education_programs_on_university_id"
   end
 
-  create_table "languages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "languages", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "iso_code"
     t.datetime "created_at", precision: 6, null: false
     t.datetime "updated_at", precision: 6, null: false
   end
 
-  create_table "research_journal_articles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_articles", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "title"
     t.text "text"
     t.date "published_at"
@@ -240,7 +250,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.uuid "research_journal_id", null: false
     t.uuid "research_journal_volume_id"
     t.datetime "created_at", precision: 6, null: false
-    t.date "updated_at", null: false
+    t.datetime "updated_at", precision: 6, null: false
     t.uuid "updated_by_id"
     t.text "abstract"
     t.text "references"
@@ -258,7 +268,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["researcher_id"], name: "index_research_journal_articles_researchers_on_researcher_id"
   end
 
-  create_table "research_journal_volumes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journal_volumes", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.uuid "research_journal_id", null: false
     t.string "title"
@@ -272,7 +282,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_research_journal_volumes_on_university_id"
   end
 
-  create_table "research_journals", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_journals", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "title"
     t.text "description"
@@ -284,7 +294,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["university_id"], name: "index_research_journals_on_university_id"
   end
 
-  create_table "research_researchers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "research_researchers", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "first_name"
     t.string "last_name"
     t.text "biography"
@@ -294,7 +304,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.index ["user_id"], name: "index_research_researchers_on_user_id"
   end
 
-  create_table "universities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "universities", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.string "name"
     t.string "identifier"
     t.string "address"
@@ -309,7 +319,7 @@ ActiveRecord::Schema.define(version: 2021_10_20_090658) do
     t.string "sms_sender_name"
   end
 
-  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+  create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "first_name"
     t.string "last_name"
diff --git a/test/fixtures/action_text/rich_texts.yml b/test/fixtures/action_text/rich_texts.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8b371ea604af5fba7cacf8f712528c1e12949959
--- /dev/null
+++ b/test/fixtures/action_text/rich_texts.yml
@@ -0,0 +1,4 @@
+# one:
+#   record: name_of_fixture (ClassOfFixture)
+#   name: content
+#   body: <p>In a <i>million</i> stars!</p>
diff --git a/vendor/assets/javascripts/actiontext.js b/vendor/assets/javascripts/actiontext.js
new file mode 100644
index 0000000000000000000000000000000000000000..792a2c1fc310fd79f41117dcd4d3c5b888837776
--- /dev/null
+++ b/vendor/assets/javascripts/actiontext.js
@@ -0,0 +1,880 @@
+var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+var activestorage = {exports: {}};
+
+(function (module, exports) {
+(function(global, factory) {
+  factory(exports) ;
+})(commonjsGlobal, (function(exports) {
+  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) {
+      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");
+      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) {
+      this.id = ++id;
+      this.file = file;
+      this.url = url;
+      this.delegate = delegate;
+    }
+    create(callback) {
+      FileChecksum.create(this.file, ((error, checksum) => {
+        if (error) {
+          callback(error);
+          return;
+        }
+        const blob = new BlobRecord(this.file, checksum, this.url);
+        notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
+        blob.create((error => {
+          if (error) {
+            callback(error);
+          } else {
+            const upload = new BlobUpload(blob);
+            notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr);
+            upload.create((error => {
+              if (error) {
+                callback(error);
+              } else {
+                callback(null, blob.toJSON());
+              }
+            }));
+          }
+        }));
+      }));
+    }
+  }
+  function notify(object, methodName, ...messages) {
+    if (object && typeof object[methodName] == "function") {
+      return object[methodName](...messages);
+    }
+  }
+  class DirectUploadController {
+    constructor(input, file) {
+      this.input = input;
+      this.file = file;
+      this.directUpload = new DirectUpload(this.file, this.url, this);
+      this.dispatch("initialize");
+    }
+    start(callback) {
+      const hiddenInput = document.createElement("input");
+      hiddenInput.type = "hidden";
+      hiddenInput.name = this.input.name;
+      this.input.insertAdjacentElement("beforebegin", hiddenInput);
+      this.dispatch("start");
+      this.directUpload.create(((error, attributes) => {
+        if (error) {
+          hiddenInput.parentNode.removeChild(hiddenInput);
+          this.dispatchError(error);
+        } else {
+          hiddenInput.value = attributes.signed_id;
+        }
+        this.dispatch("end");
+        callback(error);
+      }));
+    }
+    uploadRequestDidProgress(event) {
+      const progress = event.loaded / event.total * 100;
+      if (progress) {
+        this.dispatch("progress", {
+          progress: progress
+        });
+      }
+    }
+    get url() {
+      return this.input.getAttribute("data-direct-upload-url");
+    }
+    dispatch(name, detail = {}) {
+      detail.file = this.file;
+      detail.id = this.directUpload.id;
+      return dispatchEvent(this.input, `direct-upload:${name}`, {
+        detail: detail
+      });
+    }
+    dispatchError(error) {
+      const event = this.dispatch("error", {
+        error: error
+      });
+      if (!event.defaultPrevented) {
+        alert(error);
+      }
+    }
+    directUploadWillCreateBlobWithXHR(xhr) {
+      this.dispatch("before-blob-request", {
+        xhr: xhr
+      });
+    }
+    directUploadWillStoreFileWithXHR(xhr) {
+      this.dispatch("before-storage-request", {
+        xhr: xhr
+      });
+      xhr.upload.addEventListener("progress", (event => this.uploadRequestDidProgress(event)));
+    }
+  }
+  const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
+  class DirectUploadsController {
+    constructor(form) {
+      this.form = form;
+      this.inputs = findElements(form, inputSelector).filter((input => input.files.length));
+    }
+    start(callback) {
+      const controllers = this.createDirectUploadControllers();
+      const startNextController = () => {
+        const controller = controllers.shift();
+        if (controller) {
+          controller.start((error => {
+            if (error) {
+              callback(error);
+              this.dispatch("end");
+            } else {
+              startNextController();
+            }
+          }));
+        } else {
+          callback();
+          this.dispatch("end");
+        }
+      };
+      this.dispatch("start");
+      startNextController();
+    }
+    createDirectUploadControllers() {
+      const controllers = [];
+      this.inputs.forEach((input => {
+        toArray(input.files).forEach((file => {
+          const controller = new DirectUploadController(input, file);
+          controllers.push(controller);
+        }));
+      }));
+      return controllers;
+    }
+    dispatch(name, detail = {}) {
+      return dispatchEvent(this.form, `direct-uploads:${name}`, {
+        detail: detail
+      });
+    }
+  }
+  const processingAttribute = "data-direct-uploads-processing";
+  const submitButtonsByForm = new WeakMap;
+  let started = false;
+  function start() {
+    if (!started) {
+      started = true;
+      document.addEventListener("click", didClick, true);
+      document.addEventListener("submit", didSubmitForm, true);
+      document.addEventListener("ajax:before", didSubmitRemoteElement);
+    }
+  }
+  function didClick(event) {
+    const {target: target} = event;
+    if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
+      submitButtonsByForm.set(target.form, target);
+    }
+  }
+  function didSubmitForm(event) {
+    handleFormSubmissionEvent(event);
+  }
+  function didSubmitRemoteElement(event) {
+    if (event.target.tagName == "FORM") {
+      handleFormSubmissionEvent(event);
+    }
+  }
+  function handleFormSubmissionEvent(event) {
+    const form = event.target;
+    if (form.hasAttribute(processingAttribute)) {
+      event.preventDefault();
+      return;
+    }
+    const controller = new DirectUploadsController(form);
+    const {inputs: inputs} = controller;
+    if (inputs.length) {
+      event.preventDefault();
+      form.setAttribute(processingAttribute, "");
+      inputs.forEach(disable);
+      controller.start((error => {
+        form.removeAttribute(processingAttribute);
+        if (error) {
+          inputs.forEach(enable);
+        } else {
+          submitForm(form);
+        }
+      }));
+    }
+  }
+  function submitForm(form) {
+    let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]");
+    if (button) {
+      const {disabled: disabled} = button;
+      button.disabled = false;
+      button.focus();
+      button.click();
+      button.disabled = disabled;
+    } else {
+      button = document.createElement("input");
+      button.type = "submit";
+      button.style.display = "none";
+      form.appendChild(button);
+      button.click();
+      form.removeChild(button);
+    }
+    submitButtonsByForm.delete(form);
+  }
+  function disable(input) {
+    input.disabled = true;
+  }
+  function enable(input) {
+    input.disabled = false;
+  }
+  function autostart() {
+    if (window.ActiveStorage) {
+      start();
+    }
+  }
+  setTimeout(autostart, 1);
+  exports.DirectUpload = DirectUpload;
+  exports.start = start;
+  Object.defineProperty(exports, "__esModule", {
+    value: true
+  });
+}));
+}(activestorage, activestorage.exports));
+
+class AttachmentUpload {
+  constructor(attachment, element) {
+    this.attachment = attachment;
+    this.element = element;
+    this.directUpload = new activestorage.exports.DirectUpload(attachment.file, this.directUploadUrl, this);
+  }
+
+  start() {
+    this.directUpload.create(this.directUploadDidComplete.bind(this));
+  }
+
+  directUploadWillStoreFileWithXHR(xhr) {
+    xhr.upload.addEventListener("progress", event => {
+      const progress = event.loaded / event.total * 100;
+      this.attachment.setUploadProgress(progress);
+    });
+  }
+
+  directUploadDidComplete(error, attributes) {
+    if (error) {
+      throw new Error(`Direct upload failed: ${error}`)
+    }
+
+    this.attachment.setAttributes({
+      sgid: attributes.attachable_sgid,
+      url: this.createBlobUrl(attributes.signed_id, attributes.filename)
+    });
+  }
+
+  createBlobUrl(signedId, filename) {
+    return this.blobUrlTemplate
+      .replace(":signed_id", signedId)
+      .replace(":filename", encodeURIComponent(filename))
+  }
+
+  get directUploadUrl() {
+    return this.element.dataset.directUploadUrl
+  }
+
+  get blobUrlTemplate() {
+    return this.element.dataset.blobUrlTemplate
+  }
+}
+
+addEventListener("trix-attachment-add", event => {
+  const { attachment, target } = event;
+
+  if (attachment.file) {
+    const upload = new AttachmentUpload(attachment, target);
+    upload.start();
+  }
+});