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
@@ -3,6 +3,8 @@
3
3
  require "rubygems"
4
4
  require "panda/core"
5
5
  require "panda/core/engine"
6
+ require "panda/editor"
7
+ require "panda/editor/engine"
6
8
  require "panda/cms/railtie"
7
9
 
8
10
  require "invisible_captcha"
@@ -18,7 +20,7 @@ module Panda
18
20
  ]
19
21
 
20
22
  # Basic session setup only
21
- initializer "panda_cms.session", before: :load_config_initializers do |app|
23
+ initializer "panda.cms.session", before: :load_config_initializers do |app|
22
24
  app.config.middleware = app.config.middleware.dup if app.config.middleware.frozen?
23
25
 
24
26
  app.config.session_store :cookie_store, key: "_panda_cms_session"
@@ -50,7 +52,7 @@ module Panda
50
52
  # Custom error handling
51
53
  # config.exceptions_app = Panda::CMS::ExceptionsApp.new(exceptions_app: routes)
52
54
 
53
- initializer "panda_cms.assets" do |app|
55
+ initializer "panda.cms.assets" do |app|
54
56
  if Rails.configuration.respond_to?(:assets)
55
57
  # Add JavaScript paths
56
58
  app.config.assets.paths << root.join("app/javascript")
@@ -68,7 +70,7 @@ module Panda
68
70
  end
69
71
 
70
72
  # Add importmap paths from the engine
71
- initializer "panda_cms.importmap", before: "importmap" do |app|
73
+ initializer "panda.cms.importmap", before: "importmap" do |app|
72
74
  if app.config.respond_to?(:importmap)
73
75
  # Create a new array if frozen
74
76
  app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
@@ -124,111 +126,53 @@ module Panda
124
126
  end
125
127
 
126
128
  # Set up ViewComponent
127
- initializer "panda_cms.view_component" do |app|
129
+ initializer "panda.cms.view_component" do |app|
128
130
  app.config.view_component.preview_paths ||= []
129
131
  app.config.view_component.preview_paths << root.join("spec/components/previews")
130
132
  app.config.view_component.generate.sidecar = true
131
133
  app.config.view_component.generate.preview = true
132
134
  end
133
135
 
