alchemy_cms 8.2.7 → 8.3.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.
- 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/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/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 +13 -1
- data/app/stylesheets/alchemy/admin/_tom-select.scss +240 -0
- data/app/stylesheets/alchemy/admin/archive.scss +0 -1
- data/app/stylesheets/alchemy/admin/base.scss +0 -19
- data/app/stylesheets/alchemy/admin/dashboard.scss +395 -28
- data/app/stylesheets/alchemy/admin/elements.scss +14 -17
- data/app/stylesheets/alchemy/admin/form_fields.scss +3 -3
- data/app/stylesheets/alchemy/admin/forms.scss +107 -93
- data/app/stylesheets/alchemy/admin/icons.scss +28 -0
- data/app/stylesheets/alchemy/admin/image_library.scss +20 -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 +5 -0
- data/app/stylesheets/alchemy/admin/tables.scss +32 -3
- 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/nodes/_page_nodes.html.erb +10 -8
- data/app/views/alchemy/admin/pages/_form.html.erb +25 -19
- 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/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 +15 -0
- 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 +57 -18
- 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/sites/edit.html.erb +0 -1
- data/app/views/alchemy/admin/sites/new.html.erb +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
module Admin
|
|
3
|
+
module Dashboard
|
|
4
|
+
module Widgets
|
|
5
|
+
class UserCounts < StatWidget
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def link = Alchemy.config.admin_users_path
|
|
9
|
+
def icon = "group"
|
|
10
|
+
def title = Alchemy.config.user_class.model_name.human(count: :many)
|
|
11
|
+
def count = Alchemy.config.user_class.count
|
|
12
|
+
|
|
13
|
+
def infos
|
|
14
|
+
if Alchemy.config.user_class.respond_to?(:logged_in)
|
|
15
|
+
safe_join([
|
|
16
|
+
number_with_delimiter(Alchemy.config.user_class.logged_in.length),
|
|
17
|
+
t(".online")
|
|
18
|
+
], " ")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
data-element-name="<%= name %>"
|
|
5
5
|
filter-text="<%= filter_text %>"
|
|
6
6
|
class="<%= css_classes.join(" ") %>"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
<% if compact? %>
|
|
8
|
+
compact
|
|
9
|
+
<% end %>
|
|
10
|
+
<% if created %>
|
|
11
|
+
created
|
|
12
|
+
<% end %>
|
|
13
|
+
<% if fixed? %>
|
|
14
|
+
fixed
|
|
15
|
+
<% end %>
|
|
10
16
|
>
|
|
11
17
|
<% unless fixed? %>
|
|
12
18
|
<%= render 'alchemy/admin/elements/header', element: element %>
|
|
@@ -38,28 +44,29 @@
|
|
|
38
44
|
ungrouped_ingredients,
|
|
39
45
|
element_form: f
|
|
40
46
|
) %>
|
|
47
|
+
</div>
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
) %>
|
|
53
|
-
<% end %>
|
|
49
|
+
<% grouped_ingredients.each do |group, ingredients| %>
|
|
50
|
+
<%= content_tag :details, class: "ingredient-group", id: "element_#{id}_ingredient_group_#{group.parameterize.underscore}", is: "alchemy-ingredient-group" do %>
|
|
51
|
+
<summary>
|
|
52
|
+
<%= render_icon "arrow-right-s" %>
|
|
53
|
+
<%= translated_group group %>
|
|
54
|
+
</summary>
|
|
55
|
+
<%= render Alchemy::Admin::IngredientEditor.with_collection(
|
|
56
|
+
ingredients,
|
|
57
|
+
element_form: f
|
|
58
|
+
) %>
|
|
54
59
|
<% end %>
|
|
55
|
-
|
|
60
|
+
<% end %>
|
|
56
61
|
<% end %>
|
|
57
62
|
|
|
58
63
|
<% if taggable? %>
|
|
59
|
-
|
|
60
|
-
<%=
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
<div class="element-tags">
|
|
65
|
+
<%= render Alchemy::Admin::TagsAutocomplete.new do %>
|
|
66
|
+
<%= f.label :tag_list %>
|
|
67
|
+
<%= f.text_field :tag_list, value: f.object.tag_list.join(","), disabled: cannot?(:edit, element) %>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
63
70
|
<% end %>
|
|
64
71
|
<% end %>
|
|
65
72
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
module Admin
|
|
3
|
+
class ElementScheduleTimestamps < ViewComponent::Base
|
|
4
|
+
erb_template <<~HTML
|
|
5
|
+
<div class="input-row">
|
|
6
|
+
<div class="input-column">
|
|
7
|
+
<label for="element_public_on" class="control-label"><%= Alchemy::Element.human_attribute_name(:public_on) %></label>
|
|
8
|
+
<%= datetime_local_field :element, :public_on, include_seconds: false %>
|
|
9
|
+
<%= error_for(:public_on) %>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="input-column">
|
|
12
|
+
<label for="element_public_until" class="control-label"><%= Alchemy::Element.human_attribute_name(:public_until) %></label>
|
|
13
|
+
<%= datetime_local_field :element, :public_until, include_seconds: false %>
|
|
14
|
+
<%= error_for(:public_until) %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
HTML
|
|
18
|
+
|
|
19
|
+
def initialize(element:)
|
|
20
|
+
@element = element
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def error_for(attribute)
|
|
26
|
+
errors = @element.errors[attribute]
|
|
27
|
+
return unless errors.present?
|
|
28
|
+
|
|
29
|
+
tag.span(errors.to_sentence, class: "error")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -3,11 +3,12 @@ module Alchemy
|
|
|
3
3
|
class ElementSelect < ViewComponent::Base
|
|
4
4
|
delegate :alchemy, to: :helpers
|
|
5
5
|
|
|
6
|
-
attr_reader :elements, :field_name
|
|
6
|
+
attr_reader :elements, :field_name, :autofocus
|
|
7
7
|
|
|
8
|
-
def initialize(elements, field_name: "element[name]")
|
|
8
|
+
def initialize(elements, field_name: "element[name]", autofocus: false)
|
|
9
9
|
@field_name = field_name
|
|
10
10
|
@elements = elements
|
|
11
|
+
@autofocus = autofocus
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def call
|
|
@@ -15,7 +16,7 @@ module Alchemy
|
|
|
15
16
|
options: elements_options.to_json,
|
|
16
17
|
placeholder: Alchemy.t(:select_element) do
|
|
17
18
|
text_field_tag(field_name, nil, {
|
|
18
|
-
autofocus
|
|
19
|
+
autofocus:,
|
|
19
20
|
required: true,
|
|
20
21
|
value: elements.many? ? nil : elements.first&.name,
|
|
21
22
|
class: "alchemy_selectbox full_width"
|
|
@@ -65,26 +65,7 @@
|
|
|
65
65
|
</div>
|
|
66
66
|
|
|
67
67
|
<div class="page_infos">
|
|
68
|
-
|
|
69
|
-
<span class="page_status locked">
|
|
70
|
-
<alchemy-icon name="edit" size="1x"></alchemy-icon>
|
|
71
|
-
<%= @page.status_title(:locked) %>
|
|
72
|
-
</span>
|
|
73
|
-
<% end %>
|
|
74
|
-
|
|
75
|
-
<% if @page.restricted? %>
|
|
76
|
-
<span class="page_status">
|
|
77
|
-
<alchemy-icon name="lock" size="1x"></alchemy-icon>
|
|
78
|
-
<%= @page.status_title(:restricted) %>
|
|
79
|
-
</span>
|
|
80
|
-
<% end %>
|
|
81
|
-
|
|
82
|
-
<% unless @page.public? %>
|
|
83
|
-
<span class="page_status">
|
|
84
|
-
<alchemy-icon name="cloud-off" size="1x"></alchemy-icon>
|
|
85
|
-
<%= @page.status_title(:public) %>
|
|
86
|
-
</span>
|
|
87
|
-
<% end %>
|
|
68
|
+
<%= render Alchemy::Admin::PageStatusIndicators.new(page: @page) %>
|
|
88
69
|
</div>
|
|
89
70
|
|
|
90
71
|
<div class="sitemap_right_tools">
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<alchemy-page-publication-fields>
|
|
2
|
+
<label class="checkbox">
|
|
3
|
+
<% if @page.attribute_fixed?(:public_on) || @page.attribute_fixed?(:public_until) %>
|
|
4
|
+
<sl-tooltip class="like-hint-tooltip" content="<%= Alchemy.t(:attribute_fixed) %>" placement="bottom-start">
|
|
5
|
+
<%= checkbox %>
|
|
6
|
+
<%= Alchemy::Page.human_attribute_name :public %>
|
|
7
|
+
</sl-tooltip>
|
|
8
|
+
<% else %>
|
|
9
|
+
<%= checkbox %>
|
|
10
|
+
<%= Alchemy::Page.human_attribute_name :public %>
|
|
11
|
+
<% end %>
|
|
12
|
+
</label>
|
|
13
|
+
|
|
14
|
+
<%= content_tag :div, class: [
|
|
15
|
+
@page.public_on.present? || @page.public_until.present? ? nil : 'hidden',
|
|
16
|
+
'page-publication-date-fields',
|
|
17
|
+
'input-row'
|
|
18
|
+
] do %>
|
|
19
|
+
<div class="input-column">
|
|
20
|
+
<%= label :page, :public_on %>
|
|
21
|
+
<%= datetime_local_field :page, :public_on, include_seconds: false,
|
|
22
|
+
disabled: @page.attribute_fixed?(:public_on) %>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="input-column">
|
|
25
|
+
<%= label :page, :public_until %>
|
|
26
|
+
<%= datetime_local_field :page, :public_until, include_seconds: false,
|
|
27
|
+
disabled: @page.attribute_fixed?(:public_until) %>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
</alchemy-page-publication-fields>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
module Admin
|
|
3
|
+
class PagePublicationFields < ViewComponent::Base
|
|
4
|
+
def initialize(page:)
|
|
5
|
+
@page = page
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def checkbox
|
|
11
|
+
check_box_tag :page_public, nil,
|
|
12
|
+
@page.public? || @page.scheduled?,
|
|
13
|
+
name: nil,
|
|
14
|
+
disabled: @page.attribute_fixed?(:public_on)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<% if @page.locked? %>
|
|
2
|
+
<span class="page_status locked">
|
|
3
|
+
<alchemy-icon name="edit" size="1x"></alchemy-icon>
|
|
4
|
+
<%= @page.status_title(:locked) %>
|
|
5
|
+
</span>
|
|
6
|
+
<% end %>
|
|
7
|
+
|
|
8
|
+
<% if @page.restricted? %>
|
|
9
|
+
<span class="page_status">
|
|
10
|
+
<alchemy-icon name="lock" size="1x"></alchemy-icon>
|
|
11
|
+
<%= @page.status_title(:restricted) %>
|
|
12
|
+
</span>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<% if !@page.public? && !@page.scheduled? %>
|
|
16
|
+
<span class="page_status">
|
|
17
|
+
<alchemy-icon name="cloud-off" size="1x"></alchemy-icon>
|
|
18
|
+
<%= @page.status_title(:public) %>
|
|
19
|
+
</span>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<% if @page.scheduled? %>
|
|
23
|
+
<span class="page_status">
|
|
24
|
+
<sl-tooltip content="<%= @page.status_message(:scheduled) %>">
|
|
25
|
+
<alchemy-icon name="calendar-schedule" size="1x"></alchemy-icon>
|
|
26
|
+
<%= @page.status_title(:scheduled) %>
|
|
27
|
+
</sl-tooltip>
|
|
28
|
+
</span>
|
|
29
|
+
<% end %>
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
<sl-button-group label="Example Button Group">
|
|
3
3
|
<sl-tooltip
|
|
4
4
|
content="<%= element.public? ? Alchemy.t(:hide_element) : Alchemy.t(:show_element) %>"
|
|
5
|
-
|
|
5
|
+
<% if element.scheduled? || cannot?(:update, element) %>
|
|
6
|
+
disabled
|
|
7
|
+
<% end %>
|
|
6
8
|
>
|
|
7
9
|
<%= form_tag(alchemy.publish_admin_element_path(element), method: "patch") do %>
|
|
8
10
|
<sl-button
|
|
9
11
|
variant="<%= element.public? ? "default" : "primary" %>"
|
|
10
12
|
type="submit"
|
|
11
13
|
size="small"
|
|
12
|
-
|
|
14
|
+
<% if element.scheduled? || cannot?(:update, element) %>
|
|
15
|
+
disabled
|
|
16
|
+
<% end %>
|
|
13
17
|
outline
|
|
14
18
|
pill
|
|
15
19
|
>
|
|
@@ -19,14 +23,18 @@
|
|
|
19
23
|
</sl-tooltip>
|
|
20
24
|
<sl-tooltip
|
|
21
25
|
content="<%= element.scheduled? ? Alchemy.t(:edit_element_schedule) : Alchemy.t(:schedule_element) %>"
|
|
22
|
-
|
|
26
|
+
<% if cannot?(:update, element) %>
|
|
27
|
+
disabled
|
|
28
|
+
<% end %>
|
|
23
29
|
>
|
|
24
30
|
<sl-dropdown distance="5">
|
|
25
31
|
<sl-button
|
|
26
32
|
slot="trigger"
|
|
27
33
|
variant="<%= element.scheduled? ? "primary" : "default" %>"
|
|
28
34
|
size="small"
|
|
29
|
-
|
|
35
|
+
<% if cannot?(:update, element) %>
|
|
36
|
+
disabled
|
|
37
|
+
<% end %>
|
|
30
38
|
outline
|
|
31
39
|
pill
|
|
32
40
|
>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Alchemy
|
|
2
|
+
module Admin
|
|
3
|
+
class Dashboard::WidgetsController < DashboardController
|
|
4
|
+
MODULE_NAMESPACE = "Alchemy::Admin::Dashboard::Widgets"
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
@id = params[:id]
|
|
8
|
+
@widget = get_widget(@id)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def get_widget(id)
|
|
14
|
+
"#{MODULE_NAMESPACE}::#{id.camelcase}".constantize
|
|
15
|
+
rescue NameError => e
|
|
16
|
+
Alchemy::Logger.error "No dashboard widget found for id: #{id}"
|
|
17
|
+
raise ActionController::RoutingError, e.message
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "net/http"
|
|
4
3
|
require "alchemy/version"
|
|
5
4
|
|
|
6
5
|
module Alchemy
|
|
@@ -9,20 +8,12 @@ module Alchemy
|
|
|
9
8
|
authorize_resource class: :alchemy_admin_dashboard
|
|
10
9
|
|
|
11
10
|
def index
|
|
12
|
-
@last_edited_pages = Page.all_last_edited_from(current_alchemy_user)
|
|
13
|
-
@all_locked_pages = Page.locked
|
|
14
|
-
if Alchemy.config.user_class.respond_to?(:logged_in)
|
|
15
|
-
@online_users = Alchemy.config.user_class.logged_in.to_a - [current_alchemy_user]
|
|
16
|
-
end
|
|
17
|
-
if current_alchemy_user.respond_to?(:sign_in_count) && current_alchemy_user.respond_to?(:last_sign_in_at)
|
|
18
|
-
@last_sign_at = current_alchemy_user.last_sign_in_at
|
|
19
|
-
@first_time = current_alchemy_user.sign_in_count == 1 && @last_sign_at.nil?
|
|
20
|
-
end
|
|
21
|
-
@sites = Site.all
|
|
22
11
|
end
|
|
23
12
|
|
|
24
13
|
def info
|
|
25
|
-
|
|
14
|
+
Alchemy::Deprecation.warn <<~WARN
|
|
15
|
+
Requesting `admin/dashboard/info` is deprecated. Please render Alchemy::Admin::Dashboard::Widgets::SystemInfo instead.
|
|
16
|
+
WARN
|
|
26
17
|
end
|
|
27
18
|
end
|
|
28
19
|
end
|
|
@@ -126,10 +126,11 @@ module Alchemy
|
|
|
126
126
|
def load_page
|
|
127
127
|
page_not_found! unless Current.language
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
result = PageFinder.new(params[:urlname]).call
|
|
130
|
+
if result
|
|
131
|
+
@page ||= result.page
|
|
132
|
+
params.merge!(result.extracted_params)
|
|
133
|
+
end
|
|
133
134
|
Current.page = @page
|
|
134
135
|
end
|
|
135
136
|
|
|
@@ -4,16 +4,22 @@ class AutoSubmit extends HTMLElement {
|
|
|
4
4
|
connectedCallback() {
|
|
5
5
|
// Still using jQuery here, because select2 does not emit
|
|
6
6
|
// the event from the original select element.
|
|
7
|
-
$(this).on("change",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
$(this).on("change", this.#onChange)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
disconnectedCallback() {
|
|
11
|
+
$(this).off("change", this.#onChange)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#onChange = (event) => {
|
|
15
|
+
// We need to dispatch a submit event, so that Turbo that listens
|
|
16
|
+
// to it submits the search form us.
|
|
17
|
+
const submitEvent = new Event("submit", {
|
|
18
|
+
bubbles: true,
|
|
19
|
+
cancelable: true
|
|
16
20
|
})
|
|
21
|
+
event.target.form.dispatchEvent(submitEvent)
|
|
22
|
+
return false
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
25
|
|
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Show the character counter below input fields and textareas
|
|
3
3
|
*/
|
|
4
|
-
import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element"
|
|
5
4
|
import { translate } from "alchemy_admin/i18n"
|
|
6
5
|
|
|
7
|
-
class CharCounter extends
|
|
8
|
-
|
|
9
|
-
maxChars: { default: 60 }
|
|
10
|
-
}
|
|
11
|
-
connected() {
|
|
6
|
+
class CharCounter extends HTMLElement {
|
|
7
|
+
connectedCallback() {
|
|
12
8
|
this.translation = translate("allowed_chars", this.maxChars)
|
|
13
9
|
this.formField = this.getFormField()
|
|
14
10
|
|
|
15
11
|
if (this.formField) {
|
|
16
12
|
this.createDisplayElement()
|
|
17
13
|
this.countCharacters()
|
|
18
|
-
this.formField.addEventListener("keyup",
|
|
14
|
+
this.formField.addEventListener("keyup", this)
|
|
19
15
|
}
|
|
20
16
|
}
|
|
21
17
|
|
|
18
|
+
disconnectedCallback() {
|
|
19
|
+
this.formField?.removeEventListener("keyup", this)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
handleEvent(event) {
|
|
23
|
+
if (event.type === "keyup") this.countCharacters()
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
getFormField() {
|
|
23
27
|
const formFields = this.querySelectorAll("input, textarea")
|
|
24
28
|
return formFields.length > 0 ? formFields[0] : undefined
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
createDisplayElement() {
|
|
32
|
+
this.display = this.querySelector(":scope > .alchemy-char-counter")
|
|
33
|
+
if (this.display) return
|
|
28
34
|
this.display = document.createElement("small")
|
|
29
35
|
this.display.className = "alchemy-char-counter"
|
|
30
36
|
this.formField.after(this.display)
|
|
@@ -35,6 +41,10 @@ class CharCounter extends AlchemyHTMLElement {
|
|
|
35
41
|
this.display.textContent = `${charLength} ${this.translation}`
|
|
36
42
|
this.display.classList.toggle("too-long", charLength > this.maxChars)
|
|
37
43
|
}
|
|
44
|
+
|
|
45
|
+
get maxChars() {
|
|
46
|
+
return this.getAttribute("max-chars") ?? 60
|
|
47
|
+
}
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
customElements.define("alchemy-char-counter", CharCounter)
|
|
@@ -2,12 +2,8 @@ import "clipboard"
|
|
|
2
2
|
import { growl } from "alchemy_admin/growler"
|
|
3
3
|
|
|
4
4
|
class ClipboardButton extends HTMLElement {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.innerHTML = `
|
|
9
|
-
<alchemy-icon name="clipboard"></alchemy-icon>
|
|
10
|
-
`
|
|
5
|
+
connectedCallback() {
|
|
6
|
+
this.innerHTML = '<alchemy-icon name="clipboard"></alchemy-icon>'
|
|
11
7
|
|
|
12
8
|
this.clipboard = new ClipboardJS(this, {
|
|
13
9
|
text: () => {
|
|
@@ -14,12 +14,12 @@ const formatItem = (object) => {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
class ColorSelect extends HTMLElement {
|
|
17
|
+
#select2 = null
|
|
18
|
+
|
|
17
19
|
connectedCallback() {
|
|
18
20
|
if (this.select) {
|
|
19
21
|
this.#initializeSelect2()
|
|
20
|
-
|
|
21
|
-
this.#toggleColorPicker(event.val === "custom_color")
|
|
22
|
-
)
|
|
22
|
+
this.#select2.on("change", this.#onSelectChange)
|
|
23
23
|
} else {
|
|
24
24
|
this.colorInput?.addEventListener("input", this)
|
|
25
25
|
this.textInput?.addEventListener("input", this)
|
|
@@ -41,6 +41,15 @@ class ColorSelect extends HTMLElement {
|
|
|
41
41
|
disconnectedCallback() {
|
|
42
42
|
this.colorInput?.removeEventListener("input", this)
|
|
43
43
|
this.textInput?.removeEventListener("input", this)
|
|
44
|
+
if (this.#select2) {
|
|
45
|
+
this.#select2.off("change", this.#onSelectChange)
|
|
46
|
+
this.#select2.select2("destroy")
|
|
47
|
+
this.#select2 = null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#onSelectChange = (event) => {
|
|
52
|
+
this.#toggleColorPicker(event.val === "custom_color")
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
#initializeSelect2() {
|
|
@@ -50,7 +59,7 @@ class ColorSelect extends HTMLElement {
|
|
|
50
59
|
formatResult: formatItem,
|
|
51
60
|
formatSelection: formatItem
|
|
52
61
|
}
|
|
53
|
-
$(this.select).select2(options)
|
|
62
|
+
this.#select2 = $(this.select).select2(options)
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
#toggleColorPicker(enabled = true) {
|
|
@@ -1,31 +1,24 @@
|
|
|
1
|
-
import { AlchemyHTMLElement } from "alchemy_admin/components/alchemy_html_element"
|
|
2
1
|
import { translate, currentLocale } from "alchemy_admin/i18n"
|
|
3
2
|
import flatpickr from "flatpickr"
|
|
4
3
|
|
|
5
4
|
const locale = currentLocale()
|
|
6
5
|
|
|
7
|
-
class Datepicker extends
|
|
8
|
-
static properties = {
|
|
9
|
-
inputType: { default: "date" }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
constructor() {
|
|
13
|
-
super()
|
|
14
|
-
this.flatpickr = undefined
|
|
15
|
-
}
|
|
16
|
-
|
|
6
|
+
class Datepicker extends HTMLElement {
|
|
17
7
|
// Load the locales for flatpickr before setting it up.
|
|
18
|
-
async
|
|
8
|
+
async connectedCallback() {
|
|
19
9
|
// English is the default locale for flatpickr, so we don't need to load it
|
|
20
10
|
if (locale !== "en") {
|
|
21
11
|
await import(`flatpickr/${locale}.js`)
|
|
22
12
|
}
|
|
13
|
+
// Bail out if the element was disconnected while the locale was loading.
|
|
14
|
+
// Otherwise flatpickr would leak a calendar onto a detached input.
|
|
15
|
+
if (!this.isConnected) return
|
|
23
16
|
|
|
24
17
|
this.flatpickr = flatpickr(this.inputField, this.flatpickrOptions)
|
|
25
18
|
}
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
this.flatpickr
|
|
20
|
+
disconnectedCallback() {
|
|
21
|
+
this.flatpickr?.destroy()
|
|
29
22
|
}
|
|
30
23
|
|
|
31
24
|
get flatpickrOptions() {
|
|
@@ -56,6 +49,10 @@ class Datepicker extends AlchemyHTMLElement {
|
|
|
56
49
|
get inputField() {
|
|
57
50
|
return this.querySelector("input")
|
|
58
51
|
}
|
|
52
|
+
|
|
53
|
+
get inputType() {
|
|
54
|
+
return this.getAttribute("input-type") || "date"
|
|
55
|
+
}
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
customElements.define("alchemy-datepicker", Datepicker)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Dialog } from "alchemy_admin/dialog"
|
|
2
2
|
|
|
3
3
|
export class DialogLink extends HTMLAnchorElement {
|
|
4
|
-
|
|
5
|
-
super()
|
|
4
|
+
connectedCallback() {
|
|
6
5
|
this.addEventListener("click", this)
|
|
7
6
|
}
|
|
8
7
|
|
|
8
|
+
disconnectedCallback() {
|
|
9
|
+
this.removeEventListener("click", this)
|
|
10
|
+
}
|
|
11
|
+
|
|
9
12
|
handleEvent(evt) {
|
|
10
13
|
if (!this.disabled) {
|
|
11
14
|
this.openDialog()
|
|
@@ -3,14 +3,17 @@ import { removeTab } from "alchemy_admin/fixed_elements"
|
|
|
3
3
|
import { growl } from "alchemy_admin/growler"
|
|
4
4
|
import { reloadPreview } from "alchemy_admin/components/preview_window"
|
|
5
5
|
import { openConfirmDialog } from "alchemy_admin/confirm_dialog"
|
|
6
|
-
import { dispatchPageDirtyEvent } from "alchemy_admin/
|
|
6
|
+
import { dispatchPageDirtyEvent } from "alchemy_admin/utils/dispatch_page_dirty_event"
|
|
7
7
|
|
|
8
8
|
export class DeleteElementButton extends HTMLElement {
|
|
9
|
-
|
|
10
|
-
super()
|
|
9
|
+
connectedCallback() {
|
|
11
10
|
this.button?.addEventListener("click", this)
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
disconnectedCallback() {
|
|
14
|
+
this.button?.removeEventListener("click", this)
|
|
15
|
+
}
|
|
16
|
+
|
|
14
17
|
async handleEvent() {
|
|
15
18
|
const confirmed = await openConfirmDialog(this.message)
|
|
16
19
|
if (confirmed) {
|