|
14 | 14 | # * https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md |
15 | 15 | # */ |
16 | 16 |
|
17 | | -module ReactOnRails |
18 | | - module Pro |
19 | | - module Helper |
20 | | - IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \ |
21 | | - "React on Rails Pro license. " \ |
22 | | - "Please visit https://shakacode.com/react-on-rails-pro to learn more." |
| 17 | +module ReactOnRails::Pro |
| 18 | + module Helper |
| 19 | + IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \ |
| 20 | + "React on Rails Pro license. " \ |
| 21 | + "Please visit https://shakacode.com/react-on-rails-pro to learn more." |
23 | 22 |
|
24 | | - # This method is responsible for generating the necessary attributes and script tags |
25 | | - # for the immediate_hydration feature. It is enabled only when a valid |
26 | | - # React on Rails Pro license is detected. |
27 | | - def apply_immediate_hydration_if_supported(component_specification_tag, render_options) |
28 | | - return component_specification_tag unless render_options.immediate_hydration && support_pro_features? |
| 23 | + # Generates the complete component specification script tag. |
| 24 | + # Handles both immediate hydration (Pro feature) and standard cases. |
| 25 | + def generate_component_script(render_options) |
| 26 | + # Setup the page_loaded_js, which is the same regardless of prerendering or not! |
| 27 | + # The reason is that React is smart about not doing extra work if the server rendering did its job. |
| 28 | + component_specification_tag = content_tag(:script, |
| 29 | + json_safe_and_pretty(render_options.client_props).html_safe, |
| 30 | + type: "application/json", |
| 31 | + class: "js-react-on-rails-component", |
| 32 | + id: "js-react-on-rails-component-#{render_options.dom_id}", |
| 33 | + "data-component-name" => render_options.react_component_name, |
| 34 | + "data-trace" => (render_options.trace ? true : nil), |
| 35 | + "data-dom-id" => render_options.dom_id, |
| 36 | + "data-store-dependencies" => render_options.store_dependencies&.to_json, |
| 37 | + "data-immediate-hydration" => |
| 38 | + (render_options.immediate_hydration ? true : nil)) |
29 | 39 |
|
30 | | - # Add data attribute |
31 | | - component_specification_tag.gsub!("<script ", '<script data-immediate-hydration="true" ') |
32 | | - |
33 | | - # Add immediate invocation script |
34 | | - component_specification_tag.concat( |
35 | | - content_tag(:script, %( |
36 | | - typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}'); |
37 | | - ).html_safe) |
38 | | - ) |
| 40 | + # Add immediate invocation script if immediate hydration is enabled |
| 41 | + spec_tag = if render_options.immediate_hydration |
| 42 | + # Escape dom_id for JavaScript context |
| 43 | + escaped_dom_id = escape_javascript(render_options.dom_id) |
| 44 | + immediate_script = content_tag(:script, %( |
| 45 | + typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{escaped_dom_id}'); |
| 46 | + ).html_safe) |
| 47 | + "#{component_specification_tag}\n#{immediate_script}" |
| 48 | + else |
| 49 | + component_specification_tag |
39 | 50 | end |
40 | 51 |
|
41 | | - # Similar logic for redux_store |
42 | | - def apply_store_immediate_hydration_if_supported(store_hydration_data, redux_store_data) |
43 | | - return store_hydration_data unless redux_store_data[:immediate_hydration] && support_pro_features? |
| 52 | + pro_warning_badge = pro_warning_badge_if_needed(render_options.explicitly_disabled_pro_options) |
| 53 | + "#{pro_warning_badge}\n#{spec_tag}".html_safe |
| 54 | + end |
44 | 55 |
|
45 | | - # Add data attribute |
46 | | - store_hydration_data.gsub!("<script ", '<script data-immediate-hydration="true" ') |
| 56 | + # Generates the complete store hydration script tag. |
| 57 | + # Handles both immediate hydration (Pro feature) and standard cases. |
| 58 | + def generate_store_script(redux_store_data) |
| 59 | + pro_options_check_result = ReactOnRails::Pro::Utils.disable_pro_render_options_if_not_licensed(redux_store_data) |
| 60 | + redux_store_data = pro_options_check_result[:raw_options] |
| 61 | + explicitly_disabled_pro_options = pro_options_check_result[:explicitly_disabled_pro_options] |
47 | 62 |
|
48 | | - # Add immediate invocation script |
49 | | - store_hydration_data.concat( |
50 | | - content_tag(:script, <<~JS.strip_heredoc.html_safe |
51 | | - typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{redux_store_data[:store_name]}'); |
52 | | - JS |
53 | | - ) |
| 63 | + store_hydration_data = content_tag(:script, |
| 64 | + json_safe_and_pretty(redux_store_data[:props]).html_safe, |
| 65 | + type: "application/json", |
| 66 | + "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe, |
| 67 | + "data-immediate-hydration" => |
| 68 | + (redux_store_data[:immediate_hydration] ? true : nil)) |
| 69 | + |
| 70 | + # Add immediate invocation script if immediate hydration is enabled and Pro license is valid |
| 71 | + store_hydration_scripts =if redux_store_data[:immediate_hydration] |
| 72 | + # Escape store_name for JavaScript context |
| 73 | + escaped_store_name = escape_javascript(redux_store_data[:store_name]) |
| 74 | + immediate_script = content_tag(:script, <<~JS.strip_heredoc.html_safe |
| 75 | + typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{escaped_store_name}'); |
| 76 | + JS |
54 | 77 | ) |
| 78 | + "#{store_hydration_data}\n#{immediate_script}" |
| 79 | + else |
| 80 | + store_hydration_data |
55 | 81 | end |
56 | 82 |
|
57 | | - # Checks if React on Rails Pro features are available |
58 | | - # @return [Boolean] true if Pro license is valid, false otherwise |
59 | | - def support_pro_features? |
60 | | - ReactOnRails::Utils.react_on_rails_pro_licence_valid? |
61 | | - end |
| 83 | + pro_warning_badge = pro_warning_badge_if_needed(explicitly_disabled_pro_options) |
| 84 | + "#{pro_warning_badge}\n#{store_hydration_scripts}".html_safe |
| 85 | + end |
62 | 86 |
|
63 | | - def pro_warning_badge_if_needed(immediate_hydration) |
64 | | - return "".html_safe unless immediate_hydration |
65 | | - return "".html_safe if support_pro_features? |
| 87 | + def pro_warning_badge_if_needed(explicitly_disabled_pro_options) |
| 88 | + return "" unless explicitly_disabled_pro_options.any? |
66 | 89 |
|
67 | | - puts IMMEDIATE_HYDRATION_PRO_WARNING |
68 | | - Rails.logger.warn IMMEDIATE_HYDRATION_PRO_WARNING |
| 90 | + disabled_features_message = disabled_pro_features_message(explicitly_disabled_pro_options) |
| 91 | + warning_message = "[REACT ON RAILS] #{disabled_features_message}" + "\n" + |
| 92 | + "Please visit https://shakacode.com/react-on-rails-pro to learn more." |
| 93 | + puts warning_message |
| 94 | + Rails.logger.warn warning_message |
69 | 95 |
|
70 | | - tooltip_text = "The 'immediate_hydration' feature requires a React on Rails Pro license. Click to learn more." |
| 96 | + tooltip_text = "#{disabled_features_message} Click to learn more." |
71 | 97 |
|
72 | | - badge_html = <<~HTML |
73 | | - <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}"> |
74 | | - <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;"> |
75 | | - <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;"> |
76 | | - React On Rails Pro Required |
77 | | - </div> |
| 98 | + badge_html = <<~HTML.strip |
| 99 | + <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}"> |
| 100 | + <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;"> |
| 101 | + <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;"> |
| 102 | + React On Rails Pro Required |
78 | 103 | </div> |
79 | | - </a> |
80 | | - HTML |
81 | | - badge_html.strip.html_safe |
82 | | - end |
| 104 | + </div> |
| 105 | + </a> |
| 106 | + HTML |
| 107 | + badge_html |
| 108 | + end |
| 109 | + |
| 110 | + def disabled_pro_features_message(explicitly_disabled_pro_options) |
| 111 | + return "".html_safe unless explicitly_disabled_pro_options.any? |
| 112 | + |
| 113 | + feature_list = explicitly_disabled_pro_options.join(', ') |
| 114 | + feature_word = explicitly_disabled_pro_options.size == 1 ? "feature" : "features" |
| 115 | + "The '#{feature_list}' #{feature_word} #{explicitly_disabled_pro_options.size == 1 ? 'requires' : 'require'} a " \ |
| 116 | + "React on Rails Pro license. " |
83 | 117 | end |
84 | 118 | end |
85 | 119 | end |
0 commit comments