From 4414a3f28755ca8cff89c44f13eb8212218cfdca Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sat, 9 Apr 2016 10:05:45 +0100 Subject: [PATCH 001/154] Fix spelling mistake --- app/models/spree/user_decorator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 17473fd521..10a1c28972 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -55,7 +55,7 @@ Spree.user_class.class_eval do end # Returns orders and their associated payments for all distributors that have been ordered from - def compelete_orders_by_distributor + def complete_orders_by_distributor Enterprise .includes(distributed_orders: { payments: :payment_method }) .where(enterprises: { id: enterprises_ordered_from }, @@ -65,7 +65,7 @@ Spree.user_class.class_eval do def orders_by_distributor # Remove uncompleted payments as these will not be reflected in order balance - data_array = compelete_orders_by_distributor.to_a + data_array = complete_orders_by_distributor.to_a remove_uncompleted_payments(data_array) data_array.sort! { |a, b| b.distributed_orders.length <=> a.distributed_orders.length } end From bc048a943c776f910ff6cabe44df773d65b59e53 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 10 Apr 2016 22:23:39 +0100 Subject: [PATCH 002/154] Show all payments, format unsuccessful payments grey, add 'invalid' translation. --- .../stylesheets/darkswarm/account.css.sass | 7 +++++ app/models/spree/user_decorator.rb | 6 ++-- app/serializers/api/payment_serializer.rb | 2 +- app/views/spree/users/_fat.html.haml | 6 ++-- config/locales/en-GB.yml | 31 +++++++++++++++++++ config/locales/en.yml | 1 + 6 files changed, 46 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/account.css.sass b/app/assets/stylesheets/darkswarm/account.css.sass index f582545b74..0f3de5af57 100644 --- a/app/assets/stylesheets/darkswarm/account.css.sass +++ b/app/assets/stylesheets/darkswarm/account.css.sass @@ -32,6 +32,13 @@ .debit color: $clr-brick + .invalid + color: $ofn-grey + .credit + color: $ofn-grey + .debit + color: $ofn-grey + .distributor-balance.paid visibility: hidden diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 10a1c28972..274330d1da 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -66,7 +66,7 @@ Spree.user_class.class_eval do def orders_by_distributor # Remove uncompleted payments as these will not be reflected in order balance data_array = complete_orders_by_distributor.to_a - remove_uncompleted_payments(data_array) + remove_payments_in_checkout(data_array) data_array.sort! { |a, b| b.distributed_orders.length <=> a.distributed_orders.length } end @@ -78,10 +78,10 @@ Spree.user_class.class_eval do end end - def remove_uncompleted_payments(enterprises) + def remove_payments_in_checkout(enterprises) enterprises.each do |enterprise| enterprise.distributed_orders.each do |order| - order.payments.keep_if { |payment| payment.state == "completed" } + order.payments.keep_if { |payment| payment.state != "checkout" } end end end diff --git a/app/serializers/api/payment_serializer.rb b/app/serializers/api/payment_serializer.rb index d48f75a07f..8465998db6 100644 --- a/app/serializers/api/payment_serializer.rb +++ b/app/serializers/api/payment_serializer.rb @@ -1,6 +1,6 @@ module Api class PaymentSerializer < ActiveModel::Serializer - attributes :amount, :updated_at, :payment_method + attributes :amount, :updated_at, :payment_method, :state def payment_method object.payment_method.name end diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml index c10cf8254a..9f9ad67b10 100644 --- a/app/views/spree/users/_fat.html.haml +++ b/app/views/spree/users/_fat.html.haml @@ -19,10 +19,10 @@ %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","bo-text" => "order.total | localizeCurrency"} %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "bo-text" => "order.outstanding_balance | localizeCurrency"} %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "bo-text" => "order.running_balance | localizeCurrency"} - %tr.payment-row{"ng-repeat" => "payment in order.payments"} - %td.order1= t :payment + %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} + %td.order1{"bo-text" => "payment.payment_method"} %td.order2{"bo-text" => "payment.updated_at"} - %td.order3.show-for-large-up{"bo-text" => "payment.payment_method"} + %td.order3.show-for-large-up{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} %td.order4.show-for-large-up %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"} %td.order6.show-for-large-up diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 7301ef4f2c..3e793f88a4 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -635,3 +635,34 @@ Please follow the instructions there to make your enterprise visible on the Open price_graph: "Price graph" included_tax: "Included tax" remove_tax: "Remove tax" + spree: + shipment_states: + backorder: backorder + partial: partial + pending: pending + ready: ready + shipped: shipped + payment_states: + balance_due: balance due + completed: completed + checkout: checkout + credit_owed: credit owed + failed: failed + paid: paid + pending: pending + processing: processing + void: void + invalid: invalid + order_state: + address: address + adjustments: adjustments + awaiting_return: awaiting return + canceled: canceled + cart: cart + complete: complete + confirm: confirm + delivery: delivery + payment: payment + resumed: resumed + returned: returned + skrill: skrill diff --git a/config/locales/en.yml b/config/locales/en.yml index 5bf35d1aa5..020011ea8d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -996,6 +996,7 @@ Please follow the instructions there to make your enterprise visible on the Open pending: pending processing: processing void: void + invalid: invalid order_state: address: address adjustments: adjustments From 14830237692bb005c409a83fde4df9caace58a0d Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 10 Apr 2016 22:48:38 +0100 Subject: [PATCH 003/154] Add a sweet warning sign --- app/views/spree/users/_fat.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml index 9f9ad67b10..df5bd71c5d 100644 --- a/app/views/spree/users/_fat.html.haml +++ b/app/views/spree/users/_fat.html.haml @@ -22,7 +22,9 @@ %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} %td.order1{"bo-text" => "payment.payment_method"} %td.order2{"bo-text" => "payment.updated_at"} - %td.order3.show-for-large-up{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} + %td.order3.show-for-large-up + %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void'}"} + %span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} %td.order4.show-for-large-up %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"} %td.order6.show-for-large-up From fc719230a3ea29a4b9fd16c5bb4a075c323aa0e0 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Fri, 15 Apr 2016 08:26:12 +0100 Subject: [PATCH 004/154] Add failed payments, update spec --- app/views/spree/users/_fat.html.haml | 2 +- spec/models/spree/user_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml index df5bd71c5d..5c87077b4c 100644 --- a/app/views/spree/users/_fat.html.haml +++ b/app/views/spree/users/_fat.html.haml @@ -23,7 +23,7 @@ %td.order1{"bo-text" => "payment.payment_method"} %td.order2{"bo-text" => "payment.updated_at"} %td.order3.show-for-large-up - %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void'}"} + %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"} %span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} %td.order4.show-for-large-up %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"} diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index ee01326316..9911c16a51 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -92,7 +92,7 @@ describe Spree.user_class do let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id)} let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed')} - let!(:payment) { create(:payment, order: d1o2, state: 'invalid')} + let!(:payment) { create(:payment, order: d1o2, state: 'checkout')} it "returns enterprises that the user has ordered from" do expect(u1.enterprises_ordered_from).to eq [distributor1.id] @@ -114,7 +114,7 @@ describe Spree.user_class do expect(u1.orders_by_distributor.first.distributed_orders).not_to include d1o3 end - it "doesn't return uncompleted payments" do + it "doesn't return payments that are still at checkout stage" do expect(u1.orders_by_distributor.first.distributed_orders.map{|o| o.payments}.flatten).not_to include payment end end From f986c5898e5d0603a5166dd8dc21b30f13681226 Mon Sep 17 00:00:00 2001 From: Lynne Date: Sat, 16 Apr 2016 03:23:26 +0100 Subject: [PATCH 005/154] Updating en-GB file to reflect recent additions (#909) --- config/locales/en-GB.yml | 458 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 427 insertions(+), 31 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 931df49417..06039d9d66 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -21,18 +21,129 @@ en-GB: invalid: | Invalid email or password. Were you a guest last time? Perhaps you need to create an account or reset your password. + enterprise_confirmations: + enterprise: + confirmed: Thankyou, your email address has been confirmed. + not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? + confirmation_sent: "Confirmation email sent!" + confirmation_not_sent: "Could not send a confirmation email." home: "OFN" title: Open Food Network welcome_to: 'Welcome to ' + site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…" search_by_name: Search by name... producers: UK Producers producers_join: UK producers are now welcome to join Open Food Network UK. - charges_sales_tax: Charges sales tax? - print: "Print" + charges_sales_tax: Charges VAT? + print_invoice: "Print Invoice" + send_invoice: "Send Invoice" + resend_confirmation: "Resend Confirmation" + view_order: "View Order" + edit_order: "Edit Order" + ship_order: "Ship Order" + cancel_order: "Cancel Order" + confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?" + confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?" + invoice: "Invoice" + percentage_of_sales: "%{percentage} of sales" + percentage_of_turnover: "Percentage of turnover" + monthly_cap_excl_tax: "monthly cap (excl. VAT)" + capped_at_cap: "capped at %{cap}" + per_month: "per month" + free: "free" + plus_tax: "plus GST" + total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)" + say_no: "No" + say_yes: "Yes" - logo: "Logo (640x130)" - logo_mobile: "Mobile logo (75x26)" - logo_mobile_svg: "Mobile logo (SVG)" + sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" + + + admin: + # General form elements + quick_search: Quick Search + clear_all: Clear All + producer: Producer + shop: Shop + product: Product + variant: Variant + + columns: Columns + actions: Actions + viewing: "Viewing: %{current_view_name}" + + whats_this: What's this? + + customers: + index: + add_customer: "Add customer" + customer_placeholder: "customer@example.org" + inventory: + title: Inventory + description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page + sku: SKU + price: Price + on_hand: On Hand + on_demand: On Demand? + enable_reset: Enable Stock Level Reset? + inherit: Inherit? + add: Add + hide: Hide + select_a_shop: Select A Shop + review_now: Review Now + new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. + currently_empty: Your inventory is currently empty + no_matching_products: No matching products found in your inventory + no_hidden_products: No products have been hidden from this inventory + no_matching_hidden_products: No hidden products match your search criteria + no_new_products: No new products are available to add to this inventory + no_matching_new_products: No new products match your search criteria + inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. + hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. + new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + + + order_cycle: + choose_products_from: "Choose Products From:" + + enterprise: + select_outgoing_oc_products_from: Select outgoing OC products from + + enterprises: + form: + primary_details: + shopfront_requires_login: "Shopfront requires login?" + shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront." + shopfront_requires_login_false: "Public" + shopfront_requires_login_true: "Require customers to login" + + home: + hubs: + show_closed_shops: "Show closed shops" + hide_closed_shops: "Hide closed shops" + show_on_map: "Show all on the map" + shared: + register_call: + selling_on_ofn: "Interested in getting on the Open Food Network?" + register: "Register here" + shop: + messages: + login: "login" + register: "register" + contact: "contact" + require_customer_login: "This shop is for customers only." + require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer." + require_customer_html: "Please %{contact} %{enterprise} to become a customer." + + # Printable Invoice Columns + invoice_column_item: "Item" + invoice_column_qty: "Qty" + invoice_column_tax: "VAT" + invoice_column_price: "Price" + + logo: "Logo (640x130)" #FIXME + logo_mobile: "Mobile logo (75x26)" #FIXME + logo_mobile_svg: "Mobile logo (SVG)" #FIXME home_hero: "Hero image" home_show_stats: "Show statistics" footer_logo: "Logo (220x76)" @@ -46,11 +157,10 @@ en-GB: footer_links_md: "Links" footer_about_url: "About URL" footer_tos_url: "Terms of Service URL" - invoice: "Invoice" name: Name - first_name: First name - last_name: Last name + first_name: First Name + last_name: Last Name email: Email phone: Phone next: Next @@ -90,9 +200,9 @@ en-GB: cart_empty: "Cart empty" cart_edit: "Edit your cart" - card_number: Card number - card_securitycode: "Security code" - card_expiry_date: Expiry date + card_number: Card Number + card_securitycode: "Security Code" + card_expiry_date: Expiry Date ofn_cart_headline: "Current cart for:" ofn_cart_distributor: "Distributor:" @@ -187,7 +297,7 @@ en-GB: checkout_cart_total: Cart total checkout_shipping_price: Shipping checkout_total_price: Total - checkout_back_to_cart: "Back to cart" + checkout_back_to_cart: "Back to Cart" order_paid: PAID order_not_paid: NOT PAID @@ -197,12 +307,32 @@ en-GB: order_delivery_on: Delivery on order_delivery_address: Delivery address order_special_instructions: "Your notes:" - order_pickup_instructions: Collection instructions + order_pickup_time: Ready for collection + order_pickup_instructions: Collection Instructions order_produce: Produce order_total_price: Total order_includes_tax: (includes tax) order_payment_paypal_successful: Your payment via PayPal has been processed successfully. - order_hub_info: Hub info + order_hub_info: Hub Info + + bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + bom_shared: "Shared Resource?" + bom_page_title: "Bulk Order Management" + bom_no: "Order no." + bom_date: "Order date" + bom_cycle: "Order cycle" + bom_max: "Max" + bom_hub: "Hub" + bom_variant: "Product: Unit" + bom_final_weigth_volume: "Weight/Volume" + bom_quantity: "Quantity" + bom_actions_delete: "Delete Selected" + bom_loading: "Loading orders" + bom_no_results: "No orders found." + bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." + + unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." + unsaved_changes_error: "Fields with red borders contain errors." products: "Products" products_in: "in %{oc}" @@ -307,6 +437,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_cart_empty: "Cart empty" products_edit_cart: "Edit your cart" products_from: from + products_change: "No changes to save." + products_update_error: "Saving failed with the following error(s):" + products_update_error_msg: "Saving failed." + products_update_error_data: "Save failed due to invalid data:" + products_changes_saved: "Changes saved." search_no_results_html: "Sorry, no results found for %{query}. Try another search?" @@ -317,9 +452,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using groups_title: Groups groups_headline: Groups / regions + groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you." groups_search: "Search name or keyword" groups_no_groups: "No groups found" groups_about: "About Us" + groups_producers: "Our producers" groups_hubs: "Our hubs" groups_contact_web: Contact @@ -384,9 +521,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using ocs_close_time: "ORDERS CLOSE" ocs_when_headline: When do you want your order? ocs_when_text: No products are displayed until you select a date. - ocs_when_closing: "Closing on" + ocs_when_closing: "Closing On" ocs_when_choose: "Choose Order Cycle" - ocs_list: "List view" + ocs_list: "List View" producers_about: About us producers_buy: Shop for @@ -407,13 +544,26 @@ See the %{link} to find out more about %{sitename}'s features and to start using producers_signup_cta_headline: Join now! producers_signup_cta_action: Join now producers_signup_detail: Here's the detail. + producer: Producer products_item: Item products_description: Description products_variant: Variant products_quantity: Quantity products_availabel: Available? - products_price: Price + products_producer: "Producer" + products_price: "Price" + products_sku: "SKU" + products_name: "name" + products_unit: "unit" + products_on_hand: "on hand" + products_on_demand: "On demand?" + products_category: "Category" + products_tax_category: "tax category" + products_available_on: "Available On" + products_inherit: "Inherit?" + products_inherits_properties: "Inherits Properties?" + products_stock_level_reset: "Enable Stock Level Reset?" register_title: Register @@ -431,7 +581,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using shops_signup_detail: Here's the detail. orders_fees: Fees... - orders_edit_title: Shopping cart + orders_edit_title: Shopping Cart orders_edit_headline: Your shopping cart orders_edit_time: Order ready for orders_edit_continue: Continue shopping @@ -509,18 +659,17 @@ See the %{link} to find out more about %{sitename}'s features and to start using confirm_password: "Confirm password" action_signup: "Sign up now" welcome_to_ofn: "Welcome to the Open Food Network!" - signup_or_login: "Start By signing up (or logging in)" + signup_or_login: "Start By Signing Up (or logging in)" have_an_account: "Already have an account?" action_login: "Log in now." - forgot_password: "Forgot password?" + forgot_password: "Forgot Password?" password_reset_sent: "An email with instructions on resetting your password has been sent!" reset_password: "Reset password" registration_greeting: "Greetings!" who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" - enterprise_contact: "Primary contact" + enterprise_contact: "Primary Contact" enterprise_contact_required: "You need to enter a primary contact." - enterprise_email: "Email address" - enterprise_email_required: "You need to enter valid email address." + enterprise_email_address: "Email address" enterprise_phone: "Phone number" back: "Back" continue: "Continue" @@ -528,20 +677,20 @@ See the %{link} to find out more about %{sitename}'s features and to start using limit_reached_message: "You have reached the limit!" limit_reached_text: "You have reached the limit for the number of enterprises you are allowed to own on the" limit_reached_action: "Return to the homepage" - select_promo_image: "Step 3. Select promo image" + select_promo_image: "Step 3. Select Promo Image" promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px" promo_image_label: "Choose a promo image" action_or: "OR" promo_image_drag: "Drag and drop your promo here" - review_promo_image: "Step 4. Review your promo banner" + review_promo_image: "Step 4. Review Your Promo Banner" review_promo_image_tip: "Tip: for best results, your promo image should fill the available space" promo_image_placeholder: "Your logo will appear here for review once uploaded" uploading: "Uploading..." - select_logo: "Step 1. Select logo image" + select_logo: "Step 1. Select Logo Image" logo_tip: "Tip: Square images will work best, preferably at least 300×300px" logo_label: "Choose a logo image" logo_drag: "Drag and drop your logo here" - review_logo: "Step 2. Review your logo" + review_logo: "Step 2. Review Your Logo" review_logo_tip: "Tip: for best results, your logo should fill the available space" logo_placeholder: "Your logo will appear here for review once uploaded" enterprise_about_headline: "Nice one!" @@ -598,14 +747,14 @@ Please follow the instructions there to make your enterprise visible on the Open registration_type_error: "Please choose one. Are you are producer?" registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - create_profile: "Create profile" + create_profile: "Create Profile" registration_images_headline: "Thanks!" registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" - registration_detail_headline: "Let's get started" + registration_detail_headline: "Let's Get Started" registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:" registration_detail_producer: "Woot! First we need to know a little bit about your farm:" - registration_detail_name_enterprise: "Enterprise name:" - registration_detail_name_producer: "Farm name:" + registration_detail_name_enterprise: "Enterprise Name:" + registration_detail_name_producer: "Farm Name:" registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm" registration_detail_name_error: "Please choose a unique name for your enterprise" registration_detail_address1: "Address line 1:" @@ -641,3 +790,250 @@ Please follow the instructions there to make your enterprise visible on the Open price_graph: "Price graph" included_tax: "Included tax" remove_tax: "Remove tax" + balance: "Balance" + transaction: "Transaction" + transaction_date: "Date" #Transaction is only in key to avoid conflict with :date + payment_state: "Payment status" + shipping_state: "Shipping status" + value: "Value" + balance_due: "Balance due" + credit: "Credit" + Paid: "Paid" + Ready: "Ready" + you_have_no_orders_yet: "You have no orders yet" + running_balance: "Running balance" + outstanding_balance: "Outstanding balance" + admin_entreprise_relationships: "Enterprise Relationships" + admin_entreprise_relationships_everything: "Everything" + admin_entreprise_relationships_permits: "permits" + admin_entreprise_relationships_seach_placeholder: "Search" + admin_entreprise_relationships_button_create: "Create" + admin_entreprise_groups: "Enterprise Groups" + admin_entreprise_groups_name: "Name" + admin_entreprise_groups_owner: "Owner" + admin_entreprise_groups_on_front_page: "On front page ?" + admin_entreprise_groups_entreprise: "Enterprises" + admin_entreprise_groups_primary_details: "Primary Details" + admin_entreprise_groups_data_powertip: "The primary user responsible for this group." + admin_entreprise_groups_data_powertip_logo: "This is the logo for the group" + admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile" + admin_entreprise_groups_about: "About" + admin_entreprise_groups_images: "Images" + admin_entreprise_groups_contact: "Contact" + admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210" + admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street" + admin_entreprise_groups_contact_city: "Suburb" + admin_entreprise_groups_contact_city_placeholder: "eg. Northcote" + admin_entreprise_groups_contact_zipcode: "Postcode" + admin_entreprise_groups_contact_zipcode_placeholder: "eg. 3070" + admin_entreprise_groups_contact_state_id: "State" + admin_entreprise_groups_contact_country_id: "Country" + admin_entreprise_groups_web: "Web Resources" + admin_entreprise_groups_web_twitter: "eg. @the_prof" + admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com" + admin_order_cycles: "Admin Order Cycles" + open: "Open" + close: "Close" + supplier: "Supplier" + coordinator: "Coordinator" + distributor: "Distributor" + product: "Products" + enterprise_fees: "Enterprise Fees" + fee_type: "Fee Type" + tax_category: "Tax Category" + calculator: "Calculator" + calculator_values: "Calculator values" + new_order_cycles: "New Order Cycles" + select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle" + edit_order_cycle: "Edit Order Cycle" + roles: "Roles" + update: "Update" + add_producer_property: "Add producer property" + admin_settings: "Settings" + update_invoice: "Update Invoices" + finalise_invoice: "Finalise Invoices" + finalise_user_invoices: "Finalise User Invoices" + finalise_user_invoice_explained: "Use this button to finalize all invoices in the system for the previous calendar month. This task can be set up to run automatically once a month." + manually_run_task: "Manually Run Task " + update_user_invoices: "Update User Invoices" + update_user_invoice_explained: "Use this button to immediately update invoices for the month to date for each enterprise user in the system. This task can be set up to run automatically every night." + auto_finalise_invoices: "Auto-finalise invoices monthly on the 2nd at 1:30am" + auto_update_invoices: "Auto-update invoices nightly at 1:00am" + in_progress: "In Progress" + started_at: "Started at" + queued: "Queued" + scheduled_for: "Scheduled for" + customers: "Customers" + please_select_hub: "Please select a Hub" + loading_customers: "Loading Customers" + no_customers_found: "No customers found" + go: "Go" + hub: "Hub" + accounts_administration_distributor: "accounts administration distributor" + accounts_and_billing: "Accounts & Billing" + producer: "Producer" + product: "Product" + price: "Price" + on_hand: "On hand" + save_changes: "Save Changes" + spree_admin_overview_enterprises_header: "My Enterprises" + spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES" + spree_admin_enterprises_hubs_name: "Name" + spree_admin_enterprises_create_new: "CREATE NEW" + spree_admin_enterprises_shipping_methods: "Shipping Methods" + spree_admin_enterprises_fees: "Enterprise Fees" + spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" + spree_admin_enterprises_none_text: "You don't have any enterprises yet" + spree_admin_enterprises_producers_name: "Name" + spree_admin_enterprises_producers_total_products: "Total Products" + spree_admin_enterprises_producers_active_products: "Active Products" + spree_admin_enterprises_producers_order_cycles: "Products in OCs" + spree_admin_enterprises_producers_order_cycles_title: "" + spree_admin_enterprises_tabs_hubs: "HUBS" + spree_admin_enterprises_tabs_producers: "PRODUCERS" + spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES" + spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" + spree_admin_enterprises_producers_orders_cycle_text: "You don't have any active order cycles." + spree_admin_enterprises_any_active_products_text: "You don't have any active products." + spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" + spree_admin_order_cycles: "Order Cycles" + spree_admin_order_cycles_tip: "Order cycles determine when and where your products are available to customers." + dashbord: "Dashboard" + spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" + spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" + spree_admin_overview_action_required: "Action Required" + spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!" + change_package: "Change Package" + spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" + your_profil_live: "Your profile live" + on_ofn_map: "on the Open Food Network map" + see: "See" + live: "live" + manage: "Manage" + resend: "Resend" + add_and_manage_products: "Add & manage products" + add_and_manage_order_cycles: "Add & manage order cycles" + manage_order_cycles: "Manage order cycles" + manage_products: "Manage products" + edit_profile_details: "Edit profile details" + edit_profile_details_etc: "Change your profile description, images, etc." + start_date: "Start Date" + end_date: "End Date" + order_cycle: "Order Cycle" + group_buy_unit_size: "Group Buy Unit Size" + total_qtt_ordered: "Total Quantity Ordered" + max_qtt_ordered: "Max Quantity Ordered" + current_fulfilled_units: "Current Fulfilled Units" + max_fulfilled_units: "Max Fulfilled Units" + bulk_management_warning: "WARNING: Some variants do not have a unit value" + ask: "Ask?" + no_orders_found: "No orders found." + order_no: "Order No." + weight_volume: "Weight/Volume" + remove_tax: "Remove tax" + tax_settings: "Tax Settings" + products_require_tax_category: "products require tax category" + admin_shared_address_1: "Address" + admin_shared_address_2: "Address (cont.)" + admin_share_city: "City" + admin_share_zipcode: "Postcode" + admin_share_country: "Country" + admin_share_state: "State" + hub_sidebar_hubs: "Hubs" + hub_sidebar_none_available: "None Available" + hub_sidebar_manage: "Manage" + hub_sidebar_at_least: "At least one hub must be selected" + hub_sidebar_blue: "blue" + hub_sidebar_red: "red" + shop_trial_in_progress: "Your shopfront trial expires in %{days}." + shop_trial_expired: "Good news! We have decided to extend shopfront trials until further notice (probably around March 2015)." #FIXME + report_customers_distributor: "Distributor" + report_customers_supplier: "Supplier" + report_customers_cycle: "Order Cycle" + report_customers_type: "Report Type" + report_customers_csv: "Download as csv" + report_producers: "Producers: " + report_type: "Report Type: " + report_hubs: "Hubs: " + report_payment: "Payment Methods: " + report_distributor: "Distributor: " + report_payment_by: 'Payments By Type' + report_itemised_payment: 'Itemised Payment Totals' + report_payment_totals: 'Payment Totals' + report_all: 'all' + report_order_cycle: "Order Cycle: " + report_entreprises: "Enterprises: " + report_users: "Users: " + initial_invoice_number: "Initial invoice number:" + invoice_date: "Invoice date:" + due_date: "Due date:" + account_code: "Account code:" + equals: "Equals" + contains: "contains" + discount: "Discount" + filter_products: "Filter Products" + delete_product_variant: "The last variant cannot be deleted!" + progress: "progress" + saving: "Saving.." + success: "success" + failure: "failure" + unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?" + one_product_unsaved: "Changes to one product remain unsaved." + products_unsaved: "Changes to %{n} products remain unsaved." + add_manager: "Add a manager" + is_already_manager: "is already a manager!" + no_change_to_save: " No change to save" + add_manager: "Add a manager" + users: "Users" + about: "About" + images: "Images" + contact: "Contact" + web: "Web" + primary_details: "Primary Details" + adrdress: "Address" + contact: "Contact" + social: "Social" + business_details: "Business Details" + properties: "Properties" + shipping_methods: "Shipping Methods" + payment_methods: "Payment Methods" + enterprise_fees: "Enterprise Fees" + inventory_settings: "Inventory Settings" + tag_rules: "Tag Rules" + shop_preferences: "Shop Preferences" + validation_msg_relationship_already_established: "^That relationship is already established." + validation_msg_at_least_one_hub: "^At least one hub must be selected" + validation_msg_product_category_cant_be_blank: "^Product Category cant be blank" + validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" + validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" + + spree: + shipment_states: + backorder: backorder + partial: partial + pending: pending + ready: ready + shipped: shipped + payment_states: + balance_due: balance due + completed: completed + checkout: checkout + credit_owed: credit owed + failed: failed + paid: paid + pending: pending + processing: processing + void: void + order_state: + address: address + adjustments: adjustments + awaiting_return: awaiting return + canceled: canceled + cart: cart + complete: complete + confirm: confirm + delivery: delivery + payment: payment + resumed: resumed + returned: returned + skrill: skrill From 9e321a63c067f41541bd7a04c83981a1209d8d41 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 2 Mar 2016 14:56:25 +1100 Subject: [PATCH 006/154] Don't put master in order cycle - we don't do that no more --- spec/features/consumer/shopping/cart_spec.rb | 2 +- spec/features/consumer/shopping/checkout_spec.rb | 3 ++- spec/features/consumer/shopping/shopping_spec.rb | 4 ++-- spec/support/request/shop_workflow.rb | 11 +++-------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index 58c570735d..a80fd908ff 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -11,7 +11,7 @@ feature "full-page cart", js: true do let!(:zone) { create(:zone_with_member) } let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) } let(:supplier) { create(:supplier_enterprise) } - let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) } + let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) } let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 74145bbe1a..b1c8856f8d 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -11,9 +11,10 @@ feature "As a consumer I want to check out my cart", js: true do let!(:zone) { create(:zone_with_member) } let(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) } let(:supplier) { create(:supplier_enterprise) } - let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) } + let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [variant]) } let(:enterprise_fee) { create(:enterprise_fee, amount: 1.23, tax_category: product.tax_category) } let(:product) { create(:taxed_product, supplier: supplier, price: 10, zone: zone, tax_rate_amount: 0.1) } + let(:variant) { product.variants.first } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } before do diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index a8545af3db..8fa78f6006 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -182,7 +182,7 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "with variants on the product" do let(:variant) { create(:variant, product: product, on_hand: 10 ) } before do - add_product_and_variant_to_order_cycle(exchange, product, variant) + add_variant_to_order_cycle(exchange, variant) set_order_cycle(order, oc1) visit shop_path end @@ -217,7 +217,7 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:variant) { create(:variant, product: product) } before do - add_product_and_variant_to_order_cycle(exchange, product, variant) + add_variant_to_order_cycle(exchange, variant) set_order_cycle(order, oc1) visit shop_path end diff --git a/spec/support/request/shop_workflow.rb b/spec/support/request/shop_workflow.rb index a0e6ab846c..279ead2630 100644 --- a/spec/support/request/shop_workflow.rb +++ b/spec/support/request/shop_workflow.rb @@ -18,7 +18,7 @@ module ShopWorkflow def add_product_to_cart populator = Spree::OrderPopulator.new(order, order.currency) - populator.populate(variants: {product.master.id => 1}) + populator.populate(variants: {product.variants.first.id => 1}) # Recalculate fee totals order.update_distribution_charge! @@ -28,15 +28,10 @@ module ShopWorkflow find("dd a", text: name).trigger "click" end - def add_product_to_order_cycle(exchange, product) - exchange.variants << product.master + def add_variant_to_order_cycle(exchange, variant) + exchange.variants << variant end - def add_product_and_variant_to_order_cycle(exchange, product, variant) - exchange.variants << product.master - exchange.variants << variant - end - def set_order_cycle(order, order_cycle) order.update_attribute(:order_cycle, order_cycle) end From 243f59c87d9a645ef58e7e1b59f247663b96dffd Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 2 Mar 2016 15:23:38 +1100 Subject: [PATCH 007/154] When there's an out of stock product in the cart, checkout returns user to cart --- app/controllers/checkout_controller.rb | 2 +- spec/features/consumer/shopping/checkout_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 1cffc05734..3ac826df52 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -152,7 +152,7 @@ class CheckoutController < Spree::CheckoutController # Overriding Spree's methods def raise_insufficient_quantity flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) - redirect_to main_app.shop_path + redirect_to cart_path end def redirect_to_paypal_express_form_if_needed diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index b1c8856f8d..2cd959a1b4 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -46,6 +46,22 @@ feature "As a consumer I want to check out my cart", js: true do distributor.shipping_methods << sm3 end + describe "when I have an out of stock product in my cart" do + before do + Spree::Config.set allow_backorders: false + variant.on_hand = 0 + variant.save! + end + + it "returns me to the cart with an error message" do + visit checkout_path + + page.should_not have_selector 'closing', text: "Checkout now" + page.should have_selector 'closing', text: "Your shopping cart" + page.should have_content "An item in your cart has become unavailable" + end + end + context "on the checkout page" do before do visit checkout_path From d45b5254971050093cd20348da195834a0c65661 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 9 Mar 2016 15:33:37 +1100 Subject: [PATCH 008/154] When there's an out of stock product in the cart, placing order returns user to the cart --- .../darkswarm/services/checkout.js.coffee | 9 ++-- app/controllers/checkout_controller.rb | 13 +++++- .../consumer/shopping/checkout_spec.rb | 12 ++++++ .../services/checkout_spec.js.coffee | 41 +++++++++++-------- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee index 652ecd02f9..34cae22fcc 100644 --- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee @@ -10,9 +10,12 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h $http.put('/checkout', {order: @preprocess()}).success (data, status)=> Navigation.go data.path .error (response, status)=> - Loading.clear() - @errors = response.errors - RailsFlashLoader.loadFlash(response.flash) + if response.path + Navigation.go response.path + else + Loading.clear() + @errors = response.errors + RailsFlashLoader.loadFlash(response.flash) # Rails wants our Spree::Address data to be provided with _attributes preprocess: -> diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 3ac826df52..7b34bbcff5 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -151,8 +151,17 @@ class CheckoutController < Spree::CheckoutController # Overriding Spree's methods def raise_insufficient_quantity - flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) - redirect_to cart_path + respond_to do |format| + format.html do + flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) + redirect_to cart_path + end + + format.json do + flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) + render json: {path: cart_path}, status: 400 + end + end end def redirect_to_paypal_express_form_if_needed diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 2cd959a1b4..9e9d830a50 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -230,6 +230,18 @@ feature "As a consumer I want to check out my cart", js: true do page.should have_content "Your order has been processed successfully" end + it "takes us to the cart page with an error when a product becomes out of stock just before we purchase", js: true do + Spree::Config.set allow_backorders: false + variant.on_hand = 0 + variant.save! + + place_order + + page.should_not have_content "Your order has been processed successfully" + page.should have_selector 'closing', text: "Your shopping cart" + page.should have_content "Out of Stock" + end + context "when we are charged a shipping fee" do before { choose sm2.name } diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee index e3ba3f51de..718504a377 100644 --- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee @@ -5,7 +5,7 @@ describe 'Checkout service', -> Navigation = null flash = null scope = null - FlashLoaderMock = + FlashLoaderMock = loadFlash: (arg)-> paymentMethods = [{ id: 99 @@ -41,10 +41,10 @@ describe 'Checkout service', -> module 'Darkswarm' module ($provide)-> - $provide.value "RailsFlashLoader", FlashLoaderMock - $provide.value "currentOrder", orderData - $provide.value "shippingMethods", shippingMethods - $provide.value "paymentMethods", paymentMethods + $provide.value "RailsFlashLoader", FlashLoaderMock + $provide.value "currentOrder", orderData + $provide.value "shippingMethods", shippingMethods + $provide.value "paymentMethods", paymentMethods null inject ($injector, _$httpBackend_, $rootScope)-> @@ -80,26 +80,33 @@ describe 'Checkout service', -> it 'Gets the current payment method', -> expect(Checkout.paymentMethod()).toEqual null Checkout.order.payment_method_id = 99 - expect(Checkout.paymentMethod()).toEqual paymentMethods[0] + expect(Checkout.paymentMethod()).toEqual paymentMethods[0] it "Posts the Checkout to the server", -> $httpBackend.expectPUT("/checkout", {order: Checkout.preprocess()}).respond 200, {path: "test"} Checkout.submit() $httpBackend.flush() - it "sends flash messages to the flash service", -> - spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location - $httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}} - Checkout.submit() + describe "when there is an error", -> + it "redirects when a redirect is given", -> + $httpBackend.expectPUT("/checkout").respond 400, {path: 'path'} + Checkout.submit() + $httpBackend.flush() + expect(Navigation.go).toHaveBeenCalledWith 'path' - $httpBackend.flush() - expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"} + it "sends flash messages to the flash service", -> + spyOn(FlashLoaderMock, "loadFlash") # Stubbing out writes to window.location + $httpBackend.expectPUT("/checkout").respond 400, {flash: {error: "frogs"}} + Checkout.submit() - it "puts errors into the scope", -> - $httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}} - Checkout.submit() - $httpBackend.flush() - expect(Checkout.errors).toEqual {error: "frogs"} + $httpBackend.flush() + expect(FlashLoaderMock.loadFlash).toHaveBeenCalledWith {error: "frogs"} + + it "puts errors into the scope", -> + $httpBackend.expectPUT("/checkout").respond 400, {errors: {error: "frogs"}} + Checkout.submit() + $httpBackend.flush() + expect(Checkout.errors).toEqual {error: "frogs"} describe "data preprocessing", -> beforeEach -> From 17f69bd182e61590fbef23871c1e0e18141af13a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 11:56:26 +1100 Subject: [PATCH 009/154] Remove trailing whitespace --- .../templates/shop_variant.html.haml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index 00e4f70d7c..83463da328 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -1,6 +1,6 @@ .variants.row .small-12.medium-4.large-4.columns.variant-name - .table-cell + .table-cell .inline {{ variant.name_to_display }} .bulk-buy.inline{"bo-if" => "variant.product.group_buy"} %i.ofn-i_056-bulk>< @@ -10,25 +10,25 @@ -# WITHOUT GROUP BUY .small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"} - %input{type: :number, + %input{type: :number, integer: true, - value: nil, - min: 0, + value: nil, + min: 0, placeholder: "0", "ofn-disable-scroll" => true, "ng-model" => "variant.line_item.quantity", max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} - + -# WITH GROUP BUY .small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"} %span.bulk-input-container %span.bulk-input - %input.bulk.first{type: :number, - value: nil, + %input.bulk.first{type: :number, + value: nil, integer: true, - min: 0, + min: 0, "ng-model" => "variant.line_item.quantity", placeholder: "{{'shop_variant_quantity_min' | t}}", "ofn-disable-scroll" => true, @@ -46,16 +46,16 @@ name: "variant_attributes[{{variant.id}}][max_quantity]"} .small-3.medium-1.large-1.columns.variant-unit - .table-cell + .table-cell %em {{ variant.unit_to_display }} .small-4.medium-2.large-2.columns.variant-price .table-cell.price - %i.ofn-i_009-close + %i.ofn-i_009-close {{ variant.price_with_fees | localizeCurrency }} -# Now in a template in app/assets/javascripts/templates ! - %price-breakdown{"price-breakdown" => "_", variant: "variant", + %price-breakdown{"price-breakdown" => "_", variant: "variant", "price-breakdown-append-to-body" => "true", "price-breakdown-placement" => "left", "price-breakdown-animation" => true} @@ -63,4 +63,4 @@ .small-12.medium-2.large-2.columns.total-price.text-right .table-cell %strong{"ng-class" => "{filled: variant.totalPrice()}"} - {{ variant.totalPrice() | localizeCurrency }} + {{ variant.totalPrice() | localizeCurrency }} From a1bcdc616f2d857720a25721d508afe38413ce06 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 12:05:16 +1100 Subject: [PATCH 010/154] Extract add-to-cart inputs into partials --- .../shop_variant_no_group_buy.html.haml | 11 ++++++ .../shop_variant_with_group_buy.html.haml | 22 +++++++++++ .../templates/shop_variant.html.haml | 37 +------------------ 3 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml create mode 100644 app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml new file mode 100644 index 0000000000..4ce584aa42 --- /dev/null +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -0,0 +1,11 @@ +.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"} + + %input{type: :number, + integer: true, + value: nil, + min: 0, + placeholder: "0", + "ofn-disable-scroll" => true, + "ng-model" => "variant.line_item.quantity", + max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml new file mode 100644 index 0000000000..51f7599af7 --- /dev/null +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -0,0 +1,22 @@ +.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"} + %span.bulk-input-container + %span.bulk-input + %input.bulk.first{type: :number, + value: nil, + integer: true, + min: 0, + "ng-model" => "variant.line_item.quantity", + placeholder: "{{'shop_variant_quantity_min' | t}}", + "ofn-disable-scroll" => true, + max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} + %span.bulk-input + %input.bulk.second{type: :number, + "ng-disabled" => "!variant.line_item.quantity", + integer: true, + min: 0, + "ng-model" => "variant.line_item.max_quantity", + placeholder: "{{'shop_variant_quantity_max' | t}}", + "ofn-disable-scroll" => true, + max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + name: "variant_attributes[{{variant.id}}][max_quantity]"} diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index 83463da328..2c9eb932d5 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -7,44 +7,11 @@ %em>< \ {{'bulk' | t}} - -# WITHOUT GROUP BUY - .small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"} - %input{type: :number, - integer: true, - value: nil, - min: 0, - placeholder: "0", - "ofn-disable-scroll" => true, - "ng-model" => "variant.line_item.quantity", - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} + %ng-include{src: "'partials/shop_variant_no_group_buy.html'"} + %ng-include{src: "'partials/shop_variant_with_group_buy.html'"} - -# WITH GROUP BUY - .small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"} - %span.bulk-input-container - %span.bulk-input - %input.bulk.first{type: :number, - value: nil, - integer: true, - min: 0, - "ng-model" => "variant.line_item.quantity", - placeholder: "{{'shop_variant_quantity_min' | t}}", - "ofn-disable-scroll" => true, - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} - %span.bulk-input - %input.bulk.second{type: :number, - "ng-disabled" => "!variant.line_item.quantity", - integer: true, - min: 0, - "ng-model" => "variant.line_item.max_quantity", - placeholder: "{{'shop_variant_quantity_max' | t}}", - "ofn-disable-scroll" => true, - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variant_attributes[{{variant.id}}][max_quantity]"} - .small-3.medium-1.large-1.columns.variant-unit .table-cell %em {{ variant.unit_to_display }} From 292d02749800620ce5ac71f4f7b24e8be1a08d93 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 14:48:13 +1100 Subject: [PATCH 011/154] When adding an item to cart with not enough stock, add as much as we can without erroring --- app/models/spree/order_populator_decorator.rb | 25 ++++++-- spec/models/spree/order_populator_spec.rb | 60 +++++++++++++++++-- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index 1ca0f9efc2..7ff64cdf71 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -49,17 +49,32 @@ Spree::OrderPopulator.class_eval do def attempt_cart_add(variant_id, quantity, max_quantity = nil) quantity = quantity.to_i + max_quantity = max_quantity.to_i if max_quantity variant = Spree::Variant.find(variant_id) OpenFoodNetwork::ScopeVariantToHub.new(@distributor).scope(variant) - if quantity > 0 - if check_stock_levels(variant, quantity) && - check_order_cycle_provided_for(variant) && - check_variant_available_under_distribution(variant) - @order.add_variant(variant, quantity, max_quantity, currency) + if quantity > 0 && + check_order_cycle_provided_for(variant) && + check_variant_available_under_distribution(variant) + + quantity_to_add, max_quantity_to_add = quantities_to_add(variant, quantity, max_quantity) + + if quantity_to_add > 0 + @order.add_variant(variant, quantity_to_add, max_quantity_to_add, currency) end end end + def quantities_to_add(variant, quantity, max_quantity) + # If not enough stock is available, add as much as we can to the cart + on_hand = variant.on_hand + on_hand = [quantity, max_quantity].compact.max if Spree::Config.allow_backorders + quantity_to_add = [quantity, on_hand].min + max_quantity_to_add = [max_quantity, on_hand].min if max_quantity + + [quantity_to_add, max_quantity_to_add] + end + + def cart_remove(variant_id) variant = Spree::Variant.find(variant_id) @order.remove_variant(variant) diff --git a/spec/models/spree/order_populator_spec.rb b/spec/models/spree/order_populator_spec.rb index 7ba59fb47a..389c037b2e 100644 --- a/spec/models/spree/order_populator_spec.rb +++ b/spec/models/spree/order_populator_spec.rb @@ -149,13 +149,15 @@ module Spree end describe "attempt_cart_add" do - it "performs additional validations" do - variant = double(:variant) - quantity = 123 + let(:variant) { double(:variant, on_hand: 250) } + let(:quantity) { 123 } + + before do Spree::Variant.stub(:find).and_return(variant) VariantOverride.stub(:for).and_return(nil) + end - op.should_receive(:check_stock_levels).with(variant, quantity).and_return(true) + it "performs additional validations" do op.should_receive(:check_order_cycle_provided_for).with(variant).and_return(true) op.should_receive(:check_variant_available_under_distribution).with(variant). and_return(true) @@ -163,8 +165,58 @@ module Spree op.attempt_cart_add(333, quantity.to_s) end + + it "filters quantities through #quantities_to_add" do + op.should_receive(:quantities_to_add).with(variant, 123, 123). + and_return([5, 5]) + + op.stub(:check_order_cycle_provided_for) { true } + op.stub(:check_variant_available_under_distribution) { true } + + order.should_receive(:add_variant).with(variant, 5, 5, currency) + + op.attempt_cart_add(333, quantity.to_s, quantity.to_s) + end end + describe "quantities_to_add" do + let(:v) { double(:variant, on_hand: 10) } + context "when max_quantity is not provided" do + it "returns full amount when available" do + op.quantities_to_add(v, 5, nil).should == [5, nil] + end + + it "returns a limited amount when not entirely available" do + op.quantities_to_add(v, 15, nil).should == [10, nil] + end + end + + context "when max_quantity is provided" do + it "returns full amount when available" do + op.quantities_to_add(v, 5, 6).should == [5, 6] + end + + it "returns a limited amount when not entirely available" do + op.quantities_to_add(v, 15, 16).should == [10, 10] + end + end + + context "when backorders are allowed" do + around do |example| + Spree::Config.allow_backorders = true + example.run + Spree::Config.allow_backorders = false + end + + it "does not limit quantity" do + op.quantities_to_add(v, 15, nil).should == [15, nil] + end + + it "does not limit max_quantity" do + op.quantities_to_add(v, 15, 16).should == [15, 16] + end + end + end describe "validations" do describe "determining if distributor can supply products in cart" do From fee0f90a1b90251c7a8b6f969c35ed2eef3bb66f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 15:48:35 +1100 Subject: [PATCH 012/154] After adding products to cart, return status of cart and available stock levels --- .../spree/orders_controller_decorator.rb | 18 ++++++++-- .../spree/orders_controller_spec.rb | 34 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index c6d325d0f8..4c2ab8cb63 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -30,19 +30,33 @@ Spree::OrdersController.class_eval do Spree::Adjustment.without_callbacks do populator = Spree::OrderPopulator.new(current_order(true), current_currency) + if populator.populate(params.slice(:products, :variants, :quantity), true) fire_event('spree.cart.add') fire_event('spree.order.contents_changed') current_order.update! - render json: true, status: 200 + render json: {error: false, stock_levels: stock_levels}, status: 200 + else - render json: false, status: 402 + render json: {error: true}, status: 412 end end end + def stock_levels + Hash[ + current_order.line_items.map do |li| + [li.variant.id, + {quantity: li.quantity, + max_quantity: li.max_quantity, + on_hand: li.variant.on_hand}] + end + ] + end + + def update_distribution @order = current_order(true) diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index 73c72f3386..756be1d3ec 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -42,6 +42,36 @@ describe Spree::OrdersController do flash[:info].should == "The hub you have selected is temporarily closed for orders. Please try again later." end + describe "returning stock levels in JSON on success" do + let(:product) { create(:simple_product) } + + it "returns stock levels as JSON" do + controller.stub(:stock_levels) { 'my_stock_levels' } + Spree::OrderPopulator.stub(:new).and_return(populator = double()) + populator.stub(:populate) { true } + + xhr :post, :populate, use_route: :spree, format: :json + + data = JSON.parse(response.body) + data['stock_levels'].should == 'my_stock_levels' + end + + describe "generating stock levels" do + let!(:order) { create(:order) } + let!(:li) { create(:line_item, order: order, variant: v, quantity: 2, max_quantity: 3) } + let!(:v) { create(:variant, count_on_hand: 4) } + + before do + order.reload + controller.stub(:current_order) { order } + end + + it "returns a hash with variant id, quantity, max_quantity and stock on hand" do + controller.stock_levels.should == {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}} + end + end + end + context "adding a group buy product to the cart" do it "sets a variant attribute for the max quantity" do distributor_product = create(:distributor_enterprise) @@ -68,7 +98,7 @@ describe Spree::OrdersController do Spree::OrderPopulator.stub(:new).and_return(populator = double()) populator.stub(:populate).and_return false xhr :post, :populate, use_route: :spree, format: :json - response.status.should == 402 + response.status.should == 412 end it "tells populator to overwrite" do @@ -78,7 +108,7 @@ describe Spree::OrdersController do end end - context "removing line items from cart" do + describe "removing line items from cart" do describe "when I pass params that includes a line item no longer in our cart" do it "should silently ignore the missing line item" do order = subject.current_order(true) From 8a62d26af4728bd709d174a443be143170321b8d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 16:24:36 +1100 Subject: [PATCH 013/154] After adding an item to the cart, when out of stock, remove from cart and reset client-side stock level --- .../darkswarm/services/cart.js.coffee | 11 ++++++++++ .../darkswarm/services/cart_spec.js.coffee | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index f1c9421a15..c9fdd1471b 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -31,12 +31,23 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> $http.post('/orders/populate', @data()).success (data, status)=> @saved() @update_running = false + + @compareAndNotifyStockLevels data.stock_levels + @popQueue() if @update_enqueued .error (response, status)=> @scheduleRetry(status) @update_running = false + compareAndNotifyStockLevels: (stockLevels) => + for li in @line_items_present() + if !stockLevels[li.variant.id]? + alert "Variant out of stock: #{li.variant.id}" + li.quantity = 0 + li.max_quantity = 0 if li.max_quantity? + li.variant.count_on_hand = 0 + popQueue: => @update_enqueued = false @scheduleUpdate() diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 518a524010..41cee8de4b 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -126,6 +126,27 @@ describe 'Cart service', -> $httpBackend.flush() expect(Cart.scheduleRetry).toHaveBeenCalled() + describe "verifying stock levels after update", -> + describe "when an item is out of stock", -> + it "reduces the quantity in the cart", -> + li = {variant: {id: 1}, quantity: 5} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels({}) + expect(li.quantity).toEqual 0 + expect(li.max_quantity).toBeUndefined() + + it "reduces the max_quantity in the cart", -> + li = {variant: {id: 1}, quantity: 5, max_quantity: 6} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels({}) + expect(li.max_quantity).toEqual 0 + + it "resets the count on hand available", -> + li = {variant: {id: 1, count_on_hand: 10}, quantity: 5} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels({}) + expect(li.variant.count_on_hand).toEqual 0 + it "pops the queue", -> Cart.update_enqueued = true spyOn(Cart, 'scheduleUpdate') From 6fbbe580c5bfc35c2455bb8ca19c834628d8efaf Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Mar 2016 16:41:54 +1100 Subject: [PATCH 014/154] After adding an item to the cart, when less quantity available, reduce quantity and reset client-side stock level --- .../darkswarm/services/cart.js.coffee | 8 +++++ .../darkswarm/services/cart_spec.js.coffee | 29 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index c9fdd1471b..973170e414 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -47,6 +47,14 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> li.quantity = 0 li.max_quantity = 0 if li.max_quantity? li.variant.count_on_hand = 0 + else + if stockLevels[li.variant.id].quantity < li.quantity + alert "Variant quantity reduced: #{li.variant.id}" + li.quantity = stockLevels[li.variant.id].quantity + if stockLevels[li.variant.id].max_quantity < li.max_quantity + alert "Variant max_quantity reduced: #{li.variant.id}" + li.max_quantity = stockLevels[li.variant.id].max_quantity + li.variant.count_on_hand = stockLevels[li.variant.id].on_hand popQueue: => @update_enqueued = false diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 41cee8de4b..58b7795124 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -131,22 +131,45 @@ describe 'Cart service', -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 5} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels({}) + Cart.compareAndNotifyStockLevels {} expect(li.quantity).toEqual 0 expect(li.max_quantity).toBeUndefined() it "reduces the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 6} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels({}) + Cart.compareAndNotifyStockLevels {} expect(li.max_quantity).toEqual 0 it "resets the count on hand available", -> li = {variant: {id: 1, count_on_hand: 10}, quantity: 5} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels({}) + Cart.compareAndNotifyStockLevels {} expect(li.variant.count_on_hand).toEqual 0 + describe "when the quantity available is less than that requested", -> + it "reduces the quantity in the cart", -> + li = {variant: {id: 1}, quantity: 6} + stockLevels = {1: {quantity: 5, on_hand: 5}} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels stockLevels + expect(li.quantity).toEqual 5 + expect(li.max_quantity).toBeUndefined() + + it "reduces the max_quantity in the cart", -> + li = {variant: {id: 1}, quantity: 6, max_quantity: 7} + stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels stockLevels + expect(li.max_quantity).toEqual 5 + + it "resets the count on hand available", -> + li = {variant: {id: 1}, quantity: 6} + stockLevels = {1: {quantity: 5, on_hand: 6}} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels stockLevels + expect(li.variant.count_on_hand).toEqual 6 + it "pops the queue", -> Cart.update_enqueued = true spyOn(Cart, 'scheduleUpdate') From 792e17c385cd3cc3a3d6d3d2c0fb22983df1ea7d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Apr 2016 11:17:54 +1000 Subject: [PATCH 015/154] When removing variant from order, if not found then do nothing --- app/models/spree/order_decorator.rb | 2 +- spec/models/spree/order_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index f0e9324185..53a1b81873 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -99,7 +99,7 @@ Spree::Order.class_eval do def remove_variant(variant) line_items(:reload) current_item = find_line_item_by_variant(variant) - current_item.destroy + current_item.andand.destroy end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index ab86be1580..4db0541d6c 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -366,6 +366,7 @@ describe Spree::Order do let(:order) { create(:order) } let(:v1) { create(:variant) } let(:v2) { create(:variant) } + let(:v3) { create(:variant) } before do order.add_variant v1 @@ -376,6 +377,12 @@ describe Spree::Order do order.remove_variant v1 order.line_items(:reload).map(&:variant).should == [v2] end + + it "does nothing when there is no matching line item" do + expect do + order.remove_variant v3 + end.to change(order.line_items(:reload), :count).by(0) + end end describe "emptying the order" do From 8695dea0a5c36fd4c6753e89d0c93b5a652dacba Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Apr 2016 11:19:34 +1000 Subject: [PATCH 016/154] Remove variant from cart when it becomes out of stock --- app/models/spree/order_populator_decorator.rb | 2 ++ spec/models/spree/order_populator_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index 7ff64cdf71..82d9aa85e7 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -60,6 +60,8 @@ Spree::OrderPopulator.class_eval do if quantity_to_add > 0 @order.add_variant(variant, quantity_to_add, max_quantity_to_add, currency) + else + @order.remove_variant variant end end end diff --git a/spec/models/spree/order_populator_spec.rb b/spec/models/spree/order_populator_spec.rb index 389c037b2e..9f7cc47c51 100644 --- a/spec/models/spree/order_populator_spec.rb +++ b/spec/models/spree/order_populator_spec.rb @@ -177,6 +177,19 @@ module Spree op.attempt_cart_add(333, quantity.to_s, quantity.to_s) end + + it "removes variants which have become out of stock" do + op.should_receive(:quantities_to_add).with(variant, 123, 123). + and_return([0, 0]) + + op.stub(:check_order_cycle_provided_for) { true } + op.stub(:check_variant_available_under_distribution) { true } + + order.should_receive(:remove_variant).with(variant) + order.should_receive(:add_variant).never + + op.attempt_cart_add(333, quantity.to_s, quantity.to_s) + end end describe "quantities_to_add" do From cfe062918b3580c20a65a13b3c7c7c2d50a726f6 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Apr 2016 11:22:06 +1000 Subject: [PATCH 017/154] When a variant goes out of stock, disable the input and grey out the row --- .../shop_variant_no_group_buy.html.haml | 1 + .../darkswarm/_shop-product-rows.css.sass | 28 ++++++++-------- app/views/shop/products/_form.html.haml | 4 +-- .../consumer/shopping/shopping_spec.rb | 33 +++++++++++++++++++ 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml index 4ce584aa42..36a4e44ade 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -8,4 +8,5 @@ "ofn-disable-scroll" => true, "ng-model" => "variant.line_item.quantity", max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + "ng-disabled" => "!variant.on_demand && variant.count_on_hand == 0", name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass index c67b030292..c0e4fb27aa 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass +++ b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass @@ -11,20 +11,20 @@ padding-bottom: 0em display: table line-height: 1.1 - // outline: 1px solid red + // outline: 1px solid red - @media all and (max-width: 768px) - font-size: 0.875rem + @media all and (max-width: 768px) + font-size: 0.875rem + + @media all and (max-width: 640px) + font-size: 0.75rem - @media all and (max-width: 640px) - font-size: 0.75rem - .table-cell display: table-cell vertical-align: middle height: 37px - // ROW VARIANTS + // ROW VARIANTS .row.variants margin-left: 0 margin-right: 0 @@ -35,7 +35,10 @@ background-color: #f9f9f9 &:hover, &:focus, &:active background-color: $clr-brick-ultra-light - + + &.out-of-stock + opacity: 0.2 + // Variant name .variant-name padding-left: 7.9375rem @@ -52,7 +55,7 @@ height: 27px // Variant unit - .variant-unit + .variant-unit padding-left: 0rem padding-right: 0rem color: #888 @@ -88,18 +91,18 @@ margin-left: 0 margin-right: 0 background: #fff - + .columns padding-top: 1em padding-bottom: 1em line-height: 1 - + @media all and (max-width: 768px) padding-top: 0.65rem padding-bottom: 0.65rem .summary-header - padding-left: 7.9375rem + padding-left: 7.9375rem @media all and (max-width: 768px) padding-left: 4.9375rem @media all and (max-width: 640px) @@ -118,4 +121,3 @@ color: $clr-brick i font-size: 0.8em - diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index 18868e5c77..f2be498451 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -32,9 +32,9 @@ %div.pad-top{bindonce: true} %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"} - = render partial: "shop/products/summary" + = render "shop/products/summary" %shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants", "id" => "variant-{{ product.master.id }}"} - %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}"} + %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.count_on_hand == 0}"} %product{"ng-show" => "Products.loading"} .row.summary diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 8fa78f6006..ab94c678d7 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -215,9 +215,11 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } let(:product) { create(:simple_product) } let(:variant) { create(:variant, product: product) } + let(:variant2) { create(:variant, product: product) } before do add_variant_to_order_cycle(exchange, variant) + add_variant_to_order_cycle(exchange, variant2) set_order_cycle(order, oc1) visit shop_path end @@ -235,6 +237,37 @@ feature "As a consumer I want to shop with a distributor", js: true do Spree::LineItem.where(id: li).should be_empty end + + describe "when a product goes out of stock just before it's added to the cart" do + before do + variant.update_attributes! on_hand: 0 + end + + it "stops the attempt, shows an error message and refreshes the products asynchronously" do + # -- Messaging + alert_message = accept_alert do + fill_in "variants[#{variant.id}]", with: '1' + wait_until { !cart_dirty } + end + + # TODO: This will be a modal + expect(alert_message).to be + puts alert_message + + # -- Page updates + # Update amount in cart + page.should have_field "variants[#{variant.id}]", with: '0', disabled: true + page.should have_field "variants[#{variant2.id}]", with: '' + + # Update amount available in product list + # If amount falls to zero, variant should be greyed out and input disabled + page.should have_selector "#variant-#{variant.id}.out-of-stock" + page.should have_selector "#variants_#{variant.id}[max='0']" + page.should have_selector "#variants_#{variant.id}[disabled='disabled']" + end + + it "does the same for group buy products" + end end context "when no order cycles are available" do From 5e39b11c2f2d81274f3f6fff956d3e84dfae0fc3 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Apr 2016 11:44:28 +1000 Subject: [PATCH 018/154] Spec out of stock handling for group buy --- .../shop_variant_with_group_buy.html.haml | 3 +- .../consumer/shopping/shopping_spec.rb | 37 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index 51f7599af7..c44a71948b 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -19,4 +19,5 @@ placeholder: "{{'shop_variant_quantity_max' | t}}", "ofn-disable-scroll" => true, max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variant_attributes[{{variant.id}}][max_quantity]"} + name: "variant_attributes[{{variant.id}}][max_quantity]", + id: "variants_{{variant.id}}_max"} diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index ab94c678d7..c252a3f041 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -239,11 +239,9 @@ feature "As a consumer I want to shop with a distributor", js: true do end describe "when a product goes out of stock just before it's added to the cart" do - before do - variant.update_attributes! on_hand: 0 - end - it "stops the attempt, shows an error message and refreshes the products asynchronously" do + variant.update_attributes! on_hand: 0 + # -- Messaging alert_message = accept_alert do fill_in "variants[#{variant.id}]", with: '1' @@ -266,7 +264,36 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should have_selector "#variants_#{variant.id}[disabled='disabled']" end - it "does the same for group buy products" + context "group buy products" do + let(:product) { create(:simple_product, group_buy: true) } + + it "does the same" do + # -- Place in cart so we can set max_quantity, then make out of stock + fill_in "variants[#{variant.id}]", with: '1' + wait_until { !cart_dirty } + variant.update_attributes! on_hand: 0 + + # -- Messaging + alert_message = accept_alert do + fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '1' + wait_until { !cart_dirty } + end + + # TODO: This will be a modal + expect(alert_message).to be + puts alert_message + + # -- Page updates + # Update amount in cart + page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: '0', disabled: true + + # Update amount available in product list + # If amount falls to zero, variant should be greyed out and input disabled + page.should have_selector "#variant-#{variant.id}.out-of-stock" + page.should have_selector "#variants_#{variant.id}_max[max='0']" + page.should have_selector "#variants_#{variant.id}_max[disabled='disabled']" + end + end end end From 35117f7af433bc31f1a9be9cacf2f966b2d3b28b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 8 Apr 2016 15:39:25 +1000 Subject: [PATCH 019/154] Show a modal when available stock levels have reduced --- .../darkswarm/services/cart.js.coffee | 17 +++++++++--- .../templates/out_of_stock.html.haml | 10 +++++++ .../spree/orders_controller_decorator.rb | 8 ++++-- .../spree/orders_controller_spec.rb | 8 ++++++ .../consumer/shopping/shopping_spec.rb | 26 +++++++++---------- 5 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 app/assets/javascripts/templates/out_of_stock.html.haml diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index 973170e414..4f808e0710 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> +Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, storage)-> # Handles syncing of current cart/order state to server new class Cart dirty: false @@ -41,21 +41,30 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)-> @update_running = false compareAndNotifyStockLevels: (stockLevels) => + scope = $rootScope.$new(true) + scope.variants = [] + + # TODO: These changes to quantity/max_quantity trigger another cart update, which + # is unnecessary. + for li in @line_items_present() if !stockLevels[li.variant.id]? - alert "Variant out of stock: #{li.variant.id}" li.quantity = 0 li.max_quantity = 0 if li.max_quantity? li.variant.count_on_hand = 0 + scope.variants.push li.variant else if stockLevels[li.variant.id].quantity < li.quantity - alert "Variant quantity reduced: #{li.variant.id}" li.quantity = stockLevels[li.variant.id].quantity + scope.variants.push li.variant if stockLevels[li.variant.id].max_quantity < li.max_quantity - alert "Variant max_quantity reduced: #{li.variant.id}" li.max_quantity = stockLevels[li.variant.id].max_quantity + scope.variants.push li.variant li.variant.count_on_hand = stockLevels[li.variant.id].on_hand + if scope.variants.length > 0 + $modal.open(templateUrl: "out_of_stock.html", scope: scope, windowClass: 'out-of-stock-modal') + popQueue: => @update_enqueued = false @scheduleUpdate() diff --git a/app/assets/javascripts/templates/out_of_stock.html.haml b/app/assets/javascripts/templates/out_of_stock.html.haml new file mode 100644 index 0000000000..72244d7ab6 --- /dev/null +++ b/app/assets/javascripts/templates/out_of_stock.html.haml @@ -0,0 +1,10 @@ +%h3 Reduced stock available + +%p While you've been shopping, the stock levels for one or more of the products in your cart have reduced. Here's what's changed: + +%p{'ng-repeat' => "v in variants"} + %em {{ v.name_to_display }} - {{ v.unit_to_display }} + %span{'ng-if' => "v.count_on_hand == 0"} + is now out of stock. + %span{'ng-if' => "v.count_on_hand > 0"} + now only has {{ v.count_on_hand }} remaining. diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index 4c2ab8cb63..836b77b271 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -51,12 +51,11 @@ Spree::OrdersController.class_eval do [li.variant.id, {quantity: li.quantity, max_quantity: li.max_quantity, - on_hand: li.variant.on_hand}] + on_hand: wrap_json_infinity(li.variant.on_hand)}] end ] end - def update_distribution @order = current_order(true) @@ -135,4 +134,9 @@ Spree::OrdersController.class_eval do end end + # Rails to_json encodes Float::INFINITY as Infinity, which is not valid JSON + # Return it as a large integer (max 32 bit signed int) + def wrap_json_infinity(n) + n == Float::INFINITY ? 2147483647 : n + end end diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index 756be1d3ec..df7be3508d 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -69,6 +69,14 @@ describe Spree::OrdersController do it "returns a hash with variant id, quantity, max_quantity and stock on hand" do controller.stock_levels.should == {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}} end + + describe "encoding Infinity" do + let!(:v) { create(:variant, on_demand: true, count_on_hand: 0) } + + it "encodes Infinity as a large, finite integer" do + controller.stock_levels.should == {v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}} + end + end end end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index c252a3f041..b0d5a42127 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -243,14 +243,13 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update_attributes! on_hand: 0 # -- Messaging - alert_message = accept_alert do - fill_in "variants[#{variant.id}]", with: '1' - wait_until { !cart_dirty } - end + fill_in "variants[#{variant.id}]", with: '1' + wait_until { !cart_dirty } - # TODO: This will be a modal - expect(alert_message).to be - puts alert_message + within(".out-of-stock-modal") do + page.should have_content "stock levels for one or more of the products in your cart have reduced" + page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock." + end # -- Page updates # Update amount in cart @@ -274,14 +273,13 @@ feature "As a consumer I want to shop with a distributor", js: true do variant.update_attributes! on_hand: 0 # -- Messaging - alert_message = accept_alert do - fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '1' - wait_until { !cart_dirty } - end + fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '1' + wait_until { !cart_dirty } - # TODO: This will be a modal - expect(alert_message).to be - puts alert_message + within(".out-of-stock-modal") do + page.should have_content "stock levels for one or more of the products in your cart have reduced" + page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock." + end # -- Page updates # Update amount in cart From dac90c8003d5599d592f17f9dbc5a80f146827ed Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 11 Apr 2016 08:42:03 +1000 Subject: [PATCH 020/154] Fix specs --- spec/controllers/checkout_controller_spec.rb | 4 ++-- spec/features/consumer/shopping/checkout_auth_spec.rb | 11 +++++------ spec/features/consumer/shopping/shopping_spec.rb | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spec/controllers/checkout_controller_spec.rb b/spec/controllers/checkout_controller_spec.rb index ddacd0fdeb..1f5e40a841 100644 --- a/spec/controllers/checkout_controller_spec.rb +++ b/spec/controllers/checkout_controller_spec.rb @@ -34,13 +34,13 @@ describe CheckoutController do flash[:info].should == "The hub you have selected is temporarily closed for orders. Please try again later." end - it "redirects to the shop when no line items are present" do + it "redirects to the cart when some items are out of stock" do controller.stub(:current_distributor).and_return(distributor) controller.stub(:current_order_cycle).and_return(order_cycle) controller.stub(:current_order).and_return(order) order.stub_chain(:insufficient_stock_lines, :present?).and_return true get :edit - response.should redirect_to shop_path + response.should redirect_to spree.cart_path end it "renders when both distributor and order cycle is selected" do diff --git a/spec/features/consumer/shopping/checkout_auth_spec.rb b/spec/features/consumer/shopping/checkout_auth_spec.rb index a19776f7a9..0b0683def0 100644 --- a/spec/features/consumer/shopping/checkout_auth_spec.rb +++ b/spec/features/consumer/shopping/checkout_auth_spec.rb @@ -9,7 +9,7 @@ feature "As a consumer I want to check out my cart", js: true do let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } let(:supplier) { create(:supplier_enterprise) } - let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.master]) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: supplier) } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } let(:address) { create(:address, firstname: "Foo", lastname: "Bar") } @@ -23,7 +23,7 @@ feature "As a consumer I want to check out my cart", js: true do it "does not not render the login form when logged in" do quick_login_as user - visit checkout_path + visit checkout_path within "section[role='main']" do page.should_not have_content "Login" page.should have_checkout_details @@ -31,7 +31,7 @@ feature "As a consumer I want to check out my cart", js: true do end it "renders the login buttons when logged out" do - visit checkout_path + visit checkout_path within "section[role='main']" do page.should have_content "Login" click_button "Login" @@ -53,9 +53,8 @@ feature "As a consumer I want to check out my cart", js: true do end it "allows user to checkout as guest" do - visit checkout_path + visit checkout_path checkout_as_guest - page.should have_checkout_details + page.should have_checkout_details end end - diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index b0d5a42127..41a68dfb9b 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -318,7 +318,7 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:variant) { create(:variant, product: product) } before do - add_product_and_variant_to_order_cycle(exchange, product, variant) + add_variant_to_order_cycle(exchange, variant) set_order_cycle(order, oc1) distributor.require_login = true distributor.save! From b2d78e7df6f1cb50913ece0fab5a7db5cb48d913 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Mon, 11 Apr 2016 08:55:05 +1000 Subject: [PATCH 021/154] Set allow_backorders explicitly for consistency in CI --- spec/models/spree/order_populator_spec.rb | 33 +++++++++++++---------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/spec/models/spree/order_populator_spec.rb b/spec/models/spree/order_populator_spec.rb index 9f7cc47c51..4a50e59fa8 100644 --- a/spec/models/spree/order_populator_spec.rb +++ b/spec/models/spree/order_populator_spec.rb @@ -194,23 +194,28 @@ module Spree describe "quantities_to_add" do let(:v) { double(:variant, on_hand: 10) } - context "when max_quantity is not provided" do - it "returns full amount when available" do - op.quantities_to_add(v, 5, nil).should == [5, nil] + + context "when backorders are not allowed" do + before { Spree::Config.allow_backorders = false } + + context "when max_quantity is not provided" do + it "returns full amount when available" do + op.quantities_to_add(v, 5, nil).should == [5, nil] + end + + it "returns a limited amount when not entirely available" do + op.quantities_to_add(v, 15, nil).should == [10, nil] + end end - it "returns a limited amount when not entirely available" do - op.quantities_to_add(v, 15, nil).should == [10, nil] - end - end + context "when max_quantity is provided" do + it "returns full amount when available" do + op.quantities_to_add(v, 5, 6).should == [5, 6] + end - context "when max_quantity is provided" do - it "returns full amount when available" do - op.quantities_to_add(v, 5, 6).should == [5, 6] - end - - it "returns a limited amount when not entirely available" do - op.quantities_to_add(v, 15, 16).should == [10, 10] + it "returns a limited amount when not entirely available" do + op.quantities_to_add(v, 15, 16).should == [10, 10] + end end end From 9b3139dba9d2d71a7b09ec99afd24590933c31aa Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Tue, 12 Apr 2016 11:18:03 +1000 Subject: [PATCH 022/154] When there's an out of stock product in the cart, visiting the shopfront returns user to the cart --- app/controllers/enterprises_controller.rb | 12 +++++++++- .../enterprises_controller_spec.rb | 24 ++++++++++++++++++- spec/controllers/shop_controller_spec.rb | 11 --------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 8a875c76bf..9459cf43e3 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -5,6 +5,8 @@ class EnterprisesController < BaseController # These prepended filters are in the reverse order of execution prepend_before_filter :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop + before_filter :check_stock_levels, only: :shop + before_filter :clean_permalink, only: :check_permalink respond_to :js, only: :permalink_checker @@ -21,17 +23,25 @@ class EnterprisesController < BaseController end end + private def clean_permalink params[:permalink] = params[:permalink].parameterize end + def check_stock_levels + if current_order(true).insufficient_stock_lines.present? + flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) + redirect_to spree.cart_path + end + end + def reset_order distributor = Enterprise.is_distributor.find_by_permalink(params[:id]) || Enterprise.is_distributor.find(params[:id]) order = current_order(true) - if order.distributor and order.distributor != distributor + if order.distributor && order.distributor != distributor order.empty! order.set_order_cycle! nil end diff --git a/spec/controllers/enterprises_controller_spec.rb b/spec/controllers/enterprises_controller_spec.rb index b3cb6b5e32..0727488841 100644 --- a/spec/controllers/enterprises_controller_spec.rb +++ b/spec/controllers/enterprises_controller_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' describe EnterprisesController do describe "shopping for a distributor" do + let(:order) { controller.current_order(true) } before(:each) do @current_distributor = create(:distributor_enterprise, with_payment_and_shipping: true) @distributor = create(:distributor_enterprise, with_payment_and_shipping: true) @order_cycle1 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 2.days.ago, orders_close_at: 3.days.from_now ) @order_cycle2 = create(:simple_order_cycle, distributors: [@distributor], orders_open_at: 3.days.ago, orders_close_at: 4.days.from_now ) - controller.current_order(true).distributor = @current_distributor + order.set_distributor! @current_distributor end it "sets the shop as the distributor on the order when shopping for the distributor" do @@ -52,6 +53,27 @@ describe EnterprisesController do controller.current_order.line_items.size.should == 1 end + describe "when an out of stock item is in the cart" do + let(:variant) { create(:variant, on_demand: false, on_hand: 10) } + let(:line_item) { create(:line_item, variant: variant) } + let(:order_cycle) { create(:simple_order_cycle, distributors: [@distributor], variants: [variant]) } + + before do + order.set_distribution! @current_distributor, order_cycle + order.line_items << line_item + + Spree::Config.set allow_backorders: false + variant.on_hand = 0 + variant.save! + end + + it "redirects to the cart" do + spree_get :shop, {id: @current_distributor} + + response.should redirect_to spree.cart_path + end + end + it "sets order cycle if only one is available at the chosen distributor" do @order_cycle2.destroy diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index 73231bc86c..a4ddab3ad3 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -69,17 +69,6 @@ describe ShopController do end - describe "producers/suppliers" do - let(:supplier) { create(:supplier_enterprise) } - let(:product) { create(:product, supplier: supplier) } - let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) } - - before do - exchange = order_cycle.exchanges.to_enterprises(distributor).outgoing.first - exchange.variants << product.master - end - end - describe "returning products" do let(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) } let(:exchange) { order_cycle.exchanges.to_enterprises(distributor).outgoing.first } From 3dcfa810fdec28cc5f9e81886327ba3e68d3f5f1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 14 Apr 2016 11:14:16 +1000 Subject: [PATCH 023/154] Display out of stock banner when viewing cart directly --- app/controllers/checkout_controller.rb | 2 -- app/controllers/enterprises_controller.rb | 1 - .../spree/orders_controller_decorator.rb | 12 +++++++--- .../spree/orders_controller_spec.rb | 22 ++++++++++++++++++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 7b34bbcff5..c6d1291477 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -153,12 +153,10 @@ class CheckoutController < Spree::CheckoutController def raise_insufficient_quantity respond_to do |format| format.html do - flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) redirect_to cart_path end format.json do - flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) render json: {path: cart_path}, status: 400 end end diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 9459cf43e3..fe2eda8bfd 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -32,7 +32,6 @@ class EnterprisesController < BaseController def check_stock_levels if current_order(true).insufficient_stock_lines.present? - flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) redirect_to spree.cart_path end end diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index 836b77b271..1b542f0829 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -1,9 +1,9 @@ require 'spree/core/controller_helpers/order_decorator' Spree::OrdersController.class_eval do - after_filter :populate_variant_attributes, :only => :populate - before_filter :update_distribution, :only => :update - before_filter :filter_order_params, :only => :update + after_filter :populate_variant_attributes, only: :populate + before_filter :update_distribution, only: :update + before_filter :filter_order_params, only: :update prepend_before_filter :require_order_cycle, only: :edit prepend_before_filter :require_distributor_chosen, only: :edit @@ -12,13 +12,19 @@ Spree::OrdersController.class_eval do include OrderCyclesHelper layout 'darkswarm' + # Patching to redirect to shop if order is empty def edit @order = current_order(true) + if @order.line_items.empty? redirect_to main_app.shop_path else associate_user + + if @order.insufficient_stock_lines.present? + flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) + end end end diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index df7be3508d..6baa633378 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -42,6 +42,26 @@ describe Spree::OrdersController do flash[:info].should == "The hub you have selected is temporarily closed for orders. Please try again later." end + describe "when an item has insufficient stock" do + let(:order) { subject.current_order(true) } + let(:oc) { create(:simple_order_cycle, distributors: [d], variants: [variant]) } + let(:d) { create(:distributor_enterprise, shipping_methods: [create(:shipping_method)], payment_methods: [create(:payment_method)]) } + let(:variant) { create(:variant, on_demand: false, on_hand: 5) } + let(:line_item) { order.line_items.last } + + before do + order.set_distribution! d, oc + order.add_variant variant, 5 + variant.update_attributes! on_hand: 3 + end + + it "displays a flash message when we view the cart" do + spree_get :edit + expect(response.status).to eq 200 + flash[:error].should == "An item in your cart has become unavailable." + end + end + describe "returning stock levels in JSON on success" do let(:product) { create(:simple_product) } @@ -120,7 +140,7 @@ describe Spree::OrdersController do describe "when I pass params that includes a line item no longer in our cart" do it "should silently ignore the missing line item" do order = subject.current_order(true) - li = order.add_variant(create(:simple_product, on_hand: 110).master) + li = order.add_variant(create(:simple_product, on_hand: 110).variants.first) spree_get :update, order: { line_items_attributes: { "0" => {quantity: "0", id: "9999"}, "1" => {quantity: "99", id: li.id} From 5151779f805b33b6d094b16297788689e88ddce0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 14 Apr 2016 15:20:44 +1000 Subject: [PATCH 024/154] When update is for another line item, still update all stock levels and show warnings --- .../spree/orders_controller_decorator.rb | 1 + app/models/spree/line_item_decorator.rb | 10 ++++++ app/models/spree/order_decorator.rb | 5 +++ .../consumer/shopping/shopping_spec.rb | 36 +++++++++++++++++++ spec/models/spree/line_item_spec.rb | 35 ++++++++++++++++++ 5 files changed, 87 insertions(+) diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index 1b542f0829..f9c7f53001 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -41,6 +41,7 @@ Spree::OrdersController.class_eval do fire_event('spree.cart.add') fire_event('spree.order.contents_changed') + current_order.cap_quantity_at_stock! current_order.update! render json: {error: false, stock_levels: stock_levels}, status: 200 diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index 9db9ae1269..b9f8af577f 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -43,6 +43,16 @@ Spree::LineItem.class_eval do where('spree_adjustments.id IS NULL') + def cap_quantity_at_stock! + attrs = {} + + attrs[:quantity] = variant.on_hand if quantity > variant.on_hand + attrs[:max_quantity] = variant.on_hand if (max_quantity || 0) > variant.on_hand + + update_attributes!(attrs) if attrs.any? + end + + def has_tax? adjustments.included_tax.any? end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 53a1b81873..a06a34bcd0 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -144,6 +144,11 @@ Spree::Order.class_eval do current_item end + def cap_quantity_at_stock! + line_items.each &:cap_quantity_at_stock! + end + + def set_distributor!(distributor) self.distributor = distributor self.order_cycle = nil unless self.order_cycle.andand.has_distributor? distributor diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 41a68dfb9b..9dc0a39dac 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -292,6 +292,42 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should have_selector "#variants_#{variant.id}_max[disabled='disabled']" end end + + context "when the update is for another product" do + it "updates quantity" do + fill_in "variants[#{variant.id}]", with: '1' + wait_until { !cart_dirty } + + variant.update_attributes! on_hand: 0 + + fill_in "variants[#{variant2.id}]", with: '1' + wait_until { !cart_dirty } + + within(".out-of-stock-modal") do + page.should have_content "stock levels for one or more of the products in your cart have reduced" + page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock." + end + end + + context "group buy products" do + let(:product) { create(:simple_product, group_buy: true) } + + it "updates max_quantity" do + fill_in "variants[#{variant.id}]", with: '1' + fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '2' + wait_until { !cart_dirty } + variant.update_attributes! on_hand: 1 + + fill_in "variants[#{variant2.id}]", with: '1' + wait_until { !cart_dirty } + + within(".out-of-stock-modal") do + page.should have_content "stock levels for one or more of the products in your cart have reduced" + page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining" + end + end + end + end end end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 6bb18fafa3..2b5362e94f 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -42,6 +42,41 @@ module Spree end end + describe "capping quantity at stock level" do + let!(:v) { create(:variant, on_demand: false, on_hand: 10) } + let!(:li) { create(:line_item, variant: v, quantity: 10, max_quantity: 10) } + + before do + v.update_attributes! on_hand: 5 + end + + it "caps quantity" do + li.cap_quantity_at_stock! + li.reload.quantity.should == 5 + end + + it "caps max_quantity" do + li.cap_quantity_at_stock! + li.reload.max_quantity.should == 5 + end + + it "works for products without max_quantity" do + li.update_column :max_quantity, nil + li.cap_quantity_at_stock! + li.reload + li.quantity.should == 5 + li.max_quantity.should be_nil + end + + it "does nothing for on_demand items" do + v.update_attributes! on_demand: true + li.cap_quantity_at_stock! + li.reload + li.quantity.should == 10 + li.max_quantity.should == 10 + end + end + describe "calculating price with adjustments" do it "does not return fractional cents" do li = LineItem.new From 06d7665bf97fda4f4ad9a71b1c0bfba5360f2949 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 Apr 2016 09:54:56 +1000 Subject: [PATCH 025/154] Prospective fix for intermittent spec fail --- spec/controllers/spree/orders_controller_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index 6baa633378..c0a844a4bc 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -50,6 +50,7 @@ describe Spree::OrdersController do let(:line_item) { order.line_items.last } before do + Spree::Config.allow_backorders = false order.set_distribution! d, oc order.add_variant variant, 5 variant.update_attributes! on_hand: 3 From 6cba935a6561e0afdc7b66099939c8f020bca13f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 Apr 2016 11:26:30 +1000 Subject: [PATCH 026/154] Add close button to out of stock modal --- app/assets/javascripts/templates/out_of_stock.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/templates/out_of_stock.html.haml b/app/assets/javascripts/templates/out_of_stock.html.haml index 72244d7ab6..d894583fcf 100644 --- a/app/assets/javascripts/templates/out_of_stock.html.haml +++ b/app/assets/javascripts/templates/out_of_stock.html.haml @@ -1,3 +1,6 @@ +%a.close-reveal-modal{"ng-click" => "$close()"} + %i.ofn-i_009-close + %h3 Reduced stock available %p While you've been shopping, the stock levels for one or more of the products in your cart have reduced. Here's what's changed: From 779be7c5a0657d1fccba5cf35dfe197a4f3843e1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 21 Apr 2016 16:37:15 +1000 Subject: [PATCH 027/154] Extract params parsing into single method --- app/models/spree/order_populator_decorator.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index 82d9aa85e7..38c908a530 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -11,8 +11,7 @@ Spree::OrderPopulator.class_eval do if valid? @order.with_lock do - variants = read_products_hash(from_hash) + - read_variants_hash(from_hash) + variants = read_variants from_hash variants.each do |v| if varies_from_cart(v) @@ -31,6 +30,11 @@ Spree::OrderPopulator.class_eval do valid? end + def read_variants(data) + read_products_hash(data) + + read_variants_hash(data) + end + def read_products_hash(data) (data[:products] || []).map do |product_id, variant_id| {variant_id: variant_id, quantity: data[:quantity]} From 28d40bf27d3418d3e8b89bec03e65a83887be3a7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 21 Apr 2016 21:27:52 +1000 Subject: [PATCH 028/154] Fixing font styling on enterprise name in shop product summary --- app/views/shop/products/_summary.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 6d36ac81ce..8671b00cbf 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -13,8 +13,9 @@ %em = t :products_from %span - %enterprise-modal - %i.ofn-i_036-producers{"bo-text" => "enterprise.name"} + %enterprise-modal + %i.ofn-i_036-producers + %span{"bo-bind" => "enterprise.name"} .small-2.medium-2.large-1.columns.text-center .taxon-flag %render-svg{path: "{{product.primary_taxon.icon}}"} From a26266159cefcdc55c2e1bff7d5498a9d88c069d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 Apr 2016 10:47:20 +1000 Subject: [PATCH 029/154] Fix timing issue: change in client-side value during server update --- .../darkswarm/services/cart.js.coffee | 20 ++++----- .../spree/orders_controller_decorator.rb | 26 ++++++++++-- app/models/spree/order_populator_decorator.rb | 6 ++- .../spree/orders_controller_spec.rb | 30 ++++++++++++-- .../darkswarm/services/cart_spec.js.coffee | 41 +++++++++++++++++-- 5 files changed, 100 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index 4f808e0710..d75ef7a628 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -28,6 +28,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo update: => @update_running = true + $http.post('/orders/populate', @data()).success (data, status)=> @saved() @update_running = false @@ -48,19 +49,14 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo # is unnecessary. for li in @line_items_present() - if !stockLevels[li.variant.id]? - li.quantity = 0 - li.max_quantity = 0 if li.max_quantity? - li.variant.count_on_hand = 0 - scope.variants.push li.variant - else - if stockLevels[li.variant.id].quantity < li.quantity - li.quantity = stockLevels[li.variant.id].quantity - scope.variants.push li.variant - if stockLevels[li.variant.id].max_quantity < li.max_quantity - li.max_quantity = stockLevels[li.variant.id].max_quantity - scope.variants.push li.variant + if stockLevels[li.variant.id]? li.variant.count_on_hand = stockLevels[li.variant.id].on_hand + if li.quantity > li.variant.count_on_hand + li.quantity = li.variant.count_on_hand + scope.variants.push li.variant + if li.max_quantity > li.variant.count_on_hand + li.max_quantity = li.variant.count_on_hand + scope.variants.push(li.variant) unless li.variant in scope.variants if scope.variants.length > 0 $modal.open(templateUrl: "out_of_stock.html", scope: scope, windowClass: 'out-of-stock-modal') diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index f9c7f53001..e675ce7e94 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -44,7 +44,10 @@ Spree::OrdersController.class_eval do current_order.cap_quantity_at_stock! current_order.update! - render json: {error: false, stock_levels: stock_levels}, status: 200 + variant_ids = variant_ids_in(populator.variants_h) + + render json: {error: false, stock_levels: stock_levels(current_order, variant_ids)}, + status: 200 else render json: {error: true}, status: 412 @@ -52,9 +55,26 @@ Spree::OrdersController.class_eval do end end - def stock_levels + # Report the stock levels in the order for all variant ids requested + def stock_levels(order, variant_ids) + stock_levels = li_stock_levels(order) + + li_variant_ids = stock_levels.keys + (variant_ids - li_variant_ids).each do |variant_id| + stock_levels[variant_id] = {quantity: 0, max_quantity: 0, + on_hand: Spree::Variant.find(variant_id).on_hand} + end + + stock_levels + end + + def variant_ids_in(variants_h) + variants_h.map { |v| v[:variant_id].to_i } + end + + def li_stock_levels(order) Hash[ - current_order.line_items.map do |li| + order.line_items.map do |li| [li.variant.id, {quantity: li.quantity, max_quantity: li.max_quantity, diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index 38c908a530..a106de6936 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -1,6 +1,8 @@ require 'open_food_network/scope_variant_to_hub' Spree::OrderPopulator.class_eval do + attr_reader :variants_h + def populate(from_hash, overwrite = false) @distributor, @order_cycle = distributor_and_order_cycle # Refactor: We may not need this validation - we can't change distribution here, so @@ -31,8 +33,8 @@ Spree::OrderPopulator.class_eval do end def read_variants(data) - read_products_hash(data) + - read_variants_hash(data) + @variants_h = read_products_hash(data) + + read_variants_hash(data) end def read_products_hash(data) diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index c0a844a4bc..286bb9217a 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -67,9 +67,11 @@ describe Spree::OrdersController do let(:product) { create(:simple_product) } it "returns stock levels as JSON" do + controller.stub(:variant_ids_in) { [123] } controller.stub(:stock_levels) { 'my_stock_levels' } Spree::OrderPopulator.stub(:new).and_return(populator = double()) populator.stub(:populate) { true } + populator.stub(:variants_h) { {} } xhr :post, :populate, use_route: :spree, format: :json @@ -81,6 +83,7 @@ describe Spree::OrdersController do let!(:order) { create(:order) } let!(:li) { create(:line_item, order: order, variant: v, quantity: 2, max_quantity: 3) } let!(:v) { create(:variant, count_on_hand: 4) } + let!(:v2) { create(:variant, count_on_hand: 2) } before do order.reload @@ -88,17 +91,37 @@ describe Spree::OrdersController do end it "returns a hash with variant id, quantity, max_quantity and stock on hand" do - controller.stock_levels.should == {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}} + controller.stock_levels(order, [v.id]).should == + {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}} + end + + it "includes all line items, even when the variant_id is not specified" do + controller.stock_levels(order, []).should == + {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}} + end + + it "includes an empty quantity entry for variants that aren't in the order" do + controller.stock_levels(order, [v.id, v2.id]).should == + {v.id => {quantity: 2, max_quantity: 3, on_hand: 4}, + v2.id => {quantity: 0, max_quantity: 0, on_hand: 2}} end describe "encoding Infinity" do let!(:v) { create(:variant, on_demand: true, count_on_hand: 0) } it "encodes Infinity as a large, finite integer" do - controller.stock_levels.should == {v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}} + controller.stock_levels(order, [v.id]).should == + {v.id => {quantity: 2, max_quantity: 3, on_hand: 2147483647}} end end end + + it "extracts variant ids from the populator" do + variants_h = [{:variant_id=>"900", :quantity=>2, :max_quantity=>nil}, + {:variant_id=>"940", :quantity=>3, :max_quantity=>3}] + + controller.variant_ids_in(variants_h).should == [900, 940] + end end context "adding a group buy product to the cart" do @@ -118,7 +141,8 @@ describe Spree::OrdersController do it "returns HTTP success when successful" do Spree::OrderPopulator.stub(:new).and_return(populator = double()) - populator.stub(:populate).and_return true + populator.stub(:populate) { true } + populator.stub(:variants_h) { {} } xhr :post, :populate, use_route: :spree, format: :json response.status.should == 200 end diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 58b7795124..6df74e3ede 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -130,21 +130,24 @@ describe 'Cart service', -> describe "when an item is out of stock", -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 5} + stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels {} + Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 0 expect(li.max_quantity).toBeUndefined() it "reduces the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 6} + stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels {} + Cart.compareAndNotifyStockLevels stockLevels expect(li.max_quantity).toEqual 0 it "resets the count on hand available", -> li = {variant: {id: 1, count_on_hand: 10}, quantity: 5} + stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} spyOn(Cart, 'line_items_present').andReturn [li] - Cart.compareAndNotifyStockLevels {} + Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 0 describe "when the quantity available is less than that requested", -> @@ -170,6 +173,38 @@ describe 'Cart service', -> Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 6 + describe "when the client-side quantity has been increased during the request", -> + it "does not reset the quantity", -> + li = {variant: {id: 1}, quantity: 6} + stockLevels = {1: {quantity: 5, on_hand: 6}} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels stockLevels + expect(li.quantity).toEqual 6 + expect(li.max_quantity).toBeUndefined() + + it "does not reset the max_quantity", -> + li = {variant: {id: 1}, quantity: 5, max_quantity: 7} + stockLevels = {1: {quantity: 5, max_quantity: 6, on_hand: 7}} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels stockLevels + expect(li.quantity).toEqual 5 + expect(li.max_quantity).toEqual 7 + + describe "when the client-side quantity has been changed from 0 to 1 during the request", -> + it "does not reset the quantity", -> + li = {variant: {id: 1}, quantity: 1} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels {} + expect(li.quantity).toEqual 1 + expect(li.max_quantity).toBeUndefined() + + it "does not reset the max_quantity", -> + li = {variant: {id: 1}, quantity: 1, max_quantity: 1} + spyOn(Cart, 'line_items_present').andReturn [li] + Cart.compareAndNotifyStockLevels {} + expect(li.quantity).toEqual 1 + expect(li.max_quantity).toEqual 1 + it "pops the queue", -> Cart.update_enqueued = true spyOn(Cart, 'scheduleUpdate') From 23e598f2f8639e8635892cee1bbd360d43bdc675 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 20 Apr 2016 14:45:22 +1000 Subject: [PATCH 030/154] Destroy customer without flash notice --- app/controllers/admin/customers_controller.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index bd865c8130..24c26661e5 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -26,6 +26,23 @@ module Admin end end + # copy of Spree::Admin::ResourceController without flash notice + def destroy + invoke_callbacks(:destroy, :before) + if @object.destroy + invoke_callbacks(:destroy, :after) + respond_with(@object) do |format| + format.html { redirect_to location_after_destroy } + format.js { render partial: "spree/admin/shared/destroy" } + end + else + invoke_callbacks(:destroy, :fails) + respond_with(@object) do |format| + format.html { redirect_to location_after_destroy } + end + end + end + private def collection From 2367b73d3a037a9d7e7a8b981aa29c382a2353b0 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 22 Apr 2016 14:53:08 +1000 Subject: [PATCH 031/154] Revert "Associate new users with existing customer records" This reverts commit a25f4fdf44cb218343a2df3ae94748be892b24bc. Since email addresses are not validated, these associations would allow an attacker to signup with the email address of another person and view their orders. --- app/models/spree/user_decorator.rb | 5 ----- spec/models/spree/user_spec.rb | 17 ----------------- 2 files changed, 22 deletions(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index be60307d52..17473fd521 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -15,7 +15,6 @@ Spree.user_class.class_eval do accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit - after_create :associate_customers after_create :send_signup_confirmation validate :limit_owned_enterprises @@ -42,10 +41,6 @@ Spree.user_class.class_eval do customers.of(enterprise).first end - def associate_customers - Customer.update_all({ user_id: id }, { user_id: nil, email: email }) - end - def send_signup_confirmation Delayed::Job.enqueue ConfirmSignupJob.new(id) end diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 923bc93183..c7d284b143 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -53,23 +53,6 @@ describe Spree.user_class do create(:user) end.to enqueue_job ConfirmSignupJob end - - it "should not create a customer" do - expect do - create(:user) - end.to change(Customer, :count).by(0) - end - - describe "when a customer record exists" do - let!(:customer) { create(:customer, user: nil) } - - it "should not create a customer" do - expect(customer.user).to be nil - user = create(:user, email: customer.email) - customer.reload - expect(customer.user).to eq user - end - end end describe "known_users" do From 7f38f1dd1c63f7b4b804fa05a519fcfb3bfa61e9 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 26 Apr 2016 03:14:13 +0100 Subject: [PATCH 032/154] Exclude Accounts & Billing distributor --- app/models/spree/user_decorator.rb | 5 ++++- spec/features/consumer/account_spec.rb | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 9a992ba910..7e3a38afb2 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -56,7 +56,10 @@ Spree.user_class.class_eval do # Returns Enterprise IDs for distributors that the user has shopped at def enterprises_ordered_from - orders.where(state: :complete).map(&:distributor_id).uniq + orders.where(state: :complete) + .where('distributor_id != ?', Spree::Config.accounts_distributor_id) + .map(&:distributor_id) + .uniq end # Returns orders and their associated payments for all distributors that have been ordered from diff --git a/spec/features/consumer/account_spec.rb b/spec/features/consumer/account_spec.rb index b072a24357..1203d459de 100644 --- a/spec/features/consumer/account_spec.rb +++ b/spec/features/consumer/account_spec.rb @@ -13,6 +13,8 @@ feature %q{ let!(:distributor2) { create(:distributor_enterprise) } let!(:distributor_credit) { create(:distributor_enterprise) } let!(:distributor_without_orders) { create(:distributor_enterprise) } + let!(:accounts_distributor) {create :distributor_enterprise} + let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, user: user) } let!(:d1o1) { create(:completed_order_with_totals, distributor_id: distributor1.id, user_id: user.id, total: 10000)} let!(:d1o2) { create(:order_without_full_payment, distributor_id: distributor1.id, user_id: user.id, total: 5000)} let!(:d2o1) { create(:completed_order_with_totals, distributor_id: distributor2.id, user_id: user.id)} @@ -21,19 +23,24 @@ feature %q{ before do + Spree::Config.accounts_distributor_id = accounts_distributor.id credit_order.update! login_as user visit "/account" end it "shows all hubs that have been ordered from with balance or credit" do + # Single test to avoid re-rendering page expect(page).to have_content distributor1.name expect(page).to have_content distributor2.name expect(page).not_to have_content distributor_without_orders.name + # Exclude the special Accounts & Billing distributor + expect(page).not_to have_content accounts_distributor.name expect(page).to have_content distributor1.name + " " + "Balance due" expect(page).to have_content distributor_credit.name + " Credit" end + it "reveals table of orders for distributors when clicked" do expand_active_table_node distributor1.name expect(page).to have_link "Order " + d1o1.number, href:"/orders/#{d1o1.number}" From 43d6e49c3a372dba3021af7c834d4a7740b8af84 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 26 Apr 2016 03:31:56 +0100 Subject: [PATCH 033/154] Fix spec --- spec/features/consumer/account_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/account_spec.rb b/spec/features/consumer/account_spec.rb index 1203d459de..58d4e1acc6 100644 --- a/spec/features/consumer/account_spec.rb +++ b/spec/features/consumer/account_spec.rb @@ -14,7 +14,7 @@ feature %q{ let!(:distributor_credit) { create(:distributor_enterprise) } let!(:distributor_without_orders) { create(:distributor_enterprise) } let!(:accounts_distributor) {create :distributor_enterprise} - let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, user: user) } + let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: user) } let!(:d1o1) { create(:completed_order_with_totals, distributor_id: distributor1.id, user_id: user.id, total: 10000)} let!(:d1o2) { create(:order_without_full_payment, distributor_id: distributor1.id, user_id: user.id, total: 5000)} let!(:d2o1) { create(:completed_order_with_totals, distributor_id: distributor2.id, user_id: user.id)} From ad6037ac63193a921ed9bb18c118b79acbd19290 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 26 Apr 2016 04:21:50 +0100 Subject: [PATCH 034/154] Fix user spec, check config is set --- app/models/spree/user_decorator.rb | 11 +++++++---- spec/models/spree/user_spec.rb | 8 +++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index ac62fee8d1..011bffc5dd 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -51,10 +51,13 @@ Spree.user_class.class_eval do # Returns Enterprise IDs for distributors that the user has shopped at def enterprises_ordered_from - orders.where(state: :complete) - .where('distributor_id != ?', Spree::Config.accounts_distributor_id) - .map(&:distributor_id) - .uniq + enterprise_ids = orders.where(state: :complete).map(&:distributor_id).uniq + # Exclude the accounts distributor + if Spree::Config.accounts_distributor_id + enterprise_ids = enterprise_ids.keep_if{|a| a != Spree::Config.accounts_distributor_id} + end + enterprise_ids + end # Returns orders and their associated payments for all distributors that have been ordered from diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 0c15da5378..441757bf38 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -90,11 +90,17 @@ describe Spree.user_class do let!(:d1_order_for_u2) { create(:completed_order_with_totals, distributor: distributor1, user_id: u2.id) } let!(:d1o3) { create(:order, state: 'cart', distributor: distributor1, user_id: u1.id) } let!(:d2o1) { create(:completed_order_with_totals, distributor: distributor2, user_id: u2.id) } + let!(:accounts_distributor) {create :distributor_enterprise} + let!(:order_account_invoice) { create(:order, distributor: accounts_distributor, state: 'complete', user: u1) } let!(:completed_payment) { create(:payment, order: d1o1, state: 'completed') } let!(:payment) { create(:payment, order: d1o2, state: 'checkout') } - it "returns enterprises that the user has ordered from" do + before do + Spree::Config.accounts_distributor_id = accounts_distributor.id + end + + it "returns enterprises that the user has ordered from, excluding accounts distributor" do expect(u1.enterprises_ordered_from).to eq [distributor1.id] end From 2b921542a527bc0bb21f59c9281dbe5a366c1e5d Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 26 Apr 2016 04:24:44 +0100 Subject: [PATCH 035/154] Code styling --- app/models/spree/user_decorator.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 011bffc5dd..afd32bd294 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -54,10 +54,9 @@ Spree.user_class.class_eval do enterprise_ids = orders.where(state: :complete).map(&:distributor_id).uniq # Exclude the accounts distributor if Spree::Config.accounts_distributor_id - enterprise_ids = enterprise_ids.keep_if{|a| a != Spree::Config.accounts_distributor_id} + enterprise_ids = enterprise_ids.keep_if { |a| a != Spree::Config.accounts_distributor_id } end enterprise_ids - end # Returns orders and their associated payments for all distributors that have been ordered from From 8f8a1191cb2866d209985ddc7922c039773468a9 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 22 Apr 2016 11:44:04 +1000 Subject: [PATCH 036/154] Remove stock cap on max_quantity --- .../darkswarm/services/cart.js.coffee | 2 +- .../shop_variant_with_group_buy.html.haml | 2 +- app/models/spree/line_item_decorator.rb | 7 +------ app/models/spree/order_populator_decorator.rb | 2 +- spec/features/consumer/shopping/shopping_spec.rb | 16 +++++++++------- .../unit/darkswarm/services/cart_spec.js.coffee | 4 ++-- spec/models/spree/line_item_spec.rb | 4 ++-- spec/models/spree/order_populator_spec.rb | 4 ++-- 8 files changed, 19 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/darkswarm/services/cart.js.coffee b/app/assets/javascripts/darkswarm/services/cart.js.coffee index d75ef7a628..9ee03e26b3 100644 --- a/app/assets/javascripts/darkswarm/services/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/services/cart.js.coffee @@ -54,7 +54,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo if li.quantity > li.variant.count_on_hand li.quantity = li.variant.count_on_hand scope.variants.push li.variant - if li.max_quantity > li.variant.count_on_hand + if li.variant.count_on_hand == 0 && li.max_quantity > li.variant.count_on_hand li.max_quantity = li.variant.count_on_hand scope.variants.push(li.variant) unless li.variant in scope.variants diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index c44a71948b..f2b0b95837 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -18,6 +18,6 @@ "ng-model" => "variant.line_item.max_quantity", placeholder: "{{'shop_variant_quantity_max' | t}}", "ofn-disable-scroll" => true, - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + min: "{{variant.line_item.quantity}}", name: "variant_attributes[{{variant.id}}][max_quantity]", id: "variants_{{variant.id}}_max"} diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index b9f8af577f..8a89d8fdef 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -44,12 +44,7 @@ Spree::LineItem.class_eval do def cap_quantity_at_stock! - attrs = {} - - attrs[:quantity] = variant.on_hand if quantity > variant.on_hand - attrs[:max_quantity] = variant.on_hand if (max_quantity || 0) > variant.on_hand - - update_attributes!(attrs) if attrs.any? + update_attributes!(quantity: variant.on_hand) if quantity > variant.on_hand end diff --git a/app/models/spree/order_populator_decorator.rb b/app/models/spree/order_populator_decorator.rb index a106de6936..db03f1f2e2 100644 --- a/app/models/spree/order_populator_decorator.rb +++ b/app/models/spree/order_populator_decorator.rb @@ -77,7 +77,7 @@ Spree::OrderPopulator.class_eval do on_hand = variant.on_hand on_hand = [quantity, max_quantity].compact.max if Spree::Config.allow_backorders quantity_to_add = [quantity, on_hand].min - max_quantity_to_add = [max_quantity, on_hand].min if max_quantity + max_quantity_to_add = max_quantity # max_quantity is not capped [quantity_to_add, max_quantity_to_add] end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 9dc0a39dac..5b617e7db6 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -288,33 +288,32 @@ feature "As a consumer I want to shop with a distributor", js: true do # Update amount available in product list # If amount falls to zero, variant should be greyed out and input disabled page.should have_selector "#variant-#{variant.id}.out-of-stock" - page.should have_selector "#variants_#{variant.id}_max[max='0']" page.should have_selector "#variants_#{variant.id}_max[disabled='disabled']" end end context "when the update is for another product" do it "updates quantity" do - fill_in "variants[#{variant.id}]", with: '1' + fill_in "variants[#{variant.id}]", with: '2' wait_until { !cart_dirty } - variant.update_attributes! on_hand: 0 + variant.update_attributes! on_hand: 1 fill_in "variants[#{variant2.id}]", with: '1' wait_until { !cart_dirty } within(".out-of-stock-modal") do page.should have_content "stock levels for one or more of the products in your cart have reduced" - page.should have_content "#{product.name} - #{variant.unit_to_display} is now out of stock." + page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining" end end context "group buy products" do let(:product) { create(:simple_product, group_buy: true) } - it "updates max_quantity" do - fill_in "variants[#{variant.id}]", with: '1' - fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '2' + it "does not update max_quantity" do + fill_in "variants[#{variant.id}]", with: '2' + fill_in "variant_attributes[#{variant.id}][max_quantity]", with: '3' wait_until { !cart_dirty } variant.update_attributes! on_hand: 1 @@ -325,6 +324,9 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should have_content "stock levels for one or more of the products in your cart have reduced" page.should have_content "#{product.name} - #{variant.unit_to_display} now only has 1 remaining" end + + page.should have_field "variants[#{variant.id}]", with: '1' + page.should have_field "variant_attributes[#{variant.id}][max_quantity]", with: '3' end end end diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index 6df74e3ede..d985904f81 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -159,12 +159,12 @@ describe 'Cart service', -> expect(li.quantity).toEqual 5 expect(li.max_quantity).toBeUndefined() - it "reduces the max_quantity in the cart", -> + it "does not reduce the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 6, max_quantity: 7} stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}} spyOn(Cart, 'line_items_present').andReturn [li] Cart.compareAndNotifyStockLevels stockLevels - expect(li.max_quantity).toEqual 5 + expect(li.max_quantity).toEqual 7 it "resets the count on hand available", -> li = {variant: {id: 1}, quantity: 6} diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 2b5362e94f..111108f7b4 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -55,9 +55,9 @@ module Spree li.reload.quantity.should == 5 end - it "caps max_quantity" do + it "does not cap max_quantity" do li.cap_quantity_at_stock! - li.reload.max_quantity.should == 5 + li.reload.max_quantity.should == 10 end it "works for products without max_quantity" do diff --git a/spec/models/spree/order_populator_spec.rb b/spec/models/spree/order_populator_spec.rb index 4a50e59fa8..f0bbc81f55 100644 --- a/spec/models/spree/order_populator_spec.rb +++ b/spec/models/spree/order_populator_spec.rb @@ -213,8 +213,8 @@ module Spree op.quantities_to_add(v, 5, 6).should == [5, 6] end - it "returns a limited amount when not entirely available" do - op.quantities_to_add(v, 15, 16).should == [10, 10] + it "also returns the full amount when not entirely available" do + op.quantities_to_add(v, 15, 16).should == [10, 16] end end end From cf40e0432abde6c54c72fbd9c5a437492deaffa2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 12:22:51 +1000 Subject: [PATCH 037/154] When cart is updated with insufficient stock, show amount in cart, not amount entered --- .../spree/orders_controller_decorator.rb | 37 ++++++++++++++ app/views/spree/orders/_line_item.html.haml | 2 +- spec/features/consumer/shopping/cart_spec.rb | 50 +++++++++++++------ 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index e675ce7e94..2c0ff8562c 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -16,6 +16,7 @@ Spree::OrdersController.class_eval do # Patching to redirect to shop if order is empty def edit @order = current_order(true) + @insufficient_stock_lines = @order.insufficient_stock_lines if @order.line_items.empty? redirect_to main_app.shop_path @@ -28,6 +29,41 @@ Spree::OrdersController.class_eval do end end + + def update + @insufficient_stock_lines = [] + @order = current_order + unless @order + flash[:error] = t(:order_not_found) + redirect_to root_path and return + end + + if @order.update_attributes(params[:order]) + @order.line_items = @order.line_items.select {|li| li.quantity > 0 } + @order.restart_checkout_flow + + render :edit and return unless apply_coupon_code + + fire_event('spree.order.contents_changed') + respond_with(@order) do |format| + format.html do + if params.has_key?(:checkout) + @order.next_transition.run_callbacks if @order.cart? + redirect_to checkout_state_path(@order.checkout_steps.first) + else + redirect_to cart_path + end + end + end + else + # Show order with original values, not newly entered ones + @insufficient_stock_lines = @order.insufficient_stock_lines + @order.line_items(true) + respond_with(@order) + end + end + + def populate # Without intervention, the Spree::Adjustment#update_adjustable callback is called many times # during cart population, for both taxation and enterprise fees. This operation triggers a @@ -55,6 +91,7 @@ Spree::OrdersController.class_eval do end end + # Report the stock levels in the order for all variant ids requested def stock_levels(order, variant_ids) stock_levels = li_stock_levels(order) diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml index 6014b617a9..f4415199a4 100644 --- a/app/views/spree/orders/_line_item.html.haml +++ b/app/views/spree/orders/_line_item.html.haml @@ -17,7 +17,7 @@ = render 'spree/shared/line_item_name', line_item: line_item - - if @order.insufficient_stock_lines.include? line_item + - if @insufficient_stock_lines.include? line_item %span.out-of-stock = variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) %br/ diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index a80fd908ff..5097cb50b8 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -7,25 +7,45 @@ feature "full-page cart", js: true do include UIComponentHelper describe "viewing the cart" do + let!(:zone) { create(:zone_with_member) } + let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) } + let(:supplier) { create(:supplier_enterprise) } + let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } + let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) } + let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) } + let(:variant) { product.variants.first } + let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } + + before do + add_enterprise_fee enterprise_fee + set_order order + add_product_to_cart + visit spree.cart_path + end + describe "tax" do - let!(:zone) { create(:zone_with_member) } - let(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) } - let(:supplier) { create(:supplier_enterprise) } - let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } - let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product.tax_category) } - let(:product) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) } - let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } - - before do - add_enterprise_fee enterprise_fee - set_order order - add_product_to_cart - visit spree.cart_path - end - it "shows the total tax for the order, including product tax and tax on fees" do page.should have_selector '.tax-total', text: '11.00' # 10 + 1 end end + + describe "updating quantities with insufficient stock available" do + let(:li) { order.line_items(true).last } + + use_short_wait 10 + + before do + variant.update_attributes! on_hand: 2 + end + + it "shows the quantities saved, not those submitted" do + fill_in "order_line_items_attributes_0_quantity", with: '4' + + click_button 'Update' + + page.should have_field "order[line_items_attributes][0][quantity]", with: '1' + page.should have_content "Insufficient stock available, only 2 remaining" + end + end end end From 36f4df29315f9bfd3338a4449f2704ddb4b3308a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 12:27:20 +1000 Subject: [PATCH 038/154] Allow max value in cart of what's on hand --- app/views/spree/orders/_line_item.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml index f4415199a4..018cd9fa55 100644 --- a/app/views/spree/orders/_line_item.html.haml +++ b/app/views/spree/orders/_line_item.html.haml @@ -30,7 +30,7 @@ -# "price-breakdown-placement" => "left", -# "price-breakdown-animation" => true} %td.text-center.cart-item-quantity{"data-hook" => "cart_item_quantity"} - = item_form.number_field :quantity, :min => 0, :class => "line_item_quantity", :size => 5 + = item_form.number_field :quantity, :min => 0, :max => variant.on_hand, :class => "line_item_quantity", :size => 5 %td.cart-item-total.text-right{"data-hook" => "cart_item_total"} = line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil? From daa5b00a2aa2851bc0a782bea854b3893087af63 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 15 Apr 2016 12:22:17 +1000 Subject: [PATCH 039/154] Uses openstreetmap tiles --- .../darkswarm/directives/map_search.js.coffee | 15 +++++++++++++++ .../services/map_configuration.js.coffee | 9 ++++++--- app/assets/stylesheets/darkswarm/map.css.sass | 13 +++++++++++++ app/views/map/index.html.haml | 5 +++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/darkswarm/directives/map_search.js.coffee b/app/assets/javascripts/darkswarm/directives/map_search.js.coffee index f67407ffb3..20a2cba1d4 100644 --- a/app/assets/javascripts/darkswarm/directives/map_search.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/map_search.js.coffee @@ -7,6 +7,21 @@ Darkswarm.directive 'mapSearch', ($timeout)-> link: (scope, elem, attrs, ctrl)-> $timeout => map = ctrl.getMap() + + map.mapTypes.set 'OSM', new (google.maps.ImageMapType)( + getTileUrl: (coord, zoom) -> + # "Wrap" x (logitude) at 180th meridian properly + # NB: Don't touch coord.x because coord param is by reference, and changing its x property breakes something in Google's lib + tilesPerGlobe = 1 << zoom + x = coord.x % tilesPerGlobe + if x < 0 + x = tilesPerGlobe + x + # Wrap y (latitude) in a like manner if you want to enable vertical infinite scroll + 'http://tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png' + tileSize: new (google.maps.Size)(256, 256) + name: 'OpenStreetMap' + maxZoom: 18) + input = (document.getElementById("pac-input")) map.controls[google.maps.ControlPosition.TOP_LEFT].push input searchBox = new google.maps.places.SearchBox((input)) diff --git a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee index 9c5a375d8c..992872e2da 100644 --- a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee +++ b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee @@ -1,11 +1,14 @@ Darkswarm.factory "MapConfiguration", -> new class MapConfiguration options: - center: + center: latitude: -37.4713077 longitude: 144.7851531 zoom: 12 - additional_options: {} - #mapTypeId: 'satellite' + additional_options: + #mapTypeId: 'satellite' + mapTypeId: 'OSM' + mapTypeControl: false + streetViewControl: false styles: [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]},{"featureType":"road","elementType": "labels.icon","stylers":[{"visibility":"off"}]}] diff --git a/app/assets/stylesheets/darkswarm/map.css.sass b/app/assets/stylesheets/darkswarm/map.css.sass index a831e9448c..8a63803816 100644 --- a/app/assets/stylesheets/darkswarm/map.css.sass +++ b/app/assets/stylesheets/darkswarm/map.css.sass @@ -26,3 +26,16 @@ width: 80% &:active, &:focus, &.active background: rgba(255,255,255, 1) + +.map-footer + position: fixed + font-size: x-small + left: 0 + right: 0 + bottom: 0 + width: 100% + height: 23px + margin: 0 + padding: 6px + z-index: 2 + background: WHITE diff --git a/app/views/map/index.html.haml b/app/views/map/index.html.haml index a4d012282b..30948b8724 100644 --- a/app/views/map/index.html.haml +++ b/app/views/map/index.html.haml @@ -9,3 +9,8 @@ %map-search %markers{models: "OfnMap.enterprises", fit: "true", coords: "'self'", icon: "'icon'", click: "'reveal'"} + +.map-footer + \© + %a{:href => "http://www.openstreetmap.org/copyright"} OpenStreetMap + contributors From 824a29624fbba3bf270e8fa5f17a6d4a2c50e146 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 14:17:29 +1000 Subject: [PATCH 040/154] Tweak the map UI --- .../javascripts/darkswarm/directives/map_search.js.coffee | 3 ++- .../javascripts/darkswarm/services/map_configuration.js.coffee | 2 +- app/assets/stylesheets/darkswarm/map.css.sass | 3 ++- app/views/map/index.html.haml | 3 +-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/darkswarm/directives/map_search.js.coffee b/app/assets/javascripts/darkswarm/directives/map_search.js.coffee index 20a2cba1d4..e67ce529a5 100644 --- a/app/assets/javascripts/darkswarm/directives/map_search.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/map_search.js.coffee @@ -8,6 +8,7 @@ Darkswarm.directive 'mapSearch', ($timeout)-> $timeout => map = ctrl.getMap() + # Use OSM tiles server map.mapTypes.set 'OSM', new (google.maps.ImageMapType)( getTileUrl: (coord, zoom) -> # "Wrap" x (logitude) at 180th meridian properly @@ -36,7 +37,7 @@ Darkswarm.directive 'mapSearch', ($timeout)-> #map.setCenter place.geometry.location map.fitBounds place.geometry.viewport #map.fitBounds bounds - + # Bias the SearchBox results towards places that are within the bounds of the # current map's viewport. google.maps.event.addListener map, "bounds_changed", -> diff --git a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee index 992872e2da..1979affeab 100644 --- a/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee +++ b/app/assets/javascripts/darkswarm/services/map_configuration.js.coffee @@ -6,7 +6,7 @@ Darkswarm.factory "MapConfiguration", -> longitude: 144.7851531 zoom: 12 additional_options: - #mapTypeId: 'satellite' + # mapTypeId: 'satellite' mapTypeId: 'OSM' mapTypeControl: false streetViewControl: false diff --git a/app/assets/stylesheets/darkswarm/map.css.sass b/app/assets/stylesheets/darkswarm/map.css.sass index 8a63803816..4692bdc1b4 100644 --- a/app/assets/stylesheets/darkswarm/map.css.sass +++ b/app/assets/stylesheets/darkswarm/map.css.sass @@ -22,6 +22,7 @@ background: rgba(255,255,255,0.85) width: 50% margin-top: 1.2rem + margin-left: 1rem @media all and (max-width: 768px) width: 80% &:active, &:focus, &.active @@ -38,4 +39,4 @@ margin: 0 padding: 6px z-index: 2 - background: WHITE + background: #fff diff --git a/app/views/map/index.html.haml b/app/views/map/index.html.haml index 30948b8724..97f4eb602d 100644 --- a/app/views/map/index.html.haml +++ b/app/views/map/index.html.haml @@ -12,5 +12,4 @@ .map-footer \© - %a{:href => "http://www.openstreetmap.org/copyright"} OpenStreetMap - contributors + %a{:href => "http://www.openstreetmap.org/copyright"} OpenStreetMap contributors From f691636c757b452f6a173d08e38e5375ec1edcd2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 14:47:45 +1000 Subject: [PATCH 041/154] Fix spec --- spec/controllers/spree/orders_controller_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index 286bb9217a..74e9efaaf9 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -15,6 +15,7 @@ describe Spree::OrdersController do controller.stub(:current_order_cycle).and_return(order_cycle) controller.stub(:current_order).and_return order order.stub_chain(:line_items, :empty?).and_return true + order.stub(:insufficient_stock_lines).and_return [] session[:access_token] = order.token spree_get :edit response.should redirect_to shop_path From 1220ff8a068ff21fce960f06bf04d755c3fd0986 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 15:05:44 +1000 Subject: [PATCH 042/154] Notify when stock limit reached on shopfront rather than silently capping --- .../darkswarm/directives/on_hand.js.coffee | 9 +++++++++ .../partials/shop_variant_no_group_buy.html.haml | 2 +- .../partials/shop_variant_with_group_buy.html.haml | 2 +- spec/features/consumer/shopping/shopping_spec.rb | 11 +++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/directives/on_hand.js.coffee diff --git a/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee new file mode 100644 index 0000000000..0e3a96c608 --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee @@ -0,0 +1,9 @@ +Darkswarm.directive "ofnOnHand", -> + restrict: 'A' + link: (scope, elem, attr) -> + on_hand = parseInt(attr.ofnOnHand) + elem.bind 'change', (e) -> + if parseInt(elem.val()) > on_hand + scope.$apply -> + alert t('insufficient_stock', {on_hand: on_hand}) + elem.val(on_hand) diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml index 36a4e44ade..1153d93b0f 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -7,6 +7,6 @@ placeholder: "0", "ofn-disable-scroll" => true, "ng-model" => "variant.line_item.quantity", - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + "ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}", "ng-disabled" => "!variant.on_demand && variant.count_on_hand == 0", name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index f2b0b95837..6601f0c019 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -8,7 +8,7 @@ "ng-model" => "variant.line_item.quantity", placeholder: "{{'shop_variant_quantity_min' | t}}", "ofn-disable-scroll" => true, - max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", + "ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}", name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} %span.bulk-input %input.bulk.second{type: :number, diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 5b617e7db6..2ca2d63341 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -238,6 +238,17 @@ feature "As a consumer I want to shop with a distributor", js: true do Spree::LineItem.where(id: li).should be_empty end + it "alerts us when we enter a quantity greater than the stock available" do + variant.update_attributes on_hand: 5 + visit shop_path + + accept_alert 'Insufficient stock available, only 5 remaining' do + fill_in "variants[#{variant.id}]", with: '10' + end + + page.should have_field "variants[#{variant.id}]", with: '5' + end + describe "when a product goes out of stock just before it's added to the cart" do it "stops the attempt, shows an error message and refreshes the products asynchronously" do variant.update_attributes! on_hand: 0 From 1384140e41b4266caa8e933d3af4d895357fa4e4 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 15:10:54 +1000 Subject: [PATCH 043/154] Notify when stock limit reached on cart rather than silently capping --- app/views/spree/orders/_line_item.html.haml | 2 +- spec/features/consumer/shopping/cart_spec.rb | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml index 018cd9fa55..3897654467 100644 --- a/app/views/spree/orders/_line_item.html.haml +++ b/app/views/spree/orders/_line_item.html.haml @@ -30,7 +30,7 @@ -# "price-breakdown-placement" => "left", -# "price-breakdown-animation" => true} %td.text-center.cart-item-quantity{"data-hook" => "cart_item_quantity"} - = item_form.number_field :quantity, :min => 0, :max => variant.on_hand, :class => "line_item_quantity", :size => 5 + = item_form.number_field :quantity, :min => 0, "ofn-on-hand" => variant.on_hand, :class => "line_item_quantity", :size => 5 %td.cart-item-total.text-right{"data-hook" => "cart_item_total"} = line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil? diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index 5097cb50b8..ed573d0460 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -32,12 +32,20 @@ feature "full-page cart", js: true do describe "updating quantities with insufficient stock available" do let(:li) { order.line_items(true).last } - use_short_wait 10 - before do variant.update_attributes! on_hand: 2 end + it "prevents me from entering an invalid value" do + visit spree.cart_path + + accept_alert 'Insufficient stock available, only 2 remaining' do + fill_in "order_line_items_attributes_0_quantity", with: '4' + end + + page.should have_field "order_line_items_attributes_0_quantity", with: '2' + end + it "shows the quantities saved, not those submitted" do fill_in "order_line_items_attributes_0_quantity", with: '4' From 8996acf314adc698aa36faa9c946126541813a0d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 27 Apr 2016 15:25:05 +1000 Subject: [PATCH 044/154] Fix spec --- spec/features/consumer/shopping/shopping_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 2ca2d63341..dceecd43f8 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -270,7 +270,7 @@ feature "As a consumer I want to shop with a distributor", js: true do # Update amount available in product list # If amount falls to zero, variant should be greyed out and input disabled page.should have_selector "#variant-#{variant.id}.out-of-stock" - page.should have_selector "#variants_#{variant.id}[max='0']" + page.should have_selector "#variants_#{variant.id}[ofn-on-hand='0']" page.should have_selector "#variants_#{variant.id}[disabled='disabled']" end From 65895752dacf1541e25f969867a713067e1ee5c0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Apr 2016 11:49:30 +1000 Subject: [PATCH 045/154] Remove cruft --- app/assets/javascripts/admin/directives/track_master.js.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/directives/track_master.js.coffee b/app/assets/javascripts/admin/directives/track_master.js.coffee index ce26a4aa32..be84e0b204 100644 --- a/app/assets/javascripts/admin/directives/track_master.js.coffee +++ b/app/assets/javascripts/admin/directives/track_master.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (DirtyProducts) -> +angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) -> require: "ngModel" link: (scope, element, attrs, ngModel) -> ngModel.$parsers.push (viewValue) -> @@ -6,4 +6,3 @@ angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (Dirty DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue scope.displayDirtyProducts() viewValue - ] \ No newline at end of file From 88e9eb59cf5de3453965bca310bd72dddcaf8a40 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Apr 2016 11:56:48 +1000 Subject: [PATCH 046/154] Do not allow invalid quantity to reach model, triggering server update --- .../darkswarm/directives/on_hand.js.coffee | 24 +++++++++++++------ app/views/spree/orders/_line_item.html.haml | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee index 0e3a96c608..610b11d3ca 100644 --- a/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/on_hand.js.coffee @@ -1,9 +1,19 @@ Darkswarm.directive "ofnOnHand", -> restrict: 'A' - link: (scope, elem, attr) -> - on_hand = parseInt(attr.ofnOnHand) - elem.bind 'change', (e) -> - if parseInt(elem.val()) > on_hand - scope.$apply -> - alert t('insufficient_stock', {on_hand: on_hand}) - elem.val(on_hand) + require: "ngModel" + + link: (scope, elem, attr, ngModel) -> + # In cases where this field gets its value from the HTML element rather than the model, + # initialise the model with the HTML value. + if scope.$eval(attr.ngModel) == undefined + ngModel.$setViewValue elem.val() + + ngModel.$parsers.push (viewValue) -> + on_hand = parseInt(attr.ofnOnHand) + if parseInt(viewValue) > on_hand + alert t('insufficient_stock', {on_hand: on_hand}) + viewValue = on_hand + ngModel.$setViewValue viewValue + ngModel.$render() + + viewValue diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml index 3897654467..5976332a18 100644 --- a/app/views/spree/orders/_line_item.html.haml +++ b/app/views/spree/orders/_line_item.html.haml @@ -30,7 +30,7 @@ -# "price-breakdown-placement" => "left", -# "price-breakdown-animation" => true} %td.text-center.cart-item-quantity{"data-hook" => "cart_item_quantity"} - = item_form.number_field :quantity, :min => 0, "ofn-on-hand" => variant.on_hand, :class => "line_item_quantity", :size => 5 + = item_form.number_field :quantity, :min => 0, "ofn-on-hand" => variant.on_hand, "ng-model" => "line_item_#{line_item.id}", :class => "line_item_quantity", :size => 5 %td.cart-item-total.text-right{"data-hook" => "cart_item_total"} = line_item.display_amount_with_adjustments.to_html unless line_item.quantity.nil? From 36a4aab020edc8c968e2f3754bc07854df9b01b8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 29 Apr 2016 15:07:55 +1000 Subject: [PATCH 047/154] Adjust styling to blend --- app/assets/stylesheets/darkswarm/map.css.sass | 20 +++++++++++++------ app/views/map/index.html.haml | 3 +-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/map.css.sass b/app/assets/stylesheets/darkswarm/map.css.sass index 4692bdc1b4..e2a25d61c9 100644 --- a/app/assets/stylesheets/darkswarm/map.css.sass +++ b/app/assets/stylesheets/darkswarm/map.css.sass @@ -30,13 +30,21 @@ .map-footer position: fixed - font-size: x-small - left: 0 - right: 0 - bottom: 0 + z-index: 2 width: 100% height: 23px + left: 80px + right: 0 + bottom: 6px margin: 0 padding: 6px - z-index: 2 - background: #fff + font-size: 14px + font-weight: bold + text-shadow: 2px 2px #aaa + color: #fff + + a, a:hover, a:active, a:focus + color: #fff + + @media all and (max-width: 1025px) + left: 0px \ No newline at end of file diff --git a/app/views/map/index.html.haml b/app/views/map/index.html.haml index 97f4eb602d..e4a22e540c 100644 --- a/app/views/map/index.html.haml +++ b/app/views/map/index.html.haml @@ -11,5 +11,4 @@ coords: "'self'", icon: "'icon'", click: "'reveal'"} .map-footer - \© - %a{:href => "http://www.openstreetmap.org/copyright"} OpenStreetMap contributors + %a{:href => "http://www.openstreetmap.org/copyright"} © OpenStreetMap contributors From fa5fa9e2286554617996182424056f9f29569edd Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 29 Apr 2016 12:33:33 +1000 Subject: [PATCH 048/154] Auto-complete tags on customers page - new controller serving tags for an enterprise as JSON - customers page suggesting these tags - emphasising tags that have rules --- .../customers_controller.js.coffee | 12 +++++++- .../services/tags_resource.js.coffee | 9 ++++++ .../admin/filters/translate.js.coffee | 7 ----- .../shipping_methods.js.coffee | 2 +- .../tags_with_translation.js.coffee | 3 +- .../admin/utils/filters/translate.js.coffee | 7 +++++ .../javascripts/templates/admin/tag.html.haml | 8 +++++ .../admin/tag_autocomplete.html.haml | 11 +++++++ .../templates/admin/tags_input.html.haml | 7 +++++ .../stylesheets/admin/customers.css.scss | 3 ++ app/controllers/admin/tags_controller.rb | 28 ++++++++++++++++++ app/models/enterprise.rb | 14 +++++++++ app/models/spree/ability_decorator.rb | 9 ++---- .../api/admin/customer_serializer.rb | 6 +++- app/views/admin/customers/index.html.haml | 2 +- config/locales/en.yml | 4 +++ config/routes.rb | 2 ++ .../customers_controller_spec.js.coffee | 29 +++++++++++++++++++ .../admin/customer_serializer_spec.rb | 14 +++++++++ 19 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/admin/customers/services/tags_resource.js.coffee delete mode 100644 app/assets/javascripts/admin/filters/translate.js.coffee create mode 100644 app/assets/javascripts/admin/utils/filters/translate.js.coffee create mode 100644 app/assets/javascripts/templates/admin/tag.html.haml create mode 100644 app/assets/javascripts/templates/admin/tag_autocomplete.html.haml create mode 100644 app/assets/javascripts/templates/admin/tags_input.html.haml create mode 100644 app/assets/stylesheets/admin/customers.css.scss create mode 100644 app/controllers/admin/tags_controller.rb create mode 100644 spec/serializers/admin/customer_serializer_spec.rb diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index d2e6d58562..bfccfd3f4b 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, Columns, pendingChanges, shops) -> +angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) -> $scope.shop = {} $scope.shops = shops $scope.submitAll = pendingChanges.submitAll @@ -12,6 +12,16 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerR if $scope.shop.id? $scope.customers = index {enterprise_id: $scope.shop.id} + $scope.findTags = (query) -> + defer = $q.defer() + params = + enterprise_id: $scope.shop.id + TagsResource.index params, (data) => + filtered = data.filter (tag) -> + tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 + defer.resolve filtered + defer.promise + $scope.add = (email) -> params = enterprise_id: $scope.shop.id diff --git a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee b/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee new file mode 100644 index 0000000000..ad89511e32 --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee @@ -0,0 +1,9 @@ +angular.module("admin.customers").factory 'TagsResource', ($resource) -> + $resource('/admin/tags.json', {}, { + 'index': + method: 'GET' + isArray: true + cache: true + params: + enterprise_id: '@enterprise_id' + }) diff --git a/app/assets/javascripts/admin/filters/translate.js.coffee b/app/assets/javascripts/admin/filters/translate.js.coffee deleted file mode 100644 index 20becc147a..0000000000 --- a/app/assets/javascripts/admin/filters/translate.js.coffee +++ /dev/null @@ -1,7 +0,0 @@ -angular.module('ofn.admin').filter "translate", -> - (key, options) -> - t(key, options) - -angular.module('ofn.admin').filter "t", -> - (key, options) -> - t(key, options) diff --git a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee index 232eee7045..863163a9ef 100644 --- a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee @@ -1 +1 @@ -angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils']) +angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates']) diff --git a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee index 6ce7953608..7a6ae9afff 100644 --- a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee @@ -1,10 +1,11 @@ angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) -> restrict: "E" - template: "" + templateUrl: "admin/tags_input.html" scope: object: "=" tagsAttr: "@?" tagListAttr: "@?" + findTags: "&" link: (scope, element, attrs) -> $timeout -> scope.tagsAttr ||= "tags" diff --git a/app/assets/javascripts/admin/utils/filters/translate.js.coffee b/app/assets/javascripts/admin/utils/filters/translate.js.coffee new file mode 100644 index 0000000000..1a0602a59d --- /dev/null +++ b/app/assets/javascripts/admin/utils/filters/translate.js.coffee @@ -0,0 +1,7 @@ +angular.module("admin.utils").filter "translate", -> + (key, options) -> + t(key, options) + +angular.module("admin.utils").filter "t", -> + (key, options) -> + t(key, options) diff --git a/app/assets/javascripts/templates/admin/tag.html.haml b/app/assets/javascripts/templates/admin/tag.html.haml new file mode 100644 index 0000000000..c4afcfa5ad --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag.html.haml @@ -0,0 +1,8 @@ +.tag-template + %div + %span.tag-with-rules{ ng: { if: "data.rules" }, "ofn-with-tip" => "{{ 'admin.tag_has_rules' | t:{num: data.rules} }}" } + {{$getDisplayText()}} + %span{ ng: { if: "!data.rules" } } + {{$getDisplayText()}} + %a.remove-button{ ng: {click: "$removeTag()"} } + ✖ diff --git a/app/assets/javascripts/templates/admin/tag_autocomplete.html.haml b/app/assets/javascripts/templates/admin/tag_autocomplete.html.haml new file mode 100644 index 0000000000..49ff00ff27 --- /dev/null +++ b/app/assets/javascripts/templates/admin/tag_autocomplete.html.haml @@ -0,0 +1,11 @@ +.autocomplete-template + %span.tag-with-rules{ ng: { if: "data.rules" } } + {{$getDisplayText()}} + %span.tag-with-rules{ ng: { if: "data.rules == 1" } } + — + = t 'admin.has_one_rule' + %span.tag-with-rules{ ng: { if: "data.rules > 1" } } + — + = t 'admin.has_n_rules', { num: '{{data.rules}}' } + %span{ ng: { if: "!data.rules" } } + {{$getDisplayText()}} diff --git a/app/assets/javascripts/templates/admin/tags_input.html.haml b/app/assets/javascripts/templates/admin/tags_input.html.haml new file mode 100644 index 0000000000..2dd75ce5ac --- /dev/null +++ b/app/assets/javascripts/templates/admin/tags_input.html.haml @@ -0,0 +1,7 @@ +%tags-input{ template: 'admin/tag.html', ng: { model: 'object[tagsAttr]' } } + %auto-complete{source: "findTags({query: $query})", + template: "admin/tag_autocomplete.html", + "min-length" => "0", + "load-on-focus" => "true", + "load-on-empty" => "true", + "max-results-to-show" => "32"} diff --git a/app/assets/stylesheets/admin/customers.css.scss b/app/assets/stylesheets/admin/customers.css.scss new file mode 100644 index 0000000000..e3c427649c --- /dev/null +++ b/app/assets/stylesheets/admin/customers.css.scss @@ -0,0 +1,3 @@ +.tag-with-rules { + color: black; +} diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb new file mode 100644 index 0000000000..3ab5685ffe --- /dev/null +++ b/app/controllers/admin/tags_controller.rb @@ -0,0 +1,28 @@ +module Admin + class TagsController < Spree::Admin::BaseController + respond_to :json + + def index + respond_to do |format| + format.json do + serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise) + render json: serialiser.to_json + end + end + end + + private + + def enterprise + Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id]) + end + + def tags_of_enterprise + return [] unless enterprise + tag_rule_map = enterprise.rules_per_tag + tag_rule_map.keys.map do |tag| + { text: tag, rules: tag_rule_map[tag] } + end + end + end +end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index dfc5050a38..d3e86ff3ca 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -352,6 +352,20 @@ class Enterprise < ActiveRecord::Base end end + def rules_per_tag + tag_rule_map = {} + tag_rules.each do |rule| + rule.preferred_customer_tags.split(",").each do |tag| + if tag_rule_map[tag] + tag_rule_map[tag] += 1 + else + tag_rule_map[tag] = 1 + end + end + end + tag_rule_map + end + protected def devise_mailer diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 867d3c9e2b..e250d1341d 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -101,11 +101,6 @@ class AbilityDecorator can [:print], Spree::Order do |order| order.user == user end - - can [:create], Customer - can [:destroy], Customer do |customer| - user.enterprises.include? customer.enterprise - end end def add_product_management_abilities(user) @@ -221,7 +216,9 @@ class AbilityDecorator # Reports page can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report - can [:admin, :index, :update], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) + can [:create], Customer + can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) + can [:admin, :index], :tag end diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 3cb9518a9f..052f49a917 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -6,6 +6,10 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer end def tags - object.tag_list.map{ |t| { text: t } } + tag_rule_map = object.enterprise.rules_per_tag + object.tag_list.map do |tag| + { text: tag, rules: tag_rule_map[tag] } + end end + end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index e33b871df4..0a348d4a0a 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -56,7 +56,7 @@ %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } %td.tags{ 'ng-show' => 'columns.tags.visible' } .tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"} - %tags_with_translation{ object: 'customer' } + %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' } diff --git a/config/locales/en.yml b/config/locales/en.yml index 7b5f9b168f..e9de6c9abd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -80,6 +80,10 @@ en: whats_this: What's this? + tag_has_rules: "Existing rules for this tag: %{num}" + has_one_rule: "has one rule" + has_n_rules: "has %{num} rules" + customers: index: add_customer: "Add customer" diff --git a/config/routes.rb b/config/routes.rb index 8d8d6d27c3..e240f901a8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,6 +117,8 @@ Openfoodnetwork::Application.routes.draw do resources :customers, only: [:index, :create, :update, :destroy] + resources :tags, only: [:index], format: :json + resource :content resource :accounts_and_billing_settings, only: [:edit, :update] do diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 215f2834ed..6981ef4e4f 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -47,3 +47,32 @@ describe "CustomersCtrl", -> http.flush() expect(scope.customers.length).toBe 1 expect(scope.customers[0]).not.toAngularEqual customer + + describe "scope.findTags", -> + tags = [ + { text: 'one' } + { text: 'two' } + { text: 'three' } + ] + beforeEach -> + http.expectGET('/admin/tags.json?enterprise_id=1').respond 200, tags + + it "retrieves the tag list", -> + promise = scope.findTags('') + result = null + promise.then (data) -> + result = data + http.flush() + expect(result).toAngularEqual tags + + it "filters the tag list", -> + filtered_tags = [ + { text: 'two' } + { text: 'three' } + ] + promise = scope.findTags('t') + result = null + promise.then (data) -> + result = data + http.flush() + expect(result).toAngularEqual filtered_tags diff --git a/spec/serializers/admin/customer_serializer_spec.rb b/spec/serializers/admin/customer_serializer_spec.rb new file mode 100644 index 0000000000..d63f00d4aa --- /dev/null +++ b/spec/serializers/admin/customer_serializer_spec.rb @@ -0,0 +1,14 @@ +describe Api::Admin::CustomerSerializer do + let(:customer) { create(:customer, tag_list: "one, two, three") } + let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") } + + it "serializes a customer" do + serializer = Api::Admin::CustomerSerializer.new customer + result = JSON.parse(serializer.to_json) + expect(result['email']).to eq customer.email + tags = result['tags'] + expect(tags.length).to eq 3 + expect(tags[0]).to eq({ "text" => 'one', "rules" => nil }) + expect(tags[1]).to eq({ "text" => 'two', "rules" => 1 }) + end +end From bed7ec5953e0bc17d5fb9e18ab46a5e047913a83 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 1 May 2016 12:59:55 +0100 Subject: [PATCH 049/154] Add total to producer emails --- app/mailers/producer_mailer.rb | 5 +++++ app/views/producer_mailer/order_cycle_report.text.haml | 3 +++ spec/mailers/producer_mailer_spec.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index b9fb52fb41..d65d722894 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -6,6 +6,7 @@ class ProducerMailer < Spree::BaseMailer @order_cycle = order_cycle @line_items = aggregated_line_items_from(@order_cycle, @producer) @receival_instructions = @order_cycle.receival_instructions_for @producer + @total = total_from_line_items(@line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -49,4 +50,8 @@ class ProducerMailer < Spree::BaseMailer lis end end + + def total_from_line_items(aggregated_line_items) + Spree::Money.new(aggregated_line_items.values.map(&:display_amount).reduce(:+)).to_s + end end diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index 93748395ce..ea035391b8 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -15,6 +15,9 @@ Here is a summary of the orders for your products: - @line_items.each_pair do |variant, line_item| #{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_full_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount} \ +\ +Total: #{@total} +\ Thanks and best wishes, #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 0c7e82b89d..4e3fd912d2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -62,6 +62,10 @@ describe ProducerMailer do mail.body.should_not include p3.name end + it "includes the total" do + mail.body.should include 'Total: $20.00' + end + it "sends no mail when the producer has no orders" do expect do ProducerMailer.order_cycle_report(s3, order_cycle).deliver From 9ac6de4215005889d757a0cb0d1b89a47d2ee397 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 4 May 2016 11:42:07 +1000 Subject: [PATCH 050/154] Admin can set bugherd API key --- .../general_settings_controller_decorator.rb | 15 +++++++++++++ .../spree/app_configuration_decorator.rb | 3 +++ spec/features/admin/external_services_spec.rb | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 app/controllers/spree/admin/general_settings_controller_decorator.rb create mode 100644 spec/features/admin/external_services_spec.rb diff --git a/app/controllers/spree/admin/general_settings_controller_decorator.rb b/app/controllers/spree/admin/general_settings_controller_decorator.rb new file mode 100644 index 0000000000..603f74bf63 --- /dev/null +++ b/app/controllers/spree/admin/general_settings_controller_decorator.rb @@ -0,0 +1,15 @@ +module Spree + module Admin + GeneralSettingsController.class_eval do + end + + + module GeneralSettingsEditPreferences + def edit + super + @preferences_general << :bugherd_api_key + end + end + GeneralSettingsController.send(:prepend, GeneralSettingsEditPreferences) + end +end diff --git a/app/models/spree/app_configuration_decorator.rb b/app/models/spree/app_configuration_decorator.rb index 6ef1e7b848..c169ce0231 100644 --- a/app/models/spree/app_configuration_decorator.rb +++ b/app/models/spree/app_configuration_decorator.rb @@ -23,4 +23,7 @@ Spree::AppConfiguration.class_eval do # Monitoring preference :last_job_queue_heartbeat_at, :string, default: nil + + # External services + preference :bugherd_api_key, :string, default: nil end diff --git a/spec/features/admin/external_services_spec.rb b/spec/features/admin/external_services_spec.rb new file mode 100644 index 0000000000..ed81c8e6fa --- /dev/null +++ b/spec/features/admin/external_services_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +feature 'External services' do + include AuthenticationWorkflow + + describe "bugherd" do + before do + Spree::Config.bugherd_api_key = nil + login_to_admin_section + end + + it "lets me set an API key" do + visit spree.edit_admin_general_settings_path + + fill_in 'bugherd_api_key', with: 'abc123' + click_button 'Update' + + page.should have_content 'General Settings has been successfully updated!' + expect(Spree::Config.bugherd_api_key).to eq 'abc123' + end + end +end From a11696b85ea5dfdb682b893118a6c1fe0f66e610 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 4 May 2016 12:13:03 +1000 Subject: [PATCH 051/154] Include BugHerd script only if configured, and with configured API key --- app/views/layouts/_bugherd_script.html.haml | 14 +---- .../consumer/external_services_spec.rb | 57 +++++++++++++++++++ spec/support/request/web_helper.rb | 18 ++++++ 3 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 spec/features/consumer/external_services_spec.rb diff --git a/app/views/layouts/_bugherd_script.html.haml b/app/views/layouts/_bugherd_script.html.haml index ad6fe585f5..8a04160cfd 100644 --- a/app/views/layouts/_bugherd_script.html.haml +++ b/app/views/layouts/_bugherd_script.html.haml @@ -1,18 +1,8 @@ -- if Rails.env.staging? or Rails.env.production? +- if (Rails.env.staging? || Rails.env.production?) && Spree::Config.bugherd_api_key.present? :javascript (function (d, t) { var bh = d.createElement(t), s = d.getElementsByTagName(t)[0]; bh.type = 'text/javascript'; - bh.src = '//www.bugherd.com/sidebarv2.js?apikey=4ftxjbgwx7y6ssykayr04w'; + bh.src = '//www.bugherd.com/sidebarv2.js?apikey=#{Spree::Config.bugherd_api_key}'; s.parentNode.insertBefore(bh, s); })(document, 'script'); - - --#- elsif Rails.env.production? - -#:javascript - -#(function (d, t) { - -#var bh = d.createElement(t), s = d.getElementsByTagName(t)[0]; - -#bh.type = 'text/javascript'; - -#bh.src = '//www.bugherd.com/sidebarv2.js?apikey=xro3uv55objies58o2wrua'; - -#s.parentNode.insertBefore(bh, s); - -#})(document, 'script'); diff --git a/spec/features/consumer/external_services_spec.rb b/spec/features/consumer/external_services_spec.rb new file mode 100644 index 0000000000..28be1f8cb6 --- /dev/null +++ b/spec/features/consumer/external_services_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +feature 'External services' do + include AuthenticationWorkflow + include WebHelper + + describe "bugherd" do + describe "limiting inclusion by environment" do + before { Spree::Config.bugherd_api_key = 'abc123' } + + it "is not included in test" do + visit root_path + expect(script_content(with: 'bugherd')).to be_nil + end + + it "is not included in dev" do + Rails.env.stub(:development?) { true } + visit root_path + expect(script_content(with: 'bugherd')).to be_nil + end + + it "is included in staging" do + Rails.env.stub(:staging?) { true } + visit root_path + expect(script_content(with: 'bugherd')).not_to be_nil + end + + it "is included in production" do + Rails.env.stub(:production?) { true } + visit root_path + expect(script_content(with: 'bugherd')).not_to be_nil + end + end + + context "in an environment where BugHerd is displayed" do + before { Rails.env.stub(:staging?) { true } } + + context "when there is no API key set" do + before { Spree::Config.bugherd_api_key = nil } + + it "does not include the BugHerd script" do + visit root_path + expect(script_content(with: 'bugherd')).to be_nil + end + end + + context "when an API key is set" do + before { Spree::Config.bugherd_api_key = 'abc123' } + + it "includes the BugHerd script, with the correct API key" do + visit root_path + expect(script_content(with: 'bugherd')).to include 'abc123' + end + end + end + end +end diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 30ce677c6c..70c803709e 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -115,6 +115,24 @@ module WebHelper DirtyFormDialog.new(page) end + # Fetch the content of a script block + # eg. script_content with: 'my-script.com' + # Returns nil if not found + # Raises an exception if multiple matching blocks are found + def script_content(opts={}) + elems = page.all('script', visible: false) + + elems = elems.to_a.select { |e| e.text(:all).include? opts[:with] } if opts[:with] + + if elems.none? + nil + elsif elems.many? + raise "Multiple results returned for script_content" + else + elems.first.text(:all) + end + end + # http://www.elabs.se/blog/53-why-wait_until-was-removed-from-capybara # Do not use this without good reason. Capybara's built-in waiting is very effective. def wait_until(secs=nil) From ef0e41e624ec626c17cb17855e75cd23e0b14470 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 20 Apr 2016 15:31:11 +1000 Subject: [PATCH 052/154] Use save bar on order cycle form --- .../admin/order_cycles/controllers/edit.js.coffee | 5 +++++ app/assets/javascripts/templates/admin/save_bar.html.haml | 2 +- app/views/admin/order_cycles/edit.html.haml | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index fd426eb455..a17f52e42c 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -12,6 +12,9 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -81,4 +84,6 @@ angular.module('admin.orderCycles') OrderCycle.removeDistributionOfVariant(variant_id) $scope.submit = (destination) -> + StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) + this.order_cycle_form.$setPristine() diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 452e81f6e3..ac4fa55de5 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,4 +1,4 @@ -#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } +#save-bar.animate-show{ ng: { show: 'StatusMessage.active()' } } .twelve.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index f3b0bdb721..6ecb65d87e 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -27,7 +27,9 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller} do |f| += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + %save-bar{ save: "submit()", form: "order_cycle_form" } + - if order_cycles_simple_form = render 'simple_form', f: f - else From 00858656b5b4f16cf4e9d04c72529663db9704c0 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 12:15:20 +1000 Subject: [PATCH 053/154] Extend save_bar directive to support more buttons --- .../javascripts/admin/utils/directives/save_bar.js.coffee | 1 + app/assets/javascripts/templates/admin/save_bar.html.haml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 13e4f84bc6..9cd25d97cd 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -3,6 +3,7 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> scope: save: "&" form: "=" + buttons: "=" templateUrl: "admin/save_bar.html" link: (scope, element, attrs) -> scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index ac4fa55de5..83beac81a5 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -3,4 +3,6 @@ %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } } + + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "save({param: button.param})" } } + From 33d0f9fc1b2598aa27d161d17d3e763894a65adf Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 12:16:13 +1000 Subject: [PATCH 054/154] Use save_bar on order cycle update form --- app/views/admin/order_cycles/_form.html.haml | 14 ++++++-------- .../admin/order_cycles/_simple_form.html.haml | 13 +++++-------- app/views/admin/order_cycles/edit.html.haml | 3 ++- .../admin/variant_overrides/_products.html.haml | 2 +- .../spree/admin/orders/bulk_management.html.haml | 2 +- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index c447d35d58..13b317c314 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -52,14 +52,12 @@ .actions - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + + %span{'ng-show' => 'loaded()'} + or + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... + = render 'spree/admin/shared/status_message' - unless Rails.env.production? diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 4dc64a012b..34ea062102 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -23,12 +23,9 @@ .actions - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + or + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... + = render 'spree/admin/shared/status_message' diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 6ecb65d87e..4b2a397e49 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -28,7 +28,8 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ save: "submit()", form: "order_cycle_form" } + + %save-bar{ buttons: "[{text: 'Update', param: null}, {text: 'Update and Close', param: '#{main_app.admin_order_cycles_path}'}]", save: "submit(param)", form: "order_cycle_form" } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index c17e4c189a..70ae2c79bf 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,5 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ save: "update()", form: "variant_overrides_form" } + %save-bar{ save: "update()", form: "variant_overrides_form", buttons: "[{text: 'Save Changes'}]" } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 43983e87ea..b4d7de740d 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", form: "bulk_order_form" } + %save-bar{ save: "submit()", form: "bulk_order_form", buttons: "[{text: 'Save Changes'}]" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } From 70ce58f5e1d87dbc57ae831469c4ea29d257894f Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 13:47:04 +1000 Subject: [PATCH 055/154] Tweak save_bar css --- app/assets/stylesheets/admin/components/save_bar.sass | 2 +- app/assets/stylesheets/admin/openfoodnetwork.css.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index c6b1236490..fcc60dbfc3 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -3,7 +3,7 @@ bottom: 0px padding: 8px 10px font-weight: bold - background-color: #fff + background-color: #eff5fc color: #5498da h5 color: #5498da diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 17d43ff3a5..1fc95face8 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -37,6 +37,7 @@ text-angular .ta-editor { input.red { background-color: #DA5354; + margin-right: 5px; } input.search { From 63b644551cef53c0d484369d04a029987abc8fc0 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 29 Apr 2016 10:43:28 +1000 Subject: [PATCH 056/154] Keep the action with save button --- .../javascripts/admin/order_cycles/controllers/edit.js.coffee | 2 +- .../javascripts/admin/utils/directives/save_bar.js.coffee | 1 - app/assets/javascripts/templates/admin/save_bar.html.haml | 4 ++-- app/views/admin/order_cycles/edit.html.haml | 2 +- app/views/admin/variant_overrides/_products.html.haml | 2 +- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index a17f52e42c..068447d161 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -86,4 +86,4 @@ angular.module('admin.orderCycles') $scope.submit = (destination) -> StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) - this.order_cycle_form.$setPristine() + $scope.order_cycle_form.$setPristine() diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 9cd25d97cd..0999679394 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -1,7 +1,6 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> restrict: "E" scope: - save: "&" form: "=" buttons: "=" templateUrl: "admin/save_bar.html" diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 83beac81a5..ddc535d001 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,8 +1,8 @@ -#save-bar.animate-show{ ng: { show: 'StatusMessage.active()' } } +#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } .twelve.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "save({param: button.param})" } } + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 4b2a397e49..4f3e5d8516 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -29,7 +29,7 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ buttons: "[{text: 'Update', param: null}, {text: 'Update and Close', param: '#{main_app.admin_order_cycles_path}'}]", save: "submit(param)", form: "order_cycle_form" } + %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}' }]", form: "order_cycle_form" } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index 70ae2c79bf..34c8d02bb2 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,5 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ save: "update()", form: "variant_overrides_form", buttons: "[{text: 'Save Changes'}]" } + %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update }]" } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index b4d7de740d..2bd58a4cc5 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", form: "bulk_order_form", buttons: "[{text: 'Save Changes'}]" } + %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit }]" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } From 885d489bc3431f09ee8677ff381fd162ad62e4a9 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 29 Apr 2016 15:23:19 +1000 Subject: [PATCH 057/154] Fix failed test for the default form --- .../order_cycles/controllers/edit.js.coffee | 1 + spec/features/admin/order_cycles_spec.rb | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index 068447d161..db63a8cbaa 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -63,6 +63,7 @@ angular.module('admin.orderCycles') $scope.removeExchange = ($event, exchange) -> $event.preventDefault() OrderCycle.removeExchange(exchange) + $scope.order_cycle_form.$dirty = true $scope.addCoordinatorFee = ($event) -> $event.preventDefault() diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index aafaa5ee77..4827f9b916 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -317,10 +317,10 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } + page.all("table.exchanges tr.supplier td.products input").each { |e| e.trigger('click') } page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true - page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).trigger('click') # uncheck (with visible:true filter) check "order_cycle_incoming_exchange_2_variants_#{v1.id}" check "order_cycle_incoming_exchange_2_variants_#{v2.id}" @@ -343,7 +343,7 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - page.all("table.exchanges tr.distributor td.products input").each { |e| e.click } + page.all("table.exchanges tr.distributor td.products input").each { |e| e.trigger('click') } uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -359,7 +359,8 @@ feature %q{ select 'Distributor fee 2', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' # And I click Update - click_button 'Update and Close' + expect(page).to have_selector "#save-bar" + find_button('Update and Close').trigger('click') # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' @@ -607,9 +608,9 @@ feature %q{ page.all('tr.supplier').count.should == 3 page.all('tr.distributor').count.should == 3 - # When I save, then those exchanges should remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # # When I save, then those exchanges should remain + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -699,8 +700,8 @@ feature %q{ expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true # When I save, any exchanges that I can't manage remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -752,8 +753,8 @@ feature %q{ expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true # When I save, any exchange that I can't manage remains - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] From 019e16c5ba87b403370e992e0208de559546ebd1 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Tue, 3 May 2016 22:58:09 +1000 Subject: [PATCH 058/154] Update AdminEditOrderCycleCtrl unit tests --- spec/javascripts/unit/order_cycle_spec.js.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 973b348383..2f1893bc43 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -9,7 +9,7 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = { order_cycle_form: { $dirty: false}} event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = @@ -169,7 +169,9 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = + order_cycle_form: jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine']) + $watch: jasmine.createSpy('$watch') event = preventDefault: jasmine.createSpy('preventDefault') location = @@ -292,6 +294,7 @@ describe 'OrderCycle controllers', -> scope.removeExchange(event, 'exchange') expect(event.preventDefault).toHaveBeenCalled() expect(OrderCycle.removeExchange).toHaveBeenCalledWith('exchange') + expect(scope.order_cycle_form.$dirty).toEqual true it 'Adds coordinator fees', -> scope.addCoordinatorFee(event) @@ -320,6 +323,7 @@ describe 'OrderCycle controllers', -> it 'Submits the order cycle via OrderCycle update', -> scope.submit('/admin/order_cycles') expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') + expect(scope.order_cycle_form.$setPristine.calls.length).toEqual 1 describe 'OrderCycle services', -> From 1833f0dd5d2283c6e0e52b82325ba9073aeec6b8 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 11:22:25 +1000 Subject: [PATCH 059/154] Add save_bar to the order cycles simple editing form --- .../order_cycles/controllers/simple_edit.js.coffee | 4 ++++ spec/features/admin/order_cycles_spec.rb | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index bf00bb3df1..99c2e2649c 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -9,6 +9,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) => $scope.init() + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -35,5 +38,6 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", OrderCycle.removeCoordinatorFee(index) $scope.submit = (destination) -> + StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 4827f9b916..8b451fa7c0 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -889,10 +889,10 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' # And I make some product selections - uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" - check "order_cycle_incoming_exchange_0_variants_#{v2.id}" - check "order_cycle_incoming_exchange_0_variants_#{v3.id}" - uncheck "order_cycle_incoming_exchange_0_variants_#{v3.id}" + find("#order_cycle_incoming_exchange_0_variants_#{v1.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v2.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' @@ -901,9 +901,10 @@ feature %q{ select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work - click_button 'Update' + find_button('Update').trigger('click') page.should have_content 'Your order cycle has been updated.' - click_button 'Update and Close' + + find_button('Update and Close').trigger('click') # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' From 8b5e5105a8ce940e6fa6782852c2bb1776c96b29 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 12:11:03 +1000 Subject: [PATCH 060/154] Fix failed tests --- spec/features/admin/order_cycles_spec.rb | 10 +++++----- .../order_cycles/controllers/simple_edit.js.coffee | 3 ++- spec/javascripts/unit/order_cycle_spec.js.coffee | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 8b451fa7c0..99069e9f7c 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -624,10 +624,10 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # When I remove all the exchanges and save - page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").click - page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click - page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click - page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").trigger('click') + page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").trigger('click') + page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").trigger('click') + page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").trigger('click') click_button 'Update' # Then the exchanges should be removed @@ -897,7 +897,7 @@ feature %q{ # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' page.should_not have_select 'order_cycle_coordinator_fee_0_id' - click_button 'Add coordinator fee' + find_button('Add coordinator fee').trigger('click') select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee index dbad5a9f05..f5ccd25aac 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee @@ -10,7 +10,8 @@ describe "AdminSimpleEditOrderCycleCtrl", -> outgoing_exchange = {} beforeEach -> - scope = {} + scope = + $watch: jasmine.createSpy('$watch') location = absUrl: -> 'example.com/admin/order_cycles/27/edit' diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 2f1893bc43..b66acfcb1d 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -9,7 +9,7 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = { order_cycle_form: { $dirty: false}} + scope = {} event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = From 57ec7bb9a967df03b922e113241e088cfa187f26 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 12:27:24 +1000 Subject: [PATCH 061/154] Remove unused test code --- .../stylesheets/admin/components/save_bar.sass | 2 +- spec/features/admin/order_cycles_spec.rb | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index fcc60dbfc3..c6b1236490 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -3,7 +3,7 @@ bottom: 0px padding: 8px 10px font-weight: bold - background-color: #eff5fc + background-color: #fff color: #5498da h5 color: #5498da diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 99069e9f7c..8caf156af4 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -608,10 +608,6 @@ feature %q{ page.all('tr.supplier').count.should == 3 page.all('tr.distributor').count.should == 3 - # # When I save, then those exchanges should remain - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -699,10 +695,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchanges that I can't manage remain - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -752,10 +744,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchange that I can't manage remains - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed From 3cac9c452f2cb78d8cb7ad32089a54759d2c6b10 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 14:48:15 +1000 Subject: [PATCH 062/154] Tweaks --- app/views/admin/order_cycles/_form.html.haml | 8 +++----- app/views/admin/order_cycles/_simple_form.html.haml | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index 13b317c314..ed2d3acbc2 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -53,11 +53,9 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... - unless Rails.env.production? diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 34ea062102..9364d080ff 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -24,8 +24,6 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... From 7f1fc56f789cccb2fa49a6f42c0e27ea1fbd0856 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Wed, 4 May 2016 07:58:23 +0100 Subject: [PATCH 063/154] Change to use total rather than display_total (which returns a Spree::Money object) --- app/mailers/producer_mailer.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index d65d722894..52decf5d8a 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -46,12 +46,11 @@ class ProducerMailer < Spree::BaseMailer else lis[li.variant] = li end - lis end end def total_from_line_items(aggregated_line_items) - Spree::Money.new(aggregated_line_items.values.map(&:display_amount).reduce(:+)).to_s + Spree::Money.new(aggregated_line_items.values.map(&:total).reduce(:+)).to_s end end From 064e3c426ebf37028540d629dff22a059e2d33ae Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 11:00:34 +1000 Subject: [PATCH 064/154] Make the save bar look better --- .../javascripts/templates/admin/save_bar.html.haml | 12 ++++++------ .../stylesheets/admin/components/save_bar.sass | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index ddc535d001..0a4a055c87 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,8 +1,8 @@ #save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } - .twelve.columns.alpha - %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } - {{ StatusMessage.statusMessage.text || " " }} - .four.columns.omega.text-right - - %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } + .container + .twelve.columns.alpha + %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } + {{ StatusMessage.statusMessage.text || " " }} + .four.columns.omega.text-right + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index c6b1236490..23585d05f5 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -1,9 +1,11 @@ #save-bar position: fixed + width: 100% bottom: 0px - padding: 8px 10px + left: 0 + padding: 8px 8px font-weight: bold - background-color: #fff + background-color: #eff5fc color: #5498da h5 color: #5498da From 3e231da472d82a6353f211b5af6c38be881649d0 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 6 May 2016 11:13:15 +1000 Subject: [PATCH 065/154] Translate subjects of enterprise emails Minor text change Fix #906 Thanks to Nicolas Blanc: https://github.com/openfoodfoundation/openfoodnetwork/pull/937 --- app/mailers/enterprise_mailer.rb | 19 +++++++++++++------ config/locales/en.yml | 5 +++++ spec/mailers/enterprise_mailer_spec.rb | 4 ++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/mailers/enterprise_mailer.rb b/app/mailers/enterprise_mailer.rb index 248e31f109..dfd3f21636 100644 --- a/app/mailers/enterprise_mailer.rb +++ b/app/mailers/enterprise_mailer.rb @@ -4,19 +4,26 @@ class EnterpriseMailer < Spree::BaseMailer def welcome(enterprise) @enterprise = enterprise - mail(:to => enterprise.email, :from => from_address, - :subject => "#{enterprise.name} is now on #{Spree::Config[:site_name]}") + subject = t('enterprise_mailer.welcome.subject', + enterprise: @enterprise.name, + sitename: Spree::Config[:site_name]) + mail(:to => enterprise.email, + :from => from_address, + :subject => subject) end - def confirmation_instructions(record, token, opts={}) + def confirmation_instructions(record, token) @token = token find_enterprise(record) - mail(subject: "Please confirm your email for #{@enterprise.name}", - to: ( @enterprise.unconfirmed_email || @enterprise.email ), - from: from_address) + subject = t('enterprise_mailer.confirmation_instructions.subject', + enterprise: @enterprise.name) + mail(to: (@enterprise.unconfirmed_email || @enterprise.email), + from: from_address, + subject: subject) end private + def find_enterprise(enterprise) @enterprise = enterprise.is_a?(Enterprise) ? enterprise : Enterprise.find(enterprise) end diff --git a/config/locales/en.yml b/config/locales/en.yml index e9de6c9abd..b1595ba117 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -32,6 +32,11 @@ en: not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? confirmation_sent: "Confirmation email sent!" confirmation_not_sent: "Could not send a confirmation email." + enterprise_mailer: + confirmation_instructions: + subject: "Please confirm the email address for %{enterprise}" + welcome: + subject: "%{enterprise} is now on %{sitename}" home: "OFN" title: Open Food Network welcome_to: 'Welcome to ' diff --git a/spec/mailers/enterprise_mailer_spec.rb b/spec/mailers/enterprise_mailer_spec.rb index eaa21b54ae..2399d4146a 100644 --- a/spec/mailers/enterprise_mailer_spec.rb +++ b/spec/mailers/enterprise_mailer_spec.rb @@ -12,7 +12,7 @@ describe EnterpriseMailer do EnterpriseMailer.confirmation_instructions(enterprise, 'token').deliver ActionMailer::Base.deliveries.count.should == 1 mail = ActionMailer::Base.deliveries.first - expect(mail.subject).to eq "Please confirm your email for #{enterprise.name}" + expect(mail.subject).to eq "Please confirm the email address for #{enterprise.name}" expect(mail.to).to include enterprise.email expect(mail.reply_to).to be_nil end @@ -28,7 +28,7 @@ describe EnterpriseMailer do EnterpriseMailer.confirmation_instructions(enterprise, 'token').deliver ActionMailer::Base.deliveries.count.should == 1 mail = ActionMailer::Base.deliveries.first - expect(mail.subject).to eq "Please confirm your email for #{enterprise.name}" + expect(mail.subject).to eq "Please confirm the email address for #{enterprise.name}" expect(mail.to).to include enterprise.unconfirmed_email end end From 08f0011244cf47a31a236b3bcf2d45d497c9528e Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 12:43:50 +1000 Subject: [PATCH 066/154] Make the page long enough to avoid the save bar overlaying the form --- spec/features/admin/order_cycles_spec.rb | 38 ++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 8caf156af4..701780622f 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -267,6 +267,9 @@ feature %q{ scenario "updating an order cycle", js: true do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with all the settings oc = create(:order_cycle) initial_variants = oc.variants.sort_by &:id @@ -317,10 +320,10 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products input").each { |e| e.trigger('click') } + page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true - page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).trigger('click') # uncheck (with visible:true filter) + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) check "order_cycle_incoming_exchange_2_variants_#{v1.id}" check "order_cycle_incoming_exchange_2_variants_#{v2.id}" @@ -343,7 +346,7 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - page.all("table.exchanges tr.distributor td.products input").each { |e| e.trigger('click') } + page.all("table.exchanges tr.distributor td.products input").each { |e| e.click } uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -360,7 +363,8 @@ feature %q{ # And I click Update expect(page).to have_selector "#save-bar" - find_button('Update and Close').trigger('click') + save_screenshot('abc.png') + click_button 'Update and Close' # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' @@ -620,10 +624,11 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # When I remove all the exchanges and save - page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").trigger('click') - page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").trigger('click') - page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").trigger('click') - page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").trigger('click') + page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").click + page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + click_button 'Update' # Then the exchanges should be removed @@ -857,6 +862,9 @@ feature %q{ end scenario "updating an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with pickup time and instructions fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise) @@ -877,22 +885,22 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' # And I make some product selections - find("#order_cycle_incoming_exchange_0_variants_#{v1.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v2.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') + uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" + check "order_cycle_incoming_exchange_0_variants_#{v2.id}" + check "order_cycle_incoming_exchange_0_variants_#{v3.id}" + uncheck "order_cycle_incoming_exchange_0_variants_#{v3.id}" # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' page.should_not have_select 'order_cycle_coordinator_fee_0_id' - find_button('Add coordinator fee').trigger('click') + click_button 'Add coordinator fee' select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work - find_button('Update').trigger('click') + click_button 'Update' page.should have_content 'Your order cycle has been updated.' - find_button('Update and Close').trigger('click') + click_button 'Update and Close' # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' From a37820a9636e6359a96828587f7d7438c7ebe7e6 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 14:55:16 +1000 Subject: [PATCH 067/154] Resize window to fix failed test --- spec/features/admin/order_cycles_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 701780622f..cd5ef0a07e 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -619,6 +619,9 @@ feature %q{ end scenario "editing an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: distributor_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) visit edit_admin_order_cycle_path(oc) From 6dd989484ab40af3434a142b87ae9a6fe6ff88bf Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Tue, 10 May 2016 05:32:40 +0100 Subject: [PATCH 068/154] Switch to using the Spree GA tracker code --- .../spree/layouts/admin/add_analytics.html.haml.deface | 2 +- app/views/layouts/darkswarm.html.haml | 2 +- app/views/shared/_analytics.html.haml | 9 --------- 3 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 app/views/shared/_analytics.html.haml diff --git a/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface b/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface index 548439b60f..ced2d1b7b6 100644 --- a/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface +++ b/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface @@ -1,3 +1,3 @@ / insert_bottom "[data-hook='admin_footer_scripts']" -= render 'shared/analytics' += render 'spree/shared/google_analytics' diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index c758e4836e..d3f0a78087 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -43,4 +43,4 @@ #footer %loading - = render 'shared/analytics' + = render 'spree/shared/google_analytics' diff --git a/app/views/shared/_analytics.html.haml b/app/views/shared/_analytics.html.haml deleted file mode 100644 index 16ad08ff5f..0000000000 --- a/app/views/shared/_analytics.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- if Rails.env.production? - :javascript - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-62912229-1', 'auto'); - ga('send', 'pageview'); From 6ecf896fa2b34509f5d915e4d8a92d957892ac90 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 10 May 2016 23:04:06 +0100 Subject: [PATCH 069/154] HTML order cycle report email, text customisable in translations --- app/assets/stylesheets/mail/email.css.sass | 3 + app/mailers/producer_mailer.rb | 5 ++ .../order_cycle_report.html.haml | 62 +++++++++++++++++++ .../order_cycle_report.text.haml | 4 +- config/locales/en.yml | 3 + spec/mailers/producer_mailer_spec.rb | 1 + 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 app/views/producer_mailer/order_cycle_report.html.haml diff --git a/app/assets/stylesheets/mail/email.css.sass b/app/assets/stylesheets/mail/email.css.sass index 4256357923..e9eabd4edb 100644 --- a/app/assets/stylesheets/mail/email.css.sass +++ b/app/assets/stylesheets/mail/email.css.sass @@ -74,6 +74,9 @@ table.order-summary padding-left: 5px padding-right: 5px +.text-right + text-align: right + .social .soc-btn padding: 3px 7px font-size: 12px diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 52decf5d8a..9f776abc81 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -7,6 +7,7 @@ class ProducerMailer < Spree::BaseMailer @line_items = aggregated_line_items_from(@order_cycle, @producer) @receival_instructions = @order_cycle.receival_instructions_for @producer @total = total_from_line_items(@line_items) + @tax_total = tax_total_from_line_items(@line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -53,4 +54,8 @@ class ProducerMailer < Spree::BaseMailer def total_from_line_items(aggregated_line_items) Spree::Money.new(aggregated_line_items.values.map(&:total).reduce(:+)).to_s end + + def tax_total_from_line_items(aggregated_line_items) + Spree::Money.new(aggregated_line_items.values.map(&:included_tax).reduce(:+)).to_s + end end diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml new file mode 100644 index 0000000000..66276745b5 --- /dev/null +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -0,0 +1,62 @@ +%p Dear #{@producer.name}, +%p + = t :producer_mail_text_before + - if @receival_instructions + %p + Stock pickup/delivery instructions: + = @receival_instructions +%p + Here is a summary of the orders for your products: + %table.order-summary + %thead + %tr + %th + = t :sku + %th + = t :supplier + %th + = t :product + %th.text-right + = t :quantity + %th.text-right + = t :price + %th.text-right + = t :subtotal + %th.text-right + = t :included_tax + %tbody + - @line_items.each_pair do |variant, line_item| + %tr + %td + #{variant.sku} + %td + #{raw(variant.product.supplier.name)} + %td + #{raw(variant.product_and_full_name)} + %td.text-right + #{line_item.quantity} + %td.text-right + #{line_item.single_money} + %td.text-right + #{line_item.display_total} + %td.text-right + #{line_item.display_included_tax} + %tr.total_row + %td + %td + %td + %td + %td + %td.text-right + #{@total} + %td.text-right + #{@tax_total} +%p + = t :producer_mail_text_after + %em + %p + #{@coordinator.name} + %p + #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} + #{@coordinator.phone} + #{@coordinator.email} diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index ea035391b8..5c94107bd4 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -1,6 +1,6 @@ Dear #{@producer.name}, \ -We now have all the consumer orders for the next food drop. += t :producer_mail_text_before \ - if @receival_instructions Stock pickup/delivery instructions: @@ -18,7 +18,7 @@ Here is a summary of the orders for your products: \ Total: #{@total} \ -Thanks and best wishes, += t :producer_mail_text_after #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} #{@coordinator.phone} diff --git a/config/locales/en.yml b/config/locales/en.yml index 7b5f9b168f..4b53aa6e5a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -401,6 +401,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using If you are a producer or food enterprise, we are excited to have you as a part of the network." email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_text_after: "Thanks and best wishes," + shopping_oc_closed: Orders are closed shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 4e3fd912d2..e1c91af699 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -63,6 +63,7 @@ describe ProducerMailer do end it "includes the total" do + puts mail.body.encoded mail.body.should include 'Total: $20.00' end From d5a7e907a3353d959c8dccd8177d08fe997b8f70 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 10 May 2016 23:11:14 +0100 Subject: [PATCH 070/154] Fix specs, need to add new for HTML --- .../producer_mailer/order_cycle_report.html.haml | 14 +++++++------- spec/mailers/producer_mailer_spec.rb | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 66276745b5..31cef99465 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -53,10 +53,10 @@ #{@tax_total} %p = t :producer_mail_text_after - %em - %p - #{@coordinator.name} - %p - #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} - #{@coordinator.phone} - #{@coordinator.email} + %em + %p + #{@coordinator.name} + %p + #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} + #{@coordinator.phone} + #{@coordinator.email} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index e1c91af699..2ced290fed 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -44,7 +44,7 @@ describe ProducerMailer do end it "includes receival instructions" do - mail.body.should include 'Outside shed.' + mail.body.encoded.should include 'Outside shed.' end it "cc's the enterprise" do @@ -59,12 +59,11 @@ describe ProducerMailer do end it "does not include incomplete orders" do - mail.body.should_not include p3.name + mail.body.encoded.should_not include p3.name end it "includes the total" do - puts mail.body.encoded - mail.body.should include 'Total: $20.00' + mail.body.encoded.should include 'Total: $20.00' end it "sends no mail when the producer has no orders" do From 38efa218d08db5c3e696a1d1f656b97ed05fbf80 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 20 Apr 2016 15:31:11 +1000 Subject: [PATCH 071/154] Use save bar on order cycle form --- .../admin/order_cycles/controllers/edit.js.coffee | 5 +++++ app/assets/javascripts/templates/admin/save_bar.html.haml | 2 +- app/views/admin/order_cycles/edit.html.haml | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index fd426eb455..a17f52e42c 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -12,6 +12,9 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -81,4 +84,6 @@ angular.module('admin.orderCycles') OrderCycle.removeDistributionOfVariant(variant_id) $scope.submit = (destination) -> + StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) + this.order_cycle_form.$setPristine() diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 452e81f6e3..ac4fa55de5 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,4 +1,4 @@ -#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } +#save-bar.animate-show{ ng: { show: 'StatusMessage.active()' } } .twelve.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index f3b0bdb721..6ecb65d87e 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -27,7 +27,9 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' -= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller} do |f| += form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| + %save-bar{ save: "submit()", form: "order_cycle_form" } + - if order_cycles_simple_form = render 'simple_form', f: f - else From d10719330daaae1475c4e7e9d1d9d86edaa635a2 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 12:15:20 +1000 Subject: [PATCH 072/154] Extend save_bar directive to support more buttons --- .../javascripts/admin/utils/directives/save_bar.js.coffee | 1 + app/assets/javascripts/templates/admin/save_bar.html.haml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 13e4f84bc6..9cd25d97cd 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -3,6 +3,7 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> scope: save: "&" form: "=" + buttons: "=" templateUrl: "admin/save_bar.html" link: (scope, element, attrs) -> scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index ac4fa55de5..83beac81a5 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -3,4 +3,6 @@ %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } } + + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "save({param: button.param})" } } + From 2214c83ec7c644a172e25fab22d85a6294a82bce Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 12:16:13 +1000 Subject: [PATCH 073/154] Use save_bar on order cycle update form --- app/views/admin/order_cycles/_form.html.haml | 14 ++++++-------- .../admin/order_cycles/_simple_form.html.haml | 13 +++++-------- app/views/admin/order_cycles/edit.html.haml | 3 ++- .../admin/variant_overrides/_products.html.haml | 2 +- .../spree/admin/orders/bulk_management.html.haml | 2 +- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index c447d35d58..13b317c314 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -52,14 +52,12 @@ .actions - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + + %span{'ng-show' => 'loaded()'} + or + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... + = render 'spree/admin/shared/status_message' - unless Rails.env.production? diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 4dc64a012b..34ea062102 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -23,12 +23,9 @@ .actions - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - - else - = f.submit 'Update', 'ng-click' => "submit(null)", 'ng-disabled' => '!loaded()' - = f.submit 'Update and Close', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + or + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... + = render 'spree/admin/shared/status_message' diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 6ecb65d87e..4b2a397e49 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -28,7 +28,8 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ save: "submit()", form: "order_cycle_form" } + + %save-bar{ buttons: "[{text: 'Update', param: null}, {text: 'Update and Close', param: '#{main_app.admin_order_cycles_path}'}]", save: "submit(param)", form: "order_cycle_form" } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index c17e4c189a..70ae2c79bf 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,5 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ save: "update()", form: "variant_overrides_form" } + %save-bar{ save: "update()", form: "variant_overrides_form", buttons: "[{text: 'Save Changes'}]" } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 43983e87ea..b4d7de740d 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", form: "bulk_order_form" } + %save-bar{ save: "submit()", form: "bulk_order_form", buttons: "[{text: 'Save Changes'}]" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } From 7fc37e4737339fca09eff6232facf39870c18632 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 27 Apr 2016 13:47:04 +1000 Subject: [PATCH 074/154] Tweak save_bar css --- app/assets/stylesheets/admin/components/save_bar.sass | 2 +- app/assets/stylesheets/admin/openfoodnetwork.css.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index c6b1236490..fcc60dbfc3 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -3,7 +3,7 @@ bottom: 0px padding: 8px 10px font-weight: bold - background-color: #fff + background-color: #eff5fc color: #5498da h5 color: #5498da diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 17d43ff3a5..1fc95face8 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -37,6 +37,7 @@ text-angular .ta-editor { input.red { background-color: #DA5354; + margin-right: 5px; } input.search { From 78b4a35d129843f4140608074cd5b62c18a36f48 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 29 Apr 2016 10:43:28 +1000 Subject: [PATCH 075/154] Keep the action with save button --- .../javascripts/admin/order_cycles/controllers/edit.js.coffee | 2 +- .../javascripts/admin/utils/directives/save_bar.js.coffee | 1 - app/assets/javascripts/templates/admin/save_bar.html.haml | 4 ++-- app/views/admin/order_cycles/edit.html.haml | 2 +- app/views/admin/variant_overrides/_products.html.haml | 2 +- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index a17f52e42c..068447d161 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -86,4 +86,4 @@ angular.module('admin.orderCycles') $scope.submit = (destination) -> StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) - this.order_cycle_form.$setPristine() + $scope.order_cycle_form.$setPristine() diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 9cd25d97cd..0999679394 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -1,7 +1,6 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> restrict: "E" scope: - save: "&" form: "=" buttons: "=" templateUrl: "admin/save_bar.html" diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 83beac81a5..ddc535d001 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,8 +1,8 @@ -#save-bar.animate-show{ ng: { show: 'StatusMessage.active()' } } +#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } .twelve.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} .four.columns.omega.text-right - %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "save({param: button.param})" } } + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 4b2a397e49..4f3e5d8516 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -29,7 +29,7 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ buttons: "[{text: 'Update', param: null}, {text: 'Update and Close', param: '#{main_app.admin_order_cycles_path}'}]", save: "submit(param)", form: "order_cycle_form" } + %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}' }]", form: "order_cycle_form" } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index 70ae2c79bf..34c8d02bb2 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,5 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ save: "update()", form: "variant_overrides_form", buttons: "[{text: 'Save Changes'}]" } + %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update }]" } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index b4d7de740d..2bd58a4cc5 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ save: "submit()", form: "bulk_order_form", buttons: "[{text: 'Save Changes'}]" } + %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit }]" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } From d59dea29ab8967fa8bb19eda77ba460e87d4860a Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 29 Apr 2016 15:23:19 +1000 Subject: [PATCH 076/154] Fix failed test for the default form --- .../order_cycles/controllers/edit.js.coffee | 1 + spec/features/admin/order_cycles_spec.rb | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index 068447d161..db63a8cbaa 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -63,6 +63,7 @@ angular.module('admin.orderCycles') $scope.removeExchange = ($event, exchange) -> $event.preventDefault() OrderCycle.removeExchange(exchange) + $scope.order_cycle_form.$dirty = true $scope.addCoordinatorFee = ($event) -> $event.preventDefault() diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index aafaa5ee77..4827f9b916 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -317,10 +317,10 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } + page.all("table.exchanges tr.supplier td.products input").each { |e| e.trigger('click') } page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true - page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).trigger('click') # uncheck (with visible:true filter) check "order_cycle_incoming_exchange_2_variants_#{v1.id}" check "order_cycle_incoming_exchange_2_variants_#{v2.id}" @@ -343,7 +343,7 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - page.all("table.exchanges tr.distributor td.products input").each { |e| e.click } + page.all("table.exchanges tr.distributor td.products input").each { |e| e.trigger('click') } uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -359,7 +359,8 @@ feature %q{ select 'Distributor fee 2', from: 'order_cycle_outgoing_exchange_2_enterprise_fees_0_enterprise_fee_id' # And I click Update - click_button 'Update and Close' + expect(page).to have_selector "#save-bar" + find_button('Update and Close').trigger('click') # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' @@ -607,9 +608,9 @@ feature %q{ page.all('tr.supplier').count.should == 3 page.all('tr.distributor').count.should == 3 - # When I save, then those exchanges should remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # # When I save, then those exchanges should remain + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -699,8 +700,8 @@ feature %q{ expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true # When I save, any exchanges that I can't manage remain - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -752,8 +753,8 @@ feature %q{ expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true # When I save, any exchange that I can't manage remains - click_button 'Update' - page.should have_content "Your order cycle has been updated." + # click_button 'Update' + # page.should have_content "Your order cycle has been updated." oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] From 26bb1a9beb353c36039b2c95fa52293b7860e67c Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Tue, 3 May 2016 22:58:09 +1000 Subject: [PATCH 077/154] Update AdminEditOrderCycleCtrl unit tests --- spec/javascripts/unit/order_cycle_spec.js.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 973b348383..2f1893bc43 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -9,7 +9,7 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = { order_cycle_form: { $dirty: false}} event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = @@ -169,7 +169,9 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = {} + scope = + order_cycle_form: jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine']) + $watch: jasmine.createSpy('$watch') event = preventDefault: jasmine.createSpy('preventDefault') location = @@ -292,6 +294,7 @@ describe 'OrderCycle controllers', -> scope.removeExchange(event, 'exchange') expect(event.preventDefault).toHaveBeenCalled() expect(OrderCycle.removeExchange).toHaveBeenCalledWith('exchange') + expect(scope.order_cycle_form.$dirty).toEqual true it 'Adds coordinator fees', -> scope.addCoordinatorFee(event) @@ -320,6 +323,7 @@ describe 'OrderCycle controllers', -> it 'Submits the order cycle via OrderCycle update', -> scope.submit('/admin/order_cycles') expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') + expect(scope.order_cycle_form.$setPristine.calls.length).toEqual 1 describe 'OrderCycle services', -> From 3760a34b2ba66f6e5bc615af8d41eb861219d97a Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 11:22:25 +1000 Subject: [PATCH 078/154] Add save_bar to the order cycles simple editing form --- .../order_cycles/controllers/simple_edit.js.coffee | 4 ++++ spec/features/admin/order_cycles_spec.rb | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index bf00bb3df1..99c2e2649c 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -9,6 +9,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) => $scope.init() + $scope.$watch 'order_cycle_form.$dirty', (newValue) -> + StatusMessage.display 'notice', 'You have unsaved changes' if newValue + $scope.loaded = -> Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded @@ -35,5 +38,6 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", OrderCycle.removeCoordinatorFee(index) $scope.submit = (destination) -> + StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 4827f9b916..8b451fa7c0 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -889,10 +889,10 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' # And I make some product selections - uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" - check "order_cycle_incoming_exchange_0_variants_#{v2.id}" - check "order_cycle_incoming_exchange_0_variants_#{v3.id}" - uncheck "order_cycle_incoming_exchange_0_variants_#{v3.id}" + find("#order_cycle_incoming_exchange_0_variants_#{v1.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v2.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') + find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' @@ -901,9 +901,10 @@ feature %q{ select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work - click_button 'Update' + find_button('Update').trigger('click') page.should have_content 'Your order cycle has been updated.' - click_button 'Update and Close' + + find_button('Update and Close').trigger('click') # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' From e5d2e5010b72d93a9751091c017896141bfc2eac Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 12:11:03 +1000 Subject: [PATCH 079/154] Fix failed tests --- spec/features/admin/order_cycles_spec.rb | 10 +++++----- .../order_cycles/controllers/simple_edit.js.coffee | 3 ++- spec/javascripts/unit/order_cycle_spec.js.coffee | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 8b451fa7c0..99069e9f7c 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -624,10 +624,10 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # When I remove all the exchanges and save - page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").click - page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click - page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click - page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").trigger('click') + page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").trigger('click') + page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").trigger('click') + page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").trigger('click') click_button 'Update' # Then the exchanges should be removed @@ -897,7 +897,7 @@ feature %q{ # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' page.should_not have_select 'order_cycle_coordinator_fee_0_id' - click_button 'Add coordinator fee' + find_button('Add coordinator fee').trigger('click') select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee index dbad5a9f05..f5ccd25aac 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_edit.js.coffee @@ -10,7 +10,8 @@ describe "AdminSimpleEditOrderCycleCtrl", -> outgoing_exchange = {} beforeEach -> - scope = {} + scope = + $watch: jasmine.createSpy('$watch') location = absUrl: -> 'example.com/admin/order_cycles/27/edit' diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 2f1893bc43..b66acfcb1d 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -9,7 +9,7 @@ describe 'OrderCycle controllers', -> EnterpriseFee = null beforeEach -> - scope = { order_cycle_form: { $dirty: false}} + scope = {} event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = From ed1a06495bb38bfa8d6fe8c5786f43754548d754 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 12:27:24 +1000 Subject: [PATCH 080/154] Remove unused test code --- .../stylesheets/admin/components/save_bar.sass | 2 +- spec/features/admin/order_cycles_spec.rb | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index fcc60dbfc3..c6b1236490 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -3,7 +3,7 @@ bottom: 0px padding: 8px 10px font-weight: bold - background-color: #eff5fc + background-color: #fff color: #5498da h5 color: #5498da diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 99069e9f7c..8caf156af4 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -608,10 +608,6 @@ feature %q{ page.all('tr.supplier').count.should == 3 page.all('tr.distributor').count.should == 3 - # # When I save, then those exchanges should remain - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -699,10 +695,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_outgoing_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchanges that I can't manage remain - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed @@ -752,10 +744,6 @@ feature %q{ # I should be able to see but not toggle v2, because I don't have permission expect(page).to have_field "order_cycle_incoming_exchange_0_variants_#{v2.id}", disabled: true - # When I save, any exchange that I can't manage remains - # click_button 'Update' - # page.should have_content "Your order cycle has been updated." - oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] oc.coordinator.should == distributor_managed From 7bf6881cb2f6707f3e1629457896918cfe4ac47f Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Wed, 4 May 2016 14:48:15 +1000 Subject: [PATCH 081/154] Tweaks --- app/views/admin/order_cycles/_form.html.haml | 8 +++----- app/views/admin/order_cycles/_simple_form.html.haml | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index 13b317c314..ed2d3acbc2 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -53,11 +53,9 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... - unless Rails.env.production? diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 34ea062102..9364d080ff 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -24,8 +24,6 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - or - = link_to 'Cancel', main_app.admin_order_cycles_path - %span{'ng-hide' => 'loaded()'} Loading... - = render 'spree/admin/shared/status_message' + %span{'ng-show' => 'loaded()'} + = link_to 'Cancel', main_app.admin_order_cycles_path + %span{'ng-hide' => 'loaded()'} Loading... From 89ae77dfd9eee0ae0ff1c4932a90653215bff161 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 11:00:34 +1000 Subject: [PATCH 082/154] Make the save bar look better --- .../javascripts/templates/admin/save_bar.html.haml | 12 ++++++------ .../stylesheets/admin/components/save_bar.sass | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index ddc535d001..0a4a055c87 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,8 +1,8 @@ #save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } - .twelve.columns.alpha - %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } - {{ StatusMessage.statusMessage.text || " " }} - .four.columns.omega.text-right - - %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } + .container + .twelve.columns.alpha + %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } + {{ StatusMessage.statusMessage.text || " " }} + .four.columns.omega.text-right + %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index c6b1236490..23585d05f5 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -1,9 +1,11 @@ #save-bar position: fixed + width: 100% bottom: 0px - padding: 8px 10px + left: 0 + padding: 8px 8px font-weight: bold - background-color: #fff + background-color: #eff5fc color: #5498da h5 color: #5498da From fed3ae9e858d4d60aa25251da43107e193fa1567 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 12:43:50 +1000 Subject: [PATCH 083/154] Make the page long enough to avoid the save bar overlaying the form --- spec/features/admin/order_cycles_spec.rb | 38 ++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 8caf156af4..701780622f 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -267,6 +267,9 @@ feature %q{ scenario "updating an order cycle", js: true do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with all the settings oc = create(:order_cycle) initial_variants = oc.variants.sort_by &:id @@ -317,10 +320,10 @@ feature %q{ # And I add a supplier and some products select 'My supplier', from: 'new_supplier_id' click_button 'Add supplier' - page.all("table.exchanges tr.supplier td.products input").each { |e| e.trigger('click') } + page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } page.should have_selector "#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true - page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).trigger('click') # uncheck (with visible:true filter) + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) check "order_cycle_incoming_exchange_2_variants_#{v1.id}" check "order_cycle_incoming_exchange_2_variants_#{v2.id}" @@ -343,7 +346,7 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1' fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1' - page.all("table.exchanges tr.distributor td.products input").each { |e| e.trigger('click') } + page.all("table.exchanges tr.distributor td.products input").each { |e| e.click } uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}" check "order_cycle_outgoing_exchange_2_variants_#{v2.id}" @@ -360,7 +363,8 @@ feature %q{ # And I click Update expect(page).to have_selector "#save-bar" - find_button('Update and Close').trigger('click') + save_screenshot('abc.png') + click_button 'Update and Close' # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' @@ -620,10 +624,11 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # When I remove all the exchanges and save - page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").trigger('click') - page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").trigger('click') - page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").trigger('click') - page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").trigger('click') + page.find("tr.supplier-#{supplier_managed.id} a.remove-exchange").click + page.find("tr.supplier-#{supplier_permitted.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_managed.id} a.remove-exchange").click + page.find("tr.distributor-#{distributor_permitted.id} a.remove-exchange").click + click_button 'Update' # Then the exchanges should be removed @@ -857,6 +862,9 @@ feature %q{ end scenario "updating an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + # Given an order cycle with pickup time and instructions fee1 = create(:enterprise_fee, name: 'my fee', enterprise: enterprise) fee2 = create(:enterprise_fee, name: 'that fee', enterprise: enterprise) @@ -877,22 +885,22 @@ feature %q{ fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'zzy' # And I make some product selections - find("#order_cycle_incoming_exchange_0_variants_#{v1.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v2.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') - find("#order_cycle_incoming_exchange_0_variants_#{v3.id}").trigger('click') + uncheck "order_cycle_incoming_exchange_0_variants_#{v1.id}" + check "order_cycle_incoming_exchange_0_variants_#{v2.id}" + check "order_cycle_incoming_exchange_0_variants_#{v3.id}" + uncheck "order_cycle_incoming_exchange_0_variants_#{v3.id}" # And I select some fees and update click_link 'order_cycle_coordinator_fee_0_remove' page.should_not have_select 'order_cycle_coordinator_fee_0_id' - find_button('Add coordinator fee').trigger('click') + click_button 'Add coordinator fee' select 'that fee', from: 'order_cycle_coordinator_fee_0_id' # When I update, or update and close, both work - find_button('Update').trigger('click') + click_button 'Update' page.should have_content 'Your order cycle has been updated.' - find_button('Update and Close').trigger('click') + click_button 'Update and Close' # Then my order cycle should have been updated page.should have_content 'Your order cycle has been updated.' From 7040e4baae444804b1f9b8b1d4e534ae4bf39e0a Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 6 May 2016 14:55:16 +1000 Subject: [PATCH 084/154] Resize window to fix failed test --- spec/features/admin/order_cycles_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 701780622f..cd5ef0a07e 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -619,6 +619,9 @@ feature %q{ end scenario "editing an order cycle" do + # Make the page long enough to avoid the save bar overlaying the form + page.driver.resize(1280, 3600) + oc = create(:simple_order_cycle, { suppliers: [supplier_managed, supplier_permitted, supplier_unmanaged], coordinator: distributor_managed, distributors: [distributor_managed, distributor_permitted, distributor_unmanaged], name: 'Order Cycle 1' } ) visit edit_admin_order_cycle_path(oc) From f8ec0d316a943d019b8194ff7b43edb1d61710f4 Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Thu, 12 May 2016 10:52:48 +1000 Subject: [PATCH 085/154] Save bar can support cancel button --- .../admin/order_cycles/controllers/edit.js.coffee | 5 ++++- app/assets/javascripts/templates/admin/save_bar.html.haml | 6 +++--- app/assets/stylesheets/admin/components/save_bar.sass | 2 ++ app/assets/stylesheets/admin/openfoodnetwork.css.scss | 1 - app/views/admin/order_cycles/edit.html.haml | 2 +- app/views/admin/variant_overrides/_products.html.haml | 2 +- app/views/spree/admin/orders/bulk_management.html.haml | 2 +- 7 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index db63a8cbaa..227c6a3045 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -1,5 +1,5 @@ angular.module('admin.orderCycles') - .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> + .controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) -> order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1] $scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id) $scope.supplier_enterprises = Enterprise.producer_enterprises @@ -88,3 +88,6 @@ angular.module('admin.orderCycles') StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) $scope.order_cycle_form.$setPristine() + + $scope.cancel = (destination) -> + $window.location = destination diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 0a4a055c87..0d4919dcde 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,8 +1,8 @@ #save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } .container - .twelve.columns.alpha + .eight.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} - .four.columns.omega.text-right - %input.red{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { disabled: '!form.$dirty', click: "button.action(button.param)" } } + .eight.columns.omega.text-right + %input{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { class: "button.class", click: "button.action(button.param)" } } diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index 23585d05f5..87dcce82f9 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -9,3 +9,5 @@ color: #5498da h5 color: #5498da + input + margin-right: 5px diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 1fc95face8..17d43ff3a5 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -37,7 +37,6 @@ text-angular .ta-editor { input.red { background-color: #DA5354; - margin-right: 5px; } input.search { diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 4f3e5d8516..9f85261224 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -29,7 +29,7 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}' }]", form: "order_cycle_form" } + %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null, class: 'red' }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}', class: 'red' }, { text: 'Cancel', action: cancel, param: '#{main_app.admin_order_cycles_path}', class: '' }]", form: "order_cycle_form" } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index 34c8d02bb2..f255d86e0b 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,5 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update }]" } + %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update, class: 'red' }]" } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 2bd58a4cc5..d8228b5c83 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,7 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit }]" } + %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit, class: 'red' }]" } .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } From c80255e9ab99141fbe24b422e28ffb843ea9eb00 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 1 May 2016 12:59:55 +0100 Subject: [PATCH 086/154] Add total to producer emails --- app/mailers/producer_mailer.rb | 5 +++++ app/views/producer_mailer/order_cycle_report.text.haml | 3 +++ spec/mailers/producer_mailer_spec.rb | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index b9fb52fb41..d65d722894 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -6,6 +6,7 @@ class ProducerMailer < Spree::BaseMailer @order_cycle = order_cycle @line_items = aggregated_line_items_from(@order_cycle, @producer) @receival_instructions = @order_cycle.receival_instructions_for @producer + @total = total_from_line_items(@line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -49,4 +50,8 @@ class ProducerMailer < Spree::BaseMailer lis end end + + def total_from_line_items(aggregated_line_items) + Spree::Money.new(aggregated_line_items.values.map(&:display_amount).reduce(:+)).to_s + end end diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index 93748395ce..ea035391b8 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -15,6 +15,9 @@ Here is a summary of the orders for your products: - @line_items.each_pair do |variant, line_item| #{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_full_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount} \ +\ +Total: #{@total} +\ Thanks and best wishes, #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 0c7e82b89d..4e3fd912d2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -62,6 +62,10 @@ describe ProducerMailer do mail.body.should_not include p3.name end + it "includes the total" do + mail.body.should include 'Total: $20.00' + end + it "sends no mail when the producer has no orders" do expect do ProducerMailer.order_cycle_report(s3, order_cycle).deliver From ef418c7f506dd38bfa371f188f1816c92907cd15 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Wed, 4 May 2016 07:58:23 +0100 Subject: [PATCH 087/154] Change to use total rather than display_total (which returns a Spree::Money object) --- app/mailers/producer_mailer.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index d65d722894..52decf5d8a 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -46,12 +46,11 @@ class ProducerMailer < Spree::BaseMailer else lis[li.variant] = li end - lis end end def total_from_line_items(aggregated_line_items) - Spree::Money.new(aggregated_line_items.values.map(&:display_amount).reduce(:+)).to_s + Spree::Money.new(aggregated_line_items.values.map(&:total).reduce(:+)).to_s end end From 559f7afc6047d74c915960d2b50885500093ad46 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 10 May 2016 23:04:06 +0100 Subject: [PATCH 088/154] HTML order cycle report email, text customisable in translations --- app/assets/stylesheets/mail/email.css.sass | 3 + app/mailers/producer_mailer.rb | 5 ++ .../order_cycle_report.html.haml | 62 +++++++++++++++++++ .../order_cycle_report.text.haml | 4 +- config/locales/en.yml | 3 + spec/mailers/producer_mailer_spec.rb | 1 + 6 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 app/views/producer_mailer/order_cycle_report.html.haml diff --git a/app/assets/stylesheets/mail/email.css.sass b/app/assets/stylesheets/mail/email.css.sass index 4256357923..e9eabd4edb 100644 --- a/app/assets/stylesheets/mail/email.css.sass +++ b/app/assets/stylesheets/mail/email.css.sass @@ -74,6 +74,9 @@ table.order-summary padding-left: 5px padding-right: 5px +.text-right + text-align: right + .social .soc-btn padding: 3px 7px font-size: 12px diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 52decf5d8a..9f776abc81 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -7,6 +7,7 @@ class ProducerMailer < Spree::BaseMailer @line_items = aggregated_line_items_from(@order_cycle, @producer) @receival_instructions = @order_cycle.receival_instructions_for @producer @total = total_from_line_items(@line_items) + @tax_total = tax_total_from_line_items(@line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -53,4 +54,8 @@ class ProducerMailer < Spree::BaseMailer def total_from_line_items(aggregated_line_items) Spree::Money.new(aggregated_line_items.values.map(&:total).reduce(:+)).to_s end + + def tax_total_from_line_items(aggregated_line_items) + Spree::Money.new(aggregated_line_items.values.map(&:included_tax).reduce(:+)).to_s + end end diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml new file mode 100644 index 0000000000..66276745b5 --- /dev/null +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -0,0 +1,62 @@ +%p Dear #{@producer.name}, +%p + = t :producer_mail_text_before + - if @receival_instructions + %p + Stock pickup/delivery instructions: + = @receival_instructions +%p + Here is a summary of the orders for your products: + %table.order-summary + %thead + %tr + %th + = t :sku + %th + = t :supplier + %th + = t :product + %th.text-right + = t :quantity + %th.text-right + = t :price + %th.text-right + = t :subtotal + %th.text-right + = t :included_tax + %tbody + - @line_items.each_pair do |variant, line_item| + %tr + %td + #{variant.sku} + %td + #{raw(variant.product.supplier.name)} + %td + #{raw(variant.product_and_full_name)} + %td.text-right + #{line_item.quantity} + %td.text-right + #{line_item.single_money} + %td.text-right + #{line_item.display_total} + %td.text-right + #{line_item.display_included_tax} + %tr.total_row + %td + %td + %td + %td + %td + %td.text-right + #{@total} + %td.text-right + #{@tax_total} +%p + = t :producer_mail_text_after + %em + %p + #{@coordinator.name} + %p + #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} + #{@coordinator.phone} + #{@coordinator.email} diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index ea035391b8..5c94107bd4 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -1,6 +1,6 @@ Dear #{@producer.name}, \ -We now have all the consumer orders for the next food drop. += t :producer_mail_text_before \ - if @receival_instructions Stock pickup/delivery instructions: @@ -18,7 +18,7 @@ Here is a summary of the orders for your products: \ Total: #{@total} \ -Thanks and best wishes, += t :producer_mail_text_after #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} #{@coordinator.phone} diff --git a/config/locales/en.yml b/config/locales/en.yml index ac8d07bd7e..2186b01304 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -410,6 +410,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using If you are a producer or food enterprise, we are excited to have you as a part of the network." email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_text_after: "Thanks and best wishes," + shopping_oc_closed: Orders are closed shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 4e3fd912d2..e1c91af699 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -63,6 +63,7 @@ describe ProducerMailer do end it "includes the total" do + puts mail.body.encoded mail.body.should include 'Total: $20.00' end From ab37cd25773cdbb42c741744a099aa3afd2795e3 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Tue, 10 May 2016 23:11:14 +0100 Subject: [PATCH 089/154] Fix specs, need to add new for HTML --- .../producer_mailer/order_cycle_report.html.haml | 14 +++++++------- spec/mailers/producer_mailer_spec.rb | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 66276745b5..31cef99465 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -53,10 +53,10 @@ #{@tax_total} %p = t :producer_mail_text_after - %em - %p - #{@coordinator.name} - %p - #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} - #{@coordinator.phone} - #{@coordinator.email} + %em + %p + #{@coordinator.name} + %p + #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} + #{@coordinator.phone} + #{@coordinator.email} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index e1c91af699..2ced290fed 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -44,7 +44,7 @@ describe ProducerMailer do end it "includes receival instructions" do - mail.body.should include 'Outside shed.' + mail.body.encoded.should include 'Outside shed.' end it "cc's the enterprise" do @@ -59,12 +59,11 @@ describe ProducerMailer do end it "does not include incomplete orders" do - mail.body.should_not include p3.name + mail.body.encoded.should_not include p3.name end it "includes the total" do - puts mail.body.encoded - mail.body.should include 'Total: $20.00' + mail.body.encoded.should include 'Total: $20.00' end it "sends no mail when the producer has no orders" do From 9f56494c4c076a7f102b347a3074686aa3bfe507 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 13 May 2016 10:36:23 +1000 Subject: [PATCH 090/154] Simplify --- app/mailers/producer_mailer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 9f776abc81..590193c4a5 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -52,10 +52,10 @@ class ProducerMailer < Spree::BaseMailer end def total_from_line_items(aggregated_line_items) - Spree::Money.new(aggregated_line_items.values.map(&:total).reduce(:+)).to_s + Spree::Money.new aggregated_line_items.values.map(&:total).sum end def tax_total_from_line_items(aggregated_line_items) - Spree::Money.new(aggregated_line_items.values.map(&:included_tax).reduce(:+)).to_s + Spree::Money.new aggregated_line_items.values.map(&:included_tax).sum end end From 027976626c2b5a6eaa6804e7c3e2900470a67ee5 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 13 May 2016 11:19:44 +1000 Subject: [PATCH 091/154] Add spec for error summing Spree::Money --- spec/mailers/producer_mailer_spec.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 2ced290fed..1f7d5a6501 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -10,21 +10,23 @@ describe ProducerMailer do let(:p1) { create(:product, price: 12.34, supplier: s1) } let(:p2) { create(:product, price: 23.45, supplier: s2) } let(:p3) { create(:product, price: 34.56, supplier: s1) } + let(:p4) { create(:product, price: 45.67, supplier: s1) } let(:order_cycle) { create(:simple_order_cycle) } let!(:incoming_exchange) { order_cycle.exchanges.create! sender: s1, receiver: d1, incoming: true, receival_instructions: 'Outside shed.' } let!(:order) do order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete') - order.line_items << create(:line_item, variant: p1.master) - order.line_items << create(:line_item, variant: p1.master) - order.line_items << create(:line_item, variant: p2.master) + order.line_items << create(:line_item, variant: p1.variants.first) + order.line_items << create(:line_item, variant: p1.variants.first) + order.line_items << create(:line_item, variant: p2.variants.first) + order.line_items << create(:line_item, variant: p4.variants.first) order.finalize! order.save order end let!(:order_incomplete) do order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'payment') - order.line_items << create(:line_item, variant: p3.master) + order.line_items << create(:line_item, variant: p3.variants.first) order.save order end @@ -63,7 +65,7 @@ describe ProducerMailer do end it "includes the total" do - mail.body.encoded.should include 'Total: $20.00' + mail.body.encoded.should include 'Total: $30.00' end it "sends no mail when the producer has no orders" do From ba2d5548ff85d366276fca434c0b509a8fee76dd Mon Sep 17 00:00:00 2001 From: Bing Xie Date: Fri, 13 May 2016 15:44:34 +1000 Subject: [PATCH 092/154] Fix groups map view --- app/views/groups/show.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 74588176b9..b5222e5f86 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -40,6 +40,7 @@ .map-container %map{"ng-if" => "(active(\'\') && (mapShowed = true)) || mapShowed"} %google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"} + %map-search %markers{models: "mapMarkers", fit: "true", coords: "'self'", icon: "'icon'", click: "'reveal'"} From 547fcf49e002adca8fa9bd87df7a63a7793ac489 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sat, 14 May 2016 11:00:13 +0100 Subject: [PATCH 093/154] Add line item included_tax_amount method to multiply by quantity --- app/mailers/producer_mailer.rb | 2 +- app/models/spree/line_item_decorator.rb | 13 +++++++++++-- spec/models/spree/line_item_spec.rb | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 590193c4a5..ae2f77384c 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -56,6 +56,6 @@ class ProducerMailer < Spree::BaseMailer end def tax_total_from_line_items(aggregated_line_items) - Spree::Money.new aggregated_line_items.values.map(&:included_tax).sum + Spree::Money.new aggregated_line_items.values.map(&:included_tax_amount).sum end end diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index 8a89d8fdef..f848aeede2 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -52,10 +52,15 @@ Spree::LineItem.class_eval do adjustments.included_tax.any? end + # Single def included_tax adjustments.included_tax.sum(&:included_tax) end + def included_tax_amount + included_tax * quantity + end + def price_with_adjustments # EnterpriseFee#create_locked_adjustment applies adjustments on line items to their parent order, # so line_item.adjustments returns an empty array @@ -77,8 +82,12 @@ Spree::LineItem.class_eval do Spree::Money.new(amount_with_adjustments, { :currency => currency }) end - def display_included_tax - Spree::Money.new(included_tax, { :currency => currency }) + def display_included_tax_amount + Spree::Money.new(included_tax_amount, { :currency => currency }) + end + + def display_amount_without_tax + Spree::Money.new(amount_with_adjustments - included_tax_amount, { currency: currency }) end def display_name diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 111108f7b4..fb37fd4e36 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -124,6 +124,15 @@ module Spree expect(li_no_tax.included_tax).to eq 0.00 end end + + context "scaling included tax by quantity" do + it "multiplies included_tax" do + li_tax.quantity = 3 + li_tax.save + expect(li_tax.included_tax).to eq 10.00 + expect(li_tax.included_tax_amount).to eq 30.00 + end + end end describe "unit value/description" do From 08eaff1c24be8994afd3ab2578bb7a4bd7dafe4d Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 15 May 2016 23:10:38 +0100 Subject: [PATCH 094/154] Formatting changes --- .../order_cycle_report.html.haml | 20 ++++++++++++++----- config/locales/en.yml | 3 +++ spec/mailers/producer_mailer_spec.rb | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 31cef99465..51472e2658 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -1,12 +1,15 @@ -%p Dear #{@producer.name}, +%p + = t :producer_mail_greeting + #{" " + @producer.name}, %p = t :producer_mail_text_before - if @receival_instructions %p - Stock pickup/delivery instructions: + %b + =t :producer_mail_delivery_instructions = @receival_instructions %p - Here is a summary of the orders for your products: + = t :producer_mail_order_text %table.order-summary %thead %tr @@ -40,7 +43,7 @@ %td.text-right #{line_item.display_total} %td.text-right - #{line_item.display_included_tax} + #{line_item.display_included_tax_amount} %tr.total_row %td %td @@ -57,6 +60,13 @@ %p #{@coordinator.name} %p - #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} + %br + #{@coordinator.address.address1} + %br + #{@coordinator.address.city} + %br + #{@coordinator.address.zipcode} + %p #{@coordinator.phone} + %p #{@coordinator.email} diff --git a/config/locales/en.yml b/config/locales/en.yml index 2186b01304..c16ed67872 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -410,7 +410,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using If you are a producer or food enterprise, we are excited to have you as a part of the network." email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + producer_mail_greeting: "Dear" producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" producer_mail_text_after: "Thanks and best wishes," shopping_oc_closed: Orders are closed diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 1f7d5a6501..2dec166aa3 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -65,6 +65,7 @@ describe ProducerMailer do end it "includes the total" do + puts mail.body.encoded mail.body.encoded.should include 'Total: $30.00' end From 38316bae3f0078689e48a93b528e356482dc5a93 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 15 May 2016 23:24:59 +0100 Subject: [PATCH 095/154] Add Stroudco wording to en-GB --- app/views/producer_mailer/order_cycle_report.html.haml | 2 ++ config/locales/en-GB.yml | 8 ++++++++ config/locales/en.yml | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 51472e2658..49a34c0c98 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -56,6 +56,8 @@ #{@tax_total} %p = t :producer_mail_text_after +%p + #{t(:producer_mail_signoff)}, %em %p #{@coordinator.name} diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 2eae5eb8a1..5aa4dc3f0b 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -58,6 +58,14 @@ en-GB: sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" + # To customise text in emails. + producer_mail_greeting: "Dear" + producer_mail_text_before: "We now have all the consumer orders for the next food drop." + producer_mail_order_text: "Here is a summary of the orders for your products:" + producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" + producer_mail_text_after: "Please confirm that you have got this email. Please send me an invoice for this amount so that we can send you payment. If you need to phone me on the day, please use the number below." + producer_mail_signoff: "Thanks and best wishes" + admin: # General form elements diff --git a/config/locales/en.yml b/config/locales/en.yml index c16ed67872..aeb4a059ae 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -414,7 +414,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using producer_mail_text_before: "We now have all the consumer orders for the next food drop." producer_mail_order_text: "Here is a summary of the orders for your products:" producer_mail_delivery_instructions: "Stock pickup/delivery instructions:" - producer_mail_text_after: "Thanks and best wishes," + producer_mail_text_after: "" + producer_mail_signoff: "Thanks and best wishes" shopping_oc_closed: Orders are closed shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" From 93f0a7c58d45227e34e92f56bf5fd464f4fbf052 Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Sun, 15 May 2016 23:25:19 +0100 Subject: [PATCH 096/154] Remove debug statement --- spec/mailers/producer_mailer_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 2dec166aa3..1f7d5a6501 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -65,7 +65,6 @@ describe ProducerMailer do end it "includes the total" do - puts mail.body.encoded mail.body.encoded.should include 'Total: $30.00' end From 419402c5542b40a5ab335896ad69c80d4632912d Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Mon, 16 May 2016 21:30:56 +0100 Subject: [PATCH 097/154] Add some HTML email specs --- app/views/producer_mailer/order_cycle_report.html.haml | 2 +- spec/mailers/producer_mailer_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 49a34c0c98..d74d13685f 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -44,7 +44,7 @@ #{line_item.display_total} %td.text-right #{line_item.display_included_tax_amount} - %tr.total_row + %tr.total-row %td %td %td diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 1f7d5a6501..b27245abe2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -58,6 +58,9 @@ describe ProducerMailer do line.should include 'QTY: 2' line.should include '@ $10.00 = $20.00' end + Capybara.string(mail.html_part.body.encoded) + .find("table.order-summary tr", text: p1.name) + .should have_selector("td", text: "$20.00") end it "does not include incomplete orders" do @@ -66,6 +69,9 @@ describe ProducerMailer do it "includes the total" do mail.body.encoded.should include 'Total: $30.00' + Capybara.string(mail.html_part.body.encoded) + .find("tr.total-row") + .should have_selector("td", text: "$30.00") end it "sends no mail when the producer has no orders" do From c66ac0827ee1c17af3a640e1f9687153f0a565dc Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Mon, 16 May 2016 21:35:40 +0100 Subject: [PATCH 098/154] Add translations to plain text part --- app/views/producer_mailer/order_cycle_report.text.haml | 8 +++++--- spec/mailers/producer_mailer_spec.rb | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index 5c94107bd4..31e2cf54d9 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -1,16 +1,16 @@ -Dear #{@producer.name}, +#{t :producer_mail_greeting} #{@producer.name}, \ = t :producer_mail_text_before \ - if @receival_instructions - Stock pickup/delivery instructions: + = t :producer_mail_delivery_instructions = @receival_instructions \ Orders summary ================ \ -Here is a summary of the orders for your products: += t :producer_mail_order_text \ - @line_items.each_pair do |variant, line_item| #{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_full_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount} @@ -19,6 +19,8 @@ Here is a summary of the orders for your products: Total: #{@total} \ = t :producer_mail_text_after + +#{t :producer_mail_signoff}, #{@coordinator.name} #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} #{@coordinator.phone} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index b27245abe2..01042b56f2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -68,6 +68,7 @@ describe ProducerMailer do end it "includes the total" do + puts mail.text_part.body.encoded mail.body.encoded.should include 'Total: $30.00' Capybara.string(mail.html_part.body.encoded) .find("tr.total-row") From 858beb97c5ae44a0a46f59b30d7ec1e0e5f4425c Mon Sep 17 00:00:00 2001 From: Steve Pettitt Date: Wed, 18 May 2016 08:49:05 +0100 Subject: [PATCH 099/154] Temporarily comment out tax column --- app/views/producer_mailer/order_cycle_report.html.haml | 6 +++--- spec/mailers/producer_mailer_spec.rb | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index d74d13685f..5f16d8ef6b 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -25,7 +25,7 @@ = t :price %th.text-right = t :subtotal - %th.text-right + -#%th.text-right = t :included_tax %tbody - @line_items.each_pair do |variant, line_item| @@ -42,7 +42,7 @@ #{line_item.single_money} %td.text-right #{line_item.display_total} - %td.text-right + -#%td.text-right #{line_item.display_included_tax_amount} %tr.total-row %td @@ -52,7 +52,7 @@ %td %td.text-right #{@total} - %td.text-right + -#%td.text-right #{@tax_total} %p = t :producer_mail_text_after diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 01042b56f2..b27245abe2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -68,7 +68,6 @@ describe ProducerMailer do end it "includes the total" do - puts mail.text_part.body.encoded mail.body.encoded.should include 'Total: $30.00' Capybara.string(mail.html_part.body.encoded) .find("tr.total-row") From d043de08cd94b90b7b63bdb5fcb2f42fcd0fe450 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 20 May 2016 09:42:19 +1000 Subject: [PATCH 100/154] Revert "Temporarily comment out tax column" This reverts commit 858beb97c5ae44a0a46f59b30d7ec1e0e5f4425c. --- app/views/producer_mailer/order_cycle_report.html.haml | 6 +++--- spec/mailers/producer_mailer_spec.rb | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index 5f16d8ef6b..d74d13685f 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -25,7 +25,7 @@ = t :price %th.text-right = t :subtotal - -#%th.text-right + %th.text-right = t :included_tax %tbody - @line_items.each_pair do |variant, line_item| @@ -42,7 +42,7 @@ #{line_item.single_money} %td.text-right #{line_item.display_total} - -#%td.text-right + %td.text-right #{line_item.display_included_tax_amount} %tr.total-row %td @@ -52,7 +52,7 @@ %td %td.text-right #{@total} - -#%td.text-right + %td.text-right #{@tax_total} %p = t :producer_mail_text_after diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index b27245abe2..01042b56f2 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -68,6 +68,7 @@ describe ProducerMailer do end it "includes the total" do + puts mail.text_part.body.encoded mail.body.encoded.should include 'Total: $30.00' Capybara.string(mail.html_part.body.encoded) .find("tr.total-row") From 6a3f6e7bfaf41e5448acd2133d3a9554726a44cd Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 20 May 2016 09:46:31 +1000 Subject: [PATCH 101/154] Revert "Add line item included_tax_amount method to multiply by quantity" This reverts commit 547fcf49e002adca8fa9bd87df7a63a7793ac489. --- app/mailers/producer_mailer.rb | 2 +- app/models/spree/line_item_decorator.rb | 13 ++----------- spec/models/spree/line_item_spec.rb | 9 --------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index ae2f77384c..590193c4a5 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -56,6 +56,6 @@ class ProducerMailer < Spree::BaseMailer end def tax_total_from_line_items(aggregated_line_items) - Spree::Money.new aggregated_line_items.values.map(&:included_tax_amount).sum + Spree::Money.new aggregated_line_items.values.map(&:included_tax).sum end end diff --git a/app/models/spree/line_item_decorator.rb b/app/models/spree/line_item_decorator.rb index f848aeede2..8a89d8fdef 100644 --- a/app/models/spree/line_item_decorator.rb +++ b/app/models/spree/line_item_decorator.rb @@ -52,15 +52,10 @@ Spree::LineItem.class_eval do adjustments.included_tax.any? end - # Single def included_tax adjustments.included_tax.sum(&:included_tax) end - def included_tax_amount - included_tax * quantity - end - def price_with_adjustments # EnterpriseFee#create_locked_adjustment applies adjustments on line items to their parent order, # so line_item.adjustments returns an empty array @@ -82,12 +77,8 @@ Spree::LineItem.class_eval do Spree::Money.new(amount_with_adjustments, { :currency => currency }) end - def display_included_tax_amount - Spree::Money.new(included_tax_amount, { :currency => currency }) - end - - def display_amount_without_tax - Spree::Money.new(amount_with_adjustments - included_tax_amount, { currency: currency }) + def display_included_tax + Spree::Money.new(included_tax, { :currency => currency }) end def display_name diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index fb37fd4e36..111108f7b4 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -124,15 +124,6 @@ module Spree expect(li_no_tax.included_tax).to eq 0.00 end end - - context "scaling included tax by quantity" do - it "multiplies included_tax" do - li_tax.quantity = 3 - li_tax.save - expect(li_tax.included_tax).to eq 10.00 - expect(li_tax.included_tax_amount).to eq 30.00 - end - end end describe "unit value/description" do From 5d3adc0bdb33c7deea5f81667cc7a6e16c3e5487 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 20 May 2016 11:01:33 +1000 Subject: [PATCH 102/154] Fixing producer emails so that they calculate tax correctly Also listing items by full_name rather than by variant, to catch cases where line item weights/volumes have been adjusted --- app/mailers/producer_mailer.rb | 31 ++++--------- .../order_cycle_report.html.haml | 18 ++++---- .../order_cycle_report.text.haml | 4 +- spec/mailers/producer_mailer_spec.rb | 43 ++++++++++++------- 4 files changed, 46 insertions(+), 50 deletions(-) diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb index 590193c4a5..30fad48f58 100644 --- a/app/mailers/producer_mailer.rb +++ b/app/mailers/producer_mailer.rb @@ -4,10 +4,11 @@ class ProducerMailer < Spree::BaseMailer @producer = producer @coordinator = order_cycle.coordinator @order_cycle = order_cycle - @line_items = aggregated_line_items_from(@order_cycle, @producer) + line_items = line_items_from(@order_cycle, @producer) + @grouped_line_items = line_items.group_by(&:product_and_full_name) @receival_instructions = @order_cycle.receival_instructions_for @producer - @total = total_from_line_items(@line_items) - @tax_total = tax_total_from_line_items(@line_items) + @total = total_from_line_items(line_items) + @tax_total = tax_total_from_line_items(line_items) subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}" @@ -27,10 +28,6 @@ class ProducerMailer < Spree::BaseMailer line_items_from(order_cycle, producer).any? end - def aggregated_line_items_from(order_cycle, producer) - aggregate_line_items line_items_from(order_cycle, producer) - end - def line_items_from(order_cycle, producer) Spree::LineItem. joins(:order => :order_cycle, :variant => :product). @@ -39,23 +36,11 @@ class ProducerMailer < Spree::BaseMailer merge(Spree::Order.complete) end - def aggregate_line_items(line_items) - # Arrange the items in a hash to group quantities - line_items.inject({}) do |lis, li| - if lis.key? li.variant - lis[li.variant].quantity += li.quantity - else - lis[li.variant] = li - end - lis - end + def total_from_line_items(line_items) + Spree::Money.new line_items.sum(&:total) end - def total_from_line_items(aggregated_line_items) - Spree::Money.new aggregated_line_items.values.map(&:total).sum - end - - def tax_total_from_line_items(aggregated_line_items) - Spree::Money.new aggregated_line_items.values.map(&:included_tax).sum + def tax_total_from_line_items(line_items) + Spree::Money.new line_items.sum(&:included_tax) end end diff --git a/app/views/producer_mailer/order_cycle_report.html.haml b/app/views/producer_mailer/order_cycle_report.html.haml index d74d13685f..b69dde8e66 100644 --- a/app/views/producer_mailer/order_cycle_report.html.haml +++ b/app/views/producer_mailer/order_cycle_report.html.haml @@ -28,22 +28,22 @@ %th.text-right = t :included_tax %tbody - - @line_items.each_pair do |variant, line_item| + - @grouped_line_items.each_pair do |product_and_full_name, line_items| %tr %td - #{variant.sku} + #{line_items.first.variant.sku} %td - #{raw(variant.product.supplier.name)} + #{raw(line_items.first.product.supplier.name)} %td - #{raw(variant.product_and_full_name)} + #{raw(product_and_full_name)} %td.text-right - #{line_item.quantity} + #{line_items.sum(&:quantity)} %td.text-right - #{line_item.single_money} + #{line_items.first.single_money} %td.text-right - #{line_item.display_total} - %td.text-right - #{line_item.display_included_tax_amount} + #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency) } + %td.tax.text-right + #{Spree::Money.new(line_items.sum(&:included_tax), currency: line_items.first.currency) } %tr.total-row %td %td diff --git a/app/views/producer_mailer/order_cycle_report.text.haml b/app/views/producer_mailer/order_cycle_report.text.haml index 31e2cf54d9..b5b644bf01 100644 --- a/app/views/producer_mailer/order_cycle_report.text.haml +++ b/app/views/producer_mailer/order_cycle_report.text.haml @@ -12,8 +12,8 @@ Orders summary \ = t :producer_mail_order_text \ -- @line_items.each_pair do |variant, line_item| - #{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_full_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount} +- @grouped_line_items.each_pair do |product_and_full_name, line_items| + #{line_items.first.variant.sku} - #{raw(line_items.first.product.supplier.name)} - #{raw(product_and_full_name)} (QTY: #{line_items.sum(&:quantity)}) @ #{line_items.first.single_money} = #{Spree::Money.new(line_items.sum(&:total), currency: line_items.first.currency)} \ \ Total: #{@total} diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index 01042b56f2..b878c27895 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' require 'yaml' describe ProducerMailer do + let!(:zone) { create(:zone_with_member) } + let!(:tax_rate) { create(:tax_rate, included_in_price: true, calculator: Spree::Calculator::DefaultTax.new, zone: zone, amount: 0.1) } + let!(:tax_category) { create(:tax_category, tax_rates: [tax_rate]) } let(:s1) { create(:supplier_enterprise) } let(:s2) { create(:supplier_enterprise) } let(:s3) { create(:supplier_enterprise) } - let(:d1) { create(:distributor_enterprise) } + let(:d1) { create(:distributor_enterprise, charges_sales_tax: true) } let(:d2) { create(:distributor_enterprise) } - let(:p1) { create(:product, price: 12.34, supplier: s1) } + let(:p1) { create(:product, price: 12.34, supplier: s1, tax_category: tax_category) } let(:p2) { create(:product, price: 23.45, supplier: s2) } let(:p3) { create(:product, price: 34.56, supplier: s1) } let(:p4) { create(:product, price: 45.67, supplier: s1) } @@ -16,10 +19,10 @@ describe ProducerMailer do let!(:order) do order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete') - order.line_items << create(:line_item, variant: p1.variants.first) - order.line_items << create(:line_item, variant: p1.variants.first) - order.line_items << create(:line_item, variant: p2.variants.first) - order.line_items << create(:line_item, variant: p4.variants.first) + order.line_items << create(:line_item, quantity: 1, variant: p1.variants.first) + order.line_items << create(:line_item, quantity: 2, variant: p1.variants.first) + order.line_items << create(:line_item, quantity: 3, variant: p2.variants.first) + order.line_items << create(:line_item, quantity: 2, variant: p4.variants.first) order.finalize! order.save order @@ -55,12 +58,17 @@ describe ProducerMailer do it "contains an aggregated list of produce" do body_lines_including(mail, p1.name).each do |line| - line.should include 'QTY: 2' - line.should include '@ $10.00 = $20.00' + line.should include 'QTY: 3' + line.should include '@ $10.00 = $30.00' end - Capybara.string(mail.html_part.body.encoded) - .find("table.order-summary tr", text: p1.name) - .should have_selector("td", text: "$20.00") + body_as_html(mail).find("table.order-summary tr", text: p1.name) + .should have_selector("td", text: "$30.00") + end + + it "displays tax totals for each product" do + # Tax for p1 line items + body_as_html(mail).find("table.order-summary tr", text: p1.name) + .should have_selector("td", text: "$30.00") end it "does not include incomplete orders" do @@ -68,11 +76,10 @@ describe ProducerMailer do end it "includes the total" do - puts mail.text_part.body.encoded - mail.body.encoded.should include 'Total: $30.00' - Capybara.string(mail.html_part.body.encoded) - .find("tr.total-row") - .should have_selector("td", text: "$30.00") + # puts mail.text_part.body.encoded + mail.body.encoded.should include 'Total: $50.00' + body_as_html(mail).find("tr.total-row") + .should have_selector("td", text: "$50.00") end it "sends no mail when the producer has no orders" do @@ -87,4 +94,8 @@ describe ProducerMailer do def body_lines_including(mail, s) mail.body.to_s.lines.select { |line| line.include? s } end + + def body_as_html(mail) + Capybara.string(mail.html_part.body.encoded) + end end From a2d875655cf65409eac5de8b5d0a4308503f52da Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 20 May 2016 14:13:10 +1000 Subject: [PATCH 103/154] Nothing --- config/locales/en-GB.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index e22e91fa0f..7de5db0ad4 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,6 +1,7 @@ # Localization file for British English. Add more files in this directory for other locales. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + en-GB: activerecord: errors: From fc69adb135360c17a8345459055c604c6be73ec3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 23 May 2016 10:40:46 +1000 Subject: [PATCH 104/154] Fixing producer emails spec --- spec/mailers/producer_mailer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailers/producer_mailer_spec.rb b/spec/mailers/producer_mailer_spec.rb index b878c27895..7291714e03 100644 --- a/spec/mailers/producer_mailer_spec.rb +++ b/spec/mailers/producer_mailer_spec.rb @@ -68,7 +68,7 @@ describe ProducerMailer do it "displays tax totals for each product" do # Tax for p1 line items body_as_html(mail).find("table.order-summary tr", text: p1.name) - .should have_selector("td", text: "$30.00") + .should have_selector("td.tax", text: "$2.73") end it "does not include incomplete orders" do From 02be66116370f170c1e126d3fb8ef441758deb10 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 14:02:28 +1000 Subject: [PATCH 105/154] Bumping angularjs version to 1.3.15 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/assets/javascripts/darkswarm/all.js.coffee | 2 +- .../javascripts/shared/mm-foundation-tpls-0.2.2.min.js | 9 --------- .../javascripts/shared/mm-foundation-tpls-0.8.0.min.js | 10 ++++++++++ spec/javascripts/application_spec.js | 2 +- 6 files changed, 15 insertions(+), 14 deletions(-) delete mode 100644 app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js create mode 100644 app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js diff --git a/Gemfile b/Gemfile index d581c8d10b..1a635a80f9 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa' gem 'simple_form', :github => 'RohanM/simple_form' gem 'unicorn' -gem 'angularjs-rails', '1.2.13' +gem 'angularjs-rails', '1.3.15' gem 'bugsnag' gem 'newrelic_rpm' gem 'haml' diff --git a/Gemfile.lock b/Gemfile.lock index 0e31157d96..ed44002f2a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM sprockets (~> 2) tilt angularjs-file-upload-rails (1.1.0) - angularjs-rails (1.2.13) + angularjs-rails (1.3.15) ansi (1.4.2) arel (3.0.3) atomic (1.1.99) @@ -650,7 +650,7 @@ DEPENDENCIES andand angular-rails-templates (~> 0.2.0) angularjs-file-upload-rails (~> 1.1.0) - angularjs-rails (= 1.2.13) + angularjs-rails (= 1.3.15) atomic awesome_print aws-sdk diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index f4303f8037..f5429da413 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -11,7 +11,7 @@ #= require lodash.underscore.js #= require angular-scroll.min.js #= require angular-google-maps.min.js -#= require ../shared/mm-foundation-tpls-0.2.2.min.js +#= require ../shared/mm-foundation-tpls-0.8.0.min.js #= require ../shared/bindonce.min.js #= require ../shared/ng-infinite-scroll.min.js #= require ../shared/angular-local-storage.js diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js deleted file mode 100644 index be82613518..0000000000 --- a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * angular-mm-foundation - * http://madmimi.github.io/angular-foundation/ - - * Version: 0.2.2 - 2014-06-25 - * License: MIT - */ -angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.dropdownToggle","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(this.groups.indexOf(a),1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position"]).directive("dropdownToggle",["$document","$location","$position",function(a,b,c){var d=null,e=angular.noop;return{restrict:"CA",scope:{dropdownToggle:"@"},link:function(b,f){var g=angular.element(a[0].querySelector(b.dropdownToggle));b.$watch("$location.path",function(){e()}),f.bind("click",function(h){g=angular.element(a[0].querySelector(b.dropdownToggle));var i=f===d;if(h.preventDefault(),h.stopPropagation(),d&&e(),!i&&!f.hasClass("disabled")&&!f.prop("disabled")){g.css("display","block");var j=c.offset(f),k=c.offset(angular.element(g[0].offsetParent));g.css({left:j.left-k.left+"px",top:j.top-k.top+j.height+"px"}),d=f,e=function(){a.unbind("click",e),g.css("display","none"),e=angular.noop,d=null},a.bind("click",e)}}),g&&g.css("display","none")}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
")(l),f.append(k));var i=angular.element("
");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))},C=function(){};d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){a.selectExpression(a.$parent)},b.addTab=function(a){c.push(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.select&&(b.selectExpression=a(e.select)),e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||b.selectExpression(b.$parent)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){return parseInt(a.localStorage.getItem("mm.tour.step"),10)}function c(b){d=b,a.localStorage.setItem("mm.tour.step",b)}var d=b(),e={};this.add=function(a,b){e[a]=b},this.has=function(a){return!!e[a]},this.isActive=function(){return d>0},this.current=function(a){return a?void c(d):d},this.start=function(){c(1)},this.next=function(){c(d+1)},this.end=function(){c(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()}}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m){if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
\n {{heading}}\n
\n
\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
\n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","
\n \n ×\n
\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'
\n
\n
\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n') -}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
\n \n
\n

\n

\n
\n
\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
\n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
\n \n
\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
\n {{heading}}\n
\n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
\n
\n
\n
\n
\n
\n
\n
\n')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'
\n \n
\n

\n

\n Next\n End\n ×\n
\n
\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","
    \n"+'
  • \n
    \n
  • \n
\n')}]); diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js new file mode 100644 index 0000000000..8f4e630b9f --- /dev/null +++ b/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js @@ -0,0 +1,10 @@ +/* + * angular-mm-foundation + * http://pineconellc.github.io/angular-foundation/ + + * Version: 0.8.0 - 2015-10-13 + * License: MIT + * (c) Pinecone, LLC + */ +angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","mm.foundation.dropdownToggle","mm.foundation.interchange","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.topbar","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/topbar/has-dropdown.html","template/topbar/toggle-top-bar.html","template/topbar/top-bar-dropdown.html","template/topbar/top-bar-section.html","template/topbar/top-bar.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(''),c.append(''),c.append(''),c.append('');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h'),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(''),{pre:function(){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),d.type=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?"background":"include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("
")(n),g.append(m));var j=angular.element('
');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('
').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)&&a?a:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(c.unbind(x.show,k),c.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:'
'}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!g.hasClass("fixed")?(g.addClass("fixed"),h.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&g.hasClass("fixed")&&(g.removeClass("fixed"),h.css("padding-top",""))}},l=function(){var b=e.topbarBreakpoint();if(i!==b){i=e.topbarBreakpoint(),f.removeClass("expanded"),f.parent().removeClass("expanded"),a.height="";var c=angular.element(f[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},m=function(){k(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!f.hasClass("expanded"):b;d?f.addClass("expanded"):f.removeClass("expanded"),a.settings.scrolltop?!d&&f.hasClass("fixed")?(f.parent().addClass("fixed"),f.removeClass("fixed"),h.css("padding-top",a.originalHeight+"px")):d&&f.parent().hasClass("fixed")&&(f.parent().removeClass("fixed"),f.addClass("fixed"),h.css("padding-top",""),c.scrollTo(0,0)):(j()&&f.parent().addClass("fixed"),f.parent().hasClass("fixed")&&(d?(f.addClass("fixed"),f.parent().addClass("expanded"),h.css("padding-top",a.originalHeight+"px")):(f.removeClass("fixed"),f.parent().removeClass("expanded"),k())))},g.hasClass("fixed")||j()){a.stickyTopbar=!0,a.height=g[0].offsetHeight;var n=g[0].getBoundingClientRect().top+c.pageYOffset}else a.height=f[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?f.css("height",a+"px"):f.css("height","")}),angular.element(c).bind("resize",l),angular.element(c).bind("scroll",m),a.$on("$destroy",function(){angular.element(c).unbind("scroll",l),angular.element(c).unbind("resize",m)}),g.hasClass("fixed")&&h.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){f.css("left"===a?{left:-100*b+"%"}:{right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},b.backText=g.settings.customBackText?g.settings.backText:"« "+i.html(),f=angular.element(g.settings.mobileShowParentLink&&j&&j.length>1?'
  • {{backText}}
  • {{linkText}}
  • ':'
  • {{backText}}
  • '),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("
    ");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1}),j.bind("focus",function(){m=!0});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
    \n {{heading}}\n
    \n
    \n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
    \n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","
    \n \n ×\n
    \n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'
    \n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'
    \n
    \n
    \n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
    \n \n
    \n

    \n

    \n
    \n
    \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
    \n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
    \n \n
    \n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    \n {{heading}}\n
    \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n')}]),angular.module("template/topbar/has-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/has-dropdown.html",'
  • ')}]),angular.module("template/topbar/toggle-top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/toggle-top-bar.html",'')}]),angular.module("template/topbar/top-bar-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-dropdown.html",'')}]),angular.module("template/topbar/top-bar-section.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-section.html",'
    ')}]),angular.module("template/topbar/top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar.html",'')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'
    \n \n
    \n

    \n

    \n Next\n End\n ×\n
    \n
    \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","
      \n"+'
    • \n
      \n
    • \n
    \n')}]); diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 07202027b4..94f56a20c5 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -8,7 +8,7 @@ //= require lodash.underscore.js //= require angular-flash.min.js //= require shared/ng-tags-input.min.js -//= require shared/mm-foundation-tpls-0.2.2.min.js +//= require shared/mm-foundation-tpls-0.8.0.min.js //= require textAngular.min.js //= require textAngular-sanitize.min.js //= require moment From fe739f6a8daa1d6ff1656aa041b67682ebb9f005 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 14:09:37 +1000 Subject: [PATCH 106/154] angular-foundation tabs directive on shop pages is closed by default --- .../forgot_controller.js.coffee | 1 + .../authentication/login_controller.js.coffee | 1 + .../signup_controller.js.coffee | 2 ++ .../authentication_controller.js.coffee | 2 +- .../services/authentication_service.js.coffee | 2 +- .../darkswarm/services/navigation.js.coffee | 6 +++--- .../javascripts/templates/forgot.html.haml | 12 +++++------ .../javascripts/templates/login.html.haml | 20 +++++++++---------- .../javascripts/templates/signup.html.haml | 2 +- app/views/shopping_shared/_tabs.html.haml | 3 +-- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee index 85920de958..85d2a699a3 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee @@ -1,5 +1,6 @@ Darkswarm.controller "ForgotCtrl", ($scope, $http, $location, AuthenticationService) -> $scope.path = "/forgot" + $scope.active = $scope.isActive($scope.path) $scope.sent = false $scope.submit = -> diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee index f91e137ca7..07bef90f84 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee @@ -1,5 +1,6 @@ Darkswarm.controller "LoginCtrl", ($scope, $http, $window, AuthenticationService, Redirections, Loading) -> $scope.path = "/login" + $scope.active = $scope.isActive($scope.path) $scope.submit = -> Loading.message = t 'logging_in' diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee index 123079b6b5..dfa61f82b2 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee @@ -1,5 +1,7 @@ Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) -> $scope.path = "/signup" + $scope.active = $scope.isActive($scope.path) + $scope.errors = email: null password: null diff --git a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee index 1a22f34b0c..7d65b853c7 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee @@ -3,5 +3,5 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree $scope.toggle = AuthenticationService.toggle $scope.spree_user = SpreeUser.spree_user - $scope.active = AuthenticationService.active + $scope.isActive = AuthenticationService.isActive $scope.select = AuthenticationService.select diff --git a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee index 10f75675f8..478f5f28e1 100644 --- a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee @@ -22,7 +22,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir @selectedPath = path Navigation.navigate @selectedPath - active: Navigation.active + isActive: Navigation.isActive close: -> if location.pathname in ["/", "/checkout"] diff --git a/app/assets/javascripts/darkswarm/services/navigation.js.coffee b/app/assets/javascripts/darkswarm/services/navigation.js.coffee index fd59d0f348..e2b04f3ad1 100644 --- a/app/assets/javascripts/darkswarm/services/navigation.js.coffee +++ b/app/assets/javascripts/darkswarm/services/navigation.js.coffee @@ -1,9 +1,9 @@ Darkswarm.factory 'Navigation', ($location, $window) -> new class Navigation - path: null + path: null - active: (path)-> - $location.path() == path + isActive: (path)-> + $location.path() == path navigate: (path)=> @path = path diff --git a/app/assets/javascripts/templates/forgot.html.haml b/app/assets/javascripts/templates/forgot.html.haml index c1990d778a..f85b91ab0a 100644 --- a/app/assets/javascripts/templates/forgot.html.haml +++ b/app/assets/javascripts/templates/forgot.html.haml @@ -1,6 +1,6 @@ %tab#forgot{"ng-controller" => "ForgotCtrl", heading: "{{'forgot_password' | t}}", - active: "active(path)", + active: "active", select: "select(path)"} %form{"ng-submit" => "submit()"} @@ -13,18 +13,18 @@ %div{"ng-show" => "!sent"} .alert-box.alert{"ng-show" => "errors != null"} {{ errors }} - + .row .large-12.columns %label{for: "email"} {{'signup_email' | t}} - %input.title.input-text{name: "email", + %input.title.input-text{name: "email", type: "email", id: "email", tabindex: 1, "ng-model" => "spree_user.email"} .row .large-12.columns - %input.button.primary{name: "commit", - tabindex: "3", - type: "submit", + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", value: "{{'reset_password' | t}}"} diff --git a/app/assets/javascripts/templates/login.html.haml b/app/assets/javascripts/templates/login.html.haml index 44d2e08ced..3d2f9a3e06 100644 --- a/app/assets/javascripts/templates/login.html.haml +++ b/app/assets/javascripts/templates/login.html.haml @@ -1,6 +1,6 @@ -%tab#login-content{"ng-controller" => "LoginCtrl", +%tab#login-content{"ng-controller" => "LoginCtrl", heading: "{{'label_login' | t}}", - active: "active(path)", + active: "active", select: "select(path)"} %form{"ng-submit" => "submit()"} .row @@ -10,7 +10,7 @@ .row .large-12.columns %label{for: "email"} {{'email' | t}} - %input.title.input-text{name: "email", + %input.title.input-text{name: "email", type: "email", id: "email", tabindex: 1, @@ -18,7 +18,7 @@ .row .large-12.columns %label{for: "password"} {{'password' | t}} - %input.title.input-text{name: "password", + %input.title.input-text{name: "password", type: "password", id: "password", autocomplete: "off", @@ -26,15 +26,15 @@ "ng-model" => "spree_user.password"} .row .large-12.columns - %input{name: "remember_me", - type: "checkbox", - id: "remember_me", + %input{name: "remember_me", + type: "checkbox", + id: "remember_me", value: "1", "ng-model" => "spree_user.remember_me"} %label{for: "remember_me"} {{'remember_me' | t}} .row .large-12.columns - %input.button.primary{name: "commit", - tabindex: "3", - type: "submit", + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", value: "{{'label_login' | t}}"} diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index 2e780aabb5..c71d9453bd 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -1,6 +1,6 @@ %tab#sign-up-content{"ng-controller" => "SignupCtrl", heading: "{{'label_signup' | t}}", - active: "active(path)", + active: 'active', select: "select(path)"} %form{"ng-submit" => "submit()"} .row diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml index 8d0d9e021d..0d3cd79939 100644 --- a/app/views/shopping_shared/_tabs.html.haml +++ b/app/views/shopping_shared/_tabs.html.haml @@ -1,6 +1,6 @@ #tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true} .row - %tabset + %tabset{ 'open-on-load' => 'false' } -# Build all tabs. - for name, heading_cols in { about: [t(:shopping_tabs_about, distributor: current_distributor.name), 6], producers: [t(:label_producers),2], @@ -10,7 +10,6 @@ - heading, cols = heading_cols %tab.columns{heading: heading, id: "tab_#{name}", - active: "active(\'#{name}\')", select: "toggle(\'#{name}\')", class: "small-12 medium-#{cols}" } = render "shopping_shared/#{name}" From 5de9eed48ada02c7994753f8e7d95c30c08c87dd Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 15:17:42 +1000 Subject: [PATCH 107/154] Fixing broken instagram link --- app/views/shopping_shared/_contact.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shopping_shared/_contact.html.haml b/app/views/shopping_shared/_contact.html.haml index 100d64d958..405c958768 100644 --- a/app/views/shopping_shared/_contact.html.haml +++ b/app/views/shopping_shared/_contact.html.haml @@ -57,6 +57,6 @@ - unless current_distributor.instagram.blank? %span - %a{href: "http://instagram.com.#{current_distributor.instagram}", target: "_blank" } + %a{href: "http://instagram.com/#{current_distributor.instagram}", target: "_blank" } %i.ofn-i_043-instagram / = current_distributor.instagram From e5ca494db83e067f6f6863619561d5e7e8206bbd Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 19:28:04 +1000 Subject: [PATCH 108/154] Replacing bindonce with native Angularjs syntax in Darkswarm Involved changing bo-text -> ng-bind, bo-href-i -> ng-href, bo-src-i -> ng-src and ng-html -> ng-bind-html --- .../javascripts/darkswarm/all.js.coffee | 1 - .../javascripts/darkswarm/darkswarm.js.coffee | 1 - .../templates/filter_selector.html.haml | 4 +- .../templates/partials/contact.html.haml | 14 ++-- .../partials/enterprise_details.html.haml | 6 +- .../partials/enterprise_header.html.haml | 20 +++--- .../templates/partials/follow.html.haml | 29 ++++---- .../templates/partials/hub_details.html.haml | 18 ++--- .../partials/producer_details.html.haml | 20 +++--- .../shop_variant_no_group_buy.html.haml | 2 +- .../shop_variant_with_group_buy.html.haml | 2 +- .../templates/price_breakdown.html.haml | 33 +++++---- .../templates/product_modal.html.haml | 12 ++-- .../templates/registration/details.html.haml | 10 +-- .../templates/registration/type.html.haml | 4 +- .../templates/shop_variant.html.haml | 4 +- app/views/groups/_contact.html.haml | 12 ++-- app/views/groups/index.html.haml | 8 +-- app/views/groups/show.html.haml | 4 +- app/views/home/_fat.html.haml | 26 +++---- app/views/home/_skinny.html.haml | 40 +++++------ app/views/producers/_fat.html.haml | 70 +++++++++---------- app/views/producers/_skinny.html.haml | 18 ++--- app/views/producers/index.html.haml | 2 +- app/views/shop/products/_form.html.haml | 7 +- app/views/shop/products/_summary.html.haml | 6 +- app/views/shopping_shared/_about.html.haml | 6 +- app/views/spree/users/_fat.html.haml | 34 ++++----- app/views/spree/users/_skinny.html.haml | 4 +- app/views/spree/users/show.html.haml | 4 +- 30 files changed, 206 insertions(+), 215 deletions(-) diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index f5429da413..600044f09c 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -12,7 +12,6 @@ #= require angular-scroll.min.js #= require angular-google-maps.min.js #= require ../shared/mm-foundation-tpls-0.8.0.min.js -#= require ../shared/bindonce.min.js #= require ../shared/ng-infinite-scroll.min.js #= require ../shared/angular-local-storage.js #= require ../shared/angular-slideables.js diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index a062ead058..ce5a73d69e 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,7 +1,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", 'mm.foundation', 'angularLocalStorage', - 'pasvaz.bindonce', 'infinite-scroll', 'angular-flash.service', 'templates', diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml index a53c4f44db..cd4135ebee 100644 --- a/app/assets/javascripts/templates/filter_selector.html.haml +++ b/app/assets/javascripts/templates/filter_selector.html.haml @@ -1,4 +1,4 @@ -%ul{ bindonce: true } +%ul %active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } } %render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} } - %span{"bo-text" => "selector.object.name"} + %span{"ng-bind" => "::selector.object.name"} diff --git a/app/assets/javascripts/templates/partials/contact.html.haml b/app/assets/javascripts/templates/partials/contact.html.haml index 12aa5ff061..d173bcf366 100644 --- a/app/assets/javascripts/templates/partials/contact.html.haml +++ b/app/assets/javascripts/templates/partials/contact.html.haml @@ -1,11 +1,11 @@ -%div.contact-container{bindonce: true} - %div.modal-centered{"bo-if" => "enterprise.email_address || enterprise.website || enterprise.phone"} +%div.contact-container + %div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone"} %p.modal-header {{'contact' | t}} - %p{"bo-if" => "enterprise.phone", "bo-text" => "enterprise.phone"} + %p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"} - %p.word-wrap{"ng-if" => "enterprise.email_address"} - %a{"bo-href" => "enterprise.email_address | stripUrl", target: "_blank", mailto: true} - %span.email{"bo-bind" => "enterprise.email_address | stripUrl"} + %p.word-wrap{"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"} - %a{"bo-href-i" => "http://{{enterprise.website | stripUrl}}", target: "_blank", "bo-bind" => "enterprise.website | stripUrl"} + %a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"} diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml index fc0a638c8d..547e233958 100644 --- a/app/assets/javascripts/templates/partials/enterprise_details.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml @@ -1,4 +1,4 @@ -.row{bindonce: true} +.row .small-12.large-8.columns / TODO: Rob add logic for taxons and properties too: / %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"} @@ -22,8 +22,8 @@ -# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/ -# .about-container.pad-top - %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"} - %p.text-small{"ng-bind-html" => "enterprise.long_description"} + %img.enterprise-logo{"ng-src" => "::enterprise.logo", "ng-if" => "::enterprise.logo"} + %p.text-small{"ng-bind-html" => "::enterprise.long_description"} .small-12.large-4.columns %ng-include{src: "'partials/contact.html'"} %ng-include{src: "'partials/follow.html'"} diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml index 44175a57fe..39b967be06 100644 --- a/app/assets/javascripts/templates/partials/enterprise_header.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml @@ -1,13 +1,13 @@ -.highlight{bindonce: true, "ng-class" => "{'is_distributor' : enterprise.is_distributor}"} +.highlight{"ng-class" => "::{'is_distributor' : enterprise.is_distributor}"} .highlight-top.row .small-12.medium-7.large-8.columns - %h3{"ng-if" => "enterprise.is_distributor"} - %a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"} - %i{"ng-class" => "enterprise.icon_font"} - %span{"bo-text" => "enterprise.name"} - %h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"} - %i{"ng-class" => "enterprise.icon_font"} - %span{"bo-text" => "enterprise.name"} + %h3{"ng-if" => "::enterprise.is_distributor"} + %a{"ng-href" => "{{::enterprise.path}}", "ofn-change-hub" => "enterprise"} + %i{"ng-class" => "::enterprise.icon_font"} + %span{"ng-bind" => "::enterprise.name"} + %h3{"ng-if" => "::!enterprise.is_distributor", "ng-class" => "::{'is_producer' : enterprise.is_primary_producer}"} + %i{"ng-class" => "::enterprise.icon_font"} + %span{"ng-bind" => "::enterprise.name"} .small-12.medium-5.large-4.columns.text-right.small-only-text-left - %p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"} - %img.hero-img{"bo-src" => "enterprise.promo_image"} + %p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"} + %img.hero-img{"ng-src" => "::enterprise.promo_image"} diff --git a/app/assets/javascripts/templates/partials/follow.html.haml b/app/assets/javascripts/templates/partials/follow.html.haml index c04567a357..a47054d1f3 100644 --- a/app/assets/javascripts/templates/partials/follow.html.haml +++ b/app/assets/javascripts/templates/partials/follow.html.haml @@ -1,19 +1,18 @@ -%div.modal-centered{bindonce: true, "bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"} +%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"} %p.modal-header {{'follow' | t}} .follow-icons - %span{"bo-if" => "enterprise.twitter"} - %a{"bo-href-i" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"} + %span{"ng-if" => "::enterprise.twitter"} + %a{"ng-href" => "http://twitter.com/{{::enterprise.twitter}}", target: "_blank"} %i.ofn-i_041-twitter - - %span{"bo-if" => "enterprise.facebook"} - %a{"bo-href-i" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"} - %i.ofn-i_044-facebook - - %span{"bo-if" => "enterprise.linkedin"} - %a{"bo-href-i" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"} - %i.ofn-i_042-linkedin - - %span{"bo-if" => "enterprise.instagram"} - %a{"bo-href-i" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"} - %i.ofn-i_043-instagram + %span{"ng-if" => "::enterprise.facebook"} + %a{"ng-href" => "http://{{::enterprise.facebook | stripUrl}}", target: "_blank"} + %i.ofn-i_044-facebook + + %span{"ng-if" => "::enterprise.linkedin"} + %a{"ng-href" => "http://{{::enterprise.linkedin | stripUrl}}", target: "_blank"} + %i.ofn-i_042-linkedin + + %span{"ng-if" => "::enterprise.instagram"} + %a{"ng-href" => "http://instagram.com/{{::enterprise.instagram}}", target: "_blank"} + %i.ofn-i_043-instagram diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml index fa75f72253..e3a0234df1 100644 --- a/app/assets/javascripts/templates/partials/hub_details.html.haml +++ b/app/assets/javascripts/templates/partials/hub_details.html.haml @@ -1,24 +1,24 @@ -.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } } +.row.pad-top{ng: { if: 'enterprise.is_distributor' } } .cta-container.small-12.columns .row .small-4.columns %label{"active-table-hub-link" => "enterprise", change: "{{'change_shop' | t}}", shop: "{{'shop_at' | t}}"} .small-8.columns.right - %label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"} + %label.right{"ng-if" => "::enterprise.pickup || enterprise.delivery"} {{'hubs_delivery_options' | t}}: - %span{"bo-if" => "enterprise.pickup"} + %span{"ng-if" => "::enterprise.pickup"} %i.ofn-i_038-takeaway {{'hubs_pickup' | t}} - %span{"bo-if" => "enterprise.delivery"} + %span{"ng-if" => "::enterprise.delivery"} %i.ofn-i_039-delivery {{'hubs_delivery' | t}} .row .columns.small-12 - %a.cta-hub{"bo-href" => "enterprise.path", + %a.cta-hub{"ng-href" => "{{::enterprise.path}}", "ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}", "ofn-change-hub" => "enterprise"} - %i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"} - .hub-name{"bo-text" => "enterprise.name"} - .button-address{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"} + %i.ofn-i_033-open-sign{"ng-if" => "::enterprise.active"} + %i.ofn-i_032-closed-sign{"ng-if" => "::!enterprise.active"} + .hub-name{"ng-bind" => "::enterprise.name"} + .button-address{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"} / %i.ofn-i_007-caret-right diff --git a/app/assets/javascripts/templates/partials/producer_details.html.haml b/app/assets/javascripts/templates/partials/producer_details.html.haml index 599b977dfc..cb7ee70482 100644 --- a/app/assets/javascripts/templates/partials/producer_details.html.haml +++ b/app/assets/javascripts/templates/partials/producer_details.html.haml @@ -1,20 +1,20 @@ -# Show places to buy products from this producer, when there are any -# Do not show this for producer shops selling only their own produce, -# Since a shopping link will already have been displayed in hub_details.html.haml -.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"} +.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"} .columns.small-12 .row .columns.small-12.fat - %div{"bo-if" => "enterprise.name"} - %label{"bo-html" => "'shop_for_products_html' | t:{enterprise: enterprise.name}"} - %div.show-for-medium-up{"bo-if" => "!enterprise.name"} + %div{"ng-if" => "::enterprise.name"} + %label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"} + %div.show-for-medium-up{"ng-if" => "::!enterprise.name"}   .row.cta-container .columns.small-12 %a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'", - "bo-href" => "hub.path", "ofn-empties-cart" => "hub", - "bo-class" => "{primary: hub.active, secondary: !hub.active}"} - %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - .hub-name{"bo-text" => "hub.name"} - .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"} + "ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}"} + %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"} + %i.ofn-i_032-closed-sign{"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/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml index 1153d93b0f..3a52ba9d07 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -1,4 +1,4 @@ -.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"} +.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"} %input{type: :number, integer: true, diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index 6601f0c019..d4c88092b6 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -1,4 +1,4 @@ -.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"} +.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"} %span.bulk-input-container %span.bulk-input %input.bulk.first{type: :number, diff --git a/app/assets/javascripts/templates/price_breakdown.html.haml b/app/assets/javascripts/templates/price_breakdown.html.haml index 993c20bc9b..fa884cd4f0 100644 --- a/app/assets/javascripts/templates/price_breakdown.html.haml +++ b/app/assets/javascripts/templates/price_breakdown.html.haml @@ -1,38 +1,37 @@ -.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"} +.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"} %span.joyride-nub.right .joyride-content-wrapper .collapsed{"ng-show" => "!expanded"} %price-percentage{percentage: 'variant.basePricePercentage'} - %a{"ng-click" => "expanded = !expanded"} - %span{"bo-text" => "'price_breakdown' | t"} + %a{"ng-click" => "expanded = !expanded"} + %span{"ng-bind" => "::'price_breakdown' | t"} %i.ofn-i_005-caret-down .expanded{"ng-show" => "expanded"} %ul %li.cost .right {{ variant.price | localizeCurrency }} - %span{"bo-text" => "'item_cost' | t"} - %li.admin-fee{"bo-if" => "variant.fees.admin"} + %span{"ng-bind" => "::'item_cost' | t"} + %li.admin-fee{"ng-if" => "::variant.fees.admin"} .right {{ variant.fees.admin | localizeCurrency }} - %span{"bo-text" => "'admin_fee' | t"} - %li.sales-fee{"bo-if" => "variant.fees.sales"} + %span{"ng-bind" => "::'admin_fee' | t"} + %li.sales-fee{"ng-if" => "::variant.fees.sales"} .right {{ variant.fees.sales | localizeCurrency }} - %span{"bo-text" => "'sales_fee' | t"} - %li.packing-fee{"bo-if" => "variant.fees.packing"} + %span{"ng-bind" => "::'sales_fee' | t"} + %li.packing-fee{"ng-if" => "::variant.fees.packing"} .right {{ variant.fees.packing | localizeCurrency }} - %span{"bo-text" => "'packing_fee' | t"} - %li.transport-fee{"bo-if" => "variant.fees.transport"} + %span{"ng-bind" => "::'packing_fee' | t"} + %li.transport-fee{"ng-if" => "::variant.fees.transport"} .right {{ variant.fees.transport | localizeCurrency }} - %span{"bo-text" => "'transport_fee' | t"} - %li.fundraising-fee{"bo-if" => "variant.fees.fundraising"} + %span{"ng-bind" => "::'transport_fee' | t"} + %li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"} .right {{ variant.fees.fundraising | localizeCurrency }} - %span{"bo-text" => "'fundraising_fee' | t"} + %span{"ng-bind" => "::'fundraising_fee' | t"} %li.total %strong .right = {{ variant.price_with_fees | localizeCurrency }}   - %a{"ng-click" => "expanded = !expanded"} - %span{"bo-text" => "'price_graph' | t"} + %a{"ng-click" => "expanded = !expanded"} + %span{"ng-bind" => "::'price_graph' | t"} %i.ofn-i_006-caret-up - diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index e13f6df60d..f4839f6512 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -1,10 +1,10 @@ -.row{bindonce: true} +.row .columns.small-12.large-6.product-header - %h3{"bo-text" => "product.name"} + %h3{"ng-bind" => "::product.name"} %span %em {{'products_from' | t}} - %span{"bo-text" => "enterprise.name"} + %span{"ng-bind" => "::enterprise.name"} %br @@ -16,11 +16,11 @@ %div{"ng-if" => "product.description"} %hr - %p.text-small{"bo-text" => "product.description"} + %p.text-small{"ng-bind" => "::product.description"} %hr .columns.small-12.large-6 - %img.product-img{"bo-src" => "product.largeImage", "bo-if" => "product.largeImage"} - %img.product-img.placeholder{"bo-src" => "'/assets/noimage/large.png'", "bo-if" => "!product.largeImage"} + %img.product-img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"} + %img.product-img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"} %ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/registration/details.html.haml b/app/assets/javascripts/templates/registration/details.html.haml index f03a48ec59..6bd3fc02ba 100644 --- a/app/assets/javascripts/templates/registration/details.html.haml +++ b/app/assets/javascripts/templates/registration/details.html.haml @@ -1,19 +1,19 @@ -.container#registration-details{bindonce: true} +.container#registration-details %ng-include{ src: "'registration/steps.html'" } .row .small-12.columns %header %h2 {{'registration_detail_headline' | t}} - %h5{ bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}} - %h5{ bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_producer' | t}} + %h5{ ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}} + %h5{ ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_producer' | t}} %form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } } .row .small-12.medium-9.large-12.columns.end .field - %label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}} - %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}} + %label{ for: 'enterprise_name', ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}} + %label{ for: 'enterprise_name', ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}} %input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "{{'registration_detail_name_placeholder' | t}}", required: true, ng: { model: 'enterprise.name' } } %span.error{ ng: { show: "details.name.$error.required && submitted" } } {{'registration_detail_name_error' | t}} diff --git a/app/assets/javascripts/templates/registration/type.html.haml b/app/assets/javascripts/templates/registration/type.html.haml index c63b40239e..85a81398d7 100644 --- a/app/assets/javascripts/templates/registration/type.html.haml +++ b/app/assets/javascripts/templates/registration/type.html.haml @@ -1,4 +1,4 @@ -.container#registration-type{bindonce: true} +.container#registration-type %ng-include{ src: "'registration/steps.html'" } @@ -10,7 +10,7 @@ {{'registration_type_question' | t}} %form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } } - .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } } + .row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } } .small-12.columns.field .row .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index 2c9eb932d5..1d06ffbb39 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -2,16 +2,14 @@ .small-12.medium-4.large-4.columns.variant-name .table-cell .inline {{ variant.name_to_display }} - .bulk-buy.inline{"bo-if" => "variant.product.group_buy"} + .bulk-buy.inline{"ng-if" => "::variant.product.group_buy"} %i.ofn-i_056-bulk>< %em>< \ {{'bulk' | t}} - %ng-include{src: "'partials/shop_variant_no_group_buy.html'"} %ng-include{src: "'partials/shop_variant_with_group_buy.html'"} - .small-3.medium-1.large-1.columns.variant-unit .table-cell %em {{ variant.unit_to_display }} diff --git a/app/views/groups/_contact.html.haml b/app/views/groups/_contact.html.haml index 320da464bd..5c1e3aef5e 100644 --- a/app/views/groups/_contact.html.haml +++ b/app/views/groups/_contact.html.haml @@ -1,4 +1,4 @@ -%div.contact-container{bindonce: true} +%div.contact-container - if @group.email.present? || @group.website.present? || @group.phone.present? %div.modal-centered %p.modal-header @@ -16,12 +16,12 @@ =link_to_service "http://", @group.website do = t :groups_contact_website -%div{bindonce: true} +%div - if @group.facebook.present? || @group.twitter.present? || @group.linkedin.present? || @group.instagram.present? %div.modal-centered.pad-top %p.modal-header = t :groups_contact_web - .follow-icons{bindonce: true} + .follow-icons =link_to_service "http://twitter.com/", @group.twitter do %i.ofn-i_041-twitter =link_to_service "https://www.facebook.com/", @group.facebook do @@ -30,8 +30,8 @@ %i.ofn-i_042-linkedin =link_to_service "http://instagram.com/", @group.instagram do %i.ofn-i_043-instagram - -%div{bindonce: true} + +%div - if @group.address1.present? || @group.city.present? %div.modal-centered.pad-top %p.modal-header @@ -46,5 +46,3 @@ = @group.city = @group.state = @group.zipcode - - diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml index afb19e1b91..ad93d4f366 100644 --- a/app/views/groups/index.html.haml +++ b/app/views/groups/index.html.haml @@ -25,14 +25,14 @@ .group.animate-repeat{"ng-repeat" => "group in groups = (Groups.groups | groups:query | orderBy:order)", name: "group{{group.id}}", id: "group{{group.id}}"} - .row.pad-top{bindonce: true} + .row.pad-top .small-12.medium-6.columns .groups-header - %a{"bo-href-i" => "/groups/{{group.permalink}}"} + %a{"ng-href" => "/groups/{{::group.permalink}}"} %i.ofn-i_035-groups - %span.group-name{"bo-text" => "group.name"} + %span.group-name{"ng-bind" => "::group.name"} .small-3.medium-2.columns - %p{"bo-text" => "group.state"} + %p{"ng-bind" => "::group.state"} .small-9.medium-4.columns.groups-icons %p %link-to-service.ofn-i_050-mail-circle{service: '""', ref: 'group.email.split("").reverse().join("")', mailto: true} diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index b5222e5f86..8d5385f1f4 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -62,7 +62,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "producers/filters" - .row{bindonce: true} + .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", @@ -89,7 +89,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "hub_filters" - .row{bindonce: true} + .row .small-12.columns .active_table %hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}", diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml index b96b13885e..4e90771cc1 100644 --- a/app/views/home/_fat.html.haml +++ b/app/views/home/_fat.html.haml @@ -1,45 +1,45 @@ -.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}", bindonce: true} +.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"} .columns.small-12.medium-6.large-5.fat - %div{"bo-if" => "hub.taxons"} + %div{"ng-if" => "::hub.taxons"} %label = t :hubs_buy .trans-sentence %span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"} %render-svg{path: "{{taxon.icon}}"} - %span{"bo-text" => "taxon.name"} - %div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"} + %span{"ng-bind" => "::taxon.name"} + %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}   .columns.small-12.medium-3.large-2.fat - %div{"bo-if" => "hub.pickup || hub.delivery"} + %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{"bo-if" => "hub.pickup"} + %li.pickup{"ng-if" => "::hub.pickup"} %i.ofn-i_038-takeaway = t :hubs_pickup - %li.delivery{"bo-if" => "hub.delivery"} + %li.delivery{"ng-if" => "::hub.delivery"} %i.ofn-i_039-delivery = t :hubs_delivery .columns.small-12.medium-3.large-5.fat - %div{"bo-if" => "hub.producers"} + %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{"bo-text" => "enterprise.name"} - %li{"data-is-link" => "true", "class" => "more-producers-link", "bo-show" => "hub.producers.length>7"} + %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"} .more + - %span{"bo-text" => "hub.producers.length-7"} + %span{"ng-bind" => "::hub.producers.length-7"} = t :label_more .less = t :label_less %li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"} %enterprise-modal %i.ofn-i_036-producers - %span{"bo-text" => "enterprise.name"} - %div.show-for-medium-up{"bo-if" => "hub.producers.length==0"} + %span{"ng-bind" => "::enterprise.name"} + %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"}   diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml index 85e200fb94..fbc008a5c6 100644 --- a/app/views/home/_skinny.html.haml +++ b/app/views/home/_skinny.html.haml @@ -1,46 +1,46 @@ -.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true} +.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{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"} - %i{bo: {class: "hub.icon_font"}} - %span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"} + %a.hub{"ng-href" => "{{::hub.path}}", "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{"bo-text" => "hub.address.city"} + %span.margin-top{"ng-bind" => "::hub.address.city"} .columns.small-2.medium-1.large-1 - %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"} %span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km) - .columns.small-4.medium-3.large-3.text-right{"bo-if" => "hub.active"} - %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::hub.active"} + %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} %i.ofn-i_033-open-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ ng: { if: "::current()" } } %em= t :hubs_shopping_here - %span.margin-top{ bo: { if: "!current()" } } - %span{"bo-bind" => "hub.orders_close_at | sensible_timeframe"} + %span.margin-top{ ng: { if: "::!current()" } } + %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"} - .columns.small-4.medium-3.large-3.text-right{"bo-if" => "!hub.active"} - %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} + .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::!hub.active"} + %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"} %i.ofn-i_032-closed-sign - %span.margin-top{ bo: { if: "current()" } } + %span.margin-top{ ng: { if: "::current()" } } %em= t :hubs_shopping_here - %span.margin-top{ bo: { if: "!current()" } } + %span.margin-top{ ng: { if: "::!current()" } } = t :hubs_orders_closed .columns.small-2.medium-1.large-1.text-right %span.margin-top %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", bindonce: true} +.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.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"} + %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"} .columns.small-4.medium-2.large-2 - %span.margin-top{"bo-text" => "hub.address.city"} + %span.margin-top{"ng-bind" => "::hub.address.city"} .columns.small-2.medium-1.large-1 - %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"} .columns.small-6.medium-3.large-4.text-right - %span.margin-top{ bo: { if: "!current()" } } + %span.margin-top{ ng: { if: "::!current()" } } %em= t :hubs_profile_only diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml index 9faa239c02..6b665b4f34 100644 --- a/app/views/producers/_fat.html.haml +++ b/app/views/producers/_fat.html.haml @@ -2,78 +2,78 @@ .columns.small-12.medium-7.large-7.fat / Will add in long description available once clean up HTML formatting producer.long_description - %div{"bo-if" => "producer.description"} + %div{"ng-if" => "::producer.description"} %label = t :producers_about - %img.right.show-for-medium-up{"bo-src" => "producer.logo" } - %p.text-small{ "bo-text" => "producer.description"} - %div.show-for-medium-up{"bo-if" => "producer.description.length==0"} + %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   .columns.small-12.medium-5.large-5.fat - %div{"bo-if" => "producer.supplied_taxons"} + %div{"ng-if" => "::producer.supplied_taxons"} %label = t :producers_buy %p.trans-sentence %span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"} %render-svg{path: "{{taxon.icon}}"} - %span{"bo-text" => "taxon.name"} + %span{"ng-bind" => "::taxon.name"} %div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}   - %div{"bo-if" => "producer.email_address || producer.website || producer.phone"} + %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"} %label = t :producers_contact - %p.word-wrap{"bo-if" => "producer.phone"} + %p.word-wrap{"ng-if" => "::producer.phone"} = t :producers_contact_phone - %span{"bo-text" => "producer.phone"} + %span{"ng-bind" => "::producer.phone"} - %p.word-wrap{"bo-if" => "producer.email_address"} - %a{"bo-href" => "producer.email_address | stripUrl", target: "_blank", mailto: true} - %span.email{"bo-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.email{"ng-bind" => "::producer.email_address | stripUrl"} - %p.word-wrap{"bo-if" => "producer.website"} - %a{"bo-href-i" => "http://{{producer.website | stripUrl}}", target: "_blank" } - %span{"bo-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{"bo-if" => "producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} + %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"} %label = t :producers_social - .follow-icons{bindonce: true} - %span{"bo-if" => "producer.twitter"} - %a{"bo-href-i" => "http://twitter.com/{{producer.twitter}}", target: "_blank"} + .follow-icons + %span{"ng-if" => "::producer.twitter"} + %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"} %i.ofn-i_041-twitter - %span{"bo-if" => "producer.facebook"} - %a{"bo-href-i" => "http://{{producer.facebook | stripUrl}}", target: "_blank"} + %span{"ng-if" => "::producer.facebook"} + %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"} %i.ofn-i_044-facebook - %span{"bo-if" => "producer.linkedin"} - %a{"bo-href-i" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"} + %span{"ng-if" => "::producer.linkedin"} + %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"} %i.ofn-i_042-linkedin - %span{"bo-if" => "producer.instagram"} - %a{"bo-href-i" => "http://instagram.com/{{producer.instagram}}", target: "_blank"} + %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()", "bo-if" => "producer.hubs"} +.row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs"} .columns.small-12 .row .columns.small-12.fat - %div{"bo-if" => "producer.name"} + %div{"ng-if" => "::producer.name"} %label - = t :producers_buy_at_html, {enterprise: ''.html_safe} - %div.show-for-medium-up{"bo-if" => "!producer.name"} + = 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 | visible | orderBy:'-active'", - "bo-href" => "hub.path", "ofn-change-hub" => "hub", - "bo-class" => "{primary: hub.active, secondary: !hub.active}"} - %i.ofn-i_033-open-sign{"bo-if" => "hub.active"} - %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"} - .hub-name{"bo-text" => "hub.name"} - .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"} + "ng-href" => "{{::hub.path}}", "ofn-change-hub" => "hub", + "ng-class" => "::{primary: hub.active, secondary: !hub.active}"} + %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"} + %i.ofn-i_032-closed-sign{"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/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml index cf066be05a..311de9daf5 100644 --- a/app/views/producers/_skinny.html.haml +++ b/app/views/producers/_skinny.html.haml @@ -1,20 +1,20 @@ .row.active_table_row{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"} .columns.small-12.medium-4.large-4.skinny-head - %span{"bo-if" => "producer.is_distributor" } - %a.is_distributor{"bo-href" => "producer.path" } - %i{bo: {class: "producer.producer_icon_font"}} + %span{"ng-if" => "::producer.is_distributor" } + %a.is_distributor{"ng-href" => "{{::producer.path}}" } + %i{ng: {class: "::producer.producer_icon_font"}} %span.margin-top - %strong{"bo-text" => "producer.name"} - %span.producer-name{"bo-if" => "!producer.is_distributor" } - %i{bo: {class: "producer.producer_icon_font"}} + %strong{"ng-bind" => "::producer.name"} + %span.producer-name{"ng-if" => "::!producer.is_distributor" } + %i{ng: {class: "::producer.producer_icon_font"}} %span.margin-top - %strong{"bo-text" => "producer.name"} + %strong{"ng-bind" => "::producer.name"} .columns.small-6.medium-3.large-3 - %span.margin-top{"bo-text" => "producer.address.city"} + %span.margin-top{"ng-bind" => "::producer.address.city"} .columns.small-4.medium-3.large-4 - %span.margin-top{"bo-bind" => "producer.address.state_name | uppercase"} + %span.margin-top{"ng-bind" => "::producer.address.state_name | uppercase"} .columns.small-2.medium-2.large-1.text-right %span.margin-top %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index e842568cb3..8b7fd5695e 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -12,7 +12,7 @@ = render partial: "shared/components/enterprise_search" = render partial: "producers/filters" - .row{bindonce: true} + .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index f2be498451..1decc0a43c 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -29,11 +29,10 @@ .small-12.medium-6.large-6.large-offset-1.columns = render partial: "shop/products/filters" - %div.pad-top{bindonce: true} - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", - "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"} + %div.pad-top + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"} = render "shop/products/summary" - %shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants", "id" => "variant-{{ product.master.id }}"} + %shop-variant{variant: 'product.master', "ng-if" => "::!product.hasVariants", "id" => "variant-{{ product.master.id }}"} %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.count_on_hand == 0}"} %product{"ng-show" => "Products.loading"} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 8671b00cbf..fcf2aa6d7b 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,13 +1,13 @@ .product-thumb %a{"ng-click" => "triggerProductModal()"} %i.ofn-i_057-expand - %img{"bo-src" => "product.primaryImageOrMissing", "ng-click" => "triggerProductModal()"} + %img{"ng-src" => "::product.primaryImageOrMissing", "ng-click" => "triggerProductModal()"} .row.summary .small-10.medium-10.large-11.columns.summary-header %h3 %a{"ng-click" => "triggerProductModal()"} - %span{"bo-text" => "product.name"} + %span{"ng-bind" => "::product.name"} %i.ofn-i_057-expand %small %em @@ -15,7 +15,7 @@ %span %enterprise-modal %i.ofn-i_036-producers - %span{"bo-bind" => "enterprise.name"} + %span{"ng-bind" => "::enterprise.name"} .small-2.medium-2.large-1.columns.text-center .taxon-flag %render-svg{path: "{{product.primary_taxon.icon}}"} diff --git a/app/views/shopping_shared/_about.html.haml b/app/views/shopping_shared/_about.html.haml index ffe66c6c82..eac2fe5658 100644 --- a/app/views/shopping_shared/_about.html.haml +++ b/app/views/shopping_shared/_about.html.haml @@ -1,8 +1,8 @@ -.content#about{"ng-controller" => "AboutUsCtrl", bindonce: true} +.content#about{"ng-controller" => "AboutUsCtrl"} .panel .row .small-12.large-8.columns - %img.hero-img-small{"bo-src" => "CurrentHub.hub.promo_image", "bo-if" => "CurrentHub.hub.promo_image"} - %p{"bo-html" => "CurrentHub.hub.long_description"} + %img.hero-img-small{"ng-src" => "{{::CurrentHub.hub.promo_image}}", "ng-if" => "::CurrentHub.hub.promo_image"} + %p{"ng-bind-html" => "::CurrentHub.hub.long_description"} .small-12.large-4.columns   diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml index 5c87077b4c..4ce051e5b8 100644 --- a/app/views/spree/users/_fat.html.haml +++ b/app/views/spree/users/_fat.html.haml @@ -12,20 +12,20 @@ %tbody.transaction-group{"ng-repeat" => "order in distributor.distributed_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"} %tr.order-row %td.order1 - %a{"bo-href" => "order.path", "bo-text" => "('order' | t )+ ' ' + order.number"} - %td.order2{"bo-text" => "order.completed_at"} - %td.order3.show-for-large-up{"bo-text" => "'spree.payment_states.' + order.payment_state | t | capitalize"} - %td.order4.show-for-large-up{"bo-text" => "'spree.shipment_states.' + order.shipment_state | t | capitalize"} - %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","bo-text" => "order.total | localizeCurrency"} - %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "bo-text" => "order.outstanding_balance | localizeCurrency"} - %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "bo-text" => "order.running_balance | localizeCurrency"} - %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} - %td.order1{"bo-text" => "payment.payment_method"} - %td.order2{"bo-text" => "payment.updated_at"} - %td.order3.show-for-large-up - %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"} - %span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"} - %td.order4.show-for-large-up - %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"} - %td.order6.show-for-large-up - %td.order7 + %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::('order' | t )+ ' ' + order.number"} + %td.order2{"ng-bind" => "::order.completed_at"} + %td.order3.show-for-large-up{"ng-bind" => "::'spree.payment_states.' + order.payment_state | t | capitalize"} + %td.order4.show-for-large-up{"ng-bind" => "::'spree.shipment_states.' + order.shipment_state | t | capitalize"} + %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"} + %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "ng-bind" => "::order.outstanding_balance | localizeCurrency"} + %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "ng-bind" => "::order.running_balance | localizeCurrency"} + %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"} + %td.order1{"ng-bind" => "::payment.payment_method"} + %td.order2{"ng-bind" => "::payment.updated_at"} + %td.order3.show-for-large-up + %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"} + %span{"ng-bind" => "::'spree.payment_states.' + payment.state | t | capitalize"} + %td.order4.show-for-large-up + %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","ng-bind" => "::payment.amount | localizeCurrency"} + %td.order6.show-for-large-up + %td.order7 diff --git a/app/views/spree/users/_skinny.html.haml b/app/views/spree/users/_skinny.html.haml index 14c04f024a..4894824296 100644 --- a/app/views/spree/users/_skinny.html.haml +++ b/app/views/spree/users/_skinny.html.haml @@ -4,9 +4,9 @@ %img.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"} .columns.small-10.medium-5 %span.margin-top - %strong{"bo-text" => "distributor.name"} + %strong{"ng-bind" => "::distributor.name"} .columns.small-8.small-offset-2.medium-3.text-right - %span.margin-top.distributor-balance{"bo-text" => "distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" } + %span.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" } .columns.small-2.medium-2.text-right %span.margin-top %i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"} diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml index 83171daf0d..0b4800f79e 100644 --- a/app/views/spree/users/show.html.haml +++ b/app/views/spree/users/show.html.haml @@ -9,7 +9,7 @@ (#{link_to t(:edit), spree.edit_account_path}) %h3= t(:my_orders) .orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true} - .row{bindonce: true} + .row .small-12.columns .active_table %distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor", @@ -19,6 +19,6 @@ .small-12.columns = render partial: "spree/users/skinny" = render partial: "spree/users/fat" - .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "bo-text" => "'you_have_no_orders_yet' | t"} + .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"} = render partial: "shared/footer" From 1d837c32eea480ea7c817201c3404eaa6856eb2e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 20:02:19 +1000 Subject: [PATCH 109/154] Replacing bindonce with native Angularjs syntax in admin --- app/assets/javascripts/admin/all.js | 1 - .../admin/enterprises/enterprises.js.coffee | 2 +- .../variant_overrides/variant_overrides.js.coffee | 2 +- .../templates/admin/panels/enterprise_status.html.haml | 4 ++-- .../admin/enterprises/_enterprise_user_index.html.haml | 8 ++++---- app/views/admin/enterprises/form/_tag_rules.html.haml | 6 +++--- .../admin/variant_overrides/_hidden_products.html.haml | 10 +++++----- .../admin/variant_overrides/_new_products.html.haml | 10 +++++----- app/views/admin/variant_overrides/_products.html.haml | 2 +- .../variant_overrides/_products_product.html.haml | 4 ++-- .../variant_overrides/_products_variants.html.haml | 4 ++-- 11 files changed, 26 insertions(+), 27 deletions(-) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 9f99dc1dcd..73b51a20be 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -45,7 +45,6 @@ //= require ./variant_overrides/variant_overrides //= require textAngular.min.js //= require textAngular-sanitize.min.js -//= require ../shared/bindonce.min.js //= require darkswarm/i18n.js //= require darkswarm/i18n.translate.js diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee index 2074a1ea05..da122761b8 100644 --- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee @@ -1 +1 @@ -angular.module("admin.enterprises", [ "admin.paymentMethods", "admin.utils", "admin.shippingMethods", "admin.users", "textAngular", "admin.side_menu", "admin.taxons", 'admin.indexUtils', 'admin.tagRules', 'admin.dropdown', 'pasvaz.bindonce', 'ngSanitize'] ) \ No newline at end of file +angular.module("admin.enterprises", [ "admin.paymentMethods", "admin.utils", "admin.shippingMethods", "admin.users", "textAngular", "admin.side_menu", "admin.taxons", 'admin.indexUtils', 'admin.tagRules', 'admin.dropdown', 'ngSanitize'] ) \ No newline at end of file diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee index c302c0463e..2af7ba1c16 100644 --- a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -1 +1 @@ -angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"]) +angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"]) diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml index 39392a497d..518122774d 100644 --- a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml +++ b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml @@ -17,13 +17,13 @@ %td.severity %i.icon-warning-sign.issue %td.description - %span{ bo: { bind: "issue.description" } } + %span{ ng: { bind: "::issue.description" } } %td.resolve %div{ ng: { bind: { html: "issue.link" } } } %tr{ ng: { repeat: "warning in warnings"} } %td.severity %i.icon-warning-sign.warning %td.description - %span{ bo: { bind: "warning.description" } } + %span{ ng: { bind: "::warning.description" } } %td.resolve %div{ ng: { bind: { html: "warning.link" } } } diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 1fb35e595c..e53541fde2 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -15,7 +15,7 @@ %h1#no_results No enterprises found. - .row{ ng: { show: "loaded && filteredEnterprises.length > 0" }, bindonce: true } + .row{ ng: { show: "loaded && filteredEnterprises.length > 0" } } %table.index#enterprises %col.name{ width: "28%", ng: { show: 'columns.name.visible' } } %col.producer{ width: "18%", ng: { show: 'columns.producer.visible' }} @@ -33,15 +33,15 @@ %tbody{ :id => "e_{{enterprise.id}}", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )", controller: 'EnterpriseIndexRowCtrl' } } %tr.enterprise.panel-toggle-row{ object: "enterprise", ng: { class: { even: "'even'", odd: "'odd'"} } } %td.name{ ng: { show: 'columns.name.visible' } } - %span{ bo: { bind: "enterprise.name" } } + %span{ ng: { bind: "::enterprise.name" } } %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: producerError}" }, name: "producer" } %h5{ ng: { bind: "producer" } } %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: packageError}" }, name: "package" } %h5{ ng: { bind: "package" } } %td.status.panel-toggle.text-center{ ng: { show: 'columns.status.visible' }, name: "status" } - %i.icon-status{ bo: { class: "status" } } + %i.icon-status{ ng: { class: "::status()" } } %td.manage{ ng: { show: 'columns.manage.visible' } } - %a.button.fullwidth{ bo: { href: 'enterprise.edit_path' } } + %a.button.fullwidth{ ng: { href: '{{::enterprise.edit_path}}' } } Manage %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml index 1a50e4f353..4ed9aeb5c1 100644 --- a/app/views/admin/enterprises/form/_tag_rules.html.haml +++ b/app/views/admin/enterprises/form/_tag_rules.html.haml @@ -3,7 +3,7 @@ .eleven.columns.alpha.omega .no_tags{ ng: { show: "tagGroups.length == 0" } } No tags apply to this enterprise yet - .customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true } + .customer_tag{ ng: { repeat: "tagGroup in tagGroups" } } .header %table %colgroup @@ -23,8 +23,8 @@ %table %tr.tag_rule{ id: "tr_{{rule.id}}", ng: { repeat: "rule in tagGroup.rules" } } %td - %discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } } - %filter-shipping-methods{ bo: { if: "rule.type == 'TagRule::FilterShippingMethods'" } } + %discount-order{ ng: { if: "::rule.type == 'TagRule::DiscountOrder'" } } + %filter-shipping-methods{ ng: { if: "::rule.type == 'TagRule::FilterShippingMethods'" } } %td.actions %a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" } .add_rule.text-center diff --git a/app/views/admin/variant_overrides/_hidden_products.html.haml b/app/views/admin/variant_overrides/_hidden_products.html.haml index 902e24a232..9f0d4f2d94 100644 --- a/app/views/admin/variant_overrides/_hidden_products.html.haml +++ b/app/views/admin/variant_overrides/_hidden_products.html.haml @@ -10,13 +10,13 @@ %th.product=t('admin.product') %th.variant=t('(admin.variant') %th.add=t('admin.inventory.add') - %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } + %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } - %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ bo: { bind: 'product.name'} } + %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { bind: '::product.name'} } %td.variant - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } = t('admin.inventory.add') diff --git a/app/views/admin/variant_overrides/_new_products.html.haml b/app/views/admin/variant_overrides/_new_products.html.haml index 86ca180b8f..414fba224d 100644 --- a/app/views/admin/variant_overrides/_new_products.html.haml +++ b/app/views/admin/variant_overrides/_new_products.html.haml @@ -11,13 +11,13 @@ %th.variant=t('(admin.variant') %th.add=t('admin.inventory.add') %th.hide=t('admin.inventory.hide') - %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } + %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } - %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ bo: { bind: 'product.name'} } + %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { bind: '::product.name'} } %td.variant - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } = t('admin.inventory.add') diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index f255d86e0b..f0b2310fdc 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -22,6 +22,6 @@ %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.inventory.enable_reset') %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.inventory.inherit') %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.inventory.hide') - %tbody{bindonce: true, ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } + %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index 70b48e3909..b15840dd49 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,6 +1,6 @@ %tr.product.even - %td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} } - %td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} } + %td.producer{ ng: { show: 'columns.producer.visible' }, ng: { bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { show: 'columns.product.visible' }, ng: { bind: '::product.name'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %td.price{ ng: { show: 'columns.price.visible' } } %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index c26be92697..a7a94437be 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -1,8 +1,8 @@ %tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'}} %td.producer{ ng: { show: 'columns.producer.visible' } } %td.product{ ng: { show: 'columns.product.visible' } } - %span{ bo: { bind: 'variant.display_name || ""'} } - .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} } + %span{ ng: { bind: '::variant.display_name || ""'} } + .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'} %td.price{ ng: { show: 'columns.price.visible' } } From 7bc118b598b4d77952451757d3a1b0dec8b31d36 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 20:32:04 +1000 Subject: [PATCH 110/154] Final steps to remove bindonce --- app/assets/javascripts/shared/bindonce.min.js | 1 - config/ng-test.conf.js | 1 - 2 files changed, 2 deletions(-) delete mode 100644 app/assets/javascripts/shared/bindonce.min.js diff --git a/app/assets/javascripts/shared/bindonce.min.js b/app/assets/javascripts/shared/bindonce.min.js deleted file mode 100644 index 0edd3d57d4..0000000000 --- a/app/assets/javascripts/shared/bindonce.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};return l}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}(); \ No newline at end of file diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js index 0cc4fc385e..20ec36622c 100644 --- a/config/ng-test.conf.js +++ b/config/ng-test.conf.js @@ -9,7 +9,6 @@ module.exports = function(config) { 'app/assets/javascripts/shared/jquery-1.8.0.js', // TODO: Can we link to Rails' jquery? 'app/assets/javascripts/shared/jquery.timeago.js', 'app/assets/javascripts/shared/angular-local-storage.js', - 'app/assets/javascripts/shared/bindonce.min.js', 'app/assets/javascripts/shared/ng-infinite-scroll.min.js', 'app/assets/javascripts/shared/angular-slideables.js', From 9fc7908af51afe236b0daf261f429fc05efd15c3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 20:59:36 +1000 Subject: [PATCH 111/154] Removing '?' to meet expectations of js spec --- spec/javascripts/unit/order_cycle_spec.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index b66acfcb1d..025a01a6b2 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -336,7 +336,7 @@ describe 'OrderCycle services', -> inject ($injector, _$httpBackend_)-> Enterprise = $injector.get('Enterprise') $httpBackend = _$httpBackend_ - $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json?').respond [ + $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json').respond [ {id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true} {id: 2, name: 'Two', supplied_products: [3, 4]} {id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'} @@ -412,7 +412,7 @@ describe 'OrderCycle services', -> inject ($injector, _$httpBackend_)-> EnterpriseFee = $injector.get('EnterpriseFee') $httpBackend = _$httpBackend_ - $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json?').respond [ + $httpBackend.whenGET('/admin/enterprise_fees/for_order_cycle.json').respond [ {id: 1, name: "Yayfee", enterprise_id: 1} {id: 2, name: "FeeTwo", enterprise_id: 2} ] From 97e53900ad259594405f09e2e396abea92fad6e1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 22:04:33 +1000 Subject: [PATCH 112/154] Updating translation to start with a capital letter --- app/views/admin/order_cycles/set_coordinator.html.haml | 2 +- config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/set_coordinator.html.haml b/app/views/admin/order_cycles/set_coordinator.html.haml index db9bbb926c..f4c06e0a98 100644 --- a/app/views/admin/order_cycles/set_coordinator.html.haml +++ b/app/views/admin/order_cycles/set_coordinator.html.haml @@ -1,5 +1,5 @@ %h4.text-center - =t'select_a_coordinator_for_your_order_cycle' + =t 'select_a_coordinator_for_your_order_cycle' %br diff --git a/config/locales/en.yml b/config/locales/en.yml index e895522903..79f6e471b1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -869,7 +869,7 @@ Please follow the instructions there to make your enterprise visible on the Open calculator: "Calculator" calculator_values: "Calculator values" new_order_cycles: "New Order Cycles" - select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle" + select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" edit_order_cycle: "Edit Order Cycle" roles: "Roles" update: "Update" From cf94d67caf079bf5278470cf7737b91094ff444c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 22:05:41 +1000 Subject: [PATCH 113/154] Preventing default submit behaviour for order cycle form submission buttons --- .../admin/order_cycles/controllers/create.js.coffee | 3 ++- .../admin/order_cycles/controllers/edit.js.coffee | 5 +++++ .../order_cycles/controllers/simple_create.js.coffee | 3 ++- .../admin/order_cycles/controllers/simple_edit.js.coffee | 3 ++- app/assets/javascripts/templates/admin/save_bar.html.haml | 2 +- app/views/admin/order_cycles/_form.html.haml | 2 +- app/views/admin/order_cycles/_simple_form.html.haml | 2 +- spec/javascripts/unit/order_cycle_spec.js.coffee | 8 ++++++-- 8 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee index 2c98d60f0e..c837ff84ec 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -79,5 +79,6 @@ angular.module('admin.orderCycles') $scope.removeDistributionOfVariant = (variant_id) -> OrderCycle.removeDistributionOfVariant(variant_id) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index 227c6a3045..d4c63121fe 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -85,6 +85,11 @@ angular.module('admin.orderCycles') OrderCycle.removeDistributionOfVariant(variant_id) $scope.submit = (destination) -> + $event.preventDefault() + StatusMessage.display 'progress', "Saving..." + + $scope.submit = ($event, destination) -> + $event.preventDefault() StatusMessage.display 'progress', "Saving..." OrderCycle.update(destination) $scope.order_cycle_form.$setPristine() diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee index 53f00cdc36..a6d1e18535 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -41,6 +41,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" $scope.enterpriseFeesForEnterprise = (enterprise_id) -> EnterpriseFee.forEnterprise(parseInt(enterprise_id)) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.create(destination) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index 99c2e2649c..d3eeb1c9d8 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -37,7 +37,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $event.preventDefault() OrderCycle.removeCoordinatorFee(index) - $scope.submit = (destination) -> + $scope.submit = ($event, destination) -> + $event.preventDefault() StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() OrderCycle.update(destination) diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index 08b7cbdf3d..b710c070ca 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -4,4 +4,4 @@ %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} .eight.columns.omega.text-right - %input{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { class: "button.class", click: "button.action(button.param)" } } + %input{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { class: "button.class", click: "button.action($event, button.param)" } } diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index ed2d3acbc2..f61c1ad91f 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -51,7 +51,7 @@ .actions - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' + = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' %span{'ng-show' => 'loaded()'} = link_to 'Cancel', main_app.admin_order_cycles_path diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 9364d080ff..7fcf0ea80c 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -22,7 +22,7 @@ .actions - if @order_cycle.new_record? - = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' + = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' %span{'ng-show' => 'loaded()'} = link_to 'Cancel', main_app.admin_order_cycles_path diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 025a01a6b2..45dea6d36e 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -156,7 +156,9 @@ describe 'OrderCycle controllers', -> expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('variant') it 'Submits the order cycle via OrderCycle create', -> - scope.submit('/admin/order_cycles') + eventMock = {preventDefault: jasmine.createSpy()} + scope.submit(eventMock,'/admin/order_cycles') + expect(eventMock.preventDefault).toHaveBeenCalled() expect(OrderCycle.create).toHaveBeenCalledWith('/admin/order_cycles') describe 'AdminEditOrderCycleCtrl', -> @@ -321,7 +323,9 @@ describe 'OrderCycle controllers', -> expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('variant') it 'Submits the order cycle via OrderCycle update', -> - scope.submit('/admin/order_cycles') + eventMock = {preventDefault: jasmine.createSpy()} + scope.submit(eventMock,'/admin/order_cycles') + expect(eventMock.preventDefault).toHaveBeenCalled() expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') expect(scope.order_cycle_form.$setPristine.calls.length).toEqual 1 From 8838a89ecca5db54cb9e0753c5f6d60178483655 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 17 Apr 2016 23:15:11 +1000 Subject: [PATCH 114/154] Don't typecast product and variant on_hand from DOM when it is 'On demand' --- .../admin/products/bulk_edit/_products_product.html.haml | 4 ++-- .../admin/products/bulk_edit/_products_variant.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 6ac25ae286..01af8014d1 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -18,8 +18,8 @@ %td.price{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } - %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } - %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } + %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } + %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' } %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } %td.category{ 'ng-if' => 'columns.category.visible' } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index fb68704b79..f2e65d6c88 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -14,8 +14,8 @@ %td{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } %td{ 'ng-show' => 'columns.on_hand.visible' } - %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } - %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } + %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } + %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' } %td{ 'ng-show' => 'columns.on_demand.visible' } %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } %td{ 'ng-show' => 'columns.category.visible' } From 10133a13f242f2c09e0e3e9694d73346283a83d1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 18 Apr 2016 00:05:37 +1000 Subject: [PATCH 115/154] Make sure that changes are saved before attempting to click link --- spec/features/admin/bulk_order_management_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 0b23e77f6c..de5bd85d3e 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -571,6 +571,7 @@ feature %q{ # So we save the changes expect(URI.parse(current_url).path).to eq "/admin/orders/bulk_management" click_button "Save Changes" + expect(page).to have_selector "#save-bar", text: "All changes saved" # And try again within "tr#li_#{li1.id}" do From 383f7c57aaa467b5421bc3e66f3b5ea2bef41ffa Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 18 Apr 2016 01:19:02 +1000 Subject: [PATCH 116/154] Wait for login before visiting CMS --- spec/features/admin/cms_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/admin/cms_spec.rb b/spec/features/admin/cms_spec.rb index 2d4056b792..8ab180906e 100644 --- a/spec/features/admin/cms_spec.rb +++ b/spec/features/admin/cms_spec.rb @@ -26,6 +26,7 @@ feature %q{ scenario "non-admin user can't access CMS admin", js: true do login_to_consumer_section + page.should_not have_content "Login" visit cms_admin_path page.should_not have_content "ComfortableMexicanSofa" current_path.should == root_path From 02d093f6b65c129eace927b3ae9d16960b742555 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 18 Apr 2016 01:20:38 +1000 Subject: [PATCH 117/154] Loading selectors into product modal to display proprties and taxons --- .../controllers/products/product_node_controller.js.coffee | 5 +++-- app/assets/javascripts/templates/product_modal.html.haml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee index 07ddb4bc61..a692af3fb5 100644 --- a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee @@ -1,6 +1,7 @@ -Darkswarm.controller "ProductNodeCtrl", ($scope, $modal) -> +Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) -> $scope.enterprise = $scope.product.supplier # For the modal, so it's consistent $scope.triggerProductModal = -> + $scope.productTaxonSelectors = FilterSelectorsService.createSelectors() + $scope.productPropertySelectors = FilterSelectorsService.createSelectors() $modal.open(templateUrl: "product_modal.html", scope: $scope) - diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml index f4839f6512..4b21c054a4 100644 --- a/app/assets/javascripts/templates/product_modal.html.haml +++ b/app/assets/javascripts/templates/product_modal.html.haml @@ -9,10 +9,10 @@ %br .filter-shopfront.taxon-selectors.inline-block - %filter-selector{ objects: "[product] | taxonsOf" } + %filter-selector{ 'selector-set' => "productTaxonSelectors", objects: "[product] | taxonsOf" } .filter-shopfront.property-selectors.inline-block - %filter-selector{ objects: "[product] | propertiesWithValuesOf" } + %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } %div{"ng-if" => "product.description"} %hr From 668bffcd844af74db17f92d12416a38fe9567bcf Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 18 Apr 2016 01:47:33 +1000 Subject: [PATCH 118/154] Bumping AngularJS to 1.4.8 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 1a635a80f9..9344aac5e0 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa' gem 'simple_form', :github => 'RohanM/simple_form' gem 'unicorn' -gem 'angularjs-rails', '1.3.15' +gem 'angularjs-rails', '1.4.8' gem 'bugsnag' gem 'newrelic_rpm' gem 'haml' diff --git a/Gemfile.lock b/Gemfile.lock index ed44002f2a..f4a1915a56 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM sprockets (~> 2) tilt angularjs-file-upload-rails (1.1.0) - angularjs-rails (1.3.15) + angularjs-rails (1.4.8) ansi (1.4.2) arel (3.0.3) atomic (1.1.99) @@ -650,7 +650,7 @@ DEPENDENCIES andand angular-rails-templates (~> 0.2.0) angularjs-file-upload-rails (~> 1.1.0) - angularjs-rails (= 1.3.15) + angularjs-rails (= 1.4.8) atomic awesome_print aws-sdk @@ -734,4 +734,4 @@ DEPENDENCIES wkhtmltopdf-binary BUNDLED WITH - 1.10.6 + 1.11.2 From af5eec70947044034119e67083852776317dd320 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 18:28:25 +1000 Subject: [PATCH 119/154] WIP (Angular 1.4 Upgrade): Adding filters to ofn-select2 and using on order create/edit UI --- .../directives/ofn-select2.js.coffee | 8 ++++-- .../controllers/orders_controller.js.coffee | 14 ++++++---- .../javascripts/admin/orders/orders.js.coffee | 2 +- .../add_distribution_fields.html.haml.deface | 21 ++++++++++----- spec/features/admin/orders_spec.rb | 26 +++++++++++-------- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee index 132480d987..bbb5221682 100644 --- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) -> +angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $filter) -> require: 'ngModel' restrict: 'C' scope: @@ -6,15 +6,19 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) minSearch: "@?" text: "@?" blank: "=?" + filter: "=?" link: (scope, element, attrs, ngModel) -> $timeout -> scope.text ||= 'name' + scope.filter ||= -> true scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" item.name = $sanitize(item.name) for item in scope.data element.select2 minimumResultsForSearch: scope.minSearch || 0 - data: { results: scope.data, text: scope.text } + data: -> + filtered = $filter('filter')(scope.data,scope.filter) + { results: filtered, text: scope.text } formatSelection: (item) -> item[scope.text] formatResult: (item) -> diff --git a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee index 6ad7f0bfb9..060b23bed9 100644 --- a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee +++ b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee @@ -2,13 +2,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr $scope.$compile = $compile $scope.shops = shops $scope.orderCycles = orderCycles - for oc in $scope.orderCycles - oc.name_and_status = "#{oc.name} (#{oc.status})" - $scope.distributor_id = $attrs.ofnDistributorId - $scope.order_cycle_id = $attrs.ofnOrderCycleId + $scope.distributor_id = parseInt($attrs.ofnDistributorId) + $scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId) - $scope.validOrderCycle = (oc, index, array) -> + $scope.validOrderCycle = (oc) -> $scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id) $scope.distributorHasOrderCycles = (distributor) -> @@ -20,3 +18,9 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr $scope.distributionChosen = -> $scope.distributor_id && $scope.order_cycle_id + + for oc in $scope.orderCycles + oc.name_and_status = "#{oc.name} (#{oc.status})" + + for shop in $scope.shops + shop.disabled = !$scope.distributorHasOrderCycles(shop) diff --git a/app/assets/javascripts/admin/orders/orders.js.coffee b/app/assets/javascripts/admin/orders/orders.js.coffee index abfe576095..4e1ec754e3 100644 --- a/app/assets/javascripts/admin/orders/orders.js.coffee +++ b/app/assets/javascripts/admin/orders/orders.js.coffee @@ -1 +1 @@ -angular.module("admin.orders", ['ngResource']) +angular.module("admin.orders", ['admin.indexUtils', 'ngResource']) diff --git a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface index 3fa7d3ea83..a6c51d517c 100644 --- a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface +++ b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface @@ -19,11 +19,20 @@ .alpha.six.columns .field %label{for: "order_distributor_id"} Distributor - %select.fullwidth{id: "order_distributor_id", name: "order[distributor_id]", 'ng-model' => 'distributor_id'} - %option{"ng-repeat" => "shop in shops", "ng-value" => "shop.id", "ng-selected" => "distributor_id == shop.id", "ng-disabled" => "!distributorHasOrderCycles(shop)", "ng-bind" => "shop.name"} + %input.ofn-select2.fullwidth{id: "order_distributor_id", + type: 'number', + name: "order[distributor_id]", + "ng-model" => 'distributor_id', + data: "shops" } .omega.six.columns - .field{"ng-show" => "distributor_id"} - %label{for: "order_order_cycle_id"} Order Cycle - %select.select2.fullwidth{id: "order_order_cycle_id", name: "order[order_cycle_id]", 'ng-model' => 'order_cycle_id'} - %option{"ng-repeat" => "oc in orderCycles | filter:validOrderCycle", "ng-value" => "oc.id", "ng-selected" => "order_cycle_id == oc.id", "ng-bind" => "oc.name_and_status"} + .field + %label{ for: "order_order_cycle_id"} Order Cycle + %input.ofn-select2.fullwidth{id: "order_order_cycle_id", + type: 'number', + name: "order[order_cycle_id]", + "ng-model" => 'order_cycle_id', + "ng-disabled" => "!distributor_id", + data: "orderCycles", + text: "name_and_status", + filter: "validOrderCycle" } diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 725495b74b..33f91eb850 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -21,7 +21,7 @@ feature %q{ create :check_payment, order: @order, amount: @order.total end - scenario "creating an order with distributor and order cycle", retry: 3 do + scenario "creating an order with distributor and order cycle" do distributor_disabled = create(:distributor_enterprise) create(:simple_order_cycle, name: 'Two') @@ -31,12 +31,16 @@ feature %q{ click_link 'New Order' # Distributors without an order cycle should be shown as disabled - page.should have_selector "option[value='#{distributor_disabled.id}'][disabled='disabled']" + open_select2('#s2id_order_distributor_id') + page.should have_selector "ul.select2-results li.select2-result.select2-disabled", text: distributor_disabled.name + close_select2('#s2id_order_distributor_id') + + # Order cycle selector should be disabled + page.should have_selector "#s2id_order_order_cycle_id.select2-container-disabled" # When we select a distributor, it should limit order cycle selection to those for that distributor - page.should_not have_select2 'order_order_cycle_id' - select @distributor.name, from: 'order_distributor_id' - page.should have_select2 'order_order_cycle_id', options: ['', 'One (open)'] + select2_select @distributor.name, from: 'order_distributor_id' + page.should have_select2 'order_order_cycle_id', options: ['One (open)'] select2_select @order_cycle.name, from: 'order_order_cycle_id' page.should have_content 'ADD PRODUCT' @@ -80,7 +84,7 @@ feature %q{ click_edit - select d.name, from: 'order_distributor_id' + select2_select d.name, from: 'order_distributor_id' select2_select oc.name, from: 'order_order_cycle_id' click_button 'Update And Recalculate Fees' @@ -106,7 +110,7 @@ feature %q{ visit '/admin/orders' page.find('td.actions a.icon-edit').click - page.should have_no_select 'order_distributor_id' + page.should_not have_select2 'order_distributor_id' page.should_not have_select2 'order_order_cycle_id' page.should have_selector 'p', text: "Distributor: #{@order.distributor.name}" @@ -124,7 +128,7 @@ feature %q{ login_to_admin_section visit '/admin/orders' click_link 'New Order' - select @distributor.name, from: 'order_distributor_id' + select2_select @distributor.name, from: 'order_distributor_id' select2_select @order_cycle.name, from: 'order_order_cycle_id' targetted_select2_search @product.name, from: '#add_variant_id', dropdown_css: '.select2-drop' click_link 'Add' @@ -208,7 +212,7 @@ feature %q{ visit '/admin/orders' click_link 'New Order' - select distributor1.name, from: 'order_distributor_id' + select2_select distributor1.name, from: 'order_distributor_id' select2_select order_cycle1.name, from: 'order_order_cycle_id' expect(page).to have_content 'ADD PRODUCT' @@ -218,8 +222,8 @@ feature %q{ page.has_selector? "table.index tbody[data-hook='admin_order_form_line_items'] tr" # Wait for JS expect(page).to have_selector 'td', text: product.name - expect(page).to have_select 'order_distributor_id', with_options: [distributor1.name] - expect(page).to_not have_select 'order_distributor_id', with_options: [distributor2.name] + expect(page).to have_select2 'order_distributor_id', with_options: [distributor1.name] + expect(page).to_not have_select2 'order_distributor_id', with_options: [distributor2.name] expect(page).to have_select2 'order_order_cycle_id', with_options: ["#{order_cycle1.name} (open)"] expect(page).to_not have_select2 'order_order_cycle_id', with_options: ["#{order_cycle2.name} (open)"] From 39a062e90a87718777d9699fe823b705a2c5e716 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 18:29:09 +1000 Subject: [PATCH 120/154] WIP (Angular 1.4 Upgrade): Moving controllers for login tabs to prevent multiple directives asking for isolate scope --- app/assets/javascripts/templates/forgot.html.haml | 9 ++------- app/assets/javascripts/templates/login.html.haml | 7 ++----- app/assets/javascripts/templates/signup.html.haml | 7 ++----- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/templates/forgot.html.haml b/app/assets/javascripts/templates/forgot.html.haml index f85b91ab0a..3e88350321 100644 --- a/app/assets/javascripts/templates/forgot.html.haml +++ b/app/assets/javascripts/templates/forgot.html.haml @@ -1,10 +1,5 @@ -%tab#forgot{"ng-controller" => "ForgotCtrl", - heading: "{{'forgot_password' | t}}", - active: "active", - select: "select(path)"} - - %form{"ng-submit" => "submit()"} - +%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "active", select: "select(path)"} + %form{ ng: { controller: "ForgotCtrl", submit: "submit()" } } .row .large-12.columns .alert-box.success.radius{"ng-show" => "sent"} diff --git a/app/assets/javascripts/templates/login.html.haml b/app/assets/javascripts/templates/login.html.haml index 3d2f9a3e06..8b3e64cfb1 100644 --- a/app/assets/javascripts/templates/login.html.haml +++ b/app/assets/javascripts/templates/login.html.haml @@ -1,8 +1,5 @@ -%tab#login-content{"ng-controller" => "LoginCtrl", - heading: "{{'label_login' | t}}", - active: "active", - select: "select(path)"} - %form{"ng-submit" => "submit()"} +%tab#login-content{ heading: "{{'label_login' | t}}", active: "active", select: "select(path)"} + %form{ ng: { controller: "LoginCtrl", submit: "submit()" } } .row .large-12.columns .alert-box.alert{"ng-show" => "errors != null"} diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index c71d9453bd..1c13985f38 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -1,8 +1,5 @@ -%tab#sign-up-content{"ng-controller" => "SignupCtrl", - heading: "{{'label_signup' | t}}", - active: 'active', - select: "select(path)"} - %form{"ng-submit" => "submit()"} +%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'active', select: "select(path)"} + %form{ ng: { controller: "SignupCtrl", submit: "submit()" } } .row .large-12.columns %label{for: "email"} {{'signup_email' | t}} From 9d1ca6eaca70c9f15340cd18d8b2e0eaa706fb3c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 18:49:17 +1000 Subject: [PATCH 121/154] Renaming customers form to prevent conflict with customers array --- app/views/admin/customers/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 0a348d4a0a..dc38304f8e 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -30,7 +30,7 @@ .row{ ng: { show: "loaded && filteredCustomers.length > 0" } } - %form{ name: "customers" } + %form{ name: "customers_form" } %table.index#customers %col.email{ width: "20%"} %col.code{ width: "20%"} From 1b0897d53aaa41a723969097d2f29e4ce40ee5c3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 18:52:13 +1000 Subject: [PATCH 122/154] Bump to AngularJS to 1.5.5 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 9344aac5e0..eb49f89c23 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa' gem 'simple_form', :github => 'RohanM/simple_form' gem 'unicorn' -gem 'angularjs-rails', '1.4.8' +gem 'angularjs-rails', '1.5.5' gem 'bugsnag' gem 'newrelic_rpm' gem 'haml' diff --git a/Gemfile.lock b/Gemfile.lock index f4a1915a56..494882481c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,7 +153,7 @@ GEM sprockets (~> 2) tilt angularjs-file-upload-rails (1.1.0) - angularjs-rails (1.4.8) + angularjs-rails (1.5.5) ansi (1.4.2) arel (3.0.3) atomic (1.1.99) @@ -650,7 +650,7 @@ DEPENDENCIES andand angular-rails-templates (~> 0.2.0) angularjs-file-upload-rails (~> 1.1.0) - angularjs-rails (= 1.4.8) + angularjs-rails (= 1.5.5) atomic awesome_print aws-sdk From cfbfe8416fc3493046743d04d265400d8a8da37f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 21:09:44 +1000 Subject: [PATCH 123/154] WIP (AngularJS 1.5.5 upgrade): updating npm karma packages for travis --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 56d94e5952..279eecb77c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,11 +28,11 @@ before_script: - RAILS_ENV=test bundle exec rake db:create db:schema:load - > if [ "$KARMA" = "true" ]; then - npm install karma@0.12.31 - npm install karma-jasmine@0.1.5 - npm install karma-phantomjs-launcher@0.1.4 - npm install karma-coffee-preprocessor@0.2.1 - npm install -g karma-cli@0.0.4 + npm install karma@0.13.22 + npm install karma-jasmine@0.3.8 + npm install karma-phantomjs-launcher@1.0.0 + npm install karma-coffee-preprocessor@0.3.0 + npm install -g karma-cli@0.1.2 fi script: From 7a498362b36d50d3c9f507d88b6ef2084cd82f1b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 1 May 2016 21:10:28 +1000 Subject: [PATCH 124/154] Upgrading AngularJS specs to Jasmine 2 syntax --- .../customers_controller_spec.js.coffee | 17 ++--- .../enterprise_controller_spec.js.coffee | 2 +- .../enterprises_controller_spec.js.coffee | 2 +- .../index_panel_controller_spec.js.coffee | 4 +- .../permalink_controller_spec.js.coffee | 8 +-- .../side_menu_controller_spec.js.coffee | 4 +- .../enterprise_payment_methods_spec.js.coffee | 6 +- ...enterprise_shipping_methods_spec.js.coffee | 6 +- .../services/enterprises_spec.js.coffee | 11 ++-- .../services/panels_spec.js.coffee | 6 +- .../services/pending_changes_spec.js.coffee | 16 ++--- .../services/switch_class_spec.js.coffee | 6 +- .../index_utils/services/views_spec.js.coffee | 2 +- .../line_items_controller_spec.js.coffee | 27 ++++---- .../services/line_items_spec.js.coffee | 11 ++-- .../controllers/simple_create.js.coffee | 6 +- .../services/order_cycles_spec.js.coffee | 11 ++-- .../orders/services/orders_spec.js.coffee | 11 ++-- .../option_value_namer_spec.js.coffee | 20 +++--- .../side_menu/services/side_menu.js.coffee | 8 +-- .../tag_rules_controller_spec.js.coffee | 2 +- .../unit/bulk_order_management_spec.js.coffee | 38 +++++------ .../unit/bulk_product_update_spec.js.coffee | 26 ++++---- .../checkout_controller_spec.js.coffee | 2 +- .../darkswarm/services/cart_spec.js.coffee | 20 +++--- .../services/checkout_spec.js.coffee | 3 +- .../darkswarm/services/sidebar_spec.js.coffee | 9 +-- .../unit/order_cycle_spec.js.coffee | 66 +++++++++---------- 28 files changed, 177 insertions(+), 173 deletions(-) diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 6981ef4e4f..1da89f2053 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -8,9 +8,10 @@ describe "CustomersCtrl", -> scope = $rootScope http = $httpBackend $controller 'customersCtrl', {$scope: scope, CustomerResource: _CustomerResource_, shops: {}} - this.addMatchers - toAngularEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } it "has no shop pre-selected", -> expect(scope.shop).toEqual {} @@ -26,7 +27,7 @@ describe "CustomersCtrl", -> http.flush() it "retrievs the list of customers", -> - expect(scope.customers).toAngularEqual customers + expect(scope.customers).toDeepEqual customers describe "scope.add", -> it "creates a new customer", -> @@ -36,7 +37,7 @@ describe "CustomersCtrl", -> http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=1').respond 200, newCustomer scope.add(email) http.flush() - expect(scope.customers).toAngularEqual customers + expect(scope.customers).toDeepEqual customers describe "scope.deleteCustomer", -> it "deletes a customer", -> @@ -46,7 +47,7 @@ describe "CustomersCtrl", -> scope.deleteCustomer(customer) http.flush() expect(scope.customers.length).toBe 1 - expect(scope.customers[0]).not.toAngularEqual customer + expect(scope.customers[0]).not.toDeepEqual customer describe "scope.findTags", -> tags = [ @@ -63,7 +64,7 @@ describe "CustomersCtrl", -> promise.then (data) -> result = data http.flush() - expect(result).toAngularEqual tags + expect(result).toDeepEqual tags it "filters the tag list", -> filtered_tags = [ @@ -75,4 +76,4 @@ describe "CustomersCtrl", -> promise.then (data) -> result = data http.flush() - expect(result).toAngularEqual filtered_tags + expect(result).toDeepEqual filtered_tags diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee index b504ff8f90..450876c9e6 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprise_controller_spec.js.coffee @@ -53,7 +53,7 @@ describe "enterpriseCtrl", -> expect(enterprise.users).not.toContain u4 it "ignores objects that are already in the list, and alerts the user", -> - spyOn(window, "alert").andCallThrough() + spyOn(window, "alert").and.callThrough() u4 = { id: 3, email: "email-doesn't-matter.com" } scope.addManager u4 expect(enterprise.users).not.toContain u4 diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee index 6e8bdd8040..573ca33d9f 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee @@ -8,7 +8,7 @@ describe "EnterprisesCtrl", -> inject ($controller, $rootScope, _Enterprises_) -> scope = $rootScope Enterprises = _Enterprises_ - spyOn(Enterprises, "index").andReturn "list of enterprises" + spyOn(Enterprises, "index").and.returnValue "list of enterprises" ctrl = $controller 'enterprisesCtrl', {$scope: scope, Enterprises: Enterprises} describe "setting the shop on scope", -> diff --git a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee index b74595b775..8fca1c6ab5 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/index_panel_controller_spec.js.coffee @@ -20,10 +20,10 @@ describe "indexPanelCtrl", -> deferred = null beforeEach inject ($q) -> - spyOn(scope, "saved").andReturn false + spyOn(scope, "saved").and.returnValue false spyOn(scope, "$emit") deferred = $q.defer() - spyOn(Enterprises, "save").andReturn(deferred.promise) + spyOn(Enterprises, "save").and.returnValue(deferred.promise) scope.save() it "sets scope.saving to true", -> diff --git a/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee index 888a42daa3..bcecc1c5a6 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/permalink_controller_spec.js.coffee @@ -28,21 +28,21 @@ describe "permalinkCtrl", -> it "sends a request to PermalinkChecker when permalink is changed", -> deferred.resolve("") promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect(PermalinkChecker.check).toHaveBeenCalled() it "sets available to '' when PermalinkChecker resolves permalink to the existing permalink on Enterprise ", -> deferred.resolve({permalink: "something"}) promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect($scope.availability).toEqual "" it "sets available and permalink when PermalinkChecker resolves", -> deferred.resolve({ available: "Available", permalink: "permalink"}) promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect(Enterprise.permalink).toEqual "permalink" expect($scope.availability).toEqual "Available" @@ -51,7 +51,7 @@ describe "permalinkCtrl", -> $scope.availability = "Some Availability" deferred.reject() promise = deferred.promise - spyOn(PermalinkChecker, "check").andReturn promise + spyOn(PermalinkChecker, "check").and.returnValue promise $scope.$apply Enterprise.permalink = "somethingelse" # Change the permalink expect($scope.availability).toEqual "Some Availability" expect(Enterprise.permalink).toEqual "somethingelse" diff --git a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee index e88aeb44f2..c4a724a137 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/side_menu_controller_spec.js.coffee @@ -17,8 +17,8 @@ describe "menuCtrl", -> inject ($rootScope, $controller, _SideMenu_) -> scope = $rootScope SideMenu = _SideMenu_ - spyOn(SideMenu, "select").andCallThrough() - spyOn(SideMenu, "setItems").andCallThrough() + spyOn(SideMenu, "select").and.callThrough() + spyOn(SideMenu, "setItems").and.callThrough() ctrl = $controller 'sideMenuCtrl', {$scope: scope, enterprise: enterprise, SideMenu: SideMenu, enterprisePermissions: {}} describe "initialisation", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee index 0a719b203a..c4e83cd283 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprise_payment_methods_spec.js.coffee @@ -27,16 +27,16 @@ describe "EnterprisePaymentMethods service", -> describe "determining payment method colour", -> it "returns 'blue' when at least one payment method is selected", -> - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 1 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 1 expect(EnterprisePaymentMethods.displayColor()).toBe "blue" it "returns 'red' when no payment methods are selected", -> - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 0 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 0 expect(EnterprisePaymentMethods.displayColor()).toBe "red" it "returns 'red' when no payment methods exist", -> EnterprisePaymentMethods.paymentMethods = [] - spyOn(EnterprisePaymentMethods, "selectedCount").andReturn 1 + spyOn(EnterprisePaymentMethods, "selectedCount").and.returnValue 1 expect(EnterprisePaymentMethods.displayColor()).toBe "red" describe "counting selected payment methods", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee index 4b857023b8..d8b83cf6c7 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprise_shipping_methods_spec.js.coffee @@ -27,16 +27,16 @@ describe "EnterpriseShippingMethods service", -> describe "determining shipping method colour", -> it "returns 'blue' when at least one shipping method is selected", -> - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 1 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 1 expect(EnterpriseShippingMethods.displayColor()).toBe "blue" it "returns 'red' when no shipping methods are selected", -> - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 0 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 0 expect(EnterpriseShippingMethods.displayColor()).toBe "red" it "returns 'red' when no shipping methods exist", -> EnterpriseShippingMethods.shippingMethods = [] - spyOn(EnterpriseShippingMethods, "selectedCount").andReturn 1 + spyOn(EnterpriseShippingMethods, "selectedCount").and.returnValue 1 expect(EnterpriseShippingMethods.displayColor()).toBe "red" describe "counting selected shipping methods", -> diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index fb653d2df4..702ef2cc1c 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -4,9 +4,10 @@ describe "Enterprises service", -> beforeEach -> module 'admin.enterprises' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _Enterprises_, _EnterpriseResource_) -> Enterprises = _Enterprises_ @@ -98,14 +99,14 @@ describe "Enterprises service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(Enterprises, "diff").andReturn ["attr1", "attr2"] + spyOn(Enterprises, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(Enterprises.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(Enterprises, "diff").andReturn [] + spyOn(Enterprises, "diff").and.returnValue [] it "returns false", -> expect(Enterprises.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee index a55d9ffa61..7ee69cf88e 100644 --- a/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/panels_spec.js.coffee @@ -29,7 +29,7 @@ describe "Panels service", -> describe "when no panel is currently selected", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn(null) + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue(null) Panels.toggle(12, 'panel_name') it "calls #open on the scope", -> @@ -37,7 +37,7 @@ describe "Panels service", -> describe "when #toggle is called for the currently selected panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn('panel_name') + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('panel_name') Panels.toggle(12, 'panel_name') it "calls #close on the scope", -> @@ -45,7 +45,7 @@ describe "Panels service", -> describe "when #toggle is called for a different panel", -> beforeEach -> - scopeMock.getSelected = jasmine.createSpy('getSelected').andReturn('some_other_panel_name') + scopeMock.getSelected = jasmine.createSpy('getSelected').and.returnValue('some_other_panel_name') Panels.toggle(12, 'panel_name') it "calls #setSelected on the scope", -> diff --git a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee index 31b85df217..df0eaea495 100644 --- a/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/pending_changes_spec.js.coffee @@ -4,7 +4,7 @@ describe "Pending Changes", -> beforeEach -> resourcesMock = - update: jasmine.createSpy('update').andCallFake (change) -> + update: jasmine.createSpy('update').and.callFake (change) -> $promise: then: (successFn, errorFn) -> return successFn({propertyName: "new_value"}) if change.success @@ -88,7 +88,7 @@ describe "Pending Changes", -> it "sends the correct object to dataSubmitter", -> pendingChanges.submit change - expect(resourcesMock.update.calls.length).toEqual 1 + expect(resourcesMock.update.calls.count()).toBe 1 expect(resourcesMock.update).toHaveBeenCalledWith change describe "successful request", -> @@ -96,9 +96,9 @@ describe "Pending Changes", -> change.success = true it "calls remove with id and attribute name", -> - spyOn(pendingChanges, "remove").andCallFake(->) + spyOn(pendingChanges, "remove").and.callFake(->) pendingChanges.submit change - expect(pendingChanges.remove.calls.length).toEqual 1 + expect(pendingChanges.remove.calls.count()).toBe 1 expect(pendingChanges.remove).toHaveBeenCalledWith 1, "propertyName" it "calls reset on the relevant scope", -> @@ -114,7 +114,7 @@ describe "Pending Changes", -> change.success = false it "does not call remove", -> - spyOn(pendingChanges, "remove").andCallFake(->) + spyOn(pendingChanges, "remove").and.callFake(->) pendingChanges.submit change expect(pendingChanges.remove).not.toHaveBeenCalled() @@ -128,13 +128,13 @@ describe "Pending Changes", -> describe "cycling through all changes to submit to server", -> it "sends the correct object to dataSubmitter", -> - spyOn(pendingChanges, "submit").andCallFake(->) + spyOn(pendingChanges, "submit").and.callFake(->) pendingChanges.pendingChanges = 1: { "prop1": { attr: "prop1", value: 1 }, "prop2": { attr: "prop2", value: 2 } } 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop2", value: 4 } } 7: { "prop2": { attr: "prop2", value: 5 } } pendingChanges.submitAll() - expect(pendingChanges.submit.calls.length).toEqual 5 + expect(pendingChanges.submit.calls.count()).toBe 5 expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 1 } expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 2 } expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop1", value: 2 } @@ -142,7 +142,7 @@ describe "Pending Changes", -> expect(pendingChanges.submit).toHaveBeenCalledWith { attr: "prop2", value: 5 } it "returns an array of promises representing all sumbit requests", -> - spyOn(pendingChanges, "submit").andCallFake (change) -> change.value + spyOn(pendingChanges, "submit").and.callFake (change) -> change.value pendingChanges.pendingChanges = 1: { "prop1": { attr: "prop1", value: 1 } } 2: { "prop1": { attr: "prop1", value: 2 }, "prop2": { attr: "prop1", value: 4 } } diff --git a/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee index e7dedb4e92..117231ca14 100644 --- a/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/switch_class_spec.js.coffee @@ -8,7 +8,7 @@ describe "switchClass service", -> elementMock = addClass: addClass removeClass: removeClass - timeoutMock = jasmine.createSpy('timeout').andReturn "new timeout" + timeoutMock = jasmine.createSpy('timeout').and.returnValue "new timeout" timeoutMock.cancel = jasmine.createSpy('timeout.cancel') beforeEach -> @@ -22,14 +22,14 @@ describe "switchClass service", -> it "calls addClass on the element once", -> switchClassService elementMock, "addClass", [], false expect(addClass).toHaveBeenCalledWith "addClass" - expect(addClass.calls.length).toEqual 1 + expect(addClass.calls.count()).toBe 1 it "calls removeClass on the element for ", -> switchClassService elementMock, "", ["remClass1", "remClass2", "remClass3"], false expect(removeClass).toHaveBeenCalledWith "remClass1" expect(removeClass).toHaveBeenCalledWith "remClass2" expect(removeClass).toHaveBeenCalledWith "remClass3" - expect(removeClass.calls.length).toEqual 3 + expect(removeClass.calls.count()).toBe 3 it "call cancel on element.timout only if it exists", -> switchClassService elementMock, "", [], false diff --git a/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee index 7333882d5d..77df49d621 100644 --- a/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/views_spec.js.coffee @@ -9,7 +9,7 @@ describe "Views service", -> describe "setting views", -> beforeEach -> - spyOn(Views, "selectView").andCallThrough() + spyOn(Views, "selectView").and.callThrough() Views.setViews view1: { name: 'View1', visible: true } view2: { name: 'View2', visible: false } diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index 7a0b165e2b..5b4e4730b6 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -5,9 +5,10 @@ describe "LineItemsCtrl", -> beforeEach -> module "admin.lineItems" - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } beforeEach inject(($controller, $rootScope, $httpBackend, _$timeout_, _VariantUnitManager_, _Enterprises_, _Orders_, _LineItems_, _OrderCycles_) -> scope = $rootScope.$new() @@ -19,9 +20,9 @@ describe "LineItemsCtrl", -> LineItems = _LineItems_ OrderCycles = _OrderCycles_ VariantUnitManager = _VariantUnitManager_ - spyOn(window, "daysFromToday").andReturn "SomeDate" - spyOn(window, "formatDate").andReturn "SomeDate" - spyOn(window, "parseDate").andReturn "SomeDate" + spyOn(window, "daysFromToday").and.returnValue "SomeDate" + spyOn(window, "formatDate").and.returnValue "SomeDate" + spyOn(window, "parseDate").and.returnValue "SomeDate" supplier = { id: 1, name: "Supplier" } distributor = { id: 5, name: "Distributor" } @@ -51,7 +52,7 @@ describe "LineItemsCtrl", -> expect(scope.quickSearch).toBeUndefined() it "will not have reset the form state to pristine", -> - expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 0 + expect(scope.bulk_order_form.$setPristine.calls.count()).toBe 0 describe "after data is returned", -> beforeEach -> @@ -87,13 +88,13 @@ describe "LineItemsCtrl", -> expect(scope.quickSearch).toBe = "" it "resets the form state to pristine", -> - expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 1 + expect(scope.bulk_order_form.$setPristine.calls.count()).toBe 1 describe "deleting a line item", -> order = line_item1 = line_item2 = null beforeEach inject((LineItemResource) -> - spyOn(window,"confirm").andReturn true + spyOn(window,"confirm").and.returnValue true order = { number: "R12345678" } line_item1 = new LineItemResource({ id: 1, order: order }) line_item2 = new LineItemResource({ id: 2, order: order }) @@ -247,7 +248,7 @@ describe "LineItemsCtrl", -> # A Units Variant is an API object which holds unit properies of a variant beforeEach -> - spyOn(Math,"round").andCallThrough() + spyOn(Math,"round").and.callThrough() it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> expect(scope.formattedValueWithUnitName(1,{})).toEqual '' @@ -267,14 +268,14 @@ describe "LineItemsCtrl", -> it "calls Math.round with the quotient of scale and value, multiplied by 1000", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 5 + spyOn(VariantUnitManager, "getScale").and.returnValue 5 scope.formattedValueWithUnitName(10, unitsVariant) expect(Math.round).toHaveBeenCalledWith 10/5 * 1000 it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 1000 - spyOn(VariantUnitManager, "getUnitName").andReturn "kg" + spyOn(VariantUnitManager, "getScale").and.returnValue 1000 + spyOn(VariantUnitManager, "getUnitName").and.returnValue "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" describe "updating the price upon updating the weight of a line item", -> diff --git a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee index 44948ecb37..ef428377c0 100644 --- a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee @@ -4,9 +4,10 @@ describe "LineItems service", -> beforeEach -> module 'admin.lineItems' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _LineItems_, _LineItemResource_) -> LineItems = _LineItems_ @@ -72,14 +73,14 @@ describe "LineItems service", -> describe "#isSaved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(LineItems, "diff").andReturn ["attr1", "attr2"] + spyOn(LineItems, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(LineItems.isSaved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(LineItems, "diff").andReturn [] + spyOn(LineItems, "diff").and.returnValue [] it "returns false", -> expect(LineItems.isSaved({})).toBe true diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee index 09d5524fac..7404bbead2 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create.js.coffee @@ -18,11 +18,11 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> addSupplier: jasmine.createSpy() addDistributor: jasmine.createSpy() setExchangeVariants: jasmine.createSpy() - new: jasmine.createSpy().andReturn order_cycle + new: jasmine.createSpy().and.returnValue order_cycle Enterprise = - get: jasmine.createSpy().andReturn {id: 123} + get: jasmine.createSpy().and.returnValue {id: 123} index: jasmine.createSpy() - suppliedVariants: jasmine.createSpy().andReturn('supplied variants') + suppliedVariants: jasmine.createSpy().and.returnValue('supplied variants') EnterpriseFee = index: jasmine.createSpy() ocInstance = {} diff --git a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee index 2ddfe92407..aebdb19f6f 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee @@ -4,9 +4,10 @@ describe "OrderCycles service", -> beforeEach -> module 'admin.orderCycles' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _OrderCycles_, _OrderCycleResource_) -> OrderCycles = _OrderCycles_ @@ -98,14 +99,14 @@ describe "OrderCycles service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(OrderCycles, "diff").andReturn ["attr1", "attr2"] + spyOn(OrderCycles, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(OrderCycles.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(OrderCycles, "diff").andReturn [] + spyOn(OrderCycles, "diff").and.returnValue [] it "returns false", -> expect(OrderCycles.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee index cefb1d7a10..c3d6646144 100644 --- a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee @@ -4,9 +4,10 @@ describe "Orders service", -> beforeEach -> module 'admin.orders' - this.addMatchers - toDeepEqual: (expected) -> - return angular.equals(this.actual, expected) + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } inject ($q, _$httpBackend_, _Orders_, _OrderResource_) -> Orders = _Orders_ @@ -74,14 +75,14 @@ describe "Orders service", -> describe "#saved", -> describe "when attributes of the object have been altered", -> beforeEach -> - spyOn(Orders, "diff").andReturn ["attr1", "attr2"] + spyOn(Orders, "diff").and.returnValue ["attr1", "attr2"] it "returns false", -> expect(Orders.saved({})).toBe false describe "when attributes of the object have not been altered", -> beforeEach -> - spyOn(Orders, "diff").andReturn [] + spyOn(Orders, "diff").and.returnValue [] it "returns false", -> expect(Orders.saved({})).toBe true diff --git a/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee b/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee index 198a09a8b8..19353bf5dd 100644 --- a/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/option_value_namer_spec.js.coffee @@ -15,26 +15,26 @@ describe "Option Value Namer", -> it "when description is blank", -> v.unit_description = null - spyOn(namer, "value_scaled").andReturn true - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue true + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] expect(namer.name()).toBe "valueunit" it "when description is present", -> v.unit_description = 'desc' - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] - spyOn(namer, "value_scaled").andReturn true + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue true expect(namer.name()).toBe "valueunit desc" it "when value is blank and description is present", -> v.unit_description = 'desc' - spyOn(namer, "option_value_value_unit").andReturn [null, null] - spyOn(namer, "value_scaled").andReturn true + spyOn(namer, "option_value_value_unit").and.returnValue [null, null] + spyOn(namer, "value_scaled").and.returnValue true expect(namer.name()).toBe "desc" it "spaces value and unit when value is unscaled", -> v.unit_description = null - spyOn(namer, "option_value_value_unit").andReturn ["value", "unit"] - spyOn(namer, "value_scaled").andReturn false + spyOn(namer, "option_value_value_unit").and.returnValue ["value", "unit"] + spyOn(namer, "value_scaled").and.returnValue false expect(namer.name()).toBe "value unit" describe "determining if a variant's value is scaled", -> @@ -44,7 +44,7 @@ describe "Option Value Namer", -> p = {} v = { product: p } namer = new OptionValueNamer(v) - + it "returns true when the product has a scale", -> p.variant_unit_scale = 1000 expect(namer.value_scaled()).toBe true @@ -120,4 +120,4 @@ describe "Option Value Namer", -> p.variant_unit_scale = null p.variant_unit_name = 'foo' v.unit_value = null - expect(namer.option_value_value_unit()).toEqual [null, null] \ No newline at end of file + expect(namer.option_value_value_unit()).toEqual [null, null] diff --git a/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee b/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee index 1c6044b66e..0840d69c28 100644 --- a/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee +++ b/spec/javascripts/unit/admin/side_menu/services/side_menu.js.coffee @@ -73,21 +73,21 @@ describe "SideMenu service", -> describe "hiding an item by name", -> it "sets visible to false on the response from find_by_name", -> mockItem = { visible: true } - spyOn(SideMenu, 'find_by_name').andReturn mockItem + spyOn(SideMenu, 'find_by_name').and.returnValue mockItem SideMenu.hide_item_by_name() expect(mockItem.visible).toBe false it "doesn't crash if null is returned from find_by_name", -> - spyOn(SideMenu, 'find_by_name').andReturn null + spyOn(SideMenu, 'find_by_name').and.returnValue null SideMenu.hide_item_by_name() describe "showing an item by name", -> it "sets visible to false on the response from find_by_name", -> mockItem = { visible: false } - spyOn(SideMenu, 'find_by_name').andReturn mockItem + spyOn(SideMenu, 'find_by_name').and.returnValue mockItem SideMenu.show_item_by_name() expect(mockItem.visible).toBe true it "doesn't crash if null is returned from find_by_name", -> - spyOn(SideMenu, 'find_by_name').andReturn null + spyOn(SideMenu, 'find_by_name').and.returnValue null SideMenu.show_item_by_name() diff --git a/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee index 1e132ec07d..4a0c304ee8 100644 --- a/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/tag_rules/controllers/tag_rules_controller_spec.js.coffee @@ -47,7 +47,7 @@ describe "TagRulesCtrl", -> beforeEach inject ($httpBackend) -> rule = scope.tagGroups[0].rules[0] - spyOn(window, "confirm").andReturn(true) + spyOn(window, "confirm").and.returnValue(true) $httpBackend.expectDELETE('/admin/enterprises/45/tag_rules/1.json').respond(status: 204) scope.deleteTagRule(scope.tagGroups[0], rule) $httpBackend.flush() diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 3e7b27ae73..60edc958aa 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -10,7 +10,7 @@ describe "AdminOrderMgmtCtrl", -> ctrl = $controller httpBackend = $httpBackend VariantUnitManager = _VariantUnitManager_ - spyOn(window, "formatDate").andReturn "SomeDate" + spyOn(window, "formatDate").and.returnValue "SomeDate" ctrl "AdminOrderMgmtCtrl", {$scope: scope} ) @@ -24,8 +24,8 @@ describe "AdminOrderMgmtCtrl", -> httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").respond returnedDistributors httpBackend.expectGET("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=SomeDate").respond returnedOrderCycles - spyOn(scope, "initialiseVariables").andCallThrough() - spyOn(scope, "fetchOrders").andReturn "nothing" + spyOn(scope, "initialiseVariables").and.callThrough() + spyOn(scope, "fetchOrders").and.returnValue "nothing" #spyOn(returnedSuppliers, "unshift") #spyOn(returnedDistributors, "unshift") #spyOn(returnedOrderCycles, "unshift") @@ -36,8 +36,8 @@ describe "AdminOrderMgmtCtrl", -> expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] - expect(scope.initialiseVariables.calls.length).toBe 1 - expect(scope.fetchOrders.calls.length).toBe 1 + expect(scope.initialiseVariables.calls.count()).toBe 1 + expect(scope.fetchOrders.calls.count()).toBe 1 expect(scope.spree_api_key_ok).toBe true describe "fetching orders", -> @@ -63,8 +63,8 @@ describe "AdminOrderMgmtCtrl", -> describe "resetting orders", -> beforeEach -> - spyOn(scope, "matchObject").andReturn "nothing" - spyOn(scope, "resetLineItems").andReturn "nothing" + spyOn(scope, "matchObject").and.returnValue "nothing" + spyOn(scope, "resetLineItems").and.returnValue "nothing" scope.resetOrders [ "order1", "order2", "order3" ] it "sets the value of $scope.orders to the data received", -> @@ -77,8 +77,8 @@ describe "AdminOrderMgmtCtrl", -> order1 = order2 = order3 = null beforeEach -> - spyOn(scope, "matchObject").andReturn "nothing" - spyOn(scope, "lineItemOrder").andReturn "copied order" + spyOn(scope, "matchObject").and.returnValue "nothing" + spyOn(scope, "lineItemOrder").and.returnValue "copied order" order1 = { name: "order1", line_items: [ { name: "line_item1.1" }, { name: "line_item1.1" }, { name: "line_item1.1" } ] } order2 = { name: "order2", line_items: [ { name: "line_item2.1" }, { name: "line_item2.1" }, { name: "line_item2.1" } ] } order3 = { name: "order3", line_items: [ { name: "line_item3.1" }, { name: "line_item3.1" }, { name: "line_item3.1" } ] } @@ -92,18 +92,18 @@ describe "AdminOrderMgmtCtrl", -> expect(scope.lineItems[6].name).toEqual "line_item3.1" it "adds a reference to a modified parent order object to each line item", -> - expect(scope.lineItemOrder.calls.length).toEqual scope.orders.length + expect(scope.lineItemOrder.calls.count()).toBe scope.orders.length expect("copied order").toEqual line_item.order for line_item in scope.lineItems it "calls matchObject once for each line item", -> - expect(scope.matchObject.calls.length).toEqual scope.lineItems.length + expect(scope.matchObject.calls.count()).toBe scope.lineItems.length describe "copying orders", -> order1copy = null beforeEach -> - spyOn(scope, "lineItemOrder").andCallThrough() - spyOn(scope, "matchObject").andReturn "matched object" + spyOn(scope, "lineItemOrder").and.callThrough() + spyOn(scope, "matchObject").and.returnValue "matched object" order1 = { name: "order1", line_items: [ ] } scope.orders = [ order1 ] order1copy = scope.lineItemOrder order1 @@ -112,7 +112,7 @@ describe "AdminOrderMgmtCtrl", -> expect(order1copy.hasOwnProperty("line_items")).toEqual false it "calls matchObject twice for each order (once for distributor and once for order cycle)", -> - expect(scope.matchObject.calls.length).toEqual scope.lineItemOrder.calls.length * 2 + expect(scope.matchObject.calls.count()).toBe scope.lineItemOrder.calls.count() * 2 expect(order1copy.distributor).toEqual "matched object" expect(order1copy.distributor).toEqual "matched object" @@ -166,7 +166,7 @@ describe "AdminOrderMgmtCtrl", -> beforeEach -> scope.initialiseVariables() - spyOn(window,"confirm").andReturn true + spyOn(window,"confirm").and.returnValue true order = { number: "R12345678", line_items: [] } line_item1 = { id: 1, order: order } line_item2 = { id: 2, order: order } @@ -317,7 +317,7 @@ describe "AdminOrderMgmtCtrl", -> # A Units Variant is an API object which holds unit properies of a variant beforeEach -> - spyOn(Math,"round").andCallThrough() + spyOn(Math,"round").and.callThrough() it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> expect(scope.formattedValueWithUnitName(1,{})).toEqual '' @@ -337,14 +337,14 @@ describe "AdminOrderMgmtCtrl", -> it "calls Math.round with the quotient of scale and value, multiplied by 1000", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 5 + spyOn(VariantUnitManager, "getScale").and.returnValue 5 scope.formattedValueWithUnitName(10, unitsVariant) expect(Math.round).toHaveBeenCalledWith 10/5 * 1000 it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", -> unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").andReturn 1000 - spyOn(VariantUnitManager, "getUnitName").andReturn "kg" + spyOn(VariantUnitManager, "getScale").and.returnValue 1000 + spyOn(VariantUnitManager, "getUnitName").and.returnValue "kg" expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" describe "updating the price upon updating the weight of a line item", -> diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index 1f761418d2..457010da0c 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -258,10 +258,10 @@ describe "AdminProductEditCtrl", -> describe "loading data upon initialisation", -> it "gets a list of producers and then resets products with a list of data", -> $httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" - spyOn($scope, "fetchProducts").andReturn "nothing" + spyOn($scope, "fetchProducts").and.returnValue "nothing" $scope.initialise() $httpBackend.flush() - expect($scope.fetchProducts.calls.length).toEqual 1 + expect($scope.fetchProducts.calls.count()).toBe 1 expect($scope.spree_api_key_ok).toEqual true @@ -277,7 +277,7 @@ describe "AdminProductEditCtrl", -> deferred = $q.defer() deferred.resolve() spyOn $scope, "resetProducts" - spyOn(BulkProducts, "fetch").andReturn deferred.promise + spyOn(BulkProducts, "fetch").and.returnValue deferred.promise it "calls resetProducts after data has been received", -> $scope.fetchProducts() @@ -312,13 +312,13 @@ describe "AdminProductEditCtrl", -> describe "updating the product on hand count", -> it "updates when product is not available on demand", -> - spyOn($scope, "onHand").andReturn 123 + spyOn($scope, "onHand").and.returnValue 123 product = {on_demand: false} $scope.updateOnHand(product) expect(product.on_hand).toEqual 123 it "updates when product's variants are not available on demand", -> - spyOn($scope, "onHand").andReturn 123 + spyOn($scope, "onHand").and.returnValue 123 product = {on_demand: false, variants: [{on_demand: false}]} $scope.updateOnHand(product) expect(product.on_hand).toEqual 123 @@ -610,7 +610,7 @@ describe "AdminProductEditCtrl", -> describe "filtering products", -> beforeEach -> spyOn $scope, "packProduct" - spyOn(window, "filterSubmitProducts").andReturn [ + spyOn(window, "filterSubmitProducts").and.returnValue [ { id: 1 value: 3 @@ -632,7 +632,7 @@ describe "AdminProductEditCtrl", -> $scope.submitProducts() it "packs all products and all dirty products", -> - expect($scope.packProduct.calls.length).toEqual 4 + expect($scope.packProduct.calls.count()).toBe 4 it "filters returned dirty products", -> expect(filterSubmitProducts).toHaveBeenCalledWith @@ -734,7 +734,7 @@ describe "AdminProductEditCtrl", -> describe "deleting products", -> it "deletes products with a http delete request to /api/products/id/soft_delete", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -751,7 +751,7 @@ describe "AdminProductEditCtrl", -> $httpBackend.flush() it "removes the specified product from both $scope.products and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -790,7 +790,7 @@ describe "AdminProductEditCtrl", -> describe "when the variant has not been saved", -> it "removes the variant from products and dirtyProducts", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ {id: 1, variants: [{id: -1},{id: -2}]} ] @@ -806,7 +806,7 @@ describe "AdminProductEditCtrl", -> describe "when the variant has been saved", -> it "deletes variants with a http delete request to /api/products/product_permalink/variants/(variant_id)/soft_delete", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -832,7 +832,7 @@ describe "AdminProductEditCtrl", -> $httpBackend.flush() it "removes the specified variant from both the variants object and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").andReturn true + spyOn(window, "confirm").and.returnValue true $scope.products = [ { id: 9 @@ -927,7 +927,7 @@ describe "converting arrays of objects with ids to an object with ids as keys", it "sends arrays with the key 'variants' to itself", -> - spyOn(window, "toObjectWithIDKeys").andCallThrough() + spyOn(window, "toObjectWithIDKeys").and.callThrough() array = [ { id: 1 diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee index 390a7a1c94..d395ae9cc6 100644 --- a/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/checkout/checkout_controller_spec.js.coffee @@ -30,7 +30,7 @@ describe "CheckoutCtrl", -> beforeEach -> inject ($controller, $rootScope, _storage_) -> storage = _storage_ - spyOn(storage, "bind").andCallThrough() + spyOn(storage, "bind").and.callThrough() scope = $rootScope.$new() CurrentUser = { id: 1 } ctrl = $controller 'CheckoutCtrl', {$scope: scope, Checkout: Checkout, CurrentUser: CurrentUser } diff --git a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee index d985904f81..ce3fe7eec3 100644 --- a/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/cart_spec.js.coffee @@ -131,7 +131,7 @@ describe 'Cart service', -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 5} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 0 expect(li.max_quantity).toBeUndefined() @@ -139,14 +139,14 @@ describe 'Cart service', -> it "reduces the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 6} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.max_quantity).toEqual 0 it "resets the count on hand available", -> li = {variant: {id: 1, count_on_hand: 10}, quantity: 5} stockLevels = {1: {quantity: 0, max_quantity: 0, on_hand: 0}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 0 @@ -154,7 +154,7 @@ describe 'Cart service', -> it "reduces the quantity in the cart", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 5}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 5 expect(li.max_quantity).toBeUndefined() @@ -162,14 +162,14 @@ describe 'Cart service', -> it "does not reduce the max_quantity in the cart", -> li = {variant: {id: 1}, quantity: 6, max_quantity: 7} stockLevels = {1: {quantity: 5, max_quantity: 5, on_hand: 5}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.max_quantity).toEqual 7 it "resets the count on hand available", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 6}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.variant.count_on_hand).toEqual 6 @@ -177,7 +177,7 @@ describe 'Cart service', -> it "does not reset the quantity", -> li = {variant: {id: 1}, quantity: 6} stockLevels = {1: {quantity: 5, on_hand: 6}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 6 expect(li.max_quantity).toBeUndefined() @@ -185,7 +185,7 @@ describe 'Cart service', -> it "does not reset the max_quantity", -> li = {variant: {id: 1}, quantity: 5, max_quantity: 7} stockLevels = {1: {quantity: 5, max_quantity: 6, on_hand: 7}} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels stockLevels expect(li.quantity).toEqual 5 expect(li.max_quantity).toEqual 7 @@ -193,14 +193,14 @@ describe 'Cart service', -> describe "when the client-side quantity has been changed from 0 to 1 during the request", -> it "does not reset the quantity", -> li = {variant: {id: 1}, quantity: 1} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels {} expect(li.quantity).toEqual 1 expect(li.max_quantity).toBeUndefined() it "does not reset the max_quantity", -> li = {variant: {id: 1}, quantity: 1, max_quantity: 1} - spyOn(Cart, 'line_items_present').andReturn [li] + spyOn(Cart, 'line_items_present').and.returnValue [li] Cart.compareAndNotifyStockLevels {} expect(li.quantity).toEqual 1 expect(li.max_quantity).toEqual 1 diff --git a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee index 718504a377..43e83409ad 100644 --- a/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/checkout_spec.js.coffee @@ -30,6 +30,7 @@ describe 'Checkout service', -> beforeEach -> orderData = id: 3102 + shipping_method_id: null payment_method_id: null email: "test@test.com" bill_address: @@ -78,7 +79,7 @@ describe 'Checkout service', -> expect(Checkout.shippingPrice()).toEqual 13 it 'Gets the current payment method', -> - expect(Checkout.paymentMethod()).toEqual null + expect(Checkout.paymentMethod()).toBeUndefined() Checkout.order.payment_method_id = 99 expect(Checkout.paymentMethod()).toEqual paymentMethods[0] diff --git a/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee index 8551113280..6ace586e30 100644 --- a/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/sidebar_spec.js.coffee @@ -13,13 +13,13 @@ describe "Sidebar", -> it 'is active when a location in paths is set', -> - spyOn(location, "path").andReturn "/test" + spyOn(location, "path").and.returnValue "/test" expect(Sidebar.active()).toEqual true it 'is inactive if location is set', -> - spyOn(location, "path").andReturn null + spyOn(location, "path").and.returnValue null expect(Sidebar.active()).toEqual false - + describe "Toggling on/off", -> it 'toggles the current sidebar path', -> expect(Sidebar.active()).toEqual false @@ -32,6 +32,3 @@ describe "Sidebar", -> spyOn(Navigation, 'navigate') Sidebar.toggle() expect(Navigation.navigate).toHaveBeenCalledWith("/test") - - - diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 45dea6d36e..599c5530b0 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -13,10 +13,10 @@ describe 'OrderCycle controllers', -> event = preventDefault: jasmine.createSpy('preventDefault') OrderCycle = - exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected') - productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied') - variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied') - exchangeDirection: jasmine.createSpy('exchangeDirection').andReturn('exchange direction') + exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').and.returnValue('variants selected') + productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').and.returnValue('product supplied') + variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').and.returnValue('variant supplied') + exchangeDirection: jasmine.createSpy('exchangeDirection').and.returnValue('exchange direction') toggleProducts: jasmine.createSpy('toggleProducts') setExchangeVariants: jasmine.createSpy('setExchangeVariants') addSupplier: jasmine.createSpy('addSupplier') @@ -28,15 +28,15 @@ describe 'OrderCycle controllers', -> removeExchangeFee: jasmine.createSpy('removeExchangeFee') removeDistributionOfVariant: jasmine.createSpy('removeDistributionOfVariant') create: jasmine.createSpy('create') - new: jasmine.createSpy('new').andReturn "my order cycle" + new: jasmine.createSpy('new').and.returnValue "my order cycle" Enterprise = - index: jasmine.createSpy('index').andReturn('enterprises list') + index: jasmine.createSpy('index').and.returnValue('enterprises list') supplied_products: 'supplied products' - suppliedVariants: jasmine.createSpy('suppliedVariants').andReturn('supplied variants') - totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total') + suppliedVariants: jasmine.createSpy('suppliedVariants').and.returnValue('supplied variants') + totalVariants: jasmine.createSpy('totalVariants').and.returnValue('variants total') EnterpriseFee = - index: jasmine.createSpy('index').andReturn('enterprise fees list') - forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise') + index: jasmine.createSpy('index').and.returnValue('enterprise fees list') + forEnterprise: jasmine.createSpy('forEnterprise').and.returnValue('enterprise fees for enterprise') ocInstance = {} module('admin.orderCycles') @@ -99,7 +99,7 @@ describe 'OrderCycle controllers', -> 1: {id: 1, name: 'Eaterprises'} 2: {id: 2, name: 'Pepper Tree Place'} 3: {id: 3, name: 'South East'} - OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').andReturn([2]) + OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').and.returnValue([2]) EnterpriseFee.enterprise_fees = [ {enterprise_id: 2} ] # Pepper Tree Place has a fee expect(scope.enterprisesWithFees()).toEqual([ {id: 2, name: 'Pepper Tree Place'} @@ -181,10 +181,10 @@ describe 'OrderCycle controllers', -> 'example.com/admin/order_cycles/27/edit' OrderCycle = load: jasmine.createSpy('load') - exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected') - productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied') - variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied') - exchangeDirection: jasmine.createSpy('exchangeDirection').andReturn('exchange direction') + exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').and.returnValue('variants selected') + productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').and.returnValue('product supplied') + variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').and.returnValue('variant supplied') + exchangeDirection: jasmine.createSpy('exchangeDirection').and.returnValue('exchange direction') toggleProducts: jasmine.createSpy('toggleProducts') setExchangeVariants: jasmine.createSpy('setExchangeVariants') addSupplier: jasmine.createSpy('addSupplier') @@ -197,13 +197,13 @@ describe 'OrderCycle controllers', -> removeDistributionOfVariant: jasmine.createSpy('removeDistributionOfVariant') update: jasmine.createSpy('update') Enterprise = - index: jasmine.createSpy('index').andReturn('enterprises list') + index: jasmine.createSpy('index').and.returnValue('enterprises list') supplied_products: 'supplied products' - suppliedVariants: jasmine.createSpy('suppliedVariants').andReturn('supplied variants') - totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total') + suppliedVariants: jasmine.createSpy('suppliedVariants').and.returnValue('supplied variants') + totalVariants: jasmine.createSpy('totalVariants').and.returnValue('variants total') EnterpriseFee = - index: jasmine.createSpy('index').andReturn('enterprise fees list') - forEnterprise: jasmine.createSpy('forEnterprise').andReturn('enterprise fees for enterprise') + index: jasmine.createSpy('index').and.returnValue('enterprise fees list') + forEnterprise: jasmine.createSpy('forEnterprise').and.returnValue('enterprise fees for enterprise') module('admin.orderCycles') inject ($controller) -> @@ -265,7 +265,7 @@ describe 'OrderCycle controllers', -> 1: {id: 1, name: 'Eaterprises'} 2: {id: 2, name: 'Pepper Tree Place'} 3: {id: 3, name: 'South East'} - OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').andReturn([2]) + OrderCycle.participatingEnterpriseIds = jasmine.createSpy('participatingEnterpriseIds').and.returnValue([2]) EnterpriseFee.enterprise_fees = [ {enterprise_id: 2} ] # Pepper Tree Place has a fee expect(scope.enterprisesWithFees()).toEqual([ {id: 2, name: 'Pepper Tree Place'} @@ -376,7 +376,7 @@ describe 'OrderCycle services', -> expect(Enterprise.supplied_products).toEqual [1, 2, 3, 4, 5, 6] it "finds supplied variants for an enterprise", -> - spyOn(Enterprise, 'variantsOf').andReturn(10) + spyOn(Enterprise, 'variantsOf').and.returnValue(10) Enterprise.index() $httpBackend.flush() expect(Enterprise.suppliedVariants(1)).toEqual [10, 10] @@ -721,7 +721,7 @@ describe 'OrderCycle services', -> master_id: 6 variants: [{id: 7}, {id: 8}] - spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3]) + spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) it 'returns true for products whose master is supplied', -> expect(OrderCycle.productSuppliedToOrderCycle(product_master_present)).toBeTruthy() @@ -738,7 +738,7 @@ describe 'OrderCycle services', -> describe 'checking whether a variant is supplied to the order cycle', -> beforeEach -> - spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3]) + spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) it 'returns true for variants that are supplied', -> expect(OrderCycle.variantSuppliedToOrderCycle({id: 1})).toBeTruthy() @@ -814,11 +814,11 @@ describe 'OrderCycle services', -> describe 'creating an order cycle', -> beforeEach -> - spyOn(OrderCycle, 'confirmNoDistributors').andReturn true + spyOn(OrderCycle, 'confirmNoDistributors').and.returnValue true it 'redirects to the destination page on success', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPOST('/admin/order_cycles.json', { order_cycle: 'this is the submit data' }).respond {success: true} @@ -829,7 +829,7 @@ describe 'OrderCycle services', -> it 'does not redirect on error', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPOST('/admin/order_cycles.json', { order_cycle: 'this is the submit data' }).respond {success: false} @@ -840,11 +840,11 @@ describe 'OrderCycle services', -> describe 'updating an order cycle', -> beforeEach -> - spyOn(OrderCycle, 'confirmNoDistributors').andReturn true + spyOn(OrderCycle, 'confirmNoDistributors').and.returnValue true it 'redirects to the destination page on success', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPUT('/admin/order_cycles.json?reloading=1', { order_cycle: 'this is the submit data' }).respond {success: true} @@ -855,7 +855,7 @@ describe 'OrderCycle services', -> it 'does not redirect on error', -> OrderCycle.order_cycle = 'this is the order cycle' - spyOn(OrderCycle, 'dataForSubmit').andReturn('this is the submit data') + spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPUT('/admin/order_cycles.json?reloading=1', { order_cycle: 'this is the submit data' }).respond {success: false} @@ -968,19 +968,19 @@ describe 'OrderCycle services', -> outgoing_exchanges: [] it "returns true when there are distributors", -> - spyOn window, 'confirm' + spyOn(window, 'confirm') OrderCycle.order_cycle = order_cycle_with_exchanges expect(OrderCycle.confirmNoDistributors()).toBe true expect(window.confirm).not.toHaveBeenCalled() it "returns true when there are no distributors but the user confirms", -> - spyOn(window, 'confirm').andReturn true + spyOn(window, 'confirm').and.returnValue(true) OrderCycle.order_cycle = order_cycle_without_exchanges expect(OrderCycle.confirmNoDistributors()).toBe true expect(window.confirm).toHaveBeenCalled() it "returns false when there are no distributors and the user does not confirm", -> - spyOn(window, 'confirm').andReturn false + spyOn(window, 'confirm').and.returnValue(false) OrderCycle.order_cycle = order_cycle_without_exchanges expect(OrderCycle.confirmNoDistributors()).toBe false expect(window.confirm).toHaveBeenCalled() From a0254f30cd230ee0769c5360104d0de3fc21de27 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 4 May 2016 11:10:47 +1000 Subject: [PATCH 125/154] Adding package.json with npm dev dependencies for js specs --- .gitignore | 1 + .travis.yml | 6 +- npm-shrinkwrap.json | 2022 +++++++++++++++++++++++++++++++++++++++++++ package.json | 16 + 4 files changed, 2041 insertions(+), 4 deletions(-) create mode 100644 npm-shrinkwrap.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 42107a8e2e..13f5a4441c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ NERD_tree* coverage libpeerconnection.log /config/application.yml +node_modules diff --git a/.travis.yml b/.travis.yml index 279eecb77c..8a66feb11a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,10 +28,8 @@ before_script: - RAILS_ENV=test bundle exec rake db:create db:schema:load - > if [ "$KARMA" = "true" ]; then - npm install karma@0.13.22 - npm install karma-jasmine@0.3.8 - npm install karma-phantomjs-launcher@1.0.0 - npm install karma-coffee-preprocessor@0.3.0 + npm install -g npm@'~3.8.8' + npm install npm install -g karma-cli@0.1.2 fi diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000000..e1c8b2aa83 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,2022 @@ +{ + "name": "openfoodnetwork", + "version": "1.7.1", + "dependencies": { + "accepts": { + "version": "1.1.4", + "from": "accepts@1.1.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", + "dependencies": { + "mime-db": { + "version": "1.12.0", + "from": "mime-db@>=1.12.0 <1.13.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz" + }, + "mime-types": { + "version": "2.0.14", + "from": "mime-types@>=2.0.4 <2.1.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz" + } + } + }, + "adm-zip": { + "version": "0.4.7", + "from": "adm-zip@>=0.4.7 <0.5.0", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz" + }, + "after": { + "version": "0.8.1", + "from": "after@0.8.1", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.1.tgz" + }, + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + }, + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, + "anymatch": { + "version": "1.3.0", + "from": "anymatch@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" + }, + "arr-diff": { + "version": "2.0.0", + "from": "arr-diff@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" + }, + "arr-flatten": { + "version": "1.0.1", + "from": "arr-flatten@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" + }, + "array-slice": { + "version": "0.2.3", + "from": "array-slice@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz" + }, + "array-unique": { + "version": "0.2.1", + "from": "array-unique@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" + }, + "arraybuffer.slice": { + "version": "0.0.6", + "from": "arraybuffer.slice@0.0.6", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz" + }, + "arrify": { + "version": "1.0.1", + "from": "arrify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "async-each": { + "version": "1.0.0", + "from": "async-each@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.0.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "backo2": { + "version": "1.0.2", + "from": "backo2@1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz" + }, + "balanced-match": { + "version": "0.4.1", + "from": "balanced-match@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz" + }, + "base64-arraybuffer": { + "version": "0.1.2", + "from": "base64-arraybuffer@0.1.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.2.tgz" + }, + "base64id": { + "version": "0.1.0", + "from": "base64id@0.1.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz" + }, + "batch": { + "version": "0.5.3", + "from": "batch@0.5.3", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz" + }, + "benchmark": { + "version": "1.0.0", + "from": "benchmark@1.0.0", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-1.0.0.tgz" + }, + "better-assert": { + "version": "1.0.2", + "from": "better-assert@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" + }, + "binary-extensions": { + "version": "1.4.0", + "from": "binary-extensions@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.4.0.tgz" + }, + "bl": { + "version": "1.0.3", + "from": "bl@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "blob": { + "version": "0.0.4", + "from": "blob@0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz" + }, + "bluebird": { + "version": "2.10.2", + "from": "bluebird@>=2.9.27 <3.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" + }, + "body-parser": { + "version": "1.15.0", + "from": "body-parser@>=1.12.4 <2.0.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.0.tgz" + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "brace-expansion": { + "version": "1.1.4", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz" + }, + "braces": { + "version": "1.8.4", + "from": "braces@>=1.8.2 <2.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.4.tgz" + }, + "bytes": { + "version": "2.2.0", + "from": "bytes@2.2.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz" + }, + "callsite": { + "version": "1.0.0", + "from": "callsite@1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz" + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "chalk": { + "version": "1.1.3", + "from": "chalk@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + }, + "charenc": { + "version": "0.0.1", + "from": "charenc@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.1.tgz" + }, + "chokidar": { + "version": "1.4.3", + "from": "chokidar@>=0.8.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.4.3.tgz" + }, + "coffee-script": { + "version": "1.10.0", + "from": "coffee-script@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz" + }, + "colors": { + "version": "1.1.2", + "from": "colors@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "component-bind": { + "version": "1.0.0", + "from": "component-bind@1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz" + }, + "component-emitter": { + "version": "1.1.2", + "from": "component-emitter@1.1.2", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz" + }, + "component-inherit": { + "version": "0.0.3", + "from": "component-inherit@0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.5.0", + "from": "concat-stream@1.5.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + } + } + }, + "connect": { + "version": "3.4.1", + "from": "connect@>=3.3.5 <4.0.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz" + }, + "content-type": { + "version": "1.0.1", + "from": "content-type@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" + }, + "core-js": { + "version": "2.3.0", + "from": "core-js@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "crypt": { + "version": "0.0.1", + "from": "crypt@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.1.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "custom-event": { + "version": "1.0.0", + "from": "custom-event@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz" + }, + "d": { + "version": "0.1.1", + "from": "d@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" + }, + "dashdash": { + "version": "1.13.1", + "from": "dashdash@>=1.12.0 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.1.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "depd": { + "version": "1.1.0", + "from": "depd@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz" + }, + "di": { + "version": "0.0.1", + "from": "di@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz" + }, + "dom-serialize": { + "version": "2.2.1", + "from": "dom-serialize@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz" + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" + }, + "ee-first": { + "version": "1.1.1", + "from": "ee-first@1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + }, + "engine.io": { + "version": "1.6.9", + "from": "engine.io@1.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.6.9.tgz" + }, + "engine.io-client": { + "version": "1.6.9", + "from": "engine.io-client@1.6.9", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.6.9.tgz" + }, + "engine.io-parser": { + "version": "1.2.4", + "from": "engine.io-parser@1.2.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.2.4.tgz", + "dependencies": { + "has-binary": { + "version": "0.1.6", + "from": "has-binary@0.1.6", + "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.6.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + } + } + }, + "ent": { + "version": "2.2.0", + "from": "ent@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz" + }, + "es5-ext": { + "version": "0.10.11", + "from": "es5-ext@>=0.10.11 <0.11.0", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz" + }, + "es6-iterator": { + "version": "2.0.0", + "from": "es6-iterator@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" + }, + "es6-symbol": { + "version": "3.0.2", + "from": "es6-symbol@>=3.0.2 <3.1.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz" + }, + "escape-html": { + "version": "1.0.3", + "from": "escape-html@>=1.0.3 <1.1.0", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + }, + "eventemitter3": { + "version": "1.2.0", + "from": "eventemitter3@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz" + }, + "expand-braces": { + "version": "0.1.2", + "from": "expand-braces@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", + "dependencies": { + "braces": { + "version": "0.1.5", + "from": "braces@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz" + }, + "expand-range": { + "version": "0.1.1", + "from": "expand-range@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz" + }, + "is-number": { + "version": "0.1.1", + "from": "is-number@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz" + }, + "repeat-string": { + "version": "0.2.2", + "from": "repeat-string@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz" + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "from": "expand-brackets@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" + }, + "expand-range": { + "version": "1.8.1", + "from": "expand-range@>=1.8.1 <2.0.0", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz" + }, + "extend": { + "version": "3.0.0", + "from": "extend@>=3.0.0 <3.1.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + }, + "extglob": { + "version": "0.3.2", + "from": "extglob@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" + }, + "extract-zip": { + "version": "1.5.0", + "from": "extract-zip@>=1.5.0 <1.6.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "dependencies": { + "debug": { + "version": "0.7.4", + "from": "debug@0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" + }, + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, + "mkdirp": { + "version": "0.5.0", + "from": "mkdirp@0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz" + } + } + }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + }, + "fd-slicer": { + "version": "1.0.1", + "from": "fd-slicer@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" + }, + "filename-regex": { + "version": "2.0.0", + "from": "filename-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" + }, + "fill-range": { + "version": "2.2.3", + "from": "fill-range@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" + }, + "finalhandler": { + "version": "0.4.1", + "from": "finalhandler@0.4.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz" + }, + "for-in": { + "version": "0.1.5", + "from": "for-in@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.5.tgz" + }, + "for-own": { + "version": "0.1.4", + "from": "for-own@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.4.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "1.0.0-rc4", + "from": "form-data@>=1.0.0-rc3 <1.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", + "dependencies": { + "async": { + "version": "1.5.2", + "from": "async@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "mime-db": { + "version": "1.22.0", + "from": "mime-db@>=1.22.0 <1.23.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" + }, + "mime-types": { + "version": "2.1.10", + "from": "mime-types@2.1.10", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" + } + } + }, + "fs-extra": { + "version": "0.26.7", + "from": "fs-extra@>=0.26.4 <0.27.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.3", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" + } + } + }, + "fsevents": { + "version": "1.0.12", + "from": "fsevents@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.12.tgz", + "dependencies": { + "ansi": { + "version": "0.3.1", + "from": "ansi@~0.3.1", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" + }, + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@^2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + }, + "ansi-styles": { + "version": "2.2.1", + "from": "ansi-styles@^2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" + }, + "are-we-there-yet": { + "version": "1.1.2", + "from": "are-we-there-yet@~1.1.2", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@^0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "async": { + "version": "1.5.2", + "from": "async@^1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@~0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "aws4": { + "version": "1.3.2", + "from": "aws4@^1.2.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz", + "dependencies": { + "lru-cache": { + "version": "4.0.1", + "from": "lru-cache@^4.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "from": "pseudomap@^1.0.1", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" + }, + "yallist": { + "version": "2.0.0", + "from": "yallist@^2.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz" + } + } + } + } + }, + "bl": { + "version": "1.0.3", + "from": "bl@~1.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz" + }, + "block-stream": { + "version": "0.0.8", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" + }, + "boom": { + "version": "2.10.1", + "from": "boom@2.x.x", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@~0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "chalk": { + "version": "1.1.3", + "from": "chalk@^1.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@~1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "commander": { + "version": "2.9.0", + "from": "commander@^2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@2.x.x", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "dashdash": { + "version": "1.13.0", + "from": "dashdash@>=1.10.1 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@^1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "debug": { + "version": "2.2.0", + "from": "debug@~2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "deep-extend": { + "version": "0.4.1", + "from": "deep-extend@~0.4.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@~1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "delegates": { + "version": "1.0.0", + "from": "delegates@^1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.0.1 <1.0.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" + }, + "escape-string-regexp": { + "version": "1.0.5", + "from": "escape-string-regexp@^1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + }, + "extend": { + "version": "3.0.0", + "from": "extend@~3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + }, + "extsprintf": { + "version": "1.0.2", + "from": "extsprintf@1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@~0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "1.0.0-rc4", + "from": "form-data@~1.0.0-rc3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" + }, + "fstream": { + "version": "1.0.8", + "from": "fstream@^1.0.2", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz" + }, + "fstream-ignore": { + "version": "1.0.3", + "from": "fstream-ignore@~1.0.3", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz", + "dependencies": { + "minimatch": { + "version": "3.0.0", + "from": "minimatch@^3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "from": "brace-expansion@^1.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "from": "balanced-match@^0.3.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + } + } + } + } + } + }, + "gauge": { + "version": "1.2.7", + "from": "gauge@~1.2.5", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz" + }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@^2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@^1.1.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" + }, + "graceful-fs": { + "version": "4.1.3", + "from": "graceful-fs@^4.1.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" + }, + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>= 1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + }, + "har-validator": { + "version": "2.0.6", + "from": "har-validator@~2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@^2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-unicode": { + "version": "2.0.0", + "from": "has-unicode@^2.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@~3.1.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@2.x.x", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@~1.1.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@*", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "ini": { + "version": "1.3.4", + "from": "ini@~1.3.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" + }, + "is-my-json-valid": { + "version": "2.13.1", + "from": "is-my-json-valid@^2.12.4", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" + }, + "is-property": { + "version": "1.0.2", + "from": "is-property@^1.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@~1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@~0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "jodid25519": { + "version": "1.0.2", + "from": "jodid25519@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" + }, + "jsbn": { + "version": "0.1.0", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" + }, + "json-schema": { + "version": "0.2.2", + "from": "json-schema@0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@~5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "jsprim": { + "version": "1.2.2", + "from": "jsprim@^1.2.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" + }, + "lodash.pad": { + "version": "4.1.0", + "from": "lodash.pad@^4.1.0", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.1.0.tgz" + }, + "lodash.padend": { + "version": "4.2.0", + "from": "lodash.padend@^4.1.0", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.2.0.tgz" + }, + "lodash.padstart": { + "version": "4.2.0", + "from": "lodash.padstart@^4.1.0", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.2.0.tgz" + }, + "lodash.repeat": { + "version": "4.0.0", + "from": "lodash.repeat@^4.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-4.0.0.tgz" + }, + "lodash.tostring": { + "version": "4.1.2", + "from": "lodash.tostring@^4.0.0", + "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.2.tgz" + }, + "mime-db": { + "version": "1.22.0", + "from": "mime-db@~1.22.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" + }, + "mime-types": { + "version": "2.1.10", + "from": "mime-types@~2.1.7", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" + }, + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.3.0 <0.4.0||>=0.4.0 <0.5.0||>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "node-pre-gyp": { + "version": "0.6.25", + "from": "node-pre-gyp@0.6.25", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.25.tgz", + "dependencies": { + "nopt": { + "version": "3.0.6", + "from": "nopt@~3.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "dependencies": { + "abbrev": { + "version": "1.0.7", + "from": "abbrev@1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" + } + } + } + } + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@~1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "npmlog": { + "version": "2.0.3", + "from": "npmlog@~2.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.3.tgz" + }, + "oauth-sign": { + "version": "0.8.1", + "from": "oauth-sign@~0.8.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz" + }, + "once": { + "version": "1.3.3", + "from": "once@~1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" + }, + "pinkie": { + "version": "2.0.4", + "from": "pinkie@^2.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + }, + "pinkie-promise": { + "version": "2.0.0", + "from": "pinkie-promise@^2.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz" + }, + "process-nextick-args": { + "version": "1.0.6", + "from": "process-nextick-args@~1.0.6", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz" + }, + "qs": { + "version": "6.0.2", + "from": "qs@~6.0.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.2.tgz" + }, + "rc": { + "version": "1.1.6", + "from": "rc@~1.1.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", + "dependencies": { + "minimist": { + "version": "1.2.0", + "from": "minimist@^1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + } + } + }, + "readable-stream": { + "version": "2.0.6", + "from": "readable-stream@^2.0.0 || ^1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" + }, + "request": { + "version": "2.69.0", + "from": "request@2.x", + "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz" + }, + "rimraf": { + "version": "2.5.2", + "from": "rimraf@~2.5.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "dependencies": { + "glob": { + "version": "7.0.3", + "from": "glob@^7.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz", + "dependencies": { + "inflight": { + "version": "1.0.4", + "from": "inflight@^1.0.4", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "dependencies": { + "wrappy": { + "version": "1.0.1", + "from": "wrappy@1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + } + } + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "minimatch": { + "version": "3.0.0", + "from": "minimatch@2 || 3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "dependencies": { + "brace-expansion": { + "version": "1.1.3", + "from": "brace-expansion@^1.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", + "dependencies": { + "balanced-match": { + "version": "0.3.0", + "from": "balanced-match@^0.3.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + } + } + } + }, + "once": { + "version": "1.3.3", + "from": "once@^1.3.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "dependencies": { + "wrappy": { + "version": "1.0.1", + "from": "wrappy@1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + } + } + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@^1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + } + } + } + } + }, + "semver": { + "version": "5.1.0", + "from": "semver@~5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@1.x.x", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "sshpk": { + "version": "1.7.4", + "from": "sshpk@^1.7.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@~0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@^3.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "strip-json-comments": { + "version": "1.0.4", + "from": "strip-json-comments@~1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@^2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, + "tar": { + "version": "2.2.1", + "from": "tar@~2.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" + }, + "tar-pack": { + "version": "3.1.3", + "from": "tar-pack@~3.1.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.1.3.tgz" + }, + "tough-cookie": { + "version": "2.2.2", + "from": "tough-cookie@~2.2.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" + }, + "tunnel-agent": { + "version": "0.4.2", + "from": "tunnel-agent@~0.4.1", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz" + }, + "tweetnacl": { + "version": "0.14.3", + "from": "tweetnacl@>=0.13.0 <1.0.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz" + }, + "uid-number": { + "version": "0.0.6", + "from": "uid-number@~0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@~1.0.1", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "verror": { + "version": "1.3.6", + "from": "verror@1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" + }, + "wrappy": { + "version": "1.0.1", + "from": "wrappy@1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@^4.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + } + }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" + }, + "getpass": { + "version": "0.1.6", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "glob": { + "version": "7.0.3", + "from": "glob@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz" + }, + "glob-base": { + "version": "0.3.0", + "from": "glob-base@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" + }, + "glob-parent": { + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" + }, + "graceful-fs": { + "version": "4.1.3", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" + }, + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>=1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + }, + "har-validator": { + "version": "2.0.6", + "from": "har-validator@>=2.0.2 <2.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "dependencies": { + "commander": { + "version": "2.9.0", + "from": "commander@>=2.9.0 <3.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" + } + } + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-binary": { + "version": "0.1.7", + "from": "has-binary@0.1.7", + "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + } + } + }, + "has-cors": { + "version": "1.1.0", + "from": "has-cors@1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz" + }, + "hasha": { + "version": "2.2.0", + "from": "hasha@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "http-errors": { + "version": "1.4.0", + "from": "http-errors@>=1.4.0 <1.5.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz" + }, + "http-proxy": { + "version": "1.13.2", + "from": "http-proxy@>=1.13.0 <2.0.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.13.2.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" + }, + "indexof": { + "version": "0.0.1", + "from": "indexof@0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" + }, + "inflight": { + "version": "1.0.4", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "is-absolute": { + "version": "0.1.7", + "from": "is-absolute@>=0.1.7 <0.2.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz" + }, + "is-binary-path": { + "version": "1.0.1", + "from": "is-binary-path@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" + }, + "is-buffer": { + "version": "1.1.3", + "from": "is-buffer@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz" + }, + "is-dotfile": { + "version": "1.0.2", + "from": "is-dotfile@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz" + }, + "is-equal-shallow": { + "version": "0.1.3", + "from": "is-equal-shallow@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" + }, + "is-extendable": { + "version": "0.1.1", + "from": "is-extendable@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" + }, + "is-extglob": { + "version": "1.0.0", + "from": "is-extglob@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" + }, + "is-glob": { + "version": "2.0.1", + "from": "is-glob@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + }, + "is-my-json-valid": { + "version": "2.13.1", + "from": "is-my-json-valid@>=2.12.4 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" + }, + "is-number": { + "version": "2.1.0", + "from": "is-number@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" + }, + "is-posix-bracket": { + "version": "0.1.1", + "from": "is-posix-bracket@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" + }, + "is-primitive": { + "version": "2.0.0", + "from": "is-primitive@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" + }, + "is-property": { + "version": "1.0.2", + "from": "is-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + }, + "is-relative": { + "version": "0.1.3", + "from": "is-relative@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" + }, + "is-stream": { + "version": "1.1.0", + "from": "is-stream@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + }, + "isbinaryfile": { + "version": "3.0.0", + "from": "isbinaryfile@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.0.tgz" + }, + "isexe": { + "version": "1.1.2", + "from": "isexe@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" + }, + "isobject": { + "version": "2.1.0", + "from": "isobject@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "jasmine-core": { + "version": "2.4.1", + "from": "jasmine-core@>=2.4.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.4.1.tgz" + }, + "jodid25519": { + "version": "1.0.2", + "from": "jodid25519@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" + }, + "jsbn": { + "version": "0.1.0", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" + }, + "json-schema": { + "version": "0.2.2", + "from": "json-schema@0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "json3": { + "version": "3.2.6", + "from": "json3@3.2.6", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.2.6.tgz" + }, + "jsonfile": { + "version": "2.3.0", + "from": "jsonfile@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.3.0.tgz" + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "jsprim": { + "version": "1.2.2", + "from": "jsprim@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" + }, + "karma": { + "version": "0.13.22", + "from": "karma@>=0.13.22", + "resolved": "https://registry.npmjs.org/karma/-/karma-0.13.22.tgz", + "dependencies": { + "rimraf": { + "version": "2.5.2", + "from": "rimraf@>=2.3.3 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz" + } + } + }, + "karma-coffee-preprocessor": { + "version": "0.3.0", + "from": "karma-coffee-preprocessor@0.3.0", + "resolved": "https://registry.npmjs.org/karma-coffee-preprocessor/-/karma-coffee-preprocessor-0.3.0.tgz" + }, + "karma-jasmine": { + "version": "0.3.8", + "from": "karma-jasmine@>=0.3.8 <1.0.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-0.3.8.tgz" + }, + "karma-phantomjs-launcher": { + "version": "1.0.0", + "from": "karma-phantomjs-launcher@1.0.0", + "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.0.tgz", + "dependencies": { + "lodash": { + "version": "4.11.1", + "from": "lodash@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.11.1.tgz" + } + } + }, + "kew": { + "version": "0.7.0", + "from": "kew@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz" + }, + "kind-of": { + "version": "3.0.3", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.3.tgz" + }, + "klaw": { + "version": "1.2.0", + "from": "klaw@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.2.0.tgz" + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.8.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + }, + "log4js": { + "version": "0.6.35", + "from": "log4js@>=0.6.3 <0.7.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.35.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "readable-stream": { + "version": "1.0.34", + "from": "readable-stream@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" + } + } + }, + "lru-cache": { + "version": "2.2.4", + "from": "lru-cache@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz" + }, + "md5": { + "version": "2.0.0", + "from": "md5@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.0.0.tgz", + "dependencies": { + "is-buffer": { + "version": "1.0.2", + "from": "is-buffer@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.0.2.tgz" + } + } + }, + "media-typer": { + "version": "0.3.0", + "from": "media-typer@0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + }, + "micromatch": { + "version": "2.3.8", + "from": "micromatch@>=2.1.5 <3.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.8.tgz" + }, + "mime": { + "version": "1.3.4", + "from": "mime@>=1.3.4 <2.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" + }, + "mime-db": { + "version": "1.23.0", + "from": "mime-db@>=1.23.0 <1.24.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" + }, + "mime-types": { + "version": "2.1.11", + "from": "mime-types@>=2.1.10 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" + }, + "minimatch": { + "version": "3.0.0", + "from": "minimatch@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz" + }, + "minimist": { + "version": "0.0.10", + "from": "minimist@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "nan": { + "version": "2.3.3", + "from": "nan@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.3.tgz" + }, + "negotiator": { + "version": "0.4.9", + "from": "negotiator@0.4.9", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@>=1.4.7 <1.5.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "normalize-path": { + "version": "2.0.1", + "from": "normalize-path@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" + }, + "oauth-sign": { + "version": "0.8.1", + "from": "oauth-sign@>=0.8.0 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz" + }, + "object-component": { + "version": "0.0.3", + "from": "object-component@0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz" + }, + "object.omit": { + "version": "2.0.0", + "from": "object.omit@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" + }, + "on-finished": { + "version": "2.3.0", + "from": "on-finished@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + }, + "once": { + "version": "1.3.3", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" + }, + "optimist": { + "version": "0.6.1", + "from": "optimist@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" + }, + "options": { + "version": "0.0.6", + "from": "options@>=0.0.5", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz" + }, + "parse-glob": { + "version": "3.0.4", + "from": "parse-glob@>=3.0.4 <4.0.0", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" + }, + "parsejson": { + "version": "0.0.1", + "from": "parsejson@0.0.1", + "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.1.tgz" + }, + "parseqs": { + "version": "0.0.2", + "from": "parseqs@0.0.2", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.2.tgz" + }, + "parseuri": { + "version": "0.0.4", + "from": "parseuri@0.0.4", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.4.tgz" + }, + "parseurl": { + "version": "1.3.1", + "from": "parseurl@>=1.3.0 <1.4.0", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz" + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "pend": { + "version": "1.2.0", + "from": "pend@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" + }, + "phantomjs": { + "version": "2.1.1", + "from": "phantomjs@2.1.1", + "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.1.tgz" + }, + "phantomjs-prebuilt": { + "version": "2.1.7", + "from": "phantomjs-prebuilt@2.1.7", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.7.tgz" + }, + "pinkie": { + "version": "2.0.4", + "from": "pinkie@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + }, + "pinkie-promise": { + "version": "2.0.1", + "from": "pinkie-promise@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + }, + "preserve": { + "version": "0.2.0", + "from": "preserve@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" + }, + "process-nextick-args": { + "version": "1.0.6", + "from": "process-nextick-args@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz" + }, + "progress": { + "version": "1.1.8", + "from": "progress@>=1.1.8 <1.2.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz" + }, + "qs": { + "version": "6.1.0", + "from": "qs@6.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" + }, + "randomatic": { + "version": "1.1.5", + "from": "randomatic@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" + }, + "raw-body": { + "version": "2.1.6", + "from": "raw-body@>=2.1.5 <2.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.6.tgz", + "dependencies": { + "bytes": { + "version": "2.3.0", + "from": "bytes@2.3.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz" + } + } + }, + "readable-stream": { + "version": "2.1.2", + "from": "readable-stream@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.2.tgz" + }, + "readdirp": { + "version": "2.0.0", + "from": "readdirp@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz", + "dependencies": { + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.10 <3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" + } + } + }, + "regex-cache": { + "version": "0.4.3", + "from": "regex-cache@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" + }, + "repeat-element": { + "version": "1.1.2", + "from": "repeat-element@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" + }, + "repeat-string": { + "version": "1.5.4", + "from": "repeat-string@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz" + }, + "request": { + "version": "2.67.0", + "from": "request@>=2.67.0 <2.68.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz", + "dependencies": { + "mime-db": { + "version": "1.22.0", + "from": "mime-db@1.22.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" + }, + "mime-types": { + "version": "2.1.10", + "from": "mime-types@>=2.1.7 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" + }, + "qs": { + "version": "5.2.0", + "from": "qs@>=5.2.0 <5.3.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz" + } + } + }, + "request-progress": { + "version": "2.0.1", + "from": "request-progress@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz" + }, + "requires-port": { + "version": "1.0.0", + "from": "requires-port@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + }, + "rimraf": { + "version": "2.2.8", + "from": "rimraf@>=2.2.5 <2.3.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + }, + "semver": { + "version": "4.3.6", + "from": "semver@>=4.3.3 <4.4.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "socket.io": { + "version": "1.4.6", + "from": "socket.io@>=1.4.5 <2.0.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.4.6.tgz" + }, + "socket.io-adapter": { + "version": "0.4.0", + "from": "socket.io-adapter@0.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.4.0.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + }, + "socket.io-parser": { + "version": "2.2.2", + "from": "socket.io-parser@2.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.2.tgz", + "dependencies": { + "debug": { + "version": "0.7.4", + "from": "debug@0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" + } + } + } + } + }, + "socket.io-client": { + "version": "1.4.6", + "from": "socket.io-client@1.4.6", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.4.6.tgz", + "dependencies": { + "component-emitter": { + "version": "1.2.0", + "from": "component-emitter@1.2.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.0.tgz" + } + } + }, + "socket.io-parser": { + "version": "2.2.6", + "from": "socket.io-parser@2.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.6.tgz", + "dependencies": { + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1" + }, + "json3": { + "version": "3.3.2", + "from": "json3@3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz" + } + } + }, + "source-map": { + "version": "0.5.6", + "from": "source-map@>=0.5.3 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" + }, + "sshpk": { + "version": "1.8.3", + "from": "sshpk@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "statuses": { + "version": "1.2.1", + "from": "statuses@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" + }, + "strip-ansi": { + "version": "3.0.1", + "from": "strip-ansi@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, + "throttleit": { + "version": "1.0.0", + "from": "throttleit@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz" + }, + "to-array": { + "version": "0.1.4", + "from": "to-array@0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz" + }, + "tough-cookie": { + "version": "2.2.2", + "from": "tough-cookie@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" + }, + "tunnel-agent": { + "version": "0.4.2", + "from": "tunnel-agent@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz" + }, + "tweetnacl": { + "version": "0.13.3", + "from": "tweetnacl@>=0.13.0 <0.14.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" + }, + "type-is": { + "version": "1.6.12", + "from": "type-is@>=1.6.11 <1.7.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.12.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.5 <0.1.0", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "ultron": { + "version": "1.0.2", + "from": "ultron@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" + }, + "unpipe": { + "version": "1.0.0", + "from": "unpipe@1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + }, + "useragent": { + "version": "2.1.9", + "from": "useragent@>=2.1.6 <3.0.0", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.1.9.tgz" + }, + "utf8": { + "version": "2.1.0", + "from": "utf8@2.1.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.0.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "utils-merge": { + "version": "1.0.0", + "from": "utils-merge@1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" + }, + "verror": { + "version": "1.3.6", + "from": "verror@1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" + }, + "void-elements": { + "version": "2.0.1", + "from": "void-elements@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" + }, + "which": { + "version": "1.2.4", + "from": "which@>=1.2.2 <1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz" + }, + "wordwrap": { + "version": "0.0.3", + "from": "wordwrap@>=0.0.2 <0.1.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" + }, + "wrappy": { + "version": "1.0.1", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "ws": { + "version": "1.0.1", + "from": "ws@1.0.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz" + }, + "xmlhttprequest-ssl": { + "version": "1.5.1", + "from": "xmlhttprequest-ssl@1.5.1", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.1.tgz" + }, + "xtend": { + "version": "4.0.1", + "from": "xtend@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + }, + "yauzl": { + "version": "2.4.1", + "from": "yauzl@2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz" + }, + "yeast": { + "version": "0.1.2", + "from": "yeast@0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000..0e70fc1c6f --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "openfoodnetwork", + "version": "1.7.1", + "repository": { + "type": "git", + "url": "https://github.com/openfoodfoundation/openfoodnetwork" + }, + "devDependencies": { + "karma": "~0.13.22", + "karma-jasmine": "~0.3.8", + "jasmine-core": "~2.4.1", + "karma-phantomjs-launcher": "~1.0.0", + "karma-coffee-preprocessor": "~0.3.0" + }, + "license": "AGPL-1.0" +} From 2146a55c3bb12e58bb58d7a2028cad289ed72318 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 4 May 2016 15:28:35 +1000 Subject: [PATCH 126/154] Linking to PhantomJS installed on PATH --- .travis.yml | 13 +- npm-shrinkwrap.json | 2022 ------------------------------------------- package.json | 1 + 3 files changed, 12 insertions(+), 2024 deletions(-) delete mode 100644 npm-shrinkwrap.json diff --git a/.travis.yml b/.travis.yml index 8a66feb11a..04e7100f44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: ruby sudo: false -cache: bundler +cache: + - bundler + - directories: + - travis-phantomjs bundler_args: --without development rvm: - "2.1.5" @@ -23,12 +26,18 @@ env: - CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true" before_script: + - mkdir travis-phantomjs || true + - wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 + - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs + - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH + - cp config/database.travis.yml config/database.yml - cp config/application.yml.example config/application.yml - RAILS_ENV=test bundle exec rake db:create db:schema:load + - > if [ "$KARMA" = "true" ]; then - npm install -g npm@'~3.8.8' + npm install -g npm@'3.8.8' npm install npm install -g karma-cli@0.1.2 fi diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json deleted file mode 100644 index e1c8b2aa83..0000000000 --- a/npm-shrinkwrap.json +++ /dev/null @@ -1,2022 +0,0 @@ -{ - "name": "openfoodnetwork", - "version": "1.7.1", - "dependencies": { - "accepts": { - "version": "1.1.4", - "from": "accepts@1.1.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.1.4.tgz", - "dependencies": { - "mime-db": { - "version": "1.12.0", - "from": "mime-db@>=1.12.0 <1.13.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz" - }, - "mime-types": { - "version": "2.0.14", - "from": "mime-types@>=2.0.4 <2.1.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz" - } - } - }, - "adm-zip": { - "version": "0.4.7", - "from": "adm-zip@>=0.4.7 <0.5.0", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz" - }, - "after": { - "version": "0.8.1", - "from": "after@0.8.1", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.1.tgz" - }, - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "anymatch": { - "version": "1.3.0", - "from": "anymatch@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" - }, - "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz" - }, - "arr-flatten": { - "version": "1.0.1", - "from": "arr-flatten@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" - }, - "array-slice": { - "version": "0.2.3", - "from": "array-slice@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz" - }, - "array-unique": { - "version": "0.2.1", - "from": "array-unique@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" - }, - "arraybuffer.slice": { - "version": "0.0.6", - "from": "arraybuffer.slice@0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz" - }, - "arrify": { - "version": "1.0.1", - "from": "arrify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async-each": { - "version": "1.0.0", - "from": "async-each@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.0.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "backo2": { - "version": "1.0.2", - "from": "backo2@1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz" - }, - "balanced-match": { - "version": "0.4.1", - "from": "balanced-match@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.1.tgz" - }, - "base64-arraybuffer": { - "version": "0.1.2", - "from": "base64-arraybuffer@0.1.2", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.2.tgz" - }, - "base64id": { - "version": "0.1.0", - "from": "base64id@0.1.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-0.1.0.tgz" - }, - "batch": { - "version": "0.5.3", - "from": "batch@0.5.3", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.5.3.tgz" - }, - "benchmark": { - "version": "1.0.0", - "from": "benchmark@1.0.0", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-1.0.0.tgz" - }, - "better-assert": { - "version": "1.0.2", - "from": "better-assert@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz" - }, - "binary-extensions": { - "version": "1.4.0", - "from": "binary-extensions@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.4.0.tgz" - }, - "bl": { - "version": "1.0.3", - "from": "bl@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, - "blob": { - "version": "0.0.4", - "from": "blob@0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz" - }, - "bluebird": { - "version": "2.10.2", - "from": "bluebird@>=2.9.27 <3.0.0", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" - }, - "body-parser": { - "version": "1.15.0", - "from": "body-parser@>=1.12.4 <2.0.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.15.0.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "brace-expansion": { - "version": "1.1.4", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.4.tgz" - }, - "braces": { - "version": "1.8.4", - "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.4.tgz" - }, - "bytes": { - "version": "2.2.0", - "from": "bytes@2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz" - }, - "callsite": { - "version": "1.0.0", - "from": "callsite@1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "charenc": { - "version": "0.0.1", - "from": "charenc@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.1.tgz" - }, - "chokidar": { - "version": "1.4.3", - "from": "chokidar@>=0.8.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.4.3.tgz" - }, - "coffee-script": { - "version": "1.10.0", - "from": "coffee-script@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.10.0.tgz" - }, - "colors": { - "version": "1.1.2", - "from": "colors@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "component-bind": { - "version": "1.0.0", - "from": "component-bind@1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz" - }, - "component-emitter": { - "version": "1.1.2", - "from": "component-emitter@1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz" - }, - "component-inherit": { - "version": "0.0.3", - "from": "component-inherit@0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz" - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - }, - "concat-stream": { - "version": "1.5.0", - "from": "concat-stream@1.5.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", - "dependencies": { - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - } - } - }, - "connect": { - "version": "3.4.1", - "from": "connect@>=3.3.5 <4.0.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.4.1.tgz" - }, - "content-type": { - "version": "1.0.1", - "from": "content-type@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.1.tgz" - }, - "core-js": { - "version": "2.3.0", - "from": "core-js@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "crypt": { - "version": "0.0.1", - "from": "crypt@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.1.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "custom-event": { - "version": "1.0.0", - "from": "custom-event@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.0.tgz" - }, - "d": { - "version": "0.1.1", - "from": "d@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" - }, - "dashdash": { - "version": "1.13.1", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.1.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "debug": { - "version": "2.2.0", - "from": "debug@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "depd": { - "version": "1.1.0", - "from": "depd@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz" - }, - "di": { - "version": "0.0.1", - "from": "di@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz" - }, - "dom-serialize": { - "version": "2.2.1", - "from": "dom-serialize@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "ee-first": { - "version": "1.1.1", - "from": "ee-first@1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - }, - "engine.io": { - "version": "1.6.9", - "from": "engine.io@1.6.9", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.6.9.tgz" - }, - "engine.io-client": { - "version": "1.6.9", - "from": "engine.io-client@1.6.9", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.6.9.tgz" - }, - "engine.io-parser": { - "version": "1.2.4", - "from": "engine.io-parser@1.2.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.2.4.tgz", - "dependencies": { - "has-binary": { - "version": "0.1.6", - "from": "has-binary@0.1.6", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.6.tgz" - }, - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1" - } - } - }, - "ent": { - "version": "2.2.0", - "from": "ent@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz" - }, - "es5-ext": { - "version": "0.10.11", - "from": "es5-ext@>=0.10.11 <0.11.0", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz" - }, - "es6-iterator": { - "version": "2.0.0", - "from": "es6-iterator@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" - }, - "es6-symbol": { - "version": "3.0.2", - "from": "es6-symbol@>=3.0.2 <3.1.0", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz" - }, - "escape-html": { - "version": "1.0.3", - "from": "escape-html@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "eventemitter3": { - "version": "1.2.0", - "from": "eventemitter3@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz" - }, - "expand-braces": { - "version": "0.1.2", - "from": "expand-braces@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "dependencies": { - "braces": { - "version": "0.1.5", - "from": "braces@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz" - }, - "expand-range": { - "version": "0.1.1", - "from": "expand-range@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz" - }, - "is-number": { - "version": "0.1.1", - "from": "is-number@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz" - }, - "repeat-string": { - "version": "0.2.2", - "from": "repeat-string@>=0.2.2 <0.3.0", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz" - } - } - }, - "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" - }, - "expand-range": { - "version": "1.8.1", - "from": "expand-range@>=1.8.1 <2.0.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz" - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extglob": { - "version": "0.3.2", - "from": "extglob@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz" - }, - "extract-zip": { - "version": "1.5.0", - "from": "extract-zip@>=1.5.0 <1.6.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", - "dependencies": { - "debug": { - "version": "0.7.4", - "from": "debug@0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.0", - "from": "mkdirp@0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz" - } - } - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "fd-slicer": { - "version": "1.0.1", - "from": "fd-slicer@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz" - }, - "filename-regex": { - "version": "2.0.0", - "from": "filename-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" - }, - "fill-range": { - "version": "2.2.3", - "from": "fill-range@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz" - }, - "finalhandler": { - "version": "0.4.1", - "from": "finalhandler@0.4.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.4.1.tgz" - }, - "for-in": { - "version": "0.1.5", - "from": "for-in@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.5.tgz" - }, - "for-own": { - "version": "0.1.4", - "from": "for-own@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.4.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "1.0.0-rc4", - "from": "form-data@>=1.0.0-rc3 <1.1.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", - "dependencies": { - "async": { - "version": "1.5.2", - "from": "async@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "mime-db": { - "version": "1.22.0", - "from": "mime-db@>=1.22.0 <1.23.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" - }, - "mime-types": { - "version": "2.1.10", - "from": "mime-types@2.1.10", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" - } - } - }, - "fs-extra": { - "version": "0.26.7", - "from": "fs-extra@>=0.26.4 <0.27.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "dependencies": { - "graceful-fs": { - "version": "4.1.3", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" - } - } - }, - "fsevents": { - "version": "1.0.12", - "from": "fsevents@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.12.tgz", - "dependencies": { - "ansi": { - "version": "0.3.1", - "from": "ansi@~0.3.1", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz" - }, - "ansi-regex": { - "version": "2.0.0", - "from": "ansi-regex@^2.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@^2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz" - }, - "are-we-there-yet": { - "version": "1.1.2", - "from": "are-we-there-yet@~1.1.2", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz" - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@^0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" - }, - "async": { - "version": "1.5.2", - "from": "async@^1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@~0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" - }, - "aws4": { - "version": "1.3.2", - "from": "aws4@^1.2.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz", - "dependencies": { - "lru-cache": { - "version": "4.0.1", - "from": "lru-cache@^4.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", - "dependencies": { - "pseudomap": { - "version": "1.0.2", - "from": "pseudomap@^1.0.1", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" - }, - "yallist": { - "version": "2.0.0", - "from": "yallist@^2.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz" - } - } - } - } - }, - "bl": { - "version": "1.0.3", - "from": "bl@~1.0.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz" - }, - "block-stream": { - "version": "0.0.8", - "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@2.x.x", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@~0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@^1.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@~1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@^2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@~1.0.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@2.x.x", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" - }, - "dashdash": { - "version": "1.13.0", - "from": "dashdash@>=1.10.1 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@^1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "debug": { - "version": "2.2.0", - "from": "debug@~2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" - }, - "deep-extend": { - "version": "0.4.1", - "from": "deep-extend@~0.4.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz" - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@~1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "delegates": { - "version": "1.0.0", - "from": "delegates@^1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.0.1 <1.0.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz" - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@^1.0.2", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - }, - "extend": { - "version": "3.0.0", - "from": "extend@~3.0.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@~0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - }, - "form-data": { - "version": "1.0.0-rc4", - "from": "form-data@~1.0.0-rc3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz" - }, - "fstream": { - "version": "1.0.8", - "from": "fstream@^1.0.2", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz" - }, - "fstream-ignore": { - "version": "1.0.3", - "from": "fstream-ignore@~1.0.3", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.3.tgz", - "dependencies": { - "minimatch": { - "version": "3.0.0", - "from": "minimatch@^3.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", - "dependencies": { - "brace-expansion": { - "version": "1.1.3", - "from": "brace-expansion@^1.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", - "dependencies": { - "balanced-match": { - "version": "0.3.0", - "from": "balanced-match@^0.3.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz" - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - } - } - } - } - } - } - }, - "gauge": { - "version": "1.2.7", - "from": "gauge@~1.2.5", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz" - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@^2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@^1.1.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "graceful-fs": { - "version": "4.1.3", - "from": "graceful-fs@^4.1.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>= 1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@~2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz" - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@^2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "has-unicode": { - "version": "2.0.0", - "from": "has-unicode@^2.0.0", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@~3.1.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@2.x.x", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@~1.1.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "inherits": { - "version": "2.0.1", - "from": "inherits@*", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "ini": { - "version": "1.3.4", - "from": "ini@~1.3.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" - }, - "is-my-json-valid": { - "version": "2.13.1", - "from": "is-my-json-valid@^2.12.4", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@^1.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@~1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@~0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "json-schema": { - "version": "0.2.2", - "from": "json-schema@0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@~5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - }, - "jsonpointer": { - "version": "2.0.0", - "from": "jsonpointer@2.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" - }, - "jsprim": { - "version": "1.2.2", - "from": "jsprim@^1.2.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" - }, - "lodash.pad": { - "version": "4.1.0", - "from": "lodash.pad@^4.1.0", - "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.1.0.tgz" - }, - "lodash.padend": { - "version": "4.2.0", - "from": "lodash.padend@^4.1.0", - "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.2.0.tgz" - }, - "lodash.padstart": { - "version": "4.2.0", - "from": "lodash.padstart@^4.1.0", - "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.2.0.tgz" - }, - "lodash.repeat": { - "version": "4.0.0", - "from": "lodash.repeat@^4.0.0", - "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-4.0.0.tgz" - }, - "lodash.tostring": { - "version": "4.1.2", - "from": "lodash.tostring@^4.0.0", - "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.2.tgz" - }, - "mime-db": { - "version": "1.22.0", - "from": "mime-db@~1.22.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" - }, - "mime-types": { - "version": "2.1.10", - "from": "mime-types@~2.1.7", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.3.0 <0.4.0||>=0.4.0 <0.5.0||>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "node-pre-gyp": { - "version": "0.6.25", - "from": "node-pre-gyp@0.6.25", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.25.tgz", - "dependencies": { - "nopt": { - "version": "3.0.6", - "from": "nopt@~3.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "dependencies": { - "abbrev": { - "version": "1.0.7", - "from": "abbrev@1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" - } - } - } - } - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@~1.4.7", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "npmlog": { - "version": "2.0.3", - "from": "npmlog@~2.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.3.tgz" - }, - "oauth-sign": { - "version": "0.8.1", - "from": "oauth-sign@~0.8.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz" - }, - "once": { - "version": "1.3.3", - "from": "once@~1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@^2.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.0", - "from": "pinkie-promise@^2.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz" - }, - "process-nextick-args": { - "version": "1.0.6", - "from": "process-nextick-args@~1.0.6", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz" - }, - "qs": { - "version": "6.0.2", - "from": "qs@~6.0.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.0.2.tgz" - }, - "rc": { - "version": "1.1.6", - "from": "rc@~1.1.0", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", - "dependencies": { - "minimist": { - "version": "1.2.0", - "from": "minimist@^1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" - } - } - }, - "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@^2.0.0 || ^1.1.13", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" - }, - "request": { - "version": "2.69.0", - "from": "request@2.x", - "resolved": "https://registry.npmjs.org/request/-/request-2.69.0.tgz" - }, - "rimraf": { - "version": "2.5.2", - "from": "rimraf@~2.5.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", - "dependencies": { - "glob": { - "version": "7.0.3", - "from": "glob@^7.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz", - "dependencies": { - "inflight": { - "version": "1.0.4", - "from": "inflight@^1.0.4", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", - "dependencies": { - "wrappy": { - "version": "1.0.1", - "from": "wrappy@1", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" - } - } - }, - "inherits": { - "version": "2.0.1", - "from": "inherits@2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "minimatch": { - "version": "3.0.0", - "from": "minimatch@2 || 3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", - "dependencies": { - "brace-expansion": { - "version": "1.1.3", - "from": "brace-expansion@^1.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", - "dependencies": { - "balanced-match": { - "version": "0.3.0", - "from": "balanced-match@^0.3.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz" - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - } - } - } - } - }, - "once": { - "version": "1.3.3", - "from": "once@^1.3.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "dependencies": { - "wrappy": { - "version": "1.0.1", - "from": "wrappy@1", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" - } - } - }, - "path-is-absolute": { - "version": "1.0.0", - "from": "path-is-absolute@^1.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" - } - } - } - } - }, - "semver": { - "version": "5.1.0", - "from": "semver@~5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@1.x.x", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "sshpk": { - "version": "1.7.4", - "from": "sshpk@^1.7.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz" - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@~0.10.x", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@~0.0.4", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@^3.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "strip-json-comments": { - "version": "1.0.4", - "from": "strip-json-comments@~1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@^2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "tar": { - "version": "2.2.1", - "from": "tar@~2.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" - }, - "tar-pack": { - "version": "3.1.3", - "from": "tar-pack@~3.1.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.1.3.tgz" - }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@~2.2.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" - }, - "tunnel-agent": { - "version": "0.4.2", - "from": "tunnel-agent@~0.4.1", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz" - }, - "tweetnacl": { - "version": "0.14.3", - "from": "tweetnacl@>=0.13.0 <1.0.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz" - }, - "uid-number": { - "version": "0.0.6", - "from": "uid-number@~0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz" - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@~1.0.1", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "wrappy": { - "version": "1.0.1", - "from": "wrappy@1", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@^4.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" - } - } - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "glob": { - "version": "7.0.3", - "from": "glob@>=7.0.0 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz" - }, - "glob-base": { - "version": "0.3.0", - "from": "glob-base@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" - }, - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" - }, - "graceful-fs": { - "version": "4.1.3", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.2 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "dependencies": { - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz" - } - } - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" - }, - "has-binary": { - "version": "0.1.7", - "from": "has-binary@0.1.7", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1" - } - } - }, - "has-cors": { - "version": "1.1.0", - "from": "has-cors@1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz" - }, - "hasha": { - "version": "2.2.0", - "from": "hasha@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz" - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.0 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "http-errors": { - "version": "1.4.0", - "from": "http-errors@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz" - }, - "http-proxy": { - "version": "1.13.2", - "from": "http-proxy@>=1.13.0 <2.0.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.13.2.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" - }, - "iconv-lite": { - "version": "0.4.13", - "from": "iconv-lite@0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" - }, - "indexof": { - "version": "0.0.1", - "from": "indexof@0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" - }, - "inflight": { - "version": "1.0.4", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz" - }, - "inherits": { - "version": "2.0.1", - "from": "inherits@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" - }, - "is-absolute": { - "version": "0.1.7", - "from": "is-absolute@>=0.1.7 <0.2.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz" - }, - "is-binary-path": { - "version": "1.0.1", - "from": "is-binary-path@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" - }, - "is-buffer": { - "version": "1.1.3", - "from": "is-buffer@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz" - }, - "is-dotfile": { - "version": "1.0.2", - "from": "is-dotfile@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz" - }, - "is-equal-shallow": { - "version": "0.1.3", - "from": "is-equal-shallow@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" - }, - "is-extendable": { - "version": "0.1.1", - "from": "is-extendable@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz" - }, - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - }, - "is-my-json-valid": { - "version": "2.13.1", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz" - }, - "is-number": { - "version": "2.1.0", - "from": "is-number@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz" - }, - "is-posix-bracket": { - "version": "0.1.1", - "from": "is-posix-bracket@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz" - }, - "is-primitive": { - "version": "2.0.0", - "from": "is-primitive@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" - }, - "is-relative": { - "version": "0.1.3", - "from": "is-relative@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" - }, - "is-stream": { - "version": "1.1.0", - "from": "is-stream@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isbinaryfile": { - "version": "3.0.0", - "from": "isbinaryfile@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.0.tgz" - }, - "isexe": { - "version": "1.1.2", - "from": "isexe@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz" - }, - "isobject": { - "version": "2.1.0", - "from": "isobject@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - }, - "jasmine-core": { - "version": "2.4.1", - "from": "jasmine-core@>=2.4.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.4.1.tgz" - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz" - }, - "jsbn": { - "version": "0.1.0", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz" - }, - "json-schema": { - "version": "0.2.2", - "from": "json-schema@0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz" - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - }, - "json3": { - "version": "3.2.6", - "from": "json3@3.2.6", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.2.6.tgz" - }, - "jsonfile": { - "version": "2.3.0", - "from": "jsonfile@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.3.0.tgz" - }, - "jsonpointer": { - "version": "2.0.0", - "from": "jsonpointer@2.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" - }, - "jsprim": { - "version": "1.2.2", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz" - }, - "karma": { - "version": "0.13.22", - "from": "karma@>=0.13.22", - "resolved": "https://registry.npmjs.org/karma/-/karma-0.13.22.tgz", - "dependencies": { - "rimraf": { - "version": "2.5.2", - "from": "rimraf@>=2.3.3 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz" - } - } - }, - "karma-coffee-preprocessor": { - "version": "0.3.0", - "from": "karma-coffee-preprocessor@0.3.0", - "resolved": "https://registry.npmjs.org/karma-coffee-preprocessor/-/karma-coffee-preprocessor-0.3.0.tgz" - }, - "karma-jasmine": { - "version": "0.3.8", - "from": "karma-jasmine@>=0.3.8 <1.0.0", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-0.3.8.tgz" - }, - "karma-phantomjs-launcher": { - "version": "1.0.0", - "from": "karma-phantomjs-launcher@1.0.0", - "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.0.tgz", - "dependencies": { - "lodash": { - "version": "4.11.1", - "from": "lodash@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.11.1.tgz" - } - } - }, - "kew": { - "version": "0.7.0", - "from": "kew@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz" - }, - "kind-of": { - "version": "3.0.3", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.3.tgz" - }, - "klaw": { - "version": "1.2.0", - "from": "klaw@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.2.0.tgz" - }, - "lodash": { - "version": "3.10.1", - "from": "lodash@>=3.8.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" - }, - "log4js": { - "version": "0.6.35", - "from": "log4js@>=0.6.3 <0.7.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.35.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - }, - "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" - } - } - }, - "lru-cache": { - "version": "2.2.4", - "from": "lru-cache@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz" - }, - "md5": { - "version": "2.0.0", - "from": "md5@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.0.0.tgz", - "dependencies": { - "is-buffer": { - "version": "1.0.2", - "from": "is-buffer@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.0.2.tgz" - } - } - }, - "media-typer": { - "version": "0.3.0", - "from": "media-typer@0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - }, - "micromatch": { - "version": "2.3.8", - "from": "micromatch@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.8.tgz" - }, - "mime": { - "version": "1.3.4", - "from": "mime@>=1.3.4 <2.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz" - }, - "mime-db": { - "version": "1.23.0", - "from": "mime-db@>=1.23.0 <1.24.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz" - }, - "mime-types": { - "version": "2.1.11", - "from": "mime-types@>=2.1.10 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz" - }, - "minimatch": { - "version": "3.0.0", - "from": "minimatch@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz" - }, - "minimist": { - "version": "0.0.10", - "from": "minimist@>=0.0.1 <0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" - }, - "nan": { - "version": "2.3.3", - "from": "nan@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.3.tgz" - }, - "negotiator": { - "version": "0.4.9", - "from": "negotiator@0.4.9", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz" - }, - "node-uuid": { - "version": "1.4.7", - "from": "node-uuid@>=1.4.7 <1.5.0", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" - }, - "normalize-path": { - "version": "2.0.1", - "from": "normalize-path@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.0.1.tgz" - }, - "oauth-sign": { - "version": "0.8.1", - "from": "oauth-sign@>=0.8.0 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz" - }, - "object-component": { - "version": "0.0.3", - "from": "object-component@0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz" - }, - "object.omit": { - "version": "2.0.0", - "from": "object.omit@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" - }, - "on-finished": { - "version": "2.3.0", - "from": "on-finished@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" - }, - "once": { - "version": "1.3.3", - "from": "once@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz" - }, - "optimist": { - "version": "0.6.1", - "from": "optimist@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" - }, - "options": { - "version": "0.0.6", - "from": "options@>=0.0.5", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz" - }, - "parse-glob": { - "version": "3.0.4", - "from": "parse-glob@>=3.0.4 <4.0.0", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" - }, - "parsejson": { - "version": "0.0.1", - "from": "parsejson@0.0.1", - "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.1.tgz" - }, - "parseqs": { - "version": "0.0.2", - "from": "parseqs@0.0.2", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.2.tgz" - }, - "parseuri": { - "version": "0.0.4", - "from": "parseuri@0.0.4", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.4.tgz" - }, - "parseurl": { - "version": "1.3.1", - "from": "parseurl@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz" - }, - "path-is-absolute": { - "version": "1.0.0", - "from": "path-is-absolute@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" - }, - "pend": { - "version": "1.2.0", - "from": "pend@>=1.2.0 <1.3.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" - }, - "phantomjs": { - "version": "2.1.1", - "from": "phantomjs@2.1.1", - "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.1.tgz" - }, - "phantomjs-prebuilt": { - "version": "2.1.7", - "from": "phantomjs-prebuilt@2.1.7", - "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.7.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" - }, - "preserve": { - "version": "0.2.0", - "from": "preserve@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" - }, - "process-nextick-args": { - "version": "1.0.6", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz" - }, - "progress": { - "version": "1.1.8", - "from": "progress@>=1.1.8 <1.2.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz" - }, - "qs": { - "version": "6.1.0", - "from": "qs@6.1.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz" - }, - "randomatic": { - "version": "1.1.5", - "from": "randomatic@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" - }, - "raw-body": { - "version": "2.1.6", - "from": "raw-body@>=2.1.5 <2.2.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.6.tgz", - "dependencies": { - "bytes": { - "version": "2.3.0", - "from": "bytes@2.3.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.3.0.tgz" - } - } - }, - "readable-stream": { - "version": "2.1.2", - "from": "readable-stream@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.2.tgz" - }, - "readdirp": { - "version": "2.0.0", - "from": "readdirp@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz", - "dependencies": { - "minimatch": { - "version": "2.0.10", - "from": "minimatch@>=2.0.10 <3.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" - } - } - }, - "regex-cache": { - "version": "0.4.3", - "from": "regex-cache@>=0.4.2 <0.5.0", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz" - }, - "repeat-element": { - "version": "1.1.2", - "from": "repeat-element@>=1.1.2 <2.0.0", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" - }, - "repeat-string": { - "version": "1.5.4", - "from": "repeat-string@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.4.tgz" - }, - "request": { - "version": "2.67.0", - "from": "request@>=2.67.0 <2.68.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz", - "dependencies": { - "mime-db": { - "version": "1.22.0", - "from": "mime-db@1.22.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz" - }, - "mime-types": { - "version": "2.1.10", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz" - }, - "qs": { - "version": "5.2.0", - "from": "qs@>=5.2.0 <5.3.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz" - } - } - }, - "request-progress": { - "version": "2.0.1", - "from": "request-progress@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz" - }, - "requires-port": { - "version": "1.0.0", - "from": "requires-port@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" - }, - "rimraf": { - "version": "2.2.8", - "from": "rimraf@>=2.2.5 <2.3.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" - }, - "semver": { - "version": "4.3.6", - "from": "semver@>=4.3.3 <4.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" - }, - "socket.io": { - "version": "1.4.6", - "from": "socket.io@>=1.4.5 <2.0.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.4.6.tgz" - }, - "socket.io-adapter": { - "version": "0.4.0", - "from": "socket.io-adapter@0.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.4.0.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1" - }, - "socket.io-parser": { - "version": "2.2.2", - "from": "socket.io-parser@2.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.2.tgz", - "dependencies": { - "debug": { - "version": "0.7.4", - "from": "debug@0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" - } - } - } - } - }, - "socket.io-client": { - "version": "1.4.6", - "from": "socket.io-client@1.4.6", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.4.6.tgz", - "dependencies": { - "component-emitter": { - "version": "1.2.0", - "from": "component-emitter@1.2.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.0.tgz" - } - } - }, - "socket.io-parser": { - "version": "2.2.6", - "from": "socket.io-parser@2.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.2.6.tgz", - "dependencies": { - "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1" - }, - "json3": { - "version": "3.3.2", - "from": "json3@3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz" - } - } - }, - "source-map": { - "version": "0.5.6", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz" - }, - "sshpk": { - "version": "1.8.3", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.8.3.tgz", - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - } - } - }, - "statuses": { - "version": "1.2.1", - "from": "statuses@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.1.tgz" - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" - }, - "throttleit": { - "version": "1.0.0", - "from": "throttleit@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz" - }, - "to-array": { - "version": "0.1.4", - "from": "to-array@0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz" - }, - "tough-cookie": { - "version": "2.2.2", - "from": "tough-cookie@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz" - }, - "tunnel-agent": { - "version": "0.4.2", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz" - }, - "tweetnacl": { - "version": "0.13.3", - "from": "tweetnacl@>=0.13.0 <0.14.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz" - }, - "type-is": { - "version": "1.6.12", - "from": "type-is@>=1.6.11 <1.7.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.12.tgz" - }, - "typedarray": { - "version": "0.0.6", - "from": "typedarray@>=0.0.5 <0.1.0", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" - }, - "ultron": { - "version": "1.0.2", - "from": "ultron@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz" - }, - "unpipe": { - "version": "1.0.0", - "from": "unpipe@1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - }, - "useragent": { - "version": "2.1.9", - "from": "useragent@>=2.1.6 <3.0.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.1.9.tgz" - }, - "utf8": { - "version": "2.1.0", - "from": "utf8@2.1.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.0.tgz" - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "utils-merge": { - "version": "1.0.0", - "from": "utils-merge@1.0.0", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz" - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" - }, - "void-elements": { - "version": "2.0.1", - "from": "void-elements@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" - }, - "which": { - "version": "1.2.4", - "from": "which@>=1.2.2 <1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz" - }, - "wordwrap": { - "version": "0.0.3", - "from": "wordwrap@>=0.0.2 <0.1.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz" - }, - "wrappy": { - "version": "1.0.1", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" - }, - "ws": { - "version": "1.0.1", - "from": "ws@1.0.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz" - }, - "xmlhttprequest-ssl": { - "version": "1.5.1", - "from": "xmlhttprequest-ssl@1.5.1", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.1.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" - }, - "yauzl": { - "version": "2.4.1", - "from": "yauzl@2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz" - }, - "yeast": { - "version": "0.1.2", - "from": "yeast@0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz" - } - } -} diff --git a/package.json b/package.json index 0e70fc1c6f..bb174e883d 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "url": "https://github.com/openfoodfoundation/openfoodnetwork" }, "devDependencies": { + "phantomjs-prebuilt": "~2.1.7", "karma": "~0.13.22", "karma-jasmine": "~0.3.8", "jasmine-core": "~2.4.1", From 0586a710711aa4b6d98cb5b2a92f83aee95e8bbe Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 5 May 2016 12:33:06 +1000 Subject: [PATCH 127/154] Bumping Poltergeist and Capybara versions --- Gemfile.lock | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 494882481c..07931312ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -176,7 +176,8 @@ GEM columnize (~> 0.3) debugger-linecache (~> 1.2) cancan (1.6.8) - capybara (2.5.0) + capybara (2.7.1) + addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -455,7 +456,7 @@ GEM railties (>= 3.1) money (5.1.1) i18n (~> 0.6.0) - multi_json (1.11.2) + multi_json (1.12.0) multi_xml (0.5.5) newrelic_rpm (3.12.0.288) nokogiri (1.6.7.2) @@ -479,7 +480,7 @@ GEM paypal-sdk-merchant (1.106.1) paypal-sdk-core (~> 0.2.3) pg (0.13.2) - poltergeist (1.7.0) + poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) multi_json (~> 1.0) @@ -626,7 +627,7 @@ GEM webmock (1.13.0) addressable (>= 2.2.7) crack (>= 0.3.2) - websocket-driver (0.6.2) + websocket-driver (0.6.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) whenever (0.9.2) From 245db8971ae5be87eb0c1623e67b883ec81fa27e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 5 May 2016 13:50:58 +1000 Subject: [PATCH 128/154] Fixing specs broken by faster PhantomJS --- .../order_cycles/controllers/create.js.coffee | 2 +- .../controllers/simple_create.js.coffee | 2 +- .../_name_and_timing_form.html.haml | 6 +- .../admin/bulk_order_management_spec.rb | 316 +++++++++--------- .../admin/bulk_product_update_spec.rb | 89 ++--- spec/features/admin/order_cycles_spec.rb | 28 +- .../unit/order_cycle_spec.js.coffee | 3 +- 7 files changed, 240 insertions(+), 206 deletions(-) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee index c837ff84ec..3077aa8a11 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee @@ -12,7 +12,7 @@ angular.module('admin.orderCycles') $scope.StatusMessage = StatusMessage $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded $scope.suppliedVariants = (enterprise_id) -> Enterprise.suppliedVariants(enterprise_id) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee index a6d1e18535..dfa8011167 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -20,7 +20,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl" OrderCycle.order_cycle.coordinator_id = enterprise.id $scope.loaded = -> - Enterprise.loaded && EnterpriseFee.loaded + Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded $scope.removeDistributionOfVariant = angular.noop diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml index 703d8094b9..39fa19f287 100644 --- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -3,14 +3,14 @@ = f.label :name .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true + = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()' - else {{ order_cycle.name }} .two.columns = f.label :orders_open_at, 'Orders open' .omega.six.columns - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at' + = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at', 'ng-disabled' => '!loaded()' - else {{ order_cycle.orders_open_at }} @@ -23,6 +23,6 @@ = f.label :orders_close_at, 'Orders close' .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) - = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at' + = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at', 'ng-disabled' => '!loaded()' - else {{ order_cycle.orders_close_at }} diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index de5bd85d3e..ef4fc13e39 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -18,12 +18,12 @@ feature %q{ end context "displaying the list of line items" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'address', completed_at: nil ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'address', completed_at: nil ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:li3) { create(:line_item, order: o3 ) } before :each do visit '/admin/orders/bulk_management' @@ -32,15 +32,15 @@ feature %q{ it "displays a list of line items" do expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" - expect(page).to_not have_selector "tr#li_#{li3.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" end end context "displaying individual columns" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: FactoryGirl.create(:address) ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: nil ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: FactoryGirl.create(:product_with_option_types) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: create(:address) ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, bill_address: nil ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2, product: create(:product_with_option_types) ) } before :each do visit '/admin/orders/bulk_management' @@ -90,8 +90,8 @@ feature %q{ end context "tracking changes" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 5 ) } before :each do visit '/admin/orders/bulk_management' @@ -105,8 +105,8 @@ feature %q{ end context "submitting data to the server" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 5 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 5 ) } before :each do Spree::Config.set(allow_backorders: false) @@ -116,19 +116,19 @@ feature %q{ context "when acceptable data is sent to the server" do it "displays an update button which submits pending changes" do - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" fill_in "quantity", :with => 2 expect(page).to have_selector "input[name='quantity'].ng-dirty" expect(page).to have_selector "#save-bar", text: "You have unsaved changes" click_button "Save Changes" expect(page).to have_selector "#save-bar", text: "All changes saved" - expect(page).to_not have_selector "input[name='quantity'].ng-dirty" + expect(page).to have_no_selector "input[name='quantity'].ng-dirty" end end context "when unacceptable data is sent to the server" do it "displays an update button which submits pending changes" do - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" fill_in "quantity", :with => li1.variant.on_hand + li1.quantity + 10 expect(page).to have_selector "input[name='quantity'].ng-dirty" expect(page).to have_selector "#save-bar", text: "You have unsaved changes" @@ -146,28 +146,28 @@ feature %q{ admin_user = quick_login_as_admin end - let!(:p1) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:p1) { create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [create(:variant, unit_value: 1000)] ) } let!(:v1) { p1.variants.first } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, variant: v1, :quantity => 5, :final_weight_volume => 1000, price: 10.00 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1, variant: v1, :quantity => 5, :final_weight_volume => 1000, price: 10.00 ) } before { v1.update_attribute(:on_hand, 100)} context "modifying the weight/volume of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns-dropdown div.menu div.menu_item", text: "Price").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + find("div#columns-dropdown div.menu div.menu_item", text: "Price").click # hide dropdown - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$50.00" fill_in "final_weight_volume", :with => 2000 expect(page).to have_field "price", with: "$100.00" end click_button "Save Changes" - expect(page).to_not have_selector "#save-bar" + expect(page).to have_no_selector "#save-bar" li1.reload expect(li1.final_weight_volume).to eq 2000 expect(li1.price).to eq 20.00 @@ -177,9 +177,9 @@ feature %q{ context "modifying the quantity of a line item" do it "price is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Price").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Price").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}" fill_in "quantity", :with => 6 @@ -191,9 +191,9 @@ feature %q{ context "modifying the quantity of a line item" do it "weight/volume is altered" do visit '/admin/orders/bulk_management' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Weight/Volume").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do expect(page).to have_field "final_weight_volume", with: "#{li1.final_weight_volume.round}" fill_in "quantity", :with => 6 @@ -213,11 +213,11 @@ feature %q{ expect(page).to have_selector "th", :text => "QUANTITY" expect(page).to have_selector "th", :text => "MAX" - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Producer").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Producer").click + find("div#columns-dropdown", :text => "COLUMNS").click - expect(page).to_not have_selector "th", :text => "PRODUCER" + expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "ORDER DATE" expect(page).to have_selector "th", :text => "PRODUCT: UNIT" @@ -230,60 +230,64 @@ feature %q{ context "supplier filter" do let!(:s1) { create(:supplier_enterprise) } let!(:s2) { create(:supplier_enterprise) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: create(:simple_order_cycle) ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s2) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: create(:simple_order_cycle) ) } + let!(:li1) { create(:line_item, order: o1, product: create(:product, supplier: s1) ) } + let!(:li2) { create(:line_item, order: o1, product: create(:product, supplier: s2) ) } before :each do visit '/admin/orders/bulk_management' end it "displays a select box for producers, which filters line items by the selected supplier" do - supplier_names = ["All"] - Enterprise.is_primary_producer.each{ |e| supplier_names << e.name } + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" open_select2 "div.select2-container#s2id_supplier_filter" - supplier_names.each { |sn| expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: sn } + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: "All" + Enterprise.is_primary_producer.map(&:name).each do |sn| + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: sn + end close_select2 "div.select2-container#s2id_supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from supplier filter" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end end context "distributor filter" do let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: create(:simple_order_cycle) ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: create(:simple_order_cycle) ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: create(:simple_order_cycle) ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: create(:simple_order_cycle) ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' end it "displays a select box for distributors, which filters line items by the selected distributor" do - distributor_names = ["All"] - Enterprise.is_distributor.each{ |e| distributor_names << e.name } + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" open_select2 "div.select2-container#s2id_distributor_filter" - distributor_names.each { |dn| expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: dn } + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: "All" + Enterprise.is_distributor.map(&:name).each do |dn| + expect(page).to have_selector "div.select2-drop-active ul.select2-results li", text: dn + end close_select2 "div.select2-container#s2id_distributor_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true select2_select d1.name, from: "distributor_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from distributor filter" do @@ -291,7 +295,7 @@ feature %q{ expect(page).to have_selector "tr#li_#{li2.id}" select2_select d1.name, from: "distributor_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "distributor_filter" expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" @@ -300,25 +304,25 @@ feature %q{ context "order_cycle filter" do let!(:distributor) { create(:distributor_enterprise) } - let!(:oc1) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle, distributors: [distributor]) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc2 ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:oc1) { create(:simple_order_cycle, distributors: [distributor]) } + let!(:oc2) { create(:simple_order_cycle, distributors: [distributor]) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, order_cycle: oc2 ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before do visit '/admin/orders/bulk_management' end it "displays a select box for order cycles, which filters line items by the selected order cycle" do - expect(page).to have_select2 'order_cycle_filter', with_options: OrderCycle.pluck(:name).unshift("All") expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_select2 'order_cycle_filter', with_options: OrderCycle.pluck(:name).unshift("All") select2_select oc1.name, from: "order_cycle_filter" - expect(page).to_not have_selector "#loading img.spinner" + expect(page).to have_no_selector "#loading img.spinner" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end it "displays all line items when 'All' is selected from order_cycle filter" do @@ -326,7 +330,7 @@ feature %q{ expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select "All", from: "order_cycle_filter" expect(page).to have_selector "tr#li_#{li1.id}" expect(page).to have_selector "tr#li_#{li2.id}" @@ -338,42 +342,46 @@ feature %q{ let!(:s2) { create(:supplier_enterprise) } let!(:d1) { create(:distributor_enterprise) } let!(:d2) { create(:distributor_enterprise) } - let!(:oc1) { FactoryGirl.create(:simple_order_cycle, suppliers: [s1], distributors: [d1] ) } - let!(:oc2) { FactoryGirl.create(:simple_order_cycle, suppliers: [s2], distributors: [d2] ) } - let!(:p1) { FactoryGirl.create(:product, supplier: s1) } - let!(:p2) { FactoryGirl.create(:product, supplier: s2) } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: oc1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: oc2 ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, product: p1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, product: p2 ) } + let!(:oc1) { create(:simple_order_cycle, suppliers: [s1], distributors: [d1] ) } + let!(:oc2) { create(:simple_order_cycle, suppliers: [s2], distributors: [d2] ) } + let!(:p1) { create(:product, supplier: s1) } + let!(:p2) { create(:product, supplier: s2) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1, order_cycle: oc1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2, order_cycle: oc2 ) } + let!(:li1) { create(:line_item, order: o1, product: p1 ) } + let!(:li2) { create(:line_item, order: o2, product: p2 ) } before :each do visit '/admin/orders/bulk_management' end it "allows filters to be used in combination" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select d1.name, from: "distributor_filter" select2_select s1.name, from: "supplier_filter" - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select d2.name, from: "distributor_filter" select2_select s2.name, from: "supplier_filter" - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" select2_select oc2.name, from: "order_cycle_filter" - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end it "displays a 'Clear All' button which sets all select filters to 'All'" do + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" select2_select oc1.name, from: "order_cycle_filter" select2_select d1.name, from: "distributor_filter" select2_select s1.name, from: "supplier_filter" expect(page).to have_selector "tr#li_#{li1.id}" - expect(page).to_not have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" expect(page).to have_button "Clear All" click_button "Clear All" expect(page).to have_selector "div#s2id_order_cycle_filter a.select2-choice", text: "All" @@ -386,12 +394,12 @@ feature %q{ end context "using quick search" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:li3) { create(:line_item, order: o3 ) } before :each do visit '/admin/orders/bulk_management' @@ -402,23 +410,23 @@ feature %q{ end it "filters line items based on their attributes and the contents of the quick search input" do - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li3.id}" fill_in "quick_search", :with => o1.email - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}", true + expect(page).to have_no_selector "tr#li_#{li3.id}" end end context "using date restriction controls" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.current - 8).strftime("%F %T") ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: (Date.current + 2).strftime("%F %T") ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1, :quantity => 1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2, :quantity => 2 ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3, :quantity => 3 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: (Date.current - 8).strftime("%F %T") ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: (Date.current + 2).strftime("%F %T") ) } + let!(:li1) { create(:line_item, order: o1, :quantity => 1 ) } + let!(:li2) { create(:line_item, order: o2, :quantity => 2 ) } + let!(:li3) { create(:line_item, order: o3, :quantity => 3 ) } before :each do visit '/admin/orders/bulk_management' @@ -434,21 +442,21 @@ feature %q{ end it "only loads line items whose orders meet the date restriction criteria" do - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" end it "displays only line items whose orders meet the date restriction criteria, when changed" do fill_in "start_date_filter", :with => (Date.current - 9).strftime("%F") - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_no_selector "tr#li_#{li3.id}" fill_in "end_date_filter", :with => (Date.current + 3).strftime("%F") - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true - expect(page).to have_selector "tr#li_#{li3.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" + expect(page).to have_selector "tr#li_#{li3.id}" end context "when the form is dirty" do @@ -481,10 +489,10 @@ feature %q{ end context "bulk action controls" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -512,15 +520,15 @@ feature %q{ context "performing actions" do it "deletes selected items" do - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" within("tr#li_#{li2.id} td.bulk") do check "bulk" end find("div#bulk-actions-dropdown").click find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click - expect(page).to have_selector "tr#li_#{li1.id}", visible: true - expect(page).to_not have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_selector "tr#li_#{li1.id}" + expect(page).to have_no_selector "tr#li_#{li2.id}" end end @@ -540,18 +548,18 @@ feature %q{ find("div#bulk-actions-dropdown").click find("div#bulk-actions-dropdown div.menu_item", :text => "Delete Selected" ).click fill_in "quick_search", with: '' - expect(page).to_not have_selector "tr#li_#{li1.id}", visible: true - expect(page).to have_selector "tr#li_#{li2.id}", visible: true + expect(page).to have_no_selector "tr#li_#{li1.id}" + expect(page).to have_selector "tr#li_#{li2.id}" end end end context "using action buttons" do context "using edit buttons" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -564,7 +572,7 @@ feature %q{ page.driver.dismiss_modal :confirm, text: "Unsaved changes exist and will be lost if you continue." do within "tr#li_#{li1.id}" do fill_in "quantity", with: (li1.quantity + 1) - first("a.edit-order").click + find("a.edit-order").click end end @@ -575,17 +583,17 @@ feature %q{ # And try again within "tr#li_#{li1.id}" do - first("a.edit-order").click + find("a.edit-order").click end expect(URI.parse(current_url).path).to eq "/admin/orders/#{o1.number}/edit" end end context "using delete buttons" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } before :each do visit '/admin/orders/bulk_management' @@ -593,8 +601,8 @@ feature %q{ it "removes a line item when the relevant delete button is clicked" do expect(page).to have_selector "a.delete-line-item", :count => 2 - first("a.delete-line-item").click - expect(page).to_not have_selector "a.delete-line-item", :count => 2 + find("tr#li_#{li1.id} a.delete-line-item").click + expect(page).to have_no_selector "a.delete-line-item", :count => 2 expect(page).to have_selector "a.delete-line-item", :count => 1 visit '/admin/orders/bulk_management' expect(page).to have_selector "a.delete-line-item", :count => 1 @@ -603,15 +611,15 @@ feature %q{ end context "clicking the link on variant name" do - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li1) { FactoryGirl.create(:line_item, order: o1 ) } - let!(:li2) { FactoryGirl.create(:line_item, order: o2 ) } - let!(:p3) { FactoryGirl.create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [FactoryGirl.create(:variant, unit_value: 1000)] ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li1) { create(:line_item, order: o1 ) } + let!(:li2) { create(:line_item, order: o2 ) } + let!(:p3) { create(:product_with_option_types, group_buy: true, group_buy_unit_size: 5000, variant_unit: "weight", variants: [create(:variant, unit_value: 1000)] ) } let!(:v3) { p3.variants.first } - let!(:o3) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } - let!(:li3) { FactoryGirl.create(:line_item, order: o3, variant: v3, quantity: 3, max_quantity: 6 ) } - let!(:li4) { FactoryGirl.create(:line_item, order: o2, variant: v3, quantity: 1, max_quantity: 3 ) } + let!(:o3) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now ) } + let!(:li3) { create(:line_item, order: o3, variant: v3, quantity: 3, max_quantity: 6 ) } + let!(:li4) { create(:line_item, order: o2, variant: v3, quantity: 1, max_quantity: 3 ) } before :each do visit '/admin/orders/bulk_management' @@ -643,8 +651,8 @@ feature %q{ end it "all line items of the same variant" do - expect(page).to_not have_selector "tr#li_#{li1.id}", :visible => true - expect(page).to_not have_selector "tr#li_#{li2.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{li1.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{li2.id}", :visible => true expect(page).to have_selector "tr#li_#{li3.id}", :visible => true expect(page).to have_selector "tr#li_#{li4.id}", :visible => true end @@ -655,7 +663,7 @@ feature %q{ end it "shows all products and clears group buy box" do - expect(page).to_not have_selector "div#group_buy_calculation", :visible => true + expect(page).to have_no_selector "div#group_buy_calculation", :visible => true expect(page).to have_selector "tr#li_#{li1.id}", :visible => true expect(page).to have_selector "tr#li_#{li2.id}", :visible => true expect(page).to have_selector "tr#li_#{li3.id}", :visible => true @@ -669,10 +677,10 @@ feature %q{ let(:s1) { create(:supplier_enterprise, name: 'First Supplier') } let(:d1) { create(:distributor_enterprise, name: 'First Distributor') } let(:d2) { create(:distributor_enterprise, name: 'Another Distributor') } - let!(:o1) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1 ) } - let!(:o2) { FactoryGirl.create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2 ) } - let!(:line_item_distributed) { FactoryGirl.create(:line_item, order: o1, product: create(:product, supplier: s1) ) } - let!(:line_item_not_distributed) { FactoryGirl.create(:line_item, order: o2, product: create(:product, supplier: s1) ) } + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d1 ) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now, distributor: d2 ) } + let!(:line_item_distributed) { create(:line_item, order: o1, product: create(:product, supplier: s1) ) } + let!(:line_item_not_distributed) { create(:line_item, order: o2, product: create(:product, supplier: s1) ) } before(:each) do @enterprise_user = create_enterprise_user @@ -693,7 +701,7 @@ feature %q{ visit '/admin/orders/bulk_management' expect(page).to have_selector "tr#li_#{line_item_distributed.id}", :visible => true - expect(page).to_not have_selector "tr#li_#{line_item_not_distributed.id}", :visible => true + expect(page).to have_no_selector "tr#li_#{line_item_not_distributed.id}", :visible => true end end end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 9dabf93028..260caf207e 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -46,8 +46,9 @@ feature %q{ p2 = FactoryGirl.create(:product, available_on: Date.current-1) visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_field "available_on", with: p1.available_on.strftime("%F %T") expect(page).to have_field "available_on", with: p2.available_on.strftime("%F %T") @@ -73,7 +74,8 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 0, on_demand: true) visit '/admin/products/bulk_edit' - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') expect(page).to have_no_selector "span[name='on_hand']", text: "On demand", visible: true expect(page).to have_field "variant_on_hand", with: "4" @@ -109,7 +111,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, display_name: "something2" ) visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" + expect(page).to have_selector "a.view-variants", count: 2 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "product_name", with: v1.product.name @@ -124,6 +126,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, on_hand: 6) visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_selector "span[name='on_hand']", text: p1.variants.sum{ |v| v.on_hand }.to_s @@ -138,6 +141,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50) visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "price", with: "2.0", visible: false @@ -151,6 +155,7 @@ feature %q{ v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50, unit_value: 4800, unit_description: "(large bag)", display_as: "bin") visit '/admin/products/bulk_edit' + expect(page).to have_selector "a.view-variants", count: 1 all("a.view-variants").each { |e| e.trigger('click') } expect(page).to have_field "variant_unit_value_with_description", with: "1.2 (small bag)" @@ -217,7 +222,7 @@ feature %q{ fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." updated_variant = Spree::Variant.where(deleted_at: nil).last @@ -243,11 +248,12 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click - first("div#columns-dropdown div.menu div.menu_item", text: "Category").click - first("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click - first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown div.menu div.menu_item", text: "Category").click + find("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click + find("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -267,7 +273,7 @@ feature %q{ fill_in "product_sku", with: "NEW SKU" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -293,7 +299,7 @@ feature %q{ select "Items", from: "variant_unit_with_scale" fill_in "variant_unit_name", with: "loaf" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -313,12 +319,12 @@ feature %q{ login_to_admin_section visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "SKU").click - first("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "SKU").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_field "variant_sku", with: "VARIANTSKU" expect(page).to have_field "variant_price", with: "3.0" @@ -334,7 +340,7 @@ feature %q{ expect(page).to have_selector "span[name='on_hand']", text: "10" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -352,8 +358,8 @@ feature %q{ login_to_admin_section visit '/admin/products/bulk_edit' - expect(page).to have_selector "a.view-variants" - first("a.view-variants").trigger('click') + expect(page).to have_selector "a.view-variants", count: 1 + find("a.view-variants").trigger('click') expect(page).to have_field "variant_price", with: "3.0" @@ -361,7 +367,7 @@ feature %q{ fill_in "variant_price", with: "10.0" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -378,21 +384,21 @@ feature %q{ fill_in "product_name", with: "new name 1" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 1" fill_in "product_name", with: "new name 2" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 2" fill_in "product_name", with: "original name" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "original name" @@ -404,11 +410,12 @@ feature %q{ visit '/admin/products/bulk_edit' - first("a.clone-product").click + expect(page).to have_selector "a.clone-product", count: 1 + find("a.clone-product").click fill_in "product_name", :with => "new product name" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new product name" @@ -421,7 +428,7 @@ feature %q{ visit '/admin/products/bulk_edit' - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "No changes to save." end end @@ -440,7 +447,7 @@ feature %q{ expect(page).to have_no_field "product_name", with: p2.name fill_in "product_name", :with => "new product1" - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p1.reload expect(p1.name).to eq "new product1" @@ -464,7 +471,7 @@ feature %q{ expect(page).to have_selector "a.delete-product", :count => 2 within "tr#p_#{p1.id}" do - first("a.delete-product").click + find("a.delete-product").click end expect(page).to have_selector "a.delete-product", :count => 1 @@ -481,7 +488,7 @@ feature %q{ expect(page).to have_selector "a.delete-variant", :count => 3 within "tr#v_#{v3.id}" do - first("a.delete-variant").click + find("a.delete-variant").click end expect(page).to have_selector "a.delete-variant", :count => 2 @@ -509,7 +516,7 @@ feature %q{ expect(page).to have_selector "a.edit-product", :count => 2 within "tr#p_#{p1.id}" do - first("a.edit-product").click + find("a.edit-product").click end expect(URI.parse(current_url).path).to eq "/admin/products/#{p1.permalink}/edit" @@ -522,7 +529,7 @@ feature %q{ expect(page).to have_selector "a.edit-variant", :count => 2 within "tr#v_#{v1.id}" do - first("a.edit-variant").click + find("a.edit-variant").click end expect(URI.parse(current_url).path).to eq "/admin/products/#{v1.product.permalink}/variants/#{v1.id}/edit" @@ -541,7 +548,7 @@ feature %q{ expect(page).to have_selector "a.clone-product", :count => 3 within "tr#p_#{p1.id}" do - first("a.clone-product").click + find("a.clone-product").click end expect(page).to have_selector "a.clone-product", :count => 4 expect(page).to have_field "product_name", with: "COPY OF #{p1.name}" @@ -564,8 +571,9 @@ feature %q{ visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_selector "th", :text => "NAME" expect(page).to have_selector "th", :text => "PRODUCER" @@ -573,7 +581,9 @@ feature %q{ expect(page).to have_selector "th", :text => "ON HAND" expect(page).to have_selector "th", :text => "AV. ON" - first("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: /^.{0,1}Producer$/).click + find("div#columns-dropdown", :text => "COLUMNS").click expect(page).to have_no_selector "th", :text => "PRODUCER" expect(page).to have_selector "th", :text => "NAME" @@ -696,8 +706,9 @@ feature %q{ v = p.variants.first visit '/admin/products/bulk_edit' - first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click + find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click + find("div#columns-dropdown", :text => "COLUMNS").click within "tr#p_#{p.id}" do expect(page).to have_field "product_name", with: p.name @@ -718,7 +729,7 @@ feature %q{ fill_in "variant_display_as", with: "Big Bag" end - first(:button, 'Save Changes').click + click_button 'Save Changes', match: :first expect(page.find("#status-message")).to have_content "Changes saved." p.reload diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index cd5ef0a07e..22402222fe 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -608,9 +608,16 @@ feature %q{ visit edit_admin_order_cycle_path(oc) - # I should not see exchanges for supplier_unmanaged or distributor_unmanaged - page.all('tr.supplier').count.should == 3 - page.all('tr.distributor').count.should == 3 + # I should be able to see but not edit exchanges for supplier_unmanaged or distributor_unmanaged + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page).to have_selector "tr.supplier-#{supplier_permitted.id}" + expect(page).to have_selector "tr.supplier-#{supplier_unmanaged.id}" + expect(page.all('tr.supplier').count).to be 3 + + expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" + expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" + expect(page).to have_selector "tr.distributor-#{distributor_unmanaged.id}" + expect(page.all('tr.distributor').count).to be 3 oc.reload oc.suppliers.should match_array [supplier_managed, supplier_permitted, supplier_unmanaged] @@ -689,8 +696,12 @@ feature %q{ # I should only see exchanges for supplier_managed AND # distributor_managed and distributor_permitted (who I have given permission to) AND # and distributor_unmanaged (who distributes my products) - page.all('tr.supplier').count.should == 1 - page.all('tr.distributor').count.should == 2 + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page.all('tr.supplier').count).to be 1 + + expect(page).to have_selector "tr.distributor-#{distributor_managed.id}" + expect(page).to have_selector "tr.distributor-#{distributor_permitted.id}" + expect(page.all('tr.distributor').count).to be 2 # Open the products list for managed_supplier's incoming exchange within "tr.distributor-#{distributor_managed.id}" do @@ -738,8 +749,11 @@ feature %q{ visit edit_admin_order_cycle_path(oc) # I should see exchanges for my_distributor, and the incoming exchanges supplying the variants in it - page.all('tr.supplier').count.should == 1 - page.all('tr.distributor').count.should == 1 + expect(page).to have_selector "tr.supplier-#{supplier_managed.id}" + expect(page.all('tr.supplier').count).to be 1 + + expect(page).to have_selector "tr.distributor-#{my_distributor.id}" + expect(page.all('tr.distributor').count).to be 1 # Open the products list for managed_supplier's incoming exchange within "tr.supplier-#{supplier_managed.id}" do diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 599c5530b0..511051d39f 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -58,12 +58,13 @@ describe 'OrderCycle controllers', -> describe 'Reporting when all resources are loaded', -> it 'returns true when Enterprise and EnterpriseFee are loaded', -> - Enterprise.loaded = EnterpriseFee.loaded = true + Enterprise.loaded = EnterpriseFee.loaded = OrderCycle.loaded = true expect(scope.loaded()).toBe(true) it 'returns false otherwise', -> Enterprise.loaded = true EnterpriseFee.loaded = false + OrderCycle.loaded = true expect(scope.loaded()).toBe(false) it "delegates suppliedVariants to Enterprise", -> From 9e0b97dc9c22d2262160f7dac12abd5c7f50eb94 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 5 May 2016 16:46:07 +1000 Subject: [PATCH 129/154] Upgrading textAngular --- app/assets/javascripts/admin/all.js | 3 +- app/assets/stylesheets/admin/all.css | 2 +- app/assets/stylesheets/shared/textAngular.css | 193 ++++++++++++++++++ .../stylesheets/shared/textAngular.min.css | 1 - .../javascripts/textAngular-rangy.min.js | 2 + .../javascripts/textAngular-sanitize.min.js | 7 +- vendor/assets/javascripts/textAngular.min.js | 5 +- 7 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 app/assets/stylesheets/shared/textAngular.css delete mode 100644 app/assets/stylesheets/shared/textAngular.min.css create mode 100644 vendor/assets/javascripts/textAngular-rangy.min.js diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 73b51a20be..0923e5721c 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -43,8 +43,9 @@ //= require ./utils/utils //= require ./users/users //= require ./variant_overrides/variant_overrides -//= require textAngular.min.js +//= require textAngular-rangy.min.js //= require textAngular-sanitize.min.js +//= require textAngular.min.js //= require darkswarm/i18n.js //= require darkswarm/i18n.translate.js diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.css index e0d668b95b..e913e9e710 100644 --- a/app/assets/stylesheets/admin/all.css +++ b/app/assets/stylesheets/admin/all.css @@ -9,7 +9,7 @@ *= require admin/spree_promo *= require shared/jquery-ui-timepicker-addon - *= require shared/textAngular.min + *= require shared/textAngular *= require shared/ng-tags-input.min *= require_self diff --git a/app/assets/stylesheets/shared/textAngular.css b/app/assets/stylesheets/shared/textAngular.css new file mode 100644 index 0000000000..a2f76234dc --- /dev/null +++ b/app/assets/stylesheets/shared/textAngular.css @@ -0,0 +1,193 @@ +.ta-hidden-input { + width: 1px; + height: 1px; + border: none; + margin: 0; + padding: 0; + position: absolute; + top: -10000px; + left: -10000px; + opacity: 0; + overflow: hidden; +} + +/* add generic styling for the editor */ +.ta-root.focussed > .ta-scroll-window.form-control { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.ta-editor.ta-html, .ta-scroll-window.form-control { + min-height: 300px; + height: auto; + overflow: auto; + font-family: inherit; + font-size: 100%; +} + +.ta-scroll-window.form-control { + position: relative; + padding: 0; +} + +.ta-scroll-window > .ta-bind { + height: auto; + min-height: 300px; + padding: 6px 12px; +} + +.ta-editor:focus { + user-select: text; +} + +/* add the styling for the awesomness of the resizer */ +.ta-resizer-handle-overlay { + z-index: 100; + position: absolute; + display: none; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-info { + position: absolute; + bottom: 16px; + right: 16px; + border: 1px solid black; + background-color: #FFF; + padding: 0 4px; + opacity: 0.7; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-background { + position: absolute; + bottom: 5px; + right: 5px; + left: 5px; + top: 5px; + border: 1px solid black; + background-color: rgba(0, 0, 0, 0.2); +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner { + width: 10px; + height: 10px; + position: absolute; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tl{ + top: 0; + left: 0; + border-left: 1px solid black; + border-top: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tr{ + top: 0; + right: 0; + border-right: 1px solid black; + border-top: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-bl{ + bottom: 0; + left: 0; + border-left: 1px solid black; + border-bottom: 1px solid black; +} + +.ta-resizer-handle-overlay > .ta-resizer-handle-corner-br{ + bottom: 0; + right: 0; + border: 1px solid black; + cursor: se-resize; + background-color: white; +} + +/* copy the popover code from bootstrap so this will work even without it */ +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} diff --git a/app/assets/stylesheets/shared/textAngular.min.css b/app/assets/stylesheets/shared/textAngular.min.css deleted file mode 100644 index 6f132953bb..0000000000 --- a/app/assets/stylesheets/shared/textAngular.min.css +++ /dev/null @@ -1 +0,0 @@ -.ta-scroll-window.form-control{height:auto;min-height:300px;overflow:auto;font-family:inherit;font-size:100%;position:relative;padding:0}.ta-root.focussed .ta-scroll-window.form-control{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.ta-editor.ta-html{min-height:300px;height:auto;overflow:auto;font-family:inherit;font-size:100%}.ta-scroll-window>.ta-bind{height:auto;min-height:300px;padding:6px 12px}.ta-root .ta-resizer-handle-overlay{z-index:100;position:absolute;display:none}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-info{position:absolute;bottom:16px;right:16px;border:1px solid #000;background-color:#FFF;padding:0 4px;opacity:.7}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-background{position:absolute;bottom:5px;right:5px;left:5px;top:5px;border:1px solid #000;background-color:rgba(0,0,0,.2)}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner{width:10px;height:10px;position:absolute}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tl{top:0;left:0;border-left:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tr{top:0;right:0;border-right:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-bl{bottom:0;left:0;border-left:1px solid #000;border-bottom:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-br{bottom:0;right:0;border:1px solid #000;cursor:se-resize;background-color:#fff} \ No newline at end of file diff --git a/vendor/assets/javascripts/textAngular-rangy.min.js b/vendor/assets/javascripts/textAngular-rangy.min.js new file mode 100644 index 0000000000..d2de46f10e --- /dev/null +++ b/vendor/assets/javascripts/textAngular-rangy.min.js @@ -0,0 +1,2 @@ +!function(a,b){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"object"==typeof exports?module.exports=a():b.rangy=a()}(function(){function a(a,b){var c=typeof a[b];return c==u||!(c!=t||!a[b])||"unknown"==c}function b(a,b){return!(typeof a[b]!=t||!a[b])}function c(a,b){return typeof a[b]!=v}function d(a){return function(b,c){for(var d=c.length;d--;)if(!a(b,c[d]))return!1;return!0}}function e(a){return a&&A(a,z)&&C(a,y)}function f(a){return b(a,"body")?a.body:a.getElementsByTagName("body")[0]}function g(b){typeof console!=v&&a(console,"log")&&console.log(b)}function h(a,b){F&&b?alert(a):g(a)}function i(a){H.initialized=!0,H.supported=!1,h("Rangy is not supported in this environment. Reason: "+a,H.config.alertOnFail)}function j(a){h("Rangy warning: "+a,H.config.alertOnWarn)}function k(a){return a.message||a.description||String(a)}function l(){if(F&&!H.initialized){var b,c=!1,d=!1;a(document,"createRange")&&(b=document.createRange(),A(b,x)&&C(b,w)&&(c=!0));var h=f(document);if(!h||"body"!=h.nodeName.toLowerCase())return void i("No body element found");if(h&&a(h,"createTextRange")&&(b=h.createTextRange(),e(b)&&(d=!0)),!c&&!d)return void i("Neither Range nor TextRange are available");H.initialized=!0,H.features={implementsDomRange:c,implementsTextRange:d};var j,l;for(var m in E)(j=E[m])instanceof p&&j.init(j,H);for(var n=0,o=K.length;o>n;++n)try{K[n](H)}catch(q){l="Rangy init listener threw an exception. Continuing. Detail: "+k(q),g(l)}}}function m(a,b,c){c&&(a+=" in module "+c.name),H.warn("DEPRECATED: "+a+" is deprecated. Please use "+b+" instead.")}function n(a,b,c,d){a[b]=function(){return m(b,c,d),a[c].apply(a,G.toArray(arguments))}}function o(a){a=a||window,l();for(var b=0,c=L.length;c>b;++b)L[b](a)}function p(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function q(a,b,c){var d=new p(a,b,function(b){if(!b.initialized){b.initialized=!0;try{c(H,b),b.supported=!0}catch(d){var e="Module '"+a+"' failed to load: "+k(d);g(e),d.stack&&g(d.stack)}}});return E[a]=d,d}function r(){}function s(){}var t="object",u="function",v="undefined",w=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],x=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],y=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],z=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],A=d(a),B=d(b),C=d(c),D=[].forEach?function(a,b){a.forEach(b)}:function(a,b){for(var c=0,d=a.length;d>c;++c)b(a[c],c)},E={},F=typeof window!=v&&typeof document!=v,G={isHostMethod:a,isHostObject:b,isHostProperty:c,areHostMethods:A,areHostObjects:B,areHostProperties:C,isTextRange:e,getBody:f,forEach:D},H={version:"1.3.0",initialized:!1,isBrowser:F,supported:!0,util:G,features:{},modules:E,config:{alertOnFail:!1,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==v?!0:rangyAutoInitialize}};H.fail=i,H.warn=j;var I;({}).hasOwnProperty?(G.extend=I=function(a,b,c){var d,e;for(var f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&null!==d&&"object"==typeof d&&null!==e&&"object"==typeof e&&I(d,e,!0),a[f]=e);return b.hasOwnProperty("toString")&&(a.toString=b.toString),a},G.createOptions=function(a,b){var c={};return I(c,b),a&&I(c,a),c}):i("hasOwnProperty not supported"),F||i("Rangy can only run in a browser"),function(){var a;if(F){var b=document.createElement("div");b.appendChild(document.createElement("span"));var c=[].slice;try{1==c.call(b.childNodes,0)[0].nodeType&&(a=function(a){return c.call(a,0)})}catch(d){}}a||(a=function(a){for(var b=[],c=0,d=a.length;d>c;++c)b[c]=a[c];return b}),G.toArray=a}();var J;F&&(a(document,"addEventListener")?J=function(a,b,c){a.addEventListener(b,c,!1)}:a(document,"attachEvent")?J=function(a,b,c){a.attachEvent("on"+b,c)}:i("Document does not have required addEventListener or attachEvent method"),G.addListener=J);var K=[];G.deprecationNotice=m,G.createAliasForDeprecatedMethod=n,H.init=l,H.addInitListener=function(a){H.initialized?a(H):K.push(a)};var L=[];H.addShimListener=function(a){L.push(a)},F&&(H.shim=H.createMissingNativeApi=o,n(H,"createMissingNativeApi","shim")),p.prototype={init:function(){for(var a,b,c=this.dependencies||[],d=0,e=c.length;e>d;++d){if(b=c[d],a=E[b],!(a&&a instanceof p))throw new Error("required module '"+b+"' not found");if(a.init(),!a.supported)throw new Error("required module '"+b+"' not supported")}this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,new Error(a)},warn:function(a){H.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){H.warn("DEPRECATED: "+a+" in module "+this.name+" is deprecated. Please use "+b+" instead")},createError:function(a){return new Error("Error in Rangy "+this.name+" module: "+a)}},H.createModule=function(a){var b,c;2==arguments.length?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]);var d=q(a,c,b);H.initialized&&H.supported&&d.init()},H.createCoreModule=function(a,b,c){q(a,b,c)},H.RangePrototype=r,H.rangePrototype=new r,H.selectionPrototype=new s,H.createCoreModule("DomUtil",[],function(a,b){function c(a){var b;return typeof a.namespaceURI==F||null===(b=a.namespaceURI)||"http://www.w3.org/1999/xhtml"==b}function d(a){var b=a.parentNode;return 1==b.nodeType?b:null}function e(a){for(var b=0;a=a.previousSibling;)++b;return b}function f(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function g(a,b){var c,d=[];for(c=a;c;c=c.parentNode)d.push(c);for(c=b;c;c=c.parentNode)if(K(d,c))return c;return null}function h(a,b,c){for(var d=c?b:b.parentNode;d;){if(d===a)return!0;d=d.parentNode}return!1}function i(a,b){return h(a,b,!0)}function j(a,b,c){for(var d,e=c?a:a.parentNode;e;){if(d=e.parentNode,d===b)return e;e=d}return null}function k(a){var b=a.nodeType;return 3==b||4==b||8==b}function l(a){if(!a)return!1;var b=a.nodeType;return 3==b||8==b}function m(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}function n(a,b,c){var d=a.cloneNode(!1);if(d.deleteData(0,b),a.deleteData(b,a.length-b),m(d,a),c)for(var f,g=0;f=c[g++];)f.node==a&&f.offset>b?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>e(a)&&++f.offset;return d}function o(a){if(9==a.nodeType)return a;if(typeof a.ownerDocument!=F)return a.ownerDocument;if(typeof a.document!=F)return a.document;if(a.parentNode)return o(a.parentNode);throw b.createError("getDocument: no document found for node")}function p(a){var c=o(a);if(typeof c.defaultView!=F)return c.defaultView;if(typeof c.parentWindow!=F)return c.parentWindow;throw b.createError("Cannot get a window object for node")}function q(a){if(typeof a.contentDocument!=F)return a.contentDocument;if(typeof a.contentWindow!=F)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function r(a){if(typeof a.contentWindow!=F)return a.contentWindow;if(typeof a.contentDocument!=F)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}function s(a){return a&&G.isHostMethod(a,"setTimeout")&&G.isHostObject(a,"document")}function t(a,b,c){var d;if(a?G.isHostProperty(a,"nodeType")?d=1==a.nodeType&&"iframe"==a.tagName.toLowerCase()?q(a):o(a):s(a)&&(d=a.document):d=document,!d)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return d}function u(a){for(var b;b=a.parentNode;)a=b;return a}function v(a,c,d,f){var h,i,k,l,m;if(a==d)return c===f?0:f>c?-1:1;if(h=j(d,a,!0))return c<=e(h)?-1:1;if(h=j(a,d,!0))return e(h)[index:"+e(a)+",length:"+a.childNodes.length+"]["+(a.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return a.nodeName}function y(a){for(var b,c=o(a).createDocumentFragment();b=a.firstChild;)c.appendChild(b);return c}function z(a,b,c){var d=H(a),e=a.createElement("div");e.contentEditable=""+!!c,b&&(e.innerHTML=b);var f=d.firstChild;return f?d.insertBefore(e,f):d.appendChild(e),e}function A(a){return a.parentNode.removeChild(a)}function B(a){this.root=a,this._next=a}function C(a){return new B(a)}function D(a,b){this.node=a,this.offset=b}function E(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var F="undefined",G=a.util,H=G.getBody;G.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),G.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var I=document.createElement("div");G.areHostMethods(I,["insertBefore","appendChild","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),G.isHostProperty(I,"innerHTML")||b.fail("Element is missing innerHTML property");var J=document.createTextNode("test");G.areHostMethods(J,["splitText","deleteData","insertData","appendData","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"])||!G.areHostProperties(J,["data"]))||b.fail("Incomplete Text Node implementation");var K=function(a,b){for(var c=a.length;c--;)if(a[c]===b)return!0;return!1},L=!1;!function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="
    ",L=w(c),a.features.crashyTextNodes=L}();var M;typeof window.getComputedStyle!=F?M=function(a,b){return p(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=F?M=function(a,b){return a.currentStyle?a.currentStyle[b]:""}:b.fail("No means of obtaining computed style properties found"),B.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a,b,c=this._current=this._next;if(this._current)if(a=c.firstChild)this._next=a;else{for(b=null;c!==this.root&&!(b=c.nextSibling);)c=c.parentNode;this._next=b}return this._current},detach:function(){this._current=this._next=this.root=null}},D.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+x(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},E.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11,INVALID_NODE_TYPE_ERR:24},E.prototype.toString=function(){return this.message},a.dom={arrayContains:K,isHtmlNamespace:c,parentElement:d,getNodeIndex:e,getNodeLength:f,getCommonAncestor:g,isAncestorOf:h,isOrIsAncestorOf:i,getClosestAncestorIn:j,isCharacterDataNode:k,isTextOrCommentNode:l,insertAfter:m,splitDataNode:n,getDocument:o,getWindow:p,getIframeWindow:r,getIframeDocument:q,getBody:H,isWindow:s,getContentDocument:t,getRootContainer:u,comparePoints:v,isBrokenNode:w,inspectNode:x,getComputedStyleProperty:M,createTestElement:z,removeNode:A,fragmentFromNodeChildren:y,createIterator:C,DomPosition:D},a.DOMException=E}),H.createCoreModule("DomRange",["DomUtil"],function(a,b){function c(a,b){return 3!=a.nodeType&&(P(a,b.startContainer)||P(a,b.endContainer))}function d(a){return a.document||Q(a.startContainer)}function e(a){return W(a.startContainer)}function f(a){return new L(a.parentNode,O(a))}function g(a){return new L(a.parentNode,O(a)+1)}function h(a,b,c){var d=11==a.nodeType?a.firstChild:a;return N(b)?c==b.length?J.insertAfter(a,b):b.parentNode.insertBefore(a,0==c?b:S(b,c)):c>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[c]),d}function i(a,b,c){if(z(a),z(b),d(b)!=d(a))throw new M("WRONG_DOCUMENT_ERR");var e=R(a.startContainer,a.startOffset,b.endContainer,b.endOffset),f=R(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?0>=e&&f>=0:0>e&&f>0}function j(a){for(var b,c,e,f=d(a.range).createDocumentFragment();c=a.next();){if(b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(j(e)),e.detach()),10==c.nodeType)throw new M("HIERARCHY_REQUEST_ERR");f.appendChild(c)}return f}function k(a,b,c){var d,e;c=c||{stop:!1};for(var f,g;f=a.next();)if(a.isPartiallySelectedSubtree()){if(b(f)===!1)return void(c.stop=!0);if(g=a.getSubtreeIterator(),k(g,b,c),g.detach(),c.stop)return}else for(d=J.createIterator(f);e=d.next();)if(b(e)===!1)return void(c.stop=!0)}function l(a){for(var b;a.next();)a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),l(b),b.detach()):a.remove()}function m(a){for(var b,c,e=d(a.range).createDocumentFragment();b=a.next();){if(a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),c=a.getSubtreeIterator(),b.appendChild(m(c)),c.detach()):a.remove(),10==b.nodeType)throw new M("HIERARCHY_REQUEST_ERR");e.appendChild(b)}return e}function n(a,b,c){var d,e=!(!b||!b.length),f=!!c;e&&(d=new RegExp("^("+b.join("|")+")$"));var g=[];return k(new p(a,!1),function(b){if((!e||d.test(b.nodeType))&&(!f||c(b))){var h=a.startContainer;if(b!=h||!N(h)||a.startOffset!=h.length){var i=a.endContainer;b==i&&N(i)&&0==a.endOffset||g.push(b)}}}),g}function o(a){var b="undefined"==typeof a.getName?"Range":a.getName();return"["+b+"("+J.inspectNode(a.startContainer)+":"+a.startOffset+", "+J.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function p(a,b){if(this.range=a,this.clonePartiallySelectedTextNodes=b,!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&N(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==c||N(this.sc)?T(this.sc,c,!0):this.sc.childNodes[this.so],this._last=this.ec!==c||N(this.ec)?T(this.ec,c,!0):this.ec.childNodes[this.eo-1])}}function q(a){return function(b,c){for(var d,e=c?b:b.parentNode;e;){if(d=e.nodeType,V(a,d))return e;e=e.parentNode}return null}}function r(a,b){if(ea(a,b))throw new M("INVALID_NODE_TYPE_ERR")}function s(a,b){if(!V(b,a.nodeType))throw new M("INVALID_NODE_TYPE_ERR")}function t(a,b){if(0>b||b>(N(a)?a.length:a.childNodes.length))throw new M("INDEX_SIZE_ERR")}function u(a,b){if(ca(a,!0)!==ca(b,!0))throw new M("WRONG_DOCUMENT_ERR")}function v(a){if(da(a,!0))throw new M("NO_MODIFICATION_ALLOWED_ERR")}function w(a,b){if(!a)throw new M(b)}function x(a,b){return b<=(N(a)?a.length:a.childNodes.length)}function y(a){return!!a.startContainer&&!!a.endContainer&&!(X&&(J.isBrokenNode(a.startContainer)||J.isBrokenNode(a.endContainer)))&&W(a.startContainer)==W(a.endContainer)&&x(a.startContainer,a.startOffset)&&x(a.endContainer,a.endOffset)}function z(a){if(!y(a))throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: ("+a.inspect()+")")}function A(a,b){z(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,g=c===e;N(e)&&f>0&&f0&&d=O(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function B(a){z(a);var b=a.commonAncestorContainer.parentNode.cloneNode(!1);return b.appendChild(a.cloneContents()),b.innerHTML}function C(a){a.START_TO_START=ka,a.START_TO_END=la,a.END_TO_END=ma,a.END_TO_START=na,a.NODE_BEFORE=oa,a.NODE_AFTER=pa,a.NODE_BEFORE_AND_AFTER=qa,a.NODE_INSIDE=ra}function D(a){C(a),C(a.prototype)}function E(a,b){return function(){z(this);var c,d,e=this.startContainer,f=this.startOffset,h=this.commonAncestorContainer,i=new p(this,!0);e!==h&&(c=T(e,h,!0),d=g(c),e=d.node,f=d.offset),k(i,v),i.reset();var j=a(i);return i.detach(),b(this,e,f,e,f),j}}function F(b,d){function e(a,b){return function(c){s(c,Z),s(W(c),$);var d=(a?f:g)(c);(b?h:i)(this,d.node,d.offset)}}function h(a,b,c){var e=a.endContainer,f=a.endOffset;(b!==a.startContainer||c!==a.startOffset)&&((W(b)!=W(e)||1==R(b,c,e,f))&&(e=b,f=c),d(a,b,c,e,f))}function i(a,b,c){var e=a.startContainer,f=a.startOffset;(b!==a.endContainer||c!==a.endOffset)&&((W(b)!=W(e)||-1==R(b,c,e,f))&&(e=b,f=c),d(a,e,f,b,c))}var j=function(){};j.prototype=a.rangePrototype,b.prototype=new j,K.extend(b.prototype,{setStart:function(a,b){r(a,!0),t(a,b),h(this,a,b)},setEnd:function(a,b){r(a,!0),t(a,b),i(this,a,b)},setStartAndEnd:function(){var a=arguments,b=a[0],c=a[1],e=b,f=c;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}d(this,b,c,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:e(!0,!0),setStartAfter:e(!1,!0),setEndBefore:e(!0,!1),setEndAfter:e(!1,!1),collapse:function(a){z(this),a?d(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):d(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){r(a,!0),d(this,a,0,a,U(a))},selectNode:function(a){r(a,!1),s(a,Z);var b=f(a),c=g(a);d(this,b.node,b.offset,c.node,c.offset)},extractContents:E(m,d),deleteContents:E(l,d),canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},splitBoundaries:function(){A(this)},splitBoundariesPreservingPositions:function(a){A(this,a)},normalizeBoundaries:function(){z(this);var a,b=this.startContainer,c=this.startOffset,e=this.endContainer,f=this.endOffset,g=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(e=a,f=a.length,a.appendData(b.data),Y(b))},h=function(a){var d=a.previousSibling;if(d&&d.nodeType==a.nodeType){b=a;var g=a.length;if(c=d.length,a.insertData(0,d.data),Y(d),b==e)f+=c,e=b;else if(e==a.parentNode){var h=O(a);f==h?(e=a,f=g):f>h&&f--}}},i=!0;if(N(e))f==e.length?g(e):0==f&&(a=e.previousSibling,a&&a.nodeType==e.nodeType&&(f=a.length,b==e&&(i=!1),a.appendData(e.data),Y(e),e=a));else{if(f>0){var j=e.childNodes[f-1];j&&N(j)&&g(j)}i=!this.collapsed}if(i){if(N(b))0==c?h(b):c==b.length&&(a=b.nextSibling,a&&a.nodeType==b.nodeType&&(e==a&&(e=b,f+=b.length),b.appendData(a.data),Y(a)));else if(cx",ga=3==fa.firstChild.nodeType}catch(ha){}a.features.htmlParsingConforms=ga;var ia=ga?function(a){var b=this.startContainer,c=Q(b);if(!b)throw new M("INVALID_STATE_ERR");var d=null;return 1==b.nodeType?d=b:N(b)&&(d=J.parentElement(b)),d=null===d||"HTML"==d.nodeName&&J.isHtmlNamespace(Q(d).documentElement)&&J.isHtmlNamespace(d)?c.createElement("body"):d.cloneNode(!1),d.innerHTML=a,J.fragmentFromNodeChildren(d)}:function(a){var b=d(this),c=b.createElement("body");return c.innerHTML=a,J.fragmentFromNodeChildren(c)},ja=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],ka=0,la=1,ma=2,na=3,oa=0,pa=1,qa=2,ra=3;K.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){z(this),u(this.startContainer,b.startContainer);var c,d,e,f,g=a==na||a==ka?"start":"end",h=a==la||a==ka?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],R(c,d,e,f)},insertNode:function(a){if(z(this),s(a,aa),v(this.startContainer),P(a,this.startContainer))throw new M("HIERARCHY_REQUEST_ERR");var b=h(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){z(this);var a,b;if(this.collapsed)return d(this).createDocumentFragment();if(this.startContainer===this.endContainer&&N(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=d(this).createDocumentFragment(),b.appendChild(a),b;var c=new p(this,!0);return a=j(c),c.detach(),a},canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},surroundContents:function(a){if(s(a,ba),!this.canSurroundContents())throw new M("INVALID_STATE_ERR");var b=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);h(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){z(this);for(var a,b=new I(d(this)),c=ja.length;c--;)a=ja[c],b[a]=this[a];return b},toString:function(){z(this);var a=this.startContainer;if(a===this.endContainer&&N(a))return 3==a.nodeType||4==a.nodeType?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new p(this,!0);return k(c,function(a){(3==a.nodeType||4==a.nodeType)&&b.push(a.data)}),c.detach(),b.join("")},compareNode:function(a){z(this);var b=a.parentNode,c=O(a);if(!b)throw new M("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return 0>d?e>0?qa:oa:e>0?pa:ra},comparePoint:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)<0?-1:R(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ia,toHtml:function(){return B(this)},intersectsNode:function(a,b){if(z(this),W(a)!=e(this))return!1;var c=a.parentNode,d=O(a);if(!c)return!0;var f=R(c,d,this.endContainer,this.endOffset),g=R(c,d+1,this.startContainer,this.startOffset);return b?0>=f&&g>=0:0>f&&g>0},isPointInRange:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)>=0&&R(a,b,this.endContainer,this.endOffset)<=0},intersectsRange:function(a){return i(this,a,!1)},intersectsOrTouchesRange:function(a){return i(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=R(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=R(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return-1==b&&d.setStart(a.startContainer,a.startOffset),1==c&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return-1==R(a.startContainer,a.startOffset,this.startContainer,this.startOffset)&&b.setStart(a.startContainer,a.startOffset),1==R(a.endContainer,a.endOffset,this.endContainer,this.endOffset)&&b.setEnd(a.endContainer,a.endOffset),b}throw new M("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==ra},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,U(a))<=0},containsRange:function(a){var b=this.intersection(a);return null!==b&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();return b.setEnd(d,d.length),this.containsRange(b)}return this.containsNodeContents(a)},getNodes:function(a,b){return z(this),n(this,a,b)},getDocument:function(){return d(this)},collapseBefore:function(a){this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var c=d(this),e=a.createRange(c);b=b||J.getBody(c),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);for(var d,e,f,g,h=[b],i=!1,j=!1;!j&&(d=h.pop());)if(3==d.nodeType)e=c+d.length,!i&&a.start>=c&&a.start<=e&&(this.setStart(d,a.start-c),i=!0),i&&a.end>=c&&a.end<=e&&(this.setEnd(d,a.end-c),j=!0),c=e;else for(g=d.childNodes,f=g.length;f--;)h.push(g[f])},getName:function(){return"DomRange"},equals:function(a){return I.rangesEqual(this,a)},isValid:function(){return y(this)},inspect:function(){return o(this)},detach:function(){}}),F(I,H),K.extend(I,{rangeProperties:ja,RangeIterator:p,copyComparisonConstants:D,createPrototypeRange:F,inspect:o,toHtml:B,getRangeDocument:d,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=I}),H.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;if(a.features.implementsDomRange&&!function(){function d(a){for(var b,c=m.length;c--;)b=m[c],a[b]=a.nativeRange[b];a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function g(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);(f||g||h)&&(a.setEnd(d,e),a.setStart(b,c))}var k,l,m=h.rangeProperties;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,d(this)},h.createPrototypeRange(c,g),k=c.prototype,k.selectNode=function(a){this.nativeRange.selectNode(a),d(this)},k.cloneContents=function(){return this.nativeRange.cloneContents()},k.surroundContents=function(a){this.nativeRange.surroundContents(a),d(this)},k.collapse=function(a){this.nativeRange.collapse(a),d(this)},k.cloneRange=function(){return new c(this.nativeRange.cloneRange())},k.refresh=function(){d(this)},k.toString=function(){return this.nativeRange.toString()};var n=document.createTextNode("test");i(document).appendChild(n);var o=document.createRange();o.setStart(n,0),o.setEnd(n,0);try{o.setStart(n,1),k.setStart=function(a,b){this.nativeRange.setStart(a,b),d(this)},k.setEnd=function(a,b){this.nativeRange.setEnd(a,b),d(this)},l=function(a){return function(b){this.nativeRange[a](b),d(this)}}}catch(p){k.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}d(this)},k.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}d(this)},l=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(e){this.nativeRange[b](c),this.nativeRange[a](c)}d(this)}}}k.setStartBefore=l("setStartBefore","setEndBefore"),k.setStartAfter=l("setStartAfter","setEndAfter"),k.setEndBefore=l("setEndBefore","setStartBefore"),k.setEndAfter=l("setEndAfter","setStartAfter"),k.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},o.selectNodeContents(n),o.setEnd(n,3);var q=document.createRange();q.selectNodeContents(n),q.setEnd(n,4),q.setStart(n,2),-1==o.compareBoundaryPoints(o.START_TO_END,q)&&1==o.compareBoundaryPoints(o.END_TO_START,q)?k.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:k.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};var r=document.createElement("div");r.innerHTML="123";var s=r.firstChild,t=i(document);t.appendChild(r),o.setStart(s,1),o.setEnd(s,2),o.deleteContents(),"13"==s.data&&(k.deleteContents=function(){this.nativeRange.deleteContents(),d(this)},k.extractContents=function(){var a=this.nativeRange.extractContents();return d(this),a}),t.removeChild(r),t=null,f.isHostMethod(o,"createContextualFragment")&&(k.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),i(document).removeChild(n),k.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}(),a.features.implementsTextRange){var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return 0==a.compareEndPoints("StartToEnd",a)},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();if(e.isOrIsAncestorOf(b,i)||(i=b),!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");l.parentNode&&e.removeNode(l);for(var m,n,o,p,q,r=c?"StartToStart":"StartToEnd",s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;;){if(v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(r,a),0==m||s==u)break;if(-1==m){if(u==s+1)break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}if(q=l.nextSibling,-1==m&&q&&k(q)){h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(q.data)){var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;for(w=x.moveStart("character",y);-1==(m=x.compareEndPoints("StartToEnd",x));)w++,x.moveStart("character",1)}else w=h.text.length;p=new g(q,w)}else n=(d||!c)&&l.previousSibling,o=(d||c)&&l.nextSibling,p=o&&k(o)?new g(o,0):n&&k(n)?new g(n,n.data.length):new g(i,e.getNodeIndex(l));return e.removeNode(l),{boundaryPosition:p,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f,g,h=a.offset,j=e.getDocument(a.node),l=i(j).createTextRange(),m=k(a.node);return m?(c=a.node,d=c.parentNode):(g=a.node.childNodes,c=hb;++b)if(!C.isAncestorOf(a[0],a[b]))return!1;return!0}function m(a){var c=a.getNodes();if(!l(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}function n(a){return!!a&&"undefined"!=typeof a.text}function o(a,b){var c=new G(b);a._ranges=[c],h(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function p(b){if(b._ranges.length=0,"None"==b.docSelection.type)j(b);else{var c=b.docSelection.createRange();if(n(c))o(b,c);else{b.rangeCount=c.length;for(var d,e=L(c.item(0)),f=0;fh;++h)g.add(d.item(h));try{g.add(e)}catch(j){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}g.select(),p(a)}function r(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function s(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function t(a,b){for(var c,d,e=ba.length;e--;)if(c=ba[e],d=c.selection,"deleteAll"==b)s(d);else if(c.win==a)return"delete"==b?(ba.splice(e,1),!0):d;return"deleteAll"==b&&(ba.length=0),null}function u(a,c){for(var d,e=L(c[0].startContainer),f=M(e).createControlRange(),g=0,h=c.length;h>g;++g){d=m(c[g]);try{f.add(d)}catch(i){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}f.select(),p(a)}function v(a,b){if(a.win.document!=L(b))throw new H("WRONG_DOCUMENT_ERR")}function w(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function x(a){var b=[],c=new I(a.anchorNode,a.anchorOffset),d=new I(a.focusNode,a.focusOffset),e="function"==typeof a.getName?a.getName():"Selection";if("undefined"!=typeof a.rangeCount)for(var f=0,g=a.rangeCount;g>f;++f)b[f]=F.inspect(a.getRangeAt(f));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}a.config.checkSelectionRanges=!0;var y,z,A="boolean",B="number",C=a.dom,D=a.util,E=D.isHostMethod,F=a.DomRange,G=a.WrappedRange,H=a.DOMException,I=C.DomPosition,J=a.features,K="Control",L=C.getDocument,M=C.getBody,N=F.rangesEqual,O=E(window,"getSelection"),P=D.isHostObject(document,"selection");J.implementsWinGetSelection=O,J.implementsDocSelection=P;var Q=P&&(!O||a.config.preferTextRange);if(Q)y=f,a.isSelectionValid=function(a){var b=d(a,"isSelectionValid").document,c=b.selection;return"None"!=c.type||L(c.createRange().parentElement())==b};else{if(!O)return b.fail("Neither document.selection or window.getSelection() detected."),!1;y=e,a.isSelectionValid=function(){return!0}}a.getNativeSelection=y;var R=y();if(!R)return b.fail("Native selection was null (possibly issue 138?)"),!1;var S=a.createNativeRange(document),T=M(document),U=D.areHostProperties(R,["anchorNode","focusNode","anchorOffset","focusOffset"]);J.selectionHasAnchorAndFocus=U;var V=E(R,"extend");J.selectionHasExtend=V;var W=typeof R.rangeCount==B;J.selectionHasRangeCount=W;var X=!1,Y=!0,Z=V?function(b,c){var d=F.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.addRange(k(e)),b.extend(c.startContainer,c.startOffset)}:null;D.areHostMethods(R,["addRange","getRangeAt","removeAllRanges"])&&typeof R.rangeCount==B&&J.implementsDomRange&&!function(){var b=window.getSelection();if(b){for(var c=b.rangeCount,d=c>1,e=[],f=g(b),h=0;c>h;++h)e[h]=b.getRangeAt(h);var i=C.createTestElement(document,"",!1),j=i.appendChild(document.createTextNode(" ")),k=document.createRange();if(k.setStart(j,1),k.collapse(!0),b.removeAllRanges(),b.addRange(k),Y=1==b.rangeCount,b.removeAllRanges(),!d){var l=window.navigator.appVersion.match(/Chrome\/(.*?) /);if(l&&parseInt(l[1])>=36)X=!1;else{var m=k.cloneRange();k.setStart(j,0),m.setEnd(j,3),m.setStart(j,2),b.addRange(k),b.addRange(m),X=2==b.rangeCount}}for(C.removeNode(i),b.removeAllRanges(),h=0;c>h;++h)0==h&&f?Z?Z(b,e[h]):(a.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),b.addRange(e[h])):b.addRange(e[h])}}(),J.selectionSupportsMultipleRanges=X,J.collapsedNonEditableSelectionsSupported=Y;var $,_=!1;T&&E(T,"createControlRange")&&($=T.createControlRange(),D.areHostProperties($,["item","add"])&&(_=!0)),J.implementsControlRange=_,z=U?function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:function(a){return a.rangeCount?a.getRangeAt(a.rangeCount-1).collapsed:!1};var aa;E(R,"getRangeAt")?aa=function(a,b){try{return a.getRangeAt(b)}catch(c){return null}}:U&&(aa=function(b){var c=L(b.anchorNode),d=a.createRange(c);return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),r.prototype=a.selectionPrototype;var ba=[],ca=function(a){if(a&&a instanceof r)return a.refresh(),a;a=d(a,"getNativeSelection");var b=t(a),c=y(a),e=P?f(a):null;return b?(b.nativeSelection=c,b.docSelection=e,b.refresh()):(b=new r(c,e,a),ba.push({win:a,selection:b})),b};a.getSelection=ca,D.createAliasForDeprecatedMethod(a,"getIframeSelection","getSelection");var da=r.prototype;if(!Q&&U&&D.areHostMethods(R,["removeAllRanges","addRange"])){da.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),j(this)};var ea=function(a,b){Z(a.nativeSelection,b),a.refresh()};W?da.addRange=function(b,d){if(_&&P&&this.docSelection.type==K)q(this,b);else if(c(d)&&V)ea(this,b);else{var e;X?e=this.rangeCount:(this.removeAllRanges(),e=0);var f=k(b).cloneRange();try{this.nativeSelection.addRange(f)}catch(g){}if(this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==e+1){if(a.config.checkSelectionRanges){var i=aa(this.nativeSelection,this.rangeCount-1);i&&!N(i,b)&&(b=new G(i))}this._ranges[this.rangeCount-1]=b,h(this,b,ha(this.nativeSelection)),this.isCollapsed=z(this)}else this.refresh()}}:da.addRange=function(a,b){c(b)&&V?ea(this,a):(this.nativeSelection.addRange(k(a)),this.refresh())},da.setRanges=function(a){if(_&&P&&a.length>1)u(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;c>b;++b)this.addRange(a[b])}}}else{if(!(E(R,"empty")&&E(S,"select")&&_&&Q))return b.fail("No means of selecting a Range or TextRange was found"),!1;da.removeAllRanges=function(){try{if(this.docSelection.empty(),"None"!=this.docSelection.type){var a;if(this.anchorNode)a=L(this.anchorNode);else if(this.docSelection.type==K){var b=this.docSelection.createRange();b.length&&(a=L(b.item(0)))}if(a){var c=M(a).createTextRange();c.select(),this.docSelection.empty()}}}catch(d){}j(this)},da.addRange=function(b){this.docSelection.type==K?q(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,h(this,b,!1))},da.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?u(this,a):b&&this.addRange(a[0])}}da.getRangeAt=function(a){if(0>a||a>=this.rangeCount)throw new H("INDEX_SIZE_ERR");return this._ranges[a].cloneRange()};var fa;if(Q)fa=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=M(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==K?p(b):n(c)?o(b,c):j(b)};else if(E(R,"getRangeAt")&&typeof R.rangeCount==B)fa=function(b){if(_&&P&&b.docSelection.type==K)p(b);else if(b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount,b.rangeCount){for(var c=0,d=b.rangeCount;d>c;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));h(b,b._ranges[b.rangeCount-1],ha(b.nativeSelection)),b.isCollapsed=z(b)}else j(b)};else{if(!U||typeof R.isCollapsed!=A||typeof S.collapsed!=A||!J.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;fa=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=aa(c,0),a._ranges=[b],a.rangeCount=1,i(a),a.isCollapsed=z(a)):j(a)}}da.refresh=function(a){var b=a?this._ranges.slice(0):null,c=this.anchorNode,d=this.anchorOffset;if(fa(this),a){var e=b.length;if(e!=this._ranges.length)return!0;if(this.anchorNode!=c||this.anchorOffset!=d)return!0;for(;e--;)if(!N(b[e],this._ranges[e]))return!0;return!1}};var ga=function(a,b){var c=a.getAllRanges();a.removeAllRanges();for(var d=0,e=c.length;e>d;++d)N(b,c[d])||a.addRange(c[d]);a.rangeCount||j(a)};_&&P?da.removeRange=function(a){if(this.docSelection.type==K){for(var b,c=this.docSelection.createRange(),d=m(a),e=L(c.item(0)),f=M(e).createControlRange(),g=!1,h=0,i=c.length;i>h;++h)b=c.item(h),b!==d||g?f.add(c.item(h)):g=!0;f.select(),p(this)}else ga(this,a)}:da.removeRange=function(a){ga(this,a)};var ha;!Q&&U&&J.implementsDomRange?(ha=g,da.isBackward=function(){return ha(this)}):ha=da.isBackward=function(){return!1},da.isBackwards=da.isBackward,da.toString=function(){for(var a=[],b=0,c=this.rangeCount;c>b;++b)a[b]=""+this._ranges[b];return a.join("")},da.collapse=function(b,c){v(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},da.collapseToStart=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},da.collapseToEnd=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},da.selectAllChildren=function(b){v(this,b);var c=a.createRange(b);c.selectNodeContents(b),this.setSingleRange(c)},da.deleteFromDocument=function(){if(_&&P&&this.docSelection.type==K){for(var a,b=this.docSelection.createRange();b.length;)a=b.item(0),b.remove(a),C.removeNode(a);this.refresh()}else if(this.rangeCount){var c=this.getAllRanges();if(c.length){this.removeAllRanges();for(var d=0,e=c.length;e>d;++d)c[d].deleteContents();this.addRange(c[e-1])}}},da.eachRange=function(a,b){for(var c=0,d=this._ranges.length;d>c;++c)if(a(this.getRangeAt(c)))return b},da.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},da.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},da.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b||[]))}),c},da.setStart=w(!0),da.setEnd=w(!1),a.rangePrototype.select=function(a){ca(this.getDocument()).setSingleRange(this,a)},da.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&1==b.length?this.addRange(b[0],"backward"):this.setRanges(b)},da.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)||!1},da.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},da.moveToBookmark=function(b){for(var c,d,e=[],f=0;c=b.rangeBookmarks[f++];)d=a.createRange(this.win),d.moveToBookmark(c),e.push(d);b.backward?this.setSingleRange(e[0],"backward"):this.setRanges(e)},da.saveRanges=function(){return{backward:this.isBackward(),ranges:this.callMethodOnEachRange("cloneRange")}},da.restoreRanges=function(a){this.removeAllRanges();for(var b,c=0;b=a.ranges[c];++c)this.addRange(b,a.backward&&0==c)},da.toHtml=function(){var a=[];return this.eachRange(function(b){a.push(F.toHtml(b))}),a.join("")},J.implementsTextRange&&(da.getNativeTextRange=function(){var c;if(c=this.docSelection){var d=c.createRange();if(n(d))return d;throw b.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return a.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw b.createError("getNativeTextRange: selection contains no range")}),da.getName=function(){return"WrappedSelection"},da.inspect=function(){return x(this)},da.detach=function(){t(this.win,"delete"),s(this)},r.detachAll=function(){t(null,"deleteAll")},r.inspect=x,r.isDirectionBackward=c,a.Selection=r,a.selectionPrototype=da,a.addShimListener(function(a){"undefined"==typeof a.getSelection&&(a.getSelection=function(){return ca(a)}),a=null})});var M=!1,N=function(a){M||(M=!0,!H.initialized&&H.config.autoInitialize&&l())};return F&&("complete"==document.readyState?N():(a(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",N,!1),J(window,"load",N))),H},this),function(a,b){"function"==typeof define&&define.amd?define(["./rangy-core"],a):"undefined"!=typeof module&&"object"==typeof exports?module.exports=a(require("rangy")):a(b.rangy)}(function(a){return a.createModule("SaveRestore",["WrappedRange"],function(a,b){function c(a,b){return(b||document).getElementById(a)}function d(a,b){var c,d="selectionBoundary_"+ +new Date+"_"+(""+Math.random()).slice(2),e=o.getDocument(a.startContainer),f=a.cloneRange();return f.collapse(b),c=e.createElement("span"),c.id=d,c.style.lineHeight="0",c.style.display="none",c.className="rangySelectionBoundary",c.appendChild(e.createTextNode(r)),f.insertNode(c),c}function e(a,d,e,f){var g=c(e,a);g?(d[f?"setStartBefore":"setEndBefore"](g),p(g)):b.warn("Marker element has been removed. Cannot restore selection.")}function f(a,b){return b.compareBoundaryPoints(a.START_TO_START,a)}function g(b,c){var e,f,g=a.DomRange.getRangeDocument(b),h=b.toString(),i=q(c);return b.collapsed?(f=d(b,!1),{document:g,markerId:f.id,collapsed:!0}):(f=d(b,!1),e=d(b,!0),{document:g,startMarkerId:e.id,endMarkerId:f.id,collapsed:!1,backward:i,toString:function(){return"original text: '"+h+"', new text: '"+b.toString()+"'"}})}function h(d,f){var g=d.document;"undefined"==typeof f&&(f=!0);var h=a.createRange(g);if(d.collapsed){var i=c(d.markerId,g);if(i){i.style.display="inline";var j=i.previousSibling;j&&3==j.nodeType?(p(i),h.collapseToPoint(j,j.length)):(h.collapseBefore(i),p(i))}else b.warn("Marker element has been removed. Cannot restore selection.")}else e(g,h,d.startMarkerId,!0),e(g,h,d.endMarkerId,!1);return f&&h.normalizeBoundaries(),h}function i(b,d){var e,h,i=[],j=q(d);b=b.slice(0),b.sort(f);for(var k=0,l=b.length;l>k;++k)i[k]=g(b[k],j);for(k=l-1;k>=0;--k)e=b[k],h=a.DomRange.getRangeDocument(e),e.collapsed?e.collapseAfter(c(i[k].markerId,h)):(e.setEndBefore(c(i[k].endMarkerId,h)),e.setStartAfter(c(i[k].startMarkerId,h)));return i}function j(c){if(!a.isSelectionValid(c))return b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null;var d=a.getSelection(c),e=d.getAllRanges(),f=1==e.length&&d.isBackward(),g=i(e,f);return f?d.setSingleRange(e[0],f):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}}function k(a){for(var b=[],c=a.length,d=c-1;d>=0;d--)b[d]=h(a[d],!0);return b}function l(b,c){if(!b.restored){var d=b.rangeInfos,e=a.getSelection(b.win),f=k(d),g=d.length;1==g&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0}}function m(a,b){var d=c(b,a);d&&p(d)}function n(a){for(var b,c=a.rangeInfos,d=0,e=c.length;e>d;++d)b=c[d],b.collapsed?m(a.doc,b.markerId):(m(a.doc,b.startMarkerId),m(a.doc,b.endMarkerId))}var o=a.dom,p=o.removeNode,q=a.Selection.isDirectionBackward,r="\ufeff";a.util.extend(a,{saveRange:g,restoreRange:h,saveRanges:i,restoreRanges:k,saveSelection:j,restoreSelection:l,removeMarkerElement:m,removeMarkers:n})}),a},this); diff --git a/vendor/assets/javascripts/textAngular-sanitize.min.js b/vendor/assets/javascripts/textAngular-sanitize.min.js index 75534aaa01..b3ebd34363 100644 --- a/vendor/assets/javascripts/textAngular-sanitize.min.js +++ b/vendor/assets/javascripts/textAngular-sanitize.min.js @@ -1 +1,6 @@ -!function(a,b){b["true"]=a,function(a,b){"use strict";function c(){this.$get=["$$sanitizeUri",function(a){return function(b){var c=[];return f(b,k(c,function(b,c){return!/^unsafe/.test(a(b,c))})),c.join("")}}]}function d(a){var c=[],d=k(c,b.noop);return d.chars(a),c.join("")}function e(a){var b,c={},d=a.split(",");for(b=0;b=0&&j[f]!=d;f--);if(f>=0){for(e=j.length-1;e>=f;e--)c.end&&c.end(j[e]);j.length=f}}var f,h,i,j=[],k=a;for(j.last=function(){return j[j.length-1]};a;){if(h=!0,j.last()&&C[j.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+j.last()+"[^>]*>","i"),function(a,b){return b=b.replace(r,"$1").replace(t,"$1"),c.chars&&c.chars(g(b)),""}),e("",j.last());else if(0===a.indexOf("",f)===f&&(c.comment&&c.comment(a.substring(4,f)),a=a.substring(f+3),h=!1)):s.test(a)?(i=a.match(s),i&&(a=a.replace(i[0],""),h=!1)):q.test(a)?(i=a.match(n),i&&(a=a.substring(i[0].length),i[0].replace(n,e),h=!1)):p.test(a)&&(i=a.match(m),i&&(a=a.substring(i[0].length),i[0].replace(m,d),h=!1)),h){f=a.indexOf("<");var u=0>f?a:a.substring(0,f);a=0>f?"":a.substring(f),c.chars&&c.chars(g(u))}if(a==k)throw l("badparse","The sanitizer was unable to parse the following block of html: {0}",a);k=a}e()}function g(a){if(!a)return"";var b=H.exec(a),c=b[1],d=b[3],e=b[2];return e&&(G.innerHTML=e.replace(/=b||173==b||b>=1536&&1540>=b||1807==b||6068==b||6069==b||b>=8204&&8207>=b||b>=8232&&8239>=b||b>=8288&&8303>=b||65279==b||b>=65520&&65535>=b?"&#"+b+";":a}).replace(//g,">")}function i(a){var c="",d=a.split(";");return b.forEach(d,function(a){var d=a.split(":");if(2==d.length){var e=I(b.lowercase(d[0])),a=I(b.lowercase(d[1]));("color"===e&&(a.match(/^rgb\([0-9%,\. ]*\)$/i)||a.match(/^rgba\([0-9%,\. ]*\)$/i)||a.match(/^hsl\([0-9%,\. ]*\)$/i)||a.match(/^hsla\([0-9%,\. ]*\)$/i)||a.match(/^#[0-9a-f]{3,6}$/i)||a.match(/^[a-z]*$/i))||"text-align"===e&&("left"===a||"right"===a||"center"===a||"justify"===a)||"float"===e&&("left"===a||"right"===a||"none"===a)||("width"===e||"height"===e)&&a.match(/[0-9\.]*(px|em|rem|%)/))&&(c+=e+": "+a+";")}}),c}function j(a,b,c,d){return"img"===a&&b["ta-insert-video"]&&("ta-insert-video"===c||"allowfullscreen"===c||"frameborder"===c||"contenteditble"===c&&"false"===d)?!0:!1}function k(a,c){var d=!1,e=b.bind(a,a.push);return{start:function(a,f,g){a=b.lowercase(a),!d&&C[a]&&(d=a),d||D[a]!==!0||(e("<"),e(a),b.forEach(f,function(d,g){var k=b.lowercase(g),l="img"===a&&"src"===k||"background"===k;("style"===k&&""!==(d=i(d))||j(a,f,k,d)||F[k]===!0&&(E[k]!==!0||c(d,l)))&&(e(" "),e(g),e('="'),e(h(d)),e('"'))}),e(g?"/>":">"))},end:function(a){a=b.lowercase(a),d||D[a]!==!0||(e("")),a==d&&(d=!1)},chars:function(a){d||e(h(a))}}}var l=b.$$minErr("$sanitize"),m=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,n=/^<\s*\/\s*([\w:-]+)[^>]*>/,o=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,p=/^/g,s=/]*?)>/i,t=//g,u=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,v=/([^\#-~| |!])/g,w=e("area,br,col,hr,img,wbr"),x=e("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=e("rp,rt"),z=b.extend({},y,x),A=b.extend({},x,e("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),B=b.extend({},y,e("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),C=e("script,style"),D=b.extend({},w,A,B,z),E=e("background,cite,href,longdesc,src,usemap"),F=b.extend({},E,e("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width")),G=document.createElement("pre"),H=/^(\s*)([\s\S]*?)(\s*)$/,I=function(){return String.prototype.trim?function(a){return b.isString(a)?a.trim():a}:function(a){return b.isString(a)?a.replace(/^\s\s*/,"").replace(/\s\s*$/,""):a}}();b.module("ngSanitize",[]).provider("$sanitize",c),b.module("ngSanitize").filter("linky",["$sanitize",function(a){var c=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,e=/^mailto:/;return function(f,g){function h(a){a&&n.push(d(a))}function i(a,c){n.push("'),h(c),n.push("")}if(!f)return f;for(var j,k,l,m=f,n=[];j=m.match(c);)k=j[0],j[2]==j[3]&&(k="mailto:"+k),l=j.index,h(m.substr(0,l)),i(k,j[0].replace(e,"")),m=m.substring(l+j[0].length);return h(m),a(n.join(""))}}])}(window,window.angular)}({},function(){return this}()); \ No newline at end of file +/** + * @license AngularJS v1.3.10 + * (c) 2010-2014 Google, Inc. http://angularjs.org + * License: MIT + */ +!function(a,b,c){"use strict";function d(){this.$get=["$$sanitizeUri",function(a){return function(b){"undefined"!=typeof arguments[1]&&(arguments[1].version="taSanitize");var c=[];return g(b,l(c,function(b,c){return!/^unsafe/.test(a(b,c))})),c.join("")}}]}function e(a){var c=[],d=l(c,b.noop);return d.chars(a),c.join("")}function f(a){var b,c={},d=a.split(",");for(b=0;b=0&&k[f]!=d;f--);if(f>=0){for(e=k.length-1;e>=f;e--)c.end&&c.end(k[e]);k.length=f}}"string"!=typeof a&&(a=null===a||"undefined"==typeof a?"":""+a);var f,g,i,j,k=[],l=a;for(k.last=function(){return k[k.length-1]};a;){if(j="",g=!0,k.last()&&G[k.last()])a=a.replace(new RegExp("([^]*)<\\s*\\/\\s*"+k.last()+"[^>]*>","i"),function(a,b){return b=b.replace(s,"$1").replace(v,"$1"),c.chars&&c.chars(h(b)),""}),e("",k.last());else{if(y.test(a)){if(i=a.match(y)){i[0];c.whitespace&&c.whitespace(i[0]),a=a.replace(i[0],""),g=!1}}else t.test(a)?(i=a.match(t),i&&(c.comment&&c.comment(i[1]),a=a.replace(i[0],""),g=!1)):u.test(a)?(i=a.match(u),i&&(a=a.replace(i[0],""),g=!1)):r.test(a)?(i=a.match(o),i&&(a=a.substring(i[0].length),i[0].replace(o,e),g=!1)):q.test(a)&&(i=a.match(n),i?(i[4]&&(a=a.substring(i[0].length),i[0].replace(n,d)),g=!1):(j+="<",a=a.substring(1)));g&&(f=a.indexOf("<"),j+=0>f?a:a.substring(0,f),a=0>f?"":a.substring(f),c.chars&&c.chars(h(j)))}if(a==l)throw m("badparse","The sanitizer was unable to parse the following block of html: {0}",a);l=a}e()}function h(a){if(!a)return"";var b=N.exec(a),c=b[1],d=b[3],e=b[2];return e&&(M.innerHTML=e.replace(/=b||173==b||b>=1536&&1540>=b||1807==b||6068==b||6069==b||b>=8204&&8207>=b||b>=8232&&8239>=b||b>=8288&&8303>=b||65279==b||b>=65520&&65535>=b?"&#"+b+";":a}).replace(//g,">")}function j(a){var c="",d=a.split(";");return b.forEach(d,function(a){var d=a.split(":");if(2==d.length){var e=O(b.lowercase(d[0])),a=O(b.lowercase(d[1]));(("color"===e||"background-color"===e)&&(a.match(/^rgb\([0-9%,\. ]*\)$/i)||a.match(/^rgba\([0-9%,\. ]*\)$/i)||a.match(/^hsl\([0-9%,\. ]*\)$/i)||a.match(/^hsla\([0-9%,\. ]*\)$/i)||a.match(/^#[0-9a-f]{3,6}$/i)||a.match(/^[a-z]*$/i))||"text-align"===e&&("left"===a||"right"===a||"center"===a||"justify"===a)||"text-decoration"===e&&("underline"===a||"line-through"===a)||"font-weight"===e&&"bold"===a||"float"===e&&("left"===a||"right"===a||"none"===a)||("width"===e||"height"===e)&&a.match(/[0-9\.]*(px|em|rem|%)/)||"direction"===e&&a.match(/^ltr|rtl|initial|inherit$/))&&(c+=e+": "+a+";")}}),c}function k(a,b,c,d){return"img"===a&&b["ta-insert-video"]&&("ta-insert-video"===c||"allowfullscreen"===c||"frameborder"===c||"contenteditable"===c&&"false"===d)?!0:!1}function l(a,c){var d=!1,e=b.bind(a,a.push);return{start:function(a,f,g){a=b.lowercase(a),!d&&G[a]&&(d=a),d||H[a]!==!0||(e("<"),e(a),b.forEach(f,function(d,g){var h=b.lowercase(g),l="img"===a&&"src"===h||"background"===h;("style"===h&&""!==(d=j(d))||k(a,f,h,d)||L[h]===!0&&(I[h]!==!0||c(d,l)))&&(e(" "),e(g),e('="'),e(i(d)),e('"'))}),e(g?"/>":">"))},comment:function(a){e(a)},whitespace:function(a){e(i(a))},end:function(a){a=b.lowercase(a),d||H[a]!==!0||(e("")),a==d&&(d=!1)},chars:function(a){d||e(i(a))}}}var m=b.$$minErr("$sanitize"),n=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,o=/^<\/\s*([\w:-]+)[^>]*>/,p=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,q=/^/g,t=/(^)/,u=/]*?)>/i,v=//g,w=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,x=/([^\#-~| |!])/g,y=/^(\s+)/,z=f("area,br,col,hr,img,wbr,input"),A=f("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),B=f("rp,rt"),C=b.extend({},B,A),D=b.extend({},A,f("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),E=b.extend({},B,f("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),F=f("animate,animateColor,animateMotion,animateTransform,circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set,stop,svg,switch,text,title,tspan,use"),G=f("script,style"),H=b.extend({},z,D,E,C,F),I=f("background,cite,href,longdesc,src,usemap,xlink:href"),J=f("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,id,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,target,title,type,valign,value,vspace,width"),K=f("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan"),L=b.extend({},I,K,J),M=document.createElement("pre"),N=/^(\s*)([\s\S]*?)(\s*)$/,O=function(){return String.prototype.trim?function(a){return b.isString(a)?a.trim():a}:function(a){return b.isString(a)?a.replace(/^\s\s*/,"").replace(/\s\s*$/,""):a}}();b.module("ngSanitize",[]).provider("$sanitize",d),b.module("ngSanitize").filter("linky",["$sanitize",function(a){var c=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,d=/^mailto:/;return function(f,g){function h(a){a&&n.push(e(a))}function i(a,c){n.push("'),h(c),n.push("")}if(!f)return f;for(var j,k,l,m=f,n=[];j=m.match(c);)k=j[0],j[2]||j[4]||(k=(j[3]?"http://":"mailto:")+k),l=j.index,h(m.substr(0,l)),i(k,j[0].replace(d,"")),m=m.substring(l+j[0].length);return h(m),a(n.join(""))}}])}(window,window.angular); diff --git a/vendor/assets/javascripts/textAngular.min.js b/vendor/assets/javascripts/textAngular.min.js index aba313ad9f..f293a823f5 100644 --- a/vendor/assets/javascripts/textAngular.min.js +++ b/vendor/assets/javascripts/textAngular.min.js @@ -1,2 +1,3 @@ -!function(a,b){b["true"]=a,angular.module("textAngularSetup",[]).value("taOptions",{toolbar:[["h1","h2","h3","h4","h5","h6","p","pre","quote"],["bold","italics","underline","ul","ol","redo","undo","clear"],["justifyLeft","justifyCenter","justifyRight","indent","outdent"],["html","insertImage","insertLink","insertVideo"]],classes:{focussed:"focussed",toolbar:"btn-toolbar",toolbarGroup:"btn-group",toolbarButton:"btn btn-default",toolbarButtonActive:"active",disabled:"disabled",textEditor:"form-control",htmlEditor:"form-control"},setup:{textEditorSetup:function(){},htmlEditorSetup:function(){}},defaultFileDropHandler:function(a,b){var c=new FileReader;return"image"===a.type.substring(0,5)?(c.onload=function(){""!==c.result&&b("insertImage",c.result,!0)},c.readAsDataURL(a),!0):!1}}).value("taSelectableElements",["a","img"]).value("taCustomRenderers",[{selector:"img",customAttribute:"ta-insert-video",renderLogic:function(a){var b=angular.element(""),c=a.prop("attributes");angular.forEach(c,function(a){b.attr(a.name,a.value)}),b.attr("src",b.attr("ta-insert-video")),a.replaceWith(b)}}]).constant("taTranslations",{html:{buttontext:"Toggle HTML",tooltip:"Toggle html / Rich Text"},heading:{tooltip:"Heading "},p:{tooltip:"Paragraph"},pre:{tooltip:"Preformatted text"},ul:{tooltip:"Unordered List"},ol:{tooltip:"Ordered List"},quote:{tooltip:"Quote/unqoute selection or paragraph"},undo:{tooltip:"Undo"},redo:{tooltip:"Redo"},bold:{tooltip:"Bold"},italic:{tooltip:"Italic"},underline:{tooltip:"Underline"},justifyLeft:{tooltip:"Align text left"},justifyRight:{tooltip:"Align text right"},justifyCenter:{tooltip:"Center"},indent:{tooltip:"Increase indent"},outdent:{tooltip:"Decrease indent"},clear:{tooltip:"Clear formatting"},insertImage:{dialogPrompt:"Please enter an image URL to insert",tooltip:"Insert image",hotkey:"the - possibly language dependent hotkey ... for some future implementation"},insertVideo:{tooltip:"Insert video",dialogPrompt:"Please enter a youtube URL to embed"},insertLink:{tooltip:"Insert / edit link",dialogPrompt:"Please enter a URL to insert"}}).run(["taRegisterTool","$window","taTranslations","taSelection",function(a,b,c,d){a("html",{buttontext:c.html.buttontext,tooltiptext:c.html.tooltip,action:function(){this.$editor().switchView()},activeState:function(){return this.$editor().showHtml}});var e=function(a){return function(){return this.$editor().queryFormatBlockState(a)}},f=function(){return this.$editor().wrapSelection("formatBlock","<"+this.name.toUpperCase()+">")};angular.forEach(["h1","h2","h3","h4","h5","h6"],function(b){a(b.toLowerCase(),{buttontext:b.toUpperCase(),tooltiptext:c.heading.tooltip+b.charAt(1),action:f,activeState:e(b.toLowerCase())})}),a("p",{buttontext:"P",tooltiptext:c.p.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

    ")},activeState:function(){return this.$editor().queryFormatBlockState("p")}}),a("pre",{buttontext:"pre",tooltiptext:c.pre.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

    ")},activeState:function(){return this.$editor().queryFormatBlockState("pre")}}),a("ul",{iconclass:"fa fa-list-ul",tooltiptext:c.ul.tooltip,action:function(){return this.$editor().wrapSelection("insertUnorderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertUnorderedList")}}),a("ol",{iconclass:"fa fa-list-ol",tooltiptext:c.ol.tooltip,action:function(){return this.$editor().wrapSelection("insertOrderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertOrderedList")}}),a("quote",{iconclass:"fa fa-quote-right",tooltiptext:c.quote.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","
    ")},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")}}),a("undo",{iconclass:"fa fa-undo",tooltiptext:c.undo.tooltip,action:function(){return this.$editor().wrapSelection("undo",null)}}),a("redo",{iconclass:"fa fa-repeat",tooltiptext:c.redo.tooltip,action:function(){return this.$editor().wrapSelection("redo",null)}}),a("bold",{iconclass:"fa fa-bold",tooltiptext:c.bold.tooltip,action:function(){return this.$editor().wrapSelection("bold",null)},activeState:function(){return this.$editor().queryCommandState("bold")},commandKeyCode:98}),a("justifyLeft",{iconclass:"fa fa-align-left",tooltiptext:c.justifyLeft.tooltip,action:function(){return this.$editor().wrapSelection("justifyLeft",null)},activeState:function(a){var b=!1;return a&&(b="left"===a.css("text-align")||"left"===a.attr("align")||"right"!==a.css("text-align")&&"center"!==a.css("text-align")&&!this.$editor().queryCommandState("justifyRight")&&!this.$editor().queryCommandState("justifyCenter")),b=b||this.$editor().queryCommandState("justifyLeft")}}),a("justifyRight",{iconclass:"fa fa-align-right",tooltiptext:c.justifyRight.tooltip,action:function(){return this.$editor().wrapSelection("justifyRight",null)},activeState:function(a){var b=!1;return a&&(b="right"===a.css("text-align")),b=b||this.$editor().queryCommandState("justifyRight")}}),a("justifyCenter",{iconclass:"fa fa-align-center",tooltiptext:c.justifyCenter.tooltip,action:function(){return this.$editor().wrapSelection("justifyCenter",null)},activeState:function(a){var b=!1;return a&&(b="center"===a.css("text-align")),b=b||this.$editor().queryCommandState("justifyCenter")}}),a("indent",{iconclass:"fa fa-indent",tooltiptext:c.indent.tooltip,action:function(){return this.$editor().wrapSelection("indent",null)},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")}}),a("outdent",{iconclass:"fa fa-outdent",tooltiptext:c.outdent.tooltip,action:function(){return this.$editor().wrapSelection("outdent",null)},activeState:function(){return!1}}),a("italics",{iconclass:"fa fa-italic",tooltiptext:c.italic.tooltip,action:function(){return this.$editor().wrapSelection("italic",null)},activeState:function(){return this.$editor().queryCommandState("italic")},commandKeyCode:105}),a("underline",{iconclass:"fa fa-underline",tooltiptext:c.underline.tooltip,action:function(){return this.$editor().wrapSelection("underline",null)},activeState:function(){return this.$editor().queryCommandState("underline")},commandKeyCode:117}),a("clear",{iconclass:"fa fa-ban",tooltiptext:c.clear.tooltip,action:function(a,b){this.$editor().wrapSelection("removeFormat",null);var c=angular.element(d.getSelectionElement()),e=function(a){a=angular.element(a);var b=a;angular.forEach(a.children(),function(a){var c=angular.element("

    ");c.html(angular.element(a).html()),b.after(c),b=c}),a.remove()};angular.forEach(c.find("ul"),e),angular.forEach(c.find("ol"),e);var f=this.$editor(),g=function(a){a=angular.element(a),a[0]!==f.displayElements.text[0]&&a.removeAttr("class"),angular.forEach(a.children(),g)};angular.forEach(c,g),"li"!==c[0].tagName.toLowerCase()&&"ol"!==c[0].tagName.toLowerCase()&&"ul"!==c[0].tagName.toLowerCase()&&this.$editor().wrapSelection("formatBlock","

    "),b()}});var g=function(a,b,c){var d=function(){c.updateTaBindtaTextElement(),c.hidePopover()};a.preventDefault(),c.displayElements.popover.css("width","375px");var e=c.displayElements.popoverContainer;e.empty();var f=angular.element('

    '),g=angular.element('');g.on("click",function(a){a.preventDefault(),b.css({width:"100%",height:""}),d()});var h=angular.element('');h.on("click",function(a){a.preventDefault(),b.css({width:"50%",height:""}),d()});var i=angular.element('');i.on("click",function(a){a.preventDefault(),b.css({width:"25%",height:""}),d()});var j=angular.element('');j.on("click",function(a){a.preventDefault(),b.css({width:"",height:""}),d()}),f.append(g),f.append(h),f.append(i),f.append(j),e.append(f),f=angular.element('
    ');var k=angular.element('');k.on("click",function(a){a.preventDefault(),b.css("float","left"),d()});var l=angular.element('');l.on("click",function(a){a.preventDefault(),b.css("float","right"),d()});var m=angular.element('');m.on("click",function(a){a.preventDefault(),b.css("float",""),d()}),f.append(k),f.append(m),f.append(l),e.append(f),f=angular.element('
    ');var n=angular.element('');n.on("click",function(a){a.preventDefault(),b.remove(),d()}),f.append(n),e.append(f),c.showPopover(b),c.showResizeOverlay(b)};a("insertImage",{iconclass:"fa fa-picture-o",tooltiptext:c.insertImage.tooltip,action:function(){var a;return a=b.prompt(c.insertImage.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a?this.$editor().wrapSelection("insertImage",a,!0):void 0},onElementSelect:{element:"img",action:g}}),a("insertVideo",{iconclass:"fa fa-youtube-play",tooltiptext:c.insertVideo.tooltip,action:function(){var a;if(a=b.prompt(c.insertVideo.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a){var d=a.match(/(\?|&)v=[^&]*/);if(d.length>0){var e="http://www.youtube.com/embed/"+d[0].substring(3),f='';return this.$editor().wrapSelection("insertHTML",f,!0)}}},onElementSelect:{element:"img",onlyWithAttrs:["ta-insert-video"],action:g}}),a("insertLink",{tooltiptext:c.insertLink.tooltip,iconclass:"fa fa-link",action:function(){var a;return a=b.prompt(c.insertLink.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a?this.$editor().wrapSelection("createLink",a,!0):void 0},activeState:function(a){return a?"A"===a[0].tagName:!1},onElementSelect:{element:"a",action:function(a,d,e){a.preventDefault(),e.displayElements.popover.css("width","435px");var f=e.displayElements.popoverContainer;f.empty(),f.css("line-height","28px");var g=angular.element(''+d.attr("href")+"");g.css({display:"inline-block","max-width":"200px",overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap","vertical-align":"middle"}),f.append(g);var h=angular.element('
    '),i=angular.element('');i.on("click",function(a){a.preventDefault();var f=b.prompt(c.insertLink.dialogPrompt,d.attr("href"));f&&""!==f&&"http://"!==f&&(d.attr("href",f),e.updateTaBindtaTextElement()),e.hidePopover()}),h.append(i);var j=angular.element('');j.on("click",function(a){a.preventDefault(),d.replaceWith(d.contents()),e.updateTaBindtaTextElement(),e.hidePopover()}),h.append(j);var k=angular.element('');"_blank"===d.attr("target")&&k.addClass("active"),k.on("click",function(a){a.preventDefault(),d.attr("target","_blank"===d.attr("target")?"":"_blank"),k.toggleClass("active"),e.updateTaBindtaTextElement()}),h.append(k),f.append(h),e.showPopover(d)}}})}]),function(){"Use Strict";function a(a){try{return 0!==angular.element(a).length}catch(b){return!1}}function b(a,c){var d=[],e=a.children();return e.length&&angular.forEach(e,function(a){d=d.concat(b(angular.element(a),c))}),void 0!==a.attr(c)&&d.push(a),d}function c(b,c){if(!b||""===b||n.hasOwnProperty(b))throw"textAngular Error: A unique name is required for a Tool Definition";if(c.display&&(""===c.display||!a(c.display))||!c.display&&!c.buttontext&&!c.iconclass)throw'textAngular Error: Tool Definition for "'+b+'" does not have a valid display/iconclass/buttontext value';n[b]=c}var d=!1;/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)&&(document.addEventListener("click",function(){var a=window.event.target;if(d&&null!==a){for(var b=!1,c=a;null!==c&&"html"!==c.tagName.toLowerCase()&&!b;)b="true"===c.contentEditable,c=c.parentNode;b||(document.getElementById("textAngular-editableFix-010203040506070809").setSelectionRange(0,0),a.focus())}d=!1},!1),angular.element(document).ready(function(){angular.element(document.body).append(angular.element(''))}));var e=function(){var a,b=-1,c=window.navigator.userAgent,d=c.indexOf("MSIE "),e=c.indexOf("Trident/");if(d>0)b=parseInt(c.substring(d+5,c.indexOf(".",d)),10);else if(e>0){var f=c.indexOf("rv:");b=parseInt(c.substring(f+3,c.indexOf(".",f)),10)}return b>-1?b:a}();"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s\s*/,"").replace(/\s\s*$/,"")});var f,g,h,i,j;if(e>8||void 0===e){var k=function(){var a=document.createElement("style");return/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)&&a.appendChild(document.createTextNode("")),document.head.insertBefore(a,document.head.firstChild),a.sheet}();f=function(){var a=document.createElement("style");return/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)&&a.appendChild(document.createTextNode("")),document.head.appendChild(a),a.sheet}(),g=function(a,b){i(f,a,b)},i=function(a,b,c){var d;return a.rules?d=Math.max(a.rules.length-1,0):a.cssRules&&(d=Math.max(a.cssRules.length-1,0)),a.insertRule?a.insertRule(b+"{"+c+"}",d):a.addRule(b,c,d),d},h=function(a){j(f,a)},j=function(a,b){a.removeRule?a.removeRule(b):a.deleteRule(b)},i(k,".ta-scroll-window.form-control","height: auto; min-height: 300px; overflow: auto; font-family: inherit; font-size: 100%; position: relative; padding: 0;"),i(k,".ta-root.focussed .ta-scroll-window.form-control","border-color: #66afe9; outline: 0; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);"),i(k,".ta-editor.ta-html","min-height: 300px; height: auto; overflow: auto; font-family: inherit; font-size: 100%;"),i(k,".ta-scroll-window > .ta-bind","height: auto; min-height: 300px; padding: 6px 12px;"),i(k,".ta-root .ta-resizer-handle-overlay","z-index: 100; position: absolute; display: none;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-info","position: absolute; bottom: 16px; right: 16px; border: 1px solid black; background-color: #FFF; padding: 0 4px; opacity: 0.7;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-background","position: absolute; bottom: 5px; right: 5px; left: 5px; top: 5px; border: 1px solid black; background-color: rgba(0, 0, 0, 0.2);"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-corner","width: 10px; height: 10px; position: absolute;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-corner-tl","top: 0; left: 0; border-left: 1px solid black; border-top: 1px solid black;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-corner-tr","top: 0; right: 0; border-right: 1px solid black; border-top: 1px solid black;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-corner-bl","bottom: 0; left: 0; border-left: 1px solid black; border-bottom: 1px solid black;"),i(k,".ta-root .ta-resizer-handle-overlay > .ta-resizer-handle-corner-br","bottom: 0; right: 0; border: 1px solid black; cursor: se-resize; background-color: white;")}var l=!1,m=angular.module("textAngular",["ngSanitize","textAngularSetup"]),n={};m.constant("taRegisterTool",c),m.value("taTools",n),m.config([function(){angular.forEach(n,function(a,b){delete n[b]})}]),m.directive("textAngular",["$compile","$timeout","taOptions","taSelection","taExecCommand","textAngularManager","$window","$document","$animate","$log",function(a,b,c,d,e,f,g,h,i,j){return{require:"?ngModel",scope:{},restrict:"EA",link:function(k,l,m,n){var o,p,q,r,s,t,u,v,w,x=m.serial?m.serial:Math.floor(1e16*Math.random()),y=m.name?m.name:"textAngularEditor"+x,z=function(a,c,d){b(function(){var b=function(){a.off(c,b),d()};a.on(c,b)},100)};w=e(m.taDefaultWrap),angular.extend(k,angular.copy(c),{wrapSelection:function(a,b,c){w(a,!1,b),c&&k["reApplyOnSelectorHandlerstaTextElement"+x](),k.displayElements.text[0].focus()},showHtml:!1}),m.taFocussedClass&&(k.classes.focussed=m.taFocussedClass),m.taTextEditorClass&&(k.classes.textEditor=m.taTextEditorClass),m.taHtmlEditorClass&&(k.classes.htmlEditor=m.taHtmlEditorClass),m.taTextEditorSetup&&(k.setup.textEditorSetup=k.$parent.$eval(m.taTextEditorSetup)),m.taHtmlEditorSetup&&(k.setup.htmlEditorSetup=k.$parent.$eval(m.taHtmlEditorSetup)),k.fileDropHandler=m.taFileDrop?k.$parent.$eval(m.taFileDrop):k.defaultFileDropHandler,u=l[0].innerHTML,l[0].innerHTML="",k.displayElements={forminput:angular.element(""),html:angular.element(""),text:angular.element("
    "),scrollWindow:angular.element("
    "),popover:angular.element('
    '),popoverArrow:angular.element('
    '),popoverContainer:angular.element('
    '),resize:{overlay:angular.element('
    '),background:angular.element('
    '),anchors:[angular.element('
    '),angular.element('
    '),angular.element('
    '),angular.element('
    ')],info:angular.element('
    ')}},k.displayElements.popover.append(k.displayElements.popoverArrow),k.displayElements.popover.append(k.displayElements.popoverContainer),k.displayElements.scrollWindow.append(k.displayElements.popover),k.displayElements.popover.on("mousedown",function(a,b){return b&&angular.extend(a,b),a.preventDefault(),!1}),k.showPopover=function(a){k.displayElements.popover.css("display","block"),k.reflowPopover(a),i.addClass(k.displayElements.popover,"in"),z(l,"click keyup",function(){k.hidePopover()})},k.reflowPopover=function(a){k.displayElements.text[0].offsetHeight-51>a[0].offsetTop?(k.displayElements.popover.css("top",a[0].offsetTop+a[0].offsetHeight+"px"),k.displayElements.popover.removeClass("top").addClass("bottom")):(k.displayElements.popover.css("top",a[0].offsetTop-54+"px"),k.displayElements.popover.removeClass("bottom").addClass("top"));var b=k.displayElements.text[0].offsetWidth-k.displayElements.popover[0].offsetWidth,c=a[0].offsetLeft+a[0].offsetWidth/2-k.displayElements.popover[0].offsetWidth/2;k.displayElements.popover.css("left",Math.max(0,Math.min(b,c))+"px"),k.displayElements.popoverArrow.css("margin-left",Math.min(c,Math.max(0,c-b))-11+"px")},k.hidePopover=function(){i.removeClass(k.displayElements.popover,"in",function(){k.displayElements.popover.css("display",""),k.displayElements.popoverContainer.attr("style",""),k.displayElements.popoverContainer.attr("class","popover-content")})},k.displayElements.resize.overlay.append(k.displayElements.resize.background),angular.forEach(k.displayElements.resize.anchors,function(a){k.displayElements.resize.overlay.append(a)}),k.displayElements.resize.overlay.append(k.displayElements.resize.info),k.displayElements.scrollWindow.append(k.displayElements.resize.overlay),k.reflowResizeOverlay=function(a){a=angular.element(a)[0],k.displayElements.resize.overlay.css({display:"block",left:a.offsetLeft-5+"px",top:a.offsetTop-5+"px",width:a.offsetWidth+10+"px",height:a.offsetHeight+10+"px"}),k.displayElements.resize.info.text(a.offsetWidth+" x "+a.offsetHeight)},k.showResizeOverlay=function(a){var b=function(b){var c={width:parseInt(a.attr("width")),height:parseInt(a.attr("height")),x:b.clientX,y:b.clientY};void 0===c.width&&(c.width=a[0].offsetWidth),void 0===c.height&&(c.height=a[0].offsetHeight),k.hidePopover();var d=c.height/c.width,e=function(b){var e={x:Math.max(0,c.width+(b.clientX-c.x)),y:Math.max(0,c.height+(b.clientY-c.y))},f=function(a,b){a=angular.element(a),"img"===a[0].tagName.toLowerCase()&&(b.height&&(a.attr("height",b.height),delete b.height),b.width&&(a.attr("width",b.width),delete b.width)),a.css(b)};if(b.shiftKey){var g=e.y/e.x;f(a,{width:d>g?e.x:e.y/d,height:d>g?e.x*d:e.y})}else f(a,{width:e.x,height:e.y});k.reflowResizeOverlay(a)};h.find("body").on("mousemove",e),z(k.displayElements.resize.overlay,"mouseup",function(){h.find("body").off("mousemove",e),k.showPopover(a)}),b.stopPropagation(),b.preventDefault()};k.displayElements.resize.anchors[3].on("mousedown",b),k.reflowResizeOverlay(a),z(l,"click",function(){k.hideResizeOverlay()})},k.hideResizeOverlay=function(){k.displayElements.resize.overlay.css("display","")},k.setup.htmlEditorSetup(k.displayElements.html),k.setup.textEditorSetup(k.displayElements.text),k.displayElements.html.attr({id:"taHtmlElement"+x,"ng-show":"showHtml","ta-bind":"ta-bind","ng-model":"html"}),k.displayElements.text.attr({id:"taTextElement"+x,contentEditable:"true","ta-bind":"ta-bind","ng-model":"html"}),k.displayElements.scrollWindow.attr({"ng-hide":"showHtml"}),m.taDefaultWrap&&k.displayElements.text.attr("ta-default-wrap",m.taDefaultWrap),m.taUnsafeSanitizer&&(k.displayElements.text.attr("ta-unsafe-sanitizer",m.taUnsafeSanitizer),k.displayElements.html.attr("ta-unsafe-sanitizer",m.taUnsafeSanitizer)),k.displayElements.scrollWindow.append(k.displayElements.text),l.append(k.displayElements.scrollWindow),l.append(k.displayElements.html),k.displayElements.forminput.attr("name",y),l.append(k.displayElements.forminput),m.tabindex&&(l.removeAttr("tabindex"),k.displayElements.text.attr("tabindex",m.tabindex),k.displayElements.html.attr("tabindex",m.tabindex)),m.placeholder&&(k.displayElements.text.attr("placeholder",m.placeholder),k.displayElements.html.attr("placeholder",m.placeholder)),m.taDisabled&&(k.displayElements.text.attr("ta-readonly","disabled"),k.displayElements.html.attr("ta-readonly","disabled"),k.disabled=k.$parent.$eval(m.taDisabled),k.$parent.$watch(m.taDisabled,function(a){k.disabled=a,k.disabled?l.addClass(k.classes.disabled):l.removeClass(k.classes.disabled)})),a(k.displayElements.scrollWindow)(k),a(k.displayElements.html)(k),k.updateTaBindtaTextElement=k["updateTaBindtaTextElement"+x],k.updateTaBindtaHtmlElement=k["updateTaBindtaHtmlElement"+x],l.addClass("ta-root"),k.displayElements.scrollWindow.addClass("ta-text ta-editor "+k.classes.textEditor),k.displayElements.html.addClass("ta-html ta-editor "+k.classes.htmlEditor),k._actionRunning=!1;var A=!1;if(k.startAction=function(){return k._actionRunning=!0,g.rangy&&g.rangy.saveSelection?(A=g.rangy.saveSelection(),function(){A&&g.rangy.restoreSelection(A)}):void 0},k.endAction=function(){k._actionRunning=!1,A&&g.rangy.removeMarkers(A),A=!1,k.updateSelectedStyles(),k.showHtml||k["updateTaBindtaTextElement"+x]()},s=function(){l.addClass(k.classes.focussed),v.focus()},k.displayElements.html.on("focus",s),k.displayElements.text.on("focus",s),t=function(a){return k._actionRunning||h[0].activeElement===k.displayElements.html[0]||h[0].activeElement===k.displayElements.text[0]||(l.removeClass(k.classes.focussed),v.unfocus(),b(function(){l.triggerHandler("blur")},0)),a.preventDefault(),!1},k.displayElements.html.on("blur",t),k.displayElements.text.on("blur",t),k.queryFormatBlockState=function(a){return!k.showHtml&&a.toLowerCase()===h[0].queryCommandValue("formatBlock").toLowerCase()},k.queryCommandState=function(a){return k.showHtml?"":h[0].queryCommandState(a)},k.switchView=function(){k.showHtml=!k.showHtml,k.showHtml?b(function(){return k.displayElements.html[0].focus()},100):b(function(){return k.displayElements.text[0].focus()},100)},m.ngModel){var B=!0;n.$render=function(){if(B){B=!1;var a=k.$parent.$eval(m.ngModel);void 0!==a&&null!==a||!u||""===u||n.$setViewValue(u)}k.displayElements.forminput.val(n.$viewValue),k._elementSelectTriggered||h[0].activeElement===k.displayElements.html[0]||h[0].activeElement===k.displayElements.text[0]||(k.html=n.$viewValue||"")};var C=function(a){return m.required&&n.$setValidity("required",!(!a||""===a.trim())),a};n.$parsers.push(C),n.$formatters.push(C)}else k.displayElements.forminput.val(u),k.html=u;if(k.$watch("html",function(a,b){a!==b&&(m.ngModel&&n.$viewValue!==a&&n.$setViewValue(a),k.displayElements.forminput.val(a))}),m.taTargetToolbars)v=f.registerEditor(y,k,m.taTargetToolbars.split(","));else{var D=angular.element('
    ');m.taToolbar&&D.attr("ta-toolbar",m.taToolbar),m.taToolbarClass&&D.attr("ta-toolbar-class",m.taToolbarClass),m.taToolbarGroupClass&&D.attr("ta-toolbar-group-class",m.taToolbarGroupClass),m.taToolbarButtonClass&&D.attr("ta-toolbar-button-class",m.taToolbarButtonClass),m.taToolbarActiveButtonClass&&D.attr("ta-toolbar-active-button-class",m.taToolbarActiveButtonClass),m.taFocussedClass&&D.attr("ta-focussed-class",m.taFocussedClass),l.prepend(D),a(D)(k.$parent),v=f.registerEditor(y,k,["textAngularToolbar"+x])}k.$on("$destroy",function(){f.unregisterEditor(y)}),k.$on("ta-element-select",function(a,b){v.triggerElementSelect(a,b)}),k.$on("ta-drop-event",function(a,b,c,d){k.displayElements.text[0].focus(),d&&d.files&&d.files.length>0&&(angular.forEach(d.files,function(a){try{return k.fileDropHandler(a,k.wrapSelection)||k.fileDropHandler!==k.defaultFileDropHandler&&k.defaultFileDropHandler(a,k.wrapSelection)}catch(b){j.error(b)}}),c.preventDefault(),c.stopPropagation())}),k._bUpdateSelectedStyles=!1,k.updateSelectedStyles=function(){var a;void 0!==(a=d.getSelectionElement())&&a.parentNode!==k.displayElements.text[0]?v.updateSelectedStyles(angular.element(a)):v.updateSelectedStyles(),k._bUpdateSelectedStyles&&b(k.updateSelectedStyles,200)},o=function(){k._bUpdateSelectedStyles||(k._bUpdateSelectedStyles=!0,k.$apply(function(){k.updateSelectedStyles()}))},k.displayElements.html.on("keydown",o),k.displayElements.text.on("keydown",o),p=function(){k._bUpdateSelectedStyles=!1},k.displayElements.html.on("keyup",p),k.displayElements.text.on("keyup",p),q=function(a,b){b&&angular.extend(a,b),k.$apply(function(){return v.sendKeyCommand(a)?(k._bUpdateSelectedStyles||k.updateSelectedStyles(),a.preventDefault(),!1):void 0})},k.displayElements.html.on("keypress",q),k.displayElements.text.on("keypress",q),r=function(){k._bUpdateSelectedStyles=!1,k.$apply(function(){k.updateSelectedStyles()})},k.displayElements.html.on("mouseup",r),k.displayElements.text.on("mouseup",r)}}}]).factory("taBrowserTag",[function(){return function(a){return a?""===a?void 0===e?"div":8>=e?"P":"p":8>=e?a.toUpperCase():a:8>=e?"P":"p"}}]).factory("taExecCommand",["taSelection","taBrowserTag","$document",function(a,b,c){var d=/^(address|article|aside|audio|blockquote|canvas|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video)$/gi,e=/^(ul|li|ol)$/gi,f=function(b,c){var d,e,f=b.find("li");for(e=f.length-1;e>=0;e--)d=angular.element("<"+c+">"+f[e].innerHTML+""),b.after(d);b.remove(),a.setSelectionToElementEnd(d[0])},g=function(b,c){var d=angular.element("<"+c+">"+b[0].innerHTML+"");b.after(d),b.remove(),a.setSelectionToElementEnd(d.find("li")[0])},h=function(c,d,e){for(var f="",g=0;g"+c[g].innerHTML+"";var h=angular.element("<"+e+">"+f+"");d.after(h),d.remove(),a.setSelectionToElementEnd(h.find("li")[0])};return function(i){return i=b(i),function(j,k,l){var m,n,o,p,q,r=angular.element("<"+i+">"),s=a.getSelectionElement(),t=angular.element(s);if(void 0!==s){var u=s.tagName.toLowerCase();if("insertorderedlist"===j.toLowerCase()||"insertunorderedlist"===j.toLowerCase()){var v=b("insertorderedlist"===j.toLowerCase()?"ol":"ul");if(u===v)return f(t,i);if("li"===u&&t.parent()[0].tagName.toLowerCase()===v&&1===t.parent().children().length)return f(t.parent(),i);if("li"===u&&t.parent()[0].tagName.toLowerCase()!==v&&1===t.parent().children().length)return g(t.parent(),v);if(u.match(d)&&!t.hasClass("ta-bind")){if("ol"===u||"ul"===u)return g(t,v);var w=!1;return angular.forEach(t.children(),function(a){a.tagName.match(d)&&(w=!0)}),w?h(t.children(),t,v):h([angular.element("
    "+s.innerHTML+"
    ")[0]],t,v)}if(u.match(d)){if(p=a.getOnlySelectedElements(),1===p.length&&("ol"===p[0].tagName.toLowerCase()||"ul"===p[0].tagName.toLowerCase()))return p[0].tagName.toLowerCase()===v?f(angular.element(p[0]),i):g(angular.element(p[0]),v);o="";var x=[];for(m=0;m"+y[0].innerHTML+"",x.unshift(y)}return n=angular.element("<"+v+">"+o+""),x.pop().replaceWith(n),angular.forEach(x,function(a){a.remove()}),void a.setSelectionToElementEnd(n[0])}}else if("formatblock"===j.toLowerCase()){var z=l.toLowerCase().replace(/[<>]/gi,"");for(n="li"===u?t.parent():t;!n[0].tagName.match(d);)n=n.parent(),u=n[0].tagName.toLowerCase();if(u===z){p=n.children();var A=!1;for(m=0;m"),r[0].innerHTML=D[m].outerHTML,D[m]=r[0]),C.parent()[0].insertBefore(D[m],C[0]);C.remove()}return void a.setSelectionToElementEnd(n[0])}}try{c[0].execCommand(j,k,l)}catch(E){}}}}]).directive("taBind",["taSanitize","$timeout","$window","$document","taFixChrome","taBrowserTag","taSelection","taSelectableElements","taApplyCustomRenderers","taOptions",function(a,b,c,f,i,j,k,m,n,o){return{require:"ngModel",scope:{},link:function(j,p,q,r){var s,t,u=void 0!==p.attr("contenteditable")&&p.attr("contenteditable"),v=u||"textarea"===p[0].tagName.toLowerCase()||"input"===p[0].tagName.toLowerCase(),w=!1,x=!1,y=q.taUnsafeSanitizer||o.disableSanitizer;void 0===q.taDefaultWrap&&(q.taDefaultWrap="p"),""===q.taDefaultWrap?(s="",t=void 0===e?"

    ":e>=11?"


    ":8>=e?"

     

    ":"

     

    "):(s=void 0===e||e>=11?"<"+q.taDefaultWrap+">
    ":8>=e?"<"+q.taDefaultWrap.toUpperCase()+">":"<"+q.taDefaultWrap+">",t=void 0===e||e>=11?"<"+q.taDefaultWrap+">
    ":8>=e?"<"+q.taDefaultWrap.toUpperCase()+"> ":"<"+q.taDefaultWrap+"> "),p.addClass("ta-bind"); -var z=function(){if(u)return p[0].innerHTML;if(v)return p.val();throw"textAngular Error: attempting to update non-editable taBind"},A=function(a){a||(a=z()),a===t?""!==r.$viewValue&&r.$setViewValue(""):r.$viewValue!==a&&r.$setViewValue(a)};if(j.$parent["updateTaBind"+(q.id||"")]=function(){w||A()},v)if(u){if(p.on("cut",function(a){w?a.preventDefault():b(function(){A()},0)}),p.on("paste",function(a,b){b&&angular.extend(a,b);var d;if(a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData?d=(a.originalEvent||a).clipboardData.getData("text/plain"):c.clipboardData&&(d=c.clipboardData.getData("Text")),!d&&!w)return!0;if(a.preventDefault(),!w){var e=angular.element("
    ");if(e[0].innerHTML=d,d=e.text(),f[0].selection){var g=f[0].selection.createRange();g.pasteHTML(d)}else f[0].execCommand("insertText",!1,d);A()}}),p.on("keyup",function(a,b){if(b&&angular.extend(a,b),!w){if(""!==s&&13===a.keyCode&&!a.shiftKey){var c=k.getSelectionElement();if(c.tagName.toLowerCase()!==q.taDefaultWrap&&"li"!==c.tagName.toLowerCase()&&(""===c.innerHTML.trim()||"
    "===c.innerHTML.trim())){var d=angular.element(s);angular.element(c).replaceWith(d),k.setSelectionToElementStart(d[0])}}var e=z();""!==s&&""===e.trim()&&(p[0].innerHTML=s,k.setSelectionToElementStart(p.children()[0])),A(e)}}),p.on("blur",function(){x=!1,w||A(),r.$render()}),q.placeholder&&(e>8||void 0===e)){var B;if(!q.id)throw"textAngular Error: An unique ID is required for placeholders to work";B=g("#"+q.id+".placeholder-text:before",'content: "'+q.placeholder+'"'),j.$on("$destroy",function(){h(B)})}p.on("focus",function(){x=!0,r.$render()}),p.on("mousedown",function(a,b){b&&angular.extend(a,b),a.stopPropagation()})}else p.on("paste cut",function(){w||b(function(){r.$setViewValue(z())},0)}),p.on("change blur",function(){w||r.$setViewValue(z())});var C=function(b){return r.$oldViewValue=a(i(b),r.$oldViewValue,y)},D=function(a){return q.required&&r.$setValidity("required",!(!a||a.trim()===t||""===a.trim())),a};r.$parsers.push(C),r.$parsers.push(D),r.$formatters.push(C),r.$formatters.push(D);var E=function(a){return j.$emit("ta-element-select",this),a.preventDefault(),!1},F=function(a,c){if(c&&angular.extend(a,c),!l&&!w){l=!0;var d;d=a.originalEvent?a.originalEvent.dataTransfer:a.dataTransfer,j.$emit("ta-drop-event",this,a,d),b(function(){l=!1},100)}};j.$parent["reApplyOnSelectorHandlers"+(q.id||"")]=function(){w||angular.forEach(m,function(a){p.find(a).off("click",E).on("click",E)})};var G=function(a){p[0].innerHTML=a};r.$render=function(){var a=r.$viewValue||"";f[0].activeElement!==p[0]?u?(q.placeholder?""===a?(x?p.removeClass("placeholder-text"):p.addClass("placeholder-text"),G(s)):(p.removeClass("placeholder-text"),G(a)):G(""===a?s:a),w?p.off("drop",F):(angular.forEach(m,function(a){p.find(a).on("click",E)}),p.on("drop",F))):"textarea"!==p[0].tagName.toLowerCase()&&"input"!==p[0].tagName.toLowerCase()?G(n(a)):p.val(a):u&&p.removeClass("placeholder-text")},q.taReadonly&&(w=j.$parent.$eval(q.taReadonly),w?(p.addClass("ta-readonly"),("textarea"===p[0].tagName.toLowerCase()||"input"===p[0].tagName.toLowerCase())&&p.attr("disabled","disabled"),void 0!==p.attr("contenteditable")&&p.attr("contenteditable")&&p.removeAttr("contenteditable")):(p.removeClass("ta-readonly"),"textarea"===p[0].tagName.toLowerCase()||"input"===p[0].tagName.toLowerCase()?p.removeAttr("disabled"):u&&p.attr("contenteditable","true")),j.$parent.$watch(q.taReadonly,function(a,b){b!==a&&(a?(p.addClass("ta-readonly"),("textarea"===p[0].tagName.toLowerCase()||"input"===p[0].tagName.toLowerCase())&&p.attr("disabled","disabled"),void 0!==p.attr("contenteditable")&&p.attr("contenteditable")&&p.removeAttr("contenteditable"),angular.forEach(m,function(a){p.find(a).on("click",E)}),p.off("drop",F)):(p.removeClass("ta-readonly"),"textarea"===p[0].tagName.toLowerCase()||"input"===p[0].tagName.toLowerCase()?p.removeAttr("disabled"):u&&p.attr("contenteditable","true"),angular.forEach(m,function(a){p.find(a).off("click",E)}),p.on("drop",F)),w=a)})),u&&!w&&(angular.forEach(m,function(a){p.find(a).on("click",E)}),p.on("drop",F),p.on("blur",function(){/AppleWebKit\/([\d.]+)/.exec(navigator.userAgent)&&(d=!0)}))}}}]).factory("taApplyCustomRenderers",["taCustomRenderers",function(a){return function(c){var d=angular.element("
    ");return d[0].innerHTML=c,angular.forEach(a,function(a){var c=[];a.selector&&""!==a.selector?c=d.find(a.selector):a.customAttribute&&""!==a.customAttribute&&(c=b(d,a.customAttribute)),angular.forEach(c,function(b){b=angular.element(b),a.selector&&""!==a.selector&&a.customAttribute&&""!==a.customAttribute?void 0!==b.attr(a.customAttribute)&&a.renderLogic(b):a.renderLogic(b)})}),d[0].innerHTML}}]).directive("taMaxText",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){function e(a){var b=angular.element("
    ");b.html(a);var c=b.text().length;return f>=c?(d.$setValidity("taMaxText",!0),a):void d.$setValidity("taMaxText",!1)}var f=parseInt(a.$eval(c.taMaxText));if(isNaN(f))throw"Max text must be an integer";c.$observe("taMaxText",function(a){if(f=parseInt(a),isNaN(f))throw"Max text must be an integer";d.$dirty&&d.$setViewValue(d.$viewValue)}),d.$parsers.unshift(e)}}}).directive("taMinText",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){function e(a){var b=angular.element("
    ");b.html(a);var c=b.text().length;return!c||c>=f?(d.$setValidity("taMinText",!0),a):void d.$setValidity("taMinText",!1)}var f=parseInt(a.$eval(c.taMinText));if(isNaN(f))throw"Min text must be an integer";c.$observe("taMinText",function(a){if(f=parseInt(a),isNaN(f))throw"Min text must be an integer";d.$dirty&&d.$setViewValue(d.$viewValue)}),d.$parsers.unshift(e)}}}).factory("taFixChrome",function(){var a=function(a){for(var b=angular.element("
    "+a+"
    "),c=angular.element(b).find("span"),d=0;d0&&"BR"===e.next()[0].tagName&&e.next().remove(),e.replaceWith(e[0].innerHTML)))}var f=b[0].innerHTML.replace(/style="[^"]*?(line-height: 1.428571429;|color: inherit; line-height: 1.1;)[^"]*"/gi,"");return f!==b[0].innerHTML&&(b[0].innerHTML=f),b[0].innerHTML};return a}).factory("taSanitize",["$sanitize",function(a){return function(c,d,e){var f=angular.element("
    "+c+"
    ");angular.forEach(b(f,"align"),function(a){a.css("text-align",a.attr("align")),a.removeAttr("align")});var g;c=f[0].innerHTML;try{g=a(c),e&&(g=c)}catch(h){g=d||""}return g}}]).directive("textAngularToolbar",["$compile","textAngularManager","taOptions","taTools","taToolExecuteAction","$window",function(a,b,c,d,e,f){return{scope:{name:"@"},restrict:"EA",link:function(g,h,i){if(!g.name||""===g.name)throw"textAngular Error: A toolbar requires a name";angular.extend(g,angular.copy(c)),i.taToolbar&&(g.toolbar=g.$parent.$eval(i.taToolbar)),i.taToolbarClass&&(g.classes.toolbar=i.taToolbarClass),i.taToolbarGroupClass&&(g.classes.toolbarGroup=i.taToolbarGroupClass),i.taToolbarButtonClass&&(g.classes.toolbarButton=i.taToolbarButtonClass),i.taToolbarActiveButtonClass&&(g.classes.toolbarButtonActive=i.taToolbarActiveButtonClass),i.taFocussedClass&&(g.classes.focussed=i.taFocussedClass),g.disabled=!0,g.focussed=!1,g._$element=h,h[0].innerHTML="",h.addClass("ta-toolbar "+g.classes.toolbar),g.$watch("focussed",function(){g.focussed?h.addClass(g.classes.focussed):h.removeClass(g.classes.focussed)});var j=function(b,c){var d;if(d=angular.element(b&&b.display?b.display:"');g.on("click",function(a){a.preventDefault(),b.css({width:"100%",height:""}),d()});var h=angular.element('');h.on("click",function(a){a.preventDefault(),b.css({width:"50%",height:""}),d()});var i=angular.element('');i.on("click",function(a){a.preventDefault(),b.css({width:"25%",height:""}),d()});var j=angular.element('');j.on("click",function(a){a.preventDefault(),b.css({width:"",height:""}),d()}),f.append(g),f.append(h),f.append(i),f.append(j),e.append(f),f=angular.element('
    ');var k=angular.element('');k.on("click",function(a){a.preventDefault(),b.css("float","left"),b.css("cssFloat","left"),b.css("styleFloat","left"),d()});var l=angular.element('');l.on("click",function(a){a.preventDefault(),b.css("float","right"),b.css("cssFloat","right"),b.css("styleFloat","right"),d()});var m=angular.element('');m.on("click",function(a){a.preventDefault(),b.css("float",""),b.css("cssFloat",""),b.css("styleFloat",""),d()}),f.append(k),f.append(m),f.append(l),e.append(f),f=angular.element('
    ');var n=angular.element('');n.on("click",function(a){a.preventDefault(),b.remove(),d()}),f.append(n),e.append(f),c.showPopover(b),c.showResizeOverlay(b)},aOnSelectAction:function(c,d,e){c.preventDefault(),e.displayElements.popover.css("width","436px");var f=e.displayElements.popoverContainer;f.empty(),f.css("line-height","28px");var g=angular.element(''+d.attr("href")+"");g.css({display:"inline-block","max-width":"200px",overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap","vertical-align":"middle"}),f.append(g);var h=angular.element('
    '),i=angular.element('');i.on("click",function(c){c.preventDefault();var f=a.prompt(b.insertLink.dialogPrompt,d.attr("href"));f&&""!==f&&"http://"!==f&&(d.attr("href",f),e.updateTaBindtaTextElement()),e.hidePopover()}),h.append(i);var j=angular.element('');j.on("click",function(a){a.preventDefault(),d.replaceWith(d.contents()),e.updateTaBindtaTextElement(),e.hidePopover()}),h.append(j);var k=angular.element('");"_blank"===d.attr("target")&&k.addClass("active"),k.on("click",function(a){a.preventDefault(),d.attr("target","_blank"===d.attr("target")?"":"_blank"),k.toggleClass("active"),e.updateTaBindtaTextElement()}),h.append(k),f.append(h),e.showPopover(d)},extractYoutubeVideoId:function(a){var b=/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/i,c=a.match(b);return c&&c[1]||null}}}]).run(["taRegisterTool","$window","taTranslations","taSelection","taToolFunctions","$sanitize","taOptions",function(a,b,c,d,e,f,g){var h={};if(f("",h),g.forceTextAngularSanitize===!0&&"taSanitize"!==h.version)throw angular.$$minErr("textAngular")("textAngularSetup","The textAngular-sanitize provider has been replaced by another -- have you included angular-sanitize by mistake?");a("html",{iconclass:"fa fa-code",tooltiptext:c.html.tooltip,action:function(){this.$editor().switchView()},activeState:function(){return this.$editor().showHtml}});var i=function(a){return function(){return this.$editor().queryFormatBlockState(a)}},j=function(){return this.$editor().wrapSelection("formatBlock","<"+this.name.toUpperCase()+">")};angular.forEach(["h1","h2","h3","h4","h5","h6"],function(b){a(b.toLowerCase(),{buttontext:b.toUpperCase(),tooltiptext:c.heading.tooltip+b.charAt(1),action:j,activeState:i(b.toLowerCase())})}),a("p",{buttontext:"P",tooltiptext:c.p.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

    ")},activeState:function(){return this.$editor().queryFormatBlockState("p")}}),a("pre",{buttontext:"pre",tooltiptext:c.pre.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","

    ")},activeState:function(){return this.$editor().queryFormatBlockState("pre")}}),a("ul",{iconclass:"fa fa-list-ul",tooltiptext:c.ul.tooltip,action:function(){return this.$editor().wrapSelection("insertUnorderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertUnorderedList")}}),a("ol",{iconclass:"fa fa-list-ol",tooltiptext:c.ol.tooltip,action:function(){return this.$editor().wrapSelection("insertOrderedList",null)},activeState:function(){return this.$editor().queryCommandState("insertOrderedList")}}),a("quote",{iconclass:"fa fa-quote-right",tooltiptext:c.quote.tooltip,action:function(){return this.$editor().wrapSelection("formatBlock","
    ")},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")}}),a("undo",{iconclass:"fa fa-undo",tooltiptext:c.undo.tooltip,action:function(){return this.$editor().wrapSelection("undo",null)}}),a("redo",{iconclass:"fa fa-repeat",tooltiptext:c.redo.tooltip,action:function(){return this.$editor().wrapSelection("redo",null)}}),a("bold",{iconclass:"fa fa-bold",tooltiptext:c.bold.tooltip,action:function(){return this.$editor().wrapSelection("bold",null)},activeState:function(){return this.$editor().queryCommandState("bold")},commandKeyCode:98}),a("justifyLeft",{iconclass:"fa fa-align-left",tooltiptext:c.justifyLeft.tooltip,action:function(){return this.$editor().wrapSelection("justifyLeft",null)},activeState:function(a){if(a&&"#document"===a.nodeName)return!1;var b=!1;return a&&(b="left"===a.css("text-align")||"left"===a.attr("align")||"right"!==a.css("text-align")&&"center"!==a.css("text-align")&&"justify"!==a.css("text-align")&&!this.$editor().queryCommandState("justifyRight")&&!this.$editor().queryCommandState("justifyCenter")&&!this.$editor().queryCommandState("justifyFull")),b=b||this.$editor().queryCommandState("justifyLeft")}}),a("justifyRight",{iconclass:"fa fa-align-right",tooltiptext:c.justifyRight.tooltip,action:function(){return this.$editor().wrapSelection("justifyRight",null)},activeState:function(a){if(a&&"#document"===a.nodeName)return!1;var b=!1;return a&&(b="right"===a.css("text-align")),b=b||this.$editor().queryCommandState("justifyRight")}}),a("justifyFull",{iconclass:"fa fa-align-justify",tooltiptext:c.justifyFull.tooltip,action:function(){return this.$editor().wrapSelection("justifyFull",null)},activeState:function(a){var b=!1;return a&&(b="justify"===a.css("text-align")),b=b||this.$editor().queryCommandState("justifyFull")}}),a("justifyCenter",{iconclass:"fa fa-align-center",tooltiptext:c.justifyCenter.tooltip,action:function(){return this.$editor().wrapSelection("justifyCenter",null)},activeState:function(a){if(a&&"#document"===a.nodeName)return!1;var b=!1;return a&&(b="center"===a.css("text-align")),b=b||this.$editor().queryCommandState("justifyCenter")}}),a("indent",{iconclass:"fa fa-indent",tooltiptext:c.indent.tooltip,action:function(){return this.$editor().wrapSelection("indent",null)},activeState:function(){return this.$editor().queryFormatBlockState("blockquote")},commandKeyCode:"TabKey"}),a("outdent",{iconclass:"fa fa-outdent",tooltiptext:c.outdent.tooltip,action:function(){return this.$editor().wrapSelection("outdent",null)},activeState:function(){return!1},commandKeyCode:"ShiftTabKey"}),a("italics",{iconclass:"fa fa-italic",tooltiptext:c.italic.tooltip,action:function(){return this.$editor().wrapSelection("italic",null)},activeState:function(){return this.$editor().queryCommandState("italic")},commandKeyCode:105}),a("underline",{iconclass:"fa fa-underline",tooltiptext:c.underline.tooltip,action:function(){return this.$editor().wrapSelection("underline",null)},activeState:function(){return this.$editor().queryCommandState("underline")},commandKeyCode:117}),a("strikeThrough",{iconclass:"fa fa-strikethrough",tooltiptext:c.strikeThrough.tooltip,action:function(){return this.$editor().wrapSelection("strikeThrough",null)},activeState:function(){return document.queryCommandState("strikeThrough")}}),a("clear",{iconclass:"fa fa-ban",tooltiptext:c.clear.tooltip,action:function(a,b){var c;this.$editor().wrapSelection("removeFormat",null);var e=angular.element(d.getSelectionElement()),f=function(a){a=angular.element(a);var b=a;angular.forEach(a.children(),function(a){var c=angular.element("

    ");c.html(angular.element(a).html()),b.after(c),b=c}),a.remove()};if(angular.forEach(e.find("ul"),f),angular.forEach(e.find("ol"),f),"li"===e[0].tagName.toLowerCase()){var g=e[0].parentNode.childNodes,h=[],i=[],j=!1;for(c=0;c

    ");if(l.html(angular.element(e[0]).html()),0===h.length||0===i.length)0===i.length?k.after(l):k[0].parentNode.insertBefore(l[0],k[0]),0===h.length&&0===i.length?k.remove():angular.element(e[0]).remove();else{var m=angular.element("<"+k[0].tagName+">"),n=angular.element("<"+k[0].tagName+">");for(c=0;c';return this.$editor().wrapSelection("insertHTML",f,!0)}},onElementSelect:{element:"img",onlyWithAttrs:["ta-insert-video"],action:e.imgOnSelectAction}}),a("insertLink",{tooltiptext:c.insertLink.tooltip,iconclass:"fa fa-link",action:function(){var a;return a=b.prompt(c.insertLink.dialogPrompt,"http://"),a&&""!==a&&"http://"!==a?this.$editor().wrapSelection("createLink",a,!0):void 0},activeState:function(a){return a?"A"===a[0].tagName:!1},onElementSelect:{element:"a",action:e.aOnSelectAction}}),a("wordcount",{display:'
    Words:
    ',disabled:!0,wordcount:0,activeState:function(){var a=this.$editor().displayElements.text,b=a[0].innerHTML||"",c=0;return""!==b.replace(/\s*<[^>]*?>\s*/g,"")&&(c=b.replace(/<\/?(b|i|em|strong|span|u|strikethrough|a|img|small|sub|sup|label)( [^>*?])?>/gi,"").replace(/(<[^>]*?>\s*<[^>]*?>)/gi," ").replace(/(<[^>]*?>)/gi,"").replace(/\s+/gi," ").match(/\S+/g).length),this.wordcount=c,this.$editor().wordcount=c,!1}}),a("charcount",{display:'
    Characters:
    ',disabled:!0,charcount:0,activeState:function(){var a=this.$editor().displayElements.text,b=a[0].innerText||a[0].textContent,c=b.replace(/(\r\n|\n|\r)/gm,"").replace(/^\s+/g," ").replace(/\s+$/g," ").length;return this.charcount=c,this.$editor().charcount=c,!1}})}]);var e={ie:function(){for(var a,b=3,c=document.createElement("div"),d=c.getElementsByTagName("i");c.innerHTML="",d[0];);return b>4?b:a}(),webkit:/AppleWebKit\/([\d.]+)/i.test(navigator.userAgent)},f=!1;e.webkit&&(document.addEventListener("mousedown",function(a){var b=a||window.event,c=b.target;if(f&&null!==c){for(var d=!1,e=c;null!==e&&"html"!==e.tagName.toLowerCase()&&!d;)d="true"===e.contentEditable,e=e.parentNode;d||(document.getElementById("textAngular-editableFix-010203040506070809").setSelectionRange(0,0),c.focus(),c.select&&c.select())}f=!1},!1),angular.element(document).ready(function(){angular.element(document.body).append(angular.element(''))}));var g=/^(address|article|aside|audio|blockquote|canvas|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video)$/i,h=/^(ul|li|ol)$/i,i=/^(address|article|aside|audio|blockquote|canvas|dd|div|dl|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|noscript|ol|output|p|pre|section|table|tfoot|ul|video|li)$/i;String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});var j,k,l,m,n,o;if(e.ie>8||void 0===e.ie){for(var p=document.styleSheets,q=0;q
    ");return d[0].innerHTML=c,angular.forEach(a,function(a){var c=[];a.selector&&""!==a.selector?c=d.find(a.selector):a.customAttribute&&""!==a.customAttribute&&(c=b.getByAttribute(d,a.customAttribute)),angular.forEach(c,function(b){b=angular.element(b),a.selector&&""!==a.selector&&a.customAttribute&&""!==a.customAttribute?void 0!==b.attr(a.customAttribute)&&a.renderLogic(b):a.renderLogic(b)})}),d[0].innerHTML}}]).factory("taFixChrome",function(){var a=function(a){if(!a||!angular.isString(a)||a.length<=0)return a;for(var b,c,d,e=/<([^>\/]+?)style=("([^"]+)"|'([^']+)')([^>]*)>/gi,f="",g=0;b=e.exec(a);)c=b[3]||b[4],c&&c.match(/line-height: 1.[0-9]{3,12};|color: inherit; line-height: 1.1;/i)&&(c=c.replace(/( |)font-family: inherit;|( |)line-height: 1.[0-9]{3,12};|( |)color: inherit;/gi,""),d="<"+b[1].trim(),c.trim().length>0&&(d+=" style="+b[2].substring(0,1)+c+b[2].substring(0,1)),d+=b[5].trim()+">",f+=a.substring(g,b.index)+d,g=b.index+b[0].length);return f+=a.substring(g),g>0?f.replace(/(.*?)<\/span>(|)/gi,"$1"):a};return a}).factory("taSanitize",["$sanitize",function(a){function b(a,b){for(var c,d=0,e=0,f=/<[^>]*>/gi;c=f.exec(a);)if(e=c.index,"/"===c[0].substr(1,1)){if(0===d)break;d--}else d++;return b+a.substring(0,e)+angular.element(b)[0].outerHTML.substring(b.length)+a.substring(e)}function c(a){if(!a||!angular.isString(a)||a.length<=0)return a;for(var d,f,g,h,i,k,l=/<([^>\/]+?)style=("([^"]+)"|'([^']+)')([^>]*)>/gi,m="",n="",o=0;f=l.exec(a);){h=f[3]||f[4];var p=new RegExp(j,"i");if(angular.isString(h)&&p.test(h)){i="";for(var q=new RegExp(j,"ig");g=q.exec(h);)for(d=0;d");k=c(a.substring(o,f.index)),n+=m.length>0?b(k,m):k,h=h.replace(new RegExp(j,"ig"),""),n+="<"+f[1].trim(),h.length>0&&(n+=' style="'+h+'"'),n+=f[5]+">",o=f.index+f[0].length,m=i}}return n+=m.length>0?b(a.substring(o),m):a.substring(o)}function d(a){if(!a||!angular.isString(a)||a.length<=0)return a;for(var b,c=/<([^>\/]+?)align=("([^"]+)"|'([^']+)')([^>]*)>/gi,d="",e=0;b=c.exec(a);){d+=a.substring(e,b.index),e=b.index+b[0].length;var f="<"+b[1]+b[5];/style=("([^"]+)"|'([^']+)')/gi.test(f)?f=f.replace(/style=("([^"]+)"|'([^']+)')/i,'style="$2$3 text-align:'+(b[3]||b[4])+';"'):f+=' style="text-align:'+(b[3]||b[4])+';"',f+=">",d+=f}return d+a.substring(e)}for(var e=[{property:"font-weight",values:["bold"],tag:"b"},{property:"font-style",values:["italic"],tag:"i"}],f=[],g=0;g0&&(h+="|"),h+=e[g].values[i];h+=");)",f.push(h)}var j="("+f.join("|")+")";return function(b,e,f){if(!f)try{b=c(b)}catch(g){}b=d(b);var h;try{h=a(b),f&&(h=b)}catch(g){h=e||""}var i,j=h.match(/(]*>.*?<\/pre[^>]*>)/gi),k=h.replace(/(&#(9|10);)*/gi,""),l=/]*>.*?<\/pre[^>]*>/gi,m=0,n=0;for(h="";null!==(i=l.exec(k))&&m=0;e--)d=angular.element("<"+c+">"+f[e].innerHTML+""),a.after(d);a.remove(),b.setSelectionToElementEnd(d[0])},f=function(a){/()$/i.test(a.innerHTML.trim())?b.setSelectionBeforeElement(angular.element(a).find("br")[0]):b.setSelectionToElementEnd(a)},i=function(a,b){var c=angular.element("<"+b+">"+a[0].innerHTML+"");a.after(c),a.remove(),f(c.find("li")[0])},j=function(a,b,d){for(var e="",g=0;g"+a[g].innerHTML+"";var h=angular.element("<"+d+">"+e+"");b.after(h),b.remove(),f(h.find("li")[0])};return function(f,k){return f=c(f),function(l,m,n,o){var p,q,r,s,t,u,v,w=angular.element("<"+f+">");try{v=b.getSelectionElement()}catch(x){}var y=angular.element(v);if(void 0!==v){var z=v.tagName.toLowerCase();if("insertorderedlist"===l.toLowerCase()||"insertunorderedlist"===l.toLowerCase()){var A=c("insertorderedlist"===l.toLowerCase()?"ol":"ul");if(z===A)return e(y,f);if("li"===z&&y.parent()[0].tagName.toLowerCase()===A&&1===y.parent().children().length)return e(y.parent(),f);if("li"===z&&y.parent()[0].tagName.toLowerCase()!==A&&1===y.parent().children().length)return i(y.parent(),A);if(z.match(g)&&!y.hasClass("ta-bind")){if("ol"===z||"ul"===z)return i(y,A);var B=!1;return angular.forEach(y.children(),function(a){a.tagName.match(g)&&(B=!0)}),B?j(y.children(),y,A):j([angular.element("
    "+v.innerHTML+"
    ")[0]],y,A)}if(z.match(g)){if(s=b.getOnlySelectedElements(),0===s.length)q=angular.element("<"+A+">
  • "+v.innerHTML+"
  • "),y.html(""),y.append(q);else{if(1===s.length&&("ol"===s[0].tagName.toLowerCase()||"ul"===s[0].tagName.toLowerCase()))return s[0].tagName.toLowerCase()===A?e(angular.element(s[0]),f):i(angular.element(s[0]),A);r="";var C=[];for(p=0;p"+D[0].innerHTML+"":D[0].childNodes[0].innerHTML,C.unshift(D)}q=angular.element("<"+A+">"+r+""),C.pop().replaceWith(q),angular.forEach(C,function(a){a.remove()})}return void b.setSelectionToElementEnd(q[0])}}else{if("formatblock"===l.toLowerCase()){for(u=n.toLowerCase().replace(/[<>]/gi,""),"default"===u.trim()&&(u=f,n="<"+f+">"),q="li"===z?y.parent():y;!q[0].tagName||!q[0].tagName.match(g)&&!q.parent().attr("contenteditable");)q=q.parent(),z=(q[0].tagName||"").toLowerCase();if(z===u){s=q.children();var E=!1;for(p=0;p=0;p--)s[p].parentNode&&s[p].parentNode.removeChild(s[p])}else for(p=0;p"),w[0].innerHTML=G[p].outerHTML,G[p]=w[0]),F.parent()[0].insertBefore(G[p],F[0]);F.remove()}return void b.setSelectionToElementEnd(q[0])}if("createlink"===l.toLowerCase()){var H='',I="",J=b.getSelection();if(J.collapsed)b.insertHtml(H+n+I,k);else if(a.getSelection().getRangeAt(0).canSurroundContents()){var K=angular.element(H+I)[0];a.getSelection().getRangeAt(0).surroundContents(K)}return}if("inserthtml"===l.toLowerCase())return void b.insertHtml(n,k)}}try{d[0].execCommand(l,m,n)}catch(x){}}}}]).service("taSelection",["$document","taDOM",function(b,c){var d=b[0],e=function(a,b){return a.tagName&&a.tagName.match(/^br$/i)&&0===b&&!a.previousSibling?{element:a.parentNode,offset:0}:{element:a,offset:b}},f={getSelection:function(){var b=a.getSelection().getRangeAt(0),c=b.commonAncestorContainer,d={start:e(b.startContainer,b.startOffset),end:e(b.endContainer,b.endOffset),collapsed:b.collapsed};return c=3===c.nodeType?c.parentNode:c,c.parentNode===d.start.element||c.parentNode===d.end.element?d.container=c.parentNode:d.container=c,d},getOnlySelectedElements:function(){var b=a.getSelection().getRangeAt(0),c=b.commonAncestorContainer;return c=3===c.nodeType?c.parentNode:c,b.getNodes([1],function(a){return a.parentNode===c})},getSelectionElement:function(){return f.getSelection().container},setSelection:function(b,c,d){var e=a.createRange();e.setStart(b,c),e.setEnd(b,d),a.getSelection().setSingleRange(e)},setSelectionBeforeElement:function(b){var c=a.createRange();c.selectNode(b),c.collapse(!0),a.getSelection().setSingleRange(c)},setSelectionAfterElement:function(b){var c=a.createRange();c.selectNode(b),c.collapse(!1),a.getSelection().setSingleRange(c)},setSelectionToElementStart:function(b){var c=a.createRange();c.selectNodeContents(b),c.collapse(!0),a.getSelection().setSingleRange(c)},setSelectionToElementEnd:function(b){var c=a.createRange();c.selectNodeContents(b),c.collapse(!1),b.childNodes&&b.childNodes[b.childNodes.length-1]&&"br"===b.childNodes[b.childNodes.length-1].nodeName&&(c.startOffset=c.endOffset=c.startOffset-1),a.getSelection().setSingleRange(c)},insertHtml:function(b,e){var h,j,k,l,m,n,o,p=angular.element("
    "+b+"
    "),q=a.getSelection().getRangeAt(0),r=d.createDocumentFragment(),s=p[0].childNodes,t=!0;if(s.length>0){for(l=[],k=0;k)$/i.test(q.startContainer.innerHTML)&&q.selectNode(q.startContainer)}else t=!0,n=r=d.createTextNode(b);if(t)q.deleteContents();else if(q.collapsed&&q.startContainer!==e)if(q.startContainer.innerHTML&&q.startContainer.innerHTML.match(/^<[^>]*>$/i))h=q.startContainer,1===q.startOffset?(q.setStartAfter(h),q.setEndAfter(h)):(q.setStartBefore(h),q.setEndBefore(h));else{if(3===q.startContainer.nodeType&&q.startContainer.parentNode!==e)for(h=q.startContainer.parentNode,j=h.cloneNode(),c.splitNodes(h.childNodes,h,j,q.startContainer,q.startOffset);!i.test(h.nodeName);){angular.element(h).after(j),h=h.parentNode;var v=j;j=h.cloneNode(),c.splitNodes(h.childNodes,h,j,v)}else h=q.startContainer,j=h.cloneNode(),c.splitNodes(h.childNodes,h,j,void 0,void 0,q.startOffset);if(angular.element(h).after(j),q.setStartAfter(h),q.setEndAfter(h),/^(|)$/i.test(h.innerHTML.trim())&&(q.setStartBefore(h),q.setEndBefore(h),angular.element(h).remove()),/^(|)$/i.test(j.innerHTML.trim())&&angular.element(j).remove(),"li"===h.nodeName.toLowerCase()){for(o=d.createDocumentFragment(),m=0;m"),c.transferChildNodes(r.childNodes[m],p[0]),c.transferNodeAttributes(r.childNodes[m],p[0]),o.appendChild(p[0]);r=o,n&&(n=r.childNodes[r.childNodes.length-1],n=n.childNodes[n.childNodes.length-1])}}else q.deleteContents();q.insertNode(r),n&&f.setSelectionToElementEnd(n)}};return f}]).service("taDOM",function(){var a={getByAttribute:function(b,c){var d=[],e=b.children();return e.length&&angular.forEach(e,function(b){d=d.concat(a.getByAttribute(angular.element(b),c))}),void 0!==b.attr(c)&&d.push(b),d},transferChildNodes:function(a,b){for(b.innerHTML="";a.childNodes.length>0;)b.appendChild(a.childNodes[0]);return b},splitNodes:function(b,c,d,e,f,g){if(!e&&isNaN(g))throw new Error("taDOM.splitNodes requires a splitNode or splitIndex");for(var h=document.createDocumentFragment(),i=document.createDocumentFragment(),j=0;b.length>0&&(isNaN(g)||g!==j)&&b[0]!==e;)h.appendChild(b[0]),j++;for(!isNaN(f)&&f>=0&&b[0]&&(h.appendChild(document.createTextNode(b[0].nodeValue.substring(0,f))),b[0].nodeValue=b[0].nodeValue.substring(f));b.length>0;)i.appendChild(b[0]);a.transferChildNodes(h,c),a.transferChildNodes(i,d)},transferNodeAttributes:function(a,b){for(var c=0;c");return b.html(a),b.text().length<=e}}}}).directive("taMinText",function(){return{restrict:"A",require:"ngModel",link:function(a,b,c,d){var e=parseInt(a.$eval(c.taMinText)); +if(isNaN(e))throw"Min text must be an integer";c.$observe("taMinText",function(a){if(e=parseInt(a),isNaN(e))throw"Min text must be an integer";d.$dirty&&d.$validate()}),d.$validators.taMinText=function(a){var b=angular.element("
    ");return b.html(a),!b.text().length||b.text().length>=e}}}}),angular.module("textAngular.taBind",["textAngular.factories","textAngular.DOM"]).service("_taBlankTest",[function(){var a=/<(a|abbr|acronym|bdi|bdo|big|cite|code|del|dfn|img|ins|kbd|label|map|mark|q|ruby|rp|rt|s|samp|time|tt|var)[^>]*(>|$)/i;return function(b){return function(c){if(!c)return!0;var d,e=/(^[^<]|>)[^<]/i.exec(c);return e?d=e.index:(c=c.toString().replace(/="[^"]*"/i,"").replace(/="[^"]*"/i,"").replace(/="[^"]*"/i,"").replace(/="[^"]*"/i,""),d=c.indexOf(">")),c=c.trim().substring(d,d+100),/^[^<>]+$/i.test(c)?!1:0===c.length||c===b||/^>(\s| )*<\/[^>]+>$/gi.test(c)?!0:/>\s*[^\s<]/i.test(c)||a.test(c)?!1:!0}}}]).directive("taButton",[function(){return{link:function(a,b,c){b.attr("unselectable","on"),b.on("mousedown",function(a,b){return b&&angular.extend(a,b),a.preventDefault(),!1})}}}]).directive("taBind",["taSanitize","$timeout","$document","taFixChrome","taBrowserTag","taSelection","taSelectableElements","taApplyCustomRenderers","taOptions","_taBlankTest","$parse","taDOM","textAngularManager",function(b,c,d,h,j,m,n,o,p,q,s,t,u){return{priority:2,require:["ngModel","?ngModelOptions"],link:function(j,v,w,x){function y(a){var b;return R.forEach(function(c){if(c.keyCode===a.keyCode){var d=(a.metaKey?O:0)+(a.ctrlKey?N:0)+(a.shiftKey?Q:0)+(a.altKey?P:0);if(c.forbiddenModifiers&d)return;c.mustHaveModifiers.every(function(a){return d&a})&&(b=c.specialKey)}}),b}var z,A,B,C,D=x[0],E=x[1]||{},F=void 0!==v.attr("contenteditable")&&v.attr("contenteditable"),G=F||"textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase(),H=!1,I=!1,J=!1,K=w.taUnsafeSanitizer||p.disableSanitizer,L=/^(9|19|20|27|33|34|35|36|37|38|39|40|45|112|113|114|115|116|117|118|119|120|121|122|123|144|145)$/i,M=/^(8|13|32|46|59|61|107|109|173|186|187|188|189|190|191|192|219|220|221|222)$/i,N=1,O=2,P=4,Q=8,R=[{specialKey:"UndoKey",forbiddenModifiers:P+Q,mustHaveModifiers:[O+N],keyCode:90},{specialKey:"RedoKey",forbiddenModifiers:P,mustHaveModifiers:[O+N,Q],keyCode:90},{specialKey:"RedoKey",forbiddenModifiers:P+Q,mustHaveModifiers:[O+N],keyCode:89},{specialKey:"TabKey",forbiddenModifiers:O+Q+P+N,mustHaveModifiers:[],keyCode:9},{specialKey:"ShiftTabKey",forbiddenModifiers:O+P+N,mustHaveModifiers:[Q],keyCode:9}];void 0===w.taDefaultWrap&&(w.taDefaultWrap="p"),""===w.taDefaultWrap?(B="",C=void 0===e.ie?"

    ":e.ie>=11?"


    ":e.ie<=8?"

     

    ":"

     

    "):(B=void 0===e.ie||e.ie>=11?"<"+w.taDefaultWrap+">
    ":e.ie<=8?"<"+w.taDefaultWrap.toUpperCase()+">":"<"+w.taDefaultWrap+">",C=void 0===e.ie||e.ie>=11?"<"+w.taDefaultWrap+">
    ":e.ie<=8?"<"+w.taDefaultWrap.toUpperCase()+"> ":"<"+w.taDefaultWrap+"> "),E.$options||(E.$options={});var S=q(C),T=function(a){if(S(a))return a;var b=angular.element("
    "+a+"
    ");if(0===b.children().length)a="<"+w.taDefaultWrap+">"+a+"";else{var c,d=b[0].childNodes,e=!1;for(c=0;c";else if("#text"===h){var i=f.textContent;a+=i.trim()?"<"+w.taDefaultWrap+">"+i+"":i}else if(h.match(g))a+=f.outerHTML;else{var j=f.outerHTML||f.nodeValue;a+=""!==j.trim()?"<"+w.taDefaultWrap+">"+j+"":j}}else a="<"+w.taDefaultWrap+">"+a+""}return a};w.taPaste&&(A=s(w.taPaste)),v.addClass("ta-bind");var U;j["$undoManager"+(w.id||"")]=D.$undoManager={_stack:[],_index:0,_max:1e3,push:function(a){return"undefined"==typeof a||null===a||"undefined"!=typeof this.current()&&null!==this.current()&&a===this.current()?a:(this._indexthis._max&&this._stack.shift(),this._index=this._stack.length-1,a)},undo:function(){return this.setToIndex(this._index-1)},redo:function(){return this.setToIndex(this._index+1)},setToIndex:function(a){return 0>a||a>this._stack.length-1?void 0:(this._index=a,this.current())},current:function(){return this._stack[this._index]}};var V,W=j["$undoTaBind"+(w.id||"")]=function(){if(!H&&F){var a=D.$undoManager.undo();"undefined"!=typeof a&&null!==a&&(ka(a),Z(a,!1),V&&c.cancel(V),V=c(function(){v[0].focus(),m.setSelectionToElementEnd(v[0])},1))}},X=j["$redoTaBind"+(w.id||"")]=function(){if(!H&&F){var a=D.$undoManager.redo();"undefined"!=typeof a&&null!==a&&(ka(a),Z(a,!1),V&&c.cancel(V),V=c(function(){v[0].focus(),m.setSelectionToElementEnd(v[0])},1))}},Y=function(){if(F)return v[0].innerHTML;if(G)return v.val();throw"textAngular Error: attempting to update non-editable taBind"},Z=function(a,b,c){J=c||!1,("undefined"==typeof b||null===b)&&(b=F),("undefined"==typeof a||null===a)&&(a=Y()),S(a)?(""!==D.$viewValue&&D.$setViewValue(""),b&&""!==D.$undoManager.current()&&D.$undoManager.push("")):(ja(),D.$viewValue!==a&&(D.$setViewValue(a),b&&D.$undoManager.push(a))),D.$render()};j["updateTaBind"+(w.id||"")]=function(){H||Z(void 0,void 0,!0)};var $=function(a){return D.$oldViewValue=b(h(a),D.$oldViewValue,K)};if(v.attr("required")&&(D.$validators.required=function(a,b){return!S(a||b)}),D.$parsers.push($),D.$parsers.unshift(T),D.$formatters.push($),D.$formatters.unshift(T),D.$formatters.unshift(function(a){return D.$undoManager.push(a||"")}),G)if(j.events={},F){var _=!1,aa=function(a){var d=a.match(/content=["']*OneNote.File/i);if(a&&a.trim().length){if(a.match(/class=["']*Mso(Normal|List)/i)||a.match(/content=["']*Word.Document/i)||a.match(/content=["']*OneNote.File/i)){var e=a.match(/([\s\S]*?)/i);e=e?e[1]:a,e=e.replace(/[\s\S]*?<\/o:p>/gi,"").replace(/class=(["']|)MsoNormal(["']|)/gi,"");var f=angular.element("
    "+e+"
    "),g=angular.element("
    "),h={element:null,lastIndent:[],lastLi:null,isUl:!1};h.lastIndent.peek=function(){var a=this.length;return a>0?this[a-1]:void 0};for(var i=function(a){h.isUl=a,h.element=angular.element(a?"
      ":"
        "),h.lastIndent=[],h.lastIndent.peek=function(){var a=this.length;return a>0?this[a-1]:void 0},h.lastLevelMatch=null},k=0;k<=f[0].childNodes.length;k++)if(f[0].childNodes[k]&&"#text"!==f[0].childNodes[k].nodeName){var l=f[0].childNodes[k].tagName.toLowerCase();if("p"===l||"h1"===l||"h2"===l||"h3"===l||"h4"===l||"h5"===l||"h6"===l){var n=angular.element(f[0].childNodes[k]),o=(n.attr("class")||"").match(/MsoList(Bullet|Number|Paragraph)(CxSp(First|Middle|Last)|)/i);if(o){if(n[0].childNodes.length<2||n[0].childNodes[1].childNodes.length<1)continue;var p="bullet"===o[1].toLowerCase()||"number"!==o[1].toLowerCase()&&!(/^[^0-9a-z<]*[0-9a-z]+[^0-9a-z<>]]":"
          "),h.lastLi.append(h.element);else if(null!=h.lastIndent.peek()&&h.lastIndent.peek()>r){for(;null!=h.lastIndent.peek()&&h.lastIndent.peek()>r;)if("li"!==h.element.parent()[0].tagName.toLowerCase()){if(!/[uo]l/i.test(h.element.parent()[0].tagName.toLowerCase()))break;h.element=h.element.parent(),h.lastIndent.pop()}else h.element=h.element.parent();h.isUl="ul"===h.element[0].tagName.toLowerCase(),p!==h.isUl&&(i(p),g.append(h.element))}h.lastLevelMatch=s,r!==h.lastIndent.peek()&&h.lastIndent.push(r),h.lastLi=angular.element("
        1. "),h.element.append(h.lastLi),h.lastLi.html(n.html().replace(/[\s\S]*?/gi,"")),n.remove()}else i(!1),g.append(n)}}var u=function(a){a=angular.element(a);for(var b=a[0].childNodes.length-1;b>=0;b--)a.after(a[0].childNodes[b]);a.remove()};angular.forEach(g.find("span"),function(a){a.removeAttribute("lang"),a.attributes.length<=0&&u(a)}),angular.forEach(g.find("font"),u),a=g.html(),d&&(a=g.html()||f.html())}else{if(a=a.replace(/<(|\/)meta[^>]*?>/gi,""),a.match(/<[^>]*?(ta-bind)[^>]*?>/)){if(a.match(/<[^>]*?(text-angular)[^>]*?>/)){var w=angular.element("
          "+a+"
          ");w.find("textarea").remove();for(var x=t.getByAttribute(w,"ta-bind"),y=0;y',"")}}else a.match(/^.<\/span>/gi)||(a=a.replace(/<(|\/)span[^>]*?>/gi,"")));a=a.replace(/
          ]*?>/gi,"").replace(/( | )<\/span>/gi," ")}//i.test(a)&&/(|).*/i.test(a)===!1&&(a=a.replace(/.*<\/li(\s.*)?>/i,"
            $&
          ")),a=a.replace(/^[ |\u00A0]+/gm,function(a){for(var b="",c=0;c").replace(/\t/g,"    "),A&&(a=A(j,{$html:a})||a),a=b(a,"",K),m.insertHtml(a,v[0]),c(function(){D.$setViewValue(Y()),_=!1,v.removeClass("processing-paste")},0)}else _=!1,v.removeClass("processing-paste")};v.on("paste",j.events.paste=function(b,e){if(e&&angular.extend(b,e),H||_)return b.stopPropagation(),b.preventDefault(),!1;_=!0,v.addClass("processing-paste");var f,g=(b.originalEvent||b).clipboardData;if(g&&g.getData&&g.types.length>0){for(var h="",i=0;i
    ');d.find("body").append(k),k[0].focus(),c(function(){a.restoreSelection(j),aa(k[0].innerHTML),v[0].focus(),k.remove()},0)}),v.on("cut",j.events.cut=function(a){H?a.preventDefault():c(function(){D.$setViewValue(Y())},0)}),v.on("keydown",j.events.keydown=function(a,b){b&&angular.extend(a,b),a.specialKey=y(a);var c;if(p.keyMappings.forEach(function(b){a.specialKey===b.commandKeyCode&&(a.specialKey=void 0),b.testForKey(a)&&(c=b.commandKeyCode),("UndoKey"===b.commandKeyCode||"RedoKey"===b.commandKeyCode)&&(b.enablePropagation||a.preventDefault())}),"undefined"!=typeof c&&(a.specialKey=c),"undefined"==typeof a.specialKey||"UndoKey"===a.specialKey&&"RedoKey"===a.specialKey||(a.preventDefault(),u.sendKeyCommand(j,a)),!H&&("UndoKey"===a.specialKey&&(W(),a.preventDefault()),"RedoKey"===a.specialKey&&(X(),a.preventDefault()),13===a.keyCode&&!a.shiftKey)){var d,e=function(a,b){for(var c=0;c$/i.test(f.innerHTML.trim())&&!f.nextSibling){d=angular.element(f);var k=d.parent();k.after(g),d.remove(),0===k.children().length&&k.remove(),m.setSelectionToElementStart(g[0]),a.preventDefault()}/^<[^>]+><\/[^>]+>$/i.test(f.innerHTML.trim())&&(d=angular.element(f),d.after(g),d.remove(),m.setSelectionToElementStart(g[0]),a.preventDefault())}}});var ba;if(v.on("keyup",j.events.keyup=function(a,b){if(b&&angular.extend(a,b),9===a.keyCode){var d=m.getSelection();return void(d.start.element===v[0]&&v.children().length&&m.setSelectionToElementStart(v.children()[0]))}if(U&&c.cancel(U),!H&&!L.test(a.keyCode)){if(""!==B&&13===a.keyCode&&!a.shiftKey){for(var e=m.getSelectionElement();!e.tagName.match(i)&&e!==v[0];)e=e.parentNode;if(e.tagName.toLowerCase()!==w.taDefaultWrap&&"li"!==e.tagName.toLowerCase()&&(""===e.innerHTML.trim()||"
    "===e.innerHTML.trim())){var f=angular.element(B);angular.element(e).replaceWith(f),m.setSelectionToElementStart(f[0])}}var g=Y();""!==B&&""===g.trim()?(ka(B),m.setSelectionToElementStart(v.children()[0])):"<"!==g.substring(0,1)&&""!==w.taDefaultWrap;var h=z!==a.keyCode&&M.test(a.keyCode);ba&&c.cancel(ba),ba=c(function(){Z(g,h,!0)},E.$options.debounce||400),h||(U=c(function(){D.$undoManager.push(g)},250)),z=a.keyCode}}),v.on("blur",j.events.blur=function(){I=!1,H?(J=!0,D.$render()):Z(void 0,void 0,!0)}),w.placeholder&&(e.ie>8||void 0===e.ie)){var ca;if(!w.id)throw"textAngular Error: An unique ID is required for placeholders to work";ca=k("#"+w.id+".placeholder-text:before",'content: "'+w.placeholder+'"'),j.$on("$destroy",function(){l(ca)})}v.on("focus",j.events.focus=function(){I=!0,v.removeClass("placeholder-text"),ja()}),v.on("mouseup",j.events.mouseup=function(){var a=m.getSelection();a.start.element===v[0]&&v.children().length&&m.setSelectionToElementStart(v.children()[0])}),v.on("mousedown",j.events.mousedown=function(a,b){b&&angular.extend(a,b),a.stopPropagation()})}else{v.on("change blur",j.events.change=j.events.blur=function(){H||D.$setViewValue(Y())}),v.on("keydown",j.events.keydown=function(a,b){if(b&&angular.extend(a,b),9===a.keyCode){var c=this.selectionStart,d=this.selectionEnd,e=v.val();if(a.shiftKey){var f=e.lastIndexOf("\n",c),g=e.lastIndexOf(" ",c);-1!==g&&g>=f&&(v.val(e.substring(0,g)+e.substring(g+1)),this.selectionStart=this.selectionEnd=c-1)}else v.val(e.substring(0,c)+" "+e.substring(d)),this.selectionStart=this.selectionEnd=c+1;a.preventDefault()}});var da=function(a,b){for(var c="",d=0;b>d;d++)c+=a;return c},ea=function(a,b,c){for(var d=0;d"):"#text"===e?void(c+=d.textContent):void(d.outerHTML&&(c+="ul"===e||"ol"===e?"\n"+fa(d,b):"\n"+da(" ",b)+d.outerHTML))}),c+="\n"+da(" ",b-1)+a.outerHTML.substring(a.outerHTML.lastIndexOf("<"))};D.$formatters.unshift(function(a){var b=angular.element("
    "+a+"
    ")[0].childNodes;return b.length>0&&(a="",ea(b,function(b,c){var d=c.nodeName.toLowerCase();return"#comment"===d?void(a+=""):"#text"===d?void(a+=c.textContent):void(c.outerHTML&&(a.length>0&&(a+="\n"),a+="ul"===d||"ol"===d?""+fa(c,0):""+c.outerHTML))})),a})}var ga,ha=function(a){return j.$emit("ta-element-select",this),a.preventDefault(),!1},ia=function(a,b){if(b&&angular.extend(a,b),!r&&!H){r=!0;var d;d=a.originalEvent?a.originalEvent.dataTransfer:a.dataTransfer,j.$emit("ta-drop-event",this,a,d),c(function(){r=!1,Z(void 0,void 0,!0)},100)}},ja=j["reApplyOnSelectorHandlers"+(w.id||"")]=function(){H||angular.forEach(n,function(a){v.find(a).off("click",ha).on("click",ha)})},ka=function(a){v[0].innerHTML=a},la=!1;D.$render=function(){if(!la){la=!0;var a=D.$viewValue||"";J||(F&&I&&(v.removeClass("placeholder-text"),ga&&c.cancel(ga),ga=c(function(){I||(v[0].focus(),m.setSelectionToElementEnd(v.children()[v.children().length-1])),ga=void 0},1)),F?(ka(w.placeholder?""===a?B:a:""===a?B:a),H?v.off("drop",ia):(ja(),v.on("drop",ia))):"textarea"!==v[0].tagName.toLowerCase()&&"input"!==v[0].tagName.toLowerCase()?ka(o(a)):v.val(a)),F&&w.placeholder&&(""===a?I?v.removeClass("placeholder-text"):v.addClass("placeholder-text"):v.removeClass("placeholder-text")),la=J=!1}},w.taReadonly&&(H=j.$eval(w.taReadonly),H?(v.addClass("ta-readonly"),("textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase())&&v.attr("disabled","disabled"),void 0!==v.attr("contenteditable")&&v.attr("contenteditable")&&v.removeAttr("contenteditable")):(v.removeClass("ta-readonly"),"textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase()?v.removeAttr("disabled"):F&&v.attr("contenteditable","true")),j.$watch(w.taReadonly,function(a,b){b!==a&&(a?(v.addClass("ta-readonly"),("textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase())&&v.attr("disabled","disabled"),void 0!==v.attr("contenteditable")&&v.attr("contenteditable")&&v.removeAttr("contenteditable"),angular.forEach(n,function(a){v.find(a).on("click",ha)}),v.off("drop",ia)):(v.removeClass("ta-readonly"),"textarea"===v[0].tagName.toLowerCase()||"input"===v[0].tagName.toLowerCase()?v.removeAttr("disabled"):F&&v.attr("contenteditable","true"),angular.forEach(n,function(a){v.find(a).off("click",ha)}),v.on("drop",ia)),H=a)})),F&&!H&&(angular.forEach(n,function(a){v.find(a).on("click",ha)}),v.on("drop",ia),v.on("blur",function(){e.webkit&&(f=!0)}))}}}]);var r=!1,s=angular.module("textAngular",["ngSanitize","textAngularSetup","textAngular.factories","textAngular.DOM","textAngular.validators","textAngular.taBind"]);return s.config([function(){angular.forEach(d,function(a,b){delete d[b]})}]),s.directive("textAngular",["$compile","$timeout","taOptions","taSelection","taExecCommand","textAngularManager","$document","$animate","$log","$q","$parse",function(b,c,d,e,f,g,h,i,j,k,l){return{require:"?ngModel",scope:{},restrict:"EA",priority:2,link:function(m,n,o,p){var q,r,s,t,u,v,w,x,y,z,A,B=o.serial?o.serial:Math.floor(1e16*Math.random());m._name=o.name?o.name:"textAngularEditor"+B;var C=function(a,b,d){c(function(){var c=function(){a.off(b,c),d.apply(this,arguments)};a.on(b,c)},100)};if(y=f(o.taDefaultWrap),angular.extend(m,angular.copy(d),{wrapSelection:function(a,b,c){"undo"===a.toLowerCase()?m["$undoTaBindtaTextElement"+B]():"redo"===a.toLowerCase()?m["$redoTaBindtaTextElement"+B]():(y(a,!1,b,m.defaultTagAttributes),c&&m["reApplyOnSelectorHandlerstaTextElement"+B](),m.displayElements.text[0].focus())},showHtml:m.$eval(o.taShowHtml)||!1}),o.taFocussedClass&&(m.classes.focussed=o.taFocussedClass),o.taTextEditorClass&&(m.classes.textEditor=o.taTextEditorClass),o.taHtmlEditorClass&&(m.classes.htmlEditor=o.taHtmlEditorClass),o.taDefaultTagAttributes)try{angular.extend(m.defaultTagAttributes,angular.fromJson(o.taDefaultTagAttributes))}catch(D){j.error(D)}o.taTextEditorSetup&&(m.setup.textEditorSetup=m.$parent.$eval(o.taTextEditorSetup)),o.taHtmlEditorSetup&&(m.setup.htmlEditorSetup=m.$parent.$eval(o.taHtmlEditorSetup)),o.taFileDrop?m.fileDropHandler=m.$parent.$eval(o.taFileDrop):m.fileDropHandler=m.defaultFileDropHandler,w=n[0].innerHTML,n[0].innerHTML="",m.displayElements={forminput:angular.element(""),html:angular.element(""),text:angular.element("
    "),scrollWindow:angular.element("
    "),popover:angular.element('
    '),popoverArrow:angular.element('
    '),popoverContainer:angular.element('
    '),resize:{overlay:angular.element('
    '),background:angular.element('
    '),anchors:[angular.element('
    '),angular.element('
    '),angular.element('
    '),angular.element('
    ')],info:angular.element('
    ')}},m.displayElements.popover.append(m.displayElements.popoverArrow),m.displayElements.popover.append(m.displayElements.popoverContainer),m.displayElements.scrollWindow.append(m.displayElements.popover),m.displayElements.popover.on("mousedown",function(a,b){return b&&angular.extend(a,b),a.preventDefault(),!1}),m.showPopover=function(a){m.displayElements.popover.css("display","block"),m.reflowPopover(a),i.addClass(m.displayElements.popover,"in"),C(h.find("body"),"click keyup",function(){m.hidePopover()})},m.reflowPopover=function(a){m.displayElements.text[0].offsetHeight-51>a[0].offsetTop?(m.displayElements.popover.css("top",a[0].offsetTop+a[0].offsetHeight+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("top").addClass("bottom")):(m.displayElements.popover.css("top",a[0].offsetTop-54+m.displayElements.scrollWindow[0].scrollTop+"px"),m.displayElements.popover.removeClass("bottom").addClass("top"));var b=m.displayElements.text[0].offsetWidth-m.displayElements.popover[0].offsetWidth,c=a[0].offsetLeft+a[0].offsetWidth/2-m.displayElements.popover[0].offsetWidth/2;m.displayElements.popover.css("left",Math.max(0,Math.min(b,c))+"px"),m.displayElements.popoverArrow.css("margin-left",Math.min(c,Math.max(0,c-b))-11+"px")},m.hidePopover=function(){m.displayElements.popover.css("display",""),m.displayElements.popoverContainer.attr("style",""),m.displayElements.popoverContainer.attr("class","popover-content"),m.displayElements.popover.removeClass("in")},m.displayElements.resize.overlay.append(m.displayElements.resize.background),angular.forEach(m.displayElements.resize.anchors,function(a){m.displayElements.resize.overlay.append(a)}),m.displayElements.resize.overlay.append(m.displayElements.resize.info),m.displayElements.scrollWindow.append(m.displayElements.resize.overlay),m.reflowResizeOverlay=function(a){a=angular.element(a)[0],m.displayElements.resize.overlay.css({display:"block",left:a.offsetLeft-5+"px",top:a.offsetTop-5+"px",width:a.offsetWidth+10+"px",height:a.offsetHeight+10+"px"}),m.displayElements.resize.info.text(a.offsetWidth+" x "+a.offsetHeight)},m.showResizeOverlay=function(a){var b=h.find("body");z=function(c){var d={width:parseInt(a.attr("width")),height:parseInt(a.attr("height")),x:c.clientX,y:c.clientY};(void 0===d.width||isNaN(d.width))&&(d.width=a[0].offsetWidth),(void 0===d.height||isNaN(d.height))&&(d.height=a[0].offsetHeight),m.hidePopover();var e=d.height/d.width,f=function(b){function c(a){return Math.round(Math.max(0,a))}var f={x:Math.max(0,d.width+(b.clientX-d.x)),y:Math.max(0,d.height+(b.clientY-d.y))},g=void 0!==o.taResizeForceAspectRatio,h=o.taResizeMaintainAspectRatio,i=g||h&&!b.shiftKey;if(i){var j=f.y/f.x;f.x=e>j?f.x:f.y/e,f.y=e>j?f.x*e:f.y}var k=angular.element(a);k.css("height",c(f.y)+"px"),k.css("width",c(f.x)+"px"),m.reflowResizeOverlay(a)};b.on("mousemove",f),C(b,"mouseup",function(a){a.preventDefault(),a.stopPropagation(),b.off("mousemove",f),m.$apply(function(){m.hidePopover(),m.updateTaBindtaTextElement()},100)}),c.stopPropagation(),c.preventDefault()},m.displayElements.resize.anchors[3].off("mousedown"),m.displayElements.resize.anchors[3].on("mousedown",z),m.reflowResizeOverlay(a),C(b,"click",function(){m.hideResizeOverlay()})},m.hideResizeOverlay=function(){m.displayElements.resize.anchors[3].off("mousedown",z),m.displayElements.resize.overlay.css("display","")},m.setup.htmlEditorSetup(m.displayElements.html),m.setup.textEditorSetup(m.displayElements.text),m.displayElements.html.attr({id:"taHtmlElement"+B,"ng-show":"showHtml","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.text.attr({id:"taTextElement"+B,contentEditable:"true","ta-bind":"ta-bind","ng-model":"html","ng-model-options":n.attr("ng-model-options")}),m.displayElements.scrollWindow.attr({"ng-hide":"showHtml"}),o.taDefaultWrap&&m.displayElements.text.attr("ta-default-wrap",o.taDefaultWrap),o.taUnsafeSanitizer&&(m.displayElements.text.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer),m.displayElements.html.attr("ta-unsafe-sanitizer",o.taUnsafeSanitizer)),m.displayElements.scrollWindow.append(m.displayElements.text),n.append(m.displayElements.scrollWindow),n.append(m.displayElements.html),m.displayElements.forminput.attr("name",m._name),n.append(m.displayElements.forminput),o.tabindex&&(n.removeAttr("tabindex"),m.displayElements.text.attr("tabindex",o.tabindex),m.displayElements.html.attr("tabindex",o.tabindex)),o.placeholder&&(m.displayElements.text.attr("placeholder",o.placeholder),m.displayElements.html.attr("placeholder",o.placeholder)),o.taDisabled&&(m.displayElements.text.attr("ta-readonly","disabled"),m.displayElements.html.attr("ta-readonly","disabled"),m.disabled=m.$parent.$eval(o.taDisabled),m.$parent.$watch(o.taDisabled,function(a){m.disabled=a,m.disabled?n.addClass(m.classes.disabled):n.removeClass(m.classes.disabled)})),o.taPaste&&(m._pasteHandler=function(a){return l(o.taPaste)(m.$parent,{$html:a})},m.displayElements.text.attr("ta-paste","_pasteHandler($html)")),b(m.displayElements.scrollWindow)(m),b(m.displayElements.html)(m),m.updateTaBindtaTextElement=m["updateTaBindtaTextElement"+B],m.updateTaBindtaHtmlElement=m["updateTaBindtaHtmlElement"+B],n.addClass("ta-root"),m.displayElements.scrollWindow.addClass("ta-text ta-editor "+m.classes.textEditor),m.displayElements.html.addClass("ta-html ta-editor "+m.classes.htmlEditor),m._actionRunning=!1;var E=!1;if(m.startAction=function(){return m._actionRunning=!0,E=a.saveSelection(),function(){E&&a.restoreSelection(E)}},m.endAction=function(){m._actionRunning=!1,E&&(m.showHtml?m.displayElements.html[0].focus():m.displayElements.text[0].focus(),a.removeMarkers(E)),E=!1,m.updateSelectedStyles(),m.showHtml||m["updateTaBindtaTextElement"+B]()},u=function(){m.focussed=!0,n.addClass(m.classes.focussed),x.focus(),n.triggerHandler("focus")},m.displayElements.html.on("focus",u),m.displayElements.text.on("focus",u),v=function(a){return m._actionRunning||h[0].activeElement===m.displayElements.html[0]||h[0].activeElement===m.displayElements.text[0]||(n.removeClass(m.classes.focussed),x.unfocus(),c(function(){m._bUpdateSelectedStyles=!1,n.triggerHandler("blur"),m.focussed=!1},0)),a.preventDefault(),!1},m.displayElements.html.on("blur",v),m.displayElements.text.on("blur",v),m.displayElements.text.on("paste",function(a){n.triggerHandler("paste",a)}),m.queryFormatBlockState=function(a){return!m.showHtml&&a.toLowerCase()===h[0].queryCommandValue("formatBlock").toLowerCase()},m.queryCommandState=function(a){return m.showHtml?"":h[0].queryCommandState(a)},m.switchView=function(){m.showHtml=!m.showHtml,i.enabled(!1,m.displayElements.html),i.enabled(!1,m.displayElements.text),m.showHtml?c(function(){return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.html[0].focus()},100):c(function(){return i.enabled(!0,m.displayElements.html),i.enabled(!0,m.displayElements.text),m.displayElements.text[0].focus()},100)},o.ngModel){var F=!0;p.$render=function(){if(F){F=!1;var a=m.$parent.$eval(o.ngModel);void 0!==a&&null!==a||!w||""===w||p.$setViewValue(w)}m.displayElements.forminput.val(p.$viewValue),m.html=p.$viewValue||""},n.attr("required")&&(p.$validators.required=function(a,b){var c=a||b;return!(!c||""===c.trim())})}else m.displayElements.forminput.val(w),m.html=w;if(m.$watch("html",function(a,b){a!==b&&(o.ngModel&&p.$viewValue!==a&&p.$setViewValue(a),m.displayElements.forminput.val(a))}),o.taTargetToolbars)x=g.registerEditor(m._name,m,o.taTargetToolbars.split(","));else{var G=angular.element('
    ');o.taToolbar&&G.attr("ta-toolbar",o.taToolbar),o.taToolbarClass&&G.attr("ta-toolbar-class",o.taToolbarClass),o.taToolbarGroupClass&&G.attr("ta-toolbar-group-class",o.taToolbarGroupClass),o.taToolbarButtonClass&&G.attr("ta-toolbar-button-class",o.taToolbarButtonClass),o.taToolbarActiveButtonClass&&G.attr("ta-toolbar-active-button-class",o.taToolbarActiveButtonClass),o.taFocussedClass&&G.attr("ta-focussed-class",o.taFocussedClass),n.prepend(G),b(G)(m.$parent),x=g.registerEditor(m._name,m,["textAngularToolbar"+B])}m.$on("$destroy",function(){g.unregisterEditor(m._name),angular.element(window).off("blur")}),m.$on("ta-element-select",function(a,b){x.triggerElementSelect(a,b)&&m["reApplyOnSelectorHandlerstaTextElement"+B]()}),m.$on("ta-drop-event",function(a,b,d,e){m.displayElements.text[0].focus(),e&&e.files&&e.files.length>0?(angular.forEach(e.files,function(a){try{k.when(m.fileDropHandler(a,m.wrapSelection)||m.fileDropHandler!==m.defaultFileDropHandler&&k.when(m.defaultFileDropHandler(a,m.wrapSelection))).then(function(){m["updateTaBindtaTextElement"+B]()})}catch(b){j.error(b)}}),d.preventDefault(),d.stopPropagation()):c(function(){m["updateTaBindtaTextElement"+B]()},0)}),m._bUpdateSelectedStyles=!1,angular.element(window).on("blur",function(){m._bUpdateSelectedStyles=!1,m.focussed=!1}),m.updateSelectedStyles=function(){var a;A&&c.cancel(A),void 0!==(a=e.getSelectionElement())&&a.parentNode!==m.displayElements.text[0]?x.updateSelectedStyles(angular.element(a)):x.updateSelectedStyles(),m._bUpdateSelectedStyles&&(A=c(m.updateSelectedStyles,200))},q=function(){return m.focussed?void(m._bUpdateSelectedStyles||(m._bUpdateSelectedStyles=!0,m.$apply(function(){m.updateSelectedStyles()}))):void(m._bUpdateSelectedStyles=!1)},m.displayElements.html.on("keydown",q),m.displayElements.text.on("keydown",q),r=function(){m._bUpdateSelectedStyles=!1},m.displayElements.html.on("keyup",r),m.displayElements.text.on("keyup",r),s=function(a,b){b&&angular.extend(a,b),m.$apply(function(){return x.sendKeyCommand(a)?(m._bUpdateSelectedStyles||m.updateSelectedStyles(),a.preventDefault(),!1):void 0})},m.displayElements.html.on("keypress",s),m.displayElements.text.on("keypress",s),t=function(){m._bUpdateSelectedStyles=!1,m.$apply(function(){m.updateSelectedStyles()})},m.displayElements.html.on("mouseup",t),m.displayElements.text.on("mouseup",t)}}}]),s.service("textAngularManager",["taToolExecuteAction","taTools","taRegisterTool",function(a,b,c){var d={},e={};return{registerEditor:function(c,f,g){if(!c||""===c)throw"textAngular Error: An editor requires a name";if(!f)throw"textAngular Error: An editor requires a scope";if(e[c])throw'textAngular Error: An Editor with name "'+c+'" already exists';var h=[];return angular.forEach(g,function(a){d[a]&&h.push(d[a])}),e[c]={scope:f,toolbars:g,_registerToolbar:function(a){this.toolbars.indexOf(a.name)>=0&&h.push(a)},editorFunctions:{disable:function(){angular.forEach(h,function(a){a.disabled=!0})},enable:function(){angular.forEach(h,function(a){a.disabled=!1})},focus:function(){angular.forEach(h,function(a){a._parent=f,a.disabled=!1,a.focussed=!0,f.focussed=!0})},unfocus:function(){angular.forEach(h,function(a){a.disabled=!0,a.focussed=!1}),f.focussed=!1},updateSelectedStyles:function(a){angular.forEach(h,function(b){angular.forEach(b.tools,function(c){c.activeState&&(b._parent=f,c.active=c.activeState(a))})})},sendKeyCommand:function(c){var d=!1;return(c.ctrlKey||c.metaKey||c.specialKey)&&angular.forEach(b,function(b,e){if(b.commandKeyCode&&(b.commandKeyCode===c.which||b.commandKeyCode===c.specialKey))for(var g=0;g0)for(var k=0;k"),b&&b["class"]?d.addClass(b["class"]):d.addClass(g.classes.toolbarButton),d.attr("name",c.name),d.attr("ta-button","ta-button"),d.attr("ng-disabled","isDisabled()"),d.attr("tabindex","-1"),d.attr("ng-click","executeAction()"),d.attr("ng-class","displayActiveToolClass(active)"),b&&b.tooltiptext&&d.attr("title",b.tooltiptext),b&&!b.display&&!c._display&&(d[0].innerHTML="",b.buttontext&&(d[0].innerHTML=b.buttontext),b.iconclass)){var e=angular.element(""),f=d[0].innerHTML;e.addClass(b.iconclass),d[0].innerHTML="",d.append(e),f&&""!==f&&d.append(" "+f)}return c._lastToolDefinition=angular.copy(b),a(d)(c)};g.tools={},g._parent={disabled:!0,showHtml:!1,queryFormatBlockState:function(){return!1},queryCommandState:function(){return!1}};var k={$window:f,$editor:function(){return g._parent},isDisabled:function(){return"function"!=typeof this.$eval("disabled")&&this.$eval("disabled")||this.$eval("disabled()")||"html"!==this.name&&this.$editor().showHtml||this.$parent.disabled||this.$editor().disabled},displayActiveToolClass:function(a){return a?g.classes.toolbarButtonActive:""},executeAction:e};angular.forEach(g.toolbar,function(a){var b=angular.element("
    ");b.addClass(g.classes.toolbarGroup),angular.forEach(a,function(a){g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]),b.append(g.tools[a].$element)}),h.append(b)}),g.updateToolDisplay=function(a,b,c){var d=g.tools[a];if(d){if(d._lastToolDefinition&&!c&&(b=angular.extend({},d._lastToolDefinition,b)),null===b.buttontext&&null===b.iconclass&&null===b.display)throw'textAngular Error: Tool Definition for updating "'+a+'" does not have a valid display/iconclass/buttontext value';null===b.buttontext&&delete b.buttontext,null===b.iconclass&&delete b.iconclass,null===b.display&&delete b.display;var e=j(b,d);d.$element.replaceWith(e),d.$element=e}},g.addTool=function(a,b,c,e){g.tools[a]=angular.extend(g.$new(!0),d[a],k,{name:a}),g.tools[a].$element=j(d[a],g.tools[a]);var f;void 0===c&&(c=g.toolbar.length-1),f=angular.element(h.children()[c]),void 0===e?(f.append(g.tools[a].$element),g.toolbar[c][g.toolbar[c].length-1]=a):(f.children().eq(e).after(g.tools[a].$element),g.toolbar[c][e]=a)},b.registerToolbar(g),g.$on("$destroy",function(){b.unregisterToolbar(g.name)})}}}]),s.name}); From ddcfe1535a1078cf23511e579ffc9147cef2be45 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 11 May 2016 10:52:50 +1000 Subject: [PATCH 130/154] Making TagRule autocomplete available to models other than Customer Incorporate ng admin.tags module and rails TagController into existing admin.tagRules module + TagRuleController --- .../customers_controller.js.coffee | 4 +-- .../admin/customers/customers.js.coffee | 2 +- .../services/tags_resource.js.coffee | 9 ----- .../shipping_method_controller.js.coffee | 10 +++++- .../shipping_methods.js.coffee | 2 +- .../services/tag_rule_resource.js.coffee | 10 ++++++ .../admin/tag_rules/tag_rules.js.coffee | 2 +- .../javascripts/shared/ng-tags-input.min.js | 2 +- app/controllers/admin/customers_controller.rb | 10 +++--- app/controllers/admin/tag_rules_controller.rb | 33 +++++++++++++++++++ app/controllers/admin/tags_controller.rb | 28 ---------------- app/models/enterprise.rb | 14 -------- app/models/spree/ability_decorator.rb | 3 +- app/models/tag_rule.rb | 15 +++++++++ .../replace_form_fields.html.haml.deface | 2 +- .../api/admin/customer_serializer.rb | 5 ++- config/routes.rb | 4 ++- spec/javascripts/application_spec.js | 3 +- .../customers_controller_spec.js.coffee | 2 +- .../admin/customer_serializer_spec.rb | 3 +- 20 files changed, 89 insertions(+), 74 deletions(-) delete mode 100644 app/assets/javascripts/admin/customers/services/tags_resource.js.coffee create mode 100644 app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee delete mode 100644 app/controllers/admin/tags_controller.rb diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index bfccfd3f4b..763e92b400 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) -> +angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagRuleResource, $q, Columns, pendingChanges, shops) -> $scope.shop = {} $scope.shops = shops $scope.submitAll = pendingChanges.submitAll @@ -16,7 +16,7 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerR defer = $q.defer() params = enterprise_id: $scope.shop.id - TagsResource.index params, (data) => + TagRuleResource.mapByTag params, (data) => filtered = data.filter (tag) -> tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 defer.resolve filtered diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee index 1e8ae9b988..33be58c9ac 100644 --- a/app/assets/javascripts/admin/customers/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -1 +1 @@ -angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) \ No newline at end of file +angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) \ No newline at end of file diff --git a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee b/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee deleted file mode 100644 index ad89511e32..0000000000 --- a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee +++ /dev/null @@ -1,9 +0,0 @@ -angular.module("admin.customers").factory 'TagsResource', ($resource) -> - $resource('/admin/tags.json', {}, { - 'index': - method: 'GET' - isArray: true - cache: true - params: - enterprise_id: '@enterprise_id' - }) diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee index cc7bd4ee3e..67c53d4b66 100644 --- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee @@ -1,2 +1,10 @@ -angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) -> +angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod, TagRuleResource, $q) -> $scope.shippingMethod = shippingMethod + + $scope.findTags = (query) -> + defer = $q.defer() + TagRuleResource.mapByTag (data) => + filtered = data.filter (tag) -> + tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 + defer.resolve filtered + defer.promise diff --git a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee index 863163a9ef..46537b303e 100644 --- a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee @@ -1 +1 @@ -angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates']) +angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'admin.tagRules', 'templates']) diff --git a/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee new file mode 100644 index 0000000000..d4a6c45ba4 --- /dev/null +++ b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee @@ -0,0 +1,10 @@ +angular.module("admin.tagRules").factory 'TagRuleResource', ($resource) -> + $resource('/admin/tag_rules/:action.json', {}, { + 'mapByTag': + method: 'GET' + isArray: true + cache: true + params: + action: 'map_by_tag' + enterprise_id: '@enterprise_id' + }) diff --git a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee index 88c7734c33..d9a4b2bbfd 100644 --- a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee +++ b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee @@ -1 +1 @@ -angular.module("admin.tagRules", ['ngTagsInput']) +angular.module("admin.tagRules", ['ngResource', 'ngTagsInput']) diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js index 9a1acd6e0d..cf7b0a02a0 100644 --- a/app/assets/javascripts/shared/ng-tags-input.min.js +++ b/app/assets/javascripts/shared/ng-tags-input.min.js @@ -1 +1 @@ -/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); \ No newline at end of file +/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index 24c26661e5..9d3bb18017 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -7,11 +7,8 @@ module Admin respond_to do |format| format.html format.json do - serialised = ActiveModel::ArraySerializer.new( - @collection, - each_serializer: Api::Admin::CustomerSerializer, - spree_current_user: spree_current_user) - render json: serialised.to_json + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id])) + render_as_json @collection, tag_rule_mapping: tag_rule_mapping end end end @@ -20,7 +17,8 @@ module Admin @customer = Customer.new(params[:customer]) if user_can_create_customer? @customer.save - render json: Api::Admin::CustomerSerializer.new(@customer).to_json + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise)) + render_as_json @customer, tag_rule_mapping: tag_rule_mapping else redirect_to '/unauthorized' end diff --git a/app/controllers/admin/tag_rules_controller.rb b/app/controllers/admin/tag_rules_controller.rb index 7d60cb4888..25f5b177d6 100644 --- a/app/controllers/admin/tag_rules_controller.rb +++ b/app/controllers/admin/tag_rules_controller.rb @@ -6,5 +6,38 @@ module Admin respond_override destroy: { json: { success: lambda { render nothing: true, :status => 204 } } } + + def map_by_tag + respond_to do |format| + format.json do + serialiser = ActiveModel::ArraySerializer.new(collection) + render json: serialiser.to_json + end + end + end + + + private + + def collection_actions + [:index, :map_by_tag] + end + + def collection + case action + when :map_by_tag + TagRule.mapping_for(enterprises).values + else + TagRule.for(enterprises.pluck(&:id)) + end + end + + def enterprises + if params[:enterprise_id] + Enterprise.managed_by(spree_current_user).where(id: params[:enterprise_id]) + else + Enterprise.managed_by(spree_current_user) + end + end end end diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb deleted file mode 100644 index 3ab5685ffe..0000000000 --- a/app/controllers/admin/tags_controller.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Admin - class TagsController < Spree::Admin::BaseController - respond_to :json - - def index - respond_to do |format| - format.json do - serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise) - render json: serialiser.to_json - end - end - end - - private - - def enterprise - Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id]) - end - - def tags_of_enterprise - return [] unless enterprise - tag_rule_map = enterprise.rules_per_tag - tag_rule_map.keys.map do |tag| - { text: tag, rules: tag_rule_map[tag] } - end - end - end -end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index b7a5e3189a..9f839f9d08 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -351,20 +351,6 @@ class Enterprise < ActiveRecord::Base end end - def rules_per_tag - tag_rule_map = {} - tag_rules.each do |rule| - rule.preferred_customer_tags.split(",").each do |tag| - if tag_rule_map[tag] - tag_rule_map[tag] += 1 - else - tag_rule_map[tag] = 1 - end - end - end - tag_rule_map - end - protected def devise_mailer diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index e250d1341d..e1995b3c6f 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -72,7 +72,7 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty - can [:admin, :destroy], TagRule do |tag_rule| + can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule| user.enterprises.include? tag_rule.enterprise end @@ -218,7 +218,6 @@ class AbilityDecorator can [:create], Customer can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) - can [:admin, :index], :tag end diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb index 6e6405a589..8d385673db 100644 --- a/app/models/tag_rule.rb +++ b/app/models/tag_rule.rb @@ -9,6 +9,8 @@ class TagRule < ActiveRecord::Base attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags + scope :for, lambda { |enterprises| where(enterprise_id: enterprises) } + def set_context(subject, context) @subject = subject @context = context @@ -24,6 +26,19 @@ class TagRule < ActiveRecord::Base end end + def self.mapping_for(enterprises) + self.for(enterprises).inject({}) do |mapping, rule| + rule.preferred_customer_tags.split(",").each do |tag| + if mapping[tag] + mapping[tag][:rules] += 1 + else + mapping[tag] = { text: tag, rules: 1 } + end + end + mapping + end + end + private def relevant? diff --git a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface index a2f9ed766a..3cffbbabcb 100644 --- a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface +++ b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface @@ -54,7 +54,7 @@ = f.label :tags, t(:tags) .omega.eight.columns = f.hidden_field :tag_list, "ng-value" => "shippingMethod.tag_list" - %tags-with-translation#something{ object: "shippingMethod" } + %tags-with-translation#something{ object: "shippingMethod", 'find-tags' => 'findTags(query)' } .row .alpha.eleven.columns diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 052f49a917..4634625c11 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -6,10 +6,9 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer end def tags - tag_rule_map = object.enterprise.rules_per_tag object.tag_list.map do |tag| - { text: tag, rules: tag_rule_map[tag] } + tag_rule_map = options[:tag_rule_mapping][tag] + tag_rule_map || { text: tag, rules: nil } end end - end diff --git a/config/routes.rb b/config/routes.rb index e240f901a8..c9b81f8caf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -117,7 +117,9 @@ Openfoodnetwork::Application.routes.draw do resources :customers, only: [:index, :create, :update, :destroy] - resources :tags, only: [:index], format: :json + resources :tag_rules, only: [], format: :json do + get :map_by_tag, on: :collection + end resource :content diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js index 94f56a20c5..37c56918d4 100644 --- a/spec/javascripts/application_spec.js +++ b/spec/javascripts/application_spec.js @@ -9,8 +9,9 @@ //= require angular-flash.min.js //= require shared/ng-tags-input.min.js //= require shared/mm-foundation-tpls-0.8.0.min.js -//= require textAngular.min.js +//= require textAngular-rangy.min.js //= require textAngular-sanitize.min.js +//= require textAngular.min.js //= require moment angular.module('templates', []) diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 1da89f2053..b0991c7df1 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -56,7 +56,7 @@ describe "CustomersCtrl", -> { text: 'three' } ] beforeEach -> - http.expectGET('/admin/tags.json?enterprise_id=1').respond 200, tags + http.expectGET('/admin/tag_rules/map_by_tag.json?enterprise_id=1').respond 200, tags it "retrieves the tag list", -> promise = scope.findTags('') diff --git a/spec/serializers/admin/customer_serializer_spec.rb b/spec/serializers/admin/customer_serializer_spec.rb index d63f00d4aa..b8e43f5fcc 100644 --- a/spec/serializers/admin/customer_serializer_spec.rb +++ b/spec/serializers/admin/customer_serializer_spec.rb @@ -3,7 +3,8 @@ describe Api::Admin::CustomerSerializer do let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") } it "serializes a customer" do - serializer = Api::Admin::CustomerSerializer.new customer + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: customer.enterprise_id)) + serializer = Api::Admin::CustomerSerializer.new customer, tag_rule_mapping: tag_rule_mapping result = JSON.parse(serializer.to_json) expect(result['email']).to eq customer.email tags = result['tags'] From efaf728737bc975589e60c8e3d2c46ad8b3a50d3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 11 May 2016 11:04:39 +1000 Subject: [PATCH 131/154] Upgrading ngTagsInput --- app/assets/javascripts/shared/ng-tags-input.min.js | 2 +- app/assets/stylesheets/shared/ng-tags-input.min.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js index cf7b0a02a0..9af98627db 100644 --- a/app/assets/javascripts/shared/ng-tags-input.min.js +++ b/app/assets/javascripts/shared/ng-tags-input.min.js @@ -1 +1 @@ -/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); +/*! ngTagsInput v3.0.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",text:"=?",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&",onTagClicked:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tagList.items},getCurrentTagText:function(){return a.newTag.text()},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",l.items.length<=n.maxTags),i.$setValidity("minTags",l.items.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text())},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:function(a){return angular.isDefined(a)?(b.text=a,void m.trigger("input-change",a)):b.text||""},invalid:null},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){a?(l.items=h.makeObjectArray(a,n.displayProperty),b.tags=l.items):l.items=[]}),b.$watch("tags.length",function(){j(),i.$validate()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}},tag:{click:function(a){m.trigger("tag-clicked",{$tag:a})}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-clicked",b.onTagClicked).on("tag-added",function(){b.newTag.text("")}).on("tag-added tag-removed",function(){b.tags=l.items,i.$setDirty()}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text()),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,i=c.keyCode,j={};if(!h.isModifierOn(c)&&-1!==k.indexOf(i)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[i],e=(i===a.backspace||i===a["delete"])&&l.selected,g=i===a.backspace&&0===b.newTag.text().length&&n.enableEditingLastTag,f=(i===a.backspace||i===a.left||i===a.right)&&0===b.newTag.text().length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text());else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.text(m[n.displayProperty])}else e?l.removeSelected():f&&(i===a.left||i===a.backspace?l.selectPrior():i===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,h=[a.enter,a.tab,a.escape,a.up,a.down],j=b.suggestionList,k=e.registerAutocomplete(),l=b.options,m=b.events;l.tagsInput=k.getOptions(),f=function(a){return a&&a.length>=l.minLength||!a&&l.loadOnEmpty},b.addSuggestionByIndex=function(a){j.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return j.selected&&(k.addTag(angular.copy(j.selected)),j.reset(),k.focusInput(),a=!0),a},b.track=function(a){return a[l.tagsInput.keyProperty||l.tagsInput.displayProperty]},k.on("tag-added tag-removed invalid-tag input-blur",function(){j.reset()}).on("input-change",function(a){f(a)?j.load(a,k.getTags()):j.reset()}).on("input-focus",function(){var a=k.getCurrentTagText();l.loadOnFocus&&f(a)&&j.load(a,k.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(!g.isModifierOn(c)&&-1!==h.indexOf(d))return j.visible?d===a.down?(j.selectNext(),e=!0):d===a.up?(j.selectPrior(),e=!0):d===a.escape?(j.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(j.load(k.getCurrentTagText(),k.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),m.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){if(!angular.isArray(a)||0===a.length||angular.isObject(a[0]))return a;var c=[];return a.forEach(function(a){var d={};d[b]=a,c.push(d)}),c},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.isModifierOn=function(a){return a.shiftKey||a.ctrlKey||a.altKey||a.metaKey},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",'
    '),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",'
    '),a.put("ngTagsInput/auto-complete-match.html",'')}])}(); diff --git a/app/assets/stylesheets/shared/ng-tags-input.min.css b/app/assets/stylesheets/shared/ng-tags-input.min.css index ee4a4a98d5..22dcf47d66 100755 --- a/app/assets/stylesheets/shared/ng-tags-input.min.css +++ b/app/assets/stylesheets/shared/ng-tags-input.min.css @@ -1 +1 @@ -tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff} \ No newline at end of file +tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .input.invalid-tag,tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0 0 0 5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff} From 6bfe1be04547727aa00f8187a52ca233ed61baa0 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 11 May 2016 11:39:54 +1000 Subject: [PATCH 132/154] Update spec looking for matching img src --- spec/features/consumer/shopping/shopping_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index dceecd43f8..9b03259ec5 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -29,7 +29,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path page.should have_text distributor.name find("#tab_about a").click - first("distributor img")['src'].should == distributor.logo.url(:thumb) + first("distributor img")['src'].should include distributor.logo.url(:thumb) end it "shows the producers for a distributor" do From 2c9697ff4e9723688293e73c98b1d2d79f8c36aa Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 11 May 2016 14:30:41 +1000 Subject: [PATCH 133/154] Adding -nc option to wget of PhantomJS, so that we only download it if needed --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 04e7100f44..f914edbbb6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,15 +26,15 @@ env: - CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true" before_script: - - mkdir travis-phantomjs || true - - wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 - - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs - - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH - - cp config/database.travis.yml config/database.yml - cp config/application.yml.example config/application.yml - RAILS_ENV=test bundle exec rake db:create db:schema:load + - mkdir -p travis-phantomjs + - wget -nc https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 || true + - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs + - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH + - > if [ "$KARMA" = "true" ]; then npm install -g npm@'3.8.8' From 7e932f091d046affd89d6717c6f877de831657d4 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 11 May 2016 15:19:28 +1000 Subject: [PATCH 134/154] Use have_current_path rather than current_path.should == --- spec/features/admin/authentication_spec.rb | 6 +++--- spec/features/admin/cms_spec.rb | 4 ++-- spec/features/admin/orders_spec.rb | 4 ++-- spec/features/admin/payment_method_spec.rb | 2 +- spec/features/consumer/authentication_spec.rb | 2 +- spec/features/consumer/shops_spec.rb | 4 ++-- spec/support/request/web_helper.rb | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/features/admin/authentication_spec.rb b/spec/features/admin/authentication_spec.rb index 8473551c99..fe2eb156c5 100644 --- a/spec/features/admin/authentication_spec.rb +++ b/spec/features/admin/authentication_spec.rb @@ -16,14 +16,14 @@ feature "Authentication", js: true do fill_in "Email", with: user.email fill_in "Password", with: user.password click_login_button - page.should have_content "DASHBOARD" - current_path.should == spree.admin_path + expect(page).to have_content "DASHBOARD" + expect(page).to have_current_path spree.admin_path end end scenario "viewing my account" do login_to_admin_section click_link "Account" - current_path.should == spree.account_path + expect(page).to have_current_path spree.account_path end end diff --git a/spec/features/admin/cms_spec.rb b/spec/features/admin/cms_spec.rb index 8ab180906e..c200bc57e4 100644 --- a/spec/features/admin/cms_spec.rb +++ b/spec/features/admin/cms_spec.rb @@ -15,7 +15,7 @@ feature %q{ page.should have_content "ComfortableMexicanSofa" click_link 'Spree Admin' - current_path.should match(/^\/admin/) + expect(page).to have_current_path /^\/admin/ end scenario "anonymous user can't access CMS admin", js: true do @@ -29,6 +29,6 @@ feature %q{ page.should_not have_content "Login" visit cms_admin_path page.should_not have_content "ComfortableMexicanSofa" - current_path.should == root_path + expect(page).to have_current_path root_path end end diff --git a/spec/features/admin/orders_spec.rb b/spec/features/admin/orders_spec.rb index 33f91eb850..18b3466106 100644 --- a/spec/features/admin/orders_spec.rb +++ b/spec/features/admin/orders_spec.rb @@ -153,7 +153,7 @@ feature %q{ login_to_admin_section visit '/admin/orders' - current_path.should == spree.admin_orders_path + expect(page).to have_current_path spree.admin_orders_path # click the 'capture' link for the order page.find("[data-action=capture][href*=#{@order.number}]").click @@ -165,7 +165,7 @@ feature %q{ @order.payment_state.should == "paid" # we should still be on the same page - current_path.should == spree.admin_orders_path + expect(page).to have_current_path spree.admin_orders_path end diff --git a/spec/features/admin/payment_method_spec.rb b/spec/features/admin/payment_method_spec.rb index abb5feaf3e..b736155372 100644 --- a/spec/features/admin/payment_method_spec.rb +++ b/spec/features/admin/payment_method_spec.rb @@ -94,7 +94,7 @@ feature %q{ click_link "Payment Methods" end click_link 'Create One Now' - current_path.should == spree.new_admin_payment_method_path + expect(page).to have_current_path spree.new_admin_payment_method_path end it "creates payment methods" do diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index b01b04fe84..63f08efb71 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -18,7 +18,7 @@ feature "Authentication", js: true, retry: 3 do fill_in "Password", with: user.password click_login_button page.should have_content "Find local producers" - current_path.should == producers_path + expect(page).to have_current_path producers_path end end diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 4edaa702e0..9625640fe7 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -43,12 +43,12 @@ feature 'Shops', js: true do it "should link to the hub page" do follow_active_table_node distributor.name - current_path.should == enterprise_shop_path(distributor) + expect(page).to have_current_path enterprise_shop_path(distributor) end it "should show hub producer modals" do expand_active_table_node distributor.name - page.should have_content producer.name + expect(page).to have_content producer.name open_enterprise_modal producer modal_should_be_open_for producer end diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 70c803709e..23f9757c5b 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -29,7 +29,7 @@ module WebHelper def current_path_should_be path current_path = URI.parse(current_url).path - current_path.should == path + expect(page).to have_current_path path end def fill_in_fields(field_values) From c44f9d2537f5514e86d981f6a2d312b6c1aa715b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 22 Apr 2016 10:34:33 +1000 Subject: [PATCH 135/154] Making text (and links) within shopfront message more legible --- .../stylesheets/darkswarm/shop.css.sass | 19 ++++++++++++++----- app/views/shop/_messages.html.haml | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass index eab074369e..5cfcef46b8 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -84,16 +84,25 @@ padding-right: 0rem font-size: 0.8rem - .shopfront_message, .shopfront_closed_message, .shopfront_hidden_message + .alert-box.shopfront-message + border: 2px solid $clr-turquoise + border-radius: 5px + background-color: $clr-turquoise-light + color: $clr-turquoise + a + color: #0096ad + &:hover, &:focus, &:active + text-decoration: none + color: #4aadbd + + + .shopfront_closed_message, .shopfront_hidden_message padding: 15px border-radius: 5px - .shopfront_message, .shopfront_closed_message + .shopfront_closed_message border: 2px solid #eb4c46 - .shopfront_message - margin-top: 2em - .shopfront_closed_message margin: 2em 0em diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml index ba35354bf3..9e5d3b8e82 100644 --- a/app/views/shop/_messages.html.haml +++ b/app/views/shop/_messages.html.haml @@ -24,6 +24,6 @@   .row .small-12.columns - .alert-box{ "ofn-inline-alert" => true, ng: { show: "visible" } } + .alert-box.shopfront-message{ "ofn-inline-alert" => true, ng: { show: "visible" } } = current_distributor.preferred_shopfront_message.html_safe %a.close{ ng: { click: "close()" } } × From 564c1a36503c9eb7cb90905035918c07137ddf35 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 11:36:51 +1000 Subject: [PATCH 136/154] Improvements to customers page Create customer from dialog Limiting the number of products shown as once Adding SaveBar and StatusMessage --- .../customers_controller.js.coffee | 37 +++++--------- .../admin/customers/customers.js.coffee | 2 +- .../directives/new_customer_dialog.js.coffee | 46 ++++++++++++++++++ .../services/current_enterprise.js.coffee | 3 ++ .../customers/services/customers.js.coffee | 21 ++++++++ .../directives/show_more.js.coffee | 12 +++++ .../admin/index_utils/index_utils.js.coffee | 2 +- .../services/pending_changes.js.coffee | 17 +++++-- .../admin/new_customer_dialog.html.haml | 15 ++++++ .../templates/admin/show_more.html.haml | 4 ++ .../admin/components/save_bar.sass | 1 + .../admin/openfoodnetwork.css.scss | 3 ++ app/assets/stylesheets/admin/orders.css.scss | 4 -- app/controllers/admin/customers_controller.rb | 9 ++-- app/views/admin/customers/index.html.haml | 41 +++++++++------- .../variant_overrides/_show_more.html.haml | 3 +- config/locales/en.yml | 5 +- spec/features/admin/customers_spec.rb | 48 +++++++++++++++++-- .../customers_controller_spec.js.coffee | 6 +-- 19 files changed, 214 insertions(+), 65 deletions(-) create mode 100644 app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee create mode 100644 app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee create mode 100644 app/assets/javascripts/admin/customers/services/customers.js.coffee create mode 100644 app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee create mode 100644 app/assets/javascripts/templates/admin/new_customer_dialog.html.haml create mode 100644 app/assets/javascripts/templates/admin/show_more.html.haml diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 763e92b400..9e1c027531 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,43 +1,28 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagRuleResource, $q, Columns, pendingChanges, shops) -> - $scope.shop = {} +angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) -> $scope.shops = shops + $scope.CurrentShop = CurrentShop + $scope.RequestMonitor = RequestMonitor $scope.submitAll = pendingChanges.submitAll + $scope.add = Customers.add + $scope.deleteCustomer = Customers.remove + $scope.customerLimit = 20 $scope.columns = Columns.setColumns email: { name: "Email", visible: true } code: { name: "Code", visible: true } tags: { name: "Tags", visible: true } - $scope.$watch "shop.id", -> - if $scope.shop.id? - $scope.customers = index {enterprise_id: $scope.shop.id} + $scope.$watch "CurrentShop.shop", -> + if $scope.CurrentShop.shop.id? + Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) -> + $scope.customers = data $scope.findTags = (query) -> defer = $q.defer() params = - enterprise_id: $scope.shop.id + enterprise_id: $scope.CurrentShop.shop.id TagRuleResource.mapByTag params, (data) => filtered = data.filter (tag) -> tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1 defer.resolve filtered defer.promise - - $scope.add = (email) -> - params = - enterprise_id: $scope.shop.id - email: email - CustomerResource.create params, (customer) => - if customer.id - $scope.customers.push customer - $scope.quickSearch = customer.email - - $scope.deleteCustomer = (customer) -> - params = id: customer.id - CustomerResource.destroy params, -> - i = $scope.customers.indexOf customer - $scope.customers.splice i, 1 unless i < 0 - - index = (params) -> - $scope.loaded = false - CustomerResource.index params, => - $scope.loaded = true diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee index 33be58c9ac..fe8ae1de5b 100644 --- a/app/assets/javascripts/admin/customers/customers.js.coffee +++ b/app/assets/javascripts/admin/customers/customers.js.coffee @@ -1 +1 @@ -angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) \ No newline at end of file +angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown']) diff --git a/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee new file mode 100644 index 0000000000..02276875b9 --- /dev/null +++ b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee @@ -0,0 +1,46 @@ +angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, $window, CurrentShop, Customers) -> + restrict: 'A' + scope: true + link: (scope, element, attr) -> + scope.CurrentShop = CurrentShop + scope.submitted = null + scope.email = "" + scope.errors = [] + + scope.addCustomer = (valid) -> + scope.submitted = scope.email + scope.errors = [] + if valid + Customers.add(scope.email).$promise.then (data) -> + if data.id + scope.email = "" + scope.submitted = null + template.dialog('close') + , (response) -> + if response.data.errors + scope.errors.push(error) for error in response.data.errors + else + scope.errors.push("Sorry! Could not create '#{scope.email}'") + return + + # Compile modal template + template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope) + + # Set Dialog options + template.dialog + show: { effect: "fade", duration: 400 } + hide: { effect: "fade", duration: 300 } + autoOpen: false + resizable: false + width: $window.innerWidth * 0.4; + modal: true + open: (event, ui) -> + $('.ui-widget-overlay').bind 'click', -> + $(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close') + + # Link opening of dialog to click event on element + element.bind 'click', (e) -> + if CurrentShop.shop.id + template.dialog('open') + else + alert('Please select a shop first') diff --git a/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee new file mode 100644 index 0000000000..9df7e09895 --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee @@ -0,0 +1,3 @@ +angular.module("admin.customers").factory "CurrentShop", -> + new class CurrentShop + shop: {} diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee new file mode 100644 index 0000000000..a9f4f0102f --- /dev/null +++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee @@ -0,0 +1,21 @@ +angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) -> + new class Customers + customers: [] + + add: (email) -> + params = + enterprise_id: CurrentShop.shop.id + email: email + CustomerResource.create params, (customer) => + @customers.unshift customer if customer.id + + remove: (customer) -> + params = id: customer.id + CustomerResource.destroy params, => + i = @customers.indexOf customer + @customers.splice i, 1 unless i < 0 + + index: (params) -> + request = CustomerResource.index(params, (data) => @customers = data) + RequestMonitor.load(request.$promise) + request.$promise diff --git a/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee new file mode 100644 index 0000000000..a6e4efa333 --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.indexUtils").component 'showMore', + templateUrl: 'admin/show_more.html' + bindings: + data: "=" + limit: "=" + increment: "=" + +# For now, this component is not being used. +# Something about binding "data" to a variable on the parent scope that is continually refreshed by +# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved +# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this +# has the limitation of not being able to be limited AFTER the assignment has been made, which we need diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee index 5e5b5cadf2..1ea74e614b 100644 --- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -1 +1 @@ -angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file +angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 2f40a7faef..15626b1479 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -1,10 +1,12 @@ -angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> +angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, StatusMessage) -> new class pendingChanges pendingChanges: {} + errors: [] add: (id, attr, change) => @pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}") @pendingChanges["#{id}"]["#{attr}"] = change + StatusMessage.display('notice', "You have made #{@changeCount(@pendingChanges)} unsaved changes") removeAll: => @pendingChanges = {} @@ -14,11 +16,19 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> delete @pendingChanges["#{id}"]["#{attr}"] delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: => + submitAll: (form=null) => all = [] + @errors = [] + StatusMessage.display('progress', "Saving...") for id, objectChanges of @pendingChanges for attrName, change of objectChanges all.push @submit(change) + $q.all(all).then => + if @errors.length == 0 + StatusMessage.display('success', "All changes saved successfully") + form.$setPristine() if form? + else + StatusMessage.display('failure', "Oh no! I was unable to save your changes") all submit: (change) -> @@ -26,7 +36,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) -> @remove change.object.id, change.attr change.scope.reset( data["#{change.attr}"] ) change.scope.success() - , (error) -> + , (error) => + @errors.push error change.scope.error() changeCount: (objectChanges) -> diff --git a/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml new file mode 100644 index 0000000000..e30cfcd607 --- /dev/null +++ b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml @@ -0,0 +1,15 @@ +#new-customer-dialog + .text-normal.margin-bottom-30.text-center + = t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:") + + %form{ name: 'new_customer_form', novalidate: true } + + .text-center.margin-bottom-30 + %input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } } + %div{ ng: { show: "email == submitted" } } + .error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } } + = t('admin.customers.index.valid_email_error') + .error{ ng: { repeat: "error in errors", bind: "error" } } + + .text-center + %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } } diff --git a/app/assets/javascripts/templates/admin/show_more.html.haml b/app/assets/javascripts/templates/admin/show_more.html.haml new file mode 100644 index 0000000000..f9a008993c --- /dev/null +++ b/app/assets/javascripts/templates/admin/show_more.html.haml @@ -0,0 +1,4 @@ +%div{ ng: { show: "data.length > limit" } } + %input{ type: 'button', value: 'Show More', ng: { click: 'limit = limit + increment' } } + or + %input{ type: 'button', value: "Show All ({{ data.length - limit }} More)", ng: { click: 'limit = data.length' } } diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass index 87dcce82f9..f453c5ddfc 100644 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ b/app/assets/stylesheets/admin/components/save_bar.sass @@ -1,6 +1,7 @@ #save-bar position: fixed width: 100% + z-index: 100 bottom: 0px left: 0 padding: 8px 8px diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 1fc95face8..293549d0f1 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -28,6 +28,9 @@ text-angular .ta-editor { left: 275px; } +span.error, div.error { + color: #DA5354; +} /* Fix conflict between Spree and elRTE's styles */ .el-rte .toolbar { diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 761ccbc014..23cb9d8fff 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -13,10 +13,6 @@ input.show-dirty { } } -span.error { - color: #DA5354; -} - input, div { &.update-error { border: solid 1px #DA5354; diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index 9d3bb18017..fe975aa038 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -16,9 +16,12 @@ module Admin def create @customer = Customer.new(params[:customer]) if user_can_create_customer? - @customer.save - tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise)) - render_as_json @customer, tag_rule_mapping: tag_rule_mapping + if @customer.save + tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise)) + render_as_json @customer, tag_rule_mapping: tag_rule_mapping + else + render json: { errors: @customer.errors.full_messages }, status: 400 + end else redirect_to '/unauthorized' end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index dc38304f8e..ef3ef7e7f0 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -2,35 +2,43 @@ %h1.page-title =t :customers +- content_for :app_wrapper_attrs do + = "ng-app='admin.customers'" + +- content_for :page_actions do + %li + %a.button.icon-plus#new-customer{ href: "#", "new-customer-dialog" => true } + = t('admin.customers.index.new_customer') + = admin_inject_shops -%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } } - .row{ ng: { hide: "loaded && customers.length > 0" } } +%div{ ng: { controller: 'customersCtrl' } } + .row{ ng: { hide: "!RequestMonitor.loading && customers.length > 0" } } .five.columns.alpha %h3 =t :please_select_hub .four.columns - %select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' } + %select.select2.fullwidth#shop_id{ 'ng-model' => 'CurrentShop.shop', name: 'shop_id', 'ng-options' => 'shop as shop.name for shop in shops' } .seven.columns.omega   - .row{ 'ng-hide' => '!loaded || customers.length == 0' } + .row{ 'ng-hide' => 'RequestMonitor.loading || !CurrentShop.shop.id || customers.length == 0' } .controls.sixteen.columns.alpha.omega .five.columns.alpha %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } .eight.columns   = render 'admin/shared/columns_dropdown' - .row{ 'ng-if' => 'shop.id && !loaded' } + .row{ 'ng-if' => 'CurrentShop.shop.id && RequestMonitor.loading' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 =t :loading_customers - .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredCustomers.length == 0'} + .row{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredCustomers.length == 0'} %h1#no_results =t :no_customers_found - - .row{ ng: { show: "loaded && filteredCustomers.length > 0" } } + .row.margin-bottom-50{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } %form{ name: "customers_form" } + %save-bar{ save: "submitAll(customers_form)", form: "customers_form" } %table.index#customers %col.email{ width: "20%"} %col.code{ width: "20%"} @@ -48,10 +56,10 @@ %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } -# %td.bulk -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } - %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }} + %td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' } %td.code{ 'ng-show' => 'columns.code.visible' } %input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" } %td.tags{ 'ng-show' => 'columns.tags.visible' } @@ -59,12 +67,9 @@ %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } - %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' } - %form{ng: {show: "loaded", submit: 'add(newCustomerEmail)'}} - %h2= t '.add_new_customer' - .row - .five.columns.alpha - %input.fullwidth{type: "text", placeholder: t('.customer_placeholder'), ng: {model: 'newCustomerEmail'}} - .eleven.columns.omega - %input{type: "submit", value: t('.add_customer')} + -# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" } + %div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } } + %input{ type: 'button', value: 'Show More', ng: { click: 'customerLimit = customerLimit + 20' } } + or + %input{ type: 'button', value: "Show All ({{ filteredCustomers.length - customerLimit }} More)", ng: { click: 'customerLimit = filteredCustomers.length' } } diff --git a/app/views/admin/variant_overrides/_show_more.html.haml b/app/views/admin/variant_overrides/_show_more.html.haml index 8d60593ddc..21e927e443 100644 --- a/app/views/admin/variant_overrides/_show_more.html.haml +++ b/app/views/admin/variant_overrides/_show_more.html.haml @@ -1,4 +1,5 @@ -.text-center +-# %show-more.text-center{ data: "filteredProducts", limit: "productLimit", increment: "10" } +.text-center{ ng: { show: "filteredProducts.length > productLimit" } } %input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } } or %input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 79f6e471b1..02d66a5354 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -91,8 +91,11 @@ en: customers: index: - add_customer: "Add customer" + add_customer: "Add Customer" + new_customer: "New Customer" customer_placeholder: "customer@example.org" + valid_email_error: Please enter a valid email address + add_a_new_customer_for: Add a new customer for %{shop_name} inventory: title: Inventory description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index 5d2ffedb4e..964608510d 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -9,7 +9,7 @@ feature 'Customers' do let(:managed_distributor) { create(:distributor_enterprise, owner: user) } let(:unmanaged_distributor) { create(:distributor_enterprise) } - describe "using the customers index" do + describe "using the customers index", js: true do let!(:customer1) { create(:customer, enterprise: managed_distributor) } let!(:customer2) { create(:customer, enterprise: managed_distributor) } let!(:customer3) { create(:customer, enterprise: unmanaged_distributor) } @@ -19,7 +19,7 @@ feature 'Customers' do visit admin_customers_path end - it "passes the smoke test", js: true do + it "passes the smoke test" do # Prompts for a hub for a list of my managed enterprises expect(page).to have_select2 "shop_id", with_options: [managed_distributor.name], without_options: [unmanaged_distributor.name] @@ -45,7 +45,7 @@ feature 'Customers' do expect(page).to_not have_content customer1.email end - it "allows updating of attributes", js: true do + it "allows updating of attributes" do select2_select managed_distributor.name, from: "shop_id" within "tr#c_#{customer1.id}" do @@ -56,7 +56,7 @@ feature 'Customers' do find(:css, "tags-input .tags input").set "awesome\n" expect(page).to have_css ".tag_watcher.update-pending" end - click_button "Update" + click_button "Save Changes" # Every says it updated expect(page).to have_css "input#code.update-success" @@ -66,6 +66,46 @@ feature 'Customers' do expect(customer1.reload.code).to eq "new-customer-code" expect(customer1.tag_list).to eq ["awesome"] end + + describe "creating a new customer" do + context "when no shop has been selected" do + it "asks the user to select a shop" do + accept_alert 'Please select a shop first' do + click_link('New Customer') + end + end + end + + context "when a shop is selected" do + before do + select2_select managed_distributor.name, from: "shop_id" + end + + it "creates customers when the email provided is valid" do + # When an invalid email is used + expect{ + click_link('New Customer') + fill_in 'email', with: "not_an_email" + click_button 'Add Customer' + expect(page).to have_selector "#new-customer-dialog .error", text: "Please enter a valid email address" + }.to_not change{Customer.of(managed_distributor).count} + + # When an existing email is used + expect{ + fill_in 'email', with: customer1.email + click_button 'Add Customer' + expect(page).to have_selector "#new-customer-dialog .error", text: "Email is associated with an existing customer" + }.to_not change{Customer.of(managed_distributor).count} + + # When a new valid email is used + expect{ + fill_in 'email', with: "new@email.com" + click_button 'Add Customer' + expect(page).not_to have_selector "#new-customer-dialog" + }.to change{Customer.of(managed_distributor).count}.from(2).to(3) + end + end + end end end end diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index b0991c7df1..796dc93ebf 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -14,7 +14,7 @@ describe "CustomersCtrl", -> { pass: angular.equals(actual, expected) } it "has no shop pre-selected", -> - expect(scope.shop).toEqual {} + expect(scope.CurrentShop.shop).toEqual {} describe "setting the shop on scope", -> customer = { id: 5, email: 'someone@email.com'} @@ -23,7 +23,7 @@ describe "CustomersCtrl", -> beforeEach -> http.expectGET('/admin/customers.json?enterprise_id=1').respond 200, customers scope.$apply -> - scope.shop = {id: 1} + scope.CurrentShop.shop = {id: 1} http.flush() it "retrievs the list of customers", -> @@ -33,7 +33,7 @@ describe "CustomersCtrl", -> it "creates a new customer", -> email = "customer@example.org" newCustomer = {id: 6, email: email} - customers.push(newCustomer) + customers.unshift(newCustomer) http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=1').respond 200, newCustomer scope.add(email) http.flush() From abc906186ee45b24e1c19734f0593066b7645ca9 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 12:29:06 +1000 Subject: [PATCH 137/154] Using ngSrc correctly in frontend --- .../javascripts/templates/partials/enterprise_details.html.haml | 2 +- .../javascripts/templates/partials/enterprise_header.html.haml | 2 +- app/views/shop/products/_summary.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml index 547e233958..d1d208c5c4 100644 --- a/app/assets/javascripts/templates/partials/enterprise_details.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml @@ -22,7 +22,7 @@ -# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/ -# .about-container.pad-top - %img.enterprise-logo{"ng-src" => "::enterprise.logo", "ng-if" => "::enterprise.logo"} + %img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"} %p.text-small{"ng-bind-html" => "::enterprise.long_description"} .small-12.large-4.columns %ng-include{src: "'partials/contact.html'"} diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml index 39b967be06..066ebdd957 100644 --- a/app/assets/javascripts/templates/partials/enterprise_header.html.haml +++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml @@ -10,4 +10,4 @@ %span{"ng-bind" => "::enterprise.name"} .small-12.medium-5.large-4.columns.text-right.small-only-text-left %p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"} - %img.hero-img{"ng-src" => "::enterprise.promo_image"} + %img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"} diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index fcf2aa6d7b..4dd5b3f224 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,7 +1,7 @@ .product-thumb %a{"ng-click" => "triggerProductModal()"} %i.ofn-i_057-expand - %img{"ng-src" => "::product.primaryImageOrMissing", "ng-click" => "triggerProductModal()"} + %img{"ng-src" => "{{::product.primaryImageOrMissing}}", "ng-click" => "triggerProductModal()"} .row.summary .small-10.medium-10.large-11.columns.summary-header From 4b8146dd00239a86b3461fb5c1b203f715abb3f7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 12:35:17 +1000 Subject: [PATCH 138/154] Fixing translation error on final registration page --- .../services/enterprise_registration_service.js.coffee | 1 + .../javascripts/templates/registration/finished.html.haml | 2 +- spec/features/consumer/registration_spec.rb | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee index a7d6a657fa..1434ffa44f 100644 --- a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee @@ -2,6 +2,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, new class EnterpriseRegistrationService enterprise: user_ids: [CurrentUser.id] + email: CurrentUser.email email_address: CurrentUser.email address: {} country: availableCountries[0] diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml index 5c7b3b6ad0..76403cb1c8 100644 --- a/app/assets/javascripts/templates/registration/finished.html.haml +++ b/app/assets/javascripts/templates/registration/finished.html.haml @@ -10,6 +10,6 @@ .small-12.columns.text-center %h4{ "ng-bind" => "'registration_finished_activate' | t:{enterprise: enterprise.name}" } - %p{ "ng-bind-html" => "t('registration_finished_activate_instruction_html', {email: enterprise.email})"} + %p{ "ng-bind-html" => "'registration_finished_activate_instruction_html' | t:{email: enterprise.email}"} %a.button.primary{ type: "button", href: "/" } {{'registration_finished_action' | t}} > diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb index 0e54011f1c..9b2602c1c6 100644 --- a/spec/features/consumer/registration_spec.rb +++ b/spec/features/consumer/registration_spec.rb @@ -89,6 +89,7 @@ feature "Registration", js: true do # Done expect(page).to have_content "Finished!" + expect(page).to have_content "We've sent a confirmation email to #{user.email} if it hasn't been activated before." e.reload expect(e.website).to eq "www.shop.com" expect(e.facebook).to eq "FaCeBoOk" From 92d8ee1a3637affb0cb565259b99d096da6290b0 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 15:05:58 +1000 Subject: [PATCH 139/154] Making sure that hash navigation works with auth tabs --- .../controllers/authentication/forgot_controller.js.coffee | 1 - .../controllers/authentication/login_controller.js.coffee | 1 - .../controllers/authentication/signup_controller.js.coffee | 1 - .../controllers/authentication_controller.js.coffee | 5 +++++ app/assets/javascripts/templates/forgot.html.haml | 2 +- app/assets/javascripts/templates/login.html.haml | 2 +- app/assets/javascripts/templates/signup.html.haml | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee index 85d2a699a3..85920de958 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/forgot_controller.js.coffee @@ -1,6 +1,5 @@ Darkswarm.controller "ForgotCtrl", ($scope, $http, $location, AuthenticationService) -> $scope.path = "/forgot" - $scope.active = $scope.isActive($scope.path) $scope.sent = false $scope.submit = -> diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee index 07bef90f84..f91e137ca7 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/login_controller.js.coffee @@ -1,6 +1,5 @@ Darkswarm.controller "LoginCtrl", ($scope, $http, $window, AuthenticationService, Redirections, Loading) -> $scope.path = "/login" - $scope.active = $scope.isActive($scope.path) $scope.submit = -> Loading.message = t 'logging_in' diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee index dfa61f82b2..722414f9d4 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee @@ -1,6 +1,5 @@ Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) -> $scope.path = "/signup" - $scope.active = $scope.isActive($scope.path) $scope.errors = email: null diff --git a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee index 7d65b853c7..135fe37b89 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee @@ -5,3 +5,8 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree $scope.spree_user = SpreeUser.spree_user $scope.isActive = AuthenticationService.isActive $scope.select = AuthenticationService.select + + $scope.tabs = + login: { active: $scope.isActive('/login') } + signup: { active: $scope.isActive('/signup') } + forgot: { active: $scope.isActive('/forgot') } diff --git a/app/assets/javascripts/templates/forgot.html.haml b/app/assets/javascripts/templates/forgot.html.haml index 3e88350321..958f1daa39 100644 --- a/app/assets/javascripts/templates/forgot.html.haml +++ b/app/assets/javascripts/templates/forgot.html.haml @@ -1,4 +1,4 @@ -%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "active", select: "select(path)"} +%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"} %form{ ng: { controller: "ForgotCtrl", submit: "submit()" } } .row .large-12.columns diff --git a/app/assets/javascripts/templates/login.html.haml b/app/assets/javascripts/templates/login.html.haml index 8b3e64cfb1..79511d8968 100644 --- a/app/assets/javascripts/templates/login.html.haml +++ b/app/assets/javascripts/templates/login.html.haml @@ -1,4 +1,4 @@ -%tab#login-content{ heading: "{{'label_login' | t}}", active: "active", select: "select(path)"} +%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"} %form{ ng: { controller: "LoginCtrl", submit: "submit()" } } .row .large-12.columns diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index 1c13985f38..23eab8e38c 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -1,4 +1,4 @@ -%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'active', select: "select(path)"} +%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"} %form{ ng: { controller: "SignupCtrl", submit: "submit()" } } .row .large-12.columns From 93a4f19b40005e24bb781394cbb86487df8a8a67 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 15:33:23 +1000 Subject: [PATCH 140/154] Hash Navigation works on with Group and Shopping page tabs --- .../controllers/group_page_controller.js.coffee | 3 +-- .../controllers/group_tabs_controller.js.coffee | 8 ++++++++ .../controllers/groups_controller.js.coffee | 2 +- .../shopping_tabs_controller.js.coffee | 8 ++++++++ .../controllers/tabs_controller.js.coffee | 17 ++++------------- app/views/groups/show.html.haml | 14 +++++++------- app/views/shopping_shared/_tabs.html.haml | 6 +++--- 7 files changed, 32 insertions(+), 26 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee create mode 100644 app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee diff --git a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee index ed52f46bb7..5d9bb0aa02 100644 --- a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter) -> +Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) -> $scope.Enterprises = Enterprises all_enterprises_by_id = Enterprises.enterprises_by_id @@ -19,4 +19,3 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M $scope.map = angular.copy MapConfiguration.options $scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises - diff --git a/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee new file mode 100644 index 0000000000..c9a8f88f58 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.controller "GroupTabsCtrl", ($scope, $controller, Navigation) -> + angular.extend this, $controller('TabsCtrl', {$scope: $scope}) + + $scope.tabs = + map: { active: Navigation.isActive('/map') } + about: { active: Navigation.isActive('/about') } + producers: { active: Navigation.isActive('/producers') } + hubs: { active: Navigation.isActive('/hubs') } diff --git a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee index aafb7597d6..8fd47c49f8 100644 --- a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee @@ -1,3 +1,3 @@ -Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) -> +Darkswarm.controller "GroupsCtrl", ($scope, Groups) -> $scope.Groups = Groups $scope.order = 'position' diff --git a/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee new file mode 100644 index 0000000000..8daac0212c --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee @@ -0,0 +1,8 @@ +Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) -> + angular.extend this, $controller('TabsCtrl', {$scope: $scope}) + + $scope.tabs = + about: { active: Navigation.isActive('/about') } + producers: { active: Navigation.isActive('/producers') } + contact: { active: Navigation.isActive('/contact') } + groups: { active: Navigation.isActive('/groups') } diff --git a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee index fad13164ff..09095ebd42 100644 --- a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee @@ -1,15 +1,6 @@ -Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location) -> - # Return active if supplied path matches url hash path. - $scope.active = (path)-> - $location.hash() == path +Darkswarm.controller "TabsCtrl", ($scope, Navigation) -> + $scope.isActive = Navigation.isActive # Select tab by setting the url hash path. - $scope.select = (path)-> - $location.hash path - - # Toggle tab selected status by setting the url hash path. - $scope.toggle = (path)-> - if $scope.active(path) - $location.hash "" - else - $location.hash path + $scope.select = (path) -> + Navigation.navigate path diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 8d5385f1f4..af497a4195 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -32,27 +32,27 @@ .small-12.columns.pad-top .row .small-12.medium-12.large-9.columns - %div{"ng-controller" => "TabsCtrl"} + %div{"ng-controller" => "GroupTabsCtrl"} %tabset %tab{heading: t(:label_map), - active: "active(\'\')", - select: "select(\'\')"} + active: "tabs.map.active", + select: "select(\'map\')"} .map-container - %map{"ng-if" => "(active(\'\') && (mapShowed = true)) || mapShowed"} + %map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"} %google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"} %map-search %markers{models: "mapMarkers", fit: "true", coords: "'self'", icon: "'icon'", click: "'reveal'"} %tab{heading: t(:groups_about), - active: "active(\'about\')", + active: "tabs.about.active", select: "select(\'about\')"} %h1 = t :groups_about %p!= @group.long_description %tab{heading: t(:groups_producers), - active: "active(\'producers\')", + active: "tabs.producers.active", select: "select(\'producers\')"} .producers{"ng-controller" => "GroupEnterprisesCtrl"} .row @@ -78,7 +78,7 @@ = render partial: 'shared/components/enterprise_no_results' %tab{heading: t(:groups_hubs), - active: "active(\'hubs\')", + active: "tabs.hubs.active", select: "select(\'hubs\')"} .hubs{"ng-controller" => "GroupEnterprisesCtrl"} .row diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml index 0d3cd79939..3bcd2aa37e 100644 --- a/app/views/shopping_shared/_tabs.html.haml +++ b/app/views/shopping_shared/_tabs.html.haml @@ -1,4 +1,4 @@ -#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true} +#tabs{"ng-controller" => "ShoppingTabsCtrl", "ng-cloak" => true} .row %tabset{ 'open-on-load' => 'false' } -# Build all tabs. @@ -6,10 +6,10 @@ producers: [t(:label_producers),2], contact: [t(:shopping_tabs_contact),2], groups: [t(:label_groups),2]} - -# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl. - heading, cols = heading_cols %tab.columns{heading: heading, id: "tab_#{name}", - select: "toggle(\'#{name}\')", + active: "tabs.#{name}.active", + select: "select(\'#{name}\')", class: "small-12 medium-#{cols}" } = render "shopping_shared/#{name}" From 162b11dcc5ca80e366483c733c2dee1ea93ee333 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 16:30:12 +1000 Subject: [PATCH 141/154] Updating spec to new Jasmine syntax --- spec/javascripts/unit/order_cycle_spec.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 511051d39f..ea03a38786 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -328,7 +328,7 @@ describe 'OrderCycle controllers', -> scope.submit(eventMock,'/admin/order_cycles') expect(eventMock.preventDefault).toHaveBeenCalled() expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') - expect(scope.order_cycle_form.$setPristine.calls.length).toEqual 1 + expect(scope.order_cycle_form.$setPristine.calls.count()).toEqual 1 describe 'OrderCycle services', -> From ecb9646ccb26085aa71339337d26c7f43c42a682 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 16:30:39 +1000 Subject: [PATCH 142/154] Using new syntax for save-bar on customers page --- .../admin/index_utils/services/pending_changes.js.coffee | 2 +- app/views/admin/customers/index.html.haml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 15626b1479..3581b4921a 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -16,7 +16,7 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta delete @pendingChanges["#{id}"]["#{attr}"] delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: (form=null) => + submitAll: (event=null, form=null) => all = [] @errors = [] StatusMessage.display('progress', "Saving...") diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index ef3ef7e7f0..f605ec3e5a 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -38,7 +38,8 @@ .row.margin-bottom-50{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } %form{ name: "customers_form" } - %save-bar{ save: "submitAll(customers_form)", form: "customers_form" } + %save-bar{ buttons: "[{ text: 'Save Changes', action: submitAll, param: customers_form, class: 'red' }]", form: "customers_form" } + %table.index#customers %col.email{ width: "20%"} %col.code{ width: "20%"} From 533a94267a88b55246144df40d13ec68219161f6 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 17:07:12 +1000 Subject: [PATCH 143/154] Refactoring save-bar --- .../admin/index_utils/services/pending_changes.js.coffee | 2 +- .../admin/order_cycles/controllers/edit.js.coffee | 3 +-- .../admin/order_cycles/controllers/simple_edit.js.coffee | 2 +- .../admin/order_cycles/services/order_cycle.js.coffee | 3 ++- .../javascripts/admin/utils/directives/save_bar.js.coffee | 5 +++-- app/assets/javascripts/templates/admin/save_bar.html.haml | 5 ++--- app/views/admin/customers/index.html.haml | 3 ++- app/views/admin/order_cycles/_form.html.haml | 2 -- app/views/admin/order_cycles/_simple_form.html.haml | 2 -- app/views/admin/order_cycles/edit.html.haml | 5 ++++- app/views/admin/variant_overrides/_products.html.haml | 3 ++- app/views/spree/admin/orders/bulk_management.html.haml | 4 +++- spec/features/admin/order_cycles_spec.rb | 5 +++-- spec/javascripts/unit/order_cycle_spec.js.coffee | 7 ++++--- 14 files changed, 28 insertions(+), 23 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee index 3581b4921a..15626b1479 100644 --- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee @@ -16,7 +16,7 @@ angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, Sta delete @pendingChanges["#{id}"]["#{attr}"] delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1 - submitAll: (event=null, form=null) => + submitAll: (form=null) => all = [] @errors = [] StatusMessage.display('progress', "Saving...") diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index d4c63121fe..2f3b2db5e0 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -91,8 +91,7 @@ angular.module('admin.orderCycles') $scope.submit = ($event, destination) -> $event.preventDefault() StatusMessage.display 'progress', "Saving..." - OrderCycle.update(destination) - $scope.order_cycle_form.$setPristine() + OrderCycle.update(destination, $scope.order_cycle_form) $scope.cancel = (destination) -> $window.location = destination diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index d3eeb1c9d8..f5bae5a4d8 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -41,4 +41,4 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $event.preventDefault() StatusMessage.display 'progress', "Saving..." OrderCycle.mirrorIncomingToOutgoingProducts() - OrderCycle.update(destination) + OrderCycle.update(destination, $scope.order_cycle_form) diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 9ca5f1028d..662201c9f5 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -154,11 +154,12 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S else console.log('Failed to create order cycle') - update: (destination) -> + update: (destination, form) -> return unless @confirmNoDistributors() oc = new OrderCycleResource({order_cycle: this.dataForSubmit()}) oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) => if data['success'] + form.$setPristine() if form if destination? $window.location = destination else diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee index 0999679394..8bc6c98573 100644 --- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee +++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee @@ -1,8 +1,9 @@ angular.module("admin.utils").directive "saveBar", (StatusMessage) -> restrict: "E" + transclude: true scope: - form: "=" - buttons: "=" + dirty: "=" + persist: "=?" templateUrl: "admin/save_bar.html" link: (scope, element, attrs) -> scope.StatusMessage = StatusMessage diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml index b710c070ca..12ccd4537c 100644 --- a/app/assets/javascripts/templates/admin/save_bar.html.haml +++ b/app/assets/javascripts/templates/admin/save_bar.html.haml @@ -1,7 +1,6 @@ -#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } } +#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } } .container .eight.columns.alpha %h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } } {{ StatusMessage.statusMessage.text || " " }} - .eight.columns.omega.text-right - %input{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { class: "button.class", click: "button.action($event, button.param)" } } + .eight.columns.omega.text-right{ ng: { transclude: true } } diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index f605ec3e5a..4278162e30 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -38,7 +38,8 @@ .row.margin-bottom-50{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } } %form{ name: "customers_form" } - %save-bar{ buttons: "[{ text: 'Save Changes', action: submitAll, param: customers_form, class: 'red' }]", form: "customers_form" } + %save-bar{ dirty: "customers_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "submitAll(customers_form)", disabled: "!customers_form.$dirty" } } %table.index#customers %col.email{ width: "20%"} diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index f61c1ad91f..e53fd515b0 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -53,8 +53,6 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - = link_to 'Cancel', main_app.admin_order_cycles_path %span{'ng-hide' => 'loaded()'} Loading... diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 7fcf0ea80c..ba28526f7b 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -24,6 +24,4 @@ - if @order_cycle.new_record? = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()' - %span{'ng-show' => 'loaded()'} - = link_to 'Cancel', main_app.admin_order_cycles_path %span{'ng-hide' => 'loaded()'} Loading... diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 9f85261224..4712566dfd 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -29,7 +29,10 @@ - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| - %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null, class: 'red' }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}', class: 'red' }, { text: 'Cancel', action: cancel, param: '#{main_app.admin_order_cycles_path}', class: '' }]", form: "order_cycle_form" } + %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } + %input.red{ type: "button", value: "Update", ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } } + %input.red{ type: "button", value: "Update and Close", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } + %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } - if order_cycles_simple_form = render 'simple_form', f: f diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index f0b2310fdc..b54cfeba9a 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -1,5 +1,6 @@ %form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } } - %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update, class: 'red' }]" } + %save-bar{ dirty: "customers_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } } %table.index.bulk#variant-overrides %col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } } %col.product{ width: "20%", ng: { show: 'columns.product.visible' } } diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index d8228b5c83..213ba142d5 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -10,7 +10,9 @@ = render :partial => 'spree/admin/shared/order_sub_menu' %div{ ng: { controller: 'LineItemsCtrl' } } - %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit, class: 'red' }]" } + %save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" } + %input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } } + .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 22402222fe..54d25a478a 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -464,7 +464,7 @@ feature %q{ click_link 'Order Cycles' click_link oc.name within("table.exchanges tbody tr.supplier") { page.find('td.products input').click } - page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).click # uncheck + page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).trigger('click') # uncheck click_button "Update" # Then the master variant should have been removed from all exchanges @@ -917,6 +917,7 @@ feature %q{ click_button 'Update' page.should have_content 'Your order cycle has been updated.' + fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'yyz' click_button 'Update and Close' # Then my order cycle should have been updated @@ -936,7 +937,7 @@ feature %q{ # And my pickup time and instructions should have been saved ex = oc.exchanges.outgoing.first ex.pickup_time.should == 'xy' - ex.pickup_instructions.should == 'zzy' + ex.pickup_instructions.should == 'yyz' end end diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index ea03a38786..bbbff7d82b 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -327,8 +327,7 @@ describe 'OrderCycle controllers', -> eventMock = {preventDefault: jasmine.createSpy()} scope.submit(eventMock,'/admin/order_cycles') expect(eventMock.preventDefault).toHaveBeenCalled() - expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles') - expect(scope.order_cycle_form.$setPristine.calls.count()).toEqual 1 + expect(OrderCycle.update).toHaveBeenCalledWith('/admin/order_cycles', scope.order_cycle_form) describe 'OrderCycle services', -> @@ -844,15 +843,17 @@ describe 'OrderCycle services', -> spyOn(OrderCycle, 'confirmNoDistributors').and.returnValue true it 'redirects to the destination page on success', -> + form = jasmine.createSpyObj('order_cycle_form', ['$dirty', '$setPristine']) OrderCycle.order_cycle = 'this is the order cycle' spyOn(OrderCycle, 'dataForSubmit').and.returnValue('this is the submit data') $httpBackend.expectPUT('/admin/order_cycles.json?reloading=1', { order_cycle: 'this is the submit data' }).respond {success: true} - OrderCycle.update('/destination/page') + OrderCycle.update('/destination/page', form) $httpBackend.flush() expect($window.location).toEqual('/destination/page') + expect(form.$setPristine.calls.count()).toBe 1 it 'does not redirect on error', -> OrderCycle.order_cycle = 'this is the order cycle' From 3647b17110af67f755cf060bdd1c77aab7b7754c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 13 May 2016 17:50:49 +1000 Subject: [PATCH 144/154] Removing save_screenshot call --- spec/features/admin/order_cycles_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 54d25a478a..e362bf9946 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -363,7 +363,6 @@ feature %q{ # And I click Update expect(page).to have_selector "#save-bar" - save_screenshot('abc.png') click_button 'Update and Close' # Then my order cycle should have been updated From 1d8719b4744d1458c806bb42ac1ea21df566b4ef Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 4 May 2016 11:33:14 +1000 Subject: [PATCH 145/154] Default column visibility can be set per user using ColumnPreferences model --- .../admin/bulk_order_management.js.coffee | 26 +++---- .../admin/bulk_product_update.js.coffee | 13 +--- .../customers_controller.js.coffee | 6 +- .../columns_dropdown_controller.js.coffee | 4 + .../directives/columns_dropdown.js.coffee | 6 ++ .../enterprises_controller.js.coffee | 7 +- .../directives/toggle_column.js.coffee | 8 -- .../index_utils/services/columns.js.coffee | 17 +++- .../line_items_controller.js.coffee | 15 +--- .../variant_overrides_controller.js.coffee | 13 +--- .../admin/columns_dropdown.html.haml | 10 +++ .../admin/column_preferences_controller.rb | 38 +++++++++ app/helpers/admin/injection_helper.rb | 12 ++- app/models/column_preference.rb | 46 +++++++++++ app/models/column_preference_set.rb | 5 ++ app/models/spree/ability_decorator.rb | 4 + .../api/admin/column_preference_serializer.rb | 3 + app/views/admin/customers/index.html.haml | 3 +- .../_enterprise_user_index.html.haml | 2 +- app/views/admin/enterprises/index.html.haml | 2 + .../admin/shared/_columns_dropdown.html.haml | 7 -- .../variant_overrides/_controls.html.haml | 2 +- .../admin/variant_overrides/_data.html.haml | 1 + .../admin/orders/bulk_management.html.haml | 4 +- .../products/bulk_edit/_actions.html.haml | 2 +- .../admin/products/bulk_edit/_data.html.haml | 1 + config/routes.rb | 4 + ...0160116024333_create_column_preferences.rb | 13 ++++ db/schema.rb | 13 +++- .../column_preference_defaults.rb | 77 +++++++++++++++++++ .../column_preferences_controller_spec.rb | 44 +++++++++++ ...ariant_overrides_controller_spec.js.coffee | 1 + .../customers_controller_spec.js.coffee | 4 + .../enterprises_controller_spec.js.coffee | 4 + .../directives/panel_row_spec.js.coffee | 3 + .../services/columns_spec.js.coffee | 18 +++-- .../line_items_controller_spec.js.coffee | 3 + .../unit/bulk_product_update_spec.js.coffee | 1 + spec/models/column_preference_spec.rb | 59 ++++++++++++++ 39 files changed, 406 insertions(+), 95 deletions(-) create mode 100644 app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee create mode 100644 app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee delete mode 100644 app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee create mode 100644 app/assets/javascripts/templates/admin/columns_dropdown.html.haml create mode 100644 app/controllers/admin/column_preferences_controller.rb create mode 100644 app/models/column_preference.rb create mode 100644 app/models/column_preference_set.rb create mode 100644 app/serializers/api/admin/column_preference_serializer.rb delete mode 100644 app/views/admin/shared/_columns_dropdown.html.haml create mode 100644 db/migrate/20160116024333_create_column_preferences.rb create mode 100644 lib/open_food_network/column_preference_defaults.rb create mode 100644 spec/controllers/admin/column_preferences_controller_spec.rb create mode 100644 spec/models/column_preference_spec.rb diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index b747d3699e..2545a13915 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -18,19 +18,19 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ $scope.selectedUnitsVariant = {}; $scope.sharedResource = false $scope.columns = Columns.setColumns - order_no: { name: t("bom_no"), visible: false } - full_name: { name: t("name"), visible: true } - email: { name: t("email"), visible: false } - phone: { name: t("phone"), visible: false } - order_date: { name: t("bom_date"), visible: true } - producer: { name: t("producer"), visible: true } - order_cycle: { name: t("bom_cycle"), visible: false } - hub: { name: t("bom_hub"), visible: false } - variant: { name: t("bom_variant"), visible: true } - quantity: { name: t("bom_quantity"), visible: true } - max: { name: t("bom_max"), visible: true } - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } - price: { name: t("price"), visible: false } + order_no: { name: t("bom_no"), visible: false } + full_name: { name: t("name"), visible: true } + email: { name: t("email"), visible: false } + phone: { name: t("phone"), visible: false } + order_date: { name: t("bom_date"), visible: true } + producer: { name: t("producer"), visible: true } + order_cycle: { name: t("bom_cycle"), visible: false } + hub: { name: t("bom_hub"), visible: false } + variant: { name: t("bom_variant"), visible: true } + quantity: { name: t("bom_quantity"), visible: true } + max: { name: t("bom_max"), visible: true } + final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } + price: { name: t("price"), visible: false } $scope.initialise = -> $scope.initialiseVariables() authorise_api_reponse = "" diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 3d565f703a..751734a899 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -3,18 +3,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.StatusMessage = StatusMessage - $scope.columns = Columns.setColumns - producer: {name: t("products_producer"), visible: true} - sku: {name: t("products_sku"), visible: false} - name: {name: t("products_name"), visible: true} - unit: {name: t("products_unit"), visible: true} - price: {name: t("products_price"), visible: true} - on_hand: {name: t("products_on_hand"), visible: true} - on_demand: {name: t("products_on_demand"), visible: false} - category: {name: t("products_category"), visible: false} - tax_category: {name: t("products_tax_category"), visible: false} - inherits_properties: {name: t("products_inherits_properties"), visible: false} - available_on: {name: t("products_available_on"), visible: false} + $scope.columns = Columns.columns $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 9e1c027531..23bfb37738 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -6,11 +6,7 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Custo $scope.add = Customers.add $scope.deleteCustomer = Customers.remove $scope.customerLimit = 20 - - $scope.columns = Columns.setColumns - email: { name: "Email", visible: true } - code: { name: "Code", visible: true } - tags: { name: "Tags", visible: true } + $scope.columns = Columns.columns $scope.$watch "CurrentShop.shop", -> if $scope.CurrentShop.shop.id? diff --git a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee new file mode 100644 index 0000000000..6c27980495 --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee @@ -0,0 +1,4 @@ +angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) -> + $scope.columns = Columns.columns + $scope.toggle = Columns.toggleColumn + $scope.saveColumnPreferences = Columns.savePreferences diff --git a/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee new file mode 100644 index 0000000000..8248b626bf --- /dev/null +++ b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee @@ -0,0 +1,6 @@ +angular.module("admin.dropdown").directive 'columnsDropdown', -> + restrict: 'E' + templateUrl: 'admin/columns_dropdown.html' + controller: 'ColumnsDropdownCtrl' + scope: + action: '@' diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee index 3d8bfa6446..d6dda4a118 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee @@ -5,9 +5,4 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E $q.all(requests).then -> $scope.loaded = true - $scope.columns = Columns.setColumns - name: { name: "Name", visible: true } - producer: { name: "Producer", visible: true } - package: { name: "Package", visible: true } - status: { name: "Status", visible: true } - manage: { name: "Manage", visible: true } + $scope.columns = Columns.columns diff --git a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee deleted file mode 100644 index 614b8d9346..0000000000 --- a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -angular.module("admin.indexUtils").directive "toggleColumn", (Columns) -> - link: (scope, element, attrs) -> - element.addClass "selected" if scope.column.visible - - element.click "click", -> - scope.$apply -> - Columns.toggleColumn(scope.column) - element.toggleClass "selected" diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee index 8bd99bf2f2..99adc85fdd 100644 --- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -1,13 +1,14 @@ -angular.module("admin.indexUtils").factory 'Columns', ($rootScope) -> +angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) -> new class Columns columns: {} visibleCount: 0 + saving: false - setColumns: (columns) => + constructor: -> @columns = {} - @columns[name] = column for name, column of columns + for column in columns + @columns[column.column_name] = column @calculateVisibleCount() - @columns toggleColumn: (column) => column.visible = !column.visible @@ -16,3 +17,11 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope) -> calculateVisibleCount: => @visibleCount = (column for name, column of @columns when column.visible).length $rootScope.$broadcast "columnCount:changed", @visibleCount + + savePreferences: (action_name) -> + $http + method: "PUT" + url: "/admin/column_preferences/bulk_update" + data: + action_name: action_name + column_preferences: (preference for column_name, preference of @columns) diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index f905d33bfe..f969d82b05 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -9,20 +9,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.selectedUnitsProduct = {} $scope.selectedUnitsVariant = {} $scope.sharedResource = false - $scope.columns = Columns.setColumns - order_no: { name: t("bom_no"), visible: false } - full_name: { name: t("name"), visible: true } - email: { name: t("email"), visible: false } - phone: { name: t("phone"), visible: false } - order_date: { name: t("bom_date"), visible: true } - producer: { name: t("producer"), visible: true } - order_cycle: { name: t("bom_cycle"), visible: false } - hub: { name: t("bom_hub"), visible: false } - variant: { name: t("bom_variant"), visible: true } - quantity: { name: t("bom_quantity"), visible: true } - max: { name: t("bom_max"), visible: true } - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } - price: { name: t("price"), visible: false } + $scope.columns = Columns.columns $scope.confirmRefresh = -> LineItems.allSaved() || confirm(t "unsaved_changes_warning") diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee index 26d1e05250..e72be8bf71 100644 --- a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -19,19 +19,10 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", hidden: { name: "Hidden Products", visible: false } new: { name: "New Products", visible: false } - $scope.columns = Columns.setColumns - producer: { name: "Producer", visible: true } - product: { name: "Product", visible: true } - sku: { name: "SKU", visible: false } - price: { name: "Price", visible: true } - on_hand: { name: "On Hand", visible: true } - on_demand: { name: "On Demand", visible: false } - reset: { name: "Reset Stock Level", visible: false } - inheritance: { name: "Inheritance", visible: false } - visibility: { name: "Hide", visible: false } - $scope.bulkActions = [ name: "Reset Stock Levels To Defaults", callback: 'resetStock' ] + $scope.columns = Columns.columns + $scope.resetSelectFilters = -> $scope.producerFilter = 0 $scope.query = '' diff --git a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml new file mode 100644 index 0000000000..bf52142e30 --- /dev/null +++ b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml @@ -0,0 +1,10 @@ +.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } } + %span{ :class => 'icon-reorder' }= "  #{t('admin.columns')}".html_safe + %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } + %div.menu{ 'ng-show' => "expanded" } + %div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } } + %span.check + %span.name {{column.name }} + %hr + %div.menu_item.text-center + %input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { click: "saveColumnPreferences(action)"} } diff --git a/app/controllers/admin/column_preferences_controller.rb b/app/controllers/admin/column_preferences_controller.rb new file mode 100644 index 0000000000..0a9300d71c --- /dev/null +++ b/app/controllers/admin/column_preferences_controller.rb @@ -0,0 +1,38 @@ +module Admin + class ColumnPreferencesController < ResourceController + before_filter :load_collection, only: [:bulk_update] + + respond_to :json + + def bulk_update + @cp_set.collection.each { |cp| authorize! :bulk_update, cp } + + if @cp_set.save + # Return saved VOs with IDs + render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer + else + if @cp_set.errors.present? + render json: { errors: @cp_set.errors }, status: 400 + else + render nothing: true, status: 500 + end + end + end + + private + + def load_collection + collection_hash = Hash[params[:column_preferences].each_with_index.map { |cp, i| [i, cp] }] + collection_hash.reject!{ |i, cp| cp[:action_name] != params[:action_name] } + @cp_set = ColumnPreferenceSet.new @column_preferences, collection_attributes: collection_hash + end + + def collection + ColumnPreference.where(user_id: spree_current_user, action_name: params[:action_name]) + end + + def collection_actions + [:bulk_update] + end + end +end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 0c032eaa9e..ffd2c56c02 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -47,13 +47,18 @@ module Admin admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer end + def admin_inject_column_preferences(opts={module: 'ofn.admin'}) + column_preferences = ColumnPreference.for(spree_current_user, "#{controller_name}_#{action_name}") + admin_inject_json_ams_array opts[:module], "columns", column_preferences, Api::Admin::ColumnPreferenceSerializer + end + def admin_inject_enterprise_permissions permissions = {can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise), can_manage_payment_methods: can?(:manage_payment_methods, @enterprise), can_manage_enterprise_fees: can?(:manage_enterprise_fees, @enterprise)} - render partial: "admin/json/injection_ams", locals: {ngModule: "admin.enterprises", name: "enterprisePermissions", json: permissions.to_json} + admin_inject_json "admin.enterprises", "enterprisePermissions", permissions end def admin_inject_hub_permissions @@ -96,6 +101,11 @@ module Admin render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} end + def admin_inject_json(ngModule, name, data) + json = data.to_json + render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json} + end + def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) json = serializer.new(data, scope: spree_current_user).to_json render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json} diff --git a/app/models/column_preference.rb b/app/models/column_preference.rb new file mode 100644 index 0000000000..09ad0e55a4 --- /dev/null +++ b/app/models/column_preference.rb @@ -0,0 +1,46 @@ +require 'open_food_network/column_preference_defaults' + +class ColumnPreference < ActiveRecord::Base + extend OpenFoodNetwork::ColumnPreferenceDefaults + + # These are the attributes used to identify a preference + attr_accessible :user_id, :action_name, :column_name + + # These are attributes that need to be mass assignable + attr_accessible :name, :visible + + # Non-persisted attributes that only have one + # setting (ie. the default) for a given column + attr_accessor :name + + belongs_to :user, class_name: "Spree::User" + + validates :action_name, presence: true, inclusion: { in: proc { known_actions } } + validates :column_name, presence: true, inclusion: { in: proc { |p| valid_columns_for(p.action_name) } } + + def self.for(user, action_name) + stored_preferences = where(user_id: user.id, action_name: action_name) + default_preferences = send("#{action_name}_columns") + default_preferences.each_with_object([]) do |(column_name, default_attributes), preferences| + stored_preference = stored_preferences.find_by_column_name(column_name) + if stored_preference + stored_preference.assign_attributes(default_attributes.select{ |k,v| stored_preference[k].nil? }) + preferences << stored_preference + else + attributes = default_attributes.merge(user_id: user.id, action_name: action_name, column_name: column_name) + preferences << ColumnPreference.new(attributes) + end + end + end + + private + + def self.valid_columns_for(action_name) + send("#{action_name}_columns").keys.map(&:to_s) + end + + def self.known_actions + OpenFoodNetwork::ColumnPreferenceDefaults.private_instance_methods + .select{|m| m.to_s.end_with?("_columns")}.map{ |m| m.to_s.sub /_columns$/, ''} + end +end diff --git a/app/models/column_preference_set.rb b/app/models/column_preference_set.rb new file mode 100644 index 0000000000..32b9b85a1b --- /dev/null +++ b/app/models/column_preference_set.rb @@ -0,0 +1,5 @@ +class ColumnPreferenceSet < ModelSet + def initialize(collection, attributes={}) + super(ColumnPreference, collection, attributes, nil, nil ) + end +end diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index e1995b3c6f..2936a47d41 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -101,6 +101,10 @@ class AbilityDecorator can [:print], Spree::Order do |order| order.user == user end + + can [:admin, :bulk_update], ColumnPreference do |column_preference| + column_preference.user == user + end end def add_product_management_abilities(user) diff --git a/app/serializers/api/admin/column_preference_serializer.rb b/app/serializers/api/admin/column_preference_serializer.rb new file mode 100644 index 0000000000..8b8eeb1b3a --- /dev/null +++ b/app/serializers/api/admin/column_preference_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::ColumnPreferenceSerializer < ActiveModel::Serializer + attributes :id, :user_id, :action_name, :column_name, :name, :visible +end diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 4278162e30..45639b74cd 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -10,6 +10,7 @@ %a.button.icon-plus#new-customer{ href: "#", "new-customer-dialog" => true } = t('admin.customers.index.new_customer') += admin_inject_column_preferences module: 'admin.customers' = admin_inject_shops %div{ ng: { controller: 'customersCtrl' } } @@ -26,7 +27,7 @@ .five.columns.alpha %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } .eight.columns   - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } .row{ 'ng-if' => 'CurrentShop.shop.id && RequestMonitor.loading' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index e53541fde2..1f17f0f82c 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -6,7 +6,7 @@ .six.columns   -# = render 'admin/shared/bulk_actions_dropdown' .three.columns   - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } .row{ 'ng-if' => '!loaded' } .sixteen.columns.alpha#loading %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 62bc881728..14bcda7f60 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -9,6 +9,8 @@ = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' = admin_inject_monthly_bill_description += admin_inject_column_preferences module: 'admin.enterprises' + = render 'admin/shared/enterprises_sub_menu' = render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_set } diff --git a/app/views/admin/shared/_columns_dropdown.html.haml b/app/views/admin/shared/_columns_dropdown.html.haml deleted file mode 100644 index 4663443321..0000000000 --- a/app/views/admin/shared/_columns_dropdown.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.ofn-drop-down.right#columns-dropdown - %span{ :class => 'icon-reorder' }= "  #{t('admin.columns')}".html_safe - %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" } - %div.menu{ 'ng-show' => "expanded" } - %div.menu_item{ ng: { repeat: "column in columns" }, toggle: { column: true } } - %span.check - %span.name {{column.name }} diff --git a/app/views/admin/variant_overrides/_controls.html.haml b/app/views/admin/variant_overrides/_controls.html.haml index 4a25677be3..6757a8c43c 100644 --- a/app/views/admin/variant_overrides/_controls.html.haml +++ b/app/views/admin/variant_overrides/_controls.html.haml @@ -12,4 +12,4 @@ %i.icon-chevron-left Back to my inventory .four.columns.omega{ng: { show: 'views.inventory.visible' } } - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/admin/variant_overrides/_data.html.haml b/app/views/admin/variant_overrides/_data.html.haml index 9d371415fe..02c52d747d 100644 --- a/app/views/admin/variant_overrides/_data.html.haml +++ b/app/views/admin/variant_overrides/_data.html.haml @@ -2,5 +2,6 @@ = admin_inject_hubs module: 'admin.variantOverrides' = admin_inject_hub_permissions = admin_inject_producers module: 'admin.variantOverrides' += admin_inject_column_preferences module: 'admin.variantOverrides' = admin_inject_variant_overrides = admin_inject_inventory_items module: 'admin.variantOverrides' diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 213ba142d5..ce82341e03 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -9,6 +9,8 @@ = render :partial => 'spree/admin/shared/order_sub_menu' += admin_inject_column_preferences module: 'admin.lineItems' + %div{ ng: { controller: 'LineItemsCtrl' } } %save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" } %input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } } @@ -97,7 +99,7 @@ %input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' } = render 'admin/shared/bulk_actions_dropdown' %div.seven.columns   - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } %div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } %img.spinner{ src: "/assets/spinning-circles.svg" } diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml index 7ea29bdb4c..40f135d62e 100644 --- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml @@ -3,4 +3,4 @@ %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} .nine.columns = render 'spree/admin/shared/status_message' - = render 'admin/shared/columns_dropdown' + %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml index 3624421870..a17f725b11 100644 --- a/app/views/spree/admin/products/bulk_edit/_data.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -2,3 +2,4 @@ = admin_inject_taxons = admin_inject_tax_categories = admin_inject_spree_api_key += admin_inject_column_preferences diff --git a/config/routes.rb b/config/routes.rb index c9b81f8caf..f1b96de181 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -135,6 +135,10 @@ Openfoodnetwork::Application.routes.draw do resource :cache_settings resource :account, only: [:show], controller: 'account' + + resources :column_preferences, only: [], format: :json do + put :bulk_update, on: :collection + end end namespace :api do diff --git a/db/migrate/20160116024333_create_column_preferences.rb b/db/migrate/20160116024333_create_column_preferences.rb new file mode 100644 index 0000000000..e38c51d858 --- /dev/null +++ b/db/migrate/20160116024333_create_column_preferences.rb @@ -0,0 +1,13 @@ +class CreateColumnPreferences < ActiveRecord::Migration + def change + create_table :column_preferences do |t| + t.references :user, null: false, index: true + t.string :action_name, null: false, index: true + t.string :column_name, null: false + t.boolean :visible, null: false + + t.timestamps + end + add_index :column_preferences, [:user_id, :action_name, :column_name], unique: true, name: 'index_column_prefs_on_user_id_and_action_name_and_column_name' + end +end diff --git a/db/schema.rb b/db/schema.rb index dc54bd8f3d..60b72adeea 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -176,6 +176,17 @@ ActiveRecord::Schema.define(:version => 20160401043927) do add_index "cms_snippets", ["site_id", "identifier"], :name => "index_cms_snippets_on_site_id_and_identifier", :unique => true add_index "cms_snippets", ["site_id", "position"], :name => "index_cms_snippets_on_site_id_and_position" + create_table "column_preferences", :force => true do |t| + t.integer "user_id", :null => false + t.string "action_name", :null => false + t.string "column_name", :null => false + t.boolean "visible", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "column_preferences", ["user_id", "action_name", "column_name"], :name => "index_column_prefs_on_user_id_and_action_name_and_column_name", :unique => true + create_table "coordinator_fees", :force => true do |t| t.integer "order_cycle_id" t.integer "enterprise_fee_id" @@ -683,9 +694,9 @@ ActiveRecord::Schema.define(:version => 20160401043927) do t.string "email" t.text "special_instructions" t.integer "distributor_id" + t.integer "order_cycle_id" t.string "currency" t.string "last_ip_address" - t.integer "order_cycle_id" t.integer "cart_id" t.integer "customer_id" end diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb new file mode 100644 index 0000000000..126a8a567a --- /dev/null +++ b/lib/open_food_network/column_preference_defaults.rb @@ -0,0 +1,77 @@ +module OpenFoodNetwork + module ColumnPreferenceDefaults + + private + + # NOTE: These methods define valid column names (via hash keys) + # as well as default values for column attributes (eg. visiblity) + # Default values can be overridden by storing a different value + # for a given user, action_name and column_name + + def variant_overrides_index_columns + { + producer: { name: "Producer", visible: true }, + product: { name: "Product", visible: true }, + sku: { name: "SKU", visible: false }, + price: { name: "Price", visible: true }, + on_hand: { name: "On Hand", visible: true }, + on_demand: { name: "On Demand", visible: false }, + reset: { name: "Reset Stock Level", visible: false }, + inheritance: { name: "Inheritance", visible: false }, + visibility: { name: "Hide", visible: false } + } + end + + def customers_index_columns + { + email: { name: "Email", visible: true }, + code: { name: "Code", visible: true }, + tags: { name: "Tags", visible: true } + } + end + + def orders_bulk_management_columns + { + order_no: { name: t("bom_no"), visible: false }, + full_name: { name: t("name"), visible: true }, + email: { name: t("email"), visible: false }, + phone: { name: t("phone"), visible: false }, + order_date: { name: t("bom_date"), visible: true }, + producer: { name: t("producer"), visible: true }, + order_cycle: { name: t("bom_cycle"), visible: false }, + hub: { name: t("bom_hub"), visible: false }, + variant: { name: t("bom_variant"), visible: true }, + quantity: { name: t("bom_quantity"), visible: true }, + max: { name: t("bom_max"), visible: true }, + final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }, + price: { name: t("price"), visible: false } + } + end + + def products_bulk_edit_columns + { + producer: { name: t("products_producer"), visible: true }, + sku: { name: t("products_sku"), visible: false }, + name: { name: t("products_name"), visible: true }, + unit: { name: t("products_unit"), visible: true }, + price: { name: t("products_price"), visible: true }, + on_hand: { name: t("products_on_hand"), visible: true }, + on_demand: { name: t("products_on_demand"), visible: false }, + category: { name: t("products_category"), visible: false }, + tax_category: { name: t("products_tax_category"), visible: false }, + inherits_properties: { name: t("products_inherits_properties"), visible: false }, + available_on: { name: t("products_available_on"), visible: false } + } + end + + def enterprises_index_columns + { + name: { name: "Name", visible: true }, + producer: { name: "Producer", visible: true }, + package: { name: "Package", visible: true }, + status: { name: "Status", visible: true }, + manage: { name: "Manage", visible: true } + } + end + end +end diff --git a/spec/controllers/admin/column_preferences_controller_spec.rb b/spec/controllers/admin/column_preferences_controller_spec.rb new file mode 100644 index 0000000000..19d867d9b8 --- /dev/null +++ b/spec/controllers/admin/column_preferences_controller_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Admin::ColumnPreferencesController, type: :controller do + include AuthenticationWorkflow + + + describe "bulk_update" do + let!(:user1) { create(:user) } + let!(:user2) { create(:user) } + let!(:enterprise) { create(:enterprise, owner: user1, users: [user1, user2]) } + + context "json" do + let!(:column_preference) { ColumnPreference.create(user_id: user1.id, action_name: 'enterprises_index', column_name: "name", visible: true) } + + let(:column_preference_params) { [ + { id: column_preference.id, user_id: user1.id, action_name: "enterprises_index", column_name: 'name', visible: false }, + { id: nil, user_id: user1.id, action_name: "enterprises_index", column_name: 'producer', visible: true }, + { id: nil, user_id: user1.id, action_name: "enterprises_index", column_name: 'status', visible: true } + ] } + + context "where I don't own the preferences submitted" do + before do + allow(controller).to receive(:spree_current_user) { user2 } + end + + it "prevents me from updating the column preferences" do + spree_put :bulk_update, format: :json, action_name: "enterprises_index", column_preferences: column_preference_params + expect(ColumnPreference.count).to be 1 + end + end + + context "where I own the preferences submitted" do + before do + allow(controller).to receive(:spree_current_user) { user1 } + end + + it "allows me to update the column preferences" do + spree_put :bulk_update, format: :json, action_name: "enterprises_index", column_preferences: column_preference_params + expect(ColumnPreference.where(user_id: user1.id, action_name: 'enterprises_index').count).to be 3 + end + end + end + end +end diff --git a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee index de3a3d5d55..450041a417 100644 --- a/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/controllers/variant_overrides_controller_spec.js.coffee @@ -20,6 +20,7 @@ describe "VariantOverridesCtrl", -> $provide.value 'variantOverrides', variantOverrides $provide.value 'dirtyVariantOverrides', dirtyVariantOverrides $provide.value 'inventoryItems', inventoryItems + $provide.value 'columns', [] null inject ($controller, _VariantOverrides_, _DirtyVariantOverrides_, _StatusMessage_) -> diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 796dc93ebf..7854700dfd 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -4,6 +4,10 @@ describe "CustomersCtrl", -> beforeEach -> module('admin.customers') + module ($provide) -> + $provide.value 'columns', [] + null + inject ($controller, $rootScope, _CustomerResource_, $httpBackend) -> scope = $rootScope http = $httpBackend diff --git a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee index 573ca33d9f..24c4035a3e 100644 --- a/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/controllers/enterprises_controller_spec.js.coffee @@ -5,6 +5,10 @@ describe "EnterprisesCtrl", -> beforeEach -> module('admin.enterprises') + module ($provide) -> + $provide.value 'columns', [] + null + inject ($controller, $rootScope, _Enterprises_) -> scope = $rootScope Enterprises = _Enterprises_ diff --git a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee index 2161afe7de..92eba790c3 100644 --- a/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/directives/panel_row_spec.js.coffee @@ -5,6 +5,9 @@ describe "PanelRow directive", -> beforeEach -> module 'admin.indexUtils' + module ($provide) -> + $provide.value 'columns', [] + null beforeEach inject ($rootScope, $compile, $injector, $templateCache, _Panels_) -> Panels = _Panels_ diff --git a/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee index 2bff5e5a73..66122578a7 100644 --- a/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/services/columns_spec.js.coffee @@ -3,19 +3,21 @@ describe "Columns service", -> beforeEach -> module 'admin.indexUtils' - + module ($provide) -> + $provide.value 'columns', [ + { column_name: 'col1', visible: true } + { column_name: 'col2', visible: false } + ] + null inject (_Columns_) -> Columns = _Columns_ - describe "setting columns", -> + describe "initialising columns", -> it "sets resets @columns and copies each column of the provided object across", -> - Columns.setColumns({ name: { visible: true } }) - expect(Columns.columns).toEqual { name: { visible: true } } + expect(Columns.columns).toEqual { col1: { column_name: 'col1', visible: true }, col2: { column_name: 'col2', visible: false } } - it "calls calculateVisibleCount", -> - spyOn(Columns, "calculateVisibleCount") - Columns.setColumns({ name: { visible: true } }) - expect(Columns.calculateVisibleCount).toHaveBeenCalled() + it "updates visibleCount", -> + expect(Columns.visibleCount).toBe 1 describe "toggling a column", -> it "switches the visibility of the given column", -> diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index 5b4e4730b6..a1c291f5b0 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -4,6 +4,9 @@ describe "LineItemsCtrl", -> beforeEach -> module "admin.lineItems" + module ($provide) -> + $provide.value 'columns', [] + null jasmine.addMatchers toDeepEqual: (util, customEqualityTesters) -> diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index 457010da0c..cc0b574f47 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -241,6 +241,7 @@ describe "AdminProductEditCtrl", -> $provide.value "taxons", [] $provide.value "tax_categories", [] $provide.value 'SpreeApiKey', 'API_KEY' + $provide.value 'columns', [] null beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_) -> diff --git a/spec/models/column_preference_spec.rb b/spec/models/column_preference_spec.rb new file mode 100644 index 0000000000..4937bf1d34 --- /dev/null +++ b/spec/models/column_preference_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe ColumnPreference, type: :model do + describe "finding stored preferences for a user and action" do + before do + allow(ColumnPreference).to receive(:known_actions) { ['some_action'] } + allow(ColumnPreference).to receive(:valid_columns_for) { ['col1', 'col2', 'col3'] } + end + + let(:user) { create(:user) } + let!(:col1_pref) { ColumnPreference.create(user_id: user.id, action_name: 'some_action', column_name: 'col1', visible: true) } + let!(:col2_pref) { ColumnPreference.create(user_id: user.id, action_name: 'some_action', column_name: 'col2', visible: false) } + let(:defaults) { { + col1: { name: "col1", visible: false }, + col2: { name: "col2", visible: true }, + col3: { name: "col3", visible: false }, + } } + + context "when the user has preferences stored for the given action" do + before do + allow(ColumnPreference).to receive(:some_action_columns) { defaults } + end + + let(:preferences) { ColumnPreference.for(user, :some_action)} + + it "builds an entry for each column listed in the defaults" do + expect(preferences.count).to eq 3 + end + + it "uses values from stored preferences where present" do + expect(preferences).to include col1_pref, col2_pref + end + + it "uses defaults where no stored preference exists" do + default_pref = preferences.last + expect(default_pref).to be_a_new ColumnPreference + expect(default_pref.visible).to be false # As per default + end + end + + context "where the user does not have preferences stored for the given action" do + before do + allow(ColumnPreference).to receive(:some_action_columns) { defaults } + end + + let(:preferences) { ColumnPreference.for(create(:user), :some_action)} + + it "builds an entry for each column listed in the defaults" do + expect(preferences.count).to eq 3 + end + + it "uses defaults where no stored preference exists" do + expect(preferences.all?(&:new_record?)).to be true + expect(preferences.map(&:column_name)).to eq [:col1, :col2, :col3] + expect(preferences.map(&:visible)).to eq [false, true, false] + end + end + end +end From 55da83d0c031e5d280be30132ce2e8cbab4833d5 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 16 May 2016 10:31:07 +1000 Subject: [PATCH 146/154] Removing obsolete BOM controller (replaced by LineItemsCtrl) --- .../admin/bulk_order_management.js.coffee | 222 --------- .../unit/bulk_order_management_spec.js.coffee | 427 ------------------ 2 files changed, 649 deletions(-) delete mode 100644 app/assets/javascripts/admin/bulk_order_management.js.coffee delete mode 100644 spec/javascripts/unit/bulk_order_management_spec.js.coffee diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee deleted file mode 100644 index 2545a13915..0000000000 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ /dev/null @@ -1,222 +0,0 @@ -angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ - "$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns" - ($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) -> - $scope.loading = true - - $scope.initialiseVariables = -> - start = daysFromToday -7 - end = daysFromToday 1 - $scope.lineItems = [] - $scope.filteredLineItems = [] - $scope.confirmDelete = true - $scope.startDate = formatDate start - $scope.endDate = formatDate end - $scope.quickSearch = "" - $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ] - $scope.selectedBulkAction = $scope.bulkActions[0] - $scope.selectedUnitsProduct = {}; - $scope.selectedUnitsVariant = {}; - $scope.sharedResource = false - $scope.columns = Columns.setColumns - order_no: { name: t("bom_no"), visible: false } - full_name: { name: t("name"), visible: true } - email: { name: t("email"), visible: false } - phone: { name: t("phone"), visible: false } - order_date: { name: t("bom_date"), visible: true } - producer: { name: t("producer"), visible: true } - order_cycle: { name: t("bom_cycle"), visible: false } - hub: { name: t("bom_hub"), visible: false } - variant: { name: t("bom_variant"), visible: true } - quantity: { name: t("bom_quantity"), visible: true } - max: { name: t("bom_max"), visible: true } - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false } - price: { name: t("price"), visible: false } - $scope.initialise = -> - $scope.initialiseVariables() - authorise_api_reponse = "" - dataFetcher("/api/users/authorise_api?token=" + SpreeApiKey).then (data) -> - authorise_api_reponse = data - $scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised" - if $scope.spree_api_key_ok - $http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey - dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) -> - $scope.suppliers = $filter('orderBy')(data, 'name') - $scope.suppliers.unshift blankOption() - dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) -> - $scope.distributors = $filter('orderBy')(data, 'name') - $scope.distributors.unshift blankOption() - ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) -> - $scope.orderCycles = data - $scope.orderCyclesByID = [] - $scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles - $scope.orderCycles.unshift blankOption() - $scope.fetchOrders() - ocFetcher.then -> - $scope.resetSelectFilters() - else if authorise_api_reponse.hasOwnProperty("error") - $scope.api_error_msg = authorise_api_reponse("error") - else - api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access." - - $scope.fetchOrders = -> - $scope.loading = true - dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) -> - $scope.resetOrders data - $scope.loading = false - - $scope.resetOrders = (data) -> - $scope.orders = data - $scope.resetLineItems() - pendingChanges.removeAll() - - $scope.resetLineItems = -> - $scope.lineItems = $scope.orders.reduce (lineItems,order) -> - orderWithoutLineItems = $scope.lineItemOrder order - for i,line_item of order.line_items - line_item.checked = false - line_item.supplier = $scope.matchObject $scope.suppliers, line_item.supplier, null - line_item.order = orderWithoutLineItems - line_item.original_final_weight_volume = line_item.final_weight_volume - line_item.original_quantity = line_item.quantity - line_item.original_price = line_item.price - - lineItems.concat order.line_items - , [] - - $scope.lineItemOrder = (order) -> - lineItemOrder = angular.copy(order) - delete lineItemOrder.line_items - lineItemOrder.distributor = $scope.matchObject $scope.distributors, order.distributor, null - lineItemOrder.order_cycle = $scope.matchObject $scope.orderCycles, order.order_cycle, null - lineItemOrder - - $scope.matchObject = (list, testObject, noMatch) -> - for i, object of list - if angular.equals(object, testObject) - return object - return noMatch - - $scope.deleteLineItem = (lineItem) -> - if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete - $http( - method: "DELETE" - url: "/api/orders/" + lineItem.order.number + "/line_items/" + lineItem.id - ).success (data) -> - $scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1 - - $scope.deleteLineItems = (lineItems) -> - existingState = $scope.confirmDelete - $scope.confirmDelete = false - $scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked - $scope.confirmDelete = existingState - - $scope.submit = -> - if $scope.bulk_order_form.$valid - pendingChanges.submitAll() - else - alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors." - - $scope.allBoxesChecked = -> - checkedCount = $scope.filteredLineItems.reduce (count,lineItem) -> - count + (if lineItem.checked then 1 else 0 ) - , 0 - checkedCount == $scope.filteredLineItems.length - - $scope.toggleAllCheckboxes = -> - changeTo = !$scope.allBoxesChecked() - lineItem.checked = changeTo for lineItem in $scope.filteredLineItems - - $scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) -> - $scope.selectedUnitsProduct = unitsProduct - $scope.selectedUnitsVariant = unitsVariant - - $scope.sumUnitValues = -> - sum = $scope.filteredLineItems.reduce (sum,lineItem) -> - sum = sum + lineItem.final_weight_volume - , 0 - - $scope.sumMaxUnitValues = -> - sum = $scope.filteredLineItems.reduce (sum,lineItem) -> - sum = sum + Math.max(lineItem.max_quantity,lineItem.original_quantity) * lineItem.units_variant.unit_value - , 0 - - $scope.allFinalWeightVolumesPresent = -> - for i,lineItem of $scope.filteredLineItems - return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0) - true - - # How is this different to OptionValueNamer#name? - # Should it be extracted to that class or VariantUnitManager? - $scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) -> - # A Units Variant is an API object which holds unit properies of a variant - if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume") && value > 0 - scale = VariantUnitManager.getScale(value, unitsProduct.variant_unit) - Math.round(value/scale * 1000)/1000 + " " + VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit) - else - '' - - $scope.fulfilled = (sumOfUnitValues) -> - # A Units Variant is an API object which holds unit properies of a variant - if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 && - $scope.selectedUnitsProduct.hasOwnProperty("variant_unit") && - ( $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume" ) - Math.round( sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size * 1000)/1000 - else - '' - - $scope.unitsVariantSelected = -> - !angular.equals($scope.selectedUnitsVariant,{}) - - $scope.resetSelectFilters = -> - $scope.distributorFilter = $scope.distributors[0].id - $scope.supplierFilter = $scope.suppliers[0].id - $scope.orderCycleFilter = $scope.orderCycles[0].id - $scope.quickSearch = "" - - $scope.weightAdjustedPrice = (lineItem) -> - if lineItem.final_weight_volume > 0 - unit_value = lineItem.final_weight_volume / lineItem.quantity - original_unit_value = lineItem.original_final_weight_volume / lineItem.original_quantity - lineItem.price = lineItem.original_price * (unit_value / original_unit_value) - - $scope.unitValueLessThanZero = (lineItem) -> - if lineItem.units_variant.unit_value <= 0 - true - else - false - - $scope.updateOnQuantity = (lineItem) -> - if lineItem.quantity > 0 - lineItem.final_weight_volume = lineItem.original_final_weight_volume * lineItem.quantity / lineItem.original_quantity - $scope.weightAdjustedPrice(lineItem) - - $scope.$watch "orderCycleFilter", (newVal, oldVal) -> - unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal) - $scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order - $scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order -] - -daysFromToday = (days) -> - now = new Date - now.setHours(0) - now.setMinutes(0) - now.setSeconds(0) - now.setDate( now.getDate() + days ) - now - -formatDate = (date) -> - year = date.getFullYear() - month = twoDigitNumber date.getMonth() + 1 - day = twoDigitNumber date.getDate() - return year + "-" + month + "-" + day - -formatTime = (date) -> - hours = twoDigitNumber date.getHours() - mins = twoDigitNumber date.getMinutes() - secs = twoDigitNumber date.getSeconds() - return hours + ":" + mins + ":" + secs - -twoDigitNumber = (number) -> - twoDigits = "" + number - twoDigits = ("0" + number) if number < 10 - twoDigits diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee deleted file mode 100644 index 60edc958aa..0000000000 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ /dev/null @@ -1,427 +0,0 @@ -describe "AdminOrderMgmtCtrl", -> - ctrl = scope = httpBackend = VariantUnitManager = null - - beforeEach -> - module "ofn.admin", ($provide) -> - $provide.value 'SpreeApiKey', 'API_KEY' - return - beforeEach inject(($controller, $rootScope, $httpBackend, _VariantUnitManager_) -> - scope = $rootScope.$new() - ctrl = $controller - httpBackend = $httpBackend - VariantUnitManager = _VariantUnitManager_ - spyOn(window, "formatDate").and.returnValue "SomeDate" - - ctrl "AdminOrderMgmtCtrl", {$scope: scope} - ) - - describe "loading data upon initialisation", -> - it "gets a list of suppliers, a list of distributors and a list of Order Cycles and then calls fetchOrders", -> - returnedSuppliers = ["list of suppliers"] - returnedDistributors = ["list of distributors"] - returnedOrderCycles = [ "oc1", "oc2", "oc3" ] - httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" - httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers - httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").respond returnedDistributors - httpBackend.expectGET("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=SomeDate").respond returnedOrderCycles - spyOn(scope, "initialiseVariables").and.callThrough() - spyOn(scope, "fetchOrders").and.returnValue "nothing" - #spyOn(returnedSuppliers, "unshift") - #spyOn(returnedDistributors, "unshift") - #spyOn(returnedOrderCycles, "unshift") - scope.initialise() - httpBackend.flush() - - expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] - expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ] - expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ] - - expect(scope.initialiseVariables.calls.count()).toBe 1 - expect(scope.fetchOrders.calls.count()).toBe 1 - expect(scope.spree_api_key_ok).toBe true - - describe "fetching orders", -> - beforeEach -> - scope.initialiseVariables() - httpBackend.expectGET("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=SomeDate;q[completed_at_lt]=SomeDate").respond "list of orders" - - it "makes a call to dataFetcher, with current start and end date parameters", -> - scope.fetchOrders() - - it "calls resetOrders after data has been received", -> - spyOn scope, "resetOrders" - scope.fetchOrders() - httpBackend.flush() - expect(scope.resetOrders).toHaveBeenCalledWith "list of orders" - - it "sets the loading property to true before fetching orders and unsets it when loading is complete", -> - spyOn scope, "resetOrders" - scope.fetchOrders() - expect(scope.loading).toEqual true - httpBackend.flush() - expect(scope.loading).toEqual false - - describe "resetting orders", -> - beforeEach -> - spyOn(scope, "matchObject").and.returnValue "nothing" - spyOn(scope, "resetLineItems").and.returnValue "nothing" - scope.resetOrders [ "order1", "order2", "order3" ] - - it "sets the value of $scope.orders to the data received", -> - expect(scope.orders).toEqual [ "order1", "order2", "order3" ] - - it "makes a call to $scope.resetLineItems", -> - expect(scope.resetLineItems).toHaveBeenCalled() - - describe "resetting line items", -> - order1 = order2 = order3 = null - - beforeEach -> - spyOn(scope, "matchObject").and.returnValue "nothing" - spyOn(scope, "lineItemOrder").and.returnValue "copied order" - order1 = { name: "order1", line_items: [ { name: "line_item1.1" }, { name: "line_item1.1" }, { name: "line_item1.1" } ] } - order2 = { name: "order2", line_items: [ { name: "line_item2.1" }, { name: "line_item2.1" }, { name: "line_item2.1" } ] } - order3 = { name: "order3", line_items: [ { name: "line_item3.1" }, { name: "line_item3.1" }, { name: "line_item3.1" } ] } - scope.orders = [ order1, order2, order3 ] - scope.resetLineItems() - - it "creates $scope.lineItems by flattening the line_items arrays in each order object", -> - expect(scope.lineItems.length).toEqual 9 - expect(scope.lineItems[0].name).toEqual "line_item1.1" - expect(scope.lineItems[3].name).toEqual "line_item2.1" - expect(scope.lineItems[6].name).toEqual "line_item3.1" - - it "adds a reference to a modified parent order object to each line item", -> - expect(scope.lineItemOrder.calls.count()).toBe scope.orders.length - expect("copied order").toEqual line_item.order for line_item in scope.lineItems - - it "calls matchObject once for each line item", -> - expect(scope.matchObject.calls.count()).toBe scope.lineItems.length - - describe "copying orders", -> - order1copy = null - - beforeEach -> - spyOn(scope, "lineItemOrder").and.callThrough() - spyOn(scope, "matchObject").and.returnValue "matched object" - order1 = { name: "order1", line_items: [ ] } - scope.orders = [ order1 ] - order1copy = scope.lineItemOrder order1 - - it "calls removes the line_items attribute of the order, in order to avoid circular referencing)", -> - expect(order1copy.hasOwnProperty("line_items")).toEqual false - - it "calls matchObject twice for each order (once for distributor and once for order cycle)", -> - expect(scope.matchObject.calls.count()).toBe scope.lineItemOrder.calls.count() * 2 - expect(order1copy.distributor).toEqual "matched object" - expect(order1copy.distributor).toEqual "matched object" - - describe "matching objects", -> - it "returns the first matching object in the list", -> - list_item1 = - id: 1 - name: "LI1" - - list_item2 = - id: 2 - name: "LI2" - - test_item = - id: 2 - name: "LI2" - - expect(list_item2 is test_item).not.toEqual true - list = [ - list_item1 - list_item2 - ] - - returned_item = scope.matchObject list, test_item, null - expect(returned_item is list_item2).toEqual true - - it "returns the default provided if no matching item is found", -> - list_item1 = - id: 1 - name: "LI1" - - list_item2 = - id: 2 - name: "LI2" - - test_item = - id: 1 - name: "LI2" - - expect(list_item2 is test_item).not.toEqual true - list = [ - list_item1 - list_item2 - ] - - returned_item = scope.matchObject list, test_item, null - expect(returned_item is null).toEqual true - - describe "deleting a line item", -> - order = line_item1 = line_item2 = null - - beforeEach -> - scope.initialiseVariables() - spyOn(window,"confirm").and.returnValue true - order = { number: "R12345678", line_items: [] } - line_item1 = { id: 1, order: order } - line_item2 = { id: 2, order: order } - order.line_items = [ line_item1, line_item2 ] - - it "sends a delete request via the API", -> - httpBackend.expectDELETE("/api/orders/#{line_item1.order.number}/line_items/#{line_item1.id}").respond "nothing" - scope.deleteLineItem line_item1 - httpBackend.flush() - - it "does not remove line_item from the line_items array when request is not successful", -> - httpBackend.expectDELETE("/api/orders/#{line_item1.order.number}/line_items/#{line_item1.id}").respond 404, "NO CONTENT" - scope.deleteLineItem line_item1 - httpBackend.flush() - expect(order.line_items).toEqual [line_item1, line_item2] - - describe "deleting 'checked' line items", -> - line_item1 = line_item2 = line_item3 = line_item4 = null - - beforeEach -> - line_item1 = { name: "line item 1", checked: false } - line_item2 = { name: "line item 2", checked: true } - line_item3 = { name: "line item 3", checked: false } - line_item4 = { name: "line item 4", checked: true } - scope.lineItems = [ line_item1, line_item2, line_item3, line_item4 ] - - it "calls deletedLineItem for each 'checked' line item", -> - spyOn(scope, "deleteLineItem") - scope.deleteLineItems(scope.lineItems) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item2) - expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item4) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item1) - expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item3) - - describe "check boxes for line items", -> - line_item1 = line_item2 = null - - beforeEach -> - line_item1 = { name: "line item 1", checked: false } - line_item2 = { name: "line item 2", checked: false } - scope.filteredLineItems = [ line_item1, line_item2 ] - - it "keeps track of whether all filtered lines items are 'checked' or not", -> - expect(scope.allBoxesChecked()).toEqual false - line_item1.checked = true - expect(scope.allBoxesChecked()).toEqual false - line_item2.checked = true - expect(scope.allBoxesChecked()).toEqual true - line_item1.checked = false - expect(scope.allBoxesChecked()).toEqual false - - it "toggles the 'checked' attribute of all line items based to the value of allBoxesChecked", -> - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual true - line_item1.checked = false - expect(scope.allBoxesChecked()).toEqual false - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual true - scope.toggleAllCheckboxes() - expect(scope.allBoxesChecked()).toEqual false - - describe "unit calculations", -> - beforeEach -> - scope.initialiseVariables() - - describe "fulfilled()", -> - it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> - expect(scope.fulfilled()).toEqual '' - - it "returns '' if selectedUnitsVariant has no property 'group_buy_unit_size' or group_buy_unit_size is 0", -> - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 0 } - expect(scope.fulfilled()).toEqual '' - scope.selectedUnitsProduct = { variant_unit: "weight" } - expect(scope.fulfilled()).toEqual '' - - it "returns '', and does not call Math.round if variant_unit is 'items'", -> - spyOn(Math,"round") - scope.selectedUnitsProduct = { variant_unit: "items", group_buy_unit_size: 10 } - expect(scope.fulfilled()).toEqual '' - expect(Math.round).not.toHaveBeenCalled() - - it "calls Math.round() if variant_unit is 'weight' or 'volume'", -> - spyOn(Math,"round") - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 10 } - scope.fulfilled() - expect(Math.round).toHaveBeenCalled() - scope.selectedUnitsProduct = { variant_unit: "volume", group_buy_unit_size: 10 } - scope.fulfilled() - expect(Math.round).toHaveBeenCalled() - - it "returns the quantity of fulfilled group buy units", -> - scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 1000 } - expect(scope.fulfilled(1500)).toEqual 1.5 - - describe "allFinalWeightVolumesPresent()", -> - it "returns false if the unit_value of any item in filteredLineItems does not exist", -> - scope.filteredLineItems = [ - { final_weight_volume: 1000 } - { final_weight_volume: 3000 } - { final_weight_yayaya: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - - it "returns false if the unit_value of any item in filteredLineItems is not a number greater than 0", -> - scope.filteredLineItems = [ - { final_weight_volume: 0 } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - scope.filteredLineItems = [ - { final_weight_volume: 'lalala' } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual false - - it "returns true if the unit_value of all items in filteredLineItems are numbers greater than 0", -> - scope.filteredLineItems = [ - { final_weight_volume: 1000 } - { final_weight_volume: 3000 } - { final_weight_volume: 2000 } - ] - expect(scope.allFinalWeightVolumesPresent()).toEqual true - - describe "sumUnitValues()", -> - it "returns the sum of the final_weight_volumes line_items", -> - scope.filteredLineItems = [ - { final_weight_volume: 2 } - { final_weight_volume: 7 } - { final_weight_volume: 21 } - ] - expect(scope.sumUnitValues()).toEqual 30 - - describe "sumMaxUnitValues()", -> - it "returns the sum of the product of unit_value and maxOf(max_quantity,quantity) for specified line_items", -> - scope.filteredLineItems = [ - { units_variant: { unit_value: 1 }, original_quantity: 2, max_quantity: 5 } - { units_variant: { unit_value: 2 }, original_quantity: 3, max_quantity: 1 } - { units_variant: { unit_value: 3 }, original_quantity: 7, max_quantity: 10 } - ] - sp0 = scope.filteredLineItems[0].units_variant.unit_value * Math.max(scope.filteredLineItems[0].original_quantity, scope.filteredLineItems[0].max_quantity) - sp1 = scope.filteredLineItems[1].units_variant.unit_value * Math.max(scope.filteredLineItems[1].original_quantity, scope.filteredLineItems[1].max_quantity) - sp2 = scope.filteredLineItems[2].units_variant.unit_value * Math.max(scope.filteredLineItems[2].original_quantity, scope.filteredLineItems[2].max_quantity) - expect(scope.sumMaxUnitValues()).toEqual (sp0 + sp1 + sp2) - - describe "formatting a value based upon the properties of a specified Units Variant", -> - # A Units Variant is an API object which holds unit properies of a variant - - beforeEach -> - spyOn(Math,"round").and.callThrough() - - it "returns '' if selectedUnitsVariant has no property 'variant_unit'", -> - expect(scope.formattedValueWithUnitName(1,{})).toEqual '' - - it "returns '', and does not call Math.round if variant_unit is 'items'", -> - unitsVariant = { variant_unit: "items" } - expect(scope.formattedValueWithUnitName(1,unitsVariant)).toEqual '' - expect(Math.round).not.toHaveBeenCalled() - - it "calls Math.round() if variant_unit is 'weight' or 'volume'", -> - unitsVariant = { variant_unit: "weight" } - scope.formattedValueWithUnitName(1,unitsVariant) - expect(Math.round).toHaveBeenCalled() - scope.selectedUnitsVariant = { variant_unit: "volume" } - scope.formattedValueWithUnitName(1,unitsVariant) - expect(Math.round).toHaveBeenCalled() - - it "calls Math.round with the quotient of scale and value, multiplied by 1000", -> - unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").and.returnValue 5 - scope.formattedValueWithUnitName(10, unitsVariant) - expect(Math.round).toHaveBeenCalledWith 10/5 * 1000 - - it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", -> - unitsVariant = { variant_unit: "weight" } - spyOn(VariantUnitManager, "getScale").and.returnValue 1000 - spyOn(VariantUnitManager, "getUnitName").and.returnValue "kg" - expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg" - - describe "updating the price upon updating the weight of a line item", -> - it "updates the price if the weight is changed", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: 4000 } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 4.00 - - it "doesn't update the price if the weight <= 0", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: 0 } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 2.00 - - it "doesn't update the price if the weight is an empty string", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 1, original_final_weight_volume: 2000, final_weight_volume: "" } - ] - scope.weightAdjustedPrice(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].price).toEqual 2.00 - - describe "updating final_weight_volume upon updating the quantity for a line_item", -> - beforeEach -> - spyOn(scope, "weightAdjustedPrice") - - it "updates the weight if the quantity is changed, then calls weightAdjustedPrice()", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 2, original_final_weight_volume: 2000, final_weight_volume: 0 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 4000 - expect(scope.weightAdjustedPrice).toHaveBeenCalled() - - it "doesn't update the weight if the quantity <= 0", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: 0, original_final_weight_volume: 2000, final_weight_volume: 1000 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000 - - it "doesn't update the weight if the quantity is an empty string", -> - scope.filteredLineItems = [ - { original_price: 2.00, price: 2.00, original_quantity: 1, quantity: "", original_final_weight_volume: 2000, final_weight_volume: 1000 } - ] - scope.updateOnQuantity(scope.filteredLineItems[0]) - expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000 - - -describe "Auxiliary functions", -> - describe "getting a zero filled two digit number", -> - it "returns the number as a string if its value is greater than or equal to 10", -> - expect(twoDigitNumber(10)).toEqual "10" - expect(twoDigitNumber(15)).toEqual "15" - expect(twoDigitNumber(99)).toEqual "99" - - it "returns the number formatted as a zero filled string if its value is less than 10", -> - expect(twoDigitNumber(0)).toEqual "00" - expect(twoDigitNumber(1)).toEqual "01" - expect(twoDigitNumber(9)).toEqual "09" - - describe "formatting dates and times", -> - date = null - - beforeEach -> - date = new Date - date.setYear(2010) - date.setMonth(4) # Zero indexed, so 4 is May - date.setDate(15) - date.setHours(5) - date.setMinutes(10) - date.setSeconds(30) - - it "returns a date formatted as yyyy-mm-dd", -> - expect(formatDate(date)).toEqual "2010-05-15" - - it "returns a time formatted as hh-MM:ss", -> - expect(formatTime(date)).toEqual "05:10:30" From f2de498d61cb1ab076e6cd2348384f1bab6c250f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 15 May 2016 22:57:34 +1000 Subject: [PATCH 147/154] Show saved status of column preferences --- .../controllers/columns_dropdown_controller.js.coffee | 1 + .../admin/index_utils/services/columns.js.coffee | 11 +++++++++-- .../templates/admin/columns_dropdown.html.haml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee index 6c27980495..4b3a5a4516 100644 --- a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee +++ b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee @@ -2,3 +2,4 @@ angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Colu $scope.columns = Columns.columns $scope.toggle = Columns.toggleColumn $scope.saveColumnPreferences = Columns.savePreferences + $scope.saved = Columns.preferencesSaved diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee index 99adc85fdd..f2ed5463ae 100644 --- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -1,13 +1,14 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) -> new class Columns + savedColumns: {} columns: {} visibleCount: 0 - saving: false constructor: -> @columns = {} for column in columns @columns[column.column_name] = column + @savedColumns[column.column_name] = angular.copy(column) @calculateVisibleCount() toggleColumn: (column) => @@ -18,10 +19,16 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, column @visibleCount = (column for name, column of @columns when column.visible).length $rootScope.$broadcast "columnCount:changed", @visibleCount - savePreferences: (action_name) -> + preferencesSaved: => + angular.equals(@columns, @savedColumns) + + savePreferences: (action_name) => $http method: "PUT" url: "/admin/column_preferences/bulk_update" data: action_name: action_name column_preferences: (preference for column_name, preference of @columns) + .success (data) => + for column in data + @savedColumns[column.column_name] = column diff --git a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml index bf52142e30..401c3df9cc 100644 --- a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml +++ b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml @@ -7,4 +7,4 @@ %span.name {{column.name }} %hr %div.menu_item.text-center - %input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { click: "saveColumnPreferences(action)"} } + %input.fullwidth.red{ type: "button", ng: { value: "saved() ? 'Saved': 'Save As Default'", disabled: "saved()", click: "saveColumnPreferences(action)"} } From b9f6cb8800df251670de2286d902bde9632ed13d Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 15 May 2016 22:59:07 +1000 Subject: [PATCH 148/154] Adding translations for all column names --- .../line_items_controller.js.coffee | 4 +- app/helpers/admin/injection_helper.rb | 5 +- .../_enterprise_user_index.html.haml | 11 +- app/views/admin/enterprises/index.html.haml | 2 +- .../order_cycles/_advanced_settings.html.haml | 2 +- .../variant_overrides/_controls.html.haml | 6 +- .../variant_overrides/_filters.html.haml | 2 +- .../admin/variant_overrides/_header.html.haml | 6 +- .../_hidden_products.html.haml | 4 +- .../variant_overrides/_new_products.html.haml | 10 +- .../_new_products_alert.html.haml | 4 +- .../variant_overrides/_no_results.html.haml | 12 +- .../variant_overrides/_products.html.haml | 14 +- .../_products_variants.html.haml | 2 +- .../admin/orders/bulk_management.html.haml | 60 +++---- .../bulk_edit/_products_head.html.haml | 24 +-- config/locales/en.yml | 151 ++++++++++-------- .../column_preference_defaults.rb | 87 +++++----- .../admin/bulk_product_update_spec.rb | 2 +- spec/features/admin/variant_overrides_spec.rb | 4 +- 20 files changed, 215 insertions(+), 197 deletions(-) diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index f969d82b05..f105ebb0e5 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -5,14 +5,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.confirmDelete = true $scope.startDate = formatDate daysFromToday -7 $scope.endDate = formatDate daysFromToday 1 - $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ] + $scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ] $scope.selectedUnitsProduct = {} $scope.selectedUnitsVariant = {} $scope.sharedResource = false $scope.columns = Columns.columns $scope.confirmRefresh = -> - LineItems.allSaved() || confirm(t "unsaved_changes_warning") + LineItems.allSaved() || confirm(t("unsaved_changes_warning")) $scope.resetSelectFilters = -> $scope.distributorFilter = blankOption().id diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index ffd2c56c02..079f93d41f 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -47,8 +47,9 @@ module Admin admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer end - def admin_inject_column_preferences(opts={module: 'ofn.admin'}) - column_preferences = ColumnPreference.for(spree_current_user, "#{controller_name}_#{action_name}") + def admin_inject_column_preferences(opts={}) + opts.reverse_merge!(module: 'ofn.admin', action: "#{controller_name}_#{action_name}") + column_preferences = ColumnPreference.for(spree_current_user, opts[:action]) admin_inject_json_ams_array opts[:module], "columns", column_preferences, Api::Admin::ColumnPreferenceSerializer end diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index 1f17f0f82c..75dbf98f17 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -24,12 +24,11 @@ %col.manage{ width: "18%", ng: { show: 'columns.manage.visible' }} %thead %tr{ ng: { controller: "ColumnsCtrl" } } - %th.name{ ng: { show: 'columns.name.visible' } } - Name - %th.producer{ ng: { show: 'columns.producer.visible' } } Producer? - %th.package{ ng: { show: 'columns.package.visible' } } Package - %th.status{ ng: { show: 'columns.status.visible' } } Status - %th.manage{ ng: { show: 'columns.manage.visible' } } Manage + %th.name{ ng: { show: 'columns.name.visible' } }=t('admin.name') + %th.producer{ ng: { show: 'columns.producer.visible' } }=t('.producer?') + %th.package{ ng: { show: 'columns.package.visible' } }=t('.package') + %th.status{ ng: { show: 'columns.status.visible' } }=t('.status') + %th.manage{ ng: { show: 'columns.manage.visible' } }=t('.manage') %tbody{ :id => "e_{{enterprise.id}}", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )", controller: 'EnterpriseIndexRowCtrl' } } %tr.enterprise.panel-toggle-row{ object: "enterprise", ng: { class: { even: "'even'", odd: "'odd'"} } } %td.name{ ng: { show: 'columns.name.visible' } } diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index 14bcda7f60..b84659738c 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -9,7 +9,7 @@ = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' = admin_inject_monthly_bill_description -= admin_inject_column_preferences module: 'admin.enterprises' += admin_inject_column_preferences module: 'admin.enterprises', action: "enterprises_index" = render 'admin/shared/enterprises_sub_menu' diff --git a/app/views/admin/order_cycles/_advanced_settings.html.haml b/app/views/admin/order_cycles/_advanced_settings.html.haml index 27b347880b..f8f71d0b93 100644 --- a/app/views/admin/order_cycles/_advanced_settings.html.haml +++ b/app/views/admin/order_cycles/_advanced_settings.html.haml @@ -5,7 +5,7 @@ = form_for [main_app, :admin, @order_cycle] do |f| .row .six.columns.alpha - = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycle.choose_products_from') + = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from') .with-tip{'data-powertip' => "You can opt to restrict all available products (both incoming and outgoing), to only those in #{@order_cycle.coordinator.name}'s inventory."} %a What's this? .four.columns diff --git a/app/views/admin/variant_overrides/_controls.html.haml b/app/views/admin/variant_overrides/_controls.html.haml index 6757a8c43c..900f46e430 100644 --- a/app/views/admin/variant_overrides/_controls.html.haml +++ b/app/views/admin/variant_overrides/_controls.html.haml @@ -3,9 +3,9 @@ .eight.columns.alpha = render 'admin/shared/bulk_actions_dropdown' = render 'admin/shared/views_dropdown' - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.inventory.inventory_powertip')}" } } - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.inventory.hidden_powertip')}" } } - %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.inventory.new_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.inventory_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.hidden_powertip')}" } } + %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.new_powertip')}" } } .four.columns   .four.columns.omega{ ng: { show: 'views.new.visible' } } %button.fullwidth{ type: 'button', ng: { click: "selectView('inventory')" } } diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml index c9f3a27b32..a1772ed222 100644 --- a/app/views/admin/variant_overrides/_filters.html.haml +++ b/app/views/admin/variant_overrides/_filters.html.haml @@ -5,7 +5,7 @@ %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub_id'} } .two.columns   .filter_select.four.columns - %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.inventory.select_a_shop')}'" } } + %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } } %br %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs' } } .filter_select.four.columns diff --git a/app/views/admin/variant_overrides/_header.html.haml b/app/views/admin/variant_overrides/_header.html.haml index e4a8e98420..64a6bfb201 100644 --- a/app/views/admin/variant_overrides/_header.html.haml +++ b/app/views/admin/variant_overrides/_header.html.haml @@ -1,8 +1,8 @@ - content_for :html_title do - = t("admin.inventory.title") + = t("admin.variant_overrides.index.title") - content_for :page_title do - %h1.page-title= t("admin.inventory.title") - %a.with-tip{ 'data-powertip' => "#{t("admin.inventory.description")}" }=t('admin.whats_this') + %h1.page-title= t("admin.variant_overrides.index.title") + %a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this') = render :partial => 'spree/admin/shared/product_sub_menu' diff --git a/app/views/admin/variant_overrides/_hidden_products.html.haml b/app/views/admin/variant_overrides/_hidden_products.html.haml index 9f0d4f2d94..ed3e2555c8 100644 --- a/app/views/admin/variant_overrides/_hidden_products.html.haml +++ b/app/views/admin/variant_overrides/_hidden_products.html.haml @@ -9,7 +9,7 @@ %th.producer=t('admin.producer') %th.product=t('admin.product') %th.variant=t('(admin.variant') - %th.add=t('admin.inventory.add') + %th.add=t('admin.variant_overrides.index.add') %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } @@ -19,4 +19,4 @@ .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } - = t('admin.inventory.add') + = t('admin.variant_overrides.index.add') diff --git a/app/views/admin/variant_overrides/_new_products.html.haml b/app/views/admin/variant_overrides/_new_products.html.haml index 414fba224d..99827717f5 100644 --- a/app/views/admin/variant_overrides/_new_products.html.haml +++ b/app/views/admin/variant_overrides/_new_products.html.haml @@ -8,9 +8,9 @@ %tr %th.producer=t('admin.producer') %th.product=t('admin.product') - %th.variant=t('(admin.variant') - %th.add=t('admin.inventory.add') - %th.hide=t('admin.inventory.hide') + %th.variant=t('admin.variant') + %th.add=t('admin.variant_overrides.index.add') + %th.hide=t('admin.variant_overrides.index.hide') %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } } %tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } } %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} } @@ -20,7 +20,7 @@ .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} } %td.add %button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } } - = t('admin.inventory.add') + = t('admin.variant_overrides.index.add') %td.hide %button.fullwidth.hide.icon-remove{ ng: { click: "setVisibility(hub_id,variant.id,false)" } } - = t('admin.inventory.hide') + = t('admin.variant_overrides.index.hide') diff --git a/app/views/admin/variant_overrides/_new_products_alert.html.haml b/app/views/admin/variant_overrides/_new_products_alert.html.haml index 29ec4c9623..385616d817 100644 --- a/app/views/admin/variant_overrides/_new_products_alert.html.haml +++ b/app/views/admin/variant_overrides/_new_products_alert.html.haml @@ -1,5 +1,5 @@ %div{ ng: { show: '(newProductCount = (products | hubPermissions:hubPermissions:hub_id | newInventoryProducts:hub_id).length) > 0 && !views.new.visible && !alertDismissed' } } %hr.divider.sixteen.columns.alpha.omega - %alert-row{ message: "#{t('admin.inventory.new_products_alert_message', new_product_count: '{{ newProductCount }}')}", + %alert-row{ message: "#{t('admin.variant_overrides.index.new_products_alert_message', new_product_count: '{{ newProductCount }}')}", dismissed: "alertDismissed", - button: { text: "#{t('admin.inventory.review_now')}", action: "selectView('new')" } } + button: { text: "#{t('admin.variant_overrides.index.review_now')}", action: "selectView('new')" } } diff --git a/app/views/admin/variant_overrides/_no_results.html.haml b/app/views/admin/variant_overrides/_no_results.html.haml index cdec6ab8c5..7ec4e68f0e 100644 --- a/app/views/admin/variant_overrides/_no_results.html.haml +++ b/app/views/admin/variant_overrides/_no_results.html.haml @@ -1,7 +1,7 @@ %div.text-big.no-results{ ng: { show: 'hub_id && products.length > 0 && filteredProducts.length == 0' } } - %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.inventory.currently_empty') - %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.inventory.no_matching_products') - %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.inventory.no_hidden_products') - %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.inventory.no_matching_hidden_products') - %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.inventory.no_new_products') - %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.inventory.no_matching_new_products') + %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.currently_empty') + %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_products') + %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_hidden_products') + %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_hidden_products') + %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_new_products') + %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_new_products') diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index b54cfeba9a..4330496b3e 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -16,13 +16,13 @@ %tr{ ng: { controller: "ColumnsCtrl" } } %th.producer{ ng: { show: 'columns.producer.visible' } }=t('admin.producer') %th.product{ ng: { show: 'columns.product.visible' } }=t('admin.product') - %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.inventory.sku') - %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.inventory.price') - %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.inventory.on_hand') - %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.inventory.on_demand') - %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.inventory.enable_reset') - %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.inventory.inherit') - %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.inventory.hide') + %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.sku') + %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.price') + %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.on_hand') + %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.on_demand?') + %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.variant_overrides.index.enable_reset?') + %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.variant_overrides.index.inherit?') + %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.variant_overrides.index.hide') %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml index a7a94437be..c139bc7ffb 100644 --- a/app/views/admin/variant_overrides/_products_variants.html.haml +++ b/app/views/admin/variant_overrides/_products_variants.html.haml @@ -19,4 +19,4 @@ %input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true } %td.visibility{ ng: { show: 'columns.visibility.visible' } } %button.icon-remove.hide.fullwidth{ :type => 'button', ng: { click: "setVisibility(hub_id,variant.id,false)" } } - = t('admin.inventory.hide') + = t('admin.variant_overrides.index.hide') diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index ce82341e03..520d2376a4 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -3,9 +3,9 @@ - content_for :page_title do %h1.page-title - = t "bom_page_title" + = t("admin.orders.bulk_management.page_title") %a{ 'ofn-with-tip' => t("bom_tip") } - = t "admin.whats_this" + = t("admin.whats_this") = render :partial => 'spree/admin/shared/order_sub_menu' @@ -18,28 +18,28 @@ .filters{ :class => "sixteen columns alpha" } .date_filter{ :class => "two columns alpha" } %label{ :for => 'start_date_filter' } - = t "start_date" + = t("admin.start_date") %br %input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' } .date_filter{ :class => "two columns" } %label{ :for => 'end_date_filter' } - = t "end_date" + = t("admin.end_date") %br %input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' } .one.column   .filter_select{ :class => "three columns" } %label{ :for => 'supplier_filter' } - = t "producer" + = t("admin.producer") %br %select{ :class => "three columns alpha", :id => 'supplier_filter', 'select2-min-search' => 5, 'ng-model' => 'supplierFilter', 'ng-options' => 's.id as s.name for s in suppliers' } .filter_select{ :class => "three columns" } %label{ :for => 'distributor_filter' } - = t "bom_hub" + = t("admin.shop") %br %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'} .filter_select{ :class => "three columns" } %label{ :for => 'order_cycle_filter' } - = t "order_cycle" + = t("admin.order_cycle") %br %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'} .filter_clear{ :class => "two columns omega" } @@ -53,7 +53,7 @@ %div.shared_resource{ :class => "four columns alpha" } %span{ :class => 'three columns alpha' } %input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'} - = t "bom_shared" + = t("admin.orders.bulk_management.shared") %div{ :class => "eight columns" } %h6{ :class => "eight columns alpha", 'ng-show' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsProduct.name + ": ALL" }} %h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.full_name }} @@ -65,32 +65,32 @@ .one.column.alpha   .two.columns %span.two.columns - = t "group_buy_unit_size" + = t("admin.orders.bulk_management.group_buy_unit_size") %span.two.columns {{ formattedValueWithUnitName( selectedUnitsProduct.group_buy_unit_size, selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "total_qtt_ordered" + = t("admin.orders.bulk_management.total_qtt_ordered") %span.two.columns {{ formattedValueWithUnitName( sumUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "max_qtt_ordered" + = t("admin.orders.bulk_management.max_qtt_ordered") %span.two.columns {{ formattedValueWithUnitName( sumMaxUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }} .one.column   .two.columns %span.two.columns - = t "current_fulfilled_units" + = t("admin.orders.bulk_management.current_fulfilled_units") %span.two.columns {{ fulfilled(sumUnitValues()) }} .one.column   .two.columns %span.two.columns - = t "max_fulfilled_units" + = t("admin.orders.bulk_management.max_fulfilled_units") %span.two.columns {{ fulfilled(sumMaxUnitValues()) }} .one.column.omega   %div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' } %span{ :class => "eight columns alpha", style: 'color:red' } - = t "bulk_management_warning" + = t("admin.orders.bulk_management.variants_without_unit_value") %hr.divider.sixteen.columns.alpha.omega @@ -104,11 +104,11 @@ %div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } %img.spinner{ src: "/assets/spinning-circles.svg" } %h1 - =t "bom_loading" + = t("admin.orders.bulk_management.loading") %div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'} %h1#no_results - = t "bom_no_results" + = t("admin.orders.bulk_management.no_results") .margin-bottom-50{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' } %form{ name: 'bulk_order_form' } @@ -119,42 +119,42 @@ %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.order_no{ 'ng-show' => 'columns.order_no.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } - = t "order_no" + = t("admin.orders.bulk_management.order_no") %th.full_name{ 'ng-show' => 'columns.full_name.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } - = t "name" + = t("admin.name") %th.email{ 'ng-show' => 'columns.email.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } - = t "email" + = t("admin.email") %th.phone{ 'ng-show' => 'columns.phone.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } - = t "phone" + = t("admin.phone") %th.date{ 'ng-show' => 'columns.order_date.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } - =t "bom_date" + = t("admin.orders.bulk_management.order_date") %th.producer{ 'ng-show' => 'columns.producer.visible' } %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } - = t "producer" + = t("admin.producer") %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } - = t "bom_cycle" + = t("admin.order_cycle") %th.hub{ 'ng-show' => 'columns.hub.visible' } %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } - = t "bom_hub" + = t("admin.shop") %th.variant{ 'ng-show' => 'columns.variant.visible' } %a{ :href => '', 'ng-click' => "predicate = 'units_variant.full_name'; reverse = !reverse" } - = t "bom_variant" + = t("admin.orders.bulk_management.product_unit") %th.quantity{ 'ng-show' => 'columns.quantity.visible' } - = t "products_quantity" + = t("admin.quantity") %th.max{ 'ng-show' => 'columns.max.visible' } - = t "shop_variant_quantity_max" + = t("admin.orders.bulk_management.max") %th.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' } - = t "weight_volume" + = t("admin.orders.bulk_management.weight_volume") %th.price{ 'ng-show' => 'columns.price.visible' } - = t "products_price" + = t("admin.price") %th.actions %th.actions - = t "ask" + = t("admin.orders.bulk_management.ask") %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 7767b8de89..50789a2f82 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -21,18 +21,18 @@ %th.left-actions %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' } Expand All - %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer - %th.sku{ 'ng-show' => 'columns.sku.visible' } SKU - %th.name{ 'ng-show' => 'columns.name.visible' } Name - %th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value - %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As - %th.price{ 'ng-show' => 'columns.price.visible' } Price - %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand - %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand - %th.category{ 'ng-show' => 'columns.category.visible' } Category - %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category - %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties? - %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On + %th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer') + %th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku') + %th.name{ 'ng-show' => 'columns.name.visible' }=t('admin.name') + %th.unit{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.unit') + %th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.display_as') + %th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price') + %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand') + %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?') + %th.category{ 'ng-show' => 'columns.category.visible' }=t('admin.products.bulk_edit.category') + %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('admin.products.bulk_edit.tax_category') + %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('admin.products.bulk_edit.inherits_proerties?') + %th.available_on{ 'ng-show' => 'columns.available_on.visible' }=t('admin.products.bulk_edit.av_on') %th.actions %th.actions %th.actions diff --git a/config/locales/en.yml b/config/locales/en.yml index 02d66a5354..3249f00d5a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -71,13 +71,29 @@ en: admin: + # Common properties / models + date: Date + email: Email + name: Name + on_hand: On Hand + on_demand: On Demand + on_demand?: On Demand? + order_cycle: Order Cycle + phone: Phone + price: Price + producer: Producer + product: Product + quantity: Quantity + shop: Shop + sku: SKU + tags: Tags + variant: Variant + # General form elements quick_search: Quick Search clear_all: Clear All - producer: Producer - shop: Shop - product: Product - variant: Variant + start_date: "Start Date" + end_date: "End Date" columns: Columns actions: Actions @@ -96,38 +112,74 @@ en: customer_placeholder: "customer@example.org" valid_email_error: Please enter a valid email address add_a_new_customer_for: Add a new customer for %{shop_name} - inventory: - title: Inventory - description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page - sku: SKU - price: Price - on_hand: On Hand - on_demand: On Demand? - enable_reset: Enable Stock Level Reset? - inherit: Inherit? - add: Add - hide: Hide - select_a_shop: Select A Shop - review_now: Review Now - new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. - currently_empty: Your inventory is currently empty - no_matching_products: No matching products found in your inventory - no_hidden_products: No products have been hidden from this inventory - no_matching_hidden_products: No hidden products match your search criteria - no_new_products: No new products are available to add to this inventory - no_matching_new_products: No new products match your search criteria - inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. - hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. - new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + code: Code + products: + bulk_edit: + unit: Unit + display_as: Display As + category: Category + tax_category: Tax Category + inherits_properties?: Inherits Properties? + available_on: Available On + av_on: "Av. On" - order_cycle: - choose_products_from: "Choose Products From:" + variant_overrides: + index: + title: Inventory + description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page + enable_reset?: Enable Stock Reset? + inherit?: Inherit? + add: Add + hide: Hide + select_a_shop: Select A Shop + review_now: Review Now + new_products_alert_message: There are %{new_product_count} new products available to add to your inventory. + currently_empty: Your inventory is currently empty + no_matching_products: No matching products found in your inventory + no_hidden_products: No products have been hidden from this inventory + no_matching_hidden_products: No hidden products match your search criteria + no_new_products: No new products are available to add to this inventory + no_matching_new_products: No new products match your search criteria + inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown. + hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory. + new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later! + + orders: + bulk_management: + tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + shared: "Shared Resource?" + order_no: "Order No." + order_date: "Order Date" + max: "Max" + product_unit: "Product: Unit" + weight_volume: "Weight/Volume" + ask: "Ask?" + page_title: "Bulk Order Management" + actions_delete: "Delete Selected" + loading: "Loading orders" + no_results: "No orders found." + group_buy_unit_size: "Group Buy Unit Size" + total_qtt_ordered: "Total Quantity Ordered" + max_qtt_ordered: "Max Quantity Ordered" + current_fulfilled_units: "Current Fulfilled Units" + max_fulfilled_units: "Max Fulfilled Units" + order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." + variants_without_unit_value: "WARNING: Some variants do not have a unit value" + + order_cycles: + edit: + choose_products_from: "Choose Products From:" enterprise: select_outgoing_oc_products_from: Select outgoing OC products from enterprises: + index: + producer?: Producer? + package: Package + status: Status + manage: Manage form: primary_details: shopfront_requires_login: "Shopfront requires login?" @@ -332,22 +384,6 @@ en: order_payment_paypal_successful: Your payment via PayPal has been processed successfully. order_hub_info: Hub Info - bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." - bom_shared: "Shared Resource?" - bom_page_title: "Bulk Order Management" - bom_no: "Order no." - bom_date: "Order date" - bom_cycle: "Order cycle" - bom_max: "Max" - bom_hub: "Hub" - bom_variant: "Product: Unit" - bom_final_weigth_volume: "Weight/Volume" - bom_quantity: "Quantity" - bom_actions_delete: "Delete Selected" - bom_loading: "Loading orders" - bom_no_results: "No orders found." - bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." - unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." unsaved_changes_error: "Fields with red borders contain errors." @@ -574,20 +610,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using products_description: Description products_variant: Variant products_quantity: Quantity - products_availabel: Available? + products_available: Available? products_producer: "Producer" products_price: "Price" - products_sku: "SKU" - products_name: "name" - products_unit: "unit" - products_on_hand: "on hand" - products_on_demand: "On demand?" - products_category: "Category" - products_tax_category: "tax category" - products_available_on: "Available On" - products_inherit: "Inherit?" - products_inherits_properties: "Inherits Properties?" - products_stock_level_reset: "Enable Stock Level Reset?" register_title: Register @@ -945,19 +970,7 @@ Please follow the instructions there to make your enterprise visible on the Open manage_products: "Manage products" edit_profile_details: "Edit profile details" edit_profile_details_etc: "Change your profile description, images, etc." - start_date: "Start Date" - end_date: "End Date" order_cycle: "Order Cycle" - group_buy_unit_size: "Group Buy Unit Size" - total_qtt_ordered: "Total Quantity Ordered" - max_qtt_ordered: "Max Quantity Ordered" - current_fulfilled_units: "Current Fulfilled Units" - max_fulfilled_units: "Max Fulfilled Units" - bulk_management_warning: "WARNING: Some variants do not have a unit value" - ask: "Ask?" - no_orders_found: "No orders found." - order_no: "Order No." - weight_volume: "Weight/Volume" remove_tax: "Remove tax" tax_settings: "Tax Settings" products_require_tax_category: "products require tax category" diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb index 126a8a567a..c18ac79550 100644 --- a/lib/open_food_network/column_preference_defaults.rb +++ b/lib/open_food_network/column_preference_defaults.rb @@ -9,68 +9,73 @@ module OpenFoodNetwork # for a given user, action_name and column_name def variant_overrides_index_columns + node = 'admin.variant_overrides.index' { - producer: { name: "Producer", visible: true }, - product: { name: "Product", visible: true }, - sku: { name: "SKU", visible: false }, - price: { name: "Price", visible: true }, - on_hand: { name: "On Hand", visible: true }, - on_demand: { name: "On Demand", visible: false }, - reset: { name: "Reset Stock Level", visible: false }, - inheritance: { name: "Inheritance", visible: false }, - visibility: { name: "Hide", visible: false } + producer: { name: I18n.t("admin.producer"), visible: true }, + product: { name: I18n.t("admin.product"), visible: true }, + sku: { name: I18n.t("admin.sku"), visible: false }, + price: { name: I18n.t("admin.price"), visible: true }, + on_hand: { name: I18n.t("admin.on_hand"), visible: true }, + on_demand: { name: I18n.t("admin.on_demand?"), visible: false }, + reset: { name: I18n.t("#{node}.enable_reset?"), visible: false }, + inheritance: { name: I18n.t("#{node}.inherit?"), visible: false }, + visibility: { name: I18n.t("#{node}.hide"), visible: false } } end def customers_index_columns + node = 'admin.customers.index' { - email: { name: "Email", visible: true }, - code: { name: "Code", visible: true }, - tags: { name: "Tags", visible: true } + email: { name: I18n.t("admin.email"), visible: true }, + code: { name: I18n.t("#{node}.code"), visible: true }, + tags: { name: I18n.t("admin.tags"), visible: true } } end def orders_bulk_management_columns + node = "admin.orders.bulk_management" { - order_no: { name: t("bom_no"), visible: false }, - full_name: { name: t("name"), visible: true }, - email: { name: t("email"), visible: false }, - phone: { name: t("phone"), visible: false }, - order_date: { name: t("bom_date"), visible: true }, - producer: { name: t("producer"), visible: true }, - order_cycle: { name: t("bom_cycle"), visible: false }, - hub: { name: t("bom_hub"), visible: false }, - variant: { name: t("bom_variant"), visible: true }, - quantity: { name: t("bom_quantity"), visible: true }, - max: { name: t("bom_max"), visible: true }, - final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }, - price: { name: t("price"), visible: false } + order_no: { name: I18n.t("#{node}.order_no"), visible: false }, + full_name: { name: I18n.t("admin.name"), visible: true }, + email: { name: I18n.t("admin.email"), visible: false }, + phone: { name: I18n.t("admin.phone"), visible: false }, + order_date: { name: I18n.t("#{node}.order_date"), visible: true }, + producer: { name: I18n.t("admin.producer"), visible: true }, + order_cycle: { name: I18n.t("admin.order_cycle"), visible: false }, + hub: { name: I18n.t("admin.shop"), visible: false }, + variant: { name: I18n.t("#{node}.product_unit"), visible: true }, + quantity: { name: I18n.t("admin.quantity"), visible: true }, + max: { name: I18n.t("#{node}.max"), visible: true }, + final_weight_volume: { name: I18n.t("#{node}.weight_volume"), visible: false }, + price: { name: I18n.t("admin.price"), visible: false } } end def products_bulk_edit_columns + node = "admin.products.bulk_edit" { - producer: { name: t("products_producer"), visible: true }, - sku: { name: t("products_sku"), visible: false }, - name: { name: t("products_name"), visible: true }, - unit: { name: t("products_unit"), visible: true }, - price: { name: t("products_price"), visible: true }, - on_hand: { name: t("products_on_hand"), visible: true }, - on_demand: { name: t("products_on_demand"), visible: false }, - category: { name: t("products_category"), visible: false }, - tax_category: { name: t("products_tax_category"), visible: false }, - inherits_properties: { name: t("products_inherits_properties"), visible: false }, - available_on: { name: t("products_available_on"), visible: false } + producer: { name: I18n.t("admin.producer"), visible: true }, + sku: { name: I18n.t("admin.sku"), visible: false }, + name: { name: I18n.t("admin.name"), visible: true }, + unit: { name: I18n.t("#{node}.unit"), visible: true }, + price: { name: I18n.t("admin.price"), visible: true }, + on_hand: { name: I18n.t("admin.on_hand"), visible: true }, + on_demand: { name: I18n.t("admin.on_demand"), visible: false }, + category: { name: I18n.t("#{node}.category"), visible: false }, + tax_category: { name: I18n.t("#{node}.tax_category"), visible: false }, + inherits_properties: { name: I18n.t("#{node}.inherits_properties?"), visible: false }, + available_on: { name: I18n.t("#{node}.available_on"), visible: false } } end def enterprises_index_columns + node = "admin.enterprises.index" { - name: { name: "Name", visible: true }, - producer: { name: "Producer", visible: true }, - package: { name: "Package", visible: true }, - status: { name: "Status", visible: true }, - manage: { name: "Manage", visible: true } + name: { name: I18n.t("admin.name"), visible: true }, + producer: { name: I18n.t("#{node}.producer?"), visible: true }, + package: { name: I18n.t("#{node}.package"), visible: true }, + status: { name: I18n.t("#{node}.status"), visible: true }, + manage: { name: I18n.t("#{node}.manage"), visible: true } } end end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 260caf207e..f7f32f6523 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -250,7 +250,7 @@ feature %q{ find("div#columns-dropdown", :text => "COLUMNS").click find("div#columns-dropdown div.menu div.menu_item", text: "Available On").click - find("div#columns-dropdown div.menu div.menu_item", text: "Category").click + find("div#columns-dropdown div.menu div.menu_item", text: /^Category?/).click find("div#columns-dropdown div.menu div.menu_item", text: "Inherits Properties?").click find("div#columns-dropdown div.menu div.menu_item", text: "SKU").click find("div#columns-dropdown", :text => "COLUMNS").click diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index a3a08c2466..0af93d19c3 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -255,12 +255,12 @@ feature %q{ it "deletes overrides when values are cleared" do first("div#columns-dropdown", :text => "COLUMNS").click first("div#columns-dropdown div.menu div.menu_item", text: "On Demand").click - first("div#columns-dropdown div.menu div.menu_item", text: "Reset Stock Level").click + first("div#columns-dropdown div.menu div.menu_item", text: "Enable Stock Reset?").click first("div#columns-dropdown", :text => "COLUMNS").click # Clearing values by 'inheriting' first("div#columns-dropdown", :text => "COLUMNS").click - first("div#columns-dropdown div.menu div.menu_item", text: "Inheritance").click + first("div#columns-dropdown div.menu div.menu_item", text: "Inherit?").click first("div#columns-dropdown", :text => "COLUMNS").click check "variant-overrides-#{variant3.id}-inherit" From 604418d699490109faca70533d342ce11560834b Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Mon, 16 May 2016 09:23:50 +1000 Subject: [PATCH 149/154] Install phantomjs with npm --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f914edbbb6..c32401ac61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,7 @@ language: ruby sudo: false cache: - bundler - - directories: - - travis-phantomjs + - $(npm bin -g)/phantomjs bundler_args: --without development rvm: - "2.1.5" @@ -30,10 +29,9 @@ before_script: - cp config/application.yml.example config/application.yml - RAILS_ENV=test bundle exec rake db:create db:schema:load - - mkdir -p travis-phantomjs - - wget -nc https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 || true - - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs - - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH + # Only install PhantomJS if it is not already present (ie. cached) + - npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7 + - export PATH=`npm bin -g`:$PATH - > if [ "$KARMA" = "true" ]; then From 924d15501274db7d293fe2fd975e8cabbfdfe0b7 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 19 May 2016 12:43:28 +1000 Subject: [PATCH 150/154] Using correct syntax for attributes on columns --- app/views/admin/customers/index.html.haml | 6 +++--- .../admin/variant_overrides/_products_product.html.haml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index 45639b74cd..c570632c64 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -43,9 +43,9 @@ %input.red{ type: "button", value: "Save Changes", ng: { click: "submitAll(customers_form)", disabled: "!customers_form.$dirty" } } %table.index#customers - %col.email{ width: "20%"} - %col.code{ width: "20%"} - %col.tags{ width: "50%"} + %col.email{ width: "20%", 'ng-show' => 'columns.email.visible' } + %col.code{ width: "20%", 'ng-show' => 'columns.code.visible' } + %col.tags{ width: "50%", 'ng-show' => 'columns.tags.visible' } %col.actions{ width: "10%"} %thead %tr{ ng: { controller: "ColumnsCtrl" } } diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml index b15840dd49..119ee867df 100644 --- a/app/views/admin/variant_overrides/_products_product.html.haml +++ b/app/views/admin/variant_overrides/_products_product.html.haml @@ -1,6 +1,6 @@ %tr.product.even - %td.producer{ ng: { show: 'columns.producer.visible' }, ng: { bind: '::producersByID[product.producer_id].name'} } - %td.product{ ng: { show: 'columns.product.visible' }, ng: { bind: '::product.name'} } + %td.producer{ ng: { show: 'columns.producer.visible', bind: '::producersByID[product.producer_id].name'} } + %td.product{ ng: { show: 'columns.product.visible', bind: '::product.name'} } %td.sku{ ng: { show: 'columns.sku.visible' } } %td.price{ ng: { show: 'columns.price.visible' } } %td.on_hand{ ng: { show: 'columns.on_hand.visible' } } From b804a704695d331584428dc2f5ec23ee442dd577 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 19 May 2016 12:44:56 +1000 Subject: [PATCH 151/154] Columns service updates state of columns (especially id) when data received from server Also showing saving status --- .../controllers/columns_dropdown_controller.js.coffee | 7 ++++++- .../admin/index_utils/services/columns.js.coffee | 3 ++- .../javascripts/templates/admin/columns_dropdown.html.haml | 3 ++- app/assets/stylesheets/admin/openfoodnetwork.css.scss | 5 +++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee index 4b3a5a4516..a19b6aaed3 100644 --- a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee +++ b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee @@ -1,5 +1,10 @@ angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) -> $scope.columns = Columns.columns $scope.toggle = Columns.toggleColumn - $scope.saveColumnPreferences = Columns.savePreferences $scope.saved = Columns.preferencesSaved + $scope.saving = false + + $scope.saveColumnPreferences = (action_name) -> + $scope.saving = true + Columns.savePreferences(action_name).then -> + $scope.saving = false diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee index f2ed5463ae..45bea7c680 100644 --- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee @@ -31,4 +31,5 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, column column_preferences: (preference for column_name, preference of @columns) .success (data) => for column in data - @savedColumns[column.column_name] = column + angular.extend(@columns[column.column_name], column) + angular.extend(@savedColumns[column.column_name], column) diff --git a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml index 401c3df9cc..37398b200a 100644 --- a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml +++ b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml @@ -7,4 +7,5 @@ %span.name {{column.name }} %hr %div.menu_item.text-center - %input.fullwidth.red{ type: "button", ng: { value: "saved() ? 'Saved': 'Save As Default'", disabled: "saved()", click: "saveColumnPreferences(action)"} } + %input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } } + %input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} } diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 293549d0f1..8d77015af7 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -43,6 +43,11 @@ input.red { margin-right: 5px; } +input.orange { + background-color: #FF9848; + margin-right: 5px; +} + input.search { margin-bottom: 1em; } From 852a12db61db7f9e50f2c7fd077c35b741176e93 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 19 May 2016 14:49:26 +1000 Subject: [PATCH 152/154] Remove caching of global npm phantomjs install --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c32401ac61..ef54e640c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: ruby sudo: false -cache: - - bundler - - $(npm bin -g)/phantomjs +cache: bundler bundler_args: --without development rvm: - "2.1.5" From 63432c2acc925584c1b88448e4bf23af26b15085 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 25 May 2016 11:45:18 +1000 Subject: [PATCH 153/154] Resize tested page to find all products --- spec/features/admin/order_cycles_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index e362bf9946..874c8342b5 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -223,6 +223,9 @@ feature %q{ page.should have_field 'order_cycle_outgoing_exchange_1_pickup_time', with: 'time 1' page.should have_field 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'instructions 1' + # Make the whole page visible + page.driver.resize(1280, 3600) + # And the distributors should have products page.all('table.exchanges tbody tr.distributor').each do |row| row.find('td.products input').click From 21b84457539ef319d8d593d3347bf4ec237d5ed9 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 25 May 2016 11:58:57 +1000 Subject: [PATCH 154/154] Include job name in failure message --- spec/support/delayed_job_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/support/delayed_job_helper.rb b/spec/support/delayed_job_helper.rb index 5f9cfb6140..82fd79c95a 100644 --- a/spec/support/delayed_job_helper.rb +++ b/spec/support/delayed_job_helper.rb @@ -57,11 +57,11 @@ module OpenFoodNetwork end failure_message_for_should do |event_proc| - "expected job to be enqueued matching #{options.inspect} (#{@jobs_created.andand.count || '???'} others enqueued)" + "expected #{klass} to be enqueued matching #{options.inspect} (#{@jobs_created.andand.count || '???'} others enqueued)" end failure_message_for_should_not do |event_proc| - "expected job to not be enqueued matching #{options.inspect}" + "expected #{klass} to not be enqueued matching #{options.inspect}" end end end