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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/Rakefile +2 -0
  4. data/app/components/panda/cms/admin/statistics_component.rb +1 -2
  5. data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
  6. data/app/components/panda/cms/admin/user_activity_component.rb +3 -5
  7. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  8. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  9. data/app/components/panda/cms/code_component.rb +8 -4
  10. data/app/components/panda/cms/menu_component.rb +7 -6
  11. data/app/components/panda/cms/page_menu_component.rb +15 -17
  12. data/app/components/panda/cms/rich_text_component.rb +10 -11
  13. data/app/components/panda/cms/text_component.rb +6 -7
  14. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  15. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  16. data/app/controllers/panda/cms/admin/dashboard_controller.rb +14 -9
  17. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  18. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -6
  19. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  20. data/app/controllers/panda/cms/admin/pages_controller.rb +9 -8
  21. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -11
  22. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
  23. data/app/controllers/panda/cms/admin/settings_controller.rb +13 -10
  24. data/app/controllers/panda/cms/application_controller.rb +19 -6
  25. data/app/controllers/panda/cms/errors_controller.rb +5 -2
  26. data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
  27. data/app/controllers/panda/cms/pages_controller.rb +34 -31
  28. data/app/controllers/panda/cms/posts_controller.rb +2 -0
  29. data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
  30. data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
  31. data/app/helpers/panda/cms/application_helper.rb +3 -3
  32. data/app/helpers/panda/cms/asset_helper.rb +195 -0
  33. data/app/helpers/panda/cms/pages_helper.rb +2 -0
  34. data/app/helpers/panda/cms/posts_helper.rb +2 -0
  35. data/app/helpers/panda/cms/theme_helper.rb +2 -0
  36. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  37. data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
  38. data/app/javascript/panda/cms/controllers/index.js +8 -24
  39. data/app/javascript/panda/cms/stimulus-loading.js +39 -0
  40. data/app/javascript/panda_cms/stimulus-loading.js +39 -0
  41. data/app/jobs/panda/cms/application_job.rb +2 -0
  42. data/app/jobs/panda/cms/record_visit_job.rb +2 -0
  43. data/app/mailers/panda/cms/application_mailer.rb +2 -0
  44. data/app/mailers/panda/cms/form_mailer.rb +3 -1
  45. data/app/models/panda/cms/application_record.rb +2 -0
  46. data/app/models/panda/cms/block.rb +4 -1
  47. data/app/models/panda/cms/block_content.rb +3 -1
  48. data/app/models/panda/cms/current.rb +5 -12
  49. data/app/models/panda/cms/form.rb +2 -0
  50. data/app/models/panda/cms/form_submission.rb +2 -0
  51. data/app/models/panda/cms/menu.rb +12 -9
  52. data/app/models/panda/cms/menu_item.rb +10 -6
  53. data/app/models/panda/cms/page.rb +14 -12
  54. data/app/models/panda/cms/post.rb +12 -8
  55. data/app/models/panda/cms/redirect.rb +6 -3
  56. data/app/models/panda/cms/template.rb +12 -7
  57. data/app/models/panda/cms/visit.rb +3 -1
  58. data/app/models/panda/social/instagram_post.rb +2 -0
  59. data/app/services/panda/social/instagram_feed_service.rb +3 -1
  60. data/app/views/layouts/different_page.html.erb +6 -0
  61. data/app/views/layouts/homepage.html.erb +37 -0
  62. data/app/views/layouts/page.html.erb +18 -0
  63. data/app/views/layouts/panda/cms/application.html.erb +2 -1
  64. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  65. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  66. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  67. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  68. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  69. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  70. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  71. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  72. data/app/views/panda/cms/admin/pages/new.html.erb +16 -10
  73. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  74. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  75. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  76. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  77. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  78. data/app/views/panda/cms/admin/settings/index.html.erb +4 -4
  79. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  80. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  81. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  82. data/app/views/panda/cms/shared/_header.html.erb +10 -2
  83. data/app/views/panda/cms/shared/_importmap.html.erb +1 -1
  84. data/app/views/shared/_footer.html.erb +3 -0
  85. data/app/views/shared/_header.html.erb +11 -0
  86. data/config/importmap.rb +2 -0
  87. data/config/initializers/inflections.rb +2 -0
  88. data/config/initializers/panda/cms/form_errors.rb +20 -21
  89. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
  90. data/config/initializers/panda/cms.rb +8 -3
  91. data/config/initializers/zeitwork.rb +2 -0
  92. data/config/puma/test.rb +3 -1
  93. data/config/routes.rb +11 -19
  94. data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
  95. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
  96. data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
  97. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
  98. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
  99. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
  100. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
  101. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
  102. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
  103. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
  104. data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
  105. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
  106. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
  107. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
  108. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
  109. data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
  110. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
  111. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
  112. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
  113. data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
  114. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
  115. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
  116. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
  117. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
  118. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
  119. data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
  120. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
  121. data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
  122. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
  123. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
  124. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
  125. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
  126. data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
  127. data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
  128. data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
  129. data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
  130. data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
  131. data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
  132. data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
  133. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -1
  134. data/db/migrate/20250120235542_remove_paper_trail.rb +5 -4
  135. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +4 -0
  136. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  137. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  138. data/db/seeds.rb +2 -0
  139. data/lib/generators/panda/cms/install_generator.rb +2 -0
  140. data/lib/panda/cms/asset_loader.rb +390 -0
  141. data/lib/panda/cms/bulk_editor.rb +7 -3
  142. data/lib/panda/cms/demo_site_generator.rb +2 -0
  143. data/lib/panda/cms/engine.rb +57 -116
  144. data/lib/panda/cms/exceptions_app.rb +2 -0
  145. data/lib/panda/cms/railtie.rb +2 -0
  146. data/lib/panda/cms/slug.rb +3 -1
  147. data/lib/panda-cms/version.rb +3 -1
  148. data/lib/panda-cms.rb +54 -42
  149. data/lib/tasks/assets.rake +587 -0
  150. data/lib/tasks/panda/cms/install.rake +2 -0
  151. data/lib/tasks/panda/cms/migrations.rake +13 -0
  152. data/lib/tasks/panda/social/instagram.rake +2 -0
  153. data/lib/tasks/panda_cms.rake +3 -30
  154. data/public/panda-cms-assets/manifest.json +20 -0
  155. data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
  156. data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
  157. metadata +186 -49
  158. data/app/builders/panda/cms/form_builder.rb +0 -217
  159. data/app/components/panda/cms/admin/button_component.rb +0 -70
  160. data/app/components/panda/cms/admin/container_component.rb +0 -13
  161. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  162. data/app/components/panda/cms/admin/heading_component.rb +0 -45
  163. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  164. data/app/components/panda/cms/admin/table_component.rb +0 -46
  165. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  166. data/app/constraints/panda/cms/admin_constraint.rb +0 -18
  167. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -43
  168. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -94
  169. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -9
  170. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  171. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  172. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  173. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  174. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  175. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  176. data/app/models/panda/cms/breadcrumb.rb +0 -12
  177. data/app/models/panda/cms/user.rb +0 -31
  178. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -193
  179. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  180. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  181. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -5
  182. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -34
  183. data/lib/panda/cms/editor_js/blocks/base.rb +0 -33
  184. data/lib/panda/cms/editor_js/blocks/header.rb +0 -15
  185. data/lib/panda/cms/editor_js/blocks/image.rb +0 -36
  186. data/lib/panda/cms/editor_js/blocks/list.rb +0 -32
  187. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -15
  188. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -41
  189. data/lib/panda/cms/editor_js/blocks/table.rb +0 -50
  190. data/lib/panda/cms/editor_js/renderer.rb +0 -124
  191. data/lib/panda/cms/editor_js.rb +0 -16
  192. 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
- }