mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge tag 'v1.8.9' into transifex
This commit is contained in:
@@ -1 +1 @@
|
||||
angular.module("admin.products", ["admin.utils"])
|
||||
angular.module("admin.products", ["textAngular", "admin.utils"])
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("admin.utils").directive "textangularStrip", () ->
|
||||
restrict: 'CA'
|
||||
link: (scope, element, attrs) ->
|
||||
scope.stripFormatting = ($html) ->
|
||||
return String($html).replace(/<[^>]+>/gm, '')
|
||||
@@ -3,9 +3,9 @@
|
||||
%p.modal-header {{'contact' | t}}
|
||||
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
|
||||
|
||||
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
|
||||
%p{"ng-if" => "::enterprise.email_address"}
|
||||
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
|
||||
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
|
||||
|
||||
%p.word-wrap{"ng-if" => "enterprise.website"}
|
||||
%p{"ng-if" => "enterprise.website"}
|
||||
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
.filter-shopfront.property-selectors.inline-block
|
||||
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
|
||||
|
||||
%div{"ng-if" => "product.description"}
|
||||
%div{"ng-if" => "product.description_html"}
|
||||
%hr
|
||||
%p.text-small{"ng-bind" => "::product.description"}
|
||||
%p.text-small{"ng-bind-html" => "::product.description_html"}
|
||||
%hr
|
||||
|
||||
.columns.small-12.large-6
|
||||
|
||||
@@ -200,6 +200,13 @@ table#listing_enterprise_groups {
|
||||
|
||||
// textAngular wysiwyg
|
||||
text-angular {
|
||||
.ta-toolbar {
|
||||
border: 1px solid #cdd9e4;
|
||||
padding: 0.4em;
|
||||
margin-bottom: -1px;
|
||||
background-color: #f1f1f1;
|
||||
border-radius: 0.25em 0.25em 0 0;
|
||||
}
|
||||
.ta-scroll-window > .ta-bind {
|
||||
max-height: 400px;
|
||||
min-height: 100px;
|
||||
@@ -210,12 +217,18 @@ text-angular {
|
||||
}
|
||||
.ta-scroll-window.form-control {
|
||||
min-height: 100px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.btn-group {
|
||||
display: inline;
|
||||
margin-right: 8px;
|
||||
button {
|
||||
padding: 5px 10px;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
button.active:not(:hover) {
|
||||
box-shadow: 0 0 0.7em rgba(0,0,0,0.3) inset;
|
||||
background-color: #4583bf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ class AbilityDecorator
|
||||
def add_order_management_abilities(user)
|
||||
# Enterprise User can only access orders that they are a distributor for
|
||||
can [:index, :create], Spree::Order
|
||||
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
|
||||
can [:read, :update, :fire, :resend, :invoice, :print, :print_ticket], Spree::Order do |order|
|
||||
# We allow editing orders with a nil distributor as this state occurs
|
||||
# during the order creation process from the admin backend
|
||||
order.distributor.nil? || user.enterprises.include?(order.distributor) || order.order_cycle.andand.coordinated_by?(user)
|
||||
|
||||
@@ -6,7 +6,6 @@ Spree::AppConfiguration.class_eval do
|
||||
|
||||
# Terms of Service Preferences
|
||||
preference :enterprises_require_tos, :boolean, default: false
|
||||
preference :enterprise_tos_link, :string, default: "/Terms-of-service.pdf"
|
||||
|
||||
# Tax Preferences
|
||||
preference :products_require_tax_category, :boolean, default: false
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
%fieldset.enterprise_toc.no-border-bottom
|
||||
%legend{:align => "center"}= t(:enterprise_terms_of_service)
|
||||
- [:enterprise_tos_link, :enterprises_require_tos].each do |pref|
|
||||
- type = Spree::Config.preference_type(pref)
|
||||
.field
|
||||
= label_tag(pref, t(pref) + ': ') + tag(:br) if type != :boolean
|
||||
= preference_field_tag(pref, Spree::Config[pref], :type => type)
|
||||
= label_tag(pref, t(pref)) + tag(:br) if type == :boolean
|
||||
|
||||
.field
|
||||
= preference_field_tag(:enterprises_require_tos, Spree::Config[:enterprises_require_tos], :type => Spree::Config.preference_type(:enterprises_require_tos))
|
||||
= label_tag(:enterprises_require_tos, t(:enterprises_require_tos)) + tag(:br)
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
%table.index
|
||||
%thead
|
||||
%tr{"data-hook" => "producer_properties_header"}
|
||||
%th= t(:inherited_property)
|
||||
%th= t(:value)
|
||||
%th= t('admin.products.properties.inherited_property')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
%tbody#producer_properties{"data-hook" => ""}
|
||||
- @product.supplier.producer_properties.each do |producer_property|
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/ replace "tr[data-hook='product_properties_header']"
|
||||
%tr{"data-hook" => "product_properties_header"}
|
||||
%th= t('admin.products.properties.property_name')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
@@ -0,0 +1,3 @@
|
||||
/ replace "[data-hook=admin_product_form_left] code[erb-loud]:contains('f.text_area :description')"
|
||||
%text-angular{'id' => 'product_description', 'name' => 'product[description]', 'class' => 'text-angular', 'textangular-strip' => true, 'ta-paste' => "stripFormatting($html)", 'ta-toolbar' => "[['bold','italics','clear']]"}
|
||||
= sanitize(@product.description)
|
||||
@@ -0,0 +1,2 @@
|
||||
add_to_attributes 'fieldset.no-border-top'
|
||||
attributes 'ng-app' => 'admin.products'
|
||||
@@ -72,7 +72,7 @@
|
||||
= f.field_container :description do
|
||||
= f.label :product_description, t(:product_description)
|
||||
%br/
|
||||
= f.text_area :description, class: 'fullwidth', rows: 3
|
||||
%text-angular{'id' => 'product_description', 'name' => 'product[description]', 'class' => 'text-angular', 'textangular-strip' => true, 'ta-paste' => "stripFormatting($html)", 'ta-toolbar' => "[['bold','italics','clear']]"}
|
||||
= f.error_message_on :description
|
||||
.four.columns.omega{ style: "text-align: center" }
|
||||
%fieldset.no-border-bottom{ id: "image" }
|
||||
|
||||
@@ -37,7 +37,7 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
attributes :id, :name, :permalink
|
||||
attributes :on_demand, :group_buy, :notes, :description
|
||||
attributes :on_demand, :group_buy, :notes, :description, :description_html
|
||||
attributes :properties_with_values
|
||||
|
||||
has_many :variants, serializer: Api::VariantSerializer
|
||||
@@ -49,10 +49,17 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
|
||||
has_many :images, serializer: Api::ImageSerializer
|
||||
has_one :supplier, serializer: Api::IdSerializer
|
||||
|
||||
#return an unformatted descripton
|
||||
def description
|
||||
strip_tags object.description
|
||||
end
|
||||
|
||||
#return a sanitized html description
|
||||
def description_html
|
||||
d = sanitize(object.description, options = {tags: "p, b, strong, em, i"})
|
||||
d.to_s.html_safe
|
||||
end
|
||||
|
||||
def properties_with_values
|
||||
object.properties_including_inherited
|
||||
end
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
%table.index.sortable{"data-hook" => "", "data-sortable-link" => main_app.update_positions_admin_enterprise_producer_properties_url(@enterprise)}
|
||||
%thead
|
||||
%tr{"data-hook" => "producer_properties_header"}
|
||||
%th{colspan: "2"}= t('.property')
|
||||
%th= t('.value')
|
||||
%th{colspan: "2"}= t('admin.products.properties.property_name')
|
||||
%th= t('admin.description')
|
||||
%th.actions
|
||||
%tbody#producer_properties{"data-hook" => ""}
|
||||
= f.fields_for :producer_properties do |pp_form|
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
= inject_enterprise_attributes
|
||||
|
||||
- steps = %w{about contact details finished images introduction}
|
||||
- steps += %w{limit_reached logo promo social steps type}
|
||||
- steps += %w{logo promo social steps type}
|
||||
- steps.each do |step|
|
||||
= render partial: "registration/steps/#{step}"
|
||||
= render "modal"
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
= render partial: "registration/steps/limit_reached"
|
||||
|
||||
/ Directive which loads the modal
|
||||
%div{ "ofn-registration-limit-modal" => true }
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
.small-12.columns{'ng-hide' => '!tos_required' }
|
||||
%p.tos-message
|
||||
#{t(:enterprise_tos_message)}
|
||||
%a{href: "#{Spree::Config.enterprise_tos_link}", target: "_blank" } #{t(:enterprise_tos_link_text)}
|
||||
%a{href: ContentConfig.footer_tos_url, target: "_blank" } #{t(:enterprise_tos_link_text)}
|
||||
%p.tos-checkbox
|
||||
%input{ type: 'checkbox', name: 'accept_terms', id: 'accept_terms', ng: { model: "tos_accepted" } }
|
||||
%label{for: "accept_terms"} #{t(:enterprise_tos_agree)}
|
||||
|
||||
@@ -105,6 +105,7 @@ module Openfoodnetwork
|
||||
config.assets.precompile += ['mail/all.css']
|
||||
config.assets.precompile += ['search/all.css', 'search/*.js']
|
||||
config.assets.precompile += ['shared/*']
|
||||
config.assets.precompile += ['qz/*']
|
||||
|
||||
config.active_support.escape_html_entities_in_json = true
|
||||
end
|
||||
|
||||
@@ -109,6 +109,7 @@ en:
|
||||
columns: Columns
|
||||
actions: Actions
|
||||
viewing: "Viewing: %{current_view_name}"
|
||||
description: Description
|
||||
|
||||
whats_this: What's this?
|
||||
|
||||
@@ -223,6 +224,9 @@ en:
|
||||
inherits_properties?: Inherits Properties?
|
||||
available_on: Available On
|
||||
av_on: "Av. On"
|
||||
properties:
|
||||
property_name: Property Name
|
||||
inherited_property: Inherited Property
|
||||
variants:
|
||||
to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order."
|
||||
|
||||
@@ -503,9 +507,6 @@ en:
|
||||
advanced_settings: Advanced Settings
|
||||
update_and_close: Update and Close
|
||||
producer_properties:
|
||||
form:
|
||||
property: Property
|
||||
value: Value
|
||||
index:
|
||||
title: Producer Properties
|
||||
shared:
|
||||
|
||||
@@ -35,7 +35,7 @@ feature %q{
|
||||
fill_in 'product_on_hand', with: 5
|
||||
select 'Test Tax Category', from: 'product_tax_category_id'
|
||||
select 'Test Shipping Category', from: 'product_shipping_category_id'
|
||||
fill_in 'product_description', with: "A description..."
|
||||
page.find("input[name='product\[description\]']", visible: false).set('A description...')
|
||||
|
||||
click_button 'Create'
|
||||
|
||||
@@ -75,7 +75,8 @@ feature %q{
|
||||
check 'product_on_demand'
|
||||
select 'Test Tax Category', from: 'product_tax_category_id'
|
||||
select 'Test Shipping Category', from: 'product_shipping_category_id'
|
||||
fill_in 'product_description', with: "In demand, and on_demand! The hottest cakes in town."
|
||||
#fill_in 'product_description', with: "In demand, and on_demand! The hottest cakes in town."
|
||||
page.first("input[name='product\[description\]']", visible: false).set('In demand, and on_demand! The hottest cakes in town.')
|
||||
|
||||
click_button 'Create'
|
||||
|
||||
|
||||
@@ -98,6 +98,23 @@ feature "Registration", js: true do
|
||||
expect(e.instagram).to eq "@InStAgRaM"
|
||||
end
|
||||
|
||||
context "when the user has no more remaining enterprises" do
|
||||
before do
|
||||
user.update_attributes(enterprise_limit: 0)
|
||||
end
|
||||
|
||||
it "displays the limit reached page" do
|
||||
visit registration_path
|
||||
|
||||
expect(page).to have_selector "dd", text: "Login"
|
||||
switch_to_login_tab
|
||||
|
||||
# Enter Login details
|
||||
fill_in "Email", with: user.email
|
||||
fill_in "Password", with: user.password
|
||||
click_login_and_ensure_content I18n.t('limit_reached_headline')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Terms of Service agreement" do
|
||||
|
||||
60
spec/features/consumer/shopping/products_spec.rb
Normal file
60
spec/features/consumer/shopping/products_spec.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
require 'spec_helper'
|
||||
|
||||
feature "As a consumer I want to view products", js: true do
|
||||
include AuthenticationWorkflow
|
||||
include WebHelper
|
||||
include ShopWorkflow
|
||||
include UIComponentHelper
|
||||
|
||||
describe "Viewing a product" do
|
||||
let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
|
||||
let(:supplier) { create(:supplier_enterprise) }
|
||||
let(:oc1) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), orders_close_at: 2.days.from_now) }
|
||||
let(:product) { create(:simple_product, supplier: supplier) }
|
||||
let(:variant) { product.variants.first }
|
||||
let(:order) { create(:order, distributor: distributor) }
|
||||
let(:exchange1) { oc1.exchanges.to_enterprises(distributor).outgoing.first }
|
||||
|
||||
before do
|
||||
set_order order
|
||||
end
|
||||
|
||||
describe "viewing HTML product descriptions" do
|
||||
before do
|
||||
exchange1.update_attribute :pickup_time, "monday"
|
||||
add_variant_to_order_cycle(exchange1, variant)
|
||||
end
|
||||
|
||||
it "shows HTML product description" do
|
||||
product.description = "<p><b>Formatted</b> product description.</p>"
|
||||
product.save!
|
||||
|
||||
visit shop_path
|
||||
select "monday", :from => "order_cycle_id"
|
||||
|
||||
open_product_modal product
|
||||
modal_should_be_open_for product
|
||||
|
||||
within(".reveal-modal") do
|
||||
html.should include("<p><b>Formatted</b> product description.</p>")
|
||||
end
|
||||
end
|
||||
|
||||
it "does not show unsecure HTML" do
|
||||
product.description = "<script>alert('Dangerous!');</script><p>Safe</p>"
|
||||
product.save!
|
||||
|
||||
visit shop_path
|
||||
select "monday", :from => "order_cycle_id"
|
||||
|
||||
open_product_modal product
|
||||
modal_should_be_open_for product
|
||||
|
||||
within(".reveal-modal") do
|
||||
html.should include("<p>Safe</p>")
|
||||
html.should_not include("<script>alert('Dangerous!');</script>")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user