wcc-contentful-app 0.4.0.pre.alpha → 1.0.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.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +2 -2
  3. data/README.md +11 -0
  4. data/Rakefile +125 -0
  5. data/app/assets/config/manifest.js +5 -0
  6. data/app/assets/images/down-arrow-primary.svg +3 -0
  7. data/app/assets/images/up-arrow-primary.svg +3 -0
  8. data/app/assets/javascripts/wcc/contentful/app/contact-form.js +70 -0
  9. data/app/assets/javascripts/wcc/contentful/app/index.js +1 -0
  10. data/app/assets/stylesheets/_wcc-contentful-app.scss +7 -0
  11. data/app/assets/stylesheets/components/_menu-item.scss +5 -0
  12. data/app/assets/stylesheets/sections/_faq.scss +39 -0
  13. data/app/controllers/wcc/contentful/app/contact_form_controller.rb +39 -0
  14. data/app/controllers/wcc/contentful/app/pages_controller.rb +48 -0
  15. data/app/helpers/wcc/contentful/app/menu_helper.rb +85 -0
  16. data/app/helpers/wcc/contentful/app/section_helper.rb +135 -0
  17. data/app/mailers/wcc/contentful/app/contact_mailer.rb +11 -0
  18. data/app/models/concerns/wcc/contentful/app/preview_password.rb +19 -0
  19. data/app/models/wcc/contentful/app/contact_form_submission.rb +9 -0
  20. data/app/models/wcc/contentful/app/custom_markdown_render.rb +43 -0
  21. data/app/views/components/_faq_row.html.erb +21 -0
  22. data/app/views/components/_menu-item.html.erb +41 -0
  23. data/app/views/components/_other-menu-item.html.erb +4 -0
  24. data/app/views/components/_section.html.erb +11 -0
  25. data/app/views/layouts/mailer.html.erb +9 -0
  26. data/app/views/layouts/mailer.text.erb +1 -0
  27. data/app/views/pages/show.html.erb +4 -0
  28. data/app/views/sections/_block_text.html.erb +5 -0
  29. data/app/views/sections/_code_widget.html.erb +3 -0
  30. data/app/views/sections/_contact_form.html.erb +53 -0
  31. data/app/views/sections/_faq.html.erb +36 -0
  32. data/app/views/sections/_http_error.html.erb +13 -0
  33. data/app/views/sections/_marquee_text.html.erb +12 -0
  34. data/app/views/sections/_testimonials.html.erb +38 -0
  35. data/app/views/sections/_video.html.erb +12 -0
  36. data/app/views/sections/_video_highlight.html.erb +18 -0
  37. data/app/views/wcc/contentful/app/contact_mailer/contact_form_email.html.erb +7 -0
  38. data/bin/rails +14 -0
  39. data/config/routes.rb +7 -0
  40. data/doc +1 -0
  41. data/lib/generators/wcc/model_generator.rb +16 -6
  42. data/lib/generators/wcc/templates/menu/migrations/generated_add_menus.ts +285 -0
  43. data/lib/generators/wcc/templates/menu/models/dropdown_menu.rb +19 -0
  44. data/lib/generators/wcc/templates/menu/models/menu.rb +0 -4
  45. data/lib/generators/wcc/templates/menu/models/menu_button.rb +0 -4
  46. data/lib/generators/wcc/templates/page/migrations/generated_add_pages.ts +147 -0
  47. data/lib/generators/wcc/templates/page/models/page.rb +0 -4
  48. data/lib/generators/wcc/templates/page/models/redirect.rb +19 -0
  49. data/lib/generators/wcc/templates/section-block-text/migrations/generated_add_section-block-texts.ts +40 -0
  50. data/lib/generators/wcc/templates/section-block-text/models/section_block_text.rb +19 -0
  51. data/lib/generators/wcc/templates/section-code-widget/migrations/generated_add_section-code-widget.ts +90 -0
  52. data/lib/generators/wcc/templates/section-code-widget/models/section_code_widget.rb +23 -0
  53. data/lib/generators/wcc/templates/section-contact-form/migrations/create_wcc_contentful_app_contact_form_submissions.rb +12 -0
  54. data/lib/generators/wcc/templates/section-contact-form/migrations/generated_add_section-contact-forms.ts +147 -0
  55. data/lib/generators/wcc/templates/section-contact-form/models/form_field.rb +19 -0
  56. data/lib/generators/wcc/templates/section-contact-form/models/section_contact_form.rb +19 -0
  57. data/lib/generators/wcc/templates/section-faq/migrations/generated_add_section-faqs.ts +148 -0
  58. data/lib/generators/wcc/templates/section-faq/models/section_faq.rb +19 -0
  59. data/lib/generators/wcc/templates/section-http-error/migrations/generated_add_section-http-errors.ts +87 -0
  60. data/lib/generators/wcc/templates/section-http-error/models/section_http_error.rb +19 -0
  61. data/lib/generators/wcc/templates/section-marquee-text/migrations/generated_add_section-marquee-texts.ts +64 -0
  62. data/lib/generators/wcc/templates/section-marquee-text/models/section_marquee_text.rb +19 -0
  63. data/lib/generators/wcc/templates/section-testimonial/migrations/generated_add_section-testimonials.ts +182 -0
  64. data/lib/generators/wcc/templates/section-testimonial/models/section_testimonial.rb +19 -0
  65. data/lib/generators/wcc/templates/section-video-highlight/migrations/generated_add_section-video-highlights.ts +80 -0
  66. data/lib/generators/wcc/templates/section-video-highlight/models/section_video_highlight.rb +19 -0
  67. data/lib/generators/wcc/templates/section-video/migrations/generated_add_section-videos.ts +77 -0
  68. data/lib/generators/wcc/templates/section-video/models/section_video.rb +19 -0
  69. data/lib/generators/wcc/templates/site-config/migrations/generated_add_site-configs.ts +97 -0
  70. data/lib/generators/wcc/templates/site-config/models/site_config.rb +19 -0
  71. data/lib/generators/wcc/templates/wcc_contentful.rb +1 -1
  72. data/lib/wcc/contentful/app.rb +46 -3
  73. data/lib/wcc/contentful/app/configuration.rb +60 -0
  74. data/lib/wcc/contentful/app/engine.rb +15 -0
  75. data/lib/wcc/contentful/app/exceptions.rb +9 -0
  76. data/lib/wcc/contentful/app/rails.rb +6 -0
  77. data/lib/wcc/contentful/app/version.rb +1 -1
  78. data/lib/wcc/contentful/model/dropdown_menu.rb +0 -3
  79. data/lib/wcc/contentful/model/form_field.rb +4 -0
  80. data/lib/wcc/contentful/model/menu.rb +0 -2
  81. data/lib/wcc/contentful/model/menu_button.rb +18 -5
  82. data/lib/wcc/contentful/model/page.rb +0 -4
  83. data/lib/wcc/contentful/model/redirect.rb +21 -11
  84. data/lib/wcc/contentful/model/section_contact_form.rb +42 -0
  85. data/lib/wcc/contentful/model/site_config.rb +7 -0
  86. data/wcc-contentful-app.gemspec +23 -15
  87. metadata +169 -106
  88. data/Gemfile +0 -8
  89. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +0 -192
  90. data/lib/generators/wcc/templates/page/generated_add_pages.ts +0 -50
  91. data/lib/tasks/validate.rake +0 -20
  92. data/lib/wcc/contentful/app/model_validators.rb +0 -121
  93. data/lib/wcc/contentful/app/model_validators/dsl.rb +0 -166
  94. data/lib/wcc/contentful/ext/model.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6a7ac206d21b391dfbd7c33d91b97b16e7e42fe9
