alchemy_cms 8.1.8 → 8.2.0

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -1
  3. data/app/assets/builds/alchemy/admin.css +1 -1
  4. data/app/assets/builds/alchemy/alchemy_admin.min.js +2 -0
  5. data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -0
  6. data/app/assets/builds/alchemy/dark-theme.css +1 -1
  7. data/app/assets/builds/alchemy/light-theme.css +1 -1
  8. data/app/assets/builds/alchemy/theme.css +1 -1
  9. data/app/assets/builds/alchemy/welcome.css +1 -1
  10. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  11. data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -1
  12. data/app/assets/images/alchemy/icons-sprite.svg +1 -1
  13. data/app/components/alchemy/admin/current_user_name.rb +34 -0
  14. data/app/components/alchemy/admin/locale_select.rb +12 -8
  15. data/app/components/alchemy/admin/page_node.html.erb +3 -2
  16. data/app/components/alchemy/admin/picture_thumbnail.rb +1 -1
  17. data/app/components/alchemy/admin/preview_time_select.rb +55 -0
  18. data/app/components/alchemy/admin/publish_element_button.html.erb +41 -0
  19. data/app/components/alchemy/admin/publish_element_button.rb +13 -0
  20. data/app/components/alchemy/admin/timezone_select.rb +47 -0
  21. data/app/components/alchemy/ingredients/base_editor.rb +1 -1
  22. data/app/components/alchemy/ingredients/select_editor.rb +6 -1
  23. data/app/controllers/alchemy/admin/base_controller.rb +1 -0
  24. data/app/controllers/alchemy/admin/elements_controller.rb +54 -34
  25. data/app/controllers/alchemy/admin/languages_controller.rb +3 -0
  26. data/app/controllers/alchemy/admin/pages_controller.rb +1 -0
  27. data/app/controllers/alchemy/admin/resources_controller.rb +11 -6
  28. data/app/controllers/alchemy/pages_controller.rb +1 -2
  29. data/app/decorators/alchemy/ingredient_editor.rb +1 -1
  30. data/app/helpers/alchemy/admin/base_helper.rb +4 -7
  31. data/app/helpers/alchemy/url_helper.rb +2 -10
  32. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +28 -27
  33. data/app/javascript/alchemy_admin/components/element_editor.js +11 -2
  34. data/app/javascript/alchemy_admin/components/message.js +5 -1
  35. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +5 -5
  36. data/app/javascript/alchemy_admin/image_cropper.js +10 -6
  37. data/app/javascript/alchemy_admin/initializer.js +6 -33
  38. data/app/javascript/alchemy_admin/shoelace_theme.js +6 -2
  39. data/app/javascript/alchemy_admin/templates/compiled.js +1 -1
  40. data/app/javascript/alchemy_admin.js +12 -2
  41. data/app/models/alchemy/attachment.rb +1 -1
  42. data/app/models/alchemy/current.rb +5 -1
  43. data/app/models/alchemy/element/element_ingredients.rb +11 -3
  44. data/app/models/alchemy/element.rb +10 -0
  45. data/app/models/alchemy/ingredient.rb +2 -0
  46. data/app/models/alchemy/ingredients/select.rb +1 -2
  47. data/app/models/alchemy/language/code.rb +1 -0
  48. data/app/models/alchemy/page/etag_generator.rb +21 -0
  49. data/app/models/alchemy/page/url_path.rb +11 -2
  50. data/app/models/alchemy/page.rb +12 -2
  51. data/app/models/alchemy/page_version.rb +5 -5
  52. data/app/models/alchemy/picture.rb +19 -2
  53. data/app/models/alchemy/storage_adapter/active_storage.rb +9 -0
  54. data/app/models/alchemy/storage_adapter/dragonfly.rb +9 -0
  55. data/app/models/alchemy/storage_adapter.rb +1 -0
  56. data/app/models/concerns/alchemy/publishable.rb +20 -12
  57. data/app/models/concerns/alchemy/relatable_resource.rb +16 -2
  58. data/app/models/concerns/alchemy/touch_elements.rb +3 -3
  59. data/app/services/alchemy/element_preloader.rb +107 -0
  60. data/app/services/alchemy/update_checks/alchemy_app.rb +1 -1
  61. data/app/stylesheets/alchemy/_custom-properties.scss +1 -0
  62. data/app/stylesheets/alchemy/_mixins.scss +1 -1
  63. data/app/stylesheets/alchemy/_themes.scss +2 -0
  64. data/app/stylesheets/alchemy/admin/base.scss +2 -1
  65. data/app/stylesheets/alchemy/admin/elements.scss +22 -19
  66. data/app/stylesheets/alchemy/admin/form_fields.scss +3 -0
  67. data/app/stylesheets/alchemy/admin/forms.scss +14 -1
  68. data/app/stylesheets/alchemy/admin/frame.scss +9 -8
  69. data/app/stylesheets/alchemy/admin/notices.scss +1 -1
  70. data/app/stylesheets/alchemy/admin/popover.scss +37 -0
  71. data/app/stylesheets/alchemy/admin/selects.scss +4 -0
  72. data/app/stylesheets/alchemy/admin/shoelace.scss +16 -4
  73. data/app/stylesheets/alchemy/admin/toolbar.scss +8 -0
  74. data/app/stylesheets/alchemy/admin.scss +1 -0
  75. data/app/views/alchemy/admin/_header.html.erb +4 -0
  76. data/app/views/alchemy/admin/_left_menu.html.erb +24 -0
  77. data/app/views/alchemy/admin/_main_navi.html.erb +6 -0
  78. data/app/views/alchemy/admin/_top_menu.html.erb +6 -0
  79. data/app/views/alchemy/admin/_user_info.html.erb +5 -0
  80. data/app/views/alchemy/admin/crop.html.erb +6 -11
  81. data/app/views/alchemy/admin/elements/_header.html.erb +16 -6
  82. data/app/views/alchemy/admin/elements/_schedule.html.erb +62 -0
  83. data/app/views/alchemy/admin/elements/_toolbar.html.erb +1 -15
  84. data/app/views/alchemy/admin/elements/publish.turbo_stream.erb +28 -0
  85. data/app/views/alchemy/admin/nodes/index.html.erb +1 -1
  86. data/app/views/alchemy/admin/pages/_locked_pages.html.erb +5 -0
  87. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +4 -4
  88. data/app/views/alchemy/admin/pages/_table.html.erb +1 -1
  89. data/app/views/alchemy/admin/pages/edit.html.erb +6 -2
  90. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +10 -10
  91. data/app/views/alchemy/admin/partials/_site_select.html.erb +6 -3
  92. data/app/views/alchemy/admin/pictures/index.html.erb +2 -2
  93. data/app/views/alchemy/admin/tinymce/_setup.html.erb +9 -16
  94. data/app/views/alchemy/admin/uploader/_setup.html.erb +1 -6
  95. data/app/views/alchemy/language_links/_language.html.erb +1 -2
  96. data/app/views/layouts/alchemy/admin.html.erb +2 -45
  97. data/config/importmap.rb +7 -2
  98. data/config/locales/alchemy.en.yml +35 -5
  99. data/lib/alchemy/admin/preview_time.rb +23 -0
  100. data/lib/alchemy/admin/preview_url.rb +13 -2
  101. data/lib/alchemy/admin/timezone.rb +56 -0
  102. data/lib/alchemy/configuration.rb +2 -0
  103. data/lib/alchemy/configurations/main.rb +13 -1
  104. data/lib/alchemy/controller_actions.rb +2 -1
  105. data/lib/alchemy/tasks/tidy.rb +6 -7
  106. data/lib/alchemy/test_support/factories/element_factory.rb +2 -2
  107. data/lib/alchemy/test_support/relatable_resource_examples.rb +2 -2
  108. data/lib/alchemy/test_support/shared_publishable_examples.rb +44 -2
  109. data/lib/alchemy/upgrader.rb +3 -1
  110. data/lib/alchemy/version.rb +1 -1
  111. data/lib/alchemy_cms.rb +2 -0
  112. data/lib/generators/alchemy/install/install_generator.rb +2 -1
  113. data/vendor/javascript/handlebars.min.js +4 -4
  114. data/vendor/javascript/shoelace.min.js +1419 -1323
  115. data/vendor/javascript/sortable.min.js +2 -2
  116. data/vendor/javascript/tinymce.min.js +1 -1
  117. metadata +35 -1
