alchemy_cms 8.0.0.a → 8.0.0.b

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.

Potentially problematic release.


This version of alchemy_cms might be problematic. Click here for more details.

Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/app/assets/builds/alchemy/admin/page-select.css +1 -1
  4. data/app/assets/builds/alchemy/admin.css +1 -1
  5. data/app/assets/builds/alchemy/dark-theme.css +1 -0
  6. data/app/assets/builds/alchemy/light-theme.css +1 -0
  7. data/app/assets/builds/alchemy/theme.css +1 -0
  8. data/app/assets/builds/alchemy/welcome.css +1 -1
  9. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  10. data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -0
  11. data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
  12. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/content.min.css +1 -0
  13. data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -0
  14. data/app/assets/images/alchemy/element_icons/default.svg +1 -0
  15. data/app/assets/images/alchemy/icons-sprite.svg +1 -1
  16. data/app/components/alchemy/admin/element_select.rb +39 -0
  17. data/app/components/alchemy/ingredients/datetime_view.rb +4 -2
  18. data/app/controllers/alchemy/admin/attachments_controller.rb +2 -0
  19. data/app/controllers/alchemy/admin/elements_controller.rb +2 -0
  20. data/app/controllers/alchemy/admin/pages_controller.rb +2 -0
  21. data/app/controllers/alchemy/admin/pictures_controller.rb +19 -33
  22. data/app/controllers/alchemy/pages_controller.rb +19 -2
  23. data/app/controllers/concerns/alchemy/admin/resource_filter.rb +1 -0
  24. data/app/helpers/alchemy/admin/attachments_helper.rb +5 -5
  25. data/app/helpers/alchemy/pages_helper.rb +1 -1
  26. data/app/javascript/alchemy_admin/components/auto_submit.js +20 -0
  27. data/app/javascript/alchemy_admin/components/datepicker.js +8 -5
  28. data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +3 -2
  29. data/app/javascript/alchemy_admin/components/element_editor.js +25 -15
  30. data/app/javascript/alchemy_admin/components/element_select.js +43 -0
  31. data/app/javascript/alchemy_admin/components/index.js +5 -0
  32. data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
  33. data/app/javascript/alchemy_admin/components/remote_select.js +5 -1
  34. data/app/javascript/alchemy_admin/components/tinymce.js +93 -16
  35. data/app/javascript/alchemy_admin/dialog.js +1 -1
  36. data/app/javascript/alchemy_admin/file_editors.js +1 -1
  37. data/app/javascript/alchemy_admin/image_loader.js +4 -2
  38. data/app/javascript/alchemy_admin/picture_editors.js +7 -4
  39. data/app/javascript/alchemy_admin/picture_selector.js +4 -4
  40. data/app/jobs/alchemy/delete_picture_job.rb +12 -0
  41. data/app/models/alchemy/attachment.rb +2 -9
  42. data/app/models/alchemy/element.rb +1 -0
  43. data/app/models/alchemy/element_definition.rb +30 -0
  44. data/app/models/alchemy/ingredient.rb +1 -1
  45. data/app/models/alchemy/language.rb +2 -7
  46. data/app/models/alchemy/page/page_naming.rb +4 -11
  47. data/app/models/alchemy/page/page_natures.rb +16 -11
  48. data/app/models/alchemy/page.rb +1 -6
  49. data/app/models/alchemy/page_definition.rb +1 -1
  50. data/app/models/alchemy/picture.rb +6 -17
  51. data/app/models/alchemy/site/layout.rb +1 -0
  52. data/app/models/alchemy/site.rb +1 -6
  53. data/app/models/alchemy/storage_adapter/dragonfly/picture_url.rb +7 -2
  54. data/app/models/alchemy/storage_adapter/dragonfly.rb +24 -2
  55. data/app/models/concerns/alchemy/relatable_resource.rb +28 -0
  56. data/app/stylesheets/alchemy/_custom-properties.scss +162 -0
  57. data/app/stylesheets/alchemy/_mixins.scss +12 -24
  58. data/app/stylesheets/alchemy/_themes.scss +540 -0
  59. data/app/stylesheets/alchemy/admin/archive.scss +28 -8
  60. data/app/stylesheets/alchemy/admin/attachments.scss +10 -33
  61. data/app/stylesheets/alchemy/admin/base.scss +4 -1
  62. data/app/stylesheets/alchemy/admin/buttons.scss +7 -32
  63. data/app/stylesheets/alchemy/admin/dialogs.scss +17 -7
  64. data/app/stylesheets/alchemy/admin/element-select.scss +11 -0
  65. data/app/stylesheets/alchemy/admin/elements.scss +94 -33
  66. data/app/stylesheets/alchemy/admin/filters.scss +8 -9
  67. data/app/stylesheets/alchemy/admin/flatpickr.scss +12 -27
  68. data/app/stylesheets/alchemy/admin/form_fields.scss +0 -15
  69. data/app/stylesheets/alchemy/admin/forms.scss +3 -8
  70. data/app/stylesheets/alchemy/admin/frame.scss +5 -7
  71. data/app/stylesheets/alchemy/admin/icons.scss +0 -9
  72. data/app/stylesheets/alchemy/admin/image_library.scss +13 -55
  73. data/app/stylesheets/alchemy/admin/navigation.scss +1 -11
  74. data/app/stylesheets/alchemy/admin/node-select.scss +1 -10
  75. data/app/stylesheets/alchemy/admin/notices.scss +5 -4
  76. data/app/stylesheets/alchemy/admin/page-select.scss +16 -0
  77. data/app/stylesheets/alchemy/admin/pagination.scss +1 -8
  78. data/app/stylesheets/alchemy/admin/preview_window.scss +12 -1
  79. data/app/stylesheets/alchemy/admin/resource_info.scss +106 -3
  80. data/app/stylesheets/alchemy/admin/search.scss +1 -1
  81. data/app/stylesheets/alchemy/admin/selects.scss +58 -31
  82. data/app/stylesheets/alchemy/admin/shoelace.scss +32 -62
  83. data/app/stylesheets/alchemy/admin/sitemap.scss +1 -1
  84. data/app/stylesheets/alchemy/admin/tables.scss +3 -3
  85. data/app/stylesheets/alchemy/admin/tags.scss +18 -35
  86. data/app/stylesheets/alchemy/admin/toolbar.scss +0 -6
  87. data/app/stylesheets/alchemy/admin/typography.scss +2 -5
  88. data/app/stylesheets/alchemy/admin.scss +1 -1
  89. data/app/stylesheets/alchemy/dark-theme.scss +5 -0
  90. data/app/stylesheets/alchemy/light-theme.scss +6 -0
  91. data/app/stylesheets/alchemy/theme.scss +13 -0
  92. data/app/stylesheets/tinymce/skins/content/alchemy/content.scss +8 -8
  93. data/app/stylesheets/tinymce/skins/content/alchemy-dark/content.scss +70 -0
  94. data/app/stylesheets/tinymce/skins/ui/alchemy/skin.scss +28 -43
  95. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/content.scss +1 -0
  96. data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +3784 -0
  97. data/app/views/alchemy/admin/attachments/_files_list.html.erb +20 -10
  98. data/app/views/alchemy/admin/attachments/assign.js.erb +4 -3
  99. data/app/views/alchemy/admin/attachments/show.html.erb +55 -43
  100. data/app/views/alchemy/admin/crop.html.erb +1 -1
  101. data/app/views/alchemy/admin/elements/_form.html.erb +9 -9
  102. data/app/views/alchemy/admin/elements/_header.html.erb +4 -1
  103. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -1
  104. data/app/views/alchemy/admin/pages/info.html.erb +1 -1
  105. data/app/views/alchemy/admin/partials/_search_form.html.erb +1 -0
  106. data/app/views/alchemy/admin/pictures/_archive.html.erb +12 -22
  107. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +1 -6
  108. data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
  109. data/app/views/alchemy/admin/pictures/_infos.html.erb +21 -52
  110. data/app/views/alchemy/admin/pictures/_library_sidebar.html.erb +7 -0
  111. data/app/views/alchemy/admin/pictures/_picture.html.erb +14 -20
  112. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +20 -16
  113. data/app/views/alchemy/admin/pictures/_sorting_select.html.erb +13 -0
  114. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +1 -1
  115. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -6
  116. data/app/views/alchemy/admin/pictures/index.html.erb +3 -12
  117. data/app/views/alchemy/admin/pictures/show.html.erb +10 -5
  118. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +5 -15
  119. data/app/views/alchemy/admin/resources/_resource_usage_info.html.erb +36 -0
  120. data/app/views/alchemy/admin/styleguide/index.html.erb +118 -66
  121. data/app/views/alchemy/base/error_notice.html.erb +1 -1
  122. data/app/views/alchemy/ingredients/_page_editor.html.erb +0 -1
  123. data/app/views/alchemy/ingredients/_richtext_editor.html.erb +0 -1
  124. data/app/views/alchemy/ingredients/_select_editor.html.erb +1 -2
  125. data/app/views/layouts/alchemy/admin.html.erb +22 -17
  126. data/config/locales/alchemy.en.yml +26 -8
  127. data/db/migrate/20250905140323_add_created_at_index_to_pictures_and_attachments.rb +14 -0
  128. data/lib/alchemy/configurations/format_matchers.rb +1 -1
  129. data/lib/alchemy/configurations/main.rb +7 -0
  130. data/lib/alchemy/configurations/page_cache.rb +19 -0
  131. data/lib/alchemy/engine.rb +16 -7
  132. data/lib/alchemy/install/tasks.rb +0 -12
  133. data/lib/alchemy/name_conversions.rb +6 -0
  134. data/lib/alchemy/tasks/tidy.rb +18 -0
  135. data/lib/alchemy/test_support/factories/picture_factory.rb +1 -0
  136. data/lib/alchemy/test_support/relatable_resource_examples.rb +58 -0
  137. data/lib/alchemy/tinymce.rb +0 -1
  138. data/lib/alchemy/version.rb +1 -1
  139. data/lib/alchemy.rb +2 -11
  140. data/lib/generators/alchemy/install/install_generator.rb +21 -10
  141. data/lib/generators/alchemy/install/templates/alchemy.rb.tt +10 -6
  142. data/lib/tasks/alchemy/tidy.rake +6 -0
  143. data/lib/tasks/alchemy/usage.rake +2 -0
  144. data/vendor/assets/stylesheets/tinymce/skins/content/dark/content.min.css +1 -0
  145. data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +1 -0
  146. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide/skin.min.css +1 -0
  147. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/content.min.css +1 -0
  148. data/vendor/assets/stylesheets/tinymce/skins/ui/oxide-dark/skin.min.css +1 -0
  149. data/vendor/javascript/clipboard.min.js +1 -1
  150. data/vendor/javascript/cropperjs.min.js +1 -1
  151. data/vendor/javascript/handlebars.min.js +3 -3
  152. data/vendor/javascript/jquery.min.js +1 -1
  153. data/vendor/javascript/select2.min.js +3 -3
  154. data/vendor/javascript/shoelace.min.js +92 -76
  155. data/vendor/javascript/sortable.min.js +2 -2
  156. data/vendor/javascript/tinymce.min.js +1 -1
  157. data/vendor/javascript/ungap-custom-elements.min.js +2 -2
  158. metadata +46 -32
  159. data/CHANGELOG.md +0 -2100
  160. data/CODE_OF_CONDUCT.md +0 -13
  161. data/CONTRIBUTING.md +0 -73
  162. data/Gemfile +0 -78
  163. data/Rakefile +0 -102
  164. data/SECURITY.md +0 -13
  165. data/alchemy_cms.gemspec +0 -97
  166. data/app/assets/builds/alchemy/custom-properties.css +0 -1
  167. data/app/helpers/alchemy/admin/elements_helper.rb +0 -25
  168. data/app/stylesheets/alchemy/custom-properties.css +0 -244
  169. data/bin/importmap +0 -4
  170. data/bin/rails +0 -9
  171. data/bin/rspec +0 -3
  172. data/bin/setup +0 -30
  173. data/bin/start +0 -17
  174. data/bun.lockb +0 -0
  175. data/bundles/remixicon.mjs +0 -153
  176. data/bundles/shoelace.js +0 -12
  177. data/bundles/tinymce.js +0 -22
  178. data/eslint.config.js +0 -18
  179. data/lib/alchemy/upgrader/.keep +0 -0
  180. data/lib/alchemy/upgrader/tasks/.keep +0 -0
  181. data/rollup.config.mjs +0 -108
  182. data/vitest.config.js +0 -21
