alchemy_cms 8.1.16 → 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 (133) 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 +1 -9
  5. data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -9
  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/resource/action.rb +6 -4
  21. data/app/components/alchemy/admin/resource/cell.rb +6 -4
  22. data/app/components/alchemy/admin/resource/table.rb +2 -14
  23. data/app/components/alchemy/admin/timezone_select.rb +47 -0
  24. data/app/components/alchemy/ingredients/base_view.rb +1 -1
  25. data/app/components/alchemy/ingredients/picture_view.rb +1 -1
  26. data/app/components/alchemy/ingredients/select_editor.rb +6 -1
  27. data/app/components/alchemy/ingredients/text_view.rb +2 -2
  28. data/app/controllers/alchemy/admin/attachments_controller.rb +0 -9
  29. data/app/controllers/alchemy/admin/base_controller.rb +1 -0
  30. data/app/controllers/alchemy/admin/elements_controller.rb +54 -34
  31. data/app/controllers/alchemy/admin/pages_controller.rb +5 -4
  32. data/app/controllers/alchemy/admin/pictures_controller.rb +2 -23
  33. data/app/controllers/alchemy/admin/resources_controller.rb +11 -6
  34. data/app/controllers/alchemy/api/pages_controller.rb +2 -8
  35. data/app/controllers/alchemy/pages_controller.rb +1 -2
  36. data/app/helpers/alchemy/admin/base_helper.rb +4 -7
  37. data/app/helpers/alchemy/url_helper.rb +2 -10
  38. data/app/javascript/alchemy_admin/components/element_editor/publish_element_button.js +28 -27
  39. data/app/javascript/alchemy_admin/components/element_editor.js +12 -6
  40. data/app/javascript/alchemy_admin/components/message.js +5 -1
  41. data/app/javascript/alchemy_admin/components/picture_thumbnail.js +1 -0
  42. data/app/javascript/alchemy_admin/components/remote_select.js +0 -11
  43. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +5 -5
  44. data/app/javascript/alchemy_admin/image_cropper.js +10 -6
  45. data/app/javascript/alchemy_admin/initializer.js +6 -33
  46. data/app/javascript/alchemy_admin/shoelace_theme.js +6 -2
  47. data/app/javascript/alchemy_admin/templates/compiled.js +1 -1
  48. data/app/javascript/alchemy_admin.js +12 -2
  49. data/app/models/alchemy/attachment.rb +1 -33
  50. data/app/models/alchemy/current.rb +5 -1
  51. data/app/models/alchemy/element/element_ingredients.rb +11 -3
  52. data/app/models/alchemy/element.rb +10 -0
  53. data/app/models/alchemy/ingredient.rb +2 -0
  54. data/app/models/alchemy/ingredients/select.rb +1 -2
  55. data/app/models/alchemy/page/etag_generator.rb +21 -0
  56. data/app/models/alchemy/page/url_path.rb +11 -2
  57. data/app/models/alchemy/page.rb +12 -2
  58. data/app/models/alchemy/page_version.rb +5 -5
  59. data/app/models/alchemy/picture.rb +19 -2
  60. data/app/models/alchemy/storage_adapter/active_storage.rb +9 -0
  61. data/app/models/alchemy/storage_adapter/dragonfly.rb +9 -0
  62. data/app/models/alchemy/storage_adapter.rb +1 -0
  63. data/app/models/concerns/alchemy/publishable.rb +23 -13
  64. data/app/models/concerns/alchemy/relatable_resource.rb +19 -15
  65. data/app/models/concerns/alchemy/touch_elements.rb +3 -3
  66. data/app/serializers/alchemy/page_tree_serializer.rb +0 -6
  67. data/app/services/alchemy/element_preloader.rb +107 -0
  68. data/app/services/alchemy/page_tree_preloader.rb +7 -9
  69. data/app/stylesheets/alchemy/_custom-properties.scss +1 -0
  70. data/app/stylesheets/alchemy/_mixins.scss +1 -1
  71. data/app/stylesheets/alchemy/_themes.scss +2 -0
  72. data/app/stylesheets/alchemy/admin/archive.scss +2 -2
  73. data/app/stylesheets/alchemy/admin/base.scss +2 -1
  74. data/app/stylesheets/alchemy/admin/elements.scss +22 -19
  75. data/app/stylesheets/alchemy/admin/form_fields.scss +3 -0
  76. data/app/stylesheets/alchemy/admin/forms.scss +14 -1
  77. data/app/stylesheets/alchemy/admin/frame.scss +9 -8
  78. data/app/stylesheets/alchemy/admin/images.scss +2 -2
  79. data/app/stylesheets/alchemy/admin/notices.scss +1 -10
  80. data/app/stylesheets/alchemy/admin/popover.scss +37 -0
  81. data/app/stylesheets/alchemy/admin/selects.scss +4 -0
  82. data/app/stylesheets/alchemy/admin/shoelace.scss +16 -4
  83. data/app/stylesheets/alchemy/admin/toolbar.scss +8 -0
  84. data/app/stylesheets/alchemy/admin.scss +1 -0
  85. data/app/views/alchemy/admin/_header.html.erb +4 -0
  86. data/app/views/alchemy/admin/_left_menu.html.erb +24 -0
  87. data/app/views/alchemy/admin/_main_navi.html.erb +6 -0
  88. data/app/views/alchemy/admin/_top_menu.html.erb +6 -0
  89. data/app/views/alchemy/admin/_user_info.html.erb +5 -0
  90. data/app/views/alchemy/admin/attachments/_files_list.html.erb +1 -1
  91. data/app/views/alchemy/admin/crop.html.erb +6 -11
  92. data/app/views/alchemy/admin/elements/_header.html.erb +16 -6
  93. data/app/views/alchemy/admin/elements/_schedule.html.erb +62 -0
  94. data/app/views/alchemy/admin/elements/_toolbar.html.erb +1 -15
  95. data/app/views/alchemy/admin/elements/publish.turbo_stream.erb +28 -0
  96. data/app/views/alchemy/admin/layoutpages/_layoutpage.html.erb +1 -1
  97. data/app/views/alchemy/admin/nodes/index.html.erb +1 -1
  98. data/app/views/alchemy/admin/pages/_locked_pages.html.erb +5 -0
  99. data/app/views/alchemy/admin/pages/_publication_fields.html.erb +4 -4
  100. data/app/views/alchemy/admin/pages/_table.html.erb +2 -2
  101. data/app/views/alchemy/admin/pages/edit.html.erb +6 -2
  102. data/app/views/alchemy/admin/partials/_language_tree_select.html.erb +10 -10
  103. data/app/views/alchemy/admin/partials/_site_select.html.erb +6 -3
  104. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +3 -3
  105. data/app/views/alchemy/admin/pictures/_picture.html.erb +1 -1
  106. data/app/views/alchemy/admin/pictures/_picture_to_assign.html.erb +1 -1
  107. data/app/views/alchemy/admin/pictures/index.html.erb +2 -2
  108. data/app/views/alchemy/admin/tinymce/_setup.html.erb +9 -16
  109. data/app/views/alchemy/admin/uploader/_setup.html.erb +1 -6
  110. data/app/views/alchemy/language_links/_language.html.erb +1 -2
  111. data/app/views/layouts/alchemy/admin.html.erb +2 -45
  112. data/config/importmap.rb +7 -2
  113. data/config/locales/alchemy.en.yml +35 -5
  114. data/db/migrate/20251106150010_convert_select_value_for_multiple.rb +5 -12
  115. data/lib/alchemy/admin/preview_time.rb +23 -0
  116. data/lib/alchemy/admin/preview_url.rb +13 -2
  117. data/lib/alchemy/admin/timezone.rb +56 -0
  118. data/lib/alchemy/cache_digests/template_tracker.rb +2 -0
  119. data/lib/alchemy/configuration/collection_option.rb +1 -1
  120. data/lib/alchemy/configurations/main.rb +13 -1
  121. data/lib/alchemy/test_support/factories/element_factory.rb +2 -2
  122. data/lib/alchemy/test_support/relatable_resource_examples.rb +2 -2
  123. data/lib/alchemy/test_support/shared_publishable_examples.rb +44 -12
  124. data/lib/alchemy/upgrader.rb +3 -1
  125. data/lib/alchemy/version.rb +1 -1
  126. data/lib/alchemy_cms.rb +2 -0
  127. data/lib/generators/alchemy/install/install_generator.rb +2 -1
  128. data/vendor/javascript/handlebars.min.js +4 -4
  129. data/vendor/javascript/shoelace.min.js +1419 -1323
  130. data/vendor/javascript/sortable.min.js +2 -2
  131. data/vendor/javascript/tinymce.min.js +1 -1
  132. metadata +34 -3
  133. data/db/migrate/20260702090038_add_index_to_elements_pages_join_table.rb +0 -14
