panda_cms 0.5.10 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/Rakefile +0 -1
  4. data/app/assets/builds/panda_cms.css +2415 -1
  5. data/app/assets/config/panda_cms_manifest.js +2 -0
  6. data/app/assets/stylesheets/panda_cms/application.tailwind.css +3 -27
  7. data/app/builders/panda_cms/form_builder.rb +1 -1
  8. data/app/components/panda_cms/admin/button_component.rb +6 -3
  9. data/app/components/panda_cms/admin/flash_message_component.rb +1 -1
  10. data/app/components/panda_cms/admin/tag_component.rb +1 -1
  11. data/app/components/panda_cms/code_component.rb +60 -0
  12. data/app/components/panda_cms/page_menu_component.html.erb +6 -4
  13. data/app/components/panda_cms/page_menu_component.rb +21 -12
  14. data/app/components/panda_cms/rich_text_component.html.erb +6 -38
  15. data/app/components/panda_cms/rich_text_component.rb +24 -7
  16. data/app/components/panda_cms/text_component.rb +25 -22
  17. data/app/controllers/panda_cms/admin/dashboard_controller.rb +14 -6
  18. data/app/controllers/panda_cms/admin/menus_controller.rb +1 -54
  19. data/app/controllers/panda_cms/admin/pages_controller.rb +2 -1
  20. data/app/controllers/panda_cms/admin/sessions_controller.rb +13 -6
  21. data/app/controllers/panda_cms/application_controller.rb +1 -1
  22. data/app/controllers/panda_cms/pages_controller.rb +1 -1
  23. data/app/controllers/panda_cms/posts_controller.rb +1 -1
  24. data/app/helpers/panda_cms/application_helper.rb +2 -2
  25. data/app/javascript/panda_cms/@hotwired--stimulus.js +4 -0
  26. data/app/javascript/panda_cms/@hotwired--turbo.js +160 -0
  27. data/app/javascript/panda_cms/@rails--actioncable--src.js +4 -0
  28. data/app/javascript/panda_cms/application_panda_cms.js +4 -0
  29. data/app/javascript/panda_cms/controllers/dashboard_controller.js +7 -0
  30. data/app/javascript/panda_cms/controllers/index.js +42 -0
  31. data/app/javascript/panda_cms/controllers/slug_controller.js +48 -0
  32. data/app/javascript/panda_cms/panda_cms_editable.js +248 -0
  33. data/app/javascript/panda_cms/tailwindcss-stimulus-components.js +4 -0
  34. data/app/lib/panda_cms/demo_site_generator.rb +1 -1
  35. data/app/lib/panda_cms/slug.rb +1 -1
  36. data/app/models/panda_cms/block.rb +2 -2
  37. data/app/models/panda_cms/page.rb +9 -3
  38. data/app/models/panda_cms/post.rb +1 -1
  39. data/app/models/panda_cms/template.rb +4 -2
  40. data/app/models/panda_cms/user.rb +9 -1
  41. data/app/views/panda_cms/admin/dashboard/show.html.erb +11 -9
  42. data/app/views/panda_cms/admin/forms/new.html.erb +6 -7
  43. data/app/views/panda_cms/admin/menus/index.html.erb +0 -2
  44. data/app/views/panda_cms/admin/pages/edit.html.erb +18 -16
  45. data/app/views/panda_cms/admin/pages/new.html.erb +6 -7
  46. data/app/views/panda_cms/admin/posts/_form.html.erb +4 -4
  47. data/app/views/panda_cms/admin/sessions/new.html.erb +1 -2
  48. data/app/views/panda_cms/admin/shared/_sidebar.html.erb +12 -16
  49. data/app/views/panda_cms/shared/_header.html.erb +14 -14
  50. data/app/views/panda_cms/shared/_importmap.html.erb +22 -0
  51. data/config/importmap.rb +11 -10
  52. data/config/initializers/panda_cms.rb +57 -55
  53. data/config/routes.rb +9 -9
  54. data/config/tailwind.config.js +1 -0
  55. data/db/migrate/20240205223709_create_panda_cms_pages.rb +6 -4
  56. data/lib/generators/panda_cms/install_generator.rb +3 -0
  57. data/lib/panda_cms/engine.rb +27 -22
  58. data/lib/panda_cms/version.rb +1 -1
  59. data/lib/panda_cms.rb +58 -10
  60. data/lib/tasks/panda_cms.rake +41 -57
  61. data/public/panda-cms-assets/rich_text_editor.css +568 -0
  62. metadata +216 -278
  63. data/app/javascript/base.js +0 -37
  64. data/app/javascript/controllers/menu_controller.js +0 -19
  65. data/app/javascript/controllers/text_controller.js +0 -78
  66. data/app/javascript/controllers/text_field_update_controller.js +0 -51
  67. data/app/javascript/vendor/stimulus-components-rails-nested-form.js +0 -2
  68. data/app/javascript/vendor/tailwindcss-stimulus-components.js +0 -2
  69. data/app/views/panda_cms/admin/menus/_form.html.erb +0 -21
  70. data/app/views/panda_cms/admin/menus/_menu_item_fields.html.erb +0 -7
  71. data/app/views/panda_cms/admin/menus/edit.html.erb +0 -58
  72. data/app/views/panda_cms/admin/menus/new.html.erb +0 -5
  73. data/public/panda-cms-assets/javascripts/base.js +0 -37
  74. data/public/panda-cms-assets/javascripts/controllers/menu_controller.js +0 -19
  75. data/public/panda-cms-assets/javascripts/controllers/text_field_update_controller.js +0 -23
  76. data/public/panda-cms-assets/javascripts/embed/editable.js +0 -358
  77. data/public/panda-cms-assets/javascripts/embed/rich_text.css +0 -1294
  78. data/public/panda-cms-assets/javascripts/vendor/stimulus-components-rails-nested-form.js +0 -2
  79. data/public/panda-cms-assets/javascripts/vendor/stimulus-loading.js +0 -113
  80. data/public/panda-cms-assets/javascripts/vendor/tailwindcss-stimulus-components.js +0 -2
  81. /data/db/migrate/{20240804110225_add_status_to_panda_cms_pages.rb → 20240315125411_add_status_to_panda_cms_pages.rb} +0 -0
