panda-cms 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/builds/panda.cms.css +0 -50
- data/app/components/panda/cms/admin/table_component.html.erb +4 -1
- data/app/components/panda/cms/code_component.rb +2 -1
- data/app/components/panda/cms/rich_text_component.html.erb +86 -2
- data/app/components/panda/cms/rich_text_component.rb +131 -20
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +18 -7
- data/app/controllers/panda/cms/admin/files_controller.rb +22 -12
- data/app/controllers/panda/cms/admin/posts_controller.rb +33 -11
- data/app/controllers/panda/cms/pages_controller.rb +29 -0
- data/app/controllers/panda/cms/posts_controller.rb +26 -4
- data/app/helpers/panda/cms/admin/posts_helper.rb +23 -32
- data/app/helpers/panda/cms/posts_helper.rb +32 -0
- data/app/javascript/panda/cms/controllers/dashboard_controller.js +0 -1
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +134 -11
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +395 -130
- data/app/javascript/panda/cms/controllers/slug_controller.js +33 -43
- data/app/javascript/panda/cms/editor/editor_js_config.js +202 -73
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +243 -194
- data/app/javascript/panda/cms/editor/plain_text_editor.js +1 -1
- data/app/javascript/panda/cms/editor/resource_loader.js +89 -0
- data/app/javascript/panda/cms/editor/rich_text_editor.js +162 -0
- data/app/models/panda/cms/page.rb +18 -0
- data/app/models/panda/cms/post.rb +61 -3
- data/app/models/panda/cms/redirect.rb +2 -2
- data/app/views/panda/cms/admin/posts/_form.html.erb +15 -4
- data/app/views/panda/cms/admin/posts/index.html.erb +5 -3
- data/config/routes.rb +34 -6
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -0
- data/lib/panda/cms/editor_js_content.rb +14 -1
- data/lib/panda/cms/engine.rb +4 -0
- data/lib/panda-cms/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 74ac7b8103cc0535b6bbfce15e834b7b7e6dfe033fc168ca5d31bb8615b02ede
|
4
|
+
data.tar.gz: e9c66a7a385abdbb7a6c52418c754b93d8a2fcc8afaf337fc69379c8a4c2e365
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdececd7daec565b2cb6d2ba5a0ca27c84b518a0b1178ca664c848b9078f45ca4045d749a3816e5f0cb2bbeef4af88b516fcd14f5e7617a2eff53288af86db0a
|
7
|
+
data.tar.gz: 05ddb0a8850ceb30f4bd52b596e5e6ec237d1d03c70cdc6c12d1f01dd07d83ddfa1968f6db660fe7f00bbb60e41680095c6eeb993071c727f917be3d7a39e059
|
@@ -835,56 +835,6 @@ a.block-link:after {
|
|
835
835
|
}
|
836
836
|
}
|
837
837
|
|
838
|
-
.form-input,.form-textarea,.form-select,.form-multiselect {
|
839
|
-
-webkit-appearance: none;
|
840
|
-
-moz-appearance: none;
|
841
|
-
appearance: none;
|
842
|
-
background-color: #fff;
|
843
|
-
border-color: #6b7280;
|
844
|
-
border-width: 1px;
|
845
|
-
border-radius: 0px;
|
846
|
-
padding-top: 0.5rem;
|
847
|
-
padding-right: 0.75rem;
|
848
|
-
padding-bottom: 0.5rem;
|
849
|
-
padding-left: 0.75rem;
|
850
|
-
font-size: 1rem;
|
851
|
-
line-height: 1.5rem;
|
852
|
-
--tw-shadow: 0 0 #0000;
|
853
|
-
}
|
854
|
-
|
855
|
-
.form-input:focus, .form-textarea:focus, .form-select:focus, .form-multiselect:focus {
|
856
|
-
outline: 2px solid transparent;
|
857
|
-
outline-offset: 2px;
|
858
|
-
--tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
|
859
|
-
--tw-ring-offset-width: 0px;
|
860
|
-
--tw-ring-offset-color: #fff;
|
861
|
-
--tw-ring-color: #2563eb;
|
862
|
-
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
863
|
-
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
864
|
-
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
865
|
-
border-color: #2563eb;
|
866
|
-
}
|
867
|
-
|
868
|
-
.form-select {
|
869
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
870
|
-
background-position: right 0.5rem center;
|
871
|
-
background-repeat: no-repeat;
|
872
|
-
background-size: 1.5em 1.5em;
|
873
|
-
padding-right: 2.5rem;
|
874
|
-
-webkit-print-color-adjust: exact;
|
875
|
-
print-color-adjust: exact;
|
876
|
-
}
|
877
|
-
|
878
|
-
.form-select:where([size]:not([size="1"])) {
|
879
|
-
background-image: initial;
|
880
|
-
background-position: initial;
|
881
|
-
background-repeat: unset;
|
882
|
-
background-size: initial;
|
883
|
-
padding-right: 0.75rem;
|
884
|
-
-webkit-print-color-adjust: unset;
|
885
|
-
print-color-adjust: unset;
|
886
|
-
}
|
887
|
-
|
888
838
|
.aspect-h-7 {
|
889
839
|
--tw-aspect-h: 7;
|
890
840
|
}
|
@@ -10,12 +10,15 @@
|
|
10
10
|
<% if @rows.any? %>
|
11
11
|
<div class="table-row-group">
|
12
12
|
<% @rows.each do |row| %>
|
13
|
-
<div class="table-row relative bg-mid/5 hover:bg-mid/20">
|
13
|
+
<div class="table-row relative bg-mid/5 hover:bg-mid/20" data-post-id="<%= row.id %>">
|
14
14
|
<% @columns.each do |column| %>
|
15
15
|
<div class="table-cell py-5 px-3 h-20 text-sm align-middle whitespace-nowrap border-b border-mid/20">
|
16
16
|
<%= view_context.capture(row, &column.cell) %>
|
17
17
|
</div>
|
18
18
|
<% end %>
|
19
|
+
<div class="table-cell py-5 px-3 h-20 text-sm align-middle whitespace-nowrap border-b border-mid/20">
|
20
|
+
<%= button_to "Delete", view_context.admin_post_path(row), method: :delete, class: "btn btn-danger", data: { confirm: "Are you sure?" } %>
|
21
|
+
</div>
|
19
22
|
</div>
|
20
23
|
<% end %>
|
21
24
|
</div>
|
@@ -39,10 +39,11 @@ module Panda
|
|
39
39
|
"editable-page-id": Current.page.id,
|
40
40
|
"editable-block-content-id": block_content&.id
|
41
41
|
}
|
42
|
-
@options[:class] = "block bg-yellow-50 font-mono p-2 border-2 border-yellow-700"
|
42
|
+
@options[:class] = "block bg-yellow-50 font-mono text-xs p-2 border-2 border-yellow-700"
|
43
43
|
@options[:style] = "white-space: pre-wrap;"
|
44
44
|
|
45
45
|
@options[:id] = "editor-#{block_content&.id}"
|
46
|
+
|
46
47
|
# TODO: Switch between the HTML and the preview?
|
47
48
|
content_tag(:div, code_content, @options, true)
|
48
49
|
else
|
@@ -1,6 +1,90 @@
|
|
1
|
+
<%
|
2
|
+
editor_data = if @editable
|
3
|
+
begin
|
4
|
+
content = if @content.is_a?(String)
|
5
|
+
if @content.start_with?("{")
|
6
|
+
JSON.parse(@content)
|
7
|
+
else
|
8
|
+
# If it's HTML content, convert it to EditorJS format
|
9
|
+
{
|
10
|
+
"time" => Time.current.to_i * 1000,
|
11
|
+
"blocks" => [
|
12
|
+
{
|
13
|
+
"type" => "paragraph",
|
14
|
+
"data" => {
|
15
|
+
"text" => @content.to_s
|
16
|
+
}
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"version" => "2.28.2"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
else
|
23
|
+
@content
|
24
|
+
end
|
25
|
+
|
26
|
+
content = content.deep_transform_keys(&:to_s)
|
27
|
+
content["blocks"] = (content["blocks"] || []).map do |block|
|
28
|
+
case block["type"]
|
29
|
+
when "paragraph"
|
30
|
+
block["data"] = block["data"].merge(
|
31
|
+
"text" => block["data"]["text"].to_s.presence || ""
|
32
|
+
)
|
33
|
+
when "header"
|
34
|
+
block["data"] = block["data"].merge(
|
35
|
+
"text" => block["data"]["text"].to_s.presence || "",
|
36
|
+
"level" => block["data"]["level"].to_i
|
37
|
+
)
|
38
|
+
when "list"
|
39
|
+
block["data"] = block["data"].merge(
|
40
|
+
"items" => (block["data"]["items"] || []).map { |item| item.to_s.presence || "" }
|
41
|
+
)
|
42
|
+
end
|
43
|
+
block
|
44
|
+
end
|
45
|
+
|
46
|
+
content["version"] ||= "2.28.2"
|
47
|
+
content["time"] ||= Time.current.to_i * 1000
|
48
|
+
|
49
|
+
Base64.strict_encode64(content.to_json)
|
50
|
+
rescue StandardError => e
|
51
|
+
Rails.logger.error("Error encoding editor data: #{e.message}")
|
52
|
+
Rails.logger.error("Original content: #{@content.inspect}")
|
53
|
+
# Fall back to a simple paragraph with the original content
|
54
|
+
fallback_content = {
|
55
|
+
"time" => Time.current.to_i * 1000,
|
56
|
+
"blocks" => [
|
57
|
+
{
|
58
|
+
"type" => "paragraph",
|
59
|
+
"data" => {
|
60
|
+
"text" => @content.to_s
|
61
|
+
}
|
62
|
+
}
|
63
|
+
],
|
64
|
+
"version" => "2.28.2"
|
65
|
+
}
|
66
|
+
Base64.strict_encode64(fallback_content.to_json)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
%>
|
1
70
|
<% if @editable %>
|
2
|
-
<div class="panda-cms-content"
|
71
|
+
<div class="panda-cms-content"
|
72
|
+
data-editable-previous-data="<%= editor_data %>"
|
73
|
+
data-editable-content="<%= editor_data %>"
|
74
|
+
data-editable-initialized="false"
|
75
|
+
data-editable-version="2.28.2"
|
76
|
+
data-editable-autosave="false"
|
77
|
+
data-editable-tools='{"paragraph":true,"header":true,"list":true,"quote":true,"table":true}'
|
78
|
+
id="editor-<%= @options[:id] %>"
|
79
|
+
data-editable-kind="rich_text"
|
80
|
+
data-editable-block-content-id="<%= @options[:id] %>"
|
81
|
+
data-editable-page-id="<%= @options[:data][:page_id] %>"
|
82
|
+
data-controller="editor-js"
|
83
|
+
data-editor-js-initialized-value="false"
|
84
|
+
data-editor-js-content-value="<%= editor_data %>">
|
3
85
|
</div>
|
4
86
|
<% else %>
|
5
|
-
<div class="panda-cms-content"
|
87
|
+
<div class="panda-cms-content">
|
88
|
+
<%= @content.presence || "<p></p>".html_safe %>
|
89
|
+
</div>
|
6
90
|
<% end %>
|
@@ -35,49 +35,160 @@ module Panda
|
|
35
35
|
block_content = Panda::CMS::BlockContent.create!(
|
36
36
|
block: block,
|
37
37
|
panda_cms_page_id: Current.page.id,
|
38
|
-
content:
|
38
|
+
content: empty_editor_js_content
|
39
39
|
)
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
raw_content = block_content.cached_content || block_content.content
|
43
|
+
@content = raw_content.presence || empty_editor_js_content
|
45
44
|
@options[:id] = block_content.id
|
46
45
|
|
46
|
+
# Debug log the content
|
47
|
+
Rails.logger.debug("RichTextComponent content before processing: #{@content.inspect}")
|
48
|
+
|
47
49
|
if @editable
|
48
50
|
@options[:data] = {
|
49
51
|
page_id: Current.page.id,
|
50
52
|
mode: "rich_text"
|
51
53
|
}
|
52
54
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
55
|
+
# For editable mode, always ensure we have a valid EditorJS structure
|
56
|
+
@content = if @content.blank? || @content == "{}"
|
57
|
+
empty_editor_js_content
|
58
|
+
else
|
59
|
+
begin
|
60
|
+
if @content.is_a?(String)
|
61
|
+
# Try to parse as JSON first
|
62
|
+
begin
|
63
|
+
parsed = JSON.parse(@content)
|
64
|
+
if valid_editor_js_content?(parsed)
|
65
|
+
# Ensure the content is properly structured
|
66
|
+
{
|
67
|
+
"time" => parsed["time"] || Time.current.to_i * 1000,
|
68
|
+
"blocks" => parsed["blocks"].map { |block|
|
69
|
+
{
|
70
|
+
"type" => block["type"],
|
71
|
+
"data" => block["data"].merge(
|
72
|
+
"text" => block["data"]["text"].to_s.presence || ""
|
73
|
+
),
|
74
|
+
"tunes" => block["tunes"]
|
75
|
+
}.compact
|
76
|
+
},
|
77
|
+
"version" => parsed["version"] || "2.28.2"
|
78
|
+
}
|
79
|
+
else
|
80
|
+
# If not valid EditorJS, try to convert from HTML
|
81
|
+
begin
|
82
|
+
editor_content = Panda::CMS::HtmlToEditorJsConverter.convert(@content)
|
83
|
+
if valid_editor_js_content?(editor_content)
|
84
|
+
editor_content
|
85
|
+
else
|
86
|
+
empty_editor_js_content
|
87
|
+
end
|
88
|
+
rescue Panda::CMS::HtmlToEditorJsConverter::ConversionError => e
|
89
|
+
Rails.logger.error("HTML conversion error: #{e.message}")
|
90
|
+
empty_editor_js_content
|
91
|
+
end
|
92
|
+
end
|
93
|
+
rescue JSON::ParserError => e
|
94
|
+
Rails.logger.error("JSON parse error: #{e.message}")
|
95
|
+
# Try to convert from HTML
|
96
|
+
begin
|
97
|
+
editor_content = Panda::CMS::HtmlToEditorJsConverter.convert(@content)
|
98
|
+
if valid_editor_js_content?(editor_content)
|
99
|
+
editor_content
|
100
|
+
else
|
101
|
+
empty_editor_js_content
|
102
|
+
end
|
103
|
+
rescue Panda::CMS::HtmlToEditorJsConverter::ConversionError => e
|
104
|
+
Rails.logger.error("HTML conversion error: #{e.message}")
|
105
|
+
empty_editor_js_content
|
106
|
+
end
|
107
|
+
end
|
108
|
+
else
|
109
|
+
# If it's not a string, assume it's already in the correct format
|
110
|
+
valid_editor_js_content?(@content) ? @content : empty_editor_js_content
|
111
|
+
end
|
112
|
+
rescue => e
|
113
|
+
Rails.logger.error("Content processing error: #{e.message}\nContent: #{@content.inspect}")
|
114
|
+
empty_editor_js_content
|
115
|
+
end
|
66
116
|
end
|
67
117
|
else
|
68
|
-
|
118
|
+
# For non-editable mode, handle content display
|
119
|
+
@content = if @content.blank? || @content == "{}"
|
120
|
+
"<p></p>".html_safe
|
121
|
+
else
|
122
|
+
begin
|
123
|
+
# Try to parse as JSON if it looks like EditorJS format
|
124
|
+
if @content.is_a?(String) && @content.strip.match?(/^\{.*"blocks":\s*\[.*\].*\}$/m)
|
125
|
+
parsed_content = JSON.parse(@content)
|
126
|
+
if valid_editor_js_content?(parsed_content)
|
127
|
+
# Check if it's just an empty paragraph
|
128
|
+
if parsed_content["blocks"].length == 1 &&
|
129
|
+
parsed_content["blocks"][0]["type"] == "paragraph" &&
|
130
|
+
parsed_content["blocks"][0]["data"]["text"].blank?
|
131
|
+
"<p></p>".html_safe
|
132
|
+
else
|
133
|
+
renderer = Panda::CMS::EditorJs::Renderer.new(parsed_content)
|
134
|
+
rendered = renderer.render
|
135
|
+
rendered.presence&.html_safe || "<p></p>".html_safe
|
136
|
+
end
|
137
|
+
else
|
138
|
+
process_html(@content)
|
139
|
+
end
|
140
|
+
else
|
141
|
+
process_html(@content)
|
142
|
+
end
|
143
|
+
rescue JSON::ParserError
|
144
|
+
process_html(@content)
|
145
|
+
rescue => e
|
146
|
+
Rails.logger.error("RichTextComponent render error: #{e.message}\nContent: #{@content.inspect}")
|
147
|
+
"<p></p>".html_safe
|
148
|
+
end
|
149
|
+
end
|
69
150
|
end
|
70
151
|
rescue ActiveRecord::RecordNotFound => e
|
71
152
|
raise ComponentError, "Database record not found: #{e.message}"
|
72
153
|
rescue ActiveRecord::RecordInvalid => e
|
73
154
|
raise ComponentError, "Invalid record: #{e.message}"
|
74
155
|
rescue => e
|
75
|
-
|
156
|
+
Rails.logger.error("RichTextComponent error: #{e.message}\nContent: #{@content.inspect}")
|
157
|
+
@content = @editable ? empty_editor_js_content : "<p></p>".html_safe
|
158
|
+
nil
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def empty_editor_js_content
|
164
|
+
{
|
165
|
+
time: Time.current.to_i * 1000,
|
166
|
+
blocks: [{type: "paragraph", data: {text: ""}}],
|
167
|
+
version: "2.28.2"
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
def valid_editor_js_content?(content)
|
172
|
+
content.is_a?(Hash) && content["blocks"].is_a?(Array) && content["version"].present?
|
173
|
+
rescue
|
174
|
+
false
|
175
|
+
end
|
176
|
+
|
177
|
+
def process_html(content)
|
178
|
+
return "<p></p>".html_safe if content.blank?
|
179
|
+
|
180
|
+
# If it's already HTML, just return it
|
181
|
+
if content.match?(/<[^>]+>/)
|
182
|
+
content.html_safe
|
183
|
+
else
|
184
|
+
# Wrap plain text in paragraph tags
|
185
|
+
"<p>#{content}</p>".html_safe
|
186
|
+
end
|
76
187
|
end
|
77
188
|
|
78
189
|
# Only render the component if there is some content set, or if the component is editable
|
79
190
|
def render?
|
80
|
-
|
191
|
+
true # Always render, we'll show empty content if needed
|
81
192
|
end
|
82
193
|
end
|
83
194
|
end
|
@@ -15,15 +15,26 @@ module Panda
|
|
15
15
|
Rails.logger.debug "Content params: #{params.inspect}"
|
16
16
|
Rails.logger.debug "Raw content: #{request.raw_post}"
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
# Ensure content isn't HTML escaped before saving
|
19
|
+
if params[:content].present?
|
20
|
+
# Convert ActionController::Parameters to a string if needed
|
21
|
+
content_str = params[:content].is_a?(ActionController::Parameters) ? params[:content].to_json : params[:content].to_s
|
22
|
+
content = CGI.unescapeHTML(content_str)
|
21
23
|
else
|
22
|
-
|
24
|
+
content = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
if content && @block_content.update!(content: content)
|
29
|
+
@block_content.page.touch
|
30
|
+
render json: @block_content, status: :ok
|
31
|
+
else
|
32
|
+
render json: @block_content.errors, status: :unprocessable_entity
|
33
|
+
end
|
34
|
+
rescue => e
|
35
|
+
Rails.logger.error "Error updating block content: #{e.message}"
|
36
|
+
render json: {error: e.message}, status: :unprocessable_entity
|
23
37
|
end
|
24
|
-
rescue => e
|
25
|
-
Rails.logger.error "Error updating block content: #{e.message}"
|
26
|
-
render json: {error: e.message}, status: :unprocessable_entity
|
27
38
|
end
|
28
39
|
|
29
40
|
private
|
@@ -1,20 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Panda
|
2
4
|
module CMS
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def index
|
8
|
-
redirect_to admin_dashboard_path
|
9
|
-
end
|
5
|
+
module Admin
|
6
|
+
class FilesController < ApplicationController
|
7
|
+
before_action :authenticate_admin_user!
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
def create
|
10
|
+
file = params[:image]
|
11
|
+
return render json: {success: 0} unless file
|
13
12
|
|
14
|
-
|
13
|
+
blob = ActiveStorage::Blob.create_and_upload!(
|
14
|
+
io: file,
|
15
|
+
filename: file.original_filename,
|
16
|
+
content_type: file.content_type
|
17
|
+
)
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
render json: {
|
20
|
+
success: true,
|
21
|
+
file: {
|
22
|
+
url: Rails.application.routes.url_helpers.rails_blob_url(blob, only_path: true),
|
23
|
+
name: blob.filename.to_s,
|
24
|
+
size: blob.byte_size
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
18
28
|
end
|
19
29
|
end
|
20
30
|
end
|
@@ -14,7 +14,7 @@ module Panda
|
|
14
14
|
# @type GET
|
15
15
|
# @return ActiveRecord::Collection A list of all posts
|
16
16
|
def index
|
17
|
-
posts = Panda::CMS::Post.
|
17
|
+
posts = Panda::CMS::Post.with_author.ordered
|
18
18
|
render :index, locals: {posts: posts}
|
19
19
|
end
|
20
20
|
|
@@ -48,16 +48,12 @@ module Panda
|
|
48
48
|
# POST /admin/posts
|
49
49
|
def create
|
50
50
|
@post = Panda::CMS::Post.new(post_params)
|
51
|
-
|
52
|
-
|
53
|
-
@post.content = JSON.parse(post_params[:content])
|
54
|
-
rescue
|
55
|
-
@post.content = post_params[:content]
|
56
|
-
end
|
51
|
+
@post.user_id = current_user.id
|
52
|
+
@post.content = parse_content(post_params[:content]) # Parse the content before saving
|
57
53
|
|
58
54
|
if @post.save
|
59
55
|
Rails.logger.debug "Post saved successfully"
|
60
|
-
redirect_to edit_admin_post_path(@post.admin_param),
|
56
|
+
redirect_to edit_admin_post_path(@post.admin_param), success: "The post was successfully created!"
|
61
57
|
else
|
62
58
|
Rails.logger.debug "Post save failed: #{@post.errors.full_messages.inspect}"
|
63
59
|
flash.now[:error] = @post.errors.full_messages.join(", ")
|
@@ -69,11 +65,14 @@ module Panda
|
|
69
65
|
# @type PATCH/PUT
|
70
66
|
# @return
|
71
67
|
def update
|
72
|
-
Rails.logger.debug "Updating post with params: #{post_params.inspect}"
|
73
68
|
Rails.logger.debug "Current content: #{post.content.inspect}"
|
74
69
|
Rails.logger.debug "New content from params: #{post_params[:content].inspect}"
|
75
70
|
|
76
|
-
|
71
|
+
# Parse the content before updating
|
72
|
+
update_params = post_params
|
73
|
+
update_params[:content] = parse_content(post_params[:content])
|
74
|
+
update_params[:user_id] = current_user.id
|
75
|
+
if post.update(update_params)
|
77
76
|
Rails.logger.debug "Post updated successfully"
|
78
77
|
add_breadcrumb post.title, edit_admin_post_path(post.admin_param)
|
79
78
|
redirect_to edit_admin_post_path(post.admin_param),
|
@@ -136,10 +135,33 @@ module Panda
|
|
136
135
|
:slug,
|
137
136
|
:status,
|
138
137
|
:published_at,
|
139
|
-
:
|
138
|
+
:author_id,
|
140
139
|
:content
|
141
140
|
)
|
142
141
|
end
|
142
|
+
|
143
|
+
def parse_content(content)
|
144
|
+
return {} if content.blank?
|
145
|
+
|
146
|
+
begin
|
147
|
+
# If content is already a hash, return it
|
148
|
+
return content if content.is_a?(Hash)
|
149
|
+
|
150
|
+
# If it's a string, try to parse it as JSON
|
151
|
+
parsed = JSON.parse(content)
|
152
|
+
|
153
|
+
# Ensure we have a hash with expected structure
|
154
|
+
if parsed.is_a?(Hash) && parsed["blocks"].is_a?(Array)
|
155
|
+
parsed
|
156
|
+
else
|
157
|
+
# If structure is invalid, return empty blocks structure
|
158
|
+
{"blocks" => []}
|
159
|
+
end
|
160
|
+
rescue JSON::ParserError => e
|
161
|
+
Rails.logger.error "Failed to parse post content: #{e.message}"
|
162
|
+
{"blocks" => []}
|
163
|
+
end
|
164
|
+
end
|
143
165
|
end
|
144
166
|
end
|
145
167
|
end
|
@@ -4,6 +4,7 @@ module Panda
|
|
4
4
|
include ActionView::Helpers::TagHelper
|
5
5
|
|
6
6
|
before_action :check_login_required, only: [:root, :show]
|
7
|
+
before_action :handle_redirects, only: [:root, :show]
|
7
8
|
after_action :record_visit, only: [:root, :show], unless: :ignore_visit?
|
8
9
|
|
9
10
|
def root
|
@@ -40,6 +41,24 @@ module Panda
|
|
40
41
|
|
41
42
|
private
|
42
43
|
|
44
|
+
def handle_redirects
|
45
|
+
current_path = "/" + params[:path].to_s
|
46
|
+
redirect = Panda::CMS::Redirect.find_by(origin_path: current_path)
|
47
|
+
|
48
|
+
if redirect
|
49
|
+
redirect.increment!(:visits)
|
50
|
+
|
51
|
+
# Check if the destination is also a redirect
|
52
|
+
next_redirect = Panda::CMS::Redirect.find_by(origin_path: redirect.destination_path)
|
53
|
+
if next_redirect
|
54
|
+
next_redirect.increment!(:visits)
|
55
|
+
redirect_to next_redirect.destination_path, status: redirect.status_code and return
|
56
|
+
end
|
57
|
+
|
58
|
+
redirect_to redirect.destination_path, status: redirect.status_code and return
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
43
62
|
def check_login_required
|
44
63
|
if Panda::CMS.config.require_login_to_view && !user_signed_in?
|
45
64
|
redirect_to panda_cms_maintenance_path and return
|
@@ -67,6 +86,16 @@ module Panda
|
|
67
86
|
visited_at: Time.zone.now
|
68
87
|
)
|
69
88
|
end
|
89
|
+
|
90
|
+
def create_redirect_if_path_changed
|
91
|
+
if path_changed? && path_was.present?
|
92
|
+
Panda::CMS::Redirect.create!(
|
93
|
+
origin_path: path_was,
|
94
|
+
destination_path: path,
|
95
|
+
status_code: 301
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
70
99
|
end
|
71
100
|
end
|
72
101
|
end
|
@@ -1,12 +1,34 @@
|
|
1
1
|
module Panda
|
2
2
|
module CMS
|
3
3
|
class PostsController < ApplicationController
|
4
|
+
# TODO: Change from layout rendering to standard template rendering
|
5
|
+
# inside a /panda/cms/posts/... structure in the application
|
6
|
+
def index
|
7
|
+
@posts = Panda::CMS::Post.includes(:author).order(published_at: :desc)
|
8
|
+
render inline: "", layout: Panda::CMS.config.posts[:layouts][:index]
|
9
|
+
end
|
10
|
+
|
4
11
|
def show
|
5
|
-
@
|
6
|
-
|
7
|
-
|
12
|
+
@post = if params[:year] && params[:month]
|
13
|
+
# For date-based URLs
|
14
|
+
slug = "/#{params[:year]}/#{params[:month]}/#{params[:slug]}"
|
15
|
+
Panda::CMS::Post.find_by!(slug: slug)
|
16
|
+
else
|
17
|
+
# For non-date URLs
|
18
|
+
Panda::CMS::Post.find_by!(slug: "/#{params[:slug]}")
|
19
|
+
end
|
20
|
+
render inline: "", layout: Panda::CMS.config.posts[:layouts][:show]
|
21
|
+
end
|
22
|
+
|
23
|
+
def by_month
|
24
|
+
@month = Date.new(params[:year].to_i, params[:month].to_i, 1)
|
25
|
+
@posts = Panda::CMS::Post
|
26
|
+
.where(status: :active)
|
27
|
+
.where("DATE_TRUNC('month', published_at) = ?", @month)
|
28
|
+
.includes(:author)
|
29
|
+
.ordered
|
8
30
|
|
9
|
-
render inline: "",
|
31
|
+
render inline: "", layout: Panda::CMS.config.posts[:layouts][:by_month]
|
10
32
|
end
|
11
33
|
end
|
12
34
|
end
|