alchemy_cms 8.3.0 → 8.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/alchemy/admin.css +1 -1
  3. data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
  4. data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
  5. data/app/assets/builds/alchemy/dark-theme.css +1 -1
  6. data/app/assets/builds/alchemy/light-theme.css +1 -1
  7. data/app/assets/builds/alchemy/theme.css +1 -1
  8. data/app/components/alchemy/admin/dashboard/widgets/locked_pages.html.erb +9 -9
  9. data/app/components/alchemy/admin/link_dialog/anchor_tab.rb +1 -1
  10. data/app/components/alchemy/admin/link_dialog/file_tab.rb +1 -1
  11. data/app/controllers/alchemy/admin/pages_controller.rb +6 -5
  12. data/app/javascript/alchemy_admin/components/attachment_select.js +1 -1
  13. data/app/javascript/alchemy_admin/components/element_select.js +1 -1
  14. data/app/javascript/alchemy_admin/components/page_select.js +1 -1
  15. data/app/javascript/alchemy_admin/components/tags_autocomplete.js +1 -1
  16. data/app/javascript/alchemy_admin/components/tinymce.js +1 -1
  17. data/app/javascript/alchemy_admin.js +5 -0
  18. data/app/models/concerns/alchemy/relatable_resource.rb +5 -1
  19. data/app/stylesheets/alchemy/_themes.scss +2 -2
  20. data/app/stylesheets/alchemy/admin/_tom-select.scss +8 -1
  21. data/app/stylesheets/alchemy/admin/buttons.scss +1 -0
  22. data/app/stylesheets/alchemy/admin/clipboard.scss +2 -0
  23. data/app/stylesheets/alchemy/admin/elements.scss +2 -6
  24. data/app/stylesheets/alchemy/admin/filters.scss +4 -1
  25. data/app/stylesheets/alchemy/admin/forms.scss +19 -1
  26. data/app/stylesheets/alchemy/admin/image_library.scss +10 -0
  27. data/app/stylesheets/alchemy/admin/lists.scss +3 -3
  28. data/app/stylesheets/alchemy/admin/sitemap.scss +8 -0
  29. data/app/stylesheets/alchemy/admin/tags.scss +1 -6
  30. data/app/views/alchemy/admin/attachments/edit.html.erb +1 -1
  31. data/app/views/alchemy/admin/layoutpages/edit.html.erb +1 -1
  32. data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +0 -1
  33. data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +1 -1
  34. data/app/views/alchemy/admin/pages/_form.html.erb +1 -1
  35. data/app/views/alchemy/admin/pages/_locked_page.html.erb +1 -1
  36. data/app/views/alchemy/admin/pages/unlock.turbo_stream.erb +20 -0
  37. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  38. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -1
  39. data/app/views/alchemy/admin/resources/_form.html.erb +1 -1
  40. data/app/views/alchemy/admin/styleguide/index.html.erb +2 -2
  41. data/config/importmap.rb +5 -0
  42. data/config/locales/alchemy.en.yml +2 -2
  43. data/lib/alchemy/test_support/relatable_resource_examples.rb +41 -16
  44. data/lib/alchemy/version.rb +1 -1
  45. metadata +2 -2
  46. data/app/views/alchemy/admin/pages/unlock.js.erb +0 -20
@@ -132,6 +132,16 @@
132
132
  .select2-container {
133
133
  width: 100%;
134
134
  }
135
+
136
+ &.tags {
137
+ .control-label {
138
+ top: 0;
139
+ }
140
+
141
+ .select2-search-choice {
142
+ line-height: 12px;
143
+ }
144
+ }
135
145
  }
136
146
  }
137
147
  }