@@ -0,0 +1,36 @@
1
+ <div class="resource-usage-info">
2
+ <% if assignments.any? %>
3
+ <alchemy-message type="info">
4
+ <%= Alchemy.t(resource_name, scope: :used_on_these_pages) %>
5
+ </alchemy-message>
6
+ <ul class="resource_page_list">
7
+ <% assignments.group_by(&:page).each do |page, related_ingredients| %>
8
+ <% if page %>
9
+ <li>
10
+ <h3>
11
+ <%= render_icon 'file' %>
12
+ <p><%= link_to page.name, edit_admin_page_path(page) %></p>
13
+ </h3>
14
+ <ul class="list">
15
+ <% related_ingredients.group_by(&:element).each do |element, related_ingredients| %>
16
+ <li class="<%= cycle('even', 'odd') %>">
17
+ <% page_link = link_to element.display_name_with_preview_text,
18
+ edit_admin_page_path(page, anchor: "element_#{element.id}") %>
19
+ <% ingredients = related_ingredients.map { |p| Alchemy::IngredientEditor.new(p).translated_role }.to_sentence %>
20
+ <%= render_icon(:draggable, style: false) %>
21
+ <p>
22
+ <%== Alchemy.t(:pictures_in_page, page: page_link, pictures: ingredients) %>
23
+ </p>
24
+ </li>
25
+ <% end %>
26
+ </ul>
27
+ </li>
28
+ <% end %>
29
+ <% end %>
30
+ </ul>
31
+ <% else %>
32
+ <%= render_message do %>
33
+ <%= Alchemy.t(resource_name, scope: :not_in_use_yet) %>
34
+ <% end %>
35
+ <% end %>
36
+ </div>
@@ -19,71 +19,6 @@
19
19
 
