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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/panda.cms.css +0 -50
  3. data/app/components/panda/cms/admin/table_component.html.erb +4 -1
  4. data/app/components/panda/cms/code_component.rb +2 -1
  5. data/app/components/panda/cms/rich_text_component.html.erb +86 -2
  6. data/app/components/panda/cms/rich_text_component.rb +131 -20
  7. data/app/controllers/panda/cms/admin/block_contents_controller.rb +18 -7
  8. data/app/controllers/panda/cms/admin/files_controller.rb +22 -12
  9. data/app/controllers/panda/cms/admin/posts_controller.rb +33 -11
  10. data/app/controllers/panda/cms/pages_controller.rb +29 -0
  11. data/app/controllers/panda/cms/posts_controller.rb +26 -4
  12. data/app/helpers/panda/cms/admin/posts_helper.rb +23 -32
  13. data/app/helpers/panda/cms/posts_helper.rb +32 -0
  14. data/app/javascript/panda/cms/controllers/dashboard_controller.js +0 -1
  15. data/app/javascript/panda/cms/controllers/editor_form_controller.js +134 -11
  16. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +395 -130
  17. data/app/javascript/panda/cms/controllers/slug_controller.js +33 -43
  18. data/app/javascript/panda/cms/editor/editor_js_config.js +202 -73
  19. data/app/javascript/panda/cms/editor/editor_js_initializer.js +243 -194
  20. data/app/javascript/panda/cms/editor/plain_text_editor.js +1 -1
  21. data/app/javascript/panda/cms/editor/resource_loader.js +89 -0
  22. data/app/javascript/panda/cms/editor/rich_text_editor.js +162 -0
  23. data/app/models/panda/cms/page.rb +18 -0
  24. data/app/models/panda/cms/post.rb +61 -3
  25. data/app/models/panda/cms/redirect.rb +2 -2
  26. data/app/views/panda/cms/admin/posts/_form.html.erb +15 -4
  27. data/app/views/panda/cms/admin/posts/index.html.erb +5 -3
  28. data/config/routes.rb +34 -6
  29. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -0
  30. data/lib/panda/cms/editor_js_content.rb +14 -1
  31. data/lib/panda/cms/engine.rb +4 -0
  32. data/lib/panda-cms/version.rb +1 -1
  33. 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
- // Relay the error to the parent window
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 the error propagate
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
- // Ensure frame is visible after load
86
- this.frame.style.display = ""
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
- initializeEditors() {
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
- this.initializePlainTextEditors()
171
- this.initializeRichTextEditors()
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
- // Verify Editor.js is available in the iframe context
200
- if (!this.frameDocument.defaultView.EditorJS) {
201
- const error = new Error("Editor.js not loaded in iframe context")
202
- console.error("[Panda CMS]", error)
203
- throw error // This will bubble up and fail the test
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
- console.debug(`[Panda CMS] Created editor holder: ${holderId}`, {
230
- exists: !!verifyHolder,
231
- parent: element.id || 'no-id',
232
- editorJSAvailable: !!this.frameDocument.defaultView.EditorJS
233
- })
234
-
235
- // Initialize editor with empty data
236
- const editor = await initializer.initialize(holderElement, {}, holderId)
237
-
238
- // Set up save handler for this editor
239
- const saveButton = parent.document.getElementById('saveEditableButton')
240
- if (saveButton) {
241
- saveButton.addEventListener('click', async () => {
242
- const outputData = await editor.save()
243
- outputData.source = "editorJS"
244
-
245
- const pageId = element.getAttribute("data-editable-page-id")
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
- if (!response.ok) {
258
- const error = new Error('Save failed')
259
- console.error("[Panda CMS]", error)
260
- throw error
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
- this.handleSuccess()
264
- })
265
- } else {
266
- console.warn("[Panda CMS] Save button not found")
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
- return editor
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
- // Filter out any null editors and add the valid ones
274
- const validEditors = editors.filter(editor => editor !== null)
275
- this.editors.push(...validEditors)
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
- // If we didn't get any valid editors, that's an error
278
- if (validEditors.length === 0) {
279
- const error = new Error("No editors were successfully initialized")
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
  }