spina 2.7.0 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of spina might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -2
  3. data/Rakefile +2 -1
  4. data/app/assets/builds/spina/tailwind.css +3480 -0
  5. data/app/assets/config/spina/manifest.js +3 -1
  6. data/app/assets/javascripts/spina/controllers/image_collection_controller.js +5 -3
  7. data/app/assets/javascripts/spina/controllers/infinite_scroll_controller.js +4 -2
  8. data/app/assets/stylesheets/spina/{_animate.css → animate.css} +0 -0
  9. data/app/assets/stylesheets/spina/{tailwind/custom.css → application.tailwind.css} +40 -46
  10. data/app/assets/stylesheets/spina/{_fonts.css.erb → fonts.css.erb} +0 -0
  11. data/app/components/spina/forms/trix_toolbar_component.html.erb +1 -1
  12. data/app/components/spina/media_picker/image_component.html.erb +1 -1
  13. data/app/components/spina/media_picker/modal_component.html.erb +4 -4
  14. data/app/components/spina/user_interface/tab_link_component.html.erb +1 -1
  15. data/app/components/spina/user_interface/translations_component.html.erb +21 -0
  16. data/app/components/spina/user_interface/translations_component.rb +26 -0
  17. data/app/controllers/concerns/spina/frontend.rb +1 -1
  18. data/app/controllers/spina/admin/images_controller.rb +3 -2
  19. data/app/controllers/spina/admin/layout_controller.rb +1 -1
  20. data/app/controllers/spina/admin/navigation_items_controller.rb +1 -1
  21. data/app/helpers/spina/images_helper.rb +15 -0
  22. data/app/models/concerns/spina/partable.rb +1 -1
  23. data/app/models/spina/parts/attachment.rb +1 -1
  24. data/app/models/spina/parts/base.rb +2 -1
  25. data/app/presenters/spina/menu_presenter.rb +26 -3
  26. data/app/views/layouts/spina/admin/admin.html.erb +1 -1
  27. data/app/views/layouts/spina/admin/application.html.erb +2 -2
  28. data/app/views/spina/admin/images/_image.html.erb +34 -26
  29. data/app/views/spina/admin/images/index.html.erb +1 -1
  30. data/app/views/spina/admin/images/show.html.erb +3 -1
  31. data/app/views/spina/admin/layout/edit.html.erb +2 -0
  32. data/app/views/spina/admin/media_folders/_media_folder.html.erb +5 -3
  33. data/app/views/spina/admin/navigation_items/_form.html.erb +5 -1
  34. data/app/views/spina/admin/navigations/edit.html.erb +1 -1
  35. data/app/views/spina/admin/pages/_form.html.erb +2 -2
  36. data/app/views/spina/admin/pages/index.html.erb +1 -1
  37. data/app/views/spina/admin/parts/attachments/_form.html.erb +2 -1
  38. data/app/views/spina/admin/parts/image_collections/_form.html.erb +1 -0
  39. data/app/views/spina/admin/parts/images/_form.html.erb +1 -0
  40. data/app/views/spina/admin/parts/lines/_form.html.erb +1 -0
  41. data/app/views/spina/admin/parts/options/_form.html.erb +2 -1
  42. data/app/views/spina/admin/parts/repeaters/_form.html.erb +2 -1
  43. data/app/views/spina/admin/parts/texts/_form.html.erb +1 -0
  44. data/app/views/spina/admin/resources/edit.html.erb +6 -1
  45. data/app/views/spina/admin/shared/_navigation.html.erb +1 -1
  46. data/config/locales/de.yml +63 -28
  47. data/config/locales/fr.yml +190 -152
  48. data/lib/generators/spina/install_generator.rb +8 -3
  49. data/lib/generators/spina/tailwind_config_generator.rb +10 -0
  50. data/{app/assets/config/spina/tailwind.config.js → lib/generators/spina/templates/app/assets/config/spina/tailwind.config.js.tt} +5 -7
  51. data/lib/generators/spina/templates/config/initializers/themes/default.rb +1 -1
  52. data/lib/generators/spina/templates/config/initializers/themes/demo.rb +2 -2
  53. data/lib/spina/version.rb +1 -1
  54. data/lib/spina.rb +22 -11
  55. data/lib/tasks/install.rake +45 -0
  56. data/lib/tasks/tailwind.rake +22 -0
  57. metadata +27 -11
  58. data/app/assets/stylesheets/spina/_tailwind.css +0 -203643
  59. data/app/assets/stylesheets/spina/application.css +0 -6
  60. data/lib/spina/tailwind_purger.rb +0 -147
  61. data/lib/tasks/spina_tasks.rake +0 -75
