panda-cms 0.7.4 → 0.8.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 +37 -2
- data/Rakefile +2 -0
- data/app/components/panda/cms/admin/statistics_component.rb +1 -2
- data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
- data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
- data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
- data/app/components/panda/cms/admin/user_display_component.rb +2 -2
- data/app/components/panda/cms/code_component.rb +8 -4
- data/app/components/panda/cms/menu_component.rb +7 -6
- data/app/components/panda/cms/page_menu_component.rb +15 -17
- data/app/components/panda/cms/rich_text_component.rb +10 -11
- data/app/components/panda/cms/text_component.rb +6 -7
- data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
- data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
- data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
- data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
- data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
- data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
- data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
- data/app/controllers/panda/cms/application_controller.rb +19 -6
- data/app/controllers/panda/cms/errors_controller.rb +5 -2
- data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
- data/app/controllers/panda/cms/pages_controller.rb +34 -31
- data/app/controllers/panda/cms/posts_controller.rb +2 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
- data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
- data/app/helpers/panda/cms/application_helper.rb +3 -3
- data/app/helpers/panda/cms/asset_helper.rb +195 -0
- data/app/helpers/panda/cms/pages_helper.rb +2 -0
- data/app/helpers/panda/cms/posts_helper.rb +2 -0
- data/app/helpers/panda/cms/theme_helper.rb +2 -0
- data/app/javascript/panda/cms/application_panda_cms.js +2 -34
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
- data/app/javascript/panda/cms/controllers/index.js +8 -24
- data/app/javascript/panda/cms/stimulus-loading.js +39 -0
- data/app/javascript/panda_cms/stimulus-loading.js +39 -0
- data/app/jobs/panda/cms/application_job.rb +2 -0
- data/app/jobs/panda/cms/record_visit_job.rb +2 -0
- data/app/mailers/panda/cms/application_mailer.rb +2 -0
- data/app/mailers/panda/cms/form_mailer.rb +3 -1
- data/app/models/panda/cms/application_record.rb +2 -0
- data/app/models/panda/cms/block.rb +4 -1
- data/app/models/panda/cms/block_content.rb +3 -1
- data/app/models/panda/cms/current.rb +5 -12
- data/app/models/panda/cms/form.rb +2 -0
- data/app/models/panda/cms/form_submission.rb +2 -0
- data/app/models/panda/cms/menu.rb +12 -9
- data/app/models/panda/cms/menu_item.rb +10 -6
- data/app/models/panda/cms/page.rb +14 -12
- data/app/models/panda/cms/post.rb +12 -8
- data/app/models/panda/cms/redirect.rb +6 -3
- data/app/models/panda/cms/template.rb +12 -7
- data/app/models/panda/cms/visit.rb +3 -1
- data/app/models/panda/social/instagram_post.rb +2 -0
- data/app/services/panda/social/instagram_feed_service.rb +3 -1
- data/app/views/layouts/different_page.html.erb +6 -0
- data/app/views/layouts/homepage.html.erb +37 -0
- data/app/views/layouts/page.html.erb +18 -0
- data/app/views/layouts/panda/cms/application.html.erb +2 -1
- data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
- data/app/views/panda/cms/admin/files/index.html.erb +1 -1
- data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
- data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
- data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
- data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
- data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
- data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
- data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
- data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
- data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
- data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
- data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
- data/app/views/panda/cms/shared/_header.html.erb +10 -2
- data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
- data/app/views/shared/_footer.html.erb +3 -0
- data/app/views/shared/_header.html.erb +11 -0
- data/config/importmap.rb +2 -0
- data/config/initializers/inflections.rb +2 -0
- data/config/initializers/panda/cms/form_errors.rb +20 -21
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
- data/config/initializers/panda/cms.rb +8 -3
- data/config/initializers/zeitwork.rb +2 -0
- data/config/puma/test.rb +3 -1
- data/config/routes.rb +11 -19
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
- data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
- data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
- data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
- data/db/seeds.rb +2 -0
- data/lib/generators/panda/cms/install_generator.rb +2 -0
- data/lib/panda/cms/asset_loader.rb +390 -0
- data/lib/panda/cms/bulk_editor.rb +7 -3
- data/lib/panda/cms/demo_site_generator.rb +2 -0
- data/lib/panda/cms/engine.rb +57 -116
- data/lib/panda/cms/exceptions_app.rb +2 -0
- data/lib/panda/cms/railtie.rb +2 -0
- data/lib/panda/cms/slug.rb +3 -1
- data/lib/panda-cms/version.rb +3 -1
- data/lib/panda-cms.rb +54 -42
- data/lib/tasks/assets.rake +587 -0
- data/lib/tasks/panda/cms/install.rake +2 -0
- data/lib/tasks/panda/cms/migrations.rake +13 -0
- data/lib/tasks/panda/social/instagram.rake +2 -0
- data/lib/tasks/panda_cms.rake +3 -30
- data/public/panda-cms-assets/manifest.json +20 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
- metadata +186 -49
- data/app/builders/panda/cms/form_builder.rb +0 -217
- data/app/components/panda/cms/admin/button_component.rb +0 -70
- data/app/components/panda/cms/admin/container_component.rb +0 -13
- data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
- data/app/components/panda/cms/admin/heading_component.rb +0 -45
- data/app/components/panda/cms/admin/panel_component.rb +0 -13
- data/app/components/panda/cms/admin/table_component.rb +0 -46
- data/app/components/panda/cms/admin/tag_component.rb +0 -35
- data/app/constraints/panda/cms/admin_constraint.rb +0 -18
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
- data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
- data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
- data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
- data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
- data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
- data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
- data/app/models/panda/cms/breadcrumb.rb +0 -12
- data/app/models/panda/cms/user.rb +0 -31
- data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
- data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
- data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
- data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
- data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
- data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
- data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
- data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
- data/lib/panda/cms/editor_js/renderer.rb +0 -124
- data/lib/panda/cms/editor_js.rb +0 -16
- data/lib/panda/cms/editor_js_content.rb +0 -55
@@ -1,217 +0,0 @@
|
|
1
|
-
module Panda
|
2
|
-
module CMS
|
3
|
-
class FormBuilder < ActionView::Helpers::FormBuilder
|
4
|
-
include ActionView::Helpers::TagHelper
|
5
|
-
include ActionView::Helpers::FormTagHelper
|
6
|
-
|
7
|
-
def label(attribute, text = nil, options = {}, &block)
|
8
|
-
super(attribute, text, options.reverse_merge(class: label_styles))
|
9
|
-
end
|
10
|
-
|
11
|
-
def text_field(attribute, options = {})
|
12
|
-
if options.dig(:data, :prefix)
|
13
|
-
content_tag :div, class: container_styles do
|
14
|
-
label(attribute) + meta_text(options) +
|
15
|
-
content_tag(:div, class: "flex flex-grow") do
|
16
|
-
content_tag(:span, class: "inline-flex items-center px-3 text-base border border-r-none rounded-s-md whitespace-nowrap break-keep") { options.dig(:data, :prefix) } +
|
17
|
-
super(attribute, options.reverse_merge(class: input_styles_prefix + " input-prefix rounded-l-none border-l-none"))
|
18
|
-
end + error_message(attribute)
|
19
|
-
end
|
20
|
-
else
|
21
|
-
content_tag :div, class: container_styles do
|
22
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def email_field(method, options = {})
|
28
|
-
content_tag :div, class: container_styles do
|
29
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def datetime_field(method, options = {})
|
34
|
-
content_tag :div, class: container_styles do
|
35
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def text_area(method, options = {})
|
40
|
-
content_tag :div, class: container_styles do
|
41
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def password_field(attribute, options = {})
|
46
|
-
content_tag :div, class: container_styles do
|
47
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
52
|
-
content_tag :div, class: container_styles do
|
53
|
-
label(method) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg + error_message(method)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
58
|
-
content_tag :div, class: container_styles do
|
59
|
-
label(method) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options.reverse_merge(class: input_styles)) + error_message(method)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
64
|
-
wrap_field(method, options) do
|
65
|
-
super(
|
66
|
-
method,
|
67
|
-
priority_zones,
|
68
|
-
options,
|
69
|
-
html_options.reverse_merge(class: select_styles)
|
70
|
-
)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def file_field(method, options = {})
|
75
|
-
content_tag :div, class: container_styles do
|
76
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: "file:rounded file:border-0 file:text-sm file:bg-white file:text-gray-500 hover:file:bg-gray-50 bg-white px-2.5 hover:bg-gray-50".concat(input_styles)))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
def button(value = nil, options = {}, &block)
|
81
|
-
value ||= submit_default_value
|
82
|
-
options = options.dup
|
83
|
-
|
84
|
-
# Handle formmethod specially
|
85
|
-
if options[:formmethod] == "delete"
|
86
|
-
options[:name] = "_method"
|
87
|
-
options[:value] = "delete"
|
88
|
-
end
|
89
|
-
|
90
|
-
base_classes = [
|
91
|
-
"inline-flex items-center rounded-md",
|
92
|
-
"px-3 py-2",
|
93
|
-
"text-base font-semibold",
|
94
|
-
"shadow-sm"
|
95
|
-
]
|
96
|
-
|
97
|
-
# Only add fa-circle-check for non-block buttons
|
98
|
-
base_classes << "fa-circle-check" unless block_given?
|
99
|
-
|
100
|
-
options[:class] = [
|
101
|
-
*base_classes,
|
102
|
-
options[:class]
|
103
|
-
].compact.join(" ")
|
104
|
-
|
105
|
-
if block_given?
|
106
|
-
@template.button_tag(options, &block)
|
107
|
-
else
|
108
|
-
@template.button_tag(value, options)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def submit(value = nil, options = {})
|
113
|
-
value ||= submit_default_value
|
114
|
-
|
115
|
-
# Use the same style logic as ButtonComponent
|
116
|
-
action = object.persisted? ? :save : :create
|
117
|
-
button_classes = case action
|
118
|
-
when :save, :create
|
119
|
-
"text-white bg-active"
|
120
|
-
when :save_inactive
|
121
|
-
"text-white bg-inactive"
|
122
|
-
when :secondary
|
123
|
-
"text-dark border-2 border-mid bg-transparent hover:bg-light transition-all"
|
124
|
-
else
|
125
|
-
"text-dark border-2 border-mid bg-transparent hover:bg-light transition-all"
|
126
|
-
end
|
127
|
-
|
128
|
-
# Combine with common button classes
|
129
|
-
classes = "inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 px-3 py-2 #{button_classes}"
|
130
|
-
|
131
|
-
options[:class] = options[:class] ? "#{options[:class]} #{classes}" : classes
|
132
|
-
|
133
|
-
super
|
134
|
-
end
|
135
|
-
|
136
|
-
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
137
|
-
content_tag :div, class: container_styles do
|
138
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: "border-gray-300 ml-2"), checked_value, unchecked_value)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def date_field(method, options = {})
|
143
|
-
content_tag :div, class: container_styles do
|
144
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def meta_text(options)
|
149
|
-
return unless options[:meta]
|
150
|
-
@template.content_tag(:p, options[:meta], class: "block text-black/60 text-sm mb-2")
|
151
|
-
end
|
152
|
-
|
153
|
-
private
|
154
|
-
|
155
|
-
def label_styles
|
156
|
-
"font-light inline-block mb-1 text-base leading-6"
|
157
|
-
end
|
158
|
-
|
159
|
-
def base_input_styles
|
160
|
-
"bg-white block w-full rounded-md border border-mid focus:border-mid p-2 text-dark outline-0 focus:outline-0 ring-0 focus:ring-0 focus:ring-mid ring-offset-0 focus:ring-offset-0 shadow-none focus:shadow-none border-mid"
|
161
|
-
end
|
162
|
-
|
163
|
-
def input_styles
|
164
|
-
base_input_styles
|
165
|
-
end
|
166
|
-
|
167
|
-
def input_styles_prefix
|
168
|
-
input_styles.concat(" prefix")
|
169
|
-
end
|
170
|
-
|
171
|
-
def select_styles
|
172
|
-
"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-dark text-base outline-0 outline-dark focus:outline focus:-outline-offset-2 focus:outline-dark"
|
173
|
-
end
|
174
|
-
|
175
|
-
def select_svg
|
176
|
-
@template.content_tag(:svg, class: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", aria_hidden: true) do
|
177
|
-
@template.content_tag(:path, d: "M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z")
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def button_styles
|
182
|
-
"inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-0 focus-visible:outline-offset-none text-dark border-2 border-mid bg-transparent hover:bg-light transition-all gap-x-1.5 px-3 py-2 text-base gap-x-1.5 px-2.5 py-1.5 mt-2 "
|
183
|
-
end
|
184
|
-
|
185
|
-
def container_styles
|
186
|
-
"panda-cms-field-container mb-4"
|
187
|
-
end
|
188
|
-
|
189
|
-
def textarea_styles
|
190
|
-
input_styles.concat(" min-h-32")
|
191
|
-
end
|
192
|
-
|
193
|
-
def submit_default_value
|
194
|
-
object.persisted? ? "Update #{object.class.name.demodulize}" : "Create #{object.class.name.demodulize}"
|
195
|
-
end
|
196
|
-
|
197
|
-
def wrap_field(method, options = {}, &block)
|
198
|
-
@template.content_tag(:div, class: "panda-cms-field-container") do
|
199
|
-
label(method, class: "font-light inline-block mb-1 text-base leading-6") +
|
200
|
-
meta_text(options) +
|
201
|
-
@template.content_tag(:div, class: field_wrapper_styles, &block)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def field_wrapper_styles
|
206
|
-
"mt-1"
|
207
|
-
end
|
208
|
-
|
209
|
-
def error_message(attribute)
|
210
|
-
return unless object.respond_to?(:errors) && object.errors[attribute]&.any?
|
211
|
-
content_tag(:p, class: "mt-2 text-sm text-red-600") do
|
212
|
-
object.errors[attribute].join(", ")
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class ButtonComponent < ViewComponent::Base
|
7
|
-
attr_accessor :text, :action, :link, :icon, :size, :data
|
8
|
-
|
9
|
-
def initialize(text: "Button", action: nil, data: {}, link: "#", icon: nil, size: :regular, id: nil)
|
10
|
-
@text = text
|
11
|
-
@action = action
|
12
|
-
@data = data
|
13
|
-
@link = link
|
14
|
-
@icon = icon
|
15
|
-
@size = size
|
16
|
-
@id = id
|
17
|
-
end
|
18
|
-
|
19
|
-
def call
|
20
|
-
@icon = set_icon_from_action(@action) if @action && @icon.nil?
|
21
|
-
icon = content_tag(:i, "", class: "mr-2 fa-regular fa-#{@icon}") if @icon
|
22
|
-
@text = "#{icon} #{@text.titleize}".html_safe
|
23
|
-
|
24
|
-
classes = "inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
|
25
|
-
|
26
|
-
case @size
|
27
|
-
when :small, :sm
|
28
|
-
classes += "gap-x-1.5 px-2.5 py-1.5 text-sm "
|
29
|
-
when :medium, :regular, :md
|
30
|
-
classes += "gap-x-1.5 px-3 py-2 text-base "
|
31
|
-
when :large, :lg
|
32
|
-
classes += "gap-x-2 px-3.5 py-2.5 text-lg "
|
33
|
-
end
|
34
|
-
|
35
|
-
classes += case @action
|
36
|
-
when :save, :create
|
37
|
-
"text-white bg-active"
|
38
|
-
when :save_inactive
|
39
|
-
"text-white bg-inactive"
|
40
|
-
when :secondary
|
41
|
-
"text-dark border-2 border-dark bg-transparent hover:bg-light transition-all "
|
42
|
-
when :delete, :destroy, :danger
|
43
|
-
"text-error border border-error bg-red-100 hover:bg-red-200 hover:text-error focus-visible:outline-red-300 "
|
44
|
-
else
|
45
|
-
"text-dark border-2 border-dark bg-transparent hover:bg-light transition-all "
|
46
|
-
end
|
47
|
-
|
48
|
-
content_tag :a, href: @link, class: classes, data: @data, id: @id do
|
49
|
-
@text
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def set_icon_from_action(action)
|
56
|
-
case action
|
57
|
-
when :add, :new, :create
|
58
|
-
"plus"
|
59
|
-
when :save
|
60
|
-
"check"
|
61
|
-
when :edit, :update
|
62
|
-
"pencil"
|
63
|
-
when :delete, :destroy
|
64
|
-
"trash"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class ContainerComponent < ViewComponent::Base
|
7
|
-
renders_one :heading, "Panda::CMS::Admin::HeadingComponent"
|
8
|
-
renders_one :tab_bar, "Panda::CMS::Admin::TabBarComponent"
|
9
|
-
renders_one :slideover, "Panda::CMS::Admin::SlideoverComponent"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class FlashMessageComponent < ::ViewComponent::Base
|
7
|
-
attr_reader :kind, :message
|
8
|
-
|
9
|
-
def initialize(message:, kind:, temporary: true)
|
10
|
-
@kind = kind.to_sym
|
11
|
-
@message = message
|
12
|
-
@temporary = temporary
|
13
|
-
end
|
14
|
-
|
15
|
-
def text_colour_css
|
16
|
-
case kind
|
17
|
-
when :success
|
18
|
-
"text-active"
|
19
|
-
when :alert, :error
|
20
|
-
"text-error"
|
21
|
-
when :warning
|
22
|
-
"text-warning"
|
23
|
-
when :info, :notice
|
24
|
-
"text-active"
|
25
|
-
else
|
26
|
-
"text-mid"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def icon_css
|
31
|
-
case kind
|
32
|
-
when :success
|
33
|
-
"fa-circle-check"
|
34
|
-
when :alert
|
35
|
-
"fa-circle-xmark"
|
36
|
-
when :warning
|
37
|
-
"fa-triangle-exclamation"
|
38
|
-
when :info, :notice
|
39
|
-
"fa-circle-info"
|
40
|
-
else
|
41
|
-
"fa-circle-info"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class HeadingComponent < ViewComponent::Base
|
7
|
-
renders_many :buttons, Panda::CMS::Admin::ButtonComponent
|
8
|
-
|
9
|
-
attr_reader :text, :level, :icon, :additional_styles
|
10
|
-
|
11
|
-
def initialize(text:, level: 2, icon: "", additional_styles: "")
|
12
|
-
@text = text
|
13
|
-
@level = level
|
14
|
-
@icon = icon
|
15
|
-
@additional_styles = additional_styles
|
16
|
-
@additional_styles = @additional_styles.split(" ") if @additional_styles.is_a?(String)
|
17
|
-
end
|
18
|
-
|
19
|
-
def call
|
20
|
-
output = ""
|
21
|
-
output += content_tag(:div, @text, class: "grow")
|
22
|
-
|
23
|
-
if buttons?
|
24
|
-
output += content_tag(:span, class: "actions flex gap-x-2 -mt-1") do
|
25
|
-
safe_join(buttons, "")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
output = output.html_safe
|
30
|
-
base_heading_styles = "flex pt-1 text-black mb-5 -mt-1"
|
31
|
-
|
32
|
-
if level == 1
|
33
|
-
content_tag(:h1, output, class: [base_heading_styles, "text-2xl font-medium", @additional_styles])
|
34
|
-
elsif level == 2
|
35
|
-
content_tag(:h2, output, class: [base_heading_styles, "text-xl font-medium", @additional_styles])
|
36
|
-
elsif level == 3
|
37
|
-
content_tag(:h3, output, class: [base_heading_styles, "text-xl", "font-light", @additional_styles])
|
38
|
-
elsif level == :panel
|
39
|
-
content_tag(:h3, output, class: ["text-base font-medium p-4 text-white"])
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class PanelComponent < ViewComponent::Base
|
7
|
-
renders_one :heading, ->(text:, icon: "", level: :panel, additional_styles: "") do
|
8
|
-
Panda::CMS::Admin::HeadingComponent.new(text: text, icon: icon, level: level, additional_styles: additional_styles)
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class TableComponent < ViewComponent::Base
|
7
|
-
attr_reader :columns
|
8
|
-
|
9
|
-
def initialize(term:, rows:)
|
10
|
-
@term = term
|
11
|
-
@rows = rows
|
12
|
-
@columns = []
|
13
|
-
end
|
14
|
-
|
15
|
-
def column(label, &)
|
16
|
-
@columns << Column.new(label, &)
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
# Ensures @columns gets populated [https://dev.to/rolandstuder/supercharged-table-component-built-with-viewcomponent-3j6i]
|
22
|
-
def before_render
|
23
|
-
content
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class Column
|
28
|
-
attr_reader :label, :cell
|
29
|
-
|
30
|
-
def initialize(label, &block)
|
31
|
-
@label = label
|
32
|
-
@cell = block
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class TagColumn < Column
|
37
|
-
attr_reader :label, :cell
|
38
|
-
|
39
|
-
def initialize(label, &block)
|
40
|
-
@label = label
|
41
|
-
@cell = Panda::CMS::Admin::TagComponent.new(status: block)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class TagComponent < ViewComponent::Base
|
7
|
-
attr_accessor :status, :text
|
8
|
-
|
9
|
-
def initialize(status: :active, text: nil)
|
10
|
-
@status = status.to_sym
|
11
|
-
@text = text || status.to_s.humanize
|
12
|
-
end
|
13
|
-
|
14
|
-
def call
|
15
|
-
classes = "inline-flex items-center py-1 px-2 text-xs font-medium rounded-md ring-1 ring-inset "
|
16
|
-
|
17
|
-
classes += case @status
|
18
|
-
when :active
|
19
|
-
"text-white ring-black/30 bg-active border-0 "
|
20
|
-
when :draft
|
21
|
-
"text-black ring-black/30 bg-warning "
|
22
|
-
when :inactive, :hidden
|
23
|
-
"text-black ring-black/30 bg-black/5 bg-white "
|
24
|
-
else
|
25
|
-
"text-black bg-white "
|
26
|
-
end
|
27
|
-
|
28
|
-
content_tag :span, class: classes do
|
29
|
-
@text
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Panda
|
2
|
-
module CMS
|
3
|
-
class AdminConstraint
|
4
|
-
def initialize(&block)
|
5
|
-
@block = block
|
6
|
-
end
|
7
|
-
|
8
|
-
def matches?(request)
|
9
|
-
user = current_user(request)
|
10
|
-
user.present? && user.admin? && @block&.call(user)
|
11
|
-
end
|
12
|
-
|
13
|
-
def current_user(request)
|
14
|
-
User.find_by(id: request.session[:user_id])
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class MyProfileController < ApplicationController
|
7
|
-
before_action :set_initial_breadcrumb, only: %i[edit update]
|
8
|
-
before_action :authenticate_admin_user!
|
9
|
-
|
10
|
-
# Shows the edit form for the current user's profile
|
11
|
-
# @type GET
|
12
|
-
# @return void
|
13
|
-
def edit
|
14
|
-
render :edit, locals: {user: current_user}
|
15
|
-
end
|
16
|
-
|
17
|
-
# Updates the current user's profile
|
18
|
-
# @type PATCH/PUT
|
19
|
-
# @return void
|
20
|
-
def update
|
21
|
-
if current_user.update(user_params)
|
22
|
-
redirect_to edit_admin_my_profile_path, flash: {success: "Your profile has been updated successfully."}
|
23
|
-
else
|
24
|
-
render :edit, locals: {user: current_user}, status: :unprocessable_entity
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def set_initial_breadcrumb
|
31
|
-
add_breadcrumb "My Profile", edit_admin_my_profile_path
|
32
|
-
end
|
33
|
-
|
34
|
-
# Only allow a list of trusted parameters through
|
35
|
-
# @type private
|
36
|
-
# @return ActionController::StrongParameters
|
37
|
-
def user_params
|
38
|
-
params.require(:user).permit(:firstname, :lastname, :email, :current_theme)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module Admin
|
6
|
-
class SessionsController < ApplicationController
|
7
|
-
layout "panda/cms/public"
|
8
|
-
|
9
|
-
def new
|
10
|
-
@providers = Panda::CMS.config.authentication.select { |_, v| v[:enabled] && !v[:hidden] }.keys
|
11
|
-
end
|
12
|
-
|
13
|
-
def create
|
14
|
-
user_info = request.env.dig("omniauth.auth", "info")
|
15
|
-
provider = params[:provider].to_sym
|
16
|
-
|
17
|
-
unless Panda::CMS.config.authentication.dig(provider, :enabled)
|
18
|
-
Rails.logger.error "Authentication provider '#{provider}' is not enabled"
|
19
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
20
|
-
return
|
21
|
-
end
|
22
|
-
|
23
|
-
user = Panda::CMS::User.find_by(email: user_info["email"])
|
24
|
-
|
25
|
-
if !user && Panda::CMS.config.authentication.dig(provider, :create_account_on_first_login)
|
26
|
-
create_as_admin = Panda::CMS.config.authentication.dig(provider, :create_as_admin)
|
27
|
-
|
28
|
-
# Always create the first user as admin, regardless of what our settings look like
|
29
|
-
# else we can't ever really login. :)
|
30
|
-
if !create_as_admin
|
31
|
-
create_as_admin = true if !create_as_admin && Panda::CMS::User.count.zero?
|
32
|
-
end
|
33
|
-
|
34
|
-
if user_info["first_name"] && user_info["last_name"]
|
35
|
-
firstname = user_info["first_name"]
|
36
|
-
lastname = user_info["last_name"]
|
37
|
-
elsif user_info["name"]
|
38
|
-
firstname, lastname = user_info["name"].split(" ", 2)
|
39
|
-
end
|
40
|
-
|
41
|
-
user = User.find_or_create_by(
|
42
|
-
email: user_info["email"]
|
43
|
-
) do |u|
|
44
|
-
u.firstname = firstname
|
45
|
-
u.lastname = lastname
|
46
|
-
u.admin = create_as_admin
|
47
|
-
u.image_url = user_info["image"]
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
if user.nil?
|
52
|
-
# User can't be found with this email address
|
53
|
-
Rails.logger.error "User does not exist: #{user_info["email"]}"
|
54
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
55
|
-
return
|
56
|
-
end
|
57
|
-
|
58
|
-
if !user.admin?
|
59
|
-
# User can't be found with this email address or can't login
|
60
|
-
Rails.logger.error "User ID #{user.id} attempted admin login, is not admin." if user && !user.admin
|
61
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
62
|
-
return
|
63
|
-
end
|
64
|
-
|
65
|
-
session[:user_id] = user.id
|
66
|
-
Panda::CMS::Current.user = user
|
67
|
-
|
68
|
-
redirect_path = request.env["omniauth.origin"] || admin_dashboard_path
|
69
|
-
redirect_to redirect_path, flash: {success: t("panda.cms.admin.sessions.create.success")}
|
70
|
-
rescue ::OmniAuth::Strategies::OAuth2::CallbackError => e
|
71
|
-
Rails.logger.error "OAuth2 login callback error: #{e.message}"
|
72
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
73
|
-
rescue ::OAuth2::Error => e
|
74
|
-
Rails.logger.error "OAuth2 login error: #{e.message}"
|
75
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
76
|
-
rescue => e
|
77
|
-
Rails.logger.error "Unknown login error: #{e.message}"
|
78
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
79
|
-
end
|
80
|
-
|
81
|
-
def failure
|
82
|
-
Rails.logger.error "Login failure: #{params[:message]} from #{params[:origin]} using #{params[:strategy]}"
|
83
|
-
redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
|
84
|
-
end
|
85
|
-
|
86
|
-
def destroy
|
87
|
-
Panda::CMS::Current.user = nil
|
88
|
-
session[:user_id] = nil
|
89
|
-
redirect_to admin_login_path, flash: {success: t("panda.cms.admin.sessions.destroy.success")}
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|