summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/related_assets_controller.rb3
-rw-r--r--app/models/node.rb1
-rw-r--r--app/views/layouts/admin.html.erb1
-rw-r--r--app/views/nodes/edit.html.erb39
-rw-r--r--app/views/nodes/show.html.erb11
-rw-r--r--public/javascripts/admin_interface.js4
-rw-r--r--public/javascripts/related_assets.js80
-rw-r--r--public/stylesheets/admin.css75
-rw-r--r--test/controllers/related_assets_controller_test.rb2
-rw-r--r--test/models/node_test.rb29
10 files changed, 230 insertions, 15 deletions
diff --git a/app/controllers/related_assets_controller.rb b/app/controllers/related_assets_controller.rb
index ae37d2f..479ebec 100644
--- a/app/controllers/related_assets_controller.rb
+++ b/app/controllers/related_assets_controller.rb
@@ -26,7 +26,8 @@ class RelatedAssetsController < ApplicationController
26 id: related.id, 26 id: related.id,
27 asset_id: asset.id, 27 asset_id: asset.id,
28 name: asset.name, 28 name: asset.name,
29 thumb_url: asset.upload.url(:thumb) 29 thumb_url: asset.upload.url(:thumb),
30 url: node_related_asset_path(@node, related)
30 } 31 }
31 end 32 end
32 33
diff --git a/app/models/node.rb b/app/models/node.rb
index 0361c1e..6f435ee 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -80,6 +80,7 @@ class Node < ApplicationRecord
80 80
81 unless self.autosave 81 unless self.autosave
82 self.autosave = Page.create!(:editor => current_user) 82 self.autosave = Page.create!(:editor => current_user)
83 self.autosave.assets = (self.draft || self.head).assets if self.draft || self.head
83 self.save! 84 self.save!
84 end 85 end
85 self.autosave.assign_attributes(attributes) 86 self.autosave.assign_attributes(attributes)
diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb
index 5ae363f..8bbc0d5 100644
--- a/app/views/layouts/admin.html.erb
+++ b/app/views/layouts/admin.html.erb
@@ -13,6 +13,7 @@
13 <link rel="stylesheet" href="/stylesheets/admin.css"> 13 <link rel="stylesheet" href="/stylesheets/admin.css">
14 <script src="/javascripts/admin_search.js"></script> 14 <script src="/javascripts/admin_search.js"></script>
15 <script src="/javascripts/admin_interface.js"></script> 15 <script src="/javascripts/admin_interface.js"></script>
16 <script src="/javascripts/related_assets.js"></script>
16 <script> 17 <script>
17 var ADMIN_SEARCH_URL = "<%= admin_search_path %>"; 18 var ADMIN_SEARCH_URL = "<%= admin_search_path %>";
18 var ADMIN_MENU_SEARCH_URL = "<%= admin_menu_search_path %>"; 19 var ADMIN_MENU_SEARCH_URL = "<%= admin_menu_search_path %>";
diff --git a/app/views/nodes/edit.html.erb b/app/views/nodes/edit.html.erb
index 1ac05e0..e0e51d8 100644
--- a/app/views/nodes/edit.html.erb
+++ b/app/views/nodes/edit.html.erb
@@ -76,24 +76,35 @@
76 76
77 <div class="node_description">Images</div> 77 <div class="node_description">Images</div>
78 <div class="node_content"> 78 <div class="node_content">
79 <ul id="image_box" rel="<%= @page.id %>"> 79 <div id="related_assets"
80 <% @page.assets.images.each do |image| %> 80 data-search-url="<%= search_node_related_assets_path(@node) %>"
81 <li rel="images_<%= image.id %>"> 81 data-create-url="<%= node_related_assets_path(@node) %>">
82 <%= image_tag(image.upload.url(:thumb)) %> 82 <ul id="related_asset_list" class="thumbnail_list">
83 </li> 83 <% @page.related_assets.includes(:asset).each do |related| %>
84 <% end %> 84 <li data-url="<%= node_related_asset_path(@node, related) %>">
85 </ul> 85 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span>
86 <div class="clear_left right"> 86 <%= image_tag related.asset.upload.url(:thumb) %>
87 <a id="image_browser_toggle" class="unselected" href="#">image browser</a> 87 <button type="button" class="related_asset_remove" aria-label="Remove image">
88 </div> 88 <%= icon("x", library: "tabler", "aria-hidden": true) %>
89 <div id="image_browser"> 89 </button>
90 <ul> 90 </li>
91 <% Asset.images.each do |image| %>
92 <li rel="images_<%= image.id %>"><%= image_tag(image.upload.url(:thumb)) %></li>
93 <% end %> 91 <% end %>
94 </ul> 92 </ul>
93 <%= text_field_tag nil, nil, id: "related_asset_search_term", placeholder: "Search images to attach…", autocomplete: "off" %>
94 <div id="related_asset_search_results" class="search_results"></div>
95 </div> 95 </div>
96 </div> 96 </div>
97
98 <template id="related_asset_template">
99 <li>
100 <span class="related_asset_handle"><%= icon("grip-vertical", library: "tabler", "aria-hidden": true) %></span>
101 <img src="">
102 <button type="button" class="related_asset_remove" aria-label="Remove image">
103 <%= icon("x", library: "tabler", "aria-hidden": true) %>
104 </button>
105 </li>
106 </template>
107
97 </div> 108 </div>
98 </details> 109 </details>
99 110
diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb
index 280e492..8ea03cd 100644
--- a/app/views/nodes/show.html.erb
+++ b/app/views/nodes/show.html.erb
@@ -152,6 +152,17 @@
152 <div class="node_description">Tags</div> 152 <div class="node_description">Tags</div>
153 <div class="node_content node_info_group"><%= @page.tag_list %></div> 153 <div class="node_content node_info_group"><%= @page.tag_list %></div>
154 154
155 <% if @page.assets.images.any? %>
156 <div class="node_description">Images</div>
157 <div class="node_content node_info_group">
158 <ul class="thumbnail_list">
159 <% @page.assets.images.each do |asset| %>
160 <li><%= link_to image_tag(asset.upload.url(:thumb)), asset_path(asset) %></li>
161 <% end %>
162 </ul>
163 </div>
164 <% end %>
165
155 <div class="node_description">Events</div> 166 <div class="node_description">Events</div>
156 <div class="node_content node_info_group"> 167 <div class="node_content node_info_group">
157 <ul> 168 <ul>
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js
index 3f6a0a9..619933d 100644
--- a/public/javascripts/admin_interface.js
+++ b/public/javascripts/admin_interface.js
@@ -59,6 +59,10 @@ $(document).ready(function () {
59 event_search.initialize_search(); 59 event_search.initialize_search();
60 } 60 }
61 61
62 if ($("#related_asset_search_term").length != 0) {
63 related_assets.initialize();
64 }
65
62 if ($("#rrule_builder").length != 0) { 66 if ($("#rrule_builder").length != 0) {
63 rrule_builder.initialize(); 67 rrule_builder.initialize();
64 } 68 }
diff --git a/public/javascripts/related_assets.js b/public/javascripts/related_assets.js
new file mode 100644
index 0000000..2955cbf
--- /dev/null
+++ b/public/javascripts/related_assets.js
@@ -0,0 +1,80 @@
1related_assets = {
2 initialize: function() {
3 var container = $("#related_assets");
4 var searchUrl = container.data("search-url");
5 var createUrl = container.data("create-url");
6 var list = $("#related_asset_list");
7
8 initSearchPicker({
9 inputSelector: "#related_asset_search_term",
10 resultsSelector: "#related_asset_search_results",
11 url: searchUrl,
12 loadOnFocus: true,
13 renderResults: function(data, results) {
14 var found = false;
15 data.forEach(function(asset) {
16 var item = $(
17 "<a href='#' class='related_asset_result'>" +
18 "<img src='" + asset.thumb_url + "' alt=''>" +
19 "<span>" + asset.name + "</span>" +
20 "</a>"
21 );
22 item.bind("click", function() {
23 related_assets.attach(asset.id, createUrl, list);
24 return false;
25 });
26 results.append(item);
27 found = true;
28 });
29 return found;
30 }
31 });
32
33 $(document).on("click", function(e) {
34 if (!$(e.target).closest("#related_assets").length) {
35 $("#related_asset_search_results").slideUp().empty();
36 }
37 });
38
39 list.sortable({
40 handle: ".related_asset_handle",
41 update: function(event, ui) {
42 var newPosition = ui.item.index() + 1; // acts_as_list is 1-based; jQuery UI's index() is 0-based
43 $.ajax({
44 type: "PATCH",
45 url: ui.item.data("url"),
46 data: "position=" + newPosition
47 });
48 }
49 });
50
51 list.on("click", ".related_asset_remove", function() {
52 var item = $(this).closest("li");
53 $.ajax({
54 type: "DELETE",
55 url: item.data("url"),
56 success: function() {
57 item.remove();
58 }
59 });
60 });
61 },
62
63 attach: function(assetId, createUrl, list) {
64 $.ajax({
65 type: "POST",
66 url: createUrl,
67 data: "asset_id=" + assetId,
68 dataType: "json",
69 success: function(related) {
70 var item = $($("#related_asset_template").html().trim());
71 item.attr("data-url", related.url);
72 item.find("img").attr("src", related.thumb_url);
73 list.append(item);
74 list.sortable("refresh");
75 $("#related_asset_search_term").val("");
76 $("#related_asset_search_results").slideUp().empty();
77 }
78 });
79 }
80};
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index a58a061..c3c0cb4 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -1210,3 +1210,78 @@ div#image_browser {
1210div#image_browser ul li { 1210div#image_browser ul li {
1211 list-style-type: none; 1211 list-style-type: none;
1212} 1212}
1213
1214.thumbnail_list {
1215 display: flex;
1216 flex-wrap: wrap;
1217 gap: 0.75rem;
1218 list-style: none;
1219 margin: 0 0 0.75rem;
1220 padding: 0;
1221}
1222
1223.thumbnail_list li {
1224 position: relative;
1225 display: flex;
1226 align-items: center;
1227 gap: 0.25rem;
1228 border: 1px solid #e8e8e8;
1229 border-radius: 6px;
1230 padding: 4px;
1231}
1232
1233.thumbnail_list img {
1234 display: block;
1235 width: 60px;
1236 height: 60px;
1237 object-fit: cover;
1238 border-radius: 4px;
1239}
1240
1241.related_asset_handle {
1242 display: flex;
1243 color: #969696;
1244 cursor: grab;
1245}
1246
1247.related_asset_handle svg {
1248 width: 1rem;
1249 height: 1rem;
1250}
1251
1252.related_asset_remove {
1253 display: flex;
1254 align-items: center;
1255 justify-content: center;
1256 background: none;
1257 border: none;
1258 padding: 2px;
1259 cursor: pointer;
1260 color: #969696;
1261}
1262
1263.related_asset_remove svg {
1264 width: 0.9rem;
1265 height: 0.9rem;
1266}
1267
1268.related_asset_remove:hover {
1269 color: #cc0000;
1270}
1271
1272.related_asset_result {
1273 display: flex;
1274 align-items: center;
1275 gap: 0.5rem;
1276 padding: 4px;
1277 border-bottom: 1px solid #e8e8e8;
1278 text-decoration: none;
1279}
1280
1281.related_asset_result img {
1282 width: 40px;
1283 height: 40px;
1284 object-fit: cover;
1285 border-radius: 4px;
1286}
1287
diff --git a/test/controllers/related_assets_controller_test.rb b/test/controllers/related_assets_controller_test.rb
index 7f6d487..9d20cbb 100644
--- a/test/controllers/related_assets_controller_test.rb
+++ b/test/controllers/related_assets_controller_test.rb
@@ -44,6 +44,8 @@ class RelatedAssetsControllerTest < ActionController::TestCase
44 44
45 assert_response :success 45 assert_response :success
46 assert_includes node.draft.reload.related_assets.map(&:asset_id), asset.id 46 assert_includes node.draft.reload.related_assets.map(&:asset_id), asset.id
47 json = JSON.parse(response.body)
48 assert json["url"].present?
47 end 49 end
48 50
49 test "create does not duplicate an already-attached asset" do 51 test "create does not duplicate an already-attached asset" do
diff --git a/test/models/node_test.rb b/test/models/node_test.rb
index 0bce222..a1da82c 100644
--- a/test/models/node_test.rb
+++ b/test/models/node_test.rb
@@ -585,4 +585,33 @@ class NodeTest < ActiveSupport::TestCase
585 585
586 assert_not_includes Node.recently_changed, node 586 assert_not_includes Node.recently_changed, node
587 end 587 end
588
589 test "autosave! carries over the current related assets to the newly created autosave row" do
590 node = Node.root.children.create!(:slug => "autosave_asset_carryover_test")
591 user = User.find_by_login("quentin")
592 asset = Asset.create!(:name => "carryover-photo", :upload_content_type => "image/png")
593 node.draft.assets << asset
594
595 node.lock_for_editing!(user)
596 node.autosave!({:title => "v1"}, user)
597
598 assert_includes node.autosave.reload.assets, asset
599 end
600
601 test "autosave! does not reset assets already attached directly to an existing autosave" do
602 node = Node.root.children.create!(:slug => "autosave_asset_no_reset_test")
603 user = User.find_by_login("quentin")
604 original = Asset.create!(:name => "original-photo", :upload_content_type => "image/png")
605 extra = Asset.create!(:name => "extra-photo", :upload_content_type => "image/png")
606 node.draft.assets << original
607
608 node.lock_for_editing!(user)
609 node.autosave!({:title => "v1"}, user)
610 node.autosave.assets << extra
611
612 node.autosave!({:title => "v2"}, user)
613
614 assert_includes node.autosave.reload.assets, original
615 assert_includes node.autosave.reload.assets, extra
616 end
588end 617end