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,22 @@
|
|
|
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
|
+
<main class="np-page-main">
|
|
17
|
+
<%= yield %>
|
|
18
|
+
</main>
|
|
19
|
+
<%= javascript_include_tag "not_pressed/lightbox" %>
|
|
20
|
+
<%= body_injection(@page) %>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<div class="np-actions" style="margin-bottom: 1.5rem;">
|
|
2
|
+
<h2 style="margin: 0;">Add Category</h2>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<%= form_with model: @category, url: admin_categories_path, class: "np-form", style: "margin-bottom: 2rem;" do |f| %>
|
|
6
|
+
<% if @category.errors.any? %>
|
|
7
|
+
<div class="np-form-errors">
|
|
8
|
+
<ul>
|
|
9
|
+
<% @category.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: "Category name" %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="np-form-group" style="margin-bottom: 0; flex: 2;">
|
|
22
|
+
<%= f.label :description, class: "np-label" %>
|
|
23
|
+
<%= f.text_field :description, class: "np-input", placeholder: "Optional description" %>
|
|
24
|
+
</div>
|
|
25
|
+
<div>
|
|
26
|
+
<%= f.submit "Add", class: "np-btn np-btn--primary" %>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
|
|
31
|
+
<% if @categories.any? %>
|
|
32
|
+
<table class="np-table">
|
|
33
|
+
<thead>
|
|
34
|
+
<tr>
|
|
35
|
+
<th>Name</th>
|
|
36
|
+
<th>Slug</th>
|
|
37
|
+
<th>Description</th>
|
|
38
|
+
<th>Posts</th>
|
|
39
|
+
<th>Actions</th>
|
|
40
|
+
</tr>
|
|
41
|
+
</thead>
|
|
42
|
+
<tbody>
|
|
43
|
+
<% @categories.each do |category| %>
|
|
44
|
+
<tr>
|
|
45
|
+
<td><%= category.name %></td>
|
|
46
|
+
<td><code><%= category.slug %></code></td>
|
|
47
|
+
<td><%= category.description %></td>
|
|
48
|
+
<td><%= category.pages.count %></td>
|
|
49
|
+
<td>
|
|
50
|
+
<%= link_to "Delete", admin_category_path(category), data: { turbo_method: :delete, turbo_confirm: "Are you sure you want to delete this category?" }, class: "np-btn np-btn--small np-btn--danger" %>
|
|
51
|
+
</td>
|
|
52
|
+
</tr>
|
|
53
|
+
<% end %>
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
<% else %>
|
|
57
|
+
<p>No categories yet. Add one above.</p>
|
|
58
|
+
<% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<div id="<%= dom_id(block) %>" class="np-block" draggable="true" data-block-id="<%= block.id %>" data-np-sortable-target="item">
|
|
2
|
+
<div class="np-block-header">
|
|
3
|
+
<div class="np-block-header-left">
|
|
4
|
+
<span class="np-drag-handle" data-action="mousedown->np-sortable#grabHandle" title="Drag to reorder">☰</span>
|
|
5
|
+
<span class="np-block-type-label"><%= block.block_type.humanize %></span>
|
|
6
|
+
</div>
|
|
7
|
+
<%= button_to "Remove", admin_page_content_block_path(block.page, block), method: :delete, class: "np-btn np-btn--danger np-btn--small", data: { turbo_confirm: "Remove this block?" } %>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="np-block-body" data-controller="np-block-content" data-np-block-content-url-value="<%= admin_page_content_block_path(block.page, block) %>">
|
|
10
|
+
<%= form_with model: block, url: admin_page_content_block_path(block.page, block), method: :patch, class: "np-block-form" do |f| %>
|
|
11
|
+
<%= render NotPressed::Rendering::RendererRegistry.editor_partial(block.block_type), block: block %>
|
|
12
|
+
<div class="np-form-actions" style="margin-top: 0.5rem;">
|
|
13
|
+
<%= f.submit "Save", class: "np-btn np-btn--primary np-btn--small" %>
|
|
14
|
+
<span class="np-save-indicator" data-np-block-content-target="indicator"></span>
|
|
15
|
+
</div>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<dialog class="np-modal np-block-picker" data-controller="np-modal">
|
|
2
|
+
<div class="np-modal-header">
|
|
3
|
+
<h3>Add a Block</h3>
|
|
4
|
+
<button type="button" class="np-modal-close" data-action="np-modal#close">×</button>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="np-modal-body">
|
|
7
|
+
<div class="np-block-picker-grid">
|
|
8
|
+
<% block_descriptions = {
|
|
9
|
+
"text" => { emoji: "\u{1F4DD}", desc: "Rich text content" },
|
|
10
|
+
"heading" => { emoji: "\u{1F520}", desc: "Section heading" },
|
|
11
|
+
"image" => { emoji: "\u{1F5BC}", desc: "Image with caption" },
|
|
12
|
+
"video" => { emoji: "\u{1F3AC}", desc: "Embedded video" },
|
|
13
|
+
"code" => { emoji: "\u{1F4BB}", desc: "Code snippet" },
|
|
14
|
+
"quote" => { emoji: "\u{1F4AC}", desc: "Blockquote with attribution" },
|
|
15
|
+
"divider" => { emoji: "\u2796", desc: "Horizontal divider" },
|
|
16
|
+
"html" => { emoji: "\u{1F9F1}", desc: "Raw HTML block" },
|
|
17
|
+
"form" => { emoji: "\u{1F4CB}", desc: "Embeddable form" }
|
|
18
|
+
} %>
|
|
19
|
+
<% NotPressed::ContentBlock.registered_types.each do |type| %>
|
|
20
|
+
<% info = block_descriptions[type] || { emoji: "\u{1F4E6}", desc: type.humanize } %>
|
|
21
|
+
<%= button_to admin_page_content_blocks_path(page, block_type: type),
|
|
22
|
+
method: :post,
|
|
23
|
+
class: "np-block-picker-card",
|
|
24
|
+
data: { turbo_stream: true, action: "np-modal#close" } do %>
|
|
25
|
+
<span class="np-block-picker-icon"><%= info[:emoji] %></span>
|
|
26
|
+
<span class="np-block-picker-name"><%= type.humanize %></span>
|
|
27
|
+
<span class="np-block-picker-desc"><%= info[:desc] %></span>
|
|
28
|
+
<% end %>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</dialog>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= turbo_stream.remove dom_id(@block) %>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<div class="np-editor-callout">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_style_<%= block.id %>">Style</label>
|
|
4
|
+
<select
|
|
5
|
+
name="content_block[content][style]"
|
|
6
|
+
id="content_style_<%= block.id %>"
|
|
7
|
+
class="np-input"
|
|
8
|
+
data-action="change->np-block-content#save"
|
|
9
|
+
>
|
|
10
|
+
<% %w[info warning success danger].each do |style| %>
|
|
11
|
+
<option value="<%= style %>" <%= "selected" if block.content["style"] == style %>><%= style.capitalize %></option>
|
|
12
|
+
<% end %>
|
|
13
|
+
</select>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="np-editor-field">
|
|
16
|
+
<label class="np-label" for="content_title_<%= block.id %>">Title</label>
|
|
17
|
+
<input
|
|
18
|
+
type="text"
|
|
19
|
+
name="content_block[content][title]"
|
|
20
|
+
id="content_title_<%= block.id %>"
|
|
21
|
+
value="<%= block.content["title"] %>"
|
|
22
|
+
class="np-input"
|
|
23
|
+
placeholder="Callout title"
|
|
24
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
25
|
+
>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="np-editor-field">
|
|
28
|
+
<label class="np-label" for="content_text_<%= block.id %>">Text</label>
|
|
29
|
+
<textarea
|
|
30
|
+
name="content_block[content][text]"
|
|
31
|
+
id="content_text_<%= block.id %>"
|
|
32
|
+
class="np-input"
|
|
33
|
+
rows="4"
|
|
34
|
+
placeholder="Callout text content"
|
|
35
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
36
|
+
><%= block.content["text"] %></textarea>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="np-editor-code">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_language_<%= block.id %>">Language</label>
|
|
4
|
+
<select
|
|
5
|
+
name="content_block[content][language]"
|
|
6
|
+
id="content_language_<%= block.id %>"
|
|
7
|
+
class="np-input"
|
|
8
|
+
data-action="change->np-block-content#save"
|
|
9
|
+
>
|
|
10
|
+
<option value="">Select language</option>
|
|
11
|
+
<% %w[ruby javascript python html css sql bash other].each do |lang| %>
|
|
12
|
+
<option value="<%= lang %>" <%= "selected" if block.content["language"] == lang %>><%= lang.capitalize %></option>
|
|
13
|
+
<% end %>
|
|
14
|
+
</select>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="np-editor-field">
|
|
17
|
+
<label class="np-label" for="content_code_<%= block.id %>">Code</label>
|
|
18
|
+
<textarea
|
|
19
|
+
name="content_block[content][code]"
|
|
20
|
+
id="content_code_<%= block.id %>"
|
|
21
|
+
class="np-input"
|
|
22
|
+
rows="10"
|
|
23
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
24
|
+
><%= block.content["code"] %></textarea>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="np-editor-form">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_form_id_<%= block.id %>">Select Form</label>
|
|
4
|
+
<select
|
|
5
|
+
name="content_block[content][form_id]"
|
|
6
|
+
id="content_form_id_<%= block.id %>"
|
|
7
|
+
class="np-input"
|
|
8
|
+
data-action="change->np-block-content#save"
|
|
9
|
+
>
|
|
10
|
+
<option value="">Choose a form...</option>
|
|
11
|
+
<% NotPressed::Form.active.ordered.each do |form| %>
|
|
12
|
+
<option value="<%= form.id %>" <%= "selected" if block.content["form_id"].to_s == form.id.to_s %>><%= form.name %></option>
|
|
13
|
+
<% end %>
|
|
14
|
+
</select>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<div class="np-editor-gallery">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_columns_<%= block.id %>">Columns</label>
|
|
4
|
+
<select
|
|
5
|
+
name="content_block[content][columns]"
|
|
6
|
+
id="content_columns_<%= block.id %>"
|
|
7
|
+
class="np-input"
|
|
8
|
+
data-action="change->np-block-content#save"
|
|
9
|
+
>
|
|
10
|
+
<% [2, 3, 4].each do |col| %>
|
|
11
|
+
<option value="<%= col %>" <%= "selected" if block.content["columns"].to_i == col %>><%= col %> Columns</option>
|
|
12
|
+
<% end %>
|
|
13
|
+
</select>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<% images = block.content["images"] || [] %>
|
|
17
|
+
<div class="np-gallery-editor-images">
|
|
18
|
+
<% images.each_with_index do |img, idx| %>
|
|
19
|
+
<div class="np-gallery-editor-item" style="display:flex; gap:0.5rem; align-items:center; margin-bottom:0.5rem; padding:0.5rem; border:1px solid #e2e8f0; border-radius:4px;">
|
|
20
|
+
<div style="flex:1;">
|
|
21
|
+
<label class="np-label">Media ID</label>
|
|
22
|
+
<input
|
|
23
|
+
type="text"
|
|
24
|
+
name="content_block[content][images][][media_id]"
|
|
25
|
+
value="<%= img["media_id"] %>"
|
|
26
|
+
class="np-input"
|
|
27
|
+
placeholder="Media attachment ID"
|
|
28
|
+
data-action="blur->np-block-content#save"
|
|
29
|
+
>
|
|
30
|
+
</div>
|
|
31
|
+
<div style="flex:2;">
|
|
32
|
+
<label class="np-label">Caption</label>
|
|
33
|
+
<input
|
|
34
|
+
type="text"
|
|
35
|
+
name="content_block[content][images][][caption]"
|
|
36
|
+
value="<%= img["caption"] %>"
|
|
37
|
+
class="np-input"
|
|
38
|
+
placeholder="Image caption"
|
|
39
|
+
data-action="blur->np-block-content#save"
|
|
40
|
+
>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<% end %>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div style="margin-top:0.5rem;">
|
|
47
|
+
<p style="font-size:0.75rem; color:#64748b;">Add images by entering media attachment IDs and captions above. Save after adding each image.</p>
|
|
48
|
+
<% if images.empty? %>
|
|
49
|
+
<div class="np-gallery-editor-item" style="display:flex; gap:0.5rem; align-items:center; margin-bottom:0.5rem; padding:0.5rem; border:1px solid #e2e8f0; border-radius:4px;">
|
|
50
|
+
<div style="flex:1;">
|
|
51
|
+
<label class="np-label">Media ID</label>
|
|
52
|
+
<input
|
|
53
|
+
type="text"
|
|
54
|
+
name="content_block[content][images][][media_id]"
|
|
55
|
+
value=""
|
|
56
|
+
class="np-input"
|
|
57
|
+
placeholder="Media attachment ID"
|
|
58
|
+
data-action="blur->np-block-content#save"
|
|
59
|
+
>
|
|
60
|
+
</div>
|
|
61
|
+
<div style="flex:2;">
|
|
62
|
+
<label class="np-label">Caption</label>
|
|
63
|
+
<input
|
|
64
|
+
type="text"
|
|
65
|
+
name="content_block[content][images][][caption]"
|
|
66
|
+
value=""
|
|
67
|
+
class="np-input"
|
|
68
|
+
placeholder="Image caption"
|
|
69
|
+
data-action="blur->np-block-content#save"
|
|
70
|
+
>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<div class="np-editor-heading">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_text_<%= block.id %>">Heading Text</label>
|
|
4
|
+
<input
|
|
5
|
+
type="text"
|
|
6
|
+
name="content_block[content][text]"
|
|
7
|
+
id="content_text_<%= block.id %>"
|
|
8
|
+
value="<%= block.content["text"] %>"
|
|
9
|
+
class="np-input"
|
|
10
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
11
|
+
>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="np-editor-field">
|
|
14
|
+
<label class="np-label" for="content_level_<%= block.id %>">Level</label>
|
|
15
|
+
<select
|
|
16
|
+
name="content_block[content][level]"
|
|
17
|
+
id="content_level_<%= block.id %>"
|
|
18
|
+
class="np-input"
|
|
19
|
+
data-action="change->np-block-content#save"
|
|
20
|
+
>
|
|
21
|
+
<% (1..6).each do |level| %>
|
|
22
|
+
<option value="<%= level %>" <%= "selected" if block.content["level"].to_i == level %>><%= "H#{level}" %></option>
|
|
23
|
+
<% end %>
|
|
24
|
+
</select>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div class="np-editor-html">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_markup_<%= block.id %>">Raw HTML</label>
|
|
4
|
+
<p class="np-html-warning">Warning: Raw HTML is rendered without sanitization. Use with caution.</p>
|
|
5
|
+
<textarea
|
|
6
|
+
name="content_block[content][markup]"
|
|
7
|
+
id="content_markup_<%= block.id %>"
|
|
8
|
+
class="np-input"
|
|
9
|
+
rows="8"
|
|
10
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
11
|
+
><%= block.content["markup"] %></textarea>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<div class="np-editor-image" data-controller="np-media-picker" data-np-media-picker-picker-url-value="<%= picker_admin_media_attachments_path %>">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_url_<%= block.id %>">Image URL</label>
|
|
4
|
+
<input
|
|
5
|
+
type="text"
|
|
6
|
+
name="content_block[content][url]"
|
|
7
|
+
id="content_url_<%= block.id %>"
|
|
8
|
+
value="<%= block.content["url"] %>"
|
|
9
|
+
class="np-input"
|
|
10
|
+
placeholder="https://example.com/image.jpg"
|
|
11
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
12
|
+
data-np-media-picker-target="urlField"
|
|
13
|
+
>
|
|
14
|
+
</div>
|
|
15
|
+
<input type="hidden" name="content_block[content][media_id]" value="<%= block.content["media_id"] %>" data-np-media-picker-target="mediaIdField">
|
|
16
|
+
<div style="margin-bottom: 0.75rem;">
|
|
17
|
+
<button type="button" class="np-btn np-btn--small" data-action="np-media-picker#openModal">Choose from Library</button>
|
|
18
|
+
</div>
|
|
19
|
+
<% if block.content["url"].present? %>
|
|
20
|
+
<div class="np-image-preview">
|
|
21
|
+
<img src="<%= block.content["url"] %>" alt="<%= block.content["alt"] %>">
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
<div class="np-editor-field">
|
|
25
|
+
<label class="np-label" for="content_alt_<%= block.id %>">Alt Text</label>
|
|
26
|
+
<input
|
|
27
|
+
type="text"
|
|
28
|
+
name="content_block[content][alt]"
|
|
29
|
+
id="content_alt_<%= block.id %>"
|
|
30
|
+
value="<%= block.content["alt"] %>"
|
|
31
|
+
class="np-input"
|
|
32
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
33
|
+
data-np-media-picker-target="altField"
|
|
34
|
+
>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="np-editor-field">
|
|
37
|
+
<label class="np-label" for="content_caption_<%= block.id %>">Caption</label>
|
|
38
|
+
<input
|
|
39
|
+
type="text"
|
|
40
|
+
name="content_block[content][caption]"
|
|
41
|
+
id="content_caption_<%= block.id %>"
|
|
42
|
+
value="<%= block.content["caption"] %>"
|
|
43
|
+
class="np-input"
|
|
44
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
45
|
+
>
|
|
46
|
+
</div>
|
|
47
|
+
<dialog class="np-modal np-media-picker-dialog" data-np-media-picker-target="dialog" style="width:80%; max-width:900px; max-height:80vh; border:1px solid #e2e8f0; border-radius:8px; padding:0;">
|
|
48
|
+
<div style="display:flex; justify-content:space-between; align-items:center; padding:1rem; border-bottom:1px solid #e2e8f0;">
|
|
49
|
+
<h3 style="margin:0;">Select Image from Media Library</h3>
|
|
50
|
+
<button type="button" class="np-btn np-btn--small" data-action="np-media-picker#closeModal">×</button>
|
|
51
|
+
</div>
|
|
52
|
+
<div data-np-media-picker-target="grid" style="overflow-y:auto; max-height:60vh;">
|
|
53
|
+
<p style="text-align:center; padding:2rem; color:#64748b;">Loading...</p>
|
|
54
|
+
</div>
|
|
55
|
+
</dialog>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<div class="np-editor-quote">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_text_<%= block.id %>">Quote Text</label>
|
|
4
|
+
<textarea
|
|
5
|
+
name="content_block[content][text]"
|
|
6
|
+
id="content_text_<%= block.id %>"
|
|
7
|
+
class="np-input"
|
|
8
|
+
rows="4"
|
|
9
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
10
|
+
><%= block.content["text"] %></textarea>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="np-editor-field">
|
|
13
|
+
<label class="np-label" for="content_attribution_<%= block.id %>">Attribution</label>
|
|
14
|
+
<input
|
|
15
|
+
type="text"
|
|
16
|
+
name="content_block[content][attribution]"
|
|
17
|
+
id="content_attribution_<%= block.id %>"
|
|
18
|
+
value="<%= block.content["attribution"] %>"
|
|
19
|
+
class="np-input"
|
|
20
|
+
placeholder="Author name"
|
|
21
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
22
|
+
>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<div class="np-editor-text" data-controller="np-rich-text">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label">Body</label>
|
|
4
|
+
<div class="np-toolbar">
|
|
5
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#bold" title="Bold">B</button>
|
|
6
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#italic" title="Italic"><em>I</em></button>
|
|
7
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#underline" title="Underline"><u>U</u></button>
|
|
8
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#link" title="Insert Link">🔗</button>
|
|
9
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#insertUnorderedList" title="Unordered List">• List</button>
|
|
10
|
+
<button type="button" class="np-toolbar-btn" data-action="click->np-rich-text#insertOrderedList" title="Ordered List">1. List</button>
|
|
11
|
+
</div>
|
|
12
|
+
<div
|
|
13
|
+
class="np-rich-text-editor np-input"
|
|
14
|
+
contenteditable="true"
|
|
15
|
+
data-np-rich-text-target="editor"
|
|
16
|
+
data-action="blur->np-rich-text#sync blur->np-block-content#save"
|
|
17
|
+
><%= raw block.content["body"] %></div>
|
|
18
|
+
<textarea
|
|
19
|
+
name="content_block[content][body]"
|
|
20
|
+
id="content_body_<%= block.id %>"
|
|
21
|
+
class="np-input"
|
|
22
|
+
rows="8"
|
|
23
|
+
style="display: none;"
|
|
24
|
+
data-np-rich-text-target="textarea"
|
|
25
|
+
data-action="change->np-block-content#save"
|
|
26
|
+
><%= block.content["body"] %></textarea>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<div class="np-editor-video">
|
|
2
|
+
<div class="np-editor-field">
|
|
3
|
+
<label class="np-label" for="content_url_<%= block.id %>">Video URL</label>
|
|
4
|
+
<input
|
|
5
|
+
type="text"
|
|
6
|
+
name="content_block[content][url]"
|
|
7
|
+
id="content_url_<%= block.id %>"
|
|
8
|
+
value="<%= block.content["url"] %>"
|
|
9
|
+
class="np-input"
|
|
10
|
+
placeholder="https://www.youtube.com/watch?v=..."
|
|
11
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
12
|
+
>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="np-editor-field">
|
|
15
|
+
<label class="np-label" for="content_caption_<%= block.id %>">Caption</label>
|
|
16
|
+
<input
|
|
17
|
+
type="text"
|
|
18
|
+
name="content_block[content][caption]"
|
|
19
|
+
id="content_caption_<%= block.id %>"
|
|
20
|
+
value="<%= block.content["caption"] %>"
|
|
21
|
+
class="np-input"
|
|
22
|
+
data-action="blur->np-block-content#save change->np-block-content#save"
|
|
23
|
+
>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<h1>Dashboard</h1>
|
|
2
|
+
|
|
3
|
+
<div class="np-stats-grid">
|
|
4
|
+
<div class="np-stat-card">
|
|
5
|
+
<div class="np-stat-number"><%= @total_pages %></div>
|
|
6
|
+
<div class="np-stat-label">Total Pages</div>
|
|
7
|
+
<div class="np-stat-detail"><%= @published_pages %> published, <%= @draft_pages %> draft</div>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="np-stat-card">
|
|
11
|
+
<div class="np-stat-number"><%= @total_blocks %></div>
|
|
12
|
+
<div class="np-stat-label">Content Blocks</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="np-stat-card">
|
|
16
|
+
<div class="np-stat-number"><%= @total_media %></div>
|
|
17
|
+
<div class="np-stat-label">Media Files</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="np-stat-card">
|
|
21
|
+
<div class="np-stat-number"><%= @total_forms %></div>
|
|
22
|
+
<div class="np-stat-label">Forms</div>
|
|
23
|
+
<div class="np-stat-detail"><%= @active_forms %> active</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<h2>Recent Pages</h2>
|
|
28
|
+
|
|
29
|
+
<% if @recent_pages.any? %>
|
|
30
|
+
<table class="np-table">
|
|
31
|
+
<thead>
|
|
32
|
+
<tr>
|
|
33
|
+
<th>Title</th>
|
|
34
|
+
<th>Status</th>
|
|
35
|
+
<th>Content Type</th>
|
|
36
|
+
<th>Updated</th>
|
|
37
|
+
</tr>
|
|
38
|
+
</thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
<% @recent_pages.each do |page| %>
|
|
41
|
+
<tr>
|
|
42
|
+
<td><a href="#"><%= page.title %></a></td>
|
|
43
|
+
<td><span class="np-badge np-badge--<%= page.status %>"><%= page.status %></span></td>
|
|
44
|
+
<td><%= page.content_type %></td>
|
|
45
|
+
<td><%= page.updated_at.strftime("%b %d, %Y %H:%M") %></td>
|
|
46
|
+
</tr>
|
|
47
|
+
<% end %>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
<% else %>
|
|
51
|
+
<p>No pages yet. Create your first page.</p>
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
54
|
+
<h2>Quick Actions</h2>
|
|
55
|
+
|
|
56
|
+
<div class="np-actions">
|
|
57
|
+
<a href="#" class="np-btn np-btn--primary">New Page</a>
|
|
58
|
+
<a href="/" class="np-btn">View Site</a>
|
|
59
|
+
<a href="#" class="np-btn">Media Library</a>
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<div class="np-field-row" draggable="true" data-np-form-fields-target="item" data-action="dragstart->np-form-fields#dragstart dragover->np-form-fields#dragover dragend->np-form-fields#dragend drop->np-form-fields#drop">
|
|
2
|
+
<%= ff.hidden_field :id %>
|
|
3
|
+
<%= ff.hidden_field :position, data: { np_form_fields_target: "position" } %>
|
|
4
|
+
<%= ff.hidden_field :_destroy, value: "0", data: { np_form_fields_target: "destroy" } %>
|
|
5
|
+
|
|
6
|
+
<span class="np-drag-handle" title="Drag to reorder">☰</span>
|
|
7
|
+
|
|
8
|
+
<div class="np-field-row-inputs">
|
|
9
|
+
<div class="np-field-row-group np-field-row-group--label">
|
|
10
|
+
<%= ff.text_field :label, class: "np-input", placeholder: "Field label" %>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="np-field-row-group np-field-row-group--type">
|
|
14
|
+
<%= ff.select :field_type, NotPressed::FormField.field_types.keys.map { |t| [t.humanize, t] }, {}, class: "np-input", data: { action: "change->np-form-fields#toggleOptions" } %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="np-field-row-group np-field-row-group--required">
|
|
18
|
+
<label class="np-checkbox-label">
|
|
19
|
+
<%= ff.check_box :required %> Required
|
|
20
|
+
</label>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="np-field-row-group np-field-row-group--placeholder">
|
|
24
|
+
<%= ff.text_field :placeholder, class: "np-input", placeholder: "Placeholder text" %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="np-field-row-group np-field-row-group--options" data-np-form-fields-target="optionsGroup" style="<%= ff.object.has_options? ? '' : 'display:none;' %>">
|
|
28
|
+
<%= ff.text_area :options, class: "np-input", rows: 2, placeholder: "Comma-separated options" %>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<button type="button" class="np-btn np-btn--small np-btn--danger" data-action="np-form-fields#removeField" title="Remove field">×</button>
|
|
33
|
+
</div>
|