134
- # Set up authentication
135
- initializer "panda_cms.omniauth", before: "omniauth" do |app|
136
- app.config.session_store :cookie_store, key: "_panda_cms_session"
137
- app.config.middleware.use ActionDispatch::Cookies
138
- app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
139
-
140
- OmniAuth.config.logger = Rails.logger
141
-
142
- # TODO: Move this to somewhere more sensible?
143
- # Define the mapping of our provider "names" to the OmniAuth strategies and configuration
144
- auth_path = "#{Panda::CMS.route_namespace}/auth"
145
- callback_path = "/callback"
146
- available_providers = {
147
- microsoft: {
148
- strategy: :microsoft_graph,
149
- defaults: {
150
- name: "microsoft",
151
- # Setup at the following URL:
152
- # https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
153
- client_id: Rails.application.credentials.dig(:microsoft, :client_id),
154
- client_secret: Rails.application.credentials.dig(:microsoft, :client_secret),
155
- # Don't change this or the sky will fall on your head
156
- # https://github.com/synth/omniauth-microsoft_graph/tree/main?tab=readme-ov-file#domain-verification
157
- skip_domain_verification: false,
158
- # If your application is single-tenanted, replace "common" with your tenant (directory) ID
159
- # from https://portal.azure.com/#settings/directory, otherwise you'll likely want to leave
160
- # these settings unchanged
161
- client_options: {
162
- site: "https://login.microsoftonline.com/",
163
- token_url: "common/oauth2/v2.0/token",
164
- authorize_url: "common/oauth2/v2.0/authorize"
165
- },
166
- # If you assign specific users or groups, you will likely want to set this to
167
- # true to enable auto-provisioning
168
- create_account_on_first_login: false,
169
- create_admin_account_on_first_login: false,
170
- # Don't hide this provider from the login page
171
- hidden: false
172
- }
173
- },
174
- google: {
175
- strategy: :google_oauth2,
176
- defaults: {
177
- name: "google",
178
- # Setup at the following URL: https://console.developers.google.com/
179
- client_id: Rails.application.credentials.dig(:google, :client_id),
180
- client_secret: Rails.application.credentials.dig(:google, :client_secret),
181
- # If you assign specific users or groups, you will likely want to set this to
182
- # true to enable auto-provisioning
183
- create_account_on_first_login: false,
184
- create_admin_account_on_first_login: false,
185
- # Options we need
186
- scope: "email, profile",
187
- image_aspect_ratio: "square",
188
- image_size: 150,
189
- # Worth setting select_account as default, as many people have multiple Google accounts now:
190
- prompt: "select_account",
191
- # You should probably also set the 'hd' option, huh?,
192
- # Don't hide this provider from the login page
193
- hidden: false
194
- }
195
- },
196
- github: {
197
- strategy: :github,
198
- defaults: {
199
- name: "github",
200
- # Setup at the following URL: https://github.com/settings/applications/new
201
- # with a callback of
202
- # In the meantime, as long as you're set to /admin as your login path, and on
203
- # http://localhost:3000, you can use these for a first login :)
204
- client_id: Rails.application.credentials.dig(:github, :client_id),
205
- client_secret: Rails.application.credentials.dig(:github, :client_secret),
206
- scope: "user:email,read:user",
207
- create_account_on_first_login: false,
208
- create_admin_account_on_first_login: false,
209
- # Don't hide this provider from the login page
210
- hidden: false
211
- }
212
- }
213
- }
136
+ # Authentication is now handled by Panda::Core::Engine
214
137
 
215
- available_providers.each do |provider, options|
216
- next unless Panda::CMS.config.authentication.dig(provider, :enabled)
138
+ # Configure Core for CMS
139
+ initializer "panda.cms.configure_core" do |app|
140
+ Panda::Core.configure do |config|
141
+ # Customize login page
142
+ config.login_logo_path = "/panda-cms-assets/panda-nav.png"
143
+ config.login_page_title = "Panda CMS Admin"
217
144
 
218
- auth_path = auth_path.starts_with?("/") ? auth_path : "/#{auth_path}"
219
- options[:defaults][:path_prefix] = auth_path
145
+ # Set dashboard redirect path to CMS dashboard (using Core's admin_path)
146
+ config.dashboard_redirect_path = "#{Panda::Core.configuration.admin_path}/cms"
220
147
 
221
- options[:defaults][:redirect_uri] = if Rails.env.test?
222
- "#{Capybara.app_host}#{auth_path}/#{provider}#{callback_path}"
223
- else
224
- "#{Panda::CMS.config.url}#{auth_path}/#{provider}#{callback_path}"
225
- end
226
-
227
- provider_config = options[:defaults].merge(Panda::CMS.config.authentication[provider])
148
+ # Customize initial breadcrumb
149
+ config.initial_admin_breadcrumb = ->(controller) {
150
+ # Use CMS dashboard path - just use the string path
151
+ ["Admin", "#{Panda::Core.configuration.admin_path}/cms"]
152
+ }
228
153
 
229
- app.config.middleware.use OmniAuth::Builder do
230
- provider options[:strategy], provider_config
231
- end
154
+ # Dashboard widgets
155
+ config.admin_dashboard_widgets = ->(user) {
156
+ widgets = []
157
+
158
+ # Add CMS statistics widgets if CMS is available
159
+ if defined?(Panda::CMS)
160
+ widgets << Panda::CMS::Admin::StatisticsComponent.new(
161
+ metric: "Views Today",
162
+ value: Panda::CMS::Visit.group_by_day(:visited_at, last: 1).count.values.first || 0
163
+ )
164
+ widgets << Panda::CMS::Admin::StatisticsComponent.new(
165
+ metric: "Views Last Week",
166
+ value: Panda::CMS::Visit.group_by_week(:visited_at, last: 1).count.values.first || 0
167
+ )
168
+ widgets << Panda::CMS::Admin::StatisticsComponent.new(
169
+ metric: "Views Last Month",
170
+ value: Panda::CMS::Visit.group_by_month(:visited_at, last: 1).count.values.first || 0
171
+ )
172
+ end
173
+
174
+ widgets
175
+ }
232
176
  end
