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
@@ -10,6 +10,10 @@ export default class extends Controller {
10
10
 
11
11
  connect() {
12
12
  this.loadEditorResources();
13
+ // Enable submit button after a delay as fallback
14
+ setTimeout(() => {
15
+ this.enableSubmitButton();
16
+ }, 1000);
13
17
  }
14
18
 
15
19
  async loadEditorResources() {
@@ -77,6 +81,8 @@ export default class extends Controller {
77
81
  holderDiv.dataset.editorInitialized = "true";
78
82
  // Add a class to indicate the editor is ready
79
83
  holderDiv.classList.add("editor-ready");
84
+ // Enable the submit button
85
+ this.enableSubmitButton();
80
86
  // Dispatch an event when editor is ready
81
87
  this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
82
88
  },
@@ -119,12 +125,6 @@ export default class extends Controller {
119
125
  // Wait for editor to be ready
120
126
  await this.editor.isReady;
121
127
  console.debug("[Panda CMS] Editor initialized successfully");
122
- this.editorContainerTarget.dataset.editorInitialized = "true";
123
- holderDiv.dataset.editorInitialized = "true";
124
- // Add a class to indicate the editor is ready
125
- holderDiv.classList.add("editor-ready");
126
- // Dispatch an event when editor is ready
127
- this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
128
128
 
129
129
  } catch (error) {
130
130
  console.error("[Panda CMS] Editor setup failed:", error);
@@ -133,6 +133,8 @@ export default class extends Controller {
133
133
  holderDiv.dataset.editorInitialized = "false";
134
134
  holderDiv.classList.remove("editor-ready");
135
135
  }
136
+ // Still enable the submit button even if editor fails
137
+ this.enableSubmitButton();
136
138
  }
137
139
  }
138
140
 
@@ -190,6 +192,57 @@ export default class extends Controller {
190
192
  source: "editorJS",
191
193
  };
192
194
  }
195
+
196
+ enableSubmitButton() {
197
+ // Find the submit button in the form and enable it
198
+ const form = this.element.closest('form');
199
+ if (form) {
200
+ const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
201
+ if (submitButton) {
202
+ submitButton.disabled = false;
203
+ }
204
+ }
205
+ }
206
+
207
+ async submit(event) {
208
+ // Prevent the default button click behavior temporarily
209
+ event.preventDefault();
210
+
211
+ const submitButton = event.target;
212
+ const form = submitButton.closest('form');
213
+
214
+ // Re-enable the button that was disabled by data-disable-with
215
+ submitButton.disabled = false;
216
+
217
+ // Ensure editor content is saved before form submission
218
+ if (this.editor) {
219
+ try {
220
+ const outputData = await this.editor.save();
221
+ outputData.source = "editorJS";
222
+ const jsonString = JSON.stringify(outputData);
223
+ this.hiddenFieldTarget.value = jsonString;
224
+ console.log("[Panda CMS] Editor content saved before submission");
225
+ } catch (error) {
226
+ console.error("[Panda CMS] Failed to save editor content:", error);
227
+ }
228
+ }
229
+
230
+ // Now trigger the normal form submission (this will let Rails/Turbo handle it properly)
231
+ if (form) {
232
+ // Remove our custom action to prevent infinite loop
233
+ submitButton.removeAttribute('data-action');
234
+
235
+ // Create a new click event that will trigger the normal form submission
236
+ const clickEvent = new MouseEvent('click', {
237
+ bubbles: true,
238
+ cancelable: true,
239
+ view: window
240
+ });
241
+
242
+ // Dispatch the click event, which will trigger normal Rails form submission
243
+ submitButton.dispatchEvent(clickEvent);
244
+ }
245
+ }
193
246
 
