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