233
177
  end
234
178
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Panda
4
4
  module CMS
5
- VERSION = "0.7.5"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
data/lib/panda-cms.rb CHANGED
@@ -7,44 +7,64 @@ require "view_component"
7
7
 
8
8
  module Panda
9
9
  module CMS
10
- extend Dry::Configurable
11
-
12
- setting :title, default: "Demo Site"
13
- setting :admin_path, default: "/admin"
14
- setting :require_login_to_view, default: false
15
- setting :authentication, default: {}
16
- setting :posts, default: {enabled: true, prefix: "blog"}
17
- setting :route_namespace, default: "/admin"
18
- setting :url
19
- setting :editor_js_tools, default: []
20
- setting :editor_js_tool_config, default: {}
21
-
22
- setting :instagram, default: {
23
- enabled: false,
24
- username: nil,
25
- access_token: nil
26
- }
27
-
28
- setting :analytics, default: {
29
- google_analytics: {
30
- enabled: false,
31
- tracking_id: nil
32
- }
33
- }
10
+ class Configuration
11
+ attr_accessor :title, :require_login_to_view, :authentication,
12
+ :posts, :url, :editor_js_tools,
13
+ :editor_js_tool_config, :instagram, :analytics
14
+
15
+ def initialize
16
+ @title = "Demo Site"
17
+ @require_login_to_view = false
18
+ @authentication = {}
19
+ @posts = {enabled: true, prefix: "blog"}
20
+ @url = nil
21
+ @editor_js_tools = []
22
+ @editor_js_tool_config = {}
23
+ @instagram = {
24
+ enabled: false,
25
+ username: nil,
26
+ access_token: nil
27
+ }
28
+ @analytics = {
29
+ google_analytics: {
30
+ enabled: false,
31
+ tracking_id: nil
32
+ }
33
+ }
34
+ end
35
+ end
36
+
37
+ class << self
38
+ attr_writer :configuration
39
+
40
+ def configuration
41
+ @configuration ||= Configuration.new
42
+ end
43
+
44
+ def config
45
+ configuration
46
+ end
47
+
48
+ def configure
49
+ yield configuration if block_given?
50
+ end
51
+
52
+ def reset_configuration!
53
+ @configuration = Configuration.new
54
+ end
55
+ end
34
56
 
35
57
  def self.root_path
36
- config.admin_path
58
+ # Delegate to Panda::Core's admin_path configuration
59
+ Panda::Core.configuration.admin_path
37
60
  end
38
61
 
39
62
  class << self
40
63
  attr_accessor :loader
41
64
 
42
65
  def route_namespace
43
- config.admin_path
44
- end
45
-
46
- def configure
47
- yield config if block_given?
66
+ # Delegate to Panda::Core's admin_path configuration
67
+ Panda::Core.configuration.admin_path
48
68
  end
49
69
  end
50
70
  end
@@ -75,16 +95,6 @@ require_relative "panda/cms/exceptions_app"
75
95
  require_relative "panda/cms/engine"
76
96
  require_relative "panda/cms/demo_site_generator"
77
97
  require_relative "panda/cms/asset_loader"
