panda-cms 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +73 -0
- data/Rakefile +7 -0
- data/app/assets/builds/panda.cms.css +2808 -0
- data/app/assets/config/panda_cms_manifest.js +4 -0
- data/app/assets/stylesheets/panda/cms/application.tailwind.css +162 -0
- data/app/assets/stylesheets/panda/cms/editor.css +120 -0
- data/app/builders/panda/cms/form_builder.rb +234 -0
- data/app/components/panda/cms/admin/button_component.rb +70 -0
- data/app/components/panda/cms/admin/container_component.html.erb +13 -0
- data/app/components/panda/cms/admin/container_component.rb +13 -0
- data/app/components/panda/cms/admin/flash_message_component.html.erb +31 -0
- data/app/components/panda/cms/admin/flash_message_component.rb +47 -0
- data/app/components/panda/cms/admin/heading_component.rb +45 -0
- data/app/components/panda/cms/admin/panel_component.html.erb +7 -0
- data/app/components/panda/cms/admin/panel_component.rb +13 -0
- data/app/components/panda/cms/admin/slideover_component.html.erb +9 -0
- data/app/components/panda/cms/admin/slideover_component.rb +15 -0
- data/app/components/panda/cms/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/cms/admin/statistics_component.rb +17 -0
- data/app/components/panda/cms/admin/tab_bar_component.html.erb +35 -0
- data/app/components/panda/cms/admin/tab_bar_component.rb +15 -0
- data/app/components/panda/cms/admin/table_component.html.erb +29 -0
- data/app/components/panda/cms/admin/table_component.rb +46 -0
- data/app/components/panda/cms/admin/tag_component.rb +35 -0
- data/app/components/panda/cms/admin/user_activity_component.html.erb +5 -0
- data/app/components/panda/cms/admin/user_activity_component.rb +33 -0
- data/app/components/panda/cms/admin/user_display_component.html.erb +17 -0
- data/app/components/panda/cms/admin/user_display_component.rb +21 -0
- data/app/components/panda/cms/code_component.rb +64 -0
- data/app/components/panda/cms/grid_component.html.erb +6 -0
- data/app/components/panda/cms/grid_component.rb +15 -0
- data/app/components/panda/cms/menu_component.html.erb +6 -0
- data/app/components/panda/cms/menu_component.rb +58 -0
- data/app/components/panda/cms/page_menu_component.html.erb +21 -0
- data/app/components/panda/cms/page_menu_component.rb +38 -0
- data/app/components/panda/cms/rich_text_component.html.erb +6 -0
- data/app/components/panda/cms/rich_text_component.rb +84 -0
- data/app/components/panda/cms/text_component.rb +72 -0
- data/app/constraints/panda/cms/admin_constraint.rb +18 -0
- data/app/controllers/panda/cms/admin/block_contents_controller.rb +52 -0
- data/app/controllers/panda/cms/admin/dashboard_controller.rb +20 -0
- data/app/controllers/panda/cms/admin/files_controller.rb +21 -0
- data/app/controllers/panda/cms/admin/forms_controller.rb +53 -0
- data/app/controllers/panda/cms/admin/menus_controller.rb +30 -0
- data/app/controllers/panda/cms/admin/pages_controller.rb +91 -0
- data/app/controllers/panda/cms/admin/posts_controller.rb +146 -0
- data/app/controllers/panda/cms/admin/sessions_controller.rb +94 -0
- data/app/controllers/panda/cms/admin/settings/bulk_editor_controller.rb +37 -0
- data/app/controllers/panda/cms/admin/settings_controller.rb +20 -0
- data/app/controllers/panda/cms/application_controller.rb +57 -0
- data/app/controllers/panda/cms/errors_controller.rb +33 -0
- data/app/controllers/panda/cms/form_submissions_controller.rb +23 -0
- data/app/controllers/panda/cms/pages_controller.rb +72 -0
- data/app/controllers/panda/cms/posts_controller.rb +13 -0
- data/app/helpers/panda/cms/admin/files_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/admin/posts_helper.rb +48 -0
- data/app/helpers/panda/cms/application_helper.rb +120 -0
- data/app/helpers/panda/cms/pages_helper.rb +6 -0
- data/app/helpers/panda/cms/theme_helper.rb +18 -0
- data/app/javascript/panda/cms/@editorjs--editorjs.js +2577 -0
- data/app/javascript/panda/cms/@hotwired--stimulus.js +4 -0
- data/app/javascript/panda/cms/@hotwired--turbo.js +160 -0
- data/app/javascript/panda/cms/@rails--actioncable--src.js +4 -0
- data/app/javascript/panda/cms/application_panda_cms.js +39 -0
- data/app/javascript/panda/cms/controllers/dashboard_controller.js +7 -0
- data/app/javascript/panda/cms/controllers/editor_form_controller.js +77 -0
- data/app/javascript/panda/cms/controllers/editor_iframe_controller.js +320 -0
- data/app/javascript/panda/cms/controllers/index.js +48 -0
- data/app/javascript/panda/cms/controllers/slug_controller.js +87 -0
- data/app/javascript/panda/cms/editor/css_extractor.js +80 -0
- data/app/javascript/panda/cms/editor/editor_js_config.js +177 -0
- data/app/javascript/panda/cms/editor/editor_js_initializer.js +285 -0
- data/app/javascript/panda/cms/editor/plain_text_editor.js +110 -0
- data/app/javascript/panda/cms/editor/resource_loader.js +115 -0
- data/app/javascript/panda/cms/tailwindcss-stimulus-components.js +4 -0
- data/app/jobs/panda/cms/application_job.rb +6 -0
- data/app/jobs/panda/cms/record_visit_job.rb +31 -0
- data/app/mailers/panda/cms/application_mailer.rb +8 -0
- data/app/mailers/panda/cms/form_mailer.rb +21 -0
- data/app/models/action_text/rich_text_version.rb +6 -0
- data/app/models/panda/cms/application_record.rb +7 -0
- data/app/models/panda/cms/block.rb +34 -0
- data/app/models/panda/cms/block_content.rb +18 -0
- data/app/models/panda/cms/block_content_version.rb +8 -0
- data/app/models/panda/cms/breadcrumb.rb +12 -0
- data/app/models/panda/cms/current.rb +17 -0
- data/app/models/panda/cms/form.rb +9 -0
- data/app/models/panda/cms/form_submission.rb +7 -0
- data/app/models/panda/cms/menu.rb +52 -0
- data/app/models/panda/cms/menu_item.rb +58 -0
- data/app/models/panda/cms/page.rb +96 -0
- data/app/models/panda/cms/page_version.rb +8 -0
- data/app/models/panda/cms/post.rb +60 -0
- data/app/models/panda/cms/post_version.rb +8 -0
- data/app/models/panda/cms/redirect.rb +11 -0
- data/app/models/panda/cms/template.rb +124 -0
- data/app/models/panda/cms/template_version.rb +8 -0
- data/app/models/panda/cms/user.rb +31 -0
- data/app/models/panda/cms/version.rb +8 -0
- data/app/models/panda/cms/visit.rb +9 -0
- data/app/services/panda/cms/html_to_editor_js_converter.rb +200 -0
- data/app/views/active_storage/blobs/blobs/_blob.html.erb +14 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/panda/cms/application.html.erb +41 -0
- data/app/views/layouts/panda/cms/public.html.erb +3 -0
- data/app/views/panda/cms/admin/dashboard/show.html.erb +12 -0
- data/app/views/panda/cms/admin/files/index.html.erb +124 -0
- data/app/views/panda/cms/admin/files/show.html.erb +2 -0
- data/app/views/panda/cms/admin/forms/edit.html.erb +0 -0
- data/app/views/panda/cms/admin/forms/index.html.erb +13 -0
- data/app/views/panda/cms/admin/forms/new.html.erb +15 -0
- data/app/views/panda/cms/admin/forms/show.html.erb +35 -0
- data/app/views/panda/cms/admin/menus/index.html.erb +8 -0
- data/app/views/panda/cms/admin/pages/edit.html.erb +36 -0
- data/app/views/panda/cms/admin/pages/index.html.erb +22 -0
- data/app/views/panda/cms/admin/pages/new.html.erb +15 -0
- data/app/views/panda/cms/admin/pages/show.html.erb +1 -0
- data/app/views/panda/cms/admin/posts/_form.html.erb +29 -0
- data/app/views/panda/cms/admin/posts/edit.html.erb +6 -0
- data/app/views/panda/cms/admin/posts/index.html.erb +18 -0
- data/app/views/panda/cms/admin/posts/new.html.erb +6 -0
- data/app/views/panda/cms/admin/sessions/new.html.erb +17 -0
- data/app/views/panda/cms/admin/settings/bulk_editor/new.html.erb +68 -0
- data/app/views/panda/cms/admin/settings/index.html.erb +21 -0
- data/app/views/panda/cms/admin/settings/insta.html +4 -0
- data/app/views/panda/cms/admin/shared/_breadcrumbs.html.erb +28 -0
- data/app/views/panda/cms/admin/shared/_flash.html.erb +5 -0
- data/app/views/panda/cms/admin/shared/_sidebar.html.erb +41 -0
- data/app/views/panda/cms/form_mailer/notification_email.html.erb +11 -0
- data/app/views/panda/cms/shared/_editor.html.erb +0 -0
- data/app/views/panda/cms/shared/_favicons.html.erb +9 -0
- data/app/views/panda/cms/shared/_footer.html.erb +2 -0
- data/app/views/panda/cms/shared/_header.html.erb +15 -0
- data/app/views/panda/cms/shared/_importmap.html.erb +33 -0
- data/config/importmap.rb +13 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/initializers/panda/cms/form_errors.rb +38 -0
- data/config/initializers/panda/cms/healthcheck_log_silencer.rb +11 -0
- data/config/initializers/panda/cms/paper_trail.rb +7 -0
- data/config/initializers/panda/cms.rb +10 -0
- data/config/initializers/zeitwork.rb +3 -0
- data/config/locales/en.yml +49 -0
- data/config/puma/test.rb +9 -0
- data/config/routes.rb +48 -0
- data/config/tailwind.config.js +37 -0
- data/db/migrate/20240205223709_create_panda_cms_pages.rb +9 -0
- data/db/migrate/20240219213327_create_panda_cms_page_versions.rb +14 -0
- data/db/migrate/20240303002805_create_panda_cms_templates.rb +11 -0
- data/db/migrate/20240303003434_create_panda_cms_template_versions.rb +14 -0
- data/db/migrate/20240303022441_create_panda_cms_blocks.rb +13 -0
- data/db/migrate/20240303024256_create_panda_cms_block_contents.rb +10 -0
- data/db/migrate/20240303024746_create_panda_cms_block_content_versions.rb +14 -0
- data/db/migrate/20240303233238_add_panda_cms_menu_table.rb +10 -0
- data/db/migrate/20240303234724_add_panda_cms_menu_item_table.rb +12 -0
- data/db/migrate/20240304134343_add_parent_id_to_panda_cms_pages.rb +5 -0
- data/db/migrate/20240305000000_convert_html_content_to_editor_js.rb +82 -0
- data/db/migrate/20240315125411_add_status_to_panda_cms_pages.rb +9 -0
- data/db/migrate/20240315125421_add_nested_sets_to_panda_cms_pages.rb +16 -0
- data/db/migrate/20240316212822_add_kind_to_panda_cms_menus.rb +6 -0
- data/db/migrate/20240316221425_add_start_page_to_panda_cms_menus.rb +5 -0
- data/db/migrate/20240316230706_add_nested_to_panda_cms_menu_items.rb +24 -0
- data/db/migrate/20240317010532_create_panda_cms_users.rb +12 -0
- data/db/migrate/20240317161534_add_max_uses_to_panda_cms_template.rb +7 -0
- data/db/migrate/20240317163053_reset_counter_cache_on_panda_cms_template.rb +5 -0
- data/db/migrate/20240317214827_create_panda_cms_redirects.rb +14 -0
- data/db/migrate/20240317230622_create_panda_cms_visits.rb +13 -0
- data/db/migrate/20240324205703_create_active_storage_tables.active_storage.rb +58 -0
- data/db/migrate/20240408084718_default_panda_cms_users_admin_to_false.rb +5 -0
- data/db/migrate/20240701225422_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
- data/db/migrate/20240701225423_create_active_storage_variant_records.active_storage.rb +28 -0
- data/db/migrate/20240701225424_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
- data/db/migrate/20240804235210_create_panda_cms_forms.rb +11 -0
- data/db/migrate/20240805013612_create_panda_cms_form_submissions.rb +9 -0
- data/db/migrate/20240805121123_create_panda_cms_posts.rb +27 -0
- data/db/migrate/20240805123104_create_panda_cms_post_versions.rb +14 -0
- data/db/migrate/20240806112735_fix_panda_cms_visits_column_names.rb +13 -0
- data/db/migrate/20240806204412_add_completion_path_to_panda_cms_forms.rb +5 -0
- data/db/migrate/20240820081917_change_form_submissions_to_submission_count.rb +5 -0
- data/db/migrate/20240904200605_create_action_text_tables.action_text.rb +24 -0
- data/db/migrate/20240923234535_add_depth_to_panda_cms_menus.rb +11 -0
- data/db/migrate/20241031205109_add_cached_content_to_panda_cms_block_contents.rb +5 -0
- data/db/migrate/20241119214548_convert_post_content_to_editor_js.rb +35 -0
- data/db/migrate/20241119214549_remove_action_text_from_posts.rb +9 -0
- data/db/migrate/20241120000419_remove_post_tag_references.rb +19 -0
- data/db/migrate/20241120110943_add_editor_js_to_posts.rb +27 -0
- data/db/migrate/20241120113859_add_cached_content_to_panda_cms_posts.rb +5 -0
- data/db/migrate/20241123234140_remove_post_tag_id_from_posts.rb +5 -0
- data/db/migrate/migrate +1 -0
- data/db/seeds.rb +5 -0
- data/lib/generators/panda/cms/install_generator.rb +29 -0
- data/lib/panda/cms/bulk_editor.rb +171 -0
- data/lib/panda/cms/demo_site_generator.rb +67 -0
- data/lib/panda/cms/editor_js/blocks/alert.rb +34 -0
- data/lib/panda/cms/editor_js/blocks/base.rb +33 -0
- data/lib/panda/cms/editor_js/blocks/header.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/image.rb +36 -0
- data/lib/panda/cms/editor_js/blocks/list.rb +32 -0
- data/lib/panda/cms/editor_js/blocks/paragraph.rb +15 -0
- data/lib/panda/cms/editor_js/blocks/quote.rb +41 -0
- data/lib/panda/cms/editor_js/blocks/table.rb +50 -0
- data/lib/panda/cms/editor_js/renderer.rb +124 -0
- data/lib/panda/cms/editor_js.rb +16 -0
- data/lib/panda/cms/editor_js_content.rb +21 -0
- data/lib/panda/cms/engine.rb +257 -0
- data/lib/panda/cms/exceptions_app.rb +26 -0
- data/lib/panda/cms/railtie.rb +11 -0
- data/lib/panda/cms/slug.rb +24 -0
- data/lib/panda/cms.rb +0 -0
- data/lib/panda-cms/version.rb +5 -0
- data/lib/panda-cms.rb +81 -0
- data/lib/tasks/panda_cms.rake +54 -0
- data/lib/templates/erb/scaffold/_form.html.erb.tt +43 -0
- data/lib/templates/erb/scaffold/edit.html.erb.tt +8 -0
- data/lib/templates/erb/scaffold/index.html.erb.tt +14 -0
- data/lib/templates/erb/scaffold/new.html.erb.tt +7 -0
- data/lib/templates/erb/scaffold/partial.html.erb.tt +22 -0
- data/lib/templates/erb/scaffold/show.html.erb.tt +15 -0
- data/public/panda-cms-assets/favicons/android-chrome-192x192.png +0 -0
- data/public/panda-cms-assets/favicons/android-chrome-512x512.png +0 -0
- data/public/panda-cms-assets/favicons/apple-touch-icon.png +0 -0
- data/public/panda-cms-assets/favicons/browserconfig.xml +9 -0
- data/public/panda-cms-assets/favicons/favicon-16x16.png +0 -0
- data/public/panda-cms-assets/favicons/favicon-32x32.png +0 -0
- data/public/panda-cms-assets/favicons/favicon.ico +0 -0
- data/public/panda-cms-assets/favicons/mstile-150x150.png +0 -0
- data/public/panda-cms-assets/favicons/safari-pinned-tab.svg +61 -0
- data/public/panda-cms-assets/favicons/site.webmanifest +14 -0
- data/public/panda-cms-assets/panda-logo-screenprint.png +0 -0
- data/public/panda-cms-assets/panda-nav.png +0 -0
- data/public/panda-cms-assets/rich_text_editor.css +568 -0
- metadata +654 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
@tailwind base;
|
2
|
+
@tailwind components;
|
3
|
+
@tailwind utilities;
|
4
|
+
|
5
|
+
@layer base {
|
6
|
+
html[data-theme="default"] {
|
7
|
+
--color-white: 249 249 249; /* #F9F9F9 */
|
8
|
+
--color-black: 26 22 29; /* #1A161D */
|
9
|
+
|
10
|
+
--color-light: 238 206 230; /* #EECEE6 */
|
11
|
+
--color-mid: 141 94 183; /* #8D5EB7 */
|
12
|
+
--color-dark: 33 29 73; /* #211D49 */
|
13
|
+
|
14
|
+
--color-highlight: 208 64 20; /* #D04014 */
|
15
|
+
|
16
|
+
--color-active: 0 135 85; /* #008755 */
|
17
|
+
--color-warning: 250 207 142; /* #FACF8E */
|
18
|
+
--color-inactive: 216 247 245; /* #d6e4f7 */
|
19
|
+
--color-error: 245 129 129; /* #F58181 */
|
20
|
+
}
|
21
|
+
|
22
|
+
html[data-theme="sky"] {
|
23
|
+
--color-white: 249 249 249; /* #F9F9F9 */
|
24
|
+
--color-black: 26 22 29; /* #1A161D */
|
25
|
+
--color-light: 204 238 242; /* #CCEEF2 */
|
26
|
+
--color-mid: 42 102 159; /* #2A669F */
|
27
|
+
--color-dark: 20 32 74; /* #14204A */
|
28
|
+
--color-highlight: 208 64 20; /* #D04014 */
|
29
|
+
|
30
|
+
--color-active: 166 211 129; /* #A6D381 */
|
31
|
+
--color-warning: 244 190 102; /* #F4BE66 */
|
32
|
+
--color-inactive: 216 247 245; /* #d6e4f7 */
|
33
|
+
--color-error: 208 64 20; /* #D04014 */
|
34
|
+
}
|
35
|
+
|
36
|
+
a.block-link:after {
|
37
|
+
position: absolute;
|
38
|
+
content: "";
|
39
|
+
inset: 0;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
/* Default editor styles */
|
44
|
+
@layer components {
|
45
|
+
.codex-editor__redactor .ce-block .ce-block__content {
|
46
|
+
@apply text-base font-normal font-sans text-dark leading-[1.6] space-y-[1.6rem];
|
47
|
+
|
48
|
+
h1.ce-header {
|
49
|
+
@apply text-3xl md:text-4xl font-semibold font-sans leading-[1.2];
|
50
|
+
}
|
51
|
+
|
52
|
+
h2.ce-header {
|
53
|
+
@apply text-2xl font-medium font-sans leading-[1.3] mb-4 mt-8;
|
54
|
+
}
|
55
|
+
|
56
|
+
h3.ce-header {
|
57
|
+
@apply text-xl font-normal font-sans leading-[1.3] mb-4 mt-6;
|
58
|
+
}
|
59
|
+
|
60
|
+
p,
|
61
|
+
li {
|
62
|
+
@apply leading-[1.6] tracking-wide max-w-[85ch];
|
63
|
+
|
64
|
+
a {
|
65
|
+
@apply text-[#1A9597] underline underline-offset-2 hover:text-[#158486] focus:outline-2 focus:outline-offset-2 focus:outline-[#1A9597];
|
66
|
+
}
|
67
|
+
|
68
|
+
strong,
|
69
|
+
b {
|
70
|
+
@apply font-semibold;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
p {
|
75
|
+
@apply mb-4;
|
76
|
+
}
|
77
|
+
|
78
|
+
.cdx-quote {
|
79
|
+
@apply bg-[#eef0f3] border-l-inactive border-l-8 p-6 mb-4;
|
80
|
+
|
81
|
+
.cdx-quote__caption {
|
82
|
+
@apply block ml-6 mt-2 text-sm text-dark;
|
83
|
+
}
|
84
|
+
|
85
|
+
.cdx-quote__text {
|
86
|
+
quotes: "\201C""\201D""\2018""\2019";
|
87
|
+
@apply pl-6;
|
88
|
+
|
89
|
+
&:before {
|
90
|
+
@apply -ml-8 mr-2 text-dark text-6xl leading-4 align-text-bottom font-serif;
|
91
|
+
content: open-quote;
|
92
|
+
}
|
93
|
+
|
94
|
+
p {
|
95
|
+
@apply inline italic text-lg;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
.cdx-list {
|
101
|
+
@apply mb-4 pl-6;
|
102
|
+
|
103
|
+
&--ordered {
|
104
|
+
@apply list-decimal;
|
105
|
+
}
|
106
|
+
|
107
|
+
&--unordered {
|
108
|
+
@apply list-disc;
|
109
|
+
}
|
110
|
+
|
111
|
+
.cdx-list {
|
112
|
+
@apply mt-2 mb-0;
|
113
|
+
}
|
114
|
+
|
115
|
+
.cdx-list__item {
|
116
|
+
@apply mb-2 pl-2;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
.cdx-nested-list {
|
121
|
+
@apply mb-4 pl-6;
|
122
|
+
|
123
|
+
&--ordered {
|
124
|
+
@apply list-decimal;
|
125
|
+
}
|
126
|
+
|
127
|
+
&--unordered {
|
128
|
+
@apply list-disc;
|
129
|
+
}
|
130
|
+
|
131
|
+
.cdx-nested-list {
|
132
|
+
@apply mt-2 mb-0;
|
133
|
+
}
|
134
|
+
|
135
|
+
.cdx-nested-list__item {
|
136
|
+
@apply mb-2 pl-2;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
.cdx-table {
|
141
|
+
@apply w-full border-collapse border-2 border-dark my-6;
|
142
|
+
|
143
|
+
&__head {
|
144
|
+
@apply font-semibold border-dark border-r-2 p-3 bg-light;
|
145
|
+
}
|
146
|
+
|
147
|
+
&__row {
|
148
|
+
@apply border-dark border-b-2;
|
149
|
+
}
|
150
|
+
|
151
|
+
&__cell {
|
152
|
+
@apply border-dark border-r-2 p-3;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
.cdx-embed {
|
157
|
+
iframe {
|
158
|
+
@apply w-full border-none;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
/* Base content styles, where .codex-editor applies them to the Panda editor too */
|
2
|
+
@layer components {
|
3
|
+
.codex-editor__redactor .ce-block .ce-block__content {
|
4
|
+
@apply text-base font-normal font-sans text-dark leading-[1.6] space-y-[1.6rem];
|
5
|
+
|
6
|
+
h1.ce-header {
|
7
|
+
@apply text-3xl md:text-4xl font-semibold font-sans text-[#104071] leading-[1.2] max-w-[85ch];
|
8
|
+
}
|
9
|
+
|
10
|
+
h2.ce-header {
|
11
|
+
@apply text-2xl font-medium font-sans text-[#104071] leading-[1.3] mb-4 mt-8 max-w-[85ch];
|
12
|
+
}
|
13
|
+
|
14
|
+
h3.ce-header {
|
15
|
+
@apply text-xl font-normal font-sans text-[#104071] leading-[1.3] mb-4 mt-6 max-w-[85ch];
|
16
|
+
}
|
17
|
+
|
18
|
+
p,
|
19
|
+
li {
|
20
|
+
@apply leading-[1.6] tracking-wide max-w-[85ch];
|
21
|
+
|
22
|
+
a {
|
23
|
+
@apply text-[#1A9597] underline underline-offset-2 hover:text-[#158486] focus:outline-2 focus:outline-offset-2 focus:outline-[#1A9597];
|
24
|
+
}
|
25
|
+
|
26
|
+
strong,
|
27
|
+
b {
|
28
|
+
@apply font-semibold;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
p {
|
33
|
+
@apply mb-4;
|
34
|
+
}
|
35
|
+
|
36
|
+
.cdx-quote {
|
37
|
+
@apply bg-[#eef0f3] border-l-inactive border-l-8 p-6 mb-4;
|
38
|
+
|
39
|
+
.cdx-quote__caption {
|
40
|
+
@apply block ml-6 mt-2 text-sm text-dark;
|
41
|
+
}
|
42
|
+
|
43
|
+
.cdx-quote__text {
|
44
|
+
quotes: "\201C" "\201D" "\2018" "\2019";
|
45
|
+
@apply pl-6;
|
46
|
+
|
47
|
+
&:before {
|
48
|
+
@apply -ml-8 mr-2 text-dark text-6xl leading-4 align-text-bottom font-serif;
|
49
|
+
content: open-quote;
|
50
|
+
}
|
51
|
+
|
52
|
+
p {
|
53
|
+
@apply inline italic text-lg;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
.cdx-list {
|
59
|
+
@apply mb-4 pl-6;
|
60
|
+
|
61
|
+
&--ordered {
|
62
|
+
@apply list-decimal;
|
63
|
+
}
|
64
|
+
|
65
|
+
&--unordered {
|
66
|
+
@apply list-disc;
|
67
|
+
}
|
68
|
+
|
69
|
+
.cdx-list {
|
70
|
+
@apply mt-2 mb-0;
|
71
|
+
}
|
72
|
+
|
73
|
+
.cdx-list__item {
|
74
|
+
@apply mb-2 pl-2;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
.cdx-nested-list {
|
79
|
+
@apply mb-4 pl-6;
|
80
|
+
|
81
|
+
&--ordered {
|
82
|
+
@apply list-decimal;
|
83
|
+
}
|
84
|
+
|
85
|
+
&--unordered {
|
86
|
+
@apply list-disc;
|
87
|
+
}
|
88
|
+
|
89
|
+
.cdx-nested-list {
|
90
|
+
@apply mt-2 mb-0;
|
91
|
+
}
|
92
|
+
|
93
|
+
.cdx-nested-list__item {
|
94
|
+
@apply mb-2 pl-2;
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
.cdx-table {
|
99
|
+
@apply w-full border-collapse border-2 border-dark my-6;
|
100
|
+
|
101
|
+
&__head {
|
102
|
+
@apply font-semibold border-dark border-r-2 p-3 bg-light;
|
103
|
+
}
|
104
|
+
|
105
|
+
&__row {
|
106
|
+
@apply border-dark border-b-2;
|
107
|
+
}
|
108
|
+
|
109
|
+
&__cell {
|
110
|
+
@apply border-dark border-r-2 p-3;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
.cdx-embed {
|
115
|
+
iframe {
|
116
|
+
@apply w-full border-none;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
6
|
+
include ActionView::Helpers::TagHelper
|
7
|
+
include ActionView::Helpers::FormTagHelper
|
8
|
+
|
9
|
+
def label(attribute, text = nil, options = {}, &block)
|
10
|
+
super(attribute, text, options.reverse_merge(class: label_styles))
|
11
|
+
end
|
12
|
+
|
13
|
+
def text_field(attribute, options = {})
|
14
|
+
if options.dig(:data, :prefix)
|
15
|
+
content_tag :div, class: container_styles do
|
16
|
+
label(attribute) + meta_text(options) +
|
17
|
+
content_tag(:div, class: "flex flex-grow") do
|
18
|
+
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
|
+
super(attribute, options.reverse_merge(class: input_styles_prefix + " input-prefix rounded-l-none border-l-none"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
content_tag :div, class: container_styles do
|
24
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def email_field(method, options = {})
|
30
|
+
content_tag :div, class: container_styles do
|
31
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def datetime_field(method, options = {})
|
36
|
+
content_tag :div, class: container_styles do
|
37
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def text_area(method, options = {})
|
42
|
+
content_tag :div, class: container_styles do
|
43
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def password_field(attribute, options = {})
|
48
|
+
content_tag :div, class: container_styles do
|
49
|
+
label(attribute) + meta_text(options) + super(attribute, options.reverse_merge(class: input_styles))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
54
|
+
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
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
|
60
|
+
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))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
|
66
|
+
wrap_field(method, options) do
|
67
|
+
super(
|
68
|
+
method,
|
69
|
+
priority_zones,
|
70
|
+
options,
|
71
|
+
html_options.reverse_merge(class: select_styles)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def file_field(method, options = {})
|
77
|
+
content_tag :div, class: container_styles do
|
78
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: "file:rounded file:border-0 file:text-sm file:bg-white file:text-gray-500 hover:file:bg-gray-50 bg-white px-2.5 hover:bg-gray-50".concat(input_styles)))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def button(value = nil, options = {}, &block)
|
83
|
+
value ||= submit_default_value
|
84
|
+
options = options.dup
|
85
|
+
|
86
|
+
# Handle formmethod specially
|
87
|
+
if options[:formmethod] == "delete"
|
88
|
+
options[:name] = "_method"
|
89
|
+
options[:value] = "delete"
|
90
|
+
end
|
91
|
+
|
92
|
+
base_classes = [
|
93
|
+
"inline-flex items-center rounded-md",
|
94
|
+
"px-3 py-2",
|
95
|
+
"text-base font-semibold",
|
96
|
+
"shadow-sm"
|
97
|
+
]
|
98
|
+
|
99
|
+
# Only add fa-circle-check for non-block buttons
|
100
|
+
base_classes << "fa-circle-check" unless block_given?
|
101
|
+
|
102
|
+
options[:class] = [
|
103
|
+
*base_classes,
|
104
|
+
options[:class]
|
105
|
+
].compact.join(" ")
|
106
|
+
|
107
|
+
if block_given?
|
108
|
+
@template.button_tag(options, &block)
|
109
|
+
else
|
110
|
+
@template.button_tag(value, options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def submit(value = nil, options = {})
|
115
|
+
value ||= submit_default_value
|
116
|
+
|
117
|
+
# Use the same style logic as ButtonComponent
|
118
|
+
action = object.persisted? ? :save : :create
|
119
|
+
button_classes = case action
|
120
|
+
when :save, :create
|
121
|
+
"text-white bg-active"
|
122
|
+
when :save_inactive
|
123
|
+
"text-white bg-inactive"
|
124
|
+
when :secondary
|
125
|
+
"text-dark border-2 border-mid bg-transparent hover:bg-light transition-all"
|
126
|
+
else
|
127
|
+
"text-dark border-2 border-mid bg-transparent hover:bg-light transition-all"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Combine with common button classes
|
131
|
+
classes = "inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 px-3 py-2 #{button_classes}"
|
132
|
+
|
133
|
+
options[:class] = options[:class] ? "#{options[:class]} #{classes}" : classes
|
134
|
+
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
139
|
+
content_tag :div, class: container_styles do
|
140
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: "border-gray-300 ml-2"), checked_value, unchecked_value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def date_field(method, options = {})
|
145
|
+
content_tag :div, class: container_styles do
|
146
|
+
label(method) + meta_text(options) + super(method, options.reverse_merge(class: input_styles))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
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
|
+
def meta_text(options)
|
173
|
+
return unless options[:meta]
|
174
|
+
@template.content_tag(:p, options[:meta], class: "block text-black/60 text-sm mb-2")
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def label_styles
|
180
|
+
"font-light inline-block mb-1 text-base leading-6"
|
181
|
+
end
|
182
|
+
|
183
|
+
def base_input_styles
|
184
|
+
"bg-white block w-full rounded-md border border-mid focus:border-mid p-2 text-dark outline-0 focus:outline-0 ring-0 focus:ring-0 focus:ring-mid ring-offset-0 focus:ring-offset-0 shadow-none focus:shadow-none border-mid"
|
185
|
+
end
|
186
|
+
|
187
|
+
def input_styles
|
188
|
+
base_input_styles
|
189
|
+
end
|
190
|
+
|
191
|
+
def input_styles_prefix
|
192
|
+
input_styles.concat(" prefix")
|
193
|
+
end
|
194
|
+
|
195
|
+
def select_styles
|
196
|
+
"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-dark text-base outline-0 outline-dark focus:outline focus:-outline-offset-2 focus:outline-dark"
|
197
|
+
end
|
198
|
+
|
199
|
+
def select_svg
|
200
|
+
@template.content_tag(:svg, class: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400", aria_hidden: true) do
|
201
|
+
@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
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def button_styles
|
206
|
+
"inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-0 focus-visible:outline-offset-none text-dark border-2 border-mid bg-transparent hover:bg-light transition-all gap-x-1.5 px-3 py-2 text-base gap-x-1.5 px-2.5 py-1.5 mt-2 "
|
207
|
+
end
|
208
|
+
|
209
|
+
def container_styles
|
210
|
+
"panda-cms-field-container mb-4"
|
211
|
+
end
|
212
|
+
|
213
|
+
def textarea_styles
|
214
|
+
input_styles.concat(" min-h-32")
|
215
|
+
end
|
216
|
+
|
217
|
+
def submit_default_value
|
218
|
+
object.persisted? ? "Update #{object.class.name.demodulize}" : "Create #{object.class.name.demodulize}"
|
219
|
+
end
|
220
|
+
|
221
|
+
def wrap_field(method, options = {}, &block)
|
222
|
+
@template.content_tag(:div, class: "panda-cms-field-container") do
|
223
|
+
label(method, class: "font-light inline-block mb-1 text-base leading-6") +
|
224
|
+
meta_text(options) +
|
225
|
+
@template.content_tag(:div, class: field_wrapper_styles, &block)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def field_wrapper_styles
|
230
|
+
"mt-1"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
module Admin
|
6
|
+
class ButtonComponent < ViewComponent::Base
|
7
|
+
attr_accessor :text, :action, :link, :icon, :size, :data
|
8
|
+
|
9
|
+
def initialize(text: "Button", action: nil, data: {}, link: "#", icon: nil, size: :regular, id: nil)
|
10
|
+
@text = text
|
11
|
+
@action = action
|
12
|
+
@data = data
|
13
|
+
@link = link
|
14
|
+
@icon = icon
|
15
|
+
@size = size
|
16
|
+
@id = id
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
@icon = set_icon_from_action(@action) if @action && @icon.nil?
|
21
|
+
icon = content_tag(:i, "", class: "mr-2 fa-regular fa-#{@icon}") if @icon
|
22
|
+
@text = "#{icon} #{@text.titleize}".html_safe
|
23
|
+
|
24
|
+
classes = "inline-flex items-center rounded-md font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 "
|
25
|
+
|
26
|
+
case @size
|
27
|
+
when :small, :sm
|
28
|
+
classes += "gap-x-1.5 px-2.5 py-1.5 text-sm "
|
29
|
+
when :medium, :regular, :md
|
30
|
+
classes += "gap-x-1.5 px-3 py-2 text-base "
|
31
|
+
when :large, :lg
|
32
|
+
classes += "gap-x-2 px-3.5 py-2.5 text-lg "
|
33
|
+
end
|
34
|
+
|
35
|
+
classes += case @action
|
36
|
+
when :save, :create
|
37
|
+
"text-white bg-active"
|
38
|
+
when :save_inactive
|
39
|
+
"text-white bg-inactive"
|
40
|
+
when :secondary
|
41
|
+
"text-dark border-2 border-dark bg-transparent hover:bg-light transition-all "
|
42
|
+
when :delete, :destroy, :danger
|
43
|
+
"text-error border border-error bg-red-100 hover:bg-red-200 hover:text-error focus-visible:outline-red-300 "
|
44
|
+
else
|
45
|
+
"text-dark border-2 border-dark bg-transparent hover:bg-light transition-all "
|
46
|
+
end
|
47
|
+
|
48
|
+
content_tag :a, href: @link, class: classes, data: @data, id: @id do
|
49
|
+
@text
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def set_icon_from_action(action)
|
56
|
+
case action
|
57
|
+
when :add, :new, :create
|
58
|
+
"plus"
|
59
|
+
when :save
|
60
|
+
"check"
|
61
|
+
when :edit, :update
|
62
|
+
"pencil"
|
63
|
+
when :delete, :destroy
|
64
|
+
"trash"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<main class="overflow-auto flex-1 h-full min-h-full max-h-full">
|
2
|
+
<div class="overflow-auto px-2 pt-4 mx-auto sm:px-6 lg:px-6">
|
3
|
+
<%= heading %>
|
4
|
+
<%= tab_bar %>
|
5
|
+
<%# I mean, you can edit this CSS if you want, but I hope you want to lose 2 hours to iFrame joy? %>
|
6
|
+
<section class="flex-auto h-[calc(100vh-10rem)]">
|
7
|
+
<div class="flex-1 mt-4 w-full h-full">
|
8
|
+
<%= content %>
|
9
|
+
</div>
|
10
|
+
<%= slideover %>
|
11
|
+
</section>
|
12
|
+
</div>
|
13
|
+
</main>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
module Admin
|
6
|
+
class ContainerComponent < ViewComponent::Base
|
7
|
+
renders_one :heading, "Panda::CMS::Admin::HeadingComponent"
|
8
|
+
renders_one :tab_bar, "Panda::CMS::Admin::TabBarComponent"
|
9
|
+
renders_one :slideover, "Panda::CMS::Admin::SlideoverComponent"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="fixed top-2 right-2 z-50 p-2 space-y-4 w-full max-w-sm pointer-events-none sm:items-end"
|
2
|
+
data-controller="alert"
|
3
|
+
<% if @temporary %> data-alert-dismiss-after-value="3000"<% end %>
|
4
|
+
data-transition-enter="ease-in-out duration-500"
|
5
|
+
data-transition-enter-from="translate-x-full opacity-0"
|
6
|
+
data-transition-enter-to="translate-x-0 opacity-100"
|
7
|
+
data-transition-leave="ease-in-out duration-500"
|
8
|
+
data-transition-leave-from="translate-x-0 opacity-100"
|
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 pointer-events-auto">
|
11
|
+
<div class="p-4">
|
12
|
+
<div class="flex items-start">
|
13
|
+
<div class="flex-shrink-0">
|
14
|
+
<i class="fa-regular text-xl <%= icon_css %> <%= text_colour_css %>"></i>
|
15
|
+
</div>
|
16
|
+
<div class="flex-1 pt-0.5 ml-3 w-0">
|
17
|
+
<p class="mb-1 text-sm font-medium flash-message-title <%= text_colour_css %>"><%= kind.to_s.titleize %></p>
|
18
|
+
<p class="mt-1 mb-0 text-sm text-gray-500 flash-message-text"><%= message %></p>
|
19
|
+
</div>
|
20
|
+
<div class="flex flex-shrink-0 ml-4">
|
21
|
+
<button data-action="alert#close" type="button" class="inline-flex text-gray-400 bg-white rounded-md transition duration-150 ease-in-out hover:text-gray-500 focus:ring-2 focus:ring-offset-2 focus:outline-none focus:ring-sky-500">
|
22
|
+
<span class="sr-only">Close</span>
|
23
|
+
<svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
24
|
+
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
25
|
+
</svg>
|
26
|
+
</button>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module CMS
|
5
|
+
module Admin
|
6
|
+
class FlashMessageComponent < ::ViewComponent::Base
|
7
|
+
attr_reader :kind, :message
|
8
|
+
|
9
|
+
def initialize(message:, kind:, temporary: true)
|
10
|
+
@kind = kind.to_sym
|
11
|
+
@message = message
|
12
|
+
@temporary = temporary
|
13
|
+
end
|
14
|
+
|
15
|
+
def text_colour_css
|
16
|
+
case kind
|
17
|
+
when :success
|
18
|
+
"text-active"
|
19
|
+
when :alert, :error
|
20
|
+
"text-error"
|
21
|
+
when :warning
|
22
|
+
"text-warning"
|
23
|
+
when :info, :notice
|
24
|
+
"text-active"
|
25
|
+
else
|
26
|
+
"text-mid"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def icon_css
|
31
|
+
case kind
|
32
|
+
when :success
|
33
|
+
"fa-circle-check"
|
34
|
+
when :alert
|
35
|
+
"fa-circle-xmark"
|
36
|
+
when :warning
|
37
|
+
"fa-triangle-exclamation"
|
38
|
+
when :info, :notice
|
39
|
+
"fa-circle-info"
|
40
|
+
else
|
41
|
+
"fa-circle-info"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|