panda-cms 0.7.5 → 0.8.0

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/panda/cms/admin/user_activity_component.html.erb +2 -2
  3. data/app/components/panda/cms/admin/user_activity_component.rb +2 -2
  4. data/app/components/panda/cms/admin/user_display_component.html.erb +1 -1
  5. data/app/components/panda/cms/admin/user_display_component.rb +2 -2
  6. data/app/components/panda/cms/rich_text_component.rb +5 -5
  7. data/app/controllers/panda/cms/admin/base_controller.rb +18 -0
  8. data/app/controllers/panda/cms/admin/block_contents_controller.rb +1 -2
  9. data/app/controllers/panda/cms/admin/dashboard_controller.rb +5 -4
  10. data/app/controllers/panda/cms/admin/files_controller.rb +1 -3
  11. data/app/controllers/panda/cms/admin/forms_controller.rb +3 -4
  12. data/app/controllers/panda/cms/admin/menus_controller.rb +2 -3
  13. data/app/controllers/panda/cms/admin/pages_controller.rb +7 -7
  14. data/app/controllers/panda/cms/admin/posts_controller.rb +9 -10
  15. data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +4 -4
  16. data/app/controllers/panda/cms/admin/settings_controller.rb +2 -3
  17. data/app/controllers/panda/cms/application_controller.rb +13 -5
  18. data/app/controllers/panda/cms/pages_controller.rb +2 -2
  19. data/app/helpers/panda/cms/application_helper.rb +3 -3
  20. data/app/helpers/panda/cms/asset_helper.rb +14 -1
  21. data/app/javascript/panda/cms/application_panda_cms.js +2 -34
  22. data/app/javascript/panda/cms/controllers/index.js +5 -15
  23. data/app/models/panda/cms/block_content.rb +1 -1
  24. data/app/models/panda/cms/current.rb +3 -12
  25. data/app/models/panda/cms/post.rb +3 -3
  26. data/app/models/panda/cms/visit.rb +1 -1
  27. data/app/views/layouts/panda/cms/application.html.erb +1 -1
  28. data/app/views/panda/cms/admin/dashboard/show.html.erb +2 -2
  29. data/app/views/panda/cms/admin/files/index.html.erb +1 -1
  30. data/app/views/panda/cms/admin/forms/index.html.erb +4 -4
  31. data/app/views/panda/cms/admin/forms/new.html.erb +2 -2
  32. data/app/views/panda/cms/admin/forms/show.html.erb +1 -1
  33. data/app/views/panda/cms/admin/menus/index.html.erb +4 -4
  34. data/app/views/panda/cms/admin/pages/edit.html.erb +6 -6
  35. data/app/views/panda/cms/admin/pages/index.html.erb +5 -5
  36. data/app/views/panda/cms/admin/pages/new.html.erb +2 -2
  37. data/app/views/panda/cms/admin/posts/_form.html.erb +1 -1
  38. data/app/views/panda/cms/admin/posts/edit.html.erb +2 -2
  39. data/app/views/panda/cms/admin/posts/index.html.erb +5 -5
  40. data/app/views/panda/cms/admin/posts/new.html.erb +1 -1
  41. data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +1 -1
  42. data/app/views/panda/cms/admin/settings/index.html.erb +3 -3
  43. data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +3 -3
  44. data/app/views/panda/cms/admin/shared/_flash.html.erb +1 -1
  45. data/app/views/panda/cms/admin/shared/_sidebar.html.erb +8 -8
  46. data/config/initializers/panda/cms.rb +6 -3
  47. data/config/routes.rb +8 -16
  48. data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +3 -1
  49. data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +2 -0
  50. data/db/migrate/20250809231125_migrate_users_to_panda_core.rb +111 -0
  51. data/db/migrate/20250811111000_make_post_user_references_nullable.rb +11 -0
  52. data/lib/panda/cms/asset_loader.rb +4 -4
  53. data/lib/panda/cms/engine.rb +42 -98
  54. data/lib/panda-cms/version.rb +1 -1
  55. data/lib/panda-cms.rb +50 -40
  56. data/lib/tasks/assets.rake +162 -122
  57. data/lib/tasks/panda/cms/migrations.rake +13 -0
  58. metadata +19 -36
  59. data/app/builders/panda/cms/form_builder.rb +0 -225
  60. data/app/components/panda/cms/admin/button_component.rb +0 -70
  61. data/app/components/panda/cms/admin/container_component.rb +0 -13
  62. data/app/components/panda/cms/admin/flash_message_component.rb +0 -47
  63. data/app/components/panda/cms/admin/heading_component.rb +0 -46
  64. data/app/components/panda/cms/admin/panel_component.rb +0 -13
  65. data/app/components/panda/cms/admin/table_component.rb +0 -46
  66. data/app/components/panda/cms/admin/tag_component.rb +0 -35
  67. data/app/constraints/panda/cms/admin_constraint.rb +0 -21
  68. data/app/controllers/panda/cms/admin/my_profile_controller.rb +0 -44
  69. data/app/controllers/panda/cms/admin/sessions_controller.rb +0 -92
  70. data/app/javascript/panda/cms/controllers/theme_form_controller.js +0 -25
  71. data/app/javascript/panda/cms/editor/css_extractor.js +0 -80
  72. data/app/javascript/panda/cms/editor/editor_js_config.js +0 -306
  73. data/app/javascript/panda/cms/editor/editor_js_initializer.js +0 -334
  74. data/app/javascript/panda/cms/editor/plain_text_editor.js +0 -110
  75. data/app/javascript/panda/cms/editor/resource_loader.js +0 -204
  76. data/app/javascript/panda/cms/editor/rich_text_editor.js +0 -162
  77. data/app/models/panda/cms/breadcrumb.rb +0 -14
  78. data/app/models/panda/cms/user.rb +0 -33
  79. data/app/services/panda/cms/html_to_editor_js_converter.rb +0 -195
  80. data/app/views/panda/cms/admin/my_profile/edit.html.erb +0 -35
  81. data/app/views/panda/cms/admin/sessions/new.html.erb +0 -17
  82. data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +0 -7
  83. data/lib/panda/cms/editor_js/blocks/alert.rb +0 -36
  84. data/lib/panda/cms/editor_js/blocks/base.rb +0 -35
  85. data/lib/panda/cms/editor_js/blocks/header.rb +0 -17
  86. data/lib/panda/cms/editor_js/blocks/image.rb +0 -39
  87. data/lib/panda/cms/editor_js/blocks/list.rb +0 -34
  88. data/lib/panda/cms/editor_js/blocks/paragraph.rb +0 -18
  89. data/lib/panda/cms/editor_js/blocks/quote.rb +0 -44
  90. data/lib/panda/cms/editor_js/blocks/table.rb +0 -52
  91. data/lib/panda/cms/editor_js/renderer.rb +0 -127
  92. data/lib/panda/cms/editor_js.rb +0 -18
  93. data/lib/panda/cms/editor_js_content.rb +0 -61