@@ -20,6 +20,8 @@ ul.list {
20
20
  margin-bottom: 0;
21
21
 
22
22
  &.list-header {
23
+ display: flex;
24
+ justify-content: space-between;
23
25
  overflow: hidden;
24
26
  background-color: transparent;
25
27
  font-weight: bold;
@@ -45,17 +47,15 @@ ul.list {
45
47
  display: inline-flex;
46
48
  overflow: hidden;
47
49
  text-overflow: ellipsis;
48
- vertical-align: middle;
50
+ align-items: center;
49
51
  }
50
52
 
51
53
  .list-primary {
52
- float: left;
53
54
  max-width: 65%;
54
55
  }
55
56
 
56
57
  .list-secondary,
57
58
  .right {
58
- float: right;
59
59
  text-align: left;
60
60
  width: 30%;
61
61
  color: var(--color-grey_dark);
@@ -252,6 +252,14 @@ alchemy-sitemap {
252
252
  position: relative;
253
253
  top: 0.1ex;
254
254
  }
255
+
256
+ .resource_info & {
257
+ align-items: baseline;
258
+
259
+ alchemy-icon {
260
+ top: 0.3ex;
261
+ }
262
+ }
255
263
  }
256
264
 
257
265
  #page_filter_result {
@@ -128,14 +128,9 @@
128
128
  padding: var(--spacing-1) 22px;
129
129
  background-image: var(--tag-background-image);
130
130
  background-repeat: no-repeat;
131
- background-position: 6px;
131
+ background-position: var(--spacing-1);
132
132
  background-color: var(--tag-background-color);
133
133
  background-size: 1rem;
134
-
135
- > div {
136
- position: relative;
137
- top: -1px;
138
- }
139
134
  }
140
135
  }
141
136
  }
@@ -2,7 +2,7 @@
2
2
  url: {action: :update, q: search_filter_params[:q], page: params[:page]}) do |f| -%>
3
3
  <%= f.input :name, input_html: {autofocus: true} %>
4
4
  <%= f.input :file_name, input_html: {autofocus: true}, hint: Alchemy.t(:attachment_filename_notice) %>
5
- <div class="input">
5
+ <div class="input tags">
6
6
  <%= f.label :tag_list, class: "control-label" %>
7
7
  <%= render Alchemy::Admin::TagsAutocomplete.new do %>
8
8
  <%= f.text_field :tag_list, value: f.object.tag_list.join(",") %>
@@ -1,7 +1,7 @@
1
1
  <%= turbo_frame_tag @page do %>
2
2
  <%= alchemy_form_for [:admin, @page], url: alchemy.admin_layoutpage_path(@page), class: 'edit_page', remote: false do |f| %>
3
3
  <%= f.input :name, autofocus: true %>
4
- <div class="input">
4
+ <div class="input tags">
5
5
  <%= f.label :tag_list, class: "control-label" %>
6
6
  <%= render Alchemy::Admin::TagsAutocomplete.new do %>
7
7
  <%= f.text_field :tag_list, value: f.object.tag_list.join(",") %>
@@ -10,7 +10,6 @@
10
10
  <% else %>
11
11
  <div class="row even">
12
12
  <div class="col"><%= Alchemy.t('No page links for this page found') %></div>
13
- <div class="col tools"></div>
14
13
  </div>
15
14
  <% end %>
16
15
  </div>
@@ -2,7 +2,7 @@
2
2
  <table class="list">
3
3
  <tr>
4
4
  <th class="name">
5
- <%= Alchemy::Node.model_name.human %>
5
+ <%= Alchemy::Node.model_name.human(count: @page.nodes.size) %>
6
6
  </th>
7
7
  <th class="tools"></th>
8
8
  </tr>
@@ -63,7 +63,7 @@
63
63
  } %>
64
64
  <% end %>
65
65
 
66
- <div class="input">
66
+ <div class="input tags">
67
67
  <%= f.label :tag_list, class: "control-label" %>
68
68
  <%= render Alchemy::Admin::TagsAutocomplete.new do %>
69
69
  <%= f.text_field :tag_list, value: f.object.tag_list.join(",") %>
@@ -5,7 +5,7 @@
5
5
  <%= link_to alchemy.edit_admin_page_path(locked_page) do %>
6
6
  <%= render 'alchemy/admin/pages/page_infos', page: locked_page %>
7
7
  <% end %>
8
- <%= form_tag(alchemy.unlock_admin_page_path(locked_page), remote: true) do %>
8
+ <%= form_tag(alchemy.unlock_admin_page_path(locked_page)) do %>
9
9
  <button class="icon_button small" title="<%= Alchemy.t(:explain_unlocking) %>">
10
10
  <%= render_icon(:close, size: '1x') %>
11
11
  </button>
