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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -0
  3. data/README.md +195 -25
  4. data/app/assets/javascripts/railspress/admin.js +39 -0
  5. data/app/assets/javascripts/railspress/markdown_mode.js +343 -0
  6. data/app/assets/stylesheets/application.css +0 -0
  7. data/app/assets/stylesheets/railspress/admin/badges.css +70 -0
  8. data/app/assets/stylesheets/railspress/admin/base.css +25 -0
  9. data/app/assets/stylesheets/railspress/admin/buttons.css +140 -0
  10. data/app/assets/stylesheets/railspress/admin/cards.css +52 -0
  11. data/app/assets/stylesheets/railspress/admin/components/exports.css +55 -0
  12. data/app/assets/stylesheets/railspress/admin/components/focal_point.css +801 -0
  13. data/app/assets/stylesheets/railspress/admin/components/imports.css +144 -0
  14. data/app/assets/stylesheets/railspress/admin/components/lexxy.css +156 -0
  15. data/app/assets/stylesheets/railspress/admin/filters.css +73 -0
  16. data/app/assets/stylesheets/railspress/admin/flash.css +26 -0
  17. data/app/assets/stylesheets/railspress/admin/forms.css +459 -0
  18. data/app/assets/stylesheets/railspress/admin/layout.css +256 -0
  19. data/app/assets/stylesheets/railspress/admin/lists.css +24 -0
  20. data/app/assets/stylesheets/railspress/admin/page.css +111 -0
  21. data/app/assets/stylesheets/railspress/admin/responsive.css +174 -0
  22. data/app/assets/stylesheets/railspress/admin/stats.css +43 -0
  23. data/app/assets/stylesheets/railspress/admin/tables.css +163 -0
  24. data/app/assets/stylesheets/railspress/admin/utilities.css +202 -0
  25. data/app/assets/stylesheets/railspress/admin/variables.css +58 -0
  26. data/app/assets/stylesheets/railspress/application.css +44 -13
  27. data/app/controllers/railspress/admin/base_controller.rb +6 -3
  28. data/app/controllers/railspress/admin/categories_controller.rb +1 -1
  29. data/app/controllers/railspress/admin/cms_transfers_controller.rb +49 -0
  30. data/app/controllers/railspress/admin/content_element_versions_controller.rb +12 -0
  31. data/app/controllers/railspress/admin/content_elements_controller.rb +143 -0
  32. data/app/controllers/railspress/admin/content_groups_controller.rb +69 -0
  33. data/app/controllers/railspress/admin/dashboard_controller.rb +6 -0
  34. data/app/controllers/railspress/admin/entities_controller.rb +157 -0
  35. data/app/controllers/railspress/admin/exports_controller.rb +55 -0
  36. data/app/controllers/railspress/admin/focal_points_controller.rb +100 -0
  37. data/app/controllers/railspress/admin/imports_controller.rb +63 -0
  38. data/app/controllers/railspress/admin/posts_controller.rb +58 -4
  39. data/app/controllers/railspress/admin/prototypes_controller.rb +30 -0
  40. data/app/controllers/railspress/admin/tags_controller.rb +1 -1
  41. data/app/controllers/railspress/application_controller.rb +1 -0
  42. data/app/helpers/railspress/admin_helper.rb +733 -0
  43. data/app/helpers/railspress/application_helper.rb +23 -0
  44. data/app/helpers/railspress/cms_helper.rb +319 -0
  45. data/app/javascript/railspress/controllers/cms_inline_editor_controller.js +147 -0
  46. data/app/javascript/railspress/controllers/content_element_form_controller.js +15 -0
  47. data/app/javascript/railspress/controllers/crop_controller.js +224 -0
  48. data/app/javascript/railspress/controllers/dropzone_controller.js +261 -0
  49. data/app/javascript/railspress/controllers/focal_point_controller.js +124 -0
  50. data/app/javascript/railspress/controllers/image_section_controller.js +94 -0
  51. data/app/javascript/railspress/controllers/index.js +37 -0
  52. data/app/javascript/railspress/index.js +62 -0
  53. data/app/jobs/railspress/export_posts_job.rb +16 -0
  54. data/app/jobs/railspress/import_posts_job.rb +44 -0
  55. data/app/models/concerns/railspress/has_focal_point.rb +242 -0
  56. data/app/models/concerns/railspress/soft_deletable.rb +23 -0
  57. data/app/models/concerns/railspress/taggable.rb +23 -0
  58. data/app/models/railspress/content_element.rb +103 -0
  59. data/app/models/railspress/content_element_version.rb +32 -0
  60. data/app/models/railspress/content_group.rb +39 -0
  61. data/app/models/railspress/export.rb +67 -0
  62. data/app/models/railspress/focal_point.rb +70 -0
  63. data/app/models/railspress/import.rb +65 -0
  64. data/app/models/railspress/post.rb +102 -15
  65. data/app/models/railspress/post_export_processor.rb +162 -0
  66. data/app/models/railspress/post_import_processor.rb +382 -0
  67. data/app/models/railspress/tag.rb +10 -3
  68. data/app/models/railspress/tagging.rb +11 -0
  69. data/app/services/railspress/content_export_service.rb +122 -0
  70. data/app/services/railspress/content_import_service.rb +228 -0
  71. data/app/views/action_text/attachables/_remote_image.html.erb +8 -0
  72. data/app/views/active_storage/blobs/_blob.html.erb +1 -1
  73. data/app/views/layouts/railspress/admin.html.erb +3 -1
  74. data/app/views/railspress/admin/categories/index.html.erb +11 -15
  75. data/app/views/railspress/admin/cms_transfers/show.html.erb +167 -0
  76. data/app/views/railspress/admin/content_element_versions/show.html.erb +42 -0
  77. data/app/views/railspress/admin/content_elements/_form.html.erb +71 -0
  78. data/app/views/railspress/admin/content_elements/_inline_form.html.erb +32 -0
  79. data/app/views/railspress/admin/content_elements/_inline_form_frame.html.erb +6 -0
  80. data/app/views/railspress/admin/content_elements/edit.html.erb +6 -0
  81. data/app/views/railspress/admin/content_elements/index.html.erb +74 -0
  82. data/app/views/railspress/admin/content_elements/new.html.erb +6 -0
  83. data/app/views/railspress/admin/content_elements/show.html.erb +124 -0
  84. data/app/views/railspress/admin/content_groups/_form.html.erb +9 -0
  85. data/app/views/railspress/admin/content_groups/edit.html.erb +6 -0
  86. data/app/views/railspress/admin/content_groups/index.html.erb +42 -0
  87. data/app/views/railspress/admin/content_groups/new.html.erb +6 -0
  88. data/app/views/railspress/admin/content_groups/show.html.erb +92 -0
  89. data/app/views/railspress/admin/dashboard/index.html.erb +36 -1
  90. data/app/views/railspress/admin/entities/_form.html.erb +53 -0
  91. data/app/views/railspress/admin/entities/edit.html.erb +4 -0
  92. data/app/views/railspress/admin/entities/index.html.erb +74 -0
  93. data/app/views/railspress/admin/entities/new.html.erb +4 -0
  94. data/app/views/railspress/admin/entities/show.html.erb +117 -0
  95. data/app/views/railspress/admin/exports/show.html.erb +62 -0
  96. data/app/views/railspress/admin/imports/_instructions.html.erb +56 -0
  97. data/app/views/railspress/admin/imports/show.html.erb +137 -0
  98. data/app/views/railspress/admin/posts/_form.html.erb +102 -28
  99. data/app/views/railspress/admin/posts/_post_row.html.erb +40 -0
  100. data/app/views/railspress/admin/posts/index.html.erb +47 -36
  101. data/app/views/railspress/admin/posts/show.html.erb +55 -19
  102. data/app/views/railspress/admin/prototypes/image_section.html.erb +42 -0
  103. data/app/views/railspress/admin/shared/_dropzone.html.erb +84 -0
  104. data/app/views/railspress/admin/shared/_focal_point_editor.html.erb +102 -0
  105. data/app/views/railspress/admin/shared/_image_section.html.erb +159 -0
  106. data/app/views/railspress/admin/shared/_image_section_compact.html.erb +90 -0
  107. data/app/views/railspress/admin/shared/_image_section_editor.html.erb +171 -0
  108. data/app/views/railspress/admin/shared/_image_section_v2.html.erb +205 -0
  109. data/app/views/railspress/admin/shared/_sidebar.html.erb +73 -5
  110. data/app/views/railspress/admin/tags/index.html.erb +12 -16
  111. data/config/brakeman.ignore +18 -0
  112. data/config/importmap.rb +23 -0
  113. data/config/routes.rb +62 -1
  114. data/db/migrate/20241218000004_create_railspress_post_tags.rb +1 -1
  115. data/db/migrate/20241218000005_create_railspress_imports.rb +21 -0
  116. data/db/migrate/20241218000006_create_railspress_exports.rb +20 -0
  117. data/db/migrate/20241218000007_create_railspress_taggings.rb +20 -0
  118. data/db/migrate/20241218000008_drop_railspress_post_tags.rb +14 -0
  119. data/db/migrate/20241218000010_add_reading_time_to_railspress_posts.rb +5 -0
  120. data/db/migrate/20250105000002_create_railspress_focal_points.rb +20 -0
  121. data/db/migrate/20260206000001_create_railspress_content_groups.rb +18 -0
  122. data/db/migrate/20260206000002_create_railspress_content_elements.rb +21 -0
  123. data/db/migrate/20260206000003_create_railspress_content_element_versions.rb +20 -0
  124. data/db/migrate/20260207000001_add_unique_index_to_content_elements.rb +11 -0
  125. data/db/migrate/20260211112812_add_image_hint_to_railspress_content_elements.rb +7 -0
  126. data/db/migrate/20260211154040_add_required_to_railspress_content_elements.rb +5 -0
  127. data/lib/generators/railspress/entity/entity_generator.rb +89 -0
  128. data/lib/generators/railspress/entity/templates/migration.rb.tt +13 -0
  129. data/lib/generators/railspress/entity/templates/model.rb.tt +21 -0
  130. data/lib/generators/railspress/install/install_generator.rb +51 -40
  131. data/lib/generators/railspress/install/templates/initializer.rb +29 -0
  132. data/lib/railspress/engine.rb +38 -0
  133. data/lib/railspress/entity.rb +239 -0
  134. data/lib/railspress/version.rb +1 -1
  135. data/lib/railspress.rb +198 -8
  136. data/lib/tasks/railspress_tasks.rake +49 -4
  137. metadata +215 -21
  138. data/MIT-LICENSE +0 -20
  139. data/app/assets/stylesheets/railspress/admin.css +0 -1207
  140. data/app/models/railspress/post_tag.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23a09596799a0a89eefd920154a5aefdcae1a4247ebbfb8f161781a87adaa462