20
20
  <h1>AlchemyCMS Styleguide</h1>
21
21
 
22
- <h2>Typography</h2>
23
-
24
- <h1>Heading 1</h1>
25
- <h2>Heading 2</h2>
26
- <h3>Heading 3</h3>
27
- <h4>Heading 4</h4>
28
- <h5>Heading 5</h5>
29
-
30
- <p>This is a paragraph, with <a href="#">a link</a>, a <kbd>keyboard shortcut</kbd> and an <code>inline code snippet</code>.</p>
31
-
32
- <h2>Messages</h2>
33
-
34
- <h3>Info Message</h3>
35
-
36
- <%= render_message do %>
37
- <p>Lorem ipsum <strong>dolor</strong> sit amet, consectetur adipiscing elit.</p>
38
- <% end %>
39
-
40
- <h3>Warning Message</h3>
41
-
42
- <%= render_message :warning do %>
43
- <p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit.</p>
44
- <% end %>
45
-
46
- <h3>Error Message</h3>
47
-
48
- <%= render_message :error do %>
49
- <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
50
- <% end %>
51
-
52
- <h3>Tags Autocomplete</h3>
53
-
54
- <div>
55
- <alchemy-tags-autocomplete>
56
- <input type="text" class="full_width" />
57
- </alchemy-tags-autocomplete>
58
- </div>
59
-
60
- <h3>Growl Messages</h3>
61
-
62
- <%= link_to render_icon(:check), 'javascript:Alchemy.growl("Success message")', class: "icon_button" %>
63
- <%= link_to render_icon(:info), 'javascript:Alchemy.growl("Info message", "info")', class: "icon_button" %>
64
- <%= link_to render_icon(:alert), 'javascript:Alchemy.growl("Warning message", "warn")', class: "icon_button" %>
65
- <%= link_to render_icon(:bug), 'javascript:Alchemy.growl("Error message", "error")', class: "icon_button" %>
66
-
67
- <h2>Dialog Links</h2>
68
-
69
- <sl-tooltip content="Open in Dialog">
70
- <a
71
- href="/admin/styleguide"
72
- is="alchemy-dialog-link"
73
- class="icon_button"
74
- data-dialog-options="{&quot;size&quot;:&quot;800x600&quot;,&quot;title&quot;:&quot;Styleguide in Dialog&quot;}"
75
- >
76
- <%= render_icon "window" %>
77
- </a>
78
- </sl-tooltip>
79
- <a
80
- href="#"
81
- is="alchemy-dialog-link"
82
- class="icon_button disabled"
83
- >
84
- <%= render_icon "window" %>
85
- </a>
86
-
87
22
  <h2>Forms</h2>
