panda-cms 0.7.5 → 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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/panda/cms/admin/user_activity_component.html.erb +2 -2
  3. data/app/components/panda/cms/admin/user_activity_component.rb +2 -2
  4. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  5. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  6. data/app/components/panda/cms/rich_text_component.rb +5 -5
  7. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  8. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  9. data/app/controllers/panda/cms/admin/dashboard_controller.rb +5 -4
  10. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  11. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -4
  12. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  13. data/app/controllers/panda/cms/admin/pages_controller.rb +7 -7
  14. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -10
  15. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +4 -4
  16. data/app/controllers/panda/cms/admin/settings_controller.rb +2 -3
  17. data/app/controllers/panda/cms/application_controller.rb +13 -5
  18. data/app/controllers/panda/cms/pages_controller.rb +2 -2
  19. data/app/helpers/panda/cms/application_helper.rb +3 -3
  20. data/app/helpers/panda/cms/asset_helper.rb +14 -1
  21. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  22. data/app/javascript/panda/cms/controllers/index.js +5 -15
  23. data/app/models/panda/cms/block_content.rb +1 -1
  24. data/app/models/panda/cms/current.rb +3 -12
  25. data/app/models/panda/cms/post.rb +3 -3
  26. data/app/models/panda/cms/visit.rb +1 -1
  27. data/app/views/layouts/panda/cms/application.html.erb +1 -1
  28. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  29. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  30. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  31. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  32. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  33. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  34. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  35. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  36. data/app/views/panda/cms/admin/pages/new.html.erb +2 -2
  37. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  38. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  39. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  40. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  41. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  42. data/app/views/panda/cms/admin/settings/index.html.erb +3 -3
  43. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  44. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  45. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  46. data/config/initializers/panda/cms.rb +6 -3
  47. data/config/routes.rb +8 -16
  48. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +3 -1
  49. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +2 -0
  50. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  51. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  52. data/lib/panda/cms/asset_loader.rb +4 -4
  53. data/lib/panda/cms/engine.rb +42 -98
  54. data/lib/panda-cms/version.rb +1 -1
  55. data/lib/panda-cms.rb +50 -40
  56. data/lib/tasks/assets.rake +162 -122
  57. data/lib/tasks/panda/cms/migrations.rake +13 -0
  58. metadata +19 -36
  59. data/app/builders/panda/cms/form_builder.rb +0 -225
  60. data/app/components/panda/cms/admin/button_component.rb +0 -70
  61. data/app/components/panda/cms/admin/container_component.rb +0 -13
  62. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  63. data/app/components/panda/cms/admin/heading_component.rb +0 -46
  64. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  65. data/app/components/panda/cms/admin/table_component.rb +0 -46
  66. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  67. data/app/constraints/panda/cms/admin_constraint.rb +0 -21
  68. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -44
  69. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -92
  70. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -25
  71. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  72. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  73. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  74. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  75. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  76. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  77. data/app/models/panda/cms/breadcrumb.rb +0 -14
  78. data/app/models/panda/cms/user.rb +0 -33
  79. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -195
  80. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  81. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  82. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -7
  83. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -36
  84. data/lib/panda/cms/editor_js/blocks/base.rb +0 -35
  85. data/lib/panda/cms/editor_js/blocks/header.rb +0 -17
  86. data/lib/panda/cms/editor_js/blocks/image.rb +0 -39
  87. data/lib/panda/cms/editor_js/blocks/list.rb +0 -34
  88. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -18
  89. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -44
  90. data/lib/panda/cms/editor_js/blocks/table.rb +0 -52
  91. data/lib/panda/cms/editor_js/renderer.rb +0 -127
  92. data/lib/panda/cms/editor_js.rb +0 -18
  93. data/lib/panda/cms/editor_js_content.rb +0 -61
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class MyProfileController < ApplicationController
7
- before_action :set_initial_breadcrumb, only: %i[edit update]
8
- before_action :authenticate_admin_user!
9
-
10
- # Shows the edit form for the current user's profile
11
- # @type GET
12
- # @return void
13
- def edit
14
- render :edit, locals: {user: current_user}
15
- end
16
-
17
- # Updates the current user's profile
18
- # @type PATCH/PUT
19
- # @return void
20
- def update
21
- if current_user.update(user_params)
22
- flash[:success] = "Your profile has been updated successfully."
23
- redirect_to edit_admin_my_profile_path
24
- else
25
- render :edit, locals: {user: current_user}, status: :unprocessable_entity
26
- end
27
- end
28
-
29
- private
30
-
31
- def set_initial_breadcrumb
32
- add_breadcrumb "My Profile", edit_admin_my_profile_path
33
- end
34
-
35
- # Only allow a list of trusted parameters through
36
- # @type private
37
- # @return ActionController::StrongParameters
38
- def user_params
39
- params.require(:user).permit(:firstname, :lastname, :email, :current_theme)
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class SessionsController < ApplicationController
7
- layout "panda/cms/public"
8
-
9
- def new
10
- @providers = Panda::CMS.config.authentication.select { |_, v| v[:enabled] && !v[:hidden] }.keys
11
- end
12
-
13
- def create
14
- user_info = request.env.dig("omniauth.auth", "info")
15
- provider = params[:provider].to_sym
16
-
17
- unless Panda::CMS.config.authentication.dig(provider, :enabled)
18
- Rails.logger.error "Authentication provider '#{provider}' is not enabled"
19
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
20
- return
21
- end
22
-
23
- user = Panda::CMS::User.find_by(email: user_info["email"])
24
-
25
- if !user && Panda::CMS.config.authentication.dig(provider, :create_account_on_first_login)
26
- create_as_admin = Panda::CMS.config.authentication.dig(provider, :create_as_admin)
27
-
28
- # Always create the first user as admin, regardless of what our settings look like
29
- # else we can't ever really login. :)
30
- create_as_admin = true if !create_as_admin && Panda::CMS::User.count.zero?
31
-
32
- if user_info["first_name"] && user_info["last_name"]
33
- firstname = user_info["first_name"]
34
- lastname = user_info["last_name"]
35
- elsif user_info["name"]
36
- firstname, lastname = user_info["name"].split(" ", 2)
37
- end
38
-
39
- user = User.find_or_create_by(
40
- email: user_info["email"]
41
- ) do |u|
42
- u.firstname = firstname
43
- u.lastname = lastname
44
- u.admin = create_as_admin
45
- u.image_url = user_info["image"]
46
- end
47
- end
48
-
49
- if user.nil?
50
- # User can't be found with this email address
51
- Rails.logger.error "User does not exist: #{user_info["email"]}"
52
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
53
- return
54
- end
55
-
56
- unless user.admin?
57
- # User can't be found with this email address or can't login
58
- Rails.logger.error "User ID #{user.id} attempted admin login, is not admin."
59
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
60
- return
61
- end
62
-
63
- session[:user_id] = user.id
64
- Panda::CMS::Current.user = user
65
-
66
- redirect_path = request.env["omniauth.origin"] || admin_dashboard_path
67
- redirect_to redirect_path, flash: {success: t("panda.cms.admin.sessions.create.success")}
68
- rescue ::OmniAuth::Strategies::OAuth2::CallbackError => e
69
- Rails.logger.error "OAuth2 login callback error: #{e.message}"
70
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
71
- rescue ::OAuth2::Error => e
72
- Rails.logger.error "OAuth2 login error: #{e.message}"
73
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
74
- rescue => e
75
- Rails.logger.error "Unknown login error: #{e.message}"
76
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
77
- end
78
-
79
- def failure
80
- Rails.logger.error "Login failure: #{params[:message]} from #{params[:origin]} using #{params[:strategy]}"
81
- redirect_to admin_login_path, flash: {error: t("panda.cms.admin.sessions.create.error")}
82
- end
83
-
84
- def destroy
85
- Panda::CMS::Current.user = nil
86
- session[:user_id] = nil
87
- redirect_to admin_login_path, flash: {success: t("panda.cms.admin.sessions.destroy.success")}
88
- end
89
- end
90
- end
91
- end
92
- end
@@ -1,25 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- // Connects to data-controller="theme-form"
4
- export default class extends Controller {
5
- connect() {
6
- // Ensure submit button is enabled on connect
7
- this.enableSubmitButton();
8
- }
9
-
10
- updateTheme(event) {
11
- const newTheme = event.target.value;
12
- document.documentElement.dataset.theme = newTheme;
13
- }
14
-
15
- enableSubmitButton() {
16
- // Find the submit button in the form and ensure it's enabled
17
- const form = this.element;
18
- if (form) {
19
- const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
20
- if (submitButton) {
21
- submitButton.disabled = false;
22
- }
23
- }
24
- }
25
- }
@@ -1,80 +0,0 @@
1
- export class CSSExtractor {
2
- /**
3
- * Extracts CSS rules from within a specific selector and transforms them for EditorJS
4
- * @param {string} css - The CSS content to parse
5
- * @returns {string} The extracted and transformed CSS rules
6
- */
7
- static extractStyles(css) {
8
- const rules = []
9
- let inComponents = false
10
- let inContentRule = false
11
- let braceCount = 0
12
- let currentRule = ''
13
-
14
- // Split CSS into lines and process each line
15
- const lines = css.split('\n')
16
-
17
- for (const line of lines) {
18
- const trimmedLine = line.trim()
19
-
20
- // Check if we're entering components layer
21
- if (trimmedLine === '@layer components {') {
22
- inComponents = true
23
- continue
24
- }
25
-
26
- // Only process lines within components layer
27
- if (!inComponents) continue
28
-
29
- // If we find the .content selector
30
- if (!inContentRule && trimmedLine.startsWith('.content')) {
31
- inContentRule = true
32
- braceCount++
33
- // Transform the selector for EditorJS
34
- currentRule = '.codex-editor__redactor .ce-block .ce-block__content'
35
- if (trimmedLine.includes('{')) {
36
- currentRule += ' {'
37
- }
38
- continue
39
- }
40
-
41
- // If we're inside a content rule
42
- if (inContentRule) {
43
- // Transform selectors for EditorJS
44
- let transformedLine = line
45
- .replace(/\.content\s+/g, '.codex-editor__redactor .ce-block .ce-block__content ')
46
- .replace(/\bh1\b(?![-_])/g, 'h1.ce-header')
47
- .replace(/\bh2\b(?![-_])/g, 'h2.ce-header')
48
- .replace(/\bh3\b(?![-_])/g, 'h3.ce-header')
49
- .replace(/\bul\b(?![-_])/g, 'ul.cdx-list')
50
- .replace(/\bol\b(?![-_])/g, 'ol.cdx-list')
51
- .replace(/\bli\b(?![-_])/g, 'li.cdx-list__item')
52
- .replace(/\bblockquote\b(?![-_])/g, '.cdx-quote')
53
-
54
- currentRule += '\n' + transformedLine
55
-
56
- // Count braces to handle nested rules
57
- braceCount += (trimmedLine.match(/{/g) || []).length
58
- braceCount -= (trimmedLine.match(/}/g) || []).length
59
-
60
- // If braces are balanced, we've found the end of the rule
61
- if (braceCount === 0) {
62
- rules.push(currentRule)
63
- inContentRule = false
64
- currentRule = ''
65
- }
66
- }
67
- }
68
-
69
- return rules.join('\n\n')
70
- }
71
-
72
- /**
73
- * Gets all styles from a stylesheet that apply to the editor
74
- * @param {string} css - The CSS content to parse
75
- * @returns {string} The extracted CSS rules
76
- */
77
- static getEditorStyles(css) {
78
- return this.extractStyles(css)
79
- }
80
- }
@@ -1,306 +0,0 @@
1
- export const EDITOR_JS_RESOURCES = [
2
- "https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2.28.2",
3
- "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@2.11.3",
4
- "https://cdn.jsdelivr.net/npm/@editorjs/header@2.8.1",
5
- "https://cdn.jsdelivr.net/npm/@editorjs/nested-list@1.4.2",
6
- "https://cdn.jsdelivr.net/npm/@editorjs/quote@2.6.0",
7
- "https://cdn.jsdelivr.net/npm/@editorjs/simple-image@1.6.0",
8
- "https://cdn.jsdelivr.net/npm/@editorjs/table@2.3.0",
9
- "https://cdn.jsdelivr.net/npm/@editorjs/embed@2.7.0"
10
- ]
11
-
12
- // Allow applications to add their own resources
13
- if (window.PANDA_CMS_EDITOR_JS_RESOURCES) {
14
- EDITOR_JS_RESOURCES.push(...window.PANDA_CMS_EDITOR_JS_RESOURCES)
15
- }
16
-
17
- export const EDITOR_JS_CSS = `
18
- .codex-editor {
19
- position: relative;
20
- }
21
- .codex-editor::before {
22
- content: '';
23
- position: absolute;
24
- left: 0;
25
- top: 0;
26
- bottom: 0;
27
- width: 65px;
28
- margin-right: 5px;
29
- background-color: #f9fafb;
30
- border-right: 2px dashed #e5e7eb;
31
- z-index: 0;
32
- }
33
- .ce-block {
34
- padding-left: 70px;
35
- position: relative;
36
- min-height: 40px;
37
- margin: 0;
38
- padding-bottom: 1em;
39
- }
40
- .ce-block__content {
41
- position: relative;
42
- max-width: none;
43
- margin: 0;
44
- }
45
- .ce-paragraph {
46
- padding: 0;
47
- line-height: 1.6;
48
- min-height: 1.6em;
49
- margin: 0;
50
- }
51
- /* Override inherited heading styles */
52
- .ce-header h1,
53
- .ce-header h2,
54
- .ce-header h3,
55
- .ce-header h4,
56
- .ce-header h5,
57
- .ce-header h6 {
58
- margin: 0;
59
- padding: 0;
60
- line-height: 1.6;
61
- font-weight: 600;
62
- }
63
- .ce-header h1 { font-size: 2em; }
64
- .ce-header h2 { font-size: 1.5em; }
65
- .ce-header h3 { font-size: 1.17em; }
66
- .ce-header h4 { font-size: 1em; }
67
- .ce-header h5 { font-size: 0.83em; }
68
- .ce-header h6 { font-size: 0.67em; }
69
-
70
- .codex-editor__redactor {
71
- padding-bottom: 150px !important;
72
- min-height: 100px !important;
73
- }
74
- /* Base toolbar styles */
75
- .ce-toolbar {
76
- left: 0 !important;
77
- right: auto !important;
78
- background: none !important;
79
- position: absolute !important;
80
- width: 65px !important;
81
- height: 40px !important;
82
- display: flex !important;
83
- align-items: center !important;
84
- justify-content: flex-start !important;
85
- padding: 0 !important;
86
- margin-left: -70px !important;
87
- margin-top: -5px !important;
88
- opacity: 1 !important;
89
- visibility: visible !important;
90
- pointer-events: all !important;
91
- z-index: 2 !important;
92
- }
93
- /* Ensure toolbar is visible for all blocks */
94
- .ce-block .ce-toolbar {
95
- display: flex !important;
96
- opacity: 1 !important;
97
- visibility: visible !important;
98
- }
99
- .ce-toolbar__content {
100
- max-width: none;
101
- left: 70px !important;
102
- display: flex !important;
103
- position: relative !important;
104
- }
105
- .ce-toolbar__actions {
106
- position: relative !important;
107
- left: 5px !important;
108
- opacity: 1 !important;
109
- visibility: visible !important;
110
- background: transparent !important;
111
- z-index: 2;
112
- display: flex !important;
113
- align-items: center !important;
114
- gap: 5px !important;
115
- height: 40px !important;
116
- padding: 0 !important;
117
- }
118
- .ce-toolbar__plus {
119
- position: relative !important;
120
- left: 0px !important;
121
- opacity: 1 !important;
122
- visibility: visible !important;
123
- background: transparent !important;
124
- border: none !important;
125
- z-index: 2;
126
- display: block !important;
127
- }
128
- .ce-toolbar__settings-btn {
129
- position: relative !important;
130
- left: -10px !important;
131
- opacity: 1 !important;
132
- visibility: visible !important;
133
- background: transparent !important;
134
- border: none !important;
135
- z-index: 2;
136
- display: block !important;
137
- }
138
- /* Style the search input */
139
- .ce-popover__search {
140
- padding-left: 3px !important;
141
- }
142
- .ce-popover__search input {
143
- outline: none !important;
144
- box-shadow: none !important;
145
- border: none !important;
146
- }
147
- .ce-popover__search input::placeholder {
148
- content: 'Search';
149
- }
150
- /* Ensure popups still work */
151
- .ce-popover {
152
- z-index: 4;
153
- }
154
- .ce-inline-toolbar {
155
- z-index: 3;
156
- }
157
- /* Override any hiding behavior */
158
- .ce-toolbar--closed,
159
- .ce-toolbar--opened,
160
- .ce-toolbar--showed {
161
- display: flex !important;
162
- opacity: 1 !important;
163
- visibility: visible !important;
164
- }
165
- /* Force toolbar to show on every block */
166
- .ce-block:not(:focus):not(:hover) .ce-toolbar,
167
- .ce-block--selected .ce-toolbar,
168
- .ce-block--focused .ce-toolbar,
169
- .ce-block--hover .ce-toolbar {
170
- opacity: 1 !important;
171
- visibility: visible !important;
172
- display: flex !important;
173
- }
174
-
175
- /* Ensure last block has bottom spacing */
176
- .ce-block:last-child {
177
- padding-bottom: 2em;
178
- }
179
-
180
- /* Reset all block type margins */
181
- .ce-header,
182
- .ce-paragraph,
183
- .ce-quote,
184
- .ce-list {
185
- margin: 0 !important;
186
- padding: 0 !important;
187
- }
188
- `
189
-
190
- export const getEditorConfig = (elementId, previousData, doc = document) => {
191
- // Validate holder element exists
192
- const holder = doc.getElementById(elementId)
193
- if (!holder) {
194
- throw new Error(`Editor holder element ${elementId} not found`)
195
- }
196
-
197
- // Get the correct window context
198
- const win = doc.defaultView || window
199
-
200
- // Ensure we have a clean holder element
201
- holder.innerHTML = ""
202
-
203
- const config = {
204
- holder: elementId,
205
- data: previousData || {},
206
- placeholder: 'Click the + button to add content...',
207
- inlineToolbar: true,
208
- onChange: () => {
209
- // Ensure the editor is properly initialized before handling changes
210
- if (holder && holder.querySelector('.codex-editor')) {
211
- const event = new Event('editor:change', { bubbles: true })
212
- holder.dispatchEvent(event)
213
- }
214
- },
215
- i18n: {
216
- toolbar: {
217
- filter: {
218
- placeholder: 'Search'
219
- }
220
- }
221
- },
222
- tools: {
223
- header: {
224
- class: win.Header,
225
- inlineToolbar: true,
226
- config: {
227
- placeholder: 'Enter a header',
228
- levels: [1, 2, 3, 4, 5, 6],
229
- defaultLevel: 2
230
- }
231
- },
232
- paragraph: {
233
- class: win.Paragraph,
234
- inlineToolbar: true,
235
- config: {
236
- placeholder: 'Start writing or press Tab to add content...'
237
- }
238
- },
239
- list: {
240
- class: win.NestedList,
241
- inlineToolbar: true,
242
- config: {
243
- defaultStyle: 'unordered',
244
- enableLineBreaks: true
245
- }
246
- },
247
- quote: {
248
- class: win.Quote,
249
- inlineToolbar: true,
250
- config: {
251
- quotePlaceholder: 'Enter a quote',
252
- captionPlaceholder: 'Quote\'s author'
253
- }
254
- },
255
- image: {
256
- class: win.SimpleImage,
257
- inlineToolbar: true,
258
- config: {
259
- placeholder: 'Paste an image URL...'
260
- }
261
- },
262
- table: {
263
- class: win.Table,
264
- inlineToolbar: true,
265
- config: {
266
- rows: 2,
267
- cols: 2
268
- }
269
- },
270
- embed: {
271
- class: win.Embed,
272
- inlineToolbar: true,
273
- config: {
274
- services: {
275
- youtube: true,
276
- vimeo: true
277
- }
278
- }
279
- }
280
- }
281
- }
282
-
283
- // Remove any undefined tools from the config
284
- config.tools = Object.fromEntries(
285
- Object.entries(config.tools)
286
- .filter(([_, value]) => value?.class !== undefined)
287
- .map(([name, tool]) => {
288
- if (!tool.class) {
289
- throw new Error(`Tool ${name} has no class defined`)
290
- }
291
- return [name, tool]
292
- })
293
- )
294
-
295
- // Allow applications to customize the config through Ruby
296
- if (window.PANDA_CMS_EDITOR_JS_CONFIG) {
297
- Object.assign(config.tools, window.PANDA_CMS_EDITOR_JS_CONFIG)
298
- }
299
-
300
- // Allow applications to customize the config through JavaScript
301
- if (typeof window.customizeEditorJS === 'function') {
302
- window.customizeEditorJS(config)
303
- }
304
-
305
- return config
306
- }