panda_cms 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/panda_cms.css +17 -0
  3. data/app/assets/config/panda_cms_manifest.js +1 -0
  4. data/app/components/panda_cms/code_component.rb +3 -1
  5. data/app/components/panda_cms/menu_component.html.erb +3 -0
  6. data/app/components/panda_cms/menu_component.rb +8 -1
  7. data/app/components/panda_cms/page_menu_component.html.erb +7 -5
  8. data/app/components/panda_cms/page_menu_component.rb +3 -1
  9. data/app/components/panda_cms/rich_text_component.html.erb +2 -2
  10. data/app/components/panda_cms/rich_text_component.rb +12 -4
  11. data/app/components/panda_cms/text_component.rb +1 -1
  12. data/app/controllers/panda_cms/admin/block_contents_controller.rb +1 -1
  13. data/app/javascript/panda_cms/@editorjs--editorjs.js +2577 -0
  14. data/app/javascript/panda_cms/controllers/editor_controller.js +247 -0
  15. data/app/javascript/panda_cms/controllers/index.js +10 -7
  16. data/app/javascript/panda_cms/editor/plain_text_editor.js +102 -0
  17. data/app/javascript/panda_cms/editor/resource_loader.js +69 -0
  18. data/app/javascript/panda_cms/editor/rich_text_editor.js +89 -0
  19. data/app/lib/panda_cms/demo_site_generator.rb +0 -2
  20. data/app/lib/panda_cms/editor_js/blocks/alert.rb +32 -0
  21. data/app/lib/panda_cms/editor_js/blocks/base.rb +28 -0
  22. data/app/lib/panda_cms/editor_js/blocks/header.rb +13 -0
  23. data/app/lib/panda_cms/editor_js/blocks/image.rb +34 -0
  24. data/app/lib/panda_cms/editor_js/blocks/list.rb +30 -0
  25. data/app/lib/panda_cms/editor_js/blocks/paragraph.rb +13 -0
  26. data/app/lib/panda_cms/editor_js/blocks/quote.rb +27 -0
  27. data/app/lib/panda_cms/editor_js/blocks/table.rb +48 -0
  28. data/app/lib/panda_cms/editor_js/renderer.rb +120 -0
  29. data/app/models/panda_cms/block_content.rb +12 -2
  30. data/app/views/panda_cms/admin/pages/edit.html.erb +10 -9
  31. data/app/views/panda_cms/shared/_header.html.erb +2 -3
  32. data/app/views/panda_cms/shared/_importmap.html.erb +13 -3
  33. data/config/importmap.rb +3 -1
  34. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +5 -3
  35. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +5 -0
  36. data/db/seeds.rb +1 -0
  37. data/lib/panda_cms/engine.rb +16 -8
  38. data/lib/panda_cms/version.rb +1 -1
  39. metadata +89 -103
  40. data/app/javascript/panda_cms/panda_cms_editable.js +0 -248
