panda_cms 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (193) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/builds/panda_cms.css +1 -0
  5. data/app/assets/config/panda_cms_manifest.js +1 -0
  6. data/app/assets/stylesheets/panda_cms/application.tailwind.css +61 -0
  7. data/app/builders/panda_cms/form_builder.rb +118 -0
  8. data/app/components/panda_cms/admin/button_component.rb +65 -0
  9. data/app/components/panda_cms/admin/container_component.html.erb +13 -0
  10. data/app/components/panda_cms/admin/container_component.rb +11 -0
  11. data/app/components/panda_cms/admin/flash_message_component.html.erb +30 -0
  12. data/app/components/panda_cms/admin/flash_message_component.rb +44 -0
  13. data/app/components/panda_cms/admin/heading_component.rb +43 -0
  14. data/app/components/panda_cms/admin/panel_component.html.erb +7 -0
  15. data/app/components/panda_cms/admin/panel_component.rb +11 -0
  16. data/app/components/panda_cms/admin/slideover_component.html.erb +9 -0
  17. data/app/components/panda_cms/admin/slideover_component.rb +13 -0
  18. data/app/components/panda_cms/admin/statistics_component.html.erb +4 -0
  19. data/app/components/panda_cms/admin/statistics_component.rb +15 -0
  20. data/app/components/panda_cms/admin/tab_bar_component.html.erb +35 -0
  21. data/app/components/panda_cms/admin/tab_bar_component.rb +13 -0
  22. data/app/components/panda_cms/admin/table_component.html.erb +21 -0
  23. data/app/components/panda_cms/admin/table_component.rb +43 -0
  24. data/app/components/panda_cms/admin/tag_component.rb +33 -0
  25. data/app/components/panda_cms/admin/user_activity_component.html.erb +5 -0
  26. data/app/components/panda_cms/admin/user_activity_component.rb +19 -0
  27. data/app/components/panda_cms/admin/user_display_component.html.erb +11 -0
  28. data/app/components/panda_cms/admin/user_display_component.rb +19 -0
  29. data/app/components/panda_cms/grid_component.html.erb +6 -0
  30. data/app/components/panda_cms/grid_component.rb +13 -0
  31. data/app/components/panda_cms/menu_component.html.erb +3 -0
  32. data/app/components/panda_cms/menu_component.rb +18 -0
  33. data/app/components/panda_cms/page_menu_component.html.erb +24 -0
  34. data/app/components/panda_cms/page_menu_component.rb +24 -0
  35. data/app/components/panda_cms/rich_text_component.html.erb +40 -0
  36. data/app/components/panda_cms/rich_text_component.rb +35 -0
  37. data/app/components/panda_cms/text_component.rb +67 -0
  38. data/app/constraints/panda_cms/admin_constraint.rb +16 -0
  39. data/app/controllers/panda_cms/admin/block_contents_controller.rb +44 -0
  40. data/app/controllers/panda_cms/admin/dashboard_controller.rb +31 -0
  41. data/app/controllers/panda_cms/admin/files_controller.rb +19 -0
  42. data/app/controllers/panda_cms/admin/forms_controller.rb +51 -0
  43. data/app/controllers/panda_cms/admin/menus_controller.rb +81 -0
  44. data/app/controllers/panda_cms/admin/pages_controller.rb +88 -0
  45. data/app/controllers/panda_cms/admin/posts_controller.rb +34 -0
  46. data/app/controllers/panda_cms/admin/sessions_controller.rb +83 -0
  47. data/app/controllers/panda_cms/admin/settings/bulk_editor_controller.rb +35 -0
  48. data/app/controllers/panda_cms/admin/settings_controller.rb +18 -0
  49. data/app/controllers/panda_cms/application_controller.rb +55 -0
  50. data/app/controllers/panda_cms/errors_controller.rb +31 -0
  51. data/app/controllers/panda_cms/form_submissions_controller.rb +21 -0
  52. data/app/controllers/panda_cms/pages_controller.rb +56 -0
  53. data/app/controllers/panda_cms/posts_controller.rb +17 -0
  54. data/app/helpers/panda_cms/admin/files_helper.rb +4 -0
  55. data/app/helpers/panda_cms/admin/pages_helper.rb +4 -0
  56. data/app/helpers/panda_cms/application_helper.rb +96 -0
  57. data/app/helpers/panda_cms/pages_helper.rb +4 -0
  58. data/app/helpers/panda_cms/theme_helper.rb +16 -0
  59. data/app/javascript/base.js +37 -0
  60. data/app/javascript/controllers/menu_controller.js +19 -0
  61. data/app/javascript/controllers/text_controller.js +78 -0
  62. data/app/javascript/controllers/text_field_update_controller.js +23 -0
  63. data/app/javascript/vendor/stimulus-components-rails-nested-form.js +2 -0
  64. data/app/javascript/vendor/tailwindcss-stimulus-components.js +2 -0
  65. data/app/jobs/panda_cms/application_job.rb +4 -0
  66. data/app/jobs/panda_cms/record_visit_job.rb +29 -0
  67. data/app/lib/panda_cms/bulk_editor.rb +169 -0
  68. data/app/lib/panda_cms/demo_site_generator.rb +70 -0
  69. data/app/lib/panda_cms/slug.rb +22 -0
  70. data/app/mailers/panda_cms/application_mailer.rb +6 -0
  71. data/app/mailers/panda_cms/form_mailer.rb +19 -0
  72. data/app/models/panda_cms/application_record.rb +5 -0
  73. data/app/models/panda_cms/block.rb +32 -0
  74. data/app/models/panda_cms/block_content.rb +16 -0
  75. data/app/models/panda_cms/block_content_version.rb +6 -0
  76. data/app/models/panda_cms/breadcrumb.rb +10 -0
  77. data/app/models/panda_cms/current.rb +15 -0
  78. data/app/models/panda_cms/form.rb +7 -0
  79. data/app/models/panda_cms/form_submission.rb +5 -0
  80. data/app/models/panda_cms/menu.rb +50 -0
  81. data/app/models/panda_cms/menu_item.rb +56 -0
  82. data/app/models/panda_cms/page.rb +81 -0
  83. data/app/models/panda_cms/page_version.rb +6 -0
  84. data/app/models/panda_cms/post.rb +25 -0
  85. data/app/models/panda_cms/post_version.rb +6 -0
  86. data/app/models/panda_cms/redirect.rb +9 -0
  87. data/app/models/panda_cms/template.rb +117 -0
  88. data/app/models/panda_cms/template_version.rb +6 -0
  89. data/app/models/panda_cms/user.rb +15 -0
  90. data/app/models/panda_cms/version.rb +6 -0
  91. data/app/models/panda_cms/visit.rb +7 -0
  92. data/app/views/layouts/panda_cms/application.html.erb +44 -0
  93. data/app/views/layouts/panda_cms/public.html.erb +3 -0
  94. data/app/views/panda_cms/admin/dashboard/show.html.erb +11 -0
  95. data/app/views/panda_cms/admin/files/index.html.erb +124 -0
  96. data/app/views/panda_cms/admin/files/show.html.erb +2 -0
  97. data/app/views/panda_cms/admin/forms/edit.html.erb +0 -0
  98. data/app/views/panda_cms/admin/forms/index.html.erb +13 -0
  99. data/app/views/panda_cms/admin/forms/new.html.erb +16 -0
  100. data/app/views/panda_cms/admin/forms/show.html.erb +35 -0
  101. data/app/views/panda_cms/admin/menus/_form.html.erb +21 -0
  102. data/app/views/panda_cms/admin/menus/_menu_item_fields.html.erb +7 -0
  103. data/app/views/panda_cms/admin/menus/edit.html.erb +58 -0
  104. data/app/views/panda_cms/admin/menus/index.html.erb +10 -0
  105. data/app/views/panda_cms/admin/menus/new.html.erb +5 -0
  106. data/app/views/panda_cms/admin/pages/edit.html.erb +26 -0
  107. data/app/views/panda_cms/admin/pages/index.html.erb +16 -0
  108. data/app/views/panda_cms/admin/pages/new.html.erb +16 -0
  109. data/app/views/panda_cms/admin/pages/show.html.erb +1 -0
  110. data/app/views/panda_cms/admin/posts/index.html.erb +16 -0
  111. data/app/views/panda_cms/admin/sessions/new.html.erb +18 -0
  112. data/app/views/panda_cms/admin/settings/bulk_editor/new.html.erb +68 -0
  113. data/app/views/panda_cms/admin/settings/index.html.erb +19 -0
  114. data/app/views/panda_cms/admin/shared/_breadcrumbs.html.erb +28 -0
  115. data/app/views/panda_cms/admin/shared/_flash.html.erb +5 -0
  116. data/app/views/panda_cms/admin/shared/_sidebar.html.erb +45 -0
  117. data/app/views/panda_cms/form_mailer/notification_email.html.erb +11 -0
  118. data/app/views/panda_cms/shared/_favicons.html.erb +9 -0
  119. data/app/views/panda_cms/shared/_footer.html.erb +2 -0
  120. data/app/views/panda_cms/shared/_header.html.erb +15 -0
  121. data/config/importmap.rb +9 -0
  122. data/config/initializers/panda_cms/form_errors.rb +38 -0
  123. data/config/initializers/panda_cms/healthcheck_log_silencer.rb +11 -0
  124. data/config/initializers/panda_cms.rb +52 -0
  125. data/config/locales/en.yml +29 -0
  126. data/config/routes.rb +43 -0
  127. data/config/tailwind.config.js +35 -0
  128. data/config/tailwind.embed.config.js +20 -0
  129. data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
  130. data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
  131. data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
  132. data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
  133. data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
  134. data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
  135. data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
  136. data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
  137. data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
  138. data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
  139. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
  140. data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
  141. data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
  142. data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
  143. data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
  144. data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
  145. data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
  146. data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
  147. data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
  148. data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
  149. data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
  150. data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  151. data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
  152. data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
  153. data/db/migrate/20240804110225_add_status_to_panda_cms_pages.rb +7 -0
  154. data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
  155. data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
  156. data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
  157. data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
  158. data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
  159. data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
  160. data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
  161. data/db/seeds.rb +4 -0
  162. data/lib/generators/panda_cms/install_generator.rb +24 -0
  163. data/lib/panda_cms/engine.rb +167 -0
  164. data/lib/panda_cms/exceptions_app.rb +24 -0
  165. data/lib/panda_cms/version.rb +3 -0
  166. data/lib/panda_cms.rb +15 -0
  167. data/lib/tasks/panda_cms.rake +92 -0
  168. data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
  169. data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
  170. data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
  171. data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
  172. data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
  173. data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
  174. data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
  175. data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
  176. data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
  177. data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
  178. data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
  179. data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
  180. data/public/panda-cms-assets/favicons/favicon.ico +0 -0
  181. data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
  182. data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
  183. data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
  184. data/public/panda-cms-assets/javascripts/base.js +37 -0
  185. data/public/panda-cms-assets/javascripts/controllers/menu_controller.js +19 -0
  186. data/public/panda-cms-assets/javascripts/controllers/text_field_update_controller.js +23 -0
  187. data/public/panda-cms-assets/javascripts/embed/editable.js +308 -0
  188. data/public/panda-cms-assets/javascripts/vendor/stimulus-components-rails-nested-form.js +2 -0
  189. data/public/panda-cms-assets/javascripts/vendor/stimulus-loading.js +113 -0
  190. data/public/panda-cms-assets/javascripts/vendor/tailwindcss-stimulus-components.js +2 -0
  191. data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
  192. data/public/panda-cms-assets/panda-nav.png +0 -0
  193. metadata +1034 -0
