panda-cms 0.8.2 → 0.10.2

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +75 -5
  3. data/app/components/panda/cms/code_component.rb +154 -39
  4. data/app/components/panda/cms/grid_component.rb +26 -6
  5. data/app/components/panda/cms/menu_component.rb +72 -34
  6. data/app/components/panda/cms/page_menu_component.rb +102 -13
  7. data/app/components/panda/cms/rich_text_component.rb +229 -139
  8. data/app/components/panda/cms/text_component.rb +107 -42
  9. data/app/controllers/panda/cms/admin/base_controller.rb +19 -3
  10. data/app/controllers/panda/cms/admin/dashboard_controller.rb +3 -3
  11. data/app/controllers/panda/cms/admin/files_controller.rb +7 -0
  12. data/app/controllers/panda/cms/admin/menus_controller.rb +47 -3
  13. data/app/controllers/panda/cms/admin/pages_controller.rb +11 -2
  14. data/app/controllers/panda/cms/admin/posts_controller.rb +3 -1
  15. data/app/controllers/panda/cms/form_submissions_controller.rb +134 -11
  16. data/app/controllers/panda/cms/pages_controller.rb +7 -2
  17. data/app/controllers/panda/cms/posts_controller.rb +16 -0
  18. data/app/helpers/panda/cms/application_helper.rb +17 -4
  19. data/app/helpers/panda/cms/asset_helper.rb +14 -61
  20. data/app/helpers/panda/cms/forms_helper.rb +60 -0
  21. data/app/helpers/panda/cms/seo_helper.rb +85 -0
  22. data/app/javascript/panda/cms/{application_panda_cms.js → application.js} +5 -1
  23. data/app/javascript/panda/cms/controllers/code_editor_controller.js +95 -0
  24. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +31 -4
  25. data/app/javascript/panda/cms/controllers/file_gallery_controller.js +128 -0
  26. data/app/javascript/panda/cms/controllers/file_upload_controller.js +165 -0
  27. data/app/javascript/panda/cms/controllers/index.js +54 -13
  28. data/app/javascript/panda/cms/controllers/inline_code_editor_controller.js +96 -0
  29. data/app/javascript/panda/cms/controllers/menu_form_controller.js +53 -0
  30. data/app/javascript/panda/cms/controllers/nested_form_controller.js +35 -0
  31. data/app/javascript/panda/cms/controllers/page_form_controller.js +454 -0
  32. data/app/javascript/panda/cms/controllers/tree_controller.js +214 -0
  33. data/app/javascript/panda/cms/stimulus-loading.js +6 -7
  34. data/app/models/panda/cms/block_content.rb +9 -0
  35. data/app/models/panda/cms/menu.rb +12 -0
  36. data/app/models/panda/cms/page.rb +147 -0
  37. data/app/models/panda/cms/post.rb +98 -0
  38. data/app/views/layouts/homepage.html.erb +1 -4
  39. data/app/views/layouts/page.html.erb +1 -4
  40. data/app/views/panda/cms/admin/dashboard/show.html.erb +5 -5
  41. data/app/views/panda/cms/admin/files/_file_details.html.erb +45 -0
  42. data/app/views/panda/cms/admin/files/index.html.erb +11 -118
  43. data/app/views/panda/cms/admin/forms/index.html.erb +2 -2
  44. data/app/views/panda/cms/admin/forms/new.html.erb +1 -2
  45. data/app/views/panda/cms/admin/forms/show.html.erb +15 -30
  46. data/app/views/panda/cms/admin/menus/_menu_item_fields.html.erb +11 -0
  47. data/app/views/panda/cms/admin/menus/edit.html.erb +62 -0
  48. data/app/views/panda/cms/admin/menus/index.html.erb +3 -2
  49. data/app/views/panda/cms/admin/menus/new.html.erb +38 -0
  50. data/app/views/panda/cms/admin/pages/edit.html.erb +147 -22
  51. data/app/views/panda/cms/admin/pages/index.html.erb +49 -11
  52. data/app/views/panda/cms/admin/pages/new.html.erb +3 -11
  53. data/app/views/panda/cms/admin/posts/_form.html.erb +44 -15
  54. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  55. data/app/views/panda/cms/admin/posts/index.html.erb +6 -6
  56. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  57. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  58. data/app/views/panda/cms/admin/settings/index.html.erb +3 -3
  59. data/app/views/shared/_header.html.erb +1 -4
  60. data/config/brakeman.ignore +38 -0
  61. data/config/importmap.rb +10 -10
  62. data/config/initializers/panda/cms/healthcheck_log_silencer.rb.disabled +31 -0
  63. data/config/initializers/panda/cms.rb +52 -10
  64. data/config/locales/en.yml +41 -0
  65. data/config/routes.rb +5 -3
  66. data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +2 -2
  67. data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +6 -1
  68. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +23 -21
  69. data/db/migrate/20251104150640_add_cached_last_updated_at_to_panda_cms_pages.rb +22 -0
  70. data/db/migrate/20251104172242_add_page_type_to_panda_cms_pages.rb +6 -0
  71. data/db/migrate/20251104172638_set_page_types_for_existing_pages.rb +27 -0
  72. data/db/migrate/20251105000001_add_pending_review_status_to_pages_and_posts.panda_cms.rb +21 -0
  73. data/db/migrate/20251109131150_add_seo_fields_to_pages.rb +32 -0
  74. data/db/migrate/20251109131205_add_seo_fields_to_posts.rb +27 -0
  75. data/db/migrate/20251110114258_add_spam_tracking_to_form_submissions.rb +7 -0
  76. data/db/migrate/20251110122812_add_performance_indexes_to_pages_and_redirects.rb +13 -0
  77. data/lib/generators/panda/cms/install_generator.rb +2 -5
  78. data/lib/panda/cms/asset_loader.rb +46 -76
  79. data/lib/panda/cms/bulk_editor.rb +288 -12
  80. data/lib/panda/cms/debug.rb +29 -0
  81. data/lib/panda/cms/engine/asset_config.rb +49 -0
  82. data/lib/panda/cms/engine/autoload_config.rb +19 -0
  83. data/lib/panda/cms/engine/backtrace_config.rb +42 -0
  84. data/lib/panda/cms/engine/core_config.rb +106 -0
  85. data/lib/panda/cms/engine/helper_config.rb +20 -0
  86. data/lib/panda/cms/engine/route_config.rb +34 -0
  87. data/lib/panda/cms/engine/view_component_config.rb +31 -0
  88. data/lib/panda/cms/engine.rb +44 -162
  89. data/lib/panda/cms/features.rb +52 -0
  90. data/lib/panda/cms.rb +10 -0
  91. data/lib/panda-cms/version.rb +1 -1
  92. data/lib/panda-cms.rb +20 -7
  93. data/lib/tasks/panda_cms_tasks.rake +16 -0
  94. metadata +41 -50
  95. data/app/components/panda/cms/admin/container_component.html.erb +0 -13
  96. data/app/components/panda/cms/admin/flash_message_component.html.erb +0 -31
  97. data/app/components/panda/cms/admin/panel_component.html.erb +0 -7
  98. data/app/components/panda/cms/admin/slideover_component.html.erb +0 -9
  99. data/app/components/panda/cms/admin/slideover_component.rb +0 -15
  100. data/app/components/panda/cms/admin/statistics_component.html.erb +0 -4
  101. data/app/components/panda/cms/admin/statistics_component.rb +0 -16
  102. data/app/components/panda/cms/admin/tab_bar_component.html.erb +0 -35
  103. data/app/components/panda/cms/admin/tab_bar_component.rb +0 -15
  104. data/app/components/panda/cms/admin/table_component.html.erb +0 -29
  105. data/app/components/panda/cms/admin/user_activity_component.html.erb +0 -7
  106. data/app/components/panda/cms/admin/user_activity_component.rb +0 -20
  107. data/app/components/panda/cms/admin/user_display_component.html.erb +0 -17
  108. data/app/components/panda/cms/admin/user_display_component.rb +0 -21
  109. data/app/components/panda/cms/grid_component.html.erb +0 -6
  110. data/app/components/panda/cms/menu_component.html.erb +0 -6
  111. data/app/components/panda/cms/page_menu_component.html.erb +0 -21
  112. data/app/components/panda/cms/rich_text_component.html.erb +0 -90
  113. data/app/javascript/panda_cms/stimulus-loading.js +0 -39
  114. data/app/views/layouts/panda/cms/application.html.erb +0 -42
  115. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +0 -28
  116. data/app/views/panda/cms/admin/shared/_flash.html.erb +0 -5
  117. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +0 -41
  118. data/app/views/panda/cms/shared/_footer.html.erb +0 -2
  119. data/app/views/panda/cms/shared/_header.html.erb +0 -25
  120. data/app/views/panda/cms/shared/_importmap.html.erb +0 -34
  121. data/config/initializers/inflections.rb +0 -5
  122. data/config/initializers/panda/cms/healthcheck_log_silencer.rb +0 -13
  123. data/lib/tasks/assets.rake +0 -587
