panda-cms 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/panda.cms.css +0 -50
- data/app/components/panda/cms/admin/table_component.html.erb +4 -1
- data/app/components/panda/cms/code_component.rb +2 -1
- data/app/components/panda/cms/rich_text_component.html.erb +86 -2
- data/app/components/panda/cms/rich_text_component.rb +131 -20
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +18 -7
- data/app/controllers/panda/cms/admin/files_controller.rb +22 -12
- data/app/controllers/panda/cms/admin/posts_controller.rb +33 -11
- data/app/controllers/panda/cms/pages_controller.rb +29 -0
- data/app/controllers/panda/cms/posts_controller.rb +26 -4
- data/app/helpers/panda/cms/admin/posts_helper.rb +23 -32
- data/app/helpers/panda/cms/posts_helper.rb +32 -0
- data/app/javascript/panda/cms/controllers/dashboard_controller.js +0 -1
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +134 -11
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +395 -130
- data/app/javascript/panda/cms/controllers/slug_controller.js +33 -43
- data/app/javascript/panda/cms/editor/editor_js_config.js +202 -73
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +243 -194
- data/app/javascript/panda/cms/editor/plain_text_editor.js +1 -1
- data/app/javascript/panda/cms/editor/resource_loader.js +89 -0
- data/app/javascript/panda/cms/editor/rich_text_editor.js +162 -0
- data/app/models/panda/cms/page.rb +18 -0
- data/app/models/panda/cms/post.rb +61 -3
- data/app/models/panda/cms/redirect.rb +2 -2
- data/app/views/panda/cms/admin/posts/_form.html.erb +15 -4
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -3
- data/config/routes.rb +34 -6
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -0
- data/lib/panda/cms/editor_js_content.rb +14 -1
- data/lib/panda/cms/engine.rb +4 -0
- data/lib/panda-cms/version.rb +1 -1
- metadata +5 -2
@@ -1,6 +1,8 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
2
2
|
import { PlainTextEditor } from "panda/cms/editor/plain_text_editor"
|
3
3
|
import { EditorJSInitializer } from "panda/cms/editor/editor_js_initializer"
|
4
|
+
import { EDITOR_JS_RESOURCES, EDITOR_JS_CSS } from "panda/cms/editor/editor_js_config"
|
5
|
+
import { ResourceLoader } from "panda/cms/editor/resource_loader"
|
4
6
|
|
5
7
|
export default class extends Controller {
|
6
8
|
static values = {
|
@@ -19,6 +21,8 @@ export default class extends Controller {
|
|
19
21
|
plain: false,
|
20
22
|
rich: false
|
21
23
|
}
|
24
|
+
this.setupSlideoverHandling()
|
25
|
+
this.setupEditorInitializationListener()
|
22
26
|
}
|
23
27
|
|
24
28
|
setupControls() {
|
@@ -47,6 +51,10 @@ export default class extends Controller {
|
|
47
51
|
this.frame.style.height = "100%"
|
48
52
|
this.frame.style.minHeight = "500px"
|
49
53
|
|
54
|
+
// Set up iframe stacking context
|
55
|
+
this.frame.style.position = "relative"
|
56
|
+
this.frame.style.zIndex = "1" // Lower z-index so it doesn't block UI
|
57
|
+
|
50
58
|
// Get CSRF token
|
51
59
|
this.csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ""
|
52
60
|
|
@@ -57,9 +65,31 @@ export default class extends Controller {
|
|
57
65
|
this.body = this.frameDocument.body
|
58
66
|
this.head = this.frameDocument.head
|
59
67
|
|
68
|
+
// Ensure iframe content is properly positioned but doesn't block UI
|
69
|
+
this.body.style.position = "relative"
|
70
|
+
this.body.style.zIndex = "1"
|
71
|
+
|
72
|
+
// Add a class to help identify this frame's editors
|
73
|
+
const frameId = this.frame.id || Math.random().toString(36).substring(7)
|
74
|
+
this.frame.setAttribute('data-editor-frame-id', frameId)
|
75
|
+
this.body.setAttribute('data-editor-frame-id', frameId)
|
76
|
+
|
77
|
+
// Prevent default context menu behavior
|
78
|
+
this.body.addEventListener('contextmenu', (event) => {
|
79
|
+
// Only prevent default if the target is part of the editor
|
80
|
+
if (event.target.closest('.codex-editor')) {
|
81
|
+
event.preventDefault()
|
82
|
+
}
|
83
|
+
})
|
84
|
+
|
60
85
|
// Set up error handling for the iframe
|
61
86
|
this.frameDocument.defaultView.onerror = (message, source, lineno, colno, error) => {
|
62
|
-
//
|
87
|
+
// Ignore context menu errors
|
88
|
+
if (message.includes('anchor.getAttribute') && source.includes('script.js')) {
|
89
|
+
return true // Prevent error from propagating
|
90
|
+
}
|
91
|
+
|
92
|
+
// Relay other errors to the parent window
|
63
93
|
const fullMessage = `iFrame Error: ${message} (${source}:${lineno}:${colno})`
|
64
94
|
console.error(fullMessage, error)
|
65
95
|
|
@@ -68,11 +98,16 @@ export default class extends Controller {
|
|
68
98
|
throw new Error(fullMessage)
|
69
99
|
}, 0)
|
70
100
|
|
71
|
-
return false // Let
|
101
|
+
return false // Let other errors propagate
|
72
102
|
}
|
73
103
|
|
74
104
|
// Set up unhandled rejection handling for the iframe
|
75
105
|
this.frameDocument.defaultView.onunhandledrejection = (event) => {
|
106
|
+
// Ignore context menu related rejections
|
107
|
+
if (event.reason?.toString().includes('anchor.getAttribute')) {
|
108
|
+
return
|
109
|
+
}
|
110
|
+
|
76
111
|
const fullMessage = `iFrame Unhandled Promise Rejection: ${event.reason}`
|
77
112
|
console.error(fullMessage)
|
78
113
|
|
@@ -82,53 +117,8 @@ export default class extends Controller {
|
|
82
117
|
}, 0)
|
83
118
|
}
|
84
119
|
|
85
|
-
//
|
86
|
-
this.
|
87
|
-
this.ensureFrameVisibility()
|
88
|
-
|
89
|
-
// Wait for document to be ready
|
90
|
-
if (this.frameDocument.readyState !== 'complete') {
|
91
|
-
await new Promise(resolve => {
|
92
|
-
this.frameDocument.addEventListener('DOMContentLoaded', resolve)
|
93
|
-
})
|
94
|
-
}
|
95
|
-
|
96
|
-
// Load Editor.js resources in the iframe context
|
97
|
-
try {
|
98
|
-
const { EDITOR_JS_RESOURCES, EDITOR_JS_CSS } = await import("panda/cms/editor/editor_js_config")
|
99
|
-
const { ResourceLoader } = await import("panda/cms/editor/resource_loader")
|
100
|
-
|
101
|
-
// First load EditorJS core
|
102
|
-
const editorCore = EDITOR_JS_RESOURCES[0]
|
103
|
-
await ResourceLoader.loadScript(this.frameDocument, this.head, editorCore)
|
104
|
-
|
105
|
-
// Then load all tools in parallel
|
106
|
-
const toolLoads = EDITOR_JS_RESOURCES.slice(1).map(async (resource) => {
|
107
|
-
await ResourceLoader.loadScript(this.frameDocument, this.head, resource)
|
108
|
-
})
|
109
|
-
|
110
|
-
// Load CSS directly
|
111
|
-
await ResourceLoader.embedCSS(this.frameDocument, this.head, EDITOR_JS_CSS)
|
112
|
-
|
113
|
-
// Wait for all resources to load
|
114
|
-
await Promise.all(toolLoads)
|
115
|
-
console.debug("[Panda CMS] Editor resources loaded in iframe")
|
116
|
-
|
117
|
-
// Wait a small amount of time for scripts to initialize
|
118
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
119
|
-
|
120
|
-
// Initialize editors only if we have the body and editable elements
|
121
|
-
if (this.body && this.body.querySelector('[data-editable-kind]')) {
|
122
|
-
await this.initializeEditors()
|
123
|
-
} else {
|
124
|
-
const error = new Error("[Panda CMS] Frame body or editable elements not found")
|
125
|
-
console.error(error)
|
126
|
-
throw error
|
127
|
-
}
|
128
|
-
} catch (error) {
|
129
|
-
console.error("[Panda CMS] Error loading editor resources in iframe:", error)
|
130
|
-
throw error
|
131
|
-
}
|
120
|
+
// Initialize editors after frame is loaded
|
121
|
+
await this.initializeEditors()
|
132
122
|
})
|
133
123
|
}
|
134
124
|
|
@@ -153,7 +143,18 @@ export default class extends Controller {
|
|
153
143
|
})
|
154
144
|
}
|
155
145
|
|
156
|
-
|
146
|
+
setupEditorInitializationListener() {
|
147
|
+
// Listen for the custom event to initialize editors
|
148
|
+
this.frame.addEventListener("load", () => {
|
149
|
+
const win = this.frame.contentWindow || this.frame.contentDocument.defaultView
|
150
|
+
win.addEventListener('panda-cms:initialize-editors', async () => {
|
151
|
+
console.debug("[Panda CMS] Received initialize-editors event")
|
152
|
+
await this.initializeEditors()
|
153
|
+
})
|
154
|
+
})
|
155
|
+
}
|
156
|
+
|
157
|
+
async initializeEditors() {
|
157
158
|
console.debug("[Panda CMS] Starting editor initialization")
|
158
159
|
|
159
160
|
// Get all editable elements
|
@@ -167,11 +168,125 @@ export default class extends Controller {
|
|
167
168
|
|
168
169
|
// Initialize editors if they exist
|
169
170
|
if (plainTextElements.length > 0 || richTextElements.length > 0) {
|
170
|
-
|
171
|
-
|
171
|
+
try {
|
172
|
+
// Load resources first
|
173
|
+
await this.loadEditorResources()
|
174
|
+
|
175
|
+
// Initialize editors
|
176
|
+
await Promise.all([
|
177
|
+
this.initializePlainTextEditors(),
|
178
|
+
this.initializeRichTextEditors()
|
179
|
+
])
|
180
|
+
|
181
|
+
console.debug("[Panda CMS] All editors initialized successfully")
|
182
|
+
} catch (error) {
|
183
|
+
console.error("[Panda CMS] Error initializing editors:", error)
|
184
|
+
throw error
|
185
|
+
}
|
172
186
|
}
|
173
187
|
}
|
174
188
|
|
189
|
+
async loadEditorResources() {
|
190
|
+
console.debug("[Panda CMS] Loading editor resources in iframe...")
|
191
|
+
try {
|
192
|
+
// First load core EditorJS
|
193
|
+
await ResourceLoader.loadScript(this.frameDocument, this.frameDocument.head, EDITOR_JS_RESOURCES[0])
|
194
|
+
|
195
|
+
// Wait for EditorJS to be available with increased timeout
|
196
|
+
let timeout = 10000 // 10 seconds
|
197
|
+
const start = Date.now()
|
198
|
+
|
199
|
+
while (Date.now() - start < timeout) {
|
200
|
+
if (this.frameDocument.defaultView.EditorJS) {
|
201
|
+
console.debug("[Panda CMS] EditorJS core loaded successfully")
|
202
|
+
break
|
203
|
+
}
|
204
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
205
|
+
}
|
206
|
+
|
207
|
+
if (!this.frameDocument.defaultView.EditorJS) {
|
208
|
+
throw new Error("Timeout waiting for EditorJS core to load")
|
209
|
+
}
|
210
|
+
|
211
|
+
// Load CSS
|
212
|
+
await ResourceLoader.embedCSS(this.frameDocument, this.frameDocument.head, EDITOR_JS_CSS)
|
213
|
+
|
214
|
+
// Then load all tools sequentially
|
215
|
+
for (const resource of EDITOR_JS_RESOURCES.slice(1)) {
|
216
|
+
await ResourceLoader.loadScript(this.frameDocument, this.frameDocument.head, resource)
|
217
|
+
}
|
218
|
+
|
219
|
+
console.debug("[Panda CMS] All editor resources loaded successfully")
|
220
|
+
} catch (error) {
|
221
|
+
console.error("[Panda CMS] Error loading editor resources:", error)
|
222
|
+
throw error
|
223
|
+
}
|
224
|
+
}
|
225
|
+
|
226
|
+
waitForEditorJS() {
|
227
|
+
return new Promise((resolve, reject) => {
|
228
|
+
const timeout = setTimeout(() => {
|
229
|
+
reject(new Error("Timeout waiting for EditorJS to load"))
|
230
|
+
}, 10000)
|
231
|
+
|
232
|
+
const check = () => {
|
233
|
+
if (this.frameDocument.defaultView.EditorJS) {
|
234
|
+
clearTimeout(timeout)
|
235
|
+
resolve()
|
236
|
+
} else {
|
237
|
+
setTimeout(check, 100)
|
238
|
+
}
|
239
|
+
}
|
240
|
+
check()
|
241
|
+
})
|
242
|
+
}
|
243
|
+
|
244
|
+
waitForTool(toolName) {
|
245
|
+
return new Promise((resolve, reject) => {
|
246
|
+
const timeout = setTimeout(() => {
|
247
|
+
reject(new Error(`Timeout waiting for ${toolName} to load`))
|
248
|
+
}, 10000)
|
249
|
+
|
250
|
+
// Map tool names to their expected global class names
|
251
|
+
const toolClassMap = {
|
252
|
+
'paragraph': 'Paragraph',
|
253
|
+
'header': 'Header',
|
254
|
+
'nested-list': 'NestedList',
|
255
|
+
'quote': 'Quote',
|
256
|
+
'simple-image': 'SimpleImage',
|
257
|
+
'table': 'Table',
|
258
|
+
'embed': 'Embed'
|
259
|
+
}
|
260
|
+
|
261
|
+
const check = () => {
|
262
|
+
// Get the expected class name for this tool
|
263
|
+
const expectedClassName = toolClassMap[toolName] || toolName
|
264
|
+
const toolClass = this.frameDocument.defaultView[expectedClassName]
|
265
|
+
|
266
|
+
console.debug(`[Panda CMS] Checking for tool ${toolName} -> ${expectedClassName}:`, {
|
267
|
+
toolFound: !!toolClass,
|
268
|
+
availableClasses: Object.keys(this.frameDocument.defaultView).filter(key =>
|
269
|
+
key.includes('Header') ||
|
270
|
+
key.includes('Paragraph') ||
|
271
|
+
key.includes('List') ||
|
272
|
+
key.includes('Quote') ||
|
273
|
+
key.includes('Image') ||
|
274
|
+
key.includes('Table') ||
|
275
|
+
key.includes('Embed')
|
276
|
+
)
|
277
|
+
})
|
278
|
+
|
279
|
+
if (toolClass) {
|
280
|
+
clearTimeout(timeout)
|
281
|
+
resolve()
|
282
|
+
} else {
|
283
|
+
setTimeout(check, 100)
|
284
|
+
}
|
285
|
+
}
|
286
|
+
check()
|
287
|
+
})
|
288
|
+
}
|
289
|
+
|
175
290
|
initializePlainTextEditors() {
|
176
291
|
this.editorsInitialized.plain = false
|
177
292
|
const plainTextElements = this.body.querySelectorAll('[data-editable-kind="plain_text"], [data-editable-kind="markdown"], [data-editable-kind="html"]')
|
@@ -196,94 +311,205 @@ export default class extends Controller {
|
|
196
311
|
console.debug(`[Panda CMS] Found ${richTextElements.length} rich text elements`)
|
197
312
|
|
198
313
|
if (richTextElements.length > 0) {
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
const initializer = new EditorJSInitializer(this.frameDocument, true)
|
207
|
-
|
208
|
-
// Don't wrap in try/catch to let errors bubble up
|
209
|
-
const editors = await Promise.all(
|
210
|
-
Array.from(richTextElements).map(async element => {
|
211
|
-
// Create holder element before initialization
|
212
|
-
const holderId = `editor-${Math.random().toString(36).substr(2, 9)}`
|
213
|
-
const holderElement = this.frameDocument.createElement('div')
|
214
|
-
holderElement.id = holderId
|
215
|
-
holderElement.className = 'editor-js-holder codex-editor'
|
216
|
-
element.appendChild(holderElement)
|
217
|
-
|
218
|
-
// Wait for the holder element to be in the DOM
|
219
|
-
await new Promise(resolve => setTimeout(resolve, 0))
|
220
|
-
|
221
|
-
// Verify the holder element exists
|
222
|
-
const verifyHolder = this.frameDocument.getElementById(holderId)
|
223
|
-
if (!verifyHolder) {
|
224
|
-
const error = new Error(`Failed to create editor holder element ${holderId}`)
|
225
|
-
console.error("[Panda CMS]", error)
|
226
|
-
throw error // This will bubble up and fail the test
|
227
|
-
}
|
314
|
+
try {
|
315
|
+
// Verify Editor.js is available in the iframe context
|
316
|
+
if (!this.frameDocument.defaultView.EditorJS) {
|
317
|
+
const error = new Error("Editor.js not loaded in iframe context")
|
318
|
+
console.error("[Panda CMS]", error)
|
319
|
+
throw error
|
320
|
+
}
|
228
321
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
const blockContentId = element.getAttribute("data-editable-block-content-id")
|
247
|
-
|
248
|
-
const response = await fetch(`${this.adminPathValue}/pages/${pageId}/block_contents/${blockContentId}`, {
|
249
|
-
method: "PATCH",
|
250
|
-
headers: {
|
251
|
-
"Content-Type": "application/json",
|
252
|
-
"X-CSRF-Token": this.csrfToken
|
253
|
-
},
|
254
|
-
body: JSON.stringify({ content: outputData })
|
255
|
-
})
|
322
|
+
const initializer = new EditorJSInitializer(this.frameDocument, true)
|
323
|
+
|
324
|
+
// Initialize each editor sequentially to avoid race conditions
|
325
|
+
const editors = []
|
326
|
+
for (const element of richTextElements) {
|
327
|
+
try {
|
328
|
+
// Skip already initialized editors
|
329
|
+
if (element.dataset.editableInitialized === 'true' && element.querySelector('.codex-editor')) {
|
330
|
+
console.debug('[Panda CMS] Editor already initialized:', element.id)
|
331
|
+
continue
|
332
|
+
}
|
333
|
+
|
334
|
+
console.debug('[Panda CMS] Initializing editor for element:', {
|
335
|
+
id: element.id,
|
336
|
+
kind: element.getAttribute('data-editable-kind'),
|
337
|
+
blockContentId: element.getAttribute('data-editable-block-content-id')
|
338
|
+
})
|
256
339
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
340
|
+
// Create holder element before initialization
|
341
|
+
const holderId = `editor-${Math.random().toString(36).substr(2, 9)}`
|
342
|
+
const holderElement = this.frameDocument.createElement('div')
|
343
|
+
holderElement.id = holderId
|
344
|
+
holderElement.className = 'editor-js-holder codex-editor'
|
345
|
+
|
346
|
+
// Clear any existing content
|
347
|
+
element.textContent = ''
|
348
|
+
element.appendChild(holderElement)
|
349
|
+
|
350
|
+
// Get previous data from the data attribute if available
|
351
|
+
let previousData = { blocks: [] }
|
352
|
+
const previousDataAttr = element.getAttribute('data-editable-previous-data')
|
353
|
+
if (previousDataAttr) {
|
354
|
+
try {
|
355
|
+
let parsedData
|
356
|
+
try {
|
357
|
+
// First try to parse as base64
|
358
|
+
const decodedData = atob(previousDataAttr)
|
359
|
+
console.debug('[Panda CMS] Decoded base64 data:', decodedData)
|
360
|
+
parsedData = JSON.parse(decodedData)
|
361
|
+
} catch (e) {
|
362
|
+
// If base64 fails, try direct JSON parse
|
363
|
+
console.debug('[Panda CMS] Trying direct JSON parse')
|
364
|
+
parsedData = JSON.parse(previousDataAttr)
|
365
|
+
}
|
366
|
+
|
367
|
+
// Check if we have double-encoded data
|
368
|
+
if (parsedData.blocks?.length === 1 &&
|
369
|
+
parsedData.blocks[0]?.type === 'paragraph' &&
|
370
|
+
parsedData.blocks[0]?.data?.text) {
|
371
|
+
try {
|
372
|
+
// Try to parse the inner JSON
|
373
|
+
const innerData = JSON.parse(parsedData.blocks[0].data.text)
|
374
|
+
if (innerData.blocks) {
|
375
|
+
console.debug('[Panda CMS] Found double-encoded data, using inner content:', innerData)
|
376
|
+
parsedData = innerData
|
377
|
+
}
|
378
|
+
} catch (e) {
|
379
|
+
// If parsing fails, use the outer data
|
380
|
+
console.debug('[Panda CMS] Not double-encoded data, using as is')
|
381
|
+
}
|
382
|
+
}
|
383
|
+
|
384
|
+
if (parsedData && typeof parsedData === 'object' && Array.isArray(parsedData.blocks)) {
|
385
|
+
previousData = parsedData
|
386
|
+
console.debug('[Panda CMS] Using previous data:', previousData)
|
387
|
+
} else {
|
388
|
+
console.warn('[Panda CMS] Invalid data format:', parsedData)
|
389
|
+
}
|
390
|
+
} catch (error) {
|
391
|
+
console.error("[Panda CMS] Error parsing previous data:", error)
|
392
|
+
// If we can't parse the data, try to use it as plain text
|
393
|
+
previousData = {
|
394
|
+
time: Date.now(),
|
395
|
+
blocks: [
|
396
|
+
{
|
397
|
+
type: "paragraph",
|
398
|
+
data: {
|
399
|
+
text: element.textContent || ""
|
400
|
+
}
|
401
|
+
}
|
402
|
+
],
|
403
|
+
version: "2.28.2"
|
404
|
+
}
|
261
405
|
}
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
406
|
+
}
|
407
|
+
|
408
|
+
// Initialize editor with retry logic
|
409
|
+
let editor = null
|
410
|
+
let retryCount = 0
|
411
|
+
const maxRetries = 3
|
412
|
+
|
413
|
+
while (!editor && retryCount < maxRetries) {
|
414
|
+
try {
|
415
|
+
console.debug(`[Panda CMS] Editor initialization attempt ${retryCount + 1}`)
|
416
|
+
editor = await initializer.initialize(holderElement, previousData, holderId)
|
417
|
+
console.debug('[Panda CMS] Editor initialized successfully:', editor)
|
418
|
+
|
419
|
+
// Set up autosave if enabled
|
420
|
+
if (this.autosaveValue) {
|
421
|
+
editor.isReady.then(() => {
|
422
|
+
editor.save().then((outputData) => {
|
423
|
+
const jsonString = JSON.stringify(outputData)
|
424
|
+
element.dataset.editablePreviousData = btoa(jsonString)
|
425
|
+
element.dataset.editableContent = jsonString
|
426
|
+
element.dataset.editableInitialized = 'true'
|
427
|
+
})
|
428
|
+
})
|
429
|
+
}
|
430
|
+
|
431
|
+
break
|
432
|
+
} catch (error) {
|
433
|
+
console.warn(`[Panda CMS] Editor initialization attempt ${retryCount + 1} failed:`, error)
|
434
|
+
retryCount++
|
435
|
+
if (retryCount === maxRetries) {
|
436
|
+
throw error
|
437
|
+
}
|
438
|
+
// Wait before retrying
|
439
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
440
|
+
}
|
441
|
+
}
|
442
|
+
|
443
|
+
// Set up save handler for this editor
|
444
|
+
const saveButton = parent.document.getElementById('saveEditableButton')
|
445
|
+
if (saveButton) {
|
446
|
+
saveButton.addEventListener('click', async () => {
|
447
|
+
try {
|
448
|
+
const outputData = await editor.save()
|
449
|
+
console.debug('[Panda CMS] Editor save data:', outputData)
|
450
|
+
|
451
|
+
const pageId = element.getAttribute("data-editable-page-id")
|
452
|
+
const blockContentId = element.getAttribute("data-editable-block-content-id")
|
453
|
+
|
454
|
+
const response = await fetch(`${this.adminPathValue}/pages/${pageId}/block_contents/${blockContentId}`, {
|
455
|
+
method: "PATCH",
|
456
|
+
headers: {
|
457
|
+
"Content-Type": "application/json",
|
458
|
+
"X-CSRF-Token": this.csrfToken
|
459
|
+
},
|
460
|
+
body: JSON.stringify({ content: outputData })
|
461
|
+
})
|
462
|
+
|
463
|
+
if (!response.ok) {
|
464
|
+
throw new Error('Save failed')
|
465
|
+
}
|
466
|
+
|
467
|
+
// Update the data attributes with the new content
|
468
|
+
const jsonString = JSON.stringify(outputData)
|
469
|
+
element.dataset.editablePreviousData = btoa(jsonString)
|
470
|
+
element.dataset.editableContent = jsonString
|
471
|
+
element.dataset.editableInitialized = 'true'
|
472
|
+
|
473
|
+
this.handleSuccess()
|
474
|
+
} catch (error) {
|
475
|
+
console.error("[Panda CMS] Save error:", error)
|
476
|
+
this.handleError(error)
|
477
|
+
}
|
478
|
+
})
|
479
|
+
} else {
|
480
|
+
console.warn("[Panda CMS] Save button not found")
|
481
|
+
}
|
482
|
+
|
483
|
+
if (editor) {
|
484
|
+
editors.push(editor)
|
485
|
+
}
|
486
|
+
} catch (error) {
|
487
|
+
console.error("[Panda CMS] Editor initialization error:", error)
|
488
|
+
throw error
|
267
489
|
}
|
490
|
+
}
|
268
491
|
|
269
|
-
|
270
|
-
|
271
|
-
|
492
|
+
// Filter out any null editors and add the valid ones
|
493
|
+
const validEditors = editors.filter(editor => editor !== null)
|
494
|
+
this.editors.push(...validEditors)
|
272
495
|
|
273
|
-
|
274
|
-
|
275
|
-
|
496
|
+
// If we didn't get any valid editors, that's an error
|
497
|
+
if (validEditors.length === 0 && richTextElements.length > 0) {
|
498
|
+
const error = new Error("No editors were successfully initialized")
|
499
|
+
console.error("[Panda CMS]", error)
|
500
|
+
throw error
|
501
|
+
}
|
276
502
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
console.error("[Panda CMS]", error)
|
503
|
+
this.editorsInitialized.rich = true
|
504
|
+
this.checkAllEditorsInitialized()
|
505
|
+
} catch (error) {
|
506
|
+
console.error("[Panda CMS] Rich text editor initialization failed:", error)
|
281
507
|
throw error
|
282
508
|
}
|
509
|
+
} else {
|
510
|
+
this.editorsInitialized.rich = true
|
511
|
+
this.checkAllEditorsInitialized()
|
283
512
|
}
|
284
|
-
|
285
|
-
this.editorsInitialized.rich = true
|
286
|
-
this.checkAllEditorsInitialized()
|
287
513
|
}
|
288
514
|
|
289
515
|
checkAllEditorsInitialized() {
|
@@ -317,4 +543,43 @@ export default class extends Controller {
|
|
317
543
|
}, 3000)
|
318
544
|
}
|
319
545
|
}
|
546
|
+
|
547
|
+
setupSlideoverHandling() {
|
548
|
+
// Watch for slideover toggle
|
549
|
+
const slideoverToggle = document.getElementById('slideover-toggle')
|
550
|
+
const slideover = document.getElementById('slideover')
|
551
|
+
|
552
|
+
if (slideoverToggle && slideover) {
|
553
|
+
const observer = new MutationObserver((mutations) => {
|
554
|
+
mutations.forEach((mutation) => {
|
555
|
+
if (mutation.attributeName === 'class') {
|
556
|
+
const isVisible = !slideover.classList.contains('hidden')
|
557
|
+
this.adjustFrameZIndex(isVisible)
|
558
|
+
}
|
559
|
+
})
|
560
|
+
})
|
561
|
+
|
562
|
+
observer.observe(slideover, { attributes: true })
|
563
|
+
|
564
|
+
// Initial state
|
565
|
+
this.adjustFrameZIndex(!slideover.classList.contains('hidden'))
|
566
|
+
}
|
567
|
+
}
|
568
|
+
|
569
|
+
adjustFrameZIndex(slideoverVisible) {
|
570
|
+
if (slideoverVisible) {
|
571
|
+
// Lower z-index when slideover is visible
|
572
|
+
this.frame.style.zIndex = "0"
|
573
|
+
if (this.body) this.body.style.zIndex = "0"
|
574
|
+
} else {
|
575
|
+
// Restore z-index when slideover is hidden
|
576
|
+
this.frame.style.zIndex = "1"
|
577
|
+
if (this.body) this.body.style.zIndex = "1"
|
578
|
+
}
|
579
|
+
console.debug("[Panda CMS] Adjusted frame z-index:", {
|
580
|
+
slideoverVisible,
|
581
|
+
frameZIndex: this.frame.style.zIndex,
|
582
|
+
bodyZIndex: this.body?.style.zIndex
|
583
|
+
})
|
584
|
+
}
|
320
585
|
}
|