spina 2.15.1 → 2.16.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.

Potentially problematic release.


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

Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/spina/tailwind.css +39 -19
  3. data/app/assets/javascripts/spina/controllers/auto_file_upload_controller.js +49 -0
  4. data/app/assets/javascripts/spina/controllers/editor_insert_images_controller.js +38 -0
  5. data/app/assets/javascripts/spina/controllers/repeater_controller.js +4 -2
  6. data/app/assets/javascripts/spina/controllers/trix_controller.js +14 -2
  7. data/app/components/spina/forms/auto_file_upload_component.html.erb +6 -0
  8. data/app/components/spina/forms/auto_file_upload_component.rb +15 -0
  9. data/app/components/spina/forms/editor_insert_images_meta_component.html.erb +12 -0
  10. data/app/components/spina/forms/editor_insert_images_meta_component.rb +12 -0
  11. data/app/components/spina/forms/file_upload_component.html.erb +14 -0
  12. data/app/components/spina/forms/file_upload_component.rb +19 -0
  13. data/app/components/spina/forms/text_field_component.rb +3 -2
  14. data/app/components/spina/forms/trix_toolbar_component.html.erb +2 -2
  15. data/app/components/spina/media_picker/modal_component.html.erb +1 -11
  16. data/app/components/spina/pages/page_select_component.rb +1 -1
  17. data/app/controllers/spina/admin/images_controller.rb +8 -1
  18. data/app/controllers/spina/admin/navigation_items_controller.rb +29 -9
  19. data/app/models/spina/account.rb +8 -1
  20. data/app/models/spina/navigation_item.rb +21 -4
  21. data/app/models/spina/page.rb +1 -1
  22. data/app/views/layouts/spina/admin/admin.html.erb +2 -0
  23. data/app/views/spina/admin/navigation_items/_form.html.erb +24 -13
  24. data/app/views/spina/admin/navigation_items/_navigation_item.html.erb +15 -2
  25. data/app/views/spina/admin/navigation_items/_page_form.html.erb +5 -0
  26. data/app/views/spina/admin/navigation_items/_url_form.html.erb +13 -0
  27. data/app/views/spina/admin/navigation_items/edit.html.erb +3 -0
  28. data/app/views/spina/admin/parts/texts/_form.html.erb +2 -2
  29. data/config/locales/en.yml +6 -0
  30. data/config/locales/fr.yml +2 -2
  31. data/config/locales/ru.yml +76 -16
  32. data/db/migrate/17_add_custom_urls_to_spina_navigation_items.rb +18 -0
  33. data/lib/generators/spina/install_generator.rb +3 -2
  34. data/lib/generators/spina/tailwind_config_generator.rb +7 -1
  35. data/lib/spina/version.rb +1 -1
  36. metadata +17 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a4c285b310a44ed59a8188549402477161efe9e559dbbb6e5e52bac3a38ffc3
4
- data.tar.gz: b7055f33ccf17811f980f0e79843245a3ce7d9a5b11910b6f5ef080ed25adbe1
3
+ metadata.gz: 8740af9910fcafc714e8ebfe7c48e0676c696f8cc73aa0b2d5cfb13c832094e3
4
+ data.tar.gz: ca1863b0553c66be2487224afa9a4f037a8756b435d620b42293d435708941bb
5
5
  SHA512:
6
- metadata.gz: 715e97080cea0a49d9329eb0c52960e21438f9401548f079d04afb9d1c0d1e601671de1153c3c4d6ae8ab305c5ee726d2881a08cf85fea77fc6b415b36dcdca7
7
- data.tar.gz: 2623fea9312d74ea949df1588ff4b67bbd9c929af292c7097a1ec0cf09e738b75befb4e633092ca6fd19e248d7fd816e8f903f244fb75b0ea25a501955957f56
6
+ metadata.gz: 4cfff0e8aa2ad19e9e675ad359c7cc525c5782ba759b964a4d17bd9653cebeb113a4275bf08a8f058f841936c15d8f1efa2a0631ddd9e730d18e7363b9a649ea
7
+ data.tar.gz: ff06ff09cbced3560e43c69e86bda15f377e2faa290eb2362c4e270c54b337ec461ac14346c972459c242e181ce14e24f0127b239e0df7ecaea0676b2a669e3c
@@ -1,5 +1,5 @@
1
1
  /*
2
- ! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com
2
+ ! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com
3
3
  */
4
4
 
5
5
  /*
@@ -31,6 +31,7 @@
31
31
  3. Use a more readable tab size.
32
32
  4. Use the user's configured `sans` font-family by default.
33
33
  5. Use the user's configured `sans` font-feature-settings by default.
34
+ 6. Use the user's configured `sans` font-variation-settings by default.
34
35
  */
35
36
 