@@ -1,358 +0,0 @@
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.embedRichTextEditor();
34
- }
35
-
36
- setFrameVisible() {
37
- console.log("[Panda CMS] Setting iFrame to visible...");
38
- this.frame.style.display = "";
39
- }
40
-
41
- stylePlainTextEditor(element, status) {
42
- console.log(
43
- `[Panda CMS] Styling plain text editor ${element.id} as ${status}...`
44
- );
45
-
46
- if (status == "initial") {
47
- element.style.border = "1px dashed #ccc";
48
- element.style.outline = "none";
49
- element.style.cursor = "pointer";
50
- element.style.transition = "background 500ms linear";
51
- element.style.backgroundColor = "inherit";
52
- } else if (status == "success") {
53
- element.style.backgroundColor = "#66bd6a50";
54
- } else if (status == "error") {
55
- element.style.backgroundColor = "#dc354550";
56
- }
57
- }
58
-
59
- embedPlainTextEditors() {
60
- var elements = this.body.querySelectorAll(
61
- '[data-editable-kind="plain_text"]'
62
- );
63
-
64
- if (elements.length == 0) {
65
- return;
66
- }
67
-
68
- elements.forEach((element) => {
69
- this.stylePlainTextEditor(element, "initial");
70
-
71
- // TODO: If content hasn't changed, don't call save
72
- // Bind initial click handler and then auto-save on blur?
73
-
74
- // This binds auto-save...
75
- element.addEventListener("blur", (event) => {
76
- var target = event.target;
77
- var pageId = target.getAttribute("data-editable-page-id");
78
- var blockContentId = target.getAttribute(
79
- "data-editable-block-content-id"
80
- );
81
-
82
- fetch(`/admin/pages/${pageId}/block_contents/${blockContentId}`, {
83
- method: "PATCH",
84
- headers: {
85
- "Content-Type": "application/json",
86
- "X-CSRF-Token": document
87
- .querySelector('meta[name="csrf-token"]')
88
- .getAttribute("content"),
89
- },
90
- body: JSON.stringify({ content: target.innerHTML }),
91
- })
92
- .then((response) => response.json())
93
- .then((data) => {
94
- this.stylePlainTextEditor(target, "success");
95
- setTimeout(() => {
96
- this.stylePlainTextEditor(target, "initial");
97
- }, 1000);
98
- })
99
- .catch((error) => {
100
- this.stylePlainTextEditor(target, "error");
101
- setTimeout(() => {
102
- this.stylePlainTextEditor(target, "initial");
103
- }, 1000);
104
- alert("Error:", error);
105
- console.log(error);
106
- });
107
- });
108
- });
109
-
110
- console.debug(
111
- "[Panda CMS] Dispatching event: pandaCmsPlainTextEditorLoaded"
112
- );
113
-
114
- // Let the parent know that the external resources have been loaded
115
- this.frameDocument.dispatchEvent(
116
- new Event("pandaCmsPlainTextEditorLoaded")
117
- );
118
- }
119
-
120
- embedRichTextEditor() {
121
- if (this.body.getElementsByClassName("content-rich-text").length == 0) {
122
- this.setFrameVisible();
123
- return;
124
- }
125
-
126
- console.debug("[Panda CMS] Loading Quill rich text editor...");
127
-
128
- this.addStylesheet(
129
- this.frameDocument,
130
- this.head,
131
- "/panda-cms-assets/javascripts/embed/rich_text.css?ver=1.0.0"
132
- );
133
-
134
- var style = this.frameDocument.createElement("style");
135
- // TODO: Base these on "default" set styles
136
- style.innerHTML = `
137
- .ql-snow .ql-editor h2 {
138
- font-size: 1.5em
139
- }
140
-
141
- .ql-snow .ql-editor h3 {
142
- font-size: 1.17em
143
- }
144
-
145
- .ql-snow .ql-editor h4 {
146
- font-size: 1em
147
- }
148
-
149
- .ql-snow .ql-editor h5 {
150
- font-size: .83em
151
- }
152
-
153
- .ql-snow .ql-editor h6 {
154
- font-size: .67em
155
- }
156
-
157
- .ql-snow .ql-editor a {
158
- text-decoration: underline
159
- }
160
-
161
- .ql-snow .ql-editor blockquote {
162
- border-left: 4px solid #ccc;
163
- margin-bottom: 5px;
164
- margin-top: 5px;
165
- padding-left: 16px
166
- }
167
-
168
- .ql-snow .ql-editor code,
169
- .ql-snow .ql-editor .ql-code-block-container {
170
- background-color: #f0f0f0;
171
- border-radius: 3px
172
- }
173
-
174
- .ql-snow .ql-editor .ql-code-block-container {
175
- margin-bottom: 5px;
176
- margin-top: 5px;
177
- padding: 5px 10px
178
- }
179
-
180
- .ql-snow .ql-editor code {
181
- font-size: 85%;
182
- padding: 2px 4px
183
- }
184
-
185
- .ql-snow .ql-editor .ql-code-block-container {
186
- background-color: #23241f;
187
- color: #f8f8f2;
188
- overflow: visible
189
- }
190
-
191
- .ql-snow .ql-editor img {
192
- max-width: 100%
193
- }
194
- `;
195
- this.head.append(style);
196
-
197
- this.loadScript(
198
- this.frameDocument,
199
- this.head,
200
- "https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"
201
- )
202
- .then(() => {
203
- return this.loadScript(
204
- this.frameDocument,
205
- this.head,
206
- "https://unpkg.com/quill-image-compress@1.2.11/dist/quill.imageCompressor.min.js"
207
- );
208
- })
209
- .then(() => {
210
- return this.loadScript(
211
- this.frameDocument,
212
- this.head,
213
- "https://unpkg.com/quill-magic-url@3.0.0/dist/index.js"
214
- );
215
- })
216
- // .then(() => {
217
- // return this.loadScript(
218
- // this.frameDocument,
219
- // this.head,
220
- // "https://cdn.jsdelivr.net/npm/quill-markdown-shortcuts@latest/dist/markdownShortcuts.js"
221
- // );
222
- // })
223
- .then(() => {
224
- this.styleRichTextEditor();
225
-
226
- console.debug(
227
- "[Panda CMS] Dispatching event: pandaCmsRichTextEditorLoaded"
228
- );
229
-
230
- // Let the parent know that the external resources have been loaded
231
- this.frameDocument.dispatchEvent(
232
- new Event("pandaCmsRichTextEditorLoaded")
233
- );
234
-
235
- // Autosave the editable elements
236
- this.enableRichTextEditorAutoSave();
237
-
238
- // This prevents the flash of content before the iFrame is ready
239
- this.setFrameVisible();
240
- });
241
- }
242
-
243
- /**
244
- * Autosave the editable elements in the iFrame
245
- * @todo #TODO Only do this when we have "autosave" mode enabled?
246
- */
247
- enableRichTextEditorAutoSave() {
248
- // Grab each element that's editable and append a save handler to it
249
- var elements = this.frameDocument.querySelectorAll(".ql-editor");
250
- elements.forEach((element) => {
251
- element.addEventListener("blur", (event) => {
252
- var target = event.target;
253
- var blockContentId = target.parentElement.getAttribute(
254
- "data-block-content-id"
255
- );
256
-
257
- this.bindSaveHandler(blockContentId, target.innerHTML);
258
- });
259
- });
260
- }
261
-
262
- /**
263
- * Styles the Quill rich text editor elements in the iFrame
264
- */
265
- styleRichTextEditor() {
266
- if (this.body.getElementsByClassName("ql-editor").length == 0) {
267
- return;
268
- }
269
-
270
- var style = this.frameDocument.createElement("style");
271
- style.innerHTML = `.ql-editor:hover, .ql-container:hover {
272
- cursor: pointer !important;
273
- }
274
-
275
- .ql-container.ql-snow {
276
- margin-top: 0 important;
277
- }
278
-
279
- .ql-editor:hover, .ql-editor:focus, .ql-editor:active {
280
- background-color: #e1effa;
281
- cursor: pointer !important;
282
- -webkit-transition: background-color 500ms linear;
283
- -ms-transition: background-color 500ms linear;
284
- transition: background-color 500ms linear;
285
- }
286
-
287
- .ql-editor.success {
288
- background-color: #66bd6a50 !important;
289
- -webkit-transition: background-color 1000ms linear;
290
- -ms-transition: background-color 1000ms linear;
291
- transition: background-color 1000ms linear;
292
- }`;
293
-
294
- this.head.append(style);
295
- console.log("Appended styles to head");
296
- }
297
-
298
- bindSaveHandler(blockContentId, content) {
299
- console.debug(`[Panda CMS] Calling save handler for ${blockContentId}...`);
300
- fetch(`/admin/pages/${this.pageId}/block_contents/${blockContentId}`, {
301
- method: "PATCH",
302
- headers: {
303
- "Content-Type": "application/json",
304
- "X-CSRF-Token": this.csrfToken,
305
- },
306
- body: JSON.stringify({ content: content }),
307
- })
308
- .then((response) => response.json())
309
- .then((data) => {
310
- var editableBlock = this.frameDocument.querySelector(
311
- `[data-block-content-id="${blockContentId}"] .ql-editor`
312
- );
313
- editableBlock.classList.add("success");
314
- setTimeout(() => {
315
- editableBlock.classList.remove("success");
316
- }, 1500);
317
- })
318
- .catch((error) => {
319
- console.log(error);
320
- alert("Error updating. Please contact the support team!", error);
321
- });
322
- }
323
-
324
- addStylesheet(frameDocument, head, href) {
325
- return new Promise(function (resolve, reject) {
326
- let link = frameDocument.createElement("link");
327
- link.rel = "stylesheet";
328
- link.href = href;
329
- link.media = "none";
330
- head.append(link);
331
-
332
- link.onload = () => {
333
- if (link.media != "all") {
334
- link.media = "all";
335
- }
336
- console.debug(`[Panda CMS] Stylesheet loaded: ${href}`);
337
- resolve(link);
338
- };
339
- link.onerror = () =>
340
- reject(new Error(`[Panda CMS] Stylesheet load error for ${href}`));
341
- });
342
- }
343
-
344
- loadScript(frameDocument, head, src) {
345
- return new Promise(function (resolve, reject) {
346
- let script = frameDocument.createElement("script");
347
- script.src = src;
348
- head.append(script);
349
-
350
- script.onload = () => {
351
- console.debug(`[Panda CMS] Script loaded: ${src}`);
352
- resolve(script);
353
- };
354
- script.onerror = () =>
355
- reject(new Error(`[Panda CMS] Script load error for ${src}`));
356
- });
357
- }
358
- }