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.

Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -3
  3. data/app/assets/javascripts/spina/application.js +1 -1
  4. data/app/assets/javascripts/spina/controllers/application.js +11 -0
  5. data/app/assets/javascripts/spina/controllers/embed_controller.js +16 -0
  6. data/app/assets/javascripts/spina/controllers/embed_tag_controller.js +16 -0
  7. data/app/assets/javascripts/spina/controllers/index.js +5 -0
  8. data/app/assets/javascripts/spina/controllers/trix_controller.js +12 -2
  9. data/app/assets/stylesheets/spina/_fonts.css.erb +114 -0
  10. data/app/assets/stylesheets/spina/_tailwind.css +18 -5
  11. data/app/assets/stylesheets/spina/application.css +6 -0
  12. data/app/assets/stylesheets/spina/tailwind/custom.css +13 -5
  13. data/app/components/spina/forms/trix_toolbar_component.html.erb +26 -15
  14. data/app/components/spina/media_picker/image_component.html.erb +1 -1
  15. data/app/components/spina/pages/new_page_button_component.html.erb +32 -0
  16. data/app/components/spina/pages/new_page_button_component.rb +19 -0
  17. data/app/controllers/concerns/spina/frontend.rb +1 -1
  18. data/app/controllers/spina/admin/embeds_controller.rb +36 -0
  19. data/app/controllers/spina/admin/pages_controller.rb +1 -2
  20. data/app/controllers/spina/admin/password_resets_controller.rb +2 -4
  21. data/app/helpers/spina/admin/icons_helper.rb +2 -2
  22. data/app/helpers/spina/images_helper.rb +1 -1
  23. data/app/helpers/spina/spina_helper.rb +3 -2
  24. data/app/mailers/spina/application_mailer.rb +7 -0
  25. data/app/mailers/spina/user_mailer.rb +6 -14
  26. data/app/models/spina/embeds/base.rb +7 -0
  27. data/app/models/spina/embeds/button.rb +13 -0
  28. data/app/models/spina/embeds/vimeo.rb +39 -0
  29. data/app/models/spina/embeds/youtube.rb +39 -0
  30. data/app/models/spina/page.rb +10 -3
  31. data/app/models/spina/user.rb +6 -0
  32. data/app/presenters/spina/content_presenter.rb +1 -1
  33. data/app/presenters/spina/rich_text_presenter.rb +45 -0
  34. data/app/views/layouts/spina/admin/application.html.erb +1 -1
  35. data/app/views/layouts/spina/mail.html.erb +476 -1
  36. data/app/views/spina/admin/embeds/new.html.erb +47 -0
  37. data/app/views/spina/admin/pages/edit_template.html.erb +1 -1
  38. data/app/views/spina/admin/pages/index.html.erb +1 -21
  39. data/app/views/spina/admin/shared/_flash.html.erb +2 -2
  40. data/app/views/spina/embeds/buttons/_button.html.erb +3 -0
  41. data/app/views/spina/embeds/buttons/_button_fields.html.erb +14 -0
  42. data/app/views/spina/embeds/buttons/_trix_button.html.erb +3 -0
  43. data/app/views/spina/embeds/vimeos/_thumbnail.html.erb +6 -0
  44. data/app/views/spina/embeds/vimeos/_vimeo.html.erb +1 -0
  45. data/app/views/spina/embeds/vimeos/_vimeo_fields.html.erb +9 -0
  46. data/app/views/spina/embeds/youtubes/_thumbnail.html.erb +6 -0
  47. data/app/views/spina/embeds/youtubes/_youtube.html.erb +1 -0
  48. data/app/views/spina/embeds/youtubes/_youtube_fields.html.erb +9 -0
  49. data/app/views/spina/user_mailer/forgot_password.html.erb +57 -1
  50. data/app/views/spina/user_mailer/forgot_password.text.erb +9 -0
  51. data/config/initializers/importmap.rb +12 -0
  52. data/config/locales/en.yml +30 -0
  53. data/config/locales/nl.yml +1 -0
  54. data/config/routes.rb +5 -2
  55. data/lib/generators/spina/embed_generator.rb +18 -0
  56. data/lib/generators/spina/install_generator.rb +36 -14
  57. data/lib/generators/spina/templates/app/models/spina/embeds/embed.rb.tt +18 -0
  58. data/lib/generators/spina/templates/app/views/default/pages/homepage.html.erb +1 -1
  59. data/lib/generators/spina/templates/app/views/spina/embeds/_fields.html.erb +13 -0
  60. data/lib/generators/spina/templates/app/views/spina/embeds/_partial.html.erb +2 -0
  61. data/lib/generators/spina/templates/config/initializers/spina.rb +7 -0
  62. data/lib/generators/spina/templates/config/initializers/themes/default.rb +3 -0
  63. data/lib/generators/spina/templates/config/initializers/themes/demo.rb +4 -1
  64. data/lib/spina/embeddable.rb +48 -0
  65. data/lib/spina/embeds/trix_conversion.rb +41 -0
  66. data/lib/spina/embeds.rb +11 -0
  67. data/lib/spina/engine.rb +7 -21
  68. data/lib/spina/part.rb +10 -2
  69. data/lib/spina/railtie.rb +10 -0
  70. data/lib/spina/theme.rb +16 -3
  71. data/lib/spina/theme_reloader.rb +20 -0
  72. data/lib/spina/version.rb +1 -1
  73. data/lib/spina.rb +11 -0
  74. data/lib/tasks/spina_tasks.rake +19 -2
  75. metadata +66 -34
  76. data/app/assets/stylesheets/spina/_fonts.scss +0 -114
  77. data/app/assets/stylesheets/spina/application.sass +0 -3
  78. data/app/views/spina/user_mailer/forgot_password.txt.erb +0 -1