78
- require_relative "panda/cms/editor_js"
79
- require_relative "panda/cms/editor_js_content"
80
- require_relative "panda/cms/editor_js/renderer"
81
- require_relative "panda/cms/editor_js/blocks/alert"
82
- require_relative "panda/cms/editor_js/blocks/header"
83
- require_relative "panda/cms/editor_js/blocks/image"
84
- require_relative "panda/cms/editor_js/blocks/list"
85
- require_relative "panda/cms/editor_js/blocks/paragraph"
86
- require_relative "panda/cms/editor_js/blocks/quote"
87
- require_relative "panda/cms/editor_js/blocks/table"
88
98
  require_relative "panda/cms/slug"
89
99
 
90
100
  Panda::CMS.loader.setup
@@ -1,154 +1,156 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- namespace :panda_cms do
4
- namespace :assets do
5
- desc "Compile Panda CMS assets for GitHub release distribution"
6
- task :compile do
7
- puts "🐼 Compiling Panda CMS assets..."
8
- puts "Rails.root: #{Rails.root}"
9
- puts "Working directory: #{Dir.pwd}"
10
-
11
- # Create output directory
12
- output_dir = Rails.root.join("tmp", "panda_cms_assets")
13
- FileUtils.mkdir_p(output_dir)
14
-
15
- version = Panda::CMS::VERSION
16
- puts "Version: #{version}"
17
- puts "Output directory: #{output_dir}"
18
-
19
- # Compile JavaScript bundle
20
- js_bundle = compile_javascript_bundle(version)
21
- js_file = output_dir.join("panda-cms-#{version}.js")
22
- File.write(js_file, js_bundle)
23
- puts "✅ JavaScript compiled: #{js_file} (#{File.size(js_file)} bytes)"
24
-
25
- # Compile CSS bundle (if any CSS files exist)
26
- css_bundle = compile_css_bundle(version)
27
- if css_bundle && !css_bundle.strip.empty?
28
- css_file = output_dir.join("panda-cms-#{version}.css")
29
- File.write(css_file, css_bundle)
30
- puts "✅ CSS compiled: #{css_file} (#{File.size(css_file)} bytes)"
31
- end
3
+ namespace :panda do
4
+ namespace :cms do
5
+ namespace :assets do
6
+ desc "Compile Panda CMS assets for GitHub release distribution"
7
+ task :compile do
8
+ puts "🐼 Compiling Panda CMS assets..."
9
+ puts "Rails.root: #{Rails.root}"
10
+ puts "Working directory: #{Dir.pwd}"
11
+
12
+ # Create output directory
13
+ output_dir = Rails.root.join("tmp", "panda_cms_assets")
14
+ FileUtils.mkdir_p(output_dir)
15
+
16
+ version = Panda::CMS::VERSION
17
+ puts "Version: #{version}"
18
+ puts "Output directory: #{output_dir}"
19
+
20
+ # Compile JavaScript bundle
21
+ js_bundle = compile_javascript_bundle(version)
22
+ js_file = output_dir.join("panda-cms-#{version}.js")
23
+ File.write(js_file, js_bundle)
24
+ puts "✅ JavaScript compiled: #{js_file} (#{File.size(js_file)} bytes)"
25
+
26
+ # Compile CSS bundle (if any CSS files exist)
27
+ css_bundle = compile_css_bundle(version)
28
+ if css_bundle && !css_bundle.strip.empty?
29
+ css_file = output_dir.join("panda-cms-#{version}.css")
30
+ File.write(css_file, css_bundle)
31
+ puts "✅ CSS compiled: #{css_file} (#{File.size(css_file)} bytes)"
32
+ end
32
33
 
33
- # Create manifest file
34
- manifest = create_asset_manifest(version)
35
- manifest_file = output_dir.join("manifest.json")
36
- File.write(manifest_file, JSON.pretty_generate(manifest))
37
- puts "✅ Manifest created: #{manifest_file}"
34
+ # Create manifest file
35
+ manifest = create_asset_manifest(version)
36
+ manifest_file = output_dir.join("manifest.json")
37
+ File.write(manifest_file, JSON.pretty_generate(manifest))
38
+ puts "✅ Manifest created: #{manifest_file}"
38
39
 
