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
@@ -0,0 +1,59 @@
1
+ <%= form_tag(update_settings_admin_plugin_path(plugin_name: plugin.plugin_name), method: :patch) do %>
2
+ <% instance = plugin_info[:instance] || plugin.new %>
3
+ <% values = instance.settings_values %>
4
+
5
+ <% plugin.plugin_settings_schema.each do |field| %>
6
+ <div style="margin-bottom: 1rem;">
7
+ <label for="plugin_settings_<%= field[:key] %>">
8
+ <%= field[:label] || field[:key].to_s.humanize %>
9
+ <% if field[:required] %><span style="color: red;">*</span><% end %>
10
+ </label>
11
+
12
+ <% current_value = values[field[:key].to_s] %>
13
+ <% default_value = field[:default] %>
14
+ <% display_value = current_value.nil? ? default_value : current_value %>
15
+
16
+ <% case field[:type] %>
17
+ <% when :string %>
18
+ <%= text_field_tag "plugin_settings[#{field[:key]}]", display_value,
19
+ id: "plugin_settings_#{field[:key]}",
20
+ class: "np-input",
21
+ style: "width: 100%; padding: 0.5rem;" %>
22
+
23
+ <% when :text %>
24
+ <%= text_area_tag "plugin_settings[#{field[:key]}]", display_value,
25
+ id: "plugin_settings_#{field[:key]}",
26
+ class: "np-input",
27
+ rows: 4,
28
+ style: "width: 100%; padding: 0.5rem;" %>
29
+
30
+ <% when :boolean %>
31
+ <%= hidden_field_tag "plugin_settings[#{field[:key]}]", "0" %>
32
+ <%= check_box_tag "plugin_settings[#{field[:key]}]", "1",
33
+ display_value.to_s == "true" || display_value == true,
34
+ id: "plugin_settings_#{field[:key]}" %>
35
+
36
+ <% when :select %>
37
+ <%= select_tag "plugin_settings[#{field[:key]}]",
38
+ options_for_select(field[:options] || [], display_value),
39
+ id: "plugin_settings_#{field[:key]}",
40
+ class: "np-input",
41
+ style: "width: 100%; padding: 0.5rem;" %>
42
+
43
+ <% when :integer %>
44
+ <%= number_field_tag "plugin_settings[#{field[:key]}]", display_value,
45
+ id: "plugin_settings_#{field[:key]}",
46
+ class: "np-input",
47
+ style: "width: 100%; padding: 0.5rem;" %>
48
+ <% end %>
49
+
50
+ <% if default_value.present? && current_value.nil? %>
51
+ <small style="color: #666;">Default: <%= default_value %></small>
52
+ <% end %>
53
+ </div>
54
+ <% end %>
55
+
56
+ <div style="margin-top: 1rem;">
57
+ <%= submit_tag "Save Settings", class: "np-btn np-btn--primary" %>
58
+ </div>
59
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <div class="np-card np-card--form" style="margin-bottom: 2rem;">
2
+ <h3>Import Plugin</h3>
3
+ <%= form_tag(admin_import_plugin_path, method: :post, multipart: true, class: "np-form") do %>
4
+ <div class="np-form-group">
5
+ <%= file_field_tag :plugin_archive, accept: ".zip", class: "np-input" %>
6
+ </div>
7
+ <%= submit_tag "Import Plugin", class: "np-btn np-btn--primary" %>
8
+ <% end %>
9
+ </div>
10
+
11
+ <% if @plugins.any? %>
12
+ <table class="np-table">
13
+ <thead>
14
+ <tr>
15
+ <th>Plugin</th>
16
+ <th>Version</th>
17
+ <th>Author</th>
18
+ <th>Status</th>
19
+ <th>Actions</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @plugins.each do |name, info| %>
24
+ <tr>
25
+ <td>
26
+ <%= link_to info[:klass].plugin_name, admin_plugin_path(plugin_name: name) %>
27
+ <% if info[:klass].plugin_description.present? %>
28
+ <br><small><%= info[:klass].plugin_description %></small>
29
+ <% end %>
30
+ </td>
31
+ <td><%= info[:klass].plugin_version || "—" %></td>
32
+ <td><%= info[:klass].plugin_author || "—" %></td>
33
+ <td><%= status_badge(info[:status]) %></td>
34
+ <td>
35
+ <%= link_to "Settings", admin_plugin_path(plugin_name: name), class: "np-btn np-btn--small" %>
36
+ <%= link_to "Export", admin_export_plugin_path(id: name), class: "np-btn np-btn--small np-btn--secondary", download: "" %>
37
+ <%= button_to(info[:status] == :active ? "Disable" : "Enable",
38
+ toggle_admin_plugin_path(plugin_name: name),
39
+ method: :post,
40
+ class: "np-btn np-btn--small#{info[:status] == :active ? ' np-btn--danger' : ''}") %>
41
+ </td>
42
+ </tr>
43
+ <% end %>
44
+ </tbody>
45
+ </table>
46
+ <% else %>
47
+ <p>No plugins installed. Plugins will appear here once registered.</p>
48
+ <% end %>
@@ -0,0 +1,54 @@
1
+ <div class="np-actions" style="margin-bottom: 1.5rem;">
2
+ <%= link_to "Back to Plugins", admin_plugins_path, class: "np-btn" %>
3
+ <%= button_to(@plugin_info[:status] == :active ? "Disable Plugin" : "Enable Plugin",
4
+ toggle_admin_plugin_path(plugin_name: @plugin_class.plugin_name),
5
+ method: :post,
6
+ class: "np-btn#{@plugin_info[:status] == :active ? ' np-btn--danger' : ' np-btn--primary'}") %>
7
+ </div>
8
+
9
+ <div class="np-card" style="margin-bottom: 1.5rem; padding: 1.5rem;">
10
+ <h2><%= @plugin_class.plugin_name %></h2>
11
+ <p><%= status_badge(@plugin_info[:status]) %></p>
12
+
13
+ <% if @plugin_class.plugin_description.present? %>
14
+ <p><%= @plugin_class.plugin_description %></p>
15
+ <% end %>
16
+
17
+ <table class="np-table">
18
+ <tbody>
19
+ <% if @plugin_class.plugin_version.present? %>
20
+ <tr><td><strong>Version</strong></td><td><%= @plugin_class.plugin_version %></td></tr>
21
+ <% end %>
22
+ <% if @plugin_class.plugin_author.present? %>
23
+ <tr><td><strong>Author</strong></td><td><%= @plugin_class.plugin_author %></td></tr>
24
+ <% end %>
25
+ <% if @plugin_class.plugin_url.present? %>
26
+ <tr><td><strong>URL</strong></td><td><%= link_to @plugin_class.plugin_url, @plugin_class.plugin_url, target: "_blank", rel: "noopener" %></td></tr>
27
+ <% end %>
28
+ </tbody>
29
+ </table>
30
+ </div>
31
+
32
+ <% if @plugin_class.plugin_dependencies.present? %>
33
+ <div class="np-card" style="margin-bottom: 1.5rem; padding: 1.5rem;">
34
+ <h3>Dependencies</h3>
35
+ <ul>
36
+ <% @plugin_class.plugin_dependencies.each do |dep| %>
37
+ <li>
38
+ <%= dep[:name] %>
39
+ <% if dep[:constraint] %> (<%= dep[:constraint] %>)<% end %>
40
+ — <%= NotPressed::PluginManager.enabled?(dep[:name]) ? status_badge(:active) : status_badge(:inactive) %>
41
+ </li>
42
+ <% end %>
43
+ </ul>
44
+ </div>
45
+ <% end %>
46
+
47
+ <% if @plugin_class.plugin_settings_schema.present? %>
48
+ <div class="np-card" style="padding: 1.5rem;">
49
+ <h3>Settings</h3>
50
+ <%= render "not_pressed/admin/plugins/settings_form",
51
+ plugin: @plugin_class,
52
+ plugin_info: @plugin_info %>
53
+ </div>
54
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <%= form_with url: code_injection_admin_settings_path, method: :patch, class: "np-form" do |f| %>
2
+ <fieldset class="np-fieldset">
3
+ <legend class="np-legend">Global Code Injection</legend>
4
+
5
+ <div class="np-form-group">
6
+ <%= f.label :global_head_code, "Head Code", class: "np-label" %>
7
+ <p class="np-hint">Injected before <code>&lt;/head&gt;</code> on every page. Useful for analytics, custom fonts, or meta tags.</p>
8
+ <%= text_area_tag :global_head_code, @global_head_code, class: "np-input", rows: 8, style: "font-family: monospace;" %>
9
+ </div>
10
+
11
+ <div class="np-form-group">
12
+ <%= f.label :global_body_code, "Body Code", class: "np-label" %>
13
+ <p class="np-hint">Injected before <code>&lt;/body&gt;</code> on every page. Useful for tracking scripts or chat widgets.</p>
14
+ <%= text_area_tag :global_body_code, @global_body_code, class: "np-input", rows: 8, style: "font-family: monospace;" %>
15
+ </div>
16
+ </fieldset>
17
+
18
+ <div class="np-form-actions">
19
+ <%= f.submit "Save", class: "np-btn np-btn--primary" %>
20
+ </div>
21
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <% if @breadcrumbs.present? %>
2
+ <nav aria-label="Breadcrumb" class="np-breadcrumbs">
3
+ <% @breadcrumbs.each_with_index do |crumb, index| %>
4
+ <% if index > 0 %>
5
+ <span class="np-breadcrumb-separator">/</span>
6
+ <% end %>
7
+ <% if crumb[:path] && index < @breadcrumbs.size - 1 %>
8
+ <%= link_to crumb[:label], crumb[:path] %>
9
+ <% else %>
10
+ <span><%= crumb[:label] %></span>
11
+ <% end %>
12
+ <% end %>
13
+ </nav>
14
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <% flash.each do |type, message| %>
2
+ <% next unless %w[notice alert].include?(type) %>
3
+ <div class="np-flash np-flash--<%= type %>" data-controller="np-flash">
4
+ <span><%= message %></span>
5
+ <button class="np-flash-close" data-action="np-flash#dismiss" aria-label="Dismiss">&times;</button>
6
+ </div>
7
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <dialog id="<%= id %>" class="np-modal" data-controller="np-modal">
2
+ <div class="np-modal-header">
3
+ <h3><%= title %></h3>
4
+ <button class="np-modal-close" data-action="np-modal#close" aria-label="Close">&times;</button>
5
+ </div>
6
+ <div class="np-modal-body">
7
+ <%= yield %>
8
+ </div>
9
+ </dialog>
@@ -0,0 +1,18 @@
1
+ <aside id="admin-sidebar" data-np-sidebar-target="panel">
2
+ <div class="sidebar-header">
3
+ <h1><%= NotPressed.configuration.site_name %></h1>
4
+ </div>
5
+ <nav>
6
+ <ul>
7
+ <% NotPressed::Admin::MenuRegistry.items(section: :main).each do |item| %>
8
+ <li><%= admin_nav_item item[:label], item[:path].is_a?(Symbol) ? not_pressed.send(item[:path]) : item[:path], icon: item[:icon] %></li>
9
+ <% end %>
10
+ </ul>
11
+ <h3 class="sidebar-section-title">Settings</h3>
12
+ <ul>
13
+ <% NotPressed::Admin::MenuRegistry.items(section: :settings).each do |item| %>
14
+ <li><%= admin_nav_item item[:label], item[:path].is_a?(Symbol) ? not_pressed.send(item[:path]) : item[:path], icon: item[:icon] %></li>
15
+ <% end %>
16
+ </ul>
17
+ </nav>
18
+ </aside>
@@ -0,0 +1,52 @@
1
+ <div class="np-actions" style="margin-bottom: 1.5rem;">
2
+ <h2 style="margin: 0;">Add Tag</h2>
3
+ </div>
4
+
5
+ <%= form_with model: @tag, url: admin_tags_path, class: "np-form", style: "margin-bottom: 2rem;" do |f| %>
6
+ <% if @tag.errors.any? %>
7
+ <div class="np-form-errors">
8
+ <ul>
9
+ <% @tag.errors.full_messages.each do |message| %>
10
+ <li><%= message %></li>
11
+ <% end %>
12
+ </ul>
13
+ </div>
14
+ <% end %>
15
+
16
+ <div style="display: flex; gap: 0.5rem; align-items: flex-end;">
17
+ <div class="np-form-group" style="margin-bottom: 0; flex: 1;">
18
+ <%= f.label :name, class: "np-label" %>
19
+ <%= f.text_field :name, class: "np-input", placeholder: "Tag name" %>
20
+ </div>
21
+ <div>
22
+ <%= f.submit "Add", class: "np-btn np-btn--primary" %>
23
+ </div>
24
+ </div>
25
+ <% end %>
26
+
27
+ <% if @tags.any? %>
28
+ <table class="np-table">
29
+ <thead>
30
+ <tr>
31
+ <th>Name</th>
32
+ <th>Slug</th>
33
+ <th>Posts</th>
34
+ <th>Actions</th>
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ <% @tags.each do |tag| %>
39
+ <tr>
40
+ <td><%= tag.name %></td>
41
+ <td><code><%= tag.slug %></code></td>
42
+ <td><%= tag.pages.count %></td>
43
+ <td>
44
+ <%= link_to "Delete", admin_tag_path(tag), data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this tag?" }, class: "np-btn np-btn--small np-btn--danger" %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ <% else %>
51
+ <p>No tags yet. Add one above.</p>
52
+ <% end %>
@@ -0,0 +1,60 @@
1
+ <div class="np-card np-card--form" style="margin-bottom: 2rem;">
2
+ <h3>Import Theme</h3>
3
+ <%= form_tag(admin_import_theme_path, method: :post, multipart: true, class: "np-form") do %>
4
+ <div class="np-form-group">
5
+ <%= file_field_tag :theme_archive, accept: ".zip", class: "np-input" %>
6
+ </div>
7
+ <%= submit_tag "Import Theme", class: "np-btn np-btn--primary" %>
8
+ <% end %>
9
+ </div>
10
+
11
+ <% if @themes.any? %>
12
+ <div class="np-theme-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.5rem;">
13
+ <% @themes.each do |name, info| %>
14
+ <div class="np-card" style="padding: 1.5rem;">
15
+ <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
16
+ <h3 style="margin: 0;"><%= link_to info[:klass].plugin_name, admin_theme_path(theme_name: name) %></h3>
17
+ <% if @active_theme == info[:klass] %>
18
+ <span class="np-badge np-badge--active">Active</span>
19
+ <% end %>
20
+ </div>
21
+
22
+ <% if info[:klass].plugin_description.present? %>
23
+ <p style="color: #64748b; margin: 0 0 0.75rem;"><%= info[:klass].plugin_description %></p>
24
+ <% end %>
25
+
26
+ <table class="np-table" style="margin-bottom: 1rem;">
27
+ <tbody>
28
+ <% if info[:klass].plugin_version.present? %>
29
+ <tr><td><strong>Version</strong></td><td><%= info[:klass].plugin_version %></td></tr>
30
+ <% end %>
31
+ <% if info[:klass].plugin_author.present? %>
32
+ <tr><td><strong>Author</strong></td><td><%= info[:klass].plugin_author %></td></tr>
33
+ <% end %>
34
+ </tbody>
35
+ </table>
36
+
37
+ <% if info[:klass].theme_layouts.any? %>
38
+ <div style="margin-bottom: 1rem;">
39
+ <strong>Layouts:</strong>
40
+ <% info[:klass].theme_layouts.each do |layout| %>
41
+ <span class="np-badge"><%= layout[:label] %></span>
42
+ <% end %>
43
+ </div>
44
+ <% end %>
45
+
46
+ <div class="np-actions">
47
+ <% if @active_theme == info[:klass] %>
48
+ <%= button_to "Deactivate", deactivate_admin_theme_path(theme_name: name), method: :post, class: "np-btn np-btn--danger np-btn--small" %>
49
+ <% else %>
50
+ <%= button_to "Activate", activate_admin_theme_path(theme_name: name), method: :post, class: "np-btn np-btn--primary np-btn--small" %>
51
+ <% end %>
52
+ <%= link_to "Customize", admin_theme_path(theme_name: name), class: "np-btn np-btn--small" %>
53
+ <%= link_to "Export", admin_export_theme_path(id: name), class: "np-btn np-btn--small np-btn--secondary", download: "" %>
54
+ </div>
55
+ </div>
56
+ <% end %>
57
+ </div>
58
+ <% else %>
59
+ <p>No themes installed. Themes will appear here once registered.</p>
60
+ <% end %>
@@ -0,0 +1,66 @@
1
+ <div class="np-actions" style="margin-bottom: 1.5rem;">
2
+ <%= link_to "Back to Themes", admin_themes_path, class: "np-btn" %>
3
+ <% if NotPressed::ThemeRegistry.active == @theme_class %>
4
+ <%= button_to "Deactivate", deactivate_admin_theme_path(theme_name: @theme_class.plugin_name), method: :post, class: "np-btn np-btn--danger" %>
5
+ <% else %>
6
+ <%= button_to "Activate", activate_admin_theme_path(theme_name: @theme_class.plugin_name), method: :post, class: "np-btn np-btn--primary" %>
7
+ <% end %>
8
+ </div>
9
+
10
+ <div class="np-card" style="margin-bottom: 1.5rem; padding: 1.5rem;">
11
+ <h2><%= @theme_class.plugin_name %></h2>
12
+ <% if NotPressed::ThemeRegistry.active == @theme_class %>
13
+ <p><span class="np-badge np-badge--active">Active</span></p>
14
+ <% end %>
15
+
16
+ <% if @theme_class.plugin_description.present? %>
17
+ <p><%= @theme_class.plugin_description %></p>
18
+ <% end %>
19
+
20
+ <table class="np-table">
21
+ <tbody>
22
+ <% if @theme_class.plugin_version.present? %>
23
+ <tr><td><strong>Version</strong></td><td><%= @theme_class.plugin_version %></td></tr>
24
+ <% end %>
25
+ <% if @theme_class.plugin_author.present? %>
26
+ <tr><td><strong>Author</strong></td><td><%= @theme_class.plugin_author %></td></tr>
27
+ <% end %>
28
+ </tbody>
29
+ </table>
30
+ </div>
31
+
32
+ <% if @theme_class.theme_layouts.any? %>
33
+ <div class="np-card" style="margin-bottom: 1.5rem; padding: 1.5rem;">
34
+ <h3>Layouts</h3>
35
+ <ul>
36
+ <% @theme_class.theme_layouts.each do |layout| %>
37
+ <li>
38
+ <strong><%= layout[:label] %></strong> (<code><%= layout[:name] %></code>)
39
+ <% if layout[:primary] %>
40
+ <span class="np-badge np-badge--active">Primary</span>
41
+ <% end %>
42
+ </li>
43
+ <% end %>
44
+ </ul>
45
+ </div>
46
+ <% end %>
47
+
48
+ <% if @colors.any? %>
49
+ <div class="np-card" style="padding: 1.5rem;">
50
+ <h3>Color Customization</h3>
51
+ <%= form_with url: update_colors_admin_theme_path(theme_name: @theme_class.plugin_name), method: :patch, class: "np-form" do |f| %>
52
+ <% @colors.each do |color| %>
53
+ <div class="np-form-group" style="display: flex; align-items: center; gap: 1rem;">
54
+ <input type="color" name="theme_colors[<%= color[:key] %>]" value="<%= @color_values[color[:key]] %>" style="width: 50px; height: 36px; padding: 2px; border: 1px solid #e2e8f0; border-radius: 4px; cursor: pointer;">
55
+ <div>
56
+ <label class="np-label" style="margin-bottom: 0;"><%= color[:label] || color[:key].to_s.humanize %></label>
57
+ <small style="color: #64748b;"><%= @color_values[color[:key]] %></small>
58
+ </div>
59
+ </div>
60
+ <% end %>
61
+ <div class="np-form-actions" style="margin-top: 1rem;">
62
+ <%= f.submit "Save Colors", class: "np-btn np-btn--primary" %>
63
+ </div>
64
+ <% end %>
65
+ </div>
66
+ <% end %>
@@ -0,0 +1,24 @@
1
+ <article class="np-post-card">
2
+ <h2 class="np-post-card__title">
3
+ <%= link_to post.title, not_pressed.blog_post_path(slug: post.slug) %>
4
+ </h2>
5
+ <div class="np-post-card__meta">
6
+ <time datetime="<%= post.published_at&.iso8601 %>"><%= post.published_at&.strftime("%B %-d, %Y") %></time>
7
+ <% if post.category.present? %>
8
+ <span class="np-post-card__category">
9
+ in <%= link_to post.category.name, not_pressed.blog_category_path(slug: post.category.slug) %>
10
+ </span>
11
+ <% end %>
12
+ </div>
13
+ <% first_text = post.content_blocks.ordered.find { |b| b.block_type == "text" } %>
14
+ <% if first_text %>
15
+ <p class="np-post-card__excerpt"><%= truncate(first_text.content["body"].to_s.gsub(/<[^>]+>/, ""), length: 150) %></p>
16
+ <% end %>
17
+ <% if post.tags.any? %>
18
+ <div class="np-post-card__tags">
19
+ <% post.tags.each do |tag| %>
20
+ <%= link_to tag.name, not_pressed.blog_tag_path(slug: tag.slug), class: "np-post-card__tag" %>
21
+ <% end %>
22
+ </div>
23
+ <% end %>
24
+ </article>
@@ -0,0 +1,22 @@
1
+ xml.instruct! :xml, version: "1.0"
2
+ xml.rss version: "2.0" do
3
+ xml.channel do
4
+ xml.title NotPressed::Setting.get("site_title") || "Blog"
5
+ xml.description NotPressed::Setting.get("site_description") || ""
6
+ xml.link not_pressed.blog_url
7
+
8
+ @posts.each do |post|
9
+ xml.item do
10
+ xml.title post.title
11
+ xml.link not_pressed.blog_post_url(slug: post.slug)
12
+ xml.guid not_pressed.blog_post_url(slug: post.slug)
13
+ xml.pubDate post.published_at&.rfc822
14
+ xml.description do
15
+ first_text = post.content_blocks.ordered.find { |b| b.block_type == "text" }
16
+ xml.cdata!(first_text ? truncate(first_text.content["body"].to_s.gsub(/<[^>]+>/, ""), length: 300) : post.title)
17
+ end
18
+ xml.category post.category.name if post.category.present?
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ <div class="np-blog">
2
+ <h1 class="np-blog__heading">
3
+ <% if @category %>
4
+ Category: <%= @category.name %>
5
+ <% elsif @tag %>
6
+ Tag: <%= @tag.name %>
7
+ <% elsif @year %>
8
+ Archive: <%= @year %><%= @month ? " / #{Date::MONTHNAMES[@month]}" : "" %>
9
+ <% else %>
10
+ Blog
11
+ <% end %>
12
+ </h1>
13
+
14
+ <% if @posts.any? %>
15
+ <div class="np-blog__posts">
16
+ <% @posts.each do |post| %>
17
+ <%= render partial: "not_pressed/blog/post_card", locals: { post: post } %>
18
+ <% end %>
19
+ </div>
20
+
21
+ <% total_pages = (@total_count / 12.0).ceil %>
22
+ <% if total_pages > 1 %>
23
+ <nav class="np-blog__pagination">
24
+ <% if @page_num > 1 %>
25
+ <% pagination_params = { page: @page_num - 1 } %>
26
+ <% if @category %>
27
+ <%= link_to "Newer", not_pressed.blog_category_path(slug: @category.slug, **pagination_params), class: "np-btn" %>
28
+ <% elsif @tag %>
29
+ <%= link_to "Newer", not_pressed.blog_tag_path(slug: @tag.slug, **pagination_params), class: "np-btn" %>
30
+ <% elsif @year %>
31
+ <%= link_to "Newer", not_pressed.blog_archive_path(year: @year, month: @month, **pagination_params), class: "np-btn" %>
32
+ <% else %>
33
+ <%= link_to "Newer", not_pressed.blog_path(**pagination_params), class: "np-btn" %>
34
+ <% end %>
35
+ <% end %>
36
+
37
+ <span class="np-blog__page-info">Page <%= @page_num %> of <%= total_pages %></span>
38
+
39
+ <% if @page_num < total_pages %>
40
+ <% pagination_params = { page: @page_num + 1 } %>
41
+ <% if @category %>
42
+ <%= link_to "Older", not_pressed.blog_category_path(slug: @category.slug, **pagination_params), class: "np-btn" %>
43
+ <% elsif @tag %>
44
+ <%= link_to "Older", not_pressed.blog_tag_path(slug: @tag.slug, **pagination_params), class: "np-btn" %>
45
+ <% elsif @year %>
46
+ <%= link_to "Older", not_pressed.blog_archive_path(year: @year, month: @month, **pagination_params), class: "np-btn" %>
47
+ <% else %>
48
+ <%= link_to "Older", not_pressed.blog_path(**pagination_params), class: "np-btn" %>
49
+ <% end %>
50
+ <% end %>
51
+ </nav>
52
+ <% end %>
53
+ <% else %>
54
+ <p>No posts found.</p>
55
+ <% end %>
56
+ </div>
@@ -0,0 +1,41 @@
1
+ <article class="np-blog-post">
2
+ <header class="np-blog-post__header">
3
+ <h1><%= @post.title %></h1>
4
+ <div class="np-blog-post__meta">
5
+ <time datetime="<%= @post.published_at&.iso8601 %>"><%= @post.published_at&.strftime("%B %-d, %Y") %></time>
6
+ <% if @post.category.present? %>
7
+ <span>in <%= link_to @post.category.name, not_pressed.blog_category_path(slug: @post.category.slug) %></span>
8
+ <% end %>
9
+ </div>
10
+ <% if @post.tags.any? %>
11
+ <div class="np-blog-post__tags">
12
+ <% @post.tags.each do |tag| %>
13
+ <%= link_to tag.name, not_pressed.blog_tag_path(slug: tag.slug), class: "np-blog-post__tag" %>
14
+ <% end %>
15
+ </div>
16
+ <% end %>
17
+ </header>
18
+
19
+ <div class="np-blog-post__content">
20
+ <%= render_page_content(@post) %>
21
+ </div>
22
+
23
+ <nav class="np-blog-post__nav">
24
+ <% if @prev_post %>
25
+ <div class="np-blog-post__nav-prev">
26
+ <%= link_to not_pressed.blog_post_path(slug: @prev_post.slug) do %>
27
+ <span>&larr; Previous</span>
28
+ <span><%= @prev_post.title %></span>
29
+ <% end %>
30
+ </div>
31
+ <% end %>
32
+ <% if @next_post %>
33
+ <div class="np-blog-post__nav-next">
34
+ <%= link_to not_pressed.blog_post_path(slug: @next_post.slug) do %>
35
+ <span>Next &rarr;</span>
36
+ <span><%= @next_post.title %></span>
37
+ <% end %>
38
+ </div>
39
+ <% end %>
40
+ </nav>
41
+ </article>
@@ -0,0 +1,8 @@
1
+ New submission for "<%= @form.name %>"
2
+ <%= "=" * 40 %>
3
+
4
+ <% @form_submission.data.each do |label, value| %>
5
+ <%= label %>: <%= value %>
6
+ <% end %>
7
+
8
+ Submitted at: <%= @form_submission.submitted_at.strftime("%Y-%m-%d %H:%M:%S UTC") %>
@@ -0,0 +1,4 @@
1
+ <article class="np-page-article">
2
+ <h1><%= @page.title %></h1>
3
+ <%= render_page_content(@page) %>
4
+ </article>
@@ -0,0 +1,36 @@
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">
6
+ <%= seo_meta_tags(@page, request) %>
7
+ <%= stylesheet_link_tag "not_pressed/content", media: "all" %>
8
+ <%= stylesheet_link_tag "not_pressed/gallery", media: "all" %>
9
+ <%= theme_stylesheet_tag %>
10
+ <%= theme_color_overrides %>
11
+ <%= csrf_meta_tags %>
12
+ <%= csp_meta_tag %>
13
+ <%= head_injection(@page) %>
14
+ </head>
15
+ <body class="np-page-body <%= active_theme_class %>">
16
+ <header class="np-theme-header">
17
+ <div class="np-theme-header-inner">
18
+ <a href="/" class="np-theme-site-name"><%= NotPressed.configuration.site_name %></a>
19
+ </div>
20
+ </header>
21
+
22
+ <main class="np-theme-main">
23
+ <div class="np-theme-content">
24
+ <%= yield %>
25
+ </div>
26
+ </main>
27
+
28
+ <footer class="np-theme-footer">
29
+ <div class="np-theme-footer-inner">
30
+ <p>&copy; <%= Date.current.year %> <%= NotPressed.configuration.site_name %></p>
31
+ </div>
32
+ </footer>
33
+ <%= javascript_include_tag "not_pressed/lightbox" %>
34
+ <%= body_injection(@page) %>
35
+ </body>
36
+ </html>