not_pressed-core 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 (157) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +41 -0
  3. data/README.md +285 -0
  4. data/app/assets/javascripts/not_pressed/lightbox.js +110 -0
  5. data/app/assets/stylesheets/not_pressed/admin.css +1161 -0
  6. data/app/assets/stylesheets/not_pressed/content.css +193 -0
  7. data/app/assets/stylesheets/not_pressed/gallery.css +117 -0
  8. data/app/assets/stylesheets/not_pressed/themes/starter.css +118 -0
  9. data/app/controllers/not_pressed/admin/base_controller.rb +21 -0
  10. data/app/controllers/not_pressed/admin/categories_controller.rb +53 -0
  11. data/app/controllers/not_pressed/admin/content_blocks_controller.rb +73 -0
  12. data/app/controllers/not_pressed/admin/dashboard_controller.rb +19 -0
  13. data/app/controllers/not_pressed/admin/forms_controller.rb +86 -0
  14. data/app/controllers/not_pressed/admin/media_attachments_controller.rb +94 -0
  15. data/app/controllers/not_pressed/admin/pages_controller.rb +122 -0
  16. data/app/controllers/not_pressed/admin/plugins_controller.rb +121 -0
  17. data/app/controllers/not_pressed/admin/settings_controller.rb +19 -0
  18. data/app/controllers/not_pressed/admin/tags_controller.rb +37 -0
  19. data/app/controllers/not_pressed/admin/themes_controller.rb +104 -0
  20. data/app/controllers/not_pressed/application_controller.rb +6 -0
  21. data/app/controllers/not_pressed/blog_controller.rb +83 -0
  22. data/app/controllers/not_pressed/form_submissions_controller.rb +70 -0
  23. data/app/controllers/not_pressed/pages_controller.rb +36 -0
  24. data/app/controllers/not_pressed/robots_controller.rb +34 -0
  25. data/app/controllers/not_pressed/sitemaps_controller.rb +12 -0
  26. data/app/helpers/not_pressed/admin_helper.rb +41 -0
  27. data/app/helpers/not_pressed/application_helper.rb +6 -0
  28. data/app/helpers/not_pressed/code_injection_helper.rb +29 -0
  29. data/app/helpers/not_pressed/content_helper.rb +13 -0
  30. data/app/helpers/not_pressed/form_helper.rb +80 -0
  31. data/app/helpers/not_pressed/media_helper.rb +28 -0
  32. data/app/helpers/not_pressed/seo_helper.rb +69 -0
  33. data/app/helpers/not_pressed/theme_helper.rb +42 -0
  34. data/app/mailers/not_pressed/application_mailer.rb +10 -0
  35. data/app/mailers/not_pressed/form_mailer.rb +15 -0
  36. data/app/models/concerns/not_pressed/sluggable.rb +43 -0
  37. data/app/models/not_pressed/category.rb +16 -0
  38. data/app/models/not_pressed/content_block.rb +46 -0
  39. data/app/models/not_pressed/form.rb +55 -0
  40. data/app/models/not_pressed/form_field.rb +23 -0
  41. data/app/models/not_pressed/form_submission.rb +19 -0
  42. data/app/models/not_pressed/media_attachment.rb +68 -0
  43. data/app/models/not_pressed/page.rb +182 -0
  44. data/app/models/not_pressed/page_version.rb +15 -0
  45. data/app/models/not_pressed/setting.rb +20 -0
  46. data/app/models/not_pressed/tag.rb +15 -0
  47. data/app/models/not_pressed/tagging.rb +12 -0
  48. data/app/plugins/not_pressed/analytics_plugin.rb +106 -0
  49. data/app/plugins/not_pressed/callout_block_plugin.rb +43 -0
  50. data/app/themes/not_pressed/starter_theme.rb +26 -0
  51. data/app/views/layouts/not_pressed/admin.html.erb +745 -0
  52. data/app/views/layouts/not_pressed/application.html.erb +12 -0
  53. data/app/views/layouts/not_pressed/page.html.erb +22 -0
  54. data/app/views/not_pressed/admin/categories/index.html.erb +58 -0
  55. data/app/views/not_pressed/admin/content_blocks/_block.html.erb +18 -0
  56. data/app/views/not_pressed/admin/content_blocks/_block_picker.html.erb +32 -0
  57. data/app/views/not_pressed/admin/content_blocks/create.turbo_stream.erb +3 -0
  58. data/app/views/not_pressed/admin/content_blocks/destroy.turbo_stream.erb +1 -0
  59. data/app/views/not_pressed/admin/content_blocks/editors/_callout.html.erb +38 -0
  60. data/app/views/not_pressed/admin/content_blocks/editors/_code.html.erb +26 -0
  61. data/app/views/not_pressed/admin/content_blocks/editors/_divider.html.erb +4 -0
  62. data/app/views/not_pressed/admin/content_blocks/editors/_form.html.erb +16 -0
  63. data/app/views/not_pressed/admin/content_blocks/editors/_gallery.html.erb +75 -0
  64. data/app/views/not_pressed/admin/content_blocks/editors/_heading.html.erb +26 -0
  65. data/app/views/not_pressed/admin/content_blocks/editors/_html.html.erb +13 -0
  66. data/app/views/not_pressed/admin/content_blocks/editors/_image.html.erb +56 -0
  67. data/app/views/not_pressed/admin/content_blocks/editors/_quote.html.erb +24 -0
  68. data/app/views/not_pressed/admin/content_blocks/editors/_text.html.erb +28 -0
  69. data/app/views/not_pressed/admin/content_blocks/editors/_video.html.erb +25 -0
  70. data/app/views/not_pressed/admin/dashboard/index.html.erb +60 -0
  71. data/app/views/not_pressed/admin/forms/_field_row.html.erb +33 -0
  72. data/app/views/not_pressed/admin/forms/_form.html.erb +75 -0
  73. data/app/views/not_pressed/admin/forms/edit.html.erb +1 -0
  74. data/app/views/not_pressed/admin/forms/index.html.erb +32 -0
  75. data/app/views/not_pressed/admin/forms/new.html.erb +1 -0
  76. data/app/views/not_pressed/admin/forms/submissions.html.erb +34 -0
  77. data/app/views/not_pressed/admin/media_attachments/_media_card.html.erb +21 -0
  78. data/app/views/not_pressed/admin/media_attachments/_picker.html.erb +19 -0
  79. data/app/views/not_pressed/admin/media_attachments/edit.html.erb +57 -0
  80. data/app/views/not_pressed/admin/media_attachments/index.html.erb +48 -0
  81. data/app/views/not_pressed/admin/pages/_form.html.erb +177 -0
  82. data/app/views/not_pressed/admin/pages/_page_tree_node.html.erb +32 -0
  83. data/app/views/not_pressed/admin/pages/edit.html.erb +69 -0
  84. data/app/views/not_pressed/admin/pages/index.html.erb +21 -0
  85. data/app/views/not_pressed/admin/pages/new.html.erb +1 -0
  86. data/app/views/not_pressed/admin/pages/preview.html.erb +17 -0
  87. data/app/views/not_pressed/admin/plugins/_settings_form.html.erb +59 -0
  88. data/app/views/not_pressed/admin/plugins/index.html.erb +48 -0
  89. data/app/views/not_pressed/admin/plugins/show.html.erb +54 -0
  90. data/app/views/not_pressed/admin/settings/code_injection.html.erb +21 -0
  91. data/app/views/not_pressed/admin/shared/_breadcrumbs.html.erb +14 -0
  92. data/app/views/not_pressed/admin/shared/_flash.html.erb +7 -0
  93. data/app/views/not_pressed/admin/shared/_modal.html.erb +9 -0
  94. data/app/views/not_pressed/admin/shared/_sidebar.html.erb +18 -0
  95. data/app/views/not_pressed/admin/tags/index.html.erb +52 -0
  96. data/app/views/not_pressed/admin/themes/index.html.erb +60 -0
  97. data/app/views/not_pressed/admin/themes/show.html.erb +66 -0
  98. data/app/views/not_pressed/blog/_post_card.html.erb +24 -0
  99. data/app/views/not_pressed/blog/feed.rss.builder +22 -0
  100. data/app/views/not_pressed/blog/index.html.erb +56 -0
  101. data/app/views/not_pressed/blog/show.html.erb +41 -0
  102. data/app/views/not_pressed/form_mailer/submission_notification.text.erb +8 -0
  103. data/app/views/not_pressed/pages/show.html.erb +4 -0
  104. data/app/views/themes/starter/layouts/not_pressed/default.html.erb +36 -0
  105. data/app/views/themes/starter/layouts/not_pressed/full_width.html.erb +36 -0
  106. data/app/views/themes/starter/layouts/not_pressed/sidebar.html.erb +41 -0
  107. data/config/routes.rb +81 -0
  108. data/db/migrate/20260310000001_create_not_pressed_pages.rb +20 -0
  109. data/db/migrate/20260310000002_create_not_pressed_content_blocks.rb +17 -0
  110. data/db/migrate/20260310000003_create_not_pressed_media_attachments.rb +14 -0
  111. data/db/migrate/20260310000004_add_content_type_to_not_pressed_pages.rb +8 -0
  112. data/db/migrate/20260310000005_add_publishing_fields_to_not_pressed_pages.rb +8 -0
  113. data/db/migrate/20260310000006_create_not_pressed_page_versions.rb +16 -0
  114. data/db/migrate/20260310000007_add_settings_fields_to_not_pressed_pages.rb +11 -0
  115. data/db/migrate/20260311000001_create_not_pressed_forms.rb +42 -0
  116. data/db/migrate/20260311000002_add_seo_fields_to_not_pressed_pages.rb +10 -0
  117. data/db/migrate/20260311000003_add_code_injection_to_not_pressed_pages.rb +8 -0
  118. data/db/migrate/20260311000004_create_not_pressed_settings.rb +14 -0
  119. data/db/migrate/20260312000001_create_not_pressed_categories.rb +16 -0
  120. data/db/migrate/20260312000002_create_not_pressed_tags.rb +15 -0
  121. data/db/migrate/20260312000003_create_not_pressed_taggings.rb +14 -0
  122. data/db/migrate/20260312000004_add_category_id_to_not_pressed_pages.rb +7 -0
  123. data/lib/generators/not_pressed/install/install_generator.rb +52 -0
  124. data/lib/generators/not_pressed/install/templates/initializer.rb.tt +89 -0
  125. data/lib/generators/not_pressed/install/templates/seeds.rb.tt +131 -0
  126. data/lib/generators/not_pressed/plugin/plugin_generator.rb +37 -0
  127. data/lib/generators/not_pressed/plugin/templates/plugin.rb.tt +23 -0
  128. data/lib/not_pressed/admin/authentication.rb +48 -0
  129. data/lib/not_pressed/admin/menu_registry.rb +100 -0
  130. data/lib/not_pressed/configuration.rb +77 -0
  131. data/lib/not_pressed/content_type.rb +23 -0
  132. data/lib/not_pressed/content_type_builder.rb +51 -0
  133. data/lib/not_pressed/content_type_registry.rb +45 -0
  134. data/lib/not_pressed/engine.rb +132 -0
  135. data/lib/not_pressed/hooks.rb +166 -0
  136. data/lib/not_pressed/navigation/builder.rb +148 -0
  137. data/lib/not_pressed/navigation/menu.rb +54 -0
  138. data/lib/not_pressed/navigation/menu_item.rb +33 -0
  139. data/lib/not_pressed/navigation/node.rb +45 -0
  140. data/lib/not_pressed/navigation/partial_parser.rb +96 -0
  141. data/lib/not_pressed/navigation/route_inspector.rb +98 -0
  142. data/lib/not_pressed/navigation.rb +6 -0
  143. data/lib/not_pressed/plugin.rb +354 -0
  144. data/lib/not_pressed/plugin_importer.rb +133 -0
  145. data/lib/not_pressed/plugin_manager.rb +196 -0
  146. data/lib/not_pressed/plugin_packager.rb +129 -0
  147. data/lib/not_pressed/rendering/block_renderer.rb +222 -0
  148. data/lib/not_pressed/rendering/renderer_registry.rb +154 -0
  149. data/lib/not_pressed/rendering.rb +8 -0
  150. data/lib/not_pressed/seo/sitemap_builder.rb +61 -0
  151. data/lib/not_pressed/theme.rb +191 -0
  152. data/lib/not_pressed/theme_importer.rb +133 -0
  153. data/lib/not_pressed/theme_packager.rb +180 -0
  154. data/lib/not_pressed/theme_registry.rb +123 -0
  155. data/lib/not_pressed/version.rb +5 -0
  156. data/lib/not_pressed.rb +65 -0
  157. metadata +258 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d2c847504c7b5020949675becc86c826e4e775dd38cccc5812f1ab80197b07c7
