spina-blocks 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35c34a45bbece79b10d927e3af3bc38edc797da6ad4bcfa767876be2759a71be
4
- data.tar.gz: 2bcfc10401d4a9ffddf03bacbb042424af538d7a1a1ca1bcd0bd1161afb7216b
3
+ metadata.gz: 8080a1b60cf27e9ce3dedd84b24c0eea3bd9059071f5a8983e09281168ab701a
4
+ data.tar.gz: 4e4e314f77217684b7608810db9c3c98db9955cdcb30dda6a1b3480724f57ae6
5
5
  SHA512:
6
- metadata.gz: a84d4e1d580e4a1bbc5b5bbd20efa9898137efb226fe1b77851889981bfca5667db60f2082d6cfbcd18186dfed6a3f8cde4d15166beae1c71f19fcb334b9dd08
7
- data.tar.gz: 8af333000b3fc5ec1c7f9f4968c3e0f55165d7923010cef8f7ece5b04dbdc62a1943f455af6cb19736be028ace28581cd686384e45d394604ebea8a6e38d325a
6
+ metadata.gz: d958eaac09962f67d5cf861e27123da4b43e26332533e06575e8adc6ec32e0cbf4855ace4dfc8f9ecc549de3ad712a36b46f60e1e392a7623539dc6c515b9e0b
7
+ data.tar.gz: 1e4bf92608f2ab86fc28de5ea7e4f220b46d987a4914901dc925d80392136532e8cf43632102599aa0613a24e7e2b9fad8e72d6d8838d8b9a9e425d53e3f1fa6
@@ -0,0 +1,209 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+ import Sortable from "libraries/sortablejs";
3
+
4
+ export default class extends Controller {
5
+ static get targets() {
6
+ return [
7
+ "list",
8
+ "hiddenFields",
9
+ "dropdown",
10
+ "addButton",
11
+ "emptyMessage",
12
+ "listItemTemplate",
13
+ "groupHeaderTemplate",
14
+ "dropdownOptionTemplate",
15
+ "dropdownEmptyTemplate",
16
+ ];
17
+ }
18
+
19
+ static get values() {
20
+ return {
21
+ blocks: Array, // [{id, title, templateName, templateTitle}]
22
+ selectedIds: Array, // [id, id, ...]
23
+ };
24
+ }
25
+
26
+ connect() {
27
+ // Sanitize: filter out any null/NaN/0 values from previously corrupted data
28
+ this.selectedIdsValue = this.selectedIdsValue.filter((id) => {
29
+ return id !== null && id !== undefined && !isNaN(id) && id > 0;
30
+ });
31
+
32
+ this.sortable = Sortable.create(this.listTarget, {
33
+ handle: "[data-sortable-handle]",
34
+ animation: 150,
35
+ onEnd: this.reorderHiddenFields.bind(this),
36
+ });
37
+ this.render();
38
+ }
39
+
40
+ disconnect() {
41
+ if (this.sortable) this.sortable.destroy();
42
+ }
43
+
44
+ // --- Actions ---
45
+
46
+ add(event) {
47
+ event.preventDefault();
48
+ const id = parseInt(event.currentTarget.dataset.blockId);
49
+ if (this.selectedIdsValue.indexOf(id) !== -1) return;
50
+
51
+ this.selectedIdsValue = this.selectedIdsValue.concat([id]);
52
+ this.render();
53
+ this.closeDropdown();
54
+ }
55
+
56
+ remove(event) {
57
+ event.preventDefault();
58
+ const id = parseInt(event.currentTarget.dataset.blockId);
59
+ this.selectedIdsValue = this.selectedIdsValue.filter((sid) => sid !== id);
60
+ this.render();
61
+ }
62
+
63
+ toggleDropdown(event) {
64
+ event.preventDefault();
65
+ event.stopPropagation();
66
+ const dropdown = this.dropdownTarget;
67
+ if (dropdown.style.display === "none" || dropdown.style.display === "") {
68
+ this.openDropdown();
69
+ } else {
70
+ this.closeDropdown();
71
+ }
72
+ }
73
+
74
+ closeOnOutsideClick(event) {
75
+ if (!this.element.contains(event.target)) {
76
+ this.closeDropdown();
77
+ }
78
+ }
79
+
80
+ // --- Rendering ---
81
+
82
+ render() {
83
+ this.renderList();
84
+ this.renderHiddenFields();
85
+ this.renderDropdown();
86
+ this.renderEmptyMessage();
87
+ }
88
+
89
+ renderList() {
90
+ this.listTarget.innerHTML = "";
91
+ this.selectedIdsValue.forEach((id) => {
92
+ const block = this.findBlock(id);
93
+ if (!block) return;
94
+ this.listTarget.appendChild(this.buildListItem(block));
95
+ });
96
+ }
97
+
98
+ renderHiddenFields() {
99
+ const fieldName = this.element.dataset.fieldName;
100
+ this.hiddenFieldsTarget.innerHTML = "";
101
+ if (this.selectedIdsValue.length === 0) {
102
+ // Empty sentinel: ensures the parameter is sent so Rails clears the array
103
+ const input = document.createElement("input");
104
+ input.type = "hidden";
105
+ input.name = fieldName;
106
+ input.value = "";
107
+ this.hiddenFieldsTarget.appendChild(input);
108
+ } else {
109
+ this.selectedIdsValue.forEach((id) => {
110
+ const input = document.createElement("input");
111
+ input.type = "hidden";
112
+ input.name = fieldName;
113
+ input.value = id;
114
+ this.hiddenFieldsTarget.appendChild(input);
115
+ });
116
+ }
117
+ }
118
+
119
+ renderDropdown() {
120
+ const availableBlocks = this.blocksValue.filter((b) => {
121
+ return this.selectedIdsValue.indexOf(b.id) === -1;
122
+ });
123
+
124
+ this.dropdownTarget.innerHTML = "";
125
+
126
+ if (availableBlocks.length === 0) {
127
+ const empty = this.dropdownEmptyTemplateTarget.content.cloneNode(true);
128
+ this.dropdownTarget.appendChild(empty);
129
+ return;
130
+ }
131
+
132
+ // Group by templateTitle
133
+ const groups = {};
134
+ availableBlocks.forEach((b) => {
135
+ const key = b.templateTitle || b.templateName || "Other";
136
+ if (!groups[key]) groups[key] = [];
137
+ groups[key].push(b);
138
+ });
139
+
140
+ Object.keys(groups)
141
+ .sort()
142
+ .forEach((groupName) => {
143
+ const header = this.groupHeaderTemplateTarget.content.cloneNode(true);
144
+ header.querySelector("[data-role='group-name']").textContent =
145
+ groupName;
146
+ this.dropdownTarget.appendChild(header);
147
+
148
+ groups[groupName].forEach((block) => {
149
+ const option =
150
+ this.dropdownOptionTemplateTarget.content.cloneNode(true);
151
+ const button = option.querySelector("button");
152
+ button.dataset.blockId = block.id;
153
+ option.querySelector("[data-role='title']").textContent = block.title;
154
+ this.dropdownTarget.appendChild(option);
155
+ });
156
+ });
157
+ }
158
+
159
+ renderEmptyMessage() {
160
+ if (this.hasEmptyMessageTarget) {
161
+ this.emptyMessageTarget.style.display =
162
+ this.selectedIdsValue.length === 0 ? "" : "none";
163
+ }
164
+ }
165
+
166
+ // --- Helpers ---
167
+
168
+ buildListItem(block) {
169
+ const fragment = this.listItemTemplateTarget.content.cloneNode(true);
170
+ const root = fragment.querySelector("[data-block-id]");
171
+ root.dataset.blockId = block.id;
172
+ fragment.querySelector("[data-role='title']").textContent = block.title;
173
+ fragment.querySelector("[data-role='template-label']").textContent =
174
+ "(" + (block.templateTitle || block.templateName) + ")";
175
+ fragment.querySelector(
176
+ "[data-action='block-collection#remove']",
177
+ ).dataset.blockId = block.id;
178
+ return fragment;
179
+ }
180
+
181
+ reorderHiddenFields() {
182
+ // :scope > selects only direct children, not nested buttons that also have data-block-id
183
+ const items = this.listTarget.querySelectorAll(":scope > [data-block-id]");
184
+ const ids = [];
185
+ items.forEach((el) => {
186
+ ids.push(parseInt(el.dataset.blockId));
187
+ });
188
+ this.selectedIdsValue = ids;
189
+ this.renderHiddenFields();
190
+ }
191
+
192
+ openDropdown() {
193
+ this.dropdownTarget.style.display = "block";
194
+ this._outsideClickHandler = this.closeOnOutsideClick.bind(this);
195
+ document.addEventListener("click", this._outsideClickHandler, true);
196
+ }
197
+
198
+ closeDropdown() {
199
+ this.dropdownTarget.style.display = "none";
200
+ if (this._outsideClickHandler) {
201
+ document.removeEventListener("click", this._outsideClickHandler, true);
202
+ this._outsideClickHandler = null;
203
+ }
204
+ }
205
+
206
+ findBlock(id) {
207
+ return this.blocksValue.find((b) => b.id === id);
208
+ }
209
+ }
@@ -27,6 +27,7 @@ module Spina
27
27
  end
