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
@@ -0,0 +1,162 @@
|
|
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
|
+
}
|
@@ -67,6 +67,7 @@ module Panda
|
|
67
67
|
generate_content_blocks
|
68
68
|
update_existing_menu_items
|
69
69
|
update_auto_menus
|
70
|
+
create_redirect_if_path_changed
|
70
71
|
end
|
71
72
|
|
72
73
|
def generate_content_blocks
|
@@ -91,6 +92,23 @@ module Panda
|
|
91
92
|
def update_existing_menu_items
|
92
93
|
menu_items.where.not(text: title).update_all(text: title)
|
93
94
|
end
|
95
|
+
|
96
|
+
def create_redirect_if_path_changed
|
97
|
+
return unless saved_change_to_path?
|
98
|
+
|
99
|
+
old_path = saved_changes["path"].first
|
100
|
+
new_path = saved_changes["path"].last
|
101
|
+
|
102
|
+
# Create a redirect from the old path to the new path
|
103
|
+
Panda::CMS::Redirect.create!(
|
104
|
+
origin_panda_cms_page_id: id,
|
105
|
+
destination_panda_cms_page_id: id,
|
106
|
+
status_code: 301,
|
107
|
+
visits: 0,
|
108
|
+
origin_path: old_path,
|
109
|
+
destination_path: new_path
|
110
|
+
)
|
111
|
+
end
|
94
112
|
end
|
95
113
|
end
|
96
114
|
end
|
@@ -5,6 +5,9 @@ module Panda
|
|
5
5
|
class Post < ApplicationRecord
|
6
6
|
include ::Panda::CMS::EditorJsContent
|
7
7
|
|
8
|
+
after_commit :clear_menu_cache
|
9
|
+
before_validation :format_slug
|
10
|
+
|
8
11
|
self.table_name = "panda_cms_posts"
|
9
12
|
|
10
13
|
has_paper_trail versions: {
|
@@ -12,18 +15,20 @@ module Panda
|
|
12
15
|
}
|
13
16
|
|
14
17
|
belongs_to :user, class_name: "Panda::CMS::User"
|
18
|
+
belongs_to :author, class_name: "Panda::CMS::User"
|
15
19
|
|
16
20
|
validates :title, presence: true
|
17
21
|
validates :slug,
|
18
22
|
presence: true,
|
19
23
|
uniqueness: true,
|
20
24
|
format: {
|
21
|
-
with: /\
|
22
|
-
message: "must
|
25
|
+
with: %r{\A(/\d{4}/\d{2}/[a-z0-9-]+|/[a-z0-9-]+)\z},
|
26
|
+
message: "must be in format /YYYY/MM/slug or /slug with only lowercase letters, numbers, and hyphens"
|
23
27
|
}
|
24
28
|
|
25
29
|
scope :ordered, -> { order(published_at: :desc) }
|
26
30
|
scope :with_user, -> { includes(:user) }
|
31
|
+
scope :with_author, -> { includes(:author) }
|
27
32
|
|
28
33
|
enum :status, {
|
29
34
|
active: "active",
|
@@ -33,7 +38,31 @@ module Panda
|
|
33
38
|
}
|
34
39
|
|
35
40
|
def to_param
|
36
|
-
slug
|
41
|
+
# For date-based URLs, return just the slug portion
|
42
|
+
parts = CGI.unescape(slug).delete_prefix("/").split("/")
|
43
|
+
if parts.length == 3 # year/month/slug format
|
44
|
+
parts.last
|
45
|
+
else
|
46
|
+
parts.first
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def year
|
51
|
+
return nil unless slug.match?(%r{\A/\d{4}/})
|
52
|
+
slug.split("/")[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
def month
|
56
|
+
return nil unless slug.match?(%r{\A/\d{4}/\d{2}/})
|
57
|
+
slug.split("/")[2]
|
58
|
+
end
|
59
|
+
|
60
|
+
def route_params
|
61
|
+
if year && month
|
62
|
+
{year: year, month: month, slug: to_param}
|
63
|
+
else
|
64
|
+
{slug: to_param}
|
65
|
+
end
|
37
66
|
end
|
38
67
|
|
39
68
|
def admin_param
|
@@ -55,6 +84,35 @@ module Panda
|
|
55
84
|
text = text.squish if squish
|
56
85
|
text.truncate(length).html_safe
|
57
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def clear_menu_cache
|
91
|
+
Rails.cache.delete("panda_cms_news_months")
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_slug
|
95
|
+
return if slug.blank?
|
96
|
+
|
97
|
+
# Remove any leading/trailing slashes and decode
|
98
|
+
self.slug = CGI.unescape(slug.strip.gsub(%r{^/+|/+$}, ""))
|
99
|
+
|
100
|
+
# Handle the case where we already have a properly formatted slug
|
101
|
+
if slug.match?(%r{\A\d{4}/\d{2}/[^/]+\z})
|
102
|
+
return self.slug = "/#{slug}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Handle the case where we have a date-prefixed slug (from JS)
|
106
|
+
if (match = slug.match(%r{\A(\d{4})-(\d{2})-(.+)\z}))
|
107
|
+
year, month, base_slug = match[1], match[2], match[3]
|
108
|
+
return self.slug = "/#{year}/#{month}/#{base_slug}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# For new slugs without any date structure
|
112
|
+
base_slug = slug.downcase.gsub(/[^a-z0-9-]+/, "-").gsub(/^-+|-+$/, "")
|
113
|
+
date_prefix = published_at.present? ? published_at.strftime("%Y/%m") : Time.current.strftime("%Y/%m")
|
114
|
+
self.slug = "/#{date_prefix}/#{base_slug}"
|
115
|
+
end
|
58
116
|
end
|
59
117
|
end
|
60
118
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Panda
|
2
2
|
module CMS
|
3
3
|
class Redirect < ApplicationRecord
|
4
|
-
belongs_to :origin_page, class_name: "Panda::CMS::Page", foreign_key: :origin_panda_cms_page_id
|
5
|
-
belongs_to :destination_page, class_name: "Panda::CMS::Page", foreign_key: :destination_panda_cms_page_id
|
4
|
+
belongs_to :origin_page, class_name: "Panda::CMS::Page", foreign_key: :origin_panda_cms_page_id, optional: true
|
5
|
+
belongs_to :destination_page, class_name: "Panda::CMS::Page", foreign_key: :destination_panda_cms_page_id, optional: true
|
6
6
|
|
7
7
|
validates :status_code, presence: true
|
8
8
|
validates :visits, presence: true
|
@@ -1,7 +1,18 @@
|
|
1
1
|
<%= panda_cms_form_with model: post, url: url, method: post.persisted? ? :put : :post do |f| %>
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
<div data-controller="slug" data-slug-add-date-prefix-value="true">
|
3
|
+
<%= f.text_field :title,
|
4
|
+
class: "block w-full rounded-md border-0 p-2 text-gray-900 ring-1 ring-inset ring-mid placeholder:text-gray-300 focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer",
|
5
|
+
data: {
|
6
|
+
"slug-target": "input_text",
|
7
|
+
action: "focusout->slug#generatePath"
|
8
|
+
} %>
|
9
|
+
<%= f.text_field :slug,
|
10
|
+
class: "block w-full rounded-md border-0 p-2 text-gray-900 ring-1 ring-inset ring-mid placeholder:text-gray-300 focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer",
|
11
|
+
data: {
|
12
|
+
"slug-target": "output_text"
|
13
|
+
} %>
|
14
|
+
</div>
|
15
|
+
<%= f.select :author_id, Panda::CMS::User.admin.map { |u| [u.name, u.id] }, {}, class: "block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-mid focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
5
16
|
<%= f.datetime_field :published_at, class: "block w-full rounded-md border-0 p-2 text-gray-900 ring-1 ring-inset ring-mid placeholder:text-gray-300 focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
6
17
|
<%= f.select :status, options_for_select([["Active", "active"], ["Draft", "draft"], ["Hidden", "hidden"], ["Archived", "archived"]], selected: post.status), {}, class: "block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-mid focus:ring-1 focus:ring-inset focus:ring-dark sm:leading-6 hover:pointer" %>
|
7
18
|
|
@@ -16,7 +27,7 @@
|
|
16
27
|
} %>
|
17
28
|
<div id="<%= editor_id %>"
|
18
29
|
data-editor-form-target="editorContainer"
|
19
|
-
class="max-w-full block
|
30
|
+
class="max-w-full block bg-white pt-1 mb-4 mt-2 border border-mid rounded-md min-h-[300px]">
|
20
31
|
</div>
|
21
32
|
</div>
|
22
33
|
|
@@ -5,13 +5,15 @@
|
|
5
5
|
|
6
6
|
<%= render Panda::CMS::Admin::TableComponent.new(term: "post", rows: posts) do |table| %>
|
7
7
|
<% table.column("Title") do |post| %>
|
8
|
-
<div
|
8
|
+
<div>
|
9
9
|
<%= link_to post.title, edit_admin_post_path(post.admin_param), class: "block h-full w-full" %>
|
10
|
-
<span class="block text-xs text-black/60"
|
10
|
+
<span class="block text-xs text-black/60">
|
11
|
+
<%= CGI.unescape("#{Panda::CMS.config.posts[:prefix]}#{post.slug}") %>
|
12
|
+
</span>
|
11
13
|
</div>
|
12
14
|
<% end %>
|
13
15
|
<% table.column("Status") { |post| render Panda::CMS::Admin::TagComponent.new(status: post.status) } %>
|
14
|
-
<% table.column("Published") { |post| render Panda::CMS::Admin::UserActivityComponent.new(at: post.published_at, user: post.
|
16
|
+
<% table.column("Published") { |post| render Panda::CMS::Admin::UserActivityComponent.new(at: post.published_at, user: post.author)} %>
|
15
17
|
<% table.column("Last Updated") { |post| render Panda::CMS::Admin::UserActivityComponent.new(whodunnit_to: post)} %>
|
16
18
|
<% end %>
|
17
19
|
|
data/config/routes.rb
CHANGED
@@ -35,14 +35,42 @@ Panda::CMS::Engine.routes.draw do
|
|
35
35
|
# OmniAuth additionally adds a GET route for "#{Panda::CMS.route_namespace}/auth/:provider" but doesn't name it
|
36
36
|
delete Panda::CMS.route_namespace, to: "admin/sessions#destroy", as: :admin_logout
|
37
37
|
|
38
|
+
### APPENDED ROUTES ###
|
39
|
+
|
40
|
+
# TODO: Allow multiple types of post in future
|
38
41
|
if Panda::CMS.config.posts[:enabled]
|
39
|
-
|
40
|
-
# TODO: This now requires a page to be created, make it explicit (with a rendering posts helper?)
|
41
|
-
# get Panda::CMS.posts[:prefix], to: "posts#index", as: :posts
|
42
|
-
get "#{Panda::CMS.config.posts[:prefix]}/:slug", to: "posts#show", as: :post
|
43
|
-
end
|
42
|
+
get Panda::CMS.config.posts[:prefix], to: "posts#index", as: :posts
|
44
43
|
|
45
|
-
|
44
|
+
# Route for date-based URLs that won't encode slashes
|
45
|
+
get "#{Panda::CMS.config.posts[:prefix]}/:year/:month/:slug",
|
46
|
+
to: "posts#show",
|
47
|
+
as: :post_with_date,
|
48
|
+
constraints: {
|
49
|
+
year: /\d{4}/,
|
50
|
+
month: /\d{2}/,
|
51
|
+
slug: /[^\/]+/,
|
52
|
+
format: /html|json|xml/
|
53
|
+
}
|
54
|
+
|
55
|
+
# Route for non-date URLs
|
56
|
+
get "#{Panda::CMS.config.posts[:prefix]}/:slug",
|
57
|
+
to: "posts#show",
|
58
|
+
as: :post,
|
59
|
+
constraints: {
|
60
|
+
slug: /[^\/]+/,
|
61
|
+
format: /html|json|xml/
|
62
|
+
}
|
63
|
+
|
64
|
+
# Route for month archive
|
65
|
+
get "#{Panda::CMS.config.posts[:prefix]}/:year/:month",
|
66
|
+
to: "posts#by_month",
|
67
|
+
as: :posts_by_month,
|
68
|
+
constraints: {
|
69
|
+
year: /\d{4}/,
|
70
|
+
month: /\d{2}/,
|
71
|
+
format: /html|json|xml/
|
72
|
+
}
|
73
|
+
end
|
46
74
|
|
47
75
|
# See lib/panda/cms/engine.rb
|
48
76
|
end
|
@@ -12,10 +12,23 @@ module Panda::CMS::EditorJsContent
|
|
12
12
|
|
13
13
|
def generate_cached_content
|
14
14
|
if content.is_a?(String)
|
15
|
-
|
15
|
+
begin
|
16
|
+
parsed_content = JSON.parse(content)
|
17
|
+
self.cached_content = if parsed_content.is_a?(Hash) && parsed_content["blocks"].present?
|
18
|
+
Panda::CMS::EditorJs::Renderer.new(parsed_content).render
|
19
|
+
else
|
20
|
+
content
|
21
|
+
end
|
22
|
+
rescue JSON::ParserError
|
23
|
+
# If it's not JSON, treat it as plain text
|
24
|
+
self.cached_content = content
|
25
|
+
end
|
16
26
|
elsif content.is_a?(Hash) && content["blocks"].present?
|
17
27
|
# Process EditorJS content
|
18
28
|
self.cached_content = Panda::CMS::EditorJs::Renderer.new(content).render
|
29
|
+
else
|
30
|
+
# For any other case, store as is
|
31
|
+
self.cached_content = content.to_s
|
19
32
|
end
|
20
33
|
end
|
21
34
|
end
|
data/lib/panda/cms/engine.rb
CHANGED
@@ -3,8 +3,12 @@ require "turbo-rails"
|
|
3
3
|
require "stimulus-rails"
|
4
4
|
require "view_component"
|
5
5
|
|
6
|
+
require "panda/core"
|
6
7
|
require "panda/cms/railtie"
|
7
8
|
|
9
|
+
require "omniauth"
|
10
|
+
require "omniauth/rails_csrf_protection"
|
11
|
+
|
8
12
|
module Panda
|
9
13
|
module CMS
|
10
14
|
class Engine < ::Rails::Engine
|
data/lib/panda-cms/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: panda-cms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Panda Software Limited
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-19 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: panda-core
|
@@ -452,6 +452,7 @@ files:
|
|
452
452
|
- app/helpers/panda/cms/admin/posts_helper.rb
|
453
453
|
- app/helpers/panda/cms/application_helper.rb
|
454
454
|
- app/helpers/panda/cms/pages_helper.rb
|
455
|
+
- app/helpers/panda/cms/posts_helper.rb
|
455
456
|
- app/helpers/panda/cms/theme_helper.rb
|
456
457
|
- app/javascript/panda/cms/@editorjs--editorjs.js
|
457
458
|
- app/javascript/panda/cms/@hotwired--stimulus.js
|
@@ -468,6 +469,7 @@ files:
|
|
468
469
|
- app/javascript/panda/cms/editor/editor_js_initializer.js
|
469
470
|
- app/javascript/panda/cms/editor/plain_text_editor.js
|
470
471
|
- app/javascript/panda/cms/editor/resource_loader.js
|
472
|
+
- app/javascript/panda/cms/editor/rich_text_editor.js
|
471
473
|
- app/javascript/panda/cms/tailwindcss-stimulus-components.js
|
472
474
|
- app/jobs/panda/cms/application_job.rb
|
473
475
|
- app/jobs/panda/cms/record_visit_job.rb
|
@@ -581,6 +583,7 @@ files:
|
|
581
583
|
- db/migrate/20241120110943_add_editor_js_to_posts.rb
|
582
584
|
- db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb
|
583
585
|
- db/migrate/20241123234140_remove_post_tag_id_from_posts.rb
|
586
|
+
- db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb
|
584
587
|
- db/migrate/migrate
|
585
588
|
- db/seeds.rb
|
586
589
|
- lib/generators/panda/cms/install_generator.rb
|