alchemy_cms 8.2.7 → 8.3.1
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.
- checksums.yaml +4 -4
- data/README.md +4 -1
- data/app/assets/builds/alchemy/admin.css +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
- data/app/assets/builds/alchemy/dark-theme.css +1 -1
- data/app/assets/builds/alchemy/light-theme.css +1 -1
- data/app/assets/builds/alchemy/preview.min.js +1 -1
- data/app/assets/builds/alchemy/theme.css +1 -1
- data/app/assets/builds/alchemy/welcome.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -1
- data/app/assets/images/alchemy/admin/logo.svg +27 -0
- data/app/assets/images/alchemy/icons-sprite.svg +1 -1
- data/app/components/alchemy/admin/dashboard/widget.rb +40 -0
- data/app/components/alchemy/admin/dashboard/widgets/attachment_counts.rb +17 -0
- data/app/components/alchemy/admin/dashboard/widgets/element_usage.rb +37 -0
- data/app/components/alchemy/admin/dashboard/widgets/greeting.html.erb +13 -0
- data/app/components/alchemy/admin/dashboard/widgets/greeting.rb +21 -0
- data/app/components/alchemy/admin/dashboard/widgets/locked_pages.html.erb +54 -0
- data/app/components/alchemy/admin/dashboard/widgets/locked_pages.rb +20 -0
- data/app/components/alchemy/admin/dashboard/widgets/online_users.html.erb +22 -0
- data/app/components/alchemy/admin/dashboard/widgets/online_users.rb +19 -0
- data/app/components/alchemy/admin/dashboard/widgets/page_counts.rb +23 -0
- data/app/components/alchemy/admin/dashboard/widgets/page_usage.rb +46 -0
- data/app/components/alchemy/admin/dashboard/widgets/picture_counts.rb +17 -0
- data/app/components/alchemy/admin/dashboard/widgets/recent_pages.html.erb +41 -0
- data/app/components/alchemy/admin/dashboard/widgets/recent_pages.rb +16 -0
- data/app/components/alchemy/admin/dashboard/widgets/sites.html.erb +29 -0
- data/app/components/alchemy/admin/dashboard/widgets/sites.rb +15 -0
- data/app/components/alchemy/admin/dashboard/widgets/stat_widget.html.erb +23 -0
- data/app/components/alchemy/admin/dashboard/widgets/stat_widget.rb +19 -0
- data/app/components/alchemy/admin/dashboard/widgets/system_info.html.erb +32 -0
- data/app/components/alchemy/admin/dashboard/widgets/system_info.rb +37 -0
- data/app/components/alchemy/admin/dashboard/widgets/usage_widget.html.erb +42 -0
- data/app/components/alchemy/admin/dashboard/widgets/usage_widget.rb +66 -0
- data/app/components/alchemy/admin/dashboard/widgets/user_counts.rb +25 -0
- data/app/components/alchemy/admin/element_editor.html.erb +27 -20
- data/app/components/alchemy/admin/element_schedule_timestamps.rb +33 -0
- data/app/components/alchemy/admin/element_select.rb +4 -3
- data/app/components/alchemy/admin/link_dialog/anchor_tab.rb +1 -1
- data/app/components/alchemy/admin/link_dialog/file_tab.rb +1 -1
- data/app/components/alchemy/admin/page_node.html.erb +1 -20
- data/app/components/alchemy/admin/page_publication_fields.html.erb +30 -0
- data/app/components/alchemy/admin/page_publication_fields.rb +18 -0
- data/app/components/alchemy/admin/page_status_indicators.html.erb +29 -0
- data/app/components/alchemy/admin/page_status_indicators.rb +9 -0
- data/app/components/alchemy/admin/publish_element_button.html.erb +12 -4
- data/app/components/alchemy/ingredients/headline_editor.rb +1 -1
- data/app/controllers/alchemy/admin/dashboard/widgets_controller.rb +21 -0
- data/app/controllers/alchemy/admin/dashboard_controller.rb +3 -12
- data/app/controllers/alchemy/admin/pages_controller.rb +6 -5
- data/app/controllers/alchemy/pages_controller.rb +5 -4
- data/app/helpers/alchemy/elements_block_helper.rb +1 -0
- data/app/javascript/alchemy_admin/components/auto_submit.js +15 -9
- data/app/javascript/alchemy_admin/components/char_counter.js +17 -7
- data/app/javascript/alchemy_admin/components/clipboard_button.js +2 -6
- data/app/javascript/alchemy_admin/components/color_select.js +13 -4
- data/app/javascript/alchemy_admin/components/datepicker.js +11 -14
- data/app/javascript/alchemy_admin/components/dialog_link.js +5 -2
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +6 -3
- data/app/javascript/alchemy_admin/components/element_editor.js +45 -28
- data/app/javascript/alchemy_admin/components/element_select.js +7 -4
- data/app/javascript/alchemy_admin/components/elements_window.js +38 -31
- data/app/javascript/alchemy_admin/components/elements_window_handle.js +7 -3
- data/app/javascript/alchemy_admin/components/file_editor.js +5 -2
- data/app/javascript/alchemy_admin/components/ingredient_group.js +6 -4
- data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +1 -2
- data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +1 -2
- data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
- data/app/javascript/alchemy_admin/components/list_filter.js +44 -29
- data/app/javascript/alchemy_admin/components/message.js +22 -15
- data/app/javascript/alchemy_admin/components/overlay.js +5 -7
- data/app/javascript/alchemy_admin/components/page_publication_fields.js +38 -25
- data/app/javascript/alchemy_admin/components/picture_description_select.js +5 -2
- data/app/javascript/alchemy_admin/components/picture_editor.js +5 -10
- data/app/javascript/alchemy_admin/components/picture_thumbnail.js +4 -5
- data/app/javascript/alchemy_admin/components/preview_window.js +5 -10
- data/app/javascript/alchemy_admin/components/publish_page_button.js +2 -5
- data/app/javascript/alchemy_admin/components/remote_select.js +53 -23
- data/app/javascript/alchemy_admin/components/select.js +169 -26
- data/app/javascript/alchemy_admin/components/sortable_elements.js +1 -1
- data/app/javascript/alchemy_admin/components/spinner.js +11 -11
- data/app/javascript/alchemy_admin/components/tags_autocomplete.js +9 -1
- data/app/javascript/alchemy_admin/components/tinymce.js +16 -22
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +48 -45
- data/app/javascript/alchemy_admin/components/uploader/progress.js +70 -84
- data/app/javascript/alchemy_admin/components/uploader.js +71 -46
- data/app/javascript/alchemy_admin/dialog.js +3 -0
- data/app/javascript/alchemy_admin/hotkeys.js +0 -18
- data/app/javascript/alchemy_admin/image_cropper.js +7 -9
- data/app/javascript/alchemy_admin/initializer.js +21 -0
- data/app/javascript/alchemy_admin/utils/dispatch_page_dirty_event.js +7 -0
- data/app/javascript/tinymce/plugins/alchemy_link/index.js +9 -0
- data/app/jobs/alchemy/base_job.rb +2 -2
- data/app/jobs/alchemy/invalidate_elements_cache_job.rb +33 -0
- data/app/models/alchemy/page/page_naming.rb +28 -5
- data/app/models/alchemy/page/page_natures.rb +7 -2
- data/app/models/alchemy/page/page_scopes.rb +2 -2
- data/app/models/alchemy/page/url_path.rb +7 -2
- data/app/models/alchemy/page.rb +2 -2
- data/app/models/alchemy/page_definition.rb +1 -0
- data/app/models/alchemy/permissions.rb +1 -1
- data/app/models/concerns/alchemy/relatable_resource.rb +8 -0
- data/app/services/alchemy/page_finder.rb +88 -0
- data/app/stylesheets/alchemy/_custom-properties.scss +6 -4
- data/app/stylesheets/alchemy/_mixins.scss +1 -7
- data/app/stylesheets/alchemy/_themes.scss +15 -3
- data/app/stylesheets/alchemy/admin/_tom-select.scss +241 -0
- data/app/stylesheets/alchemy/admin/archive.scss +0 -1
- data/app/stylesheets/alchemy/admin/base.scss +0 -19
- data/app/stylesheets/alchemy/admin/buttons.scss +1 -0
- data/app/stylesheets/alchemy/admin/clipboard.scss +2 -0
- data/app/stylesheets/alchemy/admin/dashboard.scss +395 -28
- data/app/stylesheets/alchemy/admin/elements.scss +16 -23
- data/app/stylesheets/alchemy/admin/filters.scss +4 -1
- data/app/stylesheets/alchemy/admin/form_fields.scss +3 -3
- data/app/stylesheets/alchemy/admin/forms.scss +126 -94
- data/app/stylesheets/alchemy/admin/icons.scss +28 -0
- data/app/stylesheets/alchemy/admin/image_library.scss +30 -10
- data/app/stylesheets/alchemy/admin/navigation.scss +4 -1
- data/app/stylesheets/alchemy/admin/popover.scss +3 -5
- data/app/stylesheets/alchemy/admin/resource_info.scss +11 -17
- data/app/stylesheets/alchemy/admin/shoelace.scss +8 -0
- data/app/stylesheets/alchemy/admin/sitemap.scss +13 -0
- data/app/stylesheets/alchemy/admin/tables.scss +32 -3
- data/app/stylesheets/alchemy/admin/tags.scss +1 -6
- data/app/stylesheets/alchemy/admin/toolbar.scss +0 -1
- data/app/stylesheets/alchemy/admin.scss +1 -0
- data/app/stylesheets/tinymce/skins/ui/alchemy/skin.scss +0 -4
- data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +0 -4
- data/app/types/alchemy/wildcard_url_type.rb +48 -0
- data/app/views/alchemy/_menubar.html.erb +1 -5
- data/app/views/alchemy/admin/attachments/edit.html.erb +6 -3
- data/app/views/alchemy/admin/dashboard/_dashboard.html.erb +3 -2
- data/app/views/alchemy/admin/dashboard/_footer.html.erb +22 -0
- data/app/views/alchemy/admin/dashboard/_stats.html.erb +7 -0
- data/app/views/alchemy/admin/dashboard/_top.html.erb +4 -12
- data/app/views/alchemy/admin/dashboard/_widgets.html.erb +7 -0
- data/app/views/alchemy/admin/dashboard/index.html.erb +0 -17
- data/app/views/alchemy/admin/dashboard/info.html.erb +1 -62
- data/app/views/alchemy/admin/dashboard/widgets/show.html.erb +3 -0
- data/app/views/alchemy/admin/elements/_form.html.erb +2 -1
- data/app/views/alchemy/admin/elements/_schedule.html.erb +2 -15
- data/app/views/alchemy/admin/elements/_schedule_fields.html.erb +2 -0
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +6 -3
- data/app/views/alchemy/admin/legacy_page_urls/_table.html.erb +0 -1
- data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +11 -9
- data/app/views/alchemy/admin/pages/_form.html.erb +25 -19
- data/app/views/alchemy/admin/pages/_locked_page.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +2 -32
- data/app/views/alchemy/admin/pages/_table.html.erb +1 -18
- data/app/views/alchemy/admin/pages/configure.html.erb +2 -2
- data/app/views/alchemy/admin/pages/info.html.erb +6 -0
- data/app/views/alchemy/admin/pages/unlock.turbo_stream.erb +20 -0
- data/app/views/alchemy/admin/pictures/_form.html.erb +1 -1
- data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +1 -1
- data/app/views/alchemy/admin/resources/_form.html.erb +7 -4
- data/app/views/alchemy/admin/resources/edit.html.erb +3 -1
- data/app/views/alchemy/admin/resources/new.html.erb +3 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +52 -30
- data/app/views/alchemy/admin/translations/_en.js +4 -0
- data/app/views/layouts/alchemy/admin.html.erb +3 -3
- data/config/importmap.rb +2 -0
- data/config/locales/alchemy.en.yml +17 -2
- data/config/routes.rb +1 -0
- data/lib/alchemy/configuration/class_option.rb +46 -3
- data/lib/alchemy/configuration/collection_option.rb +4 -0
- data/lib/alchemy/configurations/dashboard.rb +79 -0
- data/lib/alchemy/configurations/main.rb +15 -0
- data/lib/alchemy/engine.rb +9 -3
- data/lib/alchemy/sprockets/skip_builds_compression.rb +33 -0
- data/lib/alchemy/test_support/capybara_helpers.rb +17 -0
- data/lib/alchemy/test_support/relatable_resource_examples.rb +20 -0
- data/lib/alchemy/test_support/rspec_matchers.rb +8 -0
- data/lib/alchemy/test_support/shared_publishable_examples.rb +38 -31
- data/lib/alchemy/tinymce.rb +1 -1
- data/lib/alchemy/version.rb +17 -3
- data/vendor/javascript/cropperjs.min.js +1 -1
- data/vendor/javascript/flatpickr.min.js +1 -1
- data/vendor/javascript/floating-ui.min.js +1 -0
- data/vendor/javascript/keymaster.min.js +1 -1
- data/vendor/javascript/rails-ujs.min.js +1 -1
- data/vendor/javascript/shoelace.min.js +93 -93
- data/vendor/javascript/sortable.min.js +1 -1
- data/vendor/javascript/tinymce.min.js +5 -1
- data/vendor/javascript/tom-select.min.js +1 -0
- metadata +58 -19
- data/app/javascript/alchemy_admin/components/alchemy_html_element.js +0 -129
- data/app/views/alchemy/admin/dashboard/_left_column.html.erb +0 -4
- data/app/views/alchemy/admin/dashboard/_right_column.html.erb +0 -9
- data/app/views/alchemy/admin/dashboard/widgets/_locked_pages.html.erb +0 -52
- data/app/views/alchemy/admin/dashboard/widgets/_recent_pages.html.erb +0 -34
- data/app/views/alchemy/admin/dashboard/widgets/_sites.html.erb +0 -25
- data/app/views/alchemy/admin/dashboard/widgets/_users.html.erb +0 -21
- data/app/views/alchemy/admin/languages/edit.html.erb +0 -1
- data/app/views/alchemy/admin/languages/new.html.erb +0 -1
- data/app/views/alchemy/admin/pages/unlock.js.erb +0 -20
- data/app/views/alchemy/admin/sites/edit.html.erb +0 -1
- data/app/views/alchemy/admin/sites/new.html.erb +0 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<% content_for :title do %>
|
|
2
|
-
|
|
2
|
+
Styleguide
|
|
3
3
|
<% end %>
|
|
4
4
|
|
|
5
5
|
<% content_for(:toolbar) do %>
|
|
6
6
|
<%= render Alchemy::Admin::ToolbarButton.new(
|
|
7
7
|
icon: :info,
|
|
8
8
|
label: 'Info',
|
|
9
|
-
url: alchemy.
|
|
9
|
+
url: alchemy.admin_styleguide_path,
|
|
10
10
|
title: 'Info',
|
|
11
11
|
hotkey: 'alt+i',
|
|
12
12
|
dialog_options: {
|
|
13
13
|
title: 'Info',
|
|
14
|
-
size: "
|
|
14
|
+
size: "650x400"
|
|
15
15
|
},
|
|
16
16
|
if_permitted_to: [:index, :alchemy_admin_styleguide]
|
|
17
17
|
) %>
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
<form class="simple_form alchemy" action="#" accept-charset="UTF-8" method="post">
|
|
30
30
|
<div class="input select">
|
|
31
31
|
<label class="select control-label" for="select">Select</label>
|
|
32
|
-
<select class="select" id="select" is="alchemy-select">
|
|
33
|
-
<option value="">Please choose</option>
|
|
32
|
+
<select class="select" id="select" is="alchemy-select" placeholder="Please choose">
|
|
34
33
|
<option value="1">Option 1</option>
|
|
35
34
|
<option selected value="2">Option 2</option>
|
|
36
35
|
<option value="3">Option 3</option>
|
|
@@ -53,18 +52,16 @@
|
|
|
53
52
|
|
|
54
53
|
<div class="input">
|
|
55
54
|
<label class="control-label">Date & Time Pickers</label>
|
|
56
|
-
<div class="
|
|
57
|
-
<div class="input-
|
|
58
|
-
<
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
</alchemy-datepicker>
|
|
67
|
-
</div>
|
|
55
|
+
<div class="input-row">
|
|
56
|
+
<div class="input-column">
|
|
57
|
+
<alchemy-datepicker input-type="date">
|
|
58
|
+
<input type="text" id="date_picker" autocomplete="off">
|
|
59
|
+
</alchemy-datepicker>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="input-column">
|
|
62
|
+
<alchemy-datepicker input-type="time">
|
|
63
|
+
<input type="text" id="time_picker" autocomplete="off">
|
|
64
|
+
</alchemy-datepicker>
|
|
68
65
|
</div>
|
|
69
66
|
</div>
|
|
70
67
|
</div>
|
|
@@ -139,10 +136,19 @@
|
|
|
139
136
|
|
|
140
137
|
<h2>Selects</h2>
|
|
141
138
|
|
|
142
|
-
<div style="display: flex; gap: 1em;">
|
|
139
|
+
<div style="display: flex; gap: 1em; flex-wrap: wrap">
|
|
140
|
+
<div style="width: 20%">
|
|
141
|
+
<h3>Native Select</h3>
|
|
142
|
+
<select class="full_width" autofocus>
|
|
143
|
+
<option value="1">Option 1</option>
|
|
144
|
+
<option value="2">Option 2</option>
|
|
145
|
+
<option value="3">Option 3</option>
|
|
146
|
+
</select>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
143
149
|
<div style="width: 20%">
|
|
144
150
|
<h3>Select with Search</h3>
|
|
145
|
-
<select is="alchemy-select" class="full_width">
|
|
151
|
+
<select is="alchemy-select" class="full_width" data-allow-clear placeholder="Please choose">
|
|
146
152
|
<option value="1">Option 1</option>
|
|
147
153
|
<option value="2">Option 2</option>
|
|
148
154
|
<option value="3">Option 3</option>
|
|
@@ -154,16 +160,33 @@
|
|
|
154
160
|
</div>
|
|
155
161
|
|
|
156
162
|
<div style="width: 20%">
|
|
157
|
-
<h3>
|
|
158
|
-
|
|
159
|
-
<
|
|
160
|
-
|
|
163
|
+
<h3>Multi Select</h3>
|
|
164
|
+
<select is="alchemy-select" multiple class="full_width" placeholder="Pick Options" data-allow-clear>
|
|
165
|
+
<option value="1">Option 1</option>
|
|
166
|
+
<option value="2">Option 2</option>
|
|
167
|
+
<option value="3">Option 3</option>
|
|
168
|
+
<option value="4">Option 4</option>
|
|
169
|
+
<option value="5">Option 5</option>
|
|
170
|
+
<option value="6">Option 6</option>
|
|
171
|
+
<option value="7">Option 7</option>
|
|
172
|
+
</select>
|
|
161
173
|
</div>
|
|
162
174
|
|
|
175
|
+
|
|
163
176
|
<div style="width: 20%">
|
|
164
177
|
<h3>Element Select</h3>
|
|
165
178
|
<%= render Alchemy::Admin::ElementSelect.new(Alchemy::Element.definitions) %>
|
|
166
179
|
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<h2>Remote Selects</h2>
|
|
183
|
+
<div style="display: flex; gap: 1em; flex-wrap: wrap">
|
|
184
|
+
<div style="width: 20%">
|
|
185
|
+
<h3>Page Select</h3>
|
|
186
|
+
<%= render Alchemy::Admin::PageSelect.new do %>
|
|
187
|
+
<input name="page" class="full_width">
|
|
188
|
+
<% end %>
|
|
189
|
+
</div>
|
|
167
190
|
|
|
168
191
|
<div style="width: 20%">
|
|
169
192
|
<h3>Node Select</h3>
|
|
@@ -178,14 +201,13 @@
|
|
|
178
201
|
<input name="attachment" class="full_width">
|
|
179
202
|
<% end %>
|
|
180
203
|
</div>
|
|
181
|
-
</div>
|
|
182
204
|
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
</
|
|
205
|
+
<div style="width: 20%">
|
|
206
|
+
<h3>Tags Autocomplete</h3>
|
|
207
|
+
<%= render Alchemy::Admin::TagsAutocomplete.new do %>
|
|
208
|
+
<input type="text" class="full_width" autocomplete="off">
|
|
209
|
+
<% end %>
|
|
210
|
+
</div>
|
|
189
211
|
</div>
|
|
190
212
|
|
|
191
213
|
<h2>Typography</h2>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Alchemy.translations = {
|
|
2
|
+
Add: "Add",
|
|
2
3
|
allowed_chars: "of %{count} chars",
|
|
3
4
|
cancel: "Cancel",
|
|
4
5
|
cancelled: "Cancelled",
|
|
@@ -23,6 +24,9 @@ Alchemy.translations = {
|
|
|
23
24
|
"Uploaded bytes exceed file size": "Uploaded bytes exceed file size",
|
|
24
25
|
"Abort upload": "Abort upload",
|
|
25
26
|
"Cancel all uploads": "Cancel all uploads",
|
|
27
|
+
"Clear selection": "Clear selection",
|
|
28
|
+
Remove: "Remove",
|
|
29
|
+
"No results found": "No results found",
|
|
26
30
|
None: "None",
|
|
27
31
|
"No anchors found": "No anchors found",
|
|
28
32
|
"Select a page first": "Select a page first",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
</noscript>
|
|
46
46
|
<alchemy-overlay text="<%= Alchemy.t(:please_wait) %>"></alchemy-overlay>
|
|
47
47
|
<%= render "alchemy/admin/left_menu" %>
|
|
48
|
-
<% if current_alchemy_user %>
|
|
49
|
-
<%= render "alchemy/admin/top_menu", locked_pages: @locked_pages %>
|
|
50
|
-
<% end %>
|
|
51
48
|
<%= render 'alchemy/admin/partials/flash_notices' %>
|
|
52
49
|
<div id="main_content">
|
|
53
50
|
<%= yield %>
|
|
54
51
|
</div>
|
|
52
|
+
<% if current_alchemy_user %>
|
|
53
|
+
<%= render "alchemy/admin/top_menu", locked_pages: @locked_pages %>
|
|
54
|
+
<% end %>
|
|
55
55
|
<%= render 'alchemy/admin/uploader/setup' %>
|
|
56
56
|
<%= yield(:javascripts) %>
|
|
57
57
|
<% end %>
|
data/config/importmap.rb
CHANGED
|
@@ -7,10 +7,12 @@ pin "jquery", to: "jquery.min.js", preload: true
|
|
|
7
7
|
pin "keymaster", to: "keymaster.min.js", preload: true
|
|
8
8
|
pin "select2", to: "select2.min.js", preload: true
|
|
9
9
|
pin "sortablejs", to: "sortable.min.js", preload: true # @1.15.1
|
|
10
|
+
pin "@floating-ui/dom", to: "floating-ui.min.js", preload: true
|
|
10
11
|
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|
11
12
|
pin "shoelace", to: "shoelace.min.js", preload: true
|
|
12
13
|
pin "@rails/ujs", to: "rails-ujs.min.js", preload: true # @7.1.2
|
|
13
14
|
pin "tinymce", to: "tinymce.min.js", preload: true
|
|
15
|
+
pin "tom-select", to: "tom-select.min.js", preload: true
|
|
14
16
|
|
|
15
17
|
pin "alchemy_admin", to: "alchemy/alchemy_admin.min.js", preload: true
|
|
16
18
|
pin "alchemy_admin/components/remote_select", to: "alchemy/alchemy_admin.min.js"
|
|
@@ -230,6 +230,12 @@ en:
|
|
|
230
230
|
label: "Sorting"
|
|
231
231
|
by_latest: "Latest"
|
|
232
232
|
alphabetical: "A-Z"
|
|
233
|
+
dashboard:
|
|
234
|
+
widgets:
|
|
235
|
+
page_counts:
|
|
236
|
+
published: "published"
|
|
237
|
+
user_counts:
|
|
238
|
+
online: "online"
|
|
233
239
|
elements:
|
|
234
240
|
toolbar:
|
|
235
241
|
hide: Hide
|
|
@@ -271,7 +277,7 @@ en:
|
|
|
271
277
|
"Confirm new password": "Confirm new password"
|
|
272
278
|
"Copy": "Copy"
|
|
273
279
|
"Could not load Adobe Flash® Plugin!": "Could not load Adobe Flash® Plugin!"
|
|
274
|
-
"Create node on parent:": "
|
|
280
|
+
"Create node on parent:": "Attach to menu"
|
|
275
281
|
"Currently locked pages": "Currently locked pages"
|
|
276
282
|
"Default language has to be public": "Default language has to be public"
|
|
277
283
|
"Delete image": "Delete image"
|
|
@@ -423,7 +429,7 @@ en:
|
|
|
423
429
|
"Create site": "Create a new site"
|
|
424
430
|
create_language_tree_heading: "Create new homepage"
|
|
425
431
|
create_menu: "Add a menu"
|
|
426
|
-
create_node: "Add
|
|
432
|
+
create_node: "Add to menu"
|
|
427
433
|
create_page: "Create a new subpage"
|
|
428
434
|
currently_edited_by: "This page is locked by"
|
|
429
435
|
custom_color: "Custom color"
|
|
@@ -571,6 +577,10 @@ en:
|
|
|
571
577
|
restricted:
|
|
572
578
|
"true": "Page is only accessible by members."
|
|
573
579
|
"false": "Page is accessible by all visitors."
|
|
580
|
+
scheduled:
|
|
581
|
+
public_on: "Page is visible from %{public_on}"
|
|
582
|
+
public_until: "Page is visible until %{public_until}"
|
|
583
|
+
"false": ""
|
|
574
584
|
page_status_titles:
|
|
575
585
|
public:
|
|
576
586
|
"true": "online"
|
|
@@ -581,6 +591,9 @@ en:
|
|
|
581
591
|
restricted:
|
|
582
592
|
"true": "restricted"
|
|
583
593
|
"false": "accessible"
|
|
594
|
+
scheduled:
|
|
595
|
+
"true": "scheduled"
|
|
596
|
+
"false": ""
|
|
584
597
|
page_status: "Status"
|
|
585
598
|
page_title: "Title"
|
|
586
599
|
page_type: "Type"
|
|
@@ -834,6 +847,8 @@ en:
|
|
|
834
847
|
base:
|
|
835
848
|
restrict_dependent_destroy:
|
|
836
849
|
has_many: "There are still %{record} attached to this page. Please remove them first."
|
|
850
|
+
page_layout:
|
|
851
|
+
conflicting_wildcard_param_key: 'has a conflicting wildcard param "%{param}" already used by the "%{conflicting_layout}" page layout'
|
|
837
852
|
descendants:
|
|
838
853
|
still_attached_to_nodes: "The following descendant pages are still attached to menu nodes: %{page_names}. Please remove them first."
|
|
839
854
|
alchemy/element:
|
data/config/routes.rb
CHANGED
|
@@ -11,6 +11,7 @@ Alchemy::Engine.routes.draw do
|
|
|
11
11
|
get "/", to: redirect("#{Alchemy.admin_path}/dashboard"), as: :admin
|
|
12
12
|
get "/dashboard", to: "admin/dashboard#index", as: :admin_dashboard
|
|
13
13
|
get "/dashboard/info", to: "admin/dashboard#info", as: :dashboard_info
|
|
14
|
+
get "/dashboard/widgets/:id", to: "admin/dashboard/widgets#show", as: :admin_dashboard_widget
|
|
14
15
|
get "/help", to: "admin/dashboard#help", as: :help
|
|
15
16
|
get "/update_check" => "admin/update_checks#show", :as => :update_check
|
|
16
17
|
get "/leave", to: "admin/base#leave", as: :leave_admin
|
|
@@ -5,11 +5,54 @@ require "alchemy/configuration/base_option"
|
|
|
5
5
|
module Alchemy
|
|
6
6
|
class Configuration
|
|
7
7
|
class ClassOption < BaseOption
|
|
8
|
-
def
|
|
9
|
-
String
|
|
8
|
+
def allowed_classes
|
|
9
|
+
[String, Array]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def value
|
|
12
|
+
def validate(value)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
if value.is_a?(Array)
|
|
16
|
+
validate_array!(value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def value
|
|
21
|
+
@_cached_value ||= case @value
|
|
22
|
+
when Array
|
|
23
|
+
@value[0] = @value[0]&.constantize
|
|
24
|
+
@value
|
|
25
|
+
when String
|
|
26
|
+
@value&.constantize
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def validate_array!(value)
|
|
33
|
+
@array = value
|
|
34
|
+
has_length_two! && first_value_is_string! && second_value_is_hash!
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def has_length_two!
|
|
38
|
+
return true if @array.length == 2
|
|
39
|
+
|
|
40
|
+
raise(ConfigurationError.new(name, @array, [
|
|
41
|
+
Class.new(Array) { def self.name = "an Array of length two" }
|
|
42
|
+
]))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def first_value_is_string!
|
|
46
|
+
return true if @array[0].is_a?(String)
|
|
47
|
+
|
|
48
|
+
raise(ConfigurationError.new(name, @array[0], [String]))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def second_value_is_hash!
|
|
52
|
+
return true if @array[1].is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
raise(ConfigurationError.new(name, @array[1], [Hash]))
|
|
55
|
+
end
|
|
13
56
|
end
|
|
14
57
|
end
|
|
15
58
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alchemy
|
|
4
|
+
module Configurations
|
|
5
|
+
class Dashboard < Alchemy::Configuration
|
|
6
|
+
option :stats, :collection, item_type: :class, default: [
|
|
7
|
+
[
|
|
8
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
9
|
+
id: "PageCounts",
|
|
10
|
+
style: "stat"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
15
|
+
id: "UserCounts",
|
|
16
|
+
style: "stat"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
21
|
+
id: "PictureCounts",
|
|
22
|
+
style: "stat"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
27
|
+
id: "AttachmentCounts",
|
|
28
|
+
style: "stat"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
option :widgets, :collection, item_type: :class, default: [
|
|
34
|
+
[
|
|
35
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
36
|
+
id: "LockedPages",
|
|
37
|
+
style: "wide"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
42
|
+
id: "RecentPages",
|
|
43
|
+
style: "wide"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
48
|
+
id: "ElementUsage",
|
|
49
|
+
style: "usage",
|
|
50
|
+
loading: "lazy"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
55
|
+
id: "PageUsage",
|
|
56
|
+
style: "usage",
|
|
57
|
+
loading: "lazy"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
62
|
+
id: "Sites",
|
|
63
|
+
loading: "lazy",
|
|
64
|
+
condition: -> { helpers.multi_site? }
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
"Alchemy::Admin::Dashboard::Widget", {
|
|
69
|
+
id: "OnlineUsers",
|
|
70
|
+
loading: "lazy",
|
|
71
|
+
condition: -> {
|
|
72
|
+
Alchemy.config.user_class.respond_to?(:logged_in)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "alchemy/configuration"
|
|
4
|
+
require "alchemy/configurations/dashboard"
|
|
4
5
|
require "alchemy/configurations/default_language"
|
|
5
6
|
require "alchemy/configurations/default_site"
|
|
6
7
|
require "alchemy/configurations/importmap"
|
|
@@ -434,6 +435,16 @@ module Alchemy
|
|
|
434
435
|
# The path to the page showing the user they're unauthorized
|
|
435
436
|
option :unauthorized_path, :string, default: "/"
|
|
436
437
|
|
|
438
|
+
# === Admin Users Path
|
|
439
|
+
#
|
|
440
|
+
# The path to the admin users list.
|
|
441
|
+
#
|
|
442
|
+
# == Example
|
|
443
|
+
#
|
|
444
|
+
# "/admin/users"
|
|
445
|
+
#
|
|
446
|
+
option :admin_users_path, :string
|
|
447
|
+
|
|
437
448
|
# === Edit User Path
|
|
438
449
|
#
|
|
439
450
|
# The path to the edit user form.
|
|
@@ -453,6 +464,10 @@ module Alchemy
|
|
|
453
464
|
# == Example
|
|
454
465
|
# Alchemy.config.abilities.add("MyCustom::Ability")
|
|
455
466
|
option :abilities, :collection, item_type: :class
|
|
467
|
+
|
|
468
|
+
# === Dashboard configuration
|
|
469
|
+
#
|
|
470
|
+
configuration :dashboard, Dashboard
|
|
456
471
|
end
|
|
457
472
|
end
|
|
458
473
|
end
|
data/lib/alchemy/engine.rb
CHANGED
|
@@ -17,7 +17,8 @@ module Alchemy
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
initializer "alchemy.assets" do |app|
|
|
20
|
-
if defined?(Sprockets)
|
|
20
|
+
if defined?(::Sprockets)
|
|
21
|
+
require_relative "sprockets/skip_builds_compression"
|
|
21
22
|
require_relative "../non_stupid_digest_assets"
|
|
22
23
|
NonStupidDigestAssets.whitelist += [/^tinymce\//]
|
|
23
24
|
app.config.assets.precompile << "alchemy_manifest.js"
|
|
@@ -25,7 +26,7 @@ module Alchemy
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
initializer "alchemy.admin_stylesheets" do |app|
|
|
28
|
-
if defined?(Sprockets)
|
|
29
|
+
if defined?(::Sprockets)
|
|
29
30
|
Alchemy.config.admin_stylesheets.each do |stylesheet|
|
|
30
31
|
app.config.assets.precompile << stylesheet
|
|
31
32
|
end
|
|
@@ -33,7 +34,7 @@ module Alchemy
|
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
initializer "alchemy.propshaft" do |app|
|
|
36
|
-
if defined?(Propshaft)
|
|
37
|
+
if defined?(::Propshaft)
|
|
37
38
|
if app.config.assets.server
|
|
38
39
|
# Monkey-patch Propshaft::Asset to enable access
|
|
39
40
|
# of TinyMCE assets without a hash digest.
|
|
@@ -67,6 +68,11 @@ module Alchemy
|
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
if app.config.importmap.sweep_cache
|
|
71
|
+
# The importmap pins resolve to the bundled files in app/assets/builds,
|
|
72
|
+
# so the cache must be swept when those are rebuilt. Without this the
|
|
73
|
+
# cached importmap keeps emitting the previous digest after a rebuild
|
|
74
|
+
# and the asset 404s until the server is restarted.
|
|
75
|
+
watch_paths << Alchemy::Engine.root.join("app/assets/builds")
|
|
70
76
|
Alchemy.importmap.cache_sweeper(watches: watch_paths)
|
|
71
77
|
ActiveSupport.on_load(:action_controller_base) do
|
|
72
78
|
before_action { Alchemy.importmap.cache_sweeper.execute_if_updated }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "sprockets/sass_compressor"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Sprockets::SassCompressor is only defined if sassc-rails is present,
|
|
7
|
+
# which is not the case in all environments (for example, when using Propshaft).
|
|
8
|
+
# In that case, we can skip prepending our patch module.
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module Alchemy
|
|
12
|
+
module Sprockets
|
|
13
|
+
# Alchemy ships pre-built, already-minified admin CSS in +app/assets/builds+
|
|
14
|
+
# that uses modern CSS syntax (relative colors, +oklch()+ and friends). The
|
|
15
|
+
# legacy SassC/libSass +css_compressor+ — the Sprockets default whenever
|
|
16
|
+
# +sassc-rails+ is present (for example through Solidus) — re-parses every
|
|
17
|
+
# +text/css+ asset as SCSS and raises on that syntax. These files are already
|
|
18
|
+
# minified, so leave them untouched; all other stylesheets still get
|
|
19
|
+
# compressed as before.
|
|
20
|
+
module SkipBuildsCompression
|
|
21
|
+
def call(input)
|
|
22
|
+
builds_path = Alchemy::Engine.root.join("app/assets/builds").to_s
|
|
23
|
+
if input[:filename].to_s.start_with?(builds_path)
|
|
24
|
+
{data: input[:data]}
|
|
25
|
+
else
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
::Sprockets::SassCompressor.singleton_class.prepend(self)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -25,6 +25,23 @@ module Alchemy
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# Tom Select capybara helper
|
|
29
|
+
def tom_select(value, options)
|
|
30
|
+
label = find_label_by_text(options[:from])
|
|
31
|
+
|
|
32
|
+
within label.find(:xpath, "..") do
|
|
33
|
+
find(".ts-control").click
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The dropdown is appended to the body, so search the whole page for it.
|
|
37
|
+
within_entire_page do
|
|
38
|
+
find(
|
|
39
|
+
".ts-dropdown .option",
|
|
40
|
+
text: /#{Regexp.escape(value)}/i, match: :prefer_exact
|
|
41
|
+
).click
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
def select2_search(value, options)
|
|
29
46
|
if options[:from]
|
|
30
47
|
label = find_label_by_text(options[:from])
|
|
@@ -55,4 +55,24 @@ RSpec.shared_examples_for "a relatable resource" do |args|
|
|
|
55
55
|
it { is_expected.to be(true) }
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
|
+
|
|
59
|
+
describe "after_touch" do
|
|
60
|
+
let(:related_object) { create(:"alchemy_#{args[:resource_name]}") }
|
|
61
|
+
|
|
62
|
+
context "when related ingredients exist" do
|
|
63
|
+
let!(:ingredient) { create(:"alchemy_ingredient_#{args[:ingredient_type]}", related_object:) }
|
|
64
|
+
|
|
65
|
+
it "enqueues InvalidateElementsCacheJob" do
|
|
66
|
+
expect {
|
|
67
|
+
related_object.touch
|
|
68
|
+
}.to have_enqueued_job(Alchemy::InvalidateElementsCacheJob).with(described_class.name, related_object.id)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
context "when no related ingredients exist" do
|
|
73
|
+
it "does not enqueue InvalidateElementsCacheJob" do
|
|
74
|
+
expect { related_object.touch }.to_not have_enqueued_job(Alchemy::InvalidateElementsCacheJob)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
58
78
|
end
|
|
@@ -12,3 +12,11 @@ RSpec::Matchers.define :include_language_information_for do |expected|
|
|
|
12
12
|
actual[:alchemy_language_id] == expected.id
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
# This matcher checks for the presence of an alchemy-select component with a given label.
|
|
17
|
+
RSpec::Matchers.define :have_alchemy_select do |expected|
|
|
18
|
+
match do |session|
|
|
19
|
+
label = session.find(:css, "label", exact_text: expected)
|
|
20
|
+
label.has_sibling?(%(select[is="alchemy-select"]), visible: :all)
|
|
21
|
+
end
|
|
22
|
+
end
|