@@ -0,0 +1,37 @@
1
+ .alchemy-popover {
2
+ position: relative;
3
+ padding: var(--spacing-2);
4
+
5
+ alchemy-message {
6
+ margin-left: 0;
7
+ margin-right: 0;
8
+ margin-bottom: var(--spacing-3);
9
+ }
10
+
11
+ .timezone-select {
12
+ display: flex;
13
+ gap: var(--spacing-2);
14
+ align-items: center;
15
+
16
+ .alchemy_selectbox {
17
+ min-width: var(--select-x-large-width);
18
+ }
19
+ }
20
+
21
+ span.error {
22
+ display: block;
23
+ background-color: var(--notice-error-background-color);
24
+ color: var(--notice-error-text-color);
25
+ padding: var(--spacing-2);
26
+ border-radius: var(--border-radius_medium);
27
+ font-size: var(--font-size_small);
28
+ }
29
+
30
+ .submit {
31
+ gap: var(--spacing-2);
32
+
33
+ button {
34
+ width: 50%;
35
+ }
36
+ }
37
+ }
@@ -38,6 +38,10 @@ select {
38
38
  &.large {
39
39
  width: var(--select-large-width);
40
40
  }
41
+
42
+ &.x-large {
43
+ width: var(--select-x-large-width);
44
+ }
41
45
  }
