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.
- checksums.yaml +7 -0
- data/LICENSE.txt +41 -0
- data/README.md +285 -0
- data/app/assets/javascripts/not_pressed/lightbox.js +110 -0
- data/app/assets/stylesheets/not_pressed/admin.css +1161 -0
- data/app/assets/stylesheets/not_pressed/content.css +193 -0
- data/app/assets/stylesheets/not_pressed/gallery.css +117 -0
- data/app/assets/stylesheets/not_pressed/themes/starter.css +118 -0
- data/app/controllers/not_pressed/admin/base_controller.rb +21 -0
- data/app/controllers/not_pressed/admin/categories_controller.rb +53 -0
- data/app/controllers/not_pressed/admin/content_blocks_controller.rb +73 -0
- data/app/controllers/not_pressed/admin/dashboard_controller.rb +19 -0
- data/app/controllers/not_pressed/admin/forms_controller.rb +86 -0
- data/app/controllers/not_pressed/admin/media_attachments_controller.rb +94 -0
- data/app/controllers/not_pressed/admin/pages_controller.rb +122 -0
- data/app/controllers/not_pressed/admin/plugins_controller.rb +121 -0
- data/app/controllers/not_pressed/admin/settings_controller.rb +19 -0
- data/app/controllers/not_pressed/admin/tags_controller.rb +37 -0
- data/app/controllers/not_pressed/admin/themes_controller.rb +104 -0
- data/app/controllers/not_pressed/application_controller.rb +6 -0
- data/app/controllers/not_pressed/blog_controller.rb +83 -0
- data/app/controllers/not_pressed/form_submissions_controller.rb +70 -0
- data/app/controllers/not_pressed/pages_controller.rb +36 -0
- data/app/controllers/not_pressed/robots_controller.rb +34 -0
- data/app/controllers/not_pressed/sitemaps_controller.rb +12 -0
- data/app/helpers/not_pressed/admin_helper.rb +41 -0
- data/app/helpers/not_pressed/application_helper.rb +6 -0
- data/app/helpers/not_pressed/code_injection_helper.rb +29 -0
- data/app/helpers/not_pressed/content_helper.rb +13 -0
- data/app/helpers/not_pressed/form_helper.rb +80 -0
- data/app/helpers/not_pressed/media_helper.rb +28 -0
- data/app/helpers/not_pressed/seo_helper.rb +69 -0
- data/app/helpers/not_pressed/theme_helper.rb +42 -0
- data/app/mailers/not_pressed/application_mailer.rb +10 -0
- data/app/mailers/not_pressed/form_mailer.rb +15 -0
- data/app/models/concerns/not_pressed/sluggable.rb +43 -0
- data/app/models/not_pressed/category.rb +16 -0
- data/app/models/not_pressed/content_block.rb +46 -0
- data/app/models/not_pressed/form.rb +55 -0
- data/app/models/not_pressed/form_field.rb +23 -0
- data/app/models/not_pressed/form_submission.rb +19 -0
- data/app/models/not_pressed/media_attachment.rb +68 -0
- data/app/models/not_pressed/page.rb +182 -0
- data/app/models/not_pressed/page_version.rb +15 -0
- data/app/models/not_pressed/setting.rb +20 -0
- data/app/models/not_pressed/tag.rb +15 -0
- data/app/models/not_pressed/tagging.rb +12 -0
- data/app/plugins/not_pressed/analytics_plugin.rb +106 -0
- data/app/plugins/not_pressed/callout_block_plugin.rb +43 -0
- data/app/themes/not_pressed/starter_theme.rb +26 -0
- data/app/views/layouts/not_pressed/admin.html.erb +745 -0
- data/app/views/layouts/not_pressed/application.html.erb +12 -0
- data/app/views/layouts/not_pressed/page.html.erb +22 -0
- data/app/views/not_pressed/admin/categories/index.html.erb +58 -0
- data/app/views/not_pressed/admin/content_blocks/_block.html.erb +18 -0
- data/app/views/not_pressed/admin/content_blocks/_block_picker.html.erb +32 -0
- data/app/views/not_pressed/admin/content_blocks/create.turbo_stream.erb +3 -0
- data/app/views/not_pressed/admin/content_blocks/destroy.turbo_stream.erb +1 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_callout.html.erb +38 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_code.html.erb +26 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_divider.html.erb +4 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_form.html.erb +16 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_gallery.html.erb +75 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_heading.html.erb +26 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_html.html.erb +13 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_image.html.erb +56 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_quote.html.erb +24 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_text.html.erb +28 -0
- data/app/views/not_pressed/admin/content_blocks/editors/_video.html.erb +25 -0
- data/app/views/not_pressed/admin/dashboard/index.html.erb +60 -0
- data/app/views/not_pressed/admin/forms/_field_row.html.erb +33 -0
- data/app/views/not_pressed/admin/forms/_form.html.erb +75 -0
- data/app/views/not_pressed/admin/forms/edit.html.erb +1 -0
- data/app/views/not_pressed/admin/forms/index.html.erb +32 -0
- data/app/views/not_pressed/admin/forms/new.html.erb +1 -0
- data/app/views/not_pressed/admin/forms/submissions.html.erb +34 -0
- data/app/views/not_pressed/admin/media_attachments/_media_card.html.erb +21 -0
- data/app/views/not_pressed/admin/media_attachments/_picker.html.erb +19 -0
- data/app/views/not_pressed/admin/media_attachments/edit.html.erb +57 -0
- data/app/views/not_pressed/admin/media_attachments/index.html.erb +48 -0
- data/app/views/not_pressed/admin/pages/_form.html.erb +177 -0
- data/app/views/not_pressed/admin/pages/_page_tree_node.html.erb +32 -0
- data/app/views/not_pressed/admin/pages/edit.html.erb +69 -0
- data/app/views/not_pressed/admin/pages/index.html.erb +21 -0
- data/app/views/not_pressed/admin/pages/new.html.erb +1 -0
- data/app/views/not_pressed/admin/pages/preview.html.erb +17 -0
- data/app/views/not_pressed/admin/plugins/_settings_form.html.erb +59 -0
- data/app/views/not_pressed/admin/plugins/index.html.erb +48 -0
- data/app/views/not_pressed/admin/plugins/show.html.erb +54 -0
- data/app/views/not_pressed/admin/settings/code_injection.html.erb +21 -0
- data/app/views/not_pressed/admin/shared/_breadcrumbs.html.erb +14 -0
- data/app/views/not_pressed/admin/shared/_flash.html.erb +7 -0
- data/app/views/not_pressed/admin/shared/_modal.html.erb +9 -0
- data/app/views/not_pressed/admin/shared/_sidebar.html.erb +18 -0
- data/app/views/not_pressed/admin/tags/index.html.erb +52 -0
- data/app/views/not_pressed/admin/themes/index.html.erb +60 -0
- data/app/views/not_pressed/admin/themes/show.html.erb +66 -0
- data/app/views/not_pressed/blog/_post_card.html.erb +24 -0
- data/app/views/not_pressed/blog/feed.rss.builder +22 -0
- data/app/views/not_pressed/blog/index.html.erb +56 -0
- data/app/views/not_pressed/blog/show.html.erb +41 -0
- data/app/views/not_pressed/form_mailer/submission_notification.text.erb +8 -0
- data/app/views/not_pressed/pages/show.html.erb +4 -0
- data/app/views/themes/starter/layouts/not_pressed/default.html.erb +36 -0
- data/app/views/themes/starter/layouts/not_pressed/full_width.html.erb +36 -0
- data/app/views/themes/starter/layouts/not_pressed/sidebar.html.erb +41 -0
- data/config/routes.rb +81 -0
- data/db/migrate/20260310000001_create_not_pressed_pages.rb +20 -0
- data/db/migrate/20260310000002_create_not_pressed_content_blocks.rb +17 -0
- data/db/migrate/20260310000003_create_not_pressed_media_attachments.rb +14 -0
- data/db/migrate/20260310000004_add_content_type_to_not_pressed_pages.rb +8 -0
- data/db/migrate/20260310000005_add_publishing_fields_to_not_pressed_pages.rb +8 -0
- data/db/migrate/20260310000006_create_not_pressed_page_versions.rb +16 -0
- data/db/migrate/20260310000007_add_settings_fields_to_not_pressed_pages.rb +11 -0
- data/db/migrate/20260311000001_create_not_pressed_forms.rb +42 -0
- data/db/migrate/20260311000002_add_seo_fields_to_not_pressed_pages.rb +10 -0
- data/db/migrate/20260311000003_add_code_injection_to_not_pressed_pages.rb +8 -0
- data/db/migrate/20260311000004_create_not_pressed_settings.rb +14 -0
- data/db/migrate/20260312000001_create_not_pressed_categories.rb +16 -0
- data/db/migrate/20260312000002_create_not_pressed_tags.rb +15 -0
- data/db/migrate/20260312000003_create_not_pressed_taggings.rb +14 -0
- data/db/migrate/20260312000004_add_category_id_to_not_pressed_pages.rb +7 -0
- data/lib/generators/not_pressed/install/install_generator.rb +52 -0
- data/lib/generators/not_pressed/install/templates/initializer.rb.tt +89 -0
- data/lib/generators/not_pressed/install/templates/seeds.rb.tt +131 -0
- data/lib/generators/not_pressed/plugin/plugin_generator.rb +37 -0
- data/lib/generators/not_pressed/plugin/templates/plugin.rb.tt +23 -0
- data/lib/not_pressed/admin/authentication.rb +48 -0
- data/lib/not_pressed/admin/menu_registry.rb +100 -0
- data/lib/not_pressed/configuration.rb +77 -0
- data/lib/not_pressed/content_type.rb +23 -0
- data/lib/not_pressed/content_type_builder.rb +51 -0
- data/lib/not_pressed/content_type_registry.rb +45 -0
- data/lib/not_pressed/engine.rb +132 -0
- data/lib/not_pressed/hooks.rb +166 -0
- data/lib/not_pressed/navigation/builder.rb +148 -0
- data/lib/not_pressed/navigation/menu.rb +54 -0
- data/lib/not_pressed/navigation/menu_item.rb +33 -0
- data/lib/not_pressed/navigation/node.rb +45 -0
- data/lib/not_pressed/navigation/partial_parser.rb +96 -0
- data/lib/not_pressed/navigation/route_inspector.rb +98 -0
- data/lib/not_pressed/navigation.rb +6 -0
- data/lib/not_pressed/plugin.rb +354 -0
- data/lib/not_pressed/plugin_importer.rb +133 -0
- data/lib/not_pressed/plugin_manager.rb +196 -0
- data/lib/not_pressed/plugin_packager.rb +129 -0
- data/lib/not_pressed/rendering/block_renderer.rb +222 -0
- data/lib/not_pressed/rendering/renderer_registry.rb +154 -0
- data/lib/not_pressed/rendering.rb +8 -0
- data/lib/not_pressed/seo/sitemap_builder.rb +61 -0
- data/lib/not_pressed/theme.rb +191 -0
- data/lib/not_pressed/theme_importer.rb +133 -0
- data/lib/not_pressed/theme_packager.rb +180 -0
- data/lib/not_pressed/theme_registry.rb +123 -0
- data/lib/not_pressed/version.rb +5 -0
- data/lib/not_pressed.rb +65 -0
- metadata +258 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/base"
|
|
4
|
+
|
|
5
|
+
module NotPressed
|
|
6
|
+
module Generators
|
|
7
|
+
class PluginGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
argument :plugin_name, type: :string, desc: "Name of the plugin (snake_case)"
|
|
11
|
+
|
|
12
|
+
desc "Generate a NotPressed plugin scaffold"
|
|
13
|
+
|
|
14
|
+
def create_plugin_file
|
|
15
|
+
template "plugin.rb.tt",
|
|
16
|
+
"app/plugins/not_pressed/#{plugin_name}_plugin.rb"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def show_instructions
|
|
20
|
+
say ""
|
|
21
|
+
say "NotPressed plugin '#{plugin_name}' created!", :green
|
|
22
|
+
say ""
|
|
23
|
+
say "Next steps:"
|
|
24
|
+
say " 1. Edit app/plugins/not_pressed/#{plugin_name}_plugin.rb"
|
|
25
|
+
say " 2. Uncomment and customize the hook, block_type, and settings examples"
|
|
26
|
+
say " 3. The plugin will be auto-discovered and enabled on app boot"
|
|
27
|
+
say ""
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def class_name
|
|
33
|
+
plugin_name.camelize
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class <%= class_name %>Plugin < NotPressed::Plugin
|
|
4
|
+
name "<%= plugin_name %>"
|
|
5
|
+
version "0.1.0"
|
|
6
|
+
description "TODO: Describe what <%= plugin_name %> does"
|
|
7
|
+
|
|
8
|
+
# Hook into CMS events:
|
|
9
|
+
# on(:page_before_save) { |page| ... }
|
|
10
|
+
# on(:page_after_save) { |page| ... }
|
|
11
|
+
|
|
12
|
+
# Register custom block types:
|
|
13
|
+
# block_type :my_block, label: "My Block"
|
|
14
|
+
|
|
15
|
+
# Define plugin settings:
|
|
16
|
+
# settings do
|
|
17
|
+
# string :api_key, label: "API Key", required: true
|
|
18
|
+
# boolean :enabled, label: "Enabled", default: true
|
|
19
|
+
# end
|
|
20
|
+
|
|
21
|
+
# Declare dependencies on other plugins:
|
|
22
|
+
# depends_on "other_plugin"
|
|
23
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
module Authentication
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
helper_method :current_admin_user, :admin_user_signed_in?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def authenticate_admin!
|
|
15
|
+
auth = NotPressed.configuration.admin_authentication
|
|
16
|
+
case auth
|
|
17
|
+
when Proc
|
|
18
|
+
instance_exec(&auth)
|
|
19
|
+
when Symbol, String
|
|
20
|
+
send(auth)
|
|
21
|
+
when nil
|
|
22
|
+
# No authentication configured — allow access
|
|
23
|
+
end
|
|
24
|
+
rescue NoMethodError => e
|
|
25
|
+
raise unless e.message.include?(auth.to_s)
|
|
26
|
+
|
|
27
|
+
redirect_to NotPressed.configuration.admin_unauthorized_redirect,
|
|
28
|
+
alert: "Authentication required"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def current_admin_user
|
|
32
|
+
user_method = NotPressed.configuration.admin_current_user
|
|
33
|
+
case user_method
|
|
34
|
+
when Proc
|
|
35
|
+
instance_exec(&user_method)
|
|
36
|
+
when Symbol, String
|
|
37
|
+
send(user_method)
|
|
38
|
+
when nil
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def admin_user_signed_in?
|
|
44
|
+
current_admin_user.present?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
# Manages the admin sidebar menu items.
|
|
6
|
+
#
|
|
7
|
+
# Supports multiple sections (e.g., +:main+, +:settings+) and positional
|
|
8
|
+
# ordering. Built-in items are protected from {.reset!}.
|
|
9
|
+
module MenuRegistry
|
|
10
|
+
class << self
|
|
11
|
+
# Adds a menu item to the admin sidebar.
|
|
12
|
+
#
|
|
13
|
+
# @param label [String] display text
|
|
14
|
+
# @param path [Symbol, String] route helper name or URL path
|
|
15
|
+
# @param icon [String, nil] icon identifier
|
|
16
|
+
# @param section [Symbol] menu section (default +:main+)
|
|
17
|
+
# @param position [Integer] sort order, lower values appear first (default 100)
|
|
18
|
+
# @param built_in [Boolean] if true, protected from {.reset!} (default false)
|
|
19
|
+
# @return [void]
|
|
20
|
+
def register(label, path:, icon: nil, section: :main, position: 100, built_in: false)
|
|
21
|
+
items_store << {
|
|
22
|
+
label: label,
|
|
23
|
+
path: path,
|
|
24
|
+
icon: icon,
|
|
25
|
+
section: section,
|
|
26
|
+
position: position,
|
|
27
|
+
built_in: built_in
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns menu items for a section, sorted by position.
|
|
32
|
+
#
|
|
33
|
+
# @param section [Symbol] the section to filter by (default +:main+)
|
|
34
|
+
# @return [Array<Hash>] sorted menu item hashes
|
|
35
|
+
def items(section: :main)
|
|
36
|
+
items_store
|
|
37
|
+
.select { |item| item[:section] == section }
|
|
38
|
+
.sort_by { |item| item[:position] }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Removes all non-built-in menu items.
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
44
|
+
def reset!
|
|
45
|
+
items_store.reject! { |item| !item[:built_in] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Removes a menu item matching the given label and path.
|
|
49
|
+
#
|
|
50
|
+
# @param label [String] menu item label
|
|
51
|
+
# @param path [Symbol, String] menu item path
|
|
52
|
+
# @return [void]
|
|
53
|
+
def unregister(label, path:)
|
|
54
|
+
items_store.reject! { |item| item[:label] == label && item[:path] == path }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Removes all menu items including built-ins.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def clear_all!
|
|
61
|
+
@items = []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Registers the default built-in menu items.
|
|
65
|
+
#
|
|
66
|
+
# Idempotent: only executes once unless {.reset_built_ins_flag!} is called.
|
|
67
|
+
#
|
|
68
|
+
# @return [void]
|
|
69
|
+
def register_built_ins!
|
|
70
|
+
return if @built_ins_registered
|
|
71
|
+
|
|
72
|
+
register "Dashboard", path: :admin_root_path, section: :main, position: 0, built_in: true
|
|
73
|
+
register "Pages", path: :admin_pages_path, section: :main, position: 10, built_in: true
|
|
74
|
+
register "Forms", path: :admin_forms_path, section: :main, position: 20, built_in: true
|
|
75
|
+
register "Categories", path: :admin_categories_path, section: :main, position: 25, built_in: true
|
|
76
|
+
register "Tags", path: :admin_tags_path, section: :main, position: 26, built_in: true
|
|
77
|
+
register "Media Library", path: :admin_media_attachments_path, section: :main, position: 30, built_in: true
|
|
78
|
+
register "Code Injection", path: :code_injection_admin_settings_path, section: :settings, position: 0, built_in: true
|
|
79
|
+
register "Themes", path: :admin_themes_path, section: :settings, position: 5, built_in: true
|
|
80
|
+
register "Plugins", path: :admin_plugins_path, section: :settings, position: 10, built_in: true
|
|
81
|
+
|
|
82
|
+
@built_ins_registered = true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Resets the idempotency guard so {.register_built_ins!} can run again.
|
|
86
|
+
#
|
|
87
|
+
# @return [void]
|
|
88
|
+
def reset_built_ins_flag!
|
|
89
|
+
@built_ins_registered = false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def items_store
|
|
95
|
+
@items ||= []
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
# Stores all engine-wide settings. Created automatically by {NotPressed.configuration}.
|
|
5
|
+
#
|
|
6
|
+
# Configure via the block form:
|
|
7
|
+
# NotPressed.configure do |config|
|
|
8
|
+
# config.site_name = "My Blog"
|
|
9
|
+
# config.per_page = 10
|
|
10
|
+
# end
|
|
11
|
+
class Configuration
|
|
12
|
+
# @return [String] URL path prefix for admin routes (default: "not_pressed")
|
|
13
|
+
# @return [String] site display name (default: "My Site")
|
|
14
|
+
# @return [Integer] pagination size (default: 25)
|
|
15
|
+
# @return [Boolean] whether to auto-discover navigable routes (default: true)
|
|
16
|
+
# @return [Boolean] whether to cache navigation trees (default: true)
|
|
17
|
+
# @return [Proc, nil] authentication callback for admin access
|
|
18
|
+
# @return [Proc, nil] callback returning the current admin user
|
|
19
|
+
# @return [String] redirect path when admin auth fails (default: "/")
|
|
20
|
+
# @return [Array<String>] available layout names (default: ["default"])
|
|
21
|
+
# @return [String] separator between page title and site name in SEO tags (default: " | ")
|
|
22
|
+
# @return [String, nil] custom robots.txt content
|
|
23
|
+
# @return [String, nil] HTML code injected into every page head
|
|
24
|
+
# @return [String, nil] HTML code injected into every page body
|
|
25
|
+
# @return [String] name of the default theme (default: "Starter")
|
|
26
|
+
attr_accessor :admin_path, :site_name, :per_page,
|
|
27
|
+
:auto_discover_navigation, :cache_navigation,
|
|
28
|
+
:admin_authentication, :admin_current_user,
|
|
29
|
+
:admin_unauthorized_redirect, :available_layouts,
|
|
30
|
+
:seo_title_separator, :robots_txt,
|
|
31
|
+
:global_head_code, :global_body_code, :default_theme
|
|
32
|
+
|
|
33
|
+
# @return [Hash{String => Navigation::Menu}] DSL-defined navigation menus (read-only)
|
|
34
|
+
attr_reader :menus
|
|
35
|
+
|
|
36
|
+
def initialize
|
|
37
|
+
@admin_path = "not_pressed"
|
|
38
|
+
@site_name = "My Site"
|
|
39
|
+
@per_page = 25
|
|
40
|
+
@auto_discover_navigation = true
|
|
41
|
+
@cache_navigation = true
|
|
42
|
+
@menus = {}
|
|
43
|
+
@admin_authentication = nil
|
|
44
|
+
@admin_current_user = nil
|
|
45
|
+
@admin_unauthorized_redirect = "/"
|
|
46
|
+
@available_layouts = ["default"]
|
|
47
|
+
@seo_title_separator = " | "
|
|
48
|
+
@robots_txt = nil
|
|
49
|
+
@global_head_code = nil
|
|
50
|
+
@global_body_code = nil
|
|
51
|
+
@default_theme = "Starter"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Defines a navigation menu using the {Navigation::Menu} DSL.
|
|
55
|
+
#
|
|
56
|
+
# @param name [Symbol, String] menu identifier
|
|
57
|
+
# @yield DSL block passed to {Navigation::Menu.build}
|
|
58
|
+
# @return [Navigation::Menu]
|
|
59
|
+
def menu(name, &block)
|
|
60
|
+
@menus[name.to_s] = Navigation::Menu.build(name.to_s, &block)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns the union of configured layouts and any layouts defined by the active theme.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<String>] all available layout names
|
|
66
|
+
def effective_layouts
|
|
67
|
+
layouts = @available_layouts.dup
|
|
68
|
+
theme = NotPressed::ThemeRegistry.active
|
|
69
|
+
if theme
|
|
70
|
+
theme.theme_layouts.each do |tl|
|
|
71
|
+
layouts << tl[:name].to_s unless layouts.include?(tl[:name].to_s)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
layouts
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class ContentType
|
|
5
|
+
attr_reader :name, :label, :icon, :description, :allowed_blocks, :has_slug, :has_parent, :custom_fields
|
|
6
|
+
|
|
7
|
+
def initialize(name:, label:, icon: nil, description: nil, allowed_blocks: nil, has_slug: true, has_parent: true, custom_fields: [])
|
|
8
|
+
@name = name.to_s
|
|
9
|
+
@label = label
|
|
10
|
+
@icon = icon
|
|
11
|
+
@description = description
|
|
12
|
+
@allowed_blocks = allowed_blocks
|
|
13
|
+
@has_slug = has_slug
|
|
14
|
+
@has_parent = has_parent
|
|
15
|
+
@custom_fields = custom_fields
|
|
16
|
+
freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_s
|
|
20
|
+
name
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class ContentTypeBuilder
|
|
5
|
+
FieldDefinition = Struct.new(:key, :type, :label, :required, :default, keyword_init: true)
|
|
6
|
+
|
|
7
|
+
def initialize(name)
|
|
8
|
+
@name = name
|
|
9
|
+
@options = {}
|
|
10
|
+
@fields = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def label(val)
|
|
14
|
+
@options[:label] = val
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def icon(val)
|
|
18
|
+
@options[:icon] = val
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def description(val)
|
|
22
|
+
@options[:description] = val
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def allowed_blocks(*types)
|
|
26
|
+
@options[:allowed_blocks] = types.map(&:to_s)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_slug(val)
|
|
30
|
+
@options[:has_slug] = val
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def has_parent(val)
|
|
34
|
+
@options[:has_parent] = val
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def field(key, type, **options)
|
|
38
|
+
@fields << FieldDefinition.new(
|
|
39
|
+
key: key,
|
|
40
|
+
type: type,
|
|
41
|
+
label: options[:label],
|
|
42
|
+
required: options.fetch(:required, false),
|
|
43
|
+
default: options[:default]
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def register!
|
|
48
|
+
ContentTypeRegistry.register(@name, **@options, custom_fields: @fields)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class ContentTypeRegistry
|
|
5
|
+
class NotFound < StandardError; end
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
def registry
|
|
9
|
+
@registry ||= {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def register(name, **options)
|
|
13
|
+
type = ContentType.new(name: name, **options)
|
|
14
|
+
registry[type.name] = type
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find(name)
|
|
18
|
+
registry[name.to_s] || raise(NotFound, "Content type '#{name}' not found")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def all
|
|
22
|
+
registry.values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def names
|
|
26
|
+
registry.keys
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reset!
|
|
30
|
+
@registry = {}
|
|
31
|
+
register_defaults
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def register_defaults
|
|
37
|
+
register("page", label: "Page", icon: "file-text", description: "A standard content page")
|
|
38
|
+
register("blog_post", label: "Blog Post", icon: "pen-line", description: "A blog post entry")
|
|
39
|
+
register("gallery", label: "Gallery", icon: "images", description: "An image gallery page")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
register_defaults
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace NotPressed
|
|
6
|
+
|
|
7
|
+
config.generators do |g|
|
|
8
|
+
g.orm :active_record
|
|
9
|
+
g.test_framework :rspec
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
initializer "not_pressed.mime_types" do
|
|
13
|
+
Mime::Type.register "text/vnd.turbo-stream.html", :turbo_stream unless Mime::Type.lookup_by_extension(:turbo_stream)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "not_pressed.assets" do |app|
|
|
17
|
+
if app.config.respond_to?(:assets)
|
|
18
|
+
app.config.assets.precompile += %w[not_pressed/application.css not_pressed/admin.css not_pressed/themes/starter.css not_pressed/gallery.css not_pressed/lightbox.js]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "not_pressed.active_storage_check" do
|
|
23
|
+
unless defined?(ActiveStorage)
|
|
24
|
+
Rails.logger.warn(
|
|
25
|
+
"NotPressed: Active Storage is not loaded. " \
|
|
26
|
+
"Media attachments require Active Storage. " \
|
|
27
|
+
"Add `require \"active_storage/engine\"` to your application.rb."
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
initializer "not_pressed.gallery_block" do
|
|
33
|
+
config.after_initialize do
|
|
34
|
+
NotPressed::ContentBlock.register_type("gallery")
|
|
35
|
+
|
|
36
|
+
gallery_renderer = lambda do |content, _block|
|
|
37
|
+
images = content["images"] || []
|
|
38
|
+
columns = content["columns"].to_i
|
|
39
|
+
columns = 3 unless (2..4).cover?(columns)
|
|
40
|
+
|
|
41
|
+
if images.empty?
|
|
42
|
+
return %(<div class="np-content-gallery" data-columns="#{columns}"></div>)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
items_html = images.map do |img|
|
|
46
|
+
media_id = img["media_id"]
|
|
47
|
+
caption = ERB::Util.html_escape(img["caption"].to_s)
|
|
48
|
+
url = ""
|
|
49
|
+
|
|
50
|
+
if media_id.present?
|
|
51
|
+
media = NotPressed::MediaAttachment.find_by(id: media_id)
|
|
52
|
+
if media&.file&.attached?
|
|
53
|
+
url = Rails.application.routes.url_helpers.rails_blob_path(media.file, disposition: "inline", only_path: true)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
item_html = %(<div class="np-gallery-item">)
|
|
58
|
+
item_html += %(<img src="#{ERB::Util.html_escape(url)}" alt="#{caption}" loading="lazy">) unless url.empty?
|
|
59
|
+
item_html += %(<div class="np-gallery-caption">#{caption}</div>) unless caption.empty?
|
|
60
|
+
item_html += %(</div>)
|
|
61
|
+
item_html
|
|
62
|
+
end.join("\n")
|
|
63
|
+
|
|
64
|
+
%(<div class="np-content-gallery" data-columns="#{columns}" style="--np-gallery-columns: #{columns};">#{items_html}</div>)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
NotPressed::Rendering::RendererRegistry.register("gallery", gallery_renderer)
|
|
68
|
+
NotPressed::Rendering::RendererRegistry.register_editor("gallery", "not_pressed/admin/content_blocks/editors/gallery")
|
|
69
|
+
NotPressed::Rendering::RendererRegistry.register_default_content("gallery", { "images" => [], "columns" => 3 })
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
initializer "not_pressed.admin_menu" do
|
|
74
|
+
NotPressed::Admin::MenuRegistry.register_built_ins!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
initializer "not_pressed.theme_autoload", before: :set_autoload_paths do |app|
|
|
78
|
+
# Engine's own themes
|
|
79
|
+
engine_themes = root.join("app", "themes")
|
|
80
|
+
if engine_themes.exist?
|
|
81
|
+
app.config.autoload_paths << engine_themes.to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Host app themes
|
|
85
|
+
themes_path = app.root.join("app", "themes")
|
|
86
|
+
if themes_path.exist?
|
|
87
|
+
app.config.autoload_paths << themes_path.to_s
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
initializer "not_pressed.plugin_autoload" do |app|
|
|
92
|
+
plugins_path = app.root.join("app", "plugins")
|
|
93
|
+
if plugins_path.exist?
|
|
94
|
+
app.config.autoload_paths << plugins_path.to_s
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
initializer "not_pressed.themes" do
|
|
99
|
+
config.after_initialize do
|
|
100
|
+
# Discover and register all Theme subclasses
|
|
101
|
+
NotPressed::Theme.subclasses.each do |klass|
|
|
102
|
+
next unless klass.plugin_name
|
|
103
|
+
next if NotPressed::ThemeRegistry.available.include?(klass.plugin_name)
|
|
104
|
+
NotPressed::ThemeRegistry.register(klass)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Auto-activate default theme if none active
|
|
108
|
+
default_theme = NotPressed.configuration.default_theme
|
|
109
|
+
if default_theme && NotPressed::ThemeRegistry.active.nil?
|
|
110
|
+
begin
|
|
111
|
+
NotPressed::ThemeRegistry.activate(default_theme)
|
|
112
|
+
rescue NotPressed::ThemeRegistry::NotFound
|
|
113
|
+
# Default theme not available, no action needed
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
initializer "not_pressed.plugins" do
|
|
120
|
+
config.after_initialize do
|
|
121
|
+
NotPressed::PluginManager.discover
|
|
122
|
+
NotPressed::PluginManager.enable_all
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
initializer "not_pressed.append_migrations" do |app|
|
|
127
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
128
|
+
app.config.paths["db/migrate"] << expanded_path unless app.config.paths["db/migrate"].include?(expanded_path)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|