88
23
 
89
24
  <div class="panel">
@@ -102,6 +37,13 @@
102
37
  </select>
103
38
  </div>
104
39
 
40
+ <div class="input select">
41
+ <label class="select control-label" for="select">Disabled Select</label>
42
+ <select class="select" id="disabled-select" is="alchemy-select" disabled>
43
+ <option value="">Cannot choose</option>
44
+ </select>
45
+ </div>
46
+
105
47
  <div class="input">
106
48
  <label class="control-label" for="datetime_picker">Date Time Picker</label>
107
49
  <alchemy-datepicker input-type="datetime">
@@ -171,7 +113,7 @@
171
113
  <div class="input text">
172
114
  <label class="text control-label" for="textarea">Textarea</label>
173
115
  <textarea class="text" id="textarea"></textarea>
174
- <small class="hint">With hint</small>
116
+ <small class="hint">With hint and a <a href="/admin/styleguide">link</a> inside.</small>
175
117
  </div>
176
118
 
177
119
  <div class="input text tinymce_container">
@@ -188,6 +130,116 @@
188
130
  </form>
189
131
  </div>
190
132
 
133
+ <h2>Buttons</h2>
134
+
135
+ <p>
136
+ <button class="secondary" disabled>Disabled Secondary</button>
137
+ <button type="submit" is="alchemy-button" disabled>Disabled Primary</button>
138
+ </p>
139
+
140
+ <h2>Selects</h2>
141
+
142
+ <div style="display: flex; gap: 1em;">
143
+ <div style="width: 25%">
144
+ <h3>Select with Search</h3>
145
+ <select is="alchemy-select" class="full_width">
146
+ <option value="1">Option 1</option>
147
+ <option value="2">Option 2</option>
148
+ <option value="3">Option 3</option>
149
+ <option value="4">Option 4</option>
150
+ <option value="5">Option 5</option>
151
+ <option value="6">Option 6</option>
152
+ <option value="7">Option 7</option>
153
+ </select>
154
+ </div>
155
+
156
+ <div style="width: 25%">
157
+ <h3>Page Select</h3>
158
+ <%= render Alchemy::Admin::PageSelect.new do %>
159
+ <input name="page" class="full_width" />
160
+ <% end %>
161
+ </div>
162
+
163
+ <div style="width: 25%">
164
+ <h3>Node Select</h3>
165
+ <%= render Alchemy::Admin::NodeSelect.new do %>
166
+ <input name="node" class="full_width" />
167
+ <% end %>
168
+ </div>
169
+
170
+ <div style="width: 25%">
171
+ <h3>Attachment Select</h3>
172
+ <%= render Alchemy::Admin::AttachmentSelect.new do %>
173
+ <input name="attachment" class="full_width" />
174
+ <% end %>
175
+ </div>
176
+ </div>
177
+
178
+ <h3>Tags Autocomplete</h3>
179
+
180
+ <div style="width: 25%">
181
+ <alchemy-tags-autocomplete>
182
+ <input type="text" class="full_width" />
183
+ </alchemy-tags-autocomplete>
184
+ </div>
185
+
186
+ <h2>Typography</h2>
187
+
188
+ <h1>Heading 1</h1>
189
+ <h2>Heading 2</h2>
190
+ <h3>Heading 3</h3>
191
+ <h4>Heading 4</h4>
192
+ <h5>Heading 5</h5>
193
+
194
+ <p>This is a paragraph, with <a href="#">a link</a>, a <kbd>keyboard shortcut</kbd> and an <code>inline code snippet</code>.</p>
195
+
196
+ <h2>Messages</h2>
197
+
198
+ <h3>Info Message</h3>
199
+
200
+ <%= render_message do %>
201
+ <p>Lorem ipsum <strong>dolor</strong> sit amet, <a href="">consectetur</a> adipiscing elit.</p>
202
+ <% end %>
203
+
204
+ <h3>Warning Message</h3>
205
+
206
+ <%= render_message :warning do %>
207
+ <p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit.</p>
208
+ <% end %>
209
+
210
+ <h3>Error Message</h3>
211
+
212
+ <%= render_message :error do %>
213
+ <p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit.</p>
214
+ <% end %>
215
+
216
+ <h3>Growl Messages</h3>
217
+
218
+ <%= link_to render_icon(:check), 'javascript:Alchemy.growl("Success message")', class: "icon_button" %>
219
+ <%= link_to render_icon(:info), 'javascript:Alchemy.growl("Info message", "info")', class: "icon_button" %>
220
+ <%= link_to render_icon(:alert), 'javascript:Alchemy.growl("Warning message", "warn")', class: "icon_button" %>
221
+ <%= link_to render_icon(:bug), 'javascript:Alchemy.growl("Error message", "error")', class: "icon_button" %>
222
+
223
+ <h2>Dialog Links</h2>
224
+
225
+ <sl-tooltip content="Open in Dialog">
226
+ <a
227
+ href="/admin/styleguide"
228
+ is="alchemy-dialog-link"
229
+ class="icon_button"
230
+ data-dialog-options="{&quot;size&quot;:&quot;800x600&quot;,&quot;title&quot;:&quot;Styleguide in Dialog&quot;}"
231
+ >
232
+ <%= render_icon "window" %>
233
+ </a>
234
+ </sl-tooltip>
235
+ <a
236
+ href="#"
237
+ is="alchemy-dialog-link"
238
+ class="icon_button disabled"
239
+ >
240
+ <%= render_icon "window" %>
241
+ </a>
242
+
191
243
  <h2>Tables</h2>