194
247
  disconnect() {
195
248
  if (this.editor) {
@@ -1,16 +1,10 @@
1
1
  console.debug("[Panda CMS] Importing Panda CMS Stimulus Controller...")
2
2
 
3
- import { Application as PandaCMSApplication } from "@hotwired/stimulus"
3
+ import { application } from "@hotwired/stimulus-loading"
4
4
 
5
- const pandaCmsApplication = PandaCMSApplication.start()
5
+ console.debug("[Panda CMS] Using shared Stimulus application...")
6
6
 
7
- console.debug("[Panda CMS] Application started...")
8
-
9
- // Configure Stimulus development experience
10
- const railsEnv = document.body?.dataset?.environment || "production";
11
- pandaCmsApplication.debug = railsEnv === "development";
12
-
13
- console.debug("[Panda CMS] window.pandaCmsStimulus available...")
7
+ const pandaCmsApplication = application
14
8
 
15
9
  console.debug("[Panda CMS] Registering controllers...")
16
10
 
@@ -27,21 +21,11 @@ pandaCmsApplication.register("slug", SlugController)
27
21
  import EditorIframeController from "panda/cms/controllers/editor_iframe_controller"
28
22
  pandaCmsApplication.register("editor-iframe", EditorIframeController)
29
23
 
30
- console.debug("[Panda CMS] Registering components...")
31
- import ThemeFormController from "panda/cms/controllers/theme_form_controller";
32
- pandaCmsApplication.register("theme-form", ThemeFormController);
33
-
34
- // Import and register all TailwindCSS Components or just the ones you need
35
- import { Alert, Autosave, ColorPreview, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
36
- pandaCmsApplication.register('alert', Alert)
37
- pandaCmsApplication.register('autosave', Autosave)
38
- pandaCmsApplication.register('color-preview', ColorPreview)
39
- pandaCmsApplication.register('dropdown', Dropdown)
40
- pandaCmsApplication.register('modal', Modal)
41
- pandaCmsApplication.register('popover', Popover)
42
- pandaCmsApplication.register('slideover', Slideover)
43
- pandaCmsApplication.register('tabs', Tabs)
44
- pandaCmsApplication.register('toggle', Toggle)
24
+ // Import and register TailwindCSS Stimulus Components needed by CMS
25
+ import { Toggle } from "tailwindcss-stimulus-components"
26
+ pandaCmsApplication.register("toggle", Toggle)
27
+
28
+ console.debug("[Panda CMS] Registered Toggle controller for slideover functionality")
45
29
 
46
30
  console.debug("[Panda CMS] Components registered...")
47
31
 
@@ -0,0 +1,39 @@
1
+ // Stimulus loading utilities for Panda CMS
2
+ // This provides the loading functionality that would normally come from stimulus-rails
3
+
4
+ import { Application } from "@hotwired/stimulus"
5
+
6
+ const application = Application.start()
7
+
8
+ // Configure debug mode based on environment
9
+ const railsEnv = document.body?.dataset?.environment || "production";
10
+ application.debug = railsEnv === "development"
11
+ window.Stimulus = application
12
+
13
+ // Auto-registration functionality
14
+ function eagerLoadControllersFrom(context) {
15
+ const definitions = []
16
+ for (const path of context.keys()) {
17
+ const module = context(path)
18
+ const controller = module.default
19
+ if (controller && path.match(/[_-]controller\.(js|ts)$/)) {
20
+ const name = path
21
+ .replace(/^.*\//, "")
22
+ .replace(/[_-]controller\.(js|ts)$/, "")
23
+ .replace(/_/g, "-")
24
+ definitions.push({ name, module: controller, filename: path })
25
+ }
26
+ }
27
+ return definitions
28
+ }
29
+
30
+ function lazyLoadControllersFrom(context) {
31
+ return eagerLoadControllersFrom(context)
32
+ }
33
+
34
+ // Export the functions that stimulus-loading typically provides
35
+ export {
36
+ application,
37
+ eagerLoadControllersFrom,
38
+ lazyLoadControllersFrom
39
+ }
@@ -0,0 +1,39 @@
1
+ // Stimulus loading utilities for Panda CMS
2
+ // This provides the loading functionality that would normally come from stimulus-rails
3
+
4
+ import { Application } from "@hotwired/stimulus"
5
+
6
+ const application = Application.start()
7
+
8
+ // Configure debug mode based on environment
9
+ const railsEnv = document.body?.dataset?.environment || "production";
10
+ application.debug = railsEnv === "development"
11
+ window.Stimulus = application
12
+
13
+ // Auto-registration functionality
14
+ function eagerLoadControllersFrom(context) {
15
+ const definitions = []
16
+ for (const path of context.keys()) {
17
+ const module = context(path)
18
+ const controller = module.default
19
+ if (controller && path.match(/[_-]controller\.(js|ts)$/)) {
20
+ const name = path
21
+ .replace(/^.*\//, "")
22
+ .replace(/[_-]controller\.(js|ts)$/, "")
23
+ .replace(/_/g, "-")
24
+ definitions.push({ name, module: controller, filename: path })
25
+ }
26
+ }
27
+ return definitions
28
+ }
29
+
30
+ function lazyLoadControllersFrom(context) {
31
+ return eagerLoadControllersFrom(context)
32
+ }
33
+
34
+ // Export the functions that stimulus-loading typically provides
35
+ export {
36
+ application,
37
+ eagerLoadControllersFrom,
38
+ lazyLoadControllersFrom
39
+ }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationJob < ActiveJob::Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class RecordVisitJob < ApplicationJob
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationMailer < ActionMailer::Base
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormMailer < Panda::CMS::ApplicationMailer
4
6
  def notification_email(form:, form_submission:)
5
7
  # TODO: Handle fields named just "name", and "email" better
6
8
  @submission_data = form_submission.data
7
- @sender_name = @submission_data["first_name"].to_s + " " + @submission_data["last_name"].to_s
9
+ @sender_name = "#{@submission_data["first_name"]} #{@submission_data["last_name"]}"
8
10
  @sender_email = @submission_data["email"].to_s
9
11
 
10
12
  mail(
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class ApplicationRecord < ActiveRecord::Base
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Block < ApplicationRecord
4
6
  self.table_name = "panda_cms_blocks"
5
7
 
6
8
  belongs_to :template, foreign_key: :panda_cms_template_id, class_name: "Panda::CMS::Template"
7
- has_many :block_contents, foreign_key: :panda_cms_block_id, class_name: "Panda::CMS::BlockContent", dependent: :destroy
9
+ has_many :block_contents, foreign_key: :panda_cms_block_id, class_name: "Panda::CMS::BlockContent",
10
+ dependent: :destroy
8
11
 
9
12
  validates :name, presence: true
10
13
  validates :key, presence: true, uniqueness: {scope: :panda_cms_template_id, case_sensitive: false}
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class BlockContent < ApplicationRecord
4
- include EditorJsContent
6
+ include ::Panda::Editor::Content
5
7
 
6
8
  self.table_name = "panda_cms_block_contents"
7
9
 
@@ -1,17 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
- class Current < ActiveSupport::CurrentAttributes
4
- attribute :root, :page
5
- attribute :user
6
- attribute :request_id, :user_agent, :ip_address
7
-
8
- # resets { Time.zone = nil }
9
-
10
- # def user=(user)
11
- # super
12
- # self.account = user.account
13
- # Time.zone = user.time_zone
14
- # end
5
+ class Current < Panda::Core::Current
6
+ # CMS-specific attributes
7
+ attribute :page
15
8
  end
16
9
  end
17
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Form < ApplicationRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class FormSubmission < ApplicationRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Menu < ApplicationRecord
@@ -5,13 +7,16 @@ module Panda
5
7
 
6
8
  after_save :generate_auto_menu_items, if: -> { kind == "auto" }
7
9
 
8
- has_many :menu_items, -> { order(lft: :asc) }, foreign_key: :panda_cms_menu_id, class_name: "Panda::CMS::MenuItem", inverse_of: :menu
9
- belongs_to :start_page, class_name: "Panda::CMS::Page", foreign_key: "start_page_id", inverse_of: :page_menu, optional: true
10
+ has_many :menu_items, lambda {
11
+ order(lft: :asc)
12
+ }, foreign_key: :panda_cms_menu_id, class_name: "Panda::CMS::MenuItem", inverse_of: :menu
13
+ belongs_to :start_page, class_name: "Panda::CMS::Page", foreign_key: "start_page_id", inverse_of: :page_menu,
14
+ optional: true
10
15
 
11
16
  accepts_nested_attributes_for :menu_items, reject_if: :all_blank, allow_destroy: true
12
17
 
13
18
  validates :name, presence: true, uniqueness: {case_sensitive: false}
14
- validates :kind, presence: true, inclusion: {in: ["static", "auto"]}
19
+ validates :kind, presence: true, inclusion: {in: %w[static auto]}
15
20
  validate :validate_start_page
16
21
 
17
22
  def generate_auto_menu_items
@@ -30,9 +35,7 @@ module Panda
30
35
  def generate_menu_items(parent_menu_item:, parent_page:)
31
36
  parent_page.children.where(status: [:active]).each do |page|
32
37
  menu_item = menu_items.create(text: page.title, panda_cms_page_id: page.id, parent: parent_menu_item)
33
- if page.children
34
- generate_menu_items(parent_menu_item: menu_item, parent_page: page)
35
- end
38
+ generate_menu_items(parent_menu_item: menu_item, parent_page: page) if page.children
36
39
  end
37
40
  end
38
41
 
@@ -43,9 +46,9 @@ module Panda
43
46
  # @visibility private
44
47
  #
45
48
  def validate_start_page
46
- if kind == "auto" && start_page.nil?
47
- errors.add(:start_page, "can't be blank")
48
- end
49
+ return unless kind == "auto" && start_page.nil?
50
+
51
+ errors.add(:start_page, "can't be blank")
49
52
  end
50
53
  end
51
54
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "awesome_nested_set"
2
4
 
3
5
  module Panda
@@ -8,8 +10,10 @@ module Panda
8
10
  self.implicit_order_column = "lft"
9
11
  self.table_name = "panda_cms_menu_items"
10
12
 
11
- belongs_to :menu, foreign_key: :panda_cms_menu_id, class_name: "Panda::CMS::Menu", inverse_of: :menu_items, touch: true
12
- belongs_to :page, foreign_key: :panda_cms_page_id, class_name: "Panda::CMS::Page", inverse_of: :menu_items, optional: true
13
+ belongs_to :menu, foreign_key: :panda_cms_menu_id, class_name: "Panda::CMS::Menu", inverse_of: :menu_items,
14
+ touch: true
15
+ belongs_to :page, foreign_key: :panda_cms_page_id, class_name: "Panda::CMS::Page", inverse_of: :menu_items,
16
+ optional: true
13
17
 
14
18
  validates :text, presence: true, uniqueness: {scope: :panda_cms_menu_id, case_sensitive: false}
15
19
  validates :page, presence: true, unless: -> { external_url.present? }
@@ -48,10 +52,10 @@ module Panda
48
52
  errors.add(:external_url, "must be a valid page or external link, neither are set")
49
53
  end
50
54
 
51
- if !page.nil? && !external_url.nil?
52
- errors.add(:page, "must be a valid page or external link, both are set")
53
- errors.add(:external_url, "must be a valid page or external link, both are set")
54
- end
55
+ return unless !page.nil? && !external_url.nil?
56
+
57
+ errors.add(:page, "must be a valid page or external link, both are set")
58
+ errors.add(:external_url, "must be a valid page or external link, both are set")
55
59
  end
56
60
  end
57
61
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "awesome_nested_set"
2
4
 
3
5
  module Panda
@@ -8,7 +10,8 @@ module Panda
8
10
  self.implicit_order_column = "lft"
9
11
 
10
12
  belongs_to :template, class_name: "Panda::CMS::Template", foreign_key: :panda_cms_template_id
11
- has_many :block_contents, class_name: "Panda::CMS::BlockContent", foreign_key: :panda_cms_page_id, dependent: :destroy
13
+ has_many :block_contents, class_name: "Panda::CMS::BlockContent", foreign_key: :panda_cms_page_id,
14
+ dependent: :destroy
12
15
  has_many :blocks, through: :block_contents
13
16
  has_many :menu_items, foreign_key: :panda_cms_page_id, class_name: "Panda::CMS::MenuItem", inverse_of: :page
14
17
  has_many :menus, through: :menu_items
@@ -19,7 +22,7 @@ module Panda
19
22
 
20
23
  validates :path,
21
24
  presence: true,
22
- format: {with: /\A\/.*\z/, message: "must start with a forward slash"}
25
+ format: {with: %r{\A/.*\z}, message: "must start with a forward slash"}
23
26
 
24
27
  validate :validate_unique_path_in_scope
25
28
 
@@ -62,12 +65,11 @@ module Panda
62
65
  # Find any other pages with the same path
63
66
  other_page = self.class.where(path: path).where.not(id: id).first
64
67
 
65
- if other_page
66
- # If there's another page with the same path, check if it has a different parent
67
- if other_page.parent_id == parent_id
68
- errors.add(:path, "has already been taken in this section")
69
- end
70
- end
68
+ return unless other_page
69
+ # If there's another page with the same path, check if it has a different parent
70
+ return unless other_page.parent_id == parent_id
71
+
72
+ errors.add(:path, "has already been taken in this section")
71
73
  end
72
74
 
73
75
  #
@@ -88,10 +90,10 @@ module Panda
88
90
  page_existing_block_ids = block_contents.map { |bc| bc.block.id }
89
91
  required_block_ids = template_block_ids - page_existing_block_ids
90
92
 
91
- if required_block_ids.count > 0
92
- required_block_ids.each do |block_id|
93
- Panda::CMS::BlockContent.find_or_create_by!(page: self, panda_cms_block_id: block_id, content: "")
94
- end
93
+ return unless required_block_ids.count.positive?
94
+
95
+ required_block_ids.each do |block_id|
96
+ Panda::CMS::BlockContent.find_or_create_by!(page: self, panda_cms_block_id: block_id, content: "")
95
97
  end
96
98
  end
97
99
 
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "awesome_nested_set"
2
4
 
3
5
  module Panda
4
6
  module CMS
5
7
  class Post < ApplicationRecord
6
- include ::Panda::CMS::EditorJsContent
8
+ include ::Panda::Editor::Content
7
9
 
8
10
  after_commit :clear_menu_cache
9
11
  before_validation :format_slug
10
12
 
11
13
  self.table_name = "panda_cms_posts"
12
14
 
13
- belongs_to :user, class_name: "Panda::CMS::User"
14
- belongs_to :author, class_name: "Panda::CMS::User", optional: true
15
+ belongs_to :user, class_name: "Panda::Core::User"
16
+ belongs_to :author, class_name: "Panda::Core::User", optional: true
15
17
  has_many :block_contents, as: :blockable, dependent: :destroy
16
18
  has_many :blocks, through: :block_contents
17
19
 
@@ -47,11 +49,13 @@ module Panda
47
49
 
48
50
  def year
49
51
  return nil unless slug.match?(%r{\A/\d{4}/})
52
+
50
53
  slug.split("/")[1]
51
54
  end
52
55
 
53
56
  def month
54
57
  return nil unless slug.match?(%r{\A/\d{4}/\d{2}/})
58
+
55
59
  slug.split("/")[2]
56
60
  end
57
61
 
@@ -96,13 +100,13 @@ module Panda
96
100
  self.slug = CGI.unescape(slug.strip.gsub(%r{^/+|/+$}, ""))
97
101
 
98
102
  # Handle the case where we already have a properly formatted slug
99
- if slug.match?(%r{\A\d{4}/\d{2}/[^/]+\z})
100
- return self.slug = "/#{slug}"
101
- end
103
+ return self.slug = "/#{slug}" if slug.match?(%r{\A\d{4}/\d{2}/[^/]+\z})
102
104
 
103
105
  # Handle the case where we have a date-prefixed slug (from JS)
104
- if (match = slug.match(%r{\A(\d{4})-(\d{2})-(.+)\z}))
105
- year, month, base_slug = match[1], match[2], match[3]
106
+ if (match = slug.match(/\A(\d{4})-(\d{2})-(.+)\z/))
107
+ year = match[1]
108
+ month = match[2]
109
+ base_slug = match[3]
106
110
  return self.slug = "/#{year}/#{month}/#{base_slug}"
107
111
  end
108
112
 
@@ -1,16 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Redirect < ApplicationRecord
4
6
  belongs_to :origin_page, class_name: "Panda::CMS::Page", foreign_key: :origin_panda_cms_page_id, optional: true
5
- belongs_to :destination_page, class_name: "Panda::CMS::Page", foreign_key: :destination_panda_cms_page_id, optional: true
7
+ belongs_to :destination_page, class_name: "Panda::CMS::Page", foreign_key: :destination_panda_cms_page_id,
8
+ optional: true
6
9
 
7
10
  validates :status_code, presence: true
8
11
  validates :visits, presence: true
9
12
  validates :origin_path, presence: true
10
13
  validates :destination_path, presence: true
11
14
 
12
- validates :origin_path, format: {with: /\A\/.*\z/, message: "must start with a forward slash"}
13
- validates :destination_path, format: {with: /\A\/.*\z/, message: "must start with a forward slash"}
15
+ validates :origin_path, format: {with: %r{\A/.*\z}, message: "must start with a forward slash"}
16
+ validates :destination_path, format: {with: %r{\A/.*\z}, message: "must start with a forward slash"}
14
17
  end
15
18
  end
16
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  # Represents a template in the Panda CMS application.
@@ -5,8 +7,10 @@ module Panda
5
7
  self.table_name = "panda_cms_templates"
6
8
 
7
9
  # Associations
8
- has_many :pages, class_name: "Panda::CMS::Page", dependent: :restrict_with_error, inverse_of: :template, foreign_key: :panda_cms_template_id, counter_cache: :pages_count
9
- has_many :blocks, class_name: "Panda::CMS::Block", dependent: :restrict_with_error, inverse_of: :template, foreign_key: :panda_cms_template_id
10
+ has_many :pages, class_name: "Panda::CMS::Page", dependent: :restrict_with_error, inverse_of: :template,
11
+ foreign_key: :panda_cms_template_id, counter_cache: :pages_count
12
+ has_many :blocks, class_name: "Panda::CMS::Block", dependent: :restrict_with_error, inverse_of: :template,
13
+ foreign_key: :panda_cms_template_id
10
14
  has_many :block_contents, through: :blocks
11
15
 
12
16
  # Validations
@@ -15,12 +19,12 @@ module Panda
15
19
  validates :file_path,
16
20
  presence: true,
17
21
  uniqueness: true,
18
- format: {with: /\Alayouts\/.*\z/, message: "must be a valid layout file path"}
22
+ format: {with: %r{\Alayouts/.*\z}, message: "must be a valid layout file path"}
19
23
 
20
24
  validate :validate_template_file_exists
21
25
 
22
26
  # Scopes
23
- scope :available, -> {
27
+ scope :available, lambda {
24
28
  where("max_uses IS NULL OR (max_uses > 0 AND pages_count < max_uses)")
25
29
  }
26
30
 
@@ -43,7 +47,7 @@ module Panda
43
47
  # Matches:
44
48
  # Panda::CMS::RichTextComponent.new(key: :value)
45
49
  # Panda::CMS::RichTextComponent.new key: :value, key: value
46
- line.match(/Panda::CMS::([a-zA-Z]+)Component\.new[ \(]+([^\)]+)[\)]*/) do |match|
50
+ line.match(/Panda::CMS::([a-zA-Z]+)Component\.new[ (]+([^)]+)\)*/) do |match|
47
51
  # Extract the hash values
48
52
  template_path = file.gsub("app/views/", "").gsub(".html.erb", "")
49
53
  template_name = template_path.gsub("layouts/", "").titleize
@@ -68,7 +72,8 @@ module Panda
68
72
  # Create the block if it doesn't exist
69
73
  # TODO: +/- the output if it's created or removed
70
74
  begin
71
- block = Panda::CMS::Block.find_or_create_by!(template: template, kind: block_kind, key: block_name) do |block|
75
+ block = Panda::CMS::Block.find_or_create_by!(template: template, kind: block_kind,
76
+ key: block_name) do |block|
72
77
  block.name = block_name.titleize
73
78
  end
74
79
  rescue ActiveRecord::RecordInvalid => e
@@ -108,7 +113,7 @@ module Panda
108
113
  # Extract the file path from the Rails root
109
114
  file_path = file.to_s.sub("#{Rails.root}/app/views/", "").sub(".html.erb", "")
110
115
 
111
- next if file_path == "layouts/application" || file_path == "layouts/mailer"
116
+ next if ["layouts/application", "layouts/mailer"].include?(file_path)
112
117
 
113
118
  # Find or create the template based on the file path
114
119
  find_or_create_by(file_path: file_path) do |t|
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module CMS
3
5
  class Visit < ApplicationRecord
4
6
  belongs_to :page, class_name: "Panda::CMS::Page", foreign_key: :panda_cms_page_id, optional: true
5
- belongs_to :user, class_name: "Panda::CMS::User", foreign_key: :user_id, optional: true
7
+ belongs_to :user, class_name: "Panda::Core::User", foreign_key: :user_id, optional: true
6
8
  belongs_to :redirect, class_name: "Panda::CMS::Redirect", foreign_key: :redirect_id, optional: true
7
9
  end
8
10
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Panda
2
4
  module Social
3
5
  class InstagramPost < ApplicationRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "http"
2
4
  require "down"
3
5
 
@@ -5,7 +7,7 @@ module Panda
5
7
  module Social
6
8
  class InstagramFeedService
7
9
  GRAPH_API_VERSION = "v19.0"
8
- GRAPH_API_BASE_URL = "https://graph.instagram.com/#{GRAPH_API_VERSION}"
10
+ GRAPH_API_BASE_URL = "https://graph.instagram.com/#{GRAPH_API_VERSION}".freeze
9
11
 
10
12
  def initialize(access_token)
11
13
  @access_token = access_token
@@ -0,0 +1,6 @@
1
+ <%= render "shared/header" %>
2
+ <h1><%= @page.title %></h1>
3
+ <h2>Different Page Layout</h2>
4
+
5
+ <%= yield %>
6
+ <%= render "shared/footer" %>