wcc-contentful-app 0.4.0.pre.rc → 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +2 -2
  3. data/Guardfile +1 -1
  4. data/README.md +11 -0
  5. data/Rakefile +121 -0
  6. data/app/assets/config/manifest.js +5 -0
  7. data/app/assets/images/down-arrow-primary.svg +3 -0
  8. data/app/assets/images/up-arrow-primary.svg +3 -0
  9. data/app/assets/javascripts/wcc/contentful/app/contact-form.js +70 -0
  10. data/app/assets/javascripts/wcc/contentful/app/index.js +1 -0
  11. data/app/assets/stylesheets/_wcc-contentful-app.scss +7 -0
  12. data/app/assets/stylesheets/components/_menu-item.scss +5 -0
  13. data/app/assets/stylesheets/sections/_faq.scss +39 -0
  14. data/app/controllers/wcc/contentful/app/contact_form_controller.rb +39 -0
  15. data/app/controllers/wcc/contentful/app/pages_controller.rb +48 -0
  16. data/app/helpers/wcc/contentful/app/menu_helper.rb +85 -0
  17. data/app/helpers/wcc/contentful/app/section_helper.rb +135 -0
  18. data/app/mailers/wcc/contentful/app/contact_mailer.rb +11 -0
  19. data/app/models/concerns/wcc/contentful/app/preview_password.rb +19 -0
  20. data/app/models/wcc/contentful/app/contact_form_submission.rb +9 -0
  21. data/app/models/wcc/contentful/app/custom_markdown_render.rb +43 -0
  22. data/app/views/components/_faq_row.html.erb +21 -0
  23. data/app/views/components/_menu-item.html.erb +41 -0
  24. data/app/views/components/_other-menu-item.html.erb +4 -0
  25. data/app/views/components/_section.html.erb +11 -0
  26. data/app/views/layouts/mailer.html.erb +9 -0
  27. data/app/views/layouts/mailer.text.erb +1 -0
  28. data/app/views/pages/show.html.erb +4 -0
  29. data/app/views/sections/_block_text.html.erb +5 -0
  30. data/app/views/sections/_code_widget.html.erb +3 -0
  31. data/app/views/sections/_contact_form.html.erb +53 -0
  32. data/app/views/sections/_faq.html.erb +36 -0
  33. data/app/views/sections/_http_error.html.erb +13 -0
  34. data/app/views/sections/_marquee_text.html.erb +12 -0
  35. data/app/views/sections/_testimonials.html.erb +38 -0
  36. data/app/views/sections/_video.html.erb +12 -0
  37. data/app/views/sections/_video_highlight.html.erb +18 -0
  38. data/app/views/wcc/contentful/app/contact_mailer/contact_form_email.html.erb +7 -0
  39. data/bin/rails +14 -0
  40. data/config/routes.rb +7 -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 +15 -15
  87. metadata +164 -103
  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: 85bc815850908faaa913245819d8b50690d1a518
4
- data.tar.gz: 48855069e703395ed509c12b87413b4033e156c6
2
+ SHA256:
3
+ metadata.gz: cfa9d4f23576789c57eec32e281809dbe73ecacd6b5c5bf77a2e1f2901735a66
4
+ data.tar.gz: 76e260820f5b44b8191205464ee3aa653764b1b4a78f69669f2eef13240d2573
5
5
  SHA512:
6
- metadata.gz: e5506dbfaad7e5dc3e4a73126059fe4ecd7bff08a17be8d9e74ad38343c4ce78f3b799abc747fdac5d08cd5ef32540f8505e93c698666d1313c6fd1882143419
7
- data.tar.gz: bcff44b6328836507c5892b360cca2cf9999a13d0aac7de6d75a785b79e912495d4e5bd1695907e6621243727b56006a9962fb1069ed1eae1456a67cf4c5deee
6
+ metadata.gz: 8f9090104d47faf28821061231614f975fb4f5365ec8b38bef38f94fb90c40ddcddcb25a59973ac1bd1dcc27c864958ed9cf4bbcc8fe9f39812cd976421e214c
7
+ data.tar.gz: 4def3fae3ac934d36c238d166bf1cf524ead5c6a640b9cf8f2929c22987a7d962c2bbfc2b45a70e7061494a1f9110728747af4ad7f287a9cb93df4025160e028
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/Guardfile CHANGED
@@ -1 +1 @@
1
- ../wcc-contentful/Guardfile
1
+ ./../wcc-contentful/Guardfile
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.
@@ -0,0 +1,121 @@
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
@@ -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