@@ -0,0 +1,20 @@
1
+ <alchemy-growl><%= @notice %></alchemy-growl>
2
+ <%= turbo_stream.remove "locked_page_#{@page.id}" %>
3
+ <% if @page.layoutpage %>
4
+ <%= turbo_stream.replace "page_#{@page.id}" do %>
5
+ <%= render "alchemy/admin/layoutpages/layoutpage", layoutpage: @page %>
6
+ <% end %>
7
+ <% else %>
8
+ <%= turbo_stream.replace "alchemy_pages_tree" do %>
9
+ <%= turbo_frame_tag "alchemy_pages_tree", src: alchemy.tree_admin_pages_path do %>
10
+ <alchemy-spinner></alchemy-spinner>
11
+ <% end %>
12
+ <% end %>
13
+ <% end %>
14
+ <% ["LockedPages", "RecentPages"].each do |widget| %>
15
+ <%= turbo_stream.replace widget do %>
16
+ <%= turbo_frame_tag widget, src: alchemy.admin_dashboard_widget_path(id: widget) do %>
17
+ <alchemy-spinner size="small"></alchemy-spinner>
18
+ <% end %>
19
+ <% end %>
20
+ <% end %>
@@ -11,7 +11,7 @@
11
11
  class: "picture-form" do |f| %>
12
12
  <%= f.input :name %>
13
13
  <%= render "alchemy/admin/pictures/picture_description_field", f: f %>
14
- <%= render Alchemy::Admin::TagsAutocomplete.new(additional_class: "input") do %>
14
+ <%= render Alchemy::Admin::TagsAutocomplete.new(additional_class: "input tags") do %>
15
15
  <%= f.label :tag_list %>
16
16
  <%= f.text_field :tag_list, value: f.object.tag_list.join(",") %>
17
17
  <small class="hint"><%= Alchemy.t('Please seperate the tags with commata') %></small>
@@ -12,7 +12,7 @@
12
12
  <%= text_field_tag :pictures_name %>
13
13
  </div>
14
14
 
15
- <%= render Alchemy::Admin::TagsAutocomplete.new(additional_class: "input") do %>
15
+ <%= render Alchemy::Admin::TagsAutocomplete.new(additional_class: "input tags") do %>
16
16
  <%= label_tag :pictures_tag_list, Alchemy.t(:tags), class: 'control-label' %>
17
17
  <%= text_field_tag :pictures_tag_list, @tags %>
18
18
  <small class="hint"><%= Alchemy.t('Please seperate the tags with commata') %></small>
@@ -18,7 +18,7 @@
18
18
  <% end %>
19
19
  <% end %>
20
20
  <% if f.object.respond_to?(:tag_list) %>
21
- <div class="input">
21
+ <div class="input tags">
22
22
  <%= f.label :tag_list, class: "control-label" %>
23
23
  <%= render Alchemy::Admin::TagsAutocomplete.new do %>
24
24
  <%= f.text_field :tag_list, value: f.object.tag_list.join(",") %>
@@ -151,7 +151,7 @@
151
151
  <select is="alchemy-select" class="full_width" data-allow-clear placeholder="Please choose">
152
152
  <option value="1">Option 1</option>
153
153
  <option value="2">Option 2</option>
154
- <option value="3">Option 3</option>
154
+ <option value="3">Option 3 with a very long name that exceeds the max-width very easily.</option>
155
155
  <option value="4">Option 4</option>
156
156
  <option value="5">Option 5</option>
157
157
  <option value="6">Option 6</option>
@@ -164,7 +164,7 @@
164
164
  <select is="alchemy-select" multiple class="full_width" placeholder="Pick Options" data-allow-clear>
165
165
  <option value="1">Option 1</option>
166
166
  <option value="2">Option 2</option>
167
- <option value="3">Option 3</option>
167
+ <option value="3">Option 3 with a very long name that exceeds the max-width very easily.</option>
168
168
  <option value="4">Option 4</option>
169
169
  <option value="5">Option 5</option>
170
170
  <option value="6">Option 6</option>
data/config/importmap.rb CHANGED
@@ -16,6 +16,11 @@ pin "tom-select", to: "tom-select.min.js", preload: true
16
16
 
17
17
  pin "alchemy_admin", to: "alchemy/alchemy_admin.min.js", preload: true
18
18
  pin "alchemy_admin/components/remote_select", to: "alchemy/alchemy_admin.min.js"
19
+ pin "alchemy_admin/components/page_select", to: "alchemy/alchemy_admin.min.js"
20
+ pin "alchemy_admin/components/attachment_select", to: "alchemy/alchemy_admin.min.js"
21
+ pin "alchemy_admin/components/element_select", to: "alchemy/alchemy_admin.min.js"
22
+ pin "alchemy_admin/components/tags_autocomplete", to: "alchemy/alchemy_admin.min.js"
23
+ pin "alchemy_admin/components/tinymce", to: "alchemy/alchemy_admin.min.js"
19
24
  pin "alchemy_admin/image_cropper", to: "alchemy/alchemy_admin.min.js"