42
46
 
43
47
  .select2-container {
@@ -106,9 +106,9 @@
106
106
  --sl-font-size-2x-small: 0.625rem; /* 10px */
107
107
  --sl-font-size-x-small: var(--font-size_small); /* 10px */
108
108
  --sl-font-size-small: var(--font-size_medium); /* 12px */
109
- --sl-font-size-medium: var(--font-size_large); /* 16px */
110
- --sl-font-size-large: 1.25rem; /* 20px */
111
- --sl-font-size-x-large: 1.5rem; /* 24px */
109
+ --sl-font-size-medium: var(--font-size_medium); /* 16px */
110
+ --sl-font-size-large: var(--font-size_medium); /* 20px */
111
+ --sl-font-size-x-large: var(--font-size_large); /* 24px */
112
112
  --sl-font-size-2x-large: 2.25rem; /* 36px */
113
113
  --sl-font-size-3x-large: 3rem; /* 48px */
114
114
  --sl-font-size-4x-large: 4.5rem; /* 72px */
@@ -285,6 +285,15 @@
285
285
  }
286
286
  }
287
287
 
288
+ sl-dropdown {
289
+ &::part(panel) {
290
+ background-color: var(--dialog-background-color);
291
+ box-shadow: none;
292
+ filter: drop-shadow(var(--dialog-box-shadow));
293
+ border: 1px solid var(--border-color);
294
+ }
295
+ }
296
+
288
297
  sl-tab-group {
289
298
  --indicator-color: var(--tabs_indicator-color);
290
299
  --track-color: var(--tabs_track-color);
@@ -326,6 +335,10 @@ sl-button {
326
335
  &[disabled]::part(base) {
327
336
  border-color: var(--button-disabled-border-color);
328
337
  }
338
+ &[variant="primary"]::part(base),
339
+ &[variant="default"]::part(base) {
340
+ --sl-color-neutral-0: var(--button-primary-text-color);
341
+ }
329
342
  }
330
343
 
331
344
  sl-dialog {
@@ -348,7 +361,6 @@ sl-dialog {
348
361
  }
349
362
 
350
363
  &::part(title) {
351
- --sl-font-size-large: var(--font-size_medium);
352
364
  color: var(--dialog-header-text-color);
353
365
  font-weight: var(--font-weight_bold);
354
366
  -webkit-font-smoothing: antialiased;
@@ -80,6 +80,14 @@
80
80
  margin-left: var(--spacing-1);
81
81
  }
82
82
 
83
+ #preview_time_select {
84
+ display: inline-flex;
85
+
86
+ select {
87
+ width: var(--select-x-large-width);
88
+ }
89
+ }
90
+
83
91
  #overlay_toolbar {
84
92
  @extend %gradiated-toolbar;
85
93
  }
@@ -29,6 +29,7 @@
29
29
  @use "admin/nodes";
30
30
  @use "admin/notices";
31
31
  @use "admin/pagination";
32
+ @use "admin/popover";
32
33
  @use "admin/preview_window";
33
34
  @use "admin/resource_info";
34
35
  @use "admin/search";