39
- # Copy assets to test environment location for consistent testing
40
- # Rails.root is the dummy app, so we need to go to its public directory
41
- test_asset_dir = Rails.root.join("public", "panda-cms-assets")
42
- FileUtils.mkdir_p(test_asset_dir)
40
+ # Copy assets to test environment location for consistent testing
41
+ # Rails.root is the dummy app, so we need to go to its public directory
42
+ test_asset_dir = Rails.root.join("public", "panda-cms-assets")
43
+ FileUtils.mkdir_p(test_asset_dir)
43
44
 
44
- js_file_name = "panda-cms-#{version}.js"
45
- css_file_name = "panda-cms-#{version}.css"
45
+ js_file_name = "panda-cms-#{version}.js"
46
+ css_file_name = "panda-cms-#{version}.css"
46
47
 
47
- # Copy JavaScript file
48
- if File.exist?(output_dir.join(js_file_name))
49
- FileUtils.cp(output_dir.join(js_file_name), test_asset_dir.join(js_file_name))
50
- puts "✅ Copied JavaScript to test location: #{test_asset_dir.join(js_file_name)}"
51
- end
48
+ # Copy JavaScript file
49
+ if File.exist?(output_dir.join(js_file_name))
50
+ FileUtils.cp(output_dir.join(js_file_name), test_asset_dir.join(js_file_name))
51
+ puts "✅ Copied JavaScript to test location: #{test_asset_dir.join(js_file_name)}"
52
+ end
52
53
 
53
- # Copy CSS file
54
- if File.exist?(output_dir.join(css_file_name))
55
- FileUtils.cp(output_dir.join(css_file_name), test_asset_dir.join(css_file_name))
56
- puts "✅ Copied CSS to test location: #{test_asset_dir.join(css_file_name)}"
57
- end
54
+ # Copy CSS file
55
+ if File.exist?(output_dir.join(css_file_name))
56
+ FileUtils.cp(output_dir.join(css_file_name), test_asset_dir.join(css_file_name))
57
+ puts "✅ Copied CSS to test location: #{test_asset_dir.join(css_file_name)}"
58
+ end
58
59
 
59
- # Copy manifest
60
- if File.exist?(output_dir.join("manifest.json"))
61
- FileUtils.cp(output_dir.join("manifest.json"), test_asset_dir.join("manifest.json"))
62
- puts "✅ Copied manifest to test location: #{test_asset_dir.join("manifest.json")}"
63
- end
60
+ # Copy manifest
61
+ if File.exist?(output_dir.join("manifest.json"))
62
+ FileUtils.cp(output_dir.join("manifest.json"), test_asset_dir.join("manifest.json"))
63
+ puts "✅ Copied manifest to test location: #{test_asset_dir.join("manifest.json")}"
64
+ end
64
65
 
65
- puts "🎉 Asset compilation complete!"
66
- puts "📁 Output directory: #{output_dir}"
67
- puts "📁 Test assets directory: #{test_asset_dir}"
68
- end
66
+ puts "🎉 Asset compilation complete!"
67
+ puts "📁 Output directory: #{output_dir}"
68
+ puts "📁 Test assets directory: #{test_asset_dir}"
69
+ end
69
70
 
70
- desc "Upload compiled assets to GitHub release"
71
- task upload: :compile do
72
- version = Panda::CMS::VERSION
73
- output_dir = Rails.root.join("tmp", "panda_cms_assets")
71
+ desc "Upload compiled assets to GitHub release"
72
+ task upload: :compile do
73
+ version = Panda::CMS::VERSION
74
+ output_dir = Rails.root.join("tmp", "panda_cms_assets")
74
75
 
75
- puts "📤 Uploading assets to GitHub release v#{version}..."
76
+ puts "📤 Uploading assets to GitHub release v#{version}..."
76
77
 