20
25
  pin "alchemy_admin/image_overlay", to: "alchemy/alchemy_admin.min.js"
21
26
  pin "alchemy_admin/picture_selector", to: "alchemy/alchemy_admin.min.js"
@@ -277,7 +277,7 @@ en:
277
277
  "Confirm new password": "Confirm new password"
278
278
  "Copy": "Copy"
279
279
  "Could not load Adobe Flash® Plugin!": "Could not load Adobe Flash® Plugin!"
280
- "Create node on parent:": "Create node on parent:"
280
+ "Create node on parent:": "Attach to menu"
281
281
  "Currently locked pages": "Currently locked pages"
282
282
  "Default language has to be public": "Default language has to be public"
283
283
  "Delete image": "Delete image"
@@ -429,7 +429,7 @@ en:
429
429
  "Create site": "Create a new site"
430
430
  create_language_tree_heading: "Create new homepage"
431
431
  create_menu: "Add a menu"
432
- create_node: "Add a menu node"
432
+ create_node: "Add to menu"
433
433
  create_page: "Create a new subpage"
434
434
  currently_edited_by: "This page is locked by"
435
435
  custom_color: "Custom color"
@@ -3,16 +3,21 @@ RSpec.shared_examples_for "a relatable resource" do |args|
3
3
  it { is_expected.to have_many(:related_elements).through(:related_ingredients) }
4
4
  it { is_expected.to have_many(:related_pages).through(:related_elements) }
5
5
 
6
+ let(:resource_factory_name) { args[:resource_factory_name] || "alchemy_#{args[:resource_name]}" }
7
+ let(:ingredient_factory_name) { args[:ingredient_factory_name] || "alchemy_ingredient_#{args[:ingredient_type]}" }
8
+ let(:update_attribute) { args[:update_attribute] || :name }
9
+
6
10
  describe ".deletable" do
7
11
  subject { described_class.deletable }
8
12
 
9
- let!(:assigned_resource) { create(:"alchemy_#{args[:resource_name]}") }
10
- let!(:unassigned_resource) { create(:"alchemy_#{args[:resource_name]}") }
11
- let!(:ingredient1) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object: assigned_resource) }
12
- let!(:ingredient2) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object: nil) }
13
+ let!(:assigned_resource) { create(resource_factory_name) }
14
+ let!(:unassigned_resource) { create(resource_factory_name) }
15
+ let!(:ingredient1) { create(ingredient_factory_name, related_object: assigned_resource) }
16
+ let!(:ingredient2) { create(ingredient_factory_name, related_object: nil) }
13
17
 
14
18
  it "should return all records that are not assigned to an ingredient" do
15
- is_expected.to eq [unassigned_resource]
19
+ is_expected.to include(unassigned_resource)
20
+ is_expected.to_not include(assigned_resource)
16
21
  end
17
22
  end
18
23
 
@@ -20,9 +25,9 @@ RSpec.shared_examples_for "a relatable resource" do |args|
20
25
  subject { resource.related_ingredients }
21
26
 
22
27
  context "with other related resources with same id" do
23
- let!(:resource) { create(:"alchemy_#{args[:resource_name]}") }
24
- let!(:ingredient1) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object: resource) }
25
- let!(:ingredient2) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object_type: "Event", related_object_id: resource.id) }
28
+ let!(:resource) { create(resource_factory_name) }
29
+ let!(:ingredient1) { create(ingredient_factory_name, related_object: resource) }
30
+ let!(:ingredient2) { create(ingredient_factory_name, related_object_type: "Event", related_object_id: resource.id) }
26
31
 
27
32
  it "are not included" do
28
33
  is_expected.to eq [ingredient1]
@@ -30,9 +35,9 @@ RSpec.shared_examples_for "a relatable resource" do |args|
30
35
  end
31
36
 
32
37
  context "with other related resources with same type" do
33
- let!(:resource) { create(:"alchemy_#{args[:resource_name]}") }
34
- let!(:ingredient1) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object: resource) }
35
- let!(:ingredient2) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object_type: described_class) }
38
+ let!(:resource) { create(resource_factory_name) }
39
+ let!(:ingredient1) { create(ingredient_factory_name, related_object: resource) }
40
+ let!(:ingredient2) { create(ingredient_factory_name, related_object_type: described_class) }
36
41
 
37
42
  it "are not included" do
38
43
  is_expected.to eq [ingredient1]
