diff --git a/app/controllers/admin/contents_controller.rb b/app/controllers/admin/contents_controller.rb index 0a41cc389a..ac9263fdc0 100644 --- a/app/controllers/admin/contents_controller.rb +++ b/app/controllers/admin/contents_controller.rb @@ -18,6 +18,8 @@ module Admin end end + ContentConfig.updated_at = Time.zone.now + flash[:success] = t(:successfully_updated, resource: I18n.t('admin.contents.edit.your_content')) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index afefbf5bf5..ea8c4beeab 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -68,4 +68,14 @@ module ApplicationHelper wicked_pdf_stylesheet_pack_tag(source) end end + + def cache_with_locale(key = nil, options = {}, &block) + cache(cache_key_with_locale(key, I18n.locale), options) do + yield(block) + end + end + + def cache_key_with_locale(key, locale) + Array.wrap(key) + [locale.to_s, I18nDigests.for_locale(locale)] + end end diff --git a/app/models/content_configuration.rb b/app/models/content_configuration.rb index d0160407a4..2d473db24f 100644 --- a/app/models/content_configuration.rb +++ b/app/models/content_configuration.rb @@ -83,4 +83,19 @@ class ContentConfiguration < Spree::Preferences::Configuration # User Guide preference :user_guide_link, :string, default: 'https://guide.openfoodnetwork.org/' + + # ContentConfig Caching + preference :updated_at_timestamp, :integer, default: Time.zone.today.to_time.to_i + + def updated_at + Time.zone.at updated_at_timestamp + end + + def updated_at=(time) + self.updated_at_timestamp = time.to_i + end + + def cache_key + "ContentConfig:#{updated_at_timestamp}" + end end diff --git a/app/views/components/_spinner.html.haml b/app/views/components/_spinner.html.haml index a3dee8a298..b4492c4b14 100644 --- a/app/views/components/_spinner.html.haml +++ b/app/views/components/_spinner.html.haml @@ -1 +1,2 @@ -%img.spinner{ src: image_pack_path("spinning-circles.svg"), style: "max-width: 100%" } += cache do + %img.spinner{ src: image_pack_path("spinning-circles.svg"), style: "max-width: 100%" } diff --git a/app/views/home/_tagline.html.haml b/app/views/home/_tagline.html.haml index 245d07263a..56dc937f25 100644 --- a/app/views/home/_tagline.html.haml +++ b/app/views/home/_tagline.html.haml @@ -1,8 +1,9 @@ -#tagline - .row - .small-12.text-center.columns - %h1 - %img{src: image_pack_path("logo-white-notext.png"), title: Spree::Config.site_name} - %br/ - %a.button.transparent{href: "/shops"} - = t :home_shop += cache_with_locale "sitename:#{Spree::Config.site_name}" do + #tagline + .row + .small-12.text-center.columns + %h1 + %img{src: image_pack_path("logo-white-notext.png"), title: Spree::Config.site_name} + %br/ + %a.button.transparent{href: "/shops"} + = t :home_shop diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 4f10a189fb..202f761266 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -1,91 +1,92 @@ -.row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"} - .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"} - %p.fullwidth - = render partial: "components/spinner" += cache_with_locale do + .row.active_table_row{"ng-if" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"} + .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"} + %p.fullwidth + = render partial: "components/spinner" - .columns.small-12.medium-7.large-7.fat{"ng-show" => "open() && !shopfront_loading"} - / Will add in long description available once clean up HTML formatting producer.long_description - %div{"ng-if" => "::producer.description"} - %label - = t :producers_about - %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" } - %p.text-small{ "ng-bind" => "::producer.description"} - %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"} - %label   - %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" } + .columns.small-12.medium-7.large-7.fat{"ng-show" => "open() && !shopfront_loading"} + / Will add in long description available once clean up HTML formatting producer.long_description + %div{"ng-if" => "::producer.description"} + %label + = t :producers_about + %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" } + %p.text-small{ "ng-bind" => "::producer.description"} + %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"} + %label   + %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" } - .columns.small-12.medium-5.large-5.fat{"ng-show" => "open() && !shopfront_loading"} + .columns.small-12.medium-5.large-5.fat{"ng-show" => "open() && !shopfront_loading"} - %div{"ng-if" => "::producer.supplied_taxons"} - %label - = t :producers_buy - %p.trans-sentence - %div - %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} - %span{"ng-bind" => "::taxon.name"} - %div - %span.fat-properties{"ng-repeat" => "property in producer.supplied_properties"} - %span{"ng-bind" => "property.presentation"} + %div{"ng-if" => "::producer.supplied_taxons"} + %label + = t :producers_buy + %p.trans-sentence + %div + %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} + %span{"ng-bind" => "::taxon.name"} + %div + %span.fat-properties{"ng-repeat" => "property in producer.supplied_properties"} + %span{"ng-bind" => "property.presentation"} - %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"} -   + %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"} +   - %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"} - %label - = t :producers_contact + %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"} + %label + = t :producers_contact - %p.word-wrap{"ng-if" => "::producer.phone"} - = t :producers_contact_phone - %span{"ng-bind" => "::producer.phone"} + %p.word-wrap{"ng-if" => "::producer.phone"} + = t :producers_contact_phone + %span{"ng-bind" => "::producer.phone"} - %p.word-wrap{"ng-if" => "::producer.whatsapp_phone"} - %a{"ng-href" => "{{::producer.whatsapp_url}}", target: "_blank"} - %img{ src: image_pack_path("social-logos/whatsapp.svg") } - %span{"ng-bind" => "::producer.whatsapp_phone"} + %p.word-wrap{"ng-if" => "::producer.whatsapp_phone"} + %a{"ng-href" => "{{::producer.whatsapp_url}}", target: "_blank"} + %img{ src: image_pack_path("social-logos/whatsapp.svg") } + %span{"ng-bind" => "::producer.whatsapp_phone"} - %p.word-wrap{"ng-if" => "::producer.email_address"} - %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true} - %span.obfuscatedEmail.email{"ng-bind" => "::producer.email_address | stripUrl"} + %p.word-wrap{"ng-if" => "::producer.email_address"} + %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true} + %span.obfuscatedEmail.email{"ng-bind" => "::producer.email_address | stripUrl"} - %p.word-wrap{"ng-if" => "::producer.website"} - %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" } - %span{"ng-bind" => "::producer.website | stripUrl"} + %p.word-wrap{"ng-if" => "::producer.website"} + %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" } + %span{"ng-bind" => "::producer.website | stripUrl"} - %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} - %label - = t :producers_social - .follow-icons - %span{"ng-if" => "::producer.twitter"} - %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"} - %i.ofn-i_041-twitter + %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} + %label + = t :producers_social + .follow-icons + %span{"ng-if" => "::producer.twitter"} + %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"} + %i.ofn-i_041-twitter - %span{"ng-if" => "::producer.facebook"} - %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"} - %i.ofn-i_044-facebook + %span{"ng-if" => "::producer.facebook"} + %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"} + %i.ofn-i_044-facebook - %span{"ng-if" => "::producer.linkedin"} - %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"} - %i.ofn-i_042-linkedin + %span{"ng-if" => "::producer.linkedin"} + %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"} + %i.ofn-i_042-linkedin - %span{"ng-if" => "::producer.instagram"} - %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"} - %i.ofn-i_043-instagram + %span{"ng-if" => "::producer.instagram"} + %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"} + %i.ofn-i_043-instagram -.row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs && !shopfront_loading"} - .columns.small-12{"ng-if" => "producer.hubs.length > 0"} - .row - .columns.small-12.fat - %div{"ng-if" => "::producer.name"} - %label - = t :producers_buy_at_html, enterprise: ''.html_safe - %div.show-for-medium-up{"ng-if" => "::!producer.name"} -   - .row.cta-container - .columns.small-12 - %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'", - "ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined }}", - "ng-class" => "::{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} - %i.ofn-i_068-shop-reversed{"ng-if" => "::hub.active"} - %i.ofn-i_068-shop-reversed{"ng-if" => "::!hub.active"} - .hub-name{"ng-bind" => "::hub.name"} - .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"} + .row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs && !shopfront_loading"} + .columns.small-12{"ng-if" => "producer.hubs.length > 0"} + .row + .columns.small-12.fat + %div{"ng-if" => "::producer.name"} + %label + = t :producers_buy_at_html, enterprise: ''.html_safe + %div.show-for-medium-up{"ng-if" => "::!producer.name"} +   + .row.cta-container + .columns.small-12 + %a.cta-hub{"ng-repeat" => "hub in producer.hubs | orderBy:'-active'", + "ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined }}", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + %i.ofn-i_068-shop-reversed{"ng-if" => "::hub.active"} + %i.ofn-i_068-shop-reversed{"ng-if" => "::!hub.active"} + .hub-name{"ng-bind" => "::hub.name"} + .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"} diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml index 4d6723892d..a4f1f3a3e4 100644 --- a/app/views/shared/_footer.html.haml +++ b/app/views/shared/_footer.html.haml @@ -1,115 +1,119 @@ %footer .footer-global - .row - .small-12.columns.text-center - .logo - %img{src: image_pack_path("logo-white-notext.png") } - .row - .small-12.medium-8.medium-offset-2.columns.text-center - .alert-box - = render 'shared/register_call' + = cache_with_locale "global" do + .row + .small-12.columns.text-center + .logo + %img{src: image_pack_path("logo-white-notext.png") } + .row + .small-12.medium-8.medium-offset-2.columns.text-center + .alert-box + = render 'shared/register_call' .footer-local - .row - .small-12.medium-2.medium-offset-2.columns.text-center - %p.secure-icon - %i.ofn-i_017-locked - .small-12.medium-6.columns.text-center - %p.text-big.secure-text - = t '.footer_secure' - %p.secure-text - = t '.footer_secure_text' - .small-12.medium-2.columns + = cache_with_locale "local" do + .row + .small-12.medium-2.medium-offset-2.columns.text-center + %p.secure-icon + %i.ofn-i_017-locked + .small-12.medium-6.columns.text-center + %p.text-big.secure-text + = t '.footer_secure' + %p.secure-text + = t '.footer_secure_text' + .small-12.medium-2.columns - .row - .small-12.medium-8.medium-offset-2.columns.text-center - %hr.hr-light - %br + .row + .small-12.medium-8.medium-offset-2.columns.text-center + %hr.hr-light + %br - .row - .small-6.medium-3.medium-offset-2.columns.text-left - // This is the instance-managed set of links: - %h4 - = t '.footer_contact_headline' - - if show_social_icons? - %p.social-icons - - if ContentConfig.footer_facebook_url.present? - %a{href: ContentConfig.footer_facebook_url} - %i.ofn-i_044-facebook - - if ContentConfig.footer_twitter_url.present? - %a{href: ContentConfig.footer_twitter_url} - %i.ofn-i_041-twitter - - if ContentConfig.footer_instagram_url.present? - %a{href: ContentConfig.footer_instagram_url} - %i.ofn-i_043-instagram - - if ContentConfig.footer_linkedin_url.present? - %a{href: ContentConfig.footer_linkedin_url} - %i.ofn-i_042-linkedin - - if ContentConfig.footer_googleplus_url.present? - %a{href: ContentConfig.footer_googleplus_url} - %i.ofn-i_046-g - - if ContentConfig.footer_pinterest_url.present? - %a{href: ContentConfig.footer_pinterest_url} - %i.ofn-i_045-pintrest - - if ContentConfig.footer_email.present? + = cache_with_locale ContentConfig.cache_key do + .row + .small-6.medium-3.medium-offset-2.columns.text-left + // This is the instance-managed set of links: + %h4 + = t '.footer_contact_headline' + - if show_social_icons? + %p.social-icons + - if ContentConfig.footer_facebook_url.present? + %a{href: ContentConfig.footer_facebook_url} + %i.ofn-i_044-facebook + - if ContentConfig.footer_twitter_url.present? + %a{href: ContentConfig.footer_twitter_url} + %i.ofn-i_041-twitter + - if ContentConfig.footer_instagram_url.present? + %a{href: ContentConfig.footer_instagram_url} + %i.ofn-i_043-instagram + - if ContentConfig.footer_linkedin_url.present? + %a{href: ContentConfig.footer_linkedin_url} + %i.ofn-i_042-linkedin + - if ContentConfig.footer_googleplus_url.present? + %a{href: ContentConfig.footer_googleplus_url} + %i.ofn-i_046-g + - if ContentConfig.footer_pinterest_url.present? + %a{href: ContentConfig.footer_pinterest_url} + %i.ofn-i_045-pintrest + - if ContentConfig.footer_email.present? + %p + %a{href: ContentConfig.footer_email.reverse, mailto: true, target: '_blank'} + = t '.footer_contact_email' + = render_markdown(ContentConfig.footer_links_md).html_safe + + + .small-6.medium-3.columns.text-left + %h4 + = t '.footer_nav_headline' %p - %a{href: ContentConfig.footer_email.reverse, mailto: true, target: '_blank'} - = t '.footer_contact_email' - = render_markdown(ContentConfig.footer_links_md).html_safe + %a{href: "/shops"} + = t :label_shops + %p + %a{href: "/map"} + = t :label_map + %p + %a{href: "/producers"} + = t :label_producers + %p + %a{href: "/groups"} + = t :label_groups + %p + %a{href: ContentConfig.footer_about_url} + = t :label_about + .small-12.medium-2.columns.text-left + %h4 + = t '.footer_join_headline' + %p + = t '.footer_join_body' + %a{href: "/sell"} + = t '.footer_join_cta' - .small-6.medium-3.columns.text-left - %h4 - = t '.footer_nav_headline' - %p - %a{href: "/shops"} - = t :label_shops - %p - %a{href: "/map"} - = t :label_map - %p - %a{href: "/producers"} - = t :label_producers - %p - %a{href: "/groups"} - = t :label_groups - %p - %a{href: ContentConfig.footer_about_url} - = t :label_about + .medium-2.columns.text-center + / Placeholder - .small-12.medium-2.columns.text-left - %h4 - = t '.footer_join_headline' - %p - = t '.footer_join_body' - %a{href: "/sell"} - = t '.footer_join_cta' + .row + .small-12.medium-8.medium-offset-2.columns.text-center + %hr.hr-light + %br - .medium-2.columns.text-center - / Placeholder - - .row - .small-12.medium-8.medium-offset-2.columns.text-center - %hr.hr-light - %br - - .row.legal - .small-12.medium-3.medium-offset-2.columns.text-left - %a{href: main_app.root_path} - %img{src: ContentConfig.url_for(:footer_logo), width: "220"} - .small-12.medium-5.columns.text-left - %p.text-small - = t '.footer_legal_call' - = link_to_platform_terms - | - = t '.footer_legal_visit' - %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub - %p.text-small - = t('.footer_legal_text_html', content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)' )) - %p.text-small - - if Spree::Config.privacy_policy_url.present? - = t('.footer_data_text_with_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe, privacy_policy: privacy_policy_link.html_safe) - - else - = t('.footer_data_text_without_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe) - .medium-2.columns.text-center - / Placeholder + = cache_with_locale [ContentConfig.cache_key, TermsOfServiceFile.current_url, Spree::Config.privacy_policy_url] do + .row.legal + .small-12.medium-3.medium-offset-2.columns.text-left + %a{href: main_app.root_path} + %img{src: ContentConfig.url_for(:footer_logo), width: "220"} + .small-12.medium-5.columns.text-left + %p.text-small + = t '.footer_legal_call' + = link_to_platform_terms + | + = t '.footer_legal_visit' + %a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub + %p.text-small + = t('.footer_legal_text_html', content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)' )) + %p.text-small + - if Spree::Config.privacy_policy_url.present? + = t('.footer_data_text_with_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe, privacy_policy: privacy_policy_link.html_safe) + - else + = t('.footer_data_text_without_privacy_policy_html', cookies_policy: cookies_policy_link.html_safe) + .medium-2.columns.text-center + / Placeholder diff --git a/app/views/shared/_register_call.html.haml b/app/views/shared/_register_call.html.haml index 54c55af680..1f04ef2521 100644 --- a/app/views/shared/_register_call.html.haml +++ b/app/views/shared/_register_call.html.haml @@ -1,12 +1,13 @@ -.alert-cta - %h6 - -# Please forgive the hard-coded link: - -# The more elegant 'registration_path' resolves to /signup due to spree_auth_device > config > routes.rb - -# This is one of several possible fixes. Long-term, we'd like to bring the accounts page into OFN. - -# View the discussion here: https://github.com/openfoodfoundation/openfoodnetwork/pull/3174 - %a{href: "/register", target: "_blank"} - = t '.selling_on_ofn' -   - %strong - = t '.register' - %i.ofn-i_054-point-right += cache_with_locale do + .alert-cta + %h6 + -# Please forgive the hard-coded link: + -# The more elegant 'registration_path' resolves to /signup due to spree_auth_device > config > routes.rb + -# This is one of several possible fixes. Long-term, we'd like to bring the accounts page into OFN. + -# View the discussion here: https://github.com/openfoodfoundation/openfoodnetwork/pull/3174 + %a{href: "/register", target: "_blank"} + = t '.selling_on_ofn' +   + %strong + = t '.register' + %i.ofn-i_054-point-right diff --git a/app/views/shared/menu/_cart.html.haml b/app/views/shared/menu/_cart.html.haml index 728b0c1daa..eb5fe0a563 100644 --- a/app/views/shared/menu/_cart.html.haml +++ b/app/views/shared/menu/_cart.html.haml @@ -1,8 +1,9 @@ -%span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"} - %a#cart.icon{"ng-click" => "toggleCartSidebar()"} - %span - = t '.cart' - %span.count - %img{ src: image_pack_path("menu/icn-cart.svg") } += cache_with_locale do + %span.cart-span{"ng-controller" => "CartCtrl", "ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"} + %a#cart.icon{"ng-click" => "toggleCartSidebar()"} %span - {{ Cart.total_item_count() }} + = t '.cart' + %span.count + %img{ src: image_pack_path("menu/icn-cart.svg") } + %span + {{ Cart.total_item_count() }} diff --git a/app/views/shared/menu/_cart_sidebar.html.haml b/app/views/shared/menu/_cart_sidebar.html.haml index 93ea539fdc..423d4585cd 100644 --- a/app/views/shared/menu/_cart_sidebar.html.haml +++ b/app/views/shared/menu/_cart_sidebar.html.haml @@ -1,38 +1,40 @@ .expanding-sidebar.cart-sidebar{ng: {controller: 'CartCtrl', class: "{'shown': showCartSidebar}"}} .background{ng: {click: 'toggleCartSidebar()'}} .sidebar - .cart-header - %span.title{"ng-show" => "Cart.line_items.length == 1"} - = t('.items_in_cart_singular', num: "{{ Cart.total_item_count() }}") - %span.title{"ng-show" => "Cart.line_items.length > 1"} - = t('.items_in_cart_plural', num: "{{ Cart.total_item_count() }}") - %a.close{ng: {click: 'toggleCartSidebar()'}} - = t('.close') - %i.ofn-i_009-close + = cache_with_locale "cart-header" do + .cart-header + %span.title{"ng-show" => "Cart.line_items.length == 1"} + = t('.items_in_cart_singular', num: "{{ Cart.total_item_count() }}") + %span.title{"ng-show" => "Cart.line_items.length > 1"} + = t('.items_in_cart_plural', num: "{{ Cart.total_item_count() }}") + %a.close{ng: {click: 'toggleCartSidebar()'}} + = t('.close') + %i.ofn-i_009-close .cart-content - %table - %tr.product-cart{"ng-repeat" => "line_item in Cart.line_items", "id" => "cart-variant-{{ line_item.variant.id }}"} - %td.image - %img{'ng-src' => '{{ line_item.variant.thumb_url }}'} - %td - %span {{ line_item.variant.extended_name | truncate: max_characters }} - %br - %span.options-text {{ line_item.variant.options_text | truncate: max_characters }} - %td.text-right - %span.quantity {{ line_item.quantity }} - %td - .total-price.text-right {{ line_item.total_price | localizeCurrency }} - .unit-price - %div{:style => "margin-right: 5px"} - %question-mark-with-tooltip{"question-mark-with-tooltip" => "_", - "question-mark-with-tooltip-append-to-body" => "true", - "question-mark-with-tooltip-placement" => "top", - "question-mark-with-tooltip-animation" => true, - key: "'js.shopfront.unit_price_tooltip'", - context: "'cart-sidebar'"} - .options-text - {{ line_item.variant.unit_price_price | localizeCurrency }} / {{ line_item.variant.unit_price_unit }} + = cache_with_locale "cart-table" do + %table + %tr.product-cart{"ng-repeat" => "line_item in Cart.line_items", "id" => "cart-variant-{{ line_item.variant.id }}"} + %td.image + %img{'ng-src' => '{{ line_item.variant.thumb_url }}'} + %td + %span {{ line_item.variant.extended_name | truncate: max_characters }} + %br + %span.options-text {{ line_item.variant.options_text | truncate: max_characters }} + %td.text-right + %span.quantity {{ line_item.quantity }} + %td + .total-price.text-right {{ line_item.total_price | localizeCurrency }} + .unit-price + %div{:style => "margin-right: 5px"} + %question-mark-with-tooltip{"question-mark-with-tooltip" => "_", + "question-mark-with-tooltip-append-to-body" => "true", + "question-mark-with-tooltip-placement" => "top", + "question-mark-with-tooltip-animation" => true, + key: "'js.shopfront.unit_price_tooltip'", + context: "'cart-sidebar'"} + .options-text + {{ line_item.variant.unit_price_price | localizeCurrency }} / {{ line_item.variant.unit_price_unit }} .cart-empty{"ng-show" => "Cart.line_items.length == 0"} %p @@ -42,15 +44,16 @@ = t('.take_me_shopping') .sidebar-footer{"ng-show" => "Cart.line_items.length > 0"} - %p.cart-total - %strong - = t 'total' - {{ Cart.total() | localizeCurrency }} + = cache_with_locale "cart-footer" do + %p.cart-total + %strong + = t 'total' + {{ Cart.total() | localizeCurrency }} - %div.fullwidth - %a.edit-cart.button.large.dark.left{href: main_app.cart_path, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }"} - %div{ ng: { if: "Cart.dirty" } }= t(:cart_updating) - %div{ ng: { if: "!Cart.dirty && Cart.empty()" } }= t(:cart_empty) - %div{ ng: { if: "!Cart.dirty && !Cart.empty()" } }= t('.edit_cart') - %a.checkout.button.large.bright.right{href: main_app.checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"} - = t '.checkout' + %div.fullwidth + %a.edit-cart.button.large.dark.left{href: main_app.cart_path, "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }"} + %div{ ng: { if: "Cart.dirty" } }= t(:cart_updating) + %div{ ng: { if: "!Cart.dirty && Cart.empty()" } }= t(:cart_empty) + %div{ ng: { if: "!Cart.dirty && !Cart.empty()" } }= t('.edit_cart') + %a.checkout.button.large.bright.right{href: main_app.checkout_path, "ng-disabled" => "Cart.dirty || Cart.empty()"} + = t '.checkout' diff --git a/app/views/shared/menu/_language_selector.html.haml b/app/views/shared/menu/_language_selector.html.haml index b35a1a25b0..c379ff02ec 100644 --- a/app/views/shared/menu/_language_selector.html.haml +++ b/app/views/shared/menu/_language_selector.html.haml @@ -1,8 +1,9 @@ -%li.language-switcher.has-dropdown.not-click - %a{href: '#', class: "top-bar--menu-item-with-icon"} - %i.ofn-i_071-globe - %span= t 'language_name' - %ul.dropdown - - OpenFoodNetwork::I18nConfig.locale_options.each do |l| - %li - = link_to t('language_name', locale: l), main_app.locale_path(l.to_s) += cache_with_locale OpenFoodNetwork::I18nConfig.locale_options do + %li.language-switcher.has-dropdown.not-click + %a{href: '#', class: "top-bar--menu-item-with-icon"} + %i.ofn-i_071-globe + %span= t 'language_name' + %ul.dropdown + - OpenFoodNetwork::I18nConfig.locale_options.each do |l| + %li + = link_to t('language_name', locale: l), main_app.locale_path(l.to_s) diff --git a/app/views/shared/menu/_large_menu.html.haml b/app/views/shared/menu/_large_menu.html.haml index b9a6cb3b7a..47e652fd89 100644 --- a/app/views/shared/menu/_large_menu.html.haml +++ b/app/views/shared/menu/_large_menu.html.haml @@ -1,27 +1,30 @@ %nav.top-bar.show-for-large-up %section.top-bar-section %ul.nav-logo - %li.ofn-logo - %a{href: main_logo_link(@white_label_distributor)} - - if @white_label_logo&.variable? - = image_tag @white_label_distributor.white_label_logo_url(:default) - - else - %img{src: ContentConfig.url_for(:logo)} - %li.powered-by - %img{src: '/favicon.ico'} - %span - = t 'powered_by' - %a{href: '/'} - = t 'title' + = cache_with_locale [@white_label_distributor, ContentConfig.cache_key] do + %li.ofn-logo + %a{href: main_logo_link(@white_label_distributor)} + - if @white_label_logo&.variable? + = image_tag @white_label_distributor.white_label_logo_url(:default) + - else + %img{src: ContentConfig.url_for(:logo)} + %li.powered-by + %img{src: '/favicon.ico'} + %span + = t 'powered_by' + %a{href: '/'} + = t 'title' + - unless @hide_ofn_navigation - %ul.nav-main-menu - - [*1..7].each do |menu_number| - - menu_name = "menu_#{menu_number}" - - if ContentConfig[menu_name].present? - %li - %a{href: t("#{menu_name}_url") } - %span.nav-primary - = t "#{menu_name}_title" + = cache_with_locale ContentConfig.cache_key do + %ul.nav-main-menu + - [*1..7].each do |menu_number| + - menu_name = "menu_#{menu_number}" + - if ContentConfig[menu_name].present? + %li + %a{href: t("#{menu_name}_url") } + %span.nav-primary + = t "#{menu_name}_title" %ul.nav-icons-menu - if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1 = render 'shared/menu/language_selector' @@ -31,11 +34,12 @@ - else = render 'shared/menu/signed_in' - %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id", "ng-cloak" => true} - %a{href: main_app.shop_path} - %span{ class: "top-bar--current-hub-prefix" } - = t 'label_shopping' - = '@' - %span{ class: "top-bar--current-hub-name" } {{ CurrentHub.hub.name | truncate:25 }} - %li.cart{"ng-cloak" => true} - = render partial: "shared/menu/cart" + = cache_with_locale "cart" do + %li.current_hub{"ng-controller" => "CurrentHubCtrl", "ng-show" => "CurrentHub.hub.id", "ng-cloak" => true} + %a{href: main_app.shop_path} + %span{ class: "top-bar--current-hub-prefix" } + = t 'label_shopping' + = '@' + %span{ class: "top-bar--current-hub-name" } {{ CurrentHub.hub.name | truncate:25 }} + %li.cart{"ng-cloak" => true} + = render partial: "shared/menu/cart" diff --git a/app/views/shared/menu/_mobile_menu.html.haml b/app/views/shared/menu/_mobile_menu.html.haml index 75540b5ed9..7288b2a78e 100644 --- a/app/views/shared/menu/_mobile_menu.html.haml +++ b/app/views/shared/menu/_mobile_menu.html.haml @@ -1,25 +1,26 @@ -%nav.tab-bar.show-for-medium-down - %section.left - %a.left-off-canvas-toggle.menu-icon - = image_pack_tag "menu/btn-menu-mobile.png" += cache_with_locale [@white_label_distributor, ContentConfig.cache_key] do + %nav.tab-bar.show-for-medium-down + %section.left + %a.left-off-canvas-toggle.menu-icon + = image_pack_tag "menu/btn-menu-mobile.png" - %section.left - .ofn-logo - %a{href: main_app.root_path} - - if @white_label_logo&.variable? - = image_tag @white_label_distributor.white_label_logo_url(:mobile) - - else - %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"} + %section.left + .ofn-logo + %a{href: main_app.root_path} + - if @white_label_logo&.variable? + = image_tag @white_label_distributor.white_label_logo_url(:mobile) + - else + %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"} - %section.right{"ng-cloak" => true} - %span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"} - %a.icon{ng: {click: 'toggleCartSidebar()'}} - %span - = t '.cart' - %span.count - = image_pack_tag "menu/icn-cart.svg" - %span - {{ Cart.total_item_count() }} + %section.right{"ng-cloak" => true} + %span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"} + %a.icon{ng: {click: 'toggleCartSidebar()'}} + %span + = t '.cart' + %span.count + = image_pack_tag "menu/icn-cart.svg" + %span + {{ Cart.total_item_count() }} - %a{href: main_app.shop_path} - {{ CurrentHub.hub.name }} + %a{href: main_app.shop_path} + {{ CurrentHub.hub.name }} diff --git a/app/views/shared/menu/_offcanvas_menu.html.haml b/app/views/shared/menu/_offcanvas_menu.html.haml index ca8b907cf7..1bb67b8f77 100644 --- a/app/views/shared/menu/_offcanvas_menu.html.haml +++ b/app/views/shared/menu/_offcanvas_menu.html.haml @@ -1,16 +1,18 @@ %aside.left-off-canvas-menu.show-for-medium-down{ ng: { controller: "OffcanvasCtrl" } } %ul.off-canvas-list - %li.ofn-logo - %a{href: main_app.root_path} - %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"} - - [*1..7].each do |menu_number| - - menu_name = "menu_#{menu_number}" - - if ContentConfig[menu_name].present? - %li.li-menu - %a{href: t("#{menu_name}_url") } - %span.nav-primary - %i{class: ContentConfig["#{menu_name}_icon_name"]} - = t "#{menu_name}_title" + = cache_with_locale ContentConfig.cache_key do + %li.ofn-logo + %a{href: main_app.root_path} + %img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"} + - [*1..7].each do |menu_number| + - menu_name = "menu_#{menu_number}" + - if ContentConfig[menu_name].present? + %li.li-menu + %a{href: t("#{menu_name}_url") } + %span.nav-primary + %i{class: ContentConfig["#{menu_name}_icon_name"]} + = t "#{menu_name}_title" + - if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1 %li.language-switcher.li-menu %a diff --git a/app/views/shared/menu/_signed_out.html.haml b/app/views/shared/menu/_signed_out.html.haml index 570c707b31..d6913ec554 100644 --- a/app/views/shared/menu/_signed_out.html.haml +++ b/app/views/shared/menu/_signed_out.html.haml @@ -1,5 +1,6 @@ -%li#login-link{ "data-controller": "login-modal" } - %a{"auth": "login", "data-action": "click->login-modal#call" } - %img{ src: image_pack_path("menu/icn-login.svg") } - %span - = t 'label_login' += cache_with_locale do + %li#login-link{ "data-controller": "login-modal" } + %a{"auth": "login", "data-action": "click->login-modal#call" } + %img{ src: image_pack_path("menu/icn-login.svg") } + %span + = t 'label_login' diff --git a/app/views/shop/products/_applied_filters_feedback.haml b/app/views/shop/products/_applied_filters_feedback.haml index 45730f39cf..2eb647b3b5 100644 --- a/app/views/shop/products/_applied_filters_feedback.haml +++ b/app/views/shop/products/_applied_filters_feedback.haml @@ -1,9 +1,10 @@ -%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" } - = t :products_filters_in += cache_with_locale do + %span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" } + = t :products_filters_in -%span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'} + %span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'} -%span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" } - = t :products_and + %span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" } + = t :products_and -%span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'} + %span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'} diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index f0e69d1985..869b215c60 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,5 +1,6 @@ -.filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}} - %filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"} += cache_with_locale do + .filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}} + %filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"} -.filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}} - %filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"} + .filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}} + %filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index 18471ead25..e5a51a50cc 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -1,51 +1,52 @@ -%form{action: main_app.cart_path} - %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true } += cache_with_locale do + %form{action: main_app.cart_path} + %products{"ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true } - = render partial: "shop/products/searchbar" + = render partial: "shop/products/searchbar" - .row - .footer-pad.small-12.columns.product-listing - .row.full - .medium-12.large-9.columns.full - = render partial: "shop/products/search_feedback" + .row + .footer-pad.small-12.columns.product-listing + .row.full + .medium-12.large-9.columns.full + = render partial: "shop/products/search_feedback" - %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading', "infinite-scroll-immediate-check": "false" } - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} - = render "shop/products/summary" - .shop-variants - .variants.row{"ng-controller": "ShopVariantCtrl", variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} - = render "shop/products/shop_variant" - %product{"ng-show" => "Products.loading"} - .summary - .small-12.columns.text-center - = t :products_loading - .row.full - .small-12.columns.text-center - = render partial: "components/spinner" + %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading', "infinite-scroll-immediate-check": "false" } + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} + = render "shop/products/summary" + .shop-variants + .variants.row{"ng-controller": "ShopVariantCtrl", variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} + = render "shop/products/shop_variant" + %product{"ng-show" => "Products.loading"} + .summary + .small-12.columns.text-center + = t :products_loading + .row.full + .small-12.columns.text-center + = render partial: "components/spinner" - .hide-for-medium-down.large-1.columns - -# Space between products and filters -   + .hide-for-medium-down.large-1.columns + -# Space between products and filters +   - .sticky-shop-filters-container.thin-scroll-bar.hide-for-medium-down.large-2.columns - %h5.filter-header - = t(:products_filter_by) - %span{ng: {show: 'filtersCount()' }} - = "({{ filtersCount() }} #{t(:products_filter_selected)})" - = render partial: "shop/products/filters" - - .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}} - .background{ng: {click: 'toggleFilterSidebar()'}} - .sidebar - %h5 + .sticky-shop-filters-container.thin-scroll-bar.hide-for-medium-down.large-2.columns + %h5.filter-header = t(:products_filter_by) %span{ng: {show: 'filtersCount()' }} = "({{ filtersCount() }} #{t(:products_filter_selected)})" - = render partial: "shop/products/filters" - .sidebar-footer - %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}} - = t(:products_filter_clear) - %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}} - = t(:products_filter_done) + .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}} + .background{ng: {click: 'toggleFilterSidebar()'}} + .sidebar + %h5 + = t(:products_filter_by) + %span{ng: {show: 'filtersCount()' }} + = "({{ filtersCount() }} #{t(:products_filter_selected)})" + + = render partial: "shop/products/filters" + + .sidebar-footer + %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}} + = t(:products_filter_clear) + %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}} + = t(:products_filter_done) diff --git a/app/views/shop/products/_search_feedback.haml b/app/views/shop/products/_search_feedback.haml index ea6b0c7f22..3245c6bb50 100644 --- a/app/views/shop/products/_search_feedback.haml +++ b/app/views/shop/products/_search_feedback.haml @@ -1,24 +1,25 @@ -.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" } - .small-12.columns - .alert-box.search-alert.ng-scope - %div{"ng-show" => "Products.products.length > 0"} += cache_with_locale do + .row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" } + .small-12.columns + .alert-box.search-alert.ng-scope + %div{"ng-show" => "Products.products.length > 0"} - %a.clear-all.right{"ng-click" => "clearAll()"} - = t :products_clear - %i.ofn-i_009-close + %a.clear-all.right{"ng-click" => "clearAll()"} + = t :products_clear + %i.ofn-i_009-close - %span.filter-label - = t :products_results_for - %span{ ng: { hide: "!query"} } - %span.applied-search - {{ query }} - = render partial: 'shop/products/applied_filters_feedback' + %span.filter-label + = t :products_results_for + %span{ ng: { hide: "!query"} } + %span.applied-search + {{ query }} + = render partial: 'shop/products/applied_filters_feedback' - %div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"} - .row.summary - .small-12.columns - %p.no-results - = t :products_no_results_html, query: "{{query}}".html_safe - = render partial: 'shop/products/applied_filters_feedback' - %button.clear-search{type: 'button', ng: {click: 'clearAll()'}} - = t :products_clear_search + %div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"} + .row.summary + .small-12.columns + %p.no-results + = t :products_no_results_html, query: "{{query}}".html_safe + = render partial: 'shop/products/applied_filters_feedback' + %button.clear-search{type: 'button', ng: {click: 'clearAll()'}} + = t :products_clear_search diff --git a/app/views/shop/products/_searchbar.haml b/app/views/shop/products/_searchbar.haml index a17db1e322..b7e6f98d57 100644 --- a/app/views/shop/products/_searchbar.haml +++ b/app/views/shop/products/_searchbar.haml @@ -1,17 +1,18 @@ -.shop-searchbar - .row - .small-12.large-5.columns.flex - %div.search-wrap - %input#search.text{"ng-model" => "query", - type: 'search', - placeholder: t(:products_search), - "ng-debounce" => "200", - "disable-enter-with-blur" => true} - %a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true} - = image_pack_tag "icn-close.png" += cache_with_locale do + .shop-searchbar + .row + .small-12.large-5.columns.flex + %div.search-wrap + %input#search.text{"ng-model" => "query", + type: 'search', + placeholder: t(:products_search), + "ng-debounce" => "200", + "disable-enter-with-blur" => true} + %a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true} + = image_pack_tag "icn-close.png" - .hide-for-large-up - %button{type: 'button', ng: {click: 'toggleFilterSidebar()'}} - = t(:products_filter_heading) - %span{ng: {show: 'filtersCount()' }} - ({{ filtersCount() }}) + .hide-for-large-up + %button{type: 'button', ng: {click: 'toggleFilterSidebar()'}} + = t(:products_filter_heading) + %span{ng: {show: 'filtersCount()' }} + ({{ filtersCount() }}) diff --git a/app/views/shop/products/_shop_variant.html.haml b/app/views/shop/products/_shop_variant.html.haml index 63d5f05090..b6e91c8c2f 100644 --- a/app/views/shop/products/_shop_variant.html.haml +++ b/app/views/shop/products/_shop_variant.html.haml @@ -1,22 +1,23 @@ -.small-4.medium-4.large-5.columns.variant-name - .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} - .variant-unit {{ ::variant.unit_to_display }} -.small-3.medium-3.large-2.columns.variant-price - %price-breakdown{"price-breakdown" => "_", variant: "variant", - "price-breakdown-append-to-body" => "true", - "price-breakdown-placement" => "bottom", - "price-breakdown-animation" => true} - {{ variant.price_with_fees | localizeCurrency }} - .unit-price.variant-unit-price - %question-mark-with-tooltip{"question-mark-with-tooltip" => "_", - "question-mark-with-tooltip-append-to-body" => "true", - "question-mark-with-tooltip-placement" => "top", - "question-mark-with-tooltip-animation" => true, - key: "'js.shopfront.unit_price_tooltip'"} - {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }} += cache_with_locale do + .small-4.medium-4.large-5.columns.variant-name + .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} + .variant-unit {{ ::variant.unit_to_display }} + .small-3.medium-3.large-2.columns.variant-price + %price-breakdown{"price-breakdown" => "_", variant: "variant", + "price-breakdown-append-to-body" => "true", + "price-breakdown-placement" => "bottom", + "price-breakdown-animation" => true} + {{ variant.price_with_fees | localizeCurrency }} + .unit-price.variant-unit-price + %question-mark-with-tooltip{"question-mark-with-tooltip" => "_", + "question-mark-with-tooltip-append-to-body" => "true", + "question-mark-with-tooltip-placement" => "top", + "question-mark-with-tooltip-animation" => true, + key: "'js.shopfront.unit_price_tooltip'"} + {{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }} -.medium-2.large-2.columns.total-price - %span{"ng-class" => "{filled: variant.line_item.total_price}"} - {{ variant.line_item.total_price | localizeCurrency }} -= render partial: "shop/products/shop_variant_no_group_buy" -= render partial: "shop/products/shop_variant_with_group_buy" + .medium-2.large-2.columns.total-price + %span{"ng-class" => "{filled: variant.line_item.total_price}"} + {{ variant.line_item.total_price | localizeCurrency }} + = render partial: "shop/products/shop_variant_no_group_buy" + = render partial: "shop/products/shop_variant_with_group_buy" diff --git a/app/views/shop/products/_shop_variant_no_group_buy.html.haml b/app/views/shop/products/_shop_variant_no_group_buy.html.haml index 7885a2e6b4..c2f3e4131c 100644 --- a/app/views/shop/products/_shop_variant_no_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_no_group_buy.html.haml @@ -1,22 +1,23 @@ -.small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"} += cache_with_locale do + .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::!variant.product.group_buy"} - .variant-quantity-inputs{ng: {if: "variant.line_item.quantity == 0"}} - %button.add-variant{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}} - {{ "js.shopfront.variant.add_to_cart" | t }} + .variant-quantity-inputs{ng: {if: "variant.line_item.quantity == 0"}} + %button.add-variant{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}} + {{ "js.shopfront.variant.add_to_cart" | t }} - .variant-quantity-inputs{ng: {if: "variant.line_item.quantity != 0"}} - %button.variant-quantity{type: "button", ng: {click: "add(-1)", disabled: "!canAdd(-1)"}}> - -# U+FF0D Fullwidth Hyphen-Minus - - - %input.variant-quantity{ type: "number", min: "0", max: "{{ available() }}", - ng: {model: "variant.line_item.quantity", max: "Infinity"}}> - %button.variant-quantity{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}} - -# U+FF0B Fullwidth Plus Sign - + - .variant-remaining-stock{ng: {if: "displayRemainingInStock()"}} - {{ "js.shopfront.variant.remaining_in_stock" | t:{quantity: available()} }} - .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} - {{ "js.shopfront.variant.quantity_in_cart" | t:{quantity: variant.line_item.quantity || 0} }} - %input{type: :hidden, - name: "variants[{{::variant.id}}]", - ng: {model: "variant.line_item.quantity"}} + .variant-quantity-inputs{ng: {if: "variant.line_item.quantity != 0"}} + %button.variant-quantity{type: "button", ng: {click: "add(-1)", disabled: "!canAdd(-1)"}}> + -# U+FF0D Fullwidth Hyphen-Minus + - + %input.variant-quantity{ type: "number", min: "0", max: "{{ available() }}", + ng: {model: "variant.line_item.quantity", max: "Infinity"}}> + %button.variant-quantity{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}} + -# U+FF0B Fullwidth Plus Sign + + + .variant-remaining-stock{ng: {if: "displayRemainingInStock()"}} + {{ "js.shopfront.variant.remaining_in_stock" | t:{quantity: available()} }} + .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} + {{ "js.shopfront.variant.quantity_in_cart" | t:{quantity: variant.line_item.quantity || 0} }} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.quantity"}} diff --git a/app/views/shop/products/_shop_variant_with_group_buy.html.haml b/app/views/shop/products/_shop_variant_with_group_buy.html.haml index 262ec20e3e..fc5c6533b3 100644 --- a/app/views/shop/products/_shop_variant_with_group_buy.html.haml +++ b/app/views/shop/products/_shop_variant_with_group_buy.html.haml @@ -1,17 +1,18 @@ -.small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"} += cache_with_locale do + .small-5.medium-3.large-3.columns.variant-quantity-column.text-right{"ng-if" => "::variant.product.group_buy"} - %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "addBulk(1)", disabled: "!canAdd(1)"}} - {{ "js.shopfront.variant.add_to_cart" | t }} - %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}> - {{ variant.line_item.quantity }} - %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}} - {{ variant.line_item.max_quantity || "-" }} - %br - .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} - {{ "js.shopfront.variant.in_cart" | t }} - %input{type: :hidden, - name: "variants[{{::variant.id}}]", - ng: {model: "variant.line_item.quantity"}} - %input{type: :hidden, - name: "variants[{{::variant.id}}]", - ng: {model: "variant.line_item.max_quantity"}} + %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "addBulk(1)", disabled: "!canAdd(1)"}} + {{ "js.shopfront.variant.add_to_cart" | t }} + %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}> + {{ variant.line_item.quantity }} + %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}} + {{ variant.line_item.max_quantity || "-" }} + %br + .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} + {{ "js.shopfront.variant.in_cart" | t }} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.quantity"}} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.max_quantity"}} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 7beec0d15b..66479e2ff8 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,21 +1,22 @@ -.product-thumb - %a{"ng-click" => "triggerProductModal()"} - %span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"} - = t(".bulk") - %img{"ng-src" => "{{::product.primaryImageOrMissing}}"} += cache_with_locale do + .product-thumb + %a{"ng-click" => "triggerProductModal()"} + %span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"} + = t(".bulk") + %img{"ng-src" => "{{::product.primaryImageOrMissing}}"} -.summary - .summary-header - %h3 - %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'} - %span{"ng-bind" => "::product.name"} - .product-description{ng: {"bind-html": "::product.description_html", click: "triggerProductModal()", show: "product.description_html.length"}} - %div{ "ng-switch" => "enterprise.visible" } - .product-producer - = t :products_from - %span{ "ng-switch-when": "hidden", "ng-bind" => "::enterprise.name"} - %span{ "ng-switch-default": true } - %enterprise-modal{"ng-bind" => "::enterprise.name"} + .summary + .summary-header + %h3 + %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'} + %span{"ng-bind" => "::product.name"} + .product-description{ng: {"bind-html": "::product.description_html", click: "triggerProductModal()", show: "product.description_html.length"}} + %div{ "ng-switch" => "enterprise.visible" } + .product-producer + = t :products_from + %span{ "ng-switch-when": "hidden", "ng-bind" => "::enterprise.name"} + %span{ "ng-switch-default": true } + %enterprise-modal{"ng-bind" => "::enterprise.name"} - .product-properties.filter-shopfront.property-selectors - %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } + .product-properties.filter-shopfront.property-selectors + %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } diff --git a/app/views/shops/_fat.html.haml b/app/views/shops/_fat.html.haml index a2522271ca..17a86258de 100644 --- a/app/views/shops/_fat.html.haml +++ b/app/views/shops/_fat.html.haml @@ -1,53 +1,54 @@ -.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"} - .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"} - %p.fullwidth - = render partial: "components/spinner" += cache_with_locale do + .row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : open()}"} + .columns.small-12.fat.text-center{"ng-show" => "open() && shopfront_loading"} + %p.fullwidth + = render partial: "components/spinner" - .columns.small-12.medium-6.large-5.fat{"ng-show" => "open() && !shopfront_loading"} - %div{"ng-if" => "::hub.taxons"} - %label - = t :hubs_buy - .trans-sentence - %div - %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"} - %span{"ng-bind" => "::taxon.name"} - %div - %span.fat-properties{"ng-repeat" => "property in hub.distributed_properties"} - %span{"ng-bind" => "property.presentation"} - %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"} -   - .columns.small-12.medium-3.large-2.fat{"ng-show" => "open() && !shopfront_loading"} - %div{"ng-if" => "::(hub.pickup || hub.delivery)"} - %label - = t :hubs_delivery_options - %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1 - %li.pickup{"ng-if" => "::hub.pickup"} - %i.ofn-i_038-takeaway - = t :hubs_pickup - %li.delivery{"ng-if" => "::hub.delivery"} - %i.ofn-i_039-delivery - = t :hubs_delivery - .columns.small-12.medium-3.large-5.fat{"ng-show" => "open() && !shopfront_loading"} - %div{"ng-if" => "::hub.producers"} - %label - = t :hubs_producers - %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"} - %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"} - %enterprise-modal - %i.ofn-i_036-producers - %span{"ng-bind" => "::enterprise.name"} - %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"} - %enterprise-modal - %i.ofn-i_036-producers - %span{"ng-bind" => "::enterprise.name"} - %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"} - %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers; $event.stopPropagation()"} - .more - + - %span{"ng-bind" => "::hub.producers.length-7"} - = t :label_more - .less - = t :label_less + .columns.small-12.medium-6.large-5.fat{"ng-show" => "open() && !shopfront_loading"} + %div{"ng-if" => "::hub.taxons"} + %label + = t :hubs_buy + .trans-sentence + %div + %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"} + %span{"ng-bind" => "::taxon.name"} + %div + %span.fat-properties{"ng-repeat" => "property in hub.distributed_properties"} + %span{"ng-bind" => "property.presentation"} + %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"} +   + .columns.small-12.medium-3.large-2.fat{"ng-show" => "open() && !shopfront_loading"} + %div{"ng-if" => "::(hub.pickup || hub.delivery)"} + %label + = t :hubs_delivery_options + %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1 + %li.pickup{"ng-if" => "::hub.pickup"} + %i.ofn-i_038-takeaway + = t :hubs_pickup + %li.delivery{"ng-if" => "::hub.delivery"} + %i.ofn-i_039-delivery + = t :hubs_delivery + .columns.small-12.medium-3.large-5.fat{"ng-show" => "open() && !shopfront_loading"} + %div{"ng-if" => "::hub.producers"} + %label + = t :hubs_producers + %ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"} + %li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"} + %enterprise-modal + %i.ofn-i_036-producers + %span{"ng-bind" => "::enterprise.name"} + %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"} + %enterprise-modal + %i.ofn-i_036-producers + %span{"ng-bind" => "::enterprise.name"} + %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"} + %a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers; $event.stopPropagation()"} + .more + + + %span{"ng-bind" => "::hub.producers.length-7"} + = t :label_more + .less + = t :label_less - %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"} -   + %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"} +   diff --git a/app/views/shops/_hubs.html.haml b/app/views/shops/_hubs.html.haml index 243f2e7c63..23410b2505 100644 --- a/app/views/shops/_hubs.html.haml +++ b/app/views/shops/_hubs.html.haml @@ -7,31 +7,32 @@ = render "shared/components/enterprise_search" = render "filters" - .row - .small-12.columns - .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"} - %h2 - = t :hubs_matches - = render "hubs_table", enterprises: "nameMatches" + = cache_with_locale do + .row + .small-12.columns + .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"} + %h2 + = t :hubs_matches + = render "hubs_table", enterprises: "nameMatches" - .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"} - %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"} - = t :hubs_matches - %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}... - %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}... + .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"} + %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"} + = t :hubs_matches + %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}... + %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}... - = render "hubs_table", enterprises: "distanceMatches" + = render "hubs_table", enterprises: "distanceMatches" - .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"} - %a{href: "", "ng-click" => "showDistanceMatches()"} - = t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}" - .more-controls - %span{ng: {show: "closed_shops_loading", cloak: true}} - = render partial: "components/spinner" - %span{ng: {if: "!show_closed", cloak: true}} - %a.button{href: "", ng: {click: "showClosedShops()"}} - = t '.show_closed_shops' - %span{ng: {if: "show_closed", cloak: true}} - %a.button{href: "", ng: {click: "hideClosedShops()"}} - = t '.hide_closed_shops' - %a.button{href: main_app.map_path}= t '.show_on_map' + .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"} + %a{href: "", "ng-click" => "showDistanceMatches()"} + = t :hubs_distance_filter, location: "{{ nameMatchesFiltered[0].name }}" + .more-controls + %span{ng: {show: "closed_shops_loading", cloak: true}} + = render partial: "components/spinner" + %span{ng: {if: "!show_closed", cloak: true}} + %a.button{href: "", ng: {click: "showClosedShops()"}} + = t '.show_closed_shops' + %span{ng: {if: "show_closed", cloak: true}} + %a.button{href: "", ng: {click: "hideClosedShops()"}} + = t '.hide_closed_shops' + %a.button{href: main_app.map_path}= t '.show_on_map' diff --git a/app/views/shops/_hubs_table.html.haml b/app/views/shops/_hubs_table.html.haml index 76bd2a6695..15a6b57334 100644 --- a/app/views/shops/_hubs_table.html.haml +++ b/app/views/shops/_hubs_table.html.haml @@ -1,10 +1,11 @@ -.active_table - %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | closedShops:show_closed | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | orderBy:['-active', '+distance', '+orders_close_at'])", - "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", - "ng-controller" => "HubNodeCtrl", - id: "{{hub.hash}}"} - .small-12.columns - = render 'skinny' - = render 'fat' += cache_with_locale enterprises do + .active_table + %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | closedShops:show_closed | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | orderBy:['-active', '+distance', '+orders_close_at'])", + "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", + "ng-controller" => "HubNodeCtrl", + id: "{{hub.hash}}"} + .small-12.columns + = render 'skinny' + = render 'fat' - = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered" + = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered" diff --git a/app/views/shops/_skinny.html.haml b/app/views/shops/_skinny.html.haml index a5c6c48531..33a2106fd5 100644 --- a/app/views/shops/_skinny.html.haml +++ b/app/views/shops/_skinny.html.haml @@ -1,46 +1,47 @@ -.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} - .columns.small-12.medium-5.large-5.skinny-head - %a.hub{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"} - %i{ng: {class: "::hub.icon_font"}} - %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} += cache_with_locale do + .row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} + .columns.small-12.medium-5.large-5.skinny-head + %a.hub{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"} + %i{ng: {class: "::hub.icon_font"}} + %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} - .columns.small-4.medium-2.large-2 - %span.margin-top.ellipsed{"ng-bind" => "::hub.address.city"} - .columns.small-3.medium-2.large-2 - %span.margin-top.ellipsed{"ng-bind" => "::hub.address.state_name"} - %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km) + .columns.small-4.medium-2.large-2 + %span.margin-top.ellipsed{"ng-bind" => "::hub.address.city"} + .columns.small-3.medium-2.large-2 + %span.margin-top.ellipsed{"ng-bind" => "::hub.address.state_name"} + %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km) - .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::hub.active"} - %a.hub.open_closed.flex.flex-align-center{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} - %span{ ng: { if: "::current()" } } - %em= t :hubs_shopping_here + .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::hub.active"} + %a.hub.open_closed.flex.flex-align-center{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + %span{ ng: { if: "::current()" } } + %em= t :hubs_shopping_here + %span{ ng: { if: "::!current()" } } + %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"} + %i.ofn-i_068-shop-reversed.show-for-medium-up + %span{style: "margin-left: 0.5rem;"} + %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} + + .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::!hub.active"} + %a.hub.open_closed.flex{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + %span{ ng: { if: "::current()" } } + %em= t :hubs_shopping_here + %span{ ng: { if: "::!current()" } } + = t :hubs_orders_closed + %i.ofn-i_068-shop-reversed.show-for-medium-up + %span{style: "margin-left: 0.5rem;"} + %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} + + .row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"} + .columns.small-12.medium-6.large-5.skinny-head + %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"} + %i{ng: {class: "hub.icon_font"}} + %span.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} + + .columns.small-4.medium-2.large-2 + %span.ellipsed{"ng-bind" => "::hub.address.city"} + .columns.small-2.medium-1.large-1 + %span.ellipsed{"ng-bind" => "::hub.address.state_name"} + + .columns.small-6.medium-3.large-4.text-right.no-wrap.flex.flex-align-center.flex-justify-end %span{ ng: { if: "::!current()" } } - %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"} - %i.ofn-i_068-shop-reversed.show-for-medium-up - %span{style: "margin-left: 0.5rem;"} - %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} - - .columns.small-5.medium-3.large-3.text-right.no-wrap.flex.flex-align-center.flex-justify-end{"ng-if" => "::!hub.active"} - %a.hub.open_closed.flex{"ng-href" => "{{::hub.path}}", "ng-attr-target" => "{{ embedded_layout ? '_blank' : undefined}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} - %span{ ng: { if: "::current()" } } - %em= t :hubs_shopping_here - %span{ ng: { if: "::!current()" } } - = t :hubs_orders_closed - %i.ofn-i_068-shop-reversed.show-for-medium-up - %span{style: "margin-left: 0.5rem;"} - %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} - -.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"} - .columns.small-12.medium-6.large-5.skinny-head - %a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"} - %i{ng: {class: "hub.icon_font"}} - %span.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} - - .columns.small-4.medium-2.large-2 - %span.ellipsed{"ng-bind" => "::hub.address.city"} - .columns.small-2.medium-1.large-1 - %span.ellipsed{"ng-bind" => "::hub.address.state_name"} - - .columns.small-6.medium-3.large-4.text-right.no-wrap.flex.flex-align-center.flex-justify-end - %span{ ng: { if: "::!current()" } } - %em= t :hubs_profile_only + %em= t :hubs_profile_only diff --git a/config/application.rb b/config/application.rb index 101a35e2e2..13e94443fe 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,6 +23,7 @@ end require_relative "../lib/open_food_network/i18n_config" require_relative '../lib/spree/core/environment' require_relative '../lib/spree/core/mail_interceptor' +require_relative "../lib/i18n_digests" if defined?(Bundler) # If you precompile assets before deploying to production, use this line @@ -184,6 +185,9 @@ module Openfoodnetwork config.i18n.available_locales = OpenFoodNetwork::I18nConfig.available_locales I18n.locale = config.i18n.locale = config.i18n.default_locale + # Calculate digests for locale files so we can know when they change + I18nDigests.build_digests config.i18n.available_locales + # Setting this to true causes a performance regression in Rails 3.2.17 # When we're on a version with the fix below, we can set it to true # https://github.com/svenfuchs/i18n/issues/230 diff --git a/lib/i18n_digests.rb b/lib/i18n_digests.rb new file mode 100644 index 0000000000..23fe078a6a --- /dev/null +++ b/lib/i18n_digests.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class I18nDigests + class << self + def build_digests(available_locales) + available_locales.each do |locale| + i18n_digests[locale.to_sym] = locale_file_digest(locale) + end + end + + def for_locale(locale) + i18n_digests[locale.to_sym] + end + + private + + def i18n_digests + Rails.application.config.x.i18n_digests + end + + def locale_file_digest(locale) + Digest::MD5.hexdigest(Rails.root.join("config/locales/#{locale}.yml").read) + end + end +end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index a690869f93..80847b4f7f 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -42,4 +42,51 @@ describe ApplicationHelper, type: :helper do end end end + + describe "#cache_with_locale" do + let(:available_locales) { ["en", "es"] } + let(:current_locale) { "es" } + let(:locale_digest) { "8a7s5dfy28u0as9du" } + let(:options) { { expires_in: 10.seconds } } + + before do + allow(I18n).to receive(:available_locales) { available_locales } + allow(I18n).to receive(:locale) { current_locale } + allow(I18nDigests).to receive(:for_locale) { locale_digest } + end + + it "passes key, options, and block to #cache method with locale and locale digest appended" do + expect(helper).to receive(:cache_key_with_locale). + with("test-key", current_locale).and_return(["test-key", current_locale, locale_digest]) + + expect(helper).to receive(:cache). + with(["test-key", current_locale, locale_digest], options) do |&block| + expect(block.call).to eq("cached content") + end + + helper.cache_with_locale "test-key", options do + "cached content" + end + end + end + + describe "#cache_key_with_locale" do + let(:en_digest) { "asd689asy0239" } + let(:es_digest) { "9d8tu23oirhad" } + + before { allow(I18nDigests).to receive(:for_locale).with("en") { en_digest } } + before { allow(I18nDigests).to receive(:for_locale).with("es") { es_digest } } + + it "appends locale and digest to a single key" do + expect( + helper.cache_key_with_locale("single-key", "en") + ).to eq(["single-key", "en", en_digest]) + end + + it "appends locale and digest to multiple keys" do + expect( + helper.cache_key_with_locale(["array", "of", "keys"], "es") + ).to eq(["array", "of", "keys", "es", es_digest]) + end + end end diff --git a/spec/lib/i18n_digests_spec.rb b/spec/lib/i18n_digests_spec.rb new file mode 100644 index 0000000000..391cbb0050 --- /dev/null +++ b/spec/lib/i18n_digests_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe I18nDigests do + describe "#build_digests" do + let(:available_locales) { ["en", "es"] } + let(:md5_hex_regex) { /([a-f0-9]){10}/ } + + around do |example| + original = Rails.application.config.x.i18n_digests + example.run + Rails.application.config.x.i18n_digests = original + end + + it "computes and stores digests for each locale file" do + Rails.application.config.x.i18n_digests = {} + I18nDigests.build_digests(available_locales) + + expect(Rails.application.config.x.i18n_digests.keys).to eq [:en, :es] + expect(Rails.application.config.x.i18n_digests.values).to all match(md5_hex_regex) + + expect( + Rails.application.config.x.i18n_digests[:en] + ).to eq(Digest::MD5.hexdigest(Rails.root.join("config/locales/en.yml").read)) + + expect( + Rails.application.config.x.i18n_digests[:es] + ).to eq(Digest::MD5.hexdigest(Rails.root.join("config/locales/es.yml").read)) + end + end + + describe "#for_locale" do + let(:digests) { { en: "as8d7a9sdh", es: "iausyd9asdh" } } + + before do + allow(Rails).to receive_message_chain(:application, :config, :x, :i18n_digests) { digests } + end + + it "returns the digest for a given locale" do + expect(I18nDigests.for_locale("en")).to eq "as8d7a9sdh" + end + end +end