4
+ data.tar.gz: 82315be6116a3a33d7c21b7b4c8ed155bd6cf0b71dcd044f64c7824614db6514
5
+ SHA512:
6
+ metadata.gz: 728c1a3cfe4ec9cb7025194b0dd944b03ea80db01287c7f30fc13e7dadde234a825c32d765e477ba51a7caaab9289929bc8170b1e397ba79c5bc03d4c31c6204
7
+ data.tar.gz: c1873a495fec9800e6c3ce260874e719c206a8a451710f59241e13e2273602e0e76cfeaf1ce35d3c55b425f91f9bee91a7755dcdf266a0eb2bf56a169c414214
data/LICENSE.txt ADDED
@@ -0,0 +1,41 @@
1
+ The NotPressed License (NPL-1.0)
2
+
3
+ Copyright (c) 2026 NotPressed Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ 1. The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ 2. PLUGIN DISTRIBUTION CLAUSE: Any plugin, extension, theme, or add-on that
16
+ integrates with the Software's plugin interface and is distributed publicly
17
+ (i.e., made available to third parties outside the developer's own
18
+ organization) must be made available free of charge. Public plugins may be
19
+ open source or closed source, but may not require payment, subscription,
20
+ licensing fees, or any other form of monetary compensation for their use.
21
+
22
+ This clause does NOT restrict:
23
+ (a) Private plugins developed for internal use within an organization;
24
+ (b) Custom development services (building, modifying, or configuring the
25
+ Software or plugins for clients);
26
+ (c) Selling, bundling, or redistributing the Software itself as part of a
27
+ product, service, or hosted offering;
28
+ (d) Accepting voluntary donations or sponsorships for plugin development.
29
+
30
+ 3. TRADEMARK CLAUSE: The name "NotPressed", the NotPressed logo, and
31
+ associated branding may not be used in derivative works, forks, or
32
+ products in a manner that suggests official endorsement or affiliation
33
+ without prior written permission from the copyright holders.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
41
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # not\_pressed-core
2
+
3
+ The core engine behind [NotPressed](https://github.com/notpressed/not_pressed) — a drop-in, fully open-source CMS for Rails 8. Auto-discovers your app's navigation, provides a block-based content editor, and supports plugins and themes — all without the WordPress tax.
4
+
5
+ **Not WordPress. Not pressed for cash. Not pressed for time.**
6
+
7
+ ## Why not\_pressed-core?
8
+
9
+ Most CMS options force you to either adopt a monolithic platform (WordPress, Strapi) or bolt on a headless CMS that doesn't understand your Rails app. not\_pressed-core takes a different approach: it's a Rails engine that lives *inside* your existing application, works with your routes and views, and stays out of your way.
10
+
11
+ - **3 commands to a working CMS** — install the gem, run the generator, migrate
12
+ - **Works with your app** — auto-discovers routes, partials, and navigation from your existing codebase
13
+ - **Zero external JS** — built entirely on Hotwire, Turbo, and Stimulus
14
+ - **Batteries included** — pages, blog, media library, block editor, forms, SEO, themes, plugin system
15
+ - **Extensible** — plugin architecture, custom content types, theme system, import/export
16
+
17
+ ## Requirements
18
+
19
+ - Ruby >= 3.2
20
+ - Rails >= 8.0
21
+ - Active Storage (for media library)
22
+
23
+ ## Installation
24
+
25
+ Add not\_pressed-core to your Gemfile:
26
+
27
+ ```ruby
28
+ gem "not_pressed-core"
29
+ ```
30
+
31
+ Run the install generator:
32
+
33
+ ```bash
34
+ bundle install
35
+ rails generate not_pressed:install
36
+ rails db:migrate
37
+ ```
38
+
39
+ Optionally load sample content:
40
+
41
+ ```bash
42
+ rails runner "load 'db/seeds/not_pressed.rb'"
43
+ ```
44
+
45
+ Visit `/not_pressed` to access the admin panel.
46
+
47
+ ## Features
48
+
49
+ ### Block-Based Page Editor
50
+
51
+ Build pages from ordered, typed content blocks with a drag-and-drop editor:
52
+
53
+ | Block Type | Description |
54
+ |------------|-------------|
55
+ | `text` | Rich text paragraph |
56
+ | `heading` | Section heading (h1-h6) |
57
+ | `image` | Image with caption and alt text |
58
+ | `video` | Embedded video |
59
+ | `code` | Syntax-highlighted code block |
60
+ | `quote` | Blockquote with attribution |
61
+ | `divider` | Visual separator |
62
+ | `html` | Raw HTML injection |
63
+ | `form` | Embedded form (from form builder) |
64
+ | `gallery` | Image grid with lightbox viewer |
65
+ | `callout` | Highlighted callout box (plugin) |
66
+
67
+ Custom block types can be added via the plugin system.
68
+
69
+ ### Page Management
70
+
71
+ - Hierarchical page tree with drag-to-reorder
72
+ - Draft / published / scheduled / archived workflow
73
+ - Page duplication, version history
74
+ - SEO fields, layout selection, visibility controls
75
+ - Live preview with split/toggle pane
76
+
77
+ ### Blog
78
+
79
+ Built-in blog with categories, tags, and RSS:
80
+
81
+ - Blog posts are pages with the `blog_post` content type
82
+ - Category and tag management in admin
83
+ - Public blog listing with pagination
84
+ - Category and tag filtering
85
+ - Year/month archives
86
+ - RSS 2.0 feed at `/blog/feed.rss`
87
+
88
+ ### Media Library
89
+
90
+ Centralized media management backed by Active Storage:
91
+
92
+ - Upload, browse, search, and filter
93
+ - Automatic metadata extraction (dimensions, file size, content type)
94
+ - Alt text and title fields for accessibility and SEO
95
+ - Image variants and thumbnail generation
96
+ - Media picker modal for the block editor
97
+
98
+ ### Forms
99
+
100
+ Drag-and-drop form builder with submissions:
101
+
102
+ - Field types: text, email, textarea, select, checkbox, radio, number, date, phone, url
103
+ - Submission management with CSV export
104
+ - Email notifications on submission
105
+ - Embeddable via the `form` block type
106
+
107
+ ### SEO & Discoverability
108
+
109
+ - Per-page meta title, description, Open Graph, Twitter Cards
110
+ - Canonical URLs and robots meta directives
111
+ - Auto-generated XML sitemap at `/sitemap.xml`
112
+ - Configurable `robots.txt`
113
+ - Per-page and global JS/CSS code injection
114
+
115
+ ### Navigation
116
+
117
+ not\_pressed-core builds navigation automatically using three layers:
118
+
119
+ 1. **Config DSL** — explicitly defined menus
120
+ 2. **Route introspection** — scans your Rails routes for navigable pages
121
+ 3. **Partial parsing** — detects existing nav/menu/header partials in your views
122
+
123
+ ## Configuration
124
+
125
+ The install generator creates `config/initializers/not_pressed.rb`:
126
+
127
+ ```ruby
128
+ NotPressed.configure do |config|
129
+ # Site name displayed in the admin header
130
+ config.site_name = "My Site"
131
+
132
+ # Admin panel mount path (default: "not_pressed")
133
+ config.admin_path = "admin"
134
+
135
+ # Records per page in admin listings
136
+ config.per_page = 25
137
+
138
+ # Authentication — Devise method, custom Proc, or nil (no auth)
139
+ config.admin_authentication = :authenticate_admin_user!
140
+ config.admin_current_user = :current_admin_user
141
+ config.admin_unauthorized_redirect = "/login"
142
+
143
+ # Available layouts for pages
144
+ config.available_layouts = %w[default sidebar full_width]
145
+
146
+ # Default theme
147
+ config.default_theme = "Starter"
148
+
149
+ # Auto-discover navigation from routes and view partials
150
+ config.auto_discover_navigation = true
151
+ config.cache_navigation = true
152
+
153
+ # SEO title separator
154
+ config.seo_title_separator = " | "
155
+
156
+ # Global code injection (head/body)
157
+ config.global_head_code = ""
158
+ config.global_body_code = ""
159
+
160
+ # Navigation DSL
161
+ config.menus do |menu|
162
+ menu.add "Home", "/"
163
+ menu.add "Blog", "/blog"
164
+ menu.add "About", "/about"
165
+ end
166
+ end
167
+ ```
168
+
169
+ ### Authentication
170
+
171
+ not\_pressed-core doesn't ship its own auth — it plugs into yours:
172
+
173
+ ```ruby
174
+ # Devise
175
+ config.admin_authentication = :authenticate_admin_user!
176
+
177
+ # Custom logic
178
+ config.admin_authentication = ->(controller) {
179
+ controller.redirect_to "/login" unless controller.session[:admin]
180
+ }
181
+
182
+ # No auth (development only!)
183
+ config.admin_authentication = nil
184
+ ```
185
+
186
+ ## Extending NotPressed
187
+
188
+ ### Custom Content Types
189
+
190
+ Define custom content types with the builder DSL:
191
+
192
+ ```ruby
193
+ NotPressed.define_content_type(:event) do
194
+ label "Event"
195
+ icon "calendar"
196
+ description "An event with date and location"
197
+ allowed_blocks :text, :heading, :image
198
+ has_slug true
199
+ has_parent false
200
+
201
+ field :event_date, :datetime, label: "Event Date", required: true
202
+ field :venue, :string, label: "Venue"
203
+ field :ticket_url, :url, label: "Ticket URL"
204
+ end
205
+ ```
206
+
207
+ ### Plugins
208
+
209
+ WordPress-style plugin system with hooks, filters, custom blocks, and admin extensions:
210
+
211
+ ```ruby
212
+ class MyPlugin < NotPressed::Plugin
213
+ name "my_plugin"
214
+ version "1.0.0"
215
+ description "Does something useful"
216
+
217
+ on(:after_page_save) { |page| Rails.logger.info "Saved: #{page.title}" }
218
+
219
+ block_type :callout
220
+
221
+ settings do
222
+ field :api_key, :string, label: "API Key"
223
+ end
224
+
225
+ admin_menu label: "My Plugin", icon: "puzzle-piece"
226
+
227
+ routes do
228
+ get "my-plugin", to: "my_plugin#index"
229
+ end
230
+ end
231
+ ```
232
+
233
+ Plugins can be exported as `.zip` archives and imported on other installations via the admin panel.
234
+
235
+ See [Plugin Development Guide](docs/guides/plugin-development.md) for full documentation.
236
+
237
+ ### Themes
238
+
239
+ Swappable themes with layouts, color schemes, and template overrides:
240
+
241
+ ```ruby
242
+ class MyTheme < NotPressed::Theme
243
+ name "my_theme"
244
+ version "1.0.0"
245
+
246
+ layout :default, label: "Default", primary: true
247
+ layout :sidebar, label: "With Sidebar"
248
+
249
+ stylesheet "not_pressed/themes/my_theme"
250
+
251
+ color_scheme do
252
+ color :primary, "#3366cc", label: "Primary"
253
+ color :background, "#ffffff", label: "Background"
254
+ color :text, "#333333", label: "Text"
255
+ end
256
+ end
257
+ ```
258
+
259
+ Themes support color customization from the admin panel, template overrides via view path prepending, and can be exported/imported as `.zip` archives.
260
+
261
+ See [Theme Development Guide](docs/guides/theme-development.md) for full documentation.
262
+
263
+ ## Development
264
+
265
+ After checking out the repo:
266
+
267
+ ```bash
268
+ bundle install
269
+ cd spec/dummy && rails db:create db:migrate && cd ../..
270
+ bundle exec rspec
271
+ ```
272
+
273
+ The test suite uses a Rails 8 dummy app in `spec/dummy/`.
274
+
275
+ ### Documentation
276
+
277
+ - [Plugin Development Guide](docs/guides/plugin-development.md)
278
+ - [Theme Development Guide](docs/guides/theme-development.md)
279
+ - [Hooks & Filters Reference](docs/guides/hooks-reference.md)
280
+ - [CSS Variables Reference](docs/guides/css-variables-reference.md)
281
+ - [API Reference](docs/api-reference.md)
282
+
283
+ ## License
284
+
285
+ not\_pressed-core is released under the [NPL-1.0 License](LICENSE.txt).
@@ -0,0 +1,110 @@
1
+ /**
2
+ * NotPressed Gallery Lightbox
3
+ *
4
+ * Auto-initializes on elements with data-controller="lightbox"
5
+ * or class "np-content-gallery". Provides click-to-open, keyboard
6
+ * navigation (left/right arrows, Escape), and overlay click to close.
7
+ */
8
+ (function () {
9
+ "use strict";
10
+
11
+ function initLightbox(gallery) {
12
+ var images = gallery.querySelectorAll(".np-gallery-item img");
13
+ if (!images.length) return;
14
+
15
+ var currentIndex = 0;
16
+ var overlay = null;
17
+ var lightboxImg = null;
18
+
19
+ function createOverlay() {
20
+ overlay = document.createElement("div");
21
+ overlay.className = "np-lightbox-overlay";
22
+ overlay.setAttribute("hidden", "");
23
+
24
+ lightboxImg = document.createElement("img");
25
+ lightboxImg.className = "np-lightbox-image";
26
+
27
+ var closeBtn = document.createElement("button");
28
+ closeBtn.className = "np-lightbox-close";
29
+ closeBtn.textContent = "\u00d7";
30
+ closeBtn.setAttribute("aria-label", "Close lightbox");
31
+
32
+ var prevBtn = document.createElement("button");
33
+ prevBtn.className = "np-lightbox-nav np-lightbox-nav--prev";
34
+ prevBtn.textContent = "\u2039";
35
+ prevBtn.setAttribute("aria-label", "Previous image");
36
+
37
+ var nextBtn = document.createElement("button");
38
+ nextBtn.className = "np-lightbox-nav np-lightbox-nav--next";
39
+ nextBtn.textContent = "\u203a";
40
+ nextBtn.setAttribute("aria-label", "Next image");
41
+
42
+ overlay.appendChild(closeBtn);
43
+ overlay.appendChild(prevBtn);
44
+ overlay.appendChild(lightboxImg);
45
+ overlay.appendChild(nextBtn);
46
+ document.body.appendChild(overlay);
47
+
48
+ closeBtn.addEventListener("click", close);
49
+ prevBtn.addEventListener("click", prev);
50
+ nextBtn.addEventListener("click", next);
51
+ overlay.addEventListener("click", function (e) {
52
+ if (e.target === overlay) close();
53
+ });
54
+ }
55
+
56
+ function show(index) {
57
+ if (!overlay) createOverlay();
58
+ currentIndex = index;
59
+ lightboxImg.src = images[currentIndex].src;
60
+ lightboxImg.alt = images[currentIndex].alt || "";
61
+ overlay.removeAttribute("hidden");
62
+ document.addEventListener("keydown", onKeydown);
63
+ }
64
+
65
+ function close() {
66
+ if (overlay) {
67
+ overlay.setAttribute("hidden", "");
68
+ document.removeEventListener("keydown", onKeydown);
69
+ }
70
+ }
71
+
72
+ function prev() {
73
+ currentIndex = (currentIndex - 1 + images.length) % images.length;
74
+ lightboxImg.src = images[currentIndex].src;
75
+ lightboxImg.alt = images[currentIndex].alt || "";
76
+ }
77
+
78
+ function next() {
79
+ currentIndex = (currentIndex + 1) % images.length;
80
+ lightboxImg.src = images[currentIndex].src;
81
+ lightboxImg.alt = images[currentIndex].alt || "";
82
+ }
83
+
84
+ function onKeydown(e) {
85
+ if (e.key === "Escape") close();
86
+ else if (e.key === "ArrowLeft") prev();
87
+ else if (e.key === "ArrowRight") next();
88
+ }
89
+
90
+ images.forEach(function (img, idx) {
91
+ img.style.cursor = "pointer";
92
+ img.addEventListener("click", function () {
93
+ show(idx);
94
+ });
95
+ });
96
+ }
97
+
98
+ function init() {
99
+ var galleries = document.querySelectorAll(
100
+ '.np-content-gallery, [data-controller="lightbox"]'
101
+ );
102
+ galleries.forEach(initLightbox);
103
+ }
104
+
105
+ if (document.readyState === "loading") {
106
+ document.addEventListener("DOMContentLoaded", init);
107
+ } else {
108
+ init();
109
+ }
110
+ })();