192
244
 
193
245
  <h3>Normal Table</h3>
@@ -1,3 +1,3 @@
1
1
  <%= render_message :error do %>
2
- <strong><%= @notice %></strong>
2
+ <%= @notice %>
3
3
  <% end %>
@@ -7,7 +7,6 @@
7
7
  <%= f.text_field :page_id,
8
8
  value: page_editor.page&.id,
9
9
  id: page_editor.form_field_id(:page_id),
10
- required: page_editor.presence_validation?,
11
10
  class: 'full_width' %>
12
11
  <% end %>
13
12
  <% end %>
@@ -9,7 +9,6 @@
9
9
  <%= f.text_area :value,
10
10
  minlength: richtext_editor.length_validation&.fetch(:minimum, nil),
11
11
  maxlength: richtext_editor.length_validation&.fetch(:maximum, nil),
12
- required: richtext_editor.presence_validation?,
13
12
  id: richtext_editor.form_field_id(:value) %>
14
13
  <% end %>
15
14
  <% end %>
@@ -24,8 +24,7 @@
24
24
  <%= f.select :value, options_tags, {}, {
25
25
  id: select_editor.form_field_id,
26
26
  class: ["ingredient-editor-select"],
27
- is: "alchemy-select",
28
- required: select_editor.presence_validation?
27
+ is: "alchemy-select"
29
28
  } %>
30
29
  <% end %>
31
30
  <% end %>
@@ -1,8 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html lang="<%= ::I18n.locale %>" class="no-js loading-custom-elements alchemy-light">
2
+ <html lang="<%= ::I18n.locale %>" class="no-js loading-custom-elements">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="color-scheme" content="light dark">
6
7
  <title><%= render_alchemy_title %></title>