@@ -4,6 +4,8 @@
4
4
  //= link_directory ../../javascripts/spina/controllers
5
5
  //= link_directory ../../javascripts/spina/libraries
6
6
 
7
- //= link spina/application.css
7
+ //= link spina/animate.css
8
+ //= link spina/fonts.css
9
+ //= link spina/tailwind.css
8
10
 
9
11
  //= link spina/application.js
@@ -7,9 +7,11 @@ export default class extends Controller {
7
7
  }
8
8
 
9
9
  connect() {
10
- this.sortable = Sortable.create(this.collectionTarget, {
11
- animation: 150
12
- })
10
+ setTimeout(function() {
11
+ this.sortable = Sortable.create(this.collectionTarget, {
12
+ animation: 150
13
+ })
14
+ }.bind(this), 250)
13
15
  }
14
16
 
15
17
  removeImage(event) {
@@ -7,6 +7,7 @@ export default class extends Controller {
7
7
 
8
8
  connect() {
9
9
  this.scrollElement.addEventListener("scroll", this.load.bind(this))
10
+ this.load() // Initial load
10
11
  }
11
12
 
12
13
  disconnect() {
@@ -16,8 +17,9 @@ export default class extends Controller {
16
17
  load() {
17
18
  if (this.hasButtonTarget) {
18
19
  let top = this.buttonTarget.getBoundingClientRect().top
19
- if (top < this.scrollElement.innerHeight + 500) {
20
+ if (top < window.innerHeight + 500) {
20
21
  this.buttonTarget.click()
22
+ this.buttonTarget.remove()
21
23
  }
22
24
  }
23
25
  }
@@ -26,7 +28,7 @@ export default class extends Controller {
26
28
  if (this.hasContainerTarget) {
27
29
  return this.containerTarget
28
30
  } else {
29
- return window
31
+ return document.getElementById("main")
30
32
  }
31
33
  }
32
34
 
@@ -138,58 +138,52 @@
138
138
  @apply border border-gray-400;
139
139
  }
140
140
 
141
- /* Trix */
142
- .trix-toolbar {
143
- button[data-trix-active] {
144
- @apply text-white bg-spina;
145
- }
141
+ /* Trix */
142
+ .trix-toolbar button[data-trix-active] {
143
+ @apply text-white bg-spina;
144
+ }
146
145
 
147
- button[disabled] {
148
- @apply bg-gray-100 text-gray-400;
149
- }
146
+ .trix-toolbar button[disabled] {
147
+ @apply bg-gray-100 text-gray-400;
150
148
  }
151
149
 
152
- trix-editor {
153
-
154
- [data-trix-mutable]:not(.attachment__captain-editor) {
155
- @apply select-none
156
- }
157
-
158
- figure.attachment {
159
- @apply m-0 inline-block
160
- }
161
-
162
- figure.attachment[data-trix-content-type="Spina::Image"] {
163
- max-height: 150px;
164
- max-width: 200px;
165
- }
166
-
167
- figure.attachment[data-trix-content-type="Spina::Image"] img {
168
- @apply m-0 rounded-md object-contain;
169
- }
170
-
171
- figure.attachment[data-trix-content-type="Spina::Image"] [data-label]:after {
172
- content: attr(data-label);
173
- @apply italic text-gray-500 h-8 flex items-center px-2 mt-1 text-sm;
174
- }
175
-
176
- figure[data-trix-mutable].attachment[data-trix-content-type="Spina::Image"] img {
177
- @apply shadow-lg ring ring-spina-light
178
- }
150
+ trix-editor [data-trix-mutable]:not(.attachment__captain-editor) {
151
+ @apply select-none
152
+ }
179
153
 
180
- figure[data-trix-mutable][data-trix-content-type="application/vnd+spina.embed+html"].attachment > spina-embed {
181
- @apply block;
182
- @apply shadow-lg ring ring-spina-light rounded-md;
183
- }
184
-
185
- figure .attachment__caption {
186
- @apply hidden
187
- }
154
+ trix-editor figure.attachment {
155
+ @apply m-0 inline-block
156
+ }
157
+
158
+ trix-editor figure.attachment[data-trix-content-type="Spina::Image"] {
159
+ max-height: 150px;
160
+ max-width: 200px;
161
+ }
162
+
163
+ trix-editor figure.attachment[data-trix-content-type="Spina::Image"] img {
164
+ @apply m-0 rounded-md object-contain;
165
+ }
188
166
 
189
- figure .attachment__toolbar {
190
- @apply hidden
191
- }
167
+ trix-editor figure.attachment[data-trix-content-type="Spina::Image"] [data-label]:after {
168
+ content: attr(data-label);
169
+ @apply italic text-gray-500 h-8 flex items-center px-2 mt-1 text-sm;
170
+ }
171
+
172
+ trix-editor figure[data-trix-mutable].attachment[data-trix-content-type="Spina::Image"] img {
173
+ @apply shadow-lg ring ring-spina-light
174
+ }
192
175
 
176
+ trix-editor figure[data-trix-mutable][data-trix-content-type="application/vnd+spina.embed+html"].attachment > spina-embed {
177
+ @apply block;
178
+ @apply shadow-lg ring ring-spina-light rounded-md;
179
+ }
180
+
181
+ trix-editor figure .attachment__caption {
182
+ @apply hidden
183
+ }
184
+
185
+ trix-editor figure .attachment__toolbar {
186
+ @apply hidden
193
187
  }
194
188
 
195
189
  trix-editor [data-trix-mutable]::-moz-selection,
@@ -1,4 +1,4 @@
1
- <div class="relative sticky top-0 pt-4 bg-white trix-toolbar" id="<%= @trix_id %>">
1
+ <div class="relative sticky z-10 top-0 pt-4 bg-white trix-toolbar" id="<%= @trix_id %>">
2
2
  <div class="flex items-center flex-wrap" data-controller="reveal">
3
3
  <div class="flex items-center bg-gray-200 rounded overflow-hidden mb-3 mr-3">
4
4
  <button type="button" class="hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center" data-trix-attribute="bold" data-trix-key="b" title="${Trix.config.lang.bold}" tabindex="-1">
@@ -10,7 +10,7 @@
10
10
  data-deselected-class="border-transparent"
11
11
  class="w-full h-40 overflow-hidden rounded-md transform transition-transform duration-200 border-2 border-transparent hover:scale-105">
12
12
 
13
- <%= image_tag helpers.thumbnail_url(@image), class: 'object-contain h-40 w-full', data: {controller: "image-fade-in"} %>
13
+ <%= image_tag helpers.large_thumbnail_url(@image), class: 'object-contain h-40 w-full', data: {controller: "image-fade-in"} %>
14
14
  </button>
15
15
 
16
16
  <div class="text-xs text-center text-gray-700 rounded p-1" data-selected-class="text-white bg-spina" data-deselected-class="text-gray-700"><%= @image.file.filename %></div>
@@ -1,9 +1,9 @@
1
1
  <%= render(Spina::UserInterface::ModalComponent.new(size: "max-w-6xl h-full")) do |component| %>
2
2
  <div class="h-full w-full" data-controller="infinite-scroll media-picker-modal" data-target="<%= @target %>">
3
- <turbo-frame id="media_picker">
3
+ <turbo-frame id="media_picker" data-action= "turbo:frame-load->infinite-scroll#load">
4
4
  <div class="flex flex-col md:flex-row h-full w-full">
5
5
  <div class="flex-1 bg-white flex flex-col max-h-full relative overflow-hidden">
6
- <div data-infinite-scroll-target="container" data-controller="selectable" class="p-6 h-full w-full overflow-scroll ">
6
+ <div data-infinite-scroll-target="container" data-controller="selectable" class="p-6 h-full w-full overflow-scroll" data-action="scroll->infinite-scroll#load">
7
7
 
8
8
  <!-- Images are loaded using nested turbo-frame-tags -->
9
9
  <!-- Only load images in multiples of 4 so that this grid -->
@@ -14,8 +14,8 @@
14
14
  </div>
15
15
 
16
16
  <% if @images.next_page %>
17
- <turbo-frame id="images-<%= @images.next_page %>">
18
- <%= link_to "...", helpers.path_to_next_page(@images), data: {infinite_scroll_target: "button"} %>
17
+ <turbo-frame id="images-<%= @images.next_page %>" data-action= "turbo:frame-load->infinite-scroll#load">
18
+ <%= link_to "Load more images", helpers.path_to_next_page(@images), class: "btn btn-default mt-6", data: {infinite_scroll_target: "button"} %>
19
19
  </turbo-frame>
20
20
  <% end %>
21
21
 
@@ -1,3 +1,3 @@
1
- <%= link_to @url, class: "block px-3 leading-relaxed py-1 hover:text-gray-800 rounded-md text-gray-400 font-medium text-sm flex items-center #{css_classes}" do %>
1
+ <%= link_to @url, class: "block px-3 leading-relaxed py-1 hover:text-gray-800 rounded-md text-gray-400 font-medium text-sm flex items-center whitespace-nowrap #{css_classes}" do %>
2
2
  <%= content || @name %>
3
3
  <% end %>
@@ -0,0 +1,21 @@
1
+ <div class="relative" data-controller="reveal" data-reveal-away-value>
2
+ <button type="button" class="btn btn-default px-3" data-action="reveal#toggle">
3
+ <%= helpers.heroicon("chat-alt", style: :solid, class: 'w-4 h-4 text-gray-300') %>
4
+
5
+ <span class="ml-1 font-semibold"><%= @label %></span>
6
+ </button>
7
+
8
+ <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-20">
9
+ <div class="rounded-md bg-white shadow-xs">
10
+ <div class="py-1">
11
+ <% locales.each do |locale| %>
12
+ <%= link_to "?locale=#{locale}", class: "block px-3 py-2 text-sm leading-4 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900 font-medium" do %>
13
+ <div class="text-gray-700">
14
+ <%=t('spina.pages.edit_translation', language: '<span class="font-semibold">' + t("spina.languages.#{locale}") + '</span>').html_safe %>
15
+ </div>
16
+ <% end %>
17
+ <% end %>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
@@ -0,0 +1,26 @@
1
+ module Spina
2
+ module UserInterface
3
+ class TranslationsComponent < ApplicationComponent
4
+
5
+ def initialize(record, label: nil)
6
+ @record = record
7
+ @label = label
8
+ end
9
+
10
+ def render?
11
+ spina_locales.many?
12
+ end
13
+
14
+ def locales
15
+ spina_locales
16
+ end
17
+
18
+ private
19
+
20
+ def spina_locales
21
+ Spina.config.locales.map(&:to_sym)
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -74,7 +74,7 @@ module Spina
74
74
  end
75
75
 
76
76
  def render_404
77
- render file: "#{Rails.root}/public/404.html", status: 404
77
+ render file: "#{Rails.root}/public/404.html", status: 404, layout: false
78
78
  end
79
79
 
80
80
  def render_with_template(page)
@@ -5,7 +5,7 @@ module Spina
5
5
  before_action :set_breadcrumbs
6
6
 
7
7
  def index
8
- @media_folders = MediaFolder.order(:name)
8
+ @media_folders = MediaFolder.order(:name).includes(:images)
9
9
  @images = Image.sorted.where(media_folder: @media_folder).with_attached_file.page(params[:page]).per(25)
10
10
  end
11
11
 
@@ -53,7 +53,8 @@ module Spina
53
53
  if @image.saved_change_to_media_folder_id?
54
54
  render :update
55
55
  else
56
- redirect_to [:admin, @image]
56
+ @media_folders = MediaFolder.order(:name)
57
+ render @image
57
58
  end
58
59
  end
59
60
 
@@ -11,7 +11,7 @@ module Spina::Admin
11
11
 
12
12
  def update
13
13
  if @account.update(layout_params)
14
- redirect_to spina.edit_admin_layout_path, flash: {success: t('spina.layout.saved')}
14
+ redirect_to spina.edit_admin_layout_path(locale: @locale), flash: {success: t('spina.layout.saved')}
15
15
  else
16
16
  flash.now[:error] = t('spina.layout.couldnt_be_saved')
17
17
  render partial: 'error', status: :unprocessable_entity
@@ -5,7 +5,7 @@ module Spina
5
5
 
6
6
  def new
7
7
  @navigation_item = @navigation.navigation_items.new(parent_id: params[:parent_id])
8
- @pages = Page.sorted
8
+ @pages = Page.sorted.main.includes(:translations)
9
9
  end
10
10
 
11
11
  def create
@@ -1,10 +1,25 @@
1
1
  module Spina
2
2
  module ImagesHelper
3
+
4
+ def original_url(image)
5
+ return "" if image.nil?
6
+ main_app.url_for(image.file)
7
+ end
3
8
 
4
9
  def thumbnail_url(image)
5
10
  return "" if image.nil?
6
11
  main_app.url_for(image.variant(resize_to_fill: [400, 300]))
7
12
  end
13
+
14
+ def large_thumbnail_url(image)
15
+ return "" if image.nil?
16
+ main_app.url_for(image.variant(resize_to_fit: [800, 600]))
17
+ end
18
+
19
+ def preview_url(image)
20
+ return "" if image.nil?
21
+ main_app.url_for(image.variant(resize_to_limit: [1600, 1200]))
22
+ end
8
23
 
9
24
  def embedded_image_url(image)
10
25
  return "" if image.nil?
@@ -10,7 +10,7 @@ module Spina
10
10
  part = find_part(attributes[:name]) || attributes[:part_type].constantize.new
11
11
 
12
12
  # Copy all attributes to part
13
- %w(name title options).each do |attribute|
13
+ %w(name title hint options).each do |attribute|
14
14
  part.public_send("#{attribute}=", attributes[attribute.to_sym]) if part.respond_to?(attribute)
15
15
  end
16
16
 
@@ -13,7 +13,7 @@ module Spina
13
13
  signed_blob_id.present?
14
14
  end
15
15
 
16
- def signed_id
16
+ def signed_id(expires_in: nil)
17
17
  signed_blob_id
18
18
  end
19
19
 
@@ -5,8 +5,9 @@ module Spina
5
5
 
6
6
  attr_json_config(unknown_key: :strip)
7
7
 
8
- attr_json :title, :string
9
8
  attr_json :name, :string
9
+
10
+ attr_accessor :title, :hint
10
11
 
11
12
  def label
12
13
  content&.to_s
@@ -8,9 +8,11 @@ module Spina
8
8
 
9
9
  # Configuration
10
10
  config_accessor :menu_tag, :menu_css,
11
- :list_tag, :list_css,
12
- :list_item_tag, :list_item_css,
11
+ :list_tag, :list_css,
12
+ :list_item_tag, :list_item_css,
13
13
  :link_tag_css,
14
+ :active_list_item_css,
15
+ :current_list_item_css,
14
16
  :include_drafts,
15
17
  :depth # root nodes are at depth 0
16
18
 
@@ -51,9 +53,10 @@ module Spina
51
53
 
52
54
  def render_item(item)
53
55
  return nil unless item.materialized_path
56
+
54
57
  children = scoped_collection(item.children)
55
58
 
56
- content_tag(list_item_tag, class: list_item_css, data: {page_id: item.page_id, draft: (true if item.draft?) }) do
59
+ content_tag(list_item_tag, class: item_css(item), data: { page_id: item.page_id, draft: (true if item.draft?) }) do
57
60
  buffer = ActiveSupport::SafeBuffer.new
58
61
  buffer << link_to(item.menu_title, item.materialized_path, class: link_tag_css)
59
62
  buffer << render_items(children) if render_children?(item) && children.any?
@@ -70,6 +73,26 @@ module Spina
70
73
  return true unless depth
71
74
  item.depth < depth
72
75
  end
76
+
77
+ def item_css(item)
78
+ return current_list_item_css if apply_current_css?(item)
79
+ return active_list_item_css if apply_active_css?(item)
80
+ list_item_css
81
+ end
82
+
83
+ def apply_current_css?(item)
84
+ return false if current_list_item_css.nil?
85
+ Spina::Current.page == item
86
+ end
73
87
 
88
+ def apply_active_css?(item)
89
+ return false if apply_current_css?(item)
90
+ parent_of_current?(item)
91
+ end
92
+
93
+ def parent_of_current?(item)
94
+ return false if item.homepage?
95
+ Spina::Current.page.materialized_path.starts_with? item.materialized_path
96
+ end
74
97
  end
75
98
  end
@@ -2,7 +2,7 @@
2
2
  <div class="flex flex-col md:flex-row md:h-screen min-h-screen w-full overflow-hidden">
3
3
  <%= render partial: "spina/admin/shared/navigation" %>
4
4
 
5
- <section class="w-full md:overflow-scroll">
5
+ <section id="main" class="w-full md:overflow-scroll">
6
6
  <%= yield %>
7
7
 
8
8
  <%= turbo_frame_tag "flash" do %>
@@ -10,8 +10,8 @@
10
10
 
11
11
  <title>Spina CMS</title>
12
12
 
13
- <!-- Tailwind -->
14
- <%= stylesheet_link_tag 'spina/application', data: {turbolinks_track: true} %>
13
+ <!-- Stylesheets -->
14
+ <%= stylesheet_link_tag "spina/tailwind", "spina/fonts", "spina/animate", "data-turbo-track": "reload" %>
15
15
 
16
16
  <!-- Spina's importmap -->
17
17
  <%= spina_importmap_tags %>
@@ -1,7 +1,9 @@
1
1
  <%= turbo_frame_tag dom_id(image) do %>
2
2
  <div class="flex items-center h-12 border-b border-gray-200 px-8 hover:bg-white group">
3
3
  <div class="w-8 mr-4 h-12 flex justify-center">
4
- <%= image_tag thumbnail_url(image), class: "object-contain" %>
4
+ <%= link_to spina.admin_image_path(image), class: "flex items-center cursor-zoom-in", data: {turbo_frame: "modal"} do %>
5
+ <%= image_tag thumbnail_url(image), class: "object-contain" %>
6
+ <% end %>
5
7
  </div>
6
8
 
7
9
  <turbo-frame id="<%= dom_id(image) %>_filename" class="font-medium text-gray-800 h-full flex-1 w-56 text-sm flex items-center relative">
@@ -13,34 +15,36 @@
13
15
  <%=t 'spina.ui.rename' %>
14
16
  <% end %>
15
17
 
16
- <%= render Spina::UserInterface::DropdownComponent.new do |dropdown| %>
17
- <% dropdown.button(classes: 'btn btn-default h-7 px-2 text-xs') do %>
18
- <%= heroicon('folder', style: :solid, class: 'w-4 h-4 mr-1 text-gray-600') %>
19
- <%=t 'spina.ui.move_to' %>
20
- <%= heroicon('chevron-down', style: :solid, class: 'w-4 h-4') %>
21
- <% end %>
22
-
23
- <% dropdown.menu do %>
24
- <% if image.media_folder_id.present? %>
25
- <%= form_with model: image, url: spina.admin_image_path(image) do |f| %>
26
- <%= f.hidden_field :media_folder_id, value: nil %>
27
- <%= render Spina::UserInterface::DropdownButtonComponent.new do %>
28
- <%=t 'spina.media_library.no_folder' %>
29
- <% end %>
30
- <% end %>
18
+ <% if @media_folders.present? %>
19
+ <%= render Spina::UserInterface::DropdownComponent.new do |dropdown| %>
20
+ <% dropdown.button(classes: 'btn btn-default h-7 px-2 text-xs') do %>
21
+ <%= heroicon('folder', style: :solid, class: 'w-4 h-4 mr-1 text-gray-600') %>
22
+ <%=t 'spina.ui.move_to' %>
23
+ <%= heroicon('chevron-down', style: :solid, class: 'w-4 h-4') %>
31
24
  <% end %>
32
- <% Spina::MediaFolder.order(:name).each do |media_folder| %>
33
- <% if media_folder.id == image.media_folder_id %>
34
- <%= render Spina::UserInterface::DropdownButtonComponent.new(disabled: true) do %>
35
- <%= media_folder.name %>
36
- <% end %>
37
- <% else %>
25
+
26
+ <% dropdown.menu do %>
27
+ <% if image.media_folder_id.present? %>
38
28
  <%= form_with model: image, url: spina.admin_image_path(image) do |f| %>
39
- <%= f.hidden_field :media_folder_id, value: media_folder.id %>
40
-
29
+ <%= f.hidden_field :media_folder_id, value: nil %>
41
30
  <%= render Spina::UserInterface::DropdownButtonComponent.new do %>
31
+ <%=t 'spina.media_library.no_folder' %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+ <% @media_folders.each do |media_folder| %>
36
+ <% if media_folder.id == image.media_folder_id %>
37
+ <%= render Spina::UserInterface::DropdownButtonComponent.new(disabled: true) do %>
42
38
  <%= media_folder.name %>
43
39
  <% end %>
40
+ <% else %>
41
+ <%= form_with model: image, url: spina.admin_image_path(image) do |f| %>
42
+ <%= f.hidden_field :media_folder_id, value: media_folder.id %>
43
+
44
+ <%= render Spina::UserInterface::DropdownButtonComponent.new do %>
45
+ <%= media_folder.name %>
46
+ <% end %>
47
+ <% end %>
44
48
  <% end %>
45
49
  <% end %>
46
50
  <% end %>
@@ -56,8 +60,12 @@
56
60
  </div>
57
61
  </div>
58
62
  <div class="text-gray-500 text-xs w-32 text-right whitespace-nowrap"><%=l image.created_at, format: :short %></div>
59
- <div class="">
60
- <%= button_to spina.admin_image_path(image), method: :delete, class: "block py-3 px-4 ml-2 text-gray-500 hover:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.images.delete_confirmation_html')}} do %>
63
+ <div class="flex items-center ml-2">
64
+ <%= link_to original_url(image), class: "block py-3 px-2 text-gray-500 hover:text-gray-900", download: image.file.filename do %>
65
+ <%= heroicon('download', class: 'w-5 h-5') %>
66
+ <% end %>
67
+
68
+ <%= button_to spina.admin_image_path(image), method: :delete, class: "block py-3 px-2 text-gray-500 hover:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.images.delete_confirmation_html')}} do %>
61
69
  <%= heroicon('trash', class: "w-5 h-5") %>
62
70
  <% end %>
63
71
  </div>
@@ -64,7 +64,7 @@
64
64
 
65
65
  <% if @images.next_page %>
66
66
  <%= turbo_frame_tag "images-#{@images.next_page}", data: {action: "turbo:frame-load->infinite-scroll#load"} do %>
67
- <%= link_to t('spina.ui.load_more'), path_to_next_page(@images), class: 'hidden', data: {infinite_scroll_target: "button"} %>
67
+ <%= link_to t('spina.ui.load_more'), path_to_next_page(@images), class: "btn btn-default", data: {infinite_scroll_target: "button"} %>
68
68
  <% end %>
69
69
  <% end %>
70
70
  <% end %>
@@ -1 +1,3 @@
1
- <%= render @image %>
1
+ <%= render Spina::UserInterface::ModalComponent.new(size: "max-w-screen-lg w-auto") do |modal| %>
2
+ <%= image_tag preview_url(@image) %>
3
+ <% end %>
@@ -1,5 +1,7 @@
1
1
  <%= render Spina::UserInterface::HeaderComponent.new do |header| %>
2
2
  <% header.actions do %>
3
+ <%= render Spina::UserInterface::TranslationsComponent.new(@account, label: @locale.upcase) %>
4
+
3
5
  <% if Spina::Current.theme.layout_parts.any? %>
4
6
  <%= button_tag type: :submit, form: dom_id(@account), class: 'btn btn-primary', data: {controller: "button hotkeys", hotkeys: "command+s, ctrl+s", hotkeys_target: "button", action: "button#loading", loading_message: t('spina.ui.saving')} do %>
5
7
  <%= heroicon('check', style: :solid, class: 'w-5 h-5 mr-1 -ml-2') %>
@@ -11,8 +11,10 @@
11
11
  </div>
12
12
  <div class="text-gray-500 text-xs w-32 text-right"><%=l media_folder.created_at, format: :short %></div>
13
13
 
14
- <%= button_to spina.admin_media_folder_path(media_folder), method: :delete, class: "block py-3 px-4 ml-2 text-gray-500 hover:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.media_library.media_folder_delete_confirmation_html')}} do %>
15
- <%= heroicon('trash', class: 'w-5 h-5') %>
16
- <% end %>
14
+ <div class="flex items-center ml-11">
15
+ <%= button_to spina.admin_media_folder_path(media_folder), method: :delete, class: "block py-3 px-2 text-gray-500 hover:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.media_library.media_folder_delete_confirmation_html')}} do %>
16
+ <%= heroicon('trash', class: 'w-5 h-5') %>
17
+ <% end %>
18
+ </div>
17
19
  </div>
18
20
  <% end %>
@@ -10,7 +10,11 @@
10
10
  </h3>
11
11
 
12
12
  <div class="mt-3">
13
- <%= f.select :page_id, @pages.map{|page| [page.menu_title, page.id]}, {disabled: @navigation.navigation_items.pluck(:page_id), include_blank: t('spina.navigations.choose_page')}, class: 'form-select w-full' %>
13
+
14
+ <div class="border border-gray-300 rounded-md bg-white">
15
+ <%= render Spina::Pages::PageSelectComponent.new("navigation_item[page_id]", @pages, include_blank: t("spina.navigations.choose_page"), disabled: @navigation.navigation_items.pluck(:page_id)) %>
16
+ </div>
17
+
14
18
  </div>
15
19
  </div>
16
20
  </div>
@@ -1,7 +1,7 @@
1
1
  <%= render Spina::UserInterface::HeaderComponent.new do |header| %>
2
2
  <% header.navigation do %>
3
3
  <nav class="-mb-3 mt-4">
4
- <ul class="inline-flex w-auto rounded-md bg-white">
4
+ <ul class="inline-flex flex-wrap w-auto rounded-md bg-white">
5
5
  <% @navigations.each do |navigation| %>
6
6
  <%= render Spina::UserInterface::TabLinkComponent.new(spina.edit_admin_navigation_path(navigation), active: @navigation == navigation) do %>
7
7
  <%= heroicon('menu-alt-2', style: :solid, class: 'w-3 h-3 mr-1') %>
@@ -4,7 +4,7 @@
4
4
  <% if @page.draft? %>
5
5
  <span class="text-gray-400 ml-2 text-sm">(<%= Spina::Page.human_attribute_name(:draft) %>)</span>
6
6
  <% end %>
7
- <%= link_to @page.materialized_path, class: 'px-3 py-2 flex items-center text-gray-400 hover:text-gray-700', target: :blank, data: {turbo: false} do %>
7
+ <%= link_to @page.materialized_path(locale: @locale), class: 'px-3 py-2 flex items-center text-gray-400 hover:text-gray-700', target: :blank, data: {turbo: false} do %>
8
8
  <%= heroicon("external-link", style: :solid, class: "w-4 h-4") %>
9
9
  <% end %>
10
10
  <% end %>
@@ -33,4 +33,4 @@
33
33
  <% end %>
34
34
  <% end %>
35
35
  </div>
36
- </div>
36
+ </div>
@@ -14,7 +14,7 @@
14
14
 
15
15
  <% header.navigation do %>
16
16
  <nav class="-mb-1 md:-mb-3 mt-4">
17
- <ul class="inline-flex w-auto rounded-md bg-white">
17
+ <ul class="inline-flex flex-wrap w-auto rounded-md bg-white">
18
18
  <%= render Spina::UserInterface::TabLinkComponent.new(spina.admin_pages_path, active: @resource.nil?) do %>
19
19
  <%= heroicon('collection', style: :solid, class: "h-4 w-4 mr-1 -ml-1 opacity-75") %>
20
20
  <%=t 'spina.website.main' %>
@@ -1,8 +1,9 @@
1
1
  <div class="mt-6" data-controller="attachment-picker">
2
2
  <label for="price" class="block text-sm leading-5 font-medium text-gray-700"><%= f.object.title %></label>
3
+ <div class="text-gray-400 text-sm"><%= f.object.hint %></div>
3
4
 
4
5
  <%= f.hidden_field :signed_blob_id, data: {attachment_picker_target: 'signedBlobId'} %>
5
6
  <%= f.hidden_field :filename, data: {attachment_picker_target: 'filename'} %>
6
7
 
7
- <%= f.select :attachment_id, Spina::Attachment.sorted.map{|attachment| [attachment.file&.filename, attachment.id, data: {signed_blob_id: attachment.file&.blob&.signed_id, filename: attachment.file&.filename}]}, {include_blank: t("spina.attachments.choose_attachment")}, {class: "form-select", data: {action: "attachment-picker#pick"}} %>
8
+ <%= f.select :attachment_id, Spina::Attachment.sorted.map{|attachment| [attachment.file&.filename, attachment.id, data: {signed_blob_id: attachment.file&.blob&.signed_id, filename: attachment.file&.filename}]}, {include_blank: t("spina.attachments.choose_attachment")}, {class: "form-select mt-1", data: {action: "attachment-picker#pick"}} %>
8
9
  </div>
@@ -10,6 +10,7 @@
10
10
  <label class="block text-sm leading-5 font-medium text-gray-700">
11
11
  <%= f.object.title %>
12
12
  </label>
13
+ <div class="text-gray-400 text-sm"><%= f.object.hint %></div>
13
14
 
14
15
  <div class="flex items-start mt-1">
15
16
  <div class="flex items-center" data-image-collection-target="collection">