plum-cms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +252 -0
  5. data/app/assets/builds/tailwind.css +2 -0
  6. data/app/assets/images/plum-mark.svg +7 -0
  7. data/app/assets/images/table-needs-logo.svg +83 -0
  8. data/app/assets/javascripts/plum/application.js +15 -0
  9. data/app/assets/stylesheets/application.css +10 -0
  10. data/app/assets/stylesheets/plum/control_panel.css +2 -0
  11. data/app/assets/tailwind/application.css +47 -0
  12. data/app/controllers/plum/application_controller.rb +34 -0
  13. data/app/controllers/plum/cp/assets_controller.rb +84 -0
  14. data/app/controllers/plum/cp/base_controller.rb +45 -0
  15. data/app/controllers/plum/cp/content_types_controller.rb +59 -0
  16. data/app/controllers/plum/cp/dashboard_controller.rb +10 -0
  17. data/app/controllers/plum/cp/entries_controller.rb +240 -0
  18. data/app/controllers/plum/cp/form_definitions_controller.rb +105 -0
  19. data/app/controllers/plum/cp/form_submissions_controller.rb +26 -0
  20. data/app/controllers/plum/cp/globals_controller.rb +84 -0
  21. data/app/controllers/plum/cp/nav_items_controller.rb +68 -0
  22. data/app/controllers/plum/cp/nav_menus_controller.rb +56 -0
  23. data/app/controllers/plum/cp/site_settings_controller.rb +87 -0
  24. data/app/controllers/plum/cp/taxonomies_controller.rb +56 -0
  25. data/app/controllers/plum/cp/terms_controller.rb +55 -0
  26. data/app/controllers/plum/cp/theme_previews_controller.rb +27 -0
  27. data/app/controllers/plum/cp/themes_controller.rb +98 -0
  28. data/app/controllers/plum/form_submissions_controller.rb +44 -0
  29. data/app/controllers/plum/pages_controller.rb +161 -0
  30. data/app/controllers/plum/sessions_controller.rb +25 -0
  31. data/app/controllers/plum/theme_assets_controller.rb +14 -0
  32. data/app/helpers/application_helper.rb +2 -0
  33. data/app/helpers/markdown_helper.rb +21 -0
  34. data/app/helpers/plum/cp/assets_helper.rb +21 -0
  35. data/app/helpers/plum/cp/theme_settings_helper.rb +26 -0
  36. data/app/javascript/application.js +18 -0
  37. data/app/javascript/controllers/application.js +9 -0
  38. data/app/javascript/controllers/index.js +4 -0
  39. data/app/javascript/controllers/plum/blocks_editor_controller.js +275 -0
  40. data/app/javascript/controllers/plum/blueprint_controller.js +120 -0
  41. data/app/javascript/controllers/plum/form_fields_controller.js +84 -0
  42. data/app/javascript/controllers/plum/image_picker_controller.js +143 -0
  43. data/app/javascript/controllers/plum/theme_settings_controller.js +17 -0
  44. data/app/models/plum/application_record.rb +5 -0
  45. data/app/models/plum/asset.rb +69 -0
  46. data/app/models/plum/content_type.rb +35 -0
  47. data/app/models/plum/entry.rb +67 -0
  48. data/app/models/plum/entry_term.rb +8 -0
  49. data/app/models/plum/form_definition.rb +68 -0
  50. data/app/models/plum/form_submission.rb +76 -0
  51. data/app/models/plum/global.rb +24 -0
  52. data/app/models/plum/nav_item.rb +58 -0
  53. data/app/models/plum/nav_menu.rb +24 -0
  54. data/app/models/plum/site.rb +112 -0
  55. data/app/models/plum/site_scoped.rb +17 -0
  56. data/app/models/plum/site_setting.rb +35 -0
  57. data/app/models/plum/taxonomy.rb +24 -0
  58. data/app/models/plum/term.rb +23 -0
  59. data/app/models/plum/user.rb +16 -0
  60. data/app/services/plum/base_blocks.rb +161 -0
  61. data/app/services/plum/block_editor_config.rb +65 -0
  62. data/app/services/plum/block_library.rb +31 -0
  63. data/app/services/plum/builder_renderer.rb +63 -0
  64. data/app/services/plum/field_expander.rb +78 -0
  65. data/app/services/plum/form_renderer.rb +110 -0
  66. data/app/services/plum/liquid_context.rb +335 -0
  67. data/app/services/plum/liquid_filters.rb +58 -0
  68. data/app/services/plum/liquid_renderer.rb +49 -0
  69. data/app/services/plum/theme.rb +160 -0
  70. data/app/services/plum/theme_asset_path.rb +31 -0
  71. data/app/services/plum/theme_package_installer.rb +287 -0
  72. data/app/services/plum/theme_registry.rb +63 -0
  73. data/app/services/plum/theme_resolver.rb +55 -0
  74. data/app/services/plum/theme_settings_params.rb +54 -0
  75. data/app/themes/bagel-boy/assets/screenshot.svg +92 -0
  76. data/app/themes/bagel-boy/assets/theme.css +184 -0
  77. data/app/themes/bagel-boy/blocks/hours.liquid +4 -0
  78. data/app/themes/bagel-boy/blocks/menu_item.liquid +7 -0
  79. data/app/themes/bagel-boy/layouts/base.liquid +64 -0
  80. data/app/themes/bagel-boy/templates/collections/posts.liquid +32 -0
  81. data/app/themes/bagel-boy/templates/entries/_default.liquid +13 -0
  82. data/app/themes/bagel-boy/templates/entries/landing.liquid +4 -0
  83. data/app/themes/bagel-boy/templates/entries/pages.liquid +18 -0
  84. data/app/themes/bagel-boy/templates/entries/posts.liquid +8 -0
  85. data/app/themes/bagel-boy/templates/index.liquid +10 -0
  86. data/app/themes/bagel-boy/templates/search.liquid +28 -0
  87. data/app/themes/bagel-boy/theme.yml +47 -0
  88. data/app/themes/bagel-shop/assets/screenshot.svg +28 -0
  89. data/app/themes/bagel-shop/assets/theme.css +7 -0
  90. data/app/themes/bagel-shop/layouts/base.liquid +507 -0
  91. data/app/themes/bagel-shop/templates/entries/_default.liquid +15 -0
  92. data/app/themes/bagel-shop/templates/entries/pages.liquid +12 -0
  93. data/app/themes/bagel-shop/templates/entries/posts.liquid +19 -0
  94. data/app/themes/bagel-shop/templates/index.liquid +55 -0
  95. data/app/themes/bagel-shop/theme.yml +30 -0
  96. data/app/themes/default/assets/screenshot.svg +24 -0
  97. data/app/themes/default/assets/theme.css +89 -0
  98. data/app/themes/default/layouts/base.liquid +117 -0
  99. data/app/themes/default/templates/collections/_default.liquid +16 -0
  100. data/app/themes/default/templates/collections/posts.liquid +27 -0
  101. data/app/themes/default/templates/entries/_default.liquid +26 -0
  102. data/app/themes/default/templates/entries/landing.liquid +5 -0
  103. data/app/themes/default/templates/entries/pages.liquid +17 -0
  104. data/app/themes/default/templates/entries/posts.liquid +20 -0
  105. data/app/themes/default/templates/index.liquid +22 -0
  106. data/app/themes/default/templates/search.liquid +28 -0
  107. data/app/themes/default/templates/taxonomies/_default.liquid +25 -0
  108. data/app/themes/default/templates/taxonomies/_default_index.liquid +14 -0
  109. data/app/themes/default/theme.yml +23 -0
  110. data/app/views/layouts/plum/cp.html.erb +138 -0
  111. data/app/views/layouts/plum/session.html.erb +43 -0
  112. data/app/views/plum/cp/assets/_form.html.erb +44 -0
  113. data/app/views/plum/cp/assets/edit.html.erb +14 -0
  114. data/app/views/plum/cp/assets/index.html.erb +52 -0
  115. data/app/views/plum/cp/assets/new.html.erb +9 -0
  116. data/app/views/plum/cp/content_types/_form.html.erb +113 -0
  117. data/app/views/plum/cp/content_types/edit.html.erb +8 -0
  118. data/app/views/plum/cp/content_types/index.html.erb +48 -0
  119. data/app/views/plum/cp/content_types/new.html.erb +5 -0
  120. data/app/views/plum/cp/content_types/show.html.erb +59 -0
  121. data/app/views/plum/cp/dashboard/show.html.erb +50 -0
  122. data/app/views/plum/cp/entries/_form.html.erb +196 -0
  123. data/app/views/plum/cp/entries/edit.html.erb +9 -0
  124. data/app/views/plum/cp/entries/index.html.erb +51 -0
  125. data/app/views/plum/cp/entries/new.html.erb +5 -0
  126. data/app/views/plum/cp/form_definitions/_form.html.erb +90 -0
  127. data/app/views/plum/cp/form_definitions/edit.html.erb +11 -0
  128. data/app/views/plum/cp/form_definitions/index.html.erb +43 -0
  129. data/app/views/plum/cp/form_definitions/new.html.erb +5 -0
  130. data/app/views/plum/cp/form_definitions/show.html.erb +70 -0
  131. data/app/views/plum/cp/form_submissions/show.html.erb +20 -0
  132. data/app/views/plum/cp/globals/_form.html.erb +44 -0
  133. data/app/views/plum/cp/globals/edit.html.erb +11 -0
  134. data/app/views/plum/cp/globals/index.html.erb +41 -0
  135. data/app/views/plum/cp/globals/new.html.erb +5 -0
  136. data/app/views/plum/cp/globals/show.html.erb +14 -0
  137. data/app/views/plum/cp/nav_items/_form.html.erb +56 -0
  138. data/app/views/plum/cp/nav_items/edit.html.erb +11 -0
  139. data/app/views/plum/cp/nav_items/new.html.erb +6 -0
  140. data/app/views/plum/cp/nav_menus/_form.html.erb +33 -0
  141. data/app/views/plum/cp/nav_menus/_nav_item.html.erb +24 -0
  142. data/app/views/plum/cp/nav_menus/edit.html.erb +11 -0
  143. data/app/views/plum/cp/nav_menus/index.html.erb +41 -0
  144. data/app/views/plum/cp/nav_menus/new.html.erb +5 -0
  145. data/app/views/plum/cp/nav_menus/show.html.erb +24 -0
  146. data/app/views/plum/cp/shared/_icon.html.erb +39 -0
  147. data/app/views/plum/cp/shared/_image_picker.html.erb +38 -0
  148. data/app/views/plum/cp/site_settings/edit.html.erb +145 -0
  149. data/app/views/plum/cp/site_settings/show.html.erb +62 -0
  150. data/app/views/plum/cp/taxonomies/_form.html.erb +38 -0
  151. data/app/views/plum/cp/taxonomies/edit.html.erb +2 -0
  152. data/app/views/plum/cp/taxonomies/index.html.erb +38 -0
  153. data/app/views/plum/cp/taxonomies/new.html.erb +2 -0
  154. data/app/views/plum/cp/taxonomies/show.html.erb +46 -0
  155. data/app/views/plum/cp/terms/_form.html.erb +30 -0
  156. data/app/views/plum/cp/terms/edit.html.erb +2 -0
  157. data/app/views/plum/cp/terms/new.html.erb +2 -0
  158. data/app/views/plum/cp/themes/index.html.erb +160 -0
  159. data/app/views/plum/form_mailer/submission_notification.html.erb +13 -0
  160. data/app/views/plum/form_mailer/submission_notification.text.erb +7 -0
  161. data/app/views/plum/sessions/new.html.erb +20 -0
  162. data/compat/plum-cms.rb +1 -0
  163. data/config/locales/en.yml +31 -0
  164. data/config/plum_importmap.rb +4 -0
  165. data/config/plum_routes.rb +34 -0
  166. data/db/engine_migrate/20260101000001_create_plum_tables.rb +154 -0
  167. data/lib/generators/plum/install/install_generator.rb +54 -0
  168. data/lib/generators/plum/install/templates/plum_initializer.rb +37 -0
  169. data/lib/plum/configuration.rb +88 -0
  170. data/lib/plum/content_source.rb +45 -0
  171. data/lib/plum/content_source_context.rb +30 -0
  172. data/lib/plum/content_source_registry.rb +120 -0
  173. data/lib/plum/engine.rb +30 -0
  174. data/lib/plum/liquid_tags/form_tag.rb +17 -0
  175. data/lib/plum/version.rb +7 -0
  176. data/lib/plum.rb +11 -0
  177. data/vendor/javascript/lexxy.js +11859 -0
  178. metadata +405 -0
