panda-cms 0.7.3 → 0.7.5
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 +40 -5
- data/Rakefile +2 -0
- 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 +27 -36
- data/app/components/panda/cms/admin/flash_message_component.html.erb +2 -2
- data/app/components/panda/cms/admin/heading_component.rb +5 -4
- data/app/components/panda/cms/admin/panel_component.rb +2 -2
- data/app/components/panda/cms/admin/statistics_component.rb +1 -2
- data/app/components/panda/cms/admin/user_activity_component.html.erb +3 -1
- data/app/components/panda/cms/admin/user_activity_component.rb +8 -21
- data/app/components/panda/cms/code_component.rb +8 -4
- data/app/components/panda/cms/menu_component.rb +7 -6
- data/app/components/panda/cms/page_menu_component.rb +15 -17
- data/app/components/panda/cms/rich_text_component.rb +5 -6
- data/app/components/panda/cms/text_component.rb +6 -7
- data/app/constraints/panda/cms/admin_constraint.rb +4 -1
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +0 -1
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +13 -9
- data/app/controllers/panda/cms/admin/forms_controller.rb +0 -3
- data/app/controllers/panda/cms/admin/my_profile_controller.rb +44 -0
- data/app/controllers/panda/cms/admin/pages_controller.rb +15 -4
- data/app/controllers/panda/cms/admin/posts_controller.rb +6 -22
- data/app/controllers/panda/cms/admin/sessions_controller.rb +3 -5
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +32 -25
- data/app/controllers/panda/cms/admin/settings_controller.rb +14 -10
- data/app/controllers/panda/cms/application_controller.rb +7 -2
- data/app/controllers/panda/cms/errors_controller.rb +5 -2
- data/app/controllers/panda/cms/form_submissions_controller.rb +4 -0
- data/app/controllers/panda/cms/pages_controller.rb +40 -35
- data/app/controllers/panda/cms/posts_controller.rb +2 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +5 -1
- data/app/helpers/panda/cms/admin/pages_helper.rb +5 -1
- data/app/helpers/panda/cms/asset_helper.rb +182 -0
- data/app/helpers/panda/cms/pages_helper.rb +2 -0
- data/app/helpers/panda/cms/posts_helper.rb +2 -0
- data/app/helpers/panda/cms/theme_helper.rb +2 -0
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +59 -6
- data/app/javascript/panda/cms/controllers/index.js +5 -9
- data/app/javascript/panda/cms/controllers/slug_controller.js +64 -31
- data/app/javascript/panda/cms/controllers/theme_form_controller.js +25 -0
- data/app/javascript/panda/cms/stimulus-loading.js +39 -0
- data/app/javascript/panda_cms/stimulus-loading.js +39 -0
- data/app/jobs/panda/cms/application_job.rb +2 -0
- data/app/jobs/panda/cms/record_visit_job.rb +14 -14
- data/app/mailers/panda/cms/application_mailer.rb +2 -0
- data/app/mailers/panda/cms/form_mailer.rb +3 -1
- data/app/models/panda/cms/application_record.rb +3 -0
- data/app/models/panda/cms/block.rb +12 -17
- data/app/models/panda/cms/block_content.rb +7 -6
- data/app/models/panda/cms/breadcrumb.rb +2 -0
- data/app/models/panda/cms/current.rb +2 -0
- data/app/models/panda/cms/form.rb +2 -0
- data/app/models/panda/cms/form_submission.rb +2 -0
- data/app/models/panda/cms/menu.rb +12 -9
- data/app/models/panda/cms/menu_item.rb +10 -6
- data/app/models/panda/cms/page.rb +31 -16
- data/app/models/panda/cms/post.rb +12 -10
- data/app/models/panda/cms/redirect.rb +9 -1
- data/app/models/panda/cms/template.rb +17 -13
- data/app/models/panda/cms/user.rb +2 -0
- data/app/models/panda/cms/visit.rb +3 -1
- data/app/models/panda/social/instagram_post.rb +17 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +10 -15
- data/app/services/panda/social/instagram_feed_service.rb +63 -0
- data/app/views/layouts/different_page.html.erb +6 -0
- data/app/views/layouts/homepage.html.erb +37 -0
- data/app/views/layouts/page.html.erb +18 -0
- data/app/views/layouts/panda/cms/application.html.erb +1 -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 +14 -8
- 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 +3 -1
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +1 -1
- data/app/views/panda/cms/shared/_header.html.erb +14 -4
- data/app/views/panda/cms/shared/_importmap.html.erb +2 -1
- data/app/views/shared/_footer.html.erb +3 -0
- data/app/views/shared/_header.html.erb +11 -0
- data/config/importmap.rb +2 -0
- data/config/initializers/inflections.rb +2 -0
- data/config/initializers/panda/cms/form_errors.rb +20 -21
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +2 -0
- data/config/initializers/panda/cms.rb +2 -0
- data/config/initializers/zeitwork.rb +2 -0
- data/config/locales/en.yml +5 -0
- data/config/puma/test.rb +3 -1
- data/config/routes.rb +11 -8
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +2 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +2 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +4 -1
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +2 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +4 -1
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +2 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +2 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +2 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +7 -5
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +2 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +3 -1
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +2 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +2 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +2 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +2 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +2 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +2 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +2 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +5 -2
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +2 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +8 -6
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +2 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +2 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +2 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +2 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +3 -1
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +2 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +2 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +2 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +2 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +6 -4
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +2 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +2 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +2 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +2 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +2 -0
- data/db/migrate/20250106223303_add_author_id_to_panda_cms_posts.rb +2 -0
- data/db/migrate/20250120235542_remove_paper_trail.rb +56 -0
- data/db/migrate/20250126234001_create_panda_social_instagram_posts.rb +16 -0
- data/db/migrate/20250504221812_add_current_theme_to_panda_cms_users.rb +7 -0
- data/db/seeds.rb +2 -0
- data/lib/generators/panda/cms/install_generator.rb +2 -0
- data/lib/panda/cms/asset_loader.rb +390 -0
- data/lib/panda/cms/bulk_editor.rb +7 -3
- data/lib/panda/cms/demo_site_generator.rb +27 -4
- data/lib/panda/cms/editor_js/blocks/alert.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/base.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/header.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/image.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/list.rb +2 -0
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/quote.rb +3 -0
- data/lib/panda/cms/editor_js/blocks/table.rb +3 -1
- data/lib/panda/cms/editor_js/renderer.rb +3 -0
- data/lib/panda/cms/editor_js.rb +2 -0
- data/lib/panda/cms/editor_js_content.rb +50 -23
- data/lib/panda/cms/engine.rb +36 -37
- data/lib/panda/cms/exceptions_app.rb +2 -0
- data/lib/panda/cms/railtie.rb +2 -0
- data/lib/panda/cms/slug.rb +3 -1
- data/lib/panda-cms/version.rb +3 -1
- data/lib/panda-cms.rb +17 -2
- data/lib/tasks/assets.rake +547 -0
- data/lib/tasks/panda/cms/install.rake +25 -0
- data/lib/tasks/panda/social/instagram.rake +20 -0
- data/lib/tasks/panda_cms.rake +3 -30
- 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
- data/public/panda-cms-assets/manifest.json +20 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.css +26 -0
- data/public/panda-cms-assets/panda-cms-0.7.4.js +150 -0
- metadata +71 -438
- 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: a11a8249369ac564953d025d4887020ced356b6357a5d8c823580c27e39394cb
|
4
|
+
data.tar.gz: 04d6f8dab1ae48f77414ea10b515739f36a54c0f37b79e82f7d9282305af03a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb71f7f01ba585e142738e5d28510c5a0508b69843aab7c5b60fbf5cbaa7bb89419a916df923d0282cd2311a61614d836002635e67b9d6dfadd4bd6dba4e3f81
|
7
|
+
data.tar.gz: a795545501c2633b3e911b93ccd5c302adbb7d9c9f5a0ab6c363196ae4e0d18089a2b99cc3ada4b66e44902c8a5828212251b331e1b8b4d4762dfafef74dfe0f
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ Better websites, on Rails.
|
|
12
12
|
🐼 is grown from our work at [Otaina](https://www.otaina.co.uk), a small group of freelancers. We needed something that could handle websites large and small – but where we could expand it too. We sent our first websites live in March 2024.
|
13
13
|
|
14
14
|
 
|
15
|
-
 [](https://github.com/standardrb/standard)
|
16
16
|
|
17
17
|
## Usage
|
18
18
|
|
@@ -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
|
@@ -66,8 +66,43 @@ We welcome contributions.
|
|
66
66
|
|
67
67
|
See our [Contributing Guidelines](https://docs.pandacms.io/developers/contributing/)
|
68
68
|
|
69
|
+
### Testing
|
70
|
+
|
71
|
+
Panda CMS uses RSpec for testing.
|
72
|
+
|
73
|
+
#### Using Fixtures
|
74
|
+
|
75
|
+
We encourage using fixtures for tests instead of factories for consistent test data:
|
76
|
+
|
77
|
+
1. Create fixture files in `spec/fixtures` named after the model's table (e.g., `panda_cms_pages.yml`)
|
78
|
+
2. Define records with unique names and their attributes
|
79
|
+
3. Use helper methods to create test templates with mocked file validation
|
80
|
+
|
81
|
+
Example fixture format:
|
82
|
+
|
83
|
+
```yaml
|
84
|
+
# spec/fixtures/panda_cms_pages.yml
|
85
|
+
home_page:
|
86
|
+
title: "Home"
|
87
|
+
path: "/"
|
88
|
+
panda_cms_template_id: <%= ActiveRecord::FixtureSet.identify(:page_template) %>
|
89
|
+
status: "active"
|
90
|
+
created_at: <%= Time.current %>
|
91
|
+
updated_at: <%= Time.current %>
|
92
|
+
```
|
93
|
+
|
94
|
+
Example test using fixtures:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# Access fixture using table name and record name
|
98
|
+
page = panda_cms_pages(:home_page)
|
99
|
+
expect(page.title).to eq("Home")
|
100
|
+
```
|
101
|
+
|
102
|
+
When testing models with file validations or complex callbacks, use the helper methods in `spec/models/panda/cms/page_spec.rb` as a reference.
|
103
|
+
|
69
104
|
## License
|
70
105
|
|
71
106
|
The gem is available as open source under the terms of the [BSD-3-Clause License](https://opensource.org/licenses/bsd-3-clause).
|
72
107
|
|
73
|
-
Copyright © 2024,
|
108
|
+
Copyright © 2024 - 2025, Otaina Limited.
|
data/Rakefile
CHANGED
@@ -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,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Panda
|
4
4
|
module CMS
|
@@ -6,7 +6,7 @@ module Panda
|
|
6
6
|
include ActionView::Helpers::TagHelper
|
7
7
|
include ActionView::Helpers::FormTagHelper
|
8
8
|
|
9
|
-
def label(attribute, text = nil, options = {}
|
9
|
+
def label(attribute, text = nil, options = {})
|
10
10
|
super(attribute, text, options.reverse_merge(class: label_styles))
|
11
11
|
end
|
12
12
|
|
@@ -15,50 +15,53 @@ module Panda
|
|
15
15
|
content_tag :div, class: container_styles do
|
16
16
|
label(attribute) + meta_text(options) +
|
17
17
|
content_tag(:div, class: "flex flex-grow") do
|
18
|
-
content_tag(:span,
|
19
|
-
|
20
|
-
|
18
|
+
content_tag(:span,
|
19
|
+
class: "inline-flex items-center px-3 text-base border border-r-none rounded-s-md whitespace-nowrap break-keep") do
|
20
|
+
options.dig(:data, :prefix)
|
21
|
+
end +
|
22
|
+
super(attribute, options.reverse_merge(class: "#{input_styles_prefix} input-prefix rounded-l-none border-l-none"))
|
23
|
+
end + error_message(attribute)
|
21
24
|
end
|
22
25
|
else
|
23
26
|
content_tag :div, class: container_styles do
|
24
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
27
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
28
31
|
|
29
32
|
def email_field(method, options = {})
|
30
33
|
content_tag :div, class: container_styles do
|
31
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
34
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
35
38
|
def datetime_field(method, options = {})
|
36
39
|
content_tag :div, class: container_styles do
|
37
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
40
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
38
41
|
end
|
39
42
|
end
|
40
43
|
|
41
44
|
def text_area(method, options = {})
|
42
45
|
content_tag :div, class: container_styles do
|
43
|
-
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
46
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles)) + error_message(method)
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
47
50
|
def password_field(attribute, options = {})
|
48
51
|
content_tag :div, class: container_styles do
|
49
|
-
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
52
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles)) + error_message(attribute)
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
|
-
def select(method, choices = nil, options = {}, html_options = {}
|
56
|
+
def select(method, choices = nil, options = {}, html_options = {})
|
54
57
|
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
|
58
|
+
label(method) + meta_text(options) + super(method, choices, options, html_options.reverse_merge(class: select_styles)) + select_svg + error_message(method)
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
62
|
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
60
63
|
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))
|
64
|
+
label(method) + meta_text(options) + super(method, collection, value_method, text_method, options, html_options.reverse_merge(class: input_styles)) + error_message(method)
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
@@ -147,30 +150,9 @@ module Panda
|
|
147
150
|
end
|
148
151
|
end
|
149
152
|
|
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
153
|
def meta_text(options)
|
173
154
|
return unless options[:meta]
|
155
|
+
|
174
156
|
@template.content_tag(:p, options[:meta], class: "block text-black/60 text-sm mb-2")
|
175
157
|
end
|
176
158
|
|
@@ -197,7 +179,8 @@ module Panda
|
|
197
179
|
end
|
198
180
|
|
199
181
|
def select_svg
|
200
|
-
@template.content_tag(:svg,
|
182
|
+
@template.content_tag(:svg,
|
183
|
+
class: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", aria_hidden: true) do
|
201
184
|
@template.content_tag(:path, d: "M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z")
|
202
185
|
end
|
203
186
|
end
|
@@ -229,6 +212,14 @@ module Panda
|
|
229
212
|
def field_wrapper_styles
|
230
213
|
"mt-1"
|
231
214
|
end
|
215
|
+
|
216
|
+
def error_message(attribute)
|
217
|
+
return unless object.respond_to?(:errors) && object.errors[attribute]&.any?
|
218
|
+
|
219
|
+
content_tag(:p, class: "mt-2 text-sm text-red-600") do
|
220
|
+
object.errors[attribute].join(", ")
|
221
|
+
end
|
222
|
+
end
|
232
223
|
end
|
233
224
|
end
|
234
225
|
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">
|
@@ -29,13 +29,14 @@ module Panda
|
|
29
29
|
output = output.html_safe
|
30
30
|
base_heading_styles = "flex pt-1 text-black mb-5 -mt-1"
|
31
31
|
|
32
|
-
|
32
|
+
case level
|
33
|
+
when 1
|
33
34
|
content_tag(:h1, output, class: [base_heading_styles, "text-2xl font-medium", @additional_styles])
|
34
|
-
|
35
|
+
when 2
|
35
36
|
content_tag(:h2, output, class: [base_heading_styles, "text-xl font-medium", @additional_styles])
|
36
|
-
|
37
|
+
when 3
|
37
38
|
content_tag(:h3, output, class: [base_heading_styles, "text-xl", "font-light", @additional_styles])
|
38
|
-
|
39
|
+
when :panel
|
39
40
|
content_tag(:h3, output, class: ["text-base font-medium p-4 text-white"])
|
40
41
|
end
|
41
42
|
end
|
@@ -4,9 +4,9 @@ module Panda
|
|
4
4
|
module CMS
|
5
5
|
module Admin
|
6
6
|
class PanelComponent < ViewComponent::Base
|
7
|
-
renders_one :heading,
|
7
|
+
renders_one :heading, lambda { |text:, icon: "", level: :panel, additional_styles: ""|
|
8
8
|
Panda::CMS::Admin::HeadingComponent.new(text: text, icon: icon, level: level, additional_styles: additional_styles)
|
9
|
-
|
9
|
+
}
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
-
<% if user.is_a?(Panda::CMS::User) %>
|
1
|
+
<% if user.is_a?(Panda::CMS::User) && time %>
|
2
2
|
<%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "#{time_ago_in_words(time)} ago") %>
|
3
|
+
<% elsif user.is_a?(Panda::CMS::User) %>
|
4
|
+
<%= render Panda::CMS::Admin::UserDisplayComponent.new(user: user, metadata: "Not published") %>
|
3
5
|
<% elsif time %>
|
4
6
|
<div class="text-black/60"><%= time_ago_in_words(time) %> ago</div>
|
5
7
|
<% end %>
|
@@ -4,28 +4,15 @@ module Panda
|
|
4
4
|
module CMS
|
5
5
|
module Admin
|
6
6
|
class UserActivityComponent < ViewComponent::Base
|
7
|
-
attr_accessor :model
|
8
|
-
attr_accessor :time
|
9
|
-
attr_accessor :user
|
7
|
+
attr_accessor :model, :time, :user
|
10
8
|
|
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
|
9
|
+
# @param model [ActiveRecord::Base] Model instance to which the user activity is related
|
10
|
+
# @param at [ActiveSupport::TimeWithZone] Time of the activity
|
11
|
+
# @param user [Panda::CMS::User] User who performed the activity
|
12
|
+
def initialize(model: nil, at: nil, user: nil)
|
13
|
+
@model = model
|
14
|
+
@user = user if user.is_a?(::Panda::CMS::User)
|
15
|
+
@time = at if at.is_a?(::ActiveSupport::TimeWithZone)
|
29
16
|
end
|
30
17
|
end
|
31
18
|
end
|
@@ -17,15 +17,19 @@ module Panda
|
|
17
17
|
@options[:id] ||= "code-#{key.to_s.dasherize}"
|
18
18
|
@editable = editable
|
19
19
|
|
20
|
-
raise BlockError
|
20
|
+
raise BlockError, "Key 'code' is not allowed for CodeComponent" if key == :code
|
21
21
|
end
|
22
22
|
|
23
23
|
def call
|
24
24
|
# TODO: For the non-editable version, grab this from a cache or similar?
|
25
|
-
block = Panda::CMS::Block.find_by(kind: KIND, key: @key,
|
25
|
+
block = Panda::CMS::Block.find_by(kind: KIND, key: @key,
|
26
|
+
panda_cms_template_id: Current.page.panda_cms_template_id)
|
26
27
|
|
27
28
|
if block.nil?
|
28
|
-
|
29
|
+
unless Rails.env.production?
|
30
|
+
raise Panda::CMS::MissingBlockError, "Block with key #{@key} not found for page #{Current.page.title}"
|
31
|
+
end
|
32
|
+
|
29
33
|
return false
|
30
34
|
end
|
31
35
|
|
@@ -58,7 +62,7 @@ module Panda
|
|
58
62
|
|
59
63
|
def is_embedded?
|
60
64
|
# TODO: Check security on this - embed_id should match something?
|
61
|
-
request.params
|
65
|
+
request.params[:embed_id].present?
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
@@ -23,24 +23,25 @@ module Panda
|
|
23
23
|
|
24
24
|
@menu_items = @menu_items.order(:lft).map do |menu_item|
|
25
25
|
if is_active?(menu_item)
|
26
|
-
menu_item.define_singleton_method(:css_classes) { styles[:default]
|
26
|
+
menu_item.define_singleton_method(:css_classes) { "#{styles[:default]} #{styles[:active]}" }
|
27
27
|
else
|
28
|
-
menu_item.define_singleton_method(:css_classes) { styles[:default]
|
28
|
+
menu_item.define_singleton_method(:css_classes) { "#{styles[:default]} #{styles[:inactive]}" }
|
29
29
|
end
|
30
30
|
|
31
31
|
menu_item
|
32
32
|
end
|
33
33
|
|
34
34
|
# TODO: Surely don't need this but Current.page isn't working in the component
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
return unless @render_page_menu
|
36
|
+
|
37
|
+
@current_page = Panda::CMS::Page.find_by(path: @current_path)
|
38
|
+
@page_menu_styles = page_menu_styles
|
39
39
|
end
|
40
40
|
|
41
41
|
def is_active?(menu_item)
|
42
42
|
return true if @current_path == "/" && active_link?(menu_item.page.path, match: :exact)
|
43
43
|
return true if menu_item.page.path != "/" && active_link?(menu_item.page.path, match: :starts_with)
|
44
|
+
|
44
45
|
false
|
45
46
|
end
|
46
47
|
|
@@ -3,31 +3,29 @@
|
|
3
3
|
module Panda
|
4
4
|
module CMS
|
5
5
|
class PageMenuComponent < ViewComponent::Base
|
6
|
-
attr_accessor :page
|
7
|
-
attr_accessor :menu_item
|
8
|
-
attr_accessor :styles
|
6
|
+
attr_accessor :page, :menu_item, :styles
|
9
7
|
|
10
8
|
def initialize(page:, start_depth:, styles: {}, show_heading: true)
|
11
9
|
@page = page
|
12
10
|
|
13
|
-
|
14
|
-
start_page = if @page.depth == start_depth
|
15
|
-
@page
|
16
|
-
else
|
17
|
-
@page.ancestors.find { |anc| anc.depth == start_depth }
|
18
|
-
end
|
11
|
+
return if @page.nil?
|
19
12
|
|
20
|
-
|
21
|
-
|
13
|
+
start_page = if @page.depth == start_depth
|
14
|
+
@page
|
15
|
+
else
|
16
|
+
@page.ancestors.find { |anc| anc.depth == start_depth }
|
17
|
+
end
|
22
18
|
|
23
|
-
|
19
|
+
menu = start_page&.page_menu
|
20
|
+
return if menu.nil?
|
24
21
|
|
25
|
-
|
22
|
+
@menu_item = menu.menu_items.order(:lft)&.first
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
@show_heading = show_heading
|
25
|
+
|
26
|
+
# Set some default styles for sanity
|
27
|
+
@styles = styles
|
28
|
+
@styles[:indent_with] ||= "pl-2"
|
31
29
|
end
|
32
30
|
|
33
31
|
def render?
|