@@ -7,24 +7,24 @@ module Alchemy
7
7
  # It handles folded pages and preloads all necessary associations.
8
8
  #
9
9
  # @example Preload subtree from a specific page
10
- # preloader = Alchemy::PageTreePreloader.new(page: page, ability: current_ability, user: current_user)
10
+ # preloader = Alchemy::PageTreePreloader.new(page: page, user: current_user)
11
11
  # page_with_descendants = preloader.call
12
12
  #
13
13
  class PageTreePreloader
14
14
  # @param page [Page] Starting page for loading descendants
15
- # @param ability [CanCan::Ability] Ability used to scope descendants to readable pages
16
15
  # @param user [User, nil] User for folding support
17
- def initialize(page:, ability:, user: nil)
16
+ # @param admin_includes [Boolean] Whether to include admin-only associations like :locker
17
+ def initialize(page:, user: nil, admin_includes: false)
18
18
  @page = page
19
- @ability = ability
20
19
  @user = user
20
+ @admin_includes = admin_includes
21
21
  end
22
22
 
23
23
  # Preloads and returns the page tree
24
24
  #
25
25
  # @return [Array<Page>] Pages with preloaded children, or array with single page when using from:
26
26
  def call
27
- pages = page.self_and_descendants.accessible_by(ability, :read)
27
+ pages = page.self_and_descendants
28
28
  folded_page_ids = load_folded_page_ids
