spina 2.15.1 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/location_component.html.erb +6 -5
- data/app/components/spina/pages/page_select_component.rb +1 -1
- data/app/controllers/spina/admin/images_controller.rb +9 -2
- data/app/controllers/spina/admin/layout_controller.rb +6 -0
- data/app/controllers/spina/admin/navigation_items_controller.rb +29 -9
- data/app/models/spina/account.rb +9 -2
- data/app/models/spina/navigation_item.rb +24 -7
- data/app/models/spina/page.rb +1 -1
- data/app/models/spina/parts/image.rb +4 -1
- data/app/presenters/spina/menu_presenter.rb +1 -1
- data/app/views/layouts/spina/admin/admin.html.erb +2 -0
- data/app/views/spina/admin/layout/edit.html.erb +38 -29
- 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/app/views/spina/admin/shared/_navigation.html.erb +4 -2
- data/config/locales/cs.yml +415 -0
- 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 +21 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0077bbb44829d71ab719fc2b7e51d625eff7a904c6d96d73fa4fb5d1b53c124
|
|
4
|
+
data.tar.gz: 685b9ed0d11538e6118b13cf72627fe70d8a4edf76d5ff2bbe00e72386d9e55e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 208e7e279bd356cdb47d9a12cafc7545caa73a824f25328cd119abb1d34c11c6a667f3feb49ec85390c8e836a024ce9bcde3c6cf764f8e8112a2123bc53be34f
|
|
7
|
+
data.tar.gz: b1eb0d8626ffc0873833ee45eec600ab7434392691c44877eac6353a3b9ebad0daf7641e0cbf4f5f84dcb66c5320c4f12cf91a74318211d4d0dc273741f87020
|
|
@@ -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">
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<div class="flex items-center border border-gray-300 rounded-md bg-white p-1 relative shadow-sm" data-controller="parent-pages">
|
|
2
|
+
<% if resources.many? %>
|
|
3
|
+
<%= helpers.heroicon("collection", style: :solid, class: 'w-5 h-5 ml-2 text-gray-400 absolute') %>
|
|
4
|
+
|
|
5
|
+
<%= f.select :resource_id, resources, {}, class: "form-select hover:bg-gray-100 bg-none shadow-none w-auto bg-transparent border-none focus:ring-0 cursor-pointer pr-2 pl-8 flex-none truncate", data: {controller: "select-placeholder", action: "select-placeholder#update parent-pages#update"} %>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<%= f.select :resource_id, resources, {}, class: "form-select hover:bg-gray-100 bg-none shadow-none w-auto bg-transparent border-none focus:ring-0 cursor-pointer pr-2 pl-8 flex-none truncate", data: {controller: "select-placeholder", action: "select-placeholder#update parent-pages#update"} %>
|
|
6
|
-
|
|
7
|
-
<%= helpers.heroicon('chevron-right', style: :solid, class: 'w-5 h-5 text-gray-600 flex-none') %>
|
|
7
|
+
<%= helpers.heroicon('chevron-right', style: :solid, class: 'w-5 h-5 text-gray-600 flex-none') %>
|
|
8
|
+
<% end %>
|
|
8
9
|
|
|
9
10
|
<%= helpers.turbo_frame_tag "parent_pages", src: default_parent_pages_path, class: 'flex-auto', data: {parent_pages_target: "frame"} %>
|
|
10
11
|
</div>
|
|
@@ -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
|
|
@@ -90,7 +97,7 @@ module Spina
|
|
|
90
97
|
end
|
|
91
98
|
|
|
92
99
|
def image_params
|
|
93
|
-
params.require(:image).permit(:media_folder_id, :file)
|
|
100
|
+
params.require(:image).permit(:media_folder_id, :file, files: [])
|
|
94
101
|
end
|
|
95
102
|
end
|
|
96
103
|
end
|
|
@@ -3,6 +3,7 @@ module Spina::Admin
|
|
|
3
3
|
before_action :set_account
|
|
4
4
|
before_action :set_locale
|
|
5
5
|
before_action :set_breadcrumb
|
|
6
|
+
before_action :get_layout_parts
|
|
6
7
|
|
|
7
8
|
admin_section :content
|
|
8
9
|
|
|
@@ -24,6 +25,11 @@ module Spina::Admin
|
|
|
24
25
|
def layout_params
|
|
25
26
|
params.require(:account).permit!
|
|
26
27
|
end
|
|
28
|
+
|
|
29
|
+
def get_layout_parts
|
|
30
|
+
@layout_parts = Spina::Current.theme.layout_parts
|
|
31
|
+
@layout_parts = {parts: @layout_parts} if @layout_parts.is_a?(Array)
|
|
32
|
+
end
|
|
27
33
|
|
|
28
34
|
def set_breadcrumb
|
|
29
35
|
add_breadcrumb t("spina.layout.layout")
|
|
@@ -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
|
@@ -5,7 +5,7 @@ module Spina
|
|
|
5
5
|
include Partable
|
|
6
6
|
include TranslatedContent
|
|
7
7
|
|
|
8
|
-
serialize :preferences
|
|
8
|
+
serialize :preferences, coder: Psych
|
|
9
9
|
|
|
10
10
|
after_save :bootstrap_website
|
|
11
11
|
|
|
@@ -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,18 +1,35 @@
|
|
|
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
|
|
|
8
13
|
scope :regular_pages, -> { joins(:page).where(spina_pages: {resource_id: nil}) }
|
|
9
14
|
scope :sorted, -> { order("spina_navigation_items.position") }
|
|
10
|
-
scope :live, -> {
|
|
11
|
-
scope :in_menu, -> {
|
|
12
|
-
scope :active, -> {
|
|
15
|
+
scope :live, -> { left_outer_joins(:page).where("spina_pages.draft = ? AND spina_pages.active = ? OR spina_pages.id IS NULL", false, true) }
|
|
16
|
+
scope :in_menu, -> { left_outer_joins(:page).where("spina_pages.show_in_menu = ? OR spina_pages.id IS NULL", true) }
|
|
17
|
+
scope :active, -> { left_outer_joins(:page).where("spina_pages.active = ? OR spina_pages.id IS NULL", 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
|