@@ -1,10 +1,11 @@
1
1
  module Spina::SpinaHelper
2
2
 
3
- def spina_importmap_tags(entry_point = "application")
3
+ def spina_importmap_tags(entry_point = "application", shim: true)
4
4
  safe_join [
5
5
  javascript_inline_importmap_tag(Spina.config.importmap.to_json(resolver: self)),
6
6
  javascript_importmap_module_preload_tags(Spina.config.importmap),
7
- javascript_importmap_shim_tag,
7
+ (javascript_importmap_shim_nonce_configuration_tag if shim),
8
+ (javascript_importmap_shim_tag if shim),
8
9
  javascript_import_module_tag(entry_point)
9
10
  ], "\n"
10
11
  end
@@ -0,0 +1,7 @@
1
+ module Spina
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default Spina.config.mailer_defaults
4
+
5
+ layout 'spina/mail'
6
+ end
7
+ end
@@ -1,21 +1,13 @@
1
1
  module Spina
2
- class UserMailer < ActionMailer::Base
3
- layout 'spina/mail'
2
+ class UserMailer < ApplicationMailer
4
3
 
5
- def forgot_password(user)
4
+ def forgot_password(user, user_agent_string = nil)
6
5
  @user = user
7
-
8
- mail(
9
- to: @user.email,
10
- from: current_account.email,
11
- subject: t('spina.forgot_password.mail_subject')
12
- )
6
+ @browser = Browser.new(user_agent_string)
7
+
8
+ mail to: @user.email,
9
+ subject: t('spina.user_mailer.forgot_password.subject')
13
10
  end
14
11
 
15
- private
16
-
17
- def current_account
18
- Spina::Account.first
19
- end
20
12
  end
21
13
  end