@@ -1,162 +0,0 @@
1
- import EditorJS from "@editorjs/editorjs"
2
- import Paragraph from "@editorjs/paragraph"
3
- import Header from "@editorjs/header"
4
- import List from "@editorjs/list"
5
- import Quote from "@editorjs/quote"
6
- import Table from "@editorjs/table"
7
- import NestedList from "@editorjs/nested-list"
8
-
9
- export default class RichTextEditor {
10
- constructor(element, iframe) {
11
- this.element = element
12
- this.iframe = iframe
13
- this.editor = null
14
- this.initialized = false
15
- this.initialize()
16
- }
17
-
18
- async initialize() {
19
- if (this.initialized) return
20
- console.debug("[Panda CMS] Initializing EditorJS")
21
-
22
- try {
23
- let content = this.element.dataset.editableContent || ""
24
- let previousData = this.element.dataset.editablePreviousData || ""
25
- console.debug("[Panda CMS] Initial content:", content)
26
- console.debug("[Panda CMS] Previous data:", previousData)
27
-
28
- let parsedContent
29
- if (previousData) {
30
- try {
31
- // Try to decode base64 first
32
- const decodedData = atob(previousData)
33
- console.debug("[Panda CMS] Decoded base64 data:", decodedData)
34
- parsedContent = JSON.parse(decodedData)
35
- console.debug("[Panda CMS] Successfully parsed base64 data:", parsedContent)
36
- } catch (e) {
37
- console.debug("[Panda CMS] Not base64 encoded or invalid, trying direct JSON parse:", e)
38
- try {
39
- parsedContent = JSON.parse(previousData)
40
- console.debug("[Panda CMS] Successfully parsed JSON data:", parsedContent)
41
- } catch (e2) {
42
- console.error("[Panda CMS] Failed to parse previous data:", e2)
43
- parsedContent = this.getDefaultContent()
44
- }
45
- }
46
- } else if (content) {
47
- try {
48
- parsedContent = JSON.parse(content)
49
- console.debug("[Panda CMS] Successfully parsed content:", parsedContent)
50
- } catch (e) {
51
- console.error("[Panda CMS] Failed to parse content:", e)
52
- parsedContent = this.getDefaultContent()
53
- }
54
- } else {
55
- parsedContent = this.getDefaultContent()
56
- }
57
-
58
- // Create holder element before initialization
59
- const holderId = `editor-${Math.random().toString(36).substr(2, 9)}`
60
- const holderElement = document.createElement("div")
61
- holderElement.id = holderId
62
- holderElement.className = "editor-js-holder codex-editor"
63
-
64
- // Clear any existing content and append holder
65
- this.element.textContent = ""
66
- this.element.appendChild(holderElement)
67
-
68
- // Initialize EditorJS
69
- this.editor = new EditorJS({
70
- holder: holderId,
71
- data: parsedContent,
72
- placeholder: "Click to start writing...",
73
- tools: {
74
- paragraph: {
75
- class: Paragraph,
76
- inlineToolbar: true
77
- },
78
- header: {
79
- class: Header,
80
- inlineToolbar: true
81
- },
82
- list: {
83
- class: NestedList,
84
- inlineToolbar: true,
85
- config: {
86
- defaultStyle: 'unordered',
87
- enableLineBreaks: true
88
- }
89
- },
90
- quote: {
91
- class: Quote,
92
- inlineToolbar: true
93
- },
94
- table: {
95
- class: Table,
96
- inlineToolbar: true
97
- }
98
- },
99
- onChange: () => {
100
- this.save()
101
- }
102
- })
103
-
104
- await this.editor.isReady
105
- this.initialized = true
106
- console.debug("[Panda CMS] EditorJS initialized successfully")
107
- } catch (error) {
108
- console.error("[Panda CMS] Error initializing EditorJS:", error)
109
- }
110
- }
111
-
112
- getDefaultContent() {
113
- return {
114
- time: Date.now(),
115
- blocks: [
116
- {
117
- type: "paragraph",
118
- data: {
119
- text: ""
120
- }
121
- }
122
- ],
123
- version: "2.28.2"
124
- }
125
- }
126
-
127
- async save() {
128
- if (!this.editor) return null
129
-
130
- try {
131
- const savedData = await this.editor.save()
132
- const jsonString = JSON.stringify(savedData)
133
- // Store both base64 and regular JSON
134
- this.element.dataset.editablePreviousData = btoa(jsonString)
135
- this.element.dataset.editableContent = jsonString
136
- return jsonString
137
- } catch (error) {
138
- console.error("[Panda CMS] Error saving EditorJS content:", error)
139
- return null
140
- }
141
- }
142
-
143
- async clear() {
144
- if (!this.editor) return
145
-
146
- try {
147
- await this.editor.clear()
148
- this.element.dataset.editablePreviousData = ""
149
- this.element.dataset.editableContent = ""
150
- } catch (error) {
151
- console.error("[Panda CMS] Error clearing EditorJS content:", error)
152
- }
153
- }
154
-
155
- destroy() {
156
- if (this.editor) {
157
- this.editor.destroy()
158
- this.editor = null
159
- this.initialized = false
160
- }
161
- }
162
- }
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- class Breadcrumb
6
- attr_reader :name, :path
7
-
8
- def initialize(name, path)
9
- @name = name
10
- @path = path
11
- end
12
- end
13
- end
14
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- class User < ApplicationRecord
6
- validates :firstname, presence: true
7
- validates :lastname, presence: true
8
- validates :email, presence: true, uniqueness: true
9
-
10
- before_save :downcase_email
11
-
12
- scope :admin, -> { where(admin: true) }
13
-
14
- def is_admin?
15
- admin
16
- end
17
-
18
- def name
19
- "#{firstname} #{lastname}"
20
- end
21
-
22
- def self.for_select_list(scope = :all, order = {firstname: :asc, lastname: :asc})
23
- Panda::CMS::User.send(scope).order(order).map { |u| [u.name, u.id] }
24
- end
25
-
26
- private
27
-
28
- def downcase_email
29
- self.email = email.to_s.downcase
30
- end
31
- end
32
- end
33
- end
@@ -1,195 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- class HtmlToEditorJsConverter
6
- class ConversionError < StandardError; end
7
-
8
- def self.convert(html)
9
- return {} if html.blank?
10
-
11
- # If it's already in EditorJS format, return as is
12
- return html if html.is_a?(Hash) && (html["blocks"].present? || html[:blocks].present?)
13
-
14
- begin
15
- # Parse the HTML content
16
- doc = Nokogiri::HTML.fragment(html.to_s)
17
- raise ConversionError, "Failed to parse HTML content" unless doc
18
-
19
- blocks = []
20
- current_text = ""
21
-
22
- doc.children.each do |node|
23
- case node.name
24
- when "h1", "h2", "h3", "h4", "h5", "h6"
25
- # Add any accumulated text as a paragraph before the header
26
- if current_text.present?
27
- blocks << create_paragraph_block(current_text)
28
- current_text = ""
29
- end
30
-
31
- blocks << {
32
- "type" => "header",
33
- "data" => {
34
- "text" => node.text.strip,
35
- "level" => node.name[1].to_i
36
- }
37
- }
38
- when "p", "div"
39
- # Add any accumulated text first
40
- if current_text.present?
41
- blocks << create_paragraph_block(current_text)
42
- current_text = ""
43
- end
44
-
45
- if node.name == "div"
46
- # Process div children separately
47
- node.children.each do |child|
48
- case child.name
49
- when "h1", "h2", "h3", "h4", "h5", "h6"
50
- blocks << {
51
- "type" => "header",
52
- "data" => {
53
- "text" => child.text.strip,
54
- "level" => child.name[1].to_i
55
- }
56
- }
57
- when "p"
58
- text = process_inline_elements(child)
59
- paragraphs = text.split(%r{<br\s*/?>\s*<br\s*/?>}).map(&:strip)
60
- paragraphs.each do |paragraph|
61
- blocks << create_paragraph_block(paragraph) if paragraph.present?
62
- end
63
- when "ul", "ol"
64
- items = child.css("li").map { |li| process_inline_elements(li) }
65
- next if items.empty?
66
-
67
- blocks << {
68
- "type" => "list",
69
- "data" => {
70
- "style" => (child.name == "ul") ? "unordered" : "ordered",
71
- "items" => items
72
- }
73
- }
74
- when "blockquote"
75
- blocks << {
76
- "type" => "quote",
77
- "data" => {
78
- "text" => process_inline_elements(child),
79
- "caption" => "",
80
- "alignment" => "left"
81
- }
82
- }
83
- when "text"
84
- text = child.text.strip
85
- current_text += text if text.present?
86
- end
87
- end
88
- else
89
- # Handle p with nested content
90
- text = process_inline_elements(node)
91
- paragraphs = text.split(%r{<br\s*/?>\s*<br\s*/?>}).map(&:strip)
92
- paragraphs.each do |paragraph|
93
- blocks << create_paragraph_block(paragraph) if paragraph.present?
94
- end
95
- end
96
- when "br"
97
- current_text += "\n\n"
98
- when "text"
99
- text = node.text.strip
100
- current_text += text if text.present?
101
- when "ul", "ol"
102
- # Add any accumulated text first
103
- if current_text.present?
104
- blocks << create_paragraph_block(current_text)
105
- current_text = ""
106
- end
107
-
108
- items = node.css("li").map { |li| process_inline_elements(li) }
109
- next if items.empty?
110
-
111
- blocks << {
112
- "type" => "list",
113
- "data" => {
114
- "style" => (node.name == "ul") ? "unordered" : "ordered",
115
- "items" => items
116
- }
117
- }
118
- when "blockquote"
119
- # Add any accumulated text first
120
- if current_text.present?
121
- blocks << create_paragraph_block(current_text)
122
- current_text = ""
123
- end
124
-
125
- blocks << {
126
- "type" => "quote",
127
- "data" => {
128
- "text" => process_inline_elements(node),
129
- "caption" => "",
130
- "alignment" => "left"
131
- }
132
- }
133
- end
134
- end
135
-
136
- # Add any remaining text
137
- blocks << create_paragraph_block(current_text) if current_text.present?
138
-
139
- # Return the complete EditorJS structure
140
- {
141
- "time" => Time.current.to_i * 1000,
142
- "blocks" => blocks,
143
- "version" => "2.28.2"
144
- }
145
- rescue => e
146
- Rails.logger.error "HTML to EditorJS conversion failed: #{e.message}"
147
- Rails.logger.error e.backtrace.join("\n")
148
- raise ConversionError, "Failed to convert HTML to EditorJS format: #{e.message}"
149
- end
150
- end
151
-
152
- def self.create_paragraph_block(text)
153
- {
154
- "type" => "paragraph",
155
- "data" => {
156
- "text" => text.strip
157
- }
158
- }
159
- end
160
-
161
- def self.process_inline_elements(node)
162
- result = ""
163
- node.children.each do |child|
164
- case child.name
165
- when "br"
166
- result += "<br>"
167
- when "text"
168
- result += child.text
169
- when "strong", "b"
170
- result += "<b>#{child.text}</b>"
171
- when "em", "i"
172
- result += "<i>#{child.text}</i>"
173
- when "a"
174
- href = child["href"]
175
- text = child.text.strip
176
- # Handle email links specially
177
- if href&.start_with?("mailto:")
178
- email = href.sub("mailto:", "")
179
- result += "<a href=\"mailto:#{email}\">#{text}</a>"
180
- else
181
- result += "<a href=\"#{href}\">#{text}</a>"
182
- end
183
- else
184
- result += if child.text?
185
- child.text
186
- else
187
- child.to_html
188
- end
189
- end
190
- end
191
- result.strip
192
- end
193
- end
194
- end
195
- end
@@ -1,35 +0,0 @@
1
- <%= render Panda::CMS::Admin::ContainerComponent.new do |component| %>
2
- <% component.with_heading(text: "My Profile", level: 1) %>
3
-
4
- <%= panda_cms_form_with model: user,
5
- url: admin_my_profile_path,
6
- method: :patch,
7
- data: { controller: "theme-form" } do |f| %>
8
- <% if user.errors.any? %>
9
- <div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-md">
10
- <div class="text-sm text-red-600">
11
- <% user.errors.full_messages.each do |message| %>
12
- <p><%= message %></p>
13
- <% end %>
14
- </div>
15
- </div>
16
- <% end %>
17
-
18
- <div class="space-y-4">
19
- <%= f.text_field :firstname %>
20
-
21
- <%= f.text_field :lastname %>
22
-
23
- <%= f.email_field :email %>
24
-
25
- <%= f.select :current_theme,
26
- [["Default", "default"], ["Sky", "sky"]],
27
- { label: "Theme" },
28
- data: { action: "change->theme-form#updateTheme" } %>
29
- </div>
30
-
31
- <%= f.submit "Update Profile",
32
- class: "btn btn-primary mt-6",
33
- data: { disable_with: "Saving..." } %>
34
- <% end %>
35
- <% end %>
@@ -1,17 +0,0 @@
1
- <div class="flex flex-col justify-center py-12 px-6 min-h-full text-center lg:px-8">
2
- <div class="text-center sm:mx-auto sm:w-full sm:max-w-sm">
3
- <img src="/panda-cms-assets/panda-nav.png" class="py-2 mx-auto w-auto h-32">
4
- <h2 class="mt-10 mb-6 text-2xl font-bold text-center text-white"><%= t("panda.cms.admin.sessions.new.title") %></h2>
5
- </div>
6
- <% @providers.each do |provider| %>
7
- <div class="mt-4 text-center sm:mx-auto sm:w-full sm:max-w-sm">
8
- <%= form_tag "#{Panda::CMS.root_path}/auth/#{provider}", method: "post", data: {turbo: false} do %>
9
- <input type="hidden" name="redirect_uri" value="<%= admin_login_callback_url(provider: provider) %>">
10
- <button type="submit" id="button-sign-in-<%= provider %>" class="inline-flex gap-x-2 items-center py-2.5 px-3.5 mx-auto mb-4 bg-white rounded-md border min-w-56 border-neutral-400">
11
- <i class="fa-brands fa-<%= provider %> text-xl mr-1"></i>
12
- <%= t("panda.cms.authentication.sign_in_with", provider: t("panda.cms.authentication.providers.#{provider}")) %>
13
- </button>
14
- <% end %>
15
- </div>
16
- <% end %>
17
- </div>
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class AddCurrentThemeToPandaCMSUsers < ActiveRecord::Migration[8.0]
4
- def change
5
- add_column :panda_cms_users, :current_theme, :string, default: "default"
6
- end
7
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class Alert < Base
8
- def render
9
- message = sanitize(data["message"])
10
- type = data["type"] || "primary"
11
-
12
- html_safe(
13
- "<div class=\"#{alert_classes(type)} p-4 mb-4 rounded-lg\">" \
14
- "#{message}" \
15
- "</div>"
16
- )
17
- end
18
-
19
- private
20
-
21
- def alert_classes(type)
22
- case type
23
- when "primary" then "bg-blue-100 text-blue-800"
24
- when "secondary" then "bg-gray-100 text-gray-800"
25
- when "success" then "bg-green-100 text-green-800"
26
- when "danger" then "bg-red-100 text-red-800"
27
- when "warning" then "bg-yellow-100 text-yellow-800"
28
- when "info" then "bg-indigo-100 text-indigo-800"
29
- else "bg-blue-100 text-blue-800"
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class Base
8
- include ActionView::Helpers::SanitizeHelper
9
- include ActionView::Helpers::TagHelper
10
-
11
- attr_reader :data, :options
12
-
13
- def initialize(data, options = {})
14
- @data = data
15
- @options = options
16
- end
17
-
18
- def render
19
- ""
20
- end
21
-
22
- protected
23
-
24
- def html_safe(content)
25
- content.html_safe
26
- end
27
-
28
- def sanitize(text)
29
- Rails::Html::SafeListSanitizer.new.sanitize(text, tags: %w[b i u a code])
30
- end
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class Header < Base
8
- def render
9
- content = sanitize(data["text"])
10
- level = data["level"] || 2
11
- html_safe("<h#{level}>#{content}</h#{level}>")
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class Image < Base
8
- def render
9
- url = data["url"]
10
- caption = sanitize(data["caption"])
11
- with_border = data["withBorder"]
12
- with_background = data["withBackground"]
13
- stretched = data["stretched"]
14
-
15
- css_classes = ["prose"]
16
- css_classes << "border" if with_border
17
- css_classes << "bg-gray-100" if with_background
18
- css_classes << "w-full" if stretched
19
-
20
- html_safe(<<~HTML)
21
- <figure class="#{css_classes.join(" ")}">
22
- <img src="#{url}" alt="#{caption}" />
23
- #{caption_element(caption)}
24
- </figure>
25
- HTML
26
- end
27
-
28
- private
29
-
30
- def caption_element(caption)
31
- return "" if caption.blank?
32
-
33
- "<figcaption>#{caption}</figcaption>"
34
- end
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class List < Base
8
- def render
9
- list_type = (data["style"] == "ordered") ? "ol" : "ul"
10
- html_safe(
11
- "<#{list_type}>" \
12
- "#{render_items(data["items"])}" \
13
- "</#{list_type}>"
14
- )
15
- end
16
-
17
- private
18
-
19
- def render_items(items)
20
- items.map do |item|
21
- content = item.is_a?(Hash) ? item["content"] : item
22
- nested = (item.is_a?(Hash) && item["items"].present?) ? render_nested(item["items"]) : ""
23
- "<li>#{sanitize(content)}#{nested}</li>"
24
- end.join
25
- end
26
-
27
- def render_nested(items)
28
- self.class.new({"items" => items, "style" => data["style"]}).render
29
- end
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Panda
4
- module CMS
5
- module EditorJs
6
- module Blocks
7
- class Paragraph < Base
8
- def render
9
- content = sanitize(data["text"])
10
- return "" if content.blank?
11
-
12
- html_safe("<p>#{content}</p>")
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end