spina 2.15.1 → 2.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/builds/spina/tailwind.css +39 -19
- data/app/assets/javascripts/spina/controllers/auto_file_upload_controller.js +49 -0
- data/app/assets/javascripts/spina/controllers/editor_insert_images_controller.js +38 -0
- data/app/assets/javascripts/spina/controllers/repeater_controller.js +4 -2
- data/app/assets/javascripts/spina/controllers/trix_controller.js +14 -2
- data/app/components/spina/forms/auto_file_upload_component.html.erb +6 -0
- data/app/components/spina/forms/auto_file_upload_component.rb +15 -0
- data/app/components/spina/forms/editor_insert_images_meta_component.html.erb +12 -0
- data/app/components/spina/forms/editor_insert_images_meta_component.rb +12 -0
- data/app/components/spina/forms/file_upload_component.html.erb +14 -0
- data/app/components/spina/forms/file_upload_component.rb +19 -0
- data/app/components/spina/forms/text_field_component.rb +3 -2
- data/app/components/spina/forms/trix_toolbar_component.html.erb +2 -2
- data/app/components/spina/media_picker/modal_component.html.erb +1 -11
- data/app/components/spina/pages/page_select_component.rb +1 -1
- data/app/controllers/spina/admin/images_controller.rb +8 -1
- data/app/controllers/spina/admin/navigation_items_controller.rb +29 -9
- data/app/models/spina/account.rb +8 -1
- data/app/models/spina/navigation_item.rb +21 -4
- data/app/models/spina/page.rb +1 -1
- data/app/views/layouts/spina/admin/admin.html.erb +2 -0
- data/app/views/spina/admin/navigation_items/_form.html.erb +24 -13
- data/app/views/spina/admin/navigation_items/_navigation_item.html.erb +15 -2
- data/app/views/spina/admin/navigation_items/_page_form.html.erb +5 -0
- data/app/views/spina/admin/navigation_items/_url_form.html.erb +13 -0
- data/app/views/spina/admin/navigation_items/edit.html.erb +3 -0
- data/app/views/spina/admin/parts/texts/_form.html.erb +2 -2
- data/config/locales/en.yml +6 -0
- data/config/locales/fr.yml +2 -2
- data/config/locales/ru.yml +76 -16
- data/db/migrate/17_add_custom_urls_to_spina_navigation_items.rb +18 -0
- data/lib/generators/spina/install_generator.rb +3 -2
- data/lib/generators/spina/tailwind_config_generator.rb +7 -1
- data/lib/spina/version.rb +1 -1
- metadata +17 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8740af9910fcafc714e8ebfe7c48e0676c696f8cc73aa0b2d5cfb13c832094e3
|
4
|
+
data.tar.gz: ca1863b0553c66be2487224afa9a4f037a8756b435d620b42293d435708941bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cfff0e8aa2ad19e9e675ad359c7cc525c5782ba759b964a4d17bd9653cebeb113a4275bf08a8f058f841936c15d8f1efa2a0631ddd9e730d18e7363b9a649ea
|
7
|
+
data.tar.gz: ff06ff09cbced3560e43c69e86bda15f377e2faa290eb2362c4e270c54b337ec461ac14346c972459c242e181ce14e24f0127b239e0df7ecaea0676b2a669e3c
|
@@ -1,5 +1,5 @@
|
|
1
1
|
/*
|
2
|
-
! tailwindcss v3.2
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
<%=
|
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
|
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[:
|
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 =
|
12
|
+
@navigation_item = @navigation.navigation_items.new(navigation_item_params)
|
13
13
|
if @navigation_item.save
|
14
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
data/app/models/spina/account.rb
CHANGED
@@ -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 :
|
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
|
data/app/models/spina/page.rb
CHANGED
@@ -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 }
|
@@ -1,28 +1,39 @@
|
|
1
|
-
<%= turbo_frame_tag
|
2
|
-
<%= form_with model: @navigation_item, url:
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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-
|
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>
|
@@ -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>
|
data/config/locales/en.yml
CHANGED
@@ -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
|
data/config/locales/fr.yml
CHANGED
@@ -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
|
data/config/locales/ru.yml
CHANGED
@@ -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:
|
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
|
-
|
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
|
-
|
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
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.
|
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-
|
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.
|
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: []
|