panda-cms 0.7.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.
- checksums.yaml +7 -0
- data/README.md +73 -0
- data/Rakefile +7 -0
- data/app/assets/builds/panda.cms.css +2808 -0
- data/app/assets/config/panda_cms_manifest.js +4 -0
- data/app/assets/stylesheets/panda/cms/application.tailwind.css +162 -0
- data/app/assets/stylesheets/panda/cms/editor.css +120 -0
- data/app/builders/panda/cms/form_builder.rb +234 -0
- data/app/components/panda/cms/admin/button_component.rb +70 -0
- data/app/components/panda/cms/admin/container_component.html.erb +13 -0
- data/app/components/panda/cms/admin/container_component.rb +13 -0
- data/app/components/panda/cms/admin/flash_message_component.html.erb +31 -0
- data/app/components/panda/cms/admin/flash_message_component.rb +47 -0
- data/app/components/panda/cms/admin/heading_component.rb +45 -0
- data/app/components/panda/cms/admin/panel_component.html.erb +7 -0
- data/app/components/panda/cms/admin/panel_component.rb +13 -0
- data/app/components/panda/cms/admin/slideover_component.html.erb +9 -0
- data/app/components/panda/cms/admin/slideover_component.rb +15 -0
- data/app/components/panda/cms/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/cms/admin/statistics_component.rb +17 -0
- data/app/components/panda/cms/admin/tab_bar_component.html.erb +35 -0
- data/app/components/panda/cms/admin/tab_bar_component.rb +15 -0
- data/app/components/panda/cms/admin/table_component.html.erb +29 -0
- data/app/components/panda/cms/admin/table_component.rb +46 -0
- data/app/components/panda/cms/admin/tag_component.rb +35 -0
- data/app/components/panda/cms/admin/user_activity_component.html.erb +5 -0
- data/app/components/panda/cms/admin/user_activity_component.rb +33 -0
- data/app/components/panda/cms/admin/user_display_component.html.erb +17 -0
- data/app/components/panda/cms/admin/user_display_component.rb +21 -0
- data/app/components/panda/cms/code_component.rb +64 -0
- data/app/components/panda/cms/grid_component.html.erb +6 -0
- data/app/components/panda/cms/grid_component.rb +15 -0
- data/app/components/panda/cms/menu_component.html.erb +6 -0
- data/app/components/panda/cms/menu_component.rb +58 -0
- data/app/components/panda/cms/page_menu_component.html.erb +21 -0
- data/app/components/panda/cms/page_menu_component.rb +38 -0
- data/app/components/panda/cms/rich_text_component.html.erb +6 -0
- data/app/components/panda/cms/rich_text_component.rb +84 -0
- data/app/components/panda/cms/text_component.rb +72 -0
- data/app/constraints/panda/cms/admin_constraint.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +52 -0
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +20 -0
- data/app/controllers/panda/cms/admin/files_controller.rb +21 -0
- data/app/controllers/panda/cms/admin/forms_controller.rb +53 -0
- data/app/controllers/panda/cms/admin/menus_controller.rb +30 -0
- data/app/controllers/panda/cms/admin/pages_controller.rb +91 -0
- data/app/controllers/panda/cms/admin/posts_controller.rb +146 -0
- data/app/controllers/panda/cms/admin/sessions_controller.rb +94 -0
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +37 -0
- data/app/controllers/panda/cms/admin/settings_controller.rb +20 -0
- data/app/controllers/panda/cms/application_controller.rb +57 -0
- data/app/controllers/panda/cms/errors_controller.rb +33 -0
- data/app/controllers/panda/cms/form_submissions_controller.rb +23 -0
- data/app/controllers/panda/cms/pages_controller.rb +72 -0
- data/app/controllers/panda/cms/posts_controller.rb +13 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/posts_helper.rb +48 -0
- data/app/helpers/panda/cms/application_helper.rb +120 -0
- data/app/helpers/panda/cms/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/theme_helper.rb +18 -0
- data/app/javascript/panda/cms/@editorjs--editorjs.js +2577 -0
- data/app/javascript/panda/cms/@hotwired--stimulus.js +4 -0
- data/app/javascript/panda/cms/@hotwired--turbo.js +160 -0
- data/app/javascript/panda/cms/@rails--actioncable--src.js +4 -0
- data/app/javascript/panda/cms/application_panda_cms.js +39 -0
- data/app/javascript/panda/cms/controllers/dashboard_controller.js +7 -0
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +77 -0
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +320 -0
- data/app/javascript/panda/cms/controllers/index.js +48 -0
- data/app/javascript/panda/cms/controllers/slug_controller.js +87 -0
- data/app/javascript/panda/cms/editor/css_extractor.js +80 -0
- data/app/javascript/panda/cms/editor/editor_js_config.js +177 -0
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +285 -0
- data/app/javascript/panda/cms/editor/plain_text_editor.js +110 -0
- data/app/javascript/panda/cms/editor/resource_loader.js +115 -0
- data/app/javascript/panda/cms/tailwindcss-stimulus-components.js +4 -0
- data/app/jobs/panda/cms/application_job.rb +6 -0
- data/app/jobs/panda/cms/record_visit_job.rb +31 -0
- data/app/mailers/panda/cms/application_mailer.rb +8 -0
- data/app/mailers/panda/cms/form_mailer.rb +21 -0
- data/app/models/action_text/rich_text_version.rb +6 -0
- data/app/models/panda/cms/application_record.rb +7 -0
- data/app/models/panda/cms/block.rb +34 -0
- data/app/models/panda/cms/block_content.rb +18 -0
- data/app/models/panda/cms/block_content_version.rb +8 -0
- data/app/models/panda/cms/breadcrumb.rb +12 -0
- data/app/models/panda/cms/current.rb +17 -0
- data/app/models/panda/cms/form.rb +9 -0
- data/app/models/panda/cms/form_submission.rb +7 -0
- data/app/models/panda/cms/menu.rb +52 -0
- data/app/models/panda/cms/menu_item.rb +58 -0
- data/app/models/panda/cms/page.rb +96 -0
- data/app/models/panda/cms/page_version.rb +8 -0
- data/app/models/panda/cms/post.rb +60 -0
- data/app/models/panda/cms/post_version.rb +8 -0
- data/app/models/panda/cms/redirect.rb +11 -0
- data/app/models/panda/cms/template.rb +124 -0
- data/app/models/panda/cms/template_version.rb +8 -0
- data/app/models/panda/cms/user.rb +31 -0
- data/app/models/panda/cms/version.rb +8 -0
- data/app/models/panda/cms/visit.rb +9 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +200 -0
- data/app/views/active_storage/blobs/blobs/_blob.html.erb +14 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/panda/cms/application.html.erb +41 -0
- data/app/views/layouts/panda/cms/public.html.erb +3 -0
- data/app/views/panda/cms/admin/dashboard/show.html.erb +12 -0
- data/app/views/panda/cms/admin/files/index.html.erb +124 -0
- data/app/views/panda/cms/admin/files/show.html.erb +2 -0
- data/app/views/panda/cms/admin/forms/edit.html.erb +0 -0
- data/app/views/panda/cms/admin/forms/index.html.erb +13 -0
- data/app/views/panda/cms/admin/forms/new.html.erb +15 -0
- data/app/views/panda/cms/admin/forms/show.html.erb +35 -0
- data/app/views/panda/cms/admin/menus/index.html.erb +8 -0
- data/app/views/panda/cms/admin/pages/edit.html.erb +36 -0
- data/app/views/panda/cms/admin/pages/index.html.erb +22 -0
- data/app/views/panda/cms/admin/pages/new.html.erb +15 -0
- data/app/views/panda/cms/admin/pages/show.html.erb +1 -0
- data/app/views/panda/cms/admin/posts/_form.html.erb +29 -0
- data/app/views/panda/cms/admin/posts/edit.html.erb +6 -0
- data/app/views/panda/cms/admin/posts/index.html.erb +18 -0
- data/app/views/panda/cms/admin/posts/new.html.erb +6 -0
- data/app/views/panda/cms/admin/sessions/new.html.erb +17 -0
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +68 -0
- data/app/views/panda/cms/admin/settings/index.html.erb +21 -0
- data/app/views/panda/cms/admin/settings/insta.html +4 -0
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +28 -0
- data/app/views/panda/cms/admin/shared/_flash.html.erb +5 -0
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +41 -0
- data/app/views/panda/cms/form_mailer/notification_email.html.erb +11 -0
- data/app/views/panda/cms/shared/_editor.html.erb +0 -0
- data/app/views/panda/cms/shared/_favicons.html.erb +9 -0
- data/app/views/panda/cms/shared/_footer.html.erb +2 -0
- data/app/views/panda/cms/shared/_header.html.erb +15 -0
- data/app/views/panda/cms/shared/_importmap.html.erb +33 -0
- data/config/importmap.rb +13 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/initializers/panda/cms/form_errors.rb +38 -0
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +11 -0
- data/config/initializers/panda/cms/paper_trail.rb +7 -0
- data/config/initializers/panda/cms.rb +10 -0
- data/config/initializers/zeitwork.rb +3 -0
- data/config/locales/en.yml +49 -0
- data/config/puma/test.rb +9 -0
- data/config/routes.rb +48 -0
- data/config/tailwind.config.js +37 -0
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
- data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +82 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +9 -0
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
- data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +24 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +11 -0
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +5 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +35 -0
- data/db/migrate/20241119214549_remove_action_text_from_posts.rb +9 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +19 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +27 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +5 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +5 -0
- data/db/migrate/migrate +1 -0
- data/db/seeds.rb +5 -0
- data/lib/generators/panda/cms/install_generator.rb +29 -0
- data/lib/panda/cms/bulk_editor.rb +171 -0
- data/lib/panda/cms/demo_site_generator.rb +67 -0
- data/lib/panda/cms/editor_js/blocks/alert.rb +34 -0
- data/lib/panda/cms/editor_js/blocks/base.rb +33 -0
- data/lib/panda/cms/editor_js/blocks/header.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/image.rb +36 -0
- data/lib/panda/cms/editor_js/blocks/list.rb +32 -0
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/quote.rb +41 -0
- data/lib/panda/cms/editor_js/blocks/table.rb +50 -0
- data/lib/panda/cms/editor_js/renderer.rb +124 -0
- data/lib/panda/cms/editor_js.rb +16 -0
- data/lib/panda/cms/editor_js_content.rb +21 -0
- data/lib/panda/cms/engine.rb +257 -0
- data/lib/panda/cms/exceptions_app.rb +26 -0
- data/lib/panda/cms/railtie.rb +11 -0
- data/lib/panda/cms/slug.rb +24 -0
- data/lib/panda/cms.rb +0 -0
- data/lib/panda-cms/version.rb +5 -0
- data/lib/panda-cms.rb +81 -0
- data/lib/tasks/panda_cms.rake +54 -0
- data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
- data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
- data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
- data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
- data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
- data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
- data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
- data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
- data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
- data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
- data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
- data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
- data/public/panda-cms-assets/favicons/favicon.ico +0 -0
- data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
- data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
- data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
- data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
- data/public/panda-cms-assets/panda-nav.png +0 -0
- data/public/panda-cms-assets/rich_text_editor.css +568 -0
- metadata +654 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Panda
|
|
2
|
+
module CMS
|
|
3
|
+
class User < ApplicationRecord
|
|
4
|
+
validates :firstname, presence: true
|
|
5
|
+
validates :lastname, presence: true
|
|
6
|
+
validates :email, presence: true, uniqueness: true
|
|
7
|
+
|
|
8
|
+
before_save :downcase_email
|
|
9
|
+
|
|
10
|
+
scope :admin, -> { where(admin: true) }
|
|
11
|
+
|
|
12
|
+
def is_admin?
|
|
13
|
+
admin
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
"#{firstname} #{lastname}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.for_select_list(scope = :all, order = {firstname: :asc, lastname: :asc})
|
|
21
|
+
Panda::CMS::User.send(scope).order(order).map { |u| [u.name, u.id] }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def downcase_email
|
|
27
|
+
self.email = email.to_s.downcase
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Panda
|
|
2
|
+
module CMS
|
|
3
|
+
class Visit < ApplicationRecord
|
|
4
|
+
belongs_to :page, class_name: "Panda::CMS::Page", foreign_key: :page_id, optional: true
|
|
5
|
+
belongs_to :user, class_name: "Panda::CMS::User", foreign_key: :user_id, optional: true
|
|
6
|
+
belongs_to :redirect, class_name: "Panda::CMS::Redirect", foreign_key: :redirect_id, optional: true
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
module Panda
|
|
2
|
+
module CMS
|
|
3
|
+
class HtmlToEditorJsConverter
|
|
4
|
+
class ConversionError < StandardError; end
|
|
5
|
+
|
|
6
|
+
def self.convert(html)
|
|
7
|
+
return {} if html.blank?
|
|
8
|
+
|
|
9
|
+
# If it's already in EditorJS format, return as is
|
|
10
|
+
return html if html.is_a?(Hash) && (html["blocks"].present? || html[:blocks].present?)
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
# Parse the HTML content
|
|
14
|
+
doc = Nokogiri::HTML.fragment(html.to_s)
|
|
15
|
+
raise ConversionError, "Failed to parse HTML content" unless doc
|
|
16
|
+
|
|
17
|
+
blocks = []
|
|
18
|
+
current_text = ""
|
|
19
|
+
|
|
20
|
+
doc.children.each do |node|
|
|
21
|
+
case node.name
|
|
22
|
+
when "h1", "h2", "h3", "h4", "h5", "h6"
|
|
23
|
+
# Add any accumulated text as a paragraph before the header
|
|
24
|
+
if current_text.present?
|
|
25
|
+
blocks << create_paragraph_block(current_text)
|
|
26
|
+
current_text = ""
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
blocks << {
|
|
30
|
+
"type" => "header",
|
|
31
|
+
"data" => {
|
|
32
|
+
"text" => node.text.strip,
|
|
33
|
+
"level" => node.name[1].to_i
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
when "p", "div"
|
|
37
|
+
# Add any accumulated text first
|
|
38
|
+
if current_text.present?
|
|
39
|
+
blocks << create_paragraph_block(current_text)
|
|
40
|
+
current_text = ""
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if node.name == "div"
|
|
44
|
+
# Process div children separately
|
|
45
|
+
node.children.each do |child|
|
|
46
|
+
case child.name
|
|
47
|
+
when "h1", "h2", "h3", "h4", "h5", "h6"
|
|
48
|
+
blocks << {
|
|
49
|
+
"type" => "header",
|
|
50
|
+
"data" => {
|
|
51
|
+
"text" => child.text.strip,
|
|
52
|
+
"level" => child.name[1].to_i
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
when "p"
|
|
56
|
+
text = process_inline_elements(child)
|
|
57
|
+
paragraphs = text.split(/<br\s*\/?>\s*<br\s*\/?>/).map(&:strip)
|
|
58
|
+
paragraphs.each do |paragraph|
|
|
59
|
+
blocks << create_paragraph_block(paragraph) if paragraph.present?
|
|
60
|
+
end
|
|
61
|
+
when "ul", "ol"
|
|
62
|
+
items = child.css("li").map { |li| process_inline_elements(li) }
|
|
63
|
+
next if items.empty?
|
|
64
|
+
|
|
65
|
+
blocks << {
|
|
66
|
+
"type" => "list",
|
|
67
|
+
"data" => {
|
|
68
|
+
"style" => (child.name == "ul") ? "unordered" : "ordered",
|
|
69
|
+
"items" => items
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
when "blockquote"
|
|
73
|
+
blocks << {
|
|
74
|
+
"type" => "quote",
|
|
75
|
+
"data" => {
|
|
76
|
+
"text" => process_inline_elements(child),
|
|
77
|
+
"caption" => "",
|
|
78
|
+
"alignment" => "left"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
when "text"
|
|
82
|
+
text = child.text.strip
|
|
83
|
+
current_text += text if text.present?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
else
|
|
87
|
+
# Handle p with nested content
|
|
88
|
+
text = process_inline_elements(node)
|
|
89
|
+
paragraphs = text.split(/<br\s*\/?>\s*<br\s*\/?>/).map(&:strip)
|
|
90
|
+
paragraphs.each do |paragraph|
|
|
91
|
+
blocks << create_paragraph_block(paragraph) if paragraph.present?
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
when "br"
|
|
95
|
+
current_text += "\n\n"
|
|
96
|
+
when "text"
|
|
97
|
+
text = node.text.strip
|
|
98
|
+
current_text += text if text.present?
|
|
99
|
+
when "ul", "ol"
|
|
100
|
+
# Add any accumulated text first
|
|
101
|
+
if current_text.present?
|
|
102
|
+
blocks << create_paragraph_block(current_text)
|
|
103
|
+
current_text = ""
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
items = node.css("li").map { |li| process_inline_elements(li) }
|
|
107
|
+
next if items.empty?
|
|
108
|
+
|
|
109
|
+
blocks << {
|
|
110
|
+
"type" => "list",
|
|
111
|
+
"data" => {
|
|
112
|
+
"style" => (node.name == "ul") ? "unordered" : "ordered",
|
|
113
|
+
"items" => items
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
when "blockquote"
|
|
117
|
+
# Add any accumulated text first
|
|
118
|
+
if current_text.present?
|
|
119
|
+
blocks << create_paragraph_block(current_text)
|
|
120
|
+
current_text = ""
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
blocks << {
|
|
124
|
+
"type" => "quote",
|
|
125
|
+
"data" => {
|
|
126
|
+
"text" => process_inline_elements(node),
|
|
127
|
+
"caption" => "",
|
|
128
|
+
"alignment" => "left"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Add any remaining text as a final paragraph
|
|
135
|
+
if current_text.present?
|
|
136
|
+
# Split any remaining text on double line breaks
|
|
137
|
+
paragraphs = current_text.split(/\n\n+/).map(&:strip)
|
|
138
|
+
paragraphs.each do |paragraph|
|
|
139
|
+
blocks << create_paragraph_block(paragraph) if paragraph.present?
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
raise ConversionError, "No valid content blocks found" if blocks.empty?
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
"time" => Time.current.to_i * 1000,
|
|
147
|
+
"blocks" => blocks,
|
|
148
|
+
"version" => "2.28.2"
|
|
149
|
+
}
|
|
150
|
+
rescue Nokogiri::SyntaxError => e
|
|
151
|
+
raise ConversionError, "Invalid HTML syntax: #{e.message}"
|
|
152
|
+
rescue => e
|
|
153
|
+
raise ConversionError, "Conversion failed: #{e.message}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def self.create_paragraph_block(text)
|
|
158
|
+
{
|
|
159
|
+
"type" => "paragraph",
|
|
160
|
+
"data" => {
|
|
161
|
+
"text" => text.strip
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def self.process_inline_elements(node)
|
|
167
|
+
result = ""
|
|
168
|
+
node.children.each do |child|
|
|
169
|
+
case child.name
|
|
170
|
+
when "br"
|
|
171
|
+
result += "<br>"
|
|
172
|
+
when "text"
|
|
173
|
+
result += child.text
|
|
174
|
+
when "strong", "b"
|
|
175
|
+
result += "<b>#{child.text}</b>"
|
|
176
|
+
when "em", "i"
|
|
177
|
+
result += "<i>#{child.text}</i>"
|
|
178
|
+
when "a"
|
|
179
|
+
href = child["href"]
|
|
180
|
+
text = child.text.strip
|
|
181
|
+
# Handle email links specially
|
|
182
|
+
if href&.start_with?("mailto:")
|
|
183
|
+
email = href.sub("mailto:", "")
|
|
184
|
+
result += "<a href=\"mailto:#{email}\">#{text}</a>"
|
|
185
|
+
else
|
|
186
|
+
result += "<a href=\"#{href}\">#{text}</a>"
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
result += if child.text?
|
|
190
|
+
child.text
|
|
191
|
+
else
|
|
192
|
+
child.to_html
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
result.strip
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
|
|
2
|
+
<% if blob.representable? %>
|
|
3
|
+
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<figcaption class="attachment__caption">
|
|
7
|
+
<% if caption = blob.try(:caption) %>
|
|
8
|
+
<%= caption %>
|
|
9
|
+
<% else %>
|
|
10
|
+
<span class="attachment__name"><%= blob.filename %></span>
|
|
11
|
+
<span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
|
|
12
|
+
<% end %>
|
|
13
|
+
</figcaption>
|
|
14
|
+
</figure>
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
<%= yield %>
|
|
13
|
+
</div>
|
|
14
|
+
<% if content_for :sidebar %>
|
|
15
|
+
<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"
|
|
16
|
+
data-transition-enter="transform transition ease-in-out duration-500"
|
|
17
|
+
data-transition-enter-from="translate-x-full"
|
|
18
|
+
data-transition-enter-to="translate-x-0"
|
|
19
|
+
data-transition-leave="transform transition ease-in-out duration-500"
|
|
20
|
+
data-transition-leave-from="translate-x-full"
|
|
21
|
+
data-transition-leave-to="translate-x-0"
|
|
22
|
+
id="slideover">
|
|
23
|
+
<div class="overflow-y-auto flex-1 h-0">
|
|
24
|
+
<div class="py-3 px-4 mb-4 bg-black">
|
|
25
|
+
<div class="flex justify-between items-center">
|
|
26
|
+
<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>
|
|
27
|
+
<a href="#" data-action="click->toggle#toggle touch->toggle#toggle"><i class="font-bold text-white fa-regular fa-xmark right"></i></a>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex flex-col flex-1 justify-between">
|
|
31
|
+
<div class="px-4 space-y-6">
|
|
32
|
+
<%= yield :sidebar %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</section>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<%= render "panda/cms/shared/footer" %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="" data-controller="dashboard">
|
|
2
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |container| %>
|
|
3
|
+
<% container.with_heading(text: "Dashboard", level: 1) do |heading| %>
|
|
4
|
+
<% heading.with_button(action: :add, text: "Add Page", link: new_admin_page_path) %>
|
|
5
|
+
<% end %>
|
|
6
|
+
<dl class="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-3">
|
|
7
|
+
<%= render Panda::CMS::Admin::StatisticsComponent.new(metric: "Views Today", value: Panda::CMS::Visit.group_by_day(:visited_at, last: 1).count.values.first) %>
|
|
8
|
+
<%= render Panda::CMS::Admin::StatisticsComponent.new(metric: "Views Last Week", value: Panda::CMS::Visit.group_by_week(:visited_at, last: 1).count.values.first) %>
|
|
9
|
+
<%= render Panda::CMS::Admin::StatisticsComponent.new(metric: "Views Last Month", value: Panda::CMS::Visit.group_by_month(:visited_at, last: 1).count.values.first) %>
|
|
10
|
+
</dl>
|
|
11
|
+
<% end %>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Files", level: 1) %>
|
|
3
|
+
|
|
4
|
+
<% component.with_slideover(title: "File Details") do %>
|
|
5
|
+
<div class="pt-4 pb-16 space-y-6">
|
|
6
|
+
<div>
|
|
7
|
+
<div class="block overflow-hidden w-full rounded-lg aspect-h-7 aspect-w-10">
|
|
8
|
+
<img src="https://images.unsplash.com/photo-1582053433976-25c00369fc93?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=512&q=80" alt="" class="object-cover">
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex justify-between items-start mt-4">
|
|
11
|
+
<div>
|
|
12
|
+
<h2 class="text-lg font-medium text-gray-900"><span class="sr-only">Details for </span>IMG_4985.HEIC</h2>
|
|
13
|
+
<p class="text-sm font-medium text-gray-500">3.9 MB</p>
|
|
14
|
+
</div>
|
|
15
|
+
<button type="button" class="flex relative justify-center items-center ml-4 w-8 h-8 text-gray-400 bg-white rounded-full hover:text-gray-500 hover:bg-gray-100 focus:ring-2 focus:outline-none focus:ring-panda-dark">
|
|
16
|
+
<span class="absolute -inset-1.5"></span>
|
|
17
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
|
18
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z" />
|
|
19
|
+
</svg>
|
|
20
|
+
<span class="sr-only">Favorite</span>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div>
|
|
25
|
+
<h3 class="font-medium text-gray-900">Information</h3>
|
|
26
|
+
<dl class="mt-2 border-t border-b border-gray-200 divide-y divide-gray-200">
|
|
27
|
+
<div class="flex justify-between py-3 text-sm font-medium">
|
|
28
|
+
<dt class="text-gray-500">Uploaded by</dt>
|
|
29
|
+
<dd class="text-gray-900 whitespace-nowrap">Marie Culver</dd>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex justify-between py-3 text-sm font-medium">
|
|
32
|
+
<dt class="text-gray-500">Created</dt>
|
|
33
|
+
<dd class="text-gray-900 whitespace-nowrap">June 8, 2020</dd>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="flex justify-between py-3 text-sm font-medium">
|
|
36
|
+
<dt class="text-gray-500">Last modified</dt>
|
|
37
|
+
<dd class="text-gray-900 whitespace-nowrap">June 8, 2020</dd>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex justify-between py-3 text-sm font-medium">
|
|
40
|
+
<dt class="text-gray-500">Dimensions</dt>
|
|
41
|
+
<dd class="text-gray-900 whitespace-nowrap">4032 x 3024</dd>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex justify-between py-3 text-sm font-medium">
|
|
44
|
+
<dt class="text-gray-500">Resolution</dt>
|
|
45
|
+
<dd class="text-gray-900 whitespace-nowrap">72 x 72</dd>
|
|
46
|
+
</div>
|
|
47
|
+
</dl>
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<h3 class="font-medium text-gray-900">Description</h3>
|
|
51
|
+
<div class="flex justify-between items-center mt-2">
|
|
52
|
+
<p class="text-sm italic text-gray-500">Add a description to this image.</p>
|
|
53
|
+
<button type="button" class="flex relative justify-center items-center w-8 h-8 text-gray-400 bg-white rounded-full hover:text-gray-500 hover:bg-gray-100 focus:ring-2 focus:outline-none focus:ring-panda-dark">
|
|
54
|
+
<span class="absolute -inset-1.5"></span>
|
|
55
|
+
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
56
|
+
<path d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
|
|
57
|
+
</svg>
|
|
58
|
+
<span class="sr-only">Add description</span>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<h3 class="font-medium text-gray-900">Shared with</h3>
|
|
64
|
+
<ul role="list" class="mt-2 border-t border-b border-gray-200 divide-y divide-gray-200">
|
|
65
|
+
<li class="flex justify-between items-center py-3">
|
|
66
|
+
<div class="flex items-center">
|
|
67
|
+
<img src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=3&w=1024&h=1024&q=80" alt="" class="w-8 h-8 rounded-full">
|
|
68
|
+
<p class="ml-4 text-sm font-medium text-gray-900">Aimee Douglas</p>
|
|
69
|
+
</div>
|
|
70
|
+
<button type="button" class="ml-6 text-sm font-medium bg-white rounded-md focus:ring-2 focus:ring-offset-2 focus:outline-none text-panda-dark hover:text-panda-dark focus:ring-panda-dark">Remove<span class="sr-only"> Aimee Douglas</span></button>
|
|
71
|
+
</li>
|
|
72
|
+
<li class="flex justify-between items-center py-3">
|
|
73
|
+
<div class="flex items-center">
|
|
74
|
+
<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixqx=oilqXxSqey&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" class="w-8 h-8 rounded-full">
|
|
75
|
+
<p class="ml-4 text-sm font-medium text-gray-900">Andrea McMillan</p>
|
|
76
|
+
</div>
|
|
77
|
+
<button type="button" class="ml-6 text-sm font-medium bg-white rounded-md focus:ring-2 focus:ring-offset-2 focus:outline-none text-panda-dark hover:text-panda-dark focus:ring-panda-dark">Remove<span class="sr-only"> Andrea McMillan</span></button>
|
|
78
|
+
</li>
|
|
79
|
+
|
|
80
|
+
<li class="flex justify-between items-center py-2">
|
|
81
|
+
<button type="button" class="flex items-center p-1 -ml-1 bg-white rounded-md focus:ring-2 focus:outline-none group focus:ring-panda-dark">
|
|
82
|
+
<span class="flex justify-center items-center w-8 h-8 text-gray-400 rounded-full border-2 border-gray-300 border-dashed">
|
|
83
|
+
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
84
|
+
<path d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z" />
|
|
85
|
+
</svg>
|
|
86
|
+
</span>
|
|
87
|
+
<span class="ml-4 text-sm font-medium text-panda-dark group-hover:text-panda-dark">Share</span>
|
|
88
|
+
</button>
|
|
89
|
+
</li>
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="flex gap-x-3">
|
|
93
|
+
<button type="button" class="flex-1 py-2 px-3 text-sm font-semibold text-white bg-black rounded-md shadow-sm hover:bg-black focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-panda-dark">Download</button>
|
|
94
|
+
<button type="button" class="flex-1 py-2 px-3 text-sm font-semibold text-gray-900 bg-white rounded-md ring-1 ring-inset shadow-sm hover:bg-gray-50 ring-mid">Delete</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
|
|
99
|
+
<% component.with_tab_bar do %>
|
|
100
|
+
<%= component.with_tab(name: "Recently Viewed", active: true) %>
|
|
101
|
+
<%= component.with_tab(name: "Recently Added") %>
|
|
102
|
+
<%= component.with_tab(name: "Favourited") %>
|
|
103
|
+
<% end %>
|
|
104
|
+
|
|
105
|
+
<section>
|
|
106
|
+
<h2 id="gallery-heading" class="sr-only">Recently viewed</h2>
|
|
107
|
+
<ul role="list" class="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 md:grid-cols-4 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
|
108
|
+
<li class="relative">
|
|
109
|
+
<!-- Current: "ring-2 ring-panda-dark ring-offset-2", Default: "focus-within:ring-2 focus-within:ring-panda-dark focus-within:ring-offset-2 focus-within:ring-offset-gray-100" -->
|
|
110
|
+
<div class="block overflow-hidden w-full bg-gray-100 rounded-lg ring-2 ring-offset-2 ring-panda-dark aspect-w-10 aspect-h-7 group">
|
|
111
|
+
<!-- Current: "", Default: "group-hover:opacity-75" -->
|
|
112
|
+
<img src="https://images.unsplash.com/photo-1582053433976-25c00369fc93?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=512&q=80" alt="" class="object-cover pointer-events-none">
|
|
113
|
+
<button type="button" class="absolute inset-0 focus:outline-none">
|
|
114
|
+
<span class="sr-only">View details for IMG_4985.HEIC</span>
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
<p class="block mt-2 text-sm font-medium text-gray-900 pointer-events-none truncate">IMG_4985.HEIC</p>
|
|
118
|
+
<p class="block text-sm font-medium text-gray-500 pointer-events-none">3.9 MB</p>
|
|
119
|
+
</li>
|
|
120
|
+
|
|
121
|
+
<!-- More files... -->
|
|
122
|
+
</ul>
|
|
123
|
+
</section>
|
|
124
|
+
<% end %>
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Forms", level: 1) do |heading| %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<%= render Panda::CMS::Admin::TableComponent.new(term: "form", rows: forms) do |table| %>
|
|
6
|
+
<% table.column("Name") { |form| block_link_to form.name, admin_form_path(form) } %>
|
|
7
|
+
<% table.column("Status") { |form| render Panda::CMS::Admin::TagComponent.new(status: :active) } %>
|
|
8
|
+
<% table.column("Last Submission") do |form| %>
|
|
9
|
+
<%= "#{time_ago_in_words(form.form_submissions&.last&.created_at)} ago" if form.form_submissions&.last %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% table.column("Completion Path", &:completion_path) %>
|
|
12
|
+
<% end %>
|
|
13
|
+
<% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Add Page", level: 1) do |heading| %>
|
|
3
|
+
<% end %>
|
|
4
|
+
<%= panda_cms_form_with model: page, url: admin_pages_path, method: :post do |f| %>
|
|
5
|
+
<% options = nested_set_options(Panda::CMS::Page, page) { |i| "#{"-" * i.level} #{i.title} (#{i.path})" } %>
|
|
6
|
+
<div data-controller="slug">
|
|
7
|
+
<input type="hidden" value="<%= Panda::CMS::Current.root %>" data-slug-target="existing_root">
|
|
8
|
+
<%= f.select :parent_id, options, {}, { "data-slug-target": "input_select", "data-action": "change->slug#setPrePath" } %>
|
|
9
|
+
<%= f.text_field :title, { data: { "slug-target": "input_text", action: "focusout->slug#generatePath" } } %>
|
|
10
|
+
<%= f.text_field :path, { data: { prefix: Panda::CMS::Current.root, "slug-target": "output_text" } } %>
|
|
11
|
+
<%= f.collection_select :panda_cms_template_id, Panda::CMS::Template.available, :id, :name %>
|
|
12
|
+
<%= f.button %>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: form.name, level: 1) do |heading| %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<div class="overflow-x-auto -mx-4 rounded-lg ring-1 sm:mx-0 sm:rounded-lg ring-mid">
|
|
6
|
+
<table class="min-w-full divide-y divide-gray-300 border-collapse table-auto">
|
|
7
|
+
<thead>
|
|
8
|
+
<tr>
|
|
9
|
+
<% fields.each do |field, title| %>
|
|
10
|
+
<th scope="col" class="py-3.5 px-3 text-sm font-semibold text-left text-gray-900"><%= title %></th>
|
|
11
|
+
<% end %>
|
|
12
|
+
<th scope="col" class="py-3.5 px-3 text-sm font-semibold text-left text-gray-900">Submitted</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
16
|
+
<% submissions.each do |submission| %>
|
|
17
|
+
<tr class="relative bg-white cursor-pointer hover:bg-gray-50">
|
|
18
|
+
<% fields.each do |field| %>
|
|
19
|
+
<td class="py-5 px-3 text-sm text-gray-500">
|
|
20
|
+
<% if field[0] == "email" || field[0] == "email_address" %>
|
|
21
|
+
<a href="mailto:<%= submission.data[field[0]] %>" class="border-b border-gray-500 hover:text-gray-900"><%= submission.data[field[0]] %></a>
|
|
22
|
+
<% else %>
|
|
23
|
+
<%= simple_format(submission.data[field[0]]) %>
|
|
24
|
+
<% end %>
|
|
25
|
+
</td>
|
|
26
|
+
<% end %>
|
|
27
|
+
<td class="py-5 px-3 text-sm text-gray-500 whitespace-nowrap">
|
|
28
|
+
<div class="text-gray-500"><%= time_ago_in_words(submission.created_at) %> ago</div>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
<% end %>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Menus", level: 1) do |heading| %>
|
|
3
|
+
<% end %>
|
|
4
|
+
<%= render Panda::CMS::Admin::TableComponent.new(term: "menu", rows: menus) do |table| %>
|
|
5
|
+
<% table.column("Name") { |menu| link_to menu.name, edit_admin_menu_path(menu) } %>
|
|
6
|
+
<% table.column("Kind") { |menu| render Panda::CMS::Admin::TagComponent.new(status: :active, text: menu.kind.titleize) } %>
|
|
7
|
+
<% end %>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "#{page.title}", level: 1) %>
|
|
3
|
+
<% component.with_slideover(title: "Page Details") do %>
|
|
4
|
+
<%= panda_cms_form_with model: page, url: admin_page_path, method: :put do |f| %>
|
|
5
|
+
<%= f.text_field :title, class: "block w-full rounded-md border-0 p-2 text-gray-900 ring-1 ring-inset ring-mid placeholder:text-gray-300 focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
|
6
|
+
<%= f.text_field :template, value: template.name, readonly: true, class: "read-only:bg-gray-100 block w-full rounded-md border-0 p-2 text-gray-900 ring-1 ring-inset ring-mid placeholder:text-gray-300 focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
|
7
|
+
<%= f.select :status, options_for_select([["Active", "active"], ["Draft", "draft"], ["Hidden", "hidden"], ["Archived", "archived"]], selected: page.status), {}, class: "block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-mid focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
|
8
|
+
<%= f.submit "Save" %>
|
|
9
|
+
<% end %>
|
|
10
|
+
<% end %>
|
|
11
|
+
<div id="successMessage" class="hidden">
|
|
12
|
+
<%= render Panda::CMS::Admin::FlashMessageComponent.new(kind: "success", message: "This page was successfully updated!", temporary: false) %>
|
|
13
|
+
</div>
|
|
14
|
+
<div id="errorMessage" class="hidden">
|
|
15
|
+
<%= render Panda::CMS::Admin::FlashMessageComponent.new(kind: "error", message: "There was an error updating this page.", temporary: false) %>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="grid grid-cols-2 mb-4 -mt-5">
|
|
18
|
+
<div>
|
|
19
|
+
<a class="inline-block mb-2 text-sm text-black/60" target="_blank" href="<%= @page.path %>"><%= @page.path %> <i class="ml-2 fa-solid fa-arrow-up-right-from-square"></i></a>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="relative -mt-5">
|
|
22
|
+
<span class="absolute right-0"><%= render Panda::CMS::Admin::ButtonComponent.new(text: "Save Changes", action: :save_inactive, icon: "check", link: "#", size: :regular, id: "saveEditableButton") %></span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<%= content_tag :iframe, nil,
|
|
26
|
+
src: "#{page.path}?embed_id=#{page.id}",
|
|
27
|
+
class: "p-0 m-0 w-full h-full border border-slate-200",
|
|
28
|
+
style: "min-height: 500px; width: 100%; height: 100%;",
|
|
29
|
+
id: "editablePageFrame",
|
|
30
|
+
data: {
|
|
31
|
+
controller: "editor-iframe",
|
|
32
|
+
editor_iframe_page_id_value: @page.id,
|
|
33
|
+
editor_iframe_admin_path_value: "#{admin_dashboard_url}",
|
|
34
|
+
editor_iframe_autosave_value: false
|
|
35
|
+
} %>
|
|
36
|
+
<% end %>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Pages", level: 1) do |heading| %>
|
|
3
|
+
<% heading.with_button(action: :add, text: "Add Page", link: new_admin_page_path) %>
|
|
4
|
+
<% end %>
|
|
5
|
+
|
|
6
|
+
<% if root_page %>
|
|
7
|
+
<%= render Panda::CMS::Admin::TableComponent.new(term: "page", rows: root_page.self_and_descendants) do |table| %>
|
|
8
|
+
<% table.column("Name") do |page| %>
|
|
9
|
+
<div class="<%= table_indent(page) %>">
|
|
10
|
+
<%= link_to page.title, edit_admin_page_path(page), class: "block h-full w-full" %>
|
|
11
|
+
<span class="block text-xs text-black/60"><%= page.path %></span>
|
|
12
|
+
</div>
|
|
13
|
+
<% end %>
|
|
14
|
+
<% table.column("Status") { |page| render Panda::CMS::Admin::TagComponent.new(status: page.status) } %>
|
|
15
|
+
<% table.column("Last Updated") { |page| render Panda::CMS::Admin::UserActivityComponent.new(whodunnit_to: page)} %>
|
|
16
|
+
<% end %>
|
|
17
|
+
<% else %>
|
|
18
|
+
<div class="p-6 bg-error/10 text-error rounded-lg">
|
|
19
|
+
<p class="text-base">No homepage (at <code>/</code>) found. Please create a homepage to start building your site.</p>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
<% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
|
|
2
|
+
<% component.with_heading(text: "Add Page", level: 1) do |heading| %>
|
|
3
|
+
<% end %>
|
|
4
|
+
<%= panda_cms_form_with model: page, url: admin_pages_path, method: :post do |f| %>
|
|
5
|
+
<% options = nested_set_options(Panda::CMS::Page, page) { |i| "#{"-" * i.level} #{i.title} (#{i.path})" } %>
|
|
6
|
+
<div data-controller="slug">
|
|
7
|
+
<input type="hidden" value="<%= Panda::CMS::Current.root %>" data-slug-target="existing_root">
|
|
8
|
+
<%= f.select :parent_id, options, {}, { "data-slug-target": "input_select", "data-action": "change->slug#setPrePath" } %>
|
|
9
|
+
<%= f.text_field :title, { data: { "slug-target": "input_text", action: "focusout->slug#generatePath" } } %>
|
|
10
|
+
<%= f.text_field :path, { meta: t(".path.meta"), data: { prefix: Panda::CMS::Current.root, "slug-target": "output_text" } } %>
|
|
11
|
+
<%= f.collection_select :panda_cms_template_id, Panda::CMS::Template.available, :id, :name %>
|
|
12
|
+
<%= f.button "Create Page" %>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Show page
|