4
- data.tar.gz: 11b71582d95de2d7b85123c01fcf33f512f0af65
2
+ SHA256:
3
+ metadata.gz: 4d2c8f87432e5df3bfa0ce1438ea0cbc2c89463a2c22c6ebee6a6f766b26b865
4
+ data.tar.gz: 5899cd069610da0a25a3ff330a4788e5c16f4d25618ca591bc5327678c594a7a
5
5
  SHA512:
6
- metadata.gz: f579df1cc97c1860ad224426bf06316630d62ab8383a46f6ef943f8885f1f19f79e9de7d9cf69b5a4e01328d53f370685b099483892fce1c4a54f4d43758124f
7
- data.tar.gz: caa17fafddab09ac55463fd65a2dfcb49693ddaec3aa527ab2403621a0d1e3f664a89de35714c4e946883029f760e2befeeed280875100d01e8524b83a24ded5
6
+ metadata.gz: a075b9cd2fc956189d8136848856e22aba9f3998e28e3853d4cbfe4c45d916a1f723e3ba4179b619c2da9af90de6b738b1941fb6cb707b9a922d4bb0075666a1
7
+ data.tar.gz: 689e7ec2251ff44627f5b70af5a8ce41dcc2d8d1305df6b74890ca8bc24a62c580e6f4c5ca0c892004ea8cb32a461ecbc47a57350dbcc758214495a4efd67d1e
data/.rspec CHANGED
@@ -1,4 +1,4 @@
1
- --require rails_helper
1
+ --require spec_helper
2
2
  --format documentation
