railspress-engine 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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/javascripts/railspress/admin.js +210 -0
  6. data/app/assets/stylesheets/actiontext.css +440 -0
  7. data/app/assets/stylesheets/railspress/admin.css +1199 -0
  8. data/app/assets/stylesheets/railspress/application.css +15 -0
  9. data/app/controllers/railspress/admin/base_controller.rb +38 -0
  10. data/app/controllers/railspress/admin/categories_controller.rb +53 -0
  11. data/app/controllers/railspress/admin/dashboard_controller.rb +12 -0
  12. data/app/controllers/railspress/admin/posts_controller.rb +71 -0
  13. data/app/controllers/railspress/admin/tags_controller.rb +50 -0
  14. data/app/controllers/railspress/application_controller.rb +4 -0
  15. data/app/helpers/railspress/application_helper.rb +4 -0
  16. data/app/jobs/railspress/application_job.rb +4 -0
  17. data/app/mailers/railspress/application_mailer.rb +6 -0
  18. data/app/models/railspress/application_record.rb +6 -0
  19. data/app/models/railspress/category.rb +18 -0
  20. data/app/models/railspress/post.rb +74 -0
  21. data/app/models/railspress/post_tag.rb +8 -0
  22. data/app/models/railspress/tag.rb +32 -0
  23. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  24. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  25. data/app/views/layouts/railspress/admin.html.erb +41 -0
  26. data/app/views/layouts/railspress/application.html.erb +17 -0
  27. data/app/views/railspress/admin/categories/_form.html.erb +32 -0
  28. data/app/views/railspress/admin/categories/edit.html.erb +6 -0
  29. data/app/views/railspress/admin/categories/index.html.erb +42 -0
  30. data/app/views/railspress/admin/categories/new.html.erb +6 -0
  31. data/app/views/railspress/admin/dashboard/index.html.erb +45 -0
  32. data/app/views/railspress/admin/posts/_form.html.erb +129 -0
  33. data/app/views/railspress/admin/posts/edit.html.erb +2 -0
  34. data/app/views/railspress/admin/posts/index.html.erb +55 -0
  35. data/app/views/railspress/admin/posts/new.html.erb +2 -0
  36. data/app/views/railspress/admin/posts/show.html.erb +47 -0
  37. data/app/views/railspress/admin/shared/_flash.html.erb +5 -0
  38. data/app/views/railspress/admin/shared/_sidebar.html.erb +38 -0
  39. data/app/views/railspress/admin/tags/_form.html.erb +27 -0
  40. data/app/views/railspress/admin/tags/edit.html.erb +6 -0
  41. data/app/views/railspress/admin/tags/index.html.erb +42 -0
  42. data/app/views/railspress/admin/tags/new.html.erb +6 -0
  43. data/config/routes.rb +9 -0
  44. data/db/migrate/20241218000001_create_railspress_categories.rb +14 -0
  45. data/db/migrate/20241218000002_create_railspress_tags.rb +13 -0
  46. data/db/migrate/20241218000003_create_railspress_posts.rb +19 -0
  47. data/db/migrate/20241218000004_create_railspress_post_tags.rb +12 -0
  48. data/lib/generators/railspress/authors/authors_generator.rb +73 -0
  49. data/lib/generators/railspress/authors/templates/add_author_to_railspress_posts.rb.tt +8 -0
  50. data/lib/generators/railspress/authors/templates/railspress.rb.tt +31 -0
  51. data/lib/generators/railspress/install/install_generator.rb +144 -0
  52. data/lib/railspress/engine.rb +14 -0
  53. data/lib/railspress/version.rb +3 -0
  54. data/lib/railspress.rb +79 -0
  55. data/lib/tasks/railspress_tasks.rake +4 -0
  56. metadata +141 -0
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,38 @@
1
+ module Railspress
2
+ module Admin
3
+ class BaseController < ActionController::Base
4
+ layout "railspress/admin"
5
+
6
+ helper_method :current_author, :available_authors, :authors_enabled?, :header_images_enabled?
7
+
8
+ # Authentication hook - to be configured later
9
+ # before_action :authenticate_admin!
10
+
11
+ private
12
+
13
+ def set_flash(type, message)
14
+ flash[type] = message
15
+ end
16
+
17
+ def authors_enabled?
18
+ Railspress.authors_enabled?
19
+ end
20
+
21
+ def header_images_enabled?
22
+ Railspress.header_images_enabled?
23
+ end
24
+
25
+ def current_author
26
+ return nil unless authors_enabled?
27
+ method_name = Railspress.current_author_method
28
+ return nil unless respond_to?(method_name, true)
29
+ send(method_name)
30
+ end
31
+
32
+ def available_authors
33
+ return [] unless authors_enabled?
34
+ Railspress.available_authors
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,53 @@
1
+ module Railspress
2
+ module Admin
3
+ class CategoriesController < BaseController
4
+ before_action :set_category, only: [:edit, :update, :destroy]
5
+
6
+ def index
7
+ @categories = Category.ordered
8
+ end
9
+
10
+ def new
11
+ @category = Category.new
12
+ end
13
+
14
+ def create
15
+ @category = Category.new(category_params)
16
+ if @category.save
17
+ redirect_to admin_categories_path, notice: "Category created."
18
+ else
19
+ render :new, status: :unprocessable_entity
20
+ end
21
+ end
22
+
23
+ def edit
24
+ end
25
+
26
+ def update
27
+ if @category.update(category_params)
28
+ redirect_to admin_categories_path, notice: "Category updated."
29
+ else
30
+ render :edit, status: :unprocessable_entity
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ if @category.destroy
36
+ redirect_to admin_categories_path, notice: "Category deleted."
37
+ else
38
+ redirect_to admin_categories_path, alert: "Cannot delete category with posts."
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def set_category
45
+ @category = Category.find(params[:id])
46
+ end
47
+
48
+ def category_params
49
+ params.require(:category).permit(:name, :slug, :description)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,12 @@
1
+ module Railspress
2
+ module Admin
3
+ class DashboardController < BaseController
4
+ def index
5
+ @posts_count = Post.count
6
+ @categories_count = Category.count
7
+ @tags_count = Tag.count
8
+ @recent_posts = Post.ordered.limit(5)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,71 @@
1
+ module Railspress
2
+ module Admin
3
+ class PostsController < BaseController
4
+ before_action :set_post, only: [:show, :edit, :update, :destroy]
5
+ before_action :load_categories, only: [:new, :create, :edit, :update]
6
+
7
+ def index
8
+ @posts = Post.includes(:category, :tags).ordered
9
+ end
10
+
11
+ def show
12
+ end
13
+
14
+ def new
15
+ @post = Post.new
16
+ @post.author = current_author if authors_enabled?
17
+ end
18
+
19
+ def create
20
+ @post = Post.new(post_params)
21
+ if @post.save
22
+ redirect_to admin_post_path(@post), notice: "Post created."
23
+ else
24
+ render :new, status: :unprocessable_entity
25
+ end
26
+ end
27
+
28
+ def edit
29
+ end
30
+
31
+ def update
32
+ if @post.update(post_params)
33
+ redirect_to admin_post_path(@post), notice: "Post updated."
34
+ else
35
+ render :edit, status: :unprocessable_entity
36
+ end
37
+ end
38
+
39
+ def destroy
40
+ @post.destroy
41
+ redirect_to admin_posts_path, notice: "Post deleted."
42
+ end
43
+
44
+ private
45
+
46
+ def set_post
47
+ @post = Post.find(params[:id])
48
+ end
49
+
50
+ def load_categories
51
+ @categories = Category.ordered
52
+ end
53
+
54
+ def post_params
55
+ permitted = [
56
+ :title,
57
+ :slug,
58
+ :category_id,
59
+ :content,
60
+ :status,
61
+ :meta_title,
62
+ :meta_description,
63
+ :tag_list
64
+ ]
65
+ permitted << :author_id if authors_enabled?
66
+ permitted.push(:header_image, :remove_header_image) if header_images_enabled?
67
+ params.require(:post).permit(permitted)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ module Railspress
2
+ module Admin
3
+ class TagsController < BaseController
4
+ before_action :set_tag, only: [:edit, :update, :destroy]
5
+
6
+ def index
7
+ @tags = Tag.ordered
8
+ end
9
+
10
+ def new
11
+ @tag = Tag.new
12
+ end
13
+
14
+ def create
15
+ @tag = Tag.new(tag_params)
16
+ if @tag.save
17
+ redirect_to admin_tags_path, notice: "Tag created."
18
+ else
19
+ render :new, status: :unprocessable_entity
20
+ end
21
+ end
22
+
23
+ def edit
24
+ end
25
+
26
+ def update
27
+ if @tag.update(tag_params)
28
+ redirect_to admin_tags_path, notice: "Tag updated."
29
+ else
30
+ render :edit, status: :unprocessable_entity
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ @tag.destroy
36
+ redirect_to admin_tags_path, notice: "Tag deleted."
37
+ end
38
+
39
+ private
40
+
41
+ def set_tag
42
+ @tag = Tag.find(params[:id])
43
+ end
44
+
45
+ def tag_params
46
+ params.require(:tag).permit(:name, :slug)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module Railspress
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Railspress
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Railspress
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Railspress
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Railspress
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ self.table_name_prefix = "railspress_"
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module Railspress
2
+ class Category < ApplicationRecord
3
+ has_many :posts, dependent: :restrict_with_error
4
+
5
+ validates :name, presence: true, uniqueness: true
6
+ validates :slug, presence: true, uniqueness: true
7
+
8
+ before_validation :generate_slug, if: -> { slug.blank? && name.present? }
9
+
10
+ scope :ordered, -> { order(:name) }
11
+
12
+ private
13
+
14
+ def generate_slug
15
+ self.slug = name.parameterize
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,74 @@
1
+ module Railspress
2
+ class Post < ApplicationRecord
3
+ belongs_to :category, optional: true
4
+ # Author association - only functional when Railspress.authors_enabled?
5
+ # The author class is configured via Railspress.configure { |c| c.author_class_name = "User" }
6
+ def author
7
+ return nil unless author_id.present? && Railspress.authors_enabled?
8
+ Railspress.author_class.find_by(id: author_id)
9
+ end
10
+
11
+ def author=(user)
12
+ self.author_id = user&.id
13
+ end
14
+ has_many :post_tags, dependent: :destroy
15
+ has_many :tags, through: :post_tags
16
+
17
+ has_rich_text :content
18
+ has_one_attached :header_image
19
+
20
+ # Virtual attribute for removing header image via checkbox
21
+ attr_accessor :remove_header_image
22
+ before_save :purge_header_image, if: -> { remove_header_image == "1" }
23
+
24
+ enum :status, { draft: 0, published: 1 }, default: :draft
25
+
26
+ validates :title, presence: true
27
+ validates :slug, presence: true, uniqueness: true
28
+
29
+ before_validation :generate_slug, if: -> { slug.blank? && title.present? }
30
+ before_save :set_published_at
31
+
32
+ scope :ordered, -> { order(created_at: :desc) }
33
+ scope :recent, -> { ordered.limit(10) }
34
+ scope :published, -> { where(status: :published).where.not(published_at: nil) }
35
+ scope :drafts, -> { where(status: :draft) }
36
+ scope :by_author, ->(author) { where(author_id: author.id) }
37
+
38
+ # Accepts CSV string and syncs tags
39
+ def tag_list=(csv_string)
40
+ self.tags = Tag.from_csv(csv_string)
41
+ end
42
+
43
+ def tag_list
44
+ tags.pluck(:name).join(", ")
45
+ end
46
+
47
+ private
48
+
49
+ def generate_slug
50
+ base_slug = title.parameterize
51
+ slug_candidate = base_slug
52
+ counter = 1
53
+
54
+ while self.class.where(slug: slug_candidate).where.not(id: id).exists?
55
+ slug_candidate = "#{base_slug}-#{counter}"
56
+ counter += 1
57
+ end
58
+
59
+ self.slug = slug_candidate
60
+ end
61
+
62
+ def set_published_at
63
+ if published? && published_at.nil?
64
+ self.published_at = Time.current
65
+ elsif draft?
66
+ self.published_at = nil
67
+ end
68
+ end
69
+
70
+ def purge_header_image
71
+ header_image.purge_later
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,8 @@
1
+ module Railspress
2
+ class PostTag < ApplicationRecord
3
+ belongs_to :post
4
+ belongs_to :tag
5
+
6
+ validates :post_id, uniqueness: { scope: :tag_id }
7
+ end
8
+ end
@@ -0,0 +1,32 @@
1
+ module Railspress
2
+ class Tag < ApplicationRecord
3
+ has_many :post_tags, dependent: :destroy
4
+ has_many :posts, through: :post_tags
5
+
6
+ validates :name, presence: true, uniqueness: { case_sensitive: false }
7
+ validates :slug, presence: true, uniqueness: true
8
+
9
+ before_validation :normalize_name
10
+ before_validation :generate_slug, if: -> { slug.blank? && name.present? }
11
+
12
+ scope :ordered, -> { order(:name) }
13
+
14
+ # Find or create tags from CSV string
15
+ def self.from_csv(csv_string)
16
+ return [] if csv_string.blank?
17
+
18
+ tag_names = csv_string.split(",").map { |t| t.strip.downcase }.reject(&:blank?).uniq
19
+ tag_names.map { |name| find_or_create_by(name: name) }
20
+ end
21
+
22
+ private
23
+
24
+ def normalize_name
25
+ self.name = name.strip.downcase if name.present?
26
+ end
27
+
28
+ def generate_slug
29
+ self.slug = name.parameterize
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ <figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
2
+ <% if blob.representable? %>
3
+ <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
4
+ <% end %>
5
+
6
+ <figcaption class="attachment__caption">
7
+ <% if caption = blob.try(:caption) %>
8
+ <%= caption %>
9
+ <% else %>
10
+ <span class="attachment__name"><%= blob.filename %></span>
11
+ <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
12
+ <% end %>
13
+ </figcaption>
14
+ </figure>
@@ -0,0 +1,3 @@
1
+ <div class="trix-content">
2
+ <%= yield -%>
3
+ </div>
@@ -0,0 +1,41 @@
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><%= content_for?(:title) ? "#{yield(:title)} - " : "" %>RailsPress Admin</title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= stylesheet_link_tag "lexxy", media: "all" %>
10
+ <%= stylesheet_link_tag "railspress/admin", media: "all" %>
11
+ </head>
12
+ <body>
13
+ <div class="rp-admin-layout">
14
+ <!-- Mobile Header -->
15
+ <header class="rp-mobile-header">
16
+ <button class="rp-hamburger" aria-label="Toggle menu" aria-expanded="false">
17
+ <span></span>
18
+ <span></span>
19
+ <span></span>
20
+ </button>
21
+ <span class="rp-mobile-logo">RailsPress</span>
22
+ </header>
23
+
24
+ <div class="rp-overlay"></div>
25
+
26
+ <!-- Sidebar -->
27
+ <aside class="rp-sidebar">
28
+ <%= render "railspress/admin/shared/sidebar" %>
29
+ </aside>
30
+
31
+ <!-- Main Content -->
32
+ <main class="rp-main">
33
+ <%= render "railspress/admin/shared/flash" %>
34
+ <%= yield %>
35
+ </main>
36
+ </div>
37
+
38
+ <%= javascript_importmap_tags %>
39
+ <%= javascript_include_tag "railspress/admin" %>
40
+ </body>
41
+ </html>
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Railspress</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "railspress/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,32 @@
1
+ <%= form_with model: [:admin, @category], class: "rp-form rp-form--narrow" do |form| %>
2
+ <% if @category.errors.any? %>
3
+ <div class="rp-form-errors">
4
+ <ul>
5
+ <% @category.errors.full_messages.each do |msg| %>
6
+ <li><%= msg %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="rp-form-group">
13
+ <%= form.label :name, class: "rp-label rp-label--lg rp-label--required" %>
14
+ <%= form.text_field :name, class: "rp-input rp-input--lg", placeholder: "Category name", data: { slug_source: true }, required: true %>
15
+ </div>
16
+
17
+ <div class="rp-form-group">
18
+ <%= form.label :slug, class: "rp-label" %>
19
+ <%= form.text_field :slug, class: "rp-input rp-input--mono", placeholder: "auto-generated-if-blank", data: { slug_target: true } %>
20
+ <p class="rp-hint">Leave blank to auto-generate from name</p>
21
+ </div>
22
+
23
+ <div class="rp-form-group">
24
+ <%= form.label :description, class: "rp-label" %>
25
+ <%= form.text_area :description, rows: 3, class: "rp-input", placeholder: "Brief description of this category (optional)" %>
26
+ </div>
27
+
28
+ <div class="rp-form-actions">
29
+ <%= form.submit class: "rp-btn rp-btn--primary" %>
30
+ <%= link_to "Cancel", admin_categories_path, class: "rp-btn rp-btn--secondary" %>
31
+ </div>
32
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <% content_for :title, "Edit Category" %>
2
+
3
+ <h1 class="rp-page-title rp-page-title--standalone">Edit Category</h1>
4
+ <div class="rp-card rp-card--padded">
5
+ <%= render "form" %>
6
+ </div>
@@ -0,0 +1,42 @@
1
+ <% content_for :title, "Categories" %>
2
+
3
+ <div class="rp-page-header">
4
+ <h1 class="rp-page-title">Categories</h1>
5
+ <div class="rp-page-actions">
6
+ <%= link_to "New Category", new_admin_category_path, class: "rp-btn rp-btn--primary" %>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="rp-card">
11
+ <% if @categories.any? %>
12
+ <div class="rp-table--responsive">
13
+ <table class="rp-table">
14
+ <thead>
15
+ <tr>
16
+ <th>Name</th>
17
+ <th>Slug</th>
18
+ <th>Posts</th>
19
+ <th class="rp-table-actions">Actions</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @categories.each do |category| %>
24
+ <tr>
25
+ <td class="rp-table-primary"><%= category.name %></td>
26
+ <td class="rp-table-secondary"><%= category.slug %></td>
27
+ <td><%= category.posts.count %></td>
28
+ <td class="rp-table-actions">
29
+ <%= link_to "Edit", edit_admin_category_path(category), class: "rp-link" %>
30
+ <%= button_to "Delete", admin_category_path(category), method: :delete,
31
+ data: { turbo_confirm: "Delete this category?" },
32
+ class: "rp-link rp-link--danger" %>
33
+ </td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ <% else %>
40
+ <p class="rp-empty-state">No categories yet. <%= link_to "Create your first category", new_admin_category_path, class: "rp-link" %>.</p>
41
+ <% end %>
42
+ </div>
@@ -0,0 +1,6 @@
1
+ <% content_for :title, "New Category" %>
2
+
3
+ <h1 class="rp-page-title rp-page-title--standalone">New Category</h1>
4
+ <div class="rp-card rp-card--padded">
5
+ <%= render "form" %>
6
+ </div>
@@ -0,0 +1,45 @@
1
+ <% content_for :title, "Dashboard" %>
2
+
3
+ <div class="rp-page-header">
4
+ <h1 class="rp-page-title">Dashboard</h1>
5
+ <div class="rp-page-actions">
6
+ <%= link_to "New Post", new_admin_post_path, class: "rp-btn rp-btn--primary" %>
7
+ </div>
8
+ </div>
9
+
10
+ <!-- Stats Grid -->
11
+ <div class="rp-stats-grid">
12
+ <div class="rp-stat-card">
13
+ <div class="rp-stat-value"><%= @posts_count %></div>
14
+ <div class="rp-stat-label">Posts</div>
15
+ </div>
16
+ <div class="rp-stat-card">
17
+ <div class="rp-stat-value"><%= @categories_count %></div>
18
+ <div class="rp-stat-label">Categories</div>
19
+ </div>
20
+ <div class="rp-stat-card">
21
+ <div class="rp-stat-value"><%= @tags_count %></div>
22
+ <div class="rp-stat-label">Tags</div>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- Recent Posts -->
27
+ <div class="rp-section">
28
+ <div class="rp-section-header">
29
+ <h2 class="rp-section-title">Recent Posts</h2>
30
+ </div>
31
+ <div class="rp-card">
32
+ <% if @recent_posts.any? %>
33
+ <ul class="rp-list">
34
+ <% @recent_posts.each do |post| %>
35
+ <li class="rp-list-item">
36
+ <%= link_to post.title, admin_post_path(post), class: "rp-link" %>
37
+ <span class="rp-badge rp-badge--<%= post.status %>"><%= post.status.titleize %></span>
38
+ </li>
39
+ <% end %>
40
+ </ul>
41
+ <% else %>
42
+ <p class="rp-empty-state">No posts yet. <%= link_to "Create your first post", new_admin_post_path, class: "rp-link" %>.</p>
43
+ <% end %>
44
+ </div>
45
+ </div>