@@ -0,0 +1,7 @@
1
+ module Spina::Embeds
2
+ class Base
3
+ include ActiveModel::Model
4
+ include Spina::Embeddable
5
+ include Spina::Embeds::TrixConversion
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ module Spina::Embeds
2
+ class Button < Base
3
+ attributes :url, :label
4
+
5
+ heroicon "cursor-click"
6
+
7
+ validates :url, :label, presence: true
8
+
9
+ def to_trix_partial_path
10
+ "spina/embeds/buttons/trix_button"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+
3
+ module Spina::Embeds
4
+ class Vimeo < Base
5
+ attributes :url
6
+
7
+ heroicon "video-camera"
8
+
9
+ REGEX = /(https?:\/\/)?(www.)?(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/
10
+
11
+ validates :url, presence: true, format: {with: REGEX}
12
+
13
+ def id
14
+ REGEX.match(url).try(:[], 5)
15
+ end
16
+
17
+ # Get title from Vimeo API (remote call)
18
+ def remote_title
19
+ get_vimeo_json&.dig(0, "title")
20
+ end
21
+
22
+ def to_trix_partial_path
23
+ "spina/embeds/vimeos/thumbnail"
24
+ end
25
+
26
+ private
27
+
28
+ def get_vimeo_json
29
+ uri = URI("https://vimeo.com/api/v2/video/#{id}.json")
30
+ response = Net::HTTP.get(uri)
31
+ begin
32
+ JSON.parse(response)
33
+ rescue
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+
3
+ module Spina::Embeds
4
+ class Youtube < Base
5
+ attributes :url
6
+
7
+ heroicon "video-camera"
8
+
9
+ REGEX = /(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
10
+
11
+ validates :url, presence: true, format: {with: REGEX}
12
+
13
+ def id
14
+ REGEX.match(url).try(:[], 1)
15
+ end
16
+
17
+ # Get title from Youtube API (remote call)
18
+ def remote_title
19
+ get_youtube_json&.dig("title")
20
+ end
21
+
22
+ def to_trix_partial_path
23
+ "spina/embeds/youtubes/thumbnail"
24
+ end
25
+
26
+ private
27
+
28
+ def get_youtube_json
29
+ uri = URI("https://www.youtube.com/oembed?url=http://youtube.com/watch?v=#{id}&format=json")
30
+ response = Net::HTTP.get(uri)
31
+ begin
32
+ JSON.parse(response)
33
+ rescue
34
+ nil
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -26,6 +26,8 @@ module Spina
26
26
  scope :sorted, -> { order(:position) }
27
27
  scope :live, -> { active.where(draft: false) }
28
28
  scope :in_menu, -> { where(show_in_menu: true) }
29
+
30
+ before_create :set_default_position
29
31
 
30
32
  # Copy resource from parent
31
33
  before_save :set_resource_from_parent, if: -> { parent.present? }
@@ -102,6 +104,10 @@ module Spina
102
104
  end
103
105
 
104
106
  private
107
+
108
+ def set_default_position
109
+ self.position ||= self.class.maximum(:position).to_i.next
110
+ end
105
111
 
106
112
  def set_resource_from_parent
107
113
  self.resource_id = parent.resource_id
@@ -116,11 +122,12 @@ module Spina
116
122
  end
117
123
 
118
124
  def localized_materialized_path
119
- if Mobility.locale == I18n.default_locale
120
- generate_materialized_path.prepend('/')
125
+ segments = if Mobility.locale == I18n.default_locale
126
+ [Spina.mounted_at, generate_materialized_path]
121
127
  else
122
- generate_materialized_path.prepend("/#{Mobility.locale}/").gsub(/\/\z/, "")
128
+ [Spina.mounted_at, Mobility.locale, generate_materialized_path]
123
129
  end
130
+ File.join(*segments.map(&:to_s).compact)
124
131
  end
125
132
 
126
133
  def generate_materialized_path
@@ -15,6 +15,12 @@ module Spina
15
15
  def to_s
16
16
  name
17
17
  end
18
+
19
+ def reset_passord!
20
+ regenerate_password_reset_token
21
+ self.password_reset_sent_at = Time.current
22
+ save!
23
+ end
18
24
 
19
25
  end
20
26
  end
@@ -14,7 +14,7 @@ module Spina
14
14
 
15
15
  def html(name)
16
16
  html = find_part(name)&.content
17
- ActiveSupport::SafeBuffer.new(html.to_s)
17
+ RichTextPresenter.new(view_context, html)
18
18
  end
19
19
 
20
20
  def image_tag(image, variant_options = {}, options = {})
@@ -0,0 +1,45 @@
1
+ module Spina
2
+ class RichTextPresenter
3
+ attr_reader :html, :view_context
4
+
5
+ EMBED_CONTENT_TYPE = "application/vnd+spina.embed+html"
6
+
7
+ def initialize(view_context, html)
8
+ @view_context = view_context || Spina::Current.page&.view_context
9
+ @html = html
10
+ end
11
+
12
+ def to_s
13
+ ActiveSupport::SafeBuffer.new(render_embeds(html))
14
+ end
15
+
16
+ private
17
+
18
+ def embed_selector
19
+ "figure[data-trix-content-type=\"#{EMBED_CONTENT_TYPE}\"]"
20
+ end
21
+
22
+ def render_embeds(html)
23
+ doc = Nokogiri::HTML(html)
24
+ doc.css(embed_selector).each do |node|
25
+ node.replace render_embed(node.first_element_child)
26
+ end
27
+ doc.to_s
28
+ end
29
+
30
+ def render_embed(element)
31
+ embeddable = element_to_embeddable(element)
32
+ view_context.render(embeddable)
33
+ end
34
+
35
+ def element_to_embeddable(element)
36
+ embeddable = Spina::Embeds.constantize(element["data-embed-type"])
37
+ embeddable&.from_json(element["data-embed-attributes"]) || null_object
38
+ end
39
+
40
+ def null_object
41
+ {inline: ""}
42
+ end
43
+
44
+ end
45
+ end
@@ -9,7 +9,7 @@
9
9
  <link rel="icon" href="<%= image_url('spina/favicon.png') %>" />
10
10
 
11
11
  <title>Spina CMS</title>
12
-
12
+
13
13
  <!-- Tailwind -->
14
14
  <%= stylesheet_link_tag 'spina/application', data: {turbolinks_track: true} %>
15
15