28
28
 
29
29
  def new
30
+ @block_templates = current_theme.try(:block_templates) || []
30
31
  @block = Spina::Blocks::Block.new(block_template: params[:block_template])
31
32
  end
32
33
 
@@ -35,6 +36,7 @@ module Spina
35
36
  if @block.save
36
37
  redirect_to spina.edit_blocks_admin_block_url(@block)
37
38
  else
39
+ @block_templates = current_theme.try(:block_templates) || []
38
40
  render turbo_stream: turbo_stream.update(
39
41
  helpers.dom_id(@block, :new_block_form),
40
42
  partial: 'new_block_form'
@@ -7,6 +7,11 @@ module Spina
7
7
 
8
8
  attr_accessor :options
9
9
 
10
+ # Defense: strip nils, zeros, and blanks that may sneak in from form submissions
11
+ def block_ids=(value)
12
+ super(Array(value).reject(&:blank?).map(&:to_i).reject(&:zero?).uniq)
13
+ end
14
+
10
15
  def content
11
16
  return [] if block_ids.blank?
12
17
 
@@ -1,24 +1,108 @@
1
- <div class="mt-6">
1
+ <%
2
+ blocks = Spina::Blocks::Block.active.sorted
3
+ selected_ids = f.object.block_ids || []
4
+ field_name = "#{f.object_name}[block_ids][]"
5
+
6
+ # Build block_templates title lookup from current theme
7
+ template_titles = {}
8
+ if defined?(current_theme) && current_theme.respond_to?(:block_templates)
9
+ current_theme.block_templates.each do |bt|
10
+ template_titles[bt[:name].to_s] = bt[:title].to_s
11
+ end
12
+ end
13
+
14
+ # Build JSON data for Stimulus controller
15
+ blocks_json = blocks.map { |b|
16
+ {
17
+ id: b.id,
18
+ title: b.title,
19
+ templateName: b.block_template.to_s,
20
+ templateTitle: template_titles[b.block_template.to_s] || b.block_template.to_s.titleize
21
+ }
22
+ }
23
+ %>
24
+
25
+ <div class="mt-6"
26
+ data-controller="block-collection"
27
+ data-block-collection-blocks-value="<%= blocks_json.to_json %>"
28
+ data-block-collection-selected-ids-value="<%= selected_ids.to_json %>"
29
+ data-field-name="<%= field_name %>">
30
+
2
31
  <label class="block text-sm leading-5 font-medium text-gray-700"><%= f.object.title %></label>
3
32
  <% if f.object.hint.present? %>
4
- <div class="text-gray-400 text-sm"><%= f.object.hint %></div>
33
+ <div class="text-gray-400 text-sm mb-2"><%= f.object.hint %></div>
5
34
  <% end %>
6
- <div class="mt-1">
7
- <% blocks = Spina::Blocks::Block.active.sorted %>
8
- <% selected_ids = f.object.block_ids || [] %>
9
- <div class="space-y-2">
10
- <% blocks.each do |block| %>
11
- <label class="flex items-center space-x-2 cursor-pointer">
12
- <input type="checkbox"
13
- name="<%= f.object_name %>[block_ids][]"
14
- value="<%= block.id %>"
15
- <%= 'checked' if selected_ids.include?(block.id) %>
16
- class="form-checkbox rounded text-spina border-gray-300">
17
- <span class="text-sm text-gray-700"><%= block.title %></span>
18
- <span class="text-xs text-gray-400">(<%= block.block_template %>)</span>
19
- </label>
20
- <% end %>
35
+
36
+ <%# Empty state message %>
37
+ <div data-block-collection-target="emptyMessage"
38
+ class="text-sm text-gray-400 italic py-3"
39
+ style="<%= selected_ids.any? ? 'display:none' : '' %>">
40
+ No blocks selected. Click &ldquo;Add block&rdquo; to get started.
41
+ </div>
42
+
43
+ <%# Sortable list of selected blocks (rendered by Stimulus) %>
44
+ <div data-block-collection-target="list" class="space-y-1 mb-3"></div>
45
+
46
+ <%# Hidden fields container (rendered by Stimulus) %>
47
+ <div data-block-collection-target="hiddenFields"></div>
48
+
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">
21
64
  </div>
22
- <input type="hidden" name="<%= f.object_name %>[block_ids][]" value="">
23
65
  </div>
66
+
67
+ <%# ===== Templates for Stimulus controller ===== %>
68
+
69
+ <%# Selected block list item %>
70
+ <template data-block-collection-target="listItemTemplate">
71
+ <div class="flex items-center bg-white border border-gray-200 rounded-md px-3 py-2 shadow-sm group" data-block-id="">
72
+ <button type="button" data-sortable-handle class="mr-2 text-gray-300 hover:text-gray-500 cursor-grab" title="Drag to reorder">
73
+ <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
74
+ <path d="M432 288H16c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16h416c8.8 0 16-7.2 16-16v-16c0-8.8-7.2-16-16-16zm0-112H16c-8.8 0-16 7.2-16 16v16c0 8.8 7.2 16 16 16h416c8.8 0 16-7.2 16-16v-16c0-8.8-7.2-16-16-16z"/>
75
+ </svg>
76
+ </button>
77
+ <div class="flex-1 min-w-0">
78
+ <span class="text-sm font-medium text-gray-800 truncate" data-role="title"></span>
79
+ <span class="ml-2 text-xs text-gray-400" data-role="template-label"></span>
80
+ </div>
81
+ <button type="button" class="ml-2 text-gray-300 hover:text-red-500 transition-colors"
82
+ data-action="block-collection#remove" data-block-id="" title="Remove">
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="M6 18L18 6M6 6l12 12"/>
85
+ </svg>
86
+ </button>
87
+ </div>
88
+ </template>
89
+
90
+ <%# Dropdown group header %>
91
+ <template data-block-collection-target="groupHeaderTemplate">
92
+ <div class="px-3 py-1.5 text-xs font-semibold text-gray-400 uppercase tracking-wider" data-role="group-name"></div>
93
+ </template>
94
+
95
+ <%# Dropdown option (available block) %>
96
+ <template data-block-collection-target="dropdownOptionTemplate">
97
+ <button type="button"
98
+ class="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"
99
+ data-action="block-collection#add" data-block-id="">
100
+ <span data-role="title"></span>
101
+ </button>
102
+ </template>
103
+
104
+ <%# Dropdown empty state %>
105
+ <template data-block-collection-target="dropdownEmptyTemplate">
106
+ <div class="px-3 py-2 text-sm text-gray-400 italic">All blocks added</div>
107
+ </template>
24
108
  </div>
@@ -1,15 +1,23 @@
1
1
  <%= turbo_frame_tag dom_id(@block, :new_block_form) do %>
2
2
  <%= form_with model: @block, url: spina.blocks_admin_blocks_path, data: {turbo_frame: "_top"} do |f| %>
3
- <%= f.hidden_field :block_template %>
4
-
5
3
  <div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
6
4
  <div class="sm:flex sm:items-start">
7
5
  <div class="w-full">
8
6
  <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
9
7
  <%= t('spina.blocks.new') %>
10
- <span class="text-gray-400 text-sm">(<%= f.object.block_template %>)</span>
11
8
  </h3>
12
9
 
10
+ <div class="mt-4">
11
+ <label for="block_block_template" class="block text-sm font-medium text-gray-700 mb-1">
12
+ <%= t('spina.blocks.block_type') %>
13
+ </label>
14
+ <%= f.select :block_template,
15
+ @block_templates.map { |bt| [bt[:title], bt[:name]] },
16
+ { include_blank: t('spina.blocks.select_block_type') },
17
+ class: "block w-full rounded-md border-gray-300 shadow-xs focus:border-blue-500 focus:ring-blue-500 text-sm",
18
+ required: true %>
19
+ </div>
20
+
13
21
  <div class="mt-3">
14
22
  <%= render Spina::Forms::TextFieldComponent.new(f, :title, size: 'lg', autofocus: true) %>
15
23
  </div>
@@ -2,48 +2,63 @@
2
2
  <% header.with_actions do %>
3
3
  <% block_templates = current_theme.try(:block_templates) || [] %>
4
4
  <% if block_templates.any? %>
5
- <div class="relative" data-controller="reveal" data-reveal-away-value>
6
- <button type="button" class="btn btn-primary" data-action="reveal#toggle">
7
- <%= heroicon('plus', style: :mini, class: 'w-4 h-4 mr-1 -ml-1') %>
8
- <%= t('spina.blocks.new') %>
9
- </button>
5
+ <%= link_to spina.new_blocks_admin_block_path,
6
+ class: "btn btn-primary",
7
+ data: {turbo_frame: "modal"} do %>
8
+ <%= heroicon('plus', style: :mini, class: 'w-4 h-4 mr-1 -ml-1') %>
9
+ <%= t('spina.blocks.new') %>
10
+ <% end %>
11
+ <% end %>
12
+ <% end %>
13
+
14
+ <% header.with_navigation do %>
15
+ <% if @block_templates.any? %>
16
+ <nav class="-mb-1 md:-mb-3 mt-4">
17
+ <div class="relative inline-block" data-controller="reveal" data-reveal-away-value>
18
+ <button type="button"
19
+ class="inline-flex items-center px-3 py-1.5 rounded-md text-sm font-medium bg-white border border-gray-300 text-gray-700 shadow-xs hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
20
+ data-action="reveal#toggle">
21
+ <% if @current_block_template.present? %>
22
+ <% current_bt = @block_templates.find { |bt| bt[:name].to_s == @current_block_template } %>
23
+ <%= current_bt ? current_bt[:title] : @current_block_template %>
24
+ <% else %>
25
+ <%= heroicon('squares-2x2', class: 'h-4 w-4 mr-1.5 -ml-0.5 opacity-75') %>
26
+ <%= t('spina.blocks.all') %>
27
+ <% end %>
28
+ <%= heroicon('chevron-down', style: :mini, class: 'w-4 h-4 ml-1.5 -mr-0.5 opacity-50') %>
29
+ </button>
10
30
 
11
- <div hidden data-reveal data-transition class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg border border-gray-200 z-30">
12
- <div class="rounded-md bg-white shadow-xs py-1">
13
- <% block_templates.each do |bt| %>
14
- <%= link_to spina.new_blocks_admin_block_path(block_template: bt[:name]),
15
- class: "block px-4 py-2 text-sm font-medium leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900",
16
- data: {turbo_frame: "modal", action: "reveal#hide"} do %>
17
- <%= bt[:title] %>
18
- <% if bt[:description].present? %>
19
- <span class="text-gray-400 text-xs block"><%= bt[:description] %></span>
31
+ <div hidden data-reveal data-transition class="origin-top-left absolute left-0 mt-2 w-56 rounded-md shadow-lg border border-gray-200 z-30">
32
+ <div class="rounded-md bg-white shadow-xs py-1">
33
+ <%= link_to spina.blocks_admin_blocks_path,
34
+ class: "flex items-center px-4 py-2 text-sm font-medium leading-5 #{@current_block_template.nil? ? 'text-blue-600 bg-blue-50' : 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'}",
35
+ data: {action: "reveal#hide"} do %>
36
+ <%= heroicon('squares-2x2', class: 'h-4 w-4 mr-2 opacity-75') %>
37
+ <%= t('spina.blocks.all') %>
38
+ <% if @current_block_template.nil? %>
39
+ <%= heroicon('check', style: :mini, class: 'w-4 h-4 ml-auto opacity-75') %>
20
40
  <% end %>
21
41
  <% end %>
22
- <% end %>
42
+
43
+ <div class="border-t border-gray-100 my-1"></div>
44
+
45
+ <% @block_templates.each do |bt| %>
46
+ <% active = @current_block_template == bt[:name].to_s %>
47
+ <%= link_to spina.blocks_admin_blocks_path(block_template: bt[:name]),
48
+ class: "flex items-center px-4 py-2 text-sm font-medium leading-5 #{active ? 'text-blue-600 bg-blue-50' : 'text-gray-700 hover:bg-gray-100 hover:text-gray-900'}",
49
+ data: {action: "reveal#hide"} do %>
50
+ <span class="flex-1"><%= bt[:title] %></span>
51
+ <% if active %>
52
+ <%= heroicon('check', style: :mini, class: 'w-4 h-4 ml-2 opacity-75') %>
53
+ <% end %>
54
+ <% end %>
55
+ <% end %>
56
+ </div>
23
57
  </div>
24
58
  </div>
25
- </div>
59
+ </nav>
26
60
  <% end %>
27
61
  <% end %>
28
-
29
- <% header.with_navigation do %>
30
- <nav class="-mb-1 md:-mb-3 mt-4">
31
- <ul class="inline-flex flex-wrap w-auto rounded-md bg-white">
32
- <%= render Spina::UserInterface::TabLinkComponent.new(spina.blocks_admin_blocks_path, active: @current_block_template.nil?) do %>
33
- <%= heroicon('squares-2x2', class: 'h-4 w-4 mr-1 -ml-1 opacity-75') %>
34
- <%= t('spina.blocks.all') %>
35
- <% end %>
36
-
37
- <% @block_templates.each do |bt| %>
38
- <%= render Spina::UserInterface::TabLinkComponent.new(
39
- spina.blocks_admin_blocks_path(block_template: bt[:name]),
40
- active: @current_block_template == bt[:name].to_s) do %>
41
- <%= bt[:title] %>
42
- <% end %>
43
- <% end %>
44
- </ul>
45
- </nav>
46
- <% end %>
47
62
  <% end %>
48
63
 
49
64
  <% if @blocks.any? %>
@@ -17,6 +17,8 @@ en:
17
17
  category: "Category"
18
18
  no_category: "— No category —"
19
19
  select_block: "— Select block —"
20
+ block_type: "Block type"
21
+ select_block_type: "— Select block type —"
20
22
  no_blocks_yet: "No blocks yet. Create your first block to get started."
21
23
  inactive: "Inactive"
22
24
  uncategorized: "Uncategorized"
@@ -34,6 +34,17 @@ module Spina
34
34
  end
35
35
  end
36
36
 
37
+ initializer 'spina.blocks.importmap', before: 'spina.blocks.register_parts' do
38
+ Spina.config.importmap.draw do
39
+ pin_all_from Spina::Blocks::Engine.root.join('app/assets/javascripts/spina/controllers'),
40
+ under: 'controllers', to: 'spina/controllers'
41
+ end
42
+ end
43
+
44
+ initializer 'spina.blocks.assets.precompile' do |app|
45
+ app.config.assets.precompile += %w[spina/controllers/block_collection_controller.js] if defined?(Sprockets)
46
+ end
47
+
37
48
  initializer 'spina.blocks.register_parts' do
38
49
  config.to_prepare do
39
50
  ::Spina::Part.register(
@@ -54,6 +65,7 @@ module Spina
54
65
  initializer 'spina.blocks.tailwind_content' do
55
66
  ::Spina.config.tailwind_content << "#{Spina::Blocks::Engine.root}/app/views/**/*.*"
56
67
  ::Spina.config.tailwind_content << "#{Spina::Blocks::Engine.root}/app/helpers/**/*.*"
68
+ ::Spina.config.tailwind_content << "#{Spina::Blocks::Engine.root}/app/assets/javascripts/**/*.js"
57
69
  end
58
70
 
59
71
  initializer 'spina.blocks.i18n' do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Spina
4
4
  module Blocks
5
- VERSION = '0.1.1'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spina-blocks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Kanashchuk
@@ -34,6 +34,7 @@ extra_rdoc_files: []
34
34
  files:
35
35
  - README.md
36
36
  - Rakefile
37
+ - app/assets/javascripts/spina/controllers/block_collection_controller.js
37
38
  - app/controllers/spina/blocks/admin/blocks_controller.rb
38
39
  - app/controllers/spina/blocks/admin/categories_controller.rb
39
40
  - app/controllers/spina/blocks/admin/page_blocks_controller.rb