spina-blocks 0.2.0 → 0.3.1
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 +4 -4
- data/README.md +38 -12
- data/Rakefile +20 -2
- data/app/assets/javascripts/spina/controllers/block_collection_controller.js +188 -44
- data/app/controllers/spina/blocks/admin/blocks_controller.rb +64 -20
- data/app/controllers/spina/blocks/admin/categories_controller.rb +6 -6
- data/app/controllers/spina/blocks/admin/page_blocks_controller.rb +9 -9
- data/app/helpers/spina/blocks/blocks_helper.rb +3 -3
- data/app/models/concerns/spina/parts/block_filterable.rb +18 -0
- data/app/models/spina/blocks/account_extension.rb +27 -0
- data/app/models/spina/blocks/block.rb +5 -5
- data/app/models/spina/blocks/category.rb +1 -1
- data/app/models/spina/blocks/page_block.rb +2 -2
- data/app/models/spina/blocks/page_extension.rb +14 -0
- data/app/models/spina/blocks/search_extension.rb +15 -0
- data/app/models/spina/parts/block_collection.rb +2 -0
- data/app/models/spina/parts/block_reference.rb +2 -0
- data/app/views/spina/admin/parts/block_collections/_form.html.erb +41 -22
- data/app/views/spina/admin/parts/block_references/_form.html.erb +2 -2
- data/app/views/spina/admin/pro/search/results/_block.html.erb +23 -0
- data/app/views/spina/blocks/admin/blocks/_form_block_content.html.erb +2 -2
- data/app/views/spina/blocks/admin/blocks/_modal_form.html.erb +61 -0
- data/app/views/spina/blocks/admin/blocks/_new_block_form.html.erb +1 -1
- data/app/views/spina/blocks/admin/blocks/_new_block_modal_form.html.erb +42 -0
- data/app/views/spina/blocks/admin/blocks/edit_modal.html.erb +3 -0
- data/app/views/spina/blocks/admin/blocks/index.html.erb +1 -1
- data/app/views/spina/blocks/admin/blocks/new.html.erb +5 -1
- data/app/views/spina/blocks/admin/page_blocks/index.html.erb +3 -3
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +5 -3
- data/db/migrate/1_create_spina_blocks_categories.rb +36 -0
- data/db/migrate/2_create_spina_blocks_blocks.rb +39 -0
- data/db/migrate/3_create_spina_blocks_page_blocks.rb +36 -0
- data/db/migrate/5_rename_title_to_name_in_spina_blocks_blocks.rb +18 -0
- data/lib/generators/spina/blocks/install_generator.rb +17 -0
- data/lib/spina/blocks/engine.rb +25 -25
- data/lib/spina/blocks/version.rb +1 -1
- data/lib/spina-blocks.rb +3 -3
- metadata +73 -13
- data/app/overrides/spina/account_override.rb +0 -19
- data/app/overrides/spina/page_override.rb +0 -6
- data/db/migrate/20250101000001_create_spina_blocks_categories.rb +0 -31
- data/db/migrate/20250101000002_create_spina_blocks_blocks.rb +0 -35
- data/db/migrate/20250101000003_create_spina_blocks_page_blocks.rb +0 -31
- data/db/migrate/20250101000004_remove_name_from_spina_blocks_blocks.rb +0 -24
|
@@ -13,7 +13,7 @@ module Spina
|
|
|
13
13
|
block = page_block.block
|
|
14
14
|
next unless block&.active?
|
|
15
15
|
|
|
16
|
-
concat
|
|
16
|
+
concat(render_block(block))
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -28,7 +28,7 @@ module Spina
|
|
|
28
28
|
partial_path = "#{theme_name}/blocks/#{block.block_template}"
|
|
29
29
|
|
|
30
30
|
if lookup_context.exists?(partial_path, [], true)
|
|
31
|
-
render
|
|
31
|
+
render(partial: partial_path, locals: { block: block })
|
|
32
32
|
else
|
|
33
33
|
render_block_fallback(block)
|
|
34
34
|
end
|
|
@@ -49,7 +49,7 @@ module Spina
|
|
|
49
49
|
|
|
50
50
|
def render_block_fallback(block)
|
|
51
51
|
content_tag(:div, class: "spina-block spina-block--#{block.block_template}") do
|
|
52
|
-
content_tag(:p, block.
|
|
52
|
+
content_tag(:p, block.name, class: "spina-block__title")
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spina
|
|
4
|
+
module Parts
|
|
5
|
+
module BlockFilterable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
def available_blocks
|
|
9
|
+
scope = ::Spina::Blocks::Block.active.sorted
|
|
10
|
+
opts = options.is_a?(Hash) ? options.with_indifferent_access : nil
|
|
11
|
+
if opts&.dig(:block_template).present?
|
|
12
|
+
scope = scope.where(block_template: opts[:block_template])
|
|
13
|
+
end
|
|
14
|
+
scope
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spina
|
|
4
|
+
module Blocks
|
|
5
|
+
module AccountExtension
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
after_save :bootstrap_block_categories
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def bootstrap_block_categories
|
|
15
|
+
theme_config = Spina::Theme.find_by_name(theme)
|
|
16
|
+
return unless theme_config
|
|
17
|
+
return unless theme_config.respond_to?(:block_categories) && theme_config.block_categories.present?
|
|
18
|
+
|
|
19
|
+
theme_config.block_categories.each_with_index do |category, index|
|
|
20
|
+
Spina::Blocks::Category.where(name: category[:name])
|
|
21
|
+
.first_or_create(label: category[:label])
|
|
22
|
+
.update(label: category[:label], position: index)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -8,18 +8,18 @@ module Spina
|
|
|
8
8
|
include Spina::Partable
|
|
9
9
|
include Spina::TranslatedContent
|
|
10
10
|
|
|
11
|
-
belongs_to :category, class_name:
|
|
12
|
-
has_many :page_blocks, class_name:
|
|
13
|
-
has_many :pages, through: :page_blocks, class_name:
|
|
11
|
+
belongs_to :category, class_name: "Spina::Blocks::Category", optional: true
|
|
12
|
+
has_many :page_blocks, class_name: "Spina::Blocks::PageBlock", dependent: :destroy
|
|
13
|
+
has_many :pages, through: :page_blocks, class_name: "Spina::Page"
|
|
14
14
|
|
|
15
|
-
validates :
|
|
15
|
+
validates :name, presence: true
|
|
16
16
|
validates :block_template, presence: true
|
|
17
17
|
|
|
18
18
|
scope :active, -> { where(active: true) }
|
|
19
19
|
scope :sorted, -> { order(:position) }
|
|
20
20
|
|
|
21
21
|
def to_s
|
|
22
|
-
|
|
22
|
+
name
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Spina
|
|
4
4
|
module Blocks
|
|
5
5
|
class Category < ApplicationRecord
|
|
6
|
-
has_many :blocks, class_name:
|
|
6
|
+
has_many :blocks, class_name: "Spina::Blocks::Block", foreign_key: :category_id, dependent: :nullify
|
|
7
7
|
|
|
8
8
|
validates :name, presence: true, uniqueness: true
|
|
9
9
|
validates :label, presence: true
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Spina
|
|
4
4
|
module Blocks
|
|
5
5
|
class PageBlock < ApplicationRecord
|
|
6
|
-
belongs_to :page, class_name:
|
|
7
|
-
belongs_to :block, class_name:
|
|
6
|
+
belongs_to :page, class_name: "Spina::Page"
|
|
7
|
+
belongs_to :block, class_name: "Spina::Blocks::Block"
|
|
8
8
|
|
|
9
9
|
validates :block_id, uniqueness: { scope: :page_id }
|
|
10
10
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Spina
|
|
4
|
+
module Blocks
|
|
5
|
+
module PageExtension
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
has_many :page_blocks, class_name: "Spina::Blocks::PageBlock", foreign_key: :page_id, dependent: :destroy
|
|
10
|
+
has_many :blocks, through: :page_blocks, class_name: "Spina::Blocks::Block"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%
|
|
2
|
-
blocks =
|
|
2
|
+
blocks = f.object.available_blocks
|
|
3
3
|
selected_ids = f.object.block_ids || []
|
|
4
4
|
field_name = "#{f.object_name}[block_ids][]"
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
blocks_json = blocks.map { |b|
|
|
16
16
|
{
|
|
17
17
|
id: b.id,
|
|
18
|
-
|
|
18
|
+
name: b.name,
|
|
19
19
|
templateName: b.block_template.to_s,
|
|
20
20
|
templateTitle: template_titles[b.block_template.to_s] || b.block_template.to_s.titleize
|
|
21
21
|
}
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
data-controller="block-collection"
|
|
27
27
|
data-block-collection-blocks-value="<%= blocks_json.to_json %>"
|
|
28
28
|
data-block-collection-selected-ids-value="<%= selected_ids.to_json %>"
|
|
29
|
+
data-block-collection-edit-url-value="<%= spina.edit_modal_blocks_admin_block_path("__ID__") %>"
|
|
30
|
+
data-block-collection-new-url-value="<%= spina.new_blocks_admin_block_path(modal: 1) %>"
|
|
31
|
+
data-block-collection-blocks-data-url-value="<%= spina.blocks_data_blocks_admin_blocks_path(format: :json) %>"
|
|
29
32
|
data-field-name="<%= field_name %>">
|
|
30
33
|
|
|
31
34
|
<label class="block text-sm leading-5 font-medium text-gray-700"><%= f.object.title %></label>
|
|
@@ -33,11 +36,26 @@
|
|
|
33
36
|
<div class="text-gray-400 text-sm mb-2"><%= f.object.hint %></div>
|
|
34
37
|
<% end %>
|
|
35
38
|
|
|
39
|
+
<%# Search input + dropdown %>
|
|
40
|
+
<div class="relative mb-3">
|
|
41
|
+
<input type="text"
|
|
42
|
+
data-block-collection-target="searchInput"
|
|
43
|
+
data-action="input->block-collection#onSearchInput focus->block-collection#onSearchFocus"
|
|
44
|
+
placeholder="Search or add block..."
|
|
45
|
+
autocomplete="off"
|
|
46
|
+
class="block w-full rounded-md border-gray-300 shadow-sm text-sm focus:border-spina focus:ring-spina" />
|
|
47
|
+
|
|
48
|
+
<div data-block-collection-target="dropdown"
|
|
49
|
+
style="display:none"
|
|
50
|
+
class="absolute z-10 w-full bg-white border border-gray-200 rounded-md shadow-lg py-1 max-h-60 overflow-y-auto">
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
36
54
|
<%# Empty state message %>
|
|
37
55
|
<div data-block-collection-target="emptyMessage"
|
|
38
56
|
class="text-sm text-gray-400 italic py-3"
|
|
39
57
|
style="<%= selected_ids.any? ? 'display:none' : '' %>">
|
|
40
|
-
No blocks selected.
|
|
58
|
+
No blocks selected. Use the search field above to add blocks.
|
|
41
59
|
</div>
|
|
42
60
|
|
|
43
61
|
<%# Sortable list of selected blocks (rendered by Stimulus) %>
|
|
@@ -46,24 +64,6 @@
|
|
|
46
64
|
<%# Hidden fields container (rendered by Stimulus) %>
|
|
47
65
|
<div data-block-collection-target="hiddenFields"></div>
|
|
48
66
|
|
|
49
|
-
<%# Add block button + dropdown %>
|
|
50
|
-
<div class="relative inline-block">
|
|
51
|
-
<button type="button"
|
|
52
|
-
data-action="block-collection#toggleDropdown"
|
|
53
|
-
data-block-collection-target="addButton"
|
|
54
|
-
class="inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-spina">
|
|
55
|
-
<svg class="w-4 h-4 mr-1.5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
56
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
57
|
-
</svg>
|
|
58
|
-
Add block
|
|
59
|
-
</button>
|
|
60
|
-
|
|
61
|
-
<div data-block-collection-target="dropdown"
|
|
62
|
-
style="display:none"
|
|
63
|
-
class="absolute z-10 mt-1 w-64 bg-white border border-gray-200 rounded-md shadow-lg py-1 max-h-60 overflow-y-auto">
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
67
|
<%# ===== Templates for Stimulus controller ===== %>
|
|
68
68
|
|
|
69
69
|
<%# Selected block list item %>
|
|
@@ -78,6 +78,12 @@
|
|
|
78
78
|
<span class="text-sm font-medium text-gray-800 truncate" data-role="title"></span>
|
|
79
79
|
<span class="ml-2 text-xs text-gray-400" data-role="template-label"></span>
|
|
80
80
|
</div>
|
|
81
|
+
<a class="ml-2 text-gray-300 hover:text-gray-600 transition-colors"
|
|
82
|
+
data-role="edit-link" href="" data-turbo-frame="modal" title="Edit">
|
|
83
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
84
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
85
|
+
</svg>
|
|
86
|
+
</a>
|
|
81
87
|
<button type="button" class="ml-2 text-gray-300 hover:text-red-500 transition-colors"
|
|
82
88
|
data-action="block-collection#remove" data-block-id="" title="Remove">
|
|
83
89
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -103,6 +109,19 @@
|
|
|
103
109
|
|
|
104
110
|
<%# Dropdown empty state %>
|
|
105
111
|
<template data-block-collection-target="dropdownEmptyTemplate">
|
|
106
|
-
<div class="px-3 py-2 text-sm text-gray-400 italic">
|
|
112
|
+
<div class="px-3 py-2 text-sm text-gray-400 italic">No blocks found</div>
|
|
113
|
+
</template>
|
|
114
|
+
|
|
115
|
+
<%# Dropdown "New block" link %>
|
|
116
|
+
<template data-block-collection-target="newBlockTemplate">
|
|
117
|
+
<div class="border-t border-gray-200 mt-1 pt-1">
|
|
118
|
+
<a class="w-full text-left px-3 py-2 text-sm text-spina-primary hover:bg-gray-100 flex items-center font-medium"
|
|
119
|
+
href="" data-turbo-frame="modal" data-role="new-block-link">
|
|
120
|
+
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
121
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
|
|
122
|
+
</svg>
|
|
123
|
+
<%= t('spina.blocks.new') %>
|
|
124
|
+
</a>
|
|
125
|
+
</div>
|
|
107
126
|
</template>
|
|
108
127
|
</div>
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<div class="text-gray-400 text-sm"><%= f.object.hint %></div>
|
|
5
5
|
<% end %>
|
|
6
6
|
<div class="mt-1">
|
|
7
|
-
<% blocks =
|
|
7
|
+
<% blocks = f.object.available_blocks %>
|
|
8
8
|
<%= f.select :block_id,
|
|
9
|
-
blocks.map { |b| [b.
|
|
9
|
+
blocks.map { |b| [b.name, b.id] },
|
|
10
10
|
{include_blank: t('spina.blocks.select_block')},
|
|
11
11
|
class: "form-select text-sm w-full shadow-xs" %>
|
|
12
12
|
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%= link_to spina.edit_blocks_admin_block_path(block), class: "block px-2 leading-tight", data: {selected: index.zero?, search_target: "result", action: "mouseenter->search#select", turbo_frame: "_top"} do %>
|
|
2
|
+
<div class="flex items-center justify-between w-full px-4 py-3 rounded-md bg-gray-400 bg-opacity-0 <%= 'bg-opacity-25' if index.zero? %>" data-selected-class="bg-opacity-25">
|
|
3
|
+
<div>
|
|
4
|
+
<div class="font-medium flex items-center">
|
|
5
|
+
<%= block.name %>
|
|
6
|
+
<% unless block.active? %>
|
|
7
|
+
<div class="opacity-50 text-xs ml-1">(inactive)</div>
|
|
8
|
+
<% end %>
|
|
9
|
+
<div class="flex items-center ml-2 text-xs px-1.5 py-0.5 space-x-1 bg-gray-900 bg-opacity-5 rounded">
|
|
10
|
+
<%= heroicon('cube', style: :outline, class: 'w-3 h-3 opacity-50') %>
|
|
11
|
+
<div class="opacity-75"><%= block.block_template %></div>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<% if block.category %>
|
|
15
|
+
<div class="opacity-50 text-xs"><%= block.category.label %></div>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="opacity-0 <%= 'opacity-100' if index.zero? %>" data-selected-class="opacity-100">
|
|
20
|
+
<%= heroicon('arrow-narrow-right', style: :solid, class: 'w-5 h-5 text-gray-600') %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div class="max-w-5xl">
|
|
2
2
|
<label class="font-medium text-gray-700">
|
|
3
|
-
<%= Spina::Blocks::Block.human_attribute_name(:
|
|
3
|
+
<%= Spina::Blocks::Block.human_attribute_name(:name) %>
|
|
4
4
|
</label>
|
|
5
|
-
<%= render Spina::Forms::TextFieldComponent.new(f, :
|
|
5
|
+
<%= render Spina::Forms::TextFieldComponent.new(f, :name, size: "lg") %>
|
|
6
6
|
<turbo-frame id="block_content" src="<%= spina.edit_content_blocks_admin_block_path(f.object, locale: @locale) %>">
|
|
7
7
|
</turbo-frame>
|
|
8
8
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<%# Reusable modal form for editing a block. Used by edit_modal action.
|
|
2
|
+
Expects: @block, @tabs, @locale %>
|
|
3
|
+
<%= turbo_frame_tag dom_id(@block, :modal_form) do %>
|
|
4
|
+
<div data-controller="tabs"
|
|
5
|
+
data-tabs-active="cursor-default text-gray-900 bg-spina-dark/10"
|
|
6
|
+
data-tabs-inactive="cursor-pointer bg-transparent text-gray-400 border-transparent">
|
|
7
|
+
|
|
8
|
+
<%# Modal header %>
|
|
9
|
+
<div class="px-6 pt-5 pb-4 border-b border-gray-200">
|
|
10
|
+
<div class="flex items-center justify-between">
|
|
11
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
|
12
|
+
<%= @block.name %>
|
|
13
|
+
</h3>
|
|
14
|
+
|
|
15
|
+
<%# Tab navigation %>
|
|
16
|
+
<nav>
|
|
17
|
+
<ul class="inline-flex w-auto rounded-md bg-gray-100">
|
|
18
|
+
<% @tabs.each do |tab_name| %>
|
|
19
|
+
<%= render partial: "button_#{tab_name}" %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</ul>
|
|
22
|
+
</nav>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<%# Form %>
|
|
27
|
+
<%= form_with model: @block,
|
|
28
|
+
url: spina.blocks_admin_block_path(@block),
|
|
29
|
+
id: dom_id(@block, :modal),
|
|
30
|
+
data: { turbo_frame: dom_id(@block, :modal_form) } do |f| %>
|
|
31
|
+
<%= hidden_field_tag :locale, @locale %>
|
|
32
|
+
<%= hidden_field_tag :modal, 1 %>
|
|
33
|
+
|
|
34
|
+
<div class="px-6 py-4 max-h-[70vh] overflow-y-auto">
|
|
35
|
+
<% Mobility.with_locale(@locale) do %>
|
|
36
|
+
<% @tabs.each_with_index do |tab_name, idx| %>
|
|
37
|
+
<div data-tabs-target="pane" id="<%= tab_name %>" <%= 'hidden' unless idx.zero? %>>
|
|
38
|
+
<%= render partial: "form_#{tab_name}", locals: { f: f } %>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<%# Action buttons %>
|
|
45
|
+
<div class="px-6 py-4 border-t border-gray-200 flex flex-row-reverse">
|
|
46
|
+
<button type="submit"
|
|
47
|
+
form="<%= dom_id(@block, :modal) %>"
|
|
48
|
+
class="btn btn-primary ml-3"
|
|
49
|
+
data-controller="button"
|
|
50
|
+
data-action="button#loading"
|
|
51
|
+
data-loading-message="<%= t('spina.blocks.save') %>...">
|
|
52
|
+
<%= t('spina.blocks.save') %>
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<button type="button" class="btn btn-default" data-action="modal#close">
|
|
56
|
+
<%= t('spina.ui.cancel') %>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
<% end %>
|
|
60
|
+
</div>
|
|
61
|
+
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%# Modal variant of the new block form. Used when creating a block from BlockCollection.
|
|
2
|
+
After successful creation, the modal transitions to the block edit form.
|
|
3
|
+
Expects: @block, @block_templates %>
|
|
4
|
+
<%= turbo_frame_tag dom_id(@block, :new_block_modal_form) do %>
|
|
5
|
+
<%= form_with model: @block, url: spina.blocks_admin_blocks_path, data: { turbo_frame: "modal" } do |f| %>
|
|
6
|
+
<%= hidden_field_tag :modal, 1 %>
|
|
7
|
+
|
|
8
|
+
<div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
9
|
+
<div class="sm:flex sm:items-start">
|
|
10
|
+
<div class="w-full">
|
|
11
|
+
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
|
12
|
+
<%= t('spina.blocks.new') %>
|
|
13
|
+
</h3>
|
|
14
|
+
|
|
15
|
+
<div class="mt-4">
|
|
16
|
+
<label for="block_block_template" class="block text-sm font-medium text-gray-700 mb-1">
|
|
17
|
+
<%= t('spina.blocks.block_type') %>
|
|
18
|
+
</label>
|
|
19
|
+
<%= f.select :block_template,
|
|
20
|
+
@block_templates.map { |bt| [bt[:title], bt[:name]] },
|
|
21
|
+
{ include_blank: t('spina.blocks.select_block_type') },
|
|
22
|
+
class: "block w-full rounded-md border-gray-300 shadow-xs focus:border-blue-500 focus:ring-blue-500 text-sm",
|
|
23
|
+
required: true %>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="mt-3">
|
|
27
|
+
<%= render Spina::Forms::TextFieldComponent.new(f, :name, size: 'lg', autofocus: true) %>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="px-4 pb-5 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
|
33
|
+
<button type="submit" class="btn btn-primary w-full sm:w-auto sm:ml-3">
|
|
34
|
+
<%= t('spina.blocks.create') %>
|
|
35
|
+
</button>
|
|
36
|
+
|
|
37
|
+
<button type="button" class="btn btn-default w-full sm:w-auto mt-2 sm:mt-0" data-action="modal#close">
|
|
38
|
+
<%= t('spina.ui.cancel') %>
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
<% end %>
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
</div>
|
|
76
76
|
<div class="flex-1 py-3 pr-4">
|
|
77
77
|
<%= link_to spina.edit_blocks_admin_block_path(block), class: "block" do %>
|
|
78
|
-
<div class="text-gray-900 font-medium text-sm"><%= block.
|
|
78
|
+
<div class="text-gray-900 font-medium text-sm"><%= block.name %></div>
|
|
79
79
|
<div class="text-gray-400 text-xs">
|
|
80
80
|
<%= block.block_template %>
|
|
81
81
|
<% unless block.active? %>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
params: {page_block: {block_id: block.id}},
|
|
19
19
|
method: :post,
|
|
20
20
|
class: "block w-full text-left px-4 py-2 text-sm font-medium leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900" do %>
|
|
21
|
-
<div><%= block.
|
|
21
|
+
<div><%= block.name %></div>
|
|
22
22
|
<div class="text-xs text-gray-400"><%= block.block_template %></div>
|
|
23
23
|
<% end %>
|
|
24
24
|
<% end %>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
params: {page_block: {block_id: block.id}},
|
|
34
34
|
method: :post,
|
|
35
35
|
class: "block w-full text-left px-4 py-2 text-sm font-medium leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900" do %>
|
|
36
|
-
<div><%= block.
|
|
36
|
+
<div><%= block.name %></div>
|
|
37
37
|
<div class="text-xs text-gray-400"><%= block.block_template %></div>
|
|
38
38
|
<% end %>
|
|
39
39
|
<% end %>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
</div>
|
|
65
65
|
<div class="flex-1 py-3">
|
|
66
66
|
<%= link_to spina.edit_blocks_admin_block_path(page_block.block), class: "block" do %>
|
|
67
|
-
<div class="text-gray-900 font-medium text-sm"><%= page_block.block.
|
|
67
|
+
<div class="text-gray-900 font-medium text-sm"><%= page_block.block.name %></div>
|
|
68
68
|
<div class="text-gray-400 text-xs">
|
|
69
69
|
<%= page_block.block.block_template %>
|
|
70
70
|
</div>
|
data/config/locales/en.yml
CHANGED
data/config/routes.rb
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Spina::Engine.routes.draw do
|
|
4
|
-
namespace :blocks, path:
|
|
4
|
+
namespace :blocks, path: "", module: "blocks" do
|
|
5
5
|
namespace :admin, path: Spina.config.backend_path do
|
|
6
6
|
resources :blocks do
|
|
7
7
|
member do
|
|
8
8
|
get :edit_content
|
|
9
|
+
get :edit_modal
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
collection do
|
|
12
13
|
post :sort
|
|
14
|
+
get :blocks_data
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
resources :categories, only:
|
|
18
|
+
resources :categories, only: [:index, :edit, :update]
|
|
17
19
|
|
|
18
20
|
resources :pages, only: [] do
|
|
19
|
-
resources :page_blocks, only:
|
|
21
|
+
resources :page_blocks, only: [:index, :create, :destroy] do
|
|
20
22
|
collection do
|
|
21
23
|
post :sort
|
|
22
24
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSpinaBlocksCategories < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
if previous_version_applied?("20250101000001")
|
|
6
|
+
cleanup_version!("20250101000001")
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
return if connection.table_exists?(:spina_blocks_categories)
|
|
11
|
+
|
|
12
|
+
create_table(:spina_blocks_categories) do |t|
|
|
13
|
+
t.string(:name, null: false)
|
|
14
|
+
t.string(:label, null: false)
|
|
15
|
+
t.integer(:position, default: 0)
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index(:spina_blocks_categories, :name, unique: true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def previous_version_applied?(version)
|
|
26
|
+
connection.select_value(
|
|
27
|
+
"SELECT 1 FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cleanup_version!(version)
|
|
32
|
+
connection.execute(
|
|
33
|
+
"DELETE FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSpinaBlocksBlocks < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
if previous_version_applied?("20250101000002")
|
|
6
|
+
cleanup_version!("20250101000002")
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
return if connection.table_exists?(:spina_blocks_blocks)
|
|
11
|
+
|
|
12
|
+
create_table(:spina_blocks_blocks) do |t|
|
|
13
|
+
t.string(:name, null: false)
|
|
14
|
+
t.string(:block_template, null: false)
|
|
15
|
+
t.references(:category, foreign_key: { to_table: :spina_blocks_categories }, null: true)
|
|
16
|
+
t.integer(:position, default: 0)
|
|
17
|
+
t.boolean(:active, default: true)
|
|
18
|
+
t.json(:json_attributes)
|
|
19
|
+
|
|
20
|
+
t.timestamps
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
add_index(:spina_blocks_blocks, :name, unique: true)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def previous_version_applied?(version)
|
|
29
|
+
connection.select_value(
|
|
30
|
+
"SELECT 1 FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def cleanup_version!(version)
|
|
35
|
+
connection.execute(
|
|
36
|
+
"DELETE FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSpinaBlocksPageBlocks < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
if previous_version_applied?("20250101000003")
|
|
6
|
+
cleanup_version!("20250101000003")
|
|
7
|
+
return
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
return if connection.table_exists?(:spina_blocks_page_blocks)
|
|
11
|
+
|
|
12
|
+
create_table(:spina_blocks_page_blocks) do |t|
|
|
13
|
+
t.references(:page, null: false, foreign_key: { to_table: :spina_pages })
|
|
14
|
+
t.references(:block, null: false, foreign_key: { to_table: :spina_blocks_blocks })
|
|
15
|
+
t.integer(:position, default: 0)
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index(:spina_blocks_page_blocks, [:page_id, :block_id], unique: true)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def previous_version_applied?(version)
|
|
26
|
+
connection.select_value(
|
|
27
|
+
"SELECT 1 FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cleanup_version!(version)
|
|
32
|
+
connection.execute(
|
|
33
|
+
"DELETE FROM schema_migrations WHERE version = #{connection.quote(version)}",
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|