@@ -0,0 +1,4 @@
1
+ <div id="header">
2
+ <%= render "alchemy/admin/pages/locked_pages", locked_pages: locked_pages %>
3
+ <%= render "alchemy/admin/user_info" %>
4
+ </div>
@@ -0,0 +1,24 @@
1
+ <div id="left_menu">
2
+ <%= render "alchemy/admin/main_navi" %>
3
+
4
+ <div id="logout">
5
+ <div class="main_navi_entry">
6
+ <% if current_alchemy_user %>
7
+ <%= link_to_dialog(
8
+ %(
9
+ #{render_icon('logout-box-r', class: 'module')}
10
+ <label>#{Alchemy.t(:leave)}</label>
11
+ ).html_safe,
12
+ alchemy.leave_admin_path, {
13
+ size: "320x140",
14
+ title: Alchemy.t("Leave Alchemy")
15
+ }, {'data-alchemy-hotkey' => 'alt+q'}) %>
16
+ <% else %>
17
+ <%= link_to(alchemy.root_path) do %>
18
+ <%= render_icon "logout-box-r", size: "lg" %>
19
+ <label><%= Alchemy.t(:leave) %></label>
20
+ <% end %>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
@@ -0,0 +1,6 @@
1
+ <div id="main_navi">
2
+ <% sorted_alchemy_modules.each do |alchemy_module| %>
3
+ <%= alchemy_main_navigation_entry(alchemy_module) %>
4
+ <% end %>
5
+ <%= yield(:alchemy_main_navigation) %>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <div id="top_menu">
2
+ <%= render "alchemy/admin/header", locked_pages: locked_pages %>
3
+ <div id="toolbar">
4
+ <%= yield(:toolbar) %>
5
+ </div>
6
+ </div>
@@ -0,0 +1,5 @@
1
+ <div id="user_info">
2
+ <%= render Alchemy::Admin::CurrentUserName.new(user: current_alchemy_user) %>
3
+ <%= render Alchemy::Admin::TimezoneSelect.new %>
4
+ <%= render Alchemy::Admin::LocaleSelect.new %>
5
+ </div>
@@ -18,19 +18,14 @@
18
18
  <% end %>
19
19
  <% if @settings %>
20
20
  <script type="module">
21
- import ImageCropper from "alchemy_admin/image_cropper";
21
+ import { ImageCropper } from "alchemy_admin/image_cropper";
22
22
 
23
23
  const image = document.getElementById("imageToCrop")?.querySelector("img");
24
24
 
25
- new ImageCropper(
26
- image,
27
- <%= @settings[:default_box].to_json %>,
28
- <%= @settings[:ratio] %>,
29
- [
30
- "<%= params[:crop_from_form_field_id] %>",
31
- "<%= params[:crop_size_form_field_id] %>",
32
- ],
33
- <%= @element.id %>
34
- );
25
+ new ImageCropper(image, <%== @settings.merge(
26
+ crop_from_form_field_id: params[:crop_from_form_field_id],
27
+ crop_size_form_field_id: params[:crop_size_form_field_id],
28
+ element_id: @element.id
29
+ ).to_json %>);
35
30
  </script>
36
31
  <% end %>
@@ -1,4 +1,4 @@
1
- <div class="element-header<%= ' has-hint' if element.has_hint? %>">
1
+ <div class="element-header<%= ' has-hint' if element.has_hint? %>" id="element-header-<%= element.id %>">
2
2
  <span class="element-handle<%= ' draggable' if can?(:order, element) %>">
3
3
  <% if element.definition.blank? %>
4
4
  <%= hint_with_tooltip Alchemy.t(:element_definition_missing), icon_class: "element-icon" %>
@@ -18,11 +18,21 @@
18
18
  <%= sanitize(element.preview_text.presence || '&nbsp;') %>
19
19
  </span>
20
20
  </span>