@@ -41,12 +46,12 @@ RSpec.shared_examples_for "a relatable resource" do |args|
41
46
  end
42
47
 
43
48
  describe "#deletable?" do
44
- let(:resource) { create(:"alchemy_#{args[:resource_name]}") }
49
+ let(:resource) { create(resource_factory_name) }
45
50
 
46
51
  subject { resource.deletable? }
47
52
 
48
53
  context "if related to ingredient" do
49
- let!(:ingredient) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object: resource) }
54
+ let!(:ingredient) { create(ingredient_factory_name, related_object: resource) }
50
55
 
51
56
  it { is_expected.to be(false) }
52
57
  end
@@ -56,11 +61,11 @@ RSpec.shared_examples_for "a relatable resource" do |args|
56
61
  end
57
62
  end
58
63
 
59
- describe "after_touch" do
60
- let(:related_object) { create(:"alchemy_#{args[:resource_name]}") }
64
+ describe "after_commit on touch" do
65
+ let(:related_object) { create(resource_factory_name) }
61
66
 
62
67
  context "when related ingredients exist" do
63
- let!(:ingredient) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object:) }
68
+ let!(:ingredient) { create(ingredient_factory_name, related_object:) }
64
69
 
65
70
  it "enqueues InvalidateElementsCacheJob" do
66
71
  expect {
@@ -75,4 +80,24 @@ RSpec.shared_examples_for "a relatable resource" do |args|
75
80
  end
76
81
  end
77
82
  end
83
+
84
+ describe "after_commit on update" do
85
+ let(:related_object) { create(resource_factory_name) }
86
+
87
+ context "when related ingredients exist" do
88
+ let!(:ingredient) { create(ingredient_factory_name, related_object:) }
89
+
90
+ it "enqueues InvalidateElementsCacheJob" do
91
+ expect {
92
+ related_object.update(update_attribute => "Updated name")
93
+ }.to have_enqueued_job(Alchemy::InvalidateElementsCacheJob).with(described_class.name, related_object.id)
94
+ end
95
+ end
96
+
97
+ context "when no related ingredients exist" do
98
+ it "does not enqueue InvalidateElementsCacheJob" do
99
+ expect { related_object.update(update_attribute => "Updated name") }.to_not have_enqueued_job(Alchemy::InvalidateElementsCacheJob)
100
+ end
101
+ end
102
+ end
78
103
  end
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  extend self
5
5
 
6
- VERSION = "8.3.0"
6
+ VERSION = "8.3.2"
7
7
 
8
8
  def version
9
9
  VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.3.0
4
+ version: 8.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -1207,7 +1207,7 @@ files:
1207
1207
  - app/views/alchemy/admin/pages/publish.turbo_stream.erb
1208
1208
  - app/views/alchemy/admin/pages/show.html.erb
1209
1209
  - app/views/alchemy/admin/pages/tree.html.erb
1210
- - app/views/alchemy/admin/pages/unlock.js.erb
1210
+ - app/views/alchemy/admin/pages/unlock.turbo_stream.erb
1211
1211
  - app/views/alchemy/admin/pages/update.turbo_stream.erb
1212
1212
  - app/views/alchemy/admin/partials/_autocomplete_tag_list.html.erb
1213
1213
  - app/views/alchemy/admin/partials/_flash_notices.html.erb
@@ -1,20 +0,0 @@
1
- (function() {
2
- var locked_page_icon_content
3
- var locked_page_tab = document.querySelector('#locked_page_<%= @page.id -%>')
4
- var locked_page_icon = document.querySelector(
5
- '#page_<%= @page.id -%> > .sitemap_page > .sitemap_left_images .like-hint-tooltip'
6
- ) || document.querySelector('[data-page-id="<%= @page.id -%>"] i.icon')
7
- if (locked_page_tab) {
8
- locked_page_tab.remove()
9
- }
10
- if (locked_page_icon) {
11
- if (locked_page_icon.parentElement.classList.contains("handle")) {
12
- locked_page_icon_content = '<span class="handle"><alchemy-icon name="file" size="xl"></alchemy-icon></span>'
13
- } else {
14
- locked_page_icon_content = '<alchemy-icon name="file" size="xl"></alchemy-icon>'
15
- }
16
- locked_page_icon.outerHTML = locked_page_icon_content
17
- }
18
- document.querySelector("#page_<%= @page.id -%> .page_status.locked")?.remove()
19
- Alchemy.growl('<%= flash[:notice] -%>')
20
- })()