4
- data.tar.gz: 3b8b24840927df71dedc373457eeb082cf429932e6542eceeec06cf59195cfea
3
+ metadata.gz: f1ca2f73a89851d3359c2d32b51c5b5a3770e9e5d3c4f81655e5436fcf249fd4
4
+ data.tar.gz: 5c8c712b7139a6ab50be2b30831de8320b254a99a96e8f23fa69c0ad4d2ab8ae
5
5
  SHA512:
6
- metadata.gz: 4ad75a1e6057939610e32fef6b4add2a5d4eb4b5ff47621801f45a74414205c0d30ac5e308e21b612e26d774d19f6cd488e7655dd4626772a8c7e837997680d2
7
- data.tar.gz: 4f011c5faf57b329fb81ae904ff2da1ef0df5a373b7e3c41c31c16af5bc661eeddb67430daf8e8def0b9cc4f899802217287ebbfeb235e51cf1567d0067aa0d1
6
+ metadata.gz: fc61ccdaa5f8af6baa55c5186c07525a5272bb87bf4bec19f09c1ec6bdd710d8107d804a384f4d339a5243a8d44d0ca4dc82655a6328775587a831077567b470
7
+ data.tar.gz: d8d931f5f83c225d75b7d939a5251e4d8343c6b223fc7e56d8855024da7c58fae3c3236348e51aafc2e80f144cfc6e2dc40899db430e6da5df87f8b01530dd45
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ O'Saasy License Agreement - https://osaasy.dev/
2
+
3
+ Copyright © 2026, Avi Flombaum (https://avi.nyc).
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+ No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.
9
+
10
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11
+
12
+ --
13
+
14
+ Trademarks
15
+
16
+ The Rails trademarks are the intellectual property of David Heinemeier Hansson,
17
+ and exclusively licensed to the Rails Foundation. Uses of "Rails" and "Ruby on
18
+ Rails" in this project are for identification purposes only and do not imply an
19
+ endorsement by or affiliation with Rails, the trademark owner, or the Rails
20
+ Foundation.
data/README.md CHANGED
@@ -1,60 +1,226 @@
1
- # RailsPress
1
+ <p align="center">
2
+ <strong>RailsPress</strong><br>
3
+ A mountable blog + CMS engine for Rails 8
4
+ </p>
2
5
 
3
- A simple blog engine for Rails 8 applications.
6
+ <p align="center">
7
+ <a href="https://rubygems.org/gems/railspress-engine"><img src="https://img.shields.io/gem/v/railspress-engine.svg?style=flat&bump=true" alt="Gem Version"></a>
8
+ <img src="https://img.shields.io/badge/Rails-8.1%2B-red.svg?style=flat" alt="Rails 8.1+">
9
+ <img src="https://img.shields.io/badge/Ruby-3.3%2B-red.svg?style=flat" alt="Ruby 3.3+">
10
+ <a href="https://osaasy.dev/"><img src="https://img.shields.io/badge/License-O'Saasy-blue.svg?style=flat" alt="License"></a>
11
+ </p>
4
12
 
5
- ## Features
13
+ ---
14
+
15
+ RailsPress is a mountable Rails engine that gives your app a complete content management system — namespaced and isolated so it stays out of your way.
16
+
17
+ You can read more at [Railspress.org](https://railspress.org).
18
+
19
+ It manages three kinds of content:
20
+
21
+ ## Posts, Entities, and Blocks
6
22
 
7
- - Blog posts with rich text editing (ActionText)
8
- - Categories and tags
23
+ ### Posts
24
+
25
+ Your blog. Chronological content published over time — articles, news, announcements. Categories, tags, draft/published workflow.
26
+
27
+ - Rich text editing with [Lexxy](https://github.com/aviflombaum/lexxy) + markdown mode toggle
28
+ - Categories, tags, draft/published workflow
9
29
  - SEO metadata (meta title, meta description)
10
- - Draft/published workflow with automatic publish timestamps
11
- - Admin interface for content management
30
+ - Reading time auto-calculation
31
+ - Header images with focal point cropping
32
+ - Import/export (markdown + YAML frontmatter)
33
+
34
+ ### Entities
35
+
36
+ Your pages of structured content. A portfolio with projects, a collection of case studies, a resources page with links — anything with its own schema that isn't a blog post.
37
+
38
+ You define a regular ActiveRecord model, include `Railspress::Entity`, and RailsPress gives it a full admin interface with CRUD, search, pagination, image uploads, and tagging — no scaffolding or custom views required.
39
+
40
+ - Define fields with `railspress_fields` DSL or let RailsPress auto-detect from your schema
41
+ - Supports string, text, rich text, boolean, datetime, attachments, array fields, and more
42
+ - Focal point image cropping for any attachment
43
+ - Polymorphic tagging
44
+ - Custom index columns, searchable fields, scopes
45
+ - Generator: `rails generate railspress:entity Project title:string description:text`
46
+
47
+ ### Blocks
48
+
49
+ The copy and images on your site itself. Your homepage hero headline, an "About Us" blurb, a call-to-action, a footer tagline — the content that normally lives hardcoded in templates and requires a developer to change.
50
+
51
+ Blocks are organized into **groups** (e.g., "Homepage Hero", "Contact Info") and each block is either text or an image. You reference them in your views and they become editable in the admin — or inline, right on the page.
52
+
53
+ - Chainable Ruby API: `Railspress::CMS.find("Hero").load("headline").value`
54
+ - View helpers: `cms_value("Hero", "headline")` and `cms_element("Hero", "headline")`
55
+ - Inline editing — right-click any `cms_element` in the frontend to edit in place
56
+ - Auto-versioning with full audit trail
57
+ - Required blocks that can't be accidentally deleted
58
+ - Image blocks with upload, hints, and focal points
59
+ - Export/import block groups as ZIP
60
+
61
+ ### Why all three?
62
+
63
+ Most content doesn't fit neatly into "blog posts." A portfolio piece isn't a post. A homepage headline isn't a post. RailsPress gives you the right tool for each kind of content instead of forcing everything through one model.
64
+
65
+ ## Features
66
+
67
+ **Admin Interface**
68
+ - Dashboard with content stats and recent activity
69
+ - Full CRUD for posts, categories, tags, block groups, blocks, and entities
70
+ - Drag-and-drop image uploads with progress
71
+ - Collapsible sidebar, responsive design
72
+ - Vanilla CSS with BEM naming (`rp-` prefix) — no framework dependencies
73
+
74
+ **Developer Experience**
75
+ - Focal point image concern for any model: `focal_point_image :cover_photo`
76
+ - CSS variable theming
77
+ - Generators for installation and custom entities
12
78
 
13
79
  ## Requirements
14
80
 
15
- - Rails 8.0+
81
+ - Rails 8.1+
16
82
  - Ruby 3.3+
17
- - ActionText (for rich text)
18
- - Active Storage (for image uploads)
83
+ - ActionText
84
+ - Active Storage
19
85
 
20
86
  ## Installation
21
87
 
88
+ Ensure ActionText and Active Storage are installed:
89
+
90
+ ```bash
91
+ rails action_text:install
92
+ rails active_storage:install
93
+ ```
94
+
22
95
  Add to your Gemfile:
23
96
 
24
97
  ```ruby
25
- gem "railspress"
98
+ gem "railspress-engine"
26
99
  ```
27
100
 
28
- Install the gem and copy migrations:
101
+ Run the install generator:
29
102
 
30
103
  ```bash
31
104
  bundle install
32
- rails railspress:install:migrations
105
+ rails generate railspress:install
33
106
  rails db:migrate
34
107
  ```
35
108
 
36
- Mount the engine in your routes:
109
+ Mount the engine (the install generator does this automatically):
37
110
 
38
111
  ```ruby
39
112
  # config/routes.rb
40
113
  Rails.application.routes.draw do
41
- mount Railspress::Engine => "/blog", as: :railspress
114
+ mount Railspress::Engine => "/railspress"
42
115
  end
43
116
  ```
44
117
 
45
- ## Usage
118
+ ## Authentication
46
119
 
47
- Access the admin interface at `/blog/admin`.
120
+ The admin interface is publicly accessible by default. Add authentication before deploying:
48
121
 
49
- From there you can:
50
- - Create and manage blog posts with rich text content
51
- - Organize posts with categories
52
- - Tag posts (enter tags as comma-separated values)
53
- - Save posts as drafts or publish them
122
+ ```ruby
123
+ # app/controllers/application_controller.rb
124
+ class ApplicationController < ActionController::Base
125
+ before_action :authenticate_user!, if: :railspress_admin?
54
126
 
55
- ## Development
127
+ private
128
+
129
+ def railspress_admin?
130
+ request.path.start_with?("/railspress/admin")
131
+ end
132
+ end
133
+ ```
134
+
135
+ See [CONFIGURING.md](docs/CONFIGURING.md) for more authentication patterns including Devise integration.
136
+
137
+ ## Quick Start
138
+
139
+ Access the admin at `/railspress/admin`. From there you can manage posts, entities, and blocks.
140
+
141
+ ### Posts
142
+
143
+ Create posts with rich text, categories, tags, and header images. Build your own frontend controllers and views — see the [Blogging guide](docs/BLOGGING.md).
56
144
 
57
- After checking out the repo:
145
+ ### Entities
146
+
147
+ Generate a model and register it:
148
+
149
+ ```bash
150
+ rails generate railspress:entity Project title:string client:string description:text
151
+ ```
152
+
153
+ ```ruby
154
+ class Project < ApplicationRecord
155
+ include Railspress::Entity
156
+
157
+ railspress_config do |c|
158
+ c.admin_title = "Projects"
159
+ c.searchable_columns = [:title, :client]
160
+ end
161
+ end
162
+ ```
163
+
164
+ It now has a full admin interface at `/railspress/admin/entities/projects`. See the [Entity System guide](docs/ENTITIES.md).
165
+
166
+ ### Blocks
167
+
168
+ Set up inline editing for admins:
169
+
170
+ ```ruby
171
+ # config/initializers/railspress.rb
172
+ Railspress.configure do |config|
173
+ config.inline_editing_check = ->(ctx) { ctx.current_user&.admin? }
174
+ end
175
+ ```
176
+
177
+ Reference blocks in your views:
178
+
179
+ ```erb
180
+ <%# Raw value (no editing wrapper) %>
181
+ <h1><%= cms_value("Homepage", "headline") %></h1>
182
+
183
+ <%# With inline editing (admins can right-click to edit in place) %>
184
+ <h1><%= cms_element("Homepage", "headline") %></h1>
185
+
186
+ <p><%= cms_element("Homepage", "subheadline") %></p>
187
+ <%= image_tag cms_value("Homepage", "hero_image"), alt: "Hero" %>
188
+ ```
189
+
190
+ Create the "Homepage" group and its blocks in the admin at `/railspress/admin/content_groups`. See the [Inline Editing guide](docs/INLINE_EDITING.md).
191
+
192
+ ## Generators
193
+
194
+ ```bash
195
+ rails generate railspress:install # Full setup
196
+ rails generate railspress:entity Project title:string # Add a managed entity
197
+ ```
198
+
199
+ ## Documentation
200
+
201
+ **Posts, Entities, and Blocks:**
202
+
203
+ | Guide | Description |
204
+ |-------|-------------|
205
+ | [Building a Blog](docs/BLOGGING.md) | Frontend views, RSS, SEO for posts |
206
+ | [Entity System](docs/ENTITIES.md) | Structured content pages (portfolios, case studies, etc.) |
207
+ | [Blocks & Inline Editing](docs/INLINE_EDITING.md) | Editable site copy and images |
208
+
209
+ **Everything else:**
210
+
211
+ | Guide | Description |
212
+ |-------|-------------|
213
+ | [Reference](docs/README.md) | Models, routes, and API reference |
214
+ | [Configuring](docs/CONFIGURING.md) | Authors, images, inline editing, and all options |
215
+ | [Image Focal Points](docs/image-focal-point-system.md) | Smart cropping with focal points |
216
+ | [Import/Export](docs/IMPORT_EXPORT.md) | Bulk operations for posts and CMS content |
217
+ | [Theming](docs/THEMING.md) | CSS variable customization |
218
+ | [Admin Helpers](docs/ADMIN_HELPERS.md) | View helper reference |
219
+ | [Troubleshooting](docs/TROUBLESHOOTING.md) | Common issues |
220
+ | [Upgrading](docs/UPGRADING.md) | Version upgrades and migrations |
221
+ | [Changelog](CHANGELOG.md) | Version history |
222
+
223
+ ## Development
58
224
 
59
225
  ```bash
60
226
  bundle install
@@ -64,4 +230,8 @@ bundle exec rspec
64
230
 
65
231
  ## License
66
232
 
67
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
233
+ Available as open source under the terms of the [O'Saasy License](https://osaasy.dev/).
234
+
235
+ ## Trademarks
236
+
237
+ The Rails trademarks are the intellectual property of David Heinemeier Hansson, and exclusively licensed to the Rails Foundation. Uses of "Rails" and "Ruby on Rails" in this project are for identification purposes only and do not imply an endorsement by or affiliation with Rails, the trademark owner, or the Rails Foundation.
@@ -6,6 +6,44 @@
6
6
  (function() {
7
7
  'use strict';
8
8
 
9
+ // ============================================
10
+ // Sidebar Collapse Toggle
11
+ // ============================================
12
+
13
+ function initSidebarCollapse() {
14
+ var sidebar = document.querySelector('.rp-sidebar');
15
+ var layout = document.querySelector('.rp-admin-layout');
16
+ var toggle = document.querySelector('.rp-sidebar-toggle');
17
+
18
+ if (!sidebar || !toggle || !layout) return;
19
+
20
+ var STORAGE_KEY = 'rp-sidebar-collapsed';
21
+
22
+ function isCollapsed() {
23
+ return localStorage.getItem(STORAGE_KEY) === 'true';
24
+ }
25
+
26
+ function setCollapsed(collapsed) {
27
+ if (collapsed) {
28
+ sidebar.classList.add('rp-sidebar--collapsed');
29
+ layout.classList.add('rp-admin-layout--sidebar-collapsed');
30
+ } else {
31
+ sidebar.classList.remove('rp-sidebar--collapsed');
32
+ layout.classList.remove('rp-admin-layout--sidebar-collapsed');
33
+ }
34
+ localStorage.setItem(STORAGE_KEY, collapsed ? 'true' : 'false');
35
+ }
36
+
37
+ // Restore state on load
38
+ if (isCollapsed()) {
39
+ setCollapsed(true);
40
+ }
41
+
42
+ toggle.addEventListener('click', function() {
43
+ setCollapsed(!sidebar.classList.contains('rp-sidebar--collapsed'));
44
+ });
45
+ }
46
+
9
47
  // ============================================
10
48
  // Mobile Menu Toggle
11
49
  // ============================================
@@ -194,6 +232,7 @@
194
232
  // ============================================
195
233
 
196
234
  function init() {
235
+ initSidebarCollapse();
197
236
  initMobileMenu();
198
237
  initSlugGenerator();
199
238
  initDeleteConfirmation();