21
- <span class="element-hidden-icon">
22
- <%= render_icon("cloud-off", size: "1x") %>
23
- <span class="element-hidden-label">
24
- <%= Alchemy.t(:element_hidden) %>
25
- </span>
21
+ <span class="element-status-icons">
22
+ <% unless element.public? || element.scheduled? %>
23
+ <%= render_icon("cloud-off", size: "1x") %>
24
+ <span class="element-hidden-label">
25
+ <%= Alchemy.t(:element_hidden) %>
26
+ </span>
27
+ <% end %>
28
+ <sl-tooltip
29
+ class="element-scheduled-icon"
30
+ content="<%= Alchemy.t(element.public_on&.future? ? :public_on : :public_until, scope: :element_scheduled, public_on: element.public_on && l(element.public_on, format: :"alchemy.default"), public_until: element.public_until && l(element.public_until, format: :"alchemy.default")) %>"
31
+ <%= "hidden" if !element.scheduled? %>
32
+ >
33
+ <%= render_icon("calendar-schedule", size: "1x") %>
34
+ <%= element.public_on&.future? ? element.public_on && l(element.public_on, format: :"alchemy.short_datetime") : element.public_until && l(element.public_until, format: :"alchemy.short_datetime") %>
35
+ </sl-tooltip>
26
36
  </span>
27
37
  <sl-tooltip content="<%= Alchemy.t(element.folded? ? :show_element_content : :hide_element_content) %>">
28
38
  <%= button_tag(class: "element-toggle") do %>
@@ -0,0 +1,62 @@
1
+ <div id="element-<%= element.id %>-schedule">
2
+ <% if element.errors.any? %>
3
+ <alchemy-message type="error" icon="warn">
4
+ <%= Alchemy.t(:default_message, scope: "forms.error_notification") %>
5
+ </alchemy-message>
6
+ <% else %>
7
+ <alchemy-message type="info">
8
+ <% if element.public? %>
9
+ <p><%= sanitize t(".visibility_status.public_html") %></p>
10
+ <% if element.scheduled? && element.public_until.present? %>
11
+ <%= sanitize t(".public_scheduled_html",
12
+ public_until: l(element.public_until, format: :"alchemy.element_date")) %>
13
+ <% else %>
14
+ <%= t(".public_no_schedule") %>
15
+ <% end %>
16
+ <% else %>
17
+ <p><%= sanitize t(".visibility_status.hidden_html") %></p>
18
+ <% if element.scheduled? %>
19
+ <% if element.public_until.present? %>
20
+ <%= sanitize t(".hidden_window_html",
21
+ public_on: l(element.public_on, format: :"alchemy.element_date"),
22
+ public_until: l(element.public_until, format: :"alchemy.element_date")) %>
23
+ <% else %>
24
+ <%= sanitize t(".hidden_scheduled_html",
25
+ public_on: l(element.public_on, format: :"alchemy.element_date")) %>
26
+ <% end %>
27
+ <% else %>
28
+ <%= t(".hidden_no_schedule") %>
29
+ <% end %>
30
+ <% end %>
31
+ </alchemy-message>
32
+ <% end %>
33
+
34
+ <%= alchemy_form_for [:publish, :admin, element], remote: false, data: {turbo: true} do |f| %>
35
+ <div class="input">
36
+ <div class="input-row">
37
+ <div class="input-column">
38
+ <label for="element_public_on"><%= Alchemy::Element.human_attribute_name(:public_on) %></label>
39
+ <%= f.datetime_local_field :public_on, include_seconds: false %>
40
+ <%= f.error :public_on %>
41
+ </div>
42
+ <div class="input-column">
43
+ <label for="element_public_until"><%= Alchemy::Element.human_attribute_name(:public_until) %></label>
44
+ <%= f.datetime_local_field :public_until, include_seconds: false %>
45
+ <%= f.error :public_until %>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ <div class="submit">
50
+ <button
51
+ type="button"
52
+ class="secondary"
53
+ onclick="this.form.querySelectorAll('input[type=datetime-local]').forEach((i) => i.value = '')">
54
+ <%= Alchemy.t(:clear_schedule) %>
55
+ </button>
56
+ <button type="submit">
57
+ <%= hidden_field_tag :alchemy_preview_time, params[:alchemy_preview_time] %>
58
+ <%= Alchemy.t(:save) %>
59
+ </button>
60
+ </div>
61
+ <% end %>
62
+ </div>
@@ -43,19 +43,5 @@
43
43
  </button>
44
44
  </alchemy-delete-element-button>
45
45
  </sl-tooltip>
