panda-cms 0.7.3 → 0.7.4
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/README.md +4 -4
- data/app/assets/builds/panda.cms.css +2 -6
- data/app/assets/tailwind/application.css +178 -0
- data/app/assets/tailwind/tailwind.config.js +15 -0
- data/app/builders/panda/cms/form_builder.rb +15 -32
- data/app/components/panda/cms/admin/flash_message_component.html.erb +2 -2
- data/app/components/panda/cms/admin/user_activity_component.rb +7 -18
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +0 -1
- data/app/controllers/panda/cms/admin/forms_controller.rb +0 -1
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +43 -0
- data/app/controllers/panda/cms/admin/pages_controller.rb +14 -4
- data/app/controllers/panda/cms/admin/posts_controller.rb +7 -22
- data/app/controllers/panda/cms/form_submissions_controller.rb +2 -0
- data/app/controllers/panda/cms/pages_controller.rb +10 -8
- data/app/javascript/panda/cms/controllers/index.js +4 -2
- data/app/javascript/panda/cms/controllers/slug_controller.js +64 -31
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +9 -0
- data/app/jobs/panda/cms/record_visit_job.rb +12 -14
- data/app/models/panda/cms/application_record.rb +1 -0
- data/app/models/panda/cms/block.rb +9 -17
- data/app/models/panda/cms/block_content.rb +5 -6
- data/app/models/panda/cms/page.rb +24 -11
- data/app/models/panda/cms/post.rb +3 -5
- data/app/models/panda/cms/redirect.rb +5 -0
- data/app/models/panda/cms/template.rb +6 -7
- data/app/models/panda/cms/visit.rb +1 -1
- data/app/models/panda/social/instagram_post.rb +15 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +6 -13
- data/app/services/panda/social/instagram_feed_service.rb +61 -0
- data/app/views/panda/cms/admin/my_profile/edit.html.erb +35 -0
- data/app/views/panda/cms/admin/pages/index.html.erb +1 -1
- data/app/views/panda/cms/admin/pages/new.html.erb +3 -3
- data/app/views/panda/cms/admin/posts/_form.html.erb +10 -0
- data/app/views/panda/cms/admin/posts/edit.html.erb +3 -2
- data/app/views/panda/cms/admin/posts/index.html.erb +1 -1
- data/app/views/panda/cms/admin/settings/index.html.erb +2 -0
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +1 -1
- data/app/views/panda/cms/shared/_header.html.erb +4 -2
- data/app/views/panda/cms/shared/_importmap.html.erb +1 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20250120235542_remove_paper_trail.rb +55 -0
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +14 -0
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +5 -0
- data/lib/panda/cms/demo_site_generator.rb +25 -4
- data/lib/panda/cms/editor_js_content.rb +21 -0
- data/lib/panda/cms/engine.rb +7 -5
- data/lib/panda-cms/version.rb +1 -1
- data/lib/panda-cms.rb +13 -0
- data/lib/tasks/panda/cms/install.rake +23 -0
- data/lib/tasks/panda/social/instagram.rake +18 -0
- data/public/panda-cms-assets/editor-js/core/editorjs.min.js +83 -0
- data/public/panda-cms-assets/editor-js/plugins/embed.min.js +2 -0
- data/public/panda-cms-assets/editor-js/plugins/header.min.js +9 -0
- data/public/panda-cms-assets/editor-js/plugins/nested-list.min.js +2 -0
- data/public/panda-cms-assets/editor-js/plugins/paragraph.min.js +9 -0
- data/public/panda-cms-assets/editor-js/plugins/quote.min.js +2 -0
- data/public/panda-cms-assets/editor-js/plugins/simple-image.min.js +2 -0
- data/public/panda-cms-assets/editor-js/plugins/table.min.js +2 -0
- metadata +36 -557
- data/app/models/action_text/rich_text_version.rb +0 -6
- data/app/models/panda/cms/block_content_version.rb +0 -8
- data/app/models/panda/cms/page_version.rb +0 -8
- data/app/models/panda/cms/post_version.rb +0 -8
- data/app/models/panda/cms/template_version.rb +0 -8
- data/app/models/panda/cms/version.rb +0 -8
- data/config/initializers/panda/cms/paper_trail.rb +0 -7
- data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +0 -24
- data/db/migrate/20241119214549_remove_action_text_from_posts.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07d84fd0b6bd17f5e5f253a570bae55270227bec0196037960aa3358d09903bd
|
4
|
+
data.tar.gz: c6bc6d0173f89d965a23132bc016bfd3a44e6bd0b2533960cca495fa28e645e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63b30314a40563ab4cb8f99d516cd30edc3aed7ffe8d1841d4b8760ea6c80c8b253a983ca7cadd473caba240a7b18132cda903b23ed35da845833aef4880b2d4
|
7
|
+
data.tar.gz: 820e1b606f088e8bacd7e03d997812f5a2255941e399e8fe863076a0a1196935c66c6a2aca34d50f9dd69563280cda9d795692ccba6df5af565aefd2b38439e2
|
data/README.md
CHANGED
@@ -44,8 +44,8 @@ For initial setup, run:
|
|
44
44
|
|
45
45
|
```shell
|
46
46
|
bundle install
|
47
|
-
rails generate
|
48
|
-
rails
|
47
|
+
rails generate panda_cms:install
|
48
|
+
rails panda_cms:install:migrations
|
49
49
|
rails db:seed
|
50
50
|
```
|
51
51
|
|
@@ -57,7 +57,7 @@ If you don't want to use GitHub to login (or are at a URL other than http://loca
|
|
57
57
|
|
58
58
|
This is a non-exhuastive list (there will be many more):
|
59
59
|
|
60
|
-
* To date, this has only been tested with Rails 7.1
|
60
|
+
* To date, this has only been tested with Rails 7.1, 7.2 and 8.0
|
61
61
|
* There may be conflicts if you're not using Tailwind CSS on the frontend. Please report this.
|
62
62
|
|
63
63
|
## Contributing
|
@@ -70,4 +70,4 @@ See our [Contributing Guidelines](https://docs.pandacms.io/developers/contributi
|
|
70
70
|
|
71
71
|
The gem is available as open source under the terms of the [BSD-3-Clause License](https://opensource.org/licenses/bsd-3-clause).
|
72
72
|
|
73
|
-
Copyright © 2024, Panda Software Limited.
|
73
|
+
Copyright © 2024 - 2025, Panda Software Limited.
|
@@ -1182,10 +1182,6 @@ a.block-link:after {
|
|
1182
1182
|
pointer-events: none;
|
1183
1183
|
}
|
1184
1184
|
|
1185
|
-
.pointer-events-auto {
|
1186
|
-
pointer-events: auto;
|
1187
|
-
}
|
1188
|
-
|
1189
1185
|
.visible {
|
1190
1186
|
visibility: visible;
|
1191
1187
|
}
|
@@ -1250,8 +1246,8 @@ a.block-link:after {
|
|
1250
1246
|
z-index: 10;
|
1251
1247
|
}
|
1252
1248
|
|
1253
|
-
.z
|
1254
|
-
z-index:
|
1249
|
+
.z-\[9999\] {
|
1250
|
+
z-index: 9999;
|
1255
1251
|
}
|
1256
1252
|
|
1257
1253
|
.col-span-3 {
|
@@ -0,0 +1,178 @@
|
|
1
|
+
@import "tailwindcss";
|
2
|
+
|
3
|
+
@config "tailwind.config.js";
|
4
|
+
|
5
|
+
@theme {
|
6
|
+
--color-white: var(--color-white);
|
7
|
+
--color-black: var(--color-black);
|
8
|
+
--color-light: var(--color-light);
|
9
|
+
--color-mid: var(--color-mid);
|
10
|
+
--color-dark: var(--color-dark);
|
11
|
+
--color-highlight: var(--color-highlight);
|
12
|
+
--color-active: var(--color-active);
|
13
|
+
--color-inactive: var(--color-active);
|
14
|
+
--color-warning: var(--color-warning);
|
15
|
+
--color-error: var(--color-error);
|
16
|
+
}
|
17
|
+
|
18
|
+
@plugin "tailwindcss/typography";
|
19
|
+
@plugin "tailwindcss/forms";
|
20
|
+
|
21
|
+
@layer base {
|
22
|
+
html[data-theme="default"] {
|
23
|
+
--color-white: 249 249 249; /* #F9F9F9 */
|
24
|
+
--color-black: 26 22 29; /* #1A161D */
|
25
|
+
|
26
|
+
--color-light: 238 206 230; /* #EECEE6 */
|
27
|
+
--color-mid: 141 94 183; /* #8D5EB7 */
|
28
|
+
--color-dark: 33 29 73; /* #211D49 */
|
29
|
+
|
30
|
+
--color-highlight: 208 64 20; /* #D04014 */
|
31
|
+
|
32
|
+
--color-active: 0 135 85; /* #008755 */
|
33
|
+
--color-warning: 250 207 142; /* #FACF8E */
|
34
|
+
--color-inactive: 216 247 245; /* #d6e4f7 */
|
35
|
+
--color-error: 245 129 129; /* #F58181 */
|
36
|
+
}
|
37
|
+
|
38
|
+
html[data-theme="sky"] {
|
39
|
+
--color-white: 249 249 249; /* #F9F9F9 */
|
40
|
+
--color-black: 26 22 29; /* #1A161D */
|
41
|
+
--color-light: 204 238 242; /* #CCEEF2 */
|
42
|
+
--color-mid: 42 102 159; /* #2A669F */
|
43
|
+
--color-dark: 20 32 74; /* #14204A */
|
44
|
+
--color-highlight: 208 64 20; /* #D04014 */
|
45
|
+
|
46
|
+
--color-active: 69 154 89; /* #459A59 - darker green with better contrast */
|
47
|
+
--color-warning: 244 190 102; /* #F4BE66 */
|
48
|
+
--color-inactive: 216 247 245; /* #d6e4f7 */
|
49
|
+
--color-error: 208 64 20; /* #D04014 */
|
50
|
+
}
|
51
|
+
|
52
|
+
a.block-link:after {
|
53
|
+
position: absolute;
|
54
|
+
content: "";
|
55
|
+
inset: 0;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
/* Default editor styles */
|
60
|
+
@layer components {
|
61
|
+
.codex-editor__redactor .ce-block .ce-block__content {
|
62
|
+
@apply text-base font-normal font-sans text-dark leading-[1.6] space-y-[1.6rem];
|
63
|
+
|
64
|
+
h1.ce-header {
|
65
|
+
@apply text-3xl md:text-4xl font-semibold font-sans leading-[1.2];
|
66
|
+
}
|
67
|
+
|
68
|
+
h2.ce-header {
|
69
|
+
@apply text-2xl font-medium font-sans leading-[1.3] mb-4 mt-8;
|
70
|
+
}
|
71
|
+
|
72
|
+
h3.ce-header {
|
73
|
+
@apply text-xl font-normal font-sans leading-[1.3] mb-4 mt-6;
|
74
|
+
}
|
75
|
+
|
76
|
+
p,
|
77
|
+
li {
|
78
|
+
@apply leading-[1.6] tracking-wide max-w-[85ch];
|
79
|
+
|
80
|
+
a {
|
81
|
+
@apply text-[#1A9597] underline underline-offset-2 hover:text-[#158486] focus:outline-2 focus:outline-offset-2 focus:outline-[#1A9597];
|
82
|
+
}
|
83
|
+
|
84
|
+
strong,
|
85
|
+
b {
|
86
|
+
@apply font-semibold;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
p {
|
91
|
+
@apply mb-4;
|
92
|
+
}
|
93
|
+
|
94
|
+
.cdx-quote {
|
95
|
+
@apply bg-[#eef0f3] border-l-inactive border-l-8 p-6 mb-4;
|
96
|
+
|
97
|
+
.cdx-quote__caption {
|
98
|
+
@apply block ml-6 mt-2 text-sm text-dark;
|
99
|
+
}
|
100
|
+
|
101
|
+
.cdx-quote__text {
|
102
|
+
quotes: "\201C" "\201D" "\2018" "\2019";
|
103
|
+
@apply pl-6;
|
104
|
+
|
105
|
+
&:before {
|
106
|
+
@apply -ml-8 mr-2 text-dark text-6xl leading-4 align-text-bottom font-serif;
|
107
|
+
content: open-quote;
|
108
|
+
}
|
109
|
+
|
110
|
+
p {
|
111
|
+
@apply inline italic text-lg;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
.cdx-list {
|
117
|
+
@apply mb-4 pl-6;
|
118
|
+
|
119
|
+
&--ordered {
|
120
|
+
@apply list-decimal;
|
121
|
+
}
|
122
|
+
|
123
|
+
&--unordered {
|
124
|
+
@apply list-disc;
|
125
|
+
}
|
126
|
+
|
127
|
+
.cdx-list {
|
128
|
+
@apply mt-2 mb-0;
|
129
|
+
}
|
130
|
+
|
131
|
+
.cdx-list__item {
|
132
|
+
@apply mb-2 pl-2;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
.cdx-nested-list {
|
137
|
+
@apply mb-4 pl-6;
|
138
|
+
|
139
|
+
&--ordered {
|
140
|
+
@apply list-decimal;
|
141
|
+
}
|
142
|
+
|
143
|
+
&--unordered {
|
144
|
+
@apply list-disc;
|
145
|
+
}
|
146
|
+
|
147
|
+
.cdx-nested-list {
|
148
|
+
@apply mt-2 mb-0;
|
149
|
+
}
|
150
|
+
|
151
|
+
.cdx-nested-list__item {
|
152
|
+
@apply mb-2 pl-2;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
.cdx-table {
|
157
|
+
@apply w-full border-collapse border-2 border-dark my-6;
|
158
|
+
|
159
|
+
&__head {
|
160
|
+
@apply font-semibold border-dark border-r-2 p-3 bg-light;
|
161
|
+
}
|
162
|
+
|
163
|
+
&__row {
|
164
|
+
@apply border-dark border-b-2;
|
165
|
+
}
|
166
|
+
|
167
|
+
&__cell {
|
168
|
+
@apply border-dark border-r-2 p-3;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
.cdx-embed {
|
173
|
+
iframe {
|
174
|
+
@apply w-full border-none;
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module.exports = {
|
2
|
+
content: {
|
3
|
+
relative: true,
|
4
|
+
files: [
|
5
|
+
"../../public/*.html",
|
6
|
+
"../../app/views/**/*.html.erb",
|
7
|
+
"../../app/builders/panda/cms/**/*.rb",
|
8
|
+
"../../app/components/panda/cms/**/*.html.erb",
|
9
|
+
"../../app/components/panda/cms/**/*.rb",
|
10
|
+
"../../app/helpers/panda/cms/**/*.rb",
|
11
|
+
"../../app/javascript/panda/cms/**/*.js",
|
12
|
+
"../../vendor/javascript/**/*.js",
|
13
|
+
],
|
14
|
+
},
|
15
|
+
};
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "ostruct"
|
2
|
-
|
3
1
|
module Panda
|
4
2
|
module CMS
|
5
3
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
@@ -17,48 +15,48 @@ module Panda
|
|
17
15
|
content_tag(:div, class: "flex flex-grow") do
|
18
16
|
content_tag(:span, class: "inline-flex items-center px-3 text-base border border-r-none rounded-s-md whitespace-nowrap break-keep") { options.dig(:data, :prefix) } +
|
19
17
|
super(attribute, options.reverse_merge(class: input_styles_prefix + " input-prefix rounded-l-none border-l-none"))
|
20
|
-
end
|
18
|
+
end + error_message(attribute)
|
21
19
|
end
|
22
20
|
else
|
23
21
|
content_tag :div, class: container_styles do
|
24
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
22
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
29
27
|
def email_field(method, options = {})
|
30
28
|
content_tag :div, class: container_styles do
|
31
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
29
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
32
30
|
end
|
33
31
|
end
|
34
32
|
|
35
33
|
def datetime_field(method, options = {})
|
36
34
|
content_tag :div, class: container_styles do
|
37
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
35
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
39
|
def text_area(method, options = {})
|
42
40
|
content_tag :div, class: container_styles do
|
43
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
41
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
47
45
|
def password_field(attribute, options = {})
|
48
46
|
content_tag :div, class: container_styles do
|
49
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
47
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
51
|
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
54
52
|
content_tag :div, class: container_styles do
|
55
|
-
label(method) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg
|
53
|
+
label(method) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg + error_message(method)
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
59
57
|
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
60
58
|
content_tag :div, class: container_styles do
|
61
|
-
label(method) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options.reverse_merge(class: input_styles))
|
59
|
+
label(method) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options.reverse_merge(class: input_styles)) + error_message(method)
|
62
60
|
end
|
63
61
|
end
|
64
62
|
|
@@ -147,28 +145,6 @@ module Panda
|
|
147
145
|
end
|
148
146
|
end
|
149
147
|
|
150
|
-
def rich_text_area(method, options = {})
|
151
|
-
content_tag :div, class: container_styles do
|
152
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: textarea_styles))
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def rich_text_field(method, options = {})
|
157
|
-
wrap_field(method, options) do
|
158
|
-
if defined?(ActionText)
|
159
|
-
# For test environment
|
160
|
-
if Rails.env.test?
|
161
|
-
# Just render a textarea for testing
|
162
|
-
text_area(method, options.reverse_merge(class: textarea_styles))
|
163
|
-
else
|
164
|
-
rich_text_area(method, options.reverse_merge(class: textarea_styles))
|
165
|
-
end
|
166
|
-
else
|
167
|
-
text_area(method, options.reverse_merge(class: textarea_styles))
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
148
|
def meta_text(options)
|
173
149
|
return unless options[:meta]
|
174
150
|
@template.content_tag(:p, options[:meta], class: "block text-black/60 text-sm mb-2")
|
@@ -229,6 +205,13 @@ module Panda
|
|
229
205
|
def field_wrapper_styles
|
230
206
|
"mt-1"
|
231
207
|
end
|
208
|
+
|
209
|
+
def error_message(attribute)
|
210
|
+
return unless object.respond_to?(:errors) && object.errors[attribute]&.any?
|
211
|
+
content_tag(:p, class: "mt-2 text-sm text-red-600") do
|
212
|
+
object.errors[attribute].join(", ")
|
213
|
+
end
|
214
|
+
end
|
232
215
|
end
|
233
216
|
end
|
234
217
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<div class="fixed top-2 right-2 z-
|
1
|
+
<div class="fixed top-2 right-2 z-[9999] p-2 space-y-4 w-full max-w-sm sm:items-end"
|
2
2
|
data-controller="alert"
|
3
3
|
<% if @temporary %> data-alert-dismiss-after-value="3000"<% end %>
|
4
4
|
data-transition-enter="ease-in-out duration-500"
|
@@ -7,7 +7,7 @@
|
|
7
7
|
data-transition-leave="ease-in-out duration-500"
|
8
8
|
data-transition-leave-from="translate-x-0 opacity-100"
|
9
9
|
data-transition-leave-to="translate-x-full opacity-0">
|
10
|
-
<div class="overflow-hidden w-full max-w-sm bg-white rounded-lg ring-1 ring-black ring-opacity-5 shadow-lg
|
10
|
+
<div class="overflow-hidden w-full max-w-sm bg-white rounded-lg ring-1 ring-black ring-opacity-5 shadow-lg">
|
11
11
|
<div class="p-4">
|
12
12
|
<div class="flex items-start">
|
13
13
|
<div class="flex-shrink-0">
|
@@ -8,24 +8,13 @@ module Panda
|
|
8
8
|
attr_accessor :time
|
9
9
|
attr_accessor :user
|
10
10
|
|
11
|
-
# @param
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@time = @model.updated_at
|
19
|
-
end
|
20
|
-
elsif user.is_a?(::Panda::CMS::User) && at.is_a?(::ActiveSupport::TimeWithZone)
|
21
|
-
@user = user
|
22
|
-
@time = at
|
23
|
-
end
|
24
|
-
|
25
|
-
if !@time
|
26
|
-
@user = nil
|
27
|
-
@time = nil
|
28
|
-
end
|
11
|
+
# @param model [ActiveRecord::Base] Model instance to which the user activity is related
|
12
|
+
# @param at [ActiveSupport::TimeWithZone] Time of the activity
|
13
|
+
# @param user [Panda::CMS::User] User who performed the activity
|
14
|
+
def initialize(model: nil, at: nil, user: nil)
|
15
|
+
@model = model
|
16
|
+
@user = user if user.is_a?(::Panda::CMS::User)
|
17
|
+
@time = at if at.is_a?(::ActiveSupport::TimeWithZone)
|
29
18
|
end
|
30
19
|
end
|
31
20
|
end
|
@@ -6,7 +6,6 @@ module Panda
|
|
6
6
|
class BlockContentsController < ApplicationController
|
7
7
|
before_action :set_page, only: %i[update]
|
8
8
|
before_action :set_block_content, only: %i[update]
|
9
|
-
before_action :set_paper_trail_whodunnit, only: %i[update]
|
10
9
|
before_action :authenticate_admin_user!
|
11
10
|
|
12
11
|
# @type PATCH/PUT
|
@@ -5,7 +5,6 @@ module Panda
|
|
5
5
|
module Admin
|
6
6
|
class FormsController < ApplicationController
|
7
7
|
before_action :set_initial_breadcrumb, only: %i[index show]
|
8
|
-
# before_action :set_paper_trail_whodunnit, only: %i[create update]
|
9
8
|
before_action :authenticate_admin_user!
|
10
9
|
|
11
10
|
# Lists all forms
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
module Admin
|
6
|
+
class MyProfileController < ApplicationController
|
7
|
+
before_action :set_initial_breadcrumb, only: %i[edit update]
|
8
|
+
before_action :authenticate_admin_user!
|
9
|
+
|
10
|
+
# Shows the edit form for the current user's profile
|
11
|
+
# @type GET
|
12
|
+
# @return void
|
13
|
+
def edit
|
14
|
+
render :edit, locals: {user: current_user}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Updates the current user's profile
|
18
|
+
# @type PATCH/PUT
|
19
|
+
# @return void
|
20
|
+
def update
|
21
|
+
if current_user.update(user_params)
|
22
|
+
redirect_to edit_admin_my_profile_path, flash: {success: "Your profile has been updated successfully."}
|
23
|
+
else
|
24
|
+
render :edit, locals: {user: current_user}, status: :unprocessable_entity
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def set_initial_breadcrumb
|
31
|
+
add_breadcrumb "My Profile", edit_admin_my_profile_path
|
32
|
+
end
|
33
|
+
|
34
|
+
# Only allow a list of trusted parameters through
|
35
|
+
# @type private
|
36
|
+
# @return ActionController::StrongParameters
|
37
|
+
def user_params
|
38
|
+
params.require(:user).permit(:firstname, :lastname, :email, :current_theme)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -5,7 +5,6 @@ module Panda
|
|
5
5
|
module Admin
|
6
6
|
class PagesController < ApplicationController
|
7
7
|
before_action :set_initial_breadcrumb, only: %i[index edit new create update]
|
8
|
-
before_action :set_paper_trail_whodunnit, only: %i[create update]
|
9
8
|
before_action :authenticate_admin_user!
|
10
9
|
|
11
10
|
# Lists all pages which can be managed by the administrator
|
@@ -34,11 +33,19 @@ module Panda
|
|
34
33
|
# POST /admin/pages
|
35
34
|
def create
|
36
35
|
page = Panda::CMS::Page.new(page_params)
|
36
|
+
|
37
|
+
# Normalize empty path to nil so presence validation triggers
|
38
|
+
page.path = nil if page.path.blank?
|
39
|
+
|
40
|
+
# Set the full path before validation if we have a parent
|
41
|
+
if page.parent && page.parent.path != "/" && page.path.present?
|
42
|
+
page.path = page.parent.path + page.path
|
43
|
+
end
|
44
|
+
|
37
45
|
if page.save
|
38
|
-
page.update(path: page.parent.path + page.path) unless page.parent.path == "/"
|
39
46
|
redirect_to edit_admin_page_path(page), notice: "The page was successfully created."
|
40
47
|
else
|
41
|
-
flash[:error] =
|
48
|
+
flash.now[:error] = page.errors.full_messages.to_sentence
|
42
49
|
locals = setup_new_page_form(page: page)
|
43
50
|
render :new, locals: locals, status: :unprocessable_entity
|
44
51
|
end
|
@@ -76,7 +83,10 @@ module Panda
|
|
76
83
|
|
77
84
|
def setup_new_page_form(page:)
|
78
85
|
add_breadcrumb "Add Page", new_admin_page_path
|
79
|
-
{
|
86
|
+
{
|
87
|
+
page: page,
|
88
|
+
available_templates: Panda::CMS::Template.available
|
89
|
+
}
|
80
90
|
end
|
81
91
|
|
82
92
|
# Only allow a list of trusted parameters through.
|
@@ -6,8 +6,7 @@ module Panda
|
|
6
6
|
module CMS
|
7
7
|
module Admin
|
8
8
|
class PostsController < ApplicationController
|
9
|
-
before_action :set_initial_breadcrumb, only: %i[index new
|
10
|
-
before_action :set_paper_trail_whodunnit, only: %i[create update]
|
9
|
+
before_action :set_initial_breadcrumb, only: %i[index edit new create update]
|
11
10
|
before_action :authenticate_admin_user!
|
12
11
|
|
13
12
|
# Get all posts
|
@@ -29,20 +28,7 @@ module Panda
|
|
29
28
|
# @type GET
|
30
29
|
def edit
|
31
30
|
add_breadcrumb post.title, edit_admin_post_path(post.admin_param)
|
32
|
-
|
33
|
-
# Get the latest version's content or fall back to post's content
|
34
|
-
preserved_content = if post.versions.exists?
|
35
|
-
reified_post = post.versions.last.reify
|
36
|
-
reified_post&.content || post.content
|
37
|
-
else
|
38
|
-
post.content
|
39
|
-
end
|
40
|
-
|
41
|
-
render :edit, locals: {
|
42
|
-
post: post,
|
43
|
-
url: admin_post_path(post.admin_param),
|
44
|
-
preserved_content: preserved_content
|
45
|
-
}
|
31
|
+
render :edit, locals: {post: post}
|
46
32
|
end
|
47
33
|
|
48
34
|
# POST /admin/posts
|
@@ -53,7 +39,8 @@ module Panda
|
|
53
39
|
|
54
40
|
if @post.save
|
55
41
|
Rails.logger.debug "Post saved successfully"
|
56
|
-
|
42
|
+
flash[:success] = "The post was successfully created!"
|
43
|
+
redirect_to edit_admin_post_path(@post.admin_param), status: :see_other
|
57
44
|
else
|
58
45
|
Rails.logger.debug "Post save failed: #{@post.errors.full_messages.inspect}"
|
59
46
|
flash.now[:error] = @post.errors.full_messages.join(", ")
|
@@ -75,17 +62,15 @@ module Panda
|
|
75
62
|
if post.update(update_params)
|
76
63
|
Rails.logger.debug "Post updated successfully"
|
77
64
|
add_breadcrumb post.title, edit_admin_post_path(post.admin_param)
|
78
|
-
|
79
|
-
|
80
|
-
flash: {success: "The post was successfully updated!"}
|
65
|
+
flash[:success] = "The post was successfully updated"
|
66
|
+
redirect_to edit_admin_post_path(post.admin_param), status: :see_other
|
81
67
|
else
|
82
68
|
Rails.logger.debug "Post update failed: #{post.errors.full_messages.inspect}"
|
83
69
|
Rails.logger.debug "Preserving content: #{post_params[:content].inspect}"
|
84
|
-
flash[:error] = post.errors.full_messages.join(", ")
|
85
70
|
add_breadcrumb post.title.presence || "Edit Post", edit_admin_post_path(post.admin_param)
|
71
|
+
flash.now[:error] = post.errors.full_messages.join(", ")
|
86
72
|
render :edit, locals: {
|
87
73
|
post: post,
|
88
|
-
url: admin_post_path(post.admin_param),
|
89
74
|
preserved_content: post_params[:content]
|
90
75
|
}, status: :unprocessable_entity
|
91
76
|
end
|
@@ -69,21 +69,23 @@ module Panda
|
|
69
69
|
# Ignore visits from bots (TODO: make this configurable)
|
70
70
|
return true if /bot/i.match?(request.user_agent)
|
71
71
|
# Ignore visits from Honeybadger
|
72
|
-
return true if request.headers.to_h.key?
|
73
|
-
|
72
|
+
return true if request.headers.to_h.key?("Honeybadger-Token") || request.user_agent == "Honeybadger Uptime Check"
|
73
|
+
# Ignore visits where we're asking for PHP files
|
74
|
+
return true if request.path.ends_with?(".php")
|
75
|
+
# Otherwise, record the visit
|
74
76
|
false
|
75
77
|
end
|
76
78
|
|
77
79
|
def record_visit
|
78
80
|
RecordVisitJob.perform_later(
|
79
|
-
|
81
|
+
path: request.path,
|
82
|
+
user_id: Current.user&.id,
|
83
|
+
redirect_id: @redirect&.id,
|
84
|
+
page_id: Current.page&.id,
|
80
85
|
user_agent: request.user_agent,
|
81
|
-
referrer: request.referrer,
|
82
86
|
ip_address: request.remote_ip,
|
83
|
-
|
84
|
-
|
85
|
-
params: params.to_unsafe_h.except(:controller, :action, :path),
|
86
|
-
visited_at: Time.zone.now
|
87
|
+
referer: request.referer, # TODO: Fix the naming of this column
|
88
|
+
params: request.parameters
|
87
89
|
)
|
88
90
|
end
|
89
91
|
|
@@ -7,8 +7,8 @@ const pandaCmsApplication = PandaCMSApplication.start()
|
|
7
7
|
console.debug("[Panda CMS] Application started...")
|
8
8
|
|
9
9
|
// Configure Stimulus development experience
|
10
|
-
|
11
|
-
|
10
|
+
const railsEnv = document.body?.dataset?.environment || "production";
|
11
|
+
pandaCmsApplication.debug = railsEnv === "development";
|
12
12
|
|
13
13
|
console.debug("[Panda CMS] window.pandaCmsStimulus available...")
|
14
14
|
|
@@ -28,6 +28,8 @@ import EditorIframeController from "panda/cms/controllers/editor_iframe_controll
|
|
28
28
|
pandaCmsApplication.register("editor-iframe", EditorIframeController)
|
29
29
|
|
30
30
|
console.debug("[Panda CMS] Registering components...")
|
31
|
+
import ThemeFormController from "panda/cms/controllers/theme_form_controller";
|
32
|
+
pandaCmsApplication.register("theme-form", ThemeFormController);
|
31
33
|
|
32
34
|
// Import and register all TailwindCSS Components or just the ones you need
|
33
35
|
import { Alert, Autosave, ColorPreview, Dropdown, Modal, Tabs, Popover, Toggle, Slideover } from "tailwindcss-stimulus-components"
|