3
3
  --color
4
- --order rand
4
+ --order rand
data/README.md CHANGED
@@ -0,0 +1,11 @@
1
+ [![Gem Version](https://badge.fury.io/rb/wcc-contentful-app.svg)](https://rubygems.org/gems/wcc-contentful-app)
2
+ [![Build Status](https://travis-ci.org/watermarkchurch/wcc-contentful.svg?branch=master)](https://travis-ci.org/watermarkchurch/wcc-contentful)
3
+ [![Coverage Status](https://coveralls.io/repos/github/watermarkchurch/wcc-contentful/badge.svg?branch=master)](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master)
4
+
5
+ # WCC::Contentful::App
6
+
7
+ This gem specifies default models, controllers, views, and javascripts that
8
+ get installed into all Watermarkchurch apps that use contentful. It is a highly
9
+ opinionated starting point for a very flexible site design using Contentful.
10
+
11
+ You probably should not use this.
data/Rakefile ADDED
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'fileutils'
5
+
6
+ require 'wcc/contentful'
7
+
8
+ # rubocop:disable Metrics/LineLength
9
+ # Disabling line length for contentful-schema-diff backtick execution
10
+
11
+ namespace :app do
12
+ desc "Imports a set of models from a space as a single template for the ModelGenerator.\n" \
13
+ 'Usage: rake wcc:import_model[content-type-id,space-id?,extra-content-type-1,extra-content-type-2...]'
14
+ task :import_model, [:model, :space] do |_t, args|
15
+ raise ArgumentError, 'Must give a content type' unless model = args[:model]
16
+
17
+ singular = model.downcase.singularize
18
+ plural = model.downcase.pluralize
19
+
20
+ all_models = [model, *args.extras]
21
+
22
+ unless space = args[:space].presence
23
+ space = ENV['CONTENTFUL_SPACE_ID']
24
+ space = "#{space}/#{ENV['CONTENTFUL_ENVIRONMENT']}" if ENV['CONTENTFUL_ENVIRONMENT']
25
+ end
26
+ raise ArgumentError, 'Must provide a space' unless space
27
+ unless mgmt_token = ENV['CONTENTFUL_MANAGEMENT_TOKEN']
28
+ raise ArgumentError, 'Must set CONTENTFUL_MANAGEMENT_TOKEN envvar'
29
+ end
30
+
31
+ client = WCC::Contentful::SimpleClient::Management.new(
32
+ space: space.split('/').first,
33
+ environment: space.split('/')[1],
34
+ management_token: mgmt_token
35
+ )
36
+
37
+ # Update the `content_types_mgmt_api.json` which we use to generate models in specs
38
+ content_types = JSON.parse(File.read('spec/fixtures/contentful/content_types_mgmt_api.json'))
39
+ all_models.each do |model_id|
40
+ content_type = client.content_type(model_id).raw
41
+
42
+ if index = content_types['items'].find_index { |ct| ct.dig('sys', 'id').casecmp(model_id) == 0 }
43
+ content_types['items'][index] = content_type
44
+ else
45
+ content_types['items'].push(content_type)
46
+ end
47
+ end
48
+ File.write('spec/fixtures/contentful/content_types_mgmt_api.json',
49
+ JSON.pretty_generate(content_types))
50
+
51
+ # Make the generator directory
52
+ FileUtils.mkdir_p("lib/generators/wcc/templates/#{singular}/models")
53
+
54
+ # Generate a migration for the content type
55
+ File.write('empty-export.json', <<~HEREDOC)
56
+ {
57
+ "contentTypes": [],
58
+ "editorInterfaces": [],
59
+ "entries": [],
60
+ "assets": [],
61
+ "locales": []
62
+ }
63
+ HEREDOC
64
+ begin
65
+ result = `contentful-schema-diff --from empty-export.json --to #{space} #{all_models.map { |m| "-c #{m}" }.join(' ')} -a '#{mgmt_token}' --out './' --one-file`
66
+ result = result.strip
67
+ raise StandardError, 'Error writing diff!' unless result.present?
68
+
69
+ FileUtils.mv(result, "lib/generators/wcc/templates/#{singular}/generated_add_#{plural}.ts")
70
+ ensure
71
+ File.unlink('empty-export.json')
72
+ end
73
+
74
+ const = WCC::Contentful::Helpers.constant_from_content_type(model)
75
+
76
+ # Write a model for the content type
77
+ File.write("lib/generators/wcc/templates/#{singular}/models/#{singular.tr('-', '_')}.rb", <<~HEREDOC)
78
+ # frozen_string_literal: true
79
+
80
+ # This model represents the '#{model}' content type in Contentful. Any linked
81
+ # entries of the '#{model}' content type will be resolved as instances of this class.
82
+ # It exposes .find, .find_by, and .find_all methods to query Contentful.
83
+ class #{const} < WCC::Contentful::Model::#{const}
84
+ # Override functionality or add utilities
85
+ #
86
+ # # Example: override equality
87
+ # def ===(other)
88
+ # ...
89
+ # end
90
+ #
91
+ # # Example: override "title" attribute to always be titlecase.
92
+ # # `@title` is populated by the gem in the initializer.
93
+ # def title
94
+ # @title_titlecased ||= @title.titlecase
95
+ # end
96
+ end
97
+ HEREDOC
98
+
99
+ view_name = const.demodulize.underscore.sub('section_', '')
100
+ FileUtils.touch("app/views/sections/_#{view_name}.html.erb")
101
+ File.write("spec/views/sections/_#{view_name}.html.erb_spec.rb", <<~HEREDOC)
102
+ # frozen_string_literal: true
103
+
104
+ require 'rails_helper'
105
+
106
+ RSpec.describe 'sections/#{view_name}' do
107
+ helper WCC::Contentful::App::SectionHelper
108
+
109
+ it 'renders successfully' do
110
+ section = contentful_create('#{model}')
111
+
112
+ render partial: 'components/section', locals: { section: section }
113
+
114
+ expect(rendered).to have_css('section.#{model}.default')
115
+ end
116
+ end
117
+ HEREDOC
118
+ end
119
+ end
120
+
121
+ # rubocop:enable Metrics/LineLength
122
+
123
+ task :release do
124
+ raise StandardError, 'Please run rake release only from the root folder.'
125
+ end
@@ -0,0 +1,5 @@
1
+ // https://eileencodes.com/posts/the-sprockets-4-manifest/
2
+ //= link_tree ../javascripts
3
+ //= link_directory ../stylesheets
4
+ //= link_tree ../images
5
+
@@ -0,0 +1,3 @@
1
+ <svg width="21" height="13" viewBox="0 0 21 13" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M18.55 0L10.5 8.08108L2.45 0L0 2.45946L10.5 13L21 2.45946L18.55 0Z" fill="#f47754"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="21" height="13" viewBox="0 0 21 13" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M2.45 13L10.5 4.91892L18.55 13L21 10.5405L10.5 0L0 10.5405L2.45 13Z" fill="#f47754"/>
3
+ </svg>
@@ -0,0 +1,70 @@
1
+ if (typeof window.$ == 'undefined' ) {
2
+ var $ = window.jQuery
3
+ }
4
+
5
+ $(function() {
6
+ function warningAlert(message) {
7
+ var div = $('<div>')
8
+ .addClass('alert alert-danger alert-dismissible fade show')
9
+ .text(message)
10
+
11
+ return div.append(
12
+ '<button type="button" class="close" data-dismiss="alert" aria-label="Close">' +
13
+ '<span aria-hidden="true">&times;</span>' +
14
+ '</button>'
15
+ )
16
+ }
17
+
18
+ function handleResponse($form, event, status, xhr) {
19
+ // Handle backwards compat for [rails/jquery]-ujs ajax callbacks
20
+ var json
21
+ try {
22
+ if (event.detail) {
23
+ json = JSON.parse(event.detail[2].response)
24
+ status = event.detail[1]
25
+ } else {
26
+ json = xhr.responseJSON
27
+ }
28
+ } catch(ex) {
29
+ status = 'error'
30
+ json = {}
31
+ }
32
+
33
+ if (status == 'OK' || status == 'success') {
34
+ $form.append(
35
+ $('<span>').text(json.message).delay(2000).fadeOut(2000, function() { $(this).remove() })
36
+ )
37
+ $('input:visible, textarea', $form).val('')
38
+ $('.alert', $form).remove()
39
+ if (typeof window.grecaptcha != 'undefined') {
40
+ window.grecaptcha.reset()
41
+ }
42
+ } else if (json.message) {
43
+ $form.append(warningAlert(json.message))
44
+ } else {
45
+ $form.append(warningAlert('Sorry, something went wrong.'))
46
+ }
47
+ }
48
+
49
+ $('[data-contact-form]').each(function(_, input) {
50
+ var $form = $(input)
51
+
52
+ $form.on('ajax:success', function(event, data, status, xhr) {
53
+ try {
54
+ handleResponse($form, event, status, xhr)
55
+ } catch(ex) {
56
+ alert('Sorry, something went wrong.')
57
+ throw ex
58
+ }
59
+ })
60
+
61
+ $form.on('ajax:error', function(event, xhr, status) {
62
+ try {
63
+ handleResponse($form, event, status, xhr)
64
+ } catch(ex) {
65
+ alert('Sorry, something went wrong.')
66
+ throw ex
67
+ }
68
+ })
69
+ })
70
+ })
@@ -0,0 +1 @@
1
+ //= require_tree .
@@ -0,0 +1,7 @@
1
+ @import './components/menu-item';
2
+
3
+ @import './sections/faq';
4
+
5
+ .safe-line-break {
6
+ white-space: pre-line;
7
+ }
@@ -0,0 +1,5 @@
1
+ .nav-link {
2
+ .material-icons {
3
+ vertical-align: middle;
4
+ }
5
+ }
@@ -0,0 +1,39 @@
1
+ .section-faq {
2
+ &__show-more-button {
3
+ text-decoration: none;
4
+
5
+ &[aria-expanded='false'] {
6
+ .section-faq__show-more-button__expanded {
7
+ display: none;
8
+ }
9
+ }
10
+
11
+ &[aria-expanded='true'] {
12
+ .section-faq__show-more-button__collapsed {
13
+ display: none;
14
+ }
15
+ }
16
+ }
17
+
18
+ &__faq-question {
19
+ cursor: pointer;
20
+ }
21
+
22
+ &__expander {
23
+ display: flex;
24
+ text-align: right;
25
+
26
+ a {
27
+ text-decoration: none;
28
+ width: 100%;
29
+
30
+ &[aria-expanded='false']::before {
31
+ content: '+';
32
+ }
33
+
34
+ &[aria-expanded='true']::before {
35
+ content: '-';
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WCC::Contentful::App::ContactFormController < ApplicationController
4
+ include WCC::Contentful::App::PreviewPassword
5
+
6
+ def create
7
+ address =
8
+ form_model.to_address(email_object_id: params[:email_object_id])
9
+
10
+ form_model.send_email(
11
+ form_params.merge!(
12
+ {
13
+ notification_email: address,
14
+ internal_title: params[:internal_title]
15
+ }
16
+ )
17
+ )
18
+
19
+ render json: { type: 'success', message: "Thanks for reaching out. We'll be in touch soon!" }
20
+ end
21
+
22
+ private
23
+
24
+ def form_model
25
+ raise ArgumentError, 'missing form ID' unless params[:id]
26
+
27
+ @form_model ||= form_class.find(
28
+ params[:id], options: { preview: preview? }
29
+ )
30
+ end
31
+
32
+ def form_class
33
+ WCC::Contentful::Model.resolve_constant('section-contact-form')
34
+ end
35
+
36
+ def form_params
37
+ params.slice(*form_model.fields.map(&:title))
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WCC::Contentful::App::PagesController < ApplicationController
4
+ include WCC::Contentful::App::PreviewPassword
5
+
6
+ helper ::WCC::Contentful::App::SectionHelper
7
+
8
+ def index
9
+ @page = global_site_config&.homepage ||
10
+ page_model.find_by(slug: '/', options: { include: 3, preview: preview? })
11
+ render 'pages/show'
12
+ end
13
+
14
+ def show
15
+ slug = '/' + params[:slug]
16
+ @page = page_model.find_by(slug: slug, options: { include: 3, preview: preview? })
17
+
18
+ return render 'pages/show' if @page
19
+
20
+ redirect = redirect_model.find_by(slug: slug, options: { include: 0, preview: preview? })
21
+ raise WCC::Contentful::App::PageNotFoundError, slug unless redirect
22
+
23
+ redirect_to redirect.href
24
+ end
25
+
26
+ private
27
+
28
+ def page_model
29
+ WCC::Contentful::Model.resolve_constant('page')
30
+ end
31
+
32
+ def redirect_model
33
+ WCC::Contentful::Model.resolve_constant('redirect')
34
+ end
35
+
36
+ def site_config_model
37
+ # They may have not installed `site-config` in the project
38
+ WCC::Contentful::Model.resolve_constant('site-config')
39
+ rescue WCC::Contentful::ContentTypeNotFoundError
40
+ nil
41
+ end
42
+
43
+ def global_site_config
44
+ return unless model = site_config_model
45
+
46
+ @global_site_config ||= model.instance(preview?)
47
+ end
48
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WCC::Contentful::App::MenuHelper
4
+ def dropdown?(item)
5
+ item.respond_to?(:items)
6
+ end
7
+
8
+ def menu_button?(item)
9
+ item.is_a? WCC::Contentful::Model::MenuButton
10
+ end
11
+
12
+ def item_active?(item)
13
+ return true if item.try(:label) && item_active?(item.label)
14
+ return item.items.any? { |i| item_active?(i) } if item.respond_to?(:items)
15
+ return current_page?(item.href) if item.try(:href)
16
+
17
+ false
18
+ end
19
+
20
+ def render_button(button, options = {}, &block)
21
+ html = render_button_inner_html(button, options, &block)
22
+
23
+ if button.try(:external?)
24
+ push_class('external', options)
25
+ options[:target] = :_blank
26
+ end
27
+ if button.icon.present? || button.material_icon.present? || options.dig(:icon, :fallback)
28
+ push_class('icon-only', options) unless button.text.present?
29
+ elsif button.text.present?
30
+ push_class('text-only', options)
31
+ end
32
+
33
+ push_class(button.style, options) if button.style
34
+
35
+ href = button.href
36
+ href = hash_only(href) if href.present? && local?(href)
37
+ return link_to(html, href, options) if href.present?
38
+
39
+ content_tag(:a, html, options)
40
+ end
41
+
42
+ def render_button_inner_html(button, options = {}, &block)
43
+ html = render_button_icon(button.icon, options.delete(:icon)) || ''.html_safe
44
+ html += render_button_material_icon(button.material_icon) + content_tag(:span, button.text)
45
+
46
+ html += capture(&block) if block_given?
47
+ html
48
+ end
49
+
50
+ def render_button_icon(icon, options = {})
51
+ fallback = options&.delete(:fallback)
52
+ return fallback&.call unless icon
53
+
54
+ options = {
55
+ alt: icon.description || icon.title,
56
+ width: icon.file.dig('details', 'image', 'width'),
57
+ height: icon.file.dig('details', 'image', 'height')
58
+ }.merge!(options || {})
59
+ image_tag(icon&.file&.url, options)
60
+ end
61
+
62
+ def render_button_material_icon(material_icon)
63
+ content_tag(:i, material_icon&.downcase, class: ['material-icons'])
64
+ end
65
+
66
+ def push_class(classes, options)
67
+ options[:class] = [*classes, *options[:class]]
68
+ end
69
+
70
+ def hash_only(href)
71
+ url = URI(href)
72
+ '#' + url.fragment if url.fragment.present?
73
+ end
74
+
75
+ # An href is local if it points to a part of the page
76
+ def local?(href)
77
+ return true if href =~ /^#/
78
+
79
+ url = URI(href)
80
+ return false unless url.fragment.present?
81
+
82
+ url.fragment = nil
83
+ current_page?(url.to_s)
84
+ end
85
+ end