summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-23 19:46:59 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-23 19:46:59 +0200
commit7ebb5ce8ef276e6d23d818c216be6ef2974369cc (patch)
tree75ec93871392c94e10c2627327efd5994f324de0
parentcef5d1a685f324a4779ea6d18da0b0bbf6cfb7b1 (diff)
Turn the asset upload forms into file drop targets
Dropped files land in the existing file input and submit through the unchanged form. An empty name field is prefilled from the basename.
-rw-r--r--app/views/assets/edit.html.erb7
-rw-r--r--app/views/assets/new.html.erb7
-rw-r--r--public/javascripts/admin_interface.js38
-rw-r--r--public/stylesheets/admin.css11
4 files changed, 61 insertions, 2 deletions
diff --git a/app/views/assets/edit.html.erb b/app/views/assets/edit.html.erb
index 0c1dd8fe..d0a39aa3 100644
--- a/app/views/assets/edit.html.erb
+++ b/app/views/assets/edit.html.erb
@@ -14,7 +14,12 @@
14 <% end %> 14 <% end %>
15 15
16 <div class="node_description"><%= f.label :upload, "Replace file" %></div> 16 <div class="node_description"><%= f.label :upload, "Replace file" %></div>
17 <div class="node_content"><%= f.file_field :upload %></div> 17 <div class="node_content">
18 <div id="asset_dropzone">
19 <%= f.file_field :upload %>
20 <p class="field_hint">…or drop a file here.</p>
21 </div>
22 </div>
18 23
19 <div class="node_description"><%= f.label :creator %></div> 24 <div class="node_description"><%= f.label :creator %></div>
20 <div class="node_content"><%= f.text_field :creator %></div> 25 <div class="node_content"><%= f.text_field :creator %></div>
diff --git a/app/views/assets/new.html.erb b/app/views/assets/new.html.erb
index 7bded949..8a7a6094 100644
--- a/app/views/assets/new.html.erb
+++ b/app/views/assets/new.html.erb
@@ -9,7 +9,12 @@
9 <div class="node_content"><%= f.text_field :name %></div> 9 <div class="node_content"><%= f.text_field :name %></div>
10 10
11 <div class="node_description"><%= f.label :upload, "File" %></div> 11 <div class="node_description"><%= f.label :upload, "File" %></div>
12 <div class="node_content"><%= f.file_field :upload %></div> 12 <div class="node_content">
13 <div id="asset_dropzone">
14 <%= f.file_field :upload %>
15 <p class="field_hint">…or drop a file here.</p>
16 </div>
17 </div>
13 18
14 <div class="node_description"><%= f.label :creator %></div> 19 <div class="node_description"><%= f.label :creator %></div>
15 <div class="node_content"><%= f.text_field :creator %></div> 20 <div class="node_content"><%= f.text_field :creator %></div>
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js
index 5514f3b3..284d2b72 100644
--- a/public/javascripts/admin_interface.js
+++ b/public/javascripts/admin_interface.js
@@ -85,6 +85,44 @@ $(document).ready(function () {
85 desktop_mq.addEventListener('change', sync_metadata); 85 desktop_mq.addEventListener('change', sync_metadata);
86 } 86 }
87 87
88 var dropzone = document.getElementById('asset_dropzone');
89 if (dropzone) {
90 var input = dropzone.querySelector('input[type=file]');
91
92 // Without these, a drop that misses the zone navigates the browser
93 // to the file, losing the half-filled form.
94 ['dragover', 'drop'].forEach(function(ev) {
95 window.addEventListener(ev, function(e) { e.preventDefault(); });
96 });
97
98 ['dragenter', 'dragover'].forEach(function(ev) {
99 dropzone.addEventListener(ev, function(e) {
100 e.preventDefault(); dropzone.classList.add('dragging');
101 });
102 });
103 ['dragleave', 'drop'].forEach(function(ev) {
104 dropzone.addEventListener(ev, function(e) {
105 e.preventDefault(); dropzone.classList.remove('dragging');
106 });
107 });
108
109 dropzone.addEventListener('drop', function(e) {
110 if (!e.dataTransfer.files.length) return;
111 var dt = new DataTransfer();
112 dt.items.add(e.dataTransfer.files[0]);
113 input.files = dt.files;
114 input.dispatchEvent(new Event('change', { bubbles: true }));
115 });
116
117 input.addEventListener('change', function() {
118 var name_field = document.getElementById('asset_name');
119 if (input.files.length && name_field && name_field.value === '') {
120 name_field.value = input.files[0].name.replace(/\.[^.]+$/, '');
121 }
122 });
123 }
124
125
88 jQuery.ajaxSetup({ 126 jQuery.ajaxSetup({
89 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");} 127 'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript");}
90 }); 128 });
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 9f737752..69f0fcf2 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -1386,6 +1386,17 @@ div#draft_list table td.actions a {
1386 background-color: #fffdf5; 1386 background-color: #fffdf5;
1387} 1387}
1388 1388
1389#asset_dropzone {
1390 border: 2px dashed #ccc;
1391 border-radius: 6px;
1392 padding: 1.5rem;
1393}
1394
1395#asset_dropzone.dragging {
1396 border-color: #f96;
1397 background: #fff8f2;
1398}
1399
1389/* ============================================================ 1400/* ============================================================
1390 Live edit preview 1401 Live edit preview
1391 ============================================================ */ 1402 ============================================================ */