panda-cms 0.7.0 → 0.7.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/builds/panda.cms.css +0 -50
- data/app/components/panda/cms/admin/table_component.html.erb +1 -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 +3 -6
- data/lib/panda-cms/version.rb +1 -1
- data/lib/panda-cms.rb +5 -11
- metadata +290 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e27fb8ea71f2ea75ce8b82dc1a572e9fc6c55ee7d0e1df48b3af93228881bc0e
|
4
|
+
data.tar.gz: a58147bb5374e6bfdc70aed581c9b98048b0e3937a4037c2daae7e5b6c70ae57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6494d67893174959b47efe2276c1fe1ae27606cc268e62d1bf77566bcbf95c37cc935f2a62c0bdc0db24f62b42d2ca44cc36c8a9a2a328bee634d5e079374c1
|
7
|
+
data.tar.gz: 2d263e8ca147f3c0bc9061f5f9c7149d4705a7b16fbc1d02a7072a71884292574677a588b9dc9dbbae30d90da6e2495f6b43f319fe674b98da2076fe97a64cc7
|
@@ -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,7 +10,7 @@
|
|
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) %>
|
@@ -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
|