panda-cms 0.7.4 → 0.8.0
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/README.md +37 -2
- data/Rakefile +2 -0
- data/app/components/panda/cms/admin/statistics_component.rb +1 -2
- data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
- data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
- data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
- data/app/components/panda/cms/admin/user_display_component.rb +2 -2
- data/app/components/panda/cms/code_component.rb +8 -4
- data/app/components/panda/cms/menu_component.rb +7 -6
- data/app/components/panda/cms/page_menu_component.rb +15 -17
- data/app/components/panda/cms/rich_text_component.rb +10 -11
- data/app/components/panda/cms/text_component.rb +6 -7
- data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
- data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
- data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
- data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
- data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
- data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
- data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
- data/app/controllers/panda/cms/application_controller.rb +19 -6
- data/app/controllers/panda/cms/errors_controller.rb +5 -2
- data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
- data/app/controllers/panda/cms/pages_controller.rb +34 -31
- data/app/controllers/panda/cms/posts_controller.rb +2 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
- data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
- data/app/helpers/panda/cms/application_helper.rb +3 -3
- data/app/helpers/panda/cms/asset_helper.rb +195 -0
- data/app/helpers/panda/cms/pages_helper.rb +2 -0
- data/app/helpers/panda/cms/posts_helper.rb +2 -0
- data/app/helpers/panda/cms/theme_helper.rb +2 -0
- data/app/javascript/panda/cms/application_panda_cms.js +2 -34
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
- data/app/javascript/panda/cms/controllers/index.js +8 -24
- data/app/javascript/panda/cms/stimulus-loading.js +39 -0
- data/app/javascript/panda_cms/stimulus-loading.js +39 -0
- data/app/jobs/panda/cms/application_job.rb +2 -0
- data/app/jobs/panda/cms/record_visit_job.rb +2 -0
- data/app/mailers/panda/cms/application_mailer.rb +2 -0
- data/app/mailers/panda/cms/form_mailer.rb +3 -1
- data/app/models/panda/cms/application_record.rb +2 -0
- data/app/models/panda/cms/block.rb +4 -1
- data/app/models/panda/cms/block_content.rb +3 -1
- data/app/models/panda/cms/current.rb +5 -12
- data/app/models/panda/cms/form.rb +2 -0
- data/app/models/panda/cms/form_submission.rb +2 -0
- data/app/models/panda/cms/menu.rb +12 -9
- data/app/models/panda/cms/menu_item.rb +10 -6
- data/app/models/panda/cms/page.rb +14 -12
- data/app/models/panda/cms/post.rb +12 -8
- data/app/models/panda/cms/redirect.rb +6 -3
- data/app/models/panda/cms/template.rb +12 -7
- data/app/models/panda/cms/visit.rb +3 -1
- data/app/models/panda/social/instagram_post.rb +2 -0
- data/app/services/panda/social/instagram_feed_service.rb +3 -1
- data/app/views/layouts/different_page.html.erb +6 -0
- data/app/views/layouts/homepage.html.erb +37 -0
- data/app/views/layouts/page.html.erb +18 -0
- data/app/views/layouts/panda/cms/application.html.erb +2 -1
- data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
- data/app/views/panda/cms/admin/files/index.html.erb +1 -1
- data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
- data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
- data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
- data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
- data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
- data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
- data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
- data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
- data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
- data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
- data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
- data/app/views/panda/cms/shared/_header.html.erb +10 -2
- data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
- data/app/views/shared/_footer.html.erb +3 -0
- data/app/views/shared/_header.html.erb +11 -0
- data/config/importmap.rb +2 -0
- data/config/initializers/inflections.rb +2 -0
- data/config/initializers/panda/cms/form_errors.rb +20 -21
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
- data/config/initializers/panda/cms.rb +8 -3
- data/config/initializers/zeitwork.rb +2 -0
- data/config/puma/test.rb +3 -1
- data/config/routes.rb +11 -19
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
- data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
- data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
- data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
- data/db/seeds.rb +2 -0
- data/lib/generators/panda/cms/install_generator.rb +2 -0
- data/lib/panda/cms/asset_loader.rb +390 -0
- data/lib/panda/cms/bulk_editor.rb +7 -3
- data/lib/panda/cms/demo_site_generator.rb +2 -0
- data/lib/panda/cms/engine.rb +57 -116
- data/lib/panda/cms/exceptions_app.rb +2 -0
- data/lib/panda/cms/railtie.rb +2 -0
- data/lib/panda/cms/slug.rb +3 -1
- data/lib/panda-cms/version.rb +3 -1
- data/lib/panda-cms.rb +54 -42
- data/lib/tasks/assets.rake +587 -0
- data/lib/tasks/panda/cms/install.rake +2 -0
- data/lib/tasks/panda/cms/migrations.rake +13 -0
- data/lib/tasks/panda/social/instagram.rake +2 -0
- data/lib/tasks/panda_cms.rake +3 -30
- data/public/panda-cms-assets/manifest.json +20 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
- metadata +186 -49
- data/app/builders/panda/cms/form_builder.rb +0 -217
- data/app/components/panda/cms/admin/button_component.rb +0 -70
- data/app/components/panda/cms/admin/container_component.rb +0 -13
- data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
- data/app/components/panda/cms/admin/heading_component.rb +0 -45
- data/app/components/panda/cms/admin/panel_component.rb +0 -13
- data/app/components/panda/cms/admin/table_component.rb +0 -46
- data/app/components/panda/cms/admin/tag_component.rb +0 -35
- data/app/constraints/panda/cms/admin_constraint.rb +0 -18
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
- data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
- data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
- data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
- data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
- data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
- data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
- data/app/models/panda/cms/breadcrumb.rb +0 -12
- data/app/models/panda/cms/user.rb +0 -31
- data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
- data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
- data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
- data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
- data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
- data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
- data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
- data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
- data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
- data/lib/panda/cms/editor_js/renderer.rb +0 -124
- data/lib/panda/cms/editor_js.rb +0 -16
- data/lib/panda/cms/editor_js_content.rb +0 -55
@@ -1,334 +0,0 @@
|
|
1
|
-
import { ResourceLoader } from "panda/cms/editor/resource_loader"
|
2
|
-
import { EDITOR_JS_RESOURCES, EDITOR_JS_CSS, getEditorConfig } from "panda/cms/editor/editor_js_config"
|
3
|
-
import { CSSExtractor } from "panda/cms/editor/css_extractor"
|
4
|
-
|
5
|
-
export class EditorJSInitializer {
|
6
|
-
constructor(document, withinIFrame = false) {
|
7
|
-
this.document = document
|
8
|
-
this.withinIFrame = withinIFrame
|
9
|
-
}
|
10
|
-
|
11
|
-
/**
|
12
|
-
* Initializes the EditorJS instance for a given element.
|
13
|
-
* This method loads necessary resources and returns the JavaScript code for initializing the editor.
|
14
|
-
*
|
15
|
-
* @param {HTMLElement} element - The DOM element to initialize the editor on
|
16
|
-
* @param {Object} initialData - The initial data for the editor
|
17
|
-
* @param {string} editorId - The ID to use for the editor holder
|
18
|
-
* @returns {Promise<EditorJS>} A promise that resolves to the editor instance
|
19
|
-
*/
|
20
|
-
async initialize(element, initialData = {}, editorId = null) {
|
21
|
-
await this.loadResources()
|
22
|
-
const result = await this.initializeEditor(element, initialData, editorId)
|
23
|
-
return result
|
24
|
-
}
|
25
|
-
|
26
|
-
/**
|
27
|
-
* Gets the application's styles from its configured stylesheet
|
28
|
-
* @returns {Promise<string>} The extracted CSS rules
|
29
|
-
*/
|
30
|
-
async getApplicationStyles() {
|
31
|
-
try {
|
32
|
-
// Get the configured stylesheet URL, defaulting to Tailwind Rails default
|
33
|
-
const stylesheetUrl = window.PANDA_CMS_CONFIG?.stylesheetUrl || '/assets/application.tailwind.css'
|
34
|
-
|
35
|
-
// Fetch the CSS content
|
36
|
-
const response = await fetch(stylesheetUrl)
|
37
|
-
const css = await response.text()
|
38
|
-
return CSSExtractor.getEditorStyles(css)
|
39
|
-
} catch (error) {
|
40
|
-
return ''
|
41
|
-
}
|
42
|
-
}
|
43
|
-
|
44
|
-
/**
|
45
|
-
* Loads the necessary resources for the EditorJS instance.
|
46
|
-
* This method fetches the required scripts and stylesheets and embeds them into the document.
|
47
|
-
*/
|
48
|
-
async loadResources() {
|
49
|
-
try {
|
50
|
-
// First load EditorJS core
|
51
|
-
const editorCore = EDITOR_JS_RESOURCES[0]
|
52
|
-
await ResourceLoader.loadScript(this.document, this.document.head, editorCore)
|
53
|
-
|
54
|
-
// Wait for EditorJS to be available
|
55
|
-
await this.waitForEditorJS()
|
56
|
-
|
57
|
-
// Load CSS directly
|
58
|
-
await ResourceLoader.embedCSS(this.document, this.document.head, EDITOR_JS_CSS)
|
59
|
-
|
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
|
-
}
|
74
|
-
|
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')
|
83
|
-
} catch (error) {
|
84
|
-
console.error('[Panda CMS] Error loading Editor.js resources:', error)
|
85
|
-
throw error
|
86
|
-
}
|
87
|
-
}
|
88
|
-
|
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))
|
98
|
-
}
|
99
|
-
throw new Error('[Panda CMS] Timeout waiting for EditorJS')
|
100
|
-
}
|
101
|
-
|
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
|
109
|
-
}
|
110
|
-
|
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'
|
123
|
-
}
|
124
|
-
|
125
|
-
const globalToolName = toolMapping[cleanToolName] || cleanToolName
|
126
|
-
const start = Date.now()
|
127
|
-
|
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]
|
137
|
-
}
|
138
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
139
|
-
}
|
140
|
-
throw new Error(`[Panda CMS] Timeout waiting for tool: ${cleanToolName} (${globalToolName})`)
|
141
|
-
}
|
142
|
-
|
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)
|
147
|
-
|
148
|
-
// Get the window context (either iframe or parent)
|
149
|
-
const win = this.document.defaultView || window
|
150
|
-
|
151
|
-
// Create a unique ID for this editor instance if not provided
|
152
|
-
const uniqueId = editorId || `editor-${Math.random().toString(36).substring(2)}`
|
153
|
-
|
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
|
-
}
|
160
|
-
|
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
|
-
}
|
199
|
-
}
|
200
|
-
return block
|
201
|
-
})
|
202
|
-
}
|
203
|
-
}
|
204
|
-
|
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...'
|
219
|
-
}
|
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
|
228
|
-
}
|
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'
|
244
|
-
}
|
245
|
-
}
|
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
|
266
|
-
}
|
267
|
-
}
|
268
|
-
|
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
|
-
)
|
274
|
-
|
275
|
-
console.debug('[Panda CMS] Creating editor with config:', config)
|
276
|
-
|
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)
|
327
|
-
}
|
328
|
-
})
|
329
|
-
} catch (error) {
|
330
|
-
console.error('[Panda CMS] Error initializing editor:', error)
|
331
|
-
throw error
|
332
|
-
}
|
333
|
-
}
|
334
|
-
}
|
@@ -1,110 +0,0 @@
|
|
1
|
-
export class PlainTextEditor {
|
2
|
-
/**
|
3
|
-
* Constructs a new PlainTextEditor instance.
|
4
|
-
*
|
5
|
-
* @param {HTMLElement} element - The HTML element representing the plain text editor.
|
6
|
-
* @param {HTMLIFrameElement} frame - The HTML iframe element containing the plain text editor.
|
7
|
-
* @param {Object} options - An object containing configuration options for the plain text editor.
|
8
|
-
*/
|
9
|
-
constructor(element, frame, options) {
|
10
|
-
this.element = element
|
11
|
-
this.frame = frame
|
12
|
-
this.options = options
|
13
|
-
this.setupStyles()
|
14
|
-
this.bindEvents()
|
15
|
-
}
|
16
|
-
|
17
|
-
/**
|
18
|
-
* Sets up the styles for the plain text editor element.
|
19
|
-
*
|
20
|
-
* This method applies various CSS styles to the editor element, such as a dashed border, no outline, a pointer cursor, and a background color transition. It also sets the white-space and font-family styles based on the data-editable-kind attribute of the element.
|
21
|
-
*/
|
22
|
-
setupStyles() {
|
23
|
-
this.element.style.border = "1px dashed #ccc"
|
24
|
-
this.element.style.outline = "none"
|
25
|
-
this.element.style.cursor = "pointer"
|
26
|
-
this.element.style.transition = "background 500ms linear"
|
27
|
-
this.element.style.backgroundColor = "inherit"
|
28
|
-
|
29
|
-
if (this.element.getAttribute("data-editable-kind") == "html") {
|
30
|
-
this.element.style.whiteSpace = "pre-wrap"
|
31
|
-
this.element.style.fontFamily = "monospace"
|
32
|
-
}
|
33
|
-
}
|
34
|
-
|
35
|
-
/**
|
36
|
-
* Binds event listeners for the plain text editor.
|
37
|
-
*
|
38
|
-
* If the `autosave` option is enabled, this method adds a `blur` event listener to the editor element, which triggers the `save()` method when the editor loses focus.
|
39
|
-
*
|
40
|
-
* Additionally, this method adds a `click` event listener to the "Save Editable" button, which also triggers the `save()` method when clicked.
|
41
|
-
*/
|
42
|
-
bindEvents() {
|
43
|
-
if (this.options.autosave) {
|
44
|
-
this.element.addEventListener("blur", () => this.save())
|
45
|
-
}
|
46
|
-
|
47
|
-
document.getElementById('saveEditableButton').addEventListener('click', () => this.save())
|
48
|
-
}
|
49
|
-
|
50
|
-
/**
|
51
|
-
* Saves the content of the plain text editor to the server.
|
52
|
-
*
|
53
|
-
* This method sends a PATCH request to the server with the updated content of the plain text editor. It retrieves the necessary data from the editor element's attributes, such as the block content ID and the content type (HTML or plain text). If the save is successful, it calls the `showSuccess()` method, otherwise it calls the `showError()` method with the error.
|
54
|
-
*/
|
55
|
-
save() {
|
56
|
-
const blockContentId = this.element.getAttribute("data-editable-block-content-id")
|
57
|
-
const pageId = this.element.getAttribute("data-editable-page-id")
|
58
|
-
const content = this.element.getAttribute("data-editable-kind") == "html" ?
|
59
|
-
this.element.innerHTML :
|
60
|
-
this.element.innerText
|
61
|
-
|
62
|
-
fetch(`${this.options.adminPath}/pages/${pageId}/block_contents/${blockContentId}`, {
|
63
|
-
method: "PATCH",
|
64
|
-
headers: {
|
65
|
-
"Content-Type": "application/json",
|
66
|
-
"X-CSRF-Token": this.options.csrfToken
|
67
|
-
},
|
68
|
-
body: JSON.stringify({ content: content })
|
69
|
-
})
|
70
|
-
.then(response => response.json())
|
71
|
-
.then(() => {
|
72
|
-
// Show success message in parent window
|
73
|
-
parent.document.getElementById("successMessage").classList.remove("hidden")
|
74
|
-
setTimeout(() => {
|
75
|
-
parent.document.getElementById("successMessage").classList.add("hidden")
|
76
|
-
}, 3000)
|
77
|
-
// Show visual feedback in the editor
|
78
|
-
this.showSuccess()
|
79
|
-
})
|
80
|
-
.catch(error => this.showError(error))
|
81
|
-
}
|
82
|
-
|
83
|
-
/**
|
84
|
-
* Displays a success message by temporarily changing the background color of the editor element.
|
85
|
-
*
|
86
|
-
* This method is called after a successful save operation to provide visual feedback to the user.
|
87
|
-
*/
|
88
|
-
showSuccess() {
|
89
|
-
this.element.style.backgroundColor = "#66bd6a50"
|
90
|
-
setTimeout(() => {
|
91
|
-
this.element.style.backgroundColor = "inherit"
|
92
|
-
}, 1000)
|
93
|
-
}
|
94
|
-
|
95
|
-
/**
|
96
|
-
* Displays an error message by temporarily changing the background color of the editor element and logging the error to the console.
|
97
|
-
*
|
98
|
-
* This method is called after a failed save operation to provide visual and textual feedback to the user.
|
99
|
-
*
|
100
|
-
* @param {Error} error - The error object that occurred during the save operation.
|
101
|
-
*/
|
102
|
-
showError(error) {
|
103
|
-
this.element.style.backgroundColor = "#dc354550"
|
104
|
-
setTimeout(() => {
|
105
|
-
this.element.style.backgroundColor = "inherit"
|
106
|
-
}, 1000)
|
107
|
-
console.log(error)
|
108
|
-
alert(`Error: ${error}`)
|
109
|
-
}
|
110
|
-
}
|
@@ -1,204 +0,0 @@
|
|
1
|
-
export class ResourceLoader {
|
2
|
-
static loadedResources = new Set()
|
3
|
-
|
4
|
-
/**
|
5
|
-
* Embeds CSS styles into the document head.
|
6
|
-
*
|
7
|
-
* @param {Document} frameDocument - The document object to create elements in
|
8
|
-
* @param {HTMLElement} head - The head element to append styles to
|
9
|
-
* @param {string} css - The CSS styles to embed
|
10
|
-
* @returns {Promise} A promise that resolves when the styles are embedded
|
11
|
-
*/
|
12
|
-
static embedCSS(frameDocument, head, css) {
|
13
|
-
const cssHash = this.hashString(css)
|
14
|
-
if (this.loadedResources.has(`css:${cssHash}`)) {
|
15
|
-
console.debug("[Panda CMS] CSS already embedded, skipping")
|
16
|
-
return Promise.resolve()
|
17
|
-
}
|
18
|
-
|
19
|
-
return new Promise((resolve) => {
|
20
|
-
const style = frameDocument.createElement("style")
|
21
|
-
style.textContent = css
|
22
|
-
head.append(style)
|
23
|
-
this.loadedResources.add(`css:${cssHash}`)
|
24
|
-
resolve(style)
|
25
|
-
console.debug("[Panda CMS] Embedded CSS styles")
|
26
|
-
})
|
27
|
-
}
|
28
|
-
|
29
|
-
/**
|
30
|
-
* Loads a script from a URL and appends it to the document head.
|
31
|
-
*
|
32
|
-
* @param {Document} frameDocument - The document object to create elements in
|
33
|
-
* @param {HTMLElement} head - The head element to append the script to
|
34
|
-
* @param {string} src - The URL of the script to load
|
35
|
-
* @returns {Promise} A promise that resolves when the script is loaded
|
36
|
-
*/
|
37
|
-
static loadScript(frameDocument, head, src) {
|
38
|
-
if (this.loadedResources.has(`script:${src}`)) {
|
39
|
-
console.debug(`[Panda CMS] Script already loaded: ${src}, skipping`)
|
40
|
-
return Promise.resolve()
|
41
|
-
}
|
42
|
-
|
43
|
-
return new Promise((resolve, reject) => {
|
44
|
-
const script = frameDocument.createElement("script")
|
45
|
-
script.src = src
|
46
|
-
script.onload = () => {
|
47
|
-
this.loadedResources.add(`script:${src}`)
|
48
|
-
resolve(script)
|
49
|
-
console.debug(`[Panda CMS] Script loaded: ${src}`)
|
50
|
-
}
|
51
|
-
script.onerror = () => reject(new Error(`[Panda CMS] Script load error for ${src}`))
|
52
|
-
head.append(script)
|
53
|
-
})
|
54
|
-
}
|
55
|
-
|
56
|
-
static importScript(frameDocument, head, module, src) {
|
57
|
-
const key = `module:${module}:${src}`
|
58
|
-
if (this.loadedResources.has(key)) {
|
59
|
-
console.debug(`[Panda CMS] Module already imported: ${src}, skipping`)
|
60
|
-
return Promise.resolve()
|
61
|
-
}
|
62
|
-
|
63
|
-
return new Promise((resolve, reject) => {
|
64
|
-
const script = frameDocument.createElement("script")
|
65
|
-
script.type = "module"
|
66
|
-
script.textContent = `import ${module} from "${src}"`
|
67
|
-
head.append(script)
|
68
|
-
|
69
|
-
script.onload = () => {
|
70
|
-
this.loadedResources.add(key)
|
71
|
-
console.debug(`[Panda CMS] Module script loaded: ${src}`)
|
72
|
-
resolve(script)
|
73
|
-
}
|
74
|
-
script.onerror = () => reject(new Error(`[Panda CMS] Module script load error for ${src}`))
|
75
|
-
})
|
76
|
-
}
|
77
|
-
|
78
|
-
static loadStylesheet(frameDocument, head, href) {
|
79
|
-
if (this.loadedResources.has(`stylesheet:${href}`)) {
|
80
|
-
console.debug(`[Panda CMS] Stylesheet already loaded: ${href}, skipping`)
|
81
|
-
return Promise.resolve()
|
82
|
-
}
|
83
|
-
|
84
|
-
return new Promise((resolve, reject) => {
|
85
|
-
const link = frameDocument.createElement("link")
|
86
|
-
link.rel = "stylesheet"
|
87
|
-
link.href = href
|
88
|
-
link.media = "none"
|
89
|
-
head.append(link)
|
90
|
-
|
91
|
-
link.onload = () => {
|
92
|
-
if (link.media != "all") {
|
93
|
-
link.media = "all"
|
94
|
-
}
|
95
|
-
this.loadedResources.add(`stylesheet:${href}`)
|
96
|
-
console.debug(`[Panda CMS] Stylesheet loaded: ${href}`)
|
97
|
-
resolve(link)
|
98
|
-
}
|
99
|
-
link.onerror = () => reject(new Error(`[Panda CMS] Stylesheet load error for ${href}`))
|
100
|
-
})
|
101
|
-
}
|
102
|
-
|
103
|
-
/**
|
104
|
-
* Simple string hashing function for tracking embedded CSS
|
105
|
-
*/
|
106
|
-
static hashString(str) {
|
107
|
-
let hash = 0
|
108
|
-
for (let i = 0; i < str.length; i++) {
|
109
|
-
const char = str.charCodeAt(i)
|
110
|
-
hash = ((hash << 5) - hash) + char
|
111
|
-
hash = hash & hash // Convert to 32bit integer
|
112
|
-
}
|
113
|
-
return hash.toString(36)
|
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
|
-
}
|
204
|
-
}
|