spina 2.15.0 → 2.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) 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/actions_component.html.erb +1 -1
  17. data/app/components/spina/pages/page_select_component.rb +1 -1
  18. data/app/controllers/spina/admin/images_controller.rb +8 -1
  19. data/app/controllers/spina/admin/navigation_items_controller.rb +29 -9
  20. data/app/models/spina/account.rb +8 -1
  21. data/app/models/spina/navigation_item.rb +21 -4
  22. data/app/models/spina/page.rb +1 -1
  23. data/app/views/layouts/spina/admin/admin.html.erb +2 -0
  24. data/app/views/spina/admin/attachments/index.html.erb +1 -1
  25. data/app/views/spina/admin/navigation_items/_form.html.erb +24 -13
  26. data/app/views/spina/admin/navigation_items/_navigation_item.html.erb +15 -2
  27. data/app/views/spina/admin/navigation_items/_page_form.html.erb +5 -0
  28. data/app/views/spina/admin/navigation_items/_url_form.html.erb +13 -0
  29. data/app/views/spina/admin/navigation_items/edit.html.erb +3 -0
  30. data/app/views/spina/admin/parts/texts/_form.html.erb +2 -2
  31. data/config/locales/en.yml +6 -0
  32. data/config/locales/fr.yml +2 -2
  33. data/config/locales/ru.yml +76 -16
  34. data/db/migrate/17_add_custom_urls_to_spina_navigation_items.rb +18 -0
  35. data/lib/generators/spina/install_generator.rb +3 -2
  36. data/lib/generators/spina/tailwind_config_generator.rb +7 -1
  37. data/lib/spina/version.rb +1 -1
  38. metadata +17 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3558f114de8005fc0c084368f65cd8dbae2f8b0bcc49db4e960d1c7a6222183e
4
- data.tar.gz: '098bdbd62bce0981fc65f6b4ae681a9524d9cab0402495405f9212aa37a39cfd'
3
+ metadata.gz: 8740af9910fcafc714e8ebfe7c48e0676c696f8cc73aa0b2d5cfb13c832094e3
4
+ data.tar.gz: ca1863b0553c66be2487224afa9a4f037a8756b435d620b42293d435708941bb
5
5
  SHA512:
6
- metadata.gz: 2fbf8a1433fdb7bcbe360d866559508d65c25dd15a0b3477fb99ea8fefc9c5cab9a83a4370e168f04243989c13276ab7c653f4a89f32e6c4c63001d06d3041c4
7
- data.tar.gz: 7441777cda493884b34bde643c302f2bfef71ea3533616a0d7bb0e94445c8731dd7324fe25da3322afff142137e1bb62ed73f2ddde65cc707f02525f64af50c0
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">
@@ -40,7 +40,7 @@
40
40
  <% end %>
41
41
 
42
42
  <% if @page.deletable? %>
43
- <%= button_to t('spina.pages.delete'), helpers.spina.admin_page_path(@page), method: :delete, class: "block w-full text-left px-4 py-2 text-sm leading-5 font-medium text-red-500 cursor-pointer bg-white hover:bg-red-100 hover:bg-opacity-50 hover:text-red-500 focus:outline-none focus:bg-gray-100 focus:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.pages.delete_confirmation', subject: @page.title)}} %>
43
+ <%= button_to t('spina.pages.delete'), helpers.spina.admin_page_path(@page), method: :delete, class: "block w-full text-left px-4 py-2 text-sm leading-5 font-medium text-red-500 cursor-pointer bg-white hover:bg-red-100 hover:bg-opacity-50 hover:text-red-500 focus:outline-none focus:bg-gray-100 focus:text-gray-900", form: {data: {controller: "confirm", confirm_message: t('spina.pages.delete_confirmation', subject: sanitize(@page.title))}} %>
44
44
  <% else %>
45
45
  <span class="block px-4 py-2 text-sm leading-5 text-gray-400"><%=t 'spina.pages.cannot_be_deleted' %></span>
46
46
  <% end %>
@@ -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' %>
@@ -42,7 +42,7 @@
42
42
 
43
43
  <% if @attachments.next_page %>
44
44
  <%= turbo_frame_tag "attachments-#{@attachments.next_page}", data: {action: "turbo:frame-load->infinite-scroll#load"} do %>
45
- <%= link_to t('spina.ui.load_more'), path_to_next_page(@attachments), class: 'hidden', data: {infinite_scroll_target: "button"} %>
45
+ <%= link_to t('spina.ui.load_more'), path_to_next_page(@attachments), class: "btn btn-default", data: {infinite_scroll_target: "button"} %>
46
46
  <% end %>
47
47
  <% end %>
48
48
  <% end %>
@@ -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.0"
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.0
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-05-10 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: []