7
8
  <link rel="shortcut icon" href="<%= asset_path('alchemy/favicon.ico') %>">
8
9
  <link rel="preload" href="<%= asset_path("alchemy/icons-sprite.svg") %>" as="image" type="<%= Mime::Type.lookup_by_extension(:svg) %>" crossorigin>
@@ -10,6 +11,7 @@
10
11
  <meta name="robots" content="noindex">
11
12
  <meta name="turbo-prefetch" content="false">
12
13
  <meta name="turbo-cache-control" content="no-cache">
14
+ <%= stylesheet_link_tag('alchemy/theme', media: 'screen', 'data-turbo-track' => true) %>
13
15
  <%= stylesheet_link_tag('alchemy/admin', media: 'screen', 'data-turbo-track' => true) %>
14
16
  <%= stylesheet_link_tag('alchemy/admin/print', media: 'print', 'data-turbo-track' => true) %>
15
17
  <% Alchemy.admin_stylesheets.each do |stylesheet| %>
@@ -49,23 +51,26 @@
49
51
  <% end %>
50
52
  <%= yield(:alchemy_main_navigation) %>
51
53
  </div>
52
- <div id="logout" class="main_navi_entry">
53
- <% if current_alchemy_user %>
54
- <%= link_to_dialog(
55
- %(
56
- #{render_icon('logout-box-r', class: 'module')}
57
- <label>#{Alchemy.t(:leave)}</label>
58
- ).html_safe,
59
- alchemy.leave_admin_path, {
60
- size: "300x155",
61
- title: Alchemy.t("Leave Alchemy")
62
- }, {'data-alchemy-hotkey' => 'alt+q'}) %>
63
- <% else %>
64
- <%= link_to(alchemy.root_path) do %>
65
- <%= render_icon "logout-box-r", size: "lg" %>
66
- <label><%= Alchemy.t(:leave) %></label>
54
+
55
+ <div id="logout">
56
+ <div class="main_navi_entry">
57
+ <% if current_alchemy_user %>
58
+ <%= link_to_dialog(
59
+ %(
60
+ #{render_icon('logout-box-r', class: 'module')}
61
+ <label>#{Alchemy.t(:leave)}</label>
62
+ ).html_safe,
63
+ alchemy.leave_admin_path, {
64
+ size: "300x155",
65
+ title: Alchemy.t("Leave Alchemy")
66
+ }, {'data-alchemy-hotkey' => 'alt+q'}) %>
67
+ <% else %>
68
+ <%= link_to(alchemy.root_path) do %>
69
+ <%= render_icon "logout-box-r", size: "lg" %>
70
+ <label><%= Alchemy.t(:leave) %></label>
71
+ <% end %>
67
72
  <% end %>
68
- <% end %>
73
+ </div>
69
74
  </div>
70
75
  </div>
71
76
  <% if current_alchemy_user %>
@@ -143,7 +143,7 @@ en:
143
143
  without_tag:
144
144
  name: Without tag
145
145
  deletable:
146
- name: Not linked by file content
146
+ name: Not used
147
147
  attachment:
148
148
  by_file_type:
149
149
  name: File Type
@@ -156,6 +156,8 @@ en:
156
156
  name: Recently uploaded only
157
157
  without_tag:
158
158
  name: Without tag
159
+ deletable:
160
+ name: Not used
159
161
 
160
162
  default_ingredient_texts:
161
163
  lorem: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
@@ -224,6 +226,11 @@ en:
224
226
  elements:
225
227
  toolbar:
226
228
  hide: Hide
229
+ pictures:
230
+ sorting_select:
231
+ label: "Sorting"
232
+ by_latest: "Latest"
233
+ alphabetical: "A-Z"
227
234
  add_nested_element: "Add %{name}"
228
235
  anchor: "Anchor"
229
236
  anchor_link_headline: You can link to an anchor from the current page.
@@ -333,7 +340,6 @@ en:
333
340
  "Size": "Size"
334
341
  "Successfully deleted element": "Successfully deleted %{element}"
335
342
  "Tags": "Tags"
336
- "These pictures could not be deleted, because they were in use": "These pictures could not be deleted, because they were in use: %{names}"
337
343
  "This page is locked": "This page is currently locked by %{name}"
338
344
  "Title": "Title"
339
345
  "Trash": "Trash"
@@ -561,6 +567,7 @@ en:
561
567
  password: "Password"
562
568
  paste: "paste"
563
569
  pictures_in_page: "%{page} in %{pictures}"
570
+ pictures_will_be_deleted_now: Pictures will be deleted now
564
571
  place_link: "Add link"
565
572
  player_version: "Flash Player version"
566
573
  "please enter subject and mail address": "Please enter recipient and subject."
