panda_cms 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (193) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/builds/panda_cms.css +1 -0
  5. data/app/assets/config/panda_cms_manifest.js +1 -0
  6. data/app/assets/stylesheets/panda_cms/application.tailwind.css +61 -0
  7. data/app/builders/panda_cms/form_builder.rb +118 -0
  8. data/app/components/panda_cms/admin/button_component.rb +65 -0
  9. data/app/components/panda_cms/admin/container_component.html.erb +13 -0
  10. data/app/components/panda_cms/admin/container_component.rb +11 -0
  11. data/app/components/panda_cms/admin/flash_message_component.html.erb +30 -0
  12. data/app/components/panda_cms/admin/flash_message_component.rb +44 -0
  13. data/app/components/panda_cms/admin/heading_component.rb +43 -0
  14. data/app/components/panda_cms/admin/panel_component.html.erb +7 -0
  15. data/app/components/panda_cms/admin/panel_component.rb +11 -0
  16. data/app/components/panda_cms/admin/slideover_component.html.erb +9 -0
  17. data/app/components/panda_cms/admin/slideover_component.rb +13 -0
  18. data/app/components/panda_cms/admin/statistics_component.html.erb +4 -0
  19. data/app/components/panda_cms/admin/statistics_component.rb +15 -0
  20. data/app/components/panda_cms/admin/tab_bar_component.html.erb +35 -0
  21. data/app/components/panda_cms/admin/tab_bar_component.rb +13 -0
  22. data/app/components/panda_cms/admin/table_component.html.erb +21 -0
  23. data/app/components/panda_cms/admin/table_component.rb +43 -0
  24. data/app/components/panda_cms/admin/tag_component.rb +33 -0
  25. data/app/components/panda_cms/admin/user_activity_component.html.erb +5 -0
  26. data/app/components/panda_cms/admin/user_activity_component.rb +19 -0
  27. data/app/components/panda_cms/admin/user_display_component.html.erb +11 -0
  28. data/app/components/panda_cms/admin/user_display_component.rb +19 -0
  29. data/app/components/panda_cms/grid_component.html.erb +6 -0
  30. data/app/components/panda_cms/grid_component.rb +13 -0
  31. data/app/components/panda_cms/menu_component.html.erb +3 -0
  32. data/app/components/panda_cms/menu_component.rb +18 -0
  33. data/app/components/panda_cms/page_menu_component.html.erb +24 -0
  34. data/app/components/panda_cms/page_menu_component.rb +24 -0
  35. data/app/components/panda_cms/rich_text_component.html.erb +40 -0
  36. data/app/components/panda_cms/rich_text_component.rb +35 -0
  37. data/app/components/panda_cms/text_component.rb +67 -0
  38. data/app/constraints/panda_cms/admin_constraint.rb +16 -0
  39. data/app/controllers/panda_cms/admin/block_contents_controller.rb +44 -0
  40. data/app/controllers/panda_cms/admin/dashboard_controller.rb +31 -0
  41. data/app/controllers/panda_cms/admin/files_controller.rb +19 -0
  42. data/app/controllers/panda_cms/admin/forms_controller.rb +51 -0
  43. data/app/controllers/panda_cms/admin/menus_controller.rb +81 -0
  44. data/app/controllers/panda_cms/admin/pages_controller.rb +88 -0
  45. data/app/controllers/panda_cms/admin/posts_controller.rb +34 -0
  46. data/app/controllers/panda_cms/admin/sessions_controller.rb +83 -0
  47. data/app/controllers/panda_cms/admin/settings/bulk_editor_controller.rb +35 -0
  48. data/app/controllers/panda_cms/admin/settings_controller.rb +18 -0
  49. data/app/controllers/panda_cms/application_controller.rb +55 -0
  50. data/app/controllers/panda_cms/errors_controller.rb +31 -0
  51. data/app/controllers/panda_cms/form_submissions_controller.rb +21 -0
  52. data/app/controllers/panda_cms/pages_controller.rb +56 -0
  53. data/app/controllers/panda_cms/posts_controller.rb +17 -0
  54. data/app/helpers/panda_cms/admin/files_helper.rb +4 -0
  55. data/app/helpers/panda_cms/admin/pages_helper.rb +4 -0
  56. data/app/helpers/panda_cms/application_helper.rb +96 -0
  57. data/app/helpers/panda_cms/pages_helper.rb +4 -0
  58. data/app/helpers/panda_cms/theme_helper.rb +16 -0
  59. data/app/javascript/base.js +37 -0
  60. data/app/javascript/controllers/menu_controller.js +19 -0
  61. data/app/javascript/controllers/text_controller.js +78 -0
  62. data/app/javascript/controllers/text_field_update_controller.js +23 -0
  63. data/app/javascript/vendor/stimulus-components-rails-nested-form.js +2 -0
  64. data/app/javascript/vendor/tailwindcss-stimulus-components.js +2 -0
  65. data/app/jobs/panda_cms/application_job.rb +4 -0
  66. data/app/jobs/panda_cms/record_visit_job.rb +29 -0
  67. data/app/lib/panda_cms/bulk_editor.rb +169 -0
  68. data/app/lib/panda_cms/demo_site_generator.rb +70 -0
  69. data/app/lib/panda_cms/slug.rb +22 -0
  70. data/app/mailers/panda_cms/application_mailer.rb +6 -0
  71. data/app/mailers/panda_cms/form_mailer.rb +19 -0
  72. data/app/models/panda_cms/application_record.rb +5 -0
  73. data/app/models/panda_cms/block.rb +32 -0
  74. data/app/models/panda_cms/block_content.rb +16 -0
  75. data/app/models/panda_cms/block_content_version.rb +6 -0
  76. data/app/models/panda_cms/breadcrumb.rb +10 -0
  77. data/app/models/panda_cms/current.rb +15 -0
  78. data/app/models/panda_cms/form.rb +7 -0
  79. data/app/models/panda_cms/form_submission.rb +5 -0
  80. data/app/models/panda_cms/menu.rb +50 -0
  81. data/app/models/panda_cms/menu_item.rb +56 -0
  82. data/app/models/panda_cms/page.rb +81 -0
  83. data/app/models/panda_cms/page_version.rb +6 -0
  84. data/app/models/panda_cms/post.rb +25 -0
  85. data/app/models/panda_cms/post_version.rb +6 -0
  86. data/app/models/panda_cms/redirect.rb +9 -0
  87. data/app/models/panda_cms/template.rb +117 -0
  88. data/app/models/panda_cms/template_version.rb +6 -0
  89. data/app/models/panda_cms/user.rb +15 -0
  90. data/app/models/panda_cms/version.rb +6 -0
  91. data/app/models/panda_cms/visit.rb +7 -0
  92. data/app/views/layouts/panda_cms/application.html.erb +44 -0
  93. data/app/views/layouts/panda_cms/public.html.erb +3 -0
  94. data/app/views/panda_cms/admin/dashboard/show.html.erb +11 -0
  95. data/app/views/panda_cms/admin/files/index.html.erb +124 -0
  96. data/app/views/panda_cms/admin/files/show.html.erb +2 -0
  97. data/app/views/panda_cms/admin/forms/edit.html.erb +0 -0
  98. data/app/views/panda_cms/admin/forms/index.html.erb +13 -0
  99. data/app/views/panda_cms/admin/forms/new.html.erb +16 -0
  100. data/app/views/panda_cms/admin/forms/show.html.erb +35 -0
  101. data/app/views/panda_cms/admin/menus/_form.html.erb +21 -0
  102. data/app/views/panda_cms/admin/menus/_menu_item_fields.html.erb +7 -0
  103. data/app/views/panda_cms/admin/menus/edit.html.erb +58 -0
  104. data/app/views/panda_cms/admin/menus/index.html.erb +10 -0
  105. data/app/views/panda_cms/admin/menus/new.html.erb +5 -0
  106. data/app/views/panda_cms/admin/pages/edit.html.erb +26 -0
  107. data/app/views/panda_cms/admin/pages/index.html.erb +16 -0
  108. data/app/views/panda_cms/admin/pages/new.html.erb +16 -0
  109. data/app/views/panda_cms/admin/pages/show.html.erb +1 -0
  110. data/app/views/panda_cms/admin/posts/index.html.erb +16 -0
  111. data/app/views/panda_cms/admin/sessions/new.html.erb +18 -0
  112. data/app/views/panda_cms/admin/settings/bulk_editor/new.html.erb +68 -0
  113. data/app/views/panda_cms/admin/settings/index.html.erb +19 -0
  114. data/app/views/panda_cms/admin/shared/_breadcrumbs.html.erb +28 -0
  115. data/app/views/panda_cms/admin/shared/_flash.html.erb +5 -0
  116. data/app/views/panda_cms/admin/shared/_sidebar.html.erb +45 -0
  117. data/app/views/panda_cms/form_mailer/notification_email.html.erb +11 -0
  118. data/app/views/panda_cms/shared/_favicons.html.erb +9 -0
  119. data/app/views/panda_cms/shared/_footer.html.erb +2 -0
  120. data/app/views/panda_cms/shared/_header.html.erb +15 -0
  121. data/config/importmap.rb +9 -0
  122. data/config/initializers/panda_cms/form_errors.rb +38 -0
  123. data/config/initializers/panda_cms/healthcheck_log_silencer.rb +11 -0
  124. data/config/initializers/panda_cms.rb +52 -0
  125. data/config/locales/en.yml +29 -0
  126. data/config/routes.rb +43 -0
  127. data/config/tailwind.config.js +35 -0
  128. data/config/tailwind.embed.config.js +20 -0
  129. data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
  130. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
  131. data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
  132. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
  133. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
  134. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
  135. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
  136. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
  137. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
  138. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
  139. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
  140. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
  141. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
  142. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
  143. data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
  144. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
  145. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
  146. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
  147. data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
  148. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
  149. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
  150. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  151. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
  152. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
  153. data/db/migrate/20240804110225_add_status_to_panda_cms_pages.rb +7 -0
  154. data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
  155. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
  156. data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
  157. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
  158. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
  159. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
  160. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
  161. data/db/seeds.rb +4 -0
  162. data/lib/generators/panda_cms/install_generator.rb +24 -0
  163. data/lib/panda_cms/engine.rb +167 -0
  164. data/lib/panda_cms/exceptions_app.rb +24 -0
  165. data/lib/panda_cms/version.rb +3 -0
  166. data/lib/panda_cms.rb +15 -0
  167. data/lib/tasks/panda_cms.rake +92 -0
  168. data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
  169. data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
  170. data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
  171. data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
  172. data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
  173. data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
  174. data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
  175. data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
  176. data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
  177. data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
  178. data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
  179. data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
  180. data/public/panda-cms-assets/favicons/favicon.ico +0 -0
  181. data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
  182. data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
  183. data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
  184. data/public/panda-cms-assets/javascripts/base.js +37 -0
  185. data/public/panda-cms-assets/javascripts/controllers/menu_controller.js +19 -0
  186. data/public/panda-cms-assets/javascripts/controllers/text_field_update_controller.js +23 -0
  187. data/public/panda-cms-assets/javascripts/embed/editable.js +308 -0
  188. data/public/panda-cms-assets/javascripts/vendor/stimulus-components-rails-nested-form.js +2 -0
  189. data/public/panda-cms-assets/javascripts/vendor/stimulus-loading.js +113 -0
  190. data/public/panda-cms-assets/javascripts/vendor/tailwindcss-stimulus-components.js +2 -0
  191. data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
  192. data/public/panda-cms-assets/panda-nav.png +0 -0
  193. 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
+