@@ -1,31 +0,0 @@
1
- <div class="fixed top-2 right-2 z-[9999] p-2 space-y-4 w-full max-w-sm sm:items-end"
2
- data-controller="alert"
3
- <% if @temporary %> data-alert-dismiss-after-value="3000"<% end %>
4
- data-transition-enter="ease-in-out duration-500"
5
- data-transition-enter-from="translate-x-full opacity-0"
6
- data-transition-enter-to="translate-x-0 opacity-100"
7
- data-transition-leave="ease-in-out duration-500"
8
- data-transition-leave-from="translate-x-0 opacity-100"
9
- data-transition-leave-to="translate-x-full opacity-0">
10
- <div class="overflow-hidden w-full max-w-sm bg-white rounded-lg ring-1 ring-black ring-opacity-5 shadow-lg">
11
- <div class="p-4">
12
- <div class="flex items-start">
13
- <div class="flex-shrink-0">
14
- <i class="fa-regular text-xl <%= icon_css %> <%= text_colour_css %>"></i>
15
- </div>
16
- <div class="flex-1 pt-0.5 ml-3 w-0">
17
- <p class="mb-1 text-sm font-medium flash-message-title <%= text_colour_css %>"><%= kind.to_s.titleize %></p>
18
- <p class="mt-1 mb-0 text-sm text-gray-500 flash-message-text"><%= message %></p>
19
- </div>
20
- <div class="flex flex-shrink-0 ml-4">
21
- <button data-action="alert#close" type="button" class="inline-flex text-gray-400 bg-white rounded-md transition duration-150 ease-in-out hover:text-gray-500 focus:ring-2 focus:ring-offset-2 focus:outline-none focus:ring-sky-500">
22
- <span class="sr-only">Close</span>
23
- <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
24
- <path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
25
- </svg>
26
- </button>
27
- </div>
28
- </div>
29
- </div>
30
- </div>
31
- </div>
@@ -1,7 +0,0 @@
1
- <div class="col-span-3 mt-5 rounded-lg shadow-md bg-mid shadow-inherit/20">
2
- <%= heading %>
3
-
4
- <div class="p-4 text-black bg-white rounded-b-lg">
5
- <%= content %>
6
- </div>
7
- </div>
@@ -1,9 +0,0 @@
1
- <% content_for :sidebar do %>
2
- <aside class="hidden overflow-y-auto w-96 h-full bg-white lg:block">
3
- <%= content %>
4
- </aside>
5
- <% end %>
6
-
7
- <% content_for :sidebar_title do %>
8
- <%= title %>
9
- <% end %>
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class SlideoverComponent < ViewComponent::Base
7
- attr_reader :title
8
-
9
- def initialize(title: "Settings")
10
- @title = title
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,4 +0,0 @@
1
- <div class="overflow-hidden p-4 bg-gradient-to-br rounded-lg border-2 from-light/20 to-light border-mid">
2
- <dt class="text-base font-medium truncate text-dark"><%= metric %></dt>
3
- <dd class="mt-1 text-3xl font-medium tracking-tight text-dark"><%= value %></dd>
4
- </div>
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class StatisticsComponent < ViewComponent::Base
7
- attr_reader :metric, :value
8
-
9
- def initialize(metric:, value:)
10
- @metric = metric
11
- @value = value
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,35 +0,0 @@
1
- <div class="mt-3 sm:mt-2">
2
- <div class="sm:hidden">
3
- <label for="tabs" class="sr-only">Select a tab</label>
4
- <!-- Use an "onChange" listener to redirect the user to the selected tab URL. -->
5
- <select id="tabs" name="tabs" class="block py-1.5 pr-10 pl-3 w-full text-gray-900 rounded-md border-0 ring-1 ring-inset focus:ring-2 focus:ring-inset ring-mid focus:border-panda-dark focus:ring-panda-dark">
6
- <% tabs.each do |tab| %>
7
- <option><%= tab %></option>
8
- <% end %>
9
- </select>
10
- </div>
11
- <div class="hidden sm:block">
12
- <div class="flex items-center border-b border-gray-200">
13
- <nav class="flex flex-1 -mb-px space-x-6 xl:space-x-8" aria-label="Tabs">
14
- <!-- Current: "border-panda-dark text-panda-dark", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" -->
15
- <a href="#" aria-current="page" class="py-4 px-1 text-sm font-medium whitespace-nowrap border-b-2 border-panda-dark text-panda-dark">Recently Viewed</a>
16
- <a href="#" class="py-4 px-1 text-sm font-medium text-gray-500 whitespace-nowrap border-b-2 border-transparent hover:text-gray-700 hover:border-gray-300">Recently Added</a>
17
- <a href="#" class="py-4 px-1 text-sm font-medium text-gray-500 whitespace-nowrap border-b-2 border-transparent hover:text-gray-700 hover:border-gray-300">Favourited</a>
18
- </nav>
19
- <div class="hidden items-center p-0.5 ml-6 bg-gray-100 rounded-lg sm:flex">
20
- <button type="button" class="p-1.5 text-gray-400 rounded-md hover:bg-white hover:shadow-sm focus:ring-2 focus:ring-inset focus:outline-none focus:ring-panda-dark">
21
- <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
22
- <path fill-rule="evenodd" d="M2 3.75A.75.75 0 012.75 3h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 3.75zm0 4.167a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75zm0 4.166a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75zm0 4.167a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75z" clip-rule="evenodd" />
23
- </svg>
24
- <span class="sr-only">Use list view</span>
25
- </button>
26
- <button type="button" class="p-1.5 ml-0.5 text-gray-400 bg-white rounded-md shadow-sm focus:ring-2 focus:ring-inset focus:outline-none focus:ring-panda-dark">
27
- <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
28
- <path fill-rule="evenodd" d="M4.25 2A2.25 2.25 0 002 4.25v2.5A2.25 2.25 0 004.25 9h2.5A2.25 2.25 0 009 6.75v-2.5A2.25 2.25 0 006.75 2h-2.5zm0 9A2.25 2.25 0 002 13.25v2.5A2.25 2.25 0 004.25 18h2.5A2.25 2.25 0 009 15.75v-2.5A2.25 2.25 0 006.75 11h-2.5zm9-9A2.25 2.25 0 0011 4.25v2.5A2.25 2.25 0 0013.25 9h2.5A2.25 2.25 0 0018 6.75v-2.5A2.25 2.25 0 0015.75 2h-2.5zm0 9A2.25 2.25 0 0011 13.25v2.5A2.25 2.25 0 0013.25 18h2.5A2.25 2.25 0 0018 15.75v-2.5A2.25 2.25 0 0015.75 11h-2.5z" clip-rule="evenodd" />
29
- </svg>
30
- <span class="sr-only">Use grid view</span>
31
- </button>
32
- </div>
33
- </div>
34
- </div>
35
- </div>
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class TabBarComponent < ViewComponent::Base
7
- attr_reader :tabs
8
-
9
- def initialize(tabs: [])
10
- @tabs = tabs
11
- end
12
- end
13
- end
14
- end
15
- end
@@ -1,29 +0,0 @@
1
- <div class="table overflow-x-auto mb-12 w-full rounded-lg border border-mid">
2
- <div class="table-header-group">
3
- <div class="table-row text-base font-medium text-white bg-mid">
4
- <% columns.each_with_index do |column, i| %>
5
- <div class="table-cell sticky top-0 z-10 p-4 <% if i.zero? %>rounded-tl-md<% elsif i == columns.size - 1 %>rounded-tr-md<% end %>"><%= column.label %></div>
6
- <% end %>
7
- </div>
8
- </div>
9
-
10
- <% if @rows.any? %>
11
- <div class="table-row-group">
12
- <% @rows.each do |row| %>
13
- <div class="table-row relative bg-mid/5 hover:bg-mid/20" data-post-id="<%= row.id %>">
14
- <% @columns.each do |column| %>
15
- <div class="table-cell py-5 px-3 h-20 text-sm align-middle whitespace-nowrap border-b border-mid/20">
16
- <%= view_context.capture(row, &column.cell) %>
17
- </div>
18
- <% end %>
19
- </div>
20
- <% end %>
21
- </div>
22
- </div>
23
- <% else %>
24
- </div>
25
- <div class="text-center mx-12 block border border-dashed py-12 rounded-lg">
26
- <h3 class="py-1 text-xl font-semibold text-gray-900">No <%= @term.pluralize %></h3>
27
- <p class="py-1 text-base text-gray-500">Get started by creating a new <%= @term %>.</p>
28
- </div>
29
- <% end %>
@@ -1,7 +0,0 @@
1
- <% if user.is_a?(Panda::Core::User) && time %>
2
- <%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "#{time_ago_in_words(time)} ago") %>
3
- <% elsif user.is_a?(Panda::Core::User) %>
4
- <%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "Not published") %>
5
- <% elsif time %>
6
- <div class="text-black/60"><%= time_ago_in_words(time) %> ago</div>
7
- <% end %>
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class UserActivityComponent < ViewComponent::Base
7
- attr_accessor :model, :time, :user
8
-
9
- # @param model [ActiveRecord::Base] Model instance to which the user activity is related
10
- # @param at [ActiveSupport::TimeWithZone] Time of the activity
11
- # @param user [Panda::Core::User] User who performed the activity
12
- def initialize(model: nil, at: nil, user: nil)
13
- @model = model
14
- @user = user if user.is_a?(::Panda::Core::User)
15
- @time = at if at.is_a?(::ActiveSupport::TimeWithZone)
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,17 +0,0 @@
1
- <div class="block flex-shrink-0 group">
2
- <div class="flex items-center">
3
- <% unless user.image_url.empty? %>
4
- <div>
5
- <img class="inline-block w-10 h-10 rounded-full" src="<%= user.image_url %>" alt="">
6
- </div>
7
- <% else %>
8
- <div class="inline-block w-10 h-10 bg-gray-200 rounded-full">
9
- <span class="text-center"><i class="text-mid/50 text-4xl fa-sharp fa-circle-user fa-fw"></i></span>
10
- </div>
11
- <% end %>
12
- <div class="ml-3">
13
- <p class="text-sm text-black"><%= user.name %></p>
14
- <% if metadata %><p class="text-sm text-black/60"><%= metadata %></p><% end %>
15
- </div>
16
- </div>
17
- </div>
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module Admin
6
- class UserDisplayComponent < ViewComponent::Base
7
- attr_accessor :user_id, :user, :metadata
8
-
9
- def initialize(user_id: nil, user: nil, metadata: "")
10
- @user = if user.nil? && user_id.present? && Panda::Core::User.find(user_id)
11
- Panda::Core::User.find(user_id)
12
- else
13
- user
14
- end
15
-
16
- @metadata = metadata
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,6 +0,0 @@
1
- <div class="w-full grid <%= @columns %> min-h-20">
2
- <% @colspans.each do |colspan| %>
3
- <div class="border border-red-500 bg-red-50 <%= colspan %>" onDragOver="parent.onDragOver(event);" onDrop="parent.onDrop(event);">
4
- </div>
5
- <% end %>
6
- </div>
@@ -1,6 +0,0 @@
1
- <% @menu_items.each do |menu_item| %>
2
- <a href="<%= menu_item.resolved_link %>" class="<%= menu_item.css_classes %>"><%= menu_item.text %></a>
3
- <% if @render_page_menu && menu_item.page %>
4
- <%= render Panda::CMS::PageMenuComponent.new(page: menu_item.page, start_depth: 1, styles: @page_menu_styles, show_heading: false) %>
5
- <% end %>
6
- <% end %>
@@ -1,21 +0,0 @@
1
- <nav class="<%= styles[:container] %>">
2
- <ul role="list" class="p-0 m-0">
3
- <% if @show_heading %>
4
- <li>
5
- <a href="<%= menu_item.page.path %>" class="<%= menu_item.page == Panda::CMS::Current.page ? styles[:current_page_active] : styles[:current_page_inactive] %>">
6
- <%= menu_item.text %>
7
- </a>
8
- </li>
9
- <% end %>
10
- <ul>
11
- <% Panda::CMS::MenuItem.includes(:page).each_with_level(menu_item.descendants) do |submenu_item, level| %>
12
- <% next if Panda::CMS::Current.page == menu_item.page && level > 1 # If we're on the "top" menu item, only show its direct ancestors %>
13
- <% next if submenu_item.page&.path[/\:/] %>
14
- <% next if submenu_item&.page.nil? || Panda::CMS::Current.page.nil? || (submenu_item.page&.depth&.to_i > Panda::CMS::Current.page&.depth&.to_i && !Panda::CMS::Current.page&.in?(submenu_item.page.ancestors)) %>
15
- <li data-level="<%= level %>" data-page-id="<%= submenu_item.page.id %>" class="<%= submenu_item.page == Panda::CMS::Current.page ? @styles[:active] : @styles[:inactive] %>">
16
- <a href="<%= submenu_item.page&.path %>" class="<%= helpers.menu_indent(submenu_item, indent_with: @styles[:indent_with]) %>"><%= submenu_item.page&.title %></a>
17
- </li>
18
- <% end %>
19
- </ul>
20
- </ul>
21
- </nav>
@@ -1,90 +0,0 @@
1
- <%
2
- editor_data = if @editable
3
- begin
4
- content = if @content.is_a?(String)
5
- if @content.start_with?("{")
6
- JSON.parse(@content)
7
- else
8
- # If it's HTML content, convert it to EditorJS format
9
- {
10
- "time" => Time.current.to_i * 1000,
11
- "blocks" => [
12
- {
13
- "type" => "paragraph",
14
- "data" => {
15
- "text" => @content.to_s
16
- }
17
- }
18
- ],
19
- "version" => "2.28.2"
20
- }
21
- end
22
- else
23
- @content
24
- end
25
-
26
- content = content.deep_transform_keys(&:to_s)
27
- content["blocks"] = (content["blocks"] || []).map do |block|
28
- case block["type"]
29
- when "paragraph"
30
- block["data"] = block["data"].merge(
31
- "text" => block["data"]["text"].to_s.presence || ""
32
- )
33
- when "header"
34
- block["data"] = block["data"].merge(
35
- "text" => block["data"]["text"].to_s.presence || "",
36
- "level" => block["data"]["level"].to_i
37
- )
38
- when "list"
39
- block["data"] = block["data"].merge(
40
- "items" => (block["data"]["items"] || []).map { |item| item.to_s.presence || "" }
41
- )
42
- end
43
- block
44
- end
45
-
46
- content["version"] ||= "2.28.2"
47
- content["time"] ||= Time.current.to_i * 1000
48
-
49
- Base64.strict_encode64(content.to_json)
50
- rescue StandardError => e
51
- Rails.logger.error("Error encoding editor data: #{e.message}")
52
- Rails.logger.error("Original content: #{@content.inspect}")
53
- # Fall back to a simple paragraph with the original content
54
- fallback_content = {
55
- "time" => Time.current.to_i * 1000,
56
- "blocks" => [
57
- {
58
- "type" => "paragraph",
59
- "data" => {
60
- "text" => @content.to_s
61
- }
62
- }
63
- ],
64
- "version" => "2.28.2"
65
- }
66
- Base64.strict_encode64(fallback_content.to_json)
67
- end
68
- end
69
- %>
70
- <% if @editable %>
71
- <div class="panda-cms-content"
72
- data-editable-previous-data="<%= editor_data %>"
73
- data-editable-content="<%= editor_data %>"
74
- data-editable-initialized="false"
75
- data-editable-version="2.28.2"
76
- data-editable-autosave="false"
77
- data-editable-tools='{"paragraph":true,"header":true,"list":true,"quote":true,"table":true}'
78
- id="editor-<%= @options[:id] %>"
79
- data-editable-kind="rich_text"
80
- data-editable-block-content-id="<%= @options[:id] %>"
81
- data-editable-page-id="<%= @options[:data][:page_id] %>"
82
- data-controller="editor-js"
83
- data-editor-js-initialized-value="false"
84
- data-editor-js-content-value="<%= editor_data %>">
85
- </div>
86
- <% else %>
87
- <div class="panda-cms-content">
88
- <%= @content.presence || "<p></p>".html_safe %>
89
- </div>
90
- <% end %>
@@ -1,39 +0,0 @@
1
- // Stimulus loading utilities for Panda CMS
2
- // This provides the loading functionality that would normally come from stimulus-rails
3
-
4
- import { Application } from "@hotwired/stimulus"
5
-
6
- const application = Application.start()
7
-
8
- // Configure debug mode based on environment
9
- const railsEnv = document.body?.dataset?.environment || "production";
10
- application.debug = railsEnv === "development"
11
- window.Stimulus = application
12
-
13
- // Auto-registration functionality
14
- function eagerLoadControllersFrom(context) {
15
- const definitions = []
16
- for (const path of context.keys()) {
17
- const module = context(path)
18
- const controller = module.default
19
- if (controller && path.match(/[_-]controller\.(js|ts)$/)) {
20
- const name = path
21
- .replace(/^.*\//, "")
22
- .replace(/[_-]controller\.(js|ts)$/, "")
23
- .replace(/_/g, "-")
24
- definitions.push({ name, module: controller, filename: path })
25
- }
26
- }
27
- return definitions
28
- }
29
-
30
- function lazyLoadControllersFrom(context) {
31
- return eagerLoadControllersFrom(context)
32
- }
33
-
34
- // Export the functions that stimulus-loading typically provides
35
- export {
36
- application,
37
- eagerLoadControllersFrom,
38
- lazyLoadControllersFrom
39
- }
@@ -1,42 +0,0 @@
1
- <%= render "panda/cms/shared/header", html_class: "h-full bg-white" %>
2
- <div class="flex h-full" id="panda-container">
3
- <div class="absolute top-0 w-full lg:flex lg:fixed lg:inset-y-0 lg:z-50 lg:flex-col lg:w-72">
4
- <div class="flex overflow-y-auto flex-col gap-y-5 px-4 pb-4 max-h-16 bg-gradient-to-br lg:max-h-full grow from-dark to-mid" data-transition-enter="transition-all ease-in-out duration-300" data-transition-enter-from="m-h-16" data-transition-enter-to="max-h-full" data-transition-leave="transition-all ease-in-out duration-300" data-transition-leave-from="max-h-full" data-transition-leave-to="max-h-16">
5
- <%= render "panda/cms/admin/shared/sidebar" %>
6
- </div>
7
- </div>
8
- <div class="flex flex-col flex-1 mt-16 ml-0 lg:mt-0 lg:ml-72" id="panda-inner-container" <% if content_for :sidebar %> data-controller="toggle" data-action="keydown.esc->modal#close" tabindex="-1"<% end %>>
9
- <section id="panda-main" class="flex flex-row h-full">
10
- <div class="flex-1 h-full" id="panda-cms-primary-content">
11
- <%= render "panda/cms/admin/shared/breadcrumbs" %>
12
- <%= render "panda/cms/admin/shared/flash" %>
13
- <%= yield %>
14
- </div>
15
- <% if content_for :sidebar %>
16
- <div data-toggle-target="toggleable" class="hidden flex absolute right-0 flex-col h-full bg-white divide-y divide-gray-200 shadow-xl basis-3/12"
17
- data-transition-enter="transform transition ease-in-out duration-500"
18
- data-transition-enter-from="translate-x-full"
19
- data-transition-enter-to="translate-x-0"
20
- data-transition-leave="transform transition ease-in-out duration-500"
21
- data-transition-leave-from="translate-x-full"
22
- data-transition-leave-to="translate-x-0"
23
- id="slideover">
24
- <div class="overflow-y-auto flex-1 h-0">
25
- <div class="py-3 px-4 mb-4 bg-black">
26
- <div class="flex justify-between items-center">
27
- <h2 class="text-base font-semibold leading-6 text-white" id="slideover-title"><i class="mr-2 fa-light fa-gear"></i> <%= yield :sidebar_title %> </h2>
28
- <button type="button" data-action="click->toggle#toggle touch->toggle#toggle"><i class="font-bold text-white fa-regular fa-xmark right"></i></button>
29
- </div>
30
- </div>
31
- <div class="flex flex-col flex-1 justify-between">
32
- <div class="px-4 space-y-6">
33
- <%= yield :sidebar %>
34
- </div>
35
- </div>
36
- </div>
37
- </div>
38
- <% end %>
39
- </section>
40
- </div>
41
- </div>
42
- <%= render "panda/cms/shared/footer" %>
@@ -1,28 +0,0 @@
1
- <% breadcrumb_styles = "text-black/60 hover:text-black/80" %>
2
-
3
- <div class="flex mt-1 -mb-1">
4
- <nav aria-label="Breadcrumb" id="panda-breadcrumbs" class="grow">
5
- <ol role="list" class="px-4 w-full sm:px-6">
6
- <li class="inline-block">
7
- <a href="/admin" class="<%= breadcrumb_styles %>">
8
- <i class="fa fa-regular fa-house"></i>
9
- <span class="sr-only">Home</span>
10
- </a>
11
- </li>
12
- <% breadcrumbs.each do |crumb| %>
13
- <li class="inline-block">
14
- <i class="fa fa-regular fa-chevron-right font-light <%= breadcrumb_styles %> py-5 px-2"></i>
15
- <a href="<%= crumb.path %>" class="text-sm font-normal <%= breadcrumb_styles %>"><%= crumb.name %></a>
16
- </li>
17
- <% end %>
18
- </ol>
19
- </nav>
20
-
21
- <% if content_for :sidebar %>
22
- <div class="pt-4 pr-8 text-black/80" tabindex="-1" data-controller="toggle">
23
- <button type="button" id="slideover-toggle" data-action="click->toggle#toggle touch->toggle#toggle" class="text-sm font-light">
24
- <i class="mr-1 fa-light fa-gear"></i> <%= yield :sidebar_title %>
25
- </button>
26
- </div>
27
- <% end %>
28
- </div>
@@ -1,5 +0,0 @@
1
- <% if flash.any? %>
2
- <% flash.each do |kind, message| %>
3
- <%= render Panda::Core::Admin::FlashMessageComponent.new(kind: kind, message: message) %>
4
- <% end %>
5
- <% end %>
@@ -1,41 +0,0 @@
1
- <nav class="flex flex-col flex-1">
2
- <ul role="list" class="flex flex-col flex-1">
3
- <a class="block p-0 mt-4 mb-4 ml-2 text-xl font-medium text-white"><%= Panda::CMS.config.title %></a>
4
- <% [
5
- { path: admin_cms_dashboard_path, text: "Dashboard", icon: "fa-house", matcher: :exact },
6
- { path: admin_cms_pages_path, text: "Pages", icon: "fa-page", matcher: :starts_with },
7
- { path: admin_cms_posts_path, text: "Posts", icon: "fa-notebook", matcher: :starts_with },
8
- { path: admin_cms_forms_path, text: "Forms", icon: "fa-cabinet-filing", matcher: :starts_with },
9
- { path: admin_cms_menus_path, text: "Menus", icon: "fa-list-dropdown", matcher: :starts_with },
10
- { path: admin_cms_settings_path, text: "Settings", icon: "fa-cog", matcher: :starts_with },
11
- ].each do |link| %>
12
- <li>
13
- <%= link_to link[:path], class: "#{active_link?(link[:path], match: link[:matcher]) ? selected_nav_highlight_colour_classes(request) : nav_highlight_colour_classes(request)}" do %>
14
- <span class="text-center"><i class="fa-regular <%= link[:icon] %> text-xl fa-fw"></i></span>
15
- <span class="mt-1"><%= link[:text] %></span>
16
- <% end %>
17
- </li>
18
- <% end %>
19
- <li>
20
- <%= button_to panda_core.admin_logout_path, method: :delete, id: "logout-link", data: { turbo: false }, class: nav_highlight_colour_classes(request) + " w-full" do %>
21
- <span class="text-center"><i class="text-xl fa-regular fa-door-open fa-fw"></i></span>
22
- <span class="mt-1">Logout</span>
23
- <% end %>
24
- </li>
25
- <li class="mt-auto">
26
- <%= link_to panda_core.edit_admin_my_profile_path, class: nav_highlight_colour_classes(request) + " w-full", title: "Edit my Profile" do %>
27
- <% if !current_user.image_url.to_s.empty? %>
28
- <span class="text-center"><img src="<%= current_user.image_url %>" class="w-auto h-7 rounded-full"></span>
29
- <% else %>
30
- <span class="text-center"><i class="text-xl fa-regular fa-circle-user fa-fw"></i></span>
31
- <% end %>
32
- <span class="mt-0.5"><%= current_user.firstname %> <%= current_user.lastname %></span>
33
- <% end %>
34
- </li>
35
- <li class="">
36
- <img src="/panda-cms-assets/panda-nav.png" class="inline py-2 mr-1 ml-0.5 w-auto h-14">
37
- <span class="text-xs text-white">v<%= Panda::CMS::VERSION %> | <span class="font-semibold">License:</span> Perpetual</span>
38
- <a class="absolute right-0 mt-4 mr-5 text-lg text-white lg:hidden" href="#" data-action="click->panda-cms-menu#toggle touch->panda-cms-menu#toggle"><i class="mr-2 text-white fa-sharp fa-solid fa-bars"></i> Menu</a>
39
- </li>
40
- </ul>
41
- </nav>
@@ -1,2 +0,0 @@
1
- </body>
2
- </html>
@@ -1,25 +0,0 @@
1
- <!DOCTYPE html>
2
- <html
3
- data-theme="<%= Panda::CMS::Current&.user&.current_theme || "default" %>"
4
- class="<%= html_class ||= "" %>">
5
- <head>
6
- <title><%= title_tag %></title>
7
- <%= csrf_meta_tags %>
8
- <%= csp_meta_tag %>
9
- <script src="https://kit.fontawesome.com/7835d81e75.js" defer="true" crossorigin="anonymous"></script>
10
- <%= stylesheet_link_tag "panda.cms", "data-turbo-track": "reload", media: "all" %>
11
-
12
- <% if Panda::CMS::AssetLoader.use_github_assets? || Rails.env.test? %>
13
- <!-- Using compiled Panda CMS assets -->
14
- <%= panda_cms_complete_assets %>
15
- <% else %>
16
- <!-- Using development importmap assets -->
17
- <script async src="https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js" defer="true" crossorigin="anonymous"></script>
18
- <%= render "panda/cms/shared/importmap" %>
19
- <% end %>
20
-
21
- <%= render "panda/cms/shared/favicons" %>
22
- <%= yield :head %>
23
- </head>
24
- <body class="overflow-hidden h-full" data-environment="<%= Rails.env %>">
25
- <%= render "panda/cms/admin/shared/flash" %>
@@ -1,34 +0,0 @@
1
- <script type="importmap" data-turbo-track="reload">
2
- <%=
3
- # This ended up being the best solution to not drag in the importmap from the host app when we're editing the site
4
- JSON.pretty_generate({
5
- imports: {
6
- # Main loader
7
- "application_panda_cms": asset_path("panda/cms/application_panda_cms.js"),
8
- # Vendored
9
- "@hotwired/turbo": asset_path("panda/cms/@hotwired--turbo.js"),
10
- "@hotwired/stimulus": asset_path("panda/cms/@hotwired--stimulus.js"),
11
- "@hotwired/stimulus-loading": asset_path("panda_cms/stimulus-loading.js"),
12
- "@editorjs/editorjs": asset_path("panda/cms/@editorjs--editorjs.js"),
13
- "tailwindcss-stimulus-components": asset_path("panda/cms/tailwindcss-stimulus-components.js"),
14
- # Our page editor
15
- "panda/cms/editor/plain_text_editor": asset_path("panda/cms/editor/plain_text_editor.js"),
16
- "panda/cms/editor/resource_loader": asset_path("panda/cms/editor/resource_loader.js"),
17
- "panda/cms/editor/editor_js_initializer": asset_path("panda/cms/editor/editor_js_initializer.js"),
18
- "panda/cms/editor/editor_js_config": asset_path("panda/cms/editor/editor_js_config.js"),
19
- "panda/cms/editor/css_extractor": asset_path("panda/cms/editor/css_extractor.js"),
20
- # Our controllers
21
- "panda/cms/controllers/dashboard_controller": asset_path("panda/cms/controllers/dashboard_controller.js"),
22
- "panda/cms/controllers/slug_controller": asset_path("panda/cms/controllers/slug_controller.js"),
23
- "panda/cms/controllers/editor_form_controller": asset_path("panda/cms/controllers/editor_form_controller.js"),
24
- "panda/cms/controllers/editor_iframe_controller": asset_path("panda/cms/controllers/editor_iframe_controller.js"),
25
- "panda/cms/controllers/theme_form_controller": asset_path("panda/cms/controllers/theme_form_controller.js"),
26
- # Main controller loader
27
- "controllers": asset_path("panda/cms/controllers/index.js"),
28
- }
29
- }).html_safe
30
- %>
31
- </script>
32
- <script type="module">
33
- import "application_panda_cms"
34
- </script>
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ActiveSupport::Inflector.inflections(:en) do |inflect|
4
- inflect.acronym "CMS"
5
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "silencer/rails/logger"
4
-
5
- # Don't log requests to the healthcheck endpoint
6
- Rails.application.configure do
7
- config.middleware.swap(
8
- Rails::Rack::Logger,
9
- Silencer::Logger,
10
- config.log_tags,
11
- silence: ["/up"]
12
- )
13
- end