77
- # Check if gh CLI is available
78
- unless system("gh --version > /dev/null 2>&1")
79
- puts "❌ GitHub CLI (gh) not found. Please install: https://cli.github.com/"
80
- exit 1
81
- end
78
+ # Check if gh CLI is available
79
+ unless system("gh --version > /dev/null 2>&1")
80
+ puts "❌ GitHub CLI (gh) not found. Please install: https://cli.github.com/"
81
+ exit 1
82
+ end
82
83
 
83
- # Check if release exists
84
- unless system("gh release view v#{version} > /dev/null 2>&1")
85
- puts "❌ Release v#{version} not found. Create it first with: gh release create v#{version}"
86
- exit 1
87
- end
84
+ # Check if release exists
85
+ unless system("gh release view v#{version} > /dev/null 2>&1")
86
+ puts "❌ Release v#{version} not found. Create it first with: gh release create v#{version}"
87
+ exit 1
88
+ end
88
89
 
89
- # Upload each asset file
90
- Dir.glob(output_dir.join("*")).each do |file|
91
- filename = File.basename(file)
92
- puts "Uploading #{filename}..."
90
+ # Upload each asset file
91
+ Dir.glob(output_dir.join("*")).each do |file|
92
+ filename = File.basename(file)
93
+ puts "Uploading #{filename}..."
93
94
 
94
- if system("gh release upload v#{version} #{file} --clobber")
95
- puts "✅ Uploaded: #{filename}"
96
- else
97
- puts "❌ Failed to upload: #{filename}"
98
- exit 1
95
+ if system("gh release upload v#{version} #{file} --clobber")
96
+ puts "✅ Uploaded: #{filename}"
97
+ else
98
+ puts "❌ Failed to upload: #{filename}"
99
+ exit 1
100
+ end
99
101
  end
100
- end
101
102
 
102
- puts "🎉 All assets uploaded successfully!"
103
- end
103
+ puts "🎉 All assets uploaded successfully!"
104
+ end
104
105
 
105
- desc "Download assets from GitHub release for local development"
106
- task :download do
107
- version = Panda::CMS::VERSION
108
- output_dir = Rails.root.join("public", "panda-cms-assets", version)
109
- FileUtils.mkdir_p(output_dir)
106
+ desc "Download assets from GitHub release for local development"
107
+ task :download do
108
+ version = Panda::CMS::VERSION
109
+ output_dir = Rails.root.join("public", "panda-cms-assets", version)
110
+ FileUtils.mkdir_p(output_dir)
110
111
 
111
- puts "📥 Downloading assets from GitHub release v#{version}..."
112
+ puts "📥 Downloading assets from GitHub release v#{version}..."
112
113
 
113
- # Download manifest first to know what files to get
114
- manifest_url = "https://github.com/pandacms/panda-cms/releases/download/v#{version}/manifest.json"
114
+ # Download manifest first to know what files to get
115
+ manifest_url = "https://github.com/pandacms/panda-cms/releases/download/v#{version}/manifest.json"
115
116
 
116
- begin
117
- require "net/http"
118
- require "uri"
117
+ begin
118
+ require "net/http"
119
+ require "uri"
119
120
 
120
- uri = URI(manifest_url)
121
- response = Net::HTTP.get_response(uri)
121
+ uri = URI(manifest_url)
122
+ response = Net::HTTP.get_response(uri)
122
123
 
123
- if response.code == "200"
124
- manifest = JSON.parse(response.body)
125
- puts "✅ Downloaded manifest"
124
+ if response.code == "200"
125
+ manifest = JSON.parse(response.body)
126
+ puts "✅ Downloaded manifest"
126
127
 
127
- # Download each file listed in manifest
128
- manifest["files"].each do |file_info|
129
- filename = file_info["filename"]
130
- file_url = "https://github.com/pandacms/panda-cms/releases/download/v#{version}/#{filename}"
128
+ # Download each file listed in manifest
129
+ manifest["files"].each do |file_info|
130
+ filename = file_info["filename"]
131
+ file_url = "https://github.com/pandacms/panda-cms/releases/download/v#{version}/#{filename}"
131
132
 