36
37
  html {
@@ -47,6 +48,8 @@ html {
47
48
  /* 4 */
48
49
  font-feature-settings: normal;
49
50
  /* 5 */
51
+ font-variation-settings: normal;
52
+ /* 6 */
50
53
  }
51
54
 
52
55
  /*
@@ -620,6 +623,9 @@ a, button {
620
623
  --tw-pan-y: ;
621
624
  --tw-pinch-zoom: ;
622
625
  --tw-scroll-snap-strictness: proximity;
626
+ --tw-gradient-from-position: ;
627
+ --tw-gradient-via-position: ;
628
+ --tw-gradient-to-position: ;
623
629
  --tw-ordinal: ;
624
630
  --tw-slashed-zero: ;
625
631
  --tw-numeric-figure: ;
@@ -667,6 +673,9 @@ a, button {
667
673
  --tw-pan-y: ;
668
674
  --tw-pinch-zoom: ;
669
675
  --tw-scroll-snap-strictness: proximity;
676
+ --tw-gradient-from-position: ;
677
+ --tw-gradient-via-position: ;
678
+ --tw-gradient-to-position: ;
670
679
  --tw-ordinal: ;
671
680
  --tw-slashed-zero: ;
672
681
  --tw-numeric-figure: ;
@@ -1779,10 +1788,7 @@ a, button {
1779
1788
  align-items: center;
1780
1789
  justify-content: center;
1781
1790
  position: fixed;
1782
- top: 0px;
1783
- right: 0px;
1784
- bottom: 0px;
1785
- left: 0px;
1791
+ inset: 0px;
1786
1792
  z-index: 40;
1787
1793
  height: 100%;
1788
1794
  padding: 1.5rem;
@@ -1943,10 +1949,7 @@ trix-editor [data-trix-mutable]::selection,
1943
1949
  }
1944
1950
 
1945
1951
  .inset-0 {
1946
- top: 0px;
1947
- right: 0px;
1948
- bottom: 0px;
1949
- left: 0px;
1952
+ inset: 0px;
1950
1953
  }
1951
1954
 
1952
1955
  .bottom-0 {
@@ -2066,6 +2069,14 @@ trix-editor [data-trix-mutable]::selection,
2066
2069
  margin-right: -1rem;
2067
2070
  }
2068
2071
 
2072
+ .-mt-0 {
2073
+ margin-top: -0px;
2074
+ }
2075
+
2076
+ .-mt-0\.5 {
2077
+ margin-top: -0.125rem;
2078
+ }
2079
+
2069
2080
  .-mt-4 {
2070
2081
  margin-top: -1rem;
2071
2082
  }
@@ -2110,6 +2121,10 @@ trix-editor [data-trix-mutable]::selection,
2110
2121
  margin-left: 2rem;
2111
2122
  }
2112
2123
 
2124
+ .ml-auto {
2125
+ margin-left: auto;
2126
+ }
2127
+
2113
2128
  .mr-1 {
2114
2129
  margin-right: 0.25rem;
2115
2130
  }
@@ -2266,6 +2281,10 @@ trix-editor [data-trix-mutable]::selection,
2266
2281
  width: 3.5rem;
2267
2282
  }
2268
2283
 
2284
+ .w-2\/3 {
2285
+ width: 66.666667%;
2286
+ }
2287
+
2269
2288
  .w-28 {
2270
2289
  width: 7rem;
2271
2290
  }
@@ -2842,13 +2861,13 @@ trix-editor [data-trix-mutable]::selection,
2842
2861
  }
2843
2862
 
2844
2863
  .from-spina-dark {
2845
- --tw-gradient-from: #3a3a70;
2846
- --tw-gradient-to: rgb(58 58 112 / 0);
2864
+ --tw-gradient-from: #3a3a70 var(--tw-gradient-from-position);
2865
+ --tw-gradient-to: rgb(58 58 112 / 0) var(--tw-gradient-to-position);
2847
2866
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
2848
2867
  }
2849
2868
 
2850
2869
  .to-spina-light {
2851
- --tw-gradient-to: #797ab8;
2870
+ --tw-gradient-to: #797ab8 var(--tw-gradient-to-position);
2852
2871
  }
2853
2872
 
2854
2873
  .object-contain {
@@ -2971,6 +2990,10 @@ trix-editor [data-trix-mutable]::selection,
2971
2990
  padding-bottom: 1.25rem;
2972
2991
  }
2973
2992
 
2993
+ .pl-1 {
2994
+ padding-left: 0.25rem;
2995
+ }
2996
+
2974
2997
  .pl-10 {
2975
2998
  padding-left: 2.5rem;
2976
2999
  }
@@ -3600,10 +3623,7 @@ trix-editor [data-trix-mutable]::selection,
3600
3623
  }
3601
3624
 
3602
3625
  .md\:inset-0 {
3603
- top: 0px;
3604
- right: 0px;
3605
- bottom: 0px;
3606
- left: 0px;
3626
+ inset: 0px;
3607
3627
  }
3608
3628
 
3609
3629
  .md\:-top-0 {
@@ -3769,13 +3789,13 @@ trix-editor [data-trix-mutable]::selection,
3769
3789
  }
3770
3790
 
3771
3791
  .md\:from-spina-dark {
3772
- --tw-gradient-from: #3a3a70;
3773
- --tw-gradient-to: rgb(58 58 112 / 0);
3792
+ --tw-gradient-from: #3a3a70 var(--tw-gradient-from-position);
3793
+ --tw-gradient-to: rgb(58 58 112 / 0) var(--tw-gradient-to-position);
3774
3794
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
3775
3795
  }
3776
3796
 
3777
3797
  .md\:to-spina-light {
3778
- --tw-gradient-to: #797ab8;
3798
+ --tw-gradient-to: #797ab8 var(--tw-gradient-to-position);
3779
3799
  }
3780
3800
 
3781
3801
  .md\:p-3 {
@@ -0,0 +1,49 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+
5
+ start(event) {
6
+ const file = event.detail.file
7
+ if (!file) return
8
+
9
+ const trixId = event.detail.trixId
10
+ this.trixTargetId = trixId
11
+
12
+ // DataTransfer() avoids the "TypeError: Failed to set the 'files' property on 'HTMLInputElement': Failed to convert
13
+ // value to 'FileList'." error which occurs if you try to directly assign: `this.fileField.files = [file]`
14
+ // https://stackoverflow.com/questions/52078853/is-it-possible-to-update-filelist
15
+ const fileList = new DataTransfer()
16
+ fileList.items.add(file)
17
+ this.fileField.files = fileList.files
18
+
19
+ const changeEvent = new Event("input"); // This triggers the upload
20
+ this.fileField.dispatchEvent(changeEvent);
21
+ }
22
+
23
+ get fileField() {
24
+ return this.element.querySelector('form input.image-upload-file-field')
25
+ }
26
+
27
+ get form() {
28
+ return this.element.querySelector('form')
29
+ }
30
+
31
+ get trixTargetId() {
32
+ return this.element.querySelector('form > #trix_target_id').value
33
+ }
34
+
35
+ set trixTargetId(value) {
36
+ this.element.querySelector('form > #trix_target_id').value = value
37
+ }
38
+
39
+ #imageData(imageMeta) {
40
+ return {
41
+ filename: imageMeta.dataset.filename,
42
+ signedBlobId: imageMeta.dataset.signedBlobId,
43
+ imageId: imageMeta.dataset.imageId,
44
+ embeddedUrl: imageMeta.dataset.embeddedUrl,
45
+ thumbnail: imageMeta.dataset.thumbnail
46
+ }
47
+ }
48
+
49
+ }
@@ -0,0 +1,38 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+
5
+ connect() {
6
+ // Gets triggered when refreshed by the ImagesController, and will process any images here
7
+ this.insertImages()
8
+ }
9
+
10
+ insertImages() {
11
+ const imagesMetadata = this.element.querySelectorAll('div.trix-insert-image')
12
+ imagesMetadata.forEach(tag => {
13
+ const imageData = this.#imageData(tag)
14
+ let insertImageEvent = new CustomEvent("media-picker:done", {detail: imageData})
15
+ const targetEditor = document.getElementById(this.trixTargetId)
16
+ this.trixTarget.dispatchEvent(insertImageEvent)
17
+ })
18
+ }
19
+
20
+ get trixTarget() {
21
+ return document.getElementById(this.trixTargetId)
22
+ }
23
+
24
+ get trixTargetId() {
25
+ return this.element.dataset.trixTargetId
26
+ }
27
+
28
+ #imageData(imageMeta) {
29
+ return {
30
+ filename: imageMeta.dataset.filename,
31
+ signedBlobId: imageMeta.dataset.signedBlobId,
32
+ imageId: imageMeta.dataset.imageId,
33
+ embeddedUrl: imageMeta.dataset.embeddedUrl,
34
+ thumbnail: imageMeta.dataset.thumbnail
35
+ }
36
+ }
37
+
38
+ }
@@ -40,9 +40,11 @@ export default class extends Controller {
40
40
 
41
41
  // Insert button
42
42
  this.listTarget.insertAdjacentHTML('beforeend', this.buttonHTML(time))
43
-
44
43
  // Insert fields
45
- this.contentTarget.insertAdjacentHTML('beforeend', html)
44
+ const parser = new DOMParser();
45
+ const docFields = parser.parseFromString(html, 'text/html');
46
+ this.contentTarget.appendChild(docFields.body.firstChild);
47
+
46
48
  }
47
49
 
48
50
  removeFields(event) {
@@ -19,7 +19,7 @@ export default class extends Controller {
19
19
  }
20
20
  }.bind(this))
21
21
  }
22
-
22
+
23
23
  insertEmbeddable(html) {
24
24
  let embeddable = new Trix.Attachment({
25
25
  content: html,
@@ -38,7 +38,15 @@ export default class extends Controller {
38
38
  </span>`, contentType: "Spina::Image"})
39
39
  this.editor.insertAttachment(attachment)
40
40
  }
41
-
41
+
42
+ fileAccept(event) {
43
+ const file = event.file
44
+ if(file) {
45
+ const startUploadEvent = new CustomEvent("auto-file-upload:start", { detail: { file, trixId: this.trixId }})
46
+ window.dispatchEvent(startUploadEvent)
47
+ }
48
+ }
49
+
42
50
  setAltText(event) {
43
51
  let alt = event.currentTarget.value
44
52
  let altLabel = alt
@@ -81,6 +89,10 @@ export default class extends Controller {
81
89
  get trixAttachment() {
82
90
  return this.getTrixAttachment(this.mutableImageAttachment.dataset.trixId)
83
91
  }
92
+
93
+ get trixId() {
94
+ return this.element.id
95
+ }
84
96
 
85
97
  get mutableImageAttachment() {
86
98
  return this.element.querySelector(`figure[data-trix-mutable][data-trix-content-type="Spina::Image"]`)
@@ -0,0 +1,6 @@
1
+ <div data-controller="auto-file-upload" data-action="auto-file-upload:start@window->auto-file-upload#start">
2
+ <%= render Spina::Forms::FileUploadComponent.new(origin: 'auto-file-upload', css_classes: 'hidden', turbo_frame: turbo_frame_id) %>
3
+ <turbo-frame id="auto-file-upload-images" data-trix-insert-target="">
4
+ <%= render Spina::Forms::EditorInsertImagesMetaComponent.new %>
5
+ </turbo-frame>
6
+ </div>
@@ -0,0 +1,15 @@
1
+ module Spina
2
+ module Forms
3
+
4
+ # Meant to be a hidden component for facilitating quick file uploads from drag and drop or paste action within Trix
5
+ class AutoFileUploadComponent < ApplicationComponent
6
+ attr_reader :turbo_frame_id
7
+
8
+ def initialize
9
+ @turbo_frame_id = "auto-file-upload-images"
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ <div id="auto-uploaded-images" data-controller="editor-insert-images" data-trix-target-id="<%= trix_target_id %>">
2
+ <% @images.each do |image| %>
3
+ <div
4
+ class="hidden trix-insert-image"
5
+ data-image-id="<%= image.id %>"
6
+ data-signed-blob-id="<%= image.file.blob&.signed_id %>"
7
+ data-filename="<%= image.file.filename %>"
8
+ data-thumbnail="<%= helpers.thumbnail_url(image) %>"
9
+ data-embedded-url="<%= helpers.embedded_image_url(image) %>"
10
+ ></div>
11
+ <% end %>
12
+ </div>
@@ -0,0 +1,12 @@
1
+ module Spina
2
+ module Forms
3
+ class EditorInsertImagesMetaComponent < ApplicationComponent
4
+ attr_reader :images, :trix_target_id
5
+
6
+ def initialize(trix_target_id: nil, images:[])
7
+ @trix_target_id = trix_target_id
8
+ @images = images
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ <div data-controller="file-upload-component" class=<%= @css_classes %>>
2
+ <%= form_with model: [:admin, Spina::Image.new], url: helpers.spina.admin_images_path, data: {controller: "form loading-button", loading_message: t('spina.media_library.uploading'), action: "turbo:submit-end->loading-button#doneLoading", origin: origin, turbo_frame: turbo_frame} do |f| %>
3
+ <%= hidden_field_tag :origin, origin %>
4
+ <%= hidden_field_tag :trix_target_id, value: @trix_target_id %>
5
+ <%= f.hidden_field :media_folder_id, value: media_folder&.id %>
6
+
7
+ <%= f.file_field :files, multiple: true, accept: "image/*", id: file_field_id, class: 'image-upload-file-field hidden', data: {action: "loading-button#loading form#requestSubmit"} %>
8
+
9
+ <%= button_tag(type: "button", class: "font-medium w-full text-gray-600 text-sm hover:bg-gray-200 px-3 py-2 cursor-pointer rounded-lg flex items-center w-full", data: { controller:"delegate-click", action: "delegate-click#click", 'loading-button-target': "button", 'delegate-click-target': "##{file_field_id}" }) do %>
10
+ <%= helpers.heroicon("upload", style: :solid, class: "w-5 h-5 text-spina mr-2") %>
11
+ <%=t 'spina.media_library.upload_from_device' %>
12
+ <% end %>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,19 @@
1
+ module Spina
2
+ module Forms
3
+ class FileUploadComponent < ApplicationComponent
4
+ attr_reader :css_classes, :id, :origin, :file_field_id, :media_folder, :trix_id, :turbo_frame
5
+
6
+ def initialize(origin:, css_classes: '', id: nil, trix_target_id: nil, media_folder: nil, turbo_frame: nil)
7
+ @origin = origin
8
+ @css_classes = css_classes
9
+ @id = id || SecureRandom.uuid
10
+ @media_folder = media_folder
11
+ @trix_target_id = trix_target_id # If inserting an image into Trix, specifies which one receives it
12
+ @turbo_frame = turbo_frame
13
+
14
+ @file_field_id = "image_upload_file_field_#{@id}"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -3,11 +3,12 @@ module Spina
3
3
  class TextFieldComponent < ApplicationComponent
4
4
  attr_accessor :f, :method, :size, :autofocus
5
5
 
6
- def initialize(f, method, size: "md", autofocus: false)
6
+ def initialize(f, method, size: "md", autofocus: false, placeholder: nil)
7
7
  @f = f
8
8
  @method = method
9
9
  @size = size
10
10
  @autofocus = autofocus
11
+ @placeholder = placeholder
11
12
  end
12
13
 
13
14
  def controllers
@@ -42,7 +43,7 @@ module Spina
42
43
  end
43
44
 
44
45
  def placeholder
45
- f.object.class.human_attribute_name(method)
46
+ @placeholder || f.object.class.human_attribute_name(method)
46
47
  end
47
48
  end
48
49
  end
@@ -1,6 +1,6 @@
1
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
- <div class="flex items-center bg-gray-200 rounded overflow-hidden mb-3 mr-3">
3
+ <div class="flex items-center bg-gray-200 rounded overflow-hidden mb-3 mr-3" data-trix-button-group="text-tools">
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">
5
5
  <svg class="w-4 h-4" fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z"/></svg>
6
6
  </button>
@@ -27,7 +27,7 @@
27
27
  </button>
28
28
  </div>
29
29
 
30
- <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3">
30
+ <div class="flex items-center bg-gray-200 rounded overflow-hidden mr-3 mb-3" data-trix-button-group="file-tools">
31
31
 
32
32
  <%= link_to helpers.spina.admin_media_picker_path(target: "insert_#{@trix_id}"), class: "hover:bg-gray-300 text-gray-700 w-9 h-9 flex items-center justify-center", data: {turbo_frame: "modal"} do %>
33
33
  <svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
@@ -58,17 +58,7 @@
58
58
  <% end %>
59
59
  <% end %>
60
60
 
61
- <%= form_with model: [:admin, Spina::Image.new], url: helpers.spina.admin_images_path, data: {controller: "form loading-button", loading_message: t('spina.media_library.uploading'), action: "turbo:submit-end->loading-button#doneLoading", turbo_frame: "media_picker"} do |f| %>
62
- <%= hidden_field_tag :modal, true %>
63
- <%= f.hidden_field :media_folder_id, value: @media_folder&.id %>
64
-
65
- <%= f.file_field :files, multiple: true, accept: "image/*", id: "new_image_file_field", class: 'hidden', data: {action: "loading-button#loading form#requestSubmit"} %>
66
-
67
- <button type="button" class="font-medium w-full text-gray-600 text-sm hover:bg-gray-200 px-3 py-2 cursor-pointer rounded-lg flex items-center w-full" data-controller="delegate-click" data-action="delegate-click#click" data-loading-button-target="button" data-delegate-click-target="#new_image_file_field">
68
- <%= helpers.heroicon("upload", style: :solid, class: "w-5 h-5 text-spina mr-2") %>
69
- <%=t 'spina.media_library.upload_from_device' %>
70
- </button>
71
- <% end %>
61
+ <%= render Spina::Forms::FileUploadComponent.new(origin: 'media-picker', media_folder: @media_folder, turbo_frame: 'media_picker') %>
72
62
  </div>
73
63
 
74
64
  <button type="button" class="btn btn-primary w-full mt-3" data-action="media-picker-modal#confirm modal#close">
@@ -12,7 +12,7 @@ module Spina::Pages
12
12
 
13
13
  def options
14
14
  Spina::Page.sort_by_ancestry(pages.arrange(order: :position)).map do |page|
15
- page_menu_title = page.menu_title.indent(page.depth, "–")
15
+ page_menu_title = page.menu_title&.indent(page.depth, "–")
16
16
  [page_menu_title, page.id]
17
17
  end
18
18
  end
@@ -35,8 +35,15 @@ module Spina
35
35
  image
36
36
  end.compact
37
37
 
38
- if params[:modal]
38
+ if params[:origin] == 'media-picker'
39
39
  redirect_to spina.admin_media_picker_path(media_folder_id: image_params[:media_folder_id])
40
+ elsif params[:origin] == 'auto-file-upload'
41
+ render turbo_stream: turbo_stream.update(
42
+ 'auto-file-upload-images',
43
+ Spina::Forms::EditorInsertImagesMetaComponent
44
+ .new(images: @images, trix_target_id: params[:trix_target_id])
45
+ .render_in(view_context)
46
+ )
40
47
  else
41
48
  render turbo_stream: turbo_stream.prepend("images", partial: "image", collection: @images)
42
49
  end
@@ -4,14 +4,33 @@ module Spina
4
4
  before_action :set_navigation
5
5
 
6
6
  def new
7
- @navigation_item = @navigation.navigation_items.new(parent_id: params[:parent_id])
7
+ @navigation_item = @navigation.navigation_items.new(parent_id: params[:parent_id], kind: params[:kind].presence || "page")
8
8
  @pages = Page.sorted.main.includes(:translations)
9
9
  end
10
10
 
11
11
  def create
12
- @navigation_item = NavigationItem.new(navigation_item_params)
12
+ @navigation_item = @navigation.navigation_items.new(navigation_item_params)
13
13
  if @navigation_item.save
14
- render turbo_stream: turbo_stream.append(@navigation_item.parent || @navigation, @navigation_item)
14
+ redirect_to spina.edit_admin_navigation_path(@navigation)
15
+ else
16
+ @pages = Page.sorted.main.includes(:translations)
17
+ render turbo_stream: turbo_stream.update(:navigation_item_form, partial: "form")
18
+ end
19
+ end
20
+
21
+ def edit
22
+ @navigation_item = NavigationItem.find(params[:id])
23
+ @pages = Page.sorted.main.includes(:translations)
24
+ end
25
+
26
+ def update
27
+ @navigation_item = NavigationItem.find(params[:id])
28
+
29
+ if @navigation_item.update(navigation_item_params)
30
+ redirect_to spina.edit_admin_navigation_path(@navigation)
31
+ else
32
+ @pages = Page.sorted.main.includes(:translations)
33
+ render turbo_stream: turbo_stream.update(:navigation_item_form, partial: "form")
15
34
  end
16
35
  end
17
36
 
@@ -23,13 +42,14 @@ module Spina
23
42
 
24
43
  private
25
44
 
26
- def navigation_item_params
27
- params.require(:navigation_item).permit(:page_id, :parent_id).merge(navigation_id: @navigation.id)
28
- end
45
+ def navigation_item_params
46
+ params.require(:navigation_item).permit(:kind, :page_id, :parent_id, :url_title, :url).merge(navigation_id: @navigation.id)
47
+ end
48
+
49
+ def set_navigation
50
+ @navigation = Navigation.find(params[:navigation_id])
51
+ end
29
52
 
30
- def set_navigation
31
- @navigation = Navigation.find(params[:navigation_id])
32
- end
33
53
  end
34
54
  end
35
55
  end
@@ -72,9 +72,16 @@ module Spina
72
72
 
73
73
  def find_or_create_custom_pages(theme)
74
74
  theme.custom_pages.each do |page|
75
+ ancestry = nil
76
+
77
+ if page[:parent].present?
78
+ parent_page = Page.find_by(name: page[:parent])
79
+ ancestry = [parent_page&.ancestry, parent_page&.id].compact.join("/")
80
+ end
81
+
75
82
  Page.where(name: page[:name])
76
83
  .first_or_create(title: page[:title])
77
- .update(view_template: page[:view_template], deletable: page[:deletable])
84
+ .update(view_template: page[:view_template], deletable: page[:deletable], ancestry: ancestry)
78
85
  end
79
86
  end
80
87
 
@@ -1,7 +1,12 @@
1
1
  module Spina
2
2
  class NavigationItem < ApplicationRecord
3
- belongs_to :navigation, touch: true
4
- belongs_to :page
3
+ belongs_to :navigation, touch: true, class_name: "Spina::Navigation"
4
+ belongs_to :page, optional: true, class_name: "Spina::Page"
5
+
6
+ # NavigationItems can be of two different kinds:
7
+ # - A link to a page
8
+ # - A link to a URL
9
+ enum(:kind, {page: "page", url: "url"}, default: :page, suffix: true)
5
10
 
6
11
  has_ancestry
7
12
 
@@ -11,8 +16,20 @@ module Spina
11
16
  scope :in_menu, -> { joins(:page).where(spina_pages: {show_in_menu: true}) }
12
17
  scope :active, -> { joins(:page).where(spina_pages: {active: true}) }
13
18
 
14
- validates :page, uniqueness: {scope: :navigation}
19
+ validates :page, uniqueness: {scope: :navigation}, presence: true, if: :page_kind?
20
+ validates :url, presence: true, if: :url_kind?
21
+ validates :url_title, presence: true, if: :url_kind?
15
22
 
16
- delegate :menu_title, :materialized_path, :draft?, :homepage?, to: :page
23
+ delegate :draft?, :homepage?, to: :page, allow_nil: true
24
+
25
+ def menu_title
26
+ return url_title if url_kind?
27
+ page&.menu_title
28
+ end
29
+
30
+ def materialized_path
31
+ return url if url_kind?
32
+ page&.materialized_path
33
+ end
17
34
  end
18
35
  end
@@ -19,7 +19,7 @@ module Spina
19
19
  has_many :navigations, through: :navigation_items
20
20
 
21
21
  # Pages can belong to a resource
22
- belongs_to :resource, optional: true, touch: true
22
+ belongs_to :resource, optional: true, touch: true, class_name: "Spina::Resource"
23
23
 
24
24
  scope :main, -> { where(resource_id: nil) }
25
25
  scope :regular_pages, -> { main }
@@ -13,6 +13,8 @@
13
13
 
14
14
  <!-- Make sure all modals are rendered in this frame -->
15
15
  <%= turbo_frame_tag "modal" %>
16
+
17
+ <%= render Spina::Forms::AutoFileUploadComponent.new %>
16
18
  <% end %>
17
19
 
18
20
  <%= render template: 'layouts/spina/admin/application' %>
@@ -1,28 +1,39 @@
1
- <%= turbo_frame_tag dom_id(@navigation_item, :form) do %>
2
- <%= form_with model: @navigation_item, url: spina.admin_navigation_navigation_items_path(@navigation), data: {turbo_frame: "_top", action: "turbo:submit-end->modal#close"} do |f| %>
1
+ <%= turbo_frame_tag :navigation_item_form do %>
2
+ <%= form_with model: @navigation_item, url: [:admin, @navigation, @navigation_item], data: {turbo_frame: "_top"} do |f| %>
3
3
  <%= f.hidden_field :parent_id %>
4
+ <%= f.hidden_field :kind %>
4
5
 
5
6
  <div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
6
7
  <div class="sm:flex sm:items-start">
7
8
  <div class="w-full">
8
- <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
9
- <%=t 'spina.navigations.add_menu_item' %>
9
+ <h3 class="text-lg leading-6 font-medium text-gray-900 flex" id="modal-headline">
10
+ <%= @navigation_item.persisted? ? t('spina.navigations.edit_menu_item') : t('spina.navigations.add_menu_item') %>
10
11
  </h3>
11
-
12
- <div class="mt-3">
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
-
12
+
13
+ <% if @navigation_item.new_record? %>
14
+ <ul class="inline-flex flex-wrap w-auto rounded-md mt-2">
15
+ <%= render Spina::UserInterface::TabLinkComponent.new(new_admin_navigation_navigation_item_path(@navigation, parent_id: @navigation_item.parent_id, kind: 'page'), active: @navigation_item.page_kind?) do %>
16
+ <%= heroicon('document', style: :outline, class: 'w-4 h-4 mr-1 -mt-0.5') %>
17
+ <%=t "spina.navigation_items.page" %>
18
+ <% end %>
19
+
20
+ <%= render Spina::UserInterface::TabLinkComponent.new(new_admin_navigation_navigation_item_path(@navigation, parent_id: @navigation_item.parent_id, kind: 'url'), active: @navigation_item.url_kind?) do %>
21
+ <%= heroicon('link', style: :outline, class: 'w-4 h-4 mr-1 -mt-0.5') %>
22
+ <%=t "spina.navigation_items.url" %>
23
+ <% end %>
24
+ </ul>
25
+ <% end %>
26
+
27
+ <div>
28
+ <%= render partial: "#{@navigation_item.kind}_form", locals: {f: f} %>
18
29
  </div>
19
30
  </div>
20
31
  </div>
21
32
  </div>
22
-
33
+
23
34
  <div class="px-4 pb-5 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
24
35
  <button type="submit" class="btn btn-primary w-full sm:w-auto sm:ml-2">
25
- <%=t 'spina.navigations.add_menu_item' %>
36
+ <%= @navigation_item.persisted? ? t('spina.ui.save_changes') : t('spina.navigations.add_menu_item') %>
26
37
  </button>
27
38
 
28
39
  <button type="button" class="btn btn-default w-full sm:w-auto mt-2 sm:mt-0" data-action="modal#close">
@@ -4,9 +4,17 @@
4
4
  <%= heroicon('menu-alt-4', style: :solid, class: 'w-4 h-4 -ml-1') %>
5
5
  </div>
6
6
 
7
- <div class="flex-1 truncate text-left"><%= navigation_item.menu_title %></div>
7
+ <div class="flex items-center w-2/3">
8
+ <div class="text-left truncate"><%= navigation_item.menu_title %></div>
9
+
10
+ <% if navigation_item.url_kind? %>
11
+ <%= link_to navigation_item.url, target: :blank, class: "h-7 flex items-center pl-1 text-gray-400 hover:text-gray-700" do %>
12
+ <%= heroicon("external-link", style: :mini, class: "w-4 h-4") %>
13
+ <% end %>
14
+ <% end %>
15
+ </div>
8
16
 
9
- <div data-controller="reveal" data-reveal-away-value class="relative h-full">
17
+ <div data-controller="reveal" data-reveal-away-value class="relative h-full ml-auto">
10
18
  <button type="button" data-action="reveal#toggle" class="h-full px-2 text-gray-400 hover:text-gray-700">
11
19
  <%= heroicon("dots-horizontal", class: "w-5 h-5") %>
12
20
  </button>
@@ -17,6 +25,11 @@
17
25
  <%= heroicon('plus', style: :mini, class: 'w-4 h-4 mr-2') %>
18
26
  <%=t 'spina.navigations.add_sub_item' %>
19
27
  <% end %>
28
+
29
+ <%= link_to spina.edit_admin_navigation_navigation_item_path(@navigation, id: navigation_item.id, locale: @locale), class: "px-3 py-1 text-xs leading-5 text-gray-200 hover:bg-gray-800 hover:text-white focus:outline-none flex items-center", data: {turbo_frame: "modal", action: "reveal#hide"} do %>
30
+ <%= heroicon('pencil-square', style: :mini, class: 'w-4 h-4 mr-2') %>
31
+ <%=t 'spina.navigations.edit_item' %>
32
+ <% end %>
20
33
 
21
34
  <%= button_to spina.admin_navigation_navigation_item_path(@navigation, navigation_item), method: :delete, class: "bg-transparent w-full text-left cursor-pointer font-medium px-3 py-1 text-xs leading-5 text-red-400 hover:bg-gray-800 hover:text-red-400 focus:outline-none flex items-center", form: {data: {action: "reveal#hide", controller: "confirm", confirm_message: t('spina.navigations.delete_item_confirmation_html')}} do %>
22
35
  <%= heroicon('trash', class: 'w-4 h-4 mr-2') %>
@@ -0,0 +1,5 @@
1
+ <div class="mt-4">
2
+ <div class="border border-gray-300 rounded-md bg-white">
3
+ <%= render Spina::Pages::PageSelectComponent.new("navigation_item[page_id]", @pages, include_blank: t("spina.navigations.choose_page"), disabled: @navigation.navigation_items.pluck(:page_id), selected: @navigation_item.page_id) %>
4
+ </div>
5
+ </div>
@@ -0,0 +1,13 @@
1
+ <div class="flex items-center space-x-2 mt-3">
2
+ <div class="w-56">
3
+ <%= render Spina::Forms::TextFieldComponent.new(f, :url_title, autofocus: true, placeholder: t("spina.navigations.url_title_placeholder")) %>
4
+ </div>
5
+
6
+ <div>
7
+ <%= heroicon("arrow-right", style: :mini, class: "w-4 h-4 text-gray-400") %>
8
+ </div>
9
+
10
+ <div class="w-full">
11
+ <%= render Spina::Forms::TextFieldComponent.new(f, :url, placeholder: t("spina.navigations.url_placeholder") ) %>
12
+ </div>
13
+ </div>
@@ -0,0 +1,3 @@
1
+ <%= render(Spina::UserInterface::ModalComponent.new) do %>
2
+ <%= render 'form' %>
3
+ <% end %>
@@ -4,10 +4,10 @@
4
4
  <div class="mt-1 relative">
5
5
  <%= f.hidden_field :content, id: "#{f.object.object_id}_input" %>
6
6
 
7
- <div class="relative form-textarea p-4 pt-0 shadow-sm max-w-5xl" data-controller="trix" id="<%= "insert_#{f.object.object_id}_trix-toolbar" %>" data-action="media-picker:done->trix#insertAttachment">
7
+ <div class="relative form-textarea p-4 pt-0 shadow-sm max-w-5xl" data-controller="trix" id="<%= "insert_#{f.object.object_id}_trix-toolbar" %>" data-action="media-picker:done->trix#insertAttachment trix-file-accept->trix#fileAccept">
8
8
  <%= render Spina::Forms::TrixToolbarComponent.new("#{f.object.object_id}_trix-toolbar") %>
9
9
 
10
10
  <trix-editor class="prose prose-sm focus:outline-none max-w-3xl xl:border-r border-dashed border-gray-200 pr-3" toolbar="<%= f.object.object_id %>_trix-toolbar" input="<%= f.object.object_id %>_input" data-trix-target="editor" data-action="trix-file-accept->trix#preventDefault"></trix-editor>
11
11
  </div>
12
12
  </div>
13
- </div>
13
+ </div>
@@ -225,9 +225,15 @@ en:
225
225
  delete_item: Delete item
226
226
  delete_item_confirmation_html: Are you sure you want to <span class='font-semibold'>delete</span> this menu item?
227
227
  description: Add and sort items as you see fit. You can add nested menu items, but don't have to.
228
+ edit_item: "Edit item"
228
229
  navigations: Navigations
229
230
  no_navigations: This website has no navigations yet. You can set one up in your theme configuration.
230
231
  sorting_saved: Sorting saved
232
+ url_placeholder: "https://"
233
+ url_title_placeholder: "Label"
234
+ navigation_items:
235
+ page: Page
236
+ url: URL
231
237
  notifications:
232
238
  alert: Oops! Something went wrong
233
239
  information: Information
@@ -230,7 +230,7 @@ fr:
230
230
  choose_option: Choisir option
231
231
  pages:
232
232
  add_page_to: Ajouter la page à
233
- add_translation: Ajouter traduction
233
+ add_translation: Ajouter traduction en %{language}
234
234
  advanced: Avancé
235
235
  cannot_be_deleted: Ne peut pas être supprimée
236
236
  change_order: "Changer l'ordre"
@@ -243,7 +243,7 @@ fr:
243
243
  delete_confirmation: 'Êtes-vous sûr de vouloir supprimer la page : <strong>%{subject}</strong> ?'
244
244
  deleted: Supprimé
245
245
  done_changing_order: "Changement d'ordre effectué"
246
- edit_translation: Modifier traduction
246
+ edit_translation: Modifier traduction en %{language}
247
247
  invisible_to_search_engines: "Votre site n'apparait pas sur les moteurs de recherche"
248
248
  invisible_to_search_engines_description: "Allez dans vos préférences pour activer l'indexation sur les moteurs de recherche."
249
249
  main_collection: Collection principale
@@ -1,4 +1,11 @@
1
+ ---
1
2
  ru:
3
+ activemodel:
4
+ attributes:
5
+ spina/embeds/vimeo:
6
+ url: vimeo.com/...
7
+ spina/embeds/youtube:
8
+ url: youtube.com/watch?v=...
2
9
  activerecord:
3
10
  attributes:
4
11
  spina/account:
@@ -31,10 +38,12 @@ ru:
31
38
  name: Имя
32
39
  spina/navigation:
33
40
  auto_add_pages: Автоматически добавлять страницы
34
- auto_add_pages_description: Новые страницы автоматически добавляются в эту навигацию.
41
+ auto_add_pages_description: Новые страницы автоматически добавляются в эту
42
+ навигацию.
35
43
  label: Метка
36
44
  pages: Страницы
37
- pages_description: Выберите страницы, которые вы хотите использовать в этой навигации
45
+ pages_description: Выберите страницы, которые вы хотите использовать в этой
46
+ навигации
38
47
  spina/page:
39
48
  ancestry: Родительская страница
40
49
  created_at: Создано в
@@ -51,7 +60,8 @@ ru:
51
60
  no_parent: Нет родителя
52
61
  resource: Ресурс
53
62
  seo_title: SEO <title>
54
- seo_title_description: Убедитесь, что ваш результат поиска хорошо выглядит в поисковых системах
63
+ seo_title_description: Убедитесь, что ваш результат поиска хорошо выглядит
64
+ в поисковых системах
55
65
  seo_title_placeholder: Это название используется в <title>-tag
56
66
  show_in_menu: Показать в навигации
57
67
  show_in_menu_description: При выключении эта страница не будет показана в
@@ -82,6 +92,12 @@ ru:
82
92
  password_description: Оставьте пустым, чтобы сохранить пароль
83
93
  role: Роль
84
94
  user: Пользователь
95
+ errors:
96
+ models:
97
+ spina/page:
98
+ attributes:
99
+ title:
100
+ blank: Требуется заголовок
85
101
  models:
86
102
  spina/attachment:
87
103
  few: Документа
@@ -103,6 +119,15 @@ ru:
103
119
  many: Пользователей
104
120
  one: Пользователь
105
121
  other: Пользователей
122
+ helpers:
123
+ label:
124
+ spina/embeds/button:
125
+ label: Метка кнопки
126
+ url: URL
127
+ spina/embeds/vimeo:
128
+ url: Vimeo URL
129
+ spina/embeds/youtube:
130
+ url: Youtube URL
106
131
  spina:
107
132
  accounts:
108
133
  contact_details: Контактная информация
@@ -120,6 +145,7 @@ ru:
120
145
  insert: Вставить документ
121
146
  insert_multiple: Вставить документы
122
147
  upload: Загрузить файлы
148
+ upload_one: Загрузить файл
123
149
  cancel: Отмена
124
150
  choose_from_library: Выбрать из библиотеки
125
151
  close: Закрыть
@@ -127,6 +153,9 @@ ru:
127
153
  delete_confirmation: Вы уверены, что хотите удалить <strong>%{subject}</strong>?
128
154
  edit: Редактировать
129
155
  edit_website: Редактировать сайт
156
+ embeds:
157
+ embed_a_component: Встроить компонент
158
+ embed_component: Встроить компонент
130
159
  forgot_password:
131
160
  expired: Срок действия вашего токена для сброса пароля истек
132
161
  mail_subject: Сбросить пароль
@@ -150,6 +179,7 @@ ru:
150
179
  insert_photo: Вставить фото
151
180
  insert_photos: Вставить фото
152
181
  link: Ссылка на сайт
182
+ missing_image: Отсутствует изображение
153
183
  new_folder: Новая папка
154
184
  organize: Организовать
155
185
  remove_image: Удалить изображение
@@ -189,9 +219,9 @@ ru:
189
219
  filename: Имя файла
190
220
  images: Изображения
191
221
  images_count:
192
- one: "%{count} изображение"
193
222
  few: "%{count} изображения"
194
223
  many: "%{count} изображений"
224
+ one: "%{count} изображение"
195
225
  other: "%{count} изображений"
196
226
  zero: нет изображений
197
227
  insert: Вставить изображение
@@ -213,11 +243,11 @@ ru:
213
243
  delete_item: Удалить пункт
214
244
  delete_item_confirmation_html: Вы уверены, что хотите <span class='font-semibold'>удалить</span>
215
245
  этот пункт меню?
216
- description: Добавляйте и сортируйте элементы по своему усмотрению. Вы можете добавлять элементы вложенного меню,
217
- но не обязательно.
246
+ description: Добавляйте и сортируйте элементы по своему усмотрению. Вы можете
247
+ добавлять элементы вложенного меню, но не обязательно.
218
248
  navigations: Навигация
219
- no_navigations: На этом веб-сайте еще нет навигации. Вы можете настроить её в своей
220
- конфигурации темы.
249
+ no_navigations: На этом веб-сайте еще нет навигации. Вы можете настроить её
250
+ в своей конфигурации темы.
221
251
  sorting_saved: Сортировка сохранена
222
252
  notifications:
223
253
  alert: Упс! Что-то пошло не так
@@ -226,16 +256,21 @@ ru:
226
256
  wrong_username_or_password: Неправильная почта или пароль
227
257
  options:
228
258
  choose_option: Выберите вариант
259
+ page_translations:
260
+ delete: Удалить перевод (%{translation})
261
+ delete_confirmation: Вы уверены, что хотите удалить этот перевод <strong>(%{subject})</strong>?
262
+ deleted: Перевод удален
229
263
  pages:
230
264
  add_page_to: 'Добавить страницу в:'
231
265
  add_translation: Добавить перевод %{language}
232
266
  advanced: Дополнительно
233
267
  cannot_be_deleted: Страница не может быть удалена
234
268
  change_order: Изменить порядок
235
- change_view_template: Change view template
269
+ change_view_template: Изменить шаблон представления
236
270
  change_view_template_confirmation: Вы уверены, что хотите сменить шаблон страницы?
237
271
  Содержимое страницы, которое не входит в новый шаблон, будет потеряно.
238
272
  concept: Концепция
273
+ couldnt_be_saved: Страница не может быть сохранена
239
274
  create: Создать страницу
240
275
  create_page: Создать %{template}
241
276
  delete: Удалить страницу
@@ -247,8 +282,8 @@ ru:
247
282
  invisible_to_search_engines_description: Перейти к вашим параметрам, чтобы активировать
248
283
  поисковые системы.
249
284
  main_collection: Основная коллекция
250
- materialize_path_confirmation: Вы уверены, что хотите перестроить путь для
251
- этой страницы?
285
+ materialize_path_confirmation: Вы уверены, что хотите перестроить путь для этой
286
+ страницы?
252
287
  missing_translations: Отсутствующие переводы
253
288
  move_page: Переместить страницу
254
289
  moved: Страница перемещена
@@ -272,6 +307,7 @@ ru:
272
307
  saved: Страница сохранена
273
308
  saving: Сохранение...
274
309
  search_engines: Поисковые системы
310
+ select_page: Выберите страницу
275
311
  show_in_menu: Невидимый
276
312
  skip_to_first_child: Переход на дочернюю страницу
277
313
  sorting_saved: Сортировка сохранена
@@ -318,8 +354,8 @@ ru:
318
354
  no_default_template: Нет шаблона по умолчанию
319
355
  saved: Коллекция страниц сохранена
320
356
  settings: "%{label} настройки"
321
- settings_description: Перед всеми страницами в этой коллекции будет стоять
322
- этот слаг.
357
+ settings_description: Перед всеми страницами в этой коллекции будет стоять этот
358
+ слаг.
323
359
  save: Сохранить
324
360
  search: Поиск...
325
361
  settings:
@@ -335,16 +371,38 @@ ru:
335
371
  ui:
336
372
  cancel: Отмена
337
373
  delete: Удалить
374
+ delete_item: Удалить %{item}
338
375
  load_more: Загрузи больше
339
376
  move_to: Перенести в
340
377
  new_entry: Новая запись
341
378
  optional: По желанию
379
+ or: или
342
380
  rename: Переименовать
381
+ replace: Заменить
343
382
  save_changes: Сохранить изменения
344
383
  saving: Сохранение...
384
+ user_mailer:
385
+ forgot_password:
386
+ button: Сбросить пароль
387
+ introduction: Недавно вы запросили сброс пароля для своей учетной записи Spina
388
+ CMS. Этот сброс пароля действителен только в течение следующих 2 часов.
389
+ Скопируйте и вставьте приведенный ниже URL-адрес в свой веб-браузер.
390
+ introduction_html: Недавно вы запросили сброс пароля для своей учетной записи
391
+ Spina CMS. Используйте кнопку ниже, чтобы сбросить его. <strong>Этот сброс
392
+ пароля действителен только в течение следующих 2 часов.</strong>
393
+ preheader: Воспользуйтесь этой ссылкой, чтобы сбросить пароль. Ссылка действительна
394
+ только 2 часа.
395
+ request_origin: В целях безопасности этот запрос был получен с устройства
396
+ %{platform} с использованием %{browser}. Если вы не запрашивали сброс пароля,
397
+ проигнорируйте это письмо.
398
+ salutation: Привет, %{name}
399
+ subject: Сбросить пароль
400
+ trouble: Если у вас возникли проблемы с кнопкой выше, скопируйте и вставьте
401
+ приведенный ниже URL-адрес в свой веб-браузер.
345
402
  users:
346
403
  authorization: Авторизация
347
- authorization_description: Администраторы могут управлять пользователями. Обычные пользователи не могут.
404
+ authorization_description: Администраторы могут управлять пользователями. Обычные
405
+ пользователи не могут.
348
406
  cannot_be_created: Пользователь не сохранен
349
407
  delete: Удалить пользователя
350
408
  delete_confirmation_html: Вы уверены, что хотите удалить пользователя <strong>%{user}</strong>?
@@ -353,8 +411,9 @@ ru:
353
411
  never_logged_in: Никогда не входил в систему
354
412
  new: Новый пользователь
355
413
  profile: Профиль
356
- profile_description: Эта информация будет использоваться для идентификации разных пользователей.
357
- Пользователи будут использовать свой адрес электронной почты для входа в систему.
414
+ profile_description: Эта информация будет использоваться для идентификации разных
415
+ пользователей. Пользователи будут использовать свой адрес электронной почты
416
+ для входа в систему.
358
417
  save: Сохранить пользователя
359
418
  saved: Пользователь сохранен
360
419
  visit_page: Посетите страницу
@@ -363,6 +422,7 @@ ru:
363
422
  content: Содержание
364
423
  documents: Документы
365
424
  images: Картинки
425
+ main: Основное
366
426
  media_library: Медиа библиотека
367
427
  media_library_description: Здесь вы найдете все ваши изображения и документы
368
428
  pages: Страницы
@@ -0,0 +1,18 @@
1
+ class AddCustomUrlsToSpinaNavigationItems < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :spina_navigation_items, :url, :string
4
+ add_column :spina_navigation_items, :url_title, :string
5
+ add_column :spina_navigation_items, :kind, :string, default: "page", null: false
6
+
7
+ reversible do |dir|
8
+ dir.up do
9
+ change_column_null :spina_navigation_items, :page_id, true
10
+ end
11
+
12
+ dir.down do
13
+ Spina::NavigationItem.where(page_id: nil).delete_all
14
+ change_column_null :spina_navigation_items, :page_id, false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -46,10 +46,11 @@ module Spina
46
46
  account = ::Spina::Account.first
47
47
  return if account.theme.present? && (silent_install? || !no?("Theme '#{account.theme}' is set. Skip? [Yn]"))
48
48
 
49
+ default_theme = account.theme || themes.first
49
50
  theme = if talkative_install?
50
- ask("What theme do you want to use?", limited_to: themes)
51
+ ask("What theme do you want to use?", limited_to: themes, default: default_theme)
51
52
  else
52
- account.theme || themes.first
53
+ default_theme
53
54
  end
54
55
 
55
56
  account.update(theme: theme)
@@ -3,7 +3,13 @@ module Spina
3
3
  source_root File.expand_path("../templates", __FILE__)
4
4
 
5
5
  def create_tailwind_config_file
6
- template "app/assets/config/spina/tailwind.config.js"
6
+ filename = "app/assets/config/spina/tailwind.config.js"
7
+ template filename
8
+ insert_into_file ".gitignore", <<~TEXT
9
+
10
+ # Ignore auto-generated Spina Tailwind CSS configuration
11
+ /#{filename}
12
+ TEXT
7
13
  end
8
14
  end
9
15
  end
data/lib/spina/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Spina
2
- VERSION = "2.15.1"
2
+ VERSION = "2.16.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spina
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.1
4
+ version: 2.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bram Jetten
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-28 00:00:00.000000000 Z
11
+ date: 2023-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -1191,11 +1191,13 @@ files:
1191
1191
  - app/assets/javascripts/spina/application.js
1192
1192
  - app/assets/javascripts/spina/controllers/application.js
1193
1193
  - app/assets/javascripts/spina/controllers/attachment_picker_controller.js
1194
+ - app/assets/javascripts/spina/controllers/auto_file_upload_controller.js
1194
1195
  - app/assets/javascripts/spina/controllers/autofocus_controller.js
1195
1196
  - app/assets/javascripts/spina/controllers/button_controller.js
1196
1197
  - app/assets/javascripts/spina/controllers/confetti_controller.js
1197
1198
  - app/assets/javascripts/spina/controllers/confirm_controller.js
1198
1199
  - app/assets/javascripts/spina/controllers/delegate_click_controller.js
1200
+ - app/assets/javascripts/spina/controllers/editor_insert_images_controller.js
1199
1201
  - app/assets/javascripts/spina/controllers/embed_controller.js
1200
1202
  - app/assets/javascripts/spina/controllers/embed_tag_controller.js
1201
1203
  - app/assets/javascripts/spina/controllers/exists_controller.js
@@ -1240,6 +1242,12 @@ files:
1240
1242
  - app/assets/stylesheets/spina/application.tailwind.css
1241
1243
  - app/assets/stylesheets/spina/fonts.css.erb
1242
1244
  - app/components/spina/application_component.rb
1245
+ - app/components/spina/forms/auto_file_upload_component.html.erb
1246
+ - app/components/spina/forms/auto_file_upload_component.rb
1247
+ - app/components/spina/forms/editor_insert_images_meta_component.html.erb
1248
+ - app/components/spina/forms/editor_insert_images_meta_component.rb
1249
+ - app/components/spina/forms/file_upload_component.html.erb
1250
+ - app/components/spina/forms/file_upload_component.rb
1243
1251
  - app/components/spina/forms/group_component.html.erb
1244
1252
  - app/components/spina/forms/group_component.rb
1245
1253
  - app/components/spina/forms/label_component.rb
@@ -1402,6 +1410,9 @@ files:
1402
1410
  - app/views/spina/admin/move_pages/new.html.erb
1403
1411
  - app/views/spina/admin/navigation_items/_form.html.erb
1404
1412
  - app/views/spina/admin/navigation_items/_navigation_item.html.erb
1413
+ - app/views/spina/admin/navigation_items/_page_form.html.erb
1414
+ - app/views/spina/admin/navigation_items/_url_form.html.erb
1415
+ - app/views/spina/admin/navigation_items/edit.html.erb
1405
1416
  - app/views/spina/admin/navigation_items/new.html.erb
1406
1417
  - app/views/spina/admin/navigations/_navigation.html.erb
1407
1418
  - app/views/spina/admin/navigations/edit.html.erb
@@ -1491,6 +1502,7 @@ files:
1491
1502
  - db/migrate/14_add_json_attributes_to_spina_pages.rb
1492
1503
  - db/migrate/15_add_slug_to_spina_resources.rb
1493
1504
  - db/migrate/16_add_ancestry_cache_columns_to_spina_pages.rb
1505
+ - db/migrate/17_add_custom_urls_to_spina_navigation_items.rb
1494
1506
  - db/migrate/1_create_spina_tables.rb
1495
1507
  - db/migrate/2_create_spina_translation_tables.rb
1496
1508
  - db/migrate/3_create_spina_navigations.rb
@@ -1566,8 +1578,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1566
1578
  - !ruby/object:Gem::Version
1567
1579
  version: '0'
1568
1580
  requirements: []
1569
- rubygems_version: 3.1.6
1570
- signing_key:
1581
+ rubygems_version: 3.4.18
1582
+ signing_key:
1571
1583
  specification_version: 4
1572
1584
  summary: Spina
1573
1585
  test_files: []