@@ -0,0 +1,247 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { PlainTextEditor } from "panda_cms_editor/plain_text_editor";
3
+ import { ResourceLoader } from "panda_cms_editor/resource_loader";
4
+
5
+ export class EditorController extends Controller {
6
+ /**
7
+ * Defines the static values that can be set on the EditorController.
8
+ *
9
+ * @property {number} pageId - The ID of the page being edited.
10
+ * @property {string} adminPath - The path to the admin section of the application.
11
+ * @property {boolean} autosave - Whether the editor should automatically save changes.
12
+ */
13
+ static values = {
14
+ pageId: Number,
15
+ adminPath: String,
16
+ autosave: Boolean
17
+ }
18
+
19
+ /**
20
+ * Connects the EditorController to the DOM and initializes the editors.
21
+ * This method is called when the EditorController is connected to the DOM.
22
+ * It sets up the necessary properties and event listeners to manage the editors,
23
+ * and then calls the `initializeEditors()` method to start the initialization process.
24
+ */
25
+ connect() {
26
+ console.debug("[Panda CMS] Editor controller connected")
27
+ this.frame = this.element
28
+
29
+ // In CI, show what's going on, otherwise hide the frame
30
+ if (!window.location.href.includes("0.0.0.0:3001")) {
31
+ this.frame.style.display = "none"
32
+ }
33
+
34
+ if (document.querySelector('meta[name="csrf-token"]')) {
35
+ this.csrfToken = document.querySelector('meta[name="csrf-token"]').content
36
+ } else {
37
+ this.csrfToken = ""
38
+ }
39
+
40
+ this.editors = []
41
+ this.editorsInitialized = {
42
+ plain: false,
43
+ rich: false
44
+ }
45
+
46
+ this.frame.addEventListener("load", () => {
47
+ console.debug("[Panda CMS] Frame loaded")
48
+ this.frameDocument = this.frame.contentDocument || this.frame.contentWindow.document
49
+ this.body = this.frameDocument.body
50
+ this.head = this.frameDocument.head
51
+ this.initializeEditors()
52
+ })
53
+ }
54
+
55
+ /**
56
+ * Initializes the plain text and rich text editors for the page.
57
+ * This method is responsible for finding all editable elements on the page and initializing the appropriate editor instances for them.
58
+ * It sets the editorsInitialized flags to true once the initialization is complete and calls the checkAllEditorsInitialized method.
59
+ */
60
+ initializeEditors() {
61
+ console.debug("[Panda CMS] Starting editor initialization")
62
+ this.initializePlainTextEditors()
63
+ this.initializeRichTextEditors()
64
+ }
65
+
66
+ /**
67
+ * Initializes the plain text editors for the page.
68
+ * This method is responsible for finding all elements on the page that have the "plain_text", "markdown", or "html" data-editable-kind attributes,
69
+ * and initializing a PlainTextEditor instance for each of them. It also sets the editorsInitialized.plain flag to true
70
+ * and calls the checkAllEditorsInitialized method to notify that the plain text editors have been initialized.
71
+ */
72
+ initializePlainTextEditors() {
73
+ const plainTextElements = this.body.querySelectorAll('[data-editable-kind="plain_text"], [data-editable-kind="markdown"], [data-editable-kind="html"]')
74
+ console.debug(`[Panda CMS] Found ${plainTextElements.length} plain text elements`)
75
+
76
+ plainTextElements.forEach(element => {
77
+ console.debug(`[Panda CMS] Initializing plain text editor for element:`, element)
78
+ const editor = new PlainTextEditor(element, this.frameDocument, {
79
+ autosave: this.autosaveValue,
80
+ adminPath: this.adminPathValue,
81
+ csrfToken: this.csrfToken
82
+ })
83
+ this.editors.push(editor)
84
+ })
85
+
86
+ this.editorsInitialized.plain = true
87
+ this.checkAllEditorsInitialized()
88
+ }
89
+
90
+ /**
91
+ * Initializes the rich text editors for the page.
92
+ * This method is responsible for finding all elements on the page that have the "rich_text" data-editable-kind attribute,
93
+ * and initializing a RichTextEditor instance for each of them. It also sets the editorsInitialized.rich flag to true
94
+ * and calls the checkAllEditorsInitialized method to notify that the rich text editors have been initialized.
95
+ */
96
+ initializeRichTextEditors() {
97
+ const richTextElements = this.body.querySelectorAll('[data-editable-kind="rich_text"]')
98
+ console.debug(`[Panda CMS] Found ${richTextElements.length} rich text elements`)
99
+
100
+ if (richTextElements.length > 0) {
101
+ Promise.all([
102
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"), // Base EditorJS
103
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/header@latest"), // Header Tool
104
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/list@latest"), // List Tool
105
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"), // Quote Tool
106
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/nested-list@latest"), // Nested List Tool
107
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/simple-image@latest"), // Simple Image Tool
108
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/table@latest"), // Table Tool
109
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"), // Link Tool
110
+ ResourceLoader.loadScript(this.frameDocument, this.head, "https://cdn.jsdelivr.net/npm/editorjs-alert@latest"), // Alert Tool
111
+ ResourceLoader.embedCSS(this.frameDocument, this.head, ".ce-toolbar__content { margin: 0 !important; margin-left: 40px; max-width: 100% !important; width: 100% !important; } .ce-block__content { max-width: 100%; margin: 0 !important; margin-left: 10px !important; }")
112
+ ]).then(() => {
113
+ richTextElements.forEach(element => {
114
+ console.debug(`[Panda CMS] Initializing rich text editor for element:`, element)
115
+
116
+ // TODO: Need a way to override this per-site ... or rather, block/editor?
117
+ const elementAsId = element.id.replace(/-/g, "_")
118
+ const adminPathValue = this.adminPathValue
119
+ const pageId = element.getAttribute("data-editable-page-id")
120
+ const blockContentId = element.getAttribute("data-editable-block-content-id")
121
+ const csrfToken = this.csrfToken
122
+ const previousData = element.getAttribute("data-editable-previous-data")
123
+ const editorConfig = `{
124
+ holder: '${element.id}',
125
+ data: ${previousData},
126
+ tools: {
127
+ header: {
128
+ class: Header,
129
+ config: {
130
+ placeholder: 'Enter a header',
131
+ levels: [2, 3],
132
+ defaultLevel: 2
133
+ }
134
+ },
135
+ list: {
136
+ class: NestedList,
137
+ inlineToolbar: true,
138
+ config: {
139
+ defaultStyle: 'unordered'
140
+ },
141
+ },
142
+ alert: {
143
+ class: Alert,
144
+ inlineToolbar: true,
145
+ config: {
146
+ defaultType: 'primary',
147
+ messagePlaceholder: 'Enter something',
148
+ types: {
149
+ primary: 'Primary',
150
+ secondary: 'Secondary',
151
+ success: 'Success',
152
+ danger: 'Danger',
153
+ warning: 'Warning',
154
+ info: 'Info'
155
+ }
156
+ }
157
+ },
158
+ quote: Quote,
159
+ table: {
160
+ class: Table,
161
+ inlineToolbar: true,
162
+ config: {
163
+ rows: 2,
164
+ cols: 3
165
+ }
166
+ },
167
+ image: SimpleImage,
168
+ embed: {
169
+ class: Embed,
170
+ config: {
171
+ services: {
172
+ youtube: true,
173
+ instagram: true,
174
+ miro: true,
175
+ vimeo: true,
176
+ pinterest: true,
177
+ github: true
178
+ }
179
+ }
180
+ },
181
+ }
182
+ }`
183
+
184
+ // console.log(editorConfig);
185
+
186
+ ResourceLoader.embedScript(
187
+ `EditorJS configuration for ${element.id}`,
188
+ this.frameDocument,
189
+ this.head,
190
+ `
191
+ const ${elementAsId} = new EditorJS(${editorConfig})
192
+ parent.document.getElementById('saveEditableButton').addEventListener('click', (element) => {
193
+ ${elementAsId}.save().then((outputData) => {
194
+ outputData.source = "editorJS"
195
+ console.log('Saving successful, here is outputData:')
196
+ dataToSend = JSON.stringify({ content: outputData })
197
+ console.log(dataToSend)
198
+ fetch("${adminPathValue}/pages/${pageId}/block_contents/${blockContentId}", {
199
+ method: "PATCH",
200
+ headers: {
201
+ "Content-Type": "application/json",
202
+ "X-CSRF-Token": "${this.csrfToken}"
203
+ },
204
+ body: dataToSend
205
+ })
206
+ .then(response => console.log(response))
207
+ .then(() => alert("Saved"))
208
+ .catch((error) => alert("Saving failed (1): " + error))
209
+ }).catch((error) => {
210
+ console.log('Saving failed: ', error)
211
+ alert('Saving failed (2): ' + error)
212
+ })
213
+ })
214
+
215
+ console.debug("[Panda CMS] Initialized rich text editor for element: ${elementAsId}")
216
+ `
217
+ )
218
+ this.editors.push(elementAsId)
219
+ })
220
+ }).then(() => {
221
+ this.editorsInitialized.rich = true
222
+ this.checkAllEditorsInitialized()
223
+ })
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Checks if all editors have been initialized and sets the iFrame to visible if so.
229
+ * This is called after the plain text and rich text editors have been initialized.
230
+ */
231
+ checkAllEditorsInitialized() {
232
+ console.debug("[Panda CMS] Editor initialization status:", this.editorsInitialized)
233
+ if (this.editorsInitialized.plain && this.editorsInitialized.rich) {
234
+ console.debug("[Panda CMS] All editors initialized, showing iFrame")
235
+ this.setiFrameVisible()
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Sets the visibility of the iFrame to visible.
241
+ * This is called after all editors have been initialized to show the iFrame.
242
+ */
243
+ setiFrameVisible() {
244
+ console.debug("[Panda CMS] Setting iFrame to visible")
245
+ this.frame.style.display = ""
246
+ }
247
+ }
@@ -1,10 +1,10 @@
1
- console.debug("[Panda CMS] Importing Panda CMS Stimulus Controller...");;
1
+ console.debug("[Panda CMS] Importing Panda CMS Stimulus Controller...")
2
2
 
3
3
  import { Application as PandaCmsApplication } from "@hotwired/stimulus"
4
4
 
5
5
  const pandaCmsApplication = PandaCmsApplication.start()
6
6
 
7
- console.debug("[Panda CMS] Application started...");;
7
+ console.debug("[Panda CMS] Application started...")
8
8
 
9
9
  // Configure Stimulus development experience
10
10
  pandaCmsApplication.debug = false
@@ -14,13 +14,16 @@ console.debug("[Panda CMS] window.pandaCmsStimulus available...")
14
14
 
15
15
  console.debug("[Panda CMS] Registering controllers...")
16
16
 
17
- // Grab our internal controllers manually
18
- import { DashboardController } from "controllers/dashboard_controller"
17
+ // Grab our internal controllers manually, prefixed with panda_cms_controllers so not to conflict
18
+ import { DashboardController } from "panda_cms_controllers/dashboard_controller"
19
19
  pandaCmsApplication.register("dashboard", DashboardController)
20
20
 
21
- import { SlugController } from "controllers/slug_controller"
21
+ import { SlugController } from "panda_cms_controllers/slug_controller"
22
22
  pandaCmsApplication.register("slug", SlugController)
23
23
 
24
+ import { EditorController } from "panda_cms_controllers/editor_controller"
25
+ pandaCmsApplication.register("editor", EditorController)
26
+
24
27
  console.debug("[Panda CMS] Registering components...")
25
28
 
26
29
  // Import and register all TailwindCSS Components or just the ones you need
@@ -35,8 +38,8 @@ pandaCmsApplication.register('slideover', Slideover)
35
38
  pandaCmsApplication.register('tabs', Tabs)
36
39
  pandaCmsApplication.register('toggle', Toggle)
37
40
 
38
- console.debug("[Panda CMS] Components registered...");
41
+ console.debug("[Panda CMS] Components registered...")
39
42
 
40
43
  export { pandaCmsApplication }
41
44
 
42
- console.debug("[Panda CMS] Application exported...");
45
+ console.debug("[Panda CMS] Application exported...")
@@ -0,0 +1,102 @@
1
+ export class PlainTextEditor {
2
+ /**
3
+ * Constructs a new PlainTextEditor instance.
4
+ *
5
+ * @param {HTMLElement} element - The HTML element representing the plain text editor.
6
+ * @param {HTMLIFrameElement} frame - The HTML iframe element containing the plain text editor.
7
+ * @param {Object} options - An object containing configuration options for the plain text editor.
8
+ */
9
+ constructor(element, frame, options) {
10
+ this.element = element
11
+ this.frame = frame
12
+ this.options = options
13
+ this.setupStyles()
14
+ this.bindEvents()
15
+ }
16
+
17
+ /**
18
+ * Sets up the styles for the plain text editor element.
19
+ *
20
+ * This method applies various CSS styles to the editor element, such as a dashed border, no outline, a pointer cursor, and a background color transition. It also sets the white-space and font-family styles based on the data-editable-kind attribute of the element.
21
+ */
22
+ setupStyles() {
23
+ this.element.style.border = "1px dashed #ccc"
24
+ this.element.style.outline = "none"
25
+ this.element.style.cursor = "pointer"
26
+ this.element.style.transition = "background 500ms linear"
27
+ this.element.style.backgroundColor = "inherit"
28
+
29
+ if (this.element.getAttribute("data-editable-kind") == "html") {
30
+ this.element.style.whiteSpace = "pre-wrap"
31
+ this.element.style.fontFamily = "monospace"
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Binds event listeners for the plain text editor.
37
+ *
38
+ * If the `autosave` option is enabled, this method adds a `blur` event listener to the editor element, which triggers the `save()` method when the editor loses focus.
39
+ *
40
+ * Additionally, this method adds a `click` event listener to the "Save Editable" button, which also triggers the `save()` method when clicked.
41
+ */
42
+ bindEvents() {
43
+ if (this.options.autosave) {
44
+ this.element.addEventListener("blur", () => this.save())
45
+ }
46
+
47
+ document.getElementById('saveEditableButton').addEventListener('click', () => this.save())
48
+ }
49
+
50
+ /**
51
+ * Saves the content of the plain text editor to the server.
52
+ *
53
+ * This method sends a PATCH request to the server with the updated content of the plain text editor. It retrieves the necessary data from the editor element's attributes, such as the block content ID and the content type (HTML or plain text). If the save is successful, it calls the `showSuccess()` method, otherwise it calls the `showError()` method with the error.
54
+ */
55
+ save() {
56
+ const blockContentId = this.element.getAttribute("data-editable-block-content-id")
57
+ const pageId = this.element.getAttribute("data-editable-page-id")
58
+ const content = this.element.getAttribute("data-editable-kind") == "html" ?
59
+ this.element.innerText :
60
+ this.element.innerHTML
61
+
62
+ fetch(`${this.options.adminPath}/pages/${pageId}/block_contents/${blockContentId}`, {
63
+ method: "PATCH",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ "X-CSRF-Token": this.options.csrfToken
67
+ },
68
+ body: JSON.stringify({ content: content })
69
+ })
70
+ .then(response => response.json())
71
+ .then(() => this.showSuccess())
72
+ .catch(error => this.showError(error))
73
+ }
74
+
75
+ /**
76
+ * Displays a success message by temporarily changing the background color of the editor element.
77
+ *
78
+ * This method is called after a successful save operation to provide visual feedback to the user.
79
+ */
80
+ showSuccess() {
81
+ this.element.style.backgroundColor = "#66bd6a50"
82
+ setTimeout(() => {
83
+ this.element.style.backgroundColor = "inherit"
84
+ }, 1000)
85
+ }
86
+
87
+ /**
88
+ * Displays an error message by temporarily changing the background color of the editor element and logging the error to the console.
89
+ *
90
+ * This method is called after a failed save operation to provide visual and textual feedback to the user.
91
+ *
92
+ * @param {Error} error - The error object that occurred during the save operation.
93
+ */
94
+ showError(error) {
95
+ this.element.style.backgroundColor = "#dc354550"
96
+ setTimeout(() => {
97
+ this.element.style.backgroundColor = "inherit"
98
+ }, 1000)
99
+ console.log(error)
100
+ alert("Error:", error)
101
+ }
102
+ }
@@ -0,0 +1,69 @@
1
+ export class ResourceLoader {
2
+ static loadScript(frameDocument, head, src) {
3
+ return new Promise((resolve, reject) => {
4
+ const script = frameDocument.createElement("script")
5
+ script.src = src
6
+ head.append(script)
7
+
8
+ script.onload = () => {
9
+ console.debug(`[Panda CMS] Script loaded: ${src}`)
10
+ resolve(script)
11
+ }
12
+ script.onerror = () => reject(new Error(`[Panda CMS] Script load error for ${src}`))
13
+ })
14
+ }
15
+
16
+ static importScript(frameDocument, head, module, src) {
17
+ return new Promise((resolve, reject) => {
18
+ const script = frameDocument.createElement("script")
19
+ script.type = "module"
20
+ script.textContent = `import ${module} from "${src}"`
21
+ head.append(script)
22
+
23
+ script.onload = () => {
24
+ console.debug(`[Panda CMS] Module script loaded: ${src}`)
25
+ resolve(script)
26
+ }
27
+ script.onerror = () => reject(new Error(`[Panda CMS] Module script load error for ${src}`))
28
+ })
29
+ }
30
+
31
+ static embedScript(description, frameDocument, head, code) {
32
+ return new Promise((resolve) => {
33
+ const script = frameDocument.createElement("script")
34
+ script.textContent = code
35
+ head.append(script)
36
+ resolve(script)
37
+ console.debug(`[Panda CMS] Embedded script loaded (${description})`)
38
+ })
39
+ }
40
+
41
+ static loadStylesheet(frameDocument, head, href) {
42
+ return new Promise((resolve, reject) => {
43
+ const link = frameDocument.createElement("link")
44
+ link.rel = "stylesheet"
45
+ link.href = href
46
+ link.media = "none"
47
+ head.append(link)
48
+
49
+ link.onload = () => {
50
+ if (link.media != "all") {
51
+ link.media = "all"
52
+ }
53
+ console.debug(`[Panda CMS] Stylesheet loaded: ${href}`)
54
+ resolve(link)
55
+ }
56
+ link.onerror = () => reject(new Error(`[Panda CMS] Stylesheet load error for ${href}`))
57
+ })
58
+ }
59
+
60
+ static embedCSS(frameDocument, head, css) {
61
+ return new Promise((resolve) => {
62
+ const style = frameDocument.createElement("style")
63
+ style.textContent = css
64
+ head.append(style)
65
+ console.debug(`[Panda CMS] Embedded CSS loaded`)
66
+ resolve(style)
67
+ })
68
+ }
69
+ }
@@ -0,0 +1,89 @@
1
+ // import EditorJS from '@editorjs/editorjs';
2
+
3
+ // export class RichTextEditor {
4
+ // /**
5
+ // * Constructs a new RichTextEditor instance.
6
+ // *
7
+ // * @param {HTMLElement} element - The HTML element that represents the rich text editor.
8
+ // * @param {HTMLIFrameElement} frame - The HTML iframe element containing the rich text editor.
9
+ // * @param {Object} options - An object containing configuration options for the rich text editor.
10
+ // */
11
+ // constructor(element, frame, options) {
12
+ // this.element = element
13
+ // this.frame = frame
14
+ // this.options = options
15
+ // this.bindEvents()
16
+ // }
17
+
18
+ // save() {
19
+
20
+ // }
21
+
22
+ // /**
23
+ // * Binds event listeners for the rich text editor.
24
+ // *
25
+ // * If the `autosave` option is enabled, this method adds a `blur` event listener to the editor element, which triggers the `save()` method when the editor loses focus.
26
+ // *
27
+ // * Additionally, this method adds a `click` event listener to the "Save Editable" button, which also triggers the `save()` method when clicked.
28
+ // */
29
+ // bindEvents() {
30
+ // if (this.options.autosave) {
31
+ // this.element.addEventListener("blur", () => this.save())
32
+ // }
33
+
34
+ // document.getElementById('saveEditableButton').addEventListener('click', () => this.save())
35
+ // }
36
+
37
+ // /**
38
+ // * Saves the content of the plain text editor to the server.
39
+ // *
40
+ // * This method sends a PATCH request to the server with the updated content of the plain text editor. It retrieves the necessary data from the editor element's attributes, such as the block content ID and the content type (HTML or plain text). If the save is successful, it calls the `showSuccess()` method, otherwise it calls the `showError()` method with the error.
41
+ // */
42
+ // save() {
43
+ // alert('save');
44
+ // return false;
45
+
46
+ // const blockContentId = this.element.getAttribute("data-editable-block-content-id")
47
+ // const pageId = this.element.getAttribute("data-editable-page-id")
48
+
49
+ // fetch(`${this.options.adminPath}/pages/${pageId}/block_contents/${blockContentId}`, {
50
+ // method: "PATCH",
51
+ // headers: {
52
+ // "Content-Type": "application/json",
53
+ // "X-CSRF-Token": this.options.csrfToken
54
+ // },
55
+ // body: JSON.stringify({ content: content })
56
+ // })
57
+ // .then(response => response.json())
58
+ // .then(() => this.showSuccess())
59
+ // .catch(error => this.showError(error))
60
+ // }
61
+
62
+ // /**
63
+ // * Displays a success message by temporarily changing the background color of the editor element.
64
+ // *
65
+ // * This method is called after a successful save operation to provide visual feedback to the user.
66
+ // */
67
+ // showSuccess() {
68
+ // this.element.style.backgroundColor = "#66bd6a50"
69
+ // setTimeout(() => {
70
+ // this.element.style.backgroundColor = "inherit"
71
+ // }, 1000)
72
+ // }
73
+
74
+ // /**
75
+ // * Displays an error message by temporarily changing the background color of the editor element and logging the error to the console.
76
+ // *
77
+ // * This method is called after a failed save operation to provide visual and textual feedback to the user.
78
+ // *
79
+ // * @param {Error} error - The error object that occurred during the save operation.
80
+ // */
81
+ // showError(error) {
82
+ // this.element.style.backgroundColor = "#dc354550"
83
+ // setTimeout(() => {
84
+ // this.element.style.backgroundColor = "inherit"
85
+ // }, 1000)
86
+ // console.log(error)
87
+ // alert("Error:", error)
88
+ // }
89
+ // }
@@ -24,8 +24,6 @@ module PandaCms
24
24
  @templates[key] = PandaCms::Template.find_or_create_by!(template)
25
25
  end
26
26
 
27
- PandaCms::Template.generate_missing_blocks
28
-
29
27
  @templates
30
28
  end
31
29
 
@@ -0,0 +1,32 @@
1
+ module PandaCms
2
+ module EditorJs
3
+ module Blocks
4
+ class Alert < Base
5
+ def render
6
+ message = sanitize(data["message"])
7
+ type = data["type"] || "primary"
8
+
9
+ html_safe(
10
+ "<div class=\"#{alert_classes(type)} p-4 mb-4 rounded-lg\">" \
11
+ "#{message}" \
12
+ "</div>"
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ def alert_classes(type)
19
+ case type
20
+ when "primary" then "bg-blue-100 text-blue-800"
21
+ when "secondary" then "bg-gray-100 text-gray-800"
22
+ when "success" then "bg-green-100 text-green-800"
23
+ when "danger" then "bg-red-100 text-red-800"
24
+ when "warning" then "bg-yellow-100 text-yellow-800"
25
+ when "info" then "bg-indigo-100 text-indigo-800"
26
+ else "bg-blue-100 text-blue-800"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ module PandaCms
2
+ module EditorJs
3
+ module Blocks
4
+ class Base
5
+ attr_reader :data, :options
6
+
7
+ def initialize(data, options = {})
8
+ @data = data
9
+ @options = options
10
+ end
11
+
12
+ def render
13
+ ""
14
+ end
15
+
16
+ private
17
+
18
+ def sanitize(text)
19
+ Rails::Html::SafeListSanitizer.new.sanitize(text, tags: %w[b i u a code])
20
+ end
21
+
22
+ def html_safe(text)
23
+ text.to_s.html_safe
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module PandaCms
2
+ module EditorJs
3
+ module Blocks
4
+ class Header < Base
5
+ def render
6
+ content = sanitize(data["text"])
7
+ level = data["level"] || 2
8
+ html_safe("<h#{level}>#{content}</h#{level}>")
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module PandaCms
2
+ module EditorJs
3
+ module Blocks
4
+ class Image < Base
5
+ def render
6
+ url = data["url"]
7
+ caption = sanitize(data["caption"])
8
+ with_border = data["withBorder"]
9
+ with_background = data["withBackground"]
10
+ stretched = data["stretched"]
11
+
12
+ css_classes = ["prose"]
13
+ css_classes << "border" if with_border
14
+ css_classes << "bg-gray-100" if with_background
15
+ css_classes << "w-full" if stretched
16
+
17
+ html_safe(<<~HTML)
18
+ <figure class="#{css_classes.join(" ")}">
19
+ <img src="#{url}" alt="#{caption}" />
20
+ #{caption_element(caption)}
21
+ </figure>
22
+ HTML
23
+ end
24
+
25
+ private
26
+
27
+ def caption_element(caption)
28
+ return "" if caption.blank?
29
+ "<figcaption>#{caption}</figcaption>"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end