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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/Rakefile +2 -0
  4. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  5. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  6. data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
  7. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  8. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  9. data/app/components/panda/cms/code_component.rb +8 -4
  10. data/app/components/panda/cms/menu_component.rb +7 -6
  11. data/app/components/panda/cms/page_menu_component.rb +15 -17
  12. data/app/components/panda/cms/rich_text_component.rb +10 -11
  13. data/app/components/panda/cms/text_component.rb +6 -7
  14. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  15. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  16. data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
  17. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  18. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
  19. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  20. data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
  21. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
  22. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  23. data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
  24. data/app/controllers/panda/cms/application_controller.rb +19 -6
  25. data/app/controllers/panda/cms/errors_controller.rb +5 -2
  26. data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
  27. data/app/controllers/panda/cms/pages_controller.rb +34 -31
  28. data/app/controllers/panda/cms/posts_controller.rb +2 -0
  29. data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
  30. data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
  31. data/app/helpers/panda/cms/application_helper.rb +3 -3
  32. data/app/helpers/panda/cms/asset_helper.rb +195 -0
  33. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  34. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  35. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  36. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  37. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  38. data/app/javascript/panda/cms/controllers/index.js +8 -24
  39. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  40. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  41. data/app/jobs/panda/cms/application_job.rb +2 -0
  42. data/app/jobs/panda/cms/record_visit_job.rb +2 -0
  43. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  44. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  45. data/app/models/panda/cms/application_record.rb +2 -0
  46. data/app/models/panda/cms/block.rb +4 -1
  47. data/app/models/panda/cms/block_content.rb +3 -1
  48. data/app/models/panda/cms/current.rb +5 -12
  49. data/app/models/panda/cms/form.rb +2 -0
  50. data/app/models/panda/cms/form_submission.rb +2 -0
  51. data/app/models/panda/cms/menu.rb +12 -9
  52. data/app/models/panda/cms/menu_item.rb +10 -6
  53. data/app/models/panda/cms/page.rb +14 -12
  54. data/app/models/panda/cms/post.rb +12 -8
  55. data/app/models/panda/cms/redirect.rb +6 -3
  56. data/app/models/panda/cms/template.rb +12 -7
  57. data/app/models/panda/cms/visit.rb +3 -1
  58. data/app/models/panda/social/instagram_post.rb +2 -0
  59. data/app/services/panda/social/instagram_feed_service.rb +3 -1
  60. data/app/views/layouts/different_page.html.erb +6 -0
  61. data/app/views/layouts/homepage.html.erb +37 -0
  62. data/app/views/layouts/page.html.erb +18 -0
  63. data/app/views/layouts/panda/cms/application.html.erb +2 -1
  64. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  65. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  66. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  67. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  68. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  69. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  70. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  71. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  72. data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
  73. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  74. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  75. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  76. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  77. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  78. data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
  79. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  80. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  81. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  82. data/app/views/panda/cms/shared/_header.html.erb +10 -2
  83. data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
  84. data/app/views/shared/_footer.html.erb +3 -0
  85. data/app/views/shared/_header.html.erb +11 -0
  86. data/config/importmap.rb +2 -0
  87. data/config/initializers/inflections.rb +2 -0
  88. data/config/initializers/panda/cms/form_errors.rb +20 -21
  89. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  90. data/config/initializers/panda/cms.rb +8 -3
  91. data/config/initializers/zeitwork.rb +2 -0
  92. data/config/puma/test.rb +3 -1
  93. data/config/routes.rb +11 -19
  94. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  95. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  96. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  97. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  98. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  99. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  100. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  101. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  102. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  103. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  104. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  105. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  106. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  107. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  108. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  109. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  110. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  111. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  112. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  113. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  114. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  115. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  116. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  117. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  118. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  119. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  120. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  121. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  122. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  123. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  124. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  125. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  126. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  127. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  128. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  129. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  130. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  131. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  132. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  133. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
  134. data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
  135. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
  136. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  137. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  138. data/db/seeds.rb +2 -0
  139. data/lib/generators/panda/cms/install_generator.rb +2 -0
  140. data/lib/panda/cms/asset_loader.rb +390 -0
  141. data/lib/panda/cms/bulk_editor.rb +7 -3
  142. data/lib/panda/cms/demo_site_generator.rb +2 -0
  143. data/lib/panda/cms/engine.rb +57 -116
  144. data/lib/panda/cms/exceptions_app.rb +2 -0
  145. data/lib/panda/cms/railtie.rb +2 -0
  146. data/lib/panda/cms/slug.rb +3 -1
  147. data/lib/panda-cms/version.rb +3 -1
  148. data/lib/panda-cms.rb +54 -42
  149. data/lib/tasks/assets.rake +587 -0
  150. data/lib/tasks/panda/cms/install.rake +2 -0
  151. data/lib/tasks/panda/cms/migrations.rake +13 -0
  152. data/lib/tasks/panda/social/instagram.rake +2 -0
  153. data/lib/tasks/panda_cms.rake +3 -30
  154. data/public/panda-cms-assets/manifest.json +20 -0
  155. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  156. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  157. metadata +186 -49
  158. data/app/builders/panda/cms/form_builder.rb +0 -217
  159. data/app/components/panda/cms/admin/button_component.rb +0 -70
  160. data/app/components/panda/cms/admin/container_component.rb +0 -13
  161. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  162. data/app/components/panda/cms/admin/heading_component.rb +0 -45
  163. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  164. data/app/components/panda/cms/admin/table_component.rb +0 -46
  165. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  166. data/app/constraints/panda/cms/admin_constraint.rb +0 -18
  167. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
  168. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
  169. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
  170. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  171. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  172. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  173. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  174. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  175. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  176. data/app/models/panda/cms/breadcrumb.rb +0 -12
  177. data/app/models/panda/cms/user.rb +0 -31
  178. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
  179. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  180. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  181. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
  182. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
  183. data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
  184. data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
  185. data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
  186. data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
  187. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
  188. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
  189. data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
  190. data/lib/panda/cms/editor_js/renderer.rb +0 -124
  191. data/lib/panda/cms/editor_js.rb +0 -16
  192. 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
@@ -1,9 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- // Connects to data-controller="theme-form"
4
- export default class extends Controller {
5
- updateTheme(event) {
6
- const newTheme = event.target.value;
7
- document.documentElement.dataset.theme = newTheme;
8
- }
9
- }