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
@@ -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 {
|
3
|
+
import { application } from "@hotwired/stimulus-loading"
|
4
4
|
|
5
|
-
|
5
|
+
console.debug("[Panda CMS] Using shared Stimulus application...")
|
6
6
|
|
7
|
-
|
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
|
-
|
31
|
-
import
|
32
|
-
pandaCmsApplication.register("
|
33
|
-
|
34
|
-
|
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,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"]
|
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,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",
|
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,17 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Panda
|
2
4
|
module CMS
|
3
|
-
class Current <
|
4
|
-
|
5
|
-
attribute :
|
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 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,
|
9
|
-
|
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: [
|
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
|
-
|
47
|
-
|
48
|
-
|
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,
|
12
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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,
|
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:
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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::
|
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::
|
14
|
-
belongs_to :author, class_name: "Panda::
|
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(
|
105
|
-
year
|
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,
|
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:
|
13
|
-
validates :destination_path, format: {with:
|
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,
|
9
|
-
|
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:
|
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[
|
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,
|
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
|
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::
|
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
|
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
|