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.
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: "" } }],