panda-cms 0.7.0 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
}
|