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
@@ -1,46 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Panda
|
2
4
|
module CMS
|
3
5
|
module Admin
|
4
6
|
module PostsHelper
|
5
7
|
def editor_content_for(post, preserved_content = nil)
|
6
|
-
|
7
|
-
Rails.logger.debug "Preserved content: #{preserved_content.inspect}"
|
8
|
-
Rails.logger.debug "Post content: #{post.content.inspect}"
|
8
|
+
content = preserved_content || post.content
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
# Return empty structure if no content
|
11
|
+
json_content = if content.blank?
|
12
|
+
{blocks: []}.to_json
|
13
|
+
# If content is already JSON string, use it
|
14
|
+
elsif content.is_a?(String) && valid_json?(content)
|
15
|
+
content
|
16
|
+
# If it's a hash, convert to JSON
|
17
|
+
elsif content.is_a?(Hash)
|
18
|
+
content.to_json
|
19
|
+
# Default to empty structure
|
18
20
|
else
|
19
|
-
|
21
|
+
{blocks: []}.to_json
|
20
22
|
end
|
21
23
|
|
22
|
-
|
24
|
+
# Base64 encode the JSON content
|
25
|
+
Base64.strict_encode64(json_content)
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
content = if content.blank? || !content.is_a?(Hash) || !content.key?("blocks")
|
26
|
-
Rails.logger.debug "Creating new editor content structure"
|
27
|
-
{
|
28
|
-
time: Time.now.to_i * 1000,
|
29
|
-
blocks: [],
|
30
|
-
version: "2.28.2"
|
31
|
-
}
|
32
|
-
else
|
33
|
-
# Ensure we have all required fields
|
34
|
-
Rails.logger.debug "Ensuring required fields are present"
|
35
|
-
content["time"] ||= Time.now.to_i * 1000
|
36
|
-
content["version"] ||= "2.28.2"
|
37
|
-
content["blocks"] ||= []
|
38
|
-
content
|
39
|
-
end
|
28
|
+
private
|
40
29
|
|
41
|
-
|
42
|
-
|
43
|
-
|
30
|
+
def valid_json?(string)
|
31
|
+
JSON.parse(string)
|
32
|
+
true
|
33
|
+
rescue JSON::ParserError
|
34
|
+
false
|
44
35
|
end
|
45
36
|
end
|
46
37
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Panda
|
2
|
+
module CMS
|
3
|
+
module PostsHelper
|
4
|
+
def display_post_path(post)
|
5
|
+
# Unescape the path for display purposes
|
6
|
+
CGI.unescape(post.slug)
|
7
|
+
end
|
8
|
+
|
9
|
+
def posts_months_menu
|
10
|
+
Rails.cache.fetch("panda_cms_posts_months_menu", expires_in: 1.hour) do
|
11
|
+
Panda::CMS::Post
|
12
|
+
.where(status: :active)
|
13
|
+
.select(
|
14
|
+
Arel.sql("DATE_TRUNC('month', published_at) as month"),
|
15
|
+
Arel.sql("COUNT(*) as post_count")
|
16
|
+
)
|
17
|
+
.group(Arel.sql("DATE_TRUNC('month', published_at)"))
|
18
|
+
.order(Arel.sql("DATE_TRUNC('month', published_at) DESC"))
|
19
|
+
.map do |result|
|
20
|
+
date = result.month
|
21
|
+
{
|
22
|
+
year: date.strftime("%Y"),
|
23
|
+
month: date.strftime("%m"),
|
24
|
+
month_name: "#{date.strftime("%B")} #{date.year}",
|
25
|
+
post_count: result.post_count
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
import { Controller } from "@hotwired/stimulus";
|
2
|
+
import { EDITOR_JS_RESOURCES, EDITOR_JS_CSS } from "panda/cms/editor/editor_js_config";
|
3
|
+
import { ResourceLoader } from "panda/cms/editor/resource_loader";
|
2
4
|
|
3
5
|
export default class extends Controller {
|
4
6
|
static targets = ["editorContainer", "hiddenField"];
|
@@ -7,7 +9,27 @@ export default class extends Controller {
|
|
7
9
|
};
|
8
10
|
|
9
11
|
connect() {
|
10
|
-
this.
|
12
|
+
this.loadEditorResources();
|
13
|
+
}
|
14
|
+
|
15
|
+
async loadEditorResources() {
|
16
|
+
try {
|
17
|
+
// First load EditorJS core
|
18
|
+
const editorCore = EDITOR_JS_RESOURCES[0];
|
19
|
+
await ResourceLoader.loadScript(document, document.head, editorCore);
|
20
|
+
|
21
|
+
// Load CSS
|
22
|
+
await ResourceLoader.embedCSS(document, document.head, EDITOR_JS_CSS);
|
23
|
+
|
24
|
+
// Then load all tools sequentially
|
25
|
+
for (const resource of EDITOR_JS_RESOURCES.slice(1)) {
|
26
|
+
await ResourceLoader.loadScript(document, document.head, resource);
|
27
|
+
}
|
28
|
+
|
29
|
+
await this.initializeEditor();
|
30
|
+
} catch (error) {
|
31
|
+
console.error("[Panda CMS] Failed to load editor resources:", error);
|
32
|
+
}
|
11
33
|
}
|
12
34
|
|
13
35
|
async initializeEditor() {
|
@@ -26,13 +48,15 @@ export default class extends Controller {
|
|
26
48
|
const { getEditorConfig } = await import(
|
27
49
|
"panda/cms/editor/editor_js_config"
|
28
50
|
);
|
29
|
-
const config = getEditorConfig(holderId, this.getInitialContent());
|
30
51
|
|
31
|
-
|
52
|
+
// Get initial content before creating config
|
53
|
+
const initialContent = this.getInitialContent();
|
54
|
+
console.debug("[Panda CMS] Using initial content:", initialContent);
|
32
55
|
|
33
|
-
|
34
|
-
...
|
56
|
+
const config = {
|
57
|
+
...getEditorConfig(holderId, initialContent),
|
35
58
|
holder: holderId,
|
59
|
+
data: initialContent,
|
36
60
|
autofocus: false,
|
37
61
|
minHeight: 1,
|
38
62
|
logLevel: "ERROR",
|
@@ -40,26 +64,125 @@ export default class extends Controller {
|
|
40
64
|
if (!this.editor) return;
|
41
65
|
this.editor.save().then((outputData) => {
|
42
66
|
outputData.source = "editorJS";
|
43
|
-
|
67
|
+
const jsonString = JSON.stringify(outputData);
|
68
|
+
// Store both base64 and regular JSON
|
69
|
+
this.editorContainerTarget.dataset.editablePreviousData = btoa(jsonString);
|
70
|
+
this.editorContainerTarget.dataset.editableContent = jsonString;
|
71
|
+
this.hiddenFieldTarget.value = jsonString;
|
44
72
|
});
|
45
73
|
},
|
46
|
-
|
74
|
+
onReady: () => {
|
75
|
+
console.debug("[Panda CMS] Editor ready with content:", initialContent);
|
76
|
+
this.editorContainerTarget.dataset.editorInitialized = "true";
|
77
|
+
holderDiv.dataset.editorInitialized = "true";
|
78
|
+
// Add a class to indicate the editor is ready
|
79
|
+
holderDiv.classList.add("editor-ready");
|
80
|
+
// Dispatch an event when editor is ready
|
81
|
+
this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
|
82
|
+
},
|
83
|
+
tools: {
|
84
|
+
paragraph: {
|
85
|
+
class: window.Paragraph,
|
86
|
+
inlineToolbar: true
|
87
|
+
},
|
88
|
+
header: {
|
89
|
+
class: window.Header,
|
90
|
+
inlineToolbar: true
|
91
|
+
},
|
92
|
+
list: {
|
93
|
+
class: window.NestedList,
|
94
|
+
inlineToolbar: true,
|
95
|
+
config: {
|
96
|
+
defaultStyle: 'unordered',
|
97
|
+
enableLineBreaks: true
|
98
|
+
}
|
99
|
+
},
|
100
|
+
quote: {
|
101
|
+
class: window.Quote,
|
102
|
+
inlineToolbar: true
|
103
|
+
},
|
104
|
+
table: {
|
105
|
+
class: window.Table,
|
106
|
+
inlineToolbar: true
|
107
|
+
}
|
108
|
+
}
|
109
|
+
};
|
110
|
+
|
111
|
+
// Ensure EditorJS is available
|
112
|
+
const EditorJS = window.EditorJS;
|
113
|
+
if (!EditorJS) {
|
114
|
+
throw new Error("EditorJS not loaded");
|
115
|
+
}
|
116
|
+
|
117
|
+
this.editor = new EditorJS(config);
|
118
|
+
|
119
|
+
// Wait for editor to be ready
|
120
|
+
await this.editor.isReady;
|
121
|
+
console.debug("[Panda CMS] Editor initialized successfully");
|
122
|
+
this.editorContainerTarget.dataset.editorInitialized = "true";
|
123
|
+
holderDiv.dataset.editorInitialized = "true";
|
124
|
+
// Add a class to indicate the editor is ready
|
125
|
+
holderDiv.classList.add("editor-ready");
|
126
|
+
// Dispatch an event when editor is ready
|
127
|
+
this.editorContainerTarget.dispatchEvent(new CustomEvent("editor:ready"));
|
128
|
+
|
47
129
|
} catch (error) {
|
48
130
|
console.error("[Panda CMS] Editor setup failed:", error);
|
131
|
+
this.editorContainerTarget.dataset.editorInitialized = "false";
|
132
|
+
if (holderDiv) {
|
133
|
+
holderDiv.dataset.editorInitialized = "false";
|
134
|
+
holderDiv.classList.remove("editor-ready");
|
135
|
+
}
|
49
136
|
}
|
50
137
|
}
|
51
138
|
|
52
139
|
getInitialContent() {
|
53
140
|
try {
|
54
|
-
const
|
55
|
-
if (
|
56
|
-
|
57
|
-
|
141
|
+
const initialContent = this.hiddenFieldTarget.getAttribute("data-initial-content");
|
142
|
+
if (initialContent && initialContent !== "{}") {
|
143
|
+
try {
|
144
|
+
// First try to decode as base64
|
145
|
+
try {
|
146
|
+
const decodedData = atob(initialContent);
|
147
|
+
const data = JSON.parse(decodedData);
|
148
|
+
if (data.blocks) return data;
|
149
|
+
} catch (e) {
|
150
|
+
// If base64 decode fails, try direct JSON parse
|
151
|
+
const data = JSON.parse(initialContent);
|
152
|
+
if (data.blocks) return data;
|
153
|
+
}
|
154
|
+
} catch (e) {
|
155
|
+
console.error("[Panda CMS] Failed to parse content:", e);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// Try to get content from the editor container's data attributes
|
160
|
+
const previousData = this.editorContainerTarget.dataset.editablePreviousData;
|
161
|
+
const editorContent = this.editorContainerTarget.dataset.editableContent;
|
162
|
+
|
163
|
+
if (previousData) {
|
164
|
+
try {
|
165
|
+
const decodedData = atob(previousData);
|
166
|
+
const data = JSON.parse(decodedData);
|
167
|
+
if (data.blocks) return data;
|
168
|
+
} catch (e) {
|
169
|
+
console.debug("[Panda CMS] Failed to parse base64 data:", e);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
if (editorContent && editorContent !== "{}") {
|
174
|
+
try {
|
175
|
+
const data = JSON.parse(editorContent);
|
176
|
+
if (data.blocks) return data;
|
177
|
+
} catch (e) {
|
178
|
+
console.debug("[Panda CMS] Failed to parse editor content:", e);
|
179
|
+
}
|
58
180
|
}
|
59
181
|
} catch (e) {
|
60
182
|
console.warn("[Panda CMS] Could not parse initial content:", e);
|
61
183
|
}
|
62
184
|
|
185
|
+
// Return default content if nothing else works
|
63
186
|
return {
|
64
187
|
time: Date.now(),
|
65
188
|
blocks: [{ type: "paragraph", data: { text: "" } }],
|