@@ -0,0 +1,167 @@
1
+ require "groupdate"
2
+ require "importmap-rails"
3
+ require "paper_trail"
4
+ require "view_component"
5
+
6
+ require "omniauth"
7
+ require "omniauth/rails_csrf_protection"
8
+ require "omniauth/strategies/microsoft_graph"
9
+ require "omniauth/strategies/google_oauth2"
10
+ require "omniauth/strategies/github"
11
+
12
+ require "panda_cms/exceptions_app"
13
+
14
+ module PandaCms
15
+ class Engine < ::Rails::Engine
16
+ isolate_namespace PandaCms
17
+ engine_name "panda_cms"
18
+
19
+ config.to_prepare do
20
+ ApplicationController.helper(::ApplicationHelper)
21
+ end
22
+
23
+ # We rely on ViewComponent here before we can continue
24
+ config.railties_order = [::ViewComponent::Engine, PandaCms::Engine, :main_app, :all]
25
+
26
+ # Set our generators
27
+ config.generators do |g|
28
+ g.orm :active_record, primary_key_type: :uuid
29
+ g.test_framework :rspec, fixture: true
30
+ g.fixture_replacement :factory_bot, dir: "spec/factories"
31
+ g.view_specs false
32
+ g.templates.unshift File.expand_path("../../templates", __FILE__)
33
+ end
34
+
35
+ # Make files in public available to the main app (e.g. favicon.ico)
36
+ config.app_middleware.use(
37
+ Rack::Static,
38
+ urls: ["/panda-cms-assets"],
39
+ root: PandaCms::Engine.root.join("public")
40
+ )
41
+
42
+ config.exceptions_app = PandaCms::ExceptionsApp.new(exceptions_app: routes)
43
+
44
+ # Create an importmap for the engine's JS
45
+ initializer "panda_cms.importmap", before: "importmap" do |app|
46
+ app.config.importmap.cache_sweeper(watches: PandaCms::Engine.root.join("public/panda-cms-assets"))
47
+ app.config.importmap.paths << PandaCms::Engine.root.join("config/importmap.rb")
48
+ end
49
+
50
+ # Append routes to the routes file
51
+ config.after_initialize do |app|
52
+ app.routes.append do
53
+ mount PandaCms::Engine => "/", :as => "panda_cms"
54
+ post "/_forms/:id", to: "panda_cms/form_submissions#create", as: :panda_cms_form_submit
55
+ get "/_maintenance", to: "panda_cms/errors#error_503", as: :panda_cms_maintenance
56
+ get "/*path", to: "panda_cms/pages#show", as: :panda_cms_page
57
+ root to: "panda_cms/pages#root"
58
+ end
59
+ end
60
+
61
+ # Add the migrations to the main app
62
+ initializer "panda_cms.migrations" do |app|
63
+ unless app.root.to_s.match root.to_s
64
+ config.paths["db/migrate"].expanded.each do |expanded_path|
65
+ app.config.paths["db/migrate"] << expanded_path
66
+ end
67
+ end
68
+ end
69
+
70
+ # Set up authentication
71
+ initializer "panda_cms.omniauth", before: "omniauth" do |app|
72
+ app.config.session_store :cookie_store, key: "_panda_cms_session"
73
+ app.config.middleware.use ActionDispatch::Cookies
74
+ app.config.middleware.use ActionDispatch::Session::CookieStore, app.config.session_options
75
+
76
+ OmniAuth.config.logger = Rails.logger
77
+ auth_path = "#{PandaCms.admin_path}/auth"
78
+ callback_path = "/callback"
79
+
80
+ # TODO: Move this to somewhere more sensible
81
+ # Define the mapping of our provider "names" to the OmniAuth strategies and configuration
82
+ available_providers = {
83
+ microsoft: {
84
+ strategy: :microsoft_graph,
85
+ defaults: {
86
+ name: "microsoft",
87
+ # Setup at the following URL:
88
+ # https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
89
+ client_id: Rails.application.credentials.dig(:microsoft, :client_id),
90
+ client_secret: Rails.application.credentials.dig(:microsoft, :client_secret),
91
+ # Don't change this or the sky will fall on your head
92
+ # https://github.com/synth/omniauth-microsoft_graph/tree/main?tab=readme-ov-file#domain-verification
93
+ skip_domain_verification: false,
94
+ # If your application is single-tenanted, replace "common" with your tenant (directory) ID
95
+ # from https://portal.azure.com/#settings/directory, otherwise you'll likely want to leave
96
+ # these settings unchanged
97
+ client_options: {
98
+ site: "https://login.microsoftonline.com/",
99
+ token_url: "common/oauth2/v2.0/token",
100
+ authorize_url: "common/oauth2/v2.0/authorize"
101
+ },
102
+ # If you assign specific users or groups, you will likely want to set this to
103
+ # true to enable auto-provisioning
104
+ create_account_on_first_login: false,
105
+ create_admin_account_on_first_login: false,
106
+ # Don't hide this provider from the login page
107
+ hidden: false
108
+ }
109
+ },
110
+ google: {
111
+ strategy: :google_oauth2,
112
+ defaults: {
113
+ name: "google",
114
+ # Setup at the following URL: https://console.developers.google.com/
115
+ client_id: Rails.application.credentials.dig(:google, :client_id),
116
+ client_secret: Rails.application.credentials.dig(:google, :client_secret),
117
+ # If you assign specific users or groups, you will likely want to set this to
118
+ # true to enable auto-provisioning
119
+ create_account_on_first_login: false,
120
+ create_admin_account_on_first_login: false,
121
+ # Options we need
122
+ scope: "email, profile",
123
+ image_aspect_ratio: "square",
124
+ image_size: 150,
125
+ # Worth setting select_account as default, as many people have multiple Google accounts now:
126
+ prompt: "select_account",
127
+ # You should probably also set the 'hd' option, huh?,
128
+ # Don't hide this provider from the login page
129
+ hidden: false
130
+ }
131
+ },
132
+ github: {
133
+ strategy: :github,
134
+ defaults: {
135
+ name: "github",
136
+ # Setup at the following URL: https://github.com/settings/applications/new
137
+ # with a callback of
138
+ # In the meantime, as long as you're set to /admin as your login path, and on
139
+ # http://localhost:3000, you can use these for a first login :)
140
+ client_id: Rails.application.credentials.dig(:github, :client_id),
141
+ client_secret: Rails.application.credentials.dig(:github, :client_secret),
142
+ scope: "user:email,read:user",
143
+ create_account_on_first_login: false,
144
+ create_admin_account_on_first_login: false,
145
+ # Don't hide this provider from the login page
146
+ hidden: false
147
+ }
148
+ }
149
+ }
150
+
151
+ available_providers.each do |provider, options|
152
+ if PandaCms.authentication.dig(provider, :enabled)
153
+ options[:defaults][:path_prefix] = auth_path
154
+ options[:defaults][:redirect_uri] = "#{PandaCms.url}#{auth_path}/#{provider}#{callback_path}"
155
+
156
+ provider_config = options[:defaults].merge(PandaCms.authentication[provider])
157
+
158
+ Rails.logger.info("Configuring OmniAuth for #{provider} with #{provider_config}")
159
+
160
+ app.config.middleware.use OmniAuth::Builder do
161
+ provider options[:strategy], provider_config
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,24 @@
1
+ # https://guides.rubyonrails.org/configuring.html#config-exceptions-app
2
+ module PandaCms
3
+ class ExceptionsApp
4
+ def initialize(exceptions_app:)
5
+ @exceptions_app = exceptions_app
6
+ end
7
+
8
+ def call(env)
9
+ request = ActionDispatch::Request.new(env)
10
+
11
+ fallback_to_html_format_if_invalid_mime_type(request)
12
+
13
+ @exceptions_app.call(env)
14
+ end
15
+
16
+ private
17
+
18
+ def fallback_to_html_format_if_invalid_mime_type(request)
19
+ request.formats
20
+ rescue ActionDispatch::Http::MimeNegotiation::InvalidType
21
+ request.set_header "CONTENT_TYPE", "text/html"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module PandaCms
2
+ VERSION = "0.5.0"
3
+ end
data/lib/panda_cms.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "panda_cms/version"
2
+ require "panda_cms/engine"
3
+
4
+ module PandaCms
5
+ mattr_accessor :admin_path
6
+ mattr_accessor :authentication
7
+ mattr_accessor :require_login_to_view
8
+ mattr_accessor :title
9
+ mattr_accessor :url
10
+ mattr_accessor :posts
11
+
12
+ def self.admin_path_symbol
13
+ @@admin_path.delete_prefix("/")
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ require "tailwindcss-rails"
2
+ require "shellwords"
3
+
4
+ require "panda_cms/engine"
5
+
6
+ ENV["TAILWIND_PATH"] ||= Tailwindcss::Engine.root.join("exe/tailwindcss").to_s
7
+
8
+ namespace :panda_cms do
9
+ desc "Generate missing blocks from template files"
10
+ task generate_missing_blocks: [:environment] do
11
+ PandaCms::Template.generate_missing_blocks
12
+ end
13
+
14
+ namespace :export do
15
+ desc "Generate a .json export and output to stdout"
16
+ task json: [:environment] do
17
+ puts PandaCms::BulkEditor.export
18
+ end
19
+ end
20
+
21
+ namespace :assets do
22
+ desc "Compile assets for release"
23
+ task :compile do
24
+ # Copy all the JS files into public
25
+ admin_js_path = PandaCms::Engine.root.join("app/javascript/panda_cms")
26
+ FileUtils.cp_r "#{admin_js_path}/.", PandaCms::Engine.root.join("public/panda-cms-assets/javascripts")
27
+ end
28
+
29
+ desc "Build admin assets for Panda CMS"
30
+ task :admin do
31
+ # This is enough for development
32
+ run_tailwind(
33
+ root_path: PandaCms::Engine.root,
34
+ input_path: "app/assets/stylesheets/panda_cms/application.tailwind.css",
35
+ output_path: "app/assets/builds/panda_cms.css"
36
+ )
37
+ end
38
+
39
+ desc "Build dummy assets for Panda CMS"
40
+ # We only care about this in development
41
+ task :dummy do
42
+ run_tailwind(
43
+ root_path: Rails.application.root,
44
+ input_path: "app/assets/stylesheets/application.tailwind.css",
45
+ output_path: "app/assets/builds/application.css",
46
+ config_path: "config/tailwind.config.js"
47
+ )
48
+ end
49
+
50
+ desc "Watch admin assets for Panda CMS"
51
+ # We only care about this in development
52
+ task :watch_admin do
53
+ run_tailwind(
54
+ root_path: PandaCms::Engine.root,
55
+ input_path: "app/assets/stylesheets/panda_cms/application.tailwind.css",
56
+ output_path: "app/assets/builds/panda_cms.css",
57
+ watch: true
58
+ )
59
+ end
60
+
61
+ desc "Watch dummy assets for Panda CMS"
62
+ # We only care about this in development
63
+ task :watch_dummy do
64
+ run_tailwind(
65
+ root_path: Rails.application.root,
66
+ input_path: "app/assets/stylesheets/application.tailwind.css",
67
+ output_path: "app/assets/builds/application.css",
68
+ config_path: "config/tailwind.config.js",
69
+ watch: true
70
+ )
71
+ end
72
+ end
73
+ end
74
+
75
+ task default: [:spec, :panda_cms]
76
+
77
+ def run_tailwind(root_path:, input_path: nil, output_path: nil, config_path: nil, watch: false)
78
+ Rails.logger = Logger.new($stdout)
79
+ config_path ||= root_path.join("config/tailwind.config.js")
80
+
81
+ command = [
82
+ ENV["TAILWIND_PATH"],
83
+ "-i #{root_path.join(input_path)}",
84
+ "-o #{root_path.join(output_path)}",
85
+ "-c #{root_path.join(config_path)}",
86
+ "-m"
87
+ ]
88
+
89
+ command << "-w" if watch
90
+
91
+ exec command.join(" ")
92
+ end
@@ -0,0 +1,43 @@
1
+ <%%= form_with(model: <%= model_resource_name %>, class: "contents") do |form| %>
2
+ <%% if <%= singular_table_name %>.errors.any? %>
3
+ <div id="error_explanation" class="py-2 px-3 mt-3 font-medium text-red-500 bg-red-50 rounded-lg">
4
+ <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
5
+
6
+ <ul>
7
+ <%% <%= singular_table_name %>.errors.each do |error| %>
8
+ <li><%%= error.full_message %></li>
9
+ <%% end %>
10
+ </ul>
11
+ </div>
12
+ <%% end %>
13
+
14
+ <% attributes.each do |attribute| -%>
15
+ <div class="my-5">
16
+ <% if attribute.password_digest? -%>
17
+ <%%= form.label :password %>
18
+ <%%= form.password_field :password, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
19
+ </div>
20
+
21
+ <div class="my-5">
22
+ <%%= form.label :password_confirmation %>
23
+ <%%= form.password_field :password_confirmation, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
24
+ <% elsif attribute.attachments? -%>
25
+ <%%= form.label :<%= attribute.column_name %> %>
26
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
27
+ <% else -%>
28
+ <%%= form.label :<%= attribute.column_name %> %>
29
+ <% if attribute.field_type == :text_area -%>
30
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, rows: 4, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
31
+ <% elsif attribute.field_type == :check_box -%>
32
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: "block mt-2 h-5 w-5" %>
33
+ <% else -%>
34
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
35
+ <% end -%>
36
+ <% end -%>
37
+ </div>
38
+
39
+ <% end -%>
40
+ <div class="inline">
41
+ <%%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
42
+ </div>
43
+ <%% end %>
@@ -0,0 +1,8 @@
1
+ <div class="mx-auto w-full md:w-2/3">
2
+ <h1 class="text-4xl font-bold">Editing <%= human_name.downcase %></h1>
3
+
4
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
5
+
6
+ <%%= link_to "Show this <%= human_name.downcase %>", @<%= singular_table_name %>, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
7
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
8
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="w-full">
2
+ <%% if notice.present? %>
3
+ <p class="inline-block py-2 px-3 mb-5 font-medium text-green-500 bg-green-50 rounded-lg" id="notice"><%%= notice %></p>
4
+ <%% end %>
5
+
6
+ <div class="flex justify-between items-center">
7
+ <h1 class="text-4xl font-bold"><%= human_name.pluralize %></h1>
8
+ <%%= link_to "New <%= human_name.downcase %>", new_<%= singular_route_name %>_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
9
+ </div>
10
+
11
+ <div id="<%= plural_table_name %>" class="min-w-full">
12
+ <%%= render @<%= plural_table_name %> %>
13
+ </div>
14
+ </div>
@@ -0,0 +1,7 @@
1
+ <div class="mx-auto w-full md:w-2/3">
2
+ <h1 class="text-4xl font-bold">New <%= human_name.downcase %></h1>
3
+
4
+ <%%= render "form", <%= singular_table_name %>: @<%= singular_table_name %> %>
5
+
6
+ <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
7
+ </div>
@@ -0,0 +1,22 @@
1
+ <div id="<%%= dom_id <%= singular_name %> %>">
2
+ <% attributes.reject(&:password_digest?).each do |attribute| -%>
3
+ <p class="my-5">
4
+ <strong class="block mb-1 font-medium"><%= attribute.human_name %>:</strong>
5
+ <% if attribute.attachment? -%>
6
+ <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %> if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
7
+ <% elsif attribute.attachments? -%>
8
+ <%% <%= singular_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
9
+ <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
10
+ <%% end %>
11
+ <% else -%>
12
+ <%%= <%= singular_name %>.<%= attribute.column_name %> %>
13
+ <% end -%>
14
+ </p>
15
+
16
+ <% end -%>
17
+ <%% if action_name != "show" %>
18
+ <%%= link_to "Show this <%= human_name.downcase %>", <%= singular_name %>, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
19
+ <%%= link_to "Edit this <%= human_name.downcase %>", edit_<%= singular_name %>_path(<%= singular_name %>), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
20
+ <hr class="mt-6">
21
+ <%% end %>
22
+ </div>
@@ -0,0 +1,15 @@
1
+ <div class="flex mx-auto w-full md:w-2/3">
2
+ <div class="mx-auto">
3
+ <%% if notice.present? %>
4
+ <p class="inline-block py-2 px-3 mb-5 font-medium text-green-500 bg-green-50 rounded-lg" id="notice"><%%= notice %></p>
5
+ <%% end %>
6
+
7
+ <%%= render @<%= singular_table_name %> %>
8
+
9
+ <%%= link_to "Edit this <%= singular_table_name %>", edit_<%= singular_table_name %>_path(@<%= singular_table_name %>), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
10
+ <div class="inline-block ml-2">
11
+ <%%= button_to "Destroy this <%= singular_table_name %>", <%= singular_table_name %>_path(@<%= singular_table_name %>), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
12
+ </div>
13
+ <%%= link_to "Back to <%= plural_table_name %>", <%= index_helper %>_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
14
+ </div>
15
+ </div>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig>
3
+ <msapplication>
4
+ <tile>
5
+ <square150x150logo src="/panda-cms-assets/mstile-150x150.png"/>
6
+ <TileColor>#b91d47</TileColor>
7
+ </tile>
8
+ </msapplication>
9
+ </browserconfig>
@@ -0,0 +1,61 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
3
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
4
+ <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
5
+ width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
6
+ preserveAspectRatio="xMidYMid meet">
7
+ <metadata>
8
+ Created by potrace 1.14, written by Peter Selinger 2001-2017
9
+ </metadata>
10
+ <g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
11
+ fill="#000000" stroke="none">
12
+ <path d="M565 1803 c-42 -22 -65 -43 -83 -78 -42 -82 -32 -165 29 -226 36 -36
13
+ 39 -43 33 -75 l-6 -34 -28 42 c-49 77 -190 192 -210 172 -5 -5 -17 -38 -26
14
+ -74 -26 -102 -3 -238 55 -319 l19 -26 -61 3 c-67 4 -137 -12 -137 -31 0 -24
15
+ 56 -102 100 -139 46 -40 115 -68 166 -68 32 0 32 -1 23 -32 -21 -74 -7 -156
16
+ 37 -215 29 -39 33 -89 7 -78 -59 25 -73 27 -105 21 -24 -4 -47 -19 -67 -42
17
+ -26 -29 -31 -43 -31 -87 0 -63 25 -120 76 -176 84 -93 183 -120 272 -73 44 23
18
+ 78 26 156 16 35 -5 46 -11 46 -25 0 -19 44 -59 65 -59 7 0 23 8 36 19 18 14
19
+ 46 19 110 23 65 3 95 10 123 26 50 30 164 31 227 2 118 -53 240 5 305 144 15
20
+ 33 24 70 24 103 0 44 -5 58 -31 87 -45 51 -85 56 -171 21 -29 -12 -22 38 12
21
+ 87 67 96 53 232 -35 353 -13 19 -8 30 72 150 75 113 90 143 114 230 33 117 36
22
+ 145 13 145 -23 0 -185 -155 -208 -199 l-20 -36 -11 50 c-11 50 -11 50 22 81
23
+ 125 119 48 334 -120 334 -55 0 -79 -10 -122 -51 l-31 -29 -59 22 c-83 30 -208
24
+ 29 -288 -2 l-59 -22 -31 30 c-18 17 -45 36 -60 41 -41 16 -107 13 -142 -6z
25
+ m557 -83 c74 -23 159 -80 206 -139 42 -52 92 -158 92 -194 0 -33 -41 -161 -70
26
+ -217 -39 -77 -217 -136 -385 -127 -188 10 -313 97 -375 263 -19 53 -19 54 0
27
+ 116 73 236 308 367 532 298z m-437 -49 c3 -5 -4 -12 -14 -16 -11 -3 -32 -20
28
+ -46 -36 l-25 -31 0 36 c0 23 6 39 18 45 22 13 59 14 67 2z m698 -3 c16 -13 35
29
+ -68 22 -68 -16 0 -95 62 -90 71 9 13 48 11 68 -3z m66 -442 c-50 -81 -93 -145
30
+ -96 -142 -5 4 22 49 154 259 14 22 26 37 29 34 3 -2 -37 -70 -87 -151z m-171
31
+ -273 c-74 -117 -67 -107 -72 -102 -7 7 114 199 124 199 5 0 -18 -44 -52 -97z
32
+ m-378 -72 c63 -15 156 -13 218 3 29 7 52 9 52 4 0 -21 -21 -46 -44 -52 -13 -3
33
+ -38 -22 -56 -43 -30 -34 -32 -40 -27 -91 6 -54 5 -56 -53 -143 -33 -48 -69
34
+ -90 -80 -94 -11 -3 -25 -15 -30 -26 -11 -19 -11 -19 -53 2 -54 27 -144 94
35
+ -156 117 -8 16 -1 17 82 17 75 0 99 4 127 21 48 28 80 78 80 127 0 53 -56 111
36
+ -117 121 -40 7 -41 9 -31 32 7 15 17 23 27 20 9 -3 36 -10 61 -15z m174 -288
37
+ c-64 -99 -87 -133 -91 -129 -6 5 91 156 101 156 4 0 0 -12 -10 -27z m235 -45
38
+ c-32 -44 -90 -87 -148 -109 -30 -11 -58 -18 -62 -17 -5 2 13 37 39 78 l48 75
39
+ 71 0 71 0 -19 -27z m-864 -22 c67 -28 119 -138 83 -174 -34 -34 -124 11 -159
40
+ 79 -29 58 -16 96 39 108 1 1 18 -5 37 -13z m1199 -28 c13 -36 -24 -112 -66
41
+ -138 -46 -28 -82 -26 -107 6 l-21 27 27 49 c51 93 142 124 167 56z m-649 -93
42
+ c14 -13 25 -34 25 -47 0 -19 -3 -18 -18 12 -11 24 -28 39 -52 47 -34 11 -34
43
+ 12 -7 12 17 1 38 -9 52 -24z"/>
44
+ <path d="M765 1496 c-71 -31 -115 -122 -95 -198 23 -83 91 -119 158 -84 57 30
45
+ 122 119 122 168 0 37 -34 90 -71 110 -40 21 -72 22 -114 4z m121 -98 c19 -23
46
+ 19 -23 -2 -5 -11 9 -29 17 -39 17 -27 0 -55 -30 -55 -59 0 -31 8 -44 35 -59
47
+ 11 -6 15 -12 9 -12 -26 0 -57 37 -57 68 0 65 70 96 109 50z m-31 -26 c0 -18
48
+ -20 -15 -23 4 -3 10 1 15 10 12 7 -3 13 -10 13 -16z"/>
49
+ <path d="M1119 1487 c-94 -63 -83 -180 24 -258 75 -54 149 -32 182 56 31 80
50
+ -5 172 -79 206 -54 25 -86 24 -127 -4z m84 -79 c9 -7 20 -27 23 -45 5 -26 1
51
+ -37 -19 -57 -15 -14 -34 -26 -44 -26 -11 1 -6 6 14 15 36 15 48 42 34 78 -14
52
+ 35 -53 45 -89 22 l-27 -18 19 21 c21 24 63 29 89 10z m-23 -38 c0 -11 -4 -20
53
+ -10 -20 -5 0 -10 9 -10 20 0 11 5 20 10 20 6 0 10 -9 10 -20z"/>
54
+ <path d="M948 1249 c-14 -6 -29 -16 -32 -24 -6 -18 40 -75 61 -75 13 0 13 -3
55
+ 3 -15 -17 -21 -48 -19 -62 5 -17 26 -30 26 -26 -1 2 -12 15 -31 30 -42 23 -17
56
+ 31 -19 53 -9 19 9 32 9 47 1 28 -15 65 -3 84 27 13 19 14 28 5 37 -8 8 -15 6
57
+ -27 -11 -17 -24 -46 -29 -62 -9 -8 10 -4 16 19 26 57 25 61 72 7 90 -37 13
58
+ -64 13 -100 0z m42 -23 c0 -2 -9 -6 -20 -9 -11 -3 -20 -1 -20 4 0 5 9 9 20 9
59
+ 11 0 20 -2 20 -4z"/>
60
+ </g>
61
+ </svg>
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "",
3
+ "short_name": "",
4
+ "icons": [
5
+ {
6
+ "src": "/panda-cms-assets/favicons/android-chrome-192x192.png",
7
+ "sizes": "192x192",
8
+ "type": "image/png"
9
+ }
10
+ ],
11
+ "theme_color": "#ffffff",
12
+ "background_color": "#ffffff",
13
+ "display": "standalone"
14
+ }
@@ -0,0 +1,37 @@
1
+ import { Application as PandaCmsApplication } from "@hotwired/stimulus";
2
+
3
+ const panda_cms = PandaCmsApplication.start();
4
+
5
+ // Configure Stimulus development experience
6
+ panda_cms.debug = location.hostname === "localhost";
7
+ window.pandaStimulus = panda_cms;
8
+
9
+ export { panda_cms };
10
+
11
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
12
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
13
+ eagerLoadControllersFrom("panda_cms/controllers", panda_cms);
14
+
15
+ import {
16
+ Alert,
17
+ Autosave,
18
+ ColorPreview,
19
+ Dropdown,
20
+ Modal,
21
+ Tabs,
22
+ Popover,
23
+ Toggle,
24
+ Slideover,
25
+ } from "panda_cms/vendor/tailwindcss-stimulus-components";
26
+ panda_cms.register("alert", Alert);
27
+ panda_cms.register("autosave", Autosave);
28
+ panda_cms.register("color-preview", ColorPreview);
29
+ panda_cms.register("dropdown", Dropdown);
30
+ panda_cms.register("modal", Modal);
31
+ panda_cms.register("popover", Popover);
32
+ panda_cms.register("slideover", Slideover);
33
+ panda_cms.register("tabs", Tabs);
34
+ panda_cms.register("toggle", Toggle);
35
+
36
+ import RailsNestedForm from "panda_cms/vendor/stimulus-components-rails-nested-form";
37
+ panda_cms.register("nested-form", RailsNestedForm);
@@ -0,0 +1,19 @@
1
+ import { Controller as PandaCmsController } from "@hotwired/stimulus"
2
+
3
+ export default class extends PandaCmsController {
4
+ static targets = ["pandaCmsMenu"]
5
+ static values = {
6
+ open: { type: Boolean, default: false }
7
+ }
8
+
9
+ toggle(event) {
10
+ this.openValue = !this.openValue
11
+ this.animate()
12
+ }
13
+
14
+ animate() {
15
+ this.toggleableTargets.forEach(target => {
16
+ transition(target, this.openValue)
17
+ })
18
+ }
19
+ }
@@ -0,0 +1,23 @@
1
+ import { Controller as PandaCmsController } from "@hotwired/stimulus"
2
+ export default class extends PandaCmsController {
3
+ static targets = [ "existing_root", "input_select", "input_text", "output_text" ]
4
+
5
+ connect() {
6
+ }
7
+
8
+ generatePath() {
9
+ this.output_textTarget.value = "/" + this.createSlug(this.input_textTarget.value);
10
+ }
11
+
12
+ setPrePath() {
13
+ this.parent_slugs = this.input_selectTarget.options[this.input_selectTarget.selectedIndex].text.match(/.*\((.*)\)$/)[1];
14
+ this.output_textTarget.previousSibling.innerHTML = (this.existing_rootTarget.value + this.parent_slugs).replace(/\/$/, '');;
15
+ }
16
+
17
+ createSlug(input) {
18
+ return input.toLowerCase().trim()
19
+ .replace(/[^\w\s-]/g, "-")
20
+ .replace(/&/, "and")
21
+ .replace(/[\s_-]+/g, "-");
22
+ }
23
+ }