railspress-engine 0.1.2 → 1.2.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.
- checksums.yaml +4 -4
- data/LICENSE +20 -0
- data/README.md +195 -25
- data/app/assets/javascripts/railspress/admin.js +39 -0
- data/app/assets/javascripts/railspress/markdown_mode.js +343 -0
- data/app/assets/stylesheets/application.css +0 -0
- data/app/assets/stylesheets/railspress/admin/badges.css +70 -0
- data/app/assets/stylesheets/railspress/admin/base.css +25 -0
- data/app/assets/stylesheets/railspress/admin/buttons.css +140 -0
- data/app/assets/stylesheets/railspress/admin/cards.css +52 -0
- data/app/assets/stylesheets/railspress/admin/components/exports.css +55 -0
- data/app/assets/stylesheets/railspress/admin/components/focal_point.css +801 -0
- data/app/assets/stylesheets/railspress/admin/components/imports.css +144 -0
- data/app/assets/stylesheets/railspress/admin/components/lexxy.css +156 -0
- data/app/assets/stylesheets/railspress/admin/filters.css +73 -0
- data/app/assets/stylesheets/railspress/admin/flash.css +26 -0
- data/app/assets/stylesheets/railspress/admin/forms.css +459 -0
- data/app/assets/stylesheets/railspress/admin/layout.css +256 -0
- data/app/assets/stylesheets/railspress/admin/lists.css +24 -0
- data/app/assets/stylesheets/railspress/admin/page.css +111 -0
- data/app/assets/stylesheets/railspress/admin/responsive.css +174 -0
- data/app/assets/stylesheets/railspress/admin/stats.css +43 -0
- data/app/assets/stylesheets/railspress/admin/tables.css +163 -0
- data/app/assets/stylesheets/railspress/admin/utilities.css +202 -0
- data/app/assets/stylesheets/railspress/admin/variables.css +58 -0
- data/app/assets/stylesheets/railspress/application.css +44 -13
- data/app/controllers/railspress/admin/base_controller.rb +6 -3
- data/app/controllers/railspress/admin/categories_controller.rb +1 -1
- data/app/controllers/railspress/admin/cms_transfers_controller.rb +49 -0
- data/app/controllers/railspress/admin/content_element_versions_controller.rb +12 -0
- data/app/controllers/railspress/admin/content_elements_controller.rb +143 -0
- data/app/controllers/railspress/admin/content_groups_controller.rb +69 -0
- data/app/controllers/railspress/admin/dashboard_controller.rb +6 -0
- data/app/controllers/railspress/admin/entities_controller.rb +157 -0
- data/app/controllers/railspress/admin/exports_controller.rb +55 -0
- data/app/controllers/railspress/admin/focal_points_controller.rb +100 -0
- data/app/controllers/railspress/admin/imports_controller.rb +63 -0
- data/app/controllers/railspress/admin/posts_controller.rb +58 -4
- data/app/controllers/railspress/admin/prototypes_controller.rb +30 -0
- data/app/controllers/railspress/admin/tags_controller.rb +1 -1
- data/app/controllers/railspress/application_controller.rb +1 -0
- data/app/helpers/railspress/admin_helper.rb +733 -0
- data/app/helpers/railspress/application_helper.rb +23 -0
- data/app/helpers/railspress/cms_helper.rb +319 -0
- data/app/javascript/railspress/controllers/cms_inline_editor_controller.js +147 -0
- data/app/javascript/railspress/controllers/content_element_form_controller.js +15 -0
- data/app/javascript/railspress/controllers/crop_controller.js +224 -0
- data/app/javascript/railspress/controllers/dropzone_controller.js +261 -0
- data/app/javascript/railspress/controllers/focal_point_controller.js +124 -0
- data/app/javascript/railspress/controllers/image_section_controller.js +94 -0
- data/app/javascript/railspress/controllers/index.js +37 -0
- data/app/javascript/railspress/index.js +62 -0
- data/app/jobs/railspress/export_posts_job.rb +16 -0
- data/app/jobs/railspress/import_posts_job.rb +44 -0
- data/app/models/concerns/railspress/has_focal_point.rb +242 -0
- data/app/models/concerns/railspress/soft_deletable.rb +23 -0
- data/app/models/concerns/railspress/taggable.rb +23 -0
- data/app/models/railspress/content_element.rb +103 -0
- data/app/models/railspress/content_element_version.rb +32 -0
- data/app/models/railspress/content_group.rb +39 -0
- data/app/models/railspress/export.rb +67 -0
- data/app/models/railspress/focal_point.rb +70 -0
- data/app/models/railspress/import.rb +65 -0
- data/app/models/railspress/post.rb +102 -15
- data/app/models/railspress/post_export_processor.rb +162 -0
- data/app/models/railspress/post_import_processor.rb +382 -0
- data/app/models/railspress/tag.rb +10 -3
- data/app/models/railspress/tagging.rb +11 -0
- data/app/services/railspress/content_export_service.rb +122 -0
- data/app/services/railspress/content_import_service.rb +228 -0
- data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
- data/app/views/active_storage/blobs/_blob.html.erb +1 -1
- data/app/views/layouts/railspress/admin.html.erb +3 -1
- data/app/views/railspress/admin/categories/index.html.erb +11 -15
- data/app/views/railspress/admin/cms_transfers/show.html.erb +167 -0
- data/app/views/railspress/admin/content_element_versions/show.html.erb +42 -0
- data/app/views/railspress/admin/content_elements/_form.html.erb +71 -0
- data/app/views/railspress/admin/content_elements/_inline_form.html.erb +32 -0
- data/app/views/railspress/admin/content_elements/_inline_form_frame.html.erb +6 -0
- data/app/views/railspress/admin/content_elements/edit.html.erb +6 -0
- data/app/views/railspress/admin/content_elements/index.html.erb +74 -0
- data/app/views/railspress/admin/content_elements/new.html.erb +6 -0
- data/app/views/railspress/admin/content_elements/show.html.erb +124 -0
- data/app/views/railspress/admin/content_groups/_form.html.erb +9 -0
- data/app/views/railspress/admin/content_groups/edit.html.erb +6 -0
- data/app/views/railspress/admin/content_groups/index.html.erb +42 -0
- data/app/views/railspress/admin/content_groups/new.html.erb +6 -0
- data/app/views/railspress/admin/content_groups/show.html.erb +92 -0
- data/app/views/railspress/admin/dashboard/index.html.erb +36 -1
- data/app/views/railspress/admin/entities/_form.html.erb +53 -0
- data/app/views/railspress/admin/entities/edit.html.erb +4 -0
- data/app/views/railspress/admin/entities/index.html.erb +74 -0
- data/app/views/railspress/admin/entities/new.html.erb +4 -0
- data/app/views/railspress/admin/entities/show.html.erb +117 -0
- data/app/views/railspress/admin/exports/show.html.erb +62 -0
- data/app/views/railspress/admin/imports/_instructions.html.erb +56 -0
- data/app/views/railspress/admin/imports/show.html.erb +137 -0
- data/app/views/railspress/admin/posts/_form.html.erb +102 -28
- data/app/views/railspress/admin/posts/_post_row.html.erb +40 -0
- data/app/views/railspress/admin/posts/index.html.erb +47 -36
- data/app/views/railspress/admin/posts/show.html.erb +55 -19
- data/app/views/railspress/admin/prototypes/image_section.html.erb +42 -0
- data/app/views/railspress/admin/shared/_dropzone.html.erb +84 -0
- data/app/views/railspress/admin/shared/_focal_point_editor.html.erb +102 -0
- data/app/views/railspress/admin/shared/_image_section.html.erb +159 -0
- data/app/views/railspress/admin/shared/_image_section_compact.html.erb +90 -0
- data/app/views/railspress/admin/shared/_image_section_editor.html.erb +171 -0
- data/app/views/railspress/admin/shared/_image_section_v2.html.erb +205 -0
- data/app/views/railspress/admin/shared/_sidebar.html.erb +73 -5
- data/app/views/railspress/admin/tags/index.html.erb +12 -16
- data/config/brakeman.ignore +18 -0
- data/config/importmap.rb +23 -0
- data/config/routes.rb +62 -1
- data/db/migrate/20241218000004_create_railspress_post_tags.rb +1 -1
- data/db/migrate/20241218000005_create_railspress_imports.rb +21 -0
- data/db/migrate/20241218000006_create_railspress_exports.rb +20 -0
- data/db/migrate/20241218000007_create_railspress_taggings.rb +20 -0
- data/db/migrate/20241218000008_drop_railspress_post_tags.rb +14 -0
- data/db/migrate/20241218000010_add_reading_time_to_railspress_posts.rb +5 -0
- data/db/migrate/20250105000002_create_railspress_focal_points.rb +20 -0
- data/db/migrate/20260206000001_create_railspress_content_groups.rb +18 -0
- data/db/migrate/20260206000002_create_railspress_content_elements.rb +21 -0
- data/db/migrate/20260206000003_create_railspress_content_element_versions.rb +20 -0
- data/db/migrate/20260207000001_add_unique_index_to_content_elements.rb +11 -0
- data/db/migrate/20260211112812_add_image_hint_to_railspress_content_elements.rb +7 -0
- data/db/migrate/20260211154040_add_required_to_railspress_content_elements.rb +5 -0
- data/lib/generators/railspress/entity/entity_generator.rb +89 -0
- data/lib/generators/railspress/entity/templates/migration.rb.tt +13 -0
- data/lib/generators/railspress/entity/templates/model.rb.tt +21 -0
- data/lib/generators/railspress/install/install_generator.rb +51 -40
- data/lib/generators/railspress/install/templates/initializer.rb +29 -0
- data/lib/railspress/engine.rb +38 -0
- data/lib/railspress/entity.rb +239 -0
- data/lib/railspress/version.rb +1 -1
- data/lib/railspress.rb +198 -8
- data/lib/tasks/railspress_tasks.rake +49 -4
- metadata +215 -21
- data/MIT-LICENSE +0 -20
- data/app/assets/stylesheets/railspress/admin.css +0 -1207
- data/app/models/railspress/post_tag.rb +0 -8
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<% content_for :title, "Content Elements" %>
|
|
2
|
+
|
|
3
|
+
<%= rp_page_header "Content Elements", "New Element" => new_admin_content_element_path %>
|
|
4
|
+
|
|
5
|
+
<% if @content_groups.any? %>
|
|
6
|
+
<div class="rp-filter-bar">
|
|
7
|
+
<%= form_with url: admin_content_elements_path, method: :get, class: "rp-filter-form" do |f| %>
|
|
8
|
+
<div class="rp-form-group rp-form-group--inline">
|
|
9
|
+
<%= f.label :content_group_id, "Filter by group:", class: "rp-label" %>
|
|
10
|
+
<%= f.select :content_group_id,
|
|
11
|
+
options_from_collection_for_select(@content_groups, :id, :name, params[:content_group_id]),
|
|
12
|
+
{ include_blank: "All Groups" },
|
|
13
|
+
{ class: "rp-select", onchange: "this.form.requestSubmit()" } %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
<% end %>
|
|
18
|
+
|
|
19
|
+
<div class="rp-card">
|
|
20
|
+
<% if @content_elements.any? %>
|
|
21
|
+
<div class="rp-table--responsive">
|
|
22
|
+
<table class="rp-table">
|
|
23
|
+
<thead>
|
|
24
|
+
<tr>
|
|
25
|
+
<th><%= rp_table_header("Name") %></th>
|
|
26
|
+
<th><%= rp_table_header("Group") %></th>
|
|
27
|
+
<th><%= rp_table_header("Type") %></th>
|
|
28
|
+
<th><%= rp_table_header("Content") %></th>
|
|
29
|
+
<th><%= rp_table_header("Updated") %></th>
|
|
30
|
+
<th class="rp-table-actions"><%= rp_table_header("Actions") %></th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
<% @content_elements.each do |element| %>
|
|
35
|
+
<tr>
|
|
36
|
+
<td class="rp-table-primary">
|
|
37
|
+
<%= link_to admin_content_element_path(element), class: "rp-link" do %>
|
|
38
|
+
<% if element.required? %><strong><%= element.name %></strong><% else %><%= element.name %><% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
</td>
|
|
41
|
+
<td>
|
|
42
|
+
<% if element.content_group %>
|
|
43
|
+
<%= link_to element.content_group.name, admin_content_group_path(element.content_group), class: "rp-link" %>
|
|
44
|
+
<% else %>
|
|
45
|
+
<span class="rp-table-secondary">—</span>
|
|
46
|
+
<% end %>
|
|
47
|
+
</td>
|
|
48
|
+
<td><%= rp_badge(element.content_type.titleize, status: element.text? ? :published : :info) %></td>
|
|
49
|
+
<td class="rp-table-secondary">
|
|
50
|
+
<% if element.image? && element.image.attached? %>
|
|
51
|
+
<%= image_tag main_app.rails_blob_path(element.image, only_path: true), style: "max-height: 1.5rem; width: auto; border-radius: 2px; vertical-align: middle;" %>
|
|
52
|
+
<% else %>
|
|
53
|
+
<%= rp_truncated_text(element.text_content, length: 40) %>
|
|
54
|
+
<% end %>
|
|
55
|
+
</td>
|
|
56
|
+
<td class="rp-table-secondary"><%= element.updated_at.strftime("%b %d, %Y") %></td>
|
|
57
|
+
<td class="rp-table-actions">
|
|
58
|
+
<%= rp_table_action_icons(
|
|
59
|
+
edit_path: edit_admin_content_element_path(element),
|
|
60
|
+
delete_path: admin_content_element_path(element),
|
|
61
|
+
confirm: "Delete this content element?",
|
|
62
|
+
delete_disabled: element.required?,
|
|
63
|
+
disabled_title: "Required Element"
|
|
64
|
+
) %>
|
|
65
|
+
</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<% end %>
|
|
68
|
+
</tbody>
|
|
69
|
+
</table>
|
|
70
|
+
</div>
|
|
71
|
+
<% else %>
|
|
72
|
+
<%= rp_empty_state "No content elements yet.", link_text: "Create your first content element", link_path: new_admin_content_element_path %>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<% content_for :title, @content_element.name %>
|
|
2
|
+
|
|
3
|
+
<div class="rp-page-header">
|
|
4
|
+
<div>
|
|
5
|
+
<h1 class="rp-page-title"><%= @content_element.name %></h1>
|
|
6
|
+
<p class="rp-byline">
|
|
7
|
+
in <%= link_to @content_element.content_group.name, admin_content_group_path(@content_element.content_group), class: "rp-link" %>
|
|
8
|
+
</p>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="rp-page-actions">
|
|
11
|
+
<%= link_to "Edit", edit_admin_content_element_path(@content_element), class: "rp-btn rp-btn--primary" %>
|
|
12
|
+
<%= link_to "Back", admin_content_elements_path, class: "rp-btn rp-btn--secondary" %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="rp-post-meta-card">
|
|
17
|
+
<div class="rp-meta-item">
|
|
18
|
+
<span class="rp-meta-label">Type</span>
|
|
19
|
+
<%= rp_badge(@content_element.content_type.titleize, status: @content_element.text? ? :published : :info) %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="rp-meta-item">
|
|
22
|
+
<span class="rp-meta-label">Required</span>
|
|
23
|
+
<%= rp_boolean_badge(@content_element.required) %>
|
|
24
|
+
</div>
|
|
25
|
+
<% if @content_element.image? && @content_element.image_hint.present? %>
|
|
26
|
+
<div class="rp-meta-item">
|
|
27
|
+
<span class="rp-meta-label">Image Hint</span>
|
|
28
|
+
<span class="rp-meta-value"><%= @content_element.image_hint %></span>
|
|
29
|
+
</div>
|
|
30
|
+
<% end %>
|
|
31
|
+
<div class="rp-meta-item">
|
|
32
|
+
<span class="rp-meta-label">Group</span>
|
|
33
|
+
<span class="rp-meta-value"><%= @content_element.content_group.name %></span>
|
|
34
|
+
</div>
|
|
35
|
+
<% if @content_element.position.present? %>
|
|
36
|
+
<div class="rp-meta-item">
|
|
37
|
+
<span class="rp-meta-label">Position</span>
|
|
38
|
+
<span class="rp-meta-value"><%= @content_element.position %></span>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
<div class="rp-meta-item">
|
|
42
|
+
<span class="rp-meta-label">Versions</span>
|
|
43
|
+
<span class="rp-meta-value"><%= @content_element.version_count %></span>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="rp-meta-item">
|
|
46
|
+
<span class="rp-meta-label">Created</span>
|
|
47
|
+
<span class="rp-meta-value"><%= @content_element.created_at.strftime("%b %d, %Y at %l:%M %p") %></span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="rp-meta-item">
|
|
50
|
+
<span class="rp-meta-label">Updated</span>
|
|
51
|
+
<span class="rp-meta-value"><%= @content_element.updated_at.strftime("%b %d, %Y at %l:%M %p") %></span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<% if @content_element.image? && @content_element.image.attached? %>
|
|
56
|
+
<%= render "railspress/admin/shared/image_section_compact",
|
|
57
|
+
record: @content_element, attachment_name: :image,
|
|
58
|
+
label: "Element Image",
|
|
59
|
+
editor_url: image_editor_admin_content_element_path(@content_element) %>
|
|
60
|
+
<% end %>
|
|
61
|
+
|
|
62
|
+
<%# Current Content Preview %>
|
|
63
|
+
<div class="rp-card rp-card--padded">
|
|
64
|
+
<h3 class="rp-sidebar-title">Current Content</h3>
|
|
65
|
+
<% if @content_element.text? && @content_element.text_content.present? %>
|
|
66
|
+
<div class="rp-content-preview"><%= @content_element.text_content %></div>
|
|
67
|
+
<% elsif @content_element.image? && @content_element.image.attached? %>
|
|
68
|
+
<div class="rp-content-preview">
|
|
69
|
+
<%= image_tag main_app.url_for(@content_element.image), style: "max-width: 100%; height: auto; border-radius: 4px;" %>
|
|
70
|
+
</div>
|
|
71
|
+
<% else %>
|
|
72
|
+
<p class="rp-empty-state">No content set.</p>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<%# Version History %>
|
|
77
|
+
<% if @versions.any? %>
|
|
78
|
+
<div class="rp-card" style="margin-top: 1.5rem;">
|
|
79
|
+
<div class="rp-card__header">
|
|
80
|
+
<h3 class="rp-sidebar-title" style="padding: 1rem 1.25rem; margin: 0;">Version History</h3>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="rp-table--responsive">
|
|
83
|
+
<table class="rp-table">
|
|
84
|
+
<thead>
|
|
85
|
+
<tr>
|
|
86
|
+
<th><%= rp_table_header("Version") %></th>
|
|
87
|
+
<th><%= rp_table_header("Content") %></th>
|
|
88
|
+
<th><%= rp_table_header("Created") %></th>
|
|
89
|
+
<th class="rp-table-actions"><%= rp_table_header("Actions") %></th>
|
|
90
|
+
</tr>
|
|
91
|
+
</thead>
|
|
92
|
+
<tbody>
|
|
93
|
+
<% @versions.each do |version| %>
|
|
94
|
+
<tr>
|
|
95
|
+
<td class="rp-table-primary">#<%= version.version_number %></td>
|
|
96
|
+
<td class="rp-table-secondary"><%= rp_truncated_text(version.text_content, length: 60) %></td>
|
|
97
|
+
<td class="rp-table-secondary"><%= version.created_at.strftime("%b %d, %Y %l:%M %p") %></td>
|
|
98
|
+
<td class="rp-table-actions">
|
|
99
|
+
<%= link_to admin_content_element_version_path(version), class: "rp-icon-btn", title: "View version" do %>
|
|
100
|
+
<%= rp_icon(:search) %>
|
|
101
|
+
<% end %>
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
<% end %>
|
|
105
|
+
</tbody>
|
|
106
|
+
</table>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
<% end %>
|
|
110
|
+
|
|
111
|
+
<%# API Usage %>
|
|
112
|
+
<div class="rp-card rp-card--padded" style="margin-top: 1.5rem;">
|
|
113
|
+
<h3 class="rp-sidebar-title">API Usage</h3>
|
|
114
|
+
<pre class="rp-code-block"><code># Get the value
|
|
115
|
+
Railspress::CMS.find("<%= @content_element.content_group.name %>").load("<%= @content_element.name %>").value
|
|
116
|
+
|
|
117
|
+
# In views
|
|
118
|
+
<%= cms_value("<%= @content_element.content_group.name %>", "<%= @content_element.name %>") %>
|
|
119
|
+
|
|
120
|
+
# With custom rendering
|
|
121
|
+
<%= cms_element(group: "<%= @content_element.content_group.name %>", name: "<%= @content_element.name %>") do |value| %>
|
|
122
|
+
<h1><%= value %></h1>
|
|
123
|
+
<% end %></code></pre>
|
|
124
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%= form_with model: [:admin, @content_group], class: "rp-form rp-form--narrow" do |form| %>
|
|
2
|
+
<%= rp_form_errors(@content_group) %>
|
|
3
|
+
|
|
4
|
+
<%= rp_string_field(form, :name, primary: true, placeholder: "e.g., Homepage, Footer, About Page", required: true) %>
|
|
5
|
+
|
|
6
|
+
<%= rp_text_field(form, :description, rows: 3, placeholder: "Brief description of this content group (optional)") %>
|
|
7
|
+
|
|
8
|
+
<%= rp_form_actions(form, admin_content_groups_path) %>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<% content_for :title, "Content Groups" %>
|
|
2
|
+
|
|
3
|
+
<%= rp_page_header "Content Groups", "New Group" => new_admin_content_group_path %>
|
|
4
|
+
|
|
5
|
+
<div class="rp-card">
|
|
6
|
+
<% if @content_groups.any? %>
|
|
7
|
+
<div class="rp-table--responsive">
|
|
8
|
+
<table class="rp-table">
|
|
9
|
+
<thead>
|
|
10
|
+
<tr>
|
|
11
|
+
<th><%= rp_table_header("Name") %></th>
|
|
12
|
+
<th><%= rp_table_header("Description") %></th>
|
|
13
|
+
<th><%= rp_table_header("Elements") %></th>
|
|
14
|
+
<th><%= rp_table_header("Created") %></th>
|
|
15
|
+
<th class="rp-table-actions"><%= rp_table_header("Actions") %></th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<% @content_groups.each do |group| %>
|
|
20
|
+
<tr>
|
|
21
|
+
<td class="rp-table-primary">
|
|
22
|
+
<%= link_to group.name, admin_content_group_path(group), class: "rp-link" %>
|
|
23
|
+
</td>
|
|
24
|
+
<td class="rp-table-secondary"><%= rp_truncated_text(group.description, length: 60) %></td>
|
|
25
|
+
<td><%= rp_badge(group.element_count.to_s, status: group.element_count > 0 ? :published : :draft) %></td>
|
|
26
|
+
<td class="rp-table-secondary"><%= group.created_at.strftime("%b %d, %Y") %></td>
|
|
27
|
+
<td class="rp-table-actions">
|
|
28
|
+
<%= rp_table_action_icons(
|
|
29
|
+
edit_path: edit_admin_content_group_path(group),
|
|
30
|
+
delete_path: admin_content_group_path(group),
|
|
31
|
+
confirm: "Delete this content group and all its elements?"
|
|
32
|
+
) %>
|
|
33
|
+
</td>
|
|
34
|
+
</tr>
|
|
35
|
+
<% end %>
|
|
36
|
+
</tbody>
|
|
37
|
+
</table>
|
|
38
|
+
</div>
|
|
39
|
+
<% else %>
|
|
40
|
+
<%= rp_empty_state "No content groups yet.", link_text: "Create your first content group", link_path: new_admin_content_group_path %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<% content_for :title, @content_group.name %>
|
|
2
|
+
|
|
3
|
+
<div class="rp-page-header">
|
|
4
|
+
<div>
|
|
5
|
+
<h1 class="rp-page-title"><%= @content_group.name %></h1>
|
|
6
|
+
<% if @content_group.description.present? %>
|
|
7
|
+
<p class="rp-byline"><%= @content_group.description %></p>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="rp-page-actions">
|
|
11
|
+
<%= link_to "New Element", new_admin_content_element_path(content_group_id: @content_group.id), class: "rp-btn rp-btn--primary" %>
|
|
12
|
+
<%= link_to "Edit", edit_admin_content_group_path(@content_group), class: "rp-btn rp-btn--secondary" %>
|
|
13
|
+
<%= link_to "Back", admin_content_groups_path, class: "rp-btn rp-btn--secondary" %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="rp-post-meta-card">
|
|
18
|
+
<div class="rp-meta-item">
|
|
19
|
+
<span class="rp-meta-label">Elements</span>
|
|
20
|
+
<span class="rp-meta-value"><%= @content_group.element_count %></span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="rp-meta-item">
|
|
23
|
+
<span class="rp-meta-label">Created</span>
|
|
24
|
+
<span class="rp-meta-value"><%= @content_group.created_at.strftime("%b %d, %Y") %></span>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="rp-meta-item">
|
|
27
|
+
<span class="rp-meta-label">Updated</span>
|
|
28
|
+
<span class="rp-meta-value"><%= @content_group.updated_at.strftime("%b %d, %Y") %></span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="rp-card">
|
|
33
|
+
<% if @content_elements.any? %>
|
|
34
|
+
<div class="rp-table--responsive">
|
|
35
|
+
<table class="rp-table">
|
|
36
|
+
<thead>
|
|
37
|
+
<tr>
|
|
38
|
+
<th><%= rp_table_header("Name") %></th>
|
|
39
|
+
<th><%= rp_table_header("Type") %></th>
|
|
40
|
+
<th><%= rp_table_header("Content") %></th>
|
|
41
|
+
<th><%= rp_table_header("Position") %></th>
|
|
42
|
+
<th><%= rp_table_header("Updated") %></th>
|
|
43
|
+
<th class="rp-table-actions"><%= rp_table_header("Actions") %></th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody>
|
|
47
|
+
<% @content_elements.each do |element| %>
|
|
48
|
+
<tr>
|
|
49
|
+
<td class="rp-table-primary">
|
|
50
|
+
<%= link_to admin_content_element_path(element), class: "rp-link" do %>
|
|
51
|
+
<% if element.required? %><strong><%= element.name %></strong><% else %><%= element.name %><% end %>
|
|
52
|
+
<% end %>
|
|
53
|
+
</td>
|
|
54
|
+
<td><%= rp_badge(element.content_type.titleize, status: element.text? ? :published : :info) %></td>
|
|
55
|
+
<td class="rp-table-secondary">
|
|
56
|
+
<% if element.image? && element.image.attached? %>
|
|
57
|
+
<%= image_tag main_app.rails_blob_path(element.image, only_path: true), style: "max-height: 1.5rem; width: auto; border-radius: 2px; vertical-align: middle;" %>
|
|
58
|
+
<% else %>
|
|
59
|
+
<%= rp_truncated_text(element.text_content, length: 50) %>
|
|
60
|
+
<% end %>
|
|
61
|
+
</td>
|
|
62
|
+
<td><%= element.position || "—" %></td>
|
|
63
|
+
<td class="rp-table-secondary"><%= element.updated_at.strftime("%b %d, %Y") %></td>
|
|
64
|
+
<td class="rp-table-actions">
|
|
65
|
+
<%= rp_table_action_icons(
|
|
66
|
+
edit_path: edit_admin_content_element_path(element),
|
|
67
|
+
delete_path: admin_content_element_path(element),
|
|
68
|
+
confirm: "Delete this content element?",
|
|
69
|
+
delete_disabled: element.required?,
|
|
70
|
+
disabled_title: "Required Element"
|
|
71
|
+
) %>
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
<% end %>
|
|
75
|
+
</tbody>
|
|
76
|
+
</table>
|
|
77
|
+
</div>
|
|
78
|
+
<% else %>
|
|
79
|
+
<%= rp_empty_state "No content elements in this group yet.", link_text: "Create your first element", link_path: new_admin_content_element_path(content_group_id: @content_group.id) %>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<% if @content_group.element_count > 0 %>
|
|
84
|
+
<div class="rp-card rp-card--padded" style="margin-top: 1.5rem;">
|
|
85
|
+
<h3 class="rp-sidebar-title">API Usage</h3>
|
|
86
|
+
<pre class="rp-code-block"><code># Load a specific element value
|
|
87
|
+
Railspress::CMS.find("<%= @content_group.name %>").load("element_name").value
|
|
88
|
+
|
|
89
|
+
# In views
|
|
90
|
+
<%= cms_value("<%= @content_group.name %>", "element_name") %></code></pre>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<div class="rp-stat-value"><%= @tags_count %></div>
|
|
22
22
|
<div class="rp-stat-label">Tags</div>
|
|
23
23
|
</div>
|
|
24
|
+
<% if Railspress.cms_enabled? %>
|
|
25
|
+
<div class="rp-stat-card">
|
|
26
|
+
<div class="rp-stat-value"><%= @content_groups_count %></div>
|
|
27
|
+
<div class="rp-stat-label">Content Groups</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="rp-stat-card">
|
|
30
|
+
<div class="rp-stat-value"><%= @content_elements_count %></div>
|
|
31
|
+
<div class="rp-stat-label">Content Elements</div>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
<!-- Recent Posts -->
|
|
@@ -34,7 +44,7 @@
|
|
|
34
44
|
<% @recent_posts.each do |post| %>
|
|
35
45
|
<li class="rp-list-item">
|
|
36
46
|
<%= link_to post.title, admin_post_path(post), class: "rp-link" %>
|
|
37
|
-
<span class="rp-badge rp-badge--<%= post.
|
|
47
|
+
<span class="rp-badge rp-badge--<%= post.display_status %>"><%= post.display_status.titleize %></span>
|
|
38
48
|
</li>
|
|
39
49
|
<% end %>
|
|
40
50
|
</ul>
|
|
@@ -43,3 +53,28 @@
|
|
|
43
53
|
<% end %>
|
|
44
54
|
</div>
|
|
45
55
|
</div>
|
|
56
|
+
|
|
57
|
+
<% if Railspress.cms_enabled? %>
|
|
58
|
+
<!-- Recent Content Elements -->
|
|
59
|
+
<div class="rp-section">
|
|
60
|
+
<div class="rp-section-header">
|
|
61
|
+
<h2 class="rp-section-title">Recent Content</h2>
|
|
62
|
+
<%= link_to "View All", admin_content_elements_path, class: "rp-link" %>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="rp-card">
|
|
65
|
+
<% if @recent_content_elements.any? %>
|
|
66
|
+
<ul class="rp-list">
|
|
67
|
+
<% @recent_content_elements.each do |element| %>
|
|
68
|
+
<li class="rp-list-item">
|
|
69
|
+
<%= link_to element.name, admin_content_element_path(element), class: "rp-link" %>
|
|
70
|
+
<span class="rp-table-secondary">in <%= element.content_group.name %></span>
|
|
71
|
+
<%= rp_badge(element.content_type.titleize, status: element.text? ? :published : :info) %>
|
|
72
|
+
</li>
|
|
73
|
+
<% end %>
|
|
74
|
+
</ul>
|
|
75
|
+
<% else %>
|
|
76
|
+
<p class="rp-empty-state">No content elements yet. <%= link_to "Create your first content group", new_admin_content_group_path, class: "rp-link" %>.</p>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<%= form_with model: @record,
|
|
2
|
+
url: @record.persisted? ? entity_show_path(@record) : entity_index_path,
|
|
3
|
+
class: "rp-form" do |form| %>
|
|
4
|
+
|
|
5
|
+
<%= rp_form_errors(@record) %>
|
|
6
|
+
|
|
7
|
+
<%
|
|
8
|
+
# Separate fields into main content vs sidebar metadata vs attachments
|
|
9
|
+
# Focal point images go in main content area (prominent placement)
|
|
10
|
+
main_types = [:string, :text, :rich_text, :lines, :focal_point_image]
|
|
11
|
+
sidebar_types = [:boolean, :integer, :decimal, :datetime, :date, :list]
|
|
12
|
+
attachment_types = [:attachment, :attachments]
|
|
13
|
+
|
|
14
|
+
main_fields = entity_config.fields.select { |_, f| main_types.include?(f[:type]) }
|
|
15
|
+
sidebar_fields = entity_config.fields.select { |_, f| sidebar_types.include?(f[:type]) }
|
|
16
|
+
attachment_fields = entity_config.fields.select { |_, f| attachment_types.include?(f[:type]) }
|
|
17
|
+
%>
|
|
18
|
+
|
|
19
|
+
<div class="rp-form-layout">
|
|
20
|
+
<!-- Main content column -->
|
|
21
|
+
<div class="rp-form-main">
|
|
22
|
+
<% main_fields.each_with_index do |(name, field), index| %>
|
|
23
|
+
<%= rp_render_field(form, name,
|
|
24
|
+
type: field[:type],
|
|
25
|
+
primary: index == 0 && field[:type] == :string,
|
|
26
|
+
required: @record.class.validators_on(name).any? { |v| v.is_a?(ActiveModel::Validations::PresenceValidator) }) %>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<% if sidebar_fields.any? || attachment_fields.any? %>
|
|
31
|
+
<!-- Sidebar column -->
|
|
32
|
+
<div class="rp-form-sidebar">
|
|
33
|
+
<% if sidebar_fields.any? %>
|
|
34
|
+
<%= rp_sidebar_section("Options") do %>
|
|
35
|
+
<% sidebar_fields.each do |name, field| %>
|
|
36
|
+
<%= rp_render_field(form, name, type: field[:type]) %>
|
|
37
|
+
<% end %>
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
41
|
+
<% attachment_fields.each do |name, field| %>
|
|
42
|
+
<%= rp_sidebar_section(name.to_s.humanize) do %>
|
|
43
|
+
<%= rp_render_field(form, name,
|
|
44
|
+
type: field[:type],
|
|
45
|
+
param_key: entity_config.param_key) %>
|
|
46
|
+
<% end %>
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<%= rp_form_actions(form, entity_index_path) %>
|
|
53
|
+
<% end %>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<% content_for :title, entity_config.label %>
|
|
2
|
+
|
|
3
|
+
<%= rp_page_header(entity_config.label, "New #{entity_config.singular_label}" => entity_new_path) %>
|
|
4
|
+
|
|
5
|
+
<%= rp_card do %>
|
|
6
|
+
<% if @records.any? %>
|
|
7
|
+
<% columns = entity_class.railspress_index_columns %>
|
|
8
|
+
<div class="rp-table--responsive">
|
|
9
|
+
<table class="rp-table">
|
|
10
|
+
<thead>
|
|
11
|
+
<tr>
|
|
12
|
+
<% columns.each do |col| %>
|
|
13
|
+
<th><%= col.to_s.humanize %></th>
|
|
14
|
+
<% end %>
|
|
15
|
+
<th class="rp-table-actions">Actions</th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
<% @records.each do |record| %>
|
|
20
|
+
<tr>
|
|
21
|
+
<% columns.each_with_index do |col, index| %>
|
|
22
|
+
<td<%= ' class="rp-table-truncate"'.html_safe if index == 0 %>>
|
|
23
|
+
<% value = record.public_send(col) %>
|
|
24
|
+
<% if index == 0 %>
|
|
25
|
+
<%= link_to entity_show_path(record), class: "rp-link rp-table-primary", title: value do %>
|
|
26
|
+
<%= rp_truncated_text(value, length: 60) %>
|
|
27
|
+
<% end %>
|
|
28
|
+
<% elsif value.is_a?(Time) || value.is_a?(DateTime) %>
|
|
29
|
+
<span class="rp-table-secondary" title="<%= value.strftime("%B %-d, %Y at %-I:%M %p") %>">
|
|
30
|
+
<span class="rp-table-date-full"><%= value.strftime("%-m/%-d/%Y") %></span>
|
|
31
|
+
<span class="rp-table-date-short"><%= value.strftime("%-m/%-d/%y") %></span>
|
|
32
|
+
</span>
|
|
33
|
+
<% elsif value.is_a?(Date) %>
|
|
34
|
+
<span class="rp-table-secondary" title="<%= value.strftime("%B %-d, %Y") %>">
|
|
35
|
+
<span class="rp-table-date-full"><%= value.strftime("%-m/%-d/%Y") %></span>
|
|
36
|
+
<span class="rp-table-date-short"><%= value.strftime("%-m/%-d/%y") %></span>
|
|
37
|
+
</span>
|
|
38
|
+
<% elsif value == true || value == false %>
|
|
39
|
+
<%= rp_boolean_badge(value) %>
|
|
40
|
+
<% elsif value.is_a?(Integer) %>
|
|
41
|
+
<%= value %>
|
|
42
|
+
<% else %>
|
|
43
|
+
<%= rp_truncated_text(value, length: 60) %>
|
|
44
|
+
<% end %>
|
|
45
|
+
</td>
|
|
46
|
+
<% end %>
|
|
47
|
+
<td class="rp-table-actions">
|
|
48
|
+
<%= link_to entity_edit_path(record), class: "rp-icon-btn", title: "Edit" do %>
|
|
49
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
50
|
+
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>
|
|
51
|
+
<path d="m15 5 4 4"/>
|
|
52
|
+
</svg>
|
|
53
|
+
<% end %>
|
|
54
|
+
<%= button_to entity_show_path(record), method: :delete,
|
|
55
|
+
data: { turbo_confirm: "Delete this #{entity_config.singular_label.downcase}?" },
|
|
56
|
+
class: "rp-icon-btn rp-icon-btn--danger", title: "Delete" do %>
|
|
57
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
58
|
+
<path d="M3 6h18"/>
|
|
59
|
+
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/>
|
|
60
|
+
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>
|
|
61
|
+
</svg>
|
|
62
|
+
<% end %>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<% end %>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
</div>
|
|
69
|
+
<% else %>
|
|
70
|
+
<%= rp_empty_state("No #{entity_config.label.downcase} yet.",
|
|
71
|
+
link_text: "Create your first #{entity_config.singular_label.downcase}",
|
|
72
|
+
link_path: entity_new_path) %>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% end %>
|