spina 2.3.5 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spina might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +10 -3
- data/app/assets/javascripts/spina/application.js +1 -1
- data/app/assets/javascripts/spina/controllers/application.js +11 -0
- data/app/assets/javascripts/spina/controllers/embed_controller.js +16 -0
- data/app/assets/javascripts/spina/controllers/embed_tag_controller.js +16 -0
- data/app/assets/javascripts/spina/controllers/index.js +5 -0
- data/app/assets/javascripts/spina/controllers/trix_controller.js +12 -2
- data/app/assets/stylesheets/spina/_fonts.css.erb +114 -0
- data/app/assets/stylesheets/spina/_tailwind.css +18 -5
- data/app/assets/stylesheets/spina/application.css +6 -0
- data/app/assets/stylesheets/spina/tailwind/custom.css +13 -5
- data/app/components/spina/forms/trix_toolbar_component.html.erb +26 -15
- data/app/components/spina/media_picker/image_component.html.erb +1 -1
- data/app/components/spina/pages/new_page_button_component.html.erb +32 -0
- data/app/components/spina/pages/new_page_button_component.rb +19 -0
- data/app/controllers/concerns/spina/frontend.rb +1 -1
- data/app/controllers/spina/admin/embeds_controller.rb +36 -0
- data/app/controllers/spina/admin/pages_controller.rb +1 -2
- data/app/controllers/spina/admin/password_resets_controller.rb +2 -4
- data/app/helpers/spina/admin/icons_helper.rb +2 -2
- data/app/helpers/spina/images_helper.rb +1 -1
- data/app/helpers/spina/spina_helper.rb +3 -2
- data/app/mailers/spina/application_mailer.rb +7 -0
- data/app/mailers/spina/user_mailer.rb +6 -14
- data/app/models/spina/embeds/base.rb +7 -0
- data/app/models/spina/embeds/button.rb +13 -0
- data/app/models/spina/embeds/vimeo.rb +39 -0
- data/app/models/spina/embeds/youtube.rb +39 -0
- data/app/models/spina/page.rb +10 -3
- data/app/models/spina/user.rb +6 -0
- data/app/presenters/spina/content_presenter.rb +1 -1
- data/app/presenters/spina/rich_text_presenter.rb +45 -0
- data/app/views/layouts/spina/admin/application.html.erb +1 -1
- data/app/views/layouts/spina/mail.html.erb +476 -1
- data/app/views/spina/admin/embeds/new.html.erb +47 -0
- data/app/views/spina/admin/pages/edit_template.html.erb +1 -1
- data/app/views/spina/admin/pages/index.html.erb +1 -21
- data/app/views/spina/admin/shared/_flash.html.erb +2 -2
- data/app/views/spina/embeds/buttons/_button.html.erb +3 -0
- data/app/views/spina/embeds/buttons/_button_fields.html.erb +14 -0
- data/app/views/spina/embeds/buttons/_trix_button.html.erb +3 -0
- data/app/views/spina/embeds/vimeos/_thumbnail.html.erb +6 -0
- data/app/views/spina/embeds/vimeos/_vimeo.html.erb +1 -0
- data/app/views/spina/embeds/vimeos/_vimeo_fields.html.erb +9 -0
- data/app/views/spina/embeds/youtubes/_thumbnail.html.erb +6 -0
- data/app/views/spina/embeds/youtubes/_youtube.html.erb +1 -0
- data/app/views/spina/embeds/youtubes/_youtube_fields.html.erb +9 -0
- data/app/views/spina/user_mailer/forgot_password.html.erb +57 -1
- data/app/views/spina/user_mailer/forgot_password.text.erb +9 -0
- data/config/initializers/importmap.rb +12 -0
- data/config/locales/en.yml +30 -0
- data/config/locales/nl.yml +1 -0
- data/config/routes.rb +5 -2
- data/lib/generators/spina/embed_generator.rb +18 -0
- data/lib/generators/spina/install_generator.rb +36 -14
- data/lib/generators/spina/templates/app/models/spina/embeds/embed.rb.tt +18 -0
- data/lib/generators/spina/templates/app/views/default/pages/homepage.html.erb +1 -1
- data/lib/generators/spina/templates/app/views/spina/embeds/_fields.html.erb +13 -0
- data/lib/generators/spina/templates/app/views/spina/embeds/_partial.html.erb +2 -0
- data/lib/generators/spina/templates/config/initializers/spina.rb +7 -0
- data/lib/generators/spina/templates/config/initializers/themes/default.rb +3 -0
- data/lib/generators/spina/templates/config/initializers/themes/demo.rb +4 -1
- data/lib/spina/embeddable.rb +48 -0
- data/lib/spina/embeds/trix_conversion.rb +41 -0
- data/lib/spina/embeds.rb +11 -0
- data/lib/spina/engine.rb +7 -21
- data/lib/spina/part.rb +10 -2
- data/lib/spina/railtie.rb +10 -0
- data/lib/spina/theme.rb +16 -3
- data/lib/spina/theme_reloader.rb +20 -0
- data/lib/spina/version.rb +1 -1
- data/lib/spina.rb +11 -0
- data/lib/tasks/spina_tasks.rake +19 -2
- metadata +66 -34
- data/app/assets/stylesheets/spina/_fonts.scss +0 -114
- data/app/assets/stylesheets/spina/application.sass +0 -3
- data/app/views/spina/user_mailer/forgot_password.txt.erb +0 -1
@@ -1 +1,57 @@
|
|
1
|
-
|
1
|
+
<span class="preheader" style="display: none !important; visibility: hidden; mso-hide: all; font-size: 1px; line-height: 1px; max-height: 0; max-width: 0; opacity: 0; overflow: hidden;"><%=t "spina.user_mailer.forgot_password.preheader" %></span>
|
2
|
+
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%; -premailer-width: 100%; -premailer-cellpadding: 0; -premailer-cellspacing: 0; margin: 0; padding: 0;">
|
3
|
+
<tr>
|
4
|
+
<td align="center" style="word-break: break-word; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px;">
|
5
|
+
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%; -premailer-width: 100%; -premailer-cellpadding: 0; -premailer-cellspacing: 0; margin: 0; padding: 0;">
|
6
|
+
<!-- Email Body -->
|
7
|
+
<tr>
|
8
|
+
<td class="email-body" width="570" cellpadding="0" cellspacing="0" style="word-break: break-word; margin: 0; padding: 0; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px; width: 100%; -premailer-width: 100%; -premailer-cellpadding: 0; -premailer-cellspacing: 0;">
|
9
|
+
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation" style="width: 570px; -premailer-width: 570px; -premailer-cellpadding: 0; -premailer-cellspacing: 0; margin: 0 auto; padding: 0;">
|
10
|
+
<!-- Body content -->
|
11
|
+
<tr>
|
12
|
+
<td class="content-cell" style="word-break: break-word; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px; padding: 35px;">
|
13
|
+
<div class="f-fallback">
|
14
|
+
<h1 style="margin-top: 0; color: #333333; font-size: 22px; font-weight: bold; text-align: left;" align="left">
|
15
|
+
<%=t "spina.user_mailer.forgot_password.salutation", name: @user.name %>
|
16
|
+
</h1>
|
17
|
+
<p style="font-size: 16px; line-height: 1.625; color: #333; margin: .4em 0 1.1875em;">
|
18
|
+
<%=t "spina.user_mailer.forgot_password.introduction_html" %>
|
19
|
+
</p>
|
20
|
+
<!-- Action -->
|
21
|
+
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation" style="width: 100%; -premailer-width: 100%; -premailer-cellpadding: 0; -premailer-cellspacing: 0; text-align: center; margin: 30px auto; padding: 0;">
|
22
|
+
<tr>
|
23
|
+
<td align="center" style="word-break: break-word; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px;">
|
24
|
+
<!-- Border based button
|
25
|
+
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
26
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
|
27
|
+
<tr>
|
28
|
+
<td align="center" style="word-break: break-word; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px;">
|
29
|
+
<a href="<%= spina.edit_admin_password_reset_url(@user.password_reset_token) %>" class="f-fallback button button--green" target="_blank" style="color: #FFF; border-color: #6865b4; border-style: solid; border-width: 10px 18px; background-color: #6865b4; display: inline-block; text-decoration: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.16); -webkit-text-size-adjust: none; box-sizing: border-box;"><%=t "spina.user_mailer.forgot_password.button" %></a>
|
30
|
+
</td>
|
31
|
+
</tr>
|
32
|
+
</table>
|
33
|
+
</td>
|
34
|
+
</tr>
|
35
|
+
</table>
|
36
|
+
<p style="font-size: 16px; line-height: 1.625; color: #333; margin: .4em 0 1.1875em;">
|
37
|
+
<%=t "spina.user_mailer.forgot_password.request_origin", platform: @browser.platform.name, browser: @browser.name %>
|
38
|
+
</p>
|
39
|
+
<!-- Sub copy -->
|
40
|
+
<table class="body-sub" role="presentation" style="margin-top: 25px; padding-top: 25px; border-top-width: 1px; border-top-color: #EAEAEC; border-top-style: solid;">
|
41
|
+
<tr>
|
42
|
+
<td style="word-break: break-word; font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-size: 16px;">
|
43
|
+
<p class="f-fallback sub" style="font-size: 13px; line-height: 1.625; color: #333; margin: .4em 0 1.1875em;"><%=t "spina.user_mailer.forgot_password.trouble" %></p>
|
44
|
+
<p class="f-fallback sub" style="font-size: 13px; line-height: 1.625; color: #333; margin: .4em 0 1.1875em;"><%= spina.edit_admin_password_reset_url(@user.password_reset_token) %></p>
|
45
|
+
</td>
|
46
|
+
</tr>
|
47
|
+
</table>
|
48
|
+
</div>
|
49
|
+
</td>
|
50
|
+
</tr>
|
51
|
+
</table>
|
52
|
+
</td>
|
53
|
+
</tr>
|
54
|
+
</table>
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
</table>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<%=t "spina.user_mailer.forgot_password.preheader" %>
|
2
|
+
|
3
|
+
<%=t "spina.user_mailer.forgot_password.salutation", name: @user.name %>
|
4
|
+
|
5
|
+
<%=t "spina.user_mailer.forgot_password.introduction" %>
|
6
|
+
|
7
|
+
<%= spina.edit_admin_password_reset_url(@user.password_reset_token) %>
|
8
|
+
|
9
|
+
<%=t "spina.user_mailer.forgot_password.request_origin", platform: @browser.platform.name, browser: @browser.name %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Spina.config.importmap.draw do
|
2
|
+
# Stimulus & Turbo
|
3
|
+
pin "@hotwired/stimulus", to: "stimulus.js"
|
4
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
5
|
+
pin "@hotwired/turbo-rails", to: "turbo.js"
|
6
|
+
|
7
|
+
# Spina entrypoint
|
8
|
+
pin "application", to: "spina/application.js"
|
9
|
+
|
10
|
+
pin_all_from Spina::Engine.root.join("app/assets/javascripts/spina/controllers"), under: "controllers", to: "spina/controllers"
|
11
|
+
pin_all_from Spina::Engine.root.join("app/assets/javascripts/spina/libraries"), under: "libraries", to: "spina/libraries"
|
12
|
+
end
|
data/config/locales/en.yml
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
en:
|
2
|
+
activemodel:
|
3
|
+
attributes:
|
4
|
+
spina/embeds/youtube:
|
5
|
+
url: youtube.com/watch?v=...
|
6
|
+
spina/embeds/vimeo:
|
7
|
+
url: vimeo.com/...
|
2
8
|
activerecord:
|
3
9
|
attributes:
|
4
10
|
spina/account:
|
@@ -94,6 +100,15 @@ en:
|
|
94
100
|
spina/user:
|
95
101
|
one: User
|
96
102
|
other: Users
|
103
|
+
helpers:
|
104
|
+
label:
|
105
|
+
spina/embeds/button:
|
106
|
+
url: URL
|
107
|
+
label: Button label
|
108
|
+
spina/embeds/youtube:
|
109
|
+
url: Youtube URL
|
110
|
+
spina/embeds/vimeo:
|
111
|
+
url: Vimeo URL
|
97
112
|
spina:
|
98
113
|
accounts:
|
99
114
|
contact_details: Contact details
|
@@ -117,6 +132,9 @@ en:
|
|
117
132
|
delete_confirmation: Are you sure you want to delete <strong>%{subject}</strong>?
|
118
133
|
edit: Edit
|
119
134
|
edit_website: Edit your website
|
135
|
+
embeds:
|
136
|
+
embed_a_component: Embed a component
|
137
|
+
embed_component: Embed component
|
120
138
|
forgot_password:
|
121
139
|
expired: Your password reset token has expired
|
122
140
|
mail_subject: Reset your password
|
@@ -140,6 +158,7 @@ en:
|
|
140
158
|
insert_photo: Insert photo
|
141
159
|
insert_photos: Insert photos
|
142
160
|
link: Link
|
161
|
+
missing_image: Missing image
|
143
162
|
new_folder: New folder
|
144
163
|
organize: Organize
|
145
164
|
remove_image: Remove image
|
@@ -321,6 +340,16 @@ en:
|
|
321
340
|
rename: Rename
|
322
341
|
save_changes: Save changes
|
323
342
|
saving: Saving...
|
343
|
+
user_mailer:
|
344
|
+
forgot_password:
|
345
|
+
button: Reset your password
|
346
|
+
introduction: You recently requested to reset your password for your Spina CMS account. This password reset is only valid for the next 2 hours. Copy and paste the URL below into your web browser.
|
347
|
+
introduction_html: "You recently requested to reset your password for your Spina CMS account. Use the button below to reset it. <strong>This password reset is only valid for the next 2 hours.</strong>"
|
348
|
+
preheader: Use this link to reset your password. The link is only valid for 2 hours.
|
349
|
+
request_origin: For security, this request was received from a %{platform} device using %{browser}. If you did not request a password reset, please ignore this email.
|
350
|
+
salutation: Hi, %{name}
|
351
|
+
subject: Reset your password
|
352
|
+
trouble: If you’re having trouble with the button above, copy and paste the URL below into your web browser.
|
324
353
|
users:
|
325
354
|
authorization: Authorization
|
326
355
|
authorization_description: Administrators can manage users. Regular users cannot.
|
@@ -341,6 +370,7 @@ en:
|
|
341
370
|
content: Content
|
342
371
|
documents: Documents
|
343
372
|
images: Images
|
373
|
+
main: Main
|
344
374
|
media_library: Media library
|
345
375
|
media_library_description: Here's where you'll find all of your images and documents
|
346
376
|
pages: Pages
|
data/config/locales/nl.yml
CHANGED
data/config/routes.rb
CHANGED
@@ -80,6 +80,8 @@ Spina::Engine.routes.draw do
|
|
80
80
|
resources :images
|
81
81
|
|
82
82
|
resource :media_picker, controller: "media_picker", only: [:show]
|
83
|
+
|
84
|
+
resources :embeds, only: [:new, :create]
|
83
85
|
end
|
84
86
|
|
85
87
|
# Sitemap
|
@@ -92,8 +94,9 @@ Spina::Engine.routes.draw do
|
|
92
94
|
# Pages
|
93
95
|
get '/:locale/*id' => 'pages#show', constraints: {locale: /#{Spina.config.locales.join('|')}/ }
|
94
96
|
get '/:locale/' => 'pages#homepage', constraints: {locale: /#{Spina.config.locales.join('|')}/ }
|
95
|
-
get '/*id' => 'pages#show', as: "page", controller: 'pages', constraints:
|
96
|
-
|
97
|
+
get '/*id' => 'pages#show', as: "page", controller: 'pages', constraints: -> (request) {
|
98
|
+
request.path.exclude?(ActiveStorage.routes_prefix) &&
|
99
|
+
!(Rails.env.development? && request.path.starts_with?('/rails/'))
|
97
100
|
}
|
98
101
|
end
|
99
102
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Spina
|
2
|
+
class EmbedGenerator < Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
argument :attributes, type: :array, default: [], banner: "field field"
|
6
|
+
|
7
|
+
def create_embed_object
|
8
|
+
template "app/models/spina/embeds/embed.rb.tt", "app/models/spina/embeds/#{file_name}.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_views
|
12
|
+
template "app/views/spina/embeds/_fields.html.erb", "app/views/spina/embeds/#{plural_file_name}/_#{file_name}_fields.html.erb"
|
13
|
+
|
14
|
+
template "app/views/spina/embeds/_partial.html.erb", "app/views/spina/embeds/#{plural_file_name}/_#{file_name}.html.erb"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -1,52 +1,61 @@
|
|
1
1
|
module Spina
|
2
2
|
class InstallGenerator < Rails::Generators::Base
|
3
3
|
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
class_option :first_deploy, type: :boolean, default: false
|
6
|
+
class_option :silent, type: :boolean, default: false
|
4
7
|
|
5
8
|
def create_initializer_file
|
6
|
-
return if
|
9
|
+
return if first_deploy?
|
7
10
|
template 'config/initializers/spina.rb'
|
8
11
|
end
|
9
12
|
|
10
13
|
def create_mobility_initializer_file
|
11
|
-
return if
|
14
|
+
return if first_deploy?
|
12
15
|
template 'config/initializers/mobility.rb'
|
13
16
|
end
|
14
17
|
|
15
18
|
def add_route
|
16
|
-
return if
|
19
|
+
return if first_deploy?
|
17
20
|
return if Rails.application.routes.routes.detect { |route| route.app.app == Spina::Engine }
|
18
21
|
route "mount Spina::Engine => '/'"
|
19
22
|
end
|
20
23
|
|
21
24
|
def copy_migrations
|
22
|
-
return if
|
25
|
+
return if first_deploy?
|
23
26
|
rake 'spina:install:migrations'
|
24
27
|
end
|
25
28
|
|
26
29
|
def run_migrations
|
30
|
+
return if first_deploy?
|
27
31
|
rake 'db:migrate'
|
28
32
|
end
|
29
33
|
|
30
34
|
def create_account
|
31
35
|
return if ::Spina::Account.exists? && !no?('An account already exists. Skip? [Yn]')
|
32
36
|
name = ::Spina::Account.first.try(:name) || 'MySite'
|
33
|
-
|
34
|
-
|
37
|
+
if talkative_install?
|
38
|
+
name = ask("What would you like to name your website? [#{name}]").presence || name
|
39
|
+
end
|
40
|
+
account = ::Spina::Account.first_or_create.update(name: name)
|
35
41
|
end
|
36
42
|
|
37
43
|
def set_theme
|
38
44
|
account = ::Spina::Account.first
|
39
|
-
return if account.theme.present? && !no?("Theme '#{account.theme} is set. Skip? [Yn]")
|
45
|
+
return if account.theme.present? && !no?("Theme '#{account.theme}' is set. Skip? [Yn]")
|
40
46
|
|
41
|
-
theme =
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
theme = account.theme || themes.first
|
48
|
+
if talkative_install?
|
49
|
+
theme = begin
|
50
|
+
theme = ask("What theme do you want to use? (#{themes.join('/')}) [#{theme}]").presence || theme
|
51
|
+
end until theme.in? themes
|
52
|
+
end
|
45
53
|
|
46
|
-
account.
|
54
|
+
account.update(theme: theme)
|
47
55
|
end
|
48
56
|
|
49
57
|
def copy_template_files
|
58
|
+
return if options['first_deploy']
|
50
59
|
theme = ::Spina::Account.first.theme
|
51
60
|
if theme.in? ['default', 'demo']
|
52
61
|
template "config/initializers/themes/#{theme}.rb"
|
@@ -59,10 +68,15 @@ module Spina
|
|
59
68
|
|
60
69
|
def create_user
|
61
70
|
return if ::Spina::User.exists? && !no?('A user already exists. Skip? [Yn]')
|
71
|
+
|
62
72
|
email = 'admin@domain.com'
|
63
|
-
|
73
|
+
if talkative_install?
|
74
|
+
email = ask("Please enter an email address for your first user: [#{email}]").presence || email
|
75
|
+
end
|
64
76
|
password = 'password'
|
65
|
-
|
77
|
+
if talkative_install?
|
78
|
+
password = ask("Create a temporary password: [#{password}]").presence || password
|
79
|
+
end
|
66
80
|
@temporary_password = password
|
67
81
|
::Spina::User.create name: 'admin', email: email, password: password, admin: true
|
68
82
|
end
|
@@ -91,6 +105,14 @@ module Spina
|
|
91
105
|
themes = Spina::Theme.all.map(&:name)
|
92
106
|
themes | ['default', 'demo']
|
93
107
|
end
|
108
|
+
|
109
|
+
def first_deploy?
|
110
|
+
options['first_deploy']
|
111
|
+
end
|
112
|
+
|
113
|
+
def talkative_install?
|
114
|
+
!options['silent']
|
115
|
+
end
|
94
116
|
|
95
117
|
end
|
96
118
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class <%= class_name %> < Spina::Embeds::Base
|
2
|
+
attributes <%= attributes.map{|attr| ":#{attr.name}"}.join(", ") %>
|
3
|
+
|
4
|
+
# You can use Rails validations on your attributes
|
5
|
+
validates <%= attributes.map{|attr| ":#{attr.name}"}.join(", ") %>, presence: true
|
6
|
+
|
7
|
+
# Pick an icon at https://heroicons.com
|
8
|
+
# and it'll show up in the list of embeddable components
|
9
|
+
heroicon "chip"
|
10
|
+
|
11
|
+
# If you want to render your embeddable component differently in Trix,
|
12
|
+
# you can choose to render a different partial
|
13
|
+
# Default: _<%= file_name %>.html.erb
|
14
|
+
#
|
15
|
+
# def to_trix_partial_path
|
16
|
+
# "spina/embeds/<%= plural_file_name %>/trix_<%= file_name %>"
|
17
|
+
# end
|
18
|
+
end
|
@@ -1,2 +1,2 @@
|
|
1
|
-
<h1><%= current_page.title
|
1
|
+
<h1><%= current_page.title %></h1>
|
2
2
|
<%= content.html :text %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<div class="text-xl font-bold mb-3 text-gray-800">
|
2
|
+
Your embed
|
3
|
+
<div class="text-sm text-gray-500 font-normal">
|
4
|
+
Descriptive text
|
5
|
+
</div>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<!--
|
9
|
+
Put your fields here using Spina's built-in ViewComponents:
|
10
|
+
|
11
|
+
render Spina::Forms::LabelComponent.new(f, :url)
|
12
|
+
render Spina::Forms::TextFieldComponent.new(f, :url, autofocus: true)
|
13
|
+
-->
|
@@ -39,6 +39,13 @@ Spina.configure do |config|
|
|
39
39
|
# The default is Spina::Authentication::Sessions and includes basic user management
|
40
40
|
# config.authentication = "Spina::Authentication::Sessions"
|
41
41
|
|
42
|
+
# Mailers
|
43
|
+
# ===============
|
44
|
+
# In order to send emails, you need to set a default from address.
|
45
|
+
# You can set an optional reply_to address as well.
|
46
|
+
# config.mailer_defaults.from = "no-reply@example.com"
|
47
|
+
# config.mailer_defaults.reply_to = "support@example.com"
|
48
|
+
|
42
49
|
# API
|
43
50
|
# ===============
|
44
51
|
# Set an API key to activate Spina's API.
|
@@ -37,7 +37,7 @@ Spina::Theme.register do |theme|
|
|
37
37
|
theme.view_templates = [
|
38
38
|
{name: 'homepage', title: 'Homepage', parts: %w(headline body image_collection)},
|
39
39
|
{name: 'show', title: 'Default', parts: %w(body image repeater)},
|
40
|
-
{name: 'demo', title: 'Demo', parts: %w(body image_collection image repeater)}
|
40
|
+
{name: 'demo', title: 'Demo', parts: %w(body image_collection image repeater), exclude_from: %w(articles)}
|
41
41
|
]
|
42
42
|
|
43
43
|
# Custom pages
|
@@ -69,4 +69,7 @@ Spina::Theme.register do |theme|
|
|
69
69
|
|
70
70
|
# Plugins (optional)
|
71
71
|
theme.plugins = ['reviews']
|
72
|
+
|
73
|
+
# Embeds (optional)
|
74
|
+
theme.embeds = []
|
72
75
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Spina
|
2
|
+
module Embeddable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :embed_attributes, default: []
|
7
|
+
class_attribute :icon, default: nil
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def from_json(json)
|
12
|
+
begin
|
13
|
+
attributes = JSON.parse(json)
|
14
|
+
attributes.transform_keys!(&:to_sym)
|
15
|
+
new(attributes)
|
16
|
+
rescue
|
17
|
+
Rails.logger.error "[#{self.class.name}] Couldn't parse JSON"
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def attributes(*names)
|
23
|
+
attr_accessor(*names.map(&:to_sym))
|
24
|
+
self.embed_attributes += names.map(&:to_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Give it an icon
|
28
|
+
def heroicon(name)
|
29
|
+
self.icon = name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(attributes = {})
|
34
|
+
attributes.slice(*self.class.embed_attributes).each do |key, value|
|
35
|
+
instance_variable_set("@#{key}", value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_fields_path
|
40
|
+
"#{to_partial_path}_fields"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_trix_partial_path
|
44
|
+
to_partial_path
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Spina
|
2
|
+
module Embeds
|
3
|
+
module TrixConversion
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
# Wrap rendered partial in an <embed> tag for Trix
|
7
|
+
def to_trix_attachment(content = trix_attachment_content)
|
8
|
+
wrap_with_embed_tag(content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def wrap_with_embed_tag(html)
|
12
|
+
element = html_document.create_element("spina-embed", embed_tag_attributes)
|
13
|
+
element.inner_html = html
|
14
|
+
element.to_html
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def embed_tag_attributes
|
20
|
+
spina_attributes.merge({"data-embed-attributes": embed_attributes_to_json})
|
21
|
+
end
|
22
|
+
|
23
|
+
def embed_attributes_to_json
|
24
|
+
JSON.generate Hash[self.class.embed_attributes.map{|a| [a.to_s, send(a)]}]
|
25
|
+
end
|
26
|
+
|
27
|
+
def spina_attributes
|
28
|
+
{"data-embed-type": self.class.name, "data-controller": "embed-tag"}
|
29
|
+
end
|
30
|
+
|
31
|
+
def trix_attachment_content
|
32
|
+
ApplicationController.render(partial: to_trix_partial_path, formats: :html, object: self, as: model_name.element)
|
33
|
+
end
|
34
|
+
|
35
|
+
def html_document
|
36
|
+
Nokogiri::HTML::Document.new.tap{|doc| doc.encoding = "UTF-8"}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/spina/embeds.rb
ADDED
data/lib/spina/engine.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'sass-rails'
|
2
1
|
require 'importmap-rails'
|
3
2
|
require 'turbo-rails'
|
4
3
|
require 'stimulus-rails'
|
@@ -9,13 +8,14 @@ require 'mobility'
|
|
9
8
|
require 'rack-rewrite'
|
10
9
|
require 'babosa'
|
11
10
|
require 'attr_json'
|
12
|
-
require 'view_component
|
11
|
+
require 'view_component'
|
13
12
|
require 'jsonapi/serializer'
|
13
|
+
require 'browser'
|
14
14
|
|
15
15
|
module Spina
|
16
16
|
class Engine < ::Rails::Engine
|
17
17
|
isolate_namespace Spina
|
18
|
-
|
18
|
+
|
19
19
|
config.autoload_paths += %W( #{config.root}/lib )
|
20
20
|
|
21
21
|
config.to_prepare do
|
@@ -25,8 +25,9 @@ module Spina
|
|
25
25
|
require_dependency(decorator)
|
26
26
|
end
|
27
27
|
end
|
28
|
-
|
29
|
-
|
28
|
+
end
|
29
|
+
|
30
|
+
config.to_prepare do
|
30
31
|
Spina::Part.register(
|
31
32
|
Spina::Parts::Line,
|
32
33
|
Spina::Parts::MultiLine,
|
@@ -38,21 +39,6 @@ module Spina
|
|
38
39
|
Spina::Parts::Attachment
|
39
40
|
)
|
40
41
|
end
|
41
|
-
|
42
|
-
initializer "spina.importmap" do
|
43
|
-
Spina.config.importmap.draw do
|
44
|
-
# Stimulus & Turbo
|
45
|
-
pin "@hotwired/stimulus", to: "stimulus.js"
|
46
|
-
pin "@hotwired/stimulus-autoloader", to: "stimulus-autoloader.js"
|
47
|
-
pin "@hotwired/turbo-rails", to: "turbo.js"
|
48
|
-
|
49
|
-
# Spina entrypoint
|
50
|
-
pin "application", to: "spina/application.js"
|
51
|
-
|
52
|
-
pin_all_from Spina::Engine.root.join("app/assets/javascripts/spina/controllers"), under: "controllers", to: "spina/controllers"
|
53
|
-
pin_all_from Spina::Engine.root.join("app/assets/javascripts/spina/libraries"), under: "libraries", to: "spina/libraries"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
42
|
+
|
57
43
|
end
|
58
44
|
end
|
data/lib/spina/part.rb
CHANGED
@@ -8,12 +8,20 @@ module Spina
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def register(*parts)
|
11
|
-
parts.each
|
11
|
+
parts.each do |part|
|
12
|
+
unregister(part)
|
13
|
+
all << part
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def unregister(part)
|
18
|
+
all.delete_if do |registered_part|
|
19
|
+
registered_part.name == part.name
|
20
|
+
end
|
12
21
|
end
|
13
22
|
|
14
23
|
end
|
15
24
|
|
16
|
-
|
17
25
|
end
|
18
26
|
end
|
19
27
|
|
data/lib/spina/railtie.rb
CHANGED
@@ -5,6 +5,16 @@ module Spina
|
|
5
5
|
app.config.assets.precompile += %w(spina/manifest)
|
6
6
|
end
|
7
7
|
|
8
|
+
initializer "spina.theme_reloader" do |app|
|
9
|
+
reloader = ThemeReloader.new
|
10
|
+
reloader.execute
|
11
|
+
|
12
|
+
app.reloaders << reloader
|
13
|
+
app.reloader.to_run do
|
14
|
+
reloader.execute
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
ActiveSupport.on_load(:action_controller) do
|
9
19
|
::ActionController::Base.send(:include, Spina::AdminSectionable)
|
10
20
|
end
|
data/lib/spina/theme.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module Spina
|
2
2
|
class Theme
|
3
3
|
|
4
|
-
attr_accessor :name, :title, :parts, :page_parts, :structures, :view_templates, :layout_parts, :custom_pages, :plugins, :public_theme, :config, :navigations, :resources
|
4
|
+
attr_accessor :name, :title, :parts, :page_parts, :structures, :view_templates, :layout_parts, :custom_pages, :plugins, :public_theme, :config, :navigations, :resources, :embeds
|
5
5
|
|
6
6
|
class << self
|
7
7
|
|
8
8
|
def all
|
9
9
|
::Spina::THEMES
|
10
10
|
end
|
11
|
+
|
12
|
+
def unregister(name)
|
13
|
+
theme = find_by_name(name)
|
14
|
+
all.delete(theme) if theme
|
15
|
+
end
|
11
16
|
|
12
17
|
def find_by_name(name)
|
13
18
|
all.find { |theme| theme.name == name }
|
@@ -17,6 +22,7 @@ module Spina
|
|
17
22
|
theme = ::Spina::Theme.new
|
18
23
|
yield theme
|
19
24
|
raise 'Missing theme name' if theme.name.nil?
|
25
|
+
unregister(theme.name)
|
20
26
|
if theme.plugins.nil?
|
21
27
|
theme.plugins = ::Spina::Plugin.all.map { |plugin| plugin.name }
|
22
28
|
end
|
@@ -33,18 +39,25 @@ module Spina
|
|
33
39
|
@custom_pages = []
|
34
40
|
@navigations = []
|
35
41
|
@resources = []
|
42
|
+
@embeds = []
|
36
43
|
@public_theme = false
|
37
44
|
end
|
45
|
+
|
46
|
+
def embeddables
|
47
|
+
embeds.map{|embed| Embeds.constantize(embed)}
|
48
|
+
end
|
38
49
|
|
39
|
-
def new_page_templates(
|
50
|
+
def new_page_templates(resource: nil)
|
51
|
+
page_collection = resource&.name || "main"
|
40
52
|
@view_templates.map do |view_template|
|
41
53
|
next if is_custom_undeletable_page?(view_template[:name])
|
54
|
+
next if view_template[:exclude_from]&.include?(page_collection)
|
42
55
|
|
43
56
|
OpenStruct.new({
|
44
57
|
name: view_template[:name],
|
45
58
|
title: view_template[:title],
|
46
59
|
description: view_template[:description],
|
47
|
-
recommended: view_template[:name] ==
|
60
|
+
recommended: view_template[:name] == resource&.view_template
|
48
61
|
})
|
49
62
|
end.compact.sort_by do |page_template|
|
50
63
|
[page_template.recommended ? 0 : 1]
|