46
- <sl-tooltip
47
- content="<%= element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element) %>"
48
- placement="top-end"
49
- <%= "disabled" if cannot?(:update, element) %>
50
- >
51
- <alchemy-publish-element-button>
52
- <sl-switch
53
- size="small"
54
- <%= "checked" if !element.public? %>
55
- <%= "disabled" if cannot?(:update, element) %>
56
- >
57
- <%= t(".hide") %>
58
- </sl-switch>
59
- </alchemy-publish-element-button>
60
- </sl-tooltip>
46
+ <%= render Alchemy::Admin::PublishElementButton.new(element:) %>
61
47
  </div>
@@ -0,0 +1,28 @@
1
+ <% if @element.errors.any? %>
2
+ <%= turbo_stream.replace "element-#{@element.id}-schedule",
3
+ partial: "schedule", locals: {element: @element} %>
4
+ <% else %>
5
+ <% if @element.scheduled? %>
6
+ <alchemy-growl>
7
+ <%= Alchemy.t(:successfully_scheduled_element) %>
8
+ </alchemy-growl>
9
+ <% end %>
10
+ <alchemy-action name="closeCurrentDialog"></alchemy-action>
11
+ <alchemy-action name="reloadPreview"></alchemy-action>
12
+ <%= turbo_stream.replace "element_#{@element.id}" do %>
13
+ <%= render Alchemy::Admin::ElementEditor.new(
14
+ element: @element
15
+ ) %>
16
+ <% end %>
17
+ <%= turbo_stream.replace "publish_page_button" do %>
18
+ <%= render Alchemy::Admin::PublishPageButton.new(
19
+ page: @element.page
20
+ ) %>
21
+ <% end %>
22
+ <%= turbo_stream.replace "preview_time_select" do %>
23
+ <%= render Alchemy::Admin::PreviewTimeSelect.new(
24
+ @element.page_version,
25
+ url: edit_admin_page_path(@element.page),
26
+ selected: params[:alchemy_preview_time]) %>
27
+ <% end %>
28
+ <% end %>
@@ -46,7 +46,7 @@
46
46
  </div>
47
47
 
48
48
  <script type="module">
49
- import NodeTree from "alchemy_admin/node_tree"
49
+ import { NodeTree } from "alchemy_admin/node_tree"
50
50
 
51
51
  NodeTree()
52
52
  </script>
@@ -0,0 +1,5 @@
1
+ <% if locked_pages.present? %>
2
+ <div id="locked_pages">
3
+ <%= render partial: 'alchemy/admin/pages/locked_page', collection: locked_pages %>
4
+ </div>
5
+ <% end %>
@@ -19,13 +19,13 @@
19
19
  'input-row'
20
20
  ] do %>
21
21
  <div class="input-column">
22
- <label><%= Alchemy::Page.human_attribute_name(:public_on) %></label>
23
- <%= alchemy_datepicker @page, :public_on, type: :datetime,
22
+ <%= label :page, :public_on %>
23
+ <%= datetime_local_field :page, :public_on, include_seconds: false,
24
24
  disabled: @page.attribute_fixed?(:public_on) %>
25
25
  </div>
26
26
  <div class="input-column">
27
- <label><%= Alchemy::Page.human_attribute_name(:public_until) %></label>
28
- <%= alchemy_datepicker @page, :public_until, type: :datetime,
27
+ <%= label :page, :public_until %>
28
+ <%= datetime_local_field :page, :public_until, include_seconds: false,
29
29
  disabled: @page.attribute_fixed?(:public_until) %>
30
30
  </div>
31
31
  <% end %>
@@ -70,7 +70,7 @@
70
70
  alchemy.configure_admin_page_path(page, view: "list"),
71
71
  {
72
72
  title: Alchemy.t(:edit_page_properties),
73
- size: '450x680'
73
+ size: '530x680'
74
74
  },
75
75
  class: "icon_button"
76
76
  ) -%>
@@ -49,7 +49,7 @@
49
49
  alchemy.configure_admin_page_path(@page),
50
50
  {
51
51
  title: Alchemy.t(:page_properties),
52
- size: '500x680'
52
+ size: '530x680'
53
53
  },
54
54
  class: :icon_button,
55
55
  'data-alchemy-hotkey' => 'alt+e'
@@ -91,6 +91,10 @@
91
91
  </sl-tooltip>
92
92
  </div>
93
93
  <div class="toolbar_spacer"></div>