29
29
  if folded_page_ids.any?
30
30
  pages = pages.where(
@@ -45,7 +45,7 @@ module Alchemy
45
45
 
46
46
  private
47
47
 
48
- attr_reader :page, :user, :ability
48
+ attr_reader :page, :user, :admin_includes
49
49
 
50
50
  # Load folded page IDs for the user
51
51
  def load_folded_page_ids
@@ -93,9 +93,7 @@ module Alchemy
93
93
  },
94
94
  :public_version
95
95
  ]
96
- # The admin sitemap (and the serializer's admin fields) render lock info,
97
- # so only eager-load the locker when the user may administer pages.
98
- associations.push(:locker) if ability.can?(:index, :alchemy_admin_pages)
96
+ associations.push(:locker) if admin_includes
99
97
  associations
100
98
  end
101
99
  end
@@ -155,6 +155,7 @@
155
155
  /* Select */
156
156
  --select-medium-width: 90px;
157
157
  --select-large-width: 120px;
158
+ --select-x-large-width: 180px;
158
159
 
159
160
  /* Sidebar */
160
161
  --sidebar-width: 232px;
@@ -6,7 +6,7 @@
6
6
  border-color: $border-color;
7
7
  border-radius: $border-radius;
8
8
  box-shadow: $box-shadow;
9
- outline: none;
9
+ outline: 0 none;
10
10
 
