panda-cms 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|