cms42 0.1.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 (145) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +299 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/stylesheets/cms/application.css +3 -0
  6. data/app/controllers/cms/admin/api_keys_controller.rb +52 -0
  7. data/app/controllers/cms/admin/base_controller.rb +54 -0
  8. data/app/controllers/cms/admin/documents_controller.rb +56 -0
  9. data/app/controllers/cms/admin/form_fields_controller.rb +70 -0
  10. data/app/controllers/cms/admin/form_submissions_controller.rb +36 -0
  11. data/app/controllers/cms/admin/images_controller.rb +67 -0
  12. data/app/controllers/cms/admin/pages_controller.rb +188 -0
  13. data/app/controllers/cms/admin/sections_controller.rb +177 -0
  14. data/app/controllers/cms/admin/sites_controller.rb +60 -0
  15. data/app/controllers/cms/admin/webhook_deliveries_controller.rb +19 -0
  16. data/app/controllers/cms/admin/webhooks_controller.rb +51 -0
  17. data/app/controllers/cms/api/base_controller.rb +45 -0
  18. data/app/controllers/cms/api/v1/base_controller.rb +29 -0
  19. data/app/controllers/cms/api/v1/pages_controller.rb +21 -0
  20. data/app/controllers/cms/api/v1/sites_controller.rb +18 -0
  21. data/app/controllers/cms/application_controller.rb +11 -0
  22. data/app/controllers/cms/public/base_controller.rb +23 -0
  23. data/app/controllers/cms/public/form_submissions_controller.rb +63 -0
  24. data/app/controllers/cms/public/previews_controller.rb +21 -0
  25. data/app/controllers/cms/public/sites_controller.rb +37 -0
  26. data/app/controllers/concerns/cms/admin/page_scoped_sections.rb +42 -0
  27. data/app/controllers/concerns/cms/current_site_resolver.rb +17 -0
  28. data/app/controllers/concerns/cms/public/page_paths.rb +31 -0
  29. data/app/controllers/concerns/cms/public/page_rendering.rb +23 -0
  30. data/app/controllers/concerns/cms/site_resolvable.rb +27 -0
  31. data/app/helpers/cms/admin/pages_helper.rb +29 -0
  32. data/app/helpers/cms/admin/sections_helper.rb +86 -0
  33. data/app/helpers/cms/admin/sites_helper.rb +8 -0
  34. data/app/helpers/cms/application_helper.rb +51 -0
  35. data/app/helpers/cms/media_helper.rb +28 -0
  36. data/app/helpers/cms/pages_helper.rb +16 -0
  37. data/app/helpers/cms/sections_helper.rb +25 -0
  38. data/app/helpers/cms/sites_helper.rb +11 -0
  39. data/app/javascript/cms/controllers/sortable_controller.js +38 -0
  40. data/app/jobs/cms/application_job.rb +6 -0
  41. data/app/jobs/cms/deliver_webhook_job.rb +66 -0
  42. data/app/mailers/cms/application_mailer.rb +9 -0
  43. data/app/mailers/cms/form_submission_mailer.rb +16 -0
  44. data/app/models/cms/api_key.rb +27 -0
  45. data/app/models/cms/application_record.rb +7 -0
  46. data/app/models/cms/document.rb +48 -0
  47. data/app/models/cms/form_field.rb +27 -0
  48. data/app/models/cms/form_submission.rb +42 -0
  49. data/app/models/cms/image.rb +61 -0
  50. data/app/models/cms/image_translation.rb +22 -0
  51. data/app/models/cms/page.rb +228 -0
  52. data/app/models/cms/page_section.rb +43 -0
  53. data/app/models/cms/page_translation.rb +22 -0
  54. data/app/models/cms/section/block_base.rb +32 -0
  55. data/app/models/cms/section/blocks/call_to_action_block.rb +16 -0
  56. data/app/models/cms/section/blocks/hero_block.rb +14 -0
  57. data/app/models/cms/section/blocks/image_block.rb +13 -0
  58. data/app/models/cms/section/blocks/rich_text_block.rb +12 -0
  59. data/app/models/cms/section/kind_registry.rb +66 -0
  60. data/app/models/cms/section.rb +94 -0
  61. data/app/models/cms/section_image.rb +10 -0
  62. data/app/models/cms/section_translation.rb +25 -0
  63. data/app/models/cms/site.rb +87 -0
  64. data/app/models/cms/webhook.rb +41 -0
  65. data/app/models/cms/webhook_delivery.rb +12 -0
  66. data/app/serializers/cms/api/base_serializer.rb +51 -0
  67. data/app/serializers/cms/api/page_serializer.rb +145 -0
  68. data/app/serializers/cms/api/site_serializer.rb +45 -0
  69. data/app/services/cms/locale_resolver.rb +30 -0
  70. data/app/services/cms/page_resolver.rb +73 -0
  71. data/app/services/cms/public_page_context.rb +49 -0
  72. data/app/views/cms/admin/api_keys/_form.html.erb +23 -0
  73. data/app/views/cms/admin/api_keys/create.html.erb +9 -0
  74. data/app/views/cms/admin/api_keys/edit.html.erb +5 -0
  75. data/app/views/cms/admin/api_keys/index.html.erb +36 -0
  76. data/app/views/cms/admin/api_keys/new.html.erb +5 -0
  77. data/app/views/cms/admin/documents/_form.html.erb +24 -0
  78. data/app/views/cms/admin/documents/edit.html.erb +2 -0
  79. data/app/views/cms/admin/documents/index.html.erb +37 -0
  80. data/app/views/cms/admin/documents/new.html.erb +2 -0
  81. data/app/views/cms/admin/form_fields/_form.html.erb +46 -0
  82. data/app/views/cms/admin/form_fields/edit.html.erb +2 -0
  83. data/app/views/cms/admin/form_fields/index.html.erb +41 -0
  84. data/app/views/cms/admin/form_fields/new.html.erb +2 -0
  85. data/app/views/cms/admin/form_submissions/index.html.erb +38 -0
  86. data/app/views/cms/admin/images/_form.html.erb +36 -0
  87. data/app/views/cms/admin/images/edit.html.erb +2 -0
  88. data/app/views/cms/admin/images/index.html.erb +25 -0
  89. data/app/views/cms/admin/images/new.html.erb +2 -0
  90. data/app/views/cms/admin/pages/_attach_section_panel.html.erb +20 -0
  91. data/app/views/cms/admin/pages/_form.html.erb +116 -0
  92. data/app/views/cms/admin/pages/_section_editor_frame.html.erb +3 -0
  93. data/app/views/cms/admin/pages/_sections_list.html.erb +9 -0
  94. data/app/views/cms/admin/pages/edit.html.erb +2 -0
  95. data/app/views/cms/admin/pages/index.html.erb +62 -0
  96. data/app/views/cms/admin/pages/new.html.erb +2 -0
  97. data/app/views/cms/admin/pages/show.html.erb +111 -0
  98. data/app/views/cms/admin/sections/_form.html.erb +128 -0
  99. data/app/views/cms/admin/sections/_section.html.erb +22 -0
  100. data/app/views/cms/admin/sections/edit.html.erb +9 -0
  101. data/app/views/cms/admin/sections/index.html.erb +47 -0
  102. data/app/views/cms/admin/sections/new.html.erb +9 -0
  103. data/app/views/cms/admin/sections/page_update.turbo_stream.erb +17 -0
  104. data/app/views/cms/admin/sections/show.html.erb +97 -0
  105. data/app/views/cms/admin/sites/_form.html.erb +44 -0
  106. data/app/views/cms/admin/sites/edit.html.erb +3 -0
  107. data/app/views/cms/admin/sites/new.html.erb +5 -0
  108. data/app/views/cms/admin/sites/show.html.erb +22 -0
  109. data/app/views/cms/admin/webhook_deliveries/index.html.erb +29 -0
  110. data/app/views/cms/admin/webhooks/_form.html.erb +38 -0
  111. data/app/views/cms/admin/webhooks/edit.html.erb +5 -0
  112. data/app/views/cms/admin/webhooks/index.html.erb +34 -0
  113. data/app/views/cms/admin/webhooks/new.html.erb +5 -0
  114. data/app/views/cms/form_submission_mailer/notify.html.erb +14 -0
  115. data/app/views/cms/form_submission_mailer/notify.text.erb +7 -0
  116. data/app/views/cms/public/pages/_content.html.erb +48 -0
  117. data/app/views/cms/public/pages/show.html.erb +44 -0
  118. data/app/views/cms/public/pages/templates/_custom.html.erb +3 -0
  119. data/app/views/cms/public/pages/templates/_form.html.erb +3 -0
  120. data/app/views/cms/public/pages/templates/_landing.html.erb +3 -0
  121. data/app/views/cms/public/pages/templates/_standard.html.erb +3 -0
  122. data/app/views/cms/sections/kinds/_cta.html.erb +13 -0
  123. data/app/views/cms/sections/kinds/_hero.html.erb +14 -0
  124. data/app/views/cms/sections/kinds/_image.html.erb +19 -0
  125. data/app/views/cms/sections/kinds/_rich_text.html.erb +6 -0
  126. data/app/views/layouts/cms/application.html.erb +13 -0
  127. data/app/views/layouts/cms/public.html.erb +14 -0
  128. data/bin/rails +19 -0
  129. data/bin/rubocop +9 -0
  130. data/cms.gemspec +29 -0
  131. data/config/importmap.rb +4 -0
  132. data/config/locales/activerecord.cms.en.yml +65 -0
  133. data/config/locales/en.yml +390 -0
  134. data/config/routes.rb +56 -0
  135. data/lib/cms/engine.rb +45 -0
  136. data/lib/cms/version.rb +5 -0
  137. data/lib/cms.rb +75 -0
  138. data/lib/cms42.rb +3 -0
  139. data/lib/generators/cms/install/install_generator.rb +26 -0
  140. data/lib/generators/cms/install/templates/create_cms_tables.rb +194 -0
  141. data/lib/generators/cms/install/templates/initializer.rb +21 -0
  142. data/lib/generators/cms/views/views_generator.rb +79 -0
  143. data/lib/tasks/cms_tasks.rake +6 -0
  144. data/lib/tasks/version.rake +8 -0
  145. metadata +281 -0
