panda-cms 0.7.0 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) 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 +1 -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 +3 -6
  32. data/lib/panda-cms/version.rb +1 -1
  33. data/lib/panda-cms.rb +5 -11
  34. metadata +290 -35
@@ -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
  }