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
@@ -51,235 +51,284 @@ export class EditorJSInitializer {
|
|
51
51
|
const editorCore = EDITOR_JS_RESOURCES[0]
|
52
52
|
await ResourceLoader.loadScript(this.document, this.document.head, editorCore)
|
53
53
|
|
54
|
-
//
|
55
|
-
|
56
|
-
await ResourceLoader.loadScript(this.document, this.document.head, resource)
|
57
|
-
})
|
54
|
+
// Wait for EditorJS to be available
|
55
|
+
await this.waitForEditorJS()
|
58
56
|
|
59
57
|
// Load CSS directly
|
60
58
|
await ResourceLoader.embedCSS(this.document, this.document.head, EDITOR_JS_CSS)
|
61
59
|
|
62
|
-
//
|
63
|
-
|
60
|
+
// Then load all tools sequentially to ensure proper initialization order
|
61
|
+
for (const resource of EDITOR_JS_RESOURCES.slice(1)) {
|
62
|
+
try {
|
63
|
+
await ResourceLoader.loadScript(this.document, this.document.head, resource)
|
64
|
+
// Extract tool name from resource URL
|
65
|
+
const toolName = resource.split('/').pop().split('@')[0]
|
66
|
+
// Wait for tool to be initialized
|
67
|
+
const toolClass = await this.waitForTool(toolName)
|
68
|
+
|
69
|
+
// If this is the nested-list tool, also make it available as 'list'
|
70
|
+
if (toolName === 'nested-list') {
|
71
|
+
const win = this.document.defaultView || window
|
72
|
+
win.List = toolClass
|
73
|
+
}
|
64
74
|
|
65
|
-
|
66
|
-
|
75
|
+
console.debug(`[Panda CMS] Successfully loaded tool: ${toolName}`)
|
76
|
+
} catch (error) {
|
77
|
+
console.error(`[Panda CMS] Failed to load tool: ${resource}`, error)
|
78
|
+
throw error
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
console.debug('[Panda CMS] All tools successfully loaded and verified')
|
67
83
|
} catch (error) {
|
84
|
+
console.error('[Panda CMS] Error loading Editor.js resources:', error)
|
68
85
|
throw error
|
69
86
|
}
|
70
87
|
}
|
71
88
|
|
72
|
-
async
|
73
|
-
|
74
|
-
const
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
holderElement.id = holderId
|
82
|
-
holderElement.className = 'editor-js-holder codex-editor'
|
83
|
-
|
84
|
-
// Append to the element and force a reflow
|
85
|
-
element.appendChild(holderElement)
|
86
|
-
void holderElement.offsetHeight // Force a reflow
|
89
|
+
async waitForEditorJS(timeout = 10000) {
|
90
|
+
console.debug('[Panda CMS] Waiting for EditorJS core...')
|
91
|
+
const start = Date.now()
|
92
|
+
while (Date.now() - start < timeout) {
|
93
|
+
if (typeof this.document.defaultView.EditorJS === 'function') {
|
94
|
+
console.debug('[Panda CMS] EditorJS core is ready')
|
95
|
+
return
|
96
|
+
}
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
87
98
|
}
|
99
|
+
throw new Error('[Panda CMS] Timeout waiting for EditorJS')
|
100
|
+
}
|
88
101
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
102
|
+
/**
|
103
|
+
* Wait for a specific tool to be available in window context
|
104
|
+
*/
|
105
|
+
async waitForTool(toolName, timeout = 10000) {
|
106
|
+
if (!toolName) {
|
107
|
+
console.error('[Panda CMS] Invalid tool name provided')
|
108
|
+
return null
|
93
109
|
}
|
94
110
|
|
95
|
-
//
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
111
|
+
// Clean up tool name to handle npm package format
|
112
|
+
const cleanToolName = toolName.split('/').pop().replace('@', '')
|
113
|
+
|
114
|
+
const toolMapping = {
|
115
|
+
'paragraph': 'Paragraph',
|
116
|
+
'header': 'Header',
|
117
|
+
'nested-list': 'NestedList',
|
118
|
+
'list': 'NestedList',
|
119
|
+
'quote': 'Quote',
|
120
|
+
'simple-image': 'SimpleImage',
|
121
|
+
'table': 'Table',
|
122
|
+
'embed': 'Embed'
|
101
123
|
}
|
102
124
|
|
103
|
-
|
104
|
-
const
|
125
|
+
const globalToolName = toolMapping[cleanToolName] || cleanToolName
|
126
|
+
const start = Date.now()
|
105
127
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
// Ensure tools use the correct window context
|
116
|
-
paragraph: { ...config.tools.paragraph, class: this.document.defaultView.Paragraph },
|
117
|
-
header: { ...config.tools.header, class: this.document.defaultView.Header },
|
118
|
-
list: { ...config.tools.list, class: this.document.defaultView.NestedList },
|
119
|
-
quote: { ...config.tools.quote, class: this.document.defaultView.Quote },
|
120
|
-
table: { ...config.tools.table, class: this.document.defaultView.Table },
|
121
|
-
image: { ...config.tools.image, class: this.document.defaultView.SimpleImage },
|
122
|
-
embed: { ...config.tools.embed, class: this.document.defaultView.Embed }
|
128
|
+
while (Date.now() - start < timeout) {
|
129
|
+
const win = this.document.defaultView || window
|
130
|
+
if (win[globalToolName] && typeof win[globalToolName] === 'function') {
|
131
|
+
// If this is the NestedList tool, make it available as both list and nested-list
|
132
|
+
if (globalToolName === 'NestedList') {
|
133
|
+
win.List = win[globalToolName]
|
134
|
+
}
|
135
|
+
console.debug(`[Panda CMS] Successfully loaded tool: ${globalToolName}`)
|
136
|
+
return win[globalToolName]
|
123
137
|
}
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
124
139
|
}
|
140
|
+
throw new Error(`[Panda CMS] Timeout waiting for tool: ${cleanToolName} (${globalToolName})`)
|
141
|
+
}
|
125
142
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
// Store the editor instance globally for testing
|
131
|
-
if (this.withinIFrame) {
|
132
|
-
this.document.defaultView.editor = editor
|
133
|
-
} else {
|
134
|
-
window.editor = editor
|
135
|
-
}
|
143
|
+
async initializeEditor(element, initialData = {}, editorId = null) {
|
144
|
+
try {
|
145
|
+
// Wait for EditorJS core to be available with increased timeout
|
146
|
+
await this.waitForEditorJS(15000)
|
136
147
|
|
137
|
-
|
138
|
-
|
148
|
+
// Get the window context (either iframe or parent)
|
149
|
+
const win = this.document.defaultView || window
|
139
150
|
|
140
|
-
|
141
|
-
|
142
|
-
try {
|
143
|
-
const toolbar = holderElement.querySelector('.ce-toolbar')
|
144
|
-
const blockWrapper = holderElement.querySelector('.ce-block')
|
151
|
+
// Create a unique ID for this editor instance if not provided
|
152
|
+
const uniqueId = editorId || `editor-${Math.random().toString(36).substring(2)}`
|
145
153
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
154
|
+
// Check if editor already exists
|
155
|
+
const existingEditor = element.querySelector('.codex-editor')
|
156
|
+
if (existingEditor) {
|
157
|
+
console.debug('[Panda CMS] Editor already exists, cleaning up...')
|
158
|
+
existingEditor.remove()
|
159
|
+
}
|
150
160
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
161
|
+
// Create a holder div for the editor
|
162
|
+
const holder = this.document.createElement("div")
|
163
|
+
holder.id = uniqueId
|
164
|
+
holder.classList.add("editor-js-holder")
|
165
|
+
element.appendChild(holder)
|
166
|
+
|
167
|
+
// Process initial data to handle list items and other content
|
168
|
+
let processedData = initialData
|
169
|
+
if (initialData.blocks) {
|
170
|
+
processedData = {
|
171
|
+
...initialData,
|
172
|
+
blocks: initialData.blocks.map(block => {
|
173
|
+
if (block.type === 'list' && block.data && Array.isArray(block.data.items)) {
|
174
|
+
return {
|
175
|
+
...block,
|
176
|
+
data: {
|
177
|
+
...block.data,
|
178
|
+
items: block.data.items.map(item => {
|
179
|
+
// Handle both string items and object items
|
180
|
+
if (typeof item === 'string') {
|
181
|
+
return {
|
182
|
+
content: item,
|
183
|
+
items: []
|
184
|
+
}
|
185
|
+
} else if (item.content) {
|
186
|
+
return {
|
187
|
+
content: item.content,
|
188
|
+
items: Array.isArray(item.items) ? item.items : []
|
189
|
+
}
|
190
|
+
} else {
|
191
|
+
return {
|
192
|
+
content: String(item),
|
193
|
+
items: []
|
194
|
+
}
|
195
|
+
}
|
196
|
+
})
|
197
|
+
}
|
198
|
+
}
|
155
199
|
}
|
200
|
+
return block
|
201
|
+
})
|
202
|
+
}
|
203
|
+
}
|
156
204
|
|
157
|
-
|
158
|
-
|
159
|
-
|
205
|
+
console.debug('[Panda CMS] Processed initial data:', processedData)
|
206
|
+
|
207
|
+
// Create editor configuration
|
208
|
+
const config = {
|
209
|
+
holder: holder,
|
210
|
+
data: processedData,
|
211
|
+
placeholder: 'Click to start writing...',
|
212
|
+
tools: {
|
213
|
+
paragraph: {
|
214
|
+
class: win.Paragraph,
|
215
|
+
inlineToolbar: true,
|
216
|
+
config: {
|
217
|
+
preserveBlank: true,
|
218
|
+
placeholder: 'Click to start writing...'
|
160
219
|
}
|
161
|
-
}
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
const outputData = await api.saver.save()
|
170
|
-
outputData.source = "editorJS"
|
171
|
-
const contentJson = JSON.stringify(outputData)
|
172
|
-
|
173
|
-
if (!this.withinIFrame) {
|
174
|
-
// For form-based editors, update the hidden input
|
175
|
-
const form = element.closest('[data-controller="editor-form"]')
|
176
|
-
if (form) {
|
177
|
-
const hiddenInput = form.querySelector('[data-editor-form-target="hiddenField"]')
|
178
|
-
if (hiddenInput) {
|
179
|
-
hiddenInput.value = contentJson
|
180
|
-
hiddenInput.dataset.initialContent = contentJson
|
181
|
-
hiddenInput.dispatchEvent(new Event('change', { bubbles: true }))
|
182
|
-
}
|
220
|
+
},
|
221
|
+
header: {
|
222
|
+
class: win.Header,
|
223
|
+
inlineToolbar: true,
|
224
|
+
config: {
|
225
|
+
placeholder: 'Enter a header',
|
226
|
+
levels: [1, 2, 3, 4, 5, 6],
|
227
|
+
defaultLevel: 2
|
183
228
|
}
|
184
|
-
}
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
try {
|
200
|
-
const pageId = element.getAttribute("data-editable-page-id")
|
201
|
-
const blockContentId = element.getAttribute("data-editable-block-content-id")
|
202
|
-
const pendingContent = JSON.parse(saveButton.dataset.pendingContent || '{}')
|
203
|
-
|
204
|
-
const response = await fetch(`${this.adminPathValue}/pages/${pageId}/block_contents/${blockContentId}`, {
|
205
|
-
method: "PATCH",
|
206
|
-
headers: {
|
207
|
-
"Content-Type": "application/json",
|
208
|
-
"X-CSRF-Token": this.csrfToken
|
209
|
-
},
|
210
|
-
body: JSON.stringify({ content: pendingContent })
|
211
|
-
})
|
212
|
-
|
213
|
-
if (!response.ok) {
|
214
|
-
throw new Error('Save failed')
|
215
|
-
}
|
216
|
-
|
217
|
-
// Clear pending content after successful save
|
218
|
-
delete saveButton.dataset.pendingContent
|
219
|
-
} catch (error) {
|
220
|
-
console.error('Error saving content:', error)
|
221
|
-
}
|
222
|
-
})
|
223
|
-
}
|
229
|
+
},
|
230
|
+
'list': { // Register as list instead of nested-list
|
231
|
+
class: win.NestedList,
|
232
|
+
inlineToolbar: true,
|
233
|
+
config: {
|
234
|
+
defaultStyle: 'unordered',
|
235
|
+
enableLineBreaks: true
|
236
|
+
}
|
237
|
+
},
|
238
|
+
quote: {
|
239
|
+
class: win.Quote,
|
240
|
+
inlineToolbar: true,
|
241
|
+
config: {
|
242
|
+
quotePlaceholder: 'Enter a quote',
|
243
|
+
captionPlaceholder: 'Quote\'s author'
|
224
244
|
}
|
225
245
|
}
|
226
|
-
}
|
227
|
-
|
246
|
+
},
|
247
|
+
onChange: (api, event) => {
|
248
|
+
console.debug('[Panda CMS] Editor content changed:', { api, event })
|
249
|
+
// Save content to data attributes
|
250
|
+
api.saver.save().then((outputData) => {
|
251
|
+
const jsonString = JSON.stringify(outputData)
|
252
|
+
element.dataset.editablePreviousData = btoa(jsonString)
|
253
|
+
element.dataset.editableContent = jsonString
|
254
|
+
element.dataset.editableInitialized = 'true'
|
255
|
+
})
|
256
|
+
},
|
257
|
+
onReady: () => {
|
258
|
+
console.debug('[Panda CMS] Editor ready with data:', processedData)
|
259
|
+
element.dataset.editableInitialized = 'true'
|
260
|
+
holder.editorInstance = editor
|
261
|
+
},
|
262
|
+
onError: (error) => {
|
263
|
+
console.error('[Panda CMS] Editor error:', error)
|
264
|
+
element.dataset.editableInitialized = 'false'
|
265
|
+
throw error
|
228
266
|
}
|
229
267
|
}
|
230
|
-
})
|
231
|
-
|
232
|
-
// Store editor instance on the holder element to maintain reference
|
233
|
-
holderElement.editorInstance = editor
|
234
|
-
|
235
|
-
if (!this.withinIFrame) {
|
236
|
-
// Store the editor instance on the controller element for potential future reference
|
237
|
-
const form = element.closest('[data-controller="editor-form"]')
|
238
|
-
if (form) {
|
239
|
-
form.editorInstance = editor
|
240
|
-
}
|
241
|
-
} else {
|
242
|
-
// For iframe editors, store the instance on the element itself
|
243
|
-
element.editorInstance = editor
|
244
|
-
}
|
245
|
-
|
246
|
-
// Return a promise that resolves when the editor is ready
|
247
|
-
return new Promise((resolve, reject) => {
|
248
|
-
const timeout = setTimeout(() => {
|
249
|
-
reject(new Error('Editor initialization timed out'))
|
250
|
-
}, 30000)
|
251
268
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
setTimeout(checkReady, 100)
|
258
|
-
}
|
259
|
-
}
|
260
|
-
checkReady()
|
261
|
-
})
|
262
|
-
}
|
269
|
+
// Remove any undefined tools from the config
|
270
|
+
config.tools = Object.fromEntries(
|
271
|
+
Object.entries(config.tools)
|
272
|
+
.filter(([_, value]) => value?.class !== undefined)
|
273
|
+
)
|
263
274
|
|
264
|
-
|
265
|
-
* Wait for EditorJS core to be available in window
|
266
|
-
*/
|
267
|
-
async waitForEditorJS() {
|
268
|
-
let attempts = 0
|
269
|
-
const maxAttempts = 30 // 3 seconds with 100ms intervals
|
275
|
+
console.debug('[Panda CMS] Creating editor with config:', config)
|
270
276
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
277
|
+
// Create editor instance with extended timeout
|
278
|
+
return new Promise((resolve, reject) => {
|
279
|
+
try {
|
280
|
+
// Add timeout for initialization
|
281
|
+
const timeoutId = setTimeout(() => {
|
282
|
+
reject(new Error('Editor initialization timeout'))
|
283
|
+
}, 15000) // Increased to 15 seconds
|
284
|
+
|
285
|
+
// Create editor instance with onReady callback
|
286
|
+
const editor = new win.EditorJS({
|
287
|
+
...config,
|
288
|
+
onReady: () => {
|
289
|
+
console.debug('[Panda CMS] Editor ready with data:', processedData)
|
290
|
+
clearTimeout(timeoutId)
|
291
|
+
holder.editorInstance = editor
|
292
|
+
element.dataset.editableInitialized = 'true'
|
293
|
+
resolve(editor)
|
294
|
+
},
|
295
|
+
onChange: (api, event) => {
|
296
|
+
console.debug('[Panda CMS] Editor content changed:', { api, event })
|
297
|
+
// Save content to data attributes
|
298
|
+
api.saver.save().then((outputData) => {
|
299
|
+
const jsonString = JSON.stringify(outputData)
|
300
|
+
element.dataset.editablePreviousData = btoa(jsonString)
|
301
|
+
element.dataset.editableContent = jsonString
|
302
|
+
})
|
303
|
+
},
|
304
|
+
onError: (error) => {
|
305
|
+
console.error('[Panda CMS] Editor error:', error)
|
306
|
+
element.dataset.editableInitialized = 'false'
|
307
|
+
clearTimeout(timeoutId)
|
308
|
+
reject(error)
|
309
|
+
}
|
310
|
+
})
|
311
|
+
|
312
|
+
// Add error handler
|
313
|
+
editor.isReady
|
314
|
+
.then(() => {
|
315
|
+
console.debug('[Panda CMS] Editor is ready')
|
316
|
+
element.dataset.editableInitialized = 'true'
|
317
|
+
})
|
318
|
+
.catch((error) => {
|
319
|
+
console.error('[Panda CMS] Editor failed to initialize:', error)
|
320
|
+
element.dataset.editableInitialized = 'false'
|
321
|
+
clearTimeout(timeoutId)
|
322
|
+
reject(error)
|
323
|
+
})
|
324
|
+
} catch (error) {
|
325
|
+
element.dataset.editableInitialized = 'false'
|
326
|
+
reject(error)
|
280
327
|
}
|
281
|
-
}
|
282
|
-
|
283
|
-
|
328
|
+
})
|
329
|
+
} catch (error) {
|
330
|
+
console.error('[Panda CMS] Error initializing editor:', error)
|
331
|
+
throw error
|
332
|
+
}
|
284
333
|
}
|
285
334
|
}
|
@@ -112,4 +112,93 @@ export class ResourceLoader {
|
|
112
112
|
}
|
113
113
|
return hash.toString(36)
|
114
114
|
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Load a script into the document
|
118
|
+
* @param {Document} doc - The document to load the script into
|
119
|
+
* @param {HTMLElement} target - The element to append the script to
|
120
|
+
* @param {string} url - The URL of the script to load
|
121
|
+
* @returns {Promise<void>}
|
122
|
+
*/
|
123
|
+
static async loadScript(doc, target, url) {
|
124
|
+
return new ResourceLoader().loadScript(doc, target, url)
|
125
|
+
}
|
126
|
+
|
127
|
+
/**
|
128
|
+
* Embed CSS into the document
|
129
|
+
* @param {Document} doc - The document to embed the CSS into
|
130
|
+
* @param {HTMLElement} target - The element to append the style to
|
131
|
+
* @param {string} css - The CSS to embed
|
132
|
+
* @returns {Promise<void>}
|
133
|
+
*/
|
134
|
+
static async embedCSS(doc, target, css) {
|
135
|
+
return new ResourceLoader().embedCSS(doc, target, css)
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Instance method to load a script
|
140
|
+
*/
|
141
|
+
async loadScript(doc, target, url) {
|
142
|
+
try {
|
143
|
+
// Check if script is already loaded
|
144
|
+
const existingScript = doc.querySelector(`script[src="${url}"]`)
|
145
|
+
if (existingScript) {
|
146
|
+
console.debug(`[Panda CMS] Script already loaded: ${url}, skipping`)
|
147
|
+
return
|
148
|
+
}
|
149
|
+
|
150
|
+
// Create and configure script element
|
151
|
+
const script = doc.createElement("script")
|
152
|
+
script.type = "text/javascript"
|
153
|
+
script.src = url
|
154
|
+
script.async = true
|
155
|
+
|
156
|
+
// Create a promise to track loading
|
157
|
+
const loadPromise = new Promise((resolve, reject) => {
|
158
|
+
script.onload = () => {
|
159
|
+
console.debug(`[Panda CMS] Script loaded: ${url}`)
|
160
|
+
resolve()
|
161
|
+
}
|
162
|
+
script.onerror = (error) => {
|
163
|
+
console.error(`[Panda CMS] Script failed to load: ${url}`, error)
|
164
|
+
reject(error)
|
165
|
+
}
|
166
|
+
})
|
167
|
+
|
168
|
+
// Add script to document
|
169
|
+
target.appendChild(script)
|
170
|
+
|
171
|
+
// Wait for script to load
|
172
|
+
await loadPromise
|
173
|
+
} catch (error) {
|
174
|
+
console.error(`[Panda CMS] Error loading script ${url}:`, error)
|
175
|
+
throw error
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Instance method to embed CSS
|
181
|
+
*/
|
182
|
+
async embedCSS(doc, target, css) {
|
183
|
+
try {
|
184
|
+
// Check if styles are already embedded
|
185
|
+
const existingStyle = doc.querySelector('style[data-panda-cms-styles]')
|
186
|
+
if (existingStyle) {
|
187
|
+
console.debug(`[Panda CMS] CSS already embedded, skipping`)
|
188
|
+
return
|
189
|
+
}
|
190
|
+
|
191
|
+
// Create and configure style element
|
192
|
+
const style = doc.createElement('style')
|
193
|
+
style.setAttribute('data-panda-cms-styles', 'true')
|
194
|
+
style.textContent = css
|
195
|
+
|
196
|
+
// Add style to document
|
197
|
+
target.appendChild(style)
|
198
|
+
console.debug(`[Panda CMS] Embedded CSS styles`)
|
199
|
+
} catch (error) {
|
200
|
+
console.error('[Panda CMS] Error embedding CSS:', error)
|
201
|
+
throw error
|
202
|
+
}
|
203
|
+
}
|
115
204
|
}
|