@@ -621,6 +628,12 @@ en:
621
628
  tag_list: Tags
622
629
  tags_get_created_if_used_the_first_time: "Tags get created if used the first time."
623
630
  this_picture_is_used_on_these_pages: "This picture is used on following pages"
631
+ used_on_these_pages:
632
+ attachment: "This file is used on following pages"
633
+ picture: "This picture is used on following pages"
634
+ not_in_use_yet:
635
+ attachment: "This file is not in use yet."
636
+ picture: "This picture is not in use yet."
624
637
  title: "Title"
625
638
  to_alchemy: "To Alchemy"
626
639
  unknown: "unknown"
@@ -773,16 +786,19 @@ en:
773
786
  node_ingredients_present: "This menu item is in use inside an Alchemy element on the following pages: %{page_names}."
774
787
  alchemy/site:
775
788
  attributes:
776
- languages:
777
- still_present: "are still attached to this site. Please remove them first."
789
+ base:
790
+ restrict_dependent_destroy:
791
+ has_many: "There are still %{record} attached to this site. Please remove them first."
778
792
  alchemy/language:
779
793
  attributes:
780
- pages:
781
- still_present: "are still attached to this language. Please remove them first."
794
+ base:
795
+ restrict_dependent_destroy:
796
+ has_many: "There are still %{record} attached to this language. Please remove them first."
782
797
  alchemy/page:
783
798
  attributes:
784
- nodes:
785
- still_present: "are still attached to this page. Please remove them first."
799
+ base:
800
+ restrict_dependent_destroy:
801
+ has_many: "There are still %{record} attached to this page. Please remove them first."
786
802
  models:
787
803
  gutentag/tag:
788
804
  one: Tag
@@ -869,6 +885,7 @@ en:
869
885
  public: "Public"
870
886
  locale: Localization
871
887
  code: ISO Code
888
+ nodes: Menu nodes
872
889
  alchemy/legacy_page_url:
873
890
  urlname: "URL-Path"
874
891
  alchemy/node:
@@ -887,6 +904,7 @@ en:
887
904
  meta_description: "Description"
888
905
  meta_keywords: "Keywords"
889
906
  name: "Name"
907
+ nodes: "Menu nodes"
890
908
  page_layout: "Page type"
891
909
  public: "public"
892
910
  restricted: "restricted"
@@ -0,0 +1,14 @@
1
+ class AddCreatedAtIndexToPicturesAndAttachments < ActiveRecord::Migration[7.1]
2
+ disable_ddl_transaction! if connection.adapter_name.match?(/postgres/i)
3
+
4
+ def change
5
+ add_index :alchemy_pictures, :created_at, if_not_exists: true, algorithm: algorithm
6
+ add_index :alchemy_attachments, :created_at, if_not_exists: true, algorithm: algorithm
7
+ end
8
+
9
+ private
10
+
11
+ def algorithm
12
+ connection.adapter_name.match?(/postgres/i) ? :concurrently : nil
13
+ end
14
+ end
@@ -4,7 +4,7 @@ module Alchemy
4
4
  module Configurations
5
5
  class FormatMatchers < Alchemy::Configuration
6
6
  option :email, :regexp, default: /\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/
