panda-cms 0.7.0 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/panda.cms.css +0 -50
  3. data/app/components/panda/cms/admin/table_component.html.erb +4 -1
  4. data/app/components/panda/cms/code_component.rb +2 -1
  5. data/app/components/panda/cms/rich_text_component.html.erb +86 -2
  6. data/app/components/panda/cms/rich_text_component.rb +131 -20
  7. data/app/controllers/panda/cms/admin/block_contents_controller.rb +18 -7
  8. data/app/controllers/panda/cms/admin/files_controller.rb +22 -12
  9. data/app/controllers/panda/cms/admin/posts_controller.rb +33 -11
  10. data/app/controllers/panda/cms/pages_controller.rb +29 -0
  11. data/app/controllers/panda/cms/posts_controller.rb +26 -4
  12. data/app/helpers/panda/cms/admin/posts_helper.rb +23 -32
  13. data/app/helpers/panda/cms/posts_helper.rb +32 -0
  14. data/app/javascript/panda/cms/controllers/dashboard_controller.js +0 -1
  15. data/app/javascript/panda/cms/controllers/editor_form_controller.js +134 -11
  16. data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +395 -130
  17. data/app/javascript/panda/cms/controllers/slug_controller.js +33 -43
  18. data/app/javascript/panda/cms/editor/editor_js_config.js +202 -73
  19. data/app/javascript/panda/cms/editor/editor_js_initializer.js +243 -194
  20. data/app/javascript/panda/cms/editor/plain_text_editor.js +1 -1
  21. data/app/javascript/panda/cms/editor/resource_loader.js +89 -0
  22. data/app/javascript/panda/cms/editor/rich_text_editor.js +162 -0
  23. data/app/models/panda/cms/page.rb +18 -0
  24. data/app/models/panda/cms/post.rb +61 -3
  25. data/app/models/panda/cms/redirect.rb +2 -2
  26. data/app/views/panda/cms/admin/posts/_form.html.erb +15 -4
  27. data/app/views/panda/cms/admin/posts/index.html.erb +5 -3
  28. data/config/routes.rb +34 -6
  29. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +5 -0
  30. data/lib/panda/cms/editor_js_content.rb +14 -1
  31. data/lib/panda/cms/engine.rb +4 -0
  32. data/lib/panda-cms/version.rb +1 -1
  33. 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
- Rails.logger.debug "Editor content for post: #{post.inspect}"
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
- content = if preserved_content.present?
11
- # If preserved_content is a string (JSON), parse it
12
- begin
13
- preserved_content.is_a?(String) ? JSON.parse(preserved_content) : preserved_content
14
- rescue JSON::ParserError => e
15
- Rails.logger.error "Failed to parse preserved content: #{e.message}"
16
- post.content
17
- end
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
- post.content
21
+ {blocks: []}.to_json
20
22
  end
21
23
 
22
- Rails.logger.debug "Using content: #{content.inspect}"
24
+ # Base64 encode the JSON content
25
+ Base64.strict_encode64(json_content)
26
+ end
23
27
 
24
- # Ensure we have a valid editor content structure
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
- json = content.to_json
42
- Rails.logger.debug "Returning JSON: #{json}"
43
- json
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
@@ -2,6 +2,5 @@ import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  export default class extends Controller {
4
4
  connect() {
5
- console.log("Hello from Dashboard")
6
5
  }
7
6
  }
@@ -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.initializeEditor();
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
- editor_content_post;
52
+ // Get initial content before creating config
53
+ const initialContent = this.getInitialContent();
54
+ console.debug("[Panda CMS] Using initial content:", initialContent);
32
55
 
33
- this.editor = new EditorJS({
34
- ...config,
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
- this.hiddenFieldTarget.value = JSON.stringify(outputData);
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 value = this.hiddenFieldTarget.value;
55
- if (value && value !== "{}") {
56
- const data = JSON.parse(value);
57
- if (data.blocks) return data;
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: "" } }],