@@ -0,0 +1,89 @@
1
+ /* Default theme styles */
2
+
3
+ :root {
4
+ color-scheme: light;
5
+ }
6
+
7
+ .plum-powered img {
8
+ max-width: 100%;
9
+ height: auto;
10
+ object-fit: contain;
11
+ }
12
+
13
+ /* --- Blocks (page builder sections) --- */
14
+ .plum-block {
15
+ margin: 2.5rem 0;
16
+ }
17
+ .plum-block-hero {
18
+ text-align: center;
19
+ }
20
+ .plum-block-hero-image {
21
+ width: 100%;
22
+ max-height: 420px;
23
+ object-fit: cover;
24
+ border-radius: 0.75rem;
25
+ margin-bottom: 1.5rem;
26
+ }
27
+ .plum-block-hero h2 {
28
+ font-size: 2.25rem;
29
+ margin-bottom: 0.5rem;
30
+ }
31
+ .plum-block-image img {
32
+ width: 100%;
33
+ border-radius: 0.75rem;
34
+ }
35
+ .plum-block-image figcaption {
36
+ margin-top: 0.5rem;
37
+ font-size: 0.9rem;
38
+ color: #666;
39
+ text-align: center;
40
+ }
41
+ .plum-block-image-text {
42
+ display: grid;
43
+ grid-template-columns: 1fr 1fr;
44
+ gap: 2rem;
45
+ align-items: center;
46
+ }
47
+ .plum-block-image-text.plum-image-right .plum-block-image-text-media {
48
+ order: 2;
49
+ }
50
+ .plum-block-image-text-media img {
51
+ width: 100%;
52
+ border-radius: 0.75rem;
53
+ object-fit: cover;
54
+ }
55
+ .plum-block-cta {
56
+ text-align: center;
57
+ background: #f7f5fa;
58
+ border-radius: 0.75rem;
59
+ padding: 2.5rem 1.5rem;
60
+ }
61
+ .plum-block-cta-button {
62
+ display: inline-block;
63
+ margin-top: 1rem;
64
+ background: var(--plum-primary, #7c3aed);
65
+ color: #fff;
66
+ text-decoration: none;
67
+ font-weight: 600;
68
+ padding: 0.7rem 1.4rem;
69
+ border-radius: 0.5rem;
70
+ }
71
+ .plum-block-gallery-grid {
72
+ display: grid;
73
+ grid-template-columns: repeat(3, 1fr);
74
+ gap: 1rem;
75
+ }
76
+ .plum-block-gallery-grid img {
77
+ width: 100%;
78
+ aspect-ratio: 1 / 1;
79
+ object-fit: cover;
80
+ border-radius: 0.5rem;
81
+ }
82
+ .plum-block-spacer.plum-spacer-small { height: 1rem; }
83
+ .plum-block-spacer.plum-spacer-medium { height: 2.5rem; }
84
+ .plum-block-spacer.plum-spacer-large { height: 5rem; }
85
+ @media (max-width: 640px) {
86
+ .plum-block-image-text { grid-template-columns: 1fr; }
87
+ .plum-block-image-text.plum-image-right .plum-block-image-text-media { order: 0; }
88
+ .plum-block-gallery-grid { grid-template-columns: 1fr 1fr; }
89
+ }
@@ -0,0 +1,117 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ entry.title | default: site.seo_title | default: site.name }} - {{ site.name }}</title>
7
+ <meta name="description" content="{{ site.seo_description }}">
8
+ <link rel="icon" href="/icon.svg" type="image/svg+xml">
9
+ <link rel="stylesheet" href="{{ 'theme.css' | theme_asset_url }}">
10
+ <style>
11
+ :root { --plum-primary: {{ site.theme_settings.accent_color | default: site.primary_color | default: "#7c3aed" }}; }
12
+ * { box-sizing: border-box; margin: 0; padding: 0; }
13
+ body {
14
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
+ line-height: 1.6;
16
+ color: #1a1a1a;
17
+ background: #fff;
18
+ }
19
+ .container { max-width: 720px; margin: 0 auto; padding: 2rem 1rem; }
20
+ header { border-bottom: 1px solid #eee; padding-bottom: 1rem; margin-bottom: 2rem; }
21
+ .site-header-main { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
22
+ header h1 { font-size: 1.5rem; }
23
+ header h1 a { color: inherit; text-decoration: none; }
24
+ header .site-logo { max-height: 48px; width: auto; display: block; }
25
+ header p { color: #666; font-size: 0.9rem; }
26
+ .site-nav { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 0.35rem 0.8rem; padding-top: 0.15rem; }
27
+ .site-nav a { color: #4a3d45; font-size: 0.9rem; font-weight: 600; text-decoration: none; }
28
+ .site-nav a:hover { color: var(--plum-primary); }
29
+ article h1 { font-size: 2rem; margin-bottom: 0.5rem; }
30
+ article .meta { color: #666; font-size: 0.9rem; margin-bottom: 2rem; }
31
+ article .entry-hero-image { width: 100%; margin: 1.5rem 0 2rem; border-radius: 0.5rem; }
32
+ article .content { line-height: 1.8; }
33
+ article .content h2 { margin-top: 2rem; margin-bottom: 1rem; }
34
+ article .content h3 { margin-top: 1.5rem; margin-bottom: 0.75rem; }
35
+ article .content p { margin-bottom: 1rem; }
36
+ article .content ul, article .content ol { margin-bottom: 1rem; padding-left: 1.5rem; }
37
+ article .content a { color: var(--plum-primary); }
38
+ .contact-section { margin-top: 2.5rem; }
39
+ .contact-section h2 { margin-bottom: 1rem; }
40
+ .plum-form { display: grid; gap: 1rem; }
41
+ .plum-form-field { display: grid; gap: 0.35rem; }
42
+ .plum-form label { color: #4a3d45; font-size: 0.9rem; font-weight: 600; }
43
+ .plum-form input[type="text"],
44
+ .plum-form input[type="email"],
45
+ .plum-form textarea,
46
+ .plum-form select {
47
+ width: 100%;
48
+ border: 1px solid #d8d0d7;
49
+ border-radius: 0.4rem;
50
+ padding: 0.65rem 0.75rem;
51
+ font: inherit;
52
+ }
53
+ .plum-form textarea { min-height: 8rem; }
54
+ .plum-form input[type="checkbox"] { width: 1rem; height: 1rem; }
55
+ .plum-form-submit {
56
+ justify-self: start;
57
+ border: 0;
58
+ border-radius: 0.4rem;
59
+ background: var(--plum-primary);
60
+ color: #fff;
61
+ padding: 0.7rem 1rem;
62
+ font-weight: 700;
63
+ cursor: pointer;
64
+ }
65
+ footer { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid #eee; color: #666; font-size: 0.9rem; }
66
+ footer .plum-powered { display: inline-flex; align-items: center; gap: 0.45rem; color: #4a3d45; }
67
+ footer .plum-powered img { width: 1.15rem; height: 1.15rem; border-radius: 0.25rem; }
68
+ @media (max-width: 640px) {
69
+ .site-header-main { display: block; }
70
+ .site-nav { justify-content: flex-start; margin-top: 1rem; }
71
+ }
72
+ </style>
73
+ {% if site.custom_css %}
74
+ <style id="plum-custom-css">
75
+ {{ site.custom_css }}
76
+ </style>
77
+ {% endif %}
78
+ </head>
79
+ <body>
80
+ <div class="container">
81
+ <header>
82
+ <div class="site-header-main">
83
+ <div>
84
+ <h1>
85
+ <a href="{{ site.url }}">
86
+ {% if site.logo.url %}
87
+ <img class="site-logo" src="{{ site.logo.url }}" alt="{{ site.logo.alt_text | default: site.name }}">
88
+ {% else %}
89
+ {{ site.name }}
90
+ {% endif %}
91
+ </a>
92
+ </h1>
93
+ {% if site.tagline %}<p>{{ site.tagline }}</p>{% endif %}
94
+ </div>
95
+
96
+ {% if nav.main.items.size > 0 %}
97
+ <nav class="site-nav" aria-label="Main navigation">
98
+ {% for item in nav.main.items %}
99
+ <a href="{{ item.url }}">{{ item.label }}</a>
100
+ {% endfor %}
101
+ </nav>
102
+ {% endif %}
103
+ </div>
104
+ </header>
105
+
106
+ <main>
107
+ {{ content }}
108
+ </main>
109
+
110
+ <footer>
111
+ {% unless site.theme_settings.show_powered_by == false %}
112
+ <p class="plum-powered"><a href="{{ site.powered_by_url }}">Powered by {{ site.powered_by_name }}</a></p>
113
+ {% endunless %}
114
+ </footer>
115
+ </div>
116
+ </body>
117
+ </html>
@@ -0,0 +1,16 @@
1
+ <div class="plum-powered" style="max-width: 48rem; margin: 0 auto; padding: 2rem;">
2
+ <h1>{{ collection.title }}</h1>
3
+
4
+ {% for entry in collection.entries %}
5
+ <article style="margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid #eee;">
6
+ <h2><a href="{{ entry.url }}">{{ entry.title }}</a></h2>
7
+ {% if entry.published_at %}
8
+ <p style="color: #666; font-size: 0.875rem;">{{ entry.published_at | date: "%B %d, %Y" }}</p>
9
+ {% endif %}
10
+ </article>
11
+ {% endfor %}
12
+
13
+ {% if collection.entries.size == 0 %}
14
+ <p>No entries yet.</p>
15
+ {% endif %}
16
+ </div>
@@ -0,0 +1,27 @@
1
+ <div class="plum-powered" style="max-width: 48rem; margin: 0 auto; padding: 2rem;">
2
+ <h1>{{ collection.title }}</h1>
3
+
4
+ {% for post in collection.entries %}
5
+ <article style="margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid #eee;">
6
+ <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
7
+ {% if post.published_at %}
8
+ <p style="color: #666; font-size: 0.875rem;">{{ post.published_at | date: "%B %d, %Y" }}</p>
9
+ {% endif %}
10
+ {% if post.data.excerpt %}
11
+ <p>{{ post.data.excerpt }}</p>
12
+ {% endif %}
13
+ </article>
14
+ {% endfor %}
15
+
16
+ {% if collection.entries.size == 0 %}
17
+ <p>No posts yet.</p>
18
+ {% endif %}
19
+
20
+ {% if pagination.total_pages > 1 %}
21
+ <nav style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem;">
22
+ {% if pagination.previous_url %}<a href="{{ pagination.previous_url }}">&larr; Previous</a>{% endif %}
23
+ <span>Page {{ pagination.current_page }} of {{ pagination.total_pages }}</span>
24
+ {% if pagination.next_url %}<a href="{{ pagination.next_url }}">Next &rarr;</a>{% endif %}
25
+ </nav>
26
+ {% endif %}
27
+ </div>
@@ -0,0 +1,26 @@
1
+ <article>
2
+ <h1>{{ entry.title }}</h1>
3
+ {% if entry.published_at %}
4
+ <p class="meta">Published on {{ entry.published_at | date: "%B %d, %Y" }}</p>
5
+ {% endif %}
6
+
7
+ {% if entry.data.hero_image.url %}
8
+ <img class="entry-hero-image" src="{{ entry.data.hero_image.url }}" alt="{{ entry.data.hero_image.alt_text | default: entry.title }}">
9
+ {% endif %}
10
+
11
+ {% assign body_html = entry.data.body | markdown | strip %}
12
+ {% if body_html != "" %}
13
+ <div class="content prose">{{ body_html }}</div>
14
+ {% endif %}
15
+
16
+ {% assign sections_html = entry.data.sections | strip %}
17
+ {% if sections_html != "" %}
18
+ {{ sections_html }}
19
+ {% endif %}
20
+
21
+ {% if entry.data.excerpt %}
22
+ <div class="excerpt">
23
+ <p><em>{{ entry.data.excerpt }}</em></p>
24
+ </div>
25
+ {% endif %}
26
+ </article>
@@ -0,0 +1,5 @@
1
+ <article class="landing">
2
+ <h1>{{ entry.title }}</h1>
3
+
4
+ {{ entry.data.sections }}
5
+ </article>
@@ -0,0 +1,17 @@
1
+ <article>
2
+ <h1>{{ entry.title }}</h1>
3
+
4
+ {% if entry.data.hero_image.url %}
5
+ <img class="entry-hero-image" src="{{ entry.data.hero_image.url }}" alt="{{ entry.data.hero_image.alt_text | default: entry.title }}">
6
+ {% endif %}
7
+
8
+ {% assign body_html = entry.data.body | markdown | strip %}
9
+ {% if body_html != "" %}
10
+ <div class="content prose">{{ body_html }}</div>
11
+ {% endif %}
12
+
13
+ {% assign sections_html = entry.data.sections | strip %}
14
+ {% if sections_html != "" %}
15
+ {{ sections_html }}
16
+ {% endif %}
17
+ </article>
@@ -0,0 +1,20 @@
1
+ <article>
2
+ <h1>{{ entry.title }}</h1>
3
+ {% if entry.published_at %}
4
+ <p class="meta">Published on {{ entry.published_at | date: "%B %d, %Y" }}</p>
5
+ {% endif %}
6
+
7
+ {% if entry.data.hero_image.url %}
8
+ <img class="entry-hero-image" src="{{ entry.data.hero_image.url }}" alt="{{ entry.data.hero_image.alt_text | default: entry.title }}">
9
+ {% endif %}
10
+
11
+ <div class="content prose">
12
+ {{ entry.data.body | markdown }}
13
+ </div>
14
+
15
+ {% if entry.data.excerpt %}
16
+ <aside class="excerpt">
17
+ <p><strong>Summary:</strong> {{ entry.data.excerpt }}</p>
18
+ </aside>
19
+ {% endif %}
20
+ </article>
@@ -0,0 +1,22 @@
1
+ <h1>Welcome to {{ site.name }}</h1>
2
+ {% if site.tagline %}
3
+ <p>{{ site.tagline }}</p>
4
+ {% endif %}
5
+
6
+ {% if entries.posts.size > 0 %}
7
+ <section>
8
+ <h2>Latest Posts</h2>
9
+ <ul>
10
+ {% for post in entries.posts %}
11
+ <li><a href="{{ post.url }}">{{ post.title }}</a></li>
12
+ {% endfor %}
13
+ </ul>
14
+ </section>
15
+ {% endif %}
16
+
17
+ {% if forms.contact %}
18
+ <section class="contact-section">
19
+ <h2>Contact</h2>
20
+ {% form "contact" %}
21
+ </section>
22
+ {% endif %}
@@ -0,0 +1,28 @@
1
+ <div class="plum-powered" style="max-width: 48rem; margin: 0 auto; padding: 2rem;">
2
+ <h1>Search</h1>
3
+
4
+ <form action="/search" method="get" style="margin-bottom: 2rem;">
5
+ <div style="display: flex; gap: 0.5rem;">
6
+ <input type="text" name="q" value="{{ search.query }}" placeholder="Search…"
7
+ style="flex: 1; padding: 0.5rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem;">
8
+ <button type="submit" style="padding: 0.5rem 1rem; background: #7c3aed; color: white; border: none; border-radius: 0.375rem; cursor: pointer;">Search</button>
9
+ </div>
10
+ </form>
11
+
12
+ {% if search.query != "" %}
13
+ <p style="color: #666; margin-bottom: 1.5rem;">{{ search.total }} result{% if search.total != 1 %}s{% endif %} for "{{ search.query }}"</p>
14
+
15
+ {% for entry in search.results %}
16
+ <article style="margin-bottom: 1.5rem; padding-bottom: 1.5rem; border-bottom: 1px solid #eee;">
17
+ <h2 style="margin: 0;"><a href="{{ entry.url }}">{{ entry.title }}</a></h2>
18
+ {% if entry.published_at %}
19
+ <p style="color: #666; font-size: 0.875rem; margin: 0.25rem 0;">{{ entry.published_at | date: "%B %d, %Y" }}</p>
20
+ {% endif %}
21
+ </article>
22
+ {% endfor %}
23
+
24
+ {% if search.total == 0 %}
25
+ <p>No results found.</p>
26
+ {% endif %}
27
+ {% endif %}
28
+ </div>
@@ -0,0 +1,25 @@
1
+ <div class="plum-powered" style="max-width: 48rem; margin: 0 auto; padding: 2rem;">
2
+ <h1>{{ term.name }}</h1>
3
+ <p style="color: #666; margin-bottom: 1.5rem;">{{ taxonomy.name }}: {{ term.name }}</p>
4
+
5
+ {% for entry in collection.entries %}
6
+ <article style="margin-bottom: 2rem; padding-bottom: 2rem; border-bottom: 1px solid #eee;">
7
+ <h2><a href="{{ entry.url }}">{{ entry.title }}</a></h2>
8
+ {% if entry.published_at %}
9
+ <p style="color: #666; font-size: 0.875rem;">{{ entry.published_at | date: "%B %d, %Y" }}</p>
10
+ {% endif %}
11
+ </article>
12
+ {% endfor %}
13
+
14
+ {% if collection.entries.size == 0 %}
15
+ <p>No entries in this category.</p>
16
+ {% endif %}
17
+
18
+ {% if pagination.total_pages > 1 %}
19
+ <nav style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem;">
20
+ {% if pagination.previous_url %}<a href="{{ pagination.previous_url }}">&larr; Previous</a>{% endif %}
21
+ <span>Page {{ pagination.current_page }} of {{ pagination.total_pages }}</span>
22
+ {% if pagination.next_url %}<a href="{{ pagination.next_url }}">Next &rarr;</a>{% endif %}
23
+ </nav>
24
+ {% endif %}
25
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="plum-powered" style="max-width: 48rem; margin: 0 auto; padding: 2rem;">
2
+ <h1>{{ taxonomy.name }}</h1>
3
+
4
+ {% for term in taxonomy.terms %}
5
+ <div style="margin-bottom: 1rem;">
6
+ <a href="{{ term.url }}" style="font-size: 1.125rem;">{{ term.name }}</a>
7
+ <span style="color: #999; font-size: 0.875rem;">({{ term.entries_count }})</span>
8
+ </div>
9
+ {% endfor %}
10
+
11
+ {% if taxonomy.terms.size == 0 %}
12
+ <p>No terms yet.</p>
13
+ {% endif %}
14
+ </div>
@@ -0,0 +1,23 @@
1
+ name: Default
2
+ handle: default
3
+ version: 0.1.0
4
+ author: Plum
5
+ category: Starter
6
+ screenshot: screenshot.svg
7
+ description: Clean starter theme for simple Plum sites.
8
+ settings:
9
+ fields:
10
+ - handle: accent_color
11
+ type: color
12
+ label: Accent Color
13
+ default: "#7c3aed"
14
+ help: Used for links and small accents.
15
+ - handle: show_powered_by
16
+ type: boolean
17
+ label: Show Powered By
18
+ default: true
19
+
20
+ # This theme adds no blocks of its own — it relies on Plum's engine-level base
21
+ # blocks (hero, rich_text, image, image_text, gallery, cta, button, spacer).
22
+ # A theme adds blocks here (handle/label/fields) + a blocks/<handle>.liquid
23
+ # partial, and may override a base block by reusing its handle.
@@ -0,0 +1,138 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-gray-50">
3
+ <head>
4
+ <title><%= Plum.configuration.cp_name %> <%= Plum.configuration.cp_subtitle %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= favicon_link_tag "plum-mark.svg", type: "image/svg+xml" %>
9
+ <%= stylesheet_link_tag "plum/control_panel", "data-turbo-track": "reload" %>
10
+ <%= stylesheet_link_tag "lexxy", "data-turbo-track": "reload" %>
11
+ <%= javascript_importmap_tags %>
12
+ <script type="module">import "plum/application"</script>
13
+ <style>
14
+ :root {
15
+ --plum-accent: <%= Plum.configuration.cp_accent_color %>;
16
+ --plum-sidebar-bg: <%= Plum.configuration.cp_sidebar_bg %>;
17
+ --plum-sidebar-header: <%= Plum.configuration.cp_sidebar_header_bg %>;
18
+ --plum-sidebar-text: <%= Plum.configuration.cp_sidebar_text %>;
19
+ --plum-sidebar-muted: <%= Plum.configuration.cp_sidebar_muted %>;
20
+ }
21
+ .plum-sidebar { background: var(--plum-sidebar-bg); }
22
+ .plum-sidebar-header { background: var(--plum-sidebar-header); }
23
+ .plum-sidebar-link { color: var(--plum-sidebar-text); }
24
+ .plum-sidebar-link:hover, .plum-sidebar-link.active { background: rgba(255,255,255,0.1); color: #fff; }
25
+ .plum-sidebar-section { color: var(--plum-sidebar-muted); }
26
+ .plum-sidebar-subtitle { color: var(--plum-sidebar-text); }
27
+ .plum-btn-primary { background: var(--plum-accent); }
28
+ .plum-btn-primary:hover { filter: brightness(0.9); }
29
+ .plum-link { color: var(--plum-accent); }
30
+ .plum-link:hover { filter: brightness(0.75); }
31
+
32
+ /* Override Tailwind purple classes with accent color */
33
+ .bg-purple-600 { background-color: var(--plum-accent) !important; }
34
+ .hover\:bg-purple-700:hover { background-color: var(--plum-accent) !important; filter: brightness(0.9); }
35
+ .text-purple-600 { color: var(--plum-accent) !important; }
36
+ .hover\:text-purple-900:hover { color: var(--plum-accent) !important; filter: brightness(0.75); }
37
+ .focus\:ring-purple-500:focus { --tw-ring-color: var(--plum-accent) !important; }
38
+ .focus\:border-purple-500:focus { border-color: var(--plum-accent) !important; }
39
+ .border-purple-500 { border-color: var(--plum-accent) !important; }
40
+ </style>
41
+ </head>
42
+ <body class="h-full">
43
+ <div class="min-h-full">
44
+ <!-- Sidebar -->
45
+ <div class="fixed inset-y-0 left-0 w-64 plum-sidebar">
46
+ <div class="flex flex-col h-full">
47
+ <!-- Logo -->
48
+ <div class="flex items-center h-16 px-5 border-b border-white/10 plum-sidebar-header">
49
+ <%= link_to cp_root_path, class: "flex items-center gap-3 text-white" do %>
50
+ <%= image_tag Plum.configuration.cp_logo_path, alt: "", class: "h-9 w-9 shrink-0" %>
51
+ <span class="leading-tight">
52
+ <span class="block text-lg font-semibold tracking-normal"><%= Plum.configuration.cp_name %></span>
53
+ <span class="block text-xs font-medium plum-sidebar-subtitle"><%= Plum.configuration.cp_subtitle %></span>
54
+ </span>
55
+ <% end %>
56
+ </div>
57
+
58
+ <!-- Navigation -->
59
+ <nav class="flex-1 px-4 py-6 space-y-2 overflow-y-auto">
60
+ <%= link_to "Dashboard", cp_root_path,
61
+ class: "flex items-center px-4 py-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path == cp_root_path ? 'active' : ''}" %>
62
+ <%= link_to root_path, target: "_blank", class: "flex items-center px-4 py-2 text-sm font-medium rounded-md plum-sidebar-link" do %>
63
+ View Website &nearr;
64
+ <% end %>
65
+
66
+ <div class="pt-4">
67
+ <p class="px-4 text-xs font-semibold plum-sidebar-section uppercase tracking-wider">Content</p>
68
+ <% Plum::ContentType.for_site(current_site).each do |ct| %>
69
+ <%= link_to cp_content_type_path(ct),
70
+ class: "flex items-center gap-2 px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?("/cp/content_types/#{ct.id}") ? 'active' : ''}" do %>
71
+ <%= render "plum/cp/shared/icon", name: ct.icon.presence || "document" %>
72
+ <%= ct.name %>
73
+ <% end %>
74
+ <% end %>
75
+ <%= link_to "+ New Type", new_cp_content_type_path,
76
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium plum-sidebar-link" %>
77
+ <%= link_to "Assets", cp_assets_path,
78
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/assets') ? 'active' : ''}" %>
79
+ <%= link_to "Globals", cp_globals_path,
80
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/globals') ? 'active' : ''}" %>
81
+ <%= link_to "Navigation", cp_nav_menus_path,
82
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/nav_menus') ? 'active' : ''}" %>
83
+ <%= link_to "Taxonomies", "#{cp_prefix}/taxonomies",
84
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/taxonomies') ? 'active' : ''}" %>
85
+ <%= link_to "Forms", cp_form_definitions_path,
86
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/forms') ? 'active' : ''}" %>
87
+ </div>
88
+
89
+ <div class="pt-4">
90
+ <p class="px-4 text-xs font-semibold plum-sidebar-section uppercase tracking-wider">Settings</p>
91
+ <%= link_to "Site Settings", cp_site_settings_path,
92
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/site_settings') ? 'active' : ''}" %>
93
+ <%= link_to "Themes", cp_themes_path,
94
+ class: "flex items-center px-4 py-2 mt-2 text-sm font-medium rounded-md plum-sidebar-link #{request.path.include?('/cp/themes') ? 'active' : ''}" %>
95
+ </div>
96
+ </nav>
97
+
98
+ <!-- Footer -->
99
+ <div class="p-4 border-t border-white/10">
100
+ <% if Plum.configuration.cp_back_url %>
101
+ <a href="<%= Plum.configuration.cp_back_url %>" class="flex items-center justify-center px-3 py-2 text-sm font-medium rounded-md plum-sidebar-link" style="background: rgba(255,255,255,0.1);">
102
+ <%= Plum.configuration.cp_back_label %>
103
+ </a>
104
+ <% elsif plum_managed_auth? %>
105
+ <div class="flex items-center">
106
+ <div class="flex-1 min-w-0">
107
+ <p class="text-sm font-medium text-white truncate"><%= current_user_label %></p>
108
+ <% if current_user.respond_to?(:role) %>
109
+ <p class="text-xs plum-sidebar-link truncate"><%= current_user.role.titleize %></p>
110
+ <% end %>
111
+ </div>
112
+ <%= button_to "Logout", logout_path, method: :delete,
113
+ class: "ml-2 px-3 py-1 text-xs font-medium plum-sidebar-link" %>
114
+ </div>
115
+ <% end %>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- Main content -->
121
+ <div class="pl-64">
122
+ <main class="py-8 px-8">
123
+ <% if flash[:notice] %>
124
+ <div class="mb-6 bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded">
125
+ <%= flash[:notice] %>
126
+ </div>
127
+ <% end %>
128
+ <% if flash[:alert] %>
129
+ <div class="mb-6 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
130
+ <%= flash[:alert] %>
131
+ </div>
132
+ <% end %>
133
+ <%= yield %>
134
+ </main>
135
+ </div>
136
+ </div>
137
+ </body>
138
+ </html>
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-gray-100">
3
+ <head>
4
+ <title>Login - <%= Plum.configuration.cp_name %></title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= favicon_link_tag "plum-mark.svg", type: "image/svg+xml" %>
9
+ <%= stylesheet_link_tag "plum/control_panel", "data-turbo-track": "reload" %>
10
+ <%= javascript_importmap_tags %>
11
+ <style>
12
+ :root { --plum-accent: <%= Plum.configuration.cp_accent_color %>; }
13
+ .bg-purple-600 { background-color: var(--plum-accent) !important; }
14
+ .hover\:bg-purple-700:hover { background-color: var(--plum-accent) !important; filter: brightness(0.9); }
15
+ .focus\:ring-purple-500:focus { --tw-ring-color: var(--plum-accent) !important; }
16
+ .focus\:border-purple-500:focus { border-color: var(--plum-accent) !important; }
17
+ </style>
18
+ </head>
19
+ <body class="h-full bg-[#F6F3EF]">
20
+ <div class="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
21
+ <div class="max-w-md w-full space-y-8">
22
+ <div>
23
+ <div class="flex justify-center">
24
+ <%= image_tag Plum.configuration.cp_logo_path, alt: Plum.configuration.cp_name, class: "h-16 w-16" %>
25
+ </div>
26
+ <h1 class="mt-4 text-center text-3xl font-bold" style="color: <%= Plum.configuration.cp_sidebar_bg %>"><%= Plum.configuration.cp_name %></h1>
27
+ <h2 class="mt-2 text-center text-xl text-gray-600">Sign in to your account</h2>
28
+ </div>
29
+ <% if flash[:alert] %>
30
+ <div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
31
+ <%= flash[:alert] %>
32
+ </div>
33
+ <% end %>
34
+ <% if flash[:notice] %>
35
+ <div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded">
36
+ <%= flash[:notice] %>
37
+ </div>
38
+ <% end %>
39
+ <%= yield %>
40
+ </div>
41
+ </div>
42
+ </body>
43
+ </html>