7
- option :url, :regexp, default: /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix
7
+ option :url, :regexp, default: /\A[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix
8
8
  option :link_url, :regexp, default: /^(tel:|mailto:|\/|[a-z]+:\/\/)/
9
9
  end
10
10
  end
@@ -5,6 +5,7 @@ require "alchemy/configurations/default_language"
5
5
  require "alchemy/configurations/default_site"
6
6
  require "alchemy/configurations/format_matchers"
7
7
  require "alchemy/configurations/mailer"
8
+ require "alchemy/configurations/page_cache"
8
9
  require "alchemy/configurations/preview"
9
10
  require "alchemy/configurations/sitemap"
10
11
  require "alchemy/configurations/uploader"
@@ -31,6 +32,12 @@ module Alchemy
31
32
  #
32
33
  option :cache_pages, :boolean, default: true
33
34
 
35
+ # === Page caching max age
36
+ #
37
+ # max-age [Integer] # The duration in seconds for which the page is cached before revalidation.
38
+ # stale-while-revalidate [Boolean] # If true, enables the stale-while-revalidate caching strategy.
39
+ configuration :page_cache, PageCache
40
+
34
41
  # === Sitemap
35
42
  #
36
43
  # Alchemy creates a XML, Google compatible, sitemap for you.
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module Configurations
5
+ class PageCache < Alchemy::Configuration
6
+ # === Page caching max age
7
+ #
8
+ # Control the max-age duration in seconds in the cache-control header.
9
+ #
10
+ option :max_age, :integer, default: 600
11
+
12
+ # === Page caching stale-while-revalidate
13
+ #
14
+ # Set stale-while-revalidate cache-control header.
15
+ #
16
+ option :stale_while_revalidate, :integer
17
+ end
18
+ end
19
+ end
@@ -62,13 +62,15 @@ module Alchemy
62
62
  end
63
63
  end
64
64
 
65
+ # All the initialization that needs to be re-triggered during reloads
65
66
  config.to_prepare do
67
+ # Definition files
66
68
  elements_reloader = Rails.application.config.file_watcher.new([ElementDefinition.definitions_file_path]) do
67
- Rails.logger.info "[#{engine_name}] Reloading Element Definitions."
69
+ Rails.logger.info "[alchemy] Reloading Element Definitions."
68
70
  ElementDefinition.reset!
69
71
  end
70
72
  page_layouts_reloader = Rails.application.config.file_watcher.new([PageDefinition.layouts_file_path]) do
71
- Rails.logger.info "[#{engine_name}] Reloading Page Layouts."
73
+ Rails.logger.info "[alchemy] Reloading Page Layouts."
72
74
  PageDefinition.reset!
73
75
  end
74
76
  [elements_reloader, page_layouts_reloader].each do |reloader|
@@ -77,12 +79,19 @@ module Alchemy
77
79
  reloader.execute_if_updated
78
80
  end
79
81
  end
80
- end
81
82
 
82
- # Gutentag downcases all tags before save
83
- # and Gutentag validations are not case sensitive.
84
- # But we support having tags with uppercase characters.
85
- config.to_prepare do
83
+ # The storage adapter for Pictures and Attachments
84
+ #
85
+ # Chose between 'active_storage' (default) or 'dragonfly' (legacy)
86
+ #
87
+ # Can be set via 'ALCHEMY_STORAGE_ADAPTER' env var.
88
+ Alchemy.storage_adapter = Alchemy::StorageAdapter.new(
89
+ ENV.fetch("ALCHEMY_STORAGE_ADAPTER", Alchemy.config.storage_adapter)
90
+ )
91
+
92
+ # Gutentag downcases all tags before save
93
+ # and Gutentag validations are not case sensitive.
94
+ # But we support having tags with uppercase characters.
86
95
  Gutentag.normaliser = ->(value) { value.to_s }
87
96
  Gutentag.tag_validations = Alchemy::TagValidations
88
97
  end
@@ -22,18 +22,6 @@ module Alchemy
22
22
  {after: SENTINEL, verbose: true}
23
23
  end
24
24
 
25
- def set_primary_language(code: "en", name: "English", auto_accept: false)
26
- unless auto_accept
27
- code = ask("- What is the language code of your site's primary language?", default: code)
28
- end
29
- unless auto_accept
30
- name = ask("- What is the name of your site's primary language?", default: name)
31
- end
32
- gsub_file "./config/alchemy/config.yml", /default_language:\n\s\scode:\sen\n\s\sname:\sEnglish/m do
33
- "default_language:\n code: #{code}\n name: #{name}"
34
- end
35
- end
36
-
37
25
  def inject_seeder
38
26
  seed_file = Rails.root.join("db", "seeds.rb")
39
27
  args = [seed_file, "Alchemy::Seeder.seed!\n"]
@@ -22,5 +22,11 @@ module Alchemy
22
22
  def convert_to_humanized_name(name, suffix)
23
23
  name.gsub(/\.#{::Regexp.quote(suffix)}$/i, "").tr("_", " ").strip
24
24
  end
25
+
26
+ # Sanitizes a given filename by removing directory traversal attempts and HTML entities.
27
+ def sanitized_filename(file_name)
28
+ file_name = File.basename(file_name)
29
+ CGI.escapeHTML(file_name)
30
+ end
25
31
  end
26
32
  end
@@ -73,6 +73,24 @@ module Alchemy
73
73
  log "Deleted #{count} duplicate legacy URLs"
74
74
  end
75
75
 
76
+ def remove_legacy_essence_tables
77
+ puts "\n## Removing legacy essence tables"
78
+ matching_tables = ActiveRecord::Base.connection.tables.select do |table|
79
+ table.start_with?("alchemy_essence_")
80
+ end
81
+ if matching_tables.length.zero?
82
+ log "No legacy essence tables found", :skip
83
+ nil
84
+ else
85
+ matching_tables.each do |table|
86
+ ActiveRecord::Base.connection.drop_table(table, if_exists: true)
87
+ print "."
88
+ end
89
+ puts "\n"
90
+ log "Deleted #{matching_tables.length} legacy essence tables"
91
+ end
92
+ end
93
+
76
94
  private
77
95
 
78
96
  def destroy_orphaned_records(records, class_name)
@@ -29,6 +29,7 @@ FactoryBot.define do
29
29
  when :dragonfly
30
30
  picture.image_file = acc.image_file
31
31
  picture.image_file_size = acc.image_file.size
32
+ picture.image_file_format = File.extname(acc.image_file_name).delete(".").downcase
32
33
  end
33
34
  end
34
35
  end