94
+ <%= render Alchemy::Admin::PreviewTimeSelect.new(
95
+ @page_version,
96
+ url: edit_admin_page_path(@page),
97
+ selected: params[:alchemy_preview_time]) %>
94
98
  <% if @preview_urls.many? %>
95
99
  <div class="select_with_label">
96
100
  <label><%= Alchemy.t(:preview_url) %></label>
@@ -132,7 +136,7 @@
132
136
  title="<%= Alchemy.t("Page preview") %>">
133
137
  </iframe>
134
138
 
135
- <%= turbo_frame_tag "alchemy_elements_window", src: alchemy.admin_elements_path(page_version_id: @page_version.id) do %>
139
+ <%= turbo_frame_tag "alchemy_elements_window", src: alchemy.admin_elements_path(page_version_id: @page_version.id, alchemy_preview_time: params[:alchemy_preview_time]) do %>
136
140
  <alchemy-spinner></alchemy-spinner>
137
141
  <% end %>
138
142
 
@@ -3,16 +3,16 @@
3
3
  <div class="toolbar_button">
4
4
  <sl-tooltip content="<%= Alchemy.t("Language tree") %>">
5
5
  <%= form_tag switch_admin_languages_path, method: 'get' do %>
6
- <%= select_tag(
7
- 'language_id',
8
- options_for_select(
9
- languages.map { |l| [l.name, l.id] },
10
- Alchemy::Current.language.id
11
- ),
12
- class: 'short',
13
- is: 'alchemy-select',
14
- data: {'auto-submit' => true}
15
- ) %>
6
+ <alchemy-auto-submit>
7
+ <%= select_tag(
8
+ :language_id,
9
+ options_for_select(
10
+ languages.map { |l| [l.name, l.id] },
11
+ Alchemy::Current.language.id
12
+ ),
13
+ class: "short"
14
+ ) %>
15
+ </alchemy-auto-submit>
16
16
  <% end %>
17
17
  </sl-tooltip>
18
18
  </div>
@@ -1,9 +1,12 @@
1
1
  <%- if multi_site? -%>
2
2
  <div class="toolbar_button">
3
3
  <sl-tooltip content="<%= Alchemy.t("Current site") %>">
4
- <%= select_tag 'change_site',
5
- options_for_select(sites_for_select, Alchemy::Current.site.id),
6
- is: 'alchemy-select' %>
4
+ <%= form_tag url_for, method: :get do %>
5
+ <alchemy-auto-submit>
6
+ <%= select_tag :site_id,
7
+ options_for_select(sites_for_select, Alchemy::Current.site.id) %>
8
+ </alchemy-auto-submit>
9
+ <% end %>
7
10
  </sl-tooltip>
8
11
  </div>
9
12
  <div class="toolbar_spacer"></div>
@@ -72,8 +72,8 @@
72
72
 
73
73
  <% content_for :javascripts do %>
74
74
  <script type="module">
75
- import ImageOverlay from "alchemy_admin/image_overlay";
76
- import pictureSelector from "alchemy_admin/picture_selector";
75
+ import { ImageOverlay } from "alchemy_admin/image_overlay";
76
+ import { pictureSelector } from "alchemy_admin/picture_selector";
77
77
  import { on } from "alchemy_admin/utils/events";
78
78
 
79
79
  pictureSelector();
@@ -9,20 +9,13 @@
9
9
  <% end %>
10
10
 
11
11
  <script>
12
- // Setting TinyMCE path.
13
- var tinyMCEPreInit = {
14
- <% if ActionController::Base.config.asset_host %>
15
- base: '<%= asset_url(tinymce_base_path, host: ActionController::Base.config.asset_host) %>',
16
- <% else %>
17
- base: '<%= tinymce_base_path %>',
18
- <% end %>
19
- suffix: '.min'
20
- };
21
- // Holds the default Alchemy TinyMCE configuration
22
- Alchemy.TinymceDefaults = {
23
- plugins: '<%= Alchemy::Tinymce.plugins.join(',') %>',
24
- <% Alchemy::Tinymce.init.each do |k, v| %>
25
- <%= k %>: <%== v.to_json %>,
26
- <% end %>
27
- };
12
+ var tinyMCEPreInit = <%== {
13
+ base: ActionController::Base.config.asset_host ?
14
+ asset_url(tinymce_base_path, host: ActionController::Base.config.asset_host) :
15
+ tinymce_base_path,
16
+ suffix: ".min"
17
+ }.to_json %>;
18
+ Alchemy.TinymceDefaults = <%== Alchemy::Tinymce.init.merge(
19
+ plugins: Alchemy::Tinymce.plugins.join(",")
20
+ ).to_json %>;
28
21
  </script>