11
11
  &::-moz-focus-inner {
12
12
  border: none !important;
@@ -24,6 +24,7 @@
24
24
  --button-secondary-hover-bg-color: var(--a-dark-grey);
25
25
  --button-secondary-hover-border-color: var(--a-grey);
26
26
  --button-secondary-text-color: var(--color-white);
27
+ --button-primary-text-color: var(--a-darker-grey);
27
28
 
28
29
  --code-background-color: var(--a-darkest-grey);
29
30
  --code-border-color: var(--a-grey);
@@ -309,6 +310,7 @@
309
310
  --button-secondary-hover-bg-color: var(--color-white);
310
311
  --button-secondary-hover-border-color: var(--color-grey_medium);
311
312
  --button-secondary-text-color: var(--text-color);
313
+ --button-primary-text-color: var(--color-white);
312
314
 
313
315
  --code-background-color: var(--color-grey_light);
314
316
  --code-border-color: var(--border-color);
@@ -75,8 +75,8 @@ alchemy-uploader {
75
75
  }
76
76
 
77
77
  img {
78
- max-width: var(--picture-width);
79
- max-height: var(--picture-height);
78
+ max-width: 100%;
79
+ max-height: 100%;
80
80
 
81
81
  &:not([src*="alchemy/missing-image"]) {
82
82
  background: var(--thumbnail-background);
@@ -177,7 +177,8 @@ a img {
177
177
  width: 100%;
178
178
  }
179
179
 
180
- .hidden {
180
+ .hidden,
181
+ [hidden] {
181
182
  display: none !important;
182
183
  }
183
184
 
@@ -237,21 +237,17 @@ button.element-toggle {
237
237
  .element-toggle {
238
238
  margin-left: 0;
239
239
  }
240
-
241
- .element-hidden-icon {
242
- display: inline-flex;
243
- align-items: center;
244
- gap: var(--spacing-1);
245
- margin-left: auto;
246
- }
247
-
248
- .element-hidden-label {
249
- line-height: 1;
250
- font-size: var(--font-size_small);
251
- }
252
240
  }
253
241
  }
254
242
 
243
+ .element-status-icons {
244
+ display: inline-flex;
245
+ align-items: center;
246
+ gap: var(--spacing-1);
247
+ margin-left: auto;
248
+ font-size: var(--font-size_small);
249
+ }
250
+
255
251
  &.is-fixed {
256
252
  border-width: 0;
257
253
  border-radius: 0;
@@ -355,10 +351,6 @@ button.element-toggle {
355
351
  }
356
352
 
357
353
  &.compact {
358
- .element-hidden-label {
359
- display: none;
360
- }
361
-
362
354
  .element-toolbar {
363
355
  visibility: hidden;
364
356
  position: absolute;
@@ -437,6 +429,16 @@ button.element-toggle {
437
429
  transform 1s ease-in;
438
430
  }
439
431
 
432
+ .alchemy-popover {
433
+ width: 360px;
434
+ padding-bottom: var(--spacing-0);
435
+
436
+ label {
437
+ margin-bottom: var(--spacing-1);
438
+ font-size: var(--font-size_small);
439
+ }
440
+ }
441
+
440
442
  .element-body {
441
443
  margin: var(--spacing-2);
442
444
  }
@@ -449,7 +451,7 @@ button.element-toggle {
449
451
  text-align: left;
450
452
  }
451
453
 
452
- alchemy-message {
454
+ > alchemy-message {
453
455
  margin: var(--spacing-2);
454
456
  }
455
457
 
@@ -538,7 +540,7 @@ button.element-toggle {
538
540
  }
539
541
  }
540
542
 
541
- .element-hidden-icon {
543
+ .element-status-icons {
542
544
  display: none;
543
545
  white-space: nowrap;
544
546
  flex-shrink: 0;
@@ -1075,7 +1077,8 @@ select.long {
1075
1077
  .picture_thumbnail,
1076
1078
  .select2-container,
1077
1079
  .tinymce_container {
1078
- outline: 1px solid var(--element-dirty-border-color);
1080
+ box-shadow: 0 0 0 1px var(--element-dirty-border-color);
1081
+ border-color: var(--element-dirty-border-color);
1079
1082
  }
1080
1083
  }
1081
1084
 
@@ -8,6 +8,9 @@ input[type="text"],
8
8
  input[type="email"],
9
9
  input[type="password"],
10
10
  input[type="search"],
11
+ input[type="date"],
12
+ input[type="time"],
13
+ input[type="datetime-local"],
11
14
  .thin_border,
12
15
  .input_field {
13
16
  @extend %default-input-style;
@@ -260,11 +260,24 @@ form {
260
260
  }
261
261
 
262
262
  .submit {
263
+ display: flex;
263
264
  padding: var(--spacing-1) 0;
264
- text-align: right;
265
+ justify-content: space-between;
266
+
267
+ > button:last-of-type {
268
+ margin-left: auto;
269
+ }
270
+ }
271
+
272
+ &.simple_form {
273
+ .buttons {
274
+ margin-top: var(--spacing-1);
275
+ margin-left: var(--form-left-column-width);
276
+ }
265
277
  }
266
278
 
267
279
  .input-column {
280
+ width: 50%;
268
281
  padding: 0 var(--spacing-1);
269
282
 
270
283
  input[type] {
@@ -112,22 +112,23 @@ div#overlay_text_box {
112
112
  border-left: var(--border-default);
113
113
  white-space: nowrap;
114
114
  background-color: var(--toolbar-bg-color);
115
-
116
- select {
117
- background-color: transparent;
118
- border: none;
119
- border-radius: 0;
120
- border-left: var(--border-default);
121
- }
115
+ gap: var(--spacing-1);
122
116
 
123
117
  .current-user-name {
124
118
  display: flex;
125
119
  align-items: center;
126
120
  gap: var(--spacing-1);
127
- padding: 0 var(--spacing-3);
121
+ padding-left: var(--spacing-3);
128
122
 
129
123
  @media screen and (max-width: vars.$large-screen-break-point) {
130
124
  display: none;
131
125
  }
132
126
  }
133
127
  }
128
+
129
+ #admin_locale {
130
+ background-color: transparent;
131
+ border: none;
132
+ border-radius: 0;
133
+ border-left: var(--border-default);
134
+ }
@@ -1,11 +1,11 @@
1
1
  alchemy-picture-thumbnail {
2
2
  &[loading] {
3
- > img {
3
+ img {
4
4
  opacity: 0;
5
5
  }
6
6
  }
7
7
 
8
- > img {
8
+ img {
9
9
  opacity: 1;
10
10
  transition: opacity var(--transition-duration);
11
11
  }
@@ -11,7 +11,7 @@ alchemy-message {
11
11
  word-break: break-word;
12
12
  line-height: 1.5;
13
13
 
14
- turbo-frame & {
14
+ turbo-frame > & {
15
15
  margin: var(--spacing-2);
16
16
  }
17
17
 
@@ -73,15 +73,6 @@ alchemy-message {
73
73
  margin-top: 0;
74
74
  }
75
75
 
76
- h1,
77
- h2,
78
- h3,
79
- p {
80
- &:last-child {
81
- margin-bottom: 0;
82
- }
83
- }
84
-
85
76
  a[href] {
86
77
  text-decoration-color: inherit;
87
78
  text-decoration-thickness: 1px;
@@ -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>
@@ -61,7 +61,7 @@
61
61
  file_attribute: 'file' %>
62
62
  <% end %>
63
63
  <% table.with_action(:destroy) do |attachment| %>
64
- <% if @deletable_attachment_ids.include?(attachment.id) %>
64
+ <% if attachment.deletable? %>
65
65
  <sl-tooltip content="<%= Alchemy.t(:delete_file) %>">
66
66
  <%= link_to_confirm_dialog render_icon(:minus),
67
67
  Alchemy.t(:confirm_to_delete_file),
@@ -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 %>