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,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class MediaAttachmentsController < BaseController
|
|
6
|
+
PER_PAGE = 25
|
|
7
|
+
|
|
8
|
+
before_action :set_media_attachment, only: %i[edit update destroy]
|
|
9
|
+
|
|
10
|
+
def picker
|
|
11
|
+
@media = NotPressed::MediaAttachment.images.recent.limit(50)
|
|
12
|
+
render partial: "picker", layout: false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def index
|
|
16
|
+
page_title "Media Library"
|
|
17
|
+
@media = filtered_media
|
|
18
|
+
.recent
|
|
19
|
+
.limit(PER_PAGE)
|
|
20
|
+
.offset(page_offset)
|
|
21
|
+
@total_count = filtered_media.count
|
|
22
|
+
@current_page = current_page
|
|
23
|
+
@total_pages = (@total_count.to_f / PER_PAGE).ceil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create
|
|
27
|
+
files = Array(params[:files])
|
|
28
|
+
|
|
29
|
+
files.each do |file|
|
|
30
|
+
attachment = NotPressed::MediaAttachment.new
|
|
31
|
+
attachment.file.attach(file)
|
|
32
|
+
attachment.title = file.original_filename.sub(/\.[^.]+\z/, "").titleize
|
|
33
|
+
attachment.save
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
redirect_to admin_media_attachments_path, notice: "Media uploaded successfully."
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def edit
|
|
40
|
+
page_title "Edit Media: #{@media_attachment.title}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def update
|
|
44
|
+
if @media_attachment.update(media_attachment_params)
|
|
45
|
+
redirect_to edit_admin_media_attachment_path(@media_attachment), notice: "Media updated successfully."
|
|
46
|
+
else
|
|
47
|
+
page_title "Edit Media: #{@media_attachment.title}"
|
|
48
|
+
render :edit, status: :unprocessable_entity
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def destroy
|
|
53
|
+
@media_attachment.destroy
|
|
54
|
+
redirect_to admin_media_attachments_path, notice: "Media deleted successfully."
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def filtered_media
|
|
60
|
+
scope = NotPressed::MediaAttachment.all
|
|
61
|
+
|
|
62
|
+
case params[:type]
|
|
63
|
+
when "images"
|
|
64
|
+
scope = scope.images
|
|
65
|
+
when "documents"
|
|
66
|
+
scope = scope.documents
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if params[:q].present?
|
|
70
|
+
query = "%#{params[:q]}%"
|
|
71
|
+
scope = scope.where("title LIKE :q OR alt_text LIKE :q", q: query)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
scope
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def current_page
|
|
78
|
+
[(params[:page] || 1).to_i, 1].max
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def page_offset
|
|
82
|
+
(current_page - 1) * PER_PAGE
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def set_media_attachment
|
|
86
|
+
@media_attachment = NotPressed::MediaAttachment.find(params[:id])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def media_attachment_params
|
|
90
|
+
params.require(:media_attachment).permit(:title, :alt_text)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class PagesController < BaseController
|
|
6
|
+
before_action :set_page, only: %i[edit update destroy preview duplicate]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
page_title "Pages"
|
|
10
|
+
@pages = NotPressed::Page.roots.ordered.includes(children: :children)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def new
|
|
14
|
+
page_title "New Page"
|
|
15
|
+
@page = NotPressed::Page.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create
|
|
19
|
+
@page = NotPressed::Page.new(page_params)
|
|
20
|
+
|
|
21
|
+
if @page.save
|
|
22
|
+
redirect_to edit_admin_page_path(@page), notice: "Page created successfully."
|
|
23
|
+
else
|
|
24
|
+
page_title "New Page"
|
|
25
|
+
render :new, status: :unprocessable_entity
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
page_title "Edit: #{@page.title}"
|
|
31
|
+
@blocks = @page.content_blocks.ordered
|
|
32
|
+
@versions = @page.page_versions.ordered.limit(10)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update
|
|
36
|
+
if @page.update(page_params)
|
|
37
|
+
redirect_to edit_admin_page_path(@page), notice: "Page updated successfully."
|
|
38
|
+
else
|
|
39
|
+
page_title "Edit: #{@page.title}"
|
|
40
|
+
@blocks = @page.content_blocks.ordered
|
|
41
|
+
render :edit, status: :unprocessable_entity
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def preview
|
|
46
|
+
render layout: false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def duplicate
|
|
50
|
+
new_page = NotPressed::Page.new(
|
|
51
|
+
title: "#{@page.title} (Copy)",
|
|
52
|
+
status: :draft,
|
|
53
|
+
content_type: @page.content_type,
|
|
54
|
+
meta_description: @page.meta_description,
|
|
55
|
+
parent_id: @page.parent_id,
|
|
56
|
+
position: @page.position + 1
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if new_page.save
|
|
60
|
+
@page.content_blocks.ordered.each do |block|
|
|
61
|
+
new_page.content_blocks.create!(
|
|
62
|
+
block_type: block.block_type,
|
|
63
|
+
content: block.content,
|
|
64
|
+
position: block.position
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
redirect_to edit_admin_page_path(new_page), notice: "Page duplicated successfully."
|
|
69
|
+
else
|
|
70
|
+
redirect_to admin_pages_path, alert: "Failed to duplicate page."
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def reorder
|
|
75
|
+
entries = params.permit(pages: [:id, :position, :parent_id]).fetch(:pages, [])
|
|
76
|
+
|
|
77
|
+
ActiveRecord::Base.transaction do
|
|
78
|
+
entries.each do |entry|
|
|
79
|
+
page = NotPressed::Page.find_by(id: entry[:id])
|
|
80
|
+
next unless page
|
|
81
|
+
|
|
82
|
+
new_parent_id = entry[:parent_id].presence
|
|
83
|
+
if new_parent_id.present?
|
|
84
|
+
next if new_parent_id.to_i == page.id
|
|
85
|
+
next if descendant_ids(page).include?(new_parent_id.to_i)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
page.update!(position: entry[:position], parent_id: new_parent_id)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
head :ok
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def destroy
|
|
96
|
+
@page.destroy
|
|
97
|
+
redirect_to admin_pages_path, notice: "Page deleted successfully."
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def set_page
|
|
103
|
+
@page = NotPressed::Page.find(params[:id])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def descendant_ids(page)
|
|
107
|
+
ids = []
|
|
108
|
+
children = page.children.to_a
|
|
109
|
+
while children.any?
|
|
110
|
+
child = children.shift
|
|
111
|
+
ids << child.id
|
|
112
|
+
children.concat(child.children.to_a)
|
|
113
|
+
end
|
|
114
|
+
ids
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def page_params
|
|
118
|
+
params.require(:page).permit(:title, :slug, :status, :meta_description, :content_type, :parent_id, :position, :scheduled_at, :meta_title, :og_image_url, :layout, :visibility, :page_password, :canonical_url, :meta_robots, :og_type, :twitter_card, :head_code, :body_code, :category_id, tag_ids: [])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class PluginsController < BaseController
|
|
6
|
+
before_action :set_plugin, only: [:show, :toggle, :update_settings]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
page_title "Plugins"
|
|
10
|
+
@plugins = NotPressed::PluginManager.registered
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def export
|
|
14
|
+
info = NotPressed::PluginManager.find(params[:id])
|
|
15
|
+
plugin_name = info[:klass].plugin_name
|
|
16
|
+
|
|
17
|
+
temp_dir = Dir.mktmpdir("np_plugin_export")
|
|
18
|
+
zip_path = NotPressed::PluginPackager.new(plugin_name).package(temp_dir)
|
|
19
|
+
|
|
20
|
+
send_file zip_path,
|
|
21
|
+
type: "application/zip",
|
|
22
|
+
disposition: "attachment",
|
|
23
|
+
filename: File.basename(zip_path)
|
|
24
|
+
|
|
25
|
+
# Schedule cleanup after response is sent
|
|
26
|
+
response.headers["X-Temp-Dir"] = temp_dir
|
|
27
|
+
rescue NotPressed::PluginManager::NotFound
|
|
28
|
+
redirect_to admin_plugins_path, alert: "Plugin not found."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def import
|
|
32
|
+
unless params[:plugin_archive].present?
|
|
33
|
+
redirect_to admin_plugins_path, alert: "Please select a plugin archive to import."
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
temp_file = Tempfile.new(["plugin_import", ".zip"])
|
|
38
|
+
temp_file.binmode
|
|
39
|
+
temp_file.write(params[:plugin_archive].read)
|
|
40
|
+
temp_file.close
|
|
41
|
+
|
|
42
|
+
result = NotPressed::PluginImporter.new(temp_file.path).import!
|
|
43
|
+
redirect_to admin_plugins_path, notice: "Plugin '#{result[:name]}' v#{result[:version]} imported successfully."
|
|
44
|
+
rescue NotPressed::PluginImporter::InvalidArchive => e
|
|
45
|
+
redirect_to admin_plugins_path, alert: "Import failed: #{e.message}"
|
|
46
|
+
rescue NotPressed::PluginImporter::DuplicatePlugin => e
|
|
47
|
+
redirect_to admin_plugins_path, alert: "Import failed: #{e.message}"
|
|
48
|
+
ensure
|
|
49
|
+
temp_file&.unlink
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def show
|
|
53
|
+
page_title @plugin_class.plugin_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def toggle
|
|
57
|
+
name = @plugin_class.plugin_name
|
|
58
|
+
if NotPressed::PluginManager.enabled?(name)
|
|
59
|
+
NotPressed::PluginManager.disable(name)
|
|
60
|
+
redirect_to admin_plugin_path(plugin_name: name), notice: "Plugin '#{name}' has been disabled."
|
|
61
|
+
else
|
|
62
|
+
NotPressed::PluginManager.enable(name)
|
|
63
|
+
redirect_to admin_plugin_path(plugin_name: name), notice: "Plugin '#{name}' has been enabled."
|
|
64
|
+
end
|
|
65
|
+
rescue NotPressed::PluginManager::DependencyError => e
|
|
66
|
+
redirect_to admin_plugin_path(plugin_name: name), alert: e.message
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_settings
|
|
70
|
+
name = @plugin_class.plugin_name
|
|
71
|
+
schema = @plugin_class.plugin_settings_schema || []
|
|
72
|
+
instance = plugin_instance
|
|
73
|
+
|
|
74
|
+
errors = []
|
|
75
|
+
schema.each do |field|
|
|
76
|
+
key = field[:key].to_s
|
|
77
|
+
value = params.dig(:plugin_settings, key)
|
|
78
|
+
|
|
79
|
+
if field[:required] && (value.nil? || value.to_s.strip.empty?)
|
|
80
|
+
errors << "#{field[:label] || key.humanize} is required"
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
value = cast_setting_value(value, field[:type])
|
|
85
|
+
instance.set_setting(key, value)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if errors.any?
|
|
89
|
+
redirect_to admin_plugin_path(plugin_name: name), alert: errors.join(", ")
|
|
90
|
+
else
|
|
91
|
+
redirect_to admin_plugin_path(plugin_name: name), notice: "Settings saved successfully."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def set_plugin
|
|
98
|
+
info = NotPressed::PluginManager.find(params[:plugin_name])
|
|
99
|
+
@plugin_class = info[:klass]
|
|
100
|
+
@plugin_info = info
|
|
101
|
+
rescue NotPressed::PluginManager::NotFound
|
|
102
|
+
redirect_to admin_plugins_path, alert: "Plugin not found."
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def plugin_instance
|
|
106
|
+
@plugin_info[:instance] || @plugin_class.new
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def cast_setting_value(value, type)
|
|
110
|
+
case type
|
|
111
|
+
when :boolean
|
|
112
|
+
value == "1" || value == "true"
|
|
113
|
+
when :integer
|
|
114
|
+
value.to_s.strip.empty? ? nil : value.to_i
|
|
115
|
+
else
|
|
116
|
+
value
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class SettingsController < BaseController
|
|
6
|
+
def code_injection
|
|
7
|
+
page_title "Code Injection"
|
|
8
|
+
@global_head_code = NotPressed::Setting.get("global_head_code") || ""
|
|
9
|
+
@global_body_code = NotPressed::Setting.get("global_body_code") || ""
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def update_code_injection
|
|
13
|
+
NotPressed::Setting.set("global_head_code", params[:global_head_code])
|
|
14
|
+
NotPressed::Setting.set("global_body_code", params[:global_body_code])
|
|
15
|
+
redirect_to code_injection_admin_settings_path, notice: "Code injection settings saved."
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class TagsController < BaseController
|
|
6
|
+
def index
|
|
7
|
+
page_title "Tags"
|
|
8
|
+
@tags = NotPressed::Tag.order(:name)
|
|
9
|
+
@tag = NotPressed::Tag.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
@tag = NotPressed::Tag.new(tag_params)
|
|
14
|
+
|
|
15
|
+
if @tag.save
|
|
16
|
+
redirect_to admin_tags_path, notice: "Tag created successfully."
|
|
17
|
+
else
|
|
18
|
+
page_title "Tags"
|
|
19
|
+
@tags = NotPressed::Tag.order(:name)
|
|
20
|
+
render :index, status: :unprocessable_entity
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def destroy
|
|
25
|
+
@tag = NotPressed::Tag.find(params[:id])
|
|
26
|
+
@tag.destroy
|
|
27
|
+
redirect_to admin_tags_path, notice: "Tag deleted successfully."
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def tag_params
|
|
33
|
+
params.require(:tag).permit(:name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
module Admin
|
|
5
|
+
class ThemesController < BaseController
|
|
6
|
+
before_action :set_theme, only: [:show, :activate, :deactivate, :update_colors]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
page_title "Themes"
|
|
10
|
+
@themes = NotPressed::ThemeRegistry.available.map { |name| [name, NotPressed::ThemeRegistry.find(name)] }
|
|
11
|
+
@active_theme = NotPressed::ThemeRegistry.active
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
page_title @theme_class.plugin_name
|
|
16
|
+
@colors = @theme_class.theme_color_scheme
|
|
17
|
+
@color_values = load_color_values
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def activate
|
|
21
|
+
name = @theme_class.plugin_name
|
|
22
|
+
NotPressed::ThemeRegistry.activate(name)
|
|
23
|
+
redirect_to admin_themes_path, notice: "Theme '#{name}' has been activated."
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def deactivate
|
|
27
|
+
NotPressed::ThemeRegistry.deactivate
|
|
28
|
+
redirect_to admin_themes_path, notice: "Theme has been deactivated."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update_colors
|
|
32
|
+
name = @theme_class.plugin_name
|
|
33
|
+
colors = @theme_class.theme_color_scheme
|
|
34
|
+
|
|
35
|
+
colors.each do |color|
|
|
36
|
+
key = "theme.#{name}.color_#{color[:key]}"
|
|
37
|
+
value = params.dig(:theme_colors, color[:key].to_s)
|
|
38
|
+
if value.present?
|
|
39
|
+
NotPressed::Setting.set(key, value)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
redirect_to admin_theme_path(theme_name: name), notice: "Colors saved successfully."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def export
|
|
47
|
+
info = NotPressed::ThemeRegistry.find(params[:id])
|
|
48
|
+
theme_name = info[:klass].plugin_name
|
|
49
|
+
|
|
50
|
+
temp_dir = Dir.mktmpdir("np_theme_export")
|
|
51
|
+
zip_path = NotPressed::ThemePackager.new(theme_name).package(temp_dir)
|
|
52
|
+
|
|
53
|
+
send_file zip_path,
|
|
54
|
+
type: "application/zip",
|
|
55
|
+
disposition: "attachment",
|
|
56
|
+
filename: File.basename(zip_path)
|
|
57
|
+
|
|
58
|
+
# Schedule cleanup after response is sent
|
|
59
|
+
response.headers["X-Temp-Dir"] = temp_dir
|
|
60
|
+
rescue NotPressed::ThemeRegistry::NotFound
|
|
61
|
+
redirect_to admin_themes_path, alert: "Theme not found."
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def import
|
|
65
|
+
unless params[:theme_archive].present?
|
|
66
|
+
redirect_to admin_themes_path, alert: "Please select a theme archive to import."
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
temp_file = Tempfile.new(["theme_import", ".zip"])
|
|
71
|
+
temp_file.binmode
|
|
72
|
+
temp_file.write(params[:theme_archive].read)
|
|
73
|
+
temp_file.close
|
|
74
|
+
|
|
75
|
+
result = NotPressed::ThemeImporter.new(temp_file.path).import!
|
|
76
|
+
redirect_to admin_themes_path, notice: "Theme '#{result[:name]}' v#{result[:version]} imported successfully."
|
|
77
|
+
rescue NotPressed::ThemeImporter::InvalidArchive => e
|
|
78
|
+
redirect_to admin_themes_path, alert: "Import failed: #{e.message}"
|
|
79
|
+
rescue NotPressed::ThemeImporter::DuplicateTheme => e
|
|
80
|
+
redirect_to admin_themes_path, alert: "Import failed: #{e.message}"
|
|
81
|
+
ensure
|
|
82
|
+
temp_file&.unlink
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def set_theme
|
|
88
|
+
info = NotPressed::ThemeRegistry.find(params[:theme_name])
|
|
89
|
+
@theme_class = info[:klass]
|
|
90
|
+
@theme_info = info
|
|
91
|
+
rescue NotPressed::ThemeRegistry::NotFound
|
|
92
|
+
redirect_to admin_themes_path, alert: "Theme not found."
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def load_color_values
|
|
96
|
+
name = @theme_class.plugin_name
|
|
97
|
+
@theme_class.theme_color_scheme.each_with_object({}) do |color, hash|
|
|
98
|
+
key = "theme.#{name}.color_#{color[:key]}"
|
|
99
|
+
hash[color[:key]] = NotPressed::Setting.get(key) || color[:default]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class BlogController < ApplicationController
|
|
5
|
+
include NotPressed::ContentHelper
|
|
6
|
+
|
|
7
|
+
layout :resolve_layout
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@page_num = [params.fetch(:page, 1).to_i, 1].max
|
|
11
|
+
@posts = blog_posts.order(published_at: :desc).limit(12).offset((@page_num - 1) * 12)
|
|
12
|
+
@total_count = blog_posts.count
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def show
|
|
16
|
+
@post = blog_posts.find_by!(slug: params[:slug])
|
|
17
|
+
@prev_post = blog_posts.where("published_at < ?", @post.published_at).order(published_at: :desc).first
|
|
18
|
+
@next_post = blog_posts.where("published_at > ?", @post.published_at).order(published_at: :asc).first
|
|
19
|
+
rescue ActiveRecord::RecordNotFound
|
|
20
|
+
redirect_to not_pressed.blog_path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def category
|
|
24
|
+
@category = NotPressed::Category.find_by!(slug: params[:slug])
|
|
25
|
+
@page_num = [params.fetch(:page, 1).to_i, 1].max
|
|
26
|
+
@posts = blog_posts.where(category: @category).order(published_at: :desc).limit(12).offset((@page_num - 1) * 12)
|
|
27
|
+
@total_count = blog_posts.where(category: @category).count
|
|
28
|
+
render :index
|
|
29
|
+
rescue ActiveRecord::RecordNotFound
|
|
30
|
+
redirect_to not_pressed.blog_path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def tag
|
|
34
|
+
@tag = NotPressed::Tag.find_by!(slug: params[:slug])
|
|
35
|
+
@page_num = [params.fetch(:page, 1).to_i, 1].max
|
|
36
|
+
@posts = blog_posts.joins(:taggings).where(not_pressed_taggings: { tag_id: @tag.id }).order(published_at: :desc).limit(12).offset((@page_num - 1) * 12)
|
|
37
|
+
@total_count = blog_posts.joins(:taggings).where(not_pressed_taggings: { tag_id: @tag.id }).count
|
|
38
|
+
render :index
|
|
39
|
+
rescue ActiveRecord::RecordNotFound
|
|
40
|
+
redirect_to not_pressed.blog_path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def archive
|
|
44
|
+
@year = params[:year].to_i
|
|
45
|
+
@month = params[:month]&.to_i
|
|
46
|
+
@page_num = [params.fetch(:page, 1).to_i, 1].max
|
|
47
|
+
|
|
48
|
+
scope = blog_posts.where("EXTRACT(YEAR FROM published_at) = ?", @year)
|
|
49
|
+
scope = scope.where("EXTRACT(MONTH FROM published_at) = ?", @month) if @month
|
|
50
|
+
|
|
51
|
+
@posts = scope.order(published_at: :desc).limit(12).offset((@page_num - 1) * 12)
|
|
52
|
+
@total_count = scope.count
|
|
53
|
+
render :index
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def feed
|
|
57
|
+
@posts = blog_posts.order(published_at: :desc).limit(20)
|
|
58
|
+
respond_to do |format|
|
|
59
|
+
format.rss { render layout: false }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def blog_posts
|
|
66
|
+
NotPressed::Page.blog_posts.published
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def resolve_layout
|
|
70
|
+
return false if action_name == "feed"
|
|
71
|
+
|
|
72
|
+
theme = NotPressed::ThemeRegistry.active
|
|
73
|
+
if theme
|
|
74
|
+
primary = theme.primary_layout
|
|
75
|
+
primary ? "not_pressed/#{primary[:name]}" : "not_pressed/page"
|
|
76
|
+
else
|
|
77
|
+
"not_pressed/page"
|
|
78
|
+
end
|
|
79
|
+
rescue ActionView::MissingTemplate
|
|
80
|
+
"not_pressed/page"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class FormSubmissionsController < ApplicationController
|
|
5
|
+
def create
|
|
6
|
+
@form = NotPressed::Form.find_by(slug: params[:form_slug])
|
|
7
|
+
|
|
8
|
+
unless @form
|
|
9
|
+
render plain: "Form not found", status: :not_found
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
unless @form.status_active?
|
|
14
|
+
render plain: "This form is not currently accepting submissions", status: :gone
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
data = build_submission_data
|
|
19
|
+
@submission = @form.form_submissions.build(data: data)
|
|
20
|
+
|
|
21
|
+
if @submission.save
|
|
22
|
+
send_notification_email(@submission)
|
|
23
|
+
|
|
24
|
+
if @form.redirect_url.present?
|
|
25
|
+
redirect_to @form.redirect_url, allow_other_host: true
|
|
26
|
+
else
|
|
27
|
+
render inline: thank_you_html(@form), layout: false
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
render plain: "There was an error processing your submission", status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_submission_data
|
|
37
|
+
submission_params = params[:submission] || {}
|
|
38
|
+
data = {}
|
|
39
|
+
|
|
40
|
+
@form.form_fields.ordered.each do |field|
|
|
41
|
+
key = field.label.parameterize(separator: "_")
|
|
42
|
+
value = submission_params[key]
|
|
43
|
+
data[field.label] = value.is_a?(Array) ? value.join(", ") : value.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
data
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def send_notification_email(submission)
|
|
50
|
+
return unless submission.form.email_recipient.present?
|
|
51
|
+
|
|
52
|
+
NotPressed::FormMailer.submission_notification(submission).deliver_later
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def thank_you_html(form)
|
|
56
|
+
message = ERB::Util.html_escape(form.success_message)
|
|
57
|
+
<<~HTML
|
|
58
|
+
<!DOCTYPE html>
|
|
59
|
+
<html>
|
|
60
|
+
<head><title>Thank You</title></head>
|
|
61
|
+
<body>
|
|
62
|
+
<div class="np-form-thank-you">
|
|
63
|
+
<p>#{message}</p>
|
|
64
|
+
</div>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
67
|
+
HTML
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NotPressed
|
|
4
|
+
class PagesController < ApplicationController
|
|
5
|
+
layout :resolve_layout
|
|
6
|
+
|
|
7
|
+
def show
|
|
8
|
+
@page = NotPressed::Page.published.find_by!(slug: params[:slug])
|
|
9
|
+
rescue ActiveRecord::RecordNotFound
|
|
10
|
+
render plain: "Not Found", status: :not_found
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def resolve_layout
|
|
16
|
+
theme = NotPressed::ThemeRegistry.active
|
|
17
|
+
if theme
|
|
18
|
+
page_layout = @page&.layout
|
|
19
|
+
if page_layout.present? && page_layout != "default"
|
|
20
|
+
"not_pressed/#{page_layout}"
|
|
21
|
+
else
|
|
22
|
+
primary = theme.primary_layout
|
|
23
|
+
primary ? "not_pressed/#{primary[:name]}" : "not_pressed/page"
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
if @page&.layout.present? && @page.layout != "default"
|
|
27
|
+
"not_pressed/#{@page.layout}"
|
|
28
|
+
else
|
|
29
|
+
"not_pressed/page"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
rescue ActionView::MissingTemplate
|
|
33
|
+
"not_pressed/page"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|