@@ -1,8 +1,3 @@
1
1
  <script>
2
- Alchemy.uploader_defaults = {
3
- file_size_limit: <%= Alchemy.config.uploader.file_size_limit -%>,
4
- upload_limit: <%= Alchemy.config.uploader.upload_limit -%>,
5
- allowed_filetype_pictures: "<%= Alchemy.config.uploader.allowed_filetypes.alchemy_pictures.join(", ") -%>",
6
- allowed_filetype_attachments: "<%= Alchemy.config.uploader.allowed_filetypes.alchemy_attachments.join(", ") -%>",
7
- }
2
+ Alchemy.uploader_defaults = <%== Alchemy.config.uploader.to_json %>
8
3
  </script>
@@ -1,8 +1,7 @@
1
1
  <%= link_to(
2
2
  content_tag(:span, language.label(options[:linkname])).html_safe,
3
3
  show_alchemy_page_path(
4
- language.pages.language_roots.first,
5
- locale: prefix_locale?(language.code) ? language.code : nil
4
+ language.pages.language_roots.first
6
5
  ),
7
6
  class: [
8
7
  language.code,
@@ -44,52 +44,9 @@
44
44
  <p><%= Alchemy.t(:javascript_disabled_text) %></p>
45
45
  </noscript>
46
46
  <alchemy-overlay text="<%= Alchemy.t(:please_wait) %>"></alchemy-overlay>
47
- <div id="left_menu">
48
- <div id="main_navi">
49
- <% sorted_alchemy_modules.each do |alchemy_module| %>
50
- <%= alchemy_main_navigation_entry(alchemy_module) %>
51
- <% end %>
52
- <%= yield(:alchemy_main_navigation) %>
53
- </div>
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: "320x140",
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 %>
72
- <% end %>
73
- </div>
74
- </div>
75
- </div>
47
+ <%= render "alchemy/admin/left_menu" %>
76
48
  <% if current_alchemy_user %>
77
- <div id="top_menu">
78
- <div id="header">
79
- <% if @locked_pages.present? %>
80
- <div id="locked_pages">
81
- <%= render partial: 'alchemy/admin/pages/locked_page', collection: @locked_pages %>
82
- </div>
83
- <% end %>
84
- <div id="user_info">
85
- <%= current_alchemy_user_name %>
86
- <%= render Alchemy::Admin::LocaleSelect.new %>
87
- </div>
88
- </div>
89
- <div id="toolbar">
90
- <%= yield(:toolbar) %>
91
- </div>
92
- </div>
49
+ <%= render "alchemy/admin/top_menu", locked_pages: @locked_pages %>
93
50
  <% end %>
94
51
  <%= render 'alchemy/admin/partials/flash_notices' %>
95
52
  <div id="main_content">
data/config/importmap.rb CHANGED
@@ -12,5 +12,10 @@ pin "shoelace", to: "shoelace.min.js", preload: true
12
12
  pin "@rails/ujs", to: "rails-ujs.min.js", preload: true # @7.1.2
13
13
  pin "tinymce", to: "tinymce.min.js", preload: true
14
14
 
15
- pin "alchemy_admin", to: "alchemy_admin.js", preload: true
16
- pin_all_from File.expand_path("../app/javascript/alchemy_admin", __dir__), under: "alchemy_admin", preload: true
15
+ pin "alchemy_admin", to: "alchemy/alchemy_admin.min.js", preload: true
16
+ pin "alchemy_admin/components/remote_select", to: "alchemy/alchemy_admin.min.js"
17
+ pin "alchemy_admin/image_cropper", to: "alchemy/alchemy_admin.min.js"
18
+ pin "alchemy_admin/image_overlay", to: "alchemy/alchemy_admin.min.js"
19
+ pin "alchemy_admin/picture_selector", to: "alchemy/alchemy_admin.min.js"
20
+ pin "alchemy_admin/node_tree", to: "alchemy/alchemy_admin.min.js"
21
+ pin "alchemy_admin/utils/events", to: "alchemy/alchemy_admin.min.js"