panda_cms 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +71 -0
- data/Rakefile +8 -0
- data/app/assets/builds/panda_cms.css +1 -0
- data/app/assets/config/panda_cms_manifest.js +1 -0
- data/app/assets/stylesheets/panda_cms/application.tailwind.css +61 -0
- data/app/builders/panda_cms/form_builder.rb +118 -0
- data/app/components/panda_cms/admin/button_component.rb +65 -0
- data/app/components/panda_cms/admin/container_component.html.erb +13 -0
- data/app/components/panda_cms/admin/container_component.rb +11 -0
- data/app/components/panda_cms/admin/flash_message_component.html.erb +30 -0
- data/app/components/panda_cms/admin/flash_message_component.rb +44 -0
- data/app/components/panda_cms/admin/heading_component.rb +43 -0
- data/app/components/panda_cms/admin/panel_component.html.erb +7 -0
- data/app/components/panda_cms/admin/panel_component.rb +11 -0
- data/app/components/panda_cms/admin/slideover_component.html.erb +9 -0
- data/app/components/panda_cms/admin/slideover_component.rb +13 -0
- data/app/components/panda_cms/admin/statistics_component.html.erb +4 -0
- data/app/components/panda_cms/admin/statistics_component.rb +15 -0
- data/app/components/panda_cms/admin/tab_bar_component.html.erb +35 -0
- data/app/components/panda_cms/admin/tab_bar_component.rb +13 -0
- data/app/components/panda_cms/admin/table_component.html.erb +21 -0
- data/app/components/panda_cms/admin/table_component.rb +43 -0
- data/app/components/panda_cms/admin/tag_component.rb +33 -0
- data/app/components/panda_cms/admin/user_activity_component.html.erb +5 -0
- data/app/components/panda_cms/admin/user_activity_component.rb +19 -0
- data/app/components/panda_cms/admin/user_display_component.html.erb +11 -0
- data/app/components/panda_cms/admin/user_display_component.rb +19 -0
- data/app/components/panda_cms/grid_component.html.erb +6 -0
- data/app/components/panda_cms/grid_component.rb +13 -0
- data/app/components/panda_cms/menu_component.html.erb +3 -0
- data/app/components/panda_cms/menu_component.rb +18 -0
- data/app/components/panda_cms/page_menu_component.html.erb +24 -0
- data/app/components/panda_cms/page_menu_component.rb +24 -0
- data/app/components/panda_cms/rich_text_component.html.erb +40 -0
- data/app/components/panda_cms/rich_text_component.rb +35 -0
- data/app/components/panda_cms/text_component.rb +67 -0
- data/app/constraints/panda_cms/admin_constraint.rb +16 -0
- data/app/controllers/panda_cms/admin/block_contents_controller.rb +44 -0
- data/app/controllers/panda_cms/admin/dashboard_controller.rb +31 -0
- data/app/controllers/panda_cms/admin/files_controller.rb +19 -0
- data/app/controllers/panda_cms/admin/forms_controller.rb +51 -0
- data/app/controllers/panda_cms/admin/menus_controller.rb +81 -0
- data/app/controllers/panda_cms/admin/pages_controller.rb +88 -0
- data/app/controllers/panda_cms/admin/posts_controller.rb +34 -0
- data/app/controllers/panda_cms/admin/sessions_controller.rb +83 -0
- data/app/controllers/panda_cms/admin/settings/bulk_editor_controller.rb +35 -0
- data/app/controllers/panda_cms/admin/settings_controller.rb +18 -0
- data/app/controllers/panda_cms/application_controller.rb +55 -0
- data/app/controllers/panda_cms/errors_controller.rb +31 -0
- data/app/controllers/panda_cms/form_submissions_controller.rb +21 -0
- data/app/controllers/panda_cms/pages_controller.rb +56 -0
- data/app/controllers/panda_cms/posts_controller.rb +17 -0
- data/app/helpers/panda_cms/admin/files_helper.rb +4 -0
- data/app/helpers/panda_cms/admin/pages_helper.rb +4 -0
- data/app/helpers/panda_cms/application_helper.rb +96 -0
- data/app/helpers/panda_cms/pages_helper.rb +4 -0
- data/app/helpers/panda_cms/theme_helper.rb +16 -0
- data/app/javascript/base.js +37 -0
- data/app/javascript/controllers/menu_controller.js +19 -0
- data/app/javascript/controllers/text_controller.js +78 -0
- data/app/javascript/controllers/text_field_update_controller.js +23 -0
- data/app/javascript/vendor/stimulus-components-rails-nested-form.js +2 -0
- data/app/javascript/vendor/tailwindcss-stimulus-components.js +2 -0
- data/app/jobs/panda_cms/application_job.rb +4 -0
- data/app/jobs/panda_cms/record_visit_job.rb +29 -0
- data/app/lib/panda_cms/bulk_editor.rb +169 -0
- data/app/lib/panda_cms/demo_site_generator.rb +70 -0
- data/app/lib/panda_cms/slug.rb +22 -0
- data/app/mailers/panda_cms/application_mailer.rb +6 -0
- data/app/mailers/panda_cms/form_mailer.rb +19 -0
- data/app/models/panda_cms/application_record.rb +5 -0
- data/app/models/panda_cms/block.rb +32 -0
- data/app/models/panda_cms/block_content.rb +16 -0
- data/app/models/panda_cms/block_content_version.rb +6 -0
- data/app/models/panda_cms/breadcrumb.rb +10 -0
- data/app/models/panda_cms/current.rb +15 -0
- data/app/models/panda_cms/form.rb +7 -0
- data/app/models/panda_cms/form_submission.rb +5 -0
- data/app/models/panda_cms/menu.rb +50 -0
- data/app/models/panda_cms/menu_item.rb +56 -0
- data/app/models/panda_cms/page.rb +81 -0
- data/app/models/panda_cms/page_version.rb +6 -0
- data/app/models/panda_cms/post.rb +25 -0
- data/app/models/panda_cms/post_version.rb +6 -0
- data/app/models/panda_cms/redirect.rb +9 -0
- data/app/models/panda_cms/template.rb +117 -0
- data/app/models/panda_cms/template_version.rb +6 -0
- data/app/models/panda_cms/user.rb +15 -0
- data/app/models/panda_cms/version.rb +6 -0
- data/app/models/panda_cms/visit.rb +7 -0
- data/app/views/layouts/panda_cms/application.html.erb +44 -0
- data/app/views/layouts/panda_cms/public.html.erb +3 -0
- data/app/views/panda_cms/admin/dashboard/show.html.erb +11 -0
- data/app/views/panda_cms/admin/files/index.html.erb +124 -0
- data/app/views/panda_cms/admin/files/show.html.erb +2 -0
- data/app/views/panda_cms/admin/forms/edit.html.erb +0 -0
- data/app/views/panda_cms/admin/forms/index.html.erb +13 -0
- data/app/views/panda_cms/admin/forms/new.html.erb +16 -0
- data/app/views/panda_cms/admin/forms/show.html.erb +35 -0
- data/app/views/panda_cms/admin/menus/_form.html.erb +21 -0
- data/app/views/panda_cms/admin/menus/_menu_item_fields.html.erb +7 -0
- data/app/views/panda_cms/admin/menus/edit.html.erb +58 -0
- data/app/views/panda_cms/admin/menus/index.html.erb +10 -0
- data/app/views/panda_cms/admin/menus/new.html.erb +5 -0
- data/app/views/panda_cms/admin/pages/edit.html.erb +26 -0
- data/app/views/panda_cms/admin/pages/index.html.erb +16 -0
- data/app/views/panda_cms/admin/pages/new.html.erb +16 -0
- data/app/views/panda_cms/admin/pages/show.html.erb +1 -0
- data/app/views/panda_cms/admin/posts/index.html.erb +16 -0
- data/app/views/panda_cms/admin/sessions/new.html.erb +18 -0
- data/app/views/panda_cms/admin/settings/bulk_editor/new.html.erb +68 -0
- data/app/views/panda_cms/admin/settings/index.html.erb +19 -0
- data/app/views/panda_cms/admin/shared/_breadcrumbs.html.erb +28 -0
- data/app/views/panda_cms/admin/shared/_flash.html.erb +5 -0
- data/app/views/panda_cms/admin/shared/_sidebar.html.erb +45 -0
- data/app/views/panda_cms/form_mailer/notification_email.html.erb +11 -0
- data/app/views/panda_cms/shared/_favicons.html.erb +9 -0
- data/app/views/panda_cms/shared/_footer.html.erb +2 -0
- data/app/views/panda_cms/shared/_header.html.erb +15 -0
- data/config/importmap.rb +9 -0
- data/config/initializers/panda_cms/form_errors.rb +38 -0
- data/config/initializers/panda_cms/healthcheck_log_silencer.rb +11 -0
- data/config/initializers/panda_cms.rb +52 -0
- data/config/locales/en.yml +29 -0
- data/config/routes.rb +43 -0
- data/config/tailwind.config.js +35 -0
- data/config/tailwind.embed.config.js +20 -0
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
- data/db/migrate/20240804110225_add_status_to_panda_cms_pages.rb +7 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
- data/db/seeds.rb +4 -0
- data/lib/generators/panda_cms/install_generator.rb +24 -0
- data/lib/panda_cms/engine.rb +167 -0
- data/lib/panda_cms/exceptions_app.rb +24 -0
- data/lib/panda_cms/version.rb +3 -0
- data/lib/panda_cms.rb +15 -0
- data/lib/tasks/panda_cms.rake +92 -0
- data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
- data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
- data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
- data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
- data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
- data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
- data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
- data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
- data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
- data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
- data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
- data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
- data/public/panda-cms-assets/favicons/favicon.ico +0 -0
- data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
- data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
- data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
- data/public/panda-cms-assets/javascripts/base.js +37 -0
- data/public/panda-cms-assets/javascripts/controllers/menu_controller.js +19 -0
- data/public/panda-cms-assets/javascripts/controllers/text_field_update_controller.js +23 -0
- data/public/panda-cms-assets/javascripts/embed/editable.js +308 -0
- data/public/panda-cms-assets/javascripts/vendor/stimulus-components-rails-nested-form.js +2 -0
- data/public/panda-cms-assets/javascripts/vendor/stimulus-loading.js +113 -0
- data/public/panda-cms-assets/javascripts/vendor/tailwindcss-stimulus-components.js +2 -0
- data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
- data/public/panda-cms-assets/panda-nav.png +0 -0
- metadata +1034 -0
@@ -0,0 +1,308 @@
|
|
1
|
+
/**
|
2
|
+
* Represents a controller for managing editable content within an iframe.
|
3
|
+
* @class
|
4
|
+
*/
|
5
|
+
class EditableController {
|
6
|
+
/**
|
7
|
+
* Represents the constructor for the Editable class.
|
8
|
+
* @param {HTMLIFrameElement} frame - The iFrame element to be used for editing.
|
9
|
+
*/
|
10
|
+
constructor(pageId, frame) {
|
11
|
+
this.pageId = pageId;
|
12
|
+
this.frame = frame;
|
13
|
+
this.frame.style.display = "none";
|
14
|
+
this.csrfToken = document.querySelector('meta[name="csrf-token"]').content;
|
15
|
+
|
16
|
+
this.frame.addEventListener("load", () => {
|
17
|
+
this.frameDocument =
|
18
|
+
this.frame.contentDocument || this.frame.contentWindow.document;
|
19
|
+
this.body = this.frameDocument.body;
|
20
|
+
this.head = this.frameDocument.head;
|
21
|
+
this.loadEvents();
|
22
|
+
});
|
23
|
+
}
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Load events for the editable iFrame
|
27
|
+
*/
|
28
|
+
loadEvents() {
|
29
|
+
console.debug("[Panda CMS] iFrame loaded...");
|
30
|
+
|
31
|
+
this.embedPlainTextEditors();
|
32
|
+
|
33
|
+
this.styleRichTextEditor();
|
34
|
+
this.embedRichTextEditor();
|
35
|
+
}
|
36
|
+
|
37
|
+
setFrameVisible() {
|
38
|
+
console.log("[Panda CMS] Setting iFrame to visible...");
|
39
|
+
this.frame.style.display = "";
|
40
|
+
}
|
41
|
+
|
42
|
+
stylePlainTextEditor(element, status) {
|
43
|
+
console.log(
|
44
|
+
`[Panda CMS] Styling plain text editor ${element.id} as ${status}...`
|
45
|
+
);
|
46
|
+
|
47
|
+
if (status == "initial") {
|
48
|
+
element.style.border = "1px dashed #ccc";
|
49
|
+
element.style.outline = "none";
|
50
|
+
element.style.cursor = "pointer";
|
51
|
+
element.style.transition = "background 500ms linear";
|
52
|
+
element.style.backgroundColor = "inherit";
|
53
|
+
} else if (status == "success") {
|
54
|
+
element.style.backgroundColor = "#66bd6a50";
|
55
|
+
} else if (status == "error") {
|
56
|
+
element.style.backgroundColor = "#dc354550";
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
embedPlainTextEditors() {
|
61
|
+
var elements = this.body.querySelectorAll(
|
62
|
+
'[data-editable-kind="plain_text"]'
|
63
|
+
);
|
64
|
+
|
65
|
+
if (elements.length == 0) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
|
69
|
+
elements.forEach((element) => {
|
70
|
+
this.stylePlainTextEditor(element, "initial");
|
71
|
+
|
72
|
+
// TODO: If content hasn't changed, don't call save
|
73
|
+
// Bind initial click handler and then auto-save on blur?
|
74
|
+
|
75
|
+
// This binds auto-save...
|
76
|
+
element.addEventListener("blur", (event) => {
|
77
|
+
var target = event.target;
|
78
|
+
var pageId = target.getAttribute("data-editable-page-id");
|
79
|
+
var blockContentId = target.getAttribute(
|
80
|
+
"data-editable-block-content-id"
|
81
|
+
);
|
82
|
+
|
83
|
+
fetch(`/admin/pages/${pageId}/block_contents/${blockContentId}`, {
|
84
|
+
method: "PATCH",
|
85
|
+
headers: {
|
86
|
+
"Content-Type": "application/json",
|
87
|
+
"X-CSRF-Token": document
|
88
|
+
.querySelector('meta[name="csrf-token"]')
|
89
|
+
.getAttribute("content"),
|
90
|
+
},
|
91
|
+
body: JSON.stringify({ content: target.innerHTML }),
|
92
|
+
})
|
93
|
+
.then((response) => response.json())
|
94
|
+
.then((data) => {
|
95
|
+
this.stylePlainTextEditor(target, "success");
|
96
|
+
setTimeout(() => {
|
97
|
+
this.stylePlainTextEditor(target, "initial");
|
98
|
+
}, 1000);
|
99
|
+
})
|
100
|
+
.catch((error) => {
|
101
|
+
this.stylePlainTextEditor(target, "error");
|
102
|
+
setTimeout(() => {
|
103
|
+
this.stylePlainTextEditor(target, "initial");
|
104
|
+
}, 1000);
|
105
|
+
alert("Error:", error);
|
106
|
+
console.log(error);
|
107
|
+
});
|
108
|
+
});
|
109
|
+
});
|
110
|
+
|
111
|
+
console.debug(
|
112
|
+
"[Panda CMS] Dispatching event: pandaCmsPlainTextEditorLoaded"
|
113
|
+
);
|
114
|
+
|
115
|
+
// Let the parent know that the external resources have been loaded
|
116
|
+
this.frameDocument.dispatchEvent(
|
117
|
+
new Event("pandaCmsPlainTextEditorLoaded")
|
118
|
+
);
|
119
|
+
}
|
120
|
+
|
121
|
+
embedRichTextEditor() {
|
122
|
+
if (this.body.getElementsByClassName("ql-editor").length == 0) {
|
123
|
+
this.setFrameVisible();
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
|
127
|
+
this.addStylesheet(
|
128
|
+
this.frameDocument,
|
129
|
+
this.head,
|
130
|
+
"https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.bubble.css"
|
131
|
+
)
|
132
|
+
.then(() => {
|
133
|
+
return this.loadScript(
|
134
|
+
this.frameDocument,
|
135
|
+
this.head,
|
136
|
+
"https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"
|
137
|
+
);
|
138
|
+
})
|
139
|
+
.then(() => {
|
140
|
+
return this.loadScript(
|
141
|
+
this.frameDocument,
|
142
|
+
this.head,
|
143
|
+
"https://unpkg.com/quill-image-compress@1.2.11/dist/quill.imageCompressor.min.js"
|
144
|
+
);
|
145
|
+
})
|
146
|
+
.then(() => {
|
147
|
+
return this.loadScript(
|
148
|
+
this.frameDocument,
|
149
|
+
this.head,
|
150
|
+
"https://unpkg.com/quill-magic-url@3.0.0/dist/index.js"
|
151
|
+
);
|
152
|
+
})
|
153
|
+
.then(() => {
|
154
|
+
return this.loadScript(
|
155
|
+
this.frameDocument,
|
156
|
+
this.head,
|
157
|
+
"https://cdn.jsdelivr.net/npm/quill-markdown-shortcuts@latest/dist/markdownShortcuts.js"
|
158
|
+
);
|
159
|
+
})
|
160
|
+
.then(() => {
|
161
|
+
console.debug(
|
162
|
+
"[Panda CMS] Dispatching event: pandaCmsRichTextEditorLoaded"
|
163
|
+
);
|
164
|
+
|
165
|
+
// Let the parent know that the external resources have been loaded
|
166
|
+
this.frameDocument.dispatchEvent(
|
167
|
+
new Event("pandaCmsRichTextEditorLoaded")
|
168
|
+
);
|
169
|
+
|
170
|
+
// Autosave the editable elements
|
171
|
+
this.enableRichTextEditorAutoSave();
|
172
|
+
|
173
|
+
// This prevents the flash of content before the iFrame is ready
|
174
|
+
this.setFrameVisible();
|
175
|
+
});
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* Autosave the editable elements in the iFrame
|
180
|
+
* @todo #TODO Only do this when we have "autosave" mode enabled?
|
181
|
+
*/
|
182
|
+
enableRichTextEditorAutoSave() {
|
183
|
+
// Grab each element that's editable and append a save handler to it
|
184
|
+
var elements = this.frameDocument.querySelectorAll(".ql-editor");
|
185
|
+
elements.forEach((element) => {
|
186
|
+
element.addEventListener("blur", (event) => {
|
187
|
+
var target = event.target;
|
188
|
+
var blockContentId = target.parentElement.getAttribute(
|
189
|
+
"data-block-content-id"
|
190
|
+
);
|
191
|
+
|
192
|
+
this.bindSaveHandler(blockContentId, target.innerHTML);
|
193
|
+
});
|
194
|
+
});
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Styles the Quill rich text editor elements in the iFrame
|
199
|
+
*/
|
200
|
+
styleRichTextEditor() {
|
201
|
+
if (this.body.getElementsByClassName("ql-editor").length == 0) {
|
202
|
+
return;
|
203
|
+
}
|
204
|
+
|
205
|
+
var style = this.frameDocument.createElement("style");
|
206
|
+
style.innerHTML = `.ql-container,
|
207
|
+
.ql-editor, .ql-editor * {
|
208
|
+
margin: inherit !important;
|
209
|
+
padding: inherit !important;
|
210
|
+
font-size: inherit !important;
|
211
|
+
line-height: inherit !important;
|
212
|
+
font-family: inherit !important;
|
213
|
+
}
|
214
|
+
|
215
|
+
.ql-toolbar {
|
216
|
+
background-color: #282e3270;
|
217
|
+
border-radius: 10px;
|
218
|
+
}
|
219
|
+
|
220
|
+
.ql-editor:hover, .ql-container:hover {
|
221
|
+
cursor: pointer !important;
|
222
|
+
}
|
223
|
+
|
224
|
+
.ql-editor:hover, .ql-editor:focus, .ql-editor:active {
|
225
|
+
background-color: #e1effa;
|
226
|
+
cursor: pointer !important;
|
227
|
+
font-size: inherit !important;
|
228
|
+
-webkit-transition: background-color 500ms linear;
|
229
|
+
-ms-transition: background-color 500ms linear;
|
230
|
+
transition: background-color 500ms linear;
|
231
|
+
}
|
232
|
+
|
233
|
+
.ql-editor.success {
|
234
|
+
background-color: #66bd6a50 !important;
|
235
|
+
-webkit-transition: background-color 1000ms linear;
|
236
|
+
-ms-transition: background-color 1000ms linear;
|
237
|
+
transition: background-color 1000ms linear;
|
238
|
+
}
|
239
|
+
|
240
|
+
.ql-tooltip {
|
241
|
+
z-index: 999;
|
242
|
+
min-width: 90vw;
|
243
|
+
}`;
|
244
|
+
|
245
|
+
this.head.append(style);
|
246
|
+
}
|
247
|
+
|
248
|
+
bindSaveHandler(blockContentId, content) {
|
249
|
+
console.debug(`[Panda CMS] Calling save handler for ${blockContentId}...`);
|
250
|
+
fetch(`/admin/pages/${this.pageId}/block_contents/${blockContentId}`, {
|
251
|
+
method: "PATCH",
|
252
|
+
headers: {
|
253
|
+
"Content-Type": "application/json",
|
254
|
+
"X-CSRF-Token": this.csrfToken,
|
255
|
+
},
|
256
|
+
body: JSON.stringify({ content: content }),
|
257
|
+
})
|
258
|
+
.then((response) => response.json())
|
259
|
+
.then((data) => {
|
260
|
+
var editableBlock = this.frameDocument.querySelector(
|
261
|
+
`[data-block-content-id="${blockContentId}"] .ql-editor`
|
262
|
+
);
|
263
|
+
editableBlock.classList.add("success");
|
264
|
+
setTimeout(() => {
|
265
|
+
editableBlock.classList.remove("success");
|
266
|
+
}, 1500);
|
267
|
+
})
|
268
|
+
.catch((error) => {
|
269
|
+
console.log(error);
|
270
|
+
alert("Error updating. Please contact the support team!", error);
|
271
|
+
});
|
272
|
+
}
|
273
|
+
|
274
|
+
addStylesheet(frameDocument, head, href) {
|
275
|
+
return new Promise(function (resolve, reject) {
|
276
|
+
let link = frameDocument.createElement("link");
|
277
|
+
link.rel = "stylesheet";
|
278
|
+
link.href = href;
|
279
|
+
link.media = "none";
|
280
|
+
head.append(link);
|
281
|
+
|
282
|
+
link.onload = () => {
|
283
|
+
if (link.media != "all") {
|
284
|
+
link.media = "all";
|
285
|
+
}
|
286
|
+
console.debug(`[Panda CMS] Stylesheet loaded: ${href}`);
|
287
|
+
resolve(link);
|
288
|
+
};
|
289
|
+
link.onerror = () =>
|
290
|
+
reject(new Error(`[Panda CMS] Stylesheet load error for ${href}`));
|
291
|
+
});
|
292
|
+
}
|
293
|
+
|
294
|
+
loadScript(frameDocument, head, src) {
|
295
|
+
return new Promise(function (resolve, reject) {
|
296
|
+
let script = frameDocument.createElement("script");
|
297
|
+
script.src = src;
|
298
|
+
head.append(script);
|
299
|
+
|
300
|
+
script.onload = () => {
|
301
|
+
console.debug(`[Panda CMS] Script loaded: ${src}`);
|
302
|
+
resolve(script);
|
303
|
+
};
|
304
|
+
script.onerror = () =>
|
305
|
+
reject(new Error(`[Panda CMS] Script load error for ${src}`));
|
306
|
+
});
|
307
|
+
}
|
308
|
+
}
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import{Controller as e}from"@hotwired/stimulus";const t=class _RailsNestedForm extends e{add(e){e.preventDefault();const t=this.templateTarget.innerHTML.replace(/NEW_RECORD/g,(new Date).getTime().toString());this.targetTarget.insertAdjacentHTML("beforebegin",t);const r=new CustomEvent("rails-nested-form:add",{bubbles:!0});this.element.dispatchEvent(r)}remove(e){e.preventDefault();const t=e.target.closest(this.wrapperSelectorValue);if(t.dataset.newRecord==="true")t.remove();else{t.style.display="none";const e=t.querySelector("input[name*='_destroy']");e.value="1"}const r=new CustomEvent("rails-nested-form:remove",{bubbles:!0});this.element.dispatchEvent(r)}};t.targets=["target","template"],t.values={wrapperSelector:{type:String,default:".nested-form-wrapper"}};let r=t;export{r as default};
|
2
|
+
|
@@ -0,0 +1,113 @@
|
|
1
|
+
// FIXME: es-module-shim won't shim the dynamic import without this explicit import
|
2
|
+
import "@hotwired/stimulus";
|
3
|
+
|
4
|
+
const controllerAttribute = "data-controller";
|
5
|
+
|
6
|
+
// Eager load all controllers registered beneath the `under` path in the import map to the passed application instance.
|
7
|
+
export function eagerLoadControllersFrom(under, application) {
|
8
|
+
const paths = Object.keys(parseImportmapJson()).filter((path) =>
|
9
|
+
path.match(new RegExp(`^${under}/.*_controller$`))
|
10
|
+
);
|
11
|
+
paths.forEach((path) => registerControllerFromPath(path, under, application));
|
12
|
+
}
|
13
|
+
|
14
|
+
function parseImportmapJson() {
|
15
|
+
return JSON.parse(document.querySelector("script[type=importmap]").text)
|
16
|
+
.imports;
|
17
|
+
}
|
18
|
+
|
19
|
+
function registerControllerFromPath(path, under, application) {
|
20
|
+
const name = path
|
21
|
+
.replace(new RegExp(`^${under}/`), "")
|
22
|
+
.replace("_controller", "")
|
23
|
+
.replace(/\//g, "--")
|
24
|
+
.replace(/_/g, "-");
|
25
|
+
|
26
|
+
if (canRegisterController(name, application)) {
|
27
|
+
import(path)
|
28
|
+
.then((module) => registerController(name, module, application))
|
29
|
+
.catch((error) =>
|
30
|
+
console.error(`Failed to register controller: ${name} (${path})`, error)
|
31
|
+
);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
// Lazy load controllers registered beneath the `under` path in the import map to the passed application instance.
|
36
|
+
export function lazyLoadControllersFrom(
|
37
|
+
under,
|
38
|
+
application,
|
39
|
+
element = document
|
40
|
+
) {
|
41
|
+
lazyLoadExistingControllers(under, application, element);
|
42
|
+
lazyLoadNewControllers(under, application, element);
|
43
|
+
}
|
44
|
+
|
45
|
+
function lazyLoadExistingControllers(under, application, element) {
|
46
|
+
queryControllerNamesWithin(element).forEach((controllerName) =>
|
47
|
+
loadController(controllerName, under, application)
|
48
|
+
);
|
49
|
+
}
|
50
|
+
|
51
|
+
function lazyLoadNewControllers(under, application, element) {
|
52
|
+
new MutationObserver((mutationsList) => {
|
53
|
+
for (const { attributeName, target, type } of mutationsList) {
|
54
|
+
switch (type) {
|
55
|
+
case "attributes": {
|
56
|
+
if (
|
57
|
+
attributeName == controllerAttribute &&
|
58
|
+
target.getAttribute(controllerAttribute)
|
59
|
+
) {
|
60
|
+
extractControllerNamesFrom(target).forEach((controllerName) =>
|
61
|
+
loadController(controllerName, under, application)
|
62
|
+
);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
case "childList": {
|
67
|
+
lazyLoadExistingControllers(under, application, target);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}).observe(element, {
|
72
|
+
attributeFilter: [controllerAttribute],
|
73
|
+
subtree: true,
|
74
|
+
childList: true,
|
75
|
+
});
|
76
|
+
}
|
77
|
+
|
78
|
+
function queryControllerNamesWithin(element) {
|
79
|
+
return Array.from(element.querySelectorAll(`[${controllerAttribute}]`))
|
80
|
+
.map(extractControllerNamesFrom)
|
81
|
+
.flat();
|
82
|
+
}
|
83
|
+
|
84
|
+
function extractControllerNamesFrom(element) {
|
85
|
+
return element
|
86
|
+
.getAttribute(controllerAttribute)
|
87
|
+
.split(/\s+/)
|
88
|
+
.filter((content) => content.length);
|
89
|
+
}
|
90
|
+
|
91
|
+
function loadController(name, under, application) {
|
92
|
+
if (canRegisterController(name, application)) {
|
93
|
+
import(controllerFilename(name, under))
|
94
|
+
.then((module) => registerController(name, module, application))
|
95
|
+
.catch((error) =>
|
96
|
+
console.error(`Failed to autoload controller: ${name}`, error)
|
97
|
+
);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
function controllerFilename(name, under) {
|
102
|
+
return `${under}/${name.replace(/--/g, "/").replace(/-/g, "_")}_controller`;
|
103
|
+
}
|
104
|
+
|
105
|
+
function registerController(name, module, application) {
|
106
|
+
if (canRegisterController(name, application)) {
|
107
|
+
application.register(name, module.default);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
function canRegisterController(name, application) {
|
112
|
+
return !application.router.modulesByIdentifier.has(name);
|
113
|
+
}
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import{Controller as e}from"@hotwired/stimulus";var t=Object.defineProperty;var V=(e,a,i)=>a in e?t(e,a,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[a]=i;var s=(e,t,a)=>(V(e,typeof t!="symbol"?t+"":t,a),a);async function r(e,t,a={}){t?T(e,a):b(e,a)}async function T(e,t={}){let a=e.dataset.transitionEnter||t.enter||"enter",i=e.dataset.transitionEnterFrom||t.enterFrom||"enter-from",n=e.dataset.transitionEnterTo||t.enterTo||"enter-to",o=e.dataset.toggleClass||t.toggleClass||"hidden";e.classList.add(...a.split(" ")),e.classList.add(...i.split(" ")),e.classList.remove(...n.split(" ")),e.classList.remove(...o.split(" ")),await v(),e.classList.remove(...i.split(" ")),e.classList.add(...n.split(" "));try{await x(e)}finally{e.classList.remove(...a.split(" "))}}async function b(e,t={}){let a=e.dataset.transitionLeave||t.leave||"leave",i=e.dataset.transitionLeaveFrom||t.leaveFrom||"leave-from",n=e.dataset.transitionLeaveTo||t.leaveTo||"leave-to",o=e.dataset.toggleClass||t.toggle||"hidden";e.classList.add(...a.split(" ")),e.classList.add(...i.split(" ")),e.classList.remove(...n.split(" ")),await v(),e.classList.remove(...i.split(" ")),e.classList.add(...n.split(" "));try{await x(e)}finally{e.classList.remove(...a.split(" ")),e.classList.add(...o.split(" "))}}function v(){return new Promise((e=>{requestAnimationFrame((()=>{requestAnimationFrame(e)}))}))}function x(e){return Promise.all(e.getAnimations().map((e=>e.finished)))}var a=class extends e{connect(){setTimeout((()=>{T(this.element)}),this.showDelayValue),this.hasDismissAfterValue&&setTimeout((()=>{this.close()}),this.dismissAfterValue)}close(){b(this.element).then((()=>{this.element.remove()}))}};s(a,"values",{dismissAfter:Number,showDelay:{type:Number,default:0}});var i=class extends e{connect(){this.timeout=null}save(){clearTimeout(this.timeout),this.timeout=setTimeout((()=>{this.statusTarget.textContent=this.submittingTextValue,this.formTarget.requestSubmit()}),this.submitDurationValue)}success(){this.setStatus(this.successTextValue)}error(){this.setStatus(this.errorTextValue)}setStatus(e){this.statusTarget.textContent=e,this.timeout=setTimeout((()=>{this.statusTarget.textContent=""}),this.statusDurationValue)}};s(i,"targets",["form","status"]),s(i,"values",{submitDuration:{type:Number,default:1e3},statusDuration:{type:Number,default:2e3},submittingText:{type:String,default:"Saving..."},successText:{type:String,default:"Saved!"},errorText:{type:String,default:"Unable to save."}});var n=class extends e{update(){this.preview=this.colorTarget.value}set preview(e){this.previewTarget.style[this.styleValue]=e;let t=this._getContrastYIQ(e);this.styleValue==="color"?this.previewTarget.style.backgroundColor=t:this.previewTarget.style.color=t}_getContrastYIQ(e){e=e.replace("#","");let t=128,a=parseInt(e.substr(0,2),16),i=parseInt(e.substr(2,2),16),n=parseInt(e.substr(4,2),16);return(a*299+i*587+n*114)/1e3>=t?"#000":"#fff"}};s(n,"targets",["preview","color"]),s(n,"values",{style:{type:String,default:"backgroundColor"}});var o=class extends e{connect(){document.addEventListener("turbo:before-cache",this.beforeCache.bind(this))}disconnect(){document.removeEventListener("turbo:before-cache",this.beforeCache.bind(this))}openValueChanged(){r(this.menuTarget,this.openValue,this.transitionOptions),this.openValue===!0&&this.hasMenuItemTarget&&this.menuItemTargets[0].focus()}show(){this.openValue=!0}close(){this.openValue=!1}hide(e){this.closeOnClickOutsideValue&&e.target.nodeType&&this.element.contains(e.target)===!1&&this.openValue&&(this.openValue=!1),this.closeOnEscapeValue&&e.key==="Escape"&&this.openValue&&(this.openValue=!1)}toggle(){this.openValue=!this.openValue}nextItem(e){e.preventDefault(),this.menuItemTargets[this.nextIndex].focus()}previousItem(e){e.preventDefault(),this.menuItemTargets[this.previousIndex].focus()}get currentItemIndex(){return this.menuItemTargets.indexOf(document.activeElement)}get nextIndex(){return Math.min(this.currentItemIndex+1,this.menuItemTargets.length-1)}get previousIndex(){return Math.max(this.currentItemIndex-1,0)}get transitionOptions(){return{enter:this.hasEnterClass?this.enterClass:"transition ease-out duration-100",enterFrom:this.hasEnterFromClass?this.enterFromClass:"transform opacity-0 scale-95",enterTo:this.hasEnterToClass?this.enterToClass:"transform opacity-100 scale-100",leave:this.hasLeaveClass?this.leaveClass:"transition ease-in duration-75",leaveFrom:this.hasLeaveFromClass?this.leaveFromClass:"transform opacity-100 scale-100",leaveTo:this.hasLeaveToClass?this.leaveToClass:"transform opacity-0 scale-95",toggleClass:this.hasToggleClass?this.toggleClass:"hidden"}}beforeCache(){this.openValue=!1,this.menuTarget.classList.add("hidden")}};s(o,"targets",["menu","button","menuItem"]),s(o,"values",{open:{type:Boolean,default:!1},closeOnEscape:{type:Boolean,default:!0},closeOnClickOutside:{type:Boolean,default:!0}}),s(o,"classes",["enter","enterFrom","enterTo","leave","leaveFrom","leaveTo","toggle"]);var l=class extends e{connect(){this.openValue&&this.open(),document.addEventListener("turbo:before-cache",this.beforeCache.bind(this))}disconnect(){document.removeEventListener("turbo:before-cache",this.beforeCache.bind(this))}open(){this.dialogTarget.showModal()}close(){this.dialogTarget.setAttribute("closing",""),Promise.all(this.dialogTarget.getAnimations().map((e=>e.finished))).then((()=>{this.dialogTarget.removeAttribute("closing"),this.dialogTarget.close()}))}backdropClose(e){e.target.nodeName=="DIALOG"&&this.close()}show(){this.dialogTarget.show()}hide(){this.close()}beforeCache(){this.close()}};s(l,"targets",["dialog"]),s(l,"values",{open:Boolean});var h=class extends e{openValueChanged(){r(this.contentTarget,this.openValue),this.shouldAutoDismiss&&this.scheduleDismissal()}show(e){this.shouldAutoDismiss&&this.scheduleDismissal(),this.openValue=!0}hide(){this.openValue=!1}toggle(){this.openValue=!this.openValue}get shouldAutoDismiss(){return this.openValue&&this.hasDismissAfterValue}scheduleDismissal(){this.hasDismissAfterValue&&(this.cancelDismissal(),this.timeoutId=setTimeout((()=>{this.hide(),this.timeoutId=void 0}),this.dismissAfterValue))}cancelDismissal(){typeof this.timeoutId=="number"&&(clearTimeout(this.timeoutId),this.timeoutId=void 0)}};s(h,"targets",["content"]),s(h,"values",{dismissAfter:Number,open:{type:Boolean,default:!1}});var u=class extends e{connect(){this.openValue&&this.open(),document.addEventListener("turbo:before-cache",this.beforeCache.bind(this))}disconnect(){document.removeEventListener("turbo:before-cache",this.beforeCache.bind(this))}open(){this.dialogTarget.showModal()}close(){this.dialogTarget.setAttribute("closing",""),Promise.all(this.dialogTarget.getAnimations().map((e=>e.finished))).then((()=>{this.dialogTarget.removeAttribute("closing"),this.dialogTarget.close()}))}backdropClose(e){e.target.nodeName=="DIALOG"&&this.close()}show(){this.open()}hide(){this.close()}beforeCache(){this.close()}};s(u,"targets",["dialog"]),s(u,"values",{open:Boolean});var c=class extends e{initialize(){this.anchor&&(this.indexValue=this.tabTargets.findIndex((e=>e.id===this.anchor)))}connect(){this.showTab()}change(e){e.currentTarget.tagName==="SELECT"?this.indexValue=e.currentTarget.selectedIndex:e.currentTarget.dataset.index?this.indexValue=e.currentTarget.dataset.index:e.currentTarget.dataset.id?this.indexValue=this.tabTargets.findIndex((t=>t.id==e.currentTarget.dataset.id)):this.indexValue=this.tabTargets.indexOf(e.currentTarget)}nextTab(){this.indexValue=Math.min(this.indexValue+1,this.tabsCount-1)}previousTab(){this.indexValue=Math.max(this.indexValue-1,0)}firstTab(){this.indexValue=0}lastTab(){this.indexValue=this.tabsCount-1}indexValueChanged(){if(this.showTab(),this.dispatch("tab-change",{target:this.tabTargets[this.indexValue],detail:{activeIndex:this.indexValue}}),this.updateAnchorValue){let e=this.tabTargets[this.indexValue].id;if(this.scrollToAnchorValue)location.hash=e;else{let t=window.location.href.split("#")[0]+"#"+e;history.replaceState({},document.title,t)}}}showTab(){this.panelTargets.forEach(((e,t)=>{let a=this.tabTargets[t];t===this.indexValue?(e.classList.remove("hidden"),a.ariaSelected="true",a.dataset.active=!0,this.hasInactiveTabClass&&a?.classList?.remove(...this.inactiveTabClasses),this.hasActiveTabClass&&a?.classList?.add(...this.activeTabClasses)):(e.classList.add("hidden"),a.ariaSelected=null,delete a.dataset.active,this.hasActiveTabClass&&a?.classList?.remove(...this.activeTabClasses),this.hasInactiveTabClass&&a?.classList?.add(...this.inactiveTabClasses))})),this.hasSelectTarget&&(this.selectTarget.selectedIndex=this.indexValue),this.scrollActiveTabIntoViewValue&&this.scrollToActiveTab()}scrollToActiveTab(){let e=this.element.querySelector("[aria-selected]");e&&e.scrollIntoView({inline:"center"})}get tabsCount(){return this.tabTargets.length}get anchor(){return document.URL.split("#").length>1?document.URL.split("#")[1]:null}};s(c,"classes",["activeTab","inactiveTab"]),s(c,"targets",["tab","panel","select"]),s(c,"values",{index:0,updateAnchor:Boolean,scrollToAnchor:Boolean,scrollActiveTabIntoView:Boolean});var d=class extends e{toggle(e){this.openValue=!this.openValue,this.animate()}toggleInput(e){this.openValue=e.target.checked,this.animate()}hide(){this.openValue=!1,this.animate()}show(){this.openValue=!0,this.animate()}animate(){this.toggleableTargets.forEach((e=>{r(e,this.openValue)}))}};s(d,"targets",["toggleable"]),s(d,"values",{open:{type:Boolean,default:!1}});export{a as Alert,i as Autosave,n as ColorPreview,o as Dropdown,l as Modal,h as Popover,u as Slideover,c as Tabs,d as Toggle};
|
2
|
+
|
Binary file
|
Binary file
|