panda-cms 0.7.5 → 0.8.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 +4 -4
- data/app/components/panda/cms/admin/user_activity_component.html.erb +2 -2
- data/app/components/panda/cms/admin/user_activity_component.rb +2 -2
- data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
- data/app/components/panda/cms/admin/user_display_component.rb +2 -2
- data/app/components/panda/cms/rich_text_component.rb +5 -5
- data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +5 -4
- data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
- data/app/controllers/panda/cms/admin/forms_controller.rb +3 -4
- data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
- data/app/controllers/panda/cms/admin/pages_controller.rb +7 -7
- data/app/controllers/panda/cms/admin/posts_controller.rb +9 -10
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +4 -4
- data/app/controllers/panda/cms/admin/settings_controller.rb +2 -3
- data/app/controllers/panda/cms/application_controller.rb +13 -5
- data/app/controllers/panda/cms/pages_controller.rb +2 -2
- data/app/helpers/panda/cms/application_helper.rb +3 -3
- data/app/helpers/panda/cms/asset_helper.rb +14 -1
- data/app/javascript/panda/cms/application_panda_cms.js +2 -34
- data/app/javascript/panda/cms/controllers/index.js +5 -15
- data/app/models/panda/cms/block_content.rb +1 -1
- data/app/models/panda/cms/current.rb +3 -12
- data/app/models/panda/cms/post.rb +3 -3
- data/app/models/panda/cms/visit.rb +1 -1
- data/app/views/layouts/panda/cms/application.html.erb +1 -1
- data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
- data/app/views/panda/cms/admin/files/index.html.erb +1 -1
- data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
- data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
- data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
- data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
- data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
- data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
- data/app/views/panda/cms/admin/pages/new.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
- data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
- data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +3 -3
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
- data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
- data/config/initializers/panda/cms.rb +6 -3
- data/config/routes.rb +8 -16
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +3 -1
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +2 -0
- data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
- data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
- data/lib/panda/cms/asset_loader.rb +4 -4
- data/lib/panda/cms/engine.rb +42 -98
- data/lib/panda-cms/version.rb +1 -1
- data/lib/panda-cms.rb +50 -40
- data/lib/tasks/assets.rake +162 -122
- data/lib/tasks/panda/cms/migrations.rake +13 -0
- metadata +19 -36
- data/app/builders/panda/cms/form_builder.rb +0 -225
- data/app/components/panda/cms/admin/button_component.rb +0 -70
- data/app/components/panda/cms/admin/container_component.rb +0 -13
- data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
- data/app/components/panda/cms/admin/heading_component.rb +0 -46
- data/app/components/panda/cms/admin/panel_component.rb +0 -13
- data/app/components/panda/cms/admin/table_component.rb +0 -46
- data/app/components/panda/cms/admin/tag_component.rb +0 -35
- data/app/constraints/panda/cms/admin_constraint.rb +0 -21
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -44
- data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -92
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -25
- data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
- data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
- data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
- data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
- data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
- data/app/models/panda/cms/breadcrumb.rb +0 -14
- data/app/models/panda/cms/user.rb +0 -33
- data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -195
- data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
- data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -7
- data/lib/panda/cms/editor_js/blocks/alert.rb +0 -36
- data/lib/panda/cms/editor_js/blocks/base.rb +0 -35
- data/lib/panda/cms/editor_js/blocks/header.rb +0 -17
- data/lib/panda/cms/editor_js/blocks/image.rb +0 -39
- data/lib/panda/cms/editor_js/blocks/list.rb +0 -34
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -18
- data/lib/panda/cms/editor_js/blocks/quote.rb +0 -44
- data/lib/panda/cms/editor_js/blocks/table.rb +0 -52
- data/lib/panda/cms/editor_js/renderer.rb +0 -127
- data/lib/panda/cms/editor_js.rb +0 -18
- data/lib/panda/cms/editor_js_content.rb +0 -61
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module EditorJs
|
6
|
-
module Blocks
|
7
|
-
class Quote < Base
|
8
|
-
def render
|
9
|
-
text = data["text"]
|
10
|
-
caption = data["caption"]
|
11
|
-
alignment = data["alignment"] || "left"
|
12
|
-
|
13
|
-
# Build the HTML structure
|
14
|
-
html = "<figure class=\"text-#{alignment}\">" \
|
15
|
-
"<blockquote>#{wrap_text_in_p(text)}</blockquote>" \
|
16
|
-
"#{caption_element(caption)}" \
|
17
|
-
"</figure>"
|
18
|
-
|
19
|
-
# Return raw HTML - validation will be handled by the main renderer if enabled
|
20
|
-
html_safe(html)
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def wrap_text_in_p(text)
|
26
|
-
# Only wrap in <p> if it's not already wrapped
|
27
|
-
text = sanitize(text)
|
28
|
-
if text.start_with?("<p>") && text.end_with?("</p>")
|
29
|
-
text
|
30
|
-
else
|
31
|
-
"<p>#{text}</p>"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def caption_element(caption)
|
36
|
-
return "" if caption.blank?
|
37
|
-
|
38
|
-
"<figcaption>#{sanitize(caption)}</figcaption>"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Panda
|
4
|
-
module CMS
|
5
|
-
module EditorJs
|
6
|
-
module Blocks
|
7
|
-
class Table < Base
|
8
|
-
def render
|
9
|
-
content = data["content"]
|
10
|
-
with_headings = data["withHeadings"]
|
11
|
-
|
12
|
-
html_safe(<<~HTML)
|
13
|
-
<div class="overflow-x-auto">
|
14
|
-
<table class="min-w-full">
|
15
|
-
#{render_rows(content, with_headings)}
|
16
|
-
</table>
|
17
|
-
</div>
|
18
|
-
HTML
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def render_rows(content, with_headings)
|
24
|
-
rows = []
|
25
|
-
index = 0
|
26
|
-
|
27
|
-
while index < content.length
|
28
|
-
rows << if index.zero? && with_headings
|
29
|
-
render_header_row(content[index])
|
30
|
-
else
|
31
|
-
render_data_row(content[index])
|
32
|
-
end
|
33
|
-
index += 1
|
34
|
-
end
|
35
|
-
|
36
|
-
rows.join("\n")
|
37
|
-
end
|
38
|
-
|
39
|
-
def render_header_row(row)
|
40
|
-
cells = row.map { |cell| "<th>#{sanitize(cell)}</th>" }
|
41
|
-
"<tr>#{cells.join}</tr>"
|
42
|
-
end
|
43
|
-
|
44
|
-
def render_data_row(row)
|
45
|
-
cells = row.map { |cell| "<td>#{sanitize(cell)}</td>" }
|
46
|
-
"<tr>#{cells.join}</tr>"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,127 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sanitize"
|
4
|
-
|
5
|
-
module Panda
|
6
|
-
module CMS
|
7
|
-
module EditorJs
|
8
|
-
class Renderer
|
9
|
-
attr_reader :content, :options, :custom_renderers, :cache_store
|
10
|
-
|
11
|
-
def initialize(content, options = {})
|
12
|
-
@content = content
|
13
|
-
@options = options
|
14
|
-
@custom_renderers = options.delete(:custom_renderers) || {}
|
15
|
-
@cache_store = options.delete(:cache_store) || Rails.cache
|
16
|
-
@validate_html = options.delete(:validate_html) || false
|
17
|
-
end
|
18
|
-
|
19
|
-
def render
|
20
|
-
return "" if content.nil? || content == {}
|
21
|
-
return content.to_s unless content.is_a?(Hash) && content["blocks"].is_a?(Array)
|
22
|
-
|
23
|
-
rendered = content["blocks"].map do |block|
|
24
|
-
render_block(block)
|
25
|
-
end.join("\n")
|
26
|
-
|
27
|
-
rendered = @validate_html ? validate_html(rendered) : rendered
|
28
|
-
rendered.presence || ""
|
29
|
-
end
|
30
|
-
|
31
|
-
def section(blocks)
|
32
|
-
return "" if blocks.nil? || blocks.empty?
|
33
|
-
|
34
|
-
content = {"blocks" => blocks}
|
35
|
-
rendered = self.class.new(content, options).render
|
36
|
-
|
37
|
-
"<section class=\"content-section\">#{rendered}</section>"
|
38
|
-
end
|
39
|
-
|
40
|
-
def article(blocks, title: nil)
|
41
|
-
return "" if blocks.nil? || blocks.empty?
|
42
|
-
|
43
|
-
content = {"blocks" => blocks}
|
44
|
-
rendered = self.class.new(content, options).render
|
45
|
-
|
46
|
-
[
|
47
|
-
"<article>",
|
48
|
-
(title ? "<h1>#{title}</h1>" : ""),
|
49
|
-
rendered,
|
50
|
-
"</article>"
|
51
|
-
].join("\n")
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def validate_html(html)
|
57
|
-
return "" if html.blank?
|
58
|
-
|
59
|
-
begin
|
60
|
-
# For quote blocks, only allow specific content
|
61
|
-
if html.include?('<figure class="text-left">')
|
62
|
-
# Only allow the exact valid content we expect
|
63
|
-
valid_content = '<figure class="text-left"><blockquote><p>Valid HTML</p></blockquote><figcaption>Valid caption</figcaption></figure>'
|
64
|
-
return html if html.strip == valid_content.strip
|
65
|
-
|
66
|
-
return ""
|
67
|
-
end
|
68
|
-
|
69
|
-
# For other HTML, use sanitize
|
70
|
-
config = Sanitize::Config::RELAXED.dup
|
71
|
-
config[:elements] += %w[figure figcaption blockquote pre code mention math]
|
72
|
-
config[:attributes].merge!({
|
73
|
-
"figure" => ["class"],
|
74
|
-
"blockquote" => ["class"],
|
75
|
-
"p" => ["class"],
|
76
|
-
"figcaption" => ["class"]
|
77
|
-
})
|
78
|
-
|
79
|
-
sanitized = Sanitize.fragment(html, config)
|
80
|
-
(sanitized == html) ? html : ""
|
81
|
-
rescue => e
|
82
|
-
Rails.logger.error("HTML validation error: #{e.message}")
|
83
|
-
""
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def render_block_with_cache(block)
|
88
|
-
cache_key = "editor_js_block/#{block["type"]}/#{Digest::MD5.hexdigest(block["data"].to_json)}"
|
89
|
-
|
90
|
-
cache_store.fetch(cache_key) do
|
91
|
-
renderer = renderer_for(block)
|
92
|
-
renderer.render
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def renderer_for(block)
|
97
|
-
if custom_renderers[block["type"]]
|
98
|
-
custom_renderers[block["type"]].new(block["data"], options)
|
99
|
-
else
|
100
|
-
default_renderer_for(block)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def default_renderer_for(block)
|
105
|
-
renderer_class = "Panda::CMS::EditorJs::Blocks::#{block["type"].classify}".constantize
|
106
|
-
renderer_class.new(block["data"], options)
|
107
|
-
rescue NameError
|
108
|
-
Panda::CMS::EditorJs::Blocks::Base.new(block["data"], options)
|
109
|
-
end
|
110
|
-
|
111
|
-
def remove_empty_paragraphs(blocks)
|
112
|
-
blocks.reject do |block|
|
113
|
-
block["type"] == "paragraph" && block["data"]["text"].blank?
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def empty_paragraph?(block)
|
118
|
-
block["type"] == "paragraph" && block["data"]["text"].blank?
|
119
|
-
end
|
120
|
-
|
121
|
-
def render_block(block)
|
122
|
-
render_block_with_cache(block)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
data/lib/panda/cms/editor_js.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "editor_js/blocks/base"
|
4
|
-
require_relative "editor_js/blocks/alert"
|
5
|
-
require_relative "editor_js/blocks/header"
|
6
|
-
require_relative "editor_js/blocks/image"
|
7
|
-
require_relative "editor_js/blocks/list"
|
8
|
-
require_relative "editor_js/blocks/paragraph"
|
9
|
-
require_relative "editor_js/blocks/quote"
|
10
|
-
require_relative "editor_js/blocks/table"
|
11
|
-
require_relative "editor_js/renderer"
|
12
|
-
|
13
|
-
module Panda
|
14
|
-
module CMS
|
15
|
-
module EditorJs
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
|
5
|
-
module Panda
|
6
|
-
module CMS
|
7
|
-
module EditorJsContent
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
included do
|
11
|
-
include ActiveModel::Validations
|
12
|
-
include ActiveModel::Callbacks
|
13
|
-
|
14
|
-
before_save :generate_cached_content
|
15
|
-
end
|
16
|
-
|
17
|
-
def content=(value)
|
18
|
-
if value.is_a?(Hash)
|
19
|
-
super(value.to_json)
|
20
|
-
else
|
21
|
-
super
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def content
|
26
|
-
value = super
|
27
|
-
if value.is_a?(String)
|
28
|
-
begin
|
29
|
-
JSON.parse(value)
|
30
|
-
rescue JSON::ParserError
|
31
|
-
value
|
32
|
-
end
|
33
|
-
else
|
34
|
-
value
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def generate_cached_content
|
39
|
-
if content.is_a?(String)
|
40
|
-
begin
|
41
|
-
parsed_content = JSON.parse(content)
|
42
|
-
self.cached_content = if parsed_content.is_a?(Hash) && parsed_content["blocks"].present?
|
43
|
-
Panda::CMS::EditorJs::Renderer.new(parsed_content).render
|
44
|
-
else
|
45
|
-
content
|
46
|
-
end
|
47
|
-
rescue JSON::ParserError
|
48
|
-
# If it's not JSON, treat it as plain text
|
49
|
-
self.cached_content = content
|
50
|
-
end
|
51
|
-
elsif content.is_a?(Hash) && content["blocks"].present?
|
52
|
-
# Process EditorJS content
|
53
|
-
self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
|
54
|
-
else
|
55
|
-
# For any other case, store as is
|
56
|
-
self.cached_content = content.to_s
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|