@@ -0,0 +1,14 @@
1
+ <h2><%= t(".title", page: @page.display_title) %></h2>
2
+
3
+ <table>
4
+ <% @fields.each do |field| %>
5
+ <tr>
6
+ <td><strong><%= field.label %></strong></td>
7
+ <td><%= @submission.data[field.field_name] %></td>
8
+ </tr>
9
+ <% end %>
10
+ </table>
11
+
12
+ <p style="color: #999; font-size: 12px;">
13
+ <%= t(".submitted_at") %> <%= cms_datetime(@submission.created_at) %>
14
+ </p>
@@ -0,0 +1,7 @@
1
+ <%= t(".title", page: @page.display_title) %>
2
+
3
+ <% @fields.each do |field| -%>
4
+ <%= field.label %>: <%= @submission.data[field.field_name] %>
5
+ <% end -%>
6
+
7
+ <%= t(".submitted_at") %> <%= cms_datetime(@submission.created_at) %>
@@ -0,0 +1,48 @@
1
+ <h1><%= @page.display_title %></h1>
2
+
3
+ <% if @page.hero_image.attached? %>
4
+ <p><%= image_tag cms_attachment_path(@page.hero_image), alt: @page.display_title, style: "max-width: 900px; height: auto;" %></p>
5
+ <% end %>
6
+
7
+ <% if @submission&.errors&.any? %>
8
+ <section>
9
+ <h2><%= t("cms.public.forms.submission_problem") %></h2>
10
+ <ul>
11
+ <% @submission.errors.full_messages.each do |message| %>
12
+ <li><%= message %></li>
13
+ <% end %>
14
+ </ul>
15
+ </section>
16
+ <% end %>
17
+
18
+ <% @sections.each do |section| %>
19
+ <%= render_section(section) %>
20
+ <% end %>
21
+
22
+ <% if @form_fields.any? %>
23
+ <section>
24
+ <h2><%= t("cms.public.forms.contact") %></h2>
25
+ <%= form_with url: page_form_submissions_path(@page, site_slug: @site.slug), scope: :submission, local: true do |form| %>
26
+ <% @form_fields.each do |field| %>
27
+ <p>
28
+ <label for="<%= field.field_name %>"><%= field.label %></label><br>
29
+ <% value = @submission.data.to_h[field.field_name] %>
30
+ <% case field.kind %>
31
+ <% when "textarea" %>
32
+ <%= form.text_area field.field_name, value: value, placeholder: field.placeholder %>
33
+ <% when "select" %>
34
+ <%= form.select field.field_name, options_for_select(field.parsed_options, value), include_blank: true %>
35
+ <% when "checkbox" %>
36
+ <%= form.check_box field.field_name, { checked: value.to_s == "1" }, "1", "0" %>
37
+ <% else %>
38
+ <%= form.text_field field.field_name, value: value, type: field.kind, placeholder: field.placeholder %>
39
+ <% end %>
40
+ </p>
41
+ <% end %>
42
+
43
+ <p>
44
+ <%= form.submit t("cms.public.forms.send") %>
45
+ </p>
46
+ <% end %>
47
+ </section>
48
+ <% end %>
@@ -0,0 +1,44 @@
1
+ <% content_for :page_title, @page.display_meta_title %>
2
+ <% content_for :page_description, @page.seo_description.to_s %>
3
+ <% content_for :head do %>
4
+ <%= site_favicon_tag(@site) %>
5
+ <% end %>
6
+
7
+ <header class="cms-site-header">
8
+ <% if @site.logo.attached? %>
9
+ <p>
10
+ <%= image_tag cms_attachment_path(@site.logo), alt: @site.name, style: "max-width: 220px; height: auto;" %>
11
+ </p>
12
+ <% end %>
13
+
14
+ <nav>
15
+ <ul>
16
+ <% @header_nav_items.each do |item| %>
17
+ <% page = item[:page] %>
18
+ <% nav_children = item[:children] %>
19
+ <li>
20
+ <%= link_to page.display_title, public_site_page_path_for(@site, page) %>
21
+ <% if nav_children.any? %>
22
+ <ul>
23
+ <% nav_children.each do |child| %>
24
+ <li>
25
+ <%= link_to child.display_title, public_site_page_path_for(@site, child) %>
26
+ </li>
27
+ <% end %>
28
+ </ul>
29
+ <% end %>
30
+ </li>
31
+ <% end %>
32
+ </ul>
33
+ </nav>
34
+ </header>
35
+
36
+ <main class="cms-page-shell">
37
+ <%= render_page_template(@page) %>
38
+ </main>
39
+
40
+ <footer class="cms-site-footer">
41
+ <% @footer_pages.each do |page| %>
42
+ <%= link_to page.display_title, public_site_page_path_for(@site, page) %>
43
+ <% end %>
44
+ </footer>
@@ -0,0 +1,3 @@
1
+ <section class="cms-page-template cms-page-template--custom">
2
+ <%= render "cms/public/pages/content" %>
3
+ </section>
@@ -0,0 +1,3 @@
1
+ <section class="cms-page-template cms-page-template--form">
2
+ <%= render "cms/public/pages/content" %>
3
+ </section>
@@ -0,0 +1,3 @@
1
+ <section class="cms-page-template cms-page-template--landing">
2
+ <%= render "cms/public/pages/content" %>
3
+ </section>
@@ -0,0 +1,3 @@
1
+ <section class="cms-page-template cms-page-template--standard">
2
+ <%= render "cms/public/pages/content" %>
3
+ </section>
@@ -0,0 +1,13 @@
1
+ <section class="cms-section cms-section--cta cms-section--cta-<%= section.alignment.presence || "center" %>">
2
+ <% if section.title.present? %>
3
+ <h2><%= section.title %></h2>
4
+ <% end %>
5
+ <% if section.content.present? %>
6
+ <%= section.content %>
7
+ <% end %>
8
+ <% if section.button_text.present? && section.button_url.present? %>
9
+ <a href="<%= section.button_url %>" class="cms-cta__button">
10
+ <%= section.button_text %>
11
+ </a>
12
+ <% end %>
13
+ </section>
@@ -0,0 +1,14 @@
1
+ <section class="cms-section cms-section--hero"
2
+ style="<%= "background-color: #{section.background_color};" if section.background_color.present? %>">
3
+ <% if section.title.present? %>
4
+ <h1><%= section.title %></h1>
5
+ <% end %>
6
+ <% if section.content.present? %>
7
+ <%= section.content %>
8
+ <% end %>
9
+ <% if section.cta_text.present? && section.cta_url.present? %>
10
+ <a href="<%= section.cta_url %>" class="cms-hero__cta">
11
+ <%= section.cta_text %>
12
+ </a>
13
+ <% end %>
14
+ </section>
@@ -0,0 +1,19 @@
1
+ <section class="cms-section cms-section--image">
2
+ <% if section.title.present? %>
3
+ <h2><%= section.title %></h2>
4
+ <% end %>
5
+ <% images = section.image_assets %>
6
+ <% if images.present? %>
7
+ <div class="cms-section__image-grid">
8
+ <% images.each do |image| %>
9
+ <figure>
10
+ <%= cms_image_tag image, alt: image.alt_text.presence || section.title.to_s, loading: "lazy" %>
11
+ <% caption = image.caption.presence %>
12
+ <% if caption.present? %>
13
+ <figcaption><%= caption %></figcaption>
14
+ <% end %>
15
+ </figure>
16
+ <% end %>
17
+ </div>
18
+ <% end %>
19
+ </section>
@@ -0,0 +1,6 @@
1
+ <section class="cms-section cms-section--rich-text">
2
+ <% if section.title.present? %>
3
+ <h2><%= section.title %></h2>
4
+ <% end %>
5
+ <%= section.content %>
6
+ </section>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>CMS</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+ <%= stylesheet_link_tag "cms/application", media: "all" %>
8
+ <%= javascript_importmap_tags if respond_to?(:javascript_importmap_tags) %>
9
+ </head>
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title><%= content_for?(:page_title) ? yield(:page_title) : "CMS" %></title>
6
+ <meta name="description" content="<%= content_for?(:page_description) ? yield(:page_description) : "" %>">
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= yield :head %>
10
+ </head>
11
+ <body>
12
+ <%= yield %>
13
+ </body>
14
+ </html>
data/bin/rails ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ ENGINE_ROOT = File.expand_path("..", __dir__)
5
+ ENGINE_PATH = File.expand_path("../lib/cms/engine", __dir__)
6
+ APP_PATH = File.expand_path("../spec/cms_app/config/application", __dir__)
7
+
8
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
9
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
10
+
11
+ require "rails"
12
+ require "active_model/railtie"
13
+ require "active_job/railtie"
14
+ require "active_record/railtie"
15
+ require "active_storage/engine"
16
+ require "action_controller/railtie"
17
+ require "action_mailer/railtie"
18
+ require "action_view/railtie"
19
+ require "rails/engine/commands"
data/bin/rubocop ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+ require "bundler/setup"
6
+
7
+ ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
8
+
9
+ load Gem.bin_path("rubocop", "rubocop")
data/cms.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/cms/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "cms42"
7
+ spec.version = Cms::VERSION
8
+ spec.authors = ["shift42"]
9
+ spec.email = ["hello@shift42.io"]
10
+ spec.summary = "Modular, mountable CMS engine for Rails apps"
11
+ spec.description = "A mountable Rails CMS engine. Multi-site, multilingual, content blocks, " \
12
+ "page tree, media management, forms, headless JSON API, and webhooks."
13
+ spec.homepage = "https://github.com/shift42/cms"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2"
16
+ spec.require_paths = ["lib"]
17
+
18
+ spec.files = Dir.chdir(__dir__) do
19
+ Dir["{app,bin,config,db,lib}/**/*", "MIT-LICENSE", "README.md", "Rakefile", "cms.gemspec"]
20
+ end
21
+
22
+ spec.add_dependency "actiontext", ">= 7.1", "< 9.0"
23
+ spec.add_dependency "discard", "~> 1.4"
24
+ spec.add_dependency "importmap-rails", ">= 1.2"
25
+ spec.add_dependency "rails", ">= 7.1", "< 9.0"
26
+ spec.add_dependency "stimulus-rails", ">= 1.3"
27
+ spec.add_dependency "turbo-rails", ">= 1.5"
28
+ spec.metadata["rubygems_mfa_required"] = "true"
29
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ pin "sortablejs", to: "https://cdn.jsdelivr.net/npm/sortablejs@1.15.3/Sortable.min.js"
4
+ pin "cms/controllers/sortable_controller", to: "cms/controllers/sortable_controller.js"
@@ -0,0 +1,65 @@
1
+ ---
2
+ en:
3
+ activerecord:
4
+ attributes:
5
+ cms/api_key:
6
+ active: Active
7
+ name: Name
8
+ cms/document:
9
+ description: Description
10
+ file: File
11
+ title: Title
12
+ cms/form_field:
13
+ field_name: Field name
14
+ hint: Hint
15
+ kind: Kind
16
+ label: Label
17
+ options: Options
18
+ placeholder: Placeholder
19
+ position: Position
20
+ required: Required
21
+ cms/image:
22
+ file: Image file
23
+ title: Title
24
+ cms/image_translation:
25
+ alt_text: Alt text
26
+ caption: Caption
27
+ locale: Locale
28
+ cms/page:
29
+ footer_order: Footer order
30
+ hero_image: Hero image
31
+ home: Home page
32
+ media_files: Gallery/attachments
33
+ nav_group: Nav group
34
+ nav_order: Nav order
35
+ parent_id: Parent page
36
+ position: Position
37
+ show_in_footer: Show in footer
38
+ show_in_header: Show in header
39
+ slug: Slug
40
+ status: Status
41
+ template_key: Template
42
+ cms/page_translation:
43
+ locale: Locale
44
+ seo_description: SEO description
45
+ seo_title: SEO title
46
+ title: Title
47
+ cms/section:
48
+ enabled: Enabled
49
+ global: Global section
50
+ kind: Kind
51
+ cms/section_translation:
52
+ content: Content
53
+ locale: Locale
54
+ title: Title
55
+ cms/site:
56
+ default_locale: Default locale
57
+ logo: Logo
58
+ name: Name
59
+ published: Published
60
+ slug: Slug
61
+ cms/webhook:
62
+ active: Active
63
+ events: Events
64
+ secret: Secret
65
+ url: Endpoint URL