132
- puts "Downloading #{filename}..."
133
- file_uri = URI(file_url)
134
- file_response = Net::HTTP.get_response(file_uri)
133
+ puts "Downloading #{filename}..."
134
+ file_uri = URI(file_url)
135
+ file_response = Net::HTTP.get_response(file_uri)
135
136
 
136
- if file_response.code == "200"
137
- File.write(output_dir.join(filename), file_response.body)
138
- puts "✅ Downloaded: #{filename}"
139
- else
140
- puts "❌ Failed to download: #{filename}"
137
+ if file_response.code == "200"
138
+ File.write(output_dir.join(filename), file_response.body)
139
+ puts "✅ Downloaded: #{filename}"
140
+ else
141
+ puts "❌ Failed to download: #{filename}"
142
+ end
141
143
  end
142
- end
143
144
 
144
- puts "🎉 Assets downloaded to: #{output_dir}"
145
- else
146
- puts "❌ Failed to download manifest from: #{manifest_url}"
147
- puts "Response: #{response.code} #{response.message}"
145
+ puts "🎉 Assets downloaded to: #{output_dir}"
146
+ else
147
+ puts "❌ Failed to download manifest from: #{manifest_url}"
148
+ puts "Response: #{response.code} #{response.message}"
149
+ end
150
+ rescue => e
151
+ puts "❌ Error downloading assets: #{e.message}"
152
+ puts "Falling back to local development mode..."
148
153
  end
149
- rescue => e
150
- puts "❌ Error downloading assets: #{e.message}"
151
- puts "Falling back to local development mode..."
152
154
  end
153
155
  end
154
156
  end
@@ -307,10 +309,48 @@ def create_tailwind_components
307
309
  " }",
308
310
  "};",
309
311
  "",
312
+ "const Toggle = {",
313
+ " static: {",
314
+ " values: { open: { type: Boolean, default: false } },",
315
+ " targets: ['toggleable']",
316
+ " },",
317
+ " connect() {",
318
+ " console.log('[Panda CMS] Toggle controller connected');",
319
+ " this.openValue = false;",
320
+ " // Find toggleable elements",
321
+ " this.toggleableTargets = Array.from(this.element.querySelectorAll('[data-toggle-target=\"toggleable\"]'));",
322
+ " if (this.toggleableTargets.length === 0) {",
323
+ " // For slideover, the toggleable element might be a sibling",
324
+ " const slideover = document.querySelector('#slideover');",
325
+ " if (slideover) {",
326
+ " this.toggleableTargets = [slideover];",
327
+ " }",
328
+ " }",
329
+ " },",
330
+ " toggle(event) {",
331
+ " event.preventDefault();",
332
+ " console.log('[Panda CMS] Toggle action triggered');",
333
+ " this.openValue = !this.openValue;",
334
+ " this.animate();",
335
+ " },",
336
+ " animate() {",
337
+ " this.toggleableTargets.forEach(element => {",
338
+ " if (this.openValue) {",
339
+ " element.classList.remove('hidden');",
340
+ " element.style.display = 'block';",
341
+ " } else {",
342
+ " element.classList.add('hidden');",
343
+ " element.style.display = 'none';",
344
+ " }",
345
+ " });",
346
+ " }",
347
+ "};",
348
+ "",
310
349
  "// Register TailwindCSS components",
311
350
  "Stimulus.register('alert', Alert);",
312
351
  "Stimulus.register('dropdown', Dropdown);",
313
352
  "Stimulus.register('modal', Modal);",
353
+ "Stimulus.register('toggle', Toggle);",
314
354
  ""
315
355
  ].join("\n")
316
356
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :panda do
4
+ namespace :cms do
5
+ namespace :install do
6
+ desc "Copy migrations from panda_cms to application"
7
+ task :migrations do
8
+ # Delegate to the auto-generated task
9
+ Rake::Task["panda_cms:install:migrations